Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / fully_shard / hsdp_scheduler.py: 73%

424 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"""HSDP scheduler""" 

16import functools 

17import inspect 

18from typing import Any, Callable, List, Mapping, Optional, ParamSpec, Tuple, TypeVar, Union 

19 

20import torch 

21from torch import nn 

22from torch.autograd import Variable 

23from torch.utils._pytree import tree_flatten, tree_unflatten 

24 

25from hyper_parallel.core.dtensor.device_mesh import DeviceMesh 

26from hyper_parallel.core.fully_shard.hook_function import PostBackwardFunction 

27from hyper_parallel.core.fully_shard.hsdp_utils import ( 

28 FSDPSchedulerState, 

29 get_managed_modules_parameters, 

30) 

31from hyper_parallel.core.fully_shard.utils import ( 

32 CommFusionPolicy, 

33 SourceShardMetaInfo, 

34 apply_to_tensors, 

35 cast_fp_tensor, 

36 parameters_dict, 

37 profiler_record, 

38) 

39from hyper_parallel.tools.logging import get_logger 

40 

41logger = get_logger("FSDP") 

42 

43nn.Module = nn.Module 

44 

45_P = ParamSpec("_P") 

46_R = TypeVar("_R") 

47 

48 

49def _dynamo_disable(func: Callable[_P, _R]) -> Callable[_P, _R]: 

50 """Disable Dynamo tracing while an FSDP runtime hook executes.""" 

51 

52 @functools.wraps(func) 

53 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R: 

54 return torch._dynamo.disable( 

55 func, 

56 recursive=True, 

57 )(*args, **kwargs) 

58 

59 return wrapper 

60 

61 

62class ParamGroupCommCtx: 

63 """Track the in-flight parameter-group communication for one module tree.""" 

64 

65 def __init__(self) -> None: 

66 self.pre_param_group = None 

67 self.all_reduce_param_group = None 

68 self.comm_handle = None 

69 

70 

71class HSDPSchedulerContext: 

72 """Share scheduler and backward-pipeline state within one HSDP module tree.""" 

73 

74 def __init__(self) -> None: 

75 self.is_last_backward: bool = True 

76 self.root_module = None 

77 # Compile tracing may enter the root more than once; initialize shared 

78 # parameter state only on the first real forward. 

79 self.lazy_init_done: bool = False 

80 self.root_bp_state = False 

81 self.post_backward_final_callback_queued = False 

82 # all_hsdp_schedulers (for one module tree structure), deduplicated at 

83 # registration time because ``fully_shard`` may be given a module list 

84 # whose modules share one scheduler. 

85 self.all_hsdp_schedulers = [] 

86 # Parameter FQNs are initialized once after all schedulers share this context. 

87 self._param_fqn_initialized = False 

88 # Backward pipeline queues shared only by schedulers in this module tree. 

89 self.pre_reduce_scatter_params = [] 

90 self.pre_all_reduce_params = [] 

91 self.pre_direct_all_reduce_grads = [] 

92 self.pre_all_reduce_groups = [] 

93 self.pending_all_reduce_groups = [] 

94 self.param_group_comm_ctx = ParamGroupCommCtx() 

95 

96 

97class HSDPSchedulerV2: 

98 """HSDPScheduler is used to scheduler hsdp""" 

99 

100 def __init__( 

101 self, 

102 cell: Union[nn.Module, Tuple[nn.Module, ...]], 

103 mesh, 

104 reshard_after_forward, 

105 shard_placement_fn, 

106 mp_policy, 

107 offload_policy, 

108 ignored_params, 

109 replicate_params, 

110 device, 

111 comm_fusion, 

112 comm_fusion_zero_copy=False, 

113 source_shard_infos: Optional[Mapping[nn.Parameter, SourceShardMetaInfo]] = None, 

114 ): 

