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

453 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +0800

1# Copyright 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"""dtensor""" 

16# pylint: disable=C9006,C9007 

17import copy as cp 

18import inspect 

19import logging 

20import warnings 

21from typing import Any, Callable, Optional, Sequence, Set, Tuple, Union 

22 

23import numpy as np 

24import torch 

25from torch import Tensor 

26from torch.optim.optimizer import _foreach_supported_types as _optimizer_foreach_supported_types 

27from torch.utils._foreach_utils import _foreach_supported_types as _torch_foreach_supported_types 

28 

29from hyper_parallel.core.dtensor import _utils 

30from hyper_parallel.core.dtensor._collective_utils import mesh_broadcast, mesh_scatter 

31from hyper_parallel.core.dtensor._ragged_utils import ( 

32 _compute_ragged_slice, 

33 _layout_has_ragged_shard, 

34 _normalize_global_shape, 

35 _scatter_ragged_tensor, 

36 _slice_ragged_tensor, 

37) 

38from hyper_parallel.core.dtensor.device_mesh import _mesh_resources 

39from hyper_parallel.core.dtensor.layout import ( 

40 DeviceMesh, 

41 Layout, 

42 _get_slice_tensor_by_layout, 

43) 

44from hyper_parallel.core.dtensor.placement_types import Partial, Placement, Replicate, StridedShard 

45from hyper_parallel.core.utils import compute_local_shape_and_global_offset 

46from hyper_parallel.core.dtensor.dtensor_base import DTensorBase 

47 

48logger = logging.getLogger(__name__) 

49 

50 

51def _device_meshes_are_compatible(lhs: Any, rhs: Any) -> bool: 

52 """Return whether two mesh objects describe the same device topology.""" 

53 if lhs is rhs: 

54 return True 

55 if not isinstance(lhs, DeviceMesh) or not isinstance(rhs, DeviceMesh): 

56 return False 

57 return lhs.device_type == rhs.device_type and lhs.to_hash() == rhs.to_hash() 

58 

59 

60class SkipDTensorDispatch(): 

61 """Context manager that disables DTensor op dispatch for the enclosed block. 

62 

63 Args: 

64 no_skip: Optional set of op callables or canonical op name strings that 

65 should still be dispatched through DTensor even within this context. 

66 All other ops bypass DTensor dispatch and operate on local tensors. 

67 

68 Example: 

69 >>> import torch 

70 >>> with SkipDTensorDispatch(no_skip={torch.zeros_like}): 

71 ... # zeros_like still goes through DTensor dispatch; 

72 ... # everything else uses the local tensor path. 

73 ... result = torch.zeros_like(dtensor) 

74 """ 

75 

76 def __init__(self, no_skip: Optional[Set] = None): 

77 self._no_skip_names: frozenset = frozenset() 

78 if no_skip: 

79 names = set() 

80 for op in no_skip: 

81 if isinstance(op, str): 

82 names.add(op) 

83 else: 

84 names.add(_utils.get_op_name(op)) 

85 self._no_skip_names = frozenset(names) 

86 self._dispatch_token = None 

87 self._ops_token = None 

88 

89 def __enter__(self): 

90 # pylint: disable=C0415 

91 from hyper_parallel.core.shard._op_dispatch import _dtensor_dispatch_disabled, _no_skip_ops 

92 self._dispatch_token = _dtensor_dispatch_disabled.set(True) 

93 if self._no_skip_names: 

94 self._ops_token = _no_skip_ops.set(_no_skip_ops.get() | self._no_skip_names) 

95 

96 def __exit__(self, exc_type, exc_val, exc_tb): 

97 # pylint: disable=C0415 

98 from hyper_parallel.core.shard._op_dispatch import _dtensor_dispatch_disabled, _no_skip_ops 

99 if self._ops_token is not None: 

100 _no_skip_ops.reset(self._ops_token) 

101 self._ops_token = None 

102 _dtensor_dispatch_disabled.reset(self._dispatch_token) 

103 self._dispatch_token = None 

104 

105 

106# Cache for _build_layout to avoid redundant Layout computations 

107# Key: (device_mesh.to_hash(), tuple(placements), tensor_dim) 

108# Value: Layout 

109_LAYOUT_CACHE = {} 

110 

111 

112def _is_alias_placements(placements) -> bool: 

113 """ 

114 Check if placements use alias strings rather than Placement objects. 

115 

116 Alias placements use mesh dimension names (strings) to specify 

117 the sharding strategy, e.g., ("dp", "tp") or (("dp", "tp"), "None"). 

118 All elements must be strings or tuples of strings for the sequence 

119 to be recognized as alias-style. 

120 

121 Args: 

122 placements: A sequence of placement specifications. 

123 

124 Returns: 

125 bool: True if all elements are alias strings or tuples of strings. 

126 """ 

127 if len(placements) == 0: 

128 return False 

129 for p in placements: 

130 if isinstance(p, str): 

131 continue 

132 if isinstance(p, tuple) and len(p) > 0 and all(isinstance(x, str) for x in p): 

133 continue 

134 return False 

135 return True 

136 

137 

138def _build_layout( 

139 device_mesh: DeviceMesh, 

140 placements: Union[Sequence[Placement], Sequence[Union[str, Tuple[str, ...]]]], 

141 tensor_dim: int 

142) -> Layout: 

143 """ 

144 Build Layout from device_mesh and placements. 

145 

146 This function uses a cache to avoid redundant Layout computations 

147 for the same (device_mesh, placements, tensor_dim) combination. 

148 

149 Args: 

150 device_mesh: The device mesh describing the device topology. 

151 placements: Supports two styles: 

152 - Placement objects (Shard, Replicate, etc.) 

153 - Alias strings ("dp", "None", ("dp", "tp"), etc.), length must 

154 equal the number of tensor dimensions (``tensor_dim``). 

155 tensor_dim: Number of dimensions in the tensor. 

156 

157 Returns: 

158 Layout: The built layout object. 

159 

160 Raises: 

161 ValueError: If alias placements length does not match tensor dimensions. 

162 """ 

163 mesh_key = device_mesh.to_hash() 

164 placements_key = tuple(placements) 

165 cache_key = (mesh_key, placements_key, tensor_dim) 

166 

167 if cache_key in _LAYOUT_CACHE: 

168 return _LAYOUT_CACHE[cache_key] 

169 

170 layout = Layout.from_device_mesh(device_mesh) 

171 

172 if _is_alias_placements(placements): 

173 if len(placements) != tensor_dim: 

174 raise ValueError( 

175 f"Alias placements length ({len(placements)}) must equal " 

176 f"tensor dimensions ({tensor_dim})." 

177 ) 

178 result = layout(*placements) 

179 else: 

180 result = layout(placements) 

181 result.placement_to_tensor_map(tensor_dim) 

182 

183 _LAYOUT_CACHE[cache_key] = result 

184 

185 return result 

186 

187 

188def _is_broadcastable(src_shape: Sequence[int], dst_shape: Sequence[int]) -> bool: 

189 """Return True iff ``src_shape`` is broadcastable to ``dst_shape``. 

190 

191 Standard NumPy / PyTorch right-aligned broadcast rule: ``src`` cannot 

192 have more dimensions than ``dst``; each right-aligned dimension pair 

193 must be equal, or ``src``'s dimension must be 1. 

194 """ 

195 src_shape = tuple(src_shape) 

196 dst_shape = tuple(dst_shape) 

197 if len(src_shape) > len(dst_shape): 

198 return False 

199 for i in range(1, len(src_shape) + 1): 

200 s, d = src_shape[-i], dst_shape[-i] 

201 if s not in (d, 1): 

202 return False 

203 return True 

204 

205 

206def _device_spec(device: Any) -> Tuple[str, Optional[int]]: 

207 """Return normalised ``(device_type, device_index)``. 

208 

209 Handles device objects and strings such as ``"npu:0"``. 

210 """ 

