pathwaysutils 0.1.10__tar.gz → 0.1.11__tar.gz

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.
Files changed (58) hide show
  1. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/PKG-INFO +2 -2
  2. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/__init__.py +1 -1
  3. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/debug/watchdog.py +1 -1
  4. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/elastic/elastic.py +8 -7
  5. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/elastic/manager.py +178 -107
  6. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/gke/jobset.py +368 -24
  7. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/deploy_pathways_service.py +51 -50
  8. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/metrics_collector.py +1 -1
  9. pathwaysutils-0.1.11/pathwaysutils/experimental/shared_pathways_service/tpu_specs.py +109 -0
  10. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/split_by_mesh_axis.py +1 -1
  11. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/lru_cache.py +3 -3
  12. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/persistence/helper.py +6 -6
  13. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/persistence/orbax_handler.py +5 -5
  14. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/plugin_executable.py +2 -2
  15. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/profiling.py +10 -8
  16. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/reshard.py +1 -1
  17. pathwaysutils-0.1.11/pathwaysutils/test/experimental/gke/jobset_test.py +977 -0
  18. pathwaysutils-0.1.10/pathwaysutils/experimental/shared_pathways_service/yamls/pw-service.yaml → pathwaysutils-0.1.11/pathwaysutils/test/experimental/gke/testdata/model_lite_tpuv5e_4x8.yaml +106 -73
  19. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/profiling_test.py +2 -2
  20. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pyproject.toml +1 -1
  21. pathwaysutils-0.1.10/pathwaysutils/test/experimental/gke/jobset_test.py +0 -210
  22. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/LICENSE +0 -0
  23. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/README.md +0 -0
  24. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/_initialize.py +0 -0
  25. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/collect_profile.py +0 -0
  26. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/debug/__init__.py +0 -0
  27. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/debug/timing.py +0 -0
  28. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/elastic/__init__.py +0 -0
  29. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/__init__.py +0 -0
  30. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/concatenate_by_mesh_axis.py +0 -0
  31. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/gke/__init__.py +0 -0
  32. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/profiling.py +0 -0
  33. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/reshard.py +0 -0
  34. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/README.md +0 -0
  35. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/__init__.py +0 -0
  36. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/dockerfiles/sample_sidecar.dockerfile +0 -0
  37. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/gke_utils.py +0 -0
  38. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/isc_pathways.py +0 -0
  39. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/run_connect_example.py +0 -0
  40. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/run_workload.py +0 -0
  41. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/start_vscode_on_cpu_np.py +0 -0
  42. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/validators.py +0 -0
  43. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/yamls/code-server.yaml +0 -0
  44. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/experimental/shared_pathways_service/yamls/pw-proxy.yaml +0 -0
  45. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/jax/__init__.py +0 -0
  46. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/persistence/__init__.py +0 -0
  47. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/proxy_backend.py +0 -0
  48. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/__init__.py +0 -0
  49. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/debug/__init__.py +0 -0
  50. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/debug/timing_test.py +0 -0
  51. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/debug/watchdog_test.py +0 -0
  52. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/experimental/reshard_test.py +0 -0
  53. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/initialize_test.py +0 -0
  54. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/lru_cache_test.py +0 -0
  55. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/persistence_test.py +0 -0
  56. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/plugin_executable_test.py +0 -0
  57. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/proxy_backend_test.py +0 -0
  58. {pathwaysutils-0.1.10 → pathwaysutils-0.1.11}/pathwaysutils/test/reshard_test.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pathwaysutils
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: Package of Pathways-on-Cloud utilities.
5
5
  Keywords:
6
6
  Author-email: Pathways-on-Cloud Utilities Developers <pathwaysutils-dev@google.com>
7
- Requires-Python: >=3.10
7
+ Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  Classifier: License :: OSI Approved :: Apache Software License
10
10
  License-File: LICENSE
@@ -21,4 +21,4 @@ is_pathways_backend_used: Callable[[], bool] = _initialize.is_pathways_backend_u
21
21
  del _initialize
22
22
 
23
23
  # When changing this, also update the CHANGELOG.md.