115 """init hsdp scheduler. 

116 

117 Args: 

118 cell: A single module or tuple of modules to manage as one FSDP unit. 

119 mesh: Explicit data-parallel device mesh. 

120 reshard_after_forward: Whether to reshard parameters after forward. 

121 shard_placement_fn: Optional function selecting the parameter shard dimension. 

122 mp_policy: Mixed-precision policy. 

123 offload_policy: Parameter offload policy. 

124 ignored_params: Parameters excluded from fully_shard management. 

125 replicate_params: Managed parameters that remain replicated. 

126 device: Target device for parameters. 

127 comm_fusion: Communication fusion configuration. 

128 comm_fusion_zero_copy: Whether fused communication writes into views. 

129 source_shard_infos: Optional per-parameter TP/EP source metadata. 

130 """ 

131 self.modules = (cell,) if isinstance(cell, nn.Module) else tuple(cell) 

132 self.cell = self.modules[0] 

133 self.mesh: DeviceMesh = mesh 

134 self.shard_placement_fn = shard_placement_fn 

135 self.mp_policy = mp_policy 

136 self.offload_policy = offload_policy 

137 self.comm_fusion_policy = CommFusionPolicy(comm_fusion, comm_fusion_zero_copy) 

138 self.ignored_params = ignored_params 

139 self.replicate_params = replicate_params 

140 self.device = device 

141 self.reshard_after_forward = reshard_after_forward 

142 self.source_shard_infos = source_shard_infos 

143 self.scheduler_state = None 

144 self.forward_prefetch_cells = [] 

145 self.backward_prefetch_cells = [] 

146 self._backup_forward_fetch = None 

147 # Flag to identify root module. 

148 self._is_root = True 

149 # module and its all sub-modules share one same 'HSDPSchedulerContext' 

150 self.scheduler_ctx = HSDPSchedulerContext() 

151 # When ``fully_shard`` is given multiple root modules, forward pre/post hooks coordinate 

152 # so unshard / PostBackward / reshard run once per forward (aligned with PyTorch FSDP2). 

153 self._fsdp_group_post_pending: Optional[set] = set() if len(self.modules) > 1 else None 

154 self._new_cell_state() 

155 self._register_hooks() 

156 

157 def _new_cell_state(self): 

158 """Create the HSDP state that owns this scheduler's managed parameters.""" 

159 # Imported lazily: ``state`` pulls in ``param_group``, which imports this 

160 # module for ``ParamGroupCommCtx``. 

161 from hyper_parallel.core.fully_shard.hsdp_state import ( # pylint: disable=C0415 

162 HSDPStateV2, 

163 ) 

164 

165 self.hsdp_state = HSDPStateV2( 

166 self.modules, 

167 self.mesh, 

168 self.shard_placement_fn, 

169 self.comm_fusion_policy, 

170 self.mp_policy, 

171 self.offload_policy, 

172 self.ignored_params, 

173 self.replicate_params, 

174 self.scheduler_ctx, 

175 self.device, 

176 source_shard_infos=self.source_shard_infos, 

177 ) 

178 

179 def _register_hooks(self): 

180 """Register the forward and backward hooks driving shard/unshard.""" 

181 self._register_forward_backward_hooks() 

182 

183 def _get_managed_params(self): 

184 """Return deduplicated parameters from all managed modules.""" 

185 return get_managed_modules_parameters(self.modules, self.ignored_params) 

186 

187 def set_reshard_after_forward(self, reshard_after_forward: bool) -> None: 

188 """Set reshard_after_forward flag. 

189 

190 Args: 

191 reshard_after_forward: Whether to reshard parameters after forward. 

192 """ 

193 if not isinstance(reshard_after_forward, bool): 

194 raise ValueError(f"reshard_after_forward should be a bool, got {type(reshard_after_forward)}") 

195 self.reshard_after_forward = reshard_after_forward 

196 

197 def set_reshard_after_backward(self, reshard_after_backward: bool) -> None: 

198 """Set reshard_after_backward flag. 

199 

200 Args: 

201 reshard_after_backward: Whether to reshard after backward completes. 

202 """ 

203 if not isinstance(reshard_after_backward, bool): 

204 raise ValueError(f"reshard_after_backward should be a bool, got {type(reshard_after_backward)}") 

205 if self.hsdp_state is not None: 

206 self.hsdp_state.reshard_after_backward = reshard_after_backward 

207 

208 def set_requires_all_reduce(self, requires_all_reduce: bool) -> None: 

209 """Set requires_all_reduce flag. 

210 

211 Args: 

212 requires_all_reduce: Whether this unit participates in all-reduce. 

213 """ 