211 device_type = getattr(device, "type", None) 

212 # Only read .index from objects that also have a .type attribute 

213 # (i.e. torch.device). Avoids capturing str.index on plain strings. 

214 device_index = ( 

215 getattr(device, "index", None) if device_type is not None else None 

216 ) 

217 device_text = str(device).lower() 

218 

219 if device_type is None: 

220 parts = device_text.split(":", maxsplit=1) 

221 device_type = parts[0] 

222 if len(parts) == 2 and parts[1].isdigit(): 

223 device_index = int(parts[1]) 

224 

225 return str(device_type).lower(), device_index 

226 

227 

228class DTensor(DTensorBase): 

229 """ 

230 DTensor - Distributed Tensor 

231 

232 A DTensor represents a tensor that is distributed across multiple devices 

233 according to a DeviceMesh and placement specifications. 

234 

235 Args: 

236 local_tensor (Tensor): The local tensor shard on this device. 

237 device_mesh (DeviceMesh): The device mesh describing the device topology. 

238 placements: The placement strategy. Supports two styles: 

239 - Placement objects (e.g., ``[Shard(0), Replicate()]``). 

240 - Alias strings (e.g., ``("dp", "None")`` or 

241 ``(("dp", "tp"), "None")``), length must equal the number of 

242 tensor dimensions. 

243 

244 Example: 

245 >>> mesh = init_device_mesh(device_type="npu", mesh_shape=(2, 2), mesh_dim_names=("dp", "tp")) 

246 >>> local_tensor = Tensor(np.ones((4, 4))) 

247 >>> # Placement style 

248 >>> dtensor = DTensor.from_local(local_tensor, mesh, [Shard(0), Replicate()]) 

249 >>> # Alias style — length matches tensor dims 

250 >>> dtensor = DTensor.from_local(local_tensor, mesh, ("dp", "None")) 

251 """ 

252 _local_tensor: Tensor 

253 _device_mesh: DeviceMesh 

254 _placements: Sequence[Placement] 

255 

256 def __init_data__( 

257 self, 

258 local_tensor: Tensor, 

259 device_mesh: DeviceMesh, 

260 placements: Union[Sequence[Placement], Sequence[Union[str, Tuple[str, ...]]]], 

261 layout: Optional[Layout] = None, 

262 shape: Optional[Tuple[int, ...]] = None, 

263 ): 

264 self._local_tensor = local_tensor 

265 self._device_mesh = device_mesh 

266 tensor_dim = len(shape) if shape is not None else len(local_tensor.shape) 

267 # Fast path: when an already-built Layout is supplied (e.g. output layouts 

268 # cached by infer_layout and passed straight through wrap_output), reuse it 

269 # directly and skip _build_layout (which otherwise recomputes device_mesh.to_hash(), 

270 # tuple(placements) and a cache lookup on every single output construction). 

271 self._layout = layout if layout is not None else _build_layout( 

272 device_mesh, placements, tensor_dim 

273 ) 

274 self._placements = tuple(self._layout.placements) 

275 is_ragged = _layout_has_ragged_shard(self._layout) 

276 if is_ragged and shape is None: 

277 raise ValueError( 

278 "DTensor.from_local with RaggedShard requires an explicit global shape" 

279 ) 

280 if shape is not None: 

281 self._global_shape = _normalize_global_shape(shape) 

282 else: 

283 self._global_shape = tuple(self._layout.get_global_shape(local_tensor.shape)) 

284 if ( 

285 shape is not None 

286 and ( 

287 self._layout.tensor_map is None 

288 or len(self._layout.tensor_map) != len(self._global_shape) 

289 ) 

290 ): 

291 raise ValueError( 

292 "DTensor global shape rank must match layout tensor_map rank, " 

293 f"got global_shape={self._global_shape!r}, tensor_map={self._layout.tensor_map!r}" 

294 ) 

295 if is_ragged: 

296 if hasattr(local_tensor, "is_contiguous") and not local_tensor.is_contiguous(): 

297 raise ValueError("RaggedShard local tensor must be contiguous") 

298 if len(local_tensor.shape) != 1: 

299 raise ValueError( 

300 "RaggedShard local tensor must use one-dimensional flat storage, " 

301 f"got local_shape={tuple(local_tensor.shape)!r}" 

302 ) 

303 expected = _compute_ragged_slice(self._global_shape, self._layout) 

304 if local_tensor.numel() != expected.local_numel: 

305 raise ValueError( 

306 "RaggedShard local tensor numel does not match its allocation, " 

307 f"got actual={local_tensor.numel()}, expected={expected.local_numel}, " 

308 f"global_shape={self._global_shape!r}, placement={self._layout.ragged_shard.placement!r}" 

309 ) 

310 

311 @property 

312 def device_mesh(self) -> DeviceMesh: 

313 """The device mesh of this DTensor.""" 

314 return self._device_mesh 

315 

316 @property 

317 def placements(self) -> Sequence[Placement]: 

318 """The placements of this DTensor.""" 

319 return self._placements 

320 

321 @property 

322 def layout(self) -> Layout: 

323 """Internal layout for redistribution (for backward compatibility).""" 

324 if not hasattr(self, '_layout'): 

325 return None 

326 return self._layout 

327 

328 @staticmethod 

329 def from_local( 

330 local_tensor: Tensor, 

331 device_mesh: DeviceMesh, 

332 placements: Union[Sequence[Placement], Sequence[Union[str, Tuple[str, ...]]]], 

333 *, 

334 run_check: bool = False, 

335 shape: Optional[Tuple[int, ...]] = None, 

336 stride: Optional[Tuple[int, ...]] = None, 

337 ) -> 'DTensor': 

338 """ 

339 Create a DTensor from a local tensor with device mesh and placements. 

340 

341 Args: 

342 local_tensor (Tensor): The local tensor shard on this device. For 

343 ``RaggedShard``, the input may use its natural rank-local shape; 

344 construction stores it as a one-dimensional view internally. 

345 device_mesh (DeviceMesh): The device mesh describing the device topology. 

346 placements: The placement strategy. Supports two styles: 

347 - Placement objects (e.g., ``[Shard(0), Replicate()]``). 

348 - Alias strings (e.g., ``("dp", "None")`` or 

349 ``(("dp", "tp"), "None")``), length must equal the number 

350 of tensor dimensions. 

351 run_check (bool, optional): When ``True``, perform cross-rank metadata 

352 checks and broadcast replicate placements from the mesh source rank. 

353 Default: ``False``. 

354 shape (tuple[int, ...], optional): Explicit logical global shape. 

355 Required for RaggedShard. 

356 stride (tuple[int, ...], optional): Explicit logical global stride. 

357 Requires ``shape``. Normal layouts also retain compatibility 

358 with shape-only construction. 

359 

360 Returns: 

361 DTensor: A new DTensor instance. 

362 

363 Example: 

364 >>> mesh = init_device_mesh(device_type="npu", mesh_shape=(2, 2), mesh_dim_names=("dp", "tp")) 

365 >>> local_tensor = Tensor(np.ones((4, 4))) 

366 >>> dtensor = DTensor.from_local(local_tensor, mesh, [Shard(0), Replicate()]) 

367 >>> dtensor = DTensor.from_local(local_tensor, mesh, ("dp", "None")) 

368 """ 

369 tensor_dim = len(shape) if shape is not None else len(local_tensor.shape) 

370 layout = _build_layout(device_mesh, placements, tensor_dim) 

371 is_ragged = _layout_has_ragged_shard(layout) 

372 if is_ragged: 

373 if stride is not None and shape is None: 

374 raise ValueError("stride requires an explicit shape") 

375 if hasattr(local_tensor, "is_contiguous") and not local_tensor.is_contiguous(): 

376 raise ValueError("RaggedShard local tensor must be contiguous") 

377 local_tensor = local_tensor.view(-1) 

