Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / mindspore / fully_shard / param.py: 46%

567 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-08-25 04:27 +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"""MindSpore fully_shard parameter lifecycle and gradient communication.""" 

16from dataclasses import dataclass 

17from typing import Any, Callable, List, Optional, Tuple, cast 

18import itertools 

19import mindspore as ms 

20from mindspore import nn 

21from mindspore.common.api import _no_grad 

22from mindspore import Parameter 

23import mindspore.mint.distributed as dist 

24from hyper_parallel.core.fully_shard.utils import ( 

25 MixedPrecisionPolicy, 

26 CPUOffloadPolicy, 

27 OffloadPolicy, 

28 DataParallelMeshInfo, 

29 DDPMeshInfo, 

30 FSDPMeshInfo, 

31 HSDPMeshInfo, 

32 SourceShardMetaInfo, 

33) 

34from hyper_parallel.core.dtensor.device_mesh import DeviceMesh 

35from hyper_parallel.core.dtensor.dtensor import DTensor 

36from hyper_parallel.core.dtensor.layout import Layout 

37from hyper_parallel.core.fully_shard.hsdp_param import HSDPParamV2 

38from hyper_parallel.core.fully_shard.hsdp_utils import ( 

39 ShardedState, 

40 apply_gradient_scaling_factor, 

41 unwrap_dtensor_param, 

42) 

43from hyper_parallel.core.dtensor.placement_types import Replicate, Shard, StridedShard 

44from hyper_parallel.core.fully_shard.hsdp_utils import ParamModuleInfo 

45from hyper_parallel.platform.mindspore.fully_shard._version_utils import copy_without_bumping_version 

46from hyper_parallel.platform.mindspore.utils import normalize_runtime_device 

47from hyper_parallel.platform.mindspore.fully_shard.pack_utils import ( 

48 build_rs_plan, 

49 pack_for_reduce_scatter, 

50 unpack_from_all_gather, 

51) 

52 

53 

54def _pack_for_reduce_scatter(local_tensor: ms.Tensor, shard_dim: int, world_size: int) -> ms.Tensor: 

55 """Pack one local gradient into the row-major reduce-scatter layout. 

56 

57 MindSpore currently aligns with the torch non-comm-fusion V1 path: 

58 

59 - shard on dim 0: identity flatten 

60 - shard on non-dim0: chunk on shard dim, then concatenate on dim 0 

61 """ 

62 if world_size <= 1 or shard_dim == 0: 

63 return local_tensor 

64 chunks = ms.mint.chunk(local_tensor, world_size, dim=shard_dim) 

65 return ms.mint.cat(chunks, dim=0).contiguous() 

66 

67 

68def _to_dtype_if_needed( 

69 tensor: ms.Tensor, dtype: Optional[ms.Type] 

70) -> ms.Tensor: 

71 """Cast tensor to the given dtype if it differs from current dtype.""" 

72 if isinstance(dtype, ms.Type) and tensor.dtype != dtype: 

73 return tensor.to(dtype) 

74 return tensor 

75 

76 

77def make_contiguous_strides_for(shape, row_major=True): 

78 """ 

79 Compute strides for a contiguous tensor of the given shape. 

80 

81 Args: 

82 shape (tuple of int): The shape of the tensor. Each dimension must be a non-negative integer. 

83 row_major (bool): 

84 - If True (default), returns C-style (row-major) strides: last dimension changes fastest. 

85 - If False, returns strides where the last two dimensions are Fortran-style 

86 (i.e., for batched matrix operations in BLAS/LAPACK): second-to-last dim changes fastest. 

87 

88 Returns: 

89 tuple of int: The computed strides. 

90 

91 Examples: 

92 >>> make_contiguous_strides_for((2, 3, 4)) 

93 (12, 4, 1) 

94 >>> make_contiguous_strides_for((2, 3, 4), row_major=False) 

95 (12, 1, 3) 

96 >>> make_contiguous_strides_for((5,)) 

97 (1,) 

98 >>> make_contiguous_strides_for((5,), row_major=False) 

99 (1,) 

100 >>> make_contiguous_strides_for(()) 

101 () 

102 """ 

103 if not isinstance(shape, (tuple, list)): 

104 raise TypeError("shape must be a tuple or list of non-negative integers") 

105 

106 # Validate shape elements 

107 for dim in shape: 

108 if not isinstance(dim, int) or dim < 0: 

109 raise ValueError("All dimensions in shape must be non-negative integers") 

110 

111 if not shape: 

112 return () 

113 

114 # Compute C-style (row-major) strides: stride[i] = product(shape[i+1:]) 

115 strides = [] 

116 multiplier = 1 

117 # Traverse shape in reverse order 

118 for size in reversed(shape): 

119 strides.append(multiplier) 

120 multiplier *= max(size, 1) # handle size=0 gracefully (treat as 1 for stride calc) 

121 

122 # Reverse to get correct order 

123 c_strides = tuple(reversed(strides)) 

124 

125 if row_major: 

126 return c_strides 

127 # For column-major: only affect last two dimensions 

128 if len(shape) < 2: 

129 return c_strides 

130 # In Fortran-style for matrices: 

131 # stride of last dim = 1 

132 # stride of second-to-last dim = shape[-1] 

133 # But note: in batched case (..., M, N), we want strides (..., N, 1) → wait! 

134 # However, the original PyTorch logic returns: result[:-2] + (1, max(shape[-2], 1)) 

135 # Let's follow that exactly: 

136 # Example: shape=(B, M, N) → c_strides=(M*N, N, 1) 

137 # col-major → (M*N, 1, M) 

138 # So: keep all but last two, then (1, shape[-2]) 

139 return c_strides[:-2] + (1, max(shape[-2], 1)) 

140 

141 

142@dataclass 

143class ReduceScatterCommCtx: 

144 """Per-parameter reduce-scatter output and asynchronous work.""" 

145 

146 reduce_scatter_output: Optional[ms.Tensor] = None 

147 reduce_scatter_handle: Optional[Any] = None 

148 

149 

150@dataclass 

151class AllReduceCommCtx: 

152 """Per-parameter all-reduce output and asynchronous work.""" 

153 

154 all_reduce_output: Optional[ms.Tensor] = None 

155 all_reduce_handle: Optional[Any] = None 

156 

157 

158@dataclass 

159class AllGatherCommCtx: 

160 """Per-parameter all-gather buffers and asynchronous work.""" 

161 

162 allgather_input: Optional[ms.Tensor] = None 

