Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / dtensor / device_mesh.py: 75%

862 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-06 05:41 +0800

1# Copyright 2025-2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""device mesh""" 

16 

17import copy 

18import threading 

19from types import TracebackType 

20from typing import Any, List, Literal, Optional, Sequence, Type, Union 

21import numpy as np 

22 

23from hyper_parallel.core.dtensor._mesh_layout import IntTuple, _MeshLayout, _contiguous_strides, _is_int 

24from hyper_parallel.platform import get_platform 

25from hyper_parallel.platform.platform import EXISTING_COMM_GROUPS, Platform, PlatformType 

26 

27platform = get_platform() 

28Tensor = platform.Tensor 

29 

30 

31def _host_tensor_from_numpy(np_array: np.ndarray): 

32 """Build a host-resident int tensor from a NumPy array for rank/mesh bookkeeping. 

33 

34 A real platform's ``from_numpy`` keeps the tensor off the meta device, so it stays 

35 ``asnumpy``-able even when a DeviceMesh is built under ``ms.DeviceCtx("meta")`` 

36 (``fully_shard`` with ``mesh=None``). Unit tests run with a mocked ``platform`` 

37 (no real ``from_numpy``, and never under a meta context), so fall back to the plain 

38 ``Tensor`` constructor there. 

39 """ 

40 if isinstance(platform, Platform): 

41 return platform.from_numpy(np_array) 

42 return Tensor(np_array).int() 

43 

44 

45class _MeshEnv(threading.local): 

46 """Per-thread stack of active :class:`DeviceMesh` (PyTorch ``_mesh_resources`` parity).""" 

47 

48 def __init__(self) -> None: 

49 super().__init__() 

50 self.mesh_stack: List["DeviceMesh"] = [] 

51 

52 def get_current_mesh(self) -> "DeviceMesh": 

53 """Return the innermost active :class:`DeviceMesh` for this thread (PyTorch parity).""" 

54 if len(self.mesh_stack) == 0: 

55 raise RuntimeError("No device mesh is currently active!") 

56 return self.mesh_stack[-1] 

57 

58 

59_mesh_resources = _MeshEnv() 

60 

61BackendConfig = Optional[str] 

62_CP_MESH_DIM_NAMES = {"cp", "co", "ds"} 

63 

64 

65def _get_sub_rank_list(mesh_shape, mesh_dim_names, rank_list, sub_mesh_dim_names, current_rank): 

66 """ 

67 Get the sub rank list for a sub mesh. 

68 

69 Args: 

70 mesh_shape (tuple[int]): The shape of the original mesh. 

71 mesh_dim_names (tuple[str]): The mesh dim names of the original mesh dimensions. 

72 rank_list (tuple[int]): A tuple of ranks that participate in this mesh. 

73 sub_mesh_dim_names (tuple[str]): The mesh dim names of the sub mesh to extract. 

74 current_rank (int): The current process rank. 

75 

76 Returns: 

77 list: The sub rank list for the sub mesh. 

78 """ 

79 mesh_tensor = np.array(rank_list).reshape(mesh_shape) 

80 

81 for dim_index, dim_name in enumerate(mesh_dim_names): 

82 if dim_name in sub_mesh_dim_names: 

83 continue 

84 

85 dim_size = mesh_shape[dim_index] 

86 sliced_tensors = np.split(mesh_tensor, dim_size, axis=dim_index) 

87 

88 for sliced_tensor in sliced_tensors: 

89 rank_exists = np.isin(np.array([current_rank]), sliced_tensor).any() 

90 if rank_exists: 

91 mesh_tensor = sliced_tensor 

92 break 

93 

94 sub_rank_list = mesh_tensor.reshape(-1).tolist() 

95 return sub_rank_list 

96 

97 

98def _normalize_backend_value(value: Any) -> BackendConfig: 

99 if value is None: 

100 return None 

101 if isinstance(value, str): 

102 return value 

103 if isinstance(value, tuple) and len(value) > 0: 

104 backend = value[0] 

105 if backend is None or isinstance(backend, str): 

106 return backend 

107 return None 

108 

109 

110def _get_cp_pg_options(mesh_dim_names: Optional[tuple[str, ...]], dim: int) -> Optional[dict[str, Any]]: 

111 if ( 

112 platform.platform_type == PlatformType.MINDSPORE 

113 and mesh_dim_names 

114 and mesh_dim_names[dim] in _CP_MESH_DIM_NAMES 

115 ): 

116 return {"hccl_config": {"hccl_op_expansion_mode": "AIV"}} 

117 return None 

118 

119 

120def _normalize_backend_override( 

121 backend_override: dict[Union[int, str], Any], 

122 ndim: int, 

123 mesh_dim_names: Optional[tuple[str, ...]] = None, 

124) -> tuple[BackendConfig, ...]: 

125 """Normalize backend overrides by dim index/name.""" 

126 remaining = dict(backend_override) 

127 normalized: list[BackendConfig] = [] 

128 mesh_dim_names = mesh_dim_names or () 

129 

130 for dim_idx in range(ndim): 

131 dim_name = mesh_dim_names[dim_idx] if dim_idx < len(mesh_dim_names) else None 

132 if dim_name is not None and dim_name in remaining: 

133 if dim_idx in remaining: 

134 raise RuntimeError( 

135 f"Found redundant dim index {dim_idx} and name {dim_name} in backend_override" 

136 ) 

137 normalized.append(_normalize_backend_value(remaining.pop(dim_name))) 

138 elif dim_idx in remaining: 

139 normalized.append(_normalize_backend_value(remaining.pop(dim_idx))) 

140 else: 

141 normalized.append(None) 

142 

143 if remaining: 

144 raise RuntimeError( 

145 f"Found invalid keys in backend_override: got {list(remaining.keys())}, " 

146 f"expected integers in range [0, {ndim}) or one of {mesh_dim_names}" 

147 ) 

148 return tuple(normalized) 

149 

150 

151def _should_defer_group_init(sub_layout: _MeshLayout, backend_override: BackendConfig) -> bool: 

152 """Whether this mesh dimension should skip eager process-group creation.""" 

153 return backend_override == "fake" or sub_layout.numel() == 1 

154 

155 

156class DeviceMesh: 

157 """ 

158 Topological abstraction describing cluster devices. 

159 

160 Args: 

161 device_type (str): Device type. Valid values depend on the active platform: 

162 

163 - **PyTorch** (same as ``torch.distributed.device_mesh.DeviceMesh``): 

164 ``"cpu"``, ``"cuda"``, ``"npu"``. 

165 - **MindSpore** (mapped to the corresponding communication backend): 

166 ``"cpu"`` → mccl, ``"gpu"`` → nccl, ``"npu"`` → hccl. 

167 mesh (Union[Tensor, list, tuple, np.ndarray, None]): A multi-dimensional array, list, or integer 

168 tensor describing the device layout. The IDs in the mesh are global IDs of the 

169 default process group, representing the multi-dimensional networking structure 

170 of devices in distributed training (e.g., [[0,1],[2,3]] represents a 2x2 device mesh). 

171 If a list or non-int32 tensor is provided, it will be automatically converted 

172 to an int32 tensor. If None, a 1D mesh containing all ranks 

173 (i.e., ``[0, 1, ..., world_size-1]``) will be created automatically. 

174 mesh_dim_names (tuple[str]): A tuple[str] of mesh dim names for each dimension of mesh. 

175 _init_backend (boolean): Whether initial process group. 

176 

177 Attributes: 

178 ndim (int): Number of dimensions in the mesh. 

179 mesh_shape (tuple[int]): Shape of the device mesh. 

180 rank_list (tuple[int]): Flattened list of ranks from the mesh. 

181 root_mesh (DeviceMesh): The parent mesh if this is a sub mesh, None otherwise. 

182 sub_mesh (list[DeviceMesh]): List of child meshes created from this mesh. 

183 

184 Context manager: 

185 Use ``with device_mesh:`` to set the **current** mesh for this thread. 

186 """ 

187 

188 device_type: Literal["cpu", "cuda", "gpu", "npu"] 

189 mesh: Union[Tensor, list, tuple, np.ndarray] 

190 mesh_dim_names: Union[tuple[str, ...], list[str], None] 

191 

192 _VALID_DEVICE_TYPES = { 

193 PlatformType.PYTORCH: {"cpu", "cuda", "npu"}, 

194 PlatformType.MINDSPORE: {"cpu", "gpu", "npu"}, 

195 } 

196 

197 def __init__(self, 

198 device_type: Literal["cpu", "cuda", "gpu", "npu"], 

199 mesh: Union[Tensor, list, tuple, np.ndarray, None] = None, 

200 *, 

201 mesh_dim_names: Union[tuple[str, ...], list[str], None] = None, 

202 _init_backend: bool = True, 

203 _layout: Optional[_MeshLayout] = None, 

204 _rank_map: Optional[Tensor] = None, 

205 _root_mesh: Optional['DeviceMesh'] = None, 

206 ): 

207 self._validate_device_type(device_type) 

208 self.device_type = device_type 

209 

210 if _init_backend: 

211 platform.init_process_group() 

212 

213 self._layout, self._rank_map = self._resolve_layout_and_rank_map(mesh, _layout, _rank_map) 

214 self._rank = platform.get_rank() 

215 self._root_mesh = _root_mesh 

216 self._refresh_mesh_view() 

217 self._set_mesh_dim_names(mesh_dim_names) 

218 self._initialize_runtime_state(_init_backend) 

219 self._coordinate_on_dim = self._compute_coordinate_on_dim() 

220 

221 @classmethod 

222 def _validate_device_type(cls, device_type: str) -> None: 