214 if not isinstance(requires_all_reduce, bool): 

215 raise ValueError(f"requires_all_reduce should be a bool, got {type(requires_all_reduce)}") 

216 if self.hsdp_state is not None: 

217 self.hsdp_state.set_requires_all_reduce(requires_all_reduce) 

218 

219 @_dynamo_disable 

220 def reset_iter_state(self) -> None: 

221 """Reset scheduler bookkeeping after a completed iteration.""" 

222 self.scheduler_ctx.root_bp_state = False 

223 self.scheduler_ctx.post_backward_final_callback_queued = False 

224 self.scheduler_ctx.pre_reduce_scatter_params.clear() 

225 self.scheduler_ctx.pre_all_reduce_params.clear() 

226 self.scheduler_ctx.pre_direct_all_reduce_grads.clear() 

227 self.scheduler_ctx.pre_all_reduce_groups.clear() 

228 self.scheduler_ctx.pending_all_reduce_groups.clear() 

229 self.scheduler_ctx.param_group_comm_ctx.pre_param_group = None 

230 self.scheduler_ctx.param_group_comm_ctx.all_reduce_param_group = None 

231 self.scheduler_ctx.param_group_comm_ctx.comm_handle = None 

232 self.scheduler_state = None 

233 if self._fsdp_group_post_pending is not None: 

234 self._fsdp_group_post_pending.clear() 

235 self._restore_forward_prefetch_after_recompute() 

236 self.hsdp_state.reset_iter_state() 

237 

238 def set_requires_grad_sync(self, requires_grad_sync: bool) -> None: 

239 """Set flag controlling whether gradients are synchronized. 

240 

241 Args: 

242 requires_grad_sync: When True, enable grad sync for this scheduler. 

243 """ 

244 if not isinstance(requires_grad_sync, bool): 

245 raise ValueError(f"requires_grad_sync should be a bool, got {type(requires_grad_sync)}") 

246 self.hsdp_state.set_requires_grad_sync(requires_grad_sync) 

247 

248 # pylint: disable=W0613 

249 def _hsdp_forward_pre_hook(self, cell, args, kwargs): 

250 """Forward pre hook to unsharded parameter for forward process.""" 

251 logger.debug("hook=forward_pre enter module=%s", self.hsdp_state) 

252 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

253 logger.debug("hook=forward_pre skip module=%s reason=pre_backward", self.hsdp_state) 

254 return args, kwargs 

255 if self.scheduler_ctx.root_bp_state: 

256 self._disable_forward_prefetch_for_recompute() 

257 if self.scheduler_ctx.root_module is None: 

258 tree_ctx = self.scheduler_ctx 

259 tree_ctx.root_module = self.cell 

260 registered_schedulers = set() 

261 for module_name, module in tree_ctx.root_module.named_modules(): 

262 from hyper_parallel.core.fully_shard.api import HSDPModule # pylint: disable=C0415 

263 if isinstance(module, HSDPModule): 

264 submod_scheduler = module.hsdp_scheduler 

265 if submod_scheduler is None or id(submod_scheduler) in registered_schedulers: 

266 continue 

267 registered_schedulers.add(id(submod_scheduler)) 

268 if submod_scheduler.scheduler_ctx is not tree_ctx: 

269 if submod_scheduler.scheduler_ctx.root_module is not None: 

270 raise ValueError( 

271 "HSDP scheduler already belongs to another initialized module tree" 

272 ) 

273 submod_scheduler.scheduler_ctx = tree_ctx 

274 submod_scheduler.hsdp_state.scheduler_ctx = tree_ctx 

275 if submod_scheduler.hsdp_state.param_group is not None: 

276 submod_scheduler.hsdp_state.param_group.comm_ctx = tree_ctx.param_group_comm_ctx 

277 submod_scheduler._is_root = submod_scheduler is self # pylint: disable=protected-access 

278 submod_scheduler.hsdp_state.module_name = module_name 

279 tree_ctx.all_hsdp_schedulers.append(submod_scheduler) 

280 

281 self.scheduler_state = FSDPSchedulerState.PRE_FORWARD 

282 if self._is_root and not self.scheduler_ctx.lazy_init_done: 