163 allgather_output: Optional[ms.Tensor] = None 

164 allgather_handle: Optional[Any] = None 

165 

166 

167class MindSporeHSDPParamV2(HSDPParamV2): 

168 """ 

169 MindSpore HSDP parameter. 

170 """ 

171 

172 def __init__( 

173 self, 

174 param: Parameter, 

175 module_info: ParamModuleInfo, 

176 mesh_info: DataParallelMeshInfo, 

177 shard_placement_fn: Optional[Callable[[Parameter], Optional[Shard]]] = None, 

178 mp_policy: Optional[MixedPrecisionPolicy] = None, 

179 offload_policy: Optional[OffloadPolicy] = None, 

180 device: Optional[str] = None, 

181 source_shard_info: Optional[SourceShardMetaInfo] = None, 

182 ): 

183 self._module_info: ParamModuleInfo = module_info 

184 self.mesh_info = mesh_info 

185 self.mp_policy = mp_policy 

186 self.device = device 

187 self.orig_dtype = None 

188 self.param_dtype = None 

189 self.reduce_dtype = None 

190 self.offload_to_cpu: bool = isinstance(offload_policy, CPUOffloadPolicy) 

191 self.pin_memory = ( 

192 self.offload_to_cpu and cast(CPUOffloadPolicy, offload_policy).pin_memory 

193 ) 

194 self._orig_param_hooks: List[Callable] = [] 

195 self.grad_offload_event: Optional[ms.runtime.Event] = None 

196 dtensor_payload = unwrap_dtensor_param(param) 

197 if (dtensor_payload is not None) != ( 

198 source_shard_info is not None and source_shard_info.origin_is_dtensor 

199 ): 

200 raise ValueError( 

201 "source_shard_info.origin_is_dtensor must be True exactly for native DTensor parameters, " 

202 f"got parameter type {type(param).__name__} and source_shard_info={source_shard_info}" 

203 ) 

204 self.source_shard_info = source_shard_info 

205 self._orig_param_is_dtensor = ( 

206 source_shard_info is not None and source_shard_info.origin_is_dtensor 

207 ) 

208 self._orig_dtensor_mesh = source_shard_info.mesh if self._orig_param_is_dtensor else None 

209 self._orig_dtensor_placements = ( 

210 tuple(source_shard_info.placements) if self._orig_param_is_dtensor else None 

211 ) 

212 self._spmd_shard_mesh_dim = self.mesh_info.shard_mesh_dim 

213 self._spmd_replicate_mesh_dim = self.mesh_info.replicate_mesh_dim 

214 self._init_sharded_param(param, shard_placement_fn) 

215 self._save_backward_hooks(param) 

216 self.unsharded_param_buffers: List[ms.Tensor] = [] 

217 self.unsharded_accumulated_grad = None 

218 self._unsharded_param: Optional[Parameter] = None 

219 self._param_fqn: Optional[str] = None 

220 # Communication attributes for prefetch pattern 

221 self.allgather_comm_ctx = AllGatherCommCtx() 

222 self.reduce_scatter_comm_ctx = ReduceScatterCommCtx() 

223 self.all_reduce_comm_ctx = AllReduceCommCtx() 

224 self._accumulated_allreduced_grad = True 

225 self._reduce_partial_output = None 

226 self._post_load_hook_handle = ( 

227 module_info.module.register_load_state_dict_post_hook( 

228 lambda *args, **kwargs: self.reset_sharded_param() 

229 ) 

230 ) 

231 self.gradient_scaling_factor = None 

232 

233 @property 

234 def accumulated_allreduced_grad(self) -> bool: 

235 return self._accumulated_allreduced_grad 

236 

237 @accumulated_allreduced_grad.setter 

238 def accumulated_allreduced_grad(self, value: bool) -> None: 

239 self._accumulated_allreduced_grad = value 

240 

241 @property 

242 def reduce_partial_output(self) -> Optional[ms.Tensor]: 

243 """Return reduce-scatter results accumulated before the final micro-step.""" 

244 return self._reduce_partial_output 

245 

246 @reduce_partial_output.setter 

247 def reduce_partial_output(self, value: Optional[ms.Tensor]) -> None: 

248 self._reduce_partial_output = value 

249 

250 def reduce_comm_dtype(self, grad: Optional[ms.Tensor] = None): 

251 """Resolve the communication dtype owned by this parameter.""" 

252 if self.reduce_dtype is not None: 

253 return self.reduce_dtype 

254 if grad is not None: 

255 return grad.dtype 

256 if self.unsharded_accumulated_grad is not None: 

257 return self.unsharded_accumulated_grad_data.dtype 

258 if self.unsharded_param is not None and self.unsharded_param.grad is not None: 

259 return self.unsharded_grad_data.dtype 

260 return self.orig_dtype 

261 

262 def _get_base_spmd_placements(self) -> tuple: 

263 """Return source-layout placements prefixed by explicit data-parallel axes.""" 

264 if self.source_shard_info is not None: 

265 self._spmd_mesh = DeviceMesh.concatenate( 

266 [self.mesh_info.mesh, self.source_shard_info.mesh] 

267 ) 

268 dp_prefix = tuple(Replicate() for _ in range(self.mesh_info.mesh.ndim)) 

269 return dp_prefix + tuple(self.source_shard_info.placements) 

270 self._spmd_mesh = self.mesh_info.mesh 

271 return tuple(Replicate() for _ in range(self._spmd_mesh.ndim)) 

272 

273 def _apply_data_parallel_placements( 

274 self, placements: list, shard_placement: Shard 

275 ) -> tuple: 

276 """Apply the parameter-specific DDP/FSDP layout to source placements.""" 

277 if len(placements) != self._spmd_mesh.ndim: 

278 raise AssertionError( 

279 f"Expected {self._spmd_mesh.ndim} unified placements, got " 

280 f"{len(placements)}: {placements}" 

281 ) 

282 if ( 

283 isinstance(self.mesh_info, DDPMeshInfo) 

284 and self._spmd_replicate_mesh_dim is not None 

285 and not self._orig_param_is_dtensor 

286 ): 

287 placements[self._spmd_replicate_mesh_dim] = Replicate() 

288 if isinstance(self.mesh_info, FSDPMeshInfo) and self._spmd_shard_mesh_dim is not None: 

289 placements[self._spmd_shard_mesh_dim] = self._get_data_parallel_shard_placement( 

290 placements, shard_placement 

291 ) 

292 return tuple(placements) 

293 