223 """Validate that the requested device type is supported on the active platform.""" 

224 valid_device_types = cls._VALID_DEVICE_TYPES.get(platform.platform_type) 

225 if valid_device_types is not None and device_type not in valid_device_types: 

226 raise ValueError( 

227 f"Invalid device_type '{device_type}' for {platform.platform_type.name} platform. " 

228 f"Valid device types are: {sorted(valid_device_types)}" 

229 ) 

230 

231 @classmethod 

232 def _resolve_layout_and_rank_map( 

233 cls, 

234 mesh: Union[Tensor, list, tuple, np.ndarray, None], 

235 layout: Optional[_MeshLayout], 

236 rank_map: Optional[Tensor], 

237 ) -> tuple[_MeshLayout, Tensor]: 

238 """Build the internal layout and rank map from either public or private constructor inputs.""" 

239 if mesh is not None and (layout is not None or rank_map is not None): 

240 raise TypeError("Cannot provide both explicit mesh and private _layout/_rank_map arguments.") 

241 

242 if mesh is None and (layout is None or rank_map is None): 

243 world_size = platform.get_world_size() 

244 mesh = list(range(world_size)) 

245 

246 if mesh is not None: 

247 mesh_tensor = cls._convert_mesh_to_tensor(mesh) 

248 if mesh_tensor.ndim == 0: 

249 raise ValueError("mesh must be at least 1-dimensional") 

250 return cls._build_layout_from_mesh(mesh_tensor), cls._build_rank_map_from_mesh(mesh_tensor) 

251 

252 rank_map_tensor = cls._convert_rank_map_to_tensor(rank_map) 

253 if layout is None or rank_map_tensor is None: 

254 raise TypeError("The mesh argument is required except for private _layout/_rank_map construction.") 

255 if not layout.check_non_overlap(): 

256 raise ValueError(f"Invalid overlapping layout {layout}.") 

257 return layout, rank_map_tensor 

258 

259 def _refresh_mesh_view(self) -> None: 

260 """Materialize the visible mesh tensor and the derived shape/rank metadata.""" 

261 # Compute everything in numpy first so the intermediate ops don't need 

262 # a real device. Otherwise the call would fail (or SIGSEGV on Ascend) 

263 # when DeviceMesh is constructed inside a ``ms.DeviceCtx("meta")`` 

264 # block — e.g., from ``DeviceMesh.concatenate`` invoked under 

265 # ``fully_shard``, which forces fresh ``Tensor()`` constructions onto 

266 # the meta device and any subsequent op (asnumpy, nonzero, …) crashes. 

267 rank_map_np = platform.tensor_to_numpy(self._rank_map).reshape(-1) 

268 full_mesh_np = self._layout.remap_to_numpy(rank_map_np) 

269 if full_mesh_np.shape[0] == 1: 

270 per_rank_mesh_np = full_mesh_np[0] 

271 else: 

272 coords = np.argwhere(full_mesh_np == self._rank) 

273 if coords.shape[0] == 0: 

274 raise RuntimeError( 

275 "In order to get the mesh tensor of a DeviceMesh it needs to " 

276 "either have all its original dimensions or contain the local rank." 

277 ) 

278 per_rank_mesh_np = full_mesh_np[coords[0, 0]] 

279 # Cache the numpy view so ``_compute_coordinate_on_dim`` doesn't need 

280 # to operate on ``self.mesh`` (which may be on the meta device). 

281 self._per_rank_mesh_np = per_rank_mesh_np 

282 self.mesh = Tensor(per_rank_mesh_np.astype(np.int32)).int() 

283 self._mesh_shape = tuple(per_rank_mesh_np.shape) 

284 self._rank_list = tuple(per_rank_mesh_np.reshape(-1).tolist()) 

285 self._flatten_rank_map = tuple(rank_map_np.tolist()) 

286 self._dev_num = np.prod(np.array(self._mesh_shape)) 

287 self._dev_rank = len(self._mesh_shape) 

288 

289 def _set_mesh_dim_names( 

290 self, 

291 mesh_dim_names: Union[tuple[str, ...], list[str], None], 

292 ) -> None: 

293 """Validate mesh dim names and build lookup tables for named access.""" 

294 self.mesh_dim_names = tuple(mesh_dim_names) if mesh_dim_names else None 

295 if self.mesh_dim_names is None: 

296 return 

297 

298 if len(self._mesh_shape) != len(self.mesh_dim_names): 

299 raise ValueError( 

300 f'mesh dimensions ({len(self._mesh_shape)}) should be equal to ' 

301 f'mesh_dim_names length ({len(self.mesh_dim_names)})' 

302 ) 

303 if len(set(self.mesh_dim_names)) != len(self.mesh_dim_names): 

304 raise ValueError(f'Each element of mesh_dim_names {self.mesh_dim_names} should be different') 

305 inter_key = "interleaved_parallel" 

306 if inter_key in self.mesh_dim_names and self.mesh_dim_names.index(inter_key) != len(self.mesh_dim_names) - 1: 

307 raise ValueError( 

308 "'interleaved_parallel' should be at the last dim of mesh_dim_names, means virtual sharding." 

309 ) 

310 self._dev_name_to_dev_id = { 

311 name: self._dev_rank - i - 1 for i, name in enumerate(self.mesh_dim_names) 

312 } 

313 self._dev_name_to_index = {name: i for i, name in enumerate(self.mesh_dim_names)} 

314 

315 def _initialize_runtime_state(self, init_backend: bool) -> None: 

316 """Initialize caches and optional process-group state for the mesh view.""" 

317 self._cache_rank_list_along_axis = {} 

318 self._global_shape_map = {} 

319 self._sub_mesh_cache = {} 

320 self._flatten_mapping: dict[str, 'DeviceMesh'] = {} 

321 self._ndim = len(self._mesh_shape) 

322 self._dim_group_backends = (None,) * self._ndim 

323 self._dim_group_sources = tuple((self, dim) for dim in range(self._ndim)) 

324 self._sub_mesh: List['DeviceMesh'] = [] 

325 if not init_backend: 

326 return 

327 self._dim_group_names = self._init_process_groups( 

328 self._mesh_shape, 

329 self.mesh_dim_names, 

330 self._rank_list, 

331 ) 

332 

333 @staticmethod 

334 def _build_layout_from_mesh(mesh: Tensor) -> _MeshLayout: 

335 mesh_shape = tuple(mesh.shape) 

336 return _MeshLayout(mesh_shape, _contiguous_strides(mesh_shape)) 

337 

338 @staticmethod 

339 def _build_rank_map_from_mesh(mesh: Tensor) -> Tensor: 

340 return _host_tensor_from_numpy(platform.tensor_to_numpy(mesh).reshape(-1).astype(np.int32)) 

341 

342 @staticmethod 

343 def _convert_rank_map_to_tensor(rank_map: Tensor) -> Tensor: 

344 """Normalize a rank-map input into the flat int32 Tensor stored on the mesh. 

345 

346 Tensor input is returned as-is to preserve its original device; list / 

347 tuple / numpy input is built into a fresh flat int32 Tensor. 

348 """ 

349 if isinstance(rank_map, Tensor): 

350 # Reuse the existing tensor as-is so we preserve its real device. 

351 # Going through ``Tensor(np_array)`` would re-create on whatever 

352 # device context is active (e.g. ``ms.DeviceCtx("meta")`` while 

353 # ``DeviceMesh.concatenate`` runs under ``fully_shard``), which then 

354 # breaks the immediate ``asnumpy()`` in ``_refresh_mesh_view``. 

355 # All in-tree callers that pass a Tensor pass an existing 

356 # ``DeviceMesh._rank_map`` — already a flat int32 tensor, so no 

357 # reshape/cast is needed. 

358 return rank_map 

359 rank_map_np = np.array(rank_map) 

360 return _host_tensor_from_numpy(rank_map_np.reshape(-1).astype(np.int32)) 

361 

362 @staticmethod 

363 def _get_mesh_tensor_from_full_mesh(full_mesh: Tensor, current_rank: Optional[int] = None) -> Tensor: 

364 """Select the per-rank mesh view from a fully materialized layout remap.""" 

365 if full_mesh.shape[0] == 1: 

366 return full_mesh[0] 

367 

368 if current_rank is None: 

369 current_rank = platform.get_rank() 

370 

371 rank_coords = (full_mesh == current_rank).nonzero() 

372 if rank_coords.shape[0] > 0: 

373 return full_mesh[rank_coords[0, 0]] 

374 raise RuntimeError( 

375 "In order to get the mesh tensor of a DeviceMesh it needs to " 

376 "either have all its original dimensions or contain the local rank." 

377 ) 

378 

379 def _compute_coordinate_on_dim(self): 

380 """Compute the current rank coordinates inside this mesh view.""" 

381 # Use the cached numpy view rather than ``self.mesh`` so this works 

382 # even when the mesh tensor lives on the meta device (DeviceMesh 

383 # constructed under ``ms.DeviceCtx("meta")`` via ``fully_shard``). 

384 per_rank_mesh_np = getattr(self, "_per_rank_mesh_np", None) 

385 if per_rank_mesh_np is not None: 

386 rank_coords = np.argwhere(per_rank_mesh_np == self._rank) 

387 if rank_coords.shape[0] not in (0, 1): 

388 raise AssertionError( 

389 f"rank_coords.shape[0] must be 0 or 1, got {rank_coords.shape[0]}" 

390 ) 

391 if rank_coords.shape[0] == 0: 

392 return None 

393 return tuple(int(x) for x in rank_coords[0]) 

