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.
Files changed (56) hide show
  1. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/PKG-INFO +2 -2
  2. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/__init__.py +1 -1
  3. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/elastic/elastic.py +84 -72
  4. pathwaysutils-0.1.9/pathwaysutils/experimental/concatenate_by_mesh_axis.py +152 -0
  5. pathwaysutils-0.1.9/pathwaysutils/experimental/gke/__init__.py +11 -0
  6. pathwaysutils-0.1.9/pathwaysutils/experimental/gke/jobset.py +414 -0
  7. pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/deploy_pathways_service.py +261 -0
  8. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/gke_utils.py +188 -36
  9. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/isc_pathways.py +157 -12
  10. pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/metrics_collector.py +296 -0
  11. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/run_connect_example.py +10 -4
  12. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/run_workload.py +17 -16
  13. pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/start_vscode_on_cpu_np.py +162 -0
  14. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/validators.py +30 -2
  15. pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/yamls/code-server.yaml +58 -0
  16. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/yamls/pw-proxy.yaml +1 -1
  17. pathwaysutils-0.1.9/pathwaysutils/experimental/shared_pathways_service/yamls/pw-service.yaml +152 -0
  18. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/jax/__init__.py +21 -3
  19. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/persistence/helper.py +0 -28
  20. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/persistence/orbax_handler.py +3 -3
  21. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/profiling.py +110 -41
  22. pathwaysutils-0.1.9/pathwaysutils/test/experimental/gke/jobset_test.py +137 -0
  23. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/experimental/reshard_test.py +15 -3
  24. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/profiling_test.py +169 -29
  25. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/reshard_test.py +10 -2
  26. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pyproject.toml +1 -1
  27. pathwaysutils-0.1.7/pathwaysutils/experimental/shared_pathways_service/yamls/pw-service-example.yaml +0 -18
  28. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/LICENSE +0 -0
  29. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/README.md +0 -0
  30. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/_initialize.py +0 -0
  31. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/collect_profile.py +0 -0
  32. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/debug/__init__.py +0 -0
  33. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/debug/timing.py +0 -0
  34. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/debug/watchdog.py +0 -0
  35. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/elastic/__init__.py +0 -0
  36. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/elastic/manager.py +0 -0
  37. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/__init__.py +0 -0
  38. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/profiling.py +0 -0
  39. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/reshard.py +0 -0
  40. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/README.md +0 -0
  41. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/shared_pathways_service/__init__.py +0 -0
  42. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/experimental/split_by_mesh_axis.py +0 -0
  43. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/lru_cache.py +0 -0
  44. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/persistence/__init__.py +0 -0
  45. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/plugin_executable.py +0 -0
  46. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/proxy_backend.py +0 -0
  47. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/reshard.py +0 -0
  48. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/__init__.py +0 -0
  49. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/debug/__init__.py +0 -0
  50. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/debug/timing_test.py +0 -0
  51. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/debug/watchdog_test.py +0 -0
  52. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/initialize_test.py +0 -0
  53. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/lru_cache_test.py +0 -0
  54. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/persistence_test.py +0 -0
  55. {pathwaysutils-0.1.7 → pathwaysutils-0.1.9}/pathwaysutils/test/plugin_executable_test.py +0 -0
  56. {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.7
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.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.7"
24
+ __version__ = "v0.1.9"
@@ -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",
@@ -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.