294 def _get_data_parallel_shard_placement(self, placements: list, shard_placement: Shard): 

295 """Return the explicit fully_shard placement on the unified SPMD mesh.""" 

296 split_factor = 1 

297 shard_mesh_dim = getattr(self, "_spmd_shard_mesh_dim", None) 

298 for mesh_idx, placement in enumerate(placements): 

299 if mesh_idx == shard_mesh_dim: 

300 continue 

301 if placement.is_shard(shard_placement.dim): 

302 split_factor *= self._spmd_mesh.mesh_shape[mesh_idx] 

303 if split_factor > 1: 

304 return StridedShard(shard_placement.dim, split_factor=split_factor) 

305 return shard_placement 

306 

307 def _release_full_param_storage_if_safe(self, param_data: ms.Tensor) -> None: 

308 """Release the temporary full-parameter storage once the sharded param is installed. 

309 

310 Skip storage reclamation only for meta tensors. Both plain Tensor inputs and DTensor local 

311 tensors should drop their original storage after the sharded Parameter has been installed 

312 onto the owning modules. 

313 """ 

314 if param_data.is_meta: 

315 return 

316 storage = param_data.untyped_storage() 

317 if storage.size() != 0: 

318 storage.resize_(0) 

319 

320 def _iter_backward_hooks(self, param: Parameter) -> List[Callable]: 

321 """Return backward hooks registered on a MindSpore Tensor/Parameter.""" 

322 hooks_getter = getattr(param, "hooks", None) 

323 if callable(hooks_getter): 

324 try: 

325 return list(hooks_getter()) 

326 except (AttributeError, RuntimeError, TypeError, ValueError): 

327 pass 

328 

329 backward_hooks = getattr(param, "_backward_hooks", None) 

330 if backward_hooks is None: 

331 return [] 

332 if hasattr(backward_hooks, "values"): 

333 return list(backward_hooks.values()) 

334 return list(backward_hooks) 

335 

336 def _save_backward_hooks(self, param: Parameter) -> None: 

337 """Save user-registered parameter backward hooks for later parameter swaps.""" 

338 if not hasattr(self, "_orig_param_hooks"): 

339 self._orig_param_hooks = [] 

340 if not hasattr(self, "_saved_hook_ids"): 

341 self._saved_hook_ids = set() 

342 

343 for hook_func in self._iter_backward_hooks(param): 

344 hook_func_id = id(hook_func) 

345 if hook_func_id not in self._saved_hook_ids: 

346 self._orig_param_hooks.append(hook_func) 

347 self._saved_hook_ids.add(hook_func_id) 

348 

349 def _migrate_backward_hooks(self, new_param: Parameter) -> None: 

350 """Migrate saved user backward hooks to the active sharded/unsharded parameter.""" 

351 if not getattr(self, "_orig_param_hooks", None): 

352 return 

353 if hasattr(new_param, "migrate_backward_hooks_run_once"): 

354 return 

355 register_hook = getattr(new_param, "register_hook", None) 

356 if not callable(register_hook): 

357 return 

358 

359 for hook_func in self._orig_param_hooks: 

360 try: 

361 if getattr(new_param, "requires_grad", False): 

362 register_hook(hook_func) 

363 except (RuntimeError, TypeError, ValueError): 

364 pass 

365 new_param.migrate_backward_hooks_run_once = True 

366 

367 @_no_grad() 

368 def _init_sharded_param( 

369 self, 

370 param: Parameter, 

371 shard_placement_fn: Optional[Callable], 

372 ) -> None: 

373 param_device = normalize_runtime_device(param.device) 

374 if param_device not in ("meta", self.device): 

375 raise AssertionError( 

376 f"Expects the parameter to already be moved to device {self.device} but got {param.device}" 

377 ) 

378 hsdp_placement = shard_placement_fn(param) if shard_placement_fn else None 

379 if hsdp_placement is None: 

380 hsdp_placement = Shard(0) 

381 elif hsdp_placement.dim < 0: 

382 # if dim is negative, add the number of dimensions of the parameter 

383 hsdp_placement = Shard(hsdp_placement.dim + param.ndim) 

384 

385 if not isinstance(hsdp_placement, Shard): 

386 raise AssertionError( 

387 f"Expected Shard, got {type(hsdp_placement)}: {hsdp_placement}" 

388 ) 

389 

390 self.hsdp_placement = hsdp_placement 

391 base_placements = list(self._get_base_spmd_placements()) 

392 self._spmd_placements = self._apply_data_parallel_placements(base_placements, hsdp_placement) 

393 param_data = unwrap_dtensor_param(param).to_local() if self._orig_param_is_dtensor else param 

394 

395 shard_dim = hsdp_placement.dim 

396 self._orig_size = param_data.shape 

397 self._contiguous_orig_stride = make_contiguous_strides_for(self._orig_size) 

398 

399 if isinstance(self.mesh_info, FSDPMeshInfo): 

400 self.shard_rank = self.mesh_info.shard_mesh_rank 

401 self.shard_world_size = self.mesh_info.shard_mesh_size 

402 else: 

403 self.shard_rank = 0 

404 self.shard_world_size = 1 

405 if isinstance(self.mesh_info, DDPMeshInfo): 

406 self.replicate_world_size = self.mesh_info.replicate_mesh_size 

407 else: 

408 self.replicate_world_size = 1 

409 self.is_replicate_param = ( 

410 isinstance(self.mesh_info, DDPMeshInfo) 

411 and not isinstance(self.mesh_info, HSDPMeshInfo) 

412 ) 

413 self.is_sharded = self.shard_world_size > 1 

414 

415 if param_data.shape[shard_dim] % self.shard_world_size != 0: 

416 raise NotImplementedError( 

417 f"Uneven sharding on dim {shard_dim} not supported: " 

418 f"shape={param_data.shape}, world_size={self.shard_world_size}" 

419 ) 

420 chunks = ms.mint.chunk(param_data, self.shard_world_size, dim=shard_dim) 

421 sharded_param = chunks[self.shard_rank].clone().contiguous() 

422 self.sharded_size = sharded_param.shape 

423 self.contiguous_sharded_stride = make_contiguous_strides_for(self.sharded_size) 

424 self._sharded_param_data = sharded_param.view(-1) 

425 

426 self._sharding_spec = Layout.from_device_mesh(self._spmd_mesh) 

427 self._sharding_spec.set_placements(self._spmd_placements) 

428 self._sharding_spec.placement_to_tensor_map(param.ndim) 

429 