394 return self._compute_coordinates_from_mesh(self.mesh, self._rank) 

395 

396 @staticmethod 

397 def _compute_coordinates_from_mesh( 

398 mesh_tensor: Tensor, 

399 rank: int, 

400 ): 

401 """Locate one rank inside a mesh tensor and return its coordinates.""" 

402 rank_coords = (mesh_tensor == rank).nonzero() 

403 if rank_coords.shape[0] not in (0, 1): 

404 raise AssertionError( 

405 f"rank_coords.shape[0] must be 0 or 1, got {rank_coords.shape[0]}" 

406 ) 

407 

408 if rank_coords.shape[0] == 0: 

409 return None 

410 

411 coords = rank_coords[0].tolist() 

412 return tuple(coords) 

413 

414 def size(self, mesh_dim=None) -> int: 

415 """Return the size along a specific mesh dimension, or total number of devices if mesh_dim is None.""" 

416 if mesh_dim is not None: 

417 return self.mesh.shape[mesh_dim] 

418 return self.mesh.numel() 

419 

420 def get_coordinate(self): 

421 """Return the multi-dimensional coordinate of the current rank in the mesh, or None.""" 

422 return self._coordinate_on_dim if self._coordinate_on_dim else None 

423 

424 def __enter__(self) -> "DeviceMesh": 

425 _mesh_resources.mesh_stack.append(self) 

426 return self 

427 

428 def __exit__( 

429 self, 

430 exc_type: Optional[Type[BaseException]], 

431 exc_val: Optional[BaseException], 

432 exc_tb: Optional[TracebackType], 

433 ) -> None: 

434 del self 

435 _mesh_resources.mesh_stack.pop() 

436 

437 @staticmethod 

438 def _convert_mesh_to_tensor(mesh: Union[Tensor, list, tuple, np.ndarray]) -> Tensor: 

439 """Convert a public mesh input into an int32 platform tensor.""" 

440 if isinstance(mesh, Tensor): 

441 mesh = platform.tensor_to_numpy(mesh) 

442 elif isinstance(mesh, (list, tuple)): 

443 mesh = np.array(mesh) 

444 elif not isinstance(mesh, np.ndarray): 

445 raise TypeError( 

446 f"mesh must be Tensor, list, tuple or numpy array, but got {type(mesh)}" 

447 ) 

448 

449 mesh = mesh.astype(np.int32) 

450 return _host_tensor_from_numpy(mesh) 

451 

452 @staticmethod 

453 def _init_one_process_group(mesh_shape: tuple[int, ...], mesh_dim_names: tuple[str, ...], 

454 dim_name: str, rank_list: tuple[int, ...]) -> str: 

455 """Create one process-group family for the named mesh dimension.""" 

456 group_key = None 

457 split_ranks = set() 

458 if not isinstance(dim_name, tuple): 

459 dim_name = (dim_name,) 

460 for rank in rank_list: 

461 split_rank = _get_sub_rank_list(mesh_shape, mesh_dim_names, rank_list, dim_name, rank) 

462 sorted_rank = tuple(sorted(split_rank)) 

463 split_ranks.add(sorted_rank) 

464 if rank == platform.get_rank(): 

465 group_key = str(sorted_rank) 

466 split_ranks = sorted([list(item) for item in split_ranks]) 

467 platform.split_group(split_ranks=split_ranks) 

468 return group_key 

469 

470 @staticmethod 

471 def _build_dim_split_ranks( 

472 sub_layout: _MeshLayout, 

473 rank_map: Tensor, 

474 ) -> tuple[list[list[int]], Optional[str]]: 

475 """Build rank lists and the local cache key for one logical mesh axis.""" 

476 pg_ranks_by_dim = sub_layout.remap_to_numpy(platform.tensor_to_numpy(rank_map)) 

477 current_rank = platform.get_rank() 

478 split_ranks = [] 

479 split_ranks_set = set() 

480 group_key = None 

481 for dim_mesh in np.array(pg_ranks_by_dim): 

482 subgroup_ranks = tuple(int(rank) for rank in np.array(dim_mesh).reshape(-1).tolist()) 

483 subgroup_ranks_sorted = tuple(sorted(subgroup_ranks)) 

484 if subgroup_ranks_sorted not in split_ranks_set: 

485 split_ranks_set.add(subgroup_ranks_sorted) 

486 split_ranks.append(list(subgroup_ranks_sorted)) 

487 if current_rank in subgroup_ranks: 

488 if group_key is not None: 

489 raise RuntimeError( 

490 "Each device mesh dimension should get only one process group per rank." 

491 ) 

492 group_key = str(subgroup_ranks_sorted) 

493 split_ranks = sorted(split_ranks) 

494 return split_ranks, group_key 

495 

496 @staticmethod 

497 def _cache_group_if_needed(group_key: Optional[str], group: Any) -> None: 

498 if group_key is not None and group is not None and group_key not in EXISTING_COMM_GROUPS: 

499 EXISTING_COMM_GROUPS[group_key] = group 

500 

501 @staticmethod 

502 def _init_process_groups_for_layout( 

503 layout: _MeshLayout, 

504 rank_map: Tensor, 

505 mesh_dim_names: Union[tuple[str, ...], None], 

506 backend_override: Optional[tuple[BackendConfig, ...]] = None, 

507 ) -> list: 

508 """Initialize process groups for each top-level axis in the given layout.""" 

509 if mesh_dim_names is None: 

510 mesh_dim_names = tuple(f"dim_{dim}" for dim in range(len(layout))) 

511 if backend_override is None: 

512 backend_override = (None,) * len(layout) 

513 if len(backend_override) != len(layout): 

514 raise ValueError( 

515 f"backend_override length {len(backend_override)} must match layout rank {len(layout)}" 

516 ) 

517 

518 dim_group_names = [] 

519 for dim, sub_layout in enumerate(layout): 

520 split_ranks, group_key = DeviceMesh._build_dim_split_ranks(sub_layout, rank_map) 

521 if _should_defer_group_init(sub_layout, backend_override[dim]): 

522 dim_group_names.append(None) 

523 continue 

524 group = platform.split_group(split_ranks=split_ranks, pg_options=_get_cp_pg_options(mesh_dim_names, dim)) 

525 DeviceMesh._cache_group_if_needed(group_key, group) 

526 dim_group_names.append(group_key) 

527 return dim_group_names 

528 

529 @staticmethod 

530 def _init_process_groups(mesh_shape: tuple[int, ...], mesh_dim_names: Union[tuple[str, ...], None], 

531 rank_list: tuple[int, ...], 

532 backend_override: Optional[tuple[BackendConfig, ...]] = None) -> list: 

533 layout = _MeshLayout(mesh_shape, _contiguous_strides(mesh_shape)) 

534 rank_map = DeviceMesh._convert_rank_map_to_tensor(rank_list) 

535 return DeviceMesh._init_process_groups_for_layout( 

536 layout, 

537 rank_map, 

538 mesh_dim_names, 

539 backend_override=backend_override, 

540 ) 

541 

542 @property 

543 def rank(self): 

544 """Return the global rank of the current process within this device mesh.""" 

545 return self._rank 

546 

547 @property 

548 def mesh_shape(self): 

549 """Return the shape of the device mesh as a tuple of integers.""" 

550 return self._mesh_shape 

551 

552 @property 

553 def rank_list(self): 

554 """Return the tuple of ranks participating in this device mesh.""" 

555 return self._rank_list 

556 

557 @property 

558 def ndim(self) -> int: 

559 """Return the number of dimensions in the device mesh.""" 

560 return self._ndim 

561 

562 @property 

563 def shape(self) -> tuple: 

564 """Return the shape of the device mesh as a tuple.""" 

565 return self._mesh_shape 

566 

567 @property 

568 def root_mesh(self) -> Optional['DeviceMesh']: 

569 """Return the root DeviceMesh from which this mesh was sliced, or None.""" 

570 return self._root_mesh 

571 

572 @root_mesh.setter 

573 def root_mesh(self, value: Optional['DeviceMesh']): 

574 """Set the root DeviceMesh from which this mesh was sliced.""" 

575 self._root_mesh = value 

576 

577 @property 

578 def sub_mesh(self) -> List['DeviceMesh']: 

579 """Return the list of sub-meshes derived from this device mesh.""" 

580 return self._sub_mesh 

581 

582 def get_flatten_mapping(self) -> dict: 

583 """Return the mapping of sub-mesh names to their flattened DeviceMesh instances.""" 

584 return self._flatten_mapping 

585 

586 def add_flatten_mapping(self, name: str, mesh: 'DeviceMesh') -> None: 

587 """Register a named DeviceMesh in the flatten mapping cache.""" 

588 self._flatten_mapping[name] = mesh 

589 

590 def __getitem__(self, sub_mesh_dim_names: Union[str, tuple[str, ...]]) -> 'DeviceMesh': 

591 if not self.mesh_dim_names: 

592 raise RuntimeError("Cannot slice a DeviceMesh without mesh_dim_names!") 

593 

594 sub_mesh_dim_names = DeviceMesh._normalize_sub_mesh_dim_names(sub_mesh_dim_names) 

595 flatten_mapping = self._get_root_mesh().get_flatten_mapping() 

596 

597 flattened_result = self._try_get_from_flatten_mapping(sub_mesh_dim_names, flatten_mapping) 

598 if flattened_result is not None: 

599 return flattened_result 

600 

601 layout = self._get_slice_mesh_layout(sub_mesh_dim_names) 

602 if sub_mesh_dim_names in self._sub_mesh_cache: 

603 return self._sub_mesh_cache[sub_mesh_dim_names] 

604 if layout == self._layout: 

