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

388 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +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 # all_hsdp_schedulers (for one module tree structure), deduplicated at 

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

83 # whose modules share one scheduler. 

84 self.all_hsdp_schedulers = [] 

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

86 self._param_fqn_initialized = False 

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

88 self.pre_reduce_scatter_params = [] 

89 self.pre_all_reduce_params = [] 

90 self.pre_direct_all_reduce_grads = [] 

91 self.pre_all_reduce_groups = [] 

92 self.pending_all_reduce_groups = [] 

93 self.param_group_comm_ctx = ParamGroupCommCtx() 

94 

95 

96class HSDPSchedulerV2: 

97 """HSDPScheduler is used to scheduler hsdp""" 

98 

99 def __init__( 

100 self, 

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

102 mesh, 

103 reshard_after_forward, 

104 shard_placement_fn, 

105 mp_policy, 

106 offload_policy, 

107 ignored_params, 

108 replicate_params, 

109 device, 

110 comm_fusion, 

111 comm_fusion_zero_copy=False, 

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

113 ): 

114 """init hsdp scheduler. 

115 

116 Args: 

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

118 mesh: Explicit data-parallel device mesh. 

119 reshard_after_forward: Whether to reshard parameters after forward. 

120 shard_placement_fn: Optional function selecting the parameter shard dimension. 

121 mp_policy: Mixed-precision policy. 

122 offload_policy: Parameter offload policy. 

123 ignored_params: Parameters excluded from fully_shard management. 

124 replicate_params: Managed parameters that remain replicated. 

125 device: Target device for parameters. 

126 comm_fusion: Communication fusion configuration. 

127 comm_fusion_zero_copy: Whether fused communication writes into views. 

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

129 """ 

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

131 self.cell = self.modules[0] 

132 self.mesh: DeviceMesh = mesh 

133 self.shard_placement_fn = shard_placement_fn 

134 self.mp_policy = mp_policy 

135 self.offload_policy = offload_policy 

136 self.comm_fusion_policy = CommFusionPolicy(comm_fusion, comm_fusion_zero_copy) 

137 self.ignored_params = ignored_params 

138 self.replicate_params = replicate_params 

139 self.device = device 

140 self.reshard_after_forward = reshard_after_forward 

141 self.source_shard_infos = source_shard_infos 

142 self.scheduler_state = None 

143 self.forward_prefetch_cells = [] 

144 self.backward_prefetch_cells = [] 

145 self._backup_forward_fetch = None 

146 # Flag to identify root module. 

147 self._is_root = True 

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

149 self.scheduler_ctx = HSDPSchedulerContext() 

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

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

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

153 self._new_cell_state() 

154 self._register_hooks() 

155 

156 def _new_cell_state(self): 

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

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

159 # module for ``ParamGroupCommCtx``. 

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

161 HSDPStateV2, 

162 ) 

163 

164 self.hsdp_state = HSDPStateV2( 

165 self.modules, 

166 self.mesh, 

167 self.shard_placement_fn, 

168 self.comm_fusion_policy, 

169 self.mp_policy, 

170 self.offload_policy, 

171 self.ignored_params, 

172 self.replicate_params, 

173 self.scheduler_ctx, 

174 self.device, 

175 source_shard_infos=self.source_shard_infos, 

176 ) 

177 

178 def _register_hooks(self): 

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

180 self._register_forward_backward_hooks() 

181 

182 def _get_managed_params(self): 

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

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

185 

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

187 """Set reshard_after_forward flag. 

188 

189 Args: 

190 reshard_after_forward: Whether to reshard parameters after forward. 

191 """ 

192 if not isinstance(reshard_after_forward, bool): 

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

194 self.reshard_after_forward = reshard_after_forward 

195 

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

197 """Set reshard_after_backward flag. 

198 

199 Args: 

200 reshard_after_backward: Whether to reshard after backward completes. 

201 """ 

202 if not isinstance(reshard_after_backward, bool): 

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

204 if self.hsdp_state is not None: 

205 self.hsdp_state.reshard_after_backward = reshard_after_backward 

206 

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

208 """Set requires_all_reduce flag. 

209 

210 Args: 

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

212 """ 

213 if not isinstance(requires_all_reduce, bool): 

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

215 if self.hsdp_state is not None: 

216 self.hsdp_state.set_requires_all_reduce(requires_all_reduce) 

217 

218 @_dynamo_disable 

219 def reset_iter_state(self) -> None: 

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

221 self.scheduler_ctx.root_bp_state = False 

222 self.scheduler_ctx.pre_reduce_scatter_params.clear() 

223 self.scheduler_ctx.pre_all_reduce_params.clear() 

224 self.scheduler_ctx.pre_direct_all_reduce_grads.clear() 

