langfun 0.1.2.dev202412180804__py3-none-any.whl → 0.1.2.dev202412230804__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,9 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  """Checkpointing evaluation runs."""
15
+ import abc
15
16
  import threading
17
+ import traceback
16
18
 
17
19
  import langfun.core as lf
18
20
  from langfun.core.eval.v2 import example as example_lib
@@ -27,6 +29,74 @@ Runner = experiment_lib.Runner
27
29
  class Checkpointer(experiment_lib.Plugin):
28
30
  """Base class for checkpointing evaluation examples."""
29
31
 
32
+ def on_experiment_start(
33
+ self,
34
+ runner: Runner,
35
+ experiment: Experiment
36
+ ) -> None:
37
+ if not experiment.is_leaf:
38
+ return
39
+
40
+ # For refresh runs, we don't want to load the previous state.
41
+ if not runner.current_run.refresh:
42
+ if runner.current_run.input_root != runner.current_run.output_root:
43
+ experiment.info(
44
+ f'Warm starting from directory: {runner.current_run.input_root}.'
45
+ )
46
+ self._load_experiment(runner, experiment)
47
+
48
+ if experiment.state.evaluated_examples:
49
+ loaded_example_ids = list(
50
+ sorted(experiment.state.evaluated_examples.keys())
51
+ )
52
+ example_ids_to_evaluate = (
53
+ set(runner.current_run.example_ids) if runner.current_run.example_ids
54
+ else set(range(1, experiment.num_examples + 1))
55
+ )
56
+ example_ids_to_evaluate -= set(loaded_example_ids)
57
+
58
+ experiment.info(
59
+ f'{len(experiment.state.evaluated_examples)} examples have been '
60
+ 'loaded from checkpoint files. Their outputs will be used '
61
+ f'for recomputing metrics. Example IDs: {loaded_example_ids}'
62
+ )
63
+ experiment.info(
64
+ f'{len(example_ids_to_evaluate)} examples will be processed from '
65
+ f'scratch. Example IDs: {list(sorted(example_ids_to_evaluate))}'
66
+ )
67
+ else:
68
+ experiment.info(
69
+ 'No examples are loaded from checkpoint files. '
70
+ f'Experiment {experiment.id} starts from scratch.'
71
+ )
72
+
73
+ def on_example_complete(
74
+ self,
75
+ runner: Runner,
76
+ experiment: Experiment,
77
+ example: Example,
78
+ ) -> None:
79
+ """Saves the example to the checkpoint file."""
80
+ if example.has_error:
81
+ experiment.warning(
82
+ f'Example {example.id} has error. Skipping checkpointing.'
83
+ )
84
+ else:
85
+ self._save_example(runner, experiment, example)
86
+
87
+ @abc.abstractmethod
88
+ def _load_experiment(self, runner: Runner, experiment: Experiment) -> None:
89
+ """Loads the experiment state from checkpoint files."""
90
+
91
+ @abc.abstractmethod
92
+ def _save_example(
93
+ self,
94
+ runner: Runner,
95
+ experiment: Experiment,
96
+ example: Example,
97
+ ) -> None:
98
+ """Saves an evaluated example."""
99
+
30
100
 
31
101
  class PerExampleCheckpointer(Checkpointer):
32
102
  """Checkpointer that saves each example to a separate file."""
@@ -39,61 +109,86 @@ class PerExampleCheckpointer(Checkpointer):
39
109
  self._checkpoint_file_prefix = prefix
40
110
  self._checkpoint_file_ext = ext
41
111
 