283 self._init_params_fqn() 

284 self._lazy_init_all_states() 

285 self.scheduler_ctx.lazy_init_done = True 

286 if self.mp_policy.cast_forward_inputs and self.mp_policy.param_dtype: 

287 cast_fn = functools.partial(cast_fp_tensor, self.mp_policy.param_dtype) 

288 args = apply_to_tensors(cast_fn, args) 

289 kwargs = apply_to_tensors(cast_fn, kwargs) 

290 with profiler_record(f"pre_forward unshard:{self.hsdp_state.module_name}"): 

291 logger.debug("hook=forward_pre action=unshard module=%s", self.hsdp_state) 

292 self.hsdp_state.unshard() 

293 for prefetch_cell in self.forward_prefetch_cells: 

294 prefetch_state = prefetch_cell.hsdp_scheduler.hsdp_state 

295 with profiler_record(f"pre_forward prefetch:" 

296 f"{prefetch_state.module_name}"): 

297 logger.debug( 

298 "hook=forward_pre action=prefetch module=%s target=%s", 

299 self.hsdp_state, 

300 prefetch_state, 

301 ) 

302 prefetch_state.prefetch() 

303 return args, kwargs 

304 

305 def _lazy_init_all_states(self): 

306 if self._is_root and self.scheduler_ctx.root_module is not None: 

307 for submod_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

308 hsdp_state = submod_scheduler.hsdp_state 

309 if hsdp_state: 

310 hsdp_state.lazy_init() 

311 

312 def _init_params_fqn(self): # pylint: disable=W0212 

313 if not self._is_root or self.scheduler_ctx.root_module is None: 

314 return 

315 if self.scheduler_ctx._param_fqn_initialized: # pylint: disable=protected-access 

316 return 

317 # Build a map from original (sharded) parameter tensor to its HSDPParam wrapper. 

318 param_to_hsdp_param = {} 

319 for submod_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

320 hsdp_state = submod_scheduler.hsdp_state 

321 if hsdp_state is None: 

322 continue 

323 for hsdp_param in hsdp_state.hsdp_params: 

324 orig_param = hsdp_param.sharded_param 

325 # Shared parameters: keep only the first mapping to preserve the 

326 # first-seen FQN (consistent with the deduplication in _init_hsdp_params). 

327 if orig_param not in param_to_hsdp_param: 

328 param_to_hsdp_param[orig_param] = hsdp_param 

329 

330 # Walk the full parameter tree and assign FQNs; skip params already seen 

331 # (shared-parameter deduplication: first name wins). 

332 visited_params = set() 

333 for param_name, parameter in parameters_dict(self.scheduler_ctx.root_module): 

334 if parameter in visited_params: 

335 continue 

336 visited_params.add(parameter) 

337 hsdp_param = param_to_hsdp_param.get(parameter) 

338 if hsdp_param is not None: 

339 hsdp_param._param_fqn = param_name # pylint: disable=W0212 

340 self.scheduler_ctx._param_fqn_initialized = True # pylint: disable=protected-access 

341 

342 # pylint: disable=W0613, R1710 

343 def _hsdp_forward_hook(self, cell, inputs, outputs): 

344 """Forward hook to shard parameter for saving memory.""" 

345 logger.debug("hook=forward enter module=%s", self.hsdp_state) 

346 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

347 logger.debug("hook=forward skip module=%s reason=pre_backward", self.hsdp_state) 

348 return 

349 self.scheduler_state = FSDPSchedulerState.FORWARD 

350 if self.reshard_after_forward: 

351 with profiler_record(f"forward reshard:{self.hsdp_state.module_name}"): 

352 logger.debug("hook=forward action=reshard module=%s", self.hsdp_state) 

353 self.hsdp_state.shard() 

354 if self.mp_policy.output_dtype is not None: 

355 outputs = apply_to_tensors( 

356 functools.partial(cast_fp_tensor, self.mp_policy.output_dtype), 

357 outputs, 

358 ) 

359 return outputs 

360 

361 # pylint: disable=W0613 

362 def _hsdp_backward_pre_hook(self, cell, grad_outputs): 

363 """Backward pre hook to unsharded parameter for backward process.""" 

