pathwaysutils 0.1.8__tar.gz → 0.1.10__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.8 → pathwaysutils-0.1.10}/PKG-INFO +2 -2
  2. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/__init__.py +1 -1
  3. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/elastic/elastic.py +84 -72
  4. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/elastic/manager.py +65 -20
  5. pathwaysutils-0.1.10/pathwaysutils/experimental/gke/__init__.py +11 -0
  6. pathwaysutils-0.1.10/pathwaysutils/experimental/gke/jobset.py +579 -0
  7. pathwaysutils-0.1.10/pathwaysutils/experimental/reshard.py +218 -0
  8. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/deploy_pathways_service.py +12 -2
  9. pathwaysutils-0.1.10/pathwaysutils/experimental/shared_pathways_service/dockerfiles/sample_sidecar.dockerfile +27 -0
  10. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/gke_utils.py +216 -34
  11. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/isc_pathways.py +70 -19
  12. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/metrics_collector.py +28 -6
  13. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/run_connect_example.py +4 -4
  14. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/run_workload.py +5 -19
  15. pathwaysutils-0.1.10/pathwaysutils/experimental/shared_pathways_service/start_vscode_on_cpu_np.py +162 -0
  16. pathwaysutils-0.1.10/pathwaysutils/experimental/shared_pathways_service/validators.py +234 -0
  17. pathwaysutils-0.1.10/pathwaysutils/experimental/shared_pathways_service/yamls/code-server.yaml +58 -0
  18. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/yamls/pw-proxy.yaml +1 -1
  19. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/yamls/pw-service.yaml +45 -2
  20. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/jax/__init__.py +1 -1
  21. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/persistence/helper.py +0 -28
  22. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/persistence/orbax_handler.py +3 -3
  23. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/profiling.py +105 -42
  24. {pathwaysutils-0.1.8/pathwaysutils/experimental → pathwaysutils-0.1.10/pathwaysutils}/reshard.py +78 -158
  25. pathwaysutils-0.1.10/pathwaysutils/test/experimental/gke/jobset_test.py +210 -0
  26. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/experimental/reshard_test.py +15 -3
  27. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/profiling_test.py +152 -46
  28. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/reshard_test.py +10 -2
  29. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pyproject.toml +1 -1
  30. pathwaysutils-0.1.8/pathwaysutils/experimental/shared_pathways_service/validators.py +0 -107
  31. pathwaysutils-0.1.8/pathwaysutils/reshard.py +0 -133
  32. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/LICENSE +0 -0
  33. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/README.md +0 -0
  34. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/_initialize.py +0 -0
  35. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/collect_profile.py +0 -0
  36. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/debug/__init__.py +0 -0
  37. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/debug/timing.py +0 -0
  38. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/debug/watchdog.py +0 -0
  39. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/elastic/__init__.py +0 -0
  40. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/__init__.py +0 -0
  41. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/concatenate_by_mesh_axis.py +0 -0
  42. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/profiling.py +0 -0
  43. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/README.md +0 -0
  44. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/shared_pathways_service/__init__.py +0 -0
  45. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/experimental/split_by_mesh_axis.py +0 -0
  46. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/lru_cache.py +0 -0
  47. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/persistence/__init__.py +0 -0
  48. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/plugin_executable.py +0 -0
  49. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/proxy_backend.py +0 -0
  50. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/__init__.py +0 -0
  51. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/debug/__init__.py +0 -0
  52. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/debug/timing_test.py +0 -0
  53. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/debug/watchdog_test.py +0 -0
  54. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/initialize_test.py +0 -0
  55. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/lru_cache_test.py +0 -0
  56. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/persistence_test.py +0 -0
  57. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/plugin_executable_test.py +0 -0
  58. {pathwaysutils-0.1.8 → pathwaysutils-0.1.10}/pathwaysutils/test/proxy_backend_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pathwaysutils
3
- Version: 0.1.8
3
+ Version: 0.1.10
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>
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: Apache Software License
10
10
  License-File: LICENSE
11
11
  Requires-Dist: absl-py
12
12
  Requires-Dist: fastapi