605 return self 

606 return self._create_and_cache_sub_mesh(sub_mesh_dim_names, layout) 

607 

608 @staticmethod 

609 def _normalize_sub_mesh_dim_names(sub_mesh_dim_names: Union[str, tuple[str, ...]]) -> tuple[str, ...]: 

610 """Normalize a slice selector into a non-empty tuple of mesh dim names.""" 

611 if isinstance(sub_mesh_dim_names, str): 

612 sub_mesh_dim_names = (sub_mesh_dim_names,) 

613 

614 if not isinstance(sub_mesh_dim_names, tuple): 

615 raise TypeError( 

616 f"sub_mesh_dim_names must be str or tuple, but got {type(sub_mesh_dim_names)}" 

617 ) 

618 

619 if len(sub_mesh_dim_names) == 0: 

620 raise ValueError("sub_mesh_dim_names cannot be empty") 

621 

622 return sub_mesh_dim_names 

623 

624 @staticmethod 

625 def _try_get_from_flatten_mapping(sub_mesh_dim_names: tuple[str, ...], 

626 flatten_mapping: dict) -> Optional['DeviceMesh']: 

627 if len(sub_mesh_dim_names) == 1 and sub_mesh_dim_names[0] in flatten_mapping: 

628 return flatten_mapping[sub_mesh_dim_names[0]] 

629 return None 

630 

631 def _get_mesh_dim_by_name(self, mesh_dim_name: str) -> int: 

632 """Resolve a named mesh axis to its integer position.""" 

633 mesh_dim_names = self.mesh_dim_names or () 

634 if len(mesh_dim_names) == 0: 

635 raise KeyError("No mesh_dim_names found.") 

636 if mesh_dim_name not in mesh_dim_names: 

637 raise KeyError( 

638 f"Mesh dimension '{mesh_dim_name}' does not exist. " 

639 f"Available mesh dimensions are: {mesh_dim_names}" 

640 ) 

641 return mesh_dim_names.index(mesh_dim_name) 

642 

643 def _get_slice_mesh_layout(self, sub_mesh_dim_names: tuple[str, ...]) -> _MeshLayout: 

644 """Construct the layout corresponding to one named sub-mesh slice request.""" 

645 root_mesh = self._get_root_mesh() 

646 slice_from_root = self == root_mesh 

647 flatten_name_to_layout = ( 

648 {key: mesh._layout for key, mesh in root_mesh.get_flatten_mapping().items()} 

649 if slice_from_root else {} 

650 ) 

651 valid_dim_names = [*(self.mesh_dim_names or ()), *flatten_name_to_layout] 

652 if not all(name in valid_dim_names for name in sub_mesh_dim_names): 

653 raise KeyError( 

654 f"Invalid mesh_dim_names {sub_mesh_dim_names} specified. " 

655 f"Valid mesh_dim_names are {valid_dim_names}." 

656 ) 

657 

658 if all(name in (self.mesh_dim_names or ()) for name in sub_mesh_dim_names): 

659 indices = [self.mesh_dim_names.index(name) for name in sub_mesh_dim_names] 

660 if indices != sorted(indices): 

661 raise ValueError( 

662 f"sub_mesh_dim_names {sub_mesh_dim_names} must follow the order of " 

663 f"original mesh_dim_names {self.mesh_dim_names}" 

664 ) 

665 

666 sliced_sizes: list[IntTuple] = [] 

667 sliced_strides: list[IntTuple] = [] 

668 for name in sub_mesh_dim_names: 

669 if name in (self.mesh_dim_names or ()): 

670 layout = self._layout[self.mesh_dim_names.index(name)] 

671 else: 

672 layout = flatten_name_to_layout[name] 

673 sliced_sizes.append(layout.sizes) 

674 sliced_strides.append(layout.strides) 

675 

676 pre_stride = -1 

677 for stride in reversed(sliced_strides): 

678 if not _is_int(stride): 

679 raise NotImplementedError( 

680 "Currently, this only allows slicing out a contiguous flattened dim." 

681 ) 

682 if stride < pre_stride: 

683 raise ValueError( 

684 f"Invalid mesh_dim_names {sub_mesh_dim_names} specified. " 

685 "Mesh dim indices should be in ascending order." 

686 ) 

687 pre_stride = stride 

688 

689 if len(sliced_sizes) == 1: 

690 layout = _MeshLayout(sliced_sizes[0], sliced_strides[0]) 

691 else: 

692 layout = _MeshLayout(tuple(sliced_sizes), tuple(sliced_strides)) 

693 if not layout.check_non_overlap(): 

694 raise RuntimeError(f"Slicing overlapping dim_names {sub_mesh_dim_names} is not allowed.") 

695 return layout 

696 

697 def _create_and_cache_sub_mesh(self, sub_mesh_dim_names: tuple[str, ...], layout: _MeshLayout) -> 'DeviceMesh': 

698 """Create a sub-mesh view, copy group metadata, and cache the result.""" 

699 root_mesh = self._get_root_mesh() 

700 sub_mesh = DeviceMesh( 

701 device_type=self.device_type, 

702 mesh_dim_names=sub_mesh_dim_names, 

703 _init_backend=False, 

704 _layout=layout, 

705 _rank_map=root_mesh._rank_map, 

706 _root_mesh=root_mesh, 

707 ) 

708 

709 slice_dim_group_name = [] 

710 slice_dim_group_backends: list[BackendConfig] = [] 

711 slice_dim_group_sources: list[tuple['DeviceMesh', int]] = [] 

712 for name in sub_mesh_dim_names: 

713 if name in (self.mesh_dim_names or ()): 

714 dim_index = self.mesh_dim_names.index(name) 

715 if hasattr(self, "_dim_group_names"): 

716 slice_dim_group_name.append(self._dim_group_names[dim_index]) 

717 slice_dim_group_backends.append(self._dim_group_backends[dim_index]) 

718 if hasattr(self, "_dim_group_sources"): 

719 slice_dim_group_sources.append(self._dim_group_sources[dim_index]) # pylint: disable=W0212 

720 else: 

721 slice_dim_group_sources.append((self, dim_index)) 

722 elif name in root_mesh.get_flatten_mapping(): 

723 flatten_mesh = root_mesh.get_flatten_mapping()[name] 

724 if hasattr(flatten_mesh, "_dim_group_names"): 

725 slice_dim_group_name.append(flatten_mesh._dim_group_names[0]) 

726 slice_dim_group_backends.append(flatten_mesh._dim_group_backends[0]) 

727 if hasattr(flatten_mesh, "_dim_group_sources"): 

728 slice_dim_group_sources.append(flatten_mesh._dim_group_sources[0]) # pylint: disable=W0212 

729 else: 

730 slice_dim_group_sources.append((flatten_mesh, 0)) 

731 if slice_dim_group_name: 

732 sub_mesh._dim_group_names = slice_dim_group_name # pylint: disable=W0212 

733 if slice_dim_group_backends: 

734 sub_mesh._dim_group_backends = tuple(slice_dim_group_backends) # pylint: disable=W0212 

735 if slice_dim_group_sources: 

736 sub_mesh._dim_group_sources = tuple(slice_dim_group_sources) # pylint: disable=W0212 

737 

738 self._sub_mesh_cache[sub_mesh_dim_names] = sub_mesh 

739 self.sub_mesh.append(sub_mesh) 

740 _register_device_mesh(sub_mesh) 

741 return sub_mesh 

742 

743 def get_group(self, mesh_dim: Optional[Union[int, str]] = None): 

744 """Return the communication group for one mesh axis.""" 

745 if not hasattr(self, "_dim_group_names"): 

746 raise RuntimeError("DeviceMesh process groups not initialized!") 

747 

748 if self.ndim > 1 and mesh_dim is None: 

749 raise RuntimeError( 

750 f"Found the DeviceMesh have {self.ndim} dimensions. " 

751 "Optional kwarg `mesh_dim` needs to be specified when device_mesh.ndim > 1." 

752 ) 

753 

754 root_mesh = self._get_root_mesh() 

755 if isinstance(mesh_dim, str) and mesh_dim in root_mesh.get_flatten_mapping(): 

756 flattened_mesh = root_mesh.get_flatten_mapping()[mesh_dim] 

757 return flattened_mesh.get_comm_group_by_axis(mesh_dim) 

758 

759 return self.get_comm_group_by_axis(mesh_dim) 

760 

761 def get_all_groups(self) -> list: 

762 """Return the communication groups for all mesh dimensions.""" 

763 if not hasattr(self, "_dim_group_names"): 

764 raise RuntimeError("DeviceMesh process groups not initialized!") 

765 

766 return [self.get_group(i) for i in range(self.ndim)] 

767 

768 @staticmethod 

769 def from_group(group: Union[Any, list[Any]], 

770 device_type: str, 

771 mesh: Union[Tensor, list, tuple, np.ndarray] = None, 

772 mesh_dim_names: Union[tuple[str, ...], list[str]] = None 

773 ) -> 'DeviceMesh': 

774 """Build a DeviceMesh from an existing process group or a list of groups.""" 

775 if not isinstance(group, list): 

776 group_ranks = platform.get_process_group_ranks(group) 

777 group_key = str(tuple(sorted(group_ranks))) 

778 if not platform.get_created_group(group_ranks): 

779 EXISTING_COMM_GROUPS[group_key] = group 

780 tensor_type_mesh_invalid = isinstance(mesh, Tensor) and mesh.tolist() != group_ranks 

781 not_tensor_type_mesh_invalid = mesh is not None and not isinstance(mesh, Tensor) and mesh != group_ranks 