364 logger.debug("hook=backward_pre enter module=%s", self.hsdp_state) 

365 self.scheduler_state = FSDPSchedulerState.PRE_BACKWARD 

366 if self.reshard_after_forward: 

367 with profiler_record(f"pre_backward unshard:{self.hsdp_state.module_name}"): 

368 logger.debug("hook=backward_pre action=unshard module=%s", self.hsdp_state) 

369 self.hsdp_state.unshard() 

370 for prefetch_cell in self.backward_prefetch_cells: 

371 prefetch_state = prefetch_cell.hsdp_scheduler.hsdp_state 

372 with profiler_record(f"pre_backward prefetch:" 

373 f"{prefetch_state.module_name}"): 

374 logger.debug( 

375 "hook=backward_pre action=prefetch module=%s target=%s", 

376 self.hsdp_state, 

377 prefetch_state, 

378 ) 

379 prefetch_state.prefetch() 

380 

381 # pylint: disable=W0613 

382 def _hsdp_backward_hook(self, cell, grad_inputs, grad_outputs): 

383 """Backward hook to shard parameter for optimizer process or saving memory.""" 

384 logger.debug("hook=backward_hook enter module=%s", self.hsdp_state) 

385 self.scheduler_state = FSDPSchedulerState.BACKWARD 

386 with profiler_record(f"post_backward:{self.hsdp_state.module_name}"): 

387 logger.debug("hook=backward_hook action=post_backward module=%s", self.hsdp_state) 

388 self.hsdp_state.post_backward() 

389 if self._fsdp_group_post_pending is not None: 

390 self._fsdp_group_post_pending.clear() 

391 

392 # pylint: disable=W0613 

393 @staticmethod 

394 def _grouped_forward_pre_hook_skip(cell, args, kwargs) -> None: 

395 """Return value when grouped pre-forward should not run (first module already did). 

396 

397 ``nn.Module`` pre-hooks use ``None`` for a no-op. 

398 """ 

399 return None 

400 

401 @staticmethod 

402 def _grouped_forward_post_hook_skip(outputs) -> None: 

403 """Return value when grouped post-forward is deferred to a later module. 

404 

405 ``nn.Module`` forward hooks use ``None`` for a no-op. 

406 """ 

407 return None 

408 

409 @_dynamo_disable 

410 def _grouped_forward_pre_hook(self, cell, args, kwargs): 

411 """Run FSDP pre-forward only for the first module in the group (PyTorch FSDP2-aligned).""" 

412 pending = self._fsdp_group_post_pending 

413 if pending is None: 

414 return self._forward_pre_hook(cell, args, kwargs) 

415 if len(pending) == 0: 

416 pending.update(self.modules) 

417 return self._forward_pre_hook(cell, args, kwargs) 

418 return self._grouped_forward_pre_hook_skip(cell, args, kwargs) 

419 

420 def _make_grouped_forward_post_hook(self, mod): 

421 """Build post-forward hook: last module in the group runs reshard + output backward hooks.""" 

422 

423 def grouped_post_hook(cell, inputs, outputs): 

424 pending = self._fsdp_group_post_pending 

425 if pending is None: 

426 return self._forward_hook(cell, inputs, outputs) 

427 pending.discard(mod) 

428 if len(pending) == 0: 

429 return self._forward_hook(cell, inputs, outputs) 

430 return self._grouped_forward_post_hook_skip(outputs) 

431 

432 return grouped_post_hook 

433 

434 def set_forward_prefetch_cells(self, hsdp_cell_list: List[Any]) -> None: 

435 """Set cells prefetched during forward. 

436 

437 Args: 

438 hsdp_cell_list: HSDP cells to prefetch ahead of forward. 

439 """ 

440 self.forward_prefetch_cells = hsdp_cell_list 

441 

442 def set_backward_prefetch_cells(self, hsdp_cell_list: List[Any]) -> None: 

443 """Set cells prefetched during backward. 

444 

445 Args: 

446 hsdp_cell_list: HSDP cells to prefetch ahead of backward. 

447 """ 

448 self.backward_prefetch_cells = hsdp_cell_list 

449 

450 def _disable_forward_prefetch_for_recompute(self) -> None: 

451 """Temporarily disable forward prefetch during activation recompute.""" 