378 elif stride is not None and shape is None: 

379 raise ValueError("stride requires an explicit shape") 

380 if run_check: 

381 # pylint: disable=C0415 

382 from hyper_parallel.core.dtensor._from_local_utils import run_from_local_checks 

383 run_from_local_checks( 

384 local_tensor, 

385 device_mesh, 

386 layout.placements, 

387 shape=shape, 

388 ) 

389 if shape is not None and stride is not None: 

390 layout = cp.deepcopy(layout) 

391 layout.set_tensor_meta(shape, stride, local_tensor.dtype) 

392 return DTensor( 

393 local_tensor, 

394 device_mesh, 

395 layout.placements, 

396 layout, 

397 shape=shape, 

398 ) 

399 

400 @staticmethod 

401 def from_local_with_layout( 

402 local_tensor: Tensor, 

403 layout: Layout, 

404 *, 

405 shape: Optional[Tuple[int, ...]] = None, 

406 ) -> 'DTensor': 

407 """Fast DTensor construction from a local tensor and a pre-built Layout. 

408 

409 Unlike :meth:`from_local`, this does NOT rebuild the layout via 

410 ``_build_layout`` — it hands the already-built ``layout`` straight to 

411 ``__init_data__``. Intended for hot paths (e.g. ``wrap_output``) where the 

412 output Layout was already inferred and cached by ``infer_layout``, so 

413 recomputing ``device_mesh.to_hash()`` / ``tuple(placements)`` / the layout 

414 cache lookup on every output is pure waste. 

415 

416 ``layout.placements`` (a plain attribute) is passed only to satisfy the 

417 constructor's non-None check; ``__init_data__`` ignores it when ``layout`` 

418 is supplied. 

419 """ 

420 return DTensor( 

421 local_tensor, 

422 layout.mesh, 

423 layout.placements, 

424 layout, 

425 shape=shape, 

426 ) 

427 

428 def _alias_placements(self) -> Sequence[Placement]: 

429 """Return alias_placements from layout, falling back to _placements.""" 

430 if hasattr(self, '_layout') and self._layout: 

431 return self._layout.alias_placements 

432 return self._placements 

433 

434 def _new_with_same_layout(self, local_tensor: Tensor) -> 'DTensor': 

435 """Create a DTensor from a local shard while retaining the logical layout. 

436 

437 The shard must retain its logical shape. Layout metadata is copied so 

438 updating its dtype cannot mutate the source. Parameter inputs produce 

439 plain DTensors. This method does not copy tensor data or communicate. 

440 """ 

441 tensor_type = DTensor if isinstance(self, torch.nn.Parameter) else self.__class__ 

442 if not isinstance(self._layout, Layout): 

443 constructor_kwargs = { 

444 "device_mesh": self._device_mesh, 

445 "placements": self._alias_placements(), 

446 } 

447 if hasattr(self, "_global_shape"): 

448 constructor_kwargs["shape"] = self._global_shape 

449 return tensor_type(local_tensor, **constructor_kwargs) 

450 layout = cp.deepcopy(self._layout) 

451 if layout.tensor_shape is not None: 

452 layout.set_tensor_meta( 

453 layout.tensor_shape, 

454 layout.tensor_stride, 

455 local_tensor.dtype, 

456 ) 

457 return tensor_type( 

458 local_tensor, 

459 device_mesh=self._device_mesh, 

460 placements=layout.placements, 

461 layout=layout, 

462 shape=self._global_shape, 

463 ) 

464 

465 def detach(self) -> "DTensor": 

466 """Detach autograd while retaining the complete logical layout. 

467 

468 The result shares local storage and owns its layout metadata so dtype 

469 conversions cannot change the source. Uneven shards need this metadata 

470 when optimizer moments are initialized with ``zeros_like`` during resume. 

471 """ 

472 if self.layout is None: 

473 return super().detach() 

474 return self._new_with_same_layout(self._local_tensor.detach()) 

475 

476 def to(self, *args, **kwargs): 

477 """Move the DTensor to a different device or dtype. 

478 

479 Delegates to the underlying local tensor's ``to`` method and 

480 reconstructs a DTensor preserving device_mesh and placements. 

481 

482 Args: 

483 *args (tuple): Arguments passed to the underlying tensor's ``to`` 

484 method (e.g., device or dtype). 

485 **kwargs (dict): Keyword arguments for the tensor conversion 

486 (e.g., dtype, device, non_blocking). 

487 

488 Returns: 

489 DTensor: A new DTensor with the converted local tensor. 

490 """ 

491 new_local = self._local_tensor.to(*args, **kwargs) 

492 return self._new_with_same_layout(new_local) 

493 

494 def float(self): 

495 """Convert the DTensor to float dtype. 

496 

497 Returns: 

498 DTensor: A new DTensor with float32 local tensor. 

499 """ 

500 new_local = self._local_tensor.float() 

501 return self._new_with_same_layout(new_local) 

502 

503 def type_as(self, other: Tensor) -> "DTensor": 

504 """Cast this DTensor to the dtype of ``other``. 

505 

506 This is a **local** operation — no communication. Each shard 

507 independently casts its elements to the target dtype. 

508 

509 Only the **dtype** of ``other`` is read; its shape, values, and 

510 layout are ignored. The returned DTensor preserves the 

511 device-mesh and placements of ``self`` unchanged. 

512 

513 Args: 

514 other (Tensor): A tensor whose ``.dtype`` will be used as the 

515 target type. May be a plain :class:`Tensor` or a 

516 :class:`DTensor`. Must reside on the same device as 

517 ``self``. 

518 

519 Returns: 

520 DTensor: A new DTensor with the converted local tensor. When 

521 ``self.dtype == other.dtype`` the method returns ``self`` 

522 unchanged (no-op). 

523 

524 Raises: 

525 ValueError: If ``other`` is not a Tensor. 

526 ValueError: If ``self`` has Partial placement (cast does not 

527 commute with reduction). 

528 ValueError: If ``self`` and ``other`` are on different devices. 

529 

530 Note: 

531 This implementation intentionally covers **dtype-only** 

532 conversion. PyTorch's native ``type_as`` may also handle 

533 cross-device transfers, but a DTensor cannot silently change 

534 its backend device while retaining the old ``DeviceMesh``. 

535 Use :meth:`to` for explicit device + dtype conversion. 

536 

537 Example: 

538 >>> # x is a DTensor of float16, y is a plain float32 Tensor 

539 >>> # on the same device. 

540 >>> z = x.type_as(y) 

541 >>> z.dtype == y.dtype 

542 True 

543 """ 

544 if not isinstance(other, Tensor): 

545 raise ValueError( 

546 f"type_as() argument must be a Tensor, but got " 

547 f"{type(other).__name__}." 

548 ) 

549 if hasattr(self, '_layout') and self._layout is not None: 

550 if self._layout.is_partial(): 

551 raise ValueError( 

552 "DTensor.type_as does not support Partial input; " 

553 "call reduce_partial() first." 

554 ) 

555 

556 other_local = other.to_local() if isinstance(other, DTensor) else other 

557 if self._local_tensor.device != other_local.device: 

558 raise ValueError( 

559 "DTensor.type_as requires self and other to be on the " 

560 "same device. Use to() for explicit device + dtype " 

561 "conversion." 

562 ) 

563 

564 target_dtype = other.dtype 

565 if self.dtype == target_dtype: 

566 return self 

567 new_local = self._local_tensor.to(dtype=target_dtype) 

568 return self._new_with_same_layout(new_local) 

569 

570 def _validate_factory_device(self, device: Any) -> None: 

571 """Raise :class:`ValueError` if ``device`` does not match the DTensor's device.""" 

572 requested_type, requested_index = _device_spec(device) 

573 local_type, local_index = _device_spec(self._local_tensor.device) 