782 if tensor_type_mesh_invalid or not_tensor_type_mesh_invalid: 

783 raise ValueError( 

784 f"Invalid mesh_shape {str(mesh)} for 1D group with ranks {group_ranks}" 

785 ) 

786 device_mesh = DeviceMesh(device_type, group_ranks, mesh_dim_names=mesh_dim_names, _init_backend=False) 

787 device_mesh._dim_group_names = [group_key] # pylint: disable=W0212 

788 return device_mesh 

789 

790 groups = list(group) 

791 if len(groups) == 0: 

792 raise ValueError("Expect at least one group be specified.") 

793 if mesh is None: 

794 raise ValueError("mesh_shape is must specified when group is a list.") 

795 mesh = DeviceMesh._convert_mesh_to_tensor(mesh) 

796 if mesh.ndim != len(groups): 

797 raise ValueError("mesh dimensions must match group dimensions.") 

798 device_mesh = DeviceMesh(device_type, mesh, mesh_dim_names=mesh_dim_names, _init_backend=False) 

799 device_mesh._dim_group_names = [] # pylint: disable=W0212 

800 for dim_group in groups: 

801 group_ranks = platform.get_process_group_ranks(dim_group) 

802 group_key = str(tuple(sorted(group_ranks))) 

803 if not platform.get_created_group(group_ranks): 

804 EXISTING_COMM_GROUPS[group_key] = dim_group 

805 device_mesh._dim_group_names.append(group_key) # pylint: disable=W0212 

806 return device_mesh 

807 

808 def get_local_rank(self, mesh_dim: Optional[Union[int, str]] = None) -> int: 

809 """Return the local coordinate of the current rank along one mesh dimension.""" 

810 if self.ndim > 1 and mesh_dim is None: 

811 raise RuntimeError( 

812 f"Found the DeviceMesh have {self.ndim} dimensions. " 

813 "Optional kwarg `mesh_dim` needs to be specified when device_mesh.ndim > 1." 

814 ) 

815 

816 if mesh_dim is None: 

817 mesh_dim = 0 

818 

819 if isinstance(mesh_dim, str): 

820 if mesh_dim not in self.mesh_dim_names: # pylint: disable=E1135 

821 raise ValueError( 

822 f"mesh_dim '{mesh_dim}' not found in mesh_dim_names {self.mesh_dim_names}" 

823 ) 

824 dim_index = self.mesh_dim_names.index(mesh_dim) 

825 else: 

826 if not isinstance(mesh_dim, int) or mesh_dim < 0 or mesh_dim >= self.ndim: 

827 raise ValueError( 

828 f"mesh_dim must be an integer in range [0, {self.ndim}), " 

829 f"but got {mesh_dim}" 

830 ) 

831 dim_index = mesh_dim 

832 

833 if self._rank not in self._rank_list: 

834 raise ValueError( 

835 f"Current rank {self._rank} not found in rank_list {self._rank_list}" 

836 ) 

837 

838 idx = self._rank_list.index(self._rank) 

839 coord = [0] * len(self._mesh_shape) 

840 temp = idx 

841 for i in range(len(self._mesh_shape) - 1, -1, -1): 

842 coord[i] = temp % self._mesh_shape[i] 

843 temp //= self._mesh_shape[i] 

844 

845 return coord[dim_index] 

846 

847 def flatten(self, mesh_dim_name: Optional[str] = None) -> 'DeviceMesh': 

848 """Flatten the device mesh into a 1-D mesh and return the new DeviceMesh.""" 

849 return self._create_flatten_mesh(mesh_dim_name) 

850 

851 def _get_root_mesh(self) -> 'DeviceMesh': 

852 """Return the canonical root mesh for this view.""" 

853 if self._root_mesh is None: 

854 return self 

855 return self._root_mesh._get_root_mesh() # pylint: disable=protected-access 

856 

857 @staticmethod 

858 def _validate_concatenate_inputs( 

859 meshes: Sequence['DeviceMesh'], 

860 ) -> tuple['DeviceMesh', tuple['DeviceMesh', ...], tuple[str, ...], tuple[int, ...]]: 

861 """Validate concatenate inputs and return root metadata plus canonical mesh views.""" 

862 if len(meshes) == 0: 

863 raise ValueError("DeviceMesh.concatenate expects at least one mesh.") 

864 if len(meshes) == 1: 

865 return ( 

866 meshes[0]._get_root_mesh(), 

867 tuple(meshes), 

868 tuple(meshes[0].mesh_dim_names or ()), 

869 meshes[0]._flatten_rank_map, 

870 ) 

871 

872 # Torch treats the flattened rank map as the common root tensor identity. 

873 # If a peer view lost root metadata, recover canonical views from any input that still has it. 

874 root_mesh = next( 

875 (mesh._get_root_mesh() for mesh in meshes if mesh.root_mesh is not None), # pylint: disable=protected-access 

876 meshes[0]._get_root_mesh(), # pylint: disable=protected-access 

877 ) 

878 requested_dim_names: list[str] = [] 

879 canonical_meshes: list['DeviceMesh'] = [] 

880 flatten_rank_map = root_mesh._flatten_rank_map # pylint: disable=protected-access 

881 anchor_meshes = DeviceMesh._collect_concatenate_anchor_meshes(meshes, root_mesh) 

882 for mesh in meshes: 

883 if not mesh.mesh_dim_names: 

884 raise ValueError("DeviceMesh.concatenate requires mesh_dim_names on every input mesh.") 

885 if mesh._flatten_rank_map == flatten_rank_map: # pylint: disable=protected-access 

886 canonical_mesh = mesh 

887 else: 

888 canonical_mesh = DeviceMesh._recover_concatenate_mesh_from_anchors( 

889 mesh, 

890 anchor_meshes, 

891 flatten_rank_map, 

892 ) 

893 if canonical_mesh is None: 

894 raise ValueError("DeviceMesh.concatenate expects all meshes to share the same root mesh.") 

895 canonical_meshes.append(canonical_mesh) 

896 requested_dim_names.extend(canonical_mesh.mesh_dim_names) 

897 return root_mesh, tuple(canonical_meshes), tuple(requested_dim_names), flatten_rank_map 

898 

899 @staticmethod 

900 def _collect_concatenate_anchor_meshes( 

901 meshes: Sequence['DeviceMesh'], 

902 root_mesh: 'DeviceMesh', 

903 ) -> list['DeviceMesh']: 

904 """Collect mesh views that can recover orphaned concatenate inputs by dim name.""" 

905 anchor_meshes: list['DeviceMesh'] = [] 

906 seen_ids: set[int] = set() 

907 

908 def add_anchor(mesh: Optional['DeviceMesh']) -> None: 

909 if mesh is None or id(mesh) in seen_ids: 

910 return 

911 seen_ids.add(id(mesh)) 

912 anchor_meshes.append(mesh) 

913 

914 add_anchor(root_mesh) 

915 for flatten_mesh in root_mesh.get_flatten_mapping().values(): 

916 add_anchor(flatten_mesh) 

917 

918 for mesh in meshes: 

919 if mesh.root_mesh is None: 

920 continue 

921 add_anchor(mesh) 

922 add_anchor(mesh._get_root_mesh()) # pylint: disable=protected-access 

923 for source_mesh, _ in getattr(mesh, "_dim_group_sources", ()): 

924 if isinstance(source_mesh, DeviceMesh): 

925 add_anchor(source_mesh) 

926 add_anchor(source_mesh._get_root_mesh()) # pylint: disable=protected-access 

927 

928 return anchor_meshes 

929 

930 @staticmethod 

931 def _recover_concatenate_mesh_from_anchors( 

932 mesh: 'DeviceMesh', 

933 anchor_meshes: Sequence['DeviceMesh'], 

934 flatten_rank_map: tuple[int, ...], 

935 ) -> Optional['DeviceMesh']: 

936 """Recover an orphan mesh as a view in the shared root coordinate system.""" 

937 mesh_dim_names = tuple(mesh.mesh_dim_names or ()) 

938 for anchor_mesh in anchor_meshes: 

939 try: 

940 candidate = anchor_mesh[mesh_dim_names] 

941 except (KeyError, ValueError, RuntimeError, NotImplementedError): 

942 continue 

943 candidate_attrs = ( 

944 candidate.device_type == mesh.device_type, 

945 candidate.mesh_shape == mesh.mesh_shape, 

946 candidate.rank_list == mesh.rank_list, 

947 candidate._flatten_rank_map == flatten_rank_map, # pylint: disable=protected-access 

948 ) 

949 if all(candidate_attrs): 

950 return candidate 

951 return None 

952 

953 @staticmethod 

954 def _validate_concatenate_root_order(root_mesh: 'DeviceMesh', requested_dim_names: tuple[str, ...]) -> None: 

955 """Require original root dims to stay in root order when concatenating by name.""" 

956 root_dim_names = tuple(root_mesh.mesh_dim_names) if root_mesh.mesh_dim_names else () 

957 if not root_dim_names or not all(dim_name in root_dim_names for dim_name in requested_dim_names): 

958 return 

959 

960 requested_indices = [root_dim_names.index(dim_name) for dim_name in requested_dim_names] 

961 if requested_indices != sorted(requested_indices): 

962 raise ValueError( 

963 "DeviceMesh.concatenate expects meshes to follow the root mesh order. " 

964 f"Got root mesh dims {root_dim_names} and requested dims {requested_dim_names}." 

965 ) 

966 

967 @staticmethod 

968 def _collect_concatenate_metadata( 

969 meshes: Sequence['DeviceMesh'], 

970 ) -> tuple[ 

971 list[str], 

972 list[IntTuple], 

973 list[IntTuple], 

974 list[Optional[str]], 

975 list[BackendConfig], 

976 list[tuple['DeviceMesh', int]], 

977 ]: 