430 shard_dtensor = DTensor.from_local(sharded_param, self._spmd_mesh, self._spmd_placements) 

431 self.sharded_param = Parameter(shard_dtensor, name=param.name) 

432 set_requires_grad_if_needed(param, self.sharded_param) 

433 self.sharded_param.grad = None 

434 

435 self._setattr_on_modules(self.sharded_param) 

436 self._release_full_param_storage_if_safe(param_data) 

437 self.sharded_param._hsdp_param_initialized = True 

438 self.sharded_state = ShardedState.SHARDED 

439 self.param_dtype = None 

440 

441 def init_dtype_attrs(self, mp_policy: MixedPrecisionPolicy): 

442 param_dtype, reduce_dtype = (mp_policy.param_dtype, mp_policy.reduce_dtype) 

443 self.orig_dtype = self.sharded_param.dtype 

444 if reduce_dtype == param_dtype: 

445 reduce_dtype = None 

446 if param_dtype == self.orig_dtype: 

447 param_dtype = None 

448 self.param_dtype = param_dtype 

449 self.reduce_dtype = reduce_dtype 

450 

451 def init_unsharded_param_buffers( 

452 self, 

453 all_gather_input_numels: list[int], 

454 all_gather_input_dtypes: list[ms.Type], 

455 world_size: int, 

456 device: str, 

457 force_recreate: bool = False, 

458 ): 

459 if not force_recreate and len(self.unsharded_param_buffers) > 0: 

460 return # already initialized 

461 self.unsharded_param_buffers = [ 

462 ms.mint.empty([numel * world_size], dtype=dtype, device=device.split(':')[0]) 

463 for numel, dtype in zip(all_gather_input_numels, all_gather_input_dtypes) 

464 ] 

465 

466 def init_unsharded_param(self): 

467 """ 

468 Initialize unsharded parameter from all-gather outputs. 

469 

470 This reconstructs the full parameter after all-gather by unpacking the 

471 gathered flat buffer back to the original tensor layout. 

472 """ 

473 unsharded_param = self._get_unsharded_param_from_all_gather_output() 

474 if self._unsharded_param is not None: 

475 # Keep the Parameter identity stable across forward-reshard-backward 

476 # cycles so backward hooks continue to read gradients from the same 

477 # object that participated in the forward graph. 

478 if self._orig_param_is_dtensor: 

479 self._unsharded_param.set_data(unsharded_param) 

480 else: 

481 self._unsharded_param.data = unsharded_param 

482 set_requires_grad_if_needed(self.sharded_param, self._unsharded_param) 

483 self._unsharded_param.grad = None 

484 return 

485 if self._orig_param_is_dtensor: 

486 self._unsharded_param = Parameter( 

487 unsharded_param, 

488 name=self.sharded_param.name, 

489 requires_grad=self.sharded_param.requires_grad, 

490 ) 

491 return 

492 # For MindSpore, if use `Parameter(tensor)`, Parameter will create a new Tensor instead of a view. 

493 # Here we need to share storage, so we use the `.data = tensor` approach to create shared storage. 

494 self._unsharded_param = Parameter( 

495 [], 

496 name=self.sharded_param.name, 

497 requires_grad=False, 

498 ) 

499 self._unsharded_param.data = unsharded_param 

500 if self.sharded_param.requires_grad: 

501 self._unsharded_param.requires_grad = True 

502 

503 def _get_unsharded_param_from_all_gather_output(self): 

504 """Reconstruct the full local parameter view from the packed all-gather output.""" 

505 if len(self.unsharded_param_buffers) != 1: 

506 raise AssertionError( 

507 f"Expected 1 unsharded_param_buffer, got {len(self.unsharded_param_buffers)}" 

508 ) 

509 unsharded_tensor = self.unsharded_param_buffers[0] 

510 plan = build_rs_plan( 

511 self, 

512 self._sharded_local_tensor, 

513 self.shard_world_size if self.is_sharded else 1, 

514 ) 

515 unsharded_param = unpack_from_all_gather(unsharded_tensor, plan) 

516 if getattr(self, "_orig_param_is_dtensor", False): 

517 unsharded_param = DTensor.from_local( 

518 unsharded_param, 

519 self._orig_dtensor_mesh, 

520 self._orig_dtensor_placements, 

521 ) 

522 return unsharded_param 

523 

524 def to_sharded(self) -> None: 

525 self._setattr_on_modules(self.sharded_param) 

526 self.free_unsharded_param() 

527 self.allgather_comm_ctx.allgather_input = None 

528 self.allgather_comm_ctx.allgather_output = None 

529 self.allgather_comm_ctx.allgather_handle = None 

530 self.sharded_state = ShardedState.SHARDED 

531 

532 def to_unsharded(self) -> None: 

533 set_requires_grad_if_needed(self.sharded_param, self._unsharded_param) 

534 self._setattr_on_modules(self._unsharded_param) 

535 self.sharded_state = ShardedState.UNSHARDED 

536 

537 def _setattr_on_modules(self, param: Parameter) -> None: 

538 if getattr(self._module_info.module.__setattr__, "__func__", None) is nn.Cell.__setattr__: 

539 # fast path 

540 self._module_info.module._params[self._module_info.param_name] = param 

541 else: 

542 # slow path 

543 setattr(self._module_info.module, self._module_info.param_name, param) 

544 if hasattr(self, "sharded_param"): 

545 self._save_backward_hooks(self.sharded_param) 

546 self._migrate_backward_hooks(param) 

547 

548 # Iterate through all modules that share this parameter to prevent pointer desync. 

549 for shared_module, shared_param_name in zip( 

550 self._module_info.shared_modules, self._module_info.shared_param_names 

551 ): 

552 if getattr(shared_module.__setattr__, "__func__", None) is nn.Cell.__setattr__: 

553 shared_module._params[shared_param_name] = param 

554 else: 

555 setattr(shared_module, shared_param_name, param) 

556 

557 def to_sharded_dtensor(self, tensor: ms.Tensor) -> DTensor: 

558 """ 

559 Converts a local tensor representing either the sharded parameter or 

560 sharded gradient to DTensor. 

561 """ 

562 return DTensor.from_local( 

563 tensor, 

564 self._sharding_spec.mesh, 

565 self._sharding_spec.placements 

566 ) 

567 

568 def _to_local_unsharded_grad(self, grad): 

569 """Normalize a pending gradient to the local tensor expected by fully_shard collectives.""" 

570 return self._normalize_unsharded_grad_to_local(grad, reduce_partial_dtensor=False) 

