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

308 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-13 05:07 +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"""dtensor""" 

16import copy as cp 

17import inspect 

18import warnings 

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

20 

21import numpy as np 

22 

23from hyper_parallel.core.dtensor.device_mesh import _mesh_resources 

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

25from hyper_parallel.core.dtensor.layout import Layout, DeviceMesh, _get_slice_tensor_by_layout 

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

27from hyper_parallel.platform import get_platform 

28from hyper_parallel.platform.platform import PlatformType 

29from hyper_parallel.core.utils import compute_local_shape_and_global_offset 

30 

31platform = get_platform() 

32DTensorBase = platform.DTensorBase 

33Tensor = platform.Tensor 

34 

35 

36class SkipDTensorDispatch(): 

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

38 

39 Args: 

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

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

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

43 

44 Example: 

45 >>> import torch 

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

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

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

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

50 """ 

51 

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

53 self._no_skip_names: frozenset = frozenset() 

54 if no_skip: 

55 names = set() 

56 for op in no_skip: 

57 if isinstance(op, str): 

58 names.add(op) 

59 else: 

60 names.add(platform.get_op_name(op)) 

61 self._no_skip_names = frozenset(names) 

62 self._dispatch_token = None 

63 self._ops_token = None 

64 

65 def __enter__(self): 

66 # pylint: disable=C0415 

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

68 self._dispatch_token = _dtensor_dispatch_disabled.set(True) 

69 if self._no_skip_names: 

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

71 

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

73 # pylint: disable=C0415 

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

75 if self._ops_token is not None: 

76 _no_skip_ops.reset(self._ops_token) 

77 self._ops_token = None 

78 _dtensor_dispatch_disabled.reset(self._dispatch_token) 

79 self._dispatch_token = None 

80 

81 

82# Cache for _build_layout to avoid redundant Layout computations 

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

84# Value: Layout 

85_LAYOUT_CACHE = {} 

86 

87 

88def _is_alias_placements(placements) -> bool: 

89 """ 

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

91 

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

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

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

95 to be recognized as alias-style. 

96 

97 Args: 

98 placements: A sequence of placement specifications. 

99 

100 Returns: 

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

102 """ 

103 if len(placements) == 0: 

104 return False 

105 for p in placements: 

106 if isinstance(p, str): 

107 continue 

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

109 continue 

110 return False 

111 return True 

112 

113 

114def _build_layout( 

115 device_mesh: DeviceMesh, 

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

117 tensor_dim: int 

118) -> Layout: 

119 """ 

120 Build Layout from device_mesh and placements. 

121 

122 This function uses a cache to avoid redundant Layout computations 

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

124 

125 Args: 

126 device_mesh: The device mesh describing the device topology. 

127 placements: Supports two styles: 

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

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

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

131 tensor_dim: Number of dimensions in the tensor. 

132 

133 Returns: 

134 Layout: The built layout object. 

135 

136 Raises: 

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

138 """ 

139 mesh_key = device_mesh.to_hash() 

140 placements_key = tuple(placements) 

141 cache_key = (mesh_key, placements_key, tensor_dim) 

142 

143 if cache_key in _LAYOUT_CACHE: 

144 return _LAYOUT_CACHE[cache_key] 

145 

146 layout = Layout.from_device_mesh(device_mesh) 

147 

148 if _is_alias_placements(placements): 

149 if len(placements) != tensor_dim: 

150 raise ValueError( 

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

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

153 ) 

154 result = layout(*placements) 

155 else: 

156 result = layout(placements) 

157 result.placement_to_tensor_map(tensor_dim) 

158 

159 _LAYOUT_CACHE[cache_key] = result 

160 

161 return result 

162 

163 

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

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

166 

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

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

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

170 """ 

171 src_shape = tuple(src_shape) 

172 dst_shape = tuple(dst_shape) 

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

174 return False 

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

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

177 if s not in (d, 1): 

178 return False 

179 return True 

180 

181 

182class DTensor(DTensorBase): 

183 """ 

184 DTensor - Distributed Tensor 

185 

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

187 according to a DeviceMesh and placement specifications. 

188 

189 Args: 

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

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

192 placements: The placement strategy. Supports two styles: 

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

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

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

196 tensor dimensions. 

197 

198 Example: 

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

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

201 >>> # Placement style 

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

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

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

205 """ 

