pathwaysutils 0.1.11__tar.gz → 0.1.12__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 (67) hide show
  1. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/PKG-INFO +17 -2
  2. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/__init__.py +1 -1
  3. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/_initialize.py +9 -5
  4. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/collect_profile.py +4 -1
  5. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/elastic/elastic.py +7 -2
  6. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/elastic/manager.py +18 -16
  7. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/gke/jobset.py +100 -102
  8. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/README.md +43 -14
  9. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/deploy_pathways_service.py +79 -9
  10. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/gke_utils.py +376 -56
  11. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/isc_pathways.py +269 -97
  12. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/metrics_collector.py +9 -0
  13. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/run_connect_example.py +35 -7
  14. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/run_workload.py +28 -7
  15. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/validators.py +0 -16
  16. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/persistence/helper.py +0 -13
  17. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/profiling.py +131 -30
  18. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/proxy_backend.py +14 -4
  19. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/experimental/gke/jobset_test.py +77 -204
  20. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/experimental/gke/testdata/model_lite_tpuv5e_4x8.yaml +0 -4
  21. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/deploy_pathways_service_test.py +313 -0
  22. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/gke_utils_test.py +1599 -0
  23. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/isc_pathways_test.py +1507 -0
  24. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/metrics_collector_test.py +247 -0
  25. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/run_workload_test.py +240 -0
  26. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/start_vscode_on_cpu_np_test.py +324 -0
  27. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/tpu_specs_test.py +77 -0
  28. pathwaysutils-0.1.12/pathwaysutils/test/experimental/shared_pathways_service/validators_test.py +314 -0
  29. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/initialize_test.py +15 -0
  30. pathwaysutils-0.1.12/pathwaysutils/test/mock_fault_injection.py +293 -0
  31. pathwaysutils-0.1.12/pathwaysutils/test/mock_fault_injection_test.py +201 -0
  32. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/profiling_test.py +334 -18
  33. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/proxy_backend_test.py +41 -0
  34. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pyproject.toml +20 -1
  35. pathwaysutils-0.1.11/pathwaysutils/persistence/orbax_handler.py +0 -292
  36. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/LICENSE +0 -0
  37. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/README.md +0 -0
  38. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/debug/__init__.py +0 -0
  39. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/debug/timing.py +0 -0
  40. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/debug/watchdog.py +0 -0
  41. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/elastic/__init__.py +0 -0
  42. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/__init__.py +0 -0
  43. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/concatenate_by_mesh_axis.py +0 -0
  44. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/gke/__init__.py +0 -0
  45. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/profiling.py +0 -0
  46. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/reshard.py +0 -0
  47. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/__init__.py +0 -0
  48. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/dockerfiles/sample_sidecar.dockerfile +0 -0
  49. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/start_vscode_on_cpu_np.py +0 -0
  50. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/tpu_specs.py +0 -0
  51. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/yamls/code-server.yaml +0 -0
  52. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/shared_pathways_service/yamls/pw-proxy.yaml +0 -0
  53. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/experimental/split_by_mesh_axis.py +0 -0
  54. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/jax/__init__.py +0 -0
  55. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/lru_cache.py +0 -0
  56. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/persistence/__init__.py +0 -0
  57. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/plugin_executable.py +0 -0
  58. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/reshard.py +0 -0
  59. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/__init__.py +0 -0
  60. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/debug/__init__.py +0 -0
  61. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/debug/timing_test.py +0 -0
  62. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/debug/watchdog_test.py +0 -0
  63. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/experimental/reshard_test.py +0 -0
  64. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/lru_cache_test.py +0 -0
  65. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/persistence_test.py +0 -0
  66. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/plugin_executable_test.py +0 -0
  67. {pathwaysutils-0.1.11 → pathwaysutils-0.1.12}/pathwaysutils/test/reshard_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pathwaysutils
3
- Version: 0.1.11
3
+ Version: 0.1.12
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>
@@ -11,16 +11,31 @@ License-File: LICENSE
11
11
  Requires-Dist: absl-py
12
12
  Requires-Dist: fastapi
13
13
  Requires-Dist: jax>=0.8.3
14
- Requires-Dist: orbax-checkpoint
14
+ Requires-Dist: orbax-checkpoint>=0.12.3
15
15
  Requires-Dist: uvicorn
16
16
  Requires-Dist: requests
17
17
  Requires-Dist: packaging
18
18
  Requires-Dist: pylint>=2.6.0 ; extra == "dev"