225 self.scheduler_ctx.pre_all_reduce_groups.clear() 

226 self.scheduler_ctx.pending_all_reduce_groups.clear() 

227 self.scheduler_ctx.param_group_comm_ctx.pre_param_group = None 

228 self.scheduler_ctx.param_group_comm_ctx.all_reduce_param_group = None 

229 self.scheduler_ctx.param_group_comm_ctx.comm_handle = None 

230 self.scheduler_state = None 

231 if self._fsdp_group_post_pending is not None: 

232 self._fsdp_group_post_pending.clear() 

233 self._restore_forward_prefetch_after_recompute() 

234 self.hsdp_state.reset_iter_state() 

235 

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

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

238 

239 Args: 

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

241 """ 

242 if not isinstance(requires_grad_sync, bool): 

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

244 self.hsdp_state.set_requires_grad_sync(requires_grad_sync) 

245 

246 # pylint: disable=W0613 

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

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

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

250 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

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

252 return args, kwargs 

253 if self.scheduler_ctx.root_bp_state: 

254 self._disable_forward_prefetch_for_recompute() 

255 if self.scheduler_ctx.root_module is None: 

256 tree_ctx = self.scheduler_ctx 

257 tree_ctx.root_module = self.cell 

258 registered_schedulers = set() 

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

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

261 if isinstance(module, HSDPModule): 

262 submod_scheduler = module.hsdp_scheduler 

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

264 continue 

265 registered_schedulers.add(id(submod_scheduler)) 

266 if submod_scheduler.scheduler_ctx is not tree_ctx: 

267 if submod_scheduler.scheduler_ctx.root_module is not None: 

268 raise ValueError( 

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

270 ) 

271 submod_scheduler.scheduler_ctx = tree_ctx 

272 submod_scheduler.hsdp_state.scheduler_ctx = tree_ctx 

273 if submod_scheduler.hsdp_state.param_group is not None: 

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

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

276 submod_scheduler.hsdp_state.module_name = module_name 

277 tree_ctx.all_hsdp_schedulers.append(submod_scheduler) 

278 

279 self.scheduler_state = FSDPSchedulerState.PRE_FORWARD 

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

281 self._init_params_fqn() 

282 self._lazy_init_all_states() 

283 self.scheduler_ctx.lazy_init_done = True 

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

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

286 args = apply_to_tensors(cast_fn, args) 

287 kwargs = apply_to_tensors(cast_fn, kwargs) 

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

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

290 self.hsdp_state.unshard() 

291 for prefetch_cell in self.forward_prefetch_cells: 

292 prefetch_state = prefetch_cell.hsdp_scheduler.hsdp_state 

293 with profiler_record(f"pre_forward prefetch:" 

294 f"{prefetch_state.module_name}"): 

295 logger.debug( 

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

297 self.hsdp_state, 

298 prefetch_state, 

299 ) 

300 prefetch_state.prefetch() 

301 return args, kwargs 

302 

303 def _lazy_init_all_states(self): 

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

305 for submod_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

306 hsdp_state = submod_scheduler.hsdp_state 

307 if hsdp_state: 

308 hsdp_state.lazy_init() 

309 

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

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

312 return 

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

314 return 

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

316 param_to_hsdp_param = {} 

317 for submod_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

318 hsdp_state = submod_scheduler.hsdp_state 

319 if hsdp_state is None: 

320 continue 

321 for hsdp_param in hsdp_state.hsdp_params: 

322 orig_param = hsdp_param.sharded_param 

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

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

325 if orig_param not in param_to_hsdp_param: 

326 param_to_hsdp_param[orig_param] = hsdp_param 

327 

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

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

330 visited_params = set() 

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

332 if parameter in visited_params: 

333 continue 

334 visited_params.add(parameter) 

335 hsdp_param = param_to_hsdp_param.get(parameter) 

336 if hsdp_param is not None: 

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

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

339 

340 # pylint: disable=W0613, R1710 

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

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

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

344 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

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

346 return 

347 self.scheduler_state = FSDPSchedulerState.FORWARD 

348 if self.reshard_after_forward: 

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

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

351 self.hsdp_state.shard() 

352 if self.mp_policy.output_dtype is not None: 

353 outputs = apply_to_tensors( 

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

355 outputs, 

356 ) 

357 return outputs 

358 

359 # pylint: disable=W0613 

360 def _hsdp_backward_pre_hook(self, cell, grad_outputs): 

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

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

363 self.scheduler_state = FSDPSchedulerState.PRE_BACKWARD 

364 if self.reshard_after_forward: 

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

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

367 self.hsdp_state.unshard() 

368 for prefetch_cell in self.backward_prefetch_cells: 

369 prefetch_state = prefetch_cell.hsdp_scheduler.hsdp_state 

370 with profiler_record(f"pre_backward prefetch:" 

371 f"{prefetch_state.module_name}"): 

372 logger.debug( 

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

374 self.hsdp_state, 

375 prefetch_state, 

376 ) 

377 prefetch_state.prefetch() 

378 

379 # pylint: disable=W0613 

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

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

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

383 self.scheduler_state = FSDPSchedulerState.BACKWARD 

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

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

386 self.hsdp_state.post_backward() 

387 if self._fsdp_group_post_pending is not None: 

388 self._fsdp_group_post_pending.clear() 

389 

390 # pylint: disable=W0613 

391 @staticmethod 

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

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

394 

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

396 """ 