206 _local_tensor: Tensor 

207 _device_mesh: DeviceMesh 

208 _placements: Sequence[Placement] 

209 

210 def __init_data__( 

211 self, 

212 local_tensor: Tensor, 

213 device_mesh: DeviceMesh, 

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

215 ): 

216 self._local_tensor = local_tensor 

217 self._device_mesh = device_mesh 

218 self._layout = _build_layout( 

219 device_mesh, placements, len(local_tensor.shape) 

220 ) 

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

222 

223 @property 

224 def device_mesh(self) -> DeviceMesh: 

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

226 return self._device_mesh 

227 

228 @property 

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

230 """The placements of this DTensor.""" 

231 return self._placements 

232 

233 @property 

234 def layout(self) -> Layout: 

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

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

237 return None 

238 return self._layout 

239 

240 @staticmethod 

241 def from_local( 

242 local_tensor: Tensor, 

243 device_mesh: DeviceMesh, 

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

245 ) -> 'DTensor': 

246 """ 

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

248 

249 Args: 

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

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

252 placements: The placement strategy. Supports two styles: 

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

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

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

256 of tensor dimensions. 

257 

258 Returns: 

259 DTensor: A new DTensor instance. 

260 

261 Example: 

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

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

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

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

266 """ 

267 return DTensor(local_tensor, device_mesh, placements) 

268 

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

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

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

272 return self._layout.alias_placements 

273 return self._placements 

274 

275 def _from_converted_local(self, local_tensor: Tensor) -> 'DTensor': 

276 """Rebuild converted DTensor data without preserving Parameter identity.""" 

277 cls = DTensor if isinstance(self, platform.Parameter) else self.__class__ 

278 return cls(local_tensor, device_mesh=self._device_mesh, 

279 placements=self._alias_placements()) 

280 

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

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

283 

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

285 reconstructs a DTensor preserving device_mesh and placements. 

286 

287 Args: 

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

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

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

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

292 

293 Returns: 

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

295 """ 

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

297 return self._from_converted_local(new_local) 

298 

299 def float(self): 

300 """Convert the DTensor to float dtype. 

301 

302 Returns: 

303 DTensor: A new DTensor with float32 local tensor. 

304 """ 

305 new_local = self._local_tensor.float() 

306 return self._from_converted_local(new_local) 

307 

308 def to_local(self) -> Tensor: 

309 """ 

310 Convert DTensor to local tensor. 

311 

312 Returns: 

313 Tensor: The local tensor shard on this device. 

314 """ 

315 return self._local_tensor 

316 

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

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

319 

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

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

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

323 

324 Constraints on ``src``: 

325 * must be a ``DTensor`` on the same ``DeviceMesh`` as ``self``; 

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

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

328 * its local shape must equal or be broadcastable to 

329 ``self._local_tensor.shape``. 

330 

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

332 to self dtype in-place. 

333 

334 Args: 

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

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

337 

338 Returns: 

339 DTensor: ``self``. 

340 

341 Raises: 

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

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

344 """ 

345 if not isinstance(src, DTensor): 

346 raise TypeError( 

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

348 ) 

349 src_local = src.to_local() 

350 if src.device_mesh is not self._device_mesh: 

351 raise ValueError( 

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

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

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

355 ) 

356 

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

358 shape_eq = src_local.shape == self._local_tensor.shape 

359 src_is_scalar = src_local.numel() == 1 

360 

361 if not placement_eq and not src_is_scalar: 

362 raise ValueError( 

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

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

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

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

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

368 ) 

369 if not shape_eq and not src_is_scalar and not _is_broadcastable( 

370 src_local.shape, self._local_tensor.shape 

371 ): 

372 raise ValueError( 

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

374 f"self local shape, but got " 

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

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

377 ) 

378 

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

380 return self 

381 

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

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

384 self._local_tensor.zero_() 

385 return self 

386 

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

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

389 self._local_tensor.fill_(value) 

390 return self 

391 

392 @property 

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

394 """ 

395 The global shape of this DTensor. 

396 

397 Returns: 

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

399 """ 

400 return self._layout.get_global_shape(self._local_tensor.shape) 

401 

402 def size(self, dim=None): 

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

404 

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

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