574 if ( 

575 requested_type != local_type 

576 or ( 

577 requested_index is not None 

578 and requested_index != local_index 

579 ) 

580 ): 

581 raise ValueError( 

582 f"DTensor requires device to match the input DTensor " 

583 f"device {self._local_tensor.device}, but got {device}." 

584 ) 

585 

586 def _new_const_tensor_op( 

587 self, 

588 method_name: str, 

589 size: Union[int, Sequence[int]], 

590 *, 

591 dtype: Optional[Any] = None, 

592 device: Optional[Any] = None, 

593 requires_grad: bool = False, 

594 layout: Optional[Any] = None, 

595 pin_memory: bool = False, 

596 ) -> 'DTensor': 

597 """Create an all-``Replicate`` constant DTensor. 

598 

599 Shared implementation for ``new_zeros`` and ``new_ones``. 

600 

601 ``self`` is only used as a dtype/device reference and mesh source; 

602 its values are ignored. The output is always **all-Replicate** 

603 because every device produces identical data independently. 

604 

605 Args: 

606 method_name: 

607 ``"new_zeros"`` or ``"new_ones"`` — the local tensor 

608 factory method to call. 

609 size: 

610 Output shape — an int or a sequence of ints. 

611 dtype: 

612 Desired dtype. Defaults to ``self.dtype`` on Torch. 

613 device: 

614 Must match ``self``'s device. 

615 requires_grad: 

616 If true, the new tensor requires gradient. 

617 layout: 

618 Desired layout of the new tensor. 

619 pin_memory: 

620 If true, the new tensor is allocated in pinned memory. 

621 

622 Returns: 

623 DTensor: A new DTensor with all-``Replicate`` placements on 

624 ``self``'s ``DeviceMesh``. 

625 

626 Raises: 

627 ValueError: If ``device`` does not match the DTensor's device. 

628 """ 

629 if isinstance(size, int): 

630 size = (size,) 

631 

632 local_kwargs = {} 

633 if dtype is not None: 

634 local_kwargs["dtype"] = dtype 

635 if device is not None: 

636 self._validate_factory_device(device) 

637 # An unindexed device such as "cuda" resolves to the framework's 

638 # current device, which may differ from this DTensor's local device. 

639 local_kwargs["device"] = self._local_tensor.device 

640 if requires_grad: 

641 local_kwargs["requires_grad"] = True 

642 if layout is not None: 

643 local_kwargs["layout"] = layout 

644 if pin_memory: 

645 local_kwargs["pin_memory"] = True 

646 

647 factory = getattr(self._local_tensor, method_name) 

648 local_result = factory(size, **local_kwargs) 

649 

650 replicated_placements = [Replicate()] * self._device_mesh.ndim 

651 return DTensor.from_local( 

652 local_result, self._device_mesh, replicated_placements, 

653 ) 

654 

655 def new_zeros( 

656 self, 

657 size: Union[int, Sequence[int]], 

658 *, 

659 dtype: Optional[Any] = None, 

660 device: Optional[Any] = None, 

661 requires_grad: bool = False, 

662 layout: Optional[Any] = None, 

663 pin_memory: bool = False, 

664 ) -> 'DTensor': 

665 """Create an all-Replicate DTensor filled with zeros. 

666 

667 The output is always **fully replicated** across every device in 

668 ``self``'s ``DeviceMesh``, regardless of how ``self`` is sharded. 

669 

670 Args: 

671 size: 

672 Output shape — an int or a sequence of ints. 

673 dtype: 

674 Desired dtype. Defaults to ``self.dtype``. 

675 device: 

676 Must match ``self``'s device. 

677 requires_grad: 

678 If true, the new tensor requires gradient. 

679 layout: 

680 Desired layout of the new tensor. 

681 pin_memory: 

682 If true, the new tensor is allocated in pinned memory. 

683 

684 Returns: 

685 DTensor: A new all-Replicate DTensor filled with zeros. 

686 """ 

687 return self._new_const_tensor_op( 

688 "new_zeros", size, 

689 dtype=dtype, 

690 device=device, 

691 requires_grad=requires_grad, 

692 layout=layout, 

693 pin_memory=pin_memory, 

694 ) 

695 

696 def new_ones( 

697 self, 

698 size: Union[int, Sequence[int]], 

699 *, 

700 dtype: Optional[Any] = None, 

701 device: Optional[Any] = None, 

702 requires_grad: bool = False, 

703 layout: Optional[Any] = None, 

704 pin_memory: bool = False, 

705 ) -> 'DTensor': 

706 """Create an all-Replicate DTensor filled with ones. 

707 

708 The output is always **fully replicated** across every device in 

709 ``self``'s ``DeviceMesh``, regardless of how ``self`` is sharded. 

710 

711 Args: 

712 size: 

713 Output shape — an int or a sequence of ints. 

714 dtype: 

715 Desired dtype. Defaults to ``self.dtype``. 

716 device: 

717 Must match ``self``'s device. 

718 requires_grad: 

719 If true, the new tensor requires gradient. 

720 layout: 

721 Desired layout of the new tensor. 

722 pin_memory: 

723 If true, the new tensor is allocated in pinned memory. 

724 

725 Returns: 

726 DTensor: A new all-Replicate DTensor filled with ones. 

727 """ 

728 return self._new_const_tensor_op( 

729 "new_ones", size, 

730 dtype=dtype, 

731 device=device, 

732 requires_grad=requires_grad, 

733 layout=layout, 

734 pin_memory=pin_memory, 

735 ) 

736 

737 def to_local(self) -> Tensor: 

738 """ 

739 Convert DTensor to local tensor. 

740 

741 Returns: 

742 Tensor: The local tensor shard on this device. 

743 """ 

744 return self._local_tensor 

745 

746 def tolist(self): 

747 """ 

748 Convert the DTensor to a nested Python list or number. 

749 

750 This operation gathers the complete tensor on every participating rank 

751 before converting it to Python values. It is an **implicit collective**: 

752 all ranks in the DeviceMesh must participate. 

753 

754 Returns: 

755 Union[list, int, float, bool]: A nested Python list, or a Python 

756 number for a scalar DTensor. 

757 

758 Note: 

759 This triggers ``full_tensor()`` under the hood, which performs 

760 all-gather communication. For large tensors, prefer slicing or 

761 index-based access to avoid materialising the full tensor. 

762 

763 If you only need the **local shard** as a list, use 

764 ``dtensor.to_local().tolist()`` instead — that path has zero 

765 communication overhead. 

766 

767 Example: 

768 >>> mesh = init_device_mesh("npu", (2,), ("dp",)) 

769 >>> x = distribute_tensor(torch.arange(8).reshape(4, 2), mesh, [Shard(0)]) 

770 >>> x.tolist() # full data: [[0,1],[2,3],[4,5],[6,7]] 

771 >>> x.to_local().tolist() # local shard only (no comm) 

772 """ 

773 return self.full_tensor().tolist() 

774 

775 def copy_(self, src: "DTensor", non_blocking: bool = False) -> "DTensor": 

776 """In-place copy of ``src`` into this DTensor's local shard. 

777 

778 Delegates to ``Tensor.copy_`` on the underlying local tensors. 

779 Follows standard ``Tensor.copy_`` semantics: version counter is 

780 bumped and autograd edges are created when grad is enabled. 

781 

782 Constraints on ``src``: 

783 * must be a ``DTensor`` on the same or an equivalent ``DeviceMesh`` topology as ``self``; 

784 * its placements must equal ``self.placements``, OR 

785 ``src._local_tensor.numel() == 1`` (single-element broadcast); 

786 * its local shape must equal or be broadcastable to 

787 ``self._local_tensor.shape``. 

788 

789 No redistribute / implicit slicing is performed; src dtype is cast 

790 to self dtype in-place. 

791 

792 Args: 

793 src (DTensor): Source DTensor satisfying the constraints above. 

794 non_blocking (bool): Forwarded to the underlying ``copy_``. 

795 

796 Returns: 

797 DTensor: ``self``. 

798 

799 Raises: 

800 TypeError: if ``src`` is not a ``DTensor``. 

801 ValueError: if mesh, placement, or shape constraint is violated. 

802 """ 