13
- Requires-Dist: jax>=0.8.0
13
+ Requires-Dist: jax>=0.8.3
14
14
  Requires-Dist: orbax-checkpoint
15
15
  Requires-Dist: uvicorn
16
16
  Requires-Dist: requests
@@ -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.8"
24
+ __version__ = "v0.1.10"
@@ -18,10 +18,13 @@ retries a function in case of `jax.errors.JaxRuntimeError` caused by slice down
18
18
  events. It also provides a utility for waiting for slices to become active.
19
19
  """
20
20
 
21
+ from abc import ABC, abstractmethod
21
22
  import collections
22
- from collections.abc import Mapping, Sequence, Set
23
+ from collections.abc import Callable, Mapping, Sequence, Set
24
+ import itertools
23
25
  import logging
24
26
  import time
27
+ from typing import Any
25
28
 
26
29
  import jax
27
30
  import numpy as np
@@ -41,44 +44,87 @@ _ELASTIC_DOWN_ADDITIONAL_ERROR_TYPES = frozenset([
41
44
  ])
42
45
 
43
46
 
44
- def _plus_one(x: jax.Array) -> jax.Array:
45
- """Adds one to each element in the array.
47
+ class SliceHealthChecker(ABC):
48
+ """Base class for slice health checkers.
46
49
 
47
- Used to test if a slice is active.
50
+ Implementations must provide `dispatch` and `validate` methods.
51
+ `dispatch` starts the health check operations, and `validate` waits for them
52
+ and returns the active slice indices.
53
+ """
48
54
 
49
- Args:
50
- x: The array to add one to.
55
+ def __init__(self, slice_to_devices: Mapping[int, Sequence[jax.Device]]):
56
+ self.slice_to_devices = slice_to_devices
51
57
 
52
- Returns:
53
- The array with one added to each element.
54
- """
55
- return x + 1
58
+ @abstractmethod
59
+ def dispatch(self) -> None:
60
+ """Dispatches the JAX operations in the background."""
56
61
 
62
+ @abstractmethod
63
+ def validate(self) -> Set[int]:
64
+ """Blocks on results and returns the set of active slice indices.
57
65
 
58
- def _simple_execution(devices: Sequence[jax.Device]) -> jax.Array:
59
- """Simple execution to test if a slice is active.
66
+ Returns:
67
+ Set of active slice indices.
68
+ """
60
69
 
61
- This function is used to test if a slice is active. It executes a simple
62
- computation on the devices and returns the result. If any of the devices are
63
- not active, the returned array will fail with a JaxRuntimeError used.
64
70
 
65
- Simply executing this function is not enough to determine if the slice is
66
- active. We also need to check the value of the returned array.
71
+ class DefaultSliceHealthChecker(SliceHealthChecker):
72
+ """Default implementation that checks each slice individually.
67
73
 
68
- Args:
69
- devices: The devices to execute on.
74
+ It executes a simple computation on each slice independently to verify
75
+ that the devices are active and responsive.
70
76
 
71
- Returns:
72
- The result of the execution.
77
+ The computation involves creating a zero array of the same size as the
78
+ number of devices in the slice, adding (_SIMPLE_EXECUTION_TEST_VALUE - 1)
79
+ to it, and then running a pmap to add 1 to each element. This verifies
80
+ that the devices are active and can perform computations.
73
81
  """
74
- if not devices:
75
- raise ValueError("No devices")
76
82
 