571 

572 def to_accumulated_grad_if_needed(self) -> None: 

573 if self._unsharded_param.grad is None: 

574 return 

575 unsharded_grad = self._unsharded_param.grad 

576 self._unsharded_param.grad = None 

577 if self.reduce_dtype is not None and unsharded_grad.dtype != self.reduce_dtype: 

578 unsharded_grad = unsharded_grad.to(self.reduce_dtype) 

579 if self.unsharded_accumulated_grad is None: 

580 self.unsharded_accumulated_grad = unsharded_grad 

581 else: 

582 self.unsharded_accumulated_grad = ms.mint.add( 

583 self.unsharded_accumulated_grad, 

584 unsharded_grad, 

585 ) 

586 

587 def accumulate_unsharded_grad_if_needed(self) -> None: 

588 if ( 

589 self.unsharded_accumulated_grad is not None 

590 and self.unsharded_param.grad is not None 

591 ): 

592 # need to handle the gradient 

593 self.unsharded_accumulated_grad = ms.mint.add( 

594 self.unsharded_accumulated_grad, 

595 self._to_local_unsharded_grad(self.unsharded_param.grad), 

596 ) 

597 self.unsharded_param.grad = None 

598 

599 def alloc_unsharded_param_buffers(self) -> None: 

600 for tensor in self.unsharded_param_buffers: 

601 expected_size = tensor.numel() * tensor.itemsize 

602 

603 storage = tensor.untyped_storage() 

604 if storage.size() != expected_size: 

605 storage.resize_(expected_size) 

606 

607 def free_unsharded_param(self) -> None: 

608 for tensor in itertools.chain( 

609 self.unsharded_param_buffers 

610 ): 

611 storage = tensor.untyped_storage() 

612 if storage.size() != 0: 

613 storage.resize_(0) 

614 

615 @property 

616 def all_gather_inputs(self) -> list[ms.Tensor]: 

617 self._assert_in_states(ShardedState.SHARDED) 

618 sharded_param_data = self._sharded_param_data 

619 if self.offload_to_cpu: 

620 sharded_param_data = sharded_param_data.to( 

621 self.device, non_blocking=True 

622 ) 

623 if self.param_dtype is not None and self.param_dtype != sharded_param_data.dtype: 

624 return [sharded_param_data.to(self.param_dtype)] 

625 return [sharded_param_data] 

626 

627 @property 

628 def unsharded_param(self) -> Parameter: 

629 """Return the full unsharded parameter after all-gather.""" 

630 return self._unsharded_param 

631 

632 @property 

633 def unsharded_grad_data(self) -> ms.Tensor: 

634 """ 

635 Get the unsharded gradient data as a local tensor. 

636 """ 

637 grad = self.unsharded_param.grad 

638 if grad is None: 

639 raise AssertionError("Expects unsharded_param.grad to not be None") 

640 return self._to_local_unsharded_grad(grad) 

641 

642 @property 

643 def unsharded_accumulated_grad_data(self) -> ms.Tensor: 

644 """ 

645 Get the unsharded accumulated gradient data as a local tensor. 

646 """ 

647 grad = self.unsharded_accumulated_grad 

648 return grad 

649 

650 @property 

651 def _sharded_local_tensor(self) -> ms.Tensor: 

652 """Return the underlying local tensor of the sharded DTensor parameter.""" 

653 return cast(DTensor, self.sharded_param)._local_tensor 

654 

655 def _sharded_param_storage_dtype(self) -> Optional[ms.Type]: 

656 """Return the dtype of the sharded parameter's on-device storage.""" 

657 if not hasattr(self.sharded_param, "dtype"): 

658 return None 

659 dtype = self.sharded_param.dtype 

660 if isinstance(dtype, ms.Type): 

661 return dtype 

662 return None 

663 

664 def _assert_in_states(self, *states: ShardedState) -> None: 

665 """Assert current state is one of expected states.""" 

666 if self.sharded_state not in states: 

667 raise AssertionError( 

668 f"Expected sharded_state in {states}, got {self.sharded_state}" 

669 ) 

670 

671 def _is_same_sharded_local_tensor(self, local_tensor: ms.Tensor) -> bool: 

672 """Whether the cached flat shard view already points to the ``local_tensor`` storage.""" 

673 if not isinstance(self._sharded_param_data, ms.Tensor): 

674 return False 

675 cached_storage = self._sharded_param_data.untyped_storage() 

676 local_storage = local_tensor.untyped_storage() 

677 # when sharding param with shape (1, ...) over 2 ranks 

678 # local_tensor on rank 1 can be size 0, data_ptr() can be 0 

679 return ( 

680 cached_storage.data_ptr() > 0 

681 and cached_storage.data_ptr() == local_storage.data_ptr() 

682 ) 

683 

684 def _validate_sharded_local_tensor_shape(self, local_tensor: ms.Tensor) -> None: 

685 """Validate that a replaced local tensor still matches the expected shard shape.""" 

686 if local_tensor.shape != self.sharded_size: 

687 raise AssertionError( 

688 f"Expected sharded_size to be {self.sharded_size}, got {local_tensor.shape}" 

689 ) 

690 

691 def _pin_sharded_local_tensor_if_needed(self, local_tensor: ms.Tensor) -> Tuple[ms.Tensor, bool]: 

692 """Pin the local tensor memory when CPU offload requires it.""" 

693 if self.pin_memory and not local_tensor.is_pinned(): 

694 return local_tensor.to("cpu").pin_memory(), True 

695 return local_tensor, False 

696 

697 def _assert_sharded_param_is_dtensor(self) -> None: 

698 """Assert that ``self.sharded_param`` is backed by a DTensor.""" 

699 if not isinstance(self.sharded_param, DTensor): 

700 raise AssertionError(f"Expected DTensor, got {type(self.sharded_param)}") 

701 

702 def _refresh_sharded_local_tensor_view( 

703 self, 

704 local_tensor: ms.Tensor, 

705 shard_dim: int, 

706 length: int, 

707 ) -> None: 

708 """Refresh ``self.sharded_param`` to point to a local tensor view.""" 

709 # Only change the local tensor object if needed 

710 with _no_grad(): 

711 local_view = local_tensor.narrow(dim=shard_dim, start=0, length=length) 

712 set_requires_grad_if_needed(self.sharded_param, local_view) 

713 self.sharded_param._local_tensor = local_view 

714 if not self.sharded_param._local_tensor.is_contiguous(): 