19
19
  Requires-Dist: pyink ; extra == "dev"
20
+ Requires-Dist: kubernetes ; extra == "gke"
21
+ Requires-Dist: pyyaml ; extra == "gke"
22
+ Requires-Dist: portpicker ; extra == "sps"
23
+ Requires-Dist: google-cloud-monitoring ; extra == "sps"
24
+ Requires-Dist: portpicker ; extra == "sps-without-monitoring"
25
+ Requires-Dist: google-cloud-monitoring ; extra == "test"
26
+ Requires-Dist: httpx ; extra == "test"
27
+ Requires-Dist: kubernetes ; extra == "test"
28
+ Requires-Dist: portpicker ; extra == "test"
29
+ Requires-Dist: pyyaml ; extra == "test"
30
+ Requires-Dist: pytest ; extra == "test"
20
31
  Project-URL: changelog, https://github.com/AI-Hypercomputer/pathways-utils/blob/main/CHANGELOG.md
21
32
  Project-URL: homepage, https://github.com/AI-Hypercomputer/pathways-utils
22
33
  Project-URL: repository, https://github.com/AI-Hypercomputer/pathways-utils
23
34
  Provides-Extra: dev
35
+ Provides-Extra: gke
36
+ Provides-Extra: sps
37
+ Provides-Extra: sps-without-monitoring
38
+ Provides-Extra: test
24
39
 
25
40
  # Package of Pathways-on-Cloud utilities
26
41
 
@@ -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.11"
24
+ __version__ = "v0.1.12"
@@ -18,10 +18,11 @@ import logging
18
18
  import os
19
19
 
20
20
  import jax
21
+ from orbax.checkpoint import type_handlers
21
22
  from orbax.checkpoint._src.metadata import array_metadata_store as array_metadata_store_lib
23
+ from orbax.checkpoint._src.serialization import cloud_pathways_array_handler
22
24
  from pathwaysutils import profiling
23
25
  from pathwaysutils import proxy_backend
24
- from pathwaysutils.persistence import orbax_handler
25
26
 
26
27
 
27
28
  _logger = logging.getLogger(__name__)
@@ -91,11 +92,14 @@ def initialize() -> None:
91
92
  _logger.debug("Detected Pathways-on-Cloud backend. Applying changes.")
92
93
  proxy_backend.register_backend_factory()
93
94
  profiling.monkey_patch_jax()
94
- # TODO: b/365549911 - Remove when OCDBT-compatible
95
95
  if _is_persistence_enabled():