77
- test_input = np.zeros(len(devices), dtype=float) + (
78
- _SIMPLE_EXECUTION_TEST_VALUE - 1
79
- )
83
+ def _plus_one(self, x: jax.Array) -> jax.Array:
84
+ return x + 1
85
+
86
+ def _simple_execution(self, devices: Sequence[jax.Device]) -> jax.Array:
87
+ if not devices:
88
+ raise ValueError("No devices")
89
+
90
+ test_input = np.zeros(len(devices), dtype=float) + (
91
+ _SIMPLE_EXECUTION_TEST_VALUE - 1
92
+ )
93
+
94
+ return jax.pmap(self._plus_one, devices=devices)(test_input)
95
+
96
+ def dispatch(self) -> None:
97
+ self.results = {
98
+ slice_index: self._simple_execution(devices)
99
+ for slice_index, devices in self.slice_to_devices.items()
100
+ }
101
+
102
+ def validate(self) -> Set[int]:
103
+ active_slice_indices = set()
104
+ for slice_index, x in self.results.items():
105
+ expected = (
106
+ np.zeros(len(self.slice_to_devices[slice_index]), dtype=float)
107
+ + _SIMPLE_EXECUTION_TEST_VALUE
108
+ )
109
+ try:
110
+ jax.block_until_ready(x)
111
+ if np.allclose(x, expected):
112
+ active_slice_indices.add(slice_index)
113
+ else:
114
+ msg = (
115
+ f"Error with _simple_execution for slice_index={slice_index}. "
116
+ f"Expected: {expected!r}, Actual: {x!r}"
117
+ )
118
+ _logger.error(msg)
119
+ raise ValueError(msg)
120
+ except jax.errors.JaxRuntimeError as error:
121
+ _logger.debug(
122
+ "Caught JaxRuntimeError for slice_index=%s: %s", slice_index, error
123
+ )
124
+ if not is_error_due_to_slice_down(error):
125
+ raise
126
+ return active_slice_indices
80
127
 
81
- return jax.pmap(_plus_one, devices=devices)(test_input)
82
128
 
83
129
 