452 self._backup_forward_fetch = self.forward_prefetch_cells 

453 self.forward_prefetch_cells = [] 

454 

455 def _restore_forward_prefetch_after_recompute(self) -> None: 

456 """Restore the saved forward prefetch list after activation recompute.""" 

457 if self._backup_forward_fetch is None: 

458 return 

459 self.forward_prefetch_cells = self._backup_forward_fetch 

460 self._backup_forward_fetch = None 

461 

462 def _register_post_backward_hook(self, args, kwargs): 

463 """Wrap forward args/kwargs through PostBackwardFunction to register backward hook.""" 

464 if not torch.is_grad_enabled(): 

465 return args, kwargs 

466 args_list, args_spec = tree_flatten(args) 

467 kwargs_list, kwargs_spec = tree_flatten(kwargs) 

468 args_kwargs_list = list(args_list) + list(kwargs_list) 

469 inp_tensor_indices: List[int] = [] 

470 inp_tensors: List[torch.Tensor] = [] 

471 for i, obj in enumerate(args_kwargs_list): 

472 if torch.is_tensor(obj) and obj.requires_grad: 

473 inp_tensor_indices.append(i) 

474 inp_tensors.append(obj) 

475 if len(inp_tensors) == 0: 

476 return args, kwargs # no tensors that require gradients 

477 processed_tensors = PostBackwardFunction.apply(self, *inp_tensors) 

478 for inp_tensor_idx, processed_tensor in zip(inp_tensor_indices, processed_tensors): 

479 args_kwargs_list[inp_tensor_idx] = processed_tensor 

480 args_list = args_kwargs_list[: len(args_list)] 

481 kwargs_list = args_kwargs_list[len(args_list) :] 

482 args = tree_unflatten(args_list, args_spec) 

483 kwargs = tree_unflatten(kwargs_list, kwargs_spec) 

484 return args, kwargs 

485 

486 @_dynamo_disable 

487 def _forward_pre_hook(self, cell, args, kwargs): 

488 """Execute forward pre hook and set up backward hook.""" 

489 args, kwargs = self._hsdp_forward_pre_hook(cell, args, kwargs) 

490 return self._register_post_backward_hook(args, kwargs) 

491 

492 def _register_backward_pre_hook(self, outputs) -> None: 

493 """Register gradient hooks on all requires-grad outputs to trigger backward pre hook.""" 

494 flat_outputs, _ = tree_flatten(outputs) 

495 for output in flat_outputs: 

496 if isinstance(output, torch.Tensor) and output.requires_grad: 

497 handle_ref = [None] 

498 # pylint: disable=C0103, W0102 

499 

500 def wrapper_for_backward_pre_hook(grad, _handle_ref=handle_ref): 

501 """Remove this hook after it fires to prevent accmulation""" 

502 handle = _handle_ref[0] 

503 if handle is not None: 

504 handle.remove() 

505 return self._backward_pre_hook(grad) 

506 # pylint: enable=C0103, W0102 

507 handle = output.register_hook(wrapper_for_backward_pre_hook) 

508 handle_ref[0] = handle 

509 

510 @_dynamo_disable 

511 def _forward_hook(self, cell, inputs, outputs): # pylint: disable=R1710 

512 """Execute forward hook.""" 

513 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

514 return 

515 self._register_backward_pre_hook(outputs) 

516 if self.scheduler_ctx.root_bp_state: 

517 self._restore_forward_prefetch_after_recompute() 

518 return 

519 return self._hsdp_forward_hook(cell, inputs, outputs) 

520 

521 # pylint: disable=W0212 

522 @_dynamo_disable 

523 def _backward_pre_hook(self, grad): 

524 """Execute backward pre hook.""" 

525 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

526 return grad 

527 if not self.scheduler_ctx.post_backward_final_callback_queued: 

528 self.scheduler_ctx.post_backward_final_callback_queued = True 

529 root_scheduler = self.scheduler_ctx.root_module.hsdp_scheduler 

530 Variable._execution_engine.queue_callback(root_scheduler._root_backward_hook) 

531 self.scheduler_ctx.root_bp_state = True 

532 self._hsdp_backward_pre_hook(self.cell, None) 