96
- orbax_handler.register_pathways_handlers(
97
- timeout=datetime.timedelta(hours=1),
98
- array_metadata_store=array_metadata_store_lib.Store(),
96
+ type_handlers.register_type_handler(
97
+ jax.Array,
98
+ cloud_pathways_array_handler.CloudPathwaysArrayHandler(
99
+ timeout=datetime.timedelta(hours=1),
100
+ array_metadata_store=array_metadata_store_lib.Store(),
101
+ ),
102
+ override=True,
99
103
  )
100
104
 
101
105
  # Turn off JAX compilation cache because Pathways handles its own
@@ -67,7 +67,10 @@ def main() -> None:
67
67
  args = parser.parse_args()
68
68
 
69
69
  if profiling.collect_profile(
70
- args.port, args.duration_ms, args.host, args.log_dir
70
+ args.port,
71
+ args.duration_ms,
72
+ args.host,
73
+ args.log_dir,
71
74
  ):
72
75
  _logger.info("Dumped profiling information in: %s", args.log_dir)
73
76
  else:
@@ -171,7 +171,7 @@ def get_active_slice_indices(
171
171
 
172
172
 
173
173
  def wait_for_slices(
174
- slice_count: int,
174
+ slice_count: int | None = None,
175
175
  poll_interval: float | int = 10,
176
176
  timeout: float | int | None = None,
177
177
  slice_to_devices: Mapping[int, Sequence[jax.Device]] | None = None,
@@ -180,7 +180,8 @@ def wait_for_slices(
180
180
  """Waits until after at least `slice_count` slices become active.
181
181
 
182
182
  Args:
183
- slice_count: The number of slices to wait for.
183
+ slice_count: The number of slices to wait for. If None, defaults to the
184
+ total number of slices.
184
185
  poll_interval: The minimum number of seconds to wait between availability
185
186
  checks. If the check takes longer than this, the next check will start
186
187
  immediately after the current check completes. Defaults to 10 seconds.
@@ -201,6 +202,10 @@ def wait_for_slices(
201
202
  _logger.debug("slice_to_devices is None. Getting from jax.devices().")
202
203
  slice_to_devices = get_slice_to_devices(jax.devices())
203
204
 
205
+ if slice_count is None:
206
+ _logger.debug("slice_count is None. Using len(slice_to_devices).")
207
+ slice_count = len(slice_to_devices)
208
+
204
209
  _logger.info(
205
210
  "Waiting for %s slices. Poll interval: %s, Timeout: %s",
206
211
  slice_count,
@@ -116,11 +116,12 @@ class Manager:
116
116
 
117
117
  self.all_slice_indices = frozenset(self.slice_to_devices.keys())
118
118
 
119
+ self._active_slice_indices = frozenset()
120
+ self.available_inactive_slices = frozenset()
121
+
119
122
  self.active_slice_indices = elastic.get_active_slice_indices(
120
123
  slice_to_devices=self.slice_to_devices
121
124
  )
122
- self.inactive_slice_indices = self.all_slice_indices - self.active_slice_indices
123
- self.available_inactive_slices = frozenset()
124
125
 
125
126
  self._stop_event = None
126
127
  self._monitor_thread = None
@@ -180,22 +181,26 @@ class Manager:
180
181
  except StopIteration as error:
181
182
  raise ValueError("No active slices") from error
182
183
 
184
+ @property
185
+ def active_slice_indices(self) -> frozenset[int]:
186
+ """The indices of active slices."""
187
+ return self._active_slice_indices
188
+
189
+ @active_slice_indices.setter
190
+ def active_slice_indices(self, value: Set[int]) -> None:
191
+ self._active_slice_indices = frozenset(value)
192
+ self.inactive_slice_indices = (
193
+ self.all_slice_indices - self._active_slice_indices
194
+ )
195
+ self.available_inactive_slices = frozenset(
196
+ self.available_inactive_slices - self._active_slice_indices
197
+ )
198
+
183
199
  @property
184
200
  def active_slice_count(self) -> int:
185
201
  """The number of active slices."""
186
202
  return len(self.active_slice_indices)
187
203
 
188
- @property
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
198
-
199
204
  def scale_by_active_slices(self, x: int | float) -> int | float:
200
205
  """Scale x by the number of active slices."""
201
206
  if isinstance(x, int):
@@ -372,9 +377,6 @@ class Manager:
372
377
  poll_interval=poll_interval,
373
378
  timeout=timeout,
374
379
  )
375
- self.inactive_slice_indices = (
376
- self.all_slice_indices - self.active_slice_indices
377
- )
378
380
  # Reset available_inactive_slices at attempt start since
379
381
  # active_slice_indices has just been updated by wait_for_slices.
380
382
  self.available_inactive_slices = frozenset()
@@ -11,17 +11,27 @@
11
11
  # limitations under the License.
12
12
  """Pathways JobSet generator and builder (with Worker Job Config)."""
13
13
 
14
+ from __future__ import annotations
15
+
14
16
  import hashlib
15
17
  import json
16
18
  import logging
17
19
  import math
18
20
  import time
19
- from typing import Any, Mapping, Sequence
20
- from kubernetes import client
21
- from kubernetes import config as k8s_config
21
+ from typing import TYPE_CHECKING, Any, Mapping, Sequence
22
22
  import yaml
23
23
 
24
- # GKE sidecar containers restartPolicy compatibility placeholder.
24
+ try:
25
+ import kubernetes
26
+ except ImportError as e:
27
+ raise ImportError(
28
+ "GKE utilities require `kubernetes`. "
29
+ "Please install pathwaysutils with GKE support:\n\n"
30
+ " pip install 'pathwaysutils[gke]'\n"
31
+ ) from e
32
+
33
+ from kubernetes import client
34
+ from kubernetes import config as k8s_config
25
35
 
26
36
  _logger = logging.getLogger(__name__)
27
37
 
@@ -71,8 +81,18 @@ MACHINE_TYPE_TO_GKE_ACCELERATOR_TYPE_MAP = {
71
81
  }
72
82
 
73
83
 
84
+ def _format_image(image: str, default_tag: str) -> str:
85
+ """Appends default_tag if image does not already specify a tag or digest."""
86
+ if "@" in image:
87
+ return image
88
+ last_slash = image.rfind("/")
89
+ if ":" in image[last_slash + 1:]:
90
+ return image
91
+ return f"{image}:{default_tag}"
92
+
93
+
74
94
  def _deserialize_dict(
75
- api_client: client.ApiClient, data_dict: Mapping[str, Any], klass: Any
95
+ api_client: Any, data_dict: Mapping[str, Any], klass: Any
76
96
  ) -> Any:
77
97
  class FakeResponse:
78
98
 
@@ -93,10 +113,8 @@ class PathwaysJobSet:
93
113
  tpu_type: str,
94
114
  topology: str,
95
115
  num_slices: int,
96
- user_pod_template: Mapping[str, Any] | None = None,
97
- main_container_name: str = "main",
98
116
  max_restarts: int = 0,
99
- max_slice_restarts: int = 0,
117
+ max_slice_restarts: int = 1_000_000,
100
118
  termination_grace_period_seconds: int | None = None,
101
119
  pathways_version: str = "latest",
102
120
  jobset_api_version: str = "v1alpha2",
@@ -104,6 +122,8 @@ class PathwaysJobSet:
104
122
  labels: Mapping[str, str] | None = None,
105
123
  annotations: Mapping[str, str] | None = None,
106
124
  shared_pathways_service: bool = False,
125
+ pathways_rm_and_worker_image: str = DEFAULT_PATHWAYS_RM_AND_WORKER_IMAGE,
126
+ pathways_proxy_image: str = DEFAULT_PATHWAYS_PROXY_IMAGE,
107
127
  ):
108
128
  """Initializes the instance.
109
129
 
@@ -114,23 +134,22 @@ class PathwaysJobSet:
114
134
  tpu_type: TPU type (e.g., "v5e").
115
135
  topology: TPU topology (e.g., "2x2").
116
136
  num_slices: Number of slices.
117
- user_pod_template: Optional user pod template for the head job.
118
- main_container_name: Name of the main container in user_pod_template.
119
137
  max_restarts: Maximum number of restarts for the JobSet.
120
- max_slice_restarts: Maximum number of slice restarts.
138
+ max_slice_restarts: Maximum number of slice restarts (defaults to 1_000_000 in headless and SPS mode).
121
139
  termination_grace_period_seconds: Optional termination grace period.
122
140
  pathways_version: Version tag for Pathways images.
123
141
  jobset_api_version: API version of JobSet.
124
142
  elastic_slices: Number of elastic slices.
125
143
  labels: Optional labels for the JobSet.
126
144
  annotations: Optional annotations for the JobSet.
145
+ shared_pathways_service: Whether to run only RM for Shared Pathways Service.
146
+ pathways_rm_and_worker_image: Base Docker image for Resource Manager and
147
+ Worker containers.
148
+ pathways_proxy_image: Base Docker image for Proxy container.
127
149
  """
128
- if shared_pathways_service and user_pod_template:
129
- raise ValueError(
130
- "Cannot enable shared_pathways_service when user_pod_template is"
131
- " provided."
132
- )
133
150
  self._shared_pathways_service = shared_pathways_service
151
+ self._pathways_rm_and_worker_image = pathways_rm_and_worker_image
152
+ self._pathways_proxy_image = pathways_proxy_image
134
153
 
135
154
  self._name = name
136
155
  self._namespace = namespace
@@ -140,6 +159,11 @@ class PathwaysJobSet:
140
159
  self._labels = dict(labels) if labels else {}
141
160
  self._annotations = dict(annotations) if annotations else {}
142
161
 
162
+ if elastic_slices > num_slices:
163
+ raise ValueError(
164
+ f"elastic_slices ({elastic_slices}) cannot be greater than "
165
+ f"num_slices ({num_slices})"
166
+ )
143
167
  tpu_version = MACHINE_TYPE_TO_TPU_VERSION_MAP.get(tpu_type.lower())
144
168
  if not tpu_version:
145
169
  raise ValueError(f"Unsupported TPU type: {tpu_type}")
@@ -166,10 +190,10 @@ class PathwaysJobSet:
166
190
  num_slices=num_slices,
167
191
  instance_type=instance_type,
168
192
  image_tag=image_tag,
169
- user_pod_template=user_pod_template,
170
- main_container_name=main_container_name,
171
193
  elastic_slices=elastic_slices,
172
194
  shared_pathways_service=shared_pathways_service,
195
+ pathways_rm_and_worker_image=pathways_rm_and_worker_image,
196
+ pathways_proxy_image=pathways_proxy_image,
173
197
  )
174
198
 
175
199
  # Build worker template.
@@ -182,10 +206,11 @@ class PathwaysJobSet:
182
206
  image_tag=image_tag,
183
207
  max_slice_restarts=max_slice_restarts,
184
208
  termination_grace_period_seconds=termination_grace_period_seconds,
209
+ pathways_rm_and_worker_image=pathways_rm_and_worker_image,
185
210
  )
186
211
 
187
212
  self._success_policy = None
188
- if user_pod_template or shared_pathways_service:
213
+ if shared_pathways_service:
189
214
  self._success_policy = {
190
215
  "operator": "All",
191
216
  "targetReplicatedJobs": [PATHWAYS_HEAD_JOB_NAME],
@@ -199,16 +224,24 @@ class PathwaysJobSet:
199
224
  def worker_job_template(self) -> client.V1JobTemplateSpec:
200
225
  return self._worker_job_template
201
226
 
227
+ @property
228
+ def pathways_rm_and_worker_image(self) -> str:
229
+ return self._pathways_rm_and_worker_image
230
+
231
+ @property
232
+ def pathways_proxy_image(self) -> str:
233
+ return self._pathways_proxy_image
234
+
202
235
  def _build_head_job_template(
203
236
  self,
204
237
  pathways_dir: str,
205
238
  num_slices: int,
206
239
  instance_type: str,
207
240
  image_tag: str,
208
- user_pod_template: Mapping[str, Any] | None,
209
- main_container_name: str,
210
241
  elastic_slices: int,
211
242
  shared_pathways_service: bool,
243
+ pathways_rm_and_worker_image: str,
244
+ pathways_proxy_image: str,
212
245
  ) -> client.V1JobTemplateSpec:
213
246
  """Builds the head job template for the JobSet.
214
247
 
@@ -217,15 +250,16 @@ class PathwaysJobSet:
217
250
  num_slices: Number of slices.
218
251
  instance_type: TPU instance type (e.g., "tpuv5:2x2").
219
252
  image_tag: Version tag for Pathways images.
220
- user_pod_template: Optional user pod template for the head job.
221
- main_container_name: Name of the main container in user_pod_template.
222
253
  elastic_slices: Number of elastic slices.
254
+ shared_pathways_service: Whether to run only RM for Shared Pathways Service.
255
+ pathways_rm_and_worker_image: Base Docker image for Resource Manager.
256
+ pathways_proxy_image: Base Docker image for Proxy container.
223
257
 
224
258
  Returns:
225
259
  The head job template.
226
260
  """
227
- rm_image = f"{DEFAULT_PATHWAYS_RM_AND_WORKER_IMAGE}:{image_tag}"
228
- proxy_image = f"{DEFAULT_PATHWAYS_PROXY_IMAGE}:{image_tag}"
261
+ rm_image = _format_image(pathways_rm_and_worker_image, image_tag)
262
+ proxy_image = _format_image(pathways_proxy_image, image_tag)
229
263
 
230
264
  rm_args = [
231
265
  f"--server_port={PATHWAYS_RM_PORT}",
@@ -318,74 +352,18 @@ class PathwaysJobSet:
318
352
  ),
319
353
  )
320
354
 
321
- api_client = client.ApiClient()
355
+ containers = [rm_container]
356
+ if not shared_pathways_service:
357
+ containers.append(proxy_container)
322
358
 
323
- if user_pod_template:
324
- user_template_obj = _deserialize_dict(
325
- api_client, user_pod_template, client.V1PodTemplateSpec
326
- )
327
- head_pod_spec = user_template_obj.spec
328
- head_pod_spec.host_network = True
329
- head_pod_spec.dns_policy = "ClusterFirstWithHostNet"
330
-
331
- rm_container.restart_policy = "Always" # pyrefly: ignore[missing-attribute]
332
- proxy_container.restart_policy = "Always" # pyrefly: ignore[missing-attribute]
333
-
334
- init_containers = head_pod_spec.init_containers or []
335
- init_containers.extend([rm_container, proxy_container])
336
- head_pod_spec.init_containers = init_containers
337
-
338
- # Inject JAX env vars into main container.
339
- jax_env = [
340
- client.V1EnvVar(
341
- name="PATHWAYS_HEAD",
342
- value_from=client.V1EnvVarSource(
343
- field_ref=client.V1ObjectFieldSelector(
344
- field_path=(
345
- "metadata.labels['jobset.sigs.k8s.io/coordinator']"
346
- )
347
- )
348
- ),
349
- ),
350
- client.V1EnvVar(name="JAX_PLATFORMS", value="proxy"),
351
- client.V1EnvVar(name="XCLOUD_ENVIRONMENT", value="GCP"),
352
- client.V1EnvVar(
353
- name="JAX_BACKEND_TARGET",
354
- value=f"grpc://$(PATHWAYS_HEAD):{PATHWAYS_PROXY_PORT}",
355
- ),
356
- ]
357
- containers = head_pod_spec.containers or []
358
- for c in containers:
359
- if c.name == main_container_name:
360
- env = c.env or []
361
- env.extend(jax_env)
362
- c.env = env
363
- break
364
- head_pod_spec.containers = containers
365
-
366
- annotations = user_pod_template.get("metadata", {}).get("annotations", {})
367
- labels = user_pod_template.get("metadata", {}).get("labels", {})
368
- else:
369
- # Headless mode.
370
- containers = [rm_container]
371
- if not shared_pathways_service:
372
- containers.append(proxy_container)
373
- head_pod_spec = client.V1PodSpec(
374
- host_network=True,
375
- dns_policy="ClusterFirstWithHostNet",
376
- containers=containers,
377
- )
378
- annotations = {}
379
- labels = {}
380
-
381
- if not head_pod_spec.restart_policy:
382
- head_pod_spec.restart_policy = "Never"
359
+ head_pod_spec = client.V1PodSpec(
360
+ containers=containers,
361
+ restart_policy="Never",
362
+ )
383
363
 
384
- # Default annotations
385
364
  job_annotations = {
386
365
  "alpha.jobset.sigs.k8s.io/exclusive-topology": "kubernetes.io/hostname"
387
366
  }
388
- job_annotations.update(annotations)
389
367
 
390
368
  head_job_template = client.V1JobTemplateSpec(
391
369
  metadata=client.V1ObjectMeta(annotations=job_annotations),
@@ -396,7 +374,7 @@ class PathwaysJobSet:
396
374
  parallelism=1,
397
375
  template=client.V1PodTemplateSpec(
398
376
  metadata=client.V1ObjectMeta(
399
- annotations=job_annotations, labels=labels
377
+ annotations=job_annotations, labels={}
400
378
  ),
401
379
  spec=head_pod_spec,
402
380
  ),
@@ -414,8 +392,10 @@ class PathwaysJobSet:
414
392
  image_tag: str,
415
393
  max_slice_restarts: int,
416
394
  termination_grace_period_seconds: int | None,
395
+ pathways_rm_and_worker_image: str,
417
396
  ) -> client.V1JobTemplateSpec:
418
- worker_image = f"{DEFAULT_PATHWAYS_RM_AND_WORKER_IMAGE}:{image_tag}"
397
+ """Builds the worker job template for the JobSet."""
398
+ worker_image = _format_image(pathways_rm_and_worker_image, image_tag)
419
399
 
420
400
  args = [
421
401
  f"--resource_manager_address=$(PATHWAYS_HEAD):{PATHWAYS_RM_PORT}",
@@ -510,9 +490,7 @@ class PathwaysJobSet:
510
490
  "cloud.google.com/gke-tpu-topology": topology,
511
491
  }
512
492
 
513
- backoff_limit = num_vms * 4
514
- if max_slice_restarts > 0:
515
- backoff_limit = num_vms * max_slice_restarts
493
+ backoff_limit = num_vms * max_slice_restarts
516
494
 
517
495
  worker_pod_spec = client.V1PodSpec(
518
496
  containers=[worker_container],
@@ -525,8 +503,6 @@ class PathwaysJobSet:
525
503
  ),
526
504
  )
527
505
  ],
528
- host_network=True,
529
- dns_policy="ClusterFirstWithHostNet",
530
506
  restart_policy="OnFailure",
531
507
  )
532
508
  if termination_grace_period_seconds is not None:
@@ -637,14 +613,15 @@ class PathwaysJobSet:
637
613
  ports=[client.V1ContainerPort(container_port=50051)],
638
614
  volume_mounts=[
639
615
  client.V1VolumeMount(name="shared-tmp", mount_path="/tmp"),
640
- client.V1VolumeMount(name=shm_volume_name, mount_path=shm_mount_path),
616
+ client.V1VolumeMount(
617
+ name=shm_volume_name, mount_path=shm_mount_path
618
+ ),
641
619
  ],
642
620
  )
643
- colocated_container.restart_policy = "Always" # pyrefly: ignore[missing-attribute]
644
621
 
645
- init_containers = pod_spec.init_containers or []
646
- init_containers.append(colocated_container)
647
- pod_spec.init_containers = init_containers
622
+ containers = pod_spec.containers or []
623
+ containers.append(colocated_container)
624
+ pod_spec.containers = containers
648
625
 
649
626
  # Add volume mount to pathways-worker.
650
627
  for container in pod_spec.containers:
@@ -660,7 +637,7 @@ class PathwaysJobSet:
660
637
  env = container.env or []
661
638
  env.append(
662
639
  client.V1EnvVar(
663
- name="cloud_pathways_sidecar_shm_directory",
640
+ name="CLOUD_PATHWAYS_SIDECAR_SHM_DIRECTORY",
664
641
  value=shm_mount_path,
665
642
  )
666
643
  )
@@ -694,7 +671,9 @@ class PathwaysJobSet:
694
671
 
695
672
  for job_template in (self._head_job_template, self._worker_job_template):
696
673
  pod_spec = job_template.spec.template.spec
697
- all_containers = (pod_spec.containers or []) + (pod_spec.init_containers or [])
674
+ all_containers = (pod_spec.containers or []) + (
675
+ pod_spec.init_containers or []
676
+ )
698
677
 
699
678
  matching = self._filter_matching_containers(containers, all_containers)
700
679
  if not matching:
@@ -824,6 +803,25 @@ class PathwaysJobSet:
824
803
  instance._head_job_template = head_job_template
825
804
  instance._worker_job_template = worker_job_template
826
805
 
806
+ rm_image = DEFAULT_PATHWAYS_RM_AND_WORKER_IMAGE
807
+ proxy_image = DEFAULT_PATHWAYS_PROXY_IMAGE
808
+ if (
809
+ head_job_template.spec
810
+ and head_job_template.spec.template
811
+ and head_job_template.spec.template.spec
812
+ ):
813
+ pod_spec = head_job_template.spec.template.spec
814
+ all_containers = (pod_spec.containers or []) + (
815
+ pod_spec.init_containers or []
816
+ )
817
+ for c in all_containers:
818
+ if c.name == "pathways-rm" and c.image:
819
+ rm_image = c.image
820
+ elif c.name == "pathways-proxy" and c.image:
821
+ proxy_image = c.image
822
+ instance._pathways_rm_and_worker_image = rm_image
823
+ instance._pathways_proxy_image = proxy_image
824
+
827
825
  instance._success_policy = config["spec"].get("successPolicy")
828
826
  return instance
829
827
 
@@ -16,13 +16,19 @@ You have a GKE cluster with at least 1 TPU slice (v5e, v5p or v6e).
16
16
 
17
17
  ### 2. Deploy the Pathways head pod
18
18
 
19
- Start the Shared Pathways Service by using [pw-service-example.yaml](yamls/pw-service-example.yaml).
20
- Make sure to modify the following values to deploy the Pathways pods:
19
+ Start the Shared Pathways Service by running `deploy_pathways_service.py`.
21
20
 
22
- - A unique Jobset name for the head pod
23
- - GCS bucket path
24
- - TPU type and topology
25
- - Number of slices
21
+ ```shell
22
+ python3 -m pathwaysutils.experimental.shared_pathways_service.deploy_pathways_service \
23
+ --jobset_name ${SERVICE_JOBSET_NAME} \
24
+ --gcs_bucket ${GCS_BUCKET} \
25
+ --server_image ${SERVER_IMAGE} \
26
+ --tpu_type ${TPU_TYPE} \
27
+ --topology ${TOPOLOGY} \
28
+ --num_slices ${NUM_SLICES} [--sidecar_image ${SIDECAR_IMAGE}] [--dry_run]
29
+ ```
30
+
31
+ Note, `server_image` is the Pathways server image, e.g, `us-docker.pkg.dev/cloud-tpu-v2-images/pathways/server:<tag>`.
26
32
 
27
33
  ### 3. Verify that the pods created in [Step#2](#2-deploy-the-pathways-head-pod) are running
28
34
 
@@ -55,13 +61,13 @@ pathways-cluster-worker-1-0-km2rf 1/1 Running 0 3m36s
55
61
 
56
62
  ```shell
57
63
  # e.g., pathways-cluster
58
- $ JOBSET_NAME=<your-jobset-name> # same as you used in [pw-service-example.yaml](#pw-service-yaml)
64
+ $ SERVICE_JOBSET_NAME=<your-jobset-name> # same as you used in [pw-service-example.yaml](#pw-service-yaml)
59
65
 
60
66
  # e.g., pathways-cluster-pathways-head-0-0-zzmn2
61
- $ HEAD_POD_NAME=$(kubectl get pods --selector=jobset.sigs.k8s.io/jobset-name=${JOBSET_NAME} -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | sed 's/ /\n/g' | grep head)
67
+ $ HEAD_POD_NAME=$(kubectl get pods --selector=jobset.sigs.k8s.io/jobset-name=${SERVICE_JOBSET_NAME} -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | sed 's/ /\n/g' | grep head)
62
68
 
63
69
  # e.g., pathways-cluster-worker-0-0-bdzq4
64
- $ WORKER0_POD_NAME=$(kubectl get pods --selector=jobset.sigs.k8s.io/jobset-name=${JOBSET_NAME} -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | sed 's/ /\n/g' | grep 'worker-0-0-')
70
+ $ WORKER0_POD_NAME=$(kubectl get pods --selector=jobset.sigs.k8s.io/jobset-name=${SERVICE_JOBSET_NAME} -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | sed 's/ /\n/g' | grep 'worker-0-0-')
65
71
  ```
66
72
 
67
73
  #### Option 3: Check project logs
@@ -80,15 +86,33 @@ I1208 20:10:18.148825 ...] argv[2]: '--resource_manager_address=pathways-c
80
86
 
81
87
  ## Instructions
82
88
 
83
- ### 1. Clone `pathwaysutils`.
89
+ ### 1. Install `pathwaysutils` and SPS dependencies.
84
90
 
85
91
  ```shell
86
- git clone https://github.com/AI-Hypercomputer/pathways-utils.git
92
+ pip install --upgrade "pathwaysutils[sps] @ git+https://github.com/AI-Hypercomputer/pathways-utils.git"
93
+
94
+ # Note: google-cloud-monitoring is optional. If you don't wish to collect SPS usage metrics, install with pathways-utils[sps_without_monitoring].
87
95
  ```
88
96
 
89
- ### 2. Use the `isc_pathways` Context Manager
97
+ ### 2. Either use `run_workload.py` or move your workload under the `isc_pathways` Context Manager
98
+
99
+ Make sure to use the proxy image compatible with the server image used while deploying SPS.
100
+
101
+ ```shell
102
+ python3 -m pathwaysutils.experimental.shared_pathways_service.run_workload \
103
+ --cluster=$CLUSTER_NAME \
104
+ --project=$PROJECT \
105
+ --region=$REGION \
106
+ --gcs_bucket=$GCS_BUCKET \
107
+ --pathways_service="$SERVICE_JOBSET_NAME-pathways-head-0-0.$SERVICE_JOBSET_NAME:29001" \
108
+ --tpu_type="$TPU_TYPE:$TOPOLOGY" \
109
+ --tpu_count=$NUM_SLICES \
110
+ --proxy_server_image=$PROXY_IMAGE \
111
+ --collect_service_metrics \
112
+ --command="python3 run_my_sample_workload.py" [--proxy_options=sidecar:true]
113
+ ```
90
114
 
91
- In your script,
115
+ Alternatively, in your script,
92
116
 
93
117
  1. Import `isc_pathways`
94
118
  2. Add `with isc_pathways.connect(...)` statement. The function takes the below values:
@@ -97,6 +121,10 @@ In your script,
97
121
  - Region
98
122
  - GCS bucket name
99
123
  - Pathways Service (See instructions to find the RM address [here](#4-find-the-pathways-service-address))
124
+ - Expected TPU instances
125
+ - Proxy server image
126
+ - Proxy options
127
+ - Whether to collect SPS usage metrics
100
128
  <a name="ml-code"></a>
101
129
  3. Write your ML code under this context manager (the `with` block) to run your JAX code on the underlying TPUs.
102
130
 
@@ -111,7 +139,8 @@ python3 pathwaysutils/experimental/shared_pathways_service/run_connect_example.p
111
139
  --gcs_bucket="gs://user-bucket" \
112
140
  --pathways_service="pathways-cluster-pathways-head-0-0.pathways-cluster:29001" \
113
141
  --tpu_type="tpuv6e:2x2" \
114
- --tpu_count=1
142
+ --tpu_count=1 \
143
+ --proxy_server_image="proxy_image"
115
144
  ```
116
145
 
117
146
  The connect block will deploy a proxy pod dedicated to your client and connect