715 raise AssertionError( 

716 "Expected sharded_param._local_tensor to be contiguous" 

717 ) 

718 

719 def reset_sharded_param(self) -> None: 

720 """Reset the sharded param after ``load_state_dict``.""" 

721 module_info = self._module_info 

722 new_param = getattr(module_info.module, module_info.param_name) 

723 if new_param is not self.sharded_param: 

724 if isinstance(new_param, DTensor): 

725 self.sharded_param = new_param 

726 if not getattr(self.sharded_param, "_hsdp_param_initialized", None): 

727 # reset _hsdp_param_initialized flag. 

728 self.sharded_param._hsdp_param_initialized = True 

729 elif isinstance(new_param, ms.Tensor): 

730 # if new_param is Tensor, don't re-ref 'self.sharded_param' 

731 # just update self.sharded_param._local_tensor and self.sharded_param_data. 

732 pass 

733 

734 local_tensor = new_param._local_tensor if isinstance(new_param, DTensor) else new_param 

735 if local_tensor.is_meta: 

736 return 

737 # local_tensor can be padded twice 

738 # 1st time in fully_shard(model) 

739 # 2nd time in model(input) lazy_init 

740 # 2nd time should be no-op if parameters remain unchanged 

741 # 2nd time shouldn't be no-op if people call model.load_state_dict(...) before lazy_init 

742 # this makes it possible for trainer to call `sd = model.state_dict()` before the training loop 

743 # and use `sd` without calling .state_dict() per iteration 

744 same_local_tensor = self._is_same_sharded_local_tensor(local_tensor) 

745 shard_dim = self.hsdp_placement.dim 

746 length = local_tensor.shape[shard_dim] if local_tensor.numel() > 0 else 0 

747 if not same_local_tensor: 

748 self._validate_sharded_local_tensor_shape(local_tensor) 

749 local_tensor, pinned_local_tensor = self._pin_sharded_local_tensor_if_needed(local_tensor) 

750 updated_local_tensor = not same_local_tensor or pinned_local_tensor 

751 if not same_local_tensor: 

752 self._sharded_param_data = local_tensor.view(-1) 

753 self._assert_sharded_param_is_dtensor() 

754 if updated_local_tensor: 

755 self._refresh_sharded_local_tensor_view(local_tensor, shard_dim, length) 

756 self._sharding_spec = cast(DTensor, self.sharded_param).layout 

757 

758 @_no_grad() 

759 def _get_unsharded_param_data( 

760 self, 

761 async_op: bool = False, 

762 ) -> Tuple[ms.Tensor, ms.Tensor, Optional[Any]]: 

763 """ 

764 Perform all-gather to get unsharded parameter data. 

765 

766 Args: 

767 async_op: Whether to execute asynchronously. 

768 

769 Returns: 

770 (all_gather_input, unsharded_param, handle): Communication input, 

771 unsharded parameter data, and communication handle. 

772 """ 

773 # Optimizer steps may refresh the underlying local tensor storage. Re-sync 

774 # the cached flat shard view before reading all_gather_inputs for the next 

775 # unshard cycle. 

776 self.reset_sharded_param() 

777 all_gather_input = self.all_gather_inputs[0] 

778 

779 # If parameter is not sharded (below threshold), no communication needed 

780 if not self.is_sharded: 

781 self.init_unsharded_param_buffers( 

782 all_gather_input_numels=[all_gather_input.numel()], 

783 all_gather_input_dtypes=[all_gather_input.dtype], 

784 world_size=1, 

785 device=all_gather_input.device.split(':')[0], 

786 ) 

787 self.alloc_unsharded_param_buffers() 

788 copy_without_bumping_version(self.unsharded_param_buffers[0], all_gather_input) 

789 return all_gather_input, self.unsharded_param_buffers[0], None 

790 

791 # Initialize output buffer 

792 self.init_unsharded_param_buffers( 

793 all_gather_input_numels=[all_gather_input.numel()], 

794 all_gather_input_dtypes=[all_gather_input.dtype], 

795 world_size=self.shard_world_size, 

796 device=self._sharded_param_data.device.split(':')[0], 

797 ) 

798 self.alloc_unsharded_param_buffers() 

799 

800 # Get communication group 

801 shard_group = self.mesh_info.shard_process_group if isinstance(self.mesh_info, FSDPMeshInfo) else None 

802 

803 if shard_group is None or self.shard_world_size <= 1: 

804 # No communication needed, just copy 

805 copy_without_bumping_version(self.unsharded_param_buffers[0], all_gather_input) 

806 return all_gather_input, self.unsharded_param_buffers[0], None 

807 

808 # Execute all_gather_into_tensor 

809 handle = dist.all_gather_into_tensor( 

810 self.unsharded_param_buffers[0], 

811 all_gather_input, 

812 group=shard_group, 

813 async_op=async_op, 

814 ) 

815 

816 return all_gather_input, self.unsharded_param_buffers[0], handle 

817 

818 def unshard(self, async_op: bool = False) -> None: 

819 if self.allgather_comm_ctx.allgather_output is not None: 

820 # Already triggered by HSDPState.prefetch(), so return directly. 

821 return # no-op 

822 

823 all_gather_input, output, handle = self._get_unsharded_param_data(async_op=async_op) 

824 self.allgather_comm_ctx.allgather_input = all_gather_input 

825 self.allgather_comm_ctx.allgather_output = output 

826 self.allgather_comm_ctx.allgather_handle = handle 

827 

828 def wait_for_unshard(self) -> None: 

829 self._assert_in_states(ShardedState.SHARDED) 

830 

831 if self.allgather_comm_ctx.allgather_handle is not None: 

832 self.allgather_comm_ctx.allgather_handle.wait() 

833 self.allgather_comm_ctx.allgather_handle = None 

834 self.allgather_comm_ctx.allgather_input = None 

835 

836 self.init_unsharded_param() 

837 self.to_unsharded() 

838 

839 def shard(self) -> None: 

840 """ 

841 Transition parameter from unsharded back to sharded state. 

842 """ 

843 self._assert_in_states(ShardedState.UNSHARDED) 

844 self.to_sharded() 

845 

846 def reduce_scatter_output(self): 

847 """Return cached reduce-scatter output after waiting pending async work.""" 

848 if self.reduce_scatter_comm_ctx.reduce_scatter_handle is not None: 

849 self.reduce_scatter_comm_ctx.reduce_scatter_handle.wait() 

850 self.reduce_scatter_comm_ctx.reduce_scatter_handle = None 