803 if not isinstance(src, DTensor): 

804 raise TypeError( 

805 f"For DTensor.copy_, src should be a DTensor, but got {type(src).__name__}." 

806 ) 

807 src_local = src.to_local() 

808 if not _device_meshes_are_compatible(src.device_mesh, self._device_mesh): 

809 raise ValueError( 

810 f"For DTensor.copy_, src and self should share the same DeviceMesh, " 

811 f"but got src.device_mesh={src.device_mesh!r}, " 

812 f"self._device_mesh={self._device_mesh!r}." 

813 ) 

814 

815 placement_eq = tuple(src.placements) == tuple(self._placements) 

816 shape_eq = src_local.shape == self._local_tensor.shape 

817 src_is_scalar = src_local.numel() == 1 

818 

819 if not placement_eq and not src_is_scalar: 

820 raise ValueError( 

821 f"For DTensor.copy_, src.placements should equal self.placements " 

822 f"or src.numel() should be 1, but got " 

823 f"src.placements={src.placements}, " 

824 f"self.placements={self._placements}, " 

825 f"src.numel()={src_local.numel()}." 

826 ) 

827 if not shape_eq and not src_is_scalar and not _is_broadcastable( 

828 src_local.shape, self._local_tensor.shape 

829 ): 

830 raise ValueError( 

831 f"For DTensor.copy_, src local shape should be broadcastable to " 

832 f"self local shape, but got " 

833 f"src.shape={tuple(src_local.shape)}, " 

834 f"self.shape={tuple(self._local_tensor.shape)}." 

835 ) 

836 

837 self._local_tensor.copy_(src_local, non_blocking=non_blocking) 

838 return self 

839 

840 def zero_(self) -> "DTensor": 

841 """In-place fill with zeros. Returns ``self``.""" 

842 self._local_tensor.zero_() 

843 return self 

844 

845 def fill_(self, value) -> "DTensor": 

846 """In-place fill with ``value``. Returns ``self``.""" 

847 self._local_tensor.fill_(value) 

848 return self 

849 

850 @property 

851 def shape(self) -> Tuple[int, ...]: 

852 """ 

853 The global shape of this DTensor. 

854 

855 Returns: 

856 Tuple[int, ...]: The global tensor shape. 

857 """ 

858 return self._global_shape 

859 

860 def size(self, dim=None): 

861 """Return the global shape, consistent with .shape. 

862 

863 Without ``dim`` returns a tuple matching ``self.shape``. 

864 With ``dim`` returns the size of that dimension. 

865 """ 

866 global_shape = self.shape 

867 if dim is not None: 

868 return global_shape[dim] 

869 return global_shape 

870 

871 def numel(self) -> int: 

872 """Return the number of elements in this DTensor.""" 

873 return int(np.prod(self.shape)) 

874 

875 @property 

876 def ndim(self) -> int: 

877 """Return the logical global tensor rank.""" 

878 return len(self._global_shape) 

879 

880 def dim(self) -> int: 

881 """Return the logical global tensor rank.""" 

882 return len(self._global_shape) 

883 

884 @property 

885 def local_shape(self) -> Tuple[int, ...]: 

886 """ 

887 The local shape of this DTensor on this device. 

888 

889 Returns: 

890 Tuple[int, ...]: The local tensor shape. 

891 """ 

892 return self._local_tensor.shape 

893 

894 def redistribute( 

895 self, 

896 device_mesh: DeviceMesh, 

897 placements: Union[Sequence[Placement], Sequence[Union[str, Tuple[str, ...]]]] 

898 ) -> 'DTensor': 

899 """ 

900 Redistribute this DTensor to a new device mesh and placements. 

901 

902 Args: 

903 device_mesh (DeviceMesh): The target device mesh. 

904 placements: The target placements. Supports Placement objects 

905 or alias strings. 

906 

907 Returns: 

908 DTensor: A new DTensor with the specified distribution. 

909 

910 Example: 

911 >>> new_dtensor = dtensor.redistribute(mesh, [Replicate(), Shard(1)]) 

912 >>> new_dtensor = dtensor.redistribute(mesh, ("None", "tp")) 

913 """ 

914 logger.debug( 

915 "redistribute: shape=%s, src_placements=%s -> dst_placements=%s, " 

916 "mesh_shape=%s, local_shape=%s", 

917 tuple(self.shape), 

918 tuple(self._placements), 

919 tuple(placements), 

920 tuple(device_mesh.shape), 

921 tuple(self._local_tensor.shape), 

922 ) 

923 

924 # Build dst_layout from device_mesh and placements 

925 dst_layout = _build_layout( 

926 device_mesh, placements, len(self._global_shape) 

927 ) 

928 

929 # pylint: disable=C0415 

930 from hyper_parallel.core.dtensor.tensor_redistribution import _tensor_redistribution 

931 out = _tensor_redistribution.redistribution(self, dst_layout) 

932 return out 

933 

934 def reduce_partial(self) -> 'DTensor': 

935 """ 

936 Reduce partial sharding state for this DTensor. 

937 

938 Returns: 

939 DTensor: A new DTensor with partial state reduced. 

940 """ 

941 if not self._layout: 

942 return self 

943 to_layout = cp.deepcopy(self._layout) 

944 to_layout.reset_partial() 

945 # pylint: disable=C0415 

946 from hyper_parallel.core.dtensor.tensor_redistribution import _tensor_redistribution 

947 out = _tensor_redistribution.reduce_partial(self, to_layout) 

948 return out 

949 

950 def full_tensor(self) -> Tensor: 

951 """ 

952 Return the full tensor of this DTensor. 

953 

954 Returns: 

955 Tensor: A Tensor object that represents the full tensor of this DTensor. 

956 The returned tensor contains the complete data gathered from 

957 all ranks. 

958 

959 Note: 

960 This operation involves communication across all ranks in the DeviceMesh, 

961 which may be expensive for large tensors. Use with caution in 

962 performance-critical code paths. 

963 

964 Example: 

965 >>> # Assume dtensor is sharded across multiple devices 

966 >>> local_tensor = dtensor.to_local() # Returns only the local shard 

967 >>> full_tensor = dtensor.full_tensor() # Returns the complete tensor 

968 """ 

969 if not self._layout: 

970 return self._local_tensor 

971 

972 # Create a fully replicated layout 

973 replicated_layout = cp.deepcopy(self._layout) 

974 

975 # Set all placements to Replicate and convert to tensor_map 

976 replicated_placements = [Replicate()] * len(replicated_layout.mesh_shape) 

977 replicated_layout.set_placements(replicated_placements) 

978 replicated_layout.placement_to_tensor_map(len(self._global_shape)) 

979 

980 # Clear partial status from original layout since Replicate has no partial 

981 replicated_layout.reset_partial() 

982 

983 # Redistribute to the replicated layout and return local tensor 

984 # pylint: disable=C0415 

985 from hyper_parallel.core.dtensor.tensor_redistribution import _tensor_redistribution 

986 out = _tensor_redistribution.redistribution(self, replicated_layout) 

987 return out.to_local() 

988 

989 

990def _register_optimizer_tensor_type() -> None: 

991 """Enable PyTorch optimizer and foreach selection for DTensor.""" 

992 for supported_types in (_optimizer_foreach_supported_types, _torch_foreach_supported_types): 

993 if DTensor not in supported_types: 

994 supported_types.append(DTensor) 

995 

996 

997_register_optimizer_tensor_type() 

998 

999 

1000def _normalize_shard_dim(dim: int, ndim: int) -> int: 

1001 return dim + ndim if dim < 0 else dim 

1002 

