pathwaysutils 0.1.7__tar.gz → 0.1.9__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.
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/PKG-INFO +2 -2
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/__init__.py +1 -1
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/elastic/elastic.py +84 -72
- pathwaysutils-0.1.9/pathwaysutils/experimental/concatenate_by_mesh_axis.py +152 -0
- pathwaysutils-0.1.9/pathwaysutils/experimental/gke/__init__.py +11 -0
- pathwaysutils-0.1.9/pathwaysutils/experimental/gke/jobset.py +414 -0
- pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/deploy_pathways_service.py +261 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/gke_utils.py +188 -36
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/isc_pathways.py +157 -12
- pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/metrics_collector.py +296 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/run_connect_example.py +10 -4
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/run_workload.py +17 -16
- pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/start_vscode_on_cpu_np.py +162 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/validators.py +30 -2
- pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/yamls/code-server.yaml +58 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/yamls/pw-proxy.yaml +1 -1
- pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/yamls/pw-service.yaml +152 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/jax/__init__.py +21 -3
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/persistence/helper.py +0 -28
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/persistence/orbax_handler.py +3 -3
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/profiling.py +110 -41
- pathwaysutils-0.1.9/pathwaysutils/test/experimental/gke/jobset_test.py +137 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/experimental/reshard_test.py +15 -3
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/profiling_test.py +169 -29
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/reshard_test.py +10 -2
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pyproject.toml +1 -1
- pathwaysutils-0.1.7/pathwaysutils/experimental/shared_pathways_service/yamls/pw-service-example.yaml +0 -18
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/LICENSE +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/README.md +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/_initialize.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/collect_profile.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/debug/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/debug/timing.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/debug/watchdog.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/elastic/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/elastic/manager.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/profiling.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/reshard.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/README.md +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/split_by_mesh_axis.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/lru_cache.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/persistence/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/plugin_executable.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/proxy_backend.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/reshard.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/debug/__init__.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/debug/timing_test.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/debug/watchdog_test.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/initialize_test.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/lru_cache_test.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/persistence_test.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/plugin_executable_test.py +0 -0
- {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/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.
|
|
3
|
+
Version: 0.1.9
|
|
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.
|
|
13
|
+
Requires-Dist: jax>=0.8.3
|
|
14
14
|
Requires-Dist: orbax-checkpoint
|
|
15
15
|
Requires-Dist: uvicorn
|
|
16
16
|
Requires-Dist: requests
|
|
@@ -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
|
-
|
|
45
|
-
"""
|
|
47
|
+
class SliceHealthChecker(ABC):
|
|
48
|
+
"""Base class for slice health checkers.
|
|
46
49
|
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
55
|
+
def __init__(self, slice_to_devices: Mapping[int, Sequence[jax.Device]]):
|
|
56
|
+
self.slice_to_devices = slice_to_devices
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
66
|
-
|
|
71
|
+
class DefaultSliceHealthChecker(SliceHealthChecker):
|
|
72
|
+
"""Default implementation that checks each slice individually.
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
It executes a simple computation on each slice independently to verify
|
|
75
|
+
that the devices are active and responsive.
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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",
|
|
@@ -0,0 +1,152 @@
|
|
|
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.
|
|
12
|
+
|
|
13
|
+
"""Pathwaysutils JAX concatenate_by_mesh_axis."""
|
|
14
|
+
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
import itertools
|
|
17
|
+
from typing import Any
|
|
18
|
+
import jax
|
|
19
|
+
import numpy as np
|
|
20
|
+
from pathwaysutils import jax as pw_jax
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def concatenate_by_mesh_axis(
|
|
24
|
+
array_trees: Sequence[Any],
|
|
25
|
+
mesh_axis: str,
|
|
26
|
+
) -> Any:
|
|
27
|
+
"""Concatenates meshes by an axis. Returns arrays on the concatenated mesh.
|
|
28
|
+
|
|
29
|
+
Note: This API always donates the given arrays.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
array_trees: Sequence of PyTrees of JAX arrays with `NamedSharding`. All
|
|
33
|
+
PyTrees in the sequence have the same structure. All arrays in each PyTree
|
|
34
|
+
are sharded/replicated on the same mesh. The input arrays are always
|
|
35
|
+
donated.
|
|
36
|
+
mesh_axis: Mesh axis to concatenate.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A PyTree with the same structure as `array_trees[i]`. It has arrays with
|
|
40
|
+
their shards concatenated to match a concatenated mesh.
|
|
41
|
+
"""
|
|
42
|
+
if not array_trees:
|
|
43
|
+
return array_trees
|
|
44
|
+
|
|
45
|
+
def _get_named_sharding(array: jax.Array) -> jax.sharding.NamedSharding:
|
|
46
|
+
if not isinstance(array, jax.Array):
|
|
47
|
+
raise ValueError(f"Elements must be jax.Array. Got {type(array)}")
|
|
48
|
+
sharding = array.sharding
|
|
49
|
+
if not isinstance(sharding, jax.sharding.NamedSharding):
|
|
50
|
+
raise ValueError(f"Expected NamedSharding. Got {type(sharding)}")
|
|
51
|
+
return sharding
|
|
52
|
+
|
|
53
|
+
flats_and_defs = [jax.tree_util.tree_flatten(at) for at in array_trees]
|
|
54
|
+
flattened_arrays = [fd[0] for fd in flats_and_defs]
|
|
55
|
+
treedefs = [fd[1] for fd in flats_and_defs]
|
|
56
|
+
input_treedef = treedefs[0]
|
|
57
|
+
for td in treedefs[1:]:
|
|
58
|
+
if td != input_treedef:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"All array trees must have the same treedef. Got"
|
|
61
|
+
f" {td} vs. {input_treedef}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Convert to have the output array structure in the outer list, and each entry
|
|
65
|
+
# be a list of arrays from each shard for the concatenated output array.
|
|
66
|
+
input_flat_arrays = list(zip(*flattened_arrays))
|
|
67
|
+
|
|
68
|
+
if not flattened_arrays[0]:
|
|
69
|
+
return array_trees[0]
|
|
70
|
+
|
|
71
|
+
# Extract the shared mesh from each PyTree (from an arbitrary array in each).
|
|
72
|
+
meshes_to_concatenate = [
|
|
73
|
+
_get_named_sharding(array).mesh for array in input_flat_arrays[0]
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Validate that the meshes are compatible.
|
|
77
|
+
reference_mesh = meshes_to_concatenate[0]
|
|
78
|
+
if mesh_axis not in reference_mesh.axis_names:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"mesh_axis '{mesh_axis}' not found in mesh axis names:"
|
|
81
|
+
f" {reference_mesh.axis_names}"
|
|
82
|
+
)
|
|
83
|
+
mesh_axis_idx = reference_mesh.axis_names.index(mesh_axis)
|
|
84
|
+
for mesh in meshes_to_concatenate:
|
|
85
|
+
if mesh.axis_names != reference_mesh.axis_names:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Meshes must have the same axis names. Got"
|
|
88
|
+
f" {mesh} vs. {reference_mesh}."
|
|
89
|
+
)
|
|
90
|
+
if (
|
|
91
|
+
mesh.axis_sizes[:mesh_axis_idx]
|
|
92
|
+
!= reference_mesh.axis_sizes[:mesh_axis_idx]
|
|
93
|
+
or mesh.axis_sizes[mesh_axis_idx + 1 :]
|
|
94
|
+
!= reference_mesh.axis_sizes[mesh_axis_idx + 1 :]
|
|
95
|
+
):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"Arrays must have the same mesh axis sizes for all axes except"
|
|
98
|
+
f" {mesh_axis}. Got {mesh} vs. {reference_mesh}."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Construct list of the mesh axis section boundaries.
|
|
102
|
+
devices = [mesh.devices for mesh in meshes_to_concatenate]
|
|
103
|
+
mesh_axis_sections = list(
|
|
104
|
+
itertools.accumulate(
|
|
105
|
+
mesh.axis_sizes[mesh_axis_idx] for mesh in meshes_to_concatenate
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
concatenated_mesh = jax.sharding.Mesh(
|
|
110
|
+
np.concatenate(devices, mesh_axis_idx),
|
|
111
|
+
axis_names=reference_mesh.axis_names,
|
|
112
|
+
axis_types=reference_mesh.axis_types,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def _get_output_sharding(
|
|
116
|
+
arrays: Sequence[jax.Array],
|
|
117
|
+
) -> jax.sharding.NamedSharding:
|
|
118
|
+
reference_sharding = _get_named_sharding(arrays[0])
|
|
119
|
+
reference_spec = reference_sharding.spec
|
|
120
|
+
return jax.sharding.NamedSharding(
|
|
121
|
+
concatenated_mesh,
|
|
122
|
+
reference_spec,
|
|
123
|
+
memory_kind=reference_sharding.memory_kind,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def _sharded_dim_idx_for_sharding(
|
|
127
|
+
sharding: jax.sharding.NamedSharding,
|
|
128
|
+
) -> int:
|
|
129
|
+
sharded_dim_idx = -1
|
|
130
|
+
for dim_idx, dim_spec in enumerate(sharding.spec):
|
|
131
|
+
flat_dim_spec, _ = jax.tree_util.tree_flatten(dim_spec)
|
|
132
|
+
if mesh_axis in flat_dim_spec:
|
|
133
|
+
sharded_dim_idx = dim_idx
|
|
134
|
+
break
|
|
135
|
+
return sharded_dim_idx
|
|
136
|
+
|
|
137
|
+
out_shardings = [_get_output_sharding(arrays) for arrays in input_flat_arrays]
|
|
138
|
+
sharded_dim_idxs = [
|
|
139
|
+
_sharded_dim_idx_for_sharding(sharding) for sharding in out_shardings
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
flat_output_arrays = pw_jax.concatenate_by_mesh_axis(
|
|
143
|
+
arrays=input_flat_arrays,
|
|
144
|
+
sharded_dim_idxs=sharded_dim_idxs,
|
|
145
|
+
mesh_axis_sizes=concatenated_mesh.axis_sizes,
|
|
146
|
+
mesh_axis_idx=mesh_axis_idx,
|
|
147
|
+
mesh_axis_sections=mesh_axis_sections,
|
|
148
|
+
out_shardings=out_shardings,
|
|
149
|
+
donate=True,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return jax.tree_util.tree_unflatten(input_treedef, flat_output_arrays)
|
|
@@ -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.
|