407 """ 

408 global_shape = self.shape 

409 if dim is not None: 

410 return global_shape[dim] 

411 return global_shape 

412 

413 def numel(self) -> int: 

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

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

416 

417 @property 

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

419 """ 

420 The local shape of this DTensor on this device. 

421 

422 Returns: 

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

424 """ 

425 return self._local_tensor.shape 

426 

427 def redistribute( 

428 self, 

429 device_mesh: DeviceMesh, 

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

431 ) -> 'DTensor': 

432 """ 

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

434 

435 Args: 

436 device_mesh (DeviceMesh): The target device mesh. 

437 placements: The target placements. Supports Placement objects 

438 or alias strings. 

439 

440 Returns: 

441 DTensor: A new DTensor with the specified distribution. 

442 

443 Example: 

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

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

446 """ 

447 # Build dst_layout from device_mesh and placements 

448 dst_layout = _build_layout( 

449 device_mesh, placements, len(self._local_tensor.shape) 

450 ) 

451 

452 # pylint: disable=C0415 

453 from hyper_parallel.core.dtensor.tensor_redistribution import _tensor_redistribution 

454 out = _tensor_redistribution.redistribution(self, dst_layout) 

455 return out 

456 

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

458 """ 

459 Reduce partial sharding state for this DTensor. 

460 

461 Returns: 

462 DTensor: A new DTensor with partial state reduced. 

463 """ 

464 if not self._layout: 

465 return self 

466 to_layout = cp.deepcopy(self._layout) 

467 to_layout.reset_partial() 

468 # pylint: disable=C0415 

469 from hyper_parallel.core.dtensor.tensor_redistribution import _tensor_redistribution 

470 out = _tensor_redistribution.reduce_partial(self, to_layout) 

471 return out 

472 

473 def full_tensor(self) -> Tensor: 

474 """ 

475 Return the full tensor of this DTensor. 

476 

477 Returns: 

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

479 The returned tensor contains the complete data gathered from 

480 all ranks. 

481 

482 Note: 

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

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

485 performance-critical code paths. 

486 

487 Example: 

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

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

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

491 """ 

492 if not self._layout: 

493 return self._local_tensor 

494 

495 # Create a fully replicated layout 

496 replicated_layout = cp.deepcopy(self._layout) 

497 

498 # Set all placements to Replicate and convert to tensor_map 

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

500 replicated_layout.set_placements(replicated_placements) 

501 replicated_layout.placement_to_tensor_map(len(self._local_tensor.shape)) 

502 

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

504 replicated_layout.reset_partial() 

505 

506 # Redistribute to the replicated layout and return local tensor 

507 # pylint: disable=C0415 

508 from hyper_parallel.core.dtensor.tensor_redistribution import _tensor_redistribution 

509 out = _tensor_redistribution.redistribution(self, replicated_layout) 

510 return out.to_local() 

511 

512 

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

514 return dim + ndim if dim < 0 else dim 

515 

516 

517def _distribute_tensor_with_communication( 

518 tensor: Tensor, 

519 device_mesh: DeviceMesh, 

520 placements: Sequence[Placement], 

521 src_data_rank: int, 

522) -> Tensor: 

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

524 local = tensor 

525 if len(placements) < device_mesh.ndim: 

526 raise ValueError( 

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

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

529 ) 

530 for mesh_dim in range(device_mesh.ndim): 

531 placement = placements[mesh_dim] 

532 if isinstance(placement, StridedShard): 

533 raise NotImplementedError( 

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

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

536 ) 

537 if placement.is_shard(): 

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

539 num_chunks = device_mesh.size(mesh_dim) 

540 if num_chunks <= 0: 

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

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

543 if not chunks: 

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

545 output = platform.empty_like(chunks[0]) 

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

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

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

549 if isinstance(placement, Partial): 

550 warnings.warn( 

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

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

553 stacklevel=3, 

554 ) 

555 else: 

556 raise RuntimeError( 

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

558 ) 

559 return local 

560 

561 

562def distribute_tensor( 

563 tensor: Tensor, 

564 device_mesh: DeviceMesh, 

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

566 *, 

567 src_data_rank: Optional[int] = None, 

568) -> DTensor: 

569 """ 

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

571 

572 Args: 

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

574 should have the same tensor data. 

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

576 placements: The placement strategy. Supports two styles: 

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

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

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

580 tensor dimensions. 

581 

582 Returns: 

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