533 return grad 

534 

535 @_dynamo_disable 

536 def _root_backward_hook(self): 

537 """Drain all DP pipelines, then run final TP reduction and apply gradients.""" 

538 logger.debug("hook=root_backward_hook enter module=%s", self.hsdp_state) 

539 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

540 # let modules which are not triggered backward_hook launch backward communication. 

541 hsdp_scheduler._backward_hook() 

542 # Keep pending gradient reductions in flight while unused parameter 

543 # prefetches finish, rather than delaying their launch on those waits. 

544 self._finalize_unused_prefetches() 

545 self.scheduler_ctx.root_bp_state = False 

546 with torch.profiler.record_function(f"root_backward reduce:{self.hsdp_state.module_name}"): 

547 logger.debug( 

548 "hook=root_backward_hook action=final_reduce module=%s", 

549 self.hsdp_state, 

550 ) 

551 self._finalize_comm_fusion_reductions() 

552 self._finalize_per_param_reductions() 

553 self.launch_tp_replicate_reduce_and_apply() 

554 # BACKWARD only deduplicates hooks within one autograd execution; 

555 # accumulated no-sync gradients must outlive this state reset. 

556 for scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

557 scheduler.scheduler_state = None 

558 self.scheduler_ctx.post_backward_final_callback_queued = False 

559 

560 def _finalize_unused_prefetches(self) -> None: 

561 """Wait and release unused all-gathers after autograd has finished. 

562 

563 A module used in forward need not participate in this backward. Its 

564 speculative prefetch may therefore have no pre-backward consumer. 

565 """ 

566 for scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

567 state = scheduler.hsdp_state 

568 if state is None: 

569 continue 

570 for param in state.hsdp_params: 

571 context = param.allgather_comm_ctx 

572 if context.allgather_handle is None and context.allgather_output is None: 

573 continue 

574 with profiler_record(f"finalize_unused_prefetch param:{param}"): 

575 if context.allgather_handle is not None: 

576 context.allgather_handle.wait() 

577 context.allgather_handle = None 

578 context.allgather_output = None 

579 if state.is_shard and param.shard_world_size > 1: 

580 param.free_unsharded_param() 

581 if state.param_group is not None: 

582 for bucket in state.param_group.all_gather_buckets: 

583 result = bucket.all_gather_result 

584 if result is None: 

585 continue 

586 param_fqns = ", ".join(str(param) for param in bucket.hsdp_params) 

587 with profiler_record(f"finalize_unused_prefetch bucket:{state.module_name}:{param_fqns}"): 

588 if result.handle is not None: 

589 result.handle.wait() 

590 result.handle = None 

591 result.all_gather_input = None 

592 result.all_gather_output = None 

593 bucket.all_gather_result = None 

594 

595 def check_state_prefetch_handles_consumed(self) -> None: 

596 """Check that all parameters in the module tree consumed their prefetch work. 

597 

598 Raises: 

599 RuntimeError: If a parameter or fused bucket retains an all-gather 

600 handle because the configured prefetch order is incorrect. 

601 """ 

602 unconsumed_param_fqns = [] 

603 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

604 hsdp_state = hsdp_scheduler.hsdp_state 

605 if hsdp_state is None: 

606 continue 

607 unconsumed_param_fqns.extend( 

608 str(param) for param in hsdp_state.hsdp_params 

609 if param.allgather_comm_ctx.allgather_handle is not None 

610 ) 

611 if hsdp_state.param_group is not None: 

612 for bucket in hsdp_state.param_group.all_gather_buckets: 

613 result = bucket.all_gather_result 

614 if result is not None and result.handle is not None: 

615 unconsumed_param_fqns.extend(str(param) for param in bucket.hsdp_params) 

616 if unconsumed_param_fqns: 

617 param_fqns = ", ".join(dict.fromkeys(unconsumed_param_fqns)) 

618 raise RuntimeError( 

619 f"Unconsumed prefetch handles for parameters: {param_fqns}. " 

620 "The prefetch order is incorrect. Set forward/backward prefetch order " 

621 "according to the model's actual execution order." 

622 ) 

623 

624 def _finalize_comm_fusion_reductions(self) -> None: 