851 return self.reduce_scatter_comm_ctx.reduce_scatter_output 

852 

853 def clear_reduce_scatter_output(self): 

854 """Clear cached reduce-scatter output.""" 

855 self.reduce_scatter_comm_ctx.reduce_scatter_output = None 

856 

857 def reduce_scatter_grad( 

858 self, 

859 async_op: bool = True, 

860 dtype: Optional[ms.Type] = None, 

861 reduce_op: str = "avg", 

862 output_buffer: Optional[ms.Tensor] = None, 

863 ) -> None: 

864 """ 

865 Perform reduce-scatter on gradient to reduce and shard the full gradient. 

866 

867 Args: 

868 async_op: Whether to execute asynchronously. 

869 dtype: reduce dtype. 

870 reduce_op: do reduce-scatter avg or sum. 

871 output_buffer: Optional pre-allocated output for fused all-reduce groups. 

872 

873 The output and optional asynchronous handle are stored in 

874 ``reduce_scatter_comm_ctx``. 

875 """ 

876 # Choose gradient source based on use_accumulated_grad flag 

877 if self.unsharded_accumulated_grad is not None: 

878 grad = self.unsharded_accumulated_grad_data 

879 else: 

880 grad = self.unsharded_grad_data 

881 reduce_dtype = dtype or self.reduce_comm_dtype(grad) 

882 grad = grad.to(reduce_dtype) 

883 grad = grad.contiguous() 

884 shard_group_info = getattr(self, "sharded_group_info", None) 

885 shard_group = shard_group_info.group if shard_group_info is not None else None 

886 shard_group_size = shard_group_info.rank_size if shard_group_info is not None else 1 

887 if shard_group is None and isinstance(self.mesh_info, FSDPMeshInfo): 

888 shard_group = self.mesh_info.shard_process_group 

889 shard_group_size = self.shard_world_size 

890 plan_world_size = ( 

891 shard_group_size 

892 if self.is_sharded and shard_group is not None and shard_group_size > 1 

893 else 1 

894 ) 

895 plan = build_rs_plan(self, grad, plan_world_size) 

896 grad_flat = pack_for_reduce_scatter(grad, plan).reshape(-1) 

897 # apply gradient_scaling_factor (reduce-scatter leg) 

898 apply_gradient_scaling_factor(grad_flat, self.gradient_scaling_factor) 

899 # If parameter is not sharded (below threshold), no reduce-scatter needed 

900 if not self.is_sharded: 

901 if output_buffer is not None: 

902 copy_without_bumping_version(output_buffer, grad_flat) 

903 self.reduce_scatter_comm_ctx.reduce_scatter_output = output_buffer 

904 else: 

905 self.reduce_scatter_comm_ctx.reduce_scatter_output = grad_flat 

906 self.reduce_scatter_comm_ctx.reduce_scatter_handle = None 

907 return 

908 

909 if shard_group is None or shard_group_size <= 1: 

910 if output_buffer is not None: 

911 copy_without_bumping_version(output_buffer, grad_flat) 

912 self.reduce_scatter_comm_ctx.reduce_scatter_output = output_buffer 

913 else: 

914 self.reduce_scatter_comm_ctx.reduce_scatter_output = grad_flat 

915 self.reduce_scatter_comm_ctx.reduce_scatter_handle = None 

916 return 

917 

918 # Calculate output size 

919 output_numel = grad_flat.numel() // shard_group_size 

920 if output_buffer is not None: 

921 if output_buffer.numel() != output_numel: 

922 raise ValueError( 

923 f"output_buffer size mismatch: expected {output_numel}, got {output_buffer.numel()}" 

924 ) 

925 if output_buffer.dtype != reduce_dtype: 

926 raise ValueError( 

927 f"output_buffer dtype mismatch: expected {reduce_dtype}, got {output_buffer.dtype}" 

928 ) 

929 self.reduce_scatter_comm_ctx.reduce_scatter_output = output_buffer 

930 else: 

931 self.reduce_scatter_comm_ctx.reduce_scatter_output = ms.mint.empty( 

932 output_numel, dtype=reduce_dtype, device=grad.device.split(":")[0] 

933 ) 

934 

935 # Ascend HCCL DistCommReduceScatter rejects non-contiguous tensors. 

936 # ``pack_for_reduce_scatter`` on a shard-dim-0 path returns the input 

937 # tensor as-is (potentially a view from to_local() / redistribute()), 

938 # and the trailing ``.reshape(-1)`` may yield a view. Force contiguous 

939 # storage here (no-op when already contig). 

940 grad_flat = grad_flat.contiguous() 

941 

942 # Execute reduce_scatter_tensor 

943 self.reduce_scatter_comm_ctx.reduce_scatter_handle = dist.reduce_scatter_tensor( 

944 self.reduce_scatter_comm_ctx.reduce_scatter_output, 

945 grad_flat, 

946 op=reduce_op, 

947 group=shard_group, 

948 async_op=async_op, 

949 ) 

950 

951 def zero_grad(self): 

952 """Reset the sharded parameter's gradient buffers to None.""" 

953 self.sharded_param.grad = None 

954 if hasattr(self.sharded_param, "main_grad"): 

955 self.sharded_param.main_grad = None 

956 

957 def all_reduce_grad( 

958 self, 

959 async_op: bool = True, 

960 reduce_op: str = "avg", 

961 ) -> None: 

962 """ 

963 Perform all-reduce on gradient (across replicate dimension in HSDP mode). 

964 

965 Args: 

966 async_op: Whether to execute asynchronously. 

967 reduce_op: Reduction operation accepted by ``mint.distributed``. 

968 

969 The output and optional asynchronous handle are stored in 

970 ``all_reduce_comm_ctx``. 

971 """ 

972 grad = self.reduce_scatter_comm_ctx.reduce_scatter_output 

973 if grad is None: 

974 raise RuntimeError("all_reduce_grad requires a completed reduce-scatter output.") 

975 if self.reduce_dtype is not None and self.reduce_dtype != grad.dtype: 

976 grad = grad.to(self.reduce_dtype) 

977 reduce_group = ( 

978 self.mesh_info.replicate_process_group 

979 if isinstance(self.mesh_info, DDPMeshInfo) 

980 else None 

981 ) 

982 if reduce_group is None or self.replicate_world_size <= 1: 

983 self.all_reduce_comm_ctx.all_reduce_output = grad 

984 self.all_reduce_comm_ctx.all_reduce_handle = None 

985 return 

986 