978 """Collect layout and process-group metadata from all concatenate inputs.""" 

979 concat_dim_names: list[str] = [] 

980 concat_sizes: list[IntTuple] = [] 

981 concat_strides: list[IntTuple] = [] 

982 concat_dim_group_names: list[Optional[str]] = [] 

983 concat_dim_group_backends: list[BackendConfig] = [] 

984 concat_dim_group_sources: list[tuple['DeviceMesh', int]] = [] 

985 

986 for mesh in meshes: 

987 for dim, sub_layout in enumerate(mesh._layout): # pylint: disable=protected-access 

988 concat_sizes.append(sub_layout.sizes) 

989 concat_strides.append(sub_layout.strides) 

990 if hasattr(mesh, "_dim_group_names"): 

991 concat_dim_group_names.append(mesh._dim_group_names[dim]) # pylint: disable=protected-access 

992 concat_dim_group_backends.append(mesh._dim_group_backends[dim]) # pylint: disable=protected-access 

993 if hasattr(mesh, "_dim_group_sources"): 

994 concat_dim_group_sources.append(mesh._dim_group_sources[dim]) # pylint: disable=protected-access 

995 else: 

996 concat_dim_group_sources.append((mesh, dim)) 

997 concat_dim_names.extend(mesh.mesh_dim_names) 

998 

999 if len(set(concat_dim_names)) != len(concat_dim_names): 

1000 raise ValueError( 

1001 f"DeviceMesh.concatenate expects disjoint mesh dims, but got {tuple(concat_dim_names)}." 

1002 ) 

1003 return ( 

1004 concat_dim_names, 

1005 concat_sizes, 

1006 concat_strides, 

1007 concat_dim_group_names, 

1008 concat_dim_group_backends, 

1009 concat_dim_group_sources, 

1010 ) 

1011 

1012 @staticmethod 

1013 def _build_concatenate_layout(concat_sizes: list[IntTuple], concat_strides: list[IntTuple]) -> _MeshLayout: 

1014 """Build the layout represented by concatenated top-level mesh axes.""" 

1015 if len(concat_sizes) == 1: 

1016 return _MeshLayout(concat_sizes[0], concat_strides[0]) 

1017 return _MeshLayout(tuple(concat_sizes), tuple(concat_strides)) 

1018 

1019 @staticmethod 

1020 def _set_concatenated_group_state( 

1021 mesh: 'DeviceMesh', 

1022 dim_group_names: list[Optional[str]], 

1023 dim_group_backends: list[BackendConfig], 

1024 dim_group_sources: list[tuple['DeviceMesh', int]], 

1025 ) -> None: 

1026 """Attach inherited process-group metadata to a concatenated mesh view.""" 

1027 if dim_group_names: 

1028 mesh._dim_group_names = dim_group_names # pylint: disable=W0212 

1029 if dim_group_backends: 

1030 mesh._dim_group_backends = tuple(dim_group_backends) # pylint: disable=W0212 

1031 if dim_group_sources: 

1032 mesh._dim_group_sources = tuple(dim_group_sources) # pylint: disable=W0212 

1033 

1034 @staticmethod 

1035 def concatenate(meshes: Sequence['DeviceMesh']) -> 'DeviceMesh': 

1036 """Concatenate multiple sub-mesh views into one wider layout-backed mesh.""" 

1037 if len(meshes) == 1: 

1038 return meshes[0] 

1039 root_mesh, canonical_meshes, requested_dim_names, _ = DeviceMesh._validate_concatenate_inputs(meshes) 

1040 DeviceMesh._validate_concatenate_root_order(root_mesh, requested_dim_names) 

1041 ( 

1042 concat_dim_names, 

1043 concat_sizes, 

1044 concat_strides, 

1045 concat_dim_group_names, 

1046 concat_dim_group_backends, 

1047 concat_dim_group_sources, 

1048 ) = DeviceMesh._collect_concatenate_metadata(canonical_meshes) 

1049 concat_layout = DeviceMesh._build_concatenate_layout(concat_sizes, concat_strides) 

1050 if not concat_layout.check_non_overlap(): 

1051 raise ValueError(f"Cannot concatenate overlapping meshes: {meshes}") 

1052 

1053 res_mesh = DeviceMesh( 

1054 root_mesh.device_type, 

1055 mesh_dim_names=tuple(concat_dim_names), 

1056 _init_backend=False, 

1057 _layout=concat_layout, 

1058 _rank_map=root_mesh._rank_map, # pylint: disable=protected-access 

1059 _root_mesh=root_mesh, 

1060 ) 

1061 DeviceMesh._set_concatenated_group_state( 

1062 res_mesh, 

1063 concat_dim_group_names, 

1064 concat_dim_group_backends, 

1065 concat_dim_group_sources, 

1066 ) 

1067 _register_device_mesh(res_mesh) 

1068 return res_mesh 

1069 

1070 _concatenate = concatenate 

1071 

1072 def _create_flatten_mesh( 

1073 self, 

1074 mesh_dim_name: Optional[str] = None, 

1075 backend_override: BackendConfig = None, 

1076 ) -> 'DeviceMesh': 

1077 """Create or reuse a flattened one-dimensional mesh view.""" 

1078 root_mesh = self._get_root_mesh() 

1079 

1080 if mesh_dim_name is None: 

1081 mesh_dim_name = "_".join(self.mesh_dim_names) 

1082 

1083 if self.ndim == 1 and mesh_dim_name in self.mesh_dim_names: # pylint: disable=E1135 

1084 return self 

1085 

1086 invalid_dim_names = root_mesh.mesh_dim_names 

1087 if mesh_dim_name in invalid_dim_names: 

1088 raise ValueError( 

1089 f"'{mesh_dim_name}' already exists in the root mesh mesh_dim_names " 

1090 f"{invalid_dim_names}. Please specify another valid mesh_dim_name." 

1091 ) 

1092 

1093 flattened_mesh_layout = self._layout.coalesce() 

1094 if len(flattened_mesh_layout) > 1: 

1095 flattened_mesh_layout = flattened_mesh_layout.nest() 

1096 

1097 flatten_mapping = root_mesh.get_flatten_mapping() 

1098 if mesh_dim_name in flatten_mapping: 

1099 cached_mesh = flatten_mapping[mesh_dim_name] 

1100 if cached_mesh._layout == flattened_mesh_layout: # pylint: disable=protected-access 

1101 return cached_mesh 

1102 raise ValueError( 

1103 f"Flatten mesh with mesh_dim_name '{mesh_dim_name}' has been created " 

1104 f"before with different layout. Please specify another valid mesh_dim_name." 

1105 ) 

1106 

1107 res_flattened_mesh = DeviceMesh( 

1108 device_type=root_mesh.device_type, 

1109 mesh_dim_names=(mesh_dim_name,), 

1110 _init_backend=False, 

1111 _layout=flattened_mesh_layout, 

1112 _rank_map=root_mesh._rank_map, 

1113 _root_mesh=root_mesh, 

1114 ) 

1115 res_flattened_mesh._dim_group_backends = (backend_override,) # pylint: disable=W0212 

1116 if hasattr(self, "_dim_group_names"): 

1117 res_flattened_mesh._dim_group_names = DeviceMesh._init_process_groups_for_layout( # pylint: disable=W0212 

1118 res_flattened_mesh._layout, 

1119 root_mesh._rank_map, 

1120 res_flattened_mesh.mesh_dim_names, 

1121 backend_override=(backend_override,), 

1122 ) 

1123 

1124 root_mesh.add_flatten_mapping(mesh_dim_name, res_flattened_mesh) 

1125 root_mesh._sub_mesh_cache[(mesh_dim_name,)] = res_flattened_mesh # pylint: disable=W0212 

1126 root_mesh.sub_mesh.append(res_flattened_mesh) 

1127 _register_device_mesh(res_flattened_mesh) 

1128 

1129 return res_flattened_mesh 

1130 

1131 def _create_unflatten_mesh( 

1132 self, 

1133 dim: int, 

1134 mesh_sizes: tuple[int, ...], 

1135 mesh_dim_names: tuple[str, ...], 

1136 backend_override: tuple[BackendConfig, ...], 

1137 ) -> 'DeviceMesh': 

1138 """Split one logical mesh axis into multiple named axes.""" 

1139 inner_layout = _MeshLayout(mesh_sizes, _contiguous_strides(mesh_sizes)) 

1140 original_layout = self._layout[dim] 

1141 if inner_layout.numel() != original_layout.numel(): 

1142 raise ValueError( 

1143 f"The product of mesh_sizes={mesh_sizes} is {inner_layout.numel()}, " 

1144 f"but the original dimension at dim={dim} has size {original_layout.numel()}." 

1145 ) 

1146 

1147 partial_layout = original_layout.composition(inner_layout) 

1148 unflattened_layout = self._layout.splice(dim, dim + 1, partial_layout) 

1149 unflattened_mesh_dim_names = list(self.mesh_dim_names or ()) 

1150 unflattened_mesh_dim_names[dim: dim + 1] = list(mesh_dim_names) 

1151 

1152 root_mesh = self._get_root_mesh() 

1153 res_mesh = DeviceMesh( 

1154 self.device_type, 

1155 mesh_dim_names=tuple(unflattened_mesh_dim_names), 

1156 _init_backend=False, 

1157 _layout=unflattened_layout, 

1158 _rank_map=root_mesh._rank_map, 

1159 _root_mesh=root_mesh, 

1160 ) 

1161 