1003 

1004def _distribute_tensor_with_communication( 

1005 tensor: Tensor, 

1006 device_mesh: DeviceMesh, 

1007 placements: Sequence[Placement], 

1008 src_data_rank: int, 

1009) -> Tensor: 

1010 """Scatter/broadcast a logical global tensor along mesh dimensions (PyTorch parity).""" 

1011 local = tensor 

1012 if len(placements) < device_mesh.ndim: 

1013 raise ValueError( 

1014 f"placements length ({len(placements)}) must be at least device_mesh.ndim " 

1015 f"({device_mesh.ndim}) when src_data_rank is set" 

1016 ) 

1017 for mesh_dim in range(device_mesh.ndim): 

1018 placement = placements[mesh_dim] 

1019 if isinstance(placement, StridedShard): 

1020 raise NotImplementedError( 

1021 "distribute_tensor with src_data_rank does not support StridedShard yet; " 

1022 "pass src_data_rank=None for local-only sharding." 

1023 ) 

1024 if placement.is_shard(): 

1025 shard_dim = _normalize_shard_dim(placement.dim, local.ndim) 

1026 num_chunks = device_mesh.size(mesh_dim) 

1027 if num_chunks <= 0: 

1028 raise ValueError(f"invalid mesh dim size {num_chunks} on mesh_dim={mesh_dim}") 

1029 chunks = tuple(local.chunk(num_chunks, dim=shard_dim)) 

1030 if not chunks: 

1031 raise ValueError(f"cannot shard dim {shard_dim} into {num_chunks} chunks") 

1032 output = torch.empty_like(chunks[0]) 

1033 local = mesh_scatter(output, chunks, device_mesh, mesh_dim, group_src=src_data_rank) 

1034 elif placement.is_replicate() or placement.is_partial(): 

1035 local = mesh_broadcast(local, device_mesh, mesh_dim, group_src=src_data_rank) 

1036 if isinstance(placement, Partial): 

1037 warnings.warn( 

1038 f"Partial placement {placement} during distribute_tensor: " 

1039 "broadcast only; partial partition is not applied yet.", 

1040 stacklevel=3, 

1041 ) 

1042 else: 

1043 raise RuntimeError( 

1044 f"unsupported placement {placement} on device mesh dimension {mesh_dim}" 

1045 ) 

1046 return local 

1047 

1048 

1049def distribute_tensor( 

1050 tensor: Tensor, 

1051 device_mesh: DeviceMesh, 

1052 placements: Union[Sequence[Placement], Sequence[Union[str, Tuple[str, ...]]]], 

1053 *, 

1054 src_data_rank: Optional[int] = None, 

1055) -> DTensor: 

1056 """ 

1057 Distribute a global tensor to the device mesh according to the placements. 

1058 

1059 Args: 

1060 tensor (Tensor): The global tensor to be distributed. All ranks 

1061 should have the same tensor data. 

1062 device_mesh (DeviceMesh): The device mesh describing the device topology. 

1063 placements: The placement strategy. Supports two styles: 

1064 - Placement objects (e.g., ``[Shard(0), Replicate()]``). 

1065 - Alias strings (e.g., ``("dp", "None")`` or 

1066 ``(("dp", "tp"), "None")``), length must equal the number of 

1067 tensor dimensions. 

1068 

1069 Returns: 

1070 DTensor: A new DTensor with the local shard on each rank. 

1071 

1072 Note: 

1073 When ``src_data_rank`` is an ``int`` (e.g. ``0``), shard/replicate 

1074 placements use scatter/broadcast from the source rank on each mesh axis, 

1075 matching PyTorch ``distribute_tensor``. When ``src_data_rank=None`` 

1076 (default), each rank slices its local tensor without communication 

1077 (legacy Hyper behavior; all ranks must hold the same global data). 

1078 

1079 Example: 

1080 >>> mesh = init_device_mesh(device_type="npu", mesh_shape=(2, 2), mesh_dim_names=("dp", "tp")) 

1081 >>> global_tensor = Tensor(np.arange(16).reshape(4, 4)) 

1082 >>> dtensor = distribute_tensor(global_tensor, mesh, [Shard(0), Replicate()]) 

1083 >>> dtensor = distribute_tensor(global_tensor, mesh, ("dp", "None")) 

1084 """ 

1085 layout = _build_layout(device_mesh, placements, len(tensor.shape)) 

1086 if _layout_has_ragged_shard(layout): 

1087 if src_data_rank is None: 

1088 local_tensor = _slice_ragged_tensor(tensor, layout) 

1089 else: 

1090 local_tensor = _scatter_ragged_tensor(tensor, layout, src_data_rank) 

1091 elif src_data_rank is None: 

1092 local_tensor = _get_slice_tensor_by_layout(tensor, layout) 

1093 else: 

1094 local_tensor = _distribute_tensor_with_communication( 

1095 tensor, device_mesh, layout.placements, src_data_rank 

1096 ) 

1097 return DTensor.from_local_with_layout( 

1098 local_tensor, 

1099 layout, 

1100 shape=tuple(tensor.shape), 

1101 ) 

1102 

1103 

1104def _distribute_module_param_source(param: Any) -> Tensor: 

1105 """Tensor data used as the global tensor for :func:`distribute_tensor`.""" 

1106 return param.data 

1107 

1108 

1109def _distribute_module_new_parameter(key: str, dtensor: DTensor, requires_grad: bool) -> Any: 

1110 """Build a :class:`torch.nn.Parameter` holding *dtensor*.""" 

1111 del key 

1112 return torch.nn.Parameter(dtensor, requires_grad=requires_grad) 

1113 

1114 

1115def _distribute_module_set_param(module: Any, key: str, new_param: Any) -> None: 

1116 """Register or assign a parameter on *module* (``nn.Module``-like).""" 

1117 if hasattr(module, "register_parameter"): 

1118 module.register_parameter(key, new_param) 

1119 return 

1120 if hasattr(module, "_params"): 

1121 module._params[key] = new_param 

1122 if hasattr(module, "_params_list"): 

1123 module._params_list[key] = new_param 

1124 if key in module.__dict__: 

1125 module.__dict__[key] = new_param 

1126 return 

1127 raise TypeError( 

1128 f"distribute_module expects nn.Module-like objects with register_parameter or _params; " 

1129 f"got {type(module)}." 

1130 ) 

1131 

1132 

1133def _distribute_module_iter_params(module: Any) -> list: 

1134 """Return ``[(name, param), ...]`` for direct parameters (``_parameters`` or ``_params``).""" 

1135 if hasattr(module, "_parameters"): 

1136 return list(module._parameters.items()) 

1137 if hasattr(module, "_params"): 

1138 return list(module._params.items()) 

1139 return [] 

1140 

1141 

1142def _distribute_module_iter_buffers(module: Any) -> list: 

1143 """Return ``[(name, buffer), ...]`` if the module has ``_buffers`` (PyTorch ``nn.Module``).""" 

1144 if hasattr(module, "_buffers"): 

1145 return list(module._buffers.items()) 

1146 return [] 

1147 

1148 

1149def _distribute_module_named_modules(module: Any): 

1150 """``nn.Module.named_modules`` (submodule FQNs).""" 

1151 if hasattr(module, "named_modules"): 

1152 return module.named_modules() 

1153 raise TypeError( 

1154 f"distribute_module expects a module-like object with named_modules; " 

1155 f"got {type(module)}." 

1156 ) 

1157 

1158 

1159def _distribute_module_named_parameters(module: Any): 

1160 """``nn.Module.named_parameters(recurse=False)``.""" 

1161 if hasattr(module, "named_parameters"): 

1162 return module.named_parameters(recurse=False) 

1163 raise TypeError( 

1164 f"distribute_module expects a module-like object with named_parameters; " 

1165 f"got {type(module)}." 

1166 ) 

1167 

1168 