584 

585 Note: 

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

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

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

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

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

591 

592 Example: 

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

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

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

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

597 """ 

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

599 if src_data_rank is None: 

600 local_tensor = _get_slice_tensor_by_layout(tensor, layout) 

601 else: 

602 local_tensor = _distribute_tensor_with_communication( 

603 tensor, device_mesh, layout.placements, src_data_rank 

604 ) 

605 return DTensor(local_tensor, device_mesh, layout.alias_placements) 

606 

607 

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

609 """Tensor data used as the global tensor for :func:`distribute_tensor` (PyTorch uses ``param.data``).""" 

610 if hasattr(param, "data"): 

611 return param.data 

612 return platform.get_param_local_data(param) 

613 

614 

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

616 """Build a framework :class:`Parameter` holding *dtensor* (Torch vs MindSpore kwargs differ).""" 

617 if platform.platform_type == PlatformType.MINDSPORE: 

618 return platform.Parameter(dtensor, name=key, requires_grad=requires_grad) 

619 return platform.Parameter(dtensor, requires_grad=requires_grad) 

620 

621 

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

623 """Register or assign a parameter on *module* (``nn.Module`` or MindSpore ``Cell``).""" 

624 if hasattr(module, "register_parameter"): 

625 module.register_parameter(key, new_param) 

626 return 

627 if hasattr(module, "_params"): 

628 module._params[key] = new_param 

629 if hasattr(module, "_params_list"): 

630 module._params_list[key] = new_param 

631 if key in module.__dict__: 

632 module.__dict__[key] = new_param 

633 return 

634 raise TypeError( 

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

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

637 ) 

638 

639 

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

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

642 if hasattr(module, "_parameters"): 

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

644 if hasattr(module, "_params"): 

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

646 return [] 

647 

648 

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

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

651 if hasattr(module, "_buffers"): 

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

653 return [] 

654 

655 

656def _distribute_module_named_modules(module: Any): 

657 """``nn.Module.named_modules`` or MindSpore ``Cell.cells_and_names`` (submodule FQNs).""" 

658 if hasattr(module, "named_modules"): 

659 return module.named_modules() 

660 if hasattr(module, "cells_and_names"): 

661 return module.cells_and_names() 

662 raise TypeError( 

663 f"distribute_module expects module-like objects with named_modules or cells_and_names; " 

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

665 ) 

666 

667 

668def _replicate_submodule_params_buffers( 

669 sub_mod: Any, 

670 device_mesh: DeviceMesh, 

671 *, 

672 module_prefix: str = "", 

673) -> None: 

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

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

676 for key, param in _distribute_module_iter_params(sub_mod): 

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

678 continue 

679 src = _distribute_module_param_source(param) 

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

681 dt = distribute_tensor(src, device_mesh, full_replicate) 

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

683 new_param = _distribute_module_new_parameter(param_name, dt, requires_grad) 

684 _distribute_module_set_param(sub_mod, key, new_param) 

685 for key, buffer in _distribute_module_iter_buffers(sub_mod): 

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

687 continue 

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

689 

690 

691def _distribute_module_run_partition_and_replicate( 

692 module: Any, 

693 device_mesh: DeviceMesh, 

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

695) -> None: 

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

697 if partition_fn is None: 

698 for mod_name, submod in _distribute_module_named_modules(module): 

699 _replicate_submodule_params_buffers(submod, device_mesh, module_prefix=mod_name) 

700 return 

701 for mod_name, submod in _distribute_module_named_modules(module): 

702 partition_fn(mod_name, submod, device_mesh) 

703 _replicate_submodule_params_buffers(submod, device_mesh, module_prefix=mod_name) 

704 

705 

706def _distribute_module_register_input_fn( 

707 module: Any, 

708 device_mesh: DeviceMesh, 

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

710) -> None: 

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

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

713 if num_args == 2: 

714 warnings.warn( 

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

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

717 FutureWarning, 

718 stacklevel=3, 

719 ) 

720 module.register_forward_pre_hook( 

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

722 ) 

723 elif num_args == 3: 

724 module.register_forward_pre_hook( 

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

726 ) 

727 else: 

728 raise ValueError( 

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

730 ) 

731 

732 

733def _distribute_module_register_output_fn( 

734 module: Any, 

735 device_mesh: DeviceMesh, 

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

737) -> None: 

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

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

740 if num_args == 2: 

741 warnings.warn( 

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

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

744 FutureWarning, 

745 stacklevel=3, 

746 ) 

747 module.register_forward_hook( 

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

749 ) 

750 elif num_args == 3: 

751 module.register_forward_hook( 

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

753 ) 

754 else: 

755 raise ValueError( 

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

757 ) 

758 

759 

760def distribute_module( 

761 module: Any, 

762 device_mesh: Optional[DeviceMesh] = None, 

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

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

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

766) -> Any: 

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

768 

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

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

771 

772 Args: 

773 module: Root ``nn.Module`` or MindSpore ``Cell`` with compatible APIs. 

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

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

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

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

778 

779 Returns: 

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

781 

782 Raises: 

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

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

785 

786 Note: 

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

788 """ 

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