397 return None 

398 

399 @staticmethod 

400 def _grouped_forward_post_hook_skip(outputs) -> None: 

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

402 

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

404 """ 

405 return None 

406 

407 @_dynamo_disable 

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

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

410 pending = self._fsdp_group_post_pending 

411 if pending is None: 

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

413 if len(pending) == 0: 

414 pending.update(self.modules) 

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

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

417 

418 def _make_grouped_forward_post_hook(self, mod): 

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

420 

421 def grouped_post_hook(cell, inputs, outputs): 

422 pending = self._fsdp_group_post_pending 

423 if pending is None: 

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

425 pending.discard(mod) 

426 if len(pending) == 0: 

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

428 return self._grouped_forward_post_hook_skip(outputs) 

429 

430 return grouped_post_hook 

431 

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

433 """Set cells prefetched during forward. 

434 

435 Args: 

436 hsdp_cell_list: HSDP cells to prefetch ahead of forward. 

437 """ 

438 self.forward_prefetch_cells = hsdp_cell_list 

439 

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

441 """Set cells prefetched during backward. 

442 

443 Args: 

444 hsdp_cell_list: HSDP cells to prefetch ahead of backward. 

445 """ 

446 self.backward_prefetch_cells = hsdp_cell_list 

447 

448 def _disable_forward_prefetch_for_recompute(self) -> None: 

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

450 self._backup_forward_fetch = self.forward_prefetch_cells 

451 self.forward_prefetch_cells = [] 

452 

453 def _restore_forward_prefetch_after_recompute(self) -> None: 

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

455 if self._backup_forward_fetch is None: 

456 return 

457 self.forward_prefetch_cells = self._backup_forward_fetch 

458 self._backup_forward_fetch = None 

459 

460 def _register_post_backward_hook(self, args, kwargs): 

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

462 if not torch.is_grad_enabled(): 

463 return args, kwargs 

464 args_list, args_spec = tree_flatten(args) 

465 kwargs_list, kwargs_spec = tree_flatten(kwargs) 

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

467 inp_tensor_indices: List[int] = [] 

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

469 for i, obj in enumerate(args_kwargs_list): 

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

471 inp_tensor_indices.append(i) 

472 inp_tensors.append(obj) 

473 if len(inp_tensors) == 0: 

474 return args, kwargs # no tensors that require gradients 

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

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

477 args_kwargs_list[inp_tensor_idx] = processed_tensor 

478 args_list = args_kwargs_list[: len(args_list)] 

479 kwargs_list = args_kwargs_list[len(args_list) :] 

480 args = tree_unflatten(args_list, args_spec) 

481 kwargs = tree_unflatten(kwargs_list, kwargs_spec) 

482 return args, kwargs 

483 

484 @_dynamo_disable 

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

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

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

488 return self._register_post_backward_hook(args, kwargs) 

489 

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

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

492 flat_outputs, _ = tree_flatten(outputs) 

493 for output in flat_outputs: 

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

495 handle_ref = [None] 

496 # pylint: disable=C0103, W0102 

497 

498 def wrapper_for_backward_pre_hook(grad, _handle_ref=handle_ref): 

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

500 handle = _handle_ref[0] 

501 if handle is not None: 

502 handle.remove() 

503 return self._backward_pre_hook(grad) 

504 # pylint: enable=C0103, W0102 

505 handle = output.register_hook(wrapper_for_backward_pre_hook) 

506 handle_ref[0] = handle 

507 

508 @_dynamo_disable 

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

510 """Execute forward hook.""" 

511 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

512 return 

513 self._register_backward_pre_hook(outputs) 

514 if self.scheduler_ctx.root_bp_state: 

515 self._restore_forward_prefetch_after_recompute() 

516 return 

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

518 

519 # pylint: disable=W0212 

520 @_dynamo_disable 

521 def _backward_pre_hook(self, grad): 

522 """Execute backward pre hook.""" 

523 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD: 

524 return grad 

525 if self._is_root: 

526 Variable._execution_engine.queue_callback(self._root_backward_hook) 

527 self.scheduler_ctx.root_bp_state = True 

528 self._hsdp_backward_pre_hook(self.cell, None) 

529 return grad 

530 

531 @_dynamo_disable 

532 def _root_backward_hook(self): 

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

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

535 self.check_state_prefetch_handles_consumed() 

536 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

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

538 hsdp_scheduler._backward_hook() 

539 self.scheduler_ctx.root_bp_state = False 

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

541 logger.debug( 

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

543 self.hsdp_state, 

544 ) 

545 self._finalize_comm_fusion_reductions() 

546 self._finalize_per_param_reductions() 

547 self.launch_tp_replicate_reduce_and_apply() 

548 

549 def check_state_prefetch_handles_consumed(self) -> None: 

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

551 

552 Raises: 

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

554 handle because the configured prefetch order is incorrect. 

555 """ 