1169def _replicate_submodule_params_buffers( 

1170 sub_mod: Any, 

1171 device_mesh: DeviceMesh, 

1172 *, 

1173 module_prefix: str = "", 

1174) -> None: 

1175 """Convert plain params/buffers on *sub_mod* to fully replicated :class:`DTensor`.""" 

1176 full_replicate = [Replicate()] * device_mesh.ndim 

1177 for key, param in _distribute_module_iter_params(sub_mod): 

1178 if param is None or isinstance(param, DTensorBase): 

1179 continue 

1180 src = _distribute_module_param_source(param) 

1181 requires_grad = bool(getattr(param, "requires_grad", True)) 

1182 dt = distribute_tensor(src, device_mesh, full_replicate) 

1183 param_name = f"{module_prefix}.{key}" if module_prefix else key 

1184 new_param = _distribute_module_new_parameter(param_name, dt, requires_grad) 

1185 _distribute_module_set_param(sub_mod, key, new_param) 

1186 for key, buffer in _distribute_module_iter_buffers(sub_mod): 

1187 if buffer is None or isinstance(buffer, DTensorBase): 

1188 continue 

1189 sub_mod._buffers[key] = distribute_tensor(buffer, device_mesh, full_replicate) 

1190 

1191 

1192def _distribute_module_run_partition_and_replicate( 

1193 module: Any, 

1194 device_mesh: DeviceMesh, 

1195 partition_fn: Optional[Callable[[str, Any, DeviceMesh], None]], 

1196) -> None: 

1197 """Call optional ``partition_fn`` per ``named_modules`` and replicate remaining tensors.""" 

1198 if partition_fn is None: 

1199 for mod_name, submod in _distribute_module_named_modules(module): 

1200 _replicate_submodule_params_buffers(submod, device_mesh, module_prefix=mod_name) 

1201 return 

1202 for mod_name, submod in _distribute_module_named_modules(module): 

1203 partition_fn(mod_name, submod, device_mesh) 

1204 _replicate_submodule_params_buffers(submod, device_mesh, module_prefix=mod_name) 

1205 

1206 

1207def _distribute_module_register_input_fn( 

1208 module: Any, 

1209 device_mesh: DeviceMesh, 

1210 input_fn: Callable[..., Any], 

1211) -> None: 

1212 """Register *input_fn* as a forward pre-hook on *module* (2- or 3-arg, PyTorch-compatible).""" 

1213 num_args = len(inspect.signature(input_fn).parameters) 

1214 if num_args == 2: 

1215 warnings.warn( 

1216 "Deprecating input_fn that takes two arguments (inputs, device_mesh), " 

1217 "please use input_fn that takes in (module, inputs, device_mesh) instead!", 

1218 FutureWarning, 

1219 stacklevel=3, 

1220 ) 

1221 module.register_forward_pre_hook( 

1222 lambda _, inputs: input_fn(inputs, device_mesh) 

1223 ) 

1224 elif num_args == 3: 

1225 module.register_forward_pre_hook( 

1226 lambda mod, inputs: input_fn(mod, inputs, device_mesh) 

1227 ) 

1228 else: 

1229 raise ValueError( 

1230 f"input_fn should take in 2 or 3 arguments, but got {num_args} arguments!" 

1231 ) 

1232 

1233 

1234def _distribute_module_register_output_fn( 

1235 module: Any, 

1236 device_mesh: DeviceMesh, 

1237 output_fn: Callable[..., Any], 

1238) -> None: 

1239 """Register *output_fn* as a forward hook on *module* (2- or 3-arg, PyTorch-compatible).""" 

1240 num_args = len(inspect.signature(output_fn).parameters) 

1241 if num_args == 2: 

1242 warnings.warn( 

1243 "Deprecating output_fn that takes two arguments (outputs, device_mesh), " 

1244 "please use output_fn that takes in (module, outputs, device_mesh) instead!", 

1245 FutureWarning, 

1246 stacklevel=3, 

1247 ) 

1248 module.register_forward_hook( 

1249 lambda mod, inputs, outputs: output_fn(outputs, device_mesh) 

1250 ) 

1251 elif num_args == 3: 

1252 module.register_forward_hook( 

1253 lambda mod, inputs, outputs: output_fn(mod, outputs, device_mesh) 

1254 ) 

1255 else: 

1256 raise ValueError( 

1257 f"output_fn should take in 2 or 3 arguments, but got {num_args} arguments!" 

1258 ) 

1259 

1260 

1261def distribute_module( 

1262 module: Any, 

1263 device_mesh: Optional[DeviceMesh] = None, 

1264 partition_fn: Optional[Callable[[str, Any, DeviceMesh], None]] = None, 

1265 input_fn: Optional[Callable[..., Any]] = None, 

1266 output_fn: Optional[Callable[..., Any]] = None, 

1267) -> Any: 

1268 """PyTorch ``distribute_module`` parity: shard/replicate params and optional I/O hooks. 

1269 

1270 Unsharded parameters and buffers become fully replicated :class:`DTensor` after 

1271 ``partition_fn``. ``input_fn`` / ``output_fn`` attach only to the root *module*. 

1272 

1273 Args: 

1274 module: Root ``nn.Module``. 

1275 device_mesh: Placement mesh; if ``None``, uses ``_mesh_resources.get_current_mesh()``. 

1276 partition_fn: Per ``named_modules`` callback before replicate pass; ``None`` replicates all. 

1277 input_fn: ``(module, inputs, mesh)`` or deprecated ``(inputs, mesh)`` pre-hook. 

1278 output_fn: ``(module, outputs, mesh)`` or deprecated ``(outputs, mesh)`` forward hook. 

1279 

1280 Returns: 

1281 *module* in place, with distributed tensors where applied. 

1282 

1283 Raises: 

1284 RuntimeError: If called twice on the same *module*. 

1285 ValueError: If ``input_fn`` / ``output_fn`` arity is not 2 or 3. 

1286 

1287 Note: 

1288 XLA / ``torch_xla`` is not supported; strided device :class:`DTensor` only. 

1289 """ 

1290 if getattr(module, "_distribute_module_applied", False): 

1291 raise RuntimeError( 

1292 "distribute_module should only be called once on a module, " 

1293 "but it has already been called on this module!" 

1294 ) 

1295 device_mesh = device_mesh or _mesh_resources.get_current_mesh() 

1296 _distribute_module_run_partition_and_replicate(module, device_mesh, partition_fn) 

1297 if input_fn is not None: 

1298 _distribute_module_register_input_fn(module, device_mesh, input_fn) 

1299 if output_fn is not None: 

1300 _distribute_module_register_output_fn(module, device_mesh, output_fn) 

1301 module._distribute_module_applied = True 

1302 return module 

1303 

1304 

1305def _dtensor_init_helper( 

1306 init_op, 

1307 size, 

1308 device_mesh, 

1309 placements, 

1310 *, 

1311 rng_tracked: bool = False, 

1312 **kwargs, 

1313) -> DTensor: 

1314 """ 

1315 Helper function to create and initialize a distributed tensor. 

1316 

1317 Args: 

1318 size: Shape of the tensor. 

1319 dtype: Data type of the tensor. 

1320 device: Target device for the tensor. 

1321 requires_grad: Whether the tensor requires gradient. 

1322 rng_tracked: When ``True``, initialize via :class:`OffsetBasedRNGTracker` 

1323 so shard/replicate random semantics match PyTorch DTensor factories. 

1324 

1325 Returns: 

1326 DTensor: The initialized distributed tensor. 

1327 """ 

1328 global_shape = (size,) if isinstance(size, int) else tuple(size) 

1329 layout = _build_layout(device_mesh, placements, len(global_shape)) 

1330 if _layout_has_ragged_shard(layout): 

1331 raise NotImplementedError( 

1332 "RaggedShard tensor factories are not implemented in the DTensor metadata phase" 

1333 ) 