1162 dim_group_backends = list(self._dim_group_backends) 

1163 dim_group_backends[dim: dim + 1] = list(backend_override) 

1164 res_mesh._dim_group_backends = tuple(dim_group_backends) # pylint: disable=W0212 

1165 

1166 if hasattr(self, "_dim_group_names"): 

1167 dim_group_names = list(self._dim_group_names) 

1168 dim_group_names[dim: dim + 1] = DeviceMesh._init_process_groups_for_layout( 

1169 partial_layout, 

1170 root_mesh._rank_map, 

1171 mesh_dim_names, 

1172 backend_override=backend_override, 

1173 ) 

1174 res_mesh._dim_group_names = dim_group_names # pylint: disable=W0212 

1175 

1176 _register_device_mesh(res_mesh) 

1177 return res_mesh 

1178 

1179 def _flatten(self, mesh_dim_name: Optional[str] = None, backend_override: Any = None) -> 'DeviceMesh': 

1180 return self._create_flatten_mesh( 

1181 mesh_dim_name, 

1182 backend_override=_normalize_backend_value(backend_override), 

1183 ) 

1184 

1185 def _unflatten( 

1186 self, 

1187 dim: Union[int, str], 

1188 mesh_sizes: tuple[int, ...], 

1189 mesh_dim_names: tuple[str, ...], 

1190 backend_override: Optional[dict[Union[int, str], Any]] = None, 

1191 ) -> 'DeviceMesh': 

1192 """Torch-compatible helper that expands one mesh axis into a nested layout.""" 

1193 if isinstance(dim, int): 

1194 if dim < 0 or dim >= self.ndim: 

1195 raise ValueError(f"dim {dim} specified in `_unflatten` is out of range {self.ndim}") 

1196 else: 

1197 mesh_dim_names_tuple = self.mesh_dim_names or () 

1198 if dim not in mesh_dim_names_tuple: 

1199 raise ValueError(f"dim {dim} specified in `_unflatten` is not in {mesh_dim_names_tuple}") 

1200 dim = mesh_dim_names_tuple.index(dim) 

1201 

1202 if len(mesh_sizes) != len(mesh_dim_names): 

1203 raise RuntimeError("mesh_dim_names must have same length as mesh_sizes in _unflatten!") 

1204 

1205 backend_override_tuple = ( 

1206 _normalize_backend_override(backend_override, len(mesh_sizes), mesh_dim_names) 

1207 if backend_override is not None 

1208 else (None,) * len(mesh_dim_names) 

1209 ) 

1210 return self._create_unflatten_mesh(dim, mesh_sizes, mesh_dim_names, backend_override_tuple) 

1211 

1212 def assert_axis(self, axis, operate_name): 

1213 """Validate that the given axis name exists in mesh_dim_names. 

1214 

1215 Raises RuntimeError if mesh_dim_names is not set, or ValueError 

1216 if the axis is not among the declared mesh dimension names. 

1217 """ 

1218 if not self.mesh_dim_names: 

1219 raise RuntimeError(f"mesh_dim_names not specified, {operate_name} is not supported.") 

1220 if axis not in self.mesh_dim_names: # pylint: disable=E1135 

1221 raise ValueError( 

1222 f"The axis name must be one of mesh dim name {self.mesh_dim_names}, but got {axis}" 

1223 ) 

1224 

1225 def axis_id(self, axis): 

1226 """Return the reverse-indexed device dimension id for the named axis, or -1 for 'None'.""" 

1227 if axis == "None": 

1228 return -1 

1229 self.assert_axis(axis, "axis_id") 

1230 return self._dev_name_to_dev_id[axis] 

1231 

1232 def axis_index(self, axis): 

1233 """Return the positional index of the named axis within the mesh dimensions.""" 

1234 self.assert_axis(axis, "axis_index") 

1235 return self._dev_name_to_index[axis] 

1236 

1237 def get_device_num_along_axis(self, axis): 

1238 """Return the number of devices along the named mesh axis.""" 

1239 self.assert_axis(axis, "get_device_num_along_axis") 

1240 return self.mesh_shape[self.mesh_dim_names.index(axis)] 

1241 

1242 def get_rank_list_along_axis(self, mesh_dim): 

1243 """Return the ranks that share every other coordinate with the current rank.""" 

1244 if mesh_dim in self._cache_rank_list_along_axis: 

1245 return self._cache_rank_list_along_axis[mesh_dim] 

1246 self.assert_axis(mesh_dim, "get_rank_list_along_axis") 

1247 

1248 mesh_shape = self.mesh_shape 

1249 mesh_dim_names = self.mesh_dim_names 

1250 rank_list = self.rank_list 

1251 rank = self.rank 

1252 

1253 if rank not in rank_list: 

1254 raise ValueError(f"Rank {rank} not found in rank_list") 

1255 

1256 idx = rank_list.index(rank) 

1257 coord = [0] * len(mesh_shape) 

1258 temp = idx 

1259 for i in range(len(mesh_shape) - 1, -1, -1): 

1260 coord[i] = temp % mesh_shape[i] 

1261 temp //= mesh_shape[i] 

1262 

1263 dim_index = mesh_dim_names.index(mesh_dim) 

1264 strides = [1] * len(mesh_shape) 

1265 for i in range(len(mesh_shape) - 2, -1, -1): 

1266 strides[i] = strides[i + 1] * mesh_shape[i + 1] 

1267 

1268 result_ranks = [] 

1269 for v in range(mesh_shape[dim_index]): 

1270 new_coord = coord.copy() 

1271 new_coord[dim_index] = v 

1272 new_idx = 0 

1273 for i in range(len(mesh_shape)): 

1274 new_idx += new_coord[i] * strides[i] 

1275 

1276 result_ranks.append(rank_list[new_idx]) 

1277 

1278 self._cache_rank_list_along_axis[mesh_dim] = result_ranks 

1279 return result_ranks 

1280 

1281 def get_global_shape(self, slice_shape, tensor_map): 

1282 """Infer the global tensor shape from a shard shape and tensor-map metadata.""" 

1283 map_key = hash((slice_shape, tensor_map)) 

1284 if map_key in self._global_shape_map: 

1285 return self._global_shape_map[map_key] 

1286 if tensor_map is None: 

1287 raise ValueError( 

1288 "tensor_map is not set. Please configure the tensor map by calling the layout." 

1289 ) 

1290 if len(slice_shape) != len(tensor_map): 

1291 raise ValueError( 

1292 f"Length of slice_shape ({len(slice_shape)}) must match " 

1293 f"the length of tensor_map ({len(tensor_map)})." 

1294 ) 

1295 

1296 n_dims = len(self._mesh_shape) 

1297 factors = [1] * len(slice_shape) 

1298 

1299 for dev_idx, size in enumerate(self._mesh_shape): 

1300 reverse_idx = n_dims - 1 - dev_idx 

1301 for axis_idx, mapping in enumerate(tensor_map): 

1302 if isinstance(mapping, int): 

1303 if mapping == -1: 

1304 continue 

1305 if mapping == reverse_idx: 

1306 factors[axis_idx] *= size 

1307 break 

1308 elif isinstance(mapping, tuple): 

1309 if reverse_idx in mapping: 

1310 factors[axis_idx] *= size 

1311 break 

1312 

1313 global_shape = [] 

1314 for i, dim in enumerate(slice_shape): 

1315 global_shape.append(dim * factors[i]) 

1316 self._global_shape_map[map_key] = tuple(global_shape) 

1317 return tuple(global_shape) 

1318 

1319 def _materialize_dim_group(self, mesh_dim: int) -> Optional[str]: 

1320 """Create a deferred process group for one mesh dimension on first use.""" 

1321 if not hasattr(self, "_dim_group_names"): 

1322 self._dim_group_names = [None] * self.ndim # pylint: disable=W0201 

1323 

1324 if hasattr(self, "_dim_group_sources"): 

1325 source_mesh, source_dim = self._dim_group_sources[mesh_dim] # pylint: disable=W0212 

1326 if source_mesh is not self or source_dim != mesh_dim: 

1327 source_group_key = source_mesh._materialize_dim_group(source_dim) # pylint: disable=W0212 

1328 self._dim_group_names[mesh_dim] = source_group_key 

1329 return source_group_key 

1330 

1331 group_key = self._dim_group_names[mesh_dim] 

1332 if group_key is not None and group_key in EXISTING_COMM_GROUPS: 

1333 return group_key 

1334 

1335 split_ranks, group_key = DeviceMesh._build_dim_split_ranks(self._layout[mesh_dim], self._rank_map) 

1336 group = platform.split_group( 

1337 split_ranks=split_ranks, 

1338 pg_options=_get_cp_pg_options(self.mesh_dim_names, mesh_dim), 

1339 ) 

1340 DeviceMesh._cache_group_if_needed(group_key, group) 

1341 self._dim_group_names[mesh_dim] = group_key 

1342 return group_key 

1343 

1344 def get_comm_group_by_axis(self, mesh_dim: Union[str, int]): 

1345 """Return the cached or lazily materialized process group for one mesh axis.""" 

1346 if self.ndim == 1 and mesh_dim is None: 

1347 mesh_dim = 0 

1348 

1349 if isinstance(mesh_dim, str): 

1350 if self.mesh_dim_names is None or len(self.mesh_dim_names) == 0: 

1351 raise ValueError(f"DeviceMesh mesh_dim_names is not set, string mesh_dim {mesh_dim}, is not support.") 

1352 if mesh_dim not in self.mesh_dim_names: # pylint: disable=E1135 