556 unconsumed_param_fqns = [] 

557 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

558 hsdp_state = hsdp_scheduler.hsdp_state 

559 if hsdp_state is None: 

560 continue 

561 unconsumed_param_fqns.extend( 

562 str(param) for param in hsdp_state.hsdp_params 

563 if param.allgather_comm_ctx.allgather_handle is not None 

564 ) 

565 if hsdp_state.param_group is not None: 

566 for bucket in hsdp_state.param_group.all_gather_buckets: 

567 result = bucket.all_gather_result 

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

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

570 if unconsumed_param_fqns: 

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

572 raise RuntimeError( 

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

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

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

576 ) 

577 

578 def _finalize_comm_fusion_reductions(self) -> None: 

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

580 comm_ctx = self.scheduler_ctx.param_group_comm_ctx 

581 if comm_ctx.all_reduce_param_group is not None: 

582 logger.debug( 

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

584 self.hsdp_state, 

585 ) 

586 comm_ctx.all_reduce_param_group.wait_all_reduce_and_save_grad() 

587 comm_ctx.all_reduce_param_group = None 

588 if comm_ctx.pre_param_group is not None: 

589 logger.debug( 

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

591 self.hsdp_state, 

592 ) 

593 comm_ctx.pre_param_group.wait_reduce_scatter_and_issue_all_reduce() 

594 comm_ctx.pre_param_group = None 

595 if comm_ctx.all_reduce_param_group is not None: 

596 comm_ctx.all_reduce_param_group.wait_all_reduce_and_save_grad() 

597 comm_ctx.all_reduce_param_group = None 

598 

599 def _finalize_per_param_reductions(self) -> None: 

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

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

602 last_all_reduce_groups = self.hsdp_state._wait_prev_reduce_scatter() 

603 self.hsdp_state._wait_prev_reduce_scatter_without_all_reduce() 

604 self.hsdp_state._issue_prev_fused_all_reduce(last_all_reduce_groups) 

605 self.hsdp_state.wait_and_split_all_reduce_work_groups() 

606 

607 def launch_tp_replicate_reduce_and_apply(self) -> None: 

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

609 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers: 

610 hsdp_state = hsdp_scheduler.hsdp_state 

611 if hsdp_state is None: 

612 continue 

613 need_synchronize = False 

614 for hsdp_param in hsdp_state.hsdp_params: 

615 reduced_grad = hsdp_param.all_reduce_comm_ctx.all_reduce_output 

616 if reduced_grad is None: 

617 reduced_grad = hsdp_param.reduce_scatter_comm_ctx.reduce_scatter_output 

618 if reduced_grad is None: 

619 continue 

620 hsdp_param.all_reduce_source_replicate_grad_inplace( 

621 reduced_grad, 

622 hsdp_state.reduce_op_type, 

623 ) 

624 need_synchronize = hsdp_param.apply_reduced_grad(reduced_grad) or need_synchronize 

625 hsdp_param.clear_all_reduce_output() 

626 hsdp_param.clear_reduce_scatter_output() 

627 hsdp_state._sync_current_stream_if_needed(need_synchronize) 

628 

629 @_dynamo_disable 

630 def _backward_hook(self): 

631 """Execute backward hook.""" 

632 if self.scheduler_state == FSDPSchedulerState.BACKWARD: 

633 return 

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

635 

636 @staticmethod 

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

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

639 sig = inspect.signature(mod.register_forward_hook) 

640 if "always_call" in sig.parameters: 

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

642 else: 

643 mod.register_forward_hook(hook, prepend=False) 

644 

645 def _register_forward_backward_hooks(self): 

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

647 if self._fsdp_group_post_pending is None: 

648 for mod in self.modules: 

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

650 mod.register_forward_hook(self._forward_hook) 

651 return 

652 for mod in self.modules: 

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

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

655 self._register_forward_module_hook(mod, grouped_forward_hook)