84
130
  def get_slice_to_devices(
@@ -94,6 +140,7 @@ def get_slice_to_devices(
94
140
  @timing.timeit
95
141
  def get_active_slice_indices(
96
142
  slice_to_devices: Mapping[int, Sequence[jax.Device]] | None = None,
143
+ checker: SliceHealthChecker | None = None,
97
144
  ) -> Set[int]:
98
145
  """Returns the set of active slices indices.
99
146
 
@@ -101,6 +148,8 @@ def get_active_slice_indices(
101
148
  slice_to_devices: A mapping from slice index to devices. If None,
102
149
  `get_slice_to_devices(jax.devices())` is used to gather all available
103
150
  devices and group them by slice.
151
+ checker: A SliceHealthChecker instance. If None, DefaultSliceHealthChecker
152
+ is used.
104
153
 
105
154
  Returns:
106
155
  A set of integers representing the indices of the active slices.
@@ -109,55 +158,16 @@ def get_active_slice_indices(
109
158
  _logger.debug("slice_to_devices is None. Getting from jax.devices().")
110
159
  slice_to_devices = get_slice_to_devices(tuple(jax.devices()))
111
160
 
161
+ if checker is None:
162
+ checker = DefaultSliceHealthChecker(slice_to_devices)
163
+
112
164
  _logger.debug(
113
165
  "Getting active slice indices for slices: %s",
114
166
  sorted(list(slice_to_devices.keys())),
115
167
  )
116
168
 
117
- active_slice_indices = set()
118
-
119
- results = {
120
- slice_index: _simple_execution(devices)
121
- for slice_index, devices in slice_to_devices.items()
122
- }
123
-
124
- for slice_index, x in results.items():
125
- _logger.debug("Checking slice_index=%s", slice_index)
126
- expected = (
127
- np.zeros(len(slice_to_devices[slice_index]), dtype=float)
128
- + _SIMPLE_EXECUTION_TEST_VALUE
129
- )
130
- try:
131
- with timing.Timer(f"Checking {slice_index=}"):
132
- _logger.debug("Blocking until ready for slice_index=%s", slice_index)
133
- jax.block_until_ready(x)
134
- _logger.debug("Execution finished for slice_index=%s", slice_index)
135
- if np.allclose(x, expected):
136
- active_slice_indices.add(slice_index)
137
- _logger.debug("slice_index=%s active", slice_index)
138
- else:
139
- _logger.error(
140
- "Error with _simple_execution for slice_index=%s. "
141
- "This should never happen. Expected: %r, Actual: %r",
142
- slice_index,
143
- expected,
144
- x,
145
- )
146
- raise ValueError(
147
- f"Error with _simple_execution for slice_index={slice_index}."
148
- )
149
- except jax.errors.JaxRuntimeError as error:
150
- _logger.debug(
151
- "Caught JaxRuntimeError for slice_index=%s: %s", slice_index, error
152
- )
153
- if not is_error_due_to_slice_down(error):
154
- _logger.info("Re-raising error for slice_index=%s", slice_index)
155
- raise
156
- _logger.debug("slice_index=%s bad", slice_index)
157
-
158
- _logger.debug("active_slice_indices=%s", active_slice_indices)
159
-
160
- return active_slice_indices
169
+ checker.dispatch()
170
+ return checker.validate()
161
171
 
162
172
 
163
173
  def wait_for_slices(
@@ -165,6 +175,7 @@ def wait_for_slices(
165
175
  poll_interval: float | int = 10,
166
176
  timeout: float | int | None = None,
167
177
  slice_to_devices: Mapping[int, Sequence[jax.Device]] | None = None,
178
+ checker: SliceHealthChecker | None = None,
168
179
  ) -> Set[int]:
169
180
  """Waits until after at least `slice_count` slices become active.
170
181
 
@@ -177,6 +188,7 @@ def wait_for_slices(
177
188
  timeout.
178
189
  slice_to_devices: A mapping from slice index to devices. If None,
179
190
  `get_slice_to_devices(jax.devices())` is used.
191
+ checker: A SliceHealthChecker instance to use for checking health.
180
192
 
181
193
  Returns:
182
194
  The active slice indices
@@ -201,7 +213,7 @@ def wait_for_slices(
201
213
  check_start_time = time.time()
202
214
 
203
215
  _logger.debug("Checking active slices...")
204
- active_slice_indices = get_active_slice_indices(slice_to_devices)
216
+ active_slice_indices = get_active_slice_indices(slice_to_devices, checker)
205
217
  if len(active_slice_indices) >= slice_count:
206
218
  _logger.info(
207
219
  "Sufficient slices active: %s >= %s. Active indices: %s",
@@ -22,7 +22,7 @@ from collections.abc import Callable, Mapping, Sequence, Set
22
22
  import functools
23
23
  import logging
24
24
  import threading
25
- from typing import Any, TypeVar
25
+ from typing import Any, TypeAlias, TypeVar
26
26
  import warnings
27
27
 
28
28
  import jax
@@ -48,6 +48,7 @@ class ScaleUpSignalError(Exception):
48
48
 
49
49
 
50
50
  _F = TypeVar("_F", bound=Callable[..., Any])
51
+ RetryPolicy: TypeAlias = Callable[[int, Exception], bool]
51
52
 
52
53
 
53
54
  def _elastic_event_cleanup() -> None:
@@ -66,6 +67,19 @@ def _elastic_event_cleanup() -> None:
66
67
  array.delete()
67
68
 
68
69
 
70
+ class ElasticRetryLimit:
71
+ """A retry callback that limits the number of attempts."""
72
+
73
+ def __init__(self, max_attempts: int):
74
+ if max_attempts <= 0:
75
+ raise ValueError("max_attempts must be positive.")
76
+ self.max_attempts = max_attempts
77
+
78
+ def __call__(self, attempt: int, error: Exception) -> bool:
79
+ del error # Unused
80
+ return attempt < self.max_attempts
81
+
82
+
69
83
  class Manager:
70
84
  """Utility class for elastic training.
71
85
 
@@ -191,12 +205,14 @@ class Manager:
191
205
 
192
206
  def elastic_retry(
193
207
  self,
194
- max_retries: int,
208
+ # TODO: b/523384843 - Remove max_retries parameter.
209
+ max_retries: int | None = None,
195
210
  minimum_slice_count: int | None = None,
196
211
  poll_interval: float | int = 10,
197
212
  timeout: float | None = None,
198
213
  pre_callback: Callable[..., Any] | None = None,
199
214
  on_elastic_event_callback: Callable[..., Any] | None = None,
215
+ retry_policy: RetryPolicy | None = None,
200
216
  ) -> Callable[[_F], _F]:
201
217
  """Retries a function with elasticity fault tolerance.
202
218
 
@@ -224,6 +240,8 @@ class Manager:
224
240
 
225
241
  Args:
226
242
  max_retries: The maximum number of times to retry the function.
243
+ Deprecated: Use `retry_policy` instead.
244
+ TODO: b/523384843 - Remove max_retries parameter.
227
245
  minimum_slice_count: The minimum number of slices required to run the
228
246
  function. If None, defaults to the total number of slices.
229
247
  poll_interval: The number of seconds to wait between activity checks.
@@ -233,6 +251,11 @@ class Manager:
233
251
  pre_callback: A callback to call before the function is attempted.
234
252
  on_elastic_event_callback: A callback to call after an elastic failure
235
253
  occurs.
254
+ retry_policy: A policy (callable) to determine if a retry should be
255
+ attempted. It accepts the attempt number (1-indexed) and the exception
256
+ that triggered the retry. If it returns False, no more retries are
257
+ attempted. If neither `retry_policy` nor `max_retries` is specified, it
258
+ defaults to unlimited retries.
236
259
 
237
260
  Returns:
238
261
  A decorator that retries the wrapped function.
@@ -248,17 +271,24 @@ class Manager:
248
271
  else minimum_slice_count
249
272
  )
250
273
 
251
- if max_retries <= 0:
252
- raise ValueError("max_retries must be positive.")
274
+ if max_retries is not None and retry_policy is not None:
275
+ raise ValueError("Cannot specify both max_retries and retry_policy.")
276
+
277
+ if retry_policy is None:
278
+ if max_retries is None:
279
+ # Default to unlimited retries if neither parameter is supplied.
280
+ retry_policy = lambda attempt, error: True
281
+ else:
282
+ if max_retries <= 0:
283
+ raise ValueError("max_retries must be positive.")
284
+ retry_policy = ElasticRetryLimit(max_retries)
253
285
 
254
286
  def decorator(func: _F) -> _F:
255
287
  @functools.wraps(func)
256
288
  def wrapper(*args: Any, **kwargs: Any) -> Any:
257
289
 
258
- def attempt_execution(retry_index: int) -> Any:
259
- _logger.info(
260
- "Elastic attempt %d out of %d", retry_index + 1, max_retries
261
- )
290
+ def attempt_execution(attempt: int) -> Any:
291
+ _logger.info("Elastic attempt %d", attempt)
262
292
  self.active_slice_indices = elastic.wait_for_slices(
263
293
  slice_count=target_slice_count,
264
294
  slice_to_devices=self.slice_to_devices,
@@ -289,34 +319,49 @@ class Manager:
289
319
  if monitor_thread is not None:
290
320
  monitor_thread.join()
291
321
 
292
- for retry_index in range(max_retries):
322
+ attempt = 1
323
+ while True:
293
324
  try:
294
- return attempt_execution(retry_index)
295
- except ScaleUpSignalError:
296
- _logger.info("Scale up requested. Retrying.")
325
+ return attempt_execution(attempt)
326
+ except ScaleUpSignalError as error:
327
+ _logger.info("Scale up requested.")
297
328
  _elastic_event_cleanup()
298
329
 
299
330
  if on_elastic_event_callback is not None:
300
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.")
301
342
  except jax.errors.JaxRuntimeError as error:
302
343
  if not elastic.is_error_due_to_slice_down(error):
303
344
  raise
304
345
 
305
346
  if self.new_slice_event.is_set():
306
- _logger.info(
307
- "Slice down event and new slice available detected. Retrying."
308
- )
347
+ _logger.info("Slice down event and new slice available detected.")
309
348
  else:
310
- _logger.info("Slice down event detected. Retrying.")
349
+ _logger.info("Slice down event detected.")
311
350
 
312
351
  _elastic_event_cleanup()
313
352
 
314
353
  if on_elastic_event_callback is not None:
315
354
  on_elastic_event_callback()
316
- else:
317
- raise ElasticRuntimeError(
318
- f"Elastic attempt {max_retries} out of {max_retries} failed."
319
- )
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
320
365
 
321
366
  return wrapper
322
367
 
@@ -0,0 +1,11 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # https://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.