24
- __version__ = "v0.1.10"
24
+ __version__ = "v0.1.11"
@@ -34,7 +34,7 @@ def _log_thread_stack(thread: threading.Thread):
34
34
  _logger.debug(
35
35
  "".join(
36
36
  traceback.format_stack(
37
- sys._current_frames().get( # pylint: disable=protected-access
37
+ sys._current_frames().get( # pylint: disable=protected-access # pyrefly: ignore[no-matching-overload]
38
38
  thread.ident, []
39
39
  )
40
40
  )
@@ -121,9 +121,9 @@ class DefaultSliceHealthChecker(SliceHealthChecker):
121
121
  _logger.debug(
122
122
  "Caught JaxRuntimeError for slice_index=%s: %s", slice_index, error
123
123
  )
124
- if not is_error_due_to_slice_down(error):
124
+ if not is_error_due_to_slice_down(error, log_traceback=False):
125
125
  raise
126
- return active_slice_indices
126
+ return frozenset(active_slice_indices)
127
127
 
128
128
 
129
129
 
@@ -249,7 +249,9 @@ def wait_for_slices(
249
249
  time.sleep(time_to_sleep)
250
250
 
251
251
 
252
- def is_error_due_to_slice_down(error: Exception) -> bool:
252
+ def is_error_due_to_slice_down(
253
+ error: Exception, log_traceback: bool = True
254
+ ) -> bool:
253
255
  """Returns True if the error is due to slice down.
254
256
 
255
257
  The error types that are considered due to slice down are
@@ -261,6 +263,7 @@ def is_error_due_to_slice_down(error: Exception) -> bool:
261
263
 
262
264
  Args:
263
265
  error: The error to check.
266
+ log_traceback: If True, log the traceback of the error.
264
267
  """
265
268
  error_due_to_slice_down = False
266
269
  traceback_logging_level = logging.DEBUG
@@ -290,9 +293,7 @@ def is_error_due_to_slice_down(error: Exception) -> bool:
290
293
 
291
294
  error_due_to_slice_down = True
292
295
 
293
- if not error_due_to_slice_down:
294
- _logger.debug("Caught an error not due to slice down")
295
-
296
- _logger.log(traceback_logging_level, "Error details:", exc_info=True)
296
+ if not error_due_to_slice_down and log_traceback:
297
+ _logger.log(traceback_logging_level, "Error details:", exc_info=True)
297
298
 
298
299
  return error_due_to_slice_down
@@ -86,17 +86,24 @@ class Manager:
86
86
  Attributes:
87
87
  slice_to_devices: A mapping from slice index to a sequence of `jax.Device`
88
88
  objects for that slice.
89
- all_slice_indices: A set of all possible slice indices.
90
- active_slice_indices: A set of indices of the currently active slices.
91
- new_slice_event: A `threading.Event` that is set when new slices become
92
- available during replica/resize mode.
89
+ all_slice_indices: A set of all possible slice indices in the allocation.
90
+ active_slice_indices: A set of indices of the slices currently participating
91
+ in the JAX computation mesh.
92
+ inactive_slice_indices: A set of indices of the slices in the allocation
93
+ that are not currently participating in the JAX computation mesh (e.g.
94
+ because they are down or have not joined yet).
95
+ available_inactive_slices: A set of indices of the inactive slices that
96
+ have been detected as up and healthy by the background monitor thread,
97
+ but have not yet been joined to the active JAX computation mesh.
93
98
  """
94
99
 
95
100
  slice_to_devices: Mapping[int, Sequence[jax.Device]]
96
101
  all_slice_indices: Set[int]
97
102
  active_slice_indices: Set[int]
98
- new_slice_event: threading.Event
99
-
103
+ inactive_slice_indices: Set[int]
104
+ available_inactive_slices: Set[int]
105
+ _stop_event: threading.Event | None
106
+ _monitor_thread: threading.Thread | None
100
107
  def __init__(self, devices: Sequence[jax.Device] | None = None) -> None:
101
108
  """Initializes the manager.
102
109
 
@@ -107,12 +114,55 @@ class Manager:
107
114
  devices = jax.devices()
108
115
  self.slice_to_devices = elastic.get_slice_to_devices(devices)
109
116
 
110
- self.all_slice_indices = set(self.slice_to_devices.keys())
117
+ self.all_slice_indices = frozenset(self.slice_to_devices.keys())
111
118
 
112
119
  self.active_slice_indices = elastic.get_active_slice_indices(
113
120
  slice_to_devices=self.slice_to_devices
114
121
  )
115
- self.new_slice_event = threading.Event()
122
+ self.inactive_slice_indices = self.all_slice_indices - self.active_slice_indices
123
+ self.available_inactive_slices = frozenset()
124
+
125
+ self._stop_event = None
126
+ self._monitor_thread = None
127
+
128
+ def start_monitoring(self, poll_interval: float | int = 10) -> None:
129
+ """Starts the background monitor thread.
130
+
131
+ Args:
132
+ poll_interval: The number of seconds to wait between activity checks.
133
+ """
134
+ if self._monitor_thread is not None and self._monitor_thread.is_alive():
135
+ _logger.warning("Monitor thread is already running.")
136
+ return
137
+
138
+ self._stop_event = threading.Event()
139
+ self._monitor_thread = threading.Thread(
140
+ target=self._monitor_new_slices,
141
+ args=(self._stop_event, poll_interval),
142
+ daemon=True,
143
+ )
144
+ self._monitor_thread.start()
145
+ _logger.info("Elastic monitor thread started with interval %s.", poll_interval)
146
+
147
+ def close(self) -> None:
148
+ """Stops the background monitor thread."""
149
+ if self._stop_event is not None:
150
+ self._stop_event.set()
151
+ if self._monitor_thread is not None:
152
+ _logger.info("Closing manager, waiting for monitor thread to stop...")
153
+ try:
154
+ self._monitor_thread.join(timeout=5)
155
+ if self._monitor_thread.is_alive():
156
+ _logger.warning(
157
+ "Elastic monitor thread failed to stop within 5s timeout."
158
+ )
159
+ except RuntimeError as e:
160
+ if "cannot join thread" in str(e):
161
+ pass
162
+ else:
163
+ raise
164
+ self._monitor_thread = None
165
+ self._stop_event = None
116
166
 
117
167
  @functools.cached_property
118
168
  def total_slice_count(self) -> int:
@@ -136,9 +186,15 @@ class Manager:
136
186
  return len(self.active_slice_indices)
137
187
 
138
188
  @property
139
- def inactive_slice_indices(self) -> set[int]:
140
- """The set of inactive slice indices."""
141
- return self.all_slice_indices - self.active_slice_indices
189
+ def new_slice_event(self) -> threading.Event:
190
+ """Deprecated compatibility property for un-updated MaxText code.
191
+
192
+ TODO: b/527183831 - Remove this property once MaxText CL 2 is submitted.
193
+ """
194
+ event = threading.Event()
195
+ if self.available_inactive_slices:
196
+ event.set()
197
+ return event
142
198
 
143
199
  def scale_by_active_slices(self, x: int | float) -> int | float:
144
200
  """Scale x by the number of active slices."""
@@ -171,37 +227,57 @@ class Manager:
171
227
  for array in jax.live_arrays():
172
228
  array.delete()
173
229
 
174
- def _monitor_new_slices(
175
- self, stop_event: threading.Event, poll_interval: float | int
176
- ) -> None:
177
- """Monitors for new slices and sets the `new_slice_event` if found."""
178
- while not stop_event.wait(poll_interval):
179
- try:
180
- if not self.inactive_slice_indices:
181
- _logger.debug("No inactive slices to check.")
182
- continue
230
+ def _check_inactive_slices(self) -> None:
231
+ """Checks inactive slices and updates available_inactive_slices."""
232
+ if not self.inactive_slice_indices:
233
+ _logger.debug("No inactive slices to check.")
234
+ if self.available_inactive_slices:
235
+ self.available_inactive_slices = frozenset()
236
+ return
183
237
 
184
- _logger.debug(
185
- "Checking inactive slices: %s", self.inactive_slice_indices
186
- )
187
- inactive_slice_to_devices = {
188
- i: self.slice_to_devices[i] for i in self.inactive_slice_indices
189
- }
190
- newly_active_indices = elastic.get_active_slice_indices(
191
- inactive_slice_to_devices
192
- )
238
+ _logger.debug(
239
+ "Now checking inactive slices %s", self.inactive_slice_indices
240
+ )
241
+ inactive_slice_to_devices = {
242
+ i: self.slice_to_devices[i] for i in self.inactive_slice_indices
243
+ }
244
+ found_slices = elastic.get_active_slice_indices(
245
+ inactive_slice_to_devices
246
+ )
193
247
 
194
- if newly_active_indices:
195
- _logger.info(
196
- "New slices found: %s. Setting new slice event.",
197
- newly_active_indices,
198
- )
199
- self.new_slice_event.set()
200
- return
248
+ _logger.debug(
249
+ "Found available and inactive slices %s", found_slices
250
+ )
251
+
252
+ # Filter against active_slice_indices in case the main thread initiated scale-up
253
+ # and claimed these slices while this background health check was running.
254
+ found_slices = found_slices - self.active_slice_indices
255
+
256
+ if found_slices != self.available_inactive_slices:
257
+ _logger.info(
258
+ "Newly available but inactive slices %s", found_slices
259
+ )
260
+ self.available_inactive_slices = frozenset(found_slices)
201
261
 
202
- _logger.debug("No new slices found.")
203
- except Exception: # pylint: disable=broad-exception-caught
204
- _logger.exception("Error in monitor thread")
262
+ def _monitor_new_slices(
263
+ self, stop_event: threading.Event, poll_interval: float | int
264
+ ) -> None:
265
+ """Monitors for new slices and updates available_inactive_slices."""
266
+ _logger.info("Elastic monitor thread started.")
267
+ try:
268
+ while not stop_event.wait(poll_interval):
269
+ try:
270
+ self._check_inactive_slices()
271
+ except Exception: # pylint: disable=broad-exception-caught
272
+ _logger.exception("Error in monitor thread loop")
273
+ except BaseException as e:
274
+ _logger.critical(
275
+ "Catastrophic error in monitor thread, thread is dying!",
276
+ exc_info=True,
277
+ )
278
+ raise
279
+ finally:
280
+ _logger.info("Elastic monitor thread stopped.")
205
281
 
206
282
  def elastic_retry(
207
283
  self,
@@ -227,10 +303,10 @@ class Manager:
227
303
  (i.e., it waits for all slices to be active).
228
304
 
229
305
  When `minimum_slice_count` is less than the total number of slices, a
230
- background thread will monitor for new slices becoming available and set
231
- `self.new_slice_event`. The user code can then poll this event and raise
232
- a `ScaleUpSignalError` to gracefully interrupt the current execution and
233
- trigger a retry.
306
+ background thread will monitor for newly joined inactive slices and populate
307
+ `self.available_inactive_slices`. User code can check this set (e.g. at step
308
+ boundaries) and raise a `ScaleUpSignalError` to gracefully interrupt the
309
+ current execution and trigger a retry with the expanded hardware.
234
310
 
235
311
  Often, the function will dispatch JAX operations and wait for them to
236
312
  complete while creating a log message. If using Python logging, it is
@@ -286,6 +362,7 @@ class Manager:
286
362
  def decorator(func: _F) -> _F:
287
363
  @functools.wraps(func)
288
364
  def wrapper(*args: Any, **kwargs: Any) -> Any:
365
+ self.start_monitoring(poll_interval)
289
366
 
290
367
  def attempt_execution(attempt: int) -> Any:
291
368
  _logger.info("Elastic attempt %d", attempt)
@@ -295,75 +372,69 @@ class Manager:
295
372
  poll_interval=poll_interval,
296
373
  timeout=timeout,
297
374
  )
375
+ self.inactive_slice_indices = (
376
+ self.all_slice_indices - self.active_slice_indices
377
+ )
378
+ # Reset available_inactive_slices at attempt start since
379
+ # active_slice_indices has just been updated by wait_for_slices.
380
+ self.available_inactive_slices = frozenset()
298
381
  if pre_callback is not None:
299
382
  pre_callback()
300
383
 
301
384
  with jax.default_device(self.default_device):
302
- self.new_slice_event.clear()
303
- stop_event = threading.Event()
304
-
305
- if target_slice_count < self.total_slice_count:
306
- monitor_thread = threading.Thread(
307
- target=self._monitor_new_slices,
308
- args=(stop_event, poll_interval),
309
- daemon=True,
310
- )
311
- monitor_thread.start()
312
- else:
313
- monitor_thread = None
314
-
385
+ return func(*args, **kwargs)
386
+
387
+ def handle_scale_up_error(attempt: int, error: ScaleUpSignalError) -> None:
388
+ _logger.info("Scale up requested.")
389
+ _elastic_event_cleanup()
390
+ # Reset available_inactive_slices before retry callback and next attempt.
391
+ self.available_inactive_slices = frozenset()
392
+
393
+ if on_elastic_event_callback is not None:
394
+ on_elastic_event_callback()
395
+
396
+ if not retry_policy(attempt, error):
397
+ _logger.info("Retry policy rejected retry after ScaleUpSignalError.")
398
+ raise ElasticRuntimeError(
399
+ f"Elastic attempt {attempt} failed."
400
+ ) from error
401
+
402
+ def handle_slice_down_error(
403
+ attempt: int, error: jax.errors.JaxRuntimeError
404
+ ) -> None:
405
+ if not elastic.is_error_due_to_slice_down(error):
406
+ raise
407
+
408
+ _logger.exception("Elastic event detected")
409
+ _elastic_event_cleanup()
410
+ # Reset available_inactive_slices on slice-down failure before retry.
411
+ self.available_inactive_slices = frozenset()
412
+
413
+ if on_elastic_event_callback is not None:
414
+ on_elastic_event_callback()
415
+
416
+ if not retry_policy(attempt, error):
417
+ _logger.info(
418
+ "Retry policy rejected retry after jax.errors.JaxRuntimeError."
419
+ )
420
+ raise ElasticRuntimeError(
421
+ f"Elastic attempt {attempt} failed."
422
+ ) from error
423
+
424
+ try:
425
+ attempt = 1
426
+ while True:
315
427
  try:
316
- return func(*args, **kwargs)
317
- finally:
318
- stop_event.set()
319
- if monitor_thread is not None:
320
- monitor_thread.join()
321
-
322
- attempt = 1
323
- while True:
324
- try:
325
- return attempt_execution(attempt)
326
- except ScaleUpSignalError as error:
327
- _logger.info("Scale up requested.")
328
- _elastic_event_cleanup()
329
-
330
- if on_elastic_event_callback is not None:
331
- on_elastic_event_callback()
332
-
333
- if not retry_policy(attempt, error):
334
- _logger.info(
335
- "Retry policy rejected retry after ScaleUpSignalError."
336
- )
337
- raise ElasticRuntimeError(
338
- f"Elastic attempt {attempt} failed."
339
- ) from error
340
-
341
- _logger.info("Retrying.")
342
- except jax.errors.JaxRuntimeError as error:
343
- if not elastic.is_error_due_to_slice_down(error):
344
- raise
345
-
346
- if self.new_slice_event.is_set():
347
- _logger.info("Slice down event and new slice available detected.")
348
- else:
349
- _logger.info("Slice down event detected.")
350
-
351
- _elastic_event_cleanup()
352
-
353
- if on_elastic_event_callback is not None:
354
- on_elastic_event_callback()
355
-
356
- if not retry_policy(attempt, error):
357
- _logger.info("Retry policy rejected retry after JaxRuntimeError.")
358
- raise ElasticRuntimeError(
359
- f"Elastic attempt {attempt} failed."
360
- ) from error
361
-
362
- _logger.info("Retrying.")
363
-
364
- attempt += 1
365
-
366
- return wrapper
428
+ return attempt_execution(attempt)
429
+ except ScaleUpSignalError as error:
430
+ handle_scale_up_error(attempt, error)
431
+ except jax.errors.JaxRuntimeError as error:
432
+ handle_slice_down_error(attempt, error)
433
+ attempt += 1
434
+ finally:
435
+ self.close()
436
+
437
+ return wrapper # pyrefly: ignore[bad-return]
367
438
 
368
439
  return decorator
369
440