42
- def on_experiment_start(
112
+ def _load_experiment(
43
113
  self,
44
114
  runner: Runner,
45
115
  experiment: Experiment,
46
116
  ) -> None:
47
117
  """Creates the checkpoint file."""
48
- if not experiment.is_leaf:
49
- return
118
+ experiment_dir = runner.current_run.input_dir(experiment)
119
+ if pg.io.path_exists(experiment_dir):
120
+ ckpt_files = [
121
+ runner.current_run.input_path_for(experiment, filename)
122
+ for filename in pg.io.listdir(experiment_dir)
123
+ if filename.startswith(self._checkpoint_file_prefix)
124
+ and filename.endswith(self._checkpoint_file_ext)
125
+ ]
126
+ else:
127
+ ckpt_files = []
50
128
 
51
- # For refresh runs, we don't want to load the previous state.
52
- if not runner.current_run.refresh:
53
- def _load_state(ckpt_file):
54
- experiment.load_state(ckpt_file)
55
-
56
- experiment_dir = runner.current_run.input_dir(experiment)
57
- if pg.io.path_exists(experiment_dir):
58
- ckpt_files = [
59
- runner.current_run.input_path_for(experiment, filename)
60
- for filename in pg.io.listdir(experiment_dir)
61
- if filename.startswith(self._checkpoint_file_prefix)
62
- and filename.endswith(self._checkpoint_file_ext)
63
- ]
64
- else:
65
- ckpt_files = []
66
-
67
- for ckpt_file, _, error in lf.concurrent_map(
68
- _load_state, ckpt_files, max_workers=64,
69
- ):
70
- if error is not None:
71
- pg.logging.warning(
72
- 'Failed to load checkpoint file %s: %s. Skipping the file.',
73
- ckpt_file, error
74
- )
129
+ experiment.info(f'Found {len(ckpt_files)} checkpoint files to load.')
75
130
 
76
- def on_example_complete(
131
+ # Load the checkpoint files in parallel.
132
+ context = dict(counter=0, counter_lock=threading.Lock())
133
+ def _load_state(ckpt_file):
134
+ error = None
135
+ with pg.timeit() as t:
136
+ try:
137
+ experiment.load_state(ckpt_file)
138
+ except BaseException as e: # pylint: disable=broad-except
139
+ error = e
140
+ finally:
141
+ with context['counter_lock']:
142
+ context['counter'] += 1
143
+
144
+ progress_str = f'{context["counter"]}/{len(ckpt_files)}'
145
+ if error is None:
146
+ experiment.info(
147
+ f'Loaded checkpoint file {ckpt_file} in {t.elapse:.2f} '
148
+ f'seconds. ({progress_str})'
149
+ )
150
+ else:
151
+ experiment.warning(
152
+ f'Failed to load checkpoint file {ckpt_file}: {error}. '
153
+ f'Skipping the file. ({progress_str})'
154
+ )
155
+
156
+ _ = list(
157
+ lf.concurrent_map(
158
+ _load_state, ckpt_files, max_workers=16, silence_on_errors=None
159
+ )
160
+ )
161
+
162
+ def _save_example(
77
163
  self,
78
164
  runner: Runner,
79
165
  experiment: Experiment,
80
166
  example: Example,
81
167
  ) -> None:
82
168
  """Saves the example to the checkpoint file."""
83
- if not example.has_error:
84
- def save_state(example: Example):
85
- writer = SequenceWriter(
86
- runner.current_run.output_path_for(
87
- experiment,
88
- (
89
- f'{self._checkpoint_file_prefix}_{example.id}'
90
- f'{self._checkpoint_file_ext}'
91
- )
92
- )
93
- )
169
+ def save_state(example: Example):
170
+ writer = SequenceWriter(
171
+ runner.current_run.output_path_for(
172
+ experiment,
173
+ (
174
+ f'{self._checkpoint_file_prefix}_{example.id}'
175
+ f'{self._checkpoint_file_ext}'
176
+ )
177
+ )
178
+ )
179
+ try:
94
180
  writer.add(example)
95
181
  writer.close()
96
- runner.background_run(save_state, example)
182
+ experiment.info(
183
+ f'Example {example.id} saved to {writer.path}.',
184
+ )
185
+ except BaseException as e: # pylint: disable=broad-except
186
+ experiment.error(
187
+ f'Failed to save example {example.id} to {writer.path}. '
188
+ f'Error: {e}, Stacktrace: \n{traceback.format_exc()}.',
189
+ )
190
+ raise e
191
+ runner.background_run(save_state, example)
97
192
 
98
193
  def _file_prefix_and_ext(self, filename: str) -> tuple[str, str]:
99
194
  ext_index = filename.rfind('.')
@@ -145,25 +240,31 @@ class BulkCheckpointer(Checkpointer):
145
240
  runner: Runner,
146
241
  experiment: Experiment,
147
242
  ) -> None:
148
- """Creates the checkpoint file."""
149
- if not experiment.is_leaf:
150
- return
151
- # For refresh runs, we don't want to load the previous state.
152
- if not runner.current_run.refresh:
153
- experiment.load_state(
154
- runner.current_run.input_path_for(
243
+ super().on_experiment_start(runner, experiment)
244
+
245
+ # Prepare the sequence writer for the experiment.
246
+ if experiment.is_leaf:
247
+ sequence_writer = SequenceWriter(
248
+ runner.current_run.output_path_for(
155
249
  experiment, self.checkpoint_filename
156
- ),
157
- raise_if_not_exist=False
250
+ )
158
251
  )
159
- sequence_writer = SequenceWriter(
160
- runner.current_run.output_path_for(
252
+ with self._lock:
253
+ if self._sequence_writer is not None:
254
+ self._sequence_writer[experiment.id] = sequence_writer
255
+
256
+ def _load_experiment(
257
+ self,
258
+ runner: Runner,
259
+ experiment: Experiment,
260
+ ) -> None:
261
+ """Creates the checkpoint file."""
262
+ experiment.load_state(
263
+ runner.current_run.input_path_for(
161
264
  experiment, self.checkpoint_filename
162
- )
265
+ ),
266
+ raise_if_not_exist=False
163
267
  )
164
- with self._lock:
165
- if self._sequence_writer is not None:
166
- self._sequence_writer[experiment.id] = sequence_writer
167
268
 
168
269
  def on_experiment_complete(
169
270
  self,
@@ -178,10 +279,14 @@ class BulkCheckpointer(Checkpointer):
178
279
  if self._sequence_writer is not None:
179
280
  # Make sure the writer is closed without delay so the file will be
180
281
  # available immediately.
181
- self._sequence_writer[experiment.id].close()
182
- del self._sequence_writer[experiment.id]
282
+ writer = self._sequence_writer.pop(experiment.id)
283
+ writer.close()
284
+ experiment.info(
285
+ f'{len(experiment.state.evaluated_examples)} examples are '
286
+ f'checkpointed to {writer.path}.'
287
+ )
183
288
 
184
- def on_example_complete(
289
+ def _save_example(
185
290
  self,
186
291
  runner: Runner,
187
292
  experiment: Experiment,
@@ -189,8 +294,20 @@ class BulkCheckpointer(Checkpointer):
189
294
  ) -> None:
190
295
  """Saves the example to the checkpoint file."""
191
296
  assert experiment.id in self._sequence_writer
192
- if not example.has_error:
193
- runner.background_run(self._sequence_writer[experiment.id].add, example)
297
+ def _save_example(example: Example):
298
+ writer = self._sequence_writer[experiment.id]
299
+ try:
300
+ writer.add(example)
301
+ experiment.info(
302
+ f'Example {example.id} added to {writer.path}.',
303
+ )
304
+ except BaseException as e: # pylint: disable=broad-except
305
+ experiment.error(
306
+ f'Failed to save example {example.id} to {writer.path}. '
307
+ f'Error: {e}, Stacktrace: \n{traceback.format_exc()}.',
308
+ )
309
+ raise e
310
+ runner.background_run(_save_example, example)
194
311
 
195
312
 
196
313
  class SequenceWriter:
@@ -198,8 +315,13 @@ class SequenceWriter:
198
315
 
199
316
  def __init__(self, path: str):
200
317
  self._lock = threading.Lock()
318
+ self._path = path
201
319
  self._sequence_writer = pg.io.open_sequence(path, 'w')
202
320
 
321
+ @property
322
+ def path(self) -> str:
323
+ return self._path
324
+
203
325
  def add(self, example: Example):
204
326
  example_blob = pg.to_json_str(
205
327
  example,
@@ -16,9 +16,9 @@ import tempfile
16
16
  import unittest
17
17
 
18
18
  from langfun.core.eval.v2 import checkpointing
19
+ from langfun.core.eval.v2 import eval_test_helper
19
20
  from langfun.core.eval.v2 import example as example_lib
20
21
  from langfun.core.eval.v2 import runners as runners_lib # pylint: disable=unused-import
21
- from langfun.core.eval.v2 import test_helper
22
22
  import pyglove as pg
23
23
 
24
24
  Example = example_lib.Example
@@ -55,8 +55,9 @@ class SequenceWriterTest(unittest.TestCase):
55
55
  class PerExampleCheckpointerTest(unittest.TestCase):
56
56
 
57
57
  def test_checkpointing(self):
58
+ pg.defaults.loggers.use_stdout()
58
59
  root_dir = os.path.join(tempfile.gettempdir(), 'per_example_checkpointer')
59
- experiment = test_helper.test_experiment()
60
+ experiment = eval_test_helper.test_experiment()
60
61
  checkpoint_filename = 'checkpoint.jsonl'
61
62
  checkpointer = checkpointing.PerExampleCheckpointer(checkpoint_filename)
62
63
  run = experiment.run(
@@ -89,7 +90,7 @@ class BulkCheckpointerTest(unittest.TestCase):
89
90
 
90
91
  def test_checkpointing(self):
91
92
  root_dir = os.path.join(tempfile.gettempdir(), 'test_bulk_checkpointer')
92
- experiment = test_helper.test_experiment()
93
+ experiment = eval_test_helper.test_experiment()
93
94
  checkpoint_filename = 'checkpoint.jsonl'
94
95
  checkpointer = checkpointing.BulkCheckpointer(checkpoint_filename)
95
96
  run = experiment.run(
@@ -14,7 +14,9 @@
14
14
  """Base class for Langfun evaluation tasks."""
15
15
 
16
16
  import abc
17
+ import datetime
17
18
  import functools
19
+ import threading
18
20
  import time
19
21
 
20
22
  from typing import Annotated, Any, Callable, Iterable
@@ -63,6 +65,8 @@ class Evaluation(experiment_lib.Experiment):
63
65
  self.__dict__.pop('is_leaf', None)
64
66
  self.__dict__.pop('children', None)
65
67
  super()._on_bound()
68
+ self._log_entries = []
69
+ self._log_lock = threading.Lock()
66
70
 
67
71
  #
68
72
  # Handling evaluation hierarchy (materialized vs. hyper evaluations).
@@ -277,6 +281,48 @@ class Evaluation(experiment_lib.Experiment):
277
281
  for metric in self.metrics:
278
282
  metric.reset()
279
283
 
284
+ #
285
+ # Evaluation-level logging.
286
+ #
287
+
288
+ def _log(self, log_func, level: lf.logging.LogLevel, message: str, **kwargs):
289
+ # Write to external logging system.
290
+ log_message = f'{self.id}: {message}'
291
+ if kwargs:
292
+ log_message = f'{log_message} (metadata: {kwargs!r})'
293
+ log_func(log_message)
294
+
295
+ # Add to experiment log history.
296
+ log_entry = lf.logging.LogEntry(
297
+ level=level,
298
+ time=datetime.datetime.now(),
299
+ message=message,
300
+ metadata=kwargs,
301
+ )
302
+ with self._log_lock:
303
+ self._log_entries.append(log_entry)
304
+
305
+ def debug(self, message: str, **kwargs):
306
+ """Logs a debug message to the session."""
307
+ self._log(pg.logging.debug, 'debug', message, **kwargs)
308
+
309
+ def info(self, message: str, **kwargs):
310
+ """Logs an info message to the session."""
311
+ self._log(pg.logging.info, 'info', message, **kwargs)
312
+
313
+ def warning(self, message: str, **kwargs):
314
+ """Logs a warning message to the session."""
315
+ self._log(pg.logging.warning, 'warning', message, **kwargs)
316
+
317
+ def error(self, message: str, **kwargs):
318
+ """Logs an error message to the session."""
319
+ self._log(pg.logging.error, 'error', message, **kwargs)
320
+
321
+ def fatal(self, message: str, **kwargs):
322
+ """Logs a fatal message to the session."""
323
+ # We use error level for fatal message, which does not trigger assertion.
324
+ self._log(pg.logging.error, 'fatal', message, **kwargs)
325
+
280
326
  #
281
327
  # HTML views.
282
328
  #
@@ -465,6 +511,25 @@ class Evaluation(experiment_lib.Experiment):
465
511
  )
466
512
  )
467
513
 
514
+ def _logs_tab() -> pg.views.html.controls.Tab:
515
+ """Renders a tab for the logs of the evaluation."""
516
+ with self._log_lock:
517
+ log_history = '\n'.join(str(l) for l in self._log_entries)
518
+ return pg.views.html.controls.Tab(
519
+ label='Logs',
520
+ content=pg.Html.element(
521
+ 'div',
522
+ [
523
+ pg.Html.element(
524
+ 'textarea',
525
+ [pg.Html.escape(log_history)],
526
+ readonly=True,
527
+ css_classes=['logs-textarea'],
528
+ )
529
+ ]
530
+ )
531
+ )
532
+
468
533
  def _main_tabs() -> pg.Html:
469
534
  return pg.Html.element(
470
535
  'div',
@@ -474,6 +539,8 @@ class Evaluation(experiment_lib.Experiment):
474
539
  _definition_tab(),
475
540
  ] + [
476
541
  _metric_tab(m) for m in self.metrics
542
+ ] + [
543
+ _logs_tab()
477
544
  ],
478
545
  selected=1,
479
546
  )
@@ -593,6 +660,14 @@ class Evaluation(experiment_lib.Experiment):
593
660
  width:100%;
594
661
  height:100%;
595
662
  }
663
+ .logs-textarea {
664
+ width: 100%;
665
+ height: 500px;
666
+ padding: 5px;
667
+ border: 1px solid #DDD;
668
+ background-color: #EEE;
669
+ resize: vertical;
670
+ }
596
671
  """
597
672
  ]
598
673
 
@@ -615,6 +690,11 @@ class EvaluationState:
615
690
  assert isinstance(example, example_lib.Example), example
616
691
  self._evaluated_examples[example.id] = example
617
692
 
693
+ @property
694
+ def evaluated_examples(self) -> dict[int, example_lib.Example]:
695
+ """Returns the examples in the state."""
696
+ return self._evaluated_examples
697
+
618
698
  def get(self, example_id: int) -> example_lib.Example | None:
619
699
  """Returns the example with the given ID."""
620
700
  return self._evaluated_examples.get(example_id)
@@ -622,9 +702,3 @@ class EvaluationState:
622
702
  def update(self, example: example_lib.Example) -> None:
623
703
  """Updates the state with the given example."""
624
704
  self._evaluated_examples[example.id] = example
625
-
626
- @property
627
- def evaluated_examples(self) -> dict[int, example_lib.Example]:
628
- """Returns the examples in the state."""
629
- return self._evaluated_examples
630
-
@@ -15,12 +15,11 @@ import os
15
15
  import tempfile
16
16
  import unittest
17
17
 
18
+ from langfun.core.eval.v2 import eval_test_helper
18
19
  from langfun.core.eval.v2 import evaluation as evaluation_lib
19
20
  from langfun.core.eval.v2 import example as example_lib
20
21
  from langfun.core.eval.v2 import experiment as experiment_lib
21
22
 
22
- from langfun.core.eval.v2 import test_helper
23
-
24
23
  import pyglove as pg
25
24
 
26
25
  Example = example_lib.Example
@@ -32,17 +31,23 @@ Run = experiment_lib.Run
32
31
  class EvaluationTest(unittest.TestCase):
33
32
 
34
33
  def test_hyper_evaluation(self):
35
- exp = test_helper.TestEvaluation(
36
- lm=test_helper.TestLLM(offset=pg.oneof(range(3)))
34
+ exp = eval_test_helper.TestEvaluation(
35
+ lm=eval_test_helper.TestLLM(offset=pg.oneof(range(3)))
37
36
  )
38
37
  self.assertFalse(exp.is_leaf)
39
38
  self.assertTrue(
40
39
  pg.eq(
41
40
  exp.children,
42
41
  [
43
- test_helper.TestEvaluation(lm=test_helper.TestLLM(offset=0)),
44
- test_helper.TestEvaluation(lm=test_helper.TestLLM(offset=1)),
45
- test_helper.TestEvaluation(lm=test_helper.TestLLM(offset=2)),
42
+ eval_test_helper.TestEvaluation(
43
+ lm=eval_test_helper.TestLLM(offset=0)
44
+ ),
45
+ eval_test_helper.TestEvaluation(
46
+ lm=eval_test_helper.TestLLM(offset=1)
47
+ ),
48
+ eval_test_helper.TestEvaluation(
49
+ lm=eval_test_helper.TestLLM(offset=2)
50
+ ),
46
51
  ]
47
52
  )
48
53
  )
@@ -57,19 +62,21 @@ class EvaluationTest(unittest.TestCase):
57
62
  )
58
63
 
59
64
  def test_input(self):
60
- exp = test_helper.TestEvaluation()
65
+ exp = eval_test_helper.TestEvaluation()
61
66
  self.assertEqual(exp.num_examples, 10)
62
- exp = test_helper.TestEvaluation(inputs=test_helper.test_inputs(None))
67
+ exp = eval_test_helper.TestEvaluation(
68
+ inputs=eval_test_helper.test_inputs(None)
69
+ )
63
70
  self.assertEqual(exp.num_examples, 20)
64
71
  @pg.functor
65
72
  def my_inputs():
66
73
  yield pg.Dict(x=1, y=2)
67
74
  yield pg.Dict(x=3, y=4)
68
- exp = test_helper.TestEvaluation(inputs=my_inputs())
75
+ exp = eval_test_helper.TestEvaluation(inputs=my_inputs())
69
76
  self.assertEqual(exp.num_examples, 2)
70
77
 
71
78
  def test_evaluate(self):
72
- exp = test_helper.TestEvaluation()
79
+ exp = eval_test_helper.TestEvaluation()
73
80
  example = exp.evaluate(Example(id=3))
74
81
  self.assertIs(exp.state.get(3), example)
75
82
  self.assertTrue(example.newly_processed)
@@ -85,7 +92,7 @@ class EvaluationTest(unittest.TestCase):
85
92
  self.assertIsNotNone(example.start_time)
86
93
  self.assertIsNotNone(example.end_time)
87
94
 
88
- exp = test_helper.TestEvaluation(lm=test_helper.TestLLM(offset=1))
95
+ exp = eval_test_helper.TestEvaluation(lm=eval_test_helper.TestLLM(offset=1))
89
96
  example = exp.evaluate(3)
90
97
  self.assertTrue(example.newly_processed)
91
98
  self.assertEqual(example.input, pg.Dict(x=2, y=4, groundtruth=6))
@@ -109,7 +116,7 @@ class EvaluationTest(unittest.TestCase):
109
116
  pg.io.mkdirs(eval_dir, exist_ok=True)
110
117
  state_file = os.path.join(eval_dir, 'state.jsonl')
111
118
  with pg.io.open_sequence(state_file, 'w') as f:
112
- exp = test_helper.TestEvaluation()
119
+ exp = eval_test_helper.TestEvaluation()
113
120
  example = exp.evaluate(3)
114
121
  self.assertTrue(example.newly_processed)
115
122
  self.assertEqual(example.input, pg.Dict(x=2, y=4, groundtruth=6))
@@ -132,7 +139,13 @@ class EvaluationTest(unittest.TestCase):
132
139
  self.assertEqual(example.usage_summary.uncached.total.num_requests, 0)
133
140
 
134
141
  def test_html_view(self):
135
- exp = test_helper.TestEvaluation()
142
+ exp = eval_test_helper.TestEvaluation()
143
+ exp.debug('debug message')
144
+ exp.info('info message')
145
+ exp.warning('warning message', x=1)
146
+ exp.error('error message', x=1)
147
+ exp.fatal('fatal message')
148
+
136
149
  self.assertIn(
137
150
  exp.id,
138
151
  exp.to_html(extra_flags=dict(card_view=True, current_run=None)).content
@@ -81,7 +81,7 @@ class Experiment(lf.Component, pg.views.HtmlTreeView.Extension):
81
81
  directory (using the ID 'latest'). Users can specify 'new' to start a fresh
82
82
  run or provide a specific run ID (typically in the format %Y%m%d_%<number>).
83
83
  Additionally, when initiating a new run, users may specify a `warm_start_from`
84
- ID to restore the experiment’s state from a previous run.
84
+ directory to restore the experiment’s state from a previous run.
85
85
 
86
86
  Examples:
87
87
 
@@ -97,9 +97,9 @@ class Experiment(lf.Component, pg.views.HtmlTreeView.Extension):
97
97
  # Start a new, clean run.
98
98
  experiment.run(root_dir, 'new')
99
99
 
100
- # Start a new run with a warm start from the previous run located in
101
- # 'run_20241031_1' of the root directory.
102
- experiment.run(root_dir, 'new', warm_start_from='20241031_1')
100
+ # Start a new run with a warm start from the another run located at
101
+ # '/path/to/another/run' (e.g. /my_expreriment/run_20241031_1).
102
+ experiment.run(root_dir, 'new', warm_start_from='/path/to/another/run')
103
103
 
104
104
  # Resume run '20241031_1', re-running failed examples and recomputing
105
105
  # metrics as needed.
@@ -959,6 +959,14 @@ class Plugin(lf.Component):
959
959
  ) -> None:
960
960
  """Called when an experiment (both leaf and non-leaf) is complete."""
961
961
 
962
+ def on_experiment_abort(
963
+ self,
964
+ runner: Runner,
965
+ experiment: Experiment,
966
+ error: BaseException,
967
+ ) -> None:
968
+ """Called when an experiment (both leaf and non-leaf) is aborted."""
969
+
962
970
  def on_example_start(
963
971
  self,
964
972
  runner: Runner,
@@ -18,9 +18,9 @@ import tempfile
18
18
  import unittest
19
19
 
20
20
  from langfun.core import console as lf_console
21
+ from langfun.core.eval.v2 import eval_test_helper
21
22
  from langfun.core.eval.v2 import progress_tracking # pylint: disable=unused-import
22
23
  from langfun.core.eval.v2 import runners as runners_lib # pylint: disable=unused-import
23
- from langfun.core.eval.v2 import test_helper
24
24
  import pyglove as pg
25
25
 
26
26
 
@@ -35,7 +35,7 @@ class HtmlProgressTrackerTest(unittest.TestCase):
35
35
  display=display
36
36
  )
37
37
  root_dir = os.path.join(tempfile.gettempdir(), 'test_html_progress_tracker')
38
- experiment = test_helper.test_experiment()
38
+ experiment = eval_test_helper.test_experiment()
39
39
  _ = experiment.run(root_dir, 'new', plugins=[])
40
40
  self.assertIsInstance(result['view'], pg.Html)
41
41
  lf_console._notebook = None
@@ -45,7 +45,7 @@ class TqdmProgressTrackerTest(unittest.TestCase):
45
45
 
46
46
  def test_basic(self):
47
47
  root_dir = os.path.join(tempfile.gettempdir(), 'test_tqdm_progress_tracker')
48
- experiment = test_helper.test_experiment()
48
+ experiment = eval_test_helper.test_experiment()
49
49
  string_io = io.StringIO()
50
50
  with contextlib.redirect_stderr(string_io):
51
51
  _ = experiment.run(root_dir, 'new', plugins=[])
@@ -55,7 +55,7 @@ class TqdmProgressTrackerTest(unittest.TestCase):
55
55
  root_dir = os.path.join(
56
56
  tempfile.gettempdir(), 'test_tqdm_progress_tracker_with_example_ids'
57
57
  )
58
- experiment = test_helper.test_experiment()
58
+ experiment = eval_test_helper.test_experiment()
59
59
  string_io = io.StringIO()
60
60
  with contextlib.redirect_stderr(string_io):
61
61
  _ = experiment.run(root_dir, 'new', example_ids=[1], plugins=[])