1334 

1335 # get local tensor shape 

1336 local_shape = compute_local_shape_and_global_offset( 

1337 size, device_mesh, placements 

1338 ) 

1339 

1340 # initialize the local tensor 

1341 if init_op is torch.full: 

1342 fill_value = kwargs.pop("fill_value", 0) 

1343 local_tensor = init_op(local_shape, fill_value, **kwargs) 

1344 elif rng_tracked: 

1345 # pylint: disable=C0415 

1346 from hyper_parallel.core.dtensor.random import is_rng_supported_mesh, OffsetBasedRNGTracker 

1347 from hyper_parallel.core.shard._op_dispatch import _OP_DISPATCHER 

1348 

1349 layout = _build_layout(device_mesh, placements, len(local_shape)) 

1350 if is_rng_supported_mesh(device_mesh): 

1351 if _OP_DISPATCHER._rng_tracker is None: 

1352 _OP_DISPATCHER._rng_tracker = OffsetBasedRNGTracker(run_state_sync=False) 

1353 with _OP_DISPATCHER._rng_tracker._distribute_region( 

1354 device_mesh, 

1355 layout.placements, 

1356 global_shape, 

1357 ): 

1358 local_tensor = init_op(local_shape, **kwargs) 

1359 else: 

1360 local_tensor = init_op(local_shape, **kwargs) 

1361 else: 

1362 local_tensor = init_op(local_shape, **kwargs) 

1363 

1364 return DTensor.from_local( 

1365 local_tensor, 

1366 device_mesh, 

1367 placements, 

1368 ) 

1369 

1370 

1371def ones( 

1372 size, 

1373 device_mesh, 

1374 placements, 

1375) -> DTensor: 

1376 """ 

1377 Returns a :class:`DTensor` filled with the scalar value 1, with the shape defined 

1378 by the variable argument ``size``. 

1379 

1380 Args: 

1381 size (Union[tuple[int], list[int], int, Tensor]): The specified shape of output tensor. Only positive integer or 

1382 tuple or Tensor containing positive integers are allowed. If it is a Tensor, 

1383 it must be a 0-D or 1-D Tensor with int32 or int64 dtypes. 

1384 

1385 Keyword args: 

1386 device_mesh: :class:`DeviceMesh` type, contains the mesh info of ranks 

1387 placements: a sequence of :class:`Placement` type: ``Shard``, ``Replicate`` 

1388 

1389 Returns: 

1390 A :class:`DTensor` object on each rank 

1391 """ 

1392 ones_ = torch.ones 

1393 return _dtensor_init_helper( 

1394 ones_, 

1395 size, 

1396 device_mesh=device_mesh, 

1397 placements=placements, 

1398 ) 

1399 

1400 

1401def empty( 

1402 size, 

1403 device_mesh, 

1404 placements, 

1405) -> DTensor: 

1406 """ 

1407 Returns a :class:`DTensor` filled with uninitialized data. The shape of the :class:`DTensor` 

1408 is defined by the variable argument ``size``. 

1409 

1410 Args: 

1411 size (Union[tuple[int], list[int], int]): The specified shape of output tensor. Can be variable numbers of 

1412 positive integers or tuple or list containing positive integers. 

1413 

1414 Keyword args: 

1415 device_mesh: :class:`DeviceMesh` type, contains the mesh info of ranks 

1416 placements: a sequence of :class:`Placement` type: ``Shard``, ``Replicate`` 

1417 

1418 Returns: 

1419 A :class:`DTensor` object on each rank 

1420 """ 

1421 empty_ = torch.empty 

1422 return _dtensor_init_helper( 

1423 empty_, 

1424 size, 

1425 device_mesh=device_mesh, 

1426 placements=placements, 

1427 ) 

1428 

1429 

1430def full( 

1431 size, 

1432 fill_value, 

1433 *, 

1434 device_mesh, 

1435 placements, 

1436) -> DTensor: 

1437 """ 

1438 Returns a :class:`DTensor` filled with ``fill_value`` according to ``device_mesh`` and 

1439 ``placements``, with the shape defined by the argument ``size``. 

1440 

1441 Args: 

1442 size (Union[tuple[int], list[int]]): The specified shape of output tensor. 

1443 fill_value (Union[numbers.Number, Tensor]): Value to fill the returned tensor. It can be a scalar number, a 0-D 

1444 Tensor, or a 1-D Tensor with only one element. 

1445 

1446 Keyword args: 

1447 device_mesh: :class:`DeviceMesh` type, contains the mesh info of ranks. 

1448 placements: a sequence of :class:`Placement` type: ``Shard``, ``Replicate`` 

1449 

1450 Returns: 

1451 A :class:`DTensor` object on each rank 

1452 """ 

1453 full_ = torch.full 

1454 return _dtensor_init_helper( 

1455 full_, 

1456 size, 

1457 fill_value=fill_value, 

1458 device_mesh=device_mesh, 

1459 placements=placements, 

1460 ) 

1461 

1462 

1463def zeros( 

1464 size, 

1465 device_mesh, 

1466 placements, 

1467) -> DTensor: 

1468 """ 

1469 Returns a :class:`DTensor` filled with the scalar value 0. 

1470 

1471 Args: 

1472 size (Union[tuple[int], list[int], int, Tensor]): The specified shape of output tensor. Only positive integer or 

1473 tuple or Tensor containing positive integers are allowed. If it is a Tensor, 

1474 it must be a 0-D or 1-D Tensor with int32 or int64 dtypes. 

1475 Keyword args: 

1476 device_mesh: :class:`DeviceMesh` type, contains the mesh info of ranks 

1477 placements: a sequence of :class:`Placement` type: ``Shard``, ``Replicate`` 

1478 

1479 Returns: 

1480 A :class:`DTensor` object on each rank 

1481 """ 

1482 zeros_ = torch.zeros 

1483 return _dtensor_init_helper( 

1484 zeros_, 

1485 size, 

1486 device_mesh=device_mesh, 

1487 placements=placements, 

1488 ) 

1489 

1490 

1491def rand( 

1492 size, 

1493 device_mesh, 

1494 placements, 

1495 **kwargs, 

1496) -> DTensor: 

1497 """ 

1498 Returns a :class:`DTensor` filled with random numbers from a uniform 

1499 distribution on ``[0, 1)``. 

1500 

1501 Args: 

1502 size: Global output shape. 

1503 device_mesh: :class:`DeviceMesh` for the distributed layout. 

1504 placements: Per-mesh-dimension :class:`Placement` values. 

1505 **kwargs: Forwarded to :func:`torch.rand` (for example ``dtype``). 

1506 

1507 Returns: 

1508 A :class:`DTensor` object on each rank. 

1509 """ 

1510 return _dtensor_init_helper( 

1511 torch.rand, 

1512 size, 

1513 device_mesh=device_mesh, 

1514 placements=placements, 

1515 rng_tracked=True, 

1516 **kwargs, 

1517 ) 

1518 

1519 

1520def randn( 

1521 size, 

1522 device_mesh, 

1523 placements, 

1524 **kwargs, 

1525) -> DTensor: 

1526 """ 

1527 Returns a :class:`DTensor` filled with random numbers from a normal 

1528 distribution with mean ``0`` and variance ``1``. 

1529 

1530 Args: 

1531 size: Global output shape. 

1532 device_mesh: :class:`DeviceMesh` for the distributed layout. 

1533 placements: Per-mesh-dimension :class:`Placement` values. 

1534 **kwargs: Forwarded to :func:`torch.randn` (for example ``dtype``). 

1535 

1536 Returns: 

1537 A :class:`DTensor` object on each rank. 

1538 """ 

1539 return _dtensor_init_helper( 

1540 torch.randn, 

1541 size, 

1542 device_mesh=device_mesh, 

1543 placements=placements, 

1544 rng_tracked=True, 

1545 **kwargs, 

1546 )