625 """Drain the comm_fusion=True RS/AR pipeline.""" 

626 comm_ctx = self.scheduler_ctx.param_group_comm_ctx 

627 if comm_ctx.all_reduce_param_group is not None: 

628 logger.debug( 

629 "hook=root_backward_hook wait=comm_fusion_all_reduce module=%s", 

630 self.hsdp_state, 

631 ) 

632 comm_ctx.all_reduce_param_group.wait_all_reduce_and_save_grad() 

633 comm_ctx.all_reduce_param_group = None 

634 if comm_ctx.pre_param_group is not None: 

635 logger.debug( 

636 "hook=root_backward_hook wait=comm_fusion_reduce_scatter module=%s", 

637 self.hsdp_state, 

638 ) 

639 comm_ctx.pre_param_group.wait_reduce_scatter_and_issue_all_reduce() 

640 comm_ctx.pre_param_group = None 

641 if comm_ctx.all_reduce_param_group is not None: 

642 comm_ctx.all_reduce_param_group.wait_all_reduce_and_save_grad() 

643 comm_ctx.all_reduce_param_group = None 

644 

645 def _finalize_per_param_reductions(self) -> None: 

646 """Drain the module-tree-local comm_fusion=False RS/AR queues.""" 

647 # A fused root may own non-fused children, so always drain the tree queues. 

648 last_all_reduce_groups = self.hsdp_state._wait_prev_reduce_scatter() 

649 self.hsdp_state._wait_prev_reduce_scatter_without_all_reduce() 

650 self.hsdp_state._issue_prev_fused_all_reduce(last_all_reduce_groups) 

651 self.hsdp_state.wait_and_split_all_reduce_work_groups() 

652 

653 def launch_tp_replicate_reduce_and_apply(self) -> None: 

654 """Run final TP replicate reductions and apply gradients for all states.""" 

655 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

656 hsdp_state = hsdp_scheduler.hsdp_state 

657 if hsdp_state is None: 

658 continue 

659 need_synchronize = False 

660 for hsdp_param in hsdp_state.hsdp_params: 

661 reduced_grad = hsdp_param.all_reduce_comm_ctx.all_reduce_output 

662 if reduced_grad is None: 

663 reduced_grad = hsdp_param.reduce_scatter_comm_ctx.reduce_scatter_output 

664 if reduced_grad is None: 

665 continue 

666 hsdp_param.all_reduce_source_replicate_grad_inplace( 

667 reduced_grad, 

668 hsdp_state.reduce_op_type, 

669 ) 

670 need_synchronize = hsdp_param.apply_reduced_grad(reduced_grad) or need_synchronize 

671 hsdp_param.clear_all_reduce_output() 

672 hsdp_param.clear_reduce_scatter_output() 

673 hsdp_state._sync_current_stream_if_needed(need_synchronize) 

674 

675 @_dynamo_disable 

676 def _backward_hook(self): 

677 """Execute backward hook.""" 

678 if self.scheduler_state == FSDPSchedulerState.BACKWARD: 

679 return 

680 self._hsdp_backward_hook(self.cell, None, None) 

681 

682 @staticmethod 

683 def _register_forward_module_hook(mod, hook) -> None: 

684 """Register forward hook; use ``always_call=True`` when supported (matches PyTorch FSDP).""" 

685 sig = inspect.signature(mod.register_forward_hook) 

686 if "always_call" in sig.parameters: 

687 mod.register_forward_hook(hook, prepend=False, always_call=True) 

688 else: 

689 mod.register_forward_hook(hook, prepend=False) 

690 

691 def _register_forward_backward_hooks(self): 

692 """Register module forward and backward hook on all managed modules.""" 

693 if self._fsdp_group_post_pending is None: 

694 for mod in self.modules: 

695 mod.register_forward_pre_hook(self._forward_pre_hook, with_kwargs=True) 

696 mod.register_forward_hook(self._forward_hook) 

697 return 

698 for mod in self.modules: 

699 mod.register_forward_pre_hook(self._grouped_forward_pre_hook, with_kwargs=True) 

700 grouped_forward_hook = _dynamo_disable(self._make_grouped_forward_post_hook(mod)) 

701 self._register_forward_module_hook(mod, grouped_forward_hook)