1353 raise ValueError( 

1354 f"mesh_dim can pass a string or integer, but string mesh_dim '{mesh_dim}' not found in " 

1355 f"mesh_dim_names {self.mesh_dim_names}" 

1356 ) 

1357 mesh_dim = self.mesh_dim_names.index(mesh_dim) 

1358 else: 

1359 if not isinstance(mesh_dim, int) or mesh_dim < 0 or mesh_dim >= self.ndim: 

1360 raise ValueError( 

1361 f"mesh_dim can pass a string or integer, if not string, mesh_dim should be a integer in range " 

1362 f"[0, {self.ndim}), but got {mesh_dim}" 

1363 ) 

1364 

1365 if not hasattr(self, "_dim_group_names"): 

1366 raise RuntimeError("DeviceMesh process groups not initialized!") 

1367 

1368 group_key = self._dim_group_names[mesh_dim] 

1369 if group_key is None or group_key not in EXISTING_COMM_GROUPS: 

1370 group_key = self._materialize_dim_group(mesh_dim) 

1371 if group_key not in EXISTING_COMM_GROUPS: 

1372 raise ValueError(f"{group_key} not in group cache {EXISTING_COMM_GROUPS.keys()}") 

1373 return EXISTING_COMM_GROUPS[group_key] 

1374 

1375 def get_devices_for_axis(self, mesh_dim: Union[str, int], rank: int): 

1376 """List peer ranks that share all coordinates except the requested axis.""" 

1377 if isinstance(mesh_dim, str): 

1378 if not self.mesh_dim_names: 

1379 raise ValueError("_mesh_dim_names is not set, string mesh_dim is not supported, please pass a integer.") 

1380 mesh_dim_names = self.mesh_dim_names 

1381 if mesh_dim not in mesh_dim_names: # pylint: disable=E1135 

1382 raise ValueError(f"mesh_dim '{mesh_dim}' not found in mesh_dim_names {mesh_dim_names}") 

1383 mesh_dim = mesh_dim_names.index(mesh_dim) 

1384 

1385 mesh_shape = self._mesh_shape 

1386 if mesh_dim < 0 or mesh_dim >= self.ndim: 

1387 raise ValueError(f"mesh_dim {mesh_dim} can not out of range [0, {self.ndim})") 

1388 rank_list = self._rank_list 

1389 if rank not in rank_list: 

1390 raise ValueError(f"Rank {rank} not found in rank_list") 

1391 

1392 idx = rank_list.index(rank) 

1393 coord = [0] * len(mesh_shape) 

1394 temp = idx 

1395 for i in range(len(mesh_shape) - 1, -1, -1): 

1396 coord[i] = temp % mesh_shape[i] 

1397 temp //= mesh_shape[i] 

1398 

1399 strides = [1] * len(mesh_shape) 

1400 for i in range(len(mesh_shape) - 2, -1, -1): 

1401 strides[i] = strides[i + 1] * mesh_shape[i + 1] 

1402 

1403 result_ranks = [] 

1404 for v in range(mesh_shape[mesh_dim]): 

1405 new_coord = coord.copy() 

1406 new_coord[mesh_dim] = v 

1407 new_idx = 0 

1408 for i in range(len(mesh_shape)): 

1409 new_idx += new_coord[i] * strides[i] 

1410 

1411 result_ranks.append(rank_list[new_idx]) 

1412 

1413 return result_ranks 

1414 

1415 def to_hash(self): 

1416 """Return a hashable tuple that uniquely identifies this device mesh configuration.""" 

1417 map_key = (self.mesh_shape, self.mesh_dim_names, self.rank_list) 

1418 return map_key 

1419 

1420 def __repr__(self): 

1421 return ( 

1422 f"DeviceMesh(device_type='{self.device_type}', mesh_shape={self._mesh_shape}, " 

1423 f"mesh_dim_names={self.mesh_dim_names}, rank_list={self._rank_list})" 

1424 ) 

1425 

1426 def __str__(self): 

1427 return self.__repr__() 

1428 

1429 def __deepcopy__(self, memo): 

1430 cls = self.__class__ 

1431 result = cls.__new__(cls) 

1432 memo[id(self)] = result 

1433 for k, v in self.__dict__.items(): 

1434 if k in ("_root_mesh", "_dim_group_sources"): 

1435 setattr(result, k, v) 

1436 else: 

1437 setattr(result, k, copy.deepcopy(v, memo)) 

1438 return result 

1439 

1440 

1441_DEVICE_MESH_MAP = {} 

1442 

1443 

1444def _device_mesh_map_key( 

1445 mesh_shape: tuple[int, ...], 

1446 mesh_dim_names: Union[tuple[str, ...], list[str], None], 

1447 rank_list: tuple[int, ...], 

1448) -> int: 

1449 mesh_dim_names = tuple(mesh_dim_names) if mesh_dim_names else None 

1450 return hash((tuple(mesh_shape), mesh_dim_names, tuple(rank_list))) 

1451 

1452 

1453def _register_device_mesh(mesh: DeviceMesh) -> DeviceMesh: 

1454 """Register a DeviceMesh in the global cache without dropping root metadata.""" 

1455 map_key = _device_mesh_map_key(mesh.mesh_shape, mesh.mesh_dim_names, mesh.rank_list) 

1456 existing = _DEVICE_MESH_MAP.get(map_key) 

1457 if existing is None: 

1458 _DEVICE_MESH_MAP[map_key] = mesh 

1459 return mesh 

1460 

1461 if existing.root_mesh is None and mesh.root_mesh is not None: 

1462 if existing is mesh._get_root_mesh(): # pylint: disable=protected-access 

1463 return existing 

1464 _DEVICE_MESH_MAP[map_key] = mesh 

1465 return mesh 

1466 

1467 return existing 

1468 

1469 

1470def _create_device_mesh(device_type: str, 

1471 mesh_shape: tuple[int, ...], 

1472 *, 

1473 mesh_dim_names: Union[tuple[str, ...], list[str], None] = None, 

1474 rank_list: tuple[int, ...], 

1475 init_backend: bool = True, ): 

1476 """Create or reuse a cached DeviceMesh with the requested topology.""" 

1477 mesh = np.array(rank_list).reshape(mesh_shape) 

1478 mesh_dim_names = tuple(mesh_dim_names) if mesh_dim_names else None 

1479 map_key = _device_mesh_map_key(mesh_shape, mesh_dim_names, rank_list) 

1480 if map_key not in _DEVICE_MESH_MAP: 

1481 _register_device_mesh( 

1482 DeviceMesh(device_type, mesh, mesh_dim_names=mesh_dim_names, _init_backend=init_backend) 

1483 ) 

1484 return _DEVICE_MESH_MAP.get(map_key, None) 

1485 

1486 

1487def init_device_mesh( 

1488 device_type: str, 

1489 mesh_shape: tuple[int, ...], 

1490 *, 

1491 mesh_dim_names: Union[tuple[str, ...], list[str], None] = None, 

1492 rank_list: Optional[tuple[int, ...]] = None, 

1493 init_backend: bool = True, 

1494) -> DeviceMesh: 

1495 """Initialize a cached DeviceMesh from the provided shape, names, and ranks.""" 

1496 total_devices = int(np.prod(np.array(mesh_shape))) 

1497 if rank_list is not None: 

1498 if len(rank_list) != total_devices: 

1499 raise ValueError( 

1500 f"rank_list length ({len(rank_list)}) must equal mesh size ({total_devices})" 

1501 ) 

1502 else: 

1503 if init_backend: 

1504 platform.init_process_group() 

1505 try: 

1506 current_rank = platform.get_rank() 

1507 except Exception as exc: 

1508 raise RuntimeError( 

1509 "init_device_mesh: failed to get current rank for automatic rank_list generation. " 

1510 "Either pass rank_list explicitly, or ensure the process group is initialized before calling " 

1511 "init_device_mesh (or set init_backend=True to let init_device_mesh initialize it)." 

1512 ) from exc 

1513 base = current_rank - (current_rank % total_devices) 

1514 rank_list = tuple(range(base, base + total_devices)) 

1515 

1516 if not isinstance(mesh_shape, tuple): 

1517 raise TypeError(f'mesh_shape must be a tuple, but got {type(mesh_shape)}') 

1518 

1519 for size in mesh_shape: 

1520 if not isinstance(size, int) or size <= 0: 

1521 raise ValueError( 

1522 f"Each element of mesh_shape must be a positive integer, but got {mesh_shape}" 

1523 ) 

1524 

1525 if mesh_dim_names is not None: 

1526 if not isinstance(mesh_dim_names, (tuple, list)): 

1527 raise TypeError( 

1528 f'mesh_dim_names must be a tuple or list, but got {type(mesh_dim_names)}' 

1529 ) 

1530 mesh_dim_names = tuple(mesh_dim_names) 

1531 if len(mesh_shape) != len(mesh_dim_names): 

1532 raise ValueError( 

1533 f'mesh_shape ({len(mesh_shape)}) and mesh_dim_names ' 

1534 f'({len(mesh_dim_names)}) should have same length' 

1535 ) 

1536 if len(set(mesh_dim_names)) != len(mesh_dim_names): 

1537 raise ValueError(f'Each element of mesh_dim_names {mesh_dim_names} should be different') 

1538 if any(not isinstance(name, str) or name == "" for name in mesh_dim_names): 

1539 raise ValueError(f'Each element of mesh_dim_names {mesh_dim_names} should be a non-empty string') 

1540 

1541 return _create_device_mesh( 

1542 device_type, 

1543 mesh_shape, 

1544 mesh_dim_names=mesh_dim_names, 

1545 rank_list=rank_list, 

1546 init_backend=init_backend, 

1547 )