790 raise RuntimeError( 

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

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

793 ) 

794 device_mesh = device_mesh or _mesh_resources.get_current_mesh() 

795 _distribute_module_run_partition_and_replicate(module, device_mesh, partition_fn) 

796 if input_fn is not None: 

797 _distribute_module_register_input_fn(module, device_mesh, input_fn) 

798 if output_fn is not None: 

799 _distribute_module_register_output_fn(module, device_mesh, output_fn) 

800 module._distribute_module_applied = True 

801 return module 

802 

803 

804def _dtensor_init_helper( 

805 init_op, 

806 size, 

807 device_mesh, 

808 placements, 

809 **kwargs, 

810) -> DTensor: 

811 """ 

812 Helper function to create and initialize a distributed tensor. 

813 

814 Args: 

815 size: Shape of the tensor. 

816 dtype: Data type of the tensor. 

817 device: Target device for the tensor. 

818 requires_grad: Whether the tensor requires gradient. 

819 

820 Returns: 

821 DTensor: The initialized distributed tensor. 

822 """ 

823 # get local tensor shape 

824 local_shape = compute_local_shape_and_global_offset( 

825 size, device_mesh, placements 

826 ) 

827 

828 # initialize the local tensor 

829 if init_op is platform.full: 

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

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

832 else: 

833 local_tensor = init_op(local_shape, **kwargs) 

834 

835 return DTensor.from_local( 

836 local_tensor, 

837 device_mesh, 

838 placements, 

839 ) 

840 

841 

842def ones( 

843 size, 

844 device_mesh, 

845 placements, 

846) -> DTensor: 

847 """ 

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

849 by the variable argument ``size``. 

850 

851 Args: 

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

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

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

855 

856 Keyword args: 

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

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

859 

860 Returns: 

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

862 """ 

863 ones_ = platform.ones 

864 return _dtensor_init_helper( 

865 ones_, 

866 size, 

867 device_mesh=device_mesh, 

868 placements=placements, 

869 ) 

870 

871 

872def empty( 

873 size, 

874 device_mesh, 

875 placements, 

876) -> DTensor: 

877 """ 

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

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

880 

881 Args: 

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

883 positive integers or tuple or list containing positive integers. 

884 

885 Keyword args: 

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

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

888 

889 Returns: 

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

891 """ 

892 empty_ = platform.empty 

893 return _dtensor_init_helper( 

894 empty_, 

895 size, 

896 device_mesh=device_mesh, 

897 placements=placements, 

898 ) 

899 

900 

901def full( 

902 size, 

903 fill_value, 

904 *, 

905 device_mesh, 

906 placements, 

907) -> DTensor: 

908 """ 

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

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

911 

912 Args: 

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

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

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

916 

917 Keyword args: 

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

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

920 

921 Returns: 

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

923 """ 

924 full_ = platform.full 

925 return _dtensor_init_helper( 

926 full_, 

927 size, 

928 fill_value=fill_value, 

929 device_mesh=device_mesh, 

930 placements=placements, 

931 ) 

932 

933 

934def zeros( 

935 size, 

936 device_mesh, 

937 placements, 

938) -> DTensor: 

939 """ 

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

941 

942 Args: 

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

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

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

946 Keyword args: 

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

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

949 

950 Returns: 

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

952 """ 

953 zeros_ = platform.zeros 

954 return _dtensor_init_helper( 

955 zeros_, 

956 size, 

957 device_mesh=device_mesh, 

958 placements=placements, 

959 )