987 # Ascend HCCL DistCommAllReduce rejects non-contiguous tensors. 

988 # ``grad`` here may be a view returned by ``_to_local_unsharded_grad`` 

989 # (DTensor.to_local() / redistribute().to_local()) or by autograd. 

990 # ``Tensor.contiguous()`` is itself a no-op when storage is already 

991 # contiguous, so the unconditional call is safe and avoids the 

992 # ``is_contiguous()`` query (which has been observed to under-detect 

993 # non-contig views from DTensor on this MS version). 

994 grad = grad.contiguous() 

995 

996 self.all_reduce_comm_ctx.all_reduce_output = grad 

997 self.all_reduce_comm_ctx.all_reduce_handle = dist.all_reduce( 

998 grad, 

999 op=reduce_op, 

1000 group=reduce_group, 

1001 async_op=async_op, 

1002 ) 

1003 

1004 def all_reduce_output(self): 

1005 """Return cached all-reduce output after waiting pending async work.""" 

1006 if self.all_reduce_comm_ctx.all_reduce_handle is not None: 

1007 self.all_reduce_comm_ctx.all_reduce_handle.wait() 

1008 self.all_reduce_comm_ctx.all_reduce_handle = None 

1009 return self.all_reduce_comm_ctx.all_reduce_output 

1010 

1011 def clear_all_reduce_output(self): 

1012 """Clear cached all-reduce output.""" 

1013 self.all_reduce_comm_ctx.all_reduce_output = None 

1014 

1015 def clear_unsharded_source_grad(self) -> None: 

1016 """Release the unsharded gradient after its communication input is safe.""" 

1017 if self.unsharded_accumulated_grad is not None: 

1018 self.unsharded_accumulated_grad = None 

1019 if self.unsharded_param is not None and self.unsharded_param.grad is not None: 

1020 self.unsharded_param.grad = None 

1021 

1022 def apply_reduced_grad(self, reduced_grad): 

1023 """ 

1024 Apply reduced gradient to the sharded parameter. 

1025 

1026 Reshapes ``reduced_grad`` to match the local shard, optionally 

1027 offloads to CPU, then accumulates or assigns onto ``grad`` or 

1028 ``main_grad`` depending on the mixed-precision policy. 

1029 Args: 

1030 reduced_grad (ms.Tensor): Gradient after reduce-scatter 

1031 and/or all-reduce. 

1032 """ 

1033 if self.mp_policy.apply_grad_on_fp32_main_grad: 

1034 if not hasattr(self.sharded_param, "main_grad"): 

1035 self.sharded_param.main_grad = None 

1036 sharded_grad = self.sharded_param.main_grad 

1037 else: 

1038 sharded_grad = self.sharded_param.grad 

1039 

1040 reduced_grad = reduced_grad.reshape(-1).narrow( 

1041 0, 0, self._sharded_local_tensor.numel() 

1042 ).view(self.sharded_size) 

1043 if not self.mp_policy.apply_grad_on_fp32_main_grad: 

1044 reduced_grad = _to_dtype_if_needed(reduced_grad, self.orig_dtype) 

1045 reduced_grad = _to_dtype_if_needed( 

1046 reduced_grad, self._sharded_param_storage_dtype() 

1047 ) 

1048 to_accumulate_grad = sharded_grad is not None 

1049 need_synchronize = False 

1050 if self.offload_to_cpu: 

1051 non_blocking = self.pin_memory and not to_accumulate_grad 

1052 reduced_grad = reduced_grad.to( 

1053 "cpu", non_blocking=non_blocking 

1054 ) 

1055 need_synchronize = True 

1056 if sharded_grad is None: 

1057 if self.mp_policy.apply_grad_on_fp32_main_grad: 

1058 self.sharded_param.main_grad = self.to_sharded_dtensor(reduced_grad) 

1059 self.sharded_param.grad = None 

1060 else: 

1061 self.sharded_param.grad = self.to_sharded_dtensor(reduced_grad) 

1062 else: 

1063 if self.mp_policy.apply_grad_on_fp32_main_grad: 

1064 accumulated_grad = ms.mint.add( 

1065 self.sharded_param.main_grad._local_tensor, 

1066 reduced_grad, 

1067 ) 

1068 self.sharded_param.main_grad = self.to_sharded_dtensor(accumulated_grad) 

1069 self.sharded_param.grad = None 

1070 else: 

1071 accumulated_grad = ms.mint.add( 

1072 self.sharded_param.grad._local_tensor, 

1073 reduced_grad, 

1074 ) 

1075 self.sharded_param.grad = self.to_sharded_dtensor(accumulated_grad) 

1076 

1077 self.clear_unsharded_source_grad() 

1078 return need_synchronize 

1079 

1080 def all_reduce_tp_replicate_grad_inplace( 

1081 self, 

1082 reduced_grad: ms.Tensor, 

1083 reduce_op: str, 

1084 ) -> None: 

1085 """All-reduce a final gradient over replicated source-layout axes.""" 

1086 if self.source_shard_info is None or not self.source_shard_info.placements: 

1087 return 

1088 source_mesh = self.source_shard_info.mesh 

1089 replicate_mesh_dims = tuple( 

1090 mesh_dim 

1091 for mesh_dim, placement in enumerate(self.source_shard_info.placements) 

1092 if placement.is_replicate() 

1093 ) 

1094 if not replicate_mesh_dims: 

1095 return 

1096 if source_mesh.mesh_dim_names is None: 

1097 raise ValueError( 

1098 "TP shard mesh must define mesh_dim_names to all-reduce replicated gradients." 

1099 ) 

1100 replicate_dim_names = tuple( 

1101 source_mesh.mesh_dim_names[mesh_dim] 

1102 for mesh_dim in replicate_mesh_dims 

1103 ) 

1104 replicate_mesh = source_mesh[replicate_dim_names].flatten() 

1105 if replicate_mesh.size() <= 1: 

1106 return 

1107 dist.all_reduce( 

1108 reduced_grad, 

1109 op=reduce_op, 

1110 group=replicate_mesh.get_group(), 

1111 async_op=False, 

1112 ) 

1113 

1114 

1115def set_requires_grad_if_needed( 

1116 src_tensor: ms.Tensor, dst_tensor: ms.Tensor 

1117) -> None: 

1118 """Synchronize the requires_grad flag from src_tensor to dst_tensor if they differ.""" 

1119 if src_tensor.requires_grad != dst_tensor.requires_grad: 

1120 dst_tensor.requires_grad_(src_tensor.requires_grad)