Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / torch / fully_shard / scheduler.py: 81%
171 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-25 04:27 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-25 04:27 +0800
1# Copyright 2025-2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Torch HSDP scheduler"""
16import functools
17import inspect
18from typing import Callable, List, ParamSpec, TypeVar
20import torch
21from torch.autograd import Variable
22from torch.utils._pytree import tree_flatten, tree_unflatten
24from hyper_parallel.core.dtensor.dtensor import DTensor
25from hyper_parallel.platform import get_platform
26from hyper_parallel.tools.logging import get_logger
27from hyper_parallel.core.fully_shard.hsdp_scheduler import HSDPSchedulerV2, FSDPSchedulerState
28from hyper_parallel.platform.torch.fully_shard.hook_function import PostBackwardFunction
29from hyper_parallel.platform.torch.fully_shard.state import TorchHSDPStateV2
31logger = get_logger("FSDP")
33_P = ParamSpec("_P")
34_R = TypeVar("_R")
37def _dynamo_disable(func: Callable[_P, _R]) -> Callable[_P, _R]:
38 """Disable Dynamo tracing while an FSDP runtime hook executes."""
40 @functools.wraps(func)
41 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
42 return torch._dynamo.disable(
43 func,
44 recursive=True,
45 )(*args, **kwargs)
47 return wrapper
50class TorchHSDPSchedulerV2(HSDPSchedulerV2):
51 """TorchHSDPScheduler is used to implement optimizer level."""
53 def __init__(self, *args, **kwargs):
54 """Initialize TorchHSDPSchedulerV2 and register forward/backward hooks."""
55 super().__init__(*args, **kwargs)
57 def _register_hooks(self):
58 """Register hooks."""
59 self._register_forward_backward_hooks()
61 def _init_platform(self):
62 """Initialize the platform."""
63 # pylint: disable=C0415
64 from hyper_parallel.platform.torch.platform import TorchPlatform
65 self.platform = get_platform()
66 if not isinstance(self.platform, TorchPlatform):
67 raise ValueError(f"TorchHSDPSchedulerV2 expect TorchPlatform, but got type: {type(self.platform)}")
69 def _new_cell_state(self):
70 """Create a new cell state for torch."""
71 self.hsdp_state = TorchHSDPStateV2(
72 self.modules,
73 self.mesh,
74 self.shard_placement_fn,
75 self.comm_fusion_policy,
76 self.mp_policy,
77 self.offload_policy,
78 self.ignored_params,
79 self.replicate_params,
80 self.platform,
81 self.scheduler_ctx,
82 self.device,
83 source_shard_infos=self.source_shard_infos,
84 )
86 def _register_post_backward_hook(self, args, kwargs):
87 """Wrap forward args/kwargs through PostBackwardFunction to register backward hook."""
88 if not torch.is_grad_enabled():
89 return args, kwargs
90 args_list, args_spec = tree_flatten(args)
91 kwargs_list, kwargs_spec = tree_flatten(kwargs)
92 args_kwargs_list = list(args_list) + list(kwargs_list)
93 inp_tensor_indices: List[int] = []
94 inp_tensors: List[torch.Tensor] = []
95 for i, obj in enumerate(args_kwargs_list):
96 if torch.is_tensor(obj) and obj.requires_grad:
97 inp_tensor_indices.append(i)
98 inp_tensors.append(obj)
99 if len(inp_tensors) == 0:
100 return args, kwargs # no tensors that require gradients
101 processed_tensors = PostBackwardFunction.apply(self, *inp_tensors)
102 for inp_tensor_idx, processed_tensor in zip(inp_tensor_indices, processed_tensors):
103 args_kwargs_list[inp_tensor_idx] = processed_tensor
104 args_list = args_kwargs_list[: len(args_list)]
105 kwargs_list = args_kwargs_list[len(args_list) :]
106 args = tree_unflatten(args_list, args_spec)
107 kwargs = tree_unflatten(kwargs_list, kwargs_spec)
108 return args, kwargs
110 @_dynamo_disable
111 def _forward_pre_hook(self, cell, args, kwargs):
112 """Execute forward pre hook and set up backward hook."""
113 args, kwargs = self._hsdp_forward_pre_hook(cell, args, kwargs)
114 return self._register_post_backward_hook(args, kwargs)
116 def _register_backward_pre_hook(self, outputs):
117 """Register gradient hooks on all requires-grad outputs to trigger backward pre hook."""
118 flat_outputs, _ = tree_flatten(outputs)
119 for output in flat_outputs:
120 if isinstance(output, torch.Tensor) and output.requires_grad:
121 handle_ref = [None]
122 # pylint: disable=C0103, W0102
124 def wrapper_for_backward_pre_hook(grad, _handle_ref=handle_ref):
125 """Remove this hook after it fires to prevent accmulation"""
126 handle = _handle_ref[0]
127 if handle is not None:
128 handle.remove()
129 return self._backward_pre_hook(grad)
130 # pylint: enable=C0103, W0102
131 handle = output.register_hook(wrapper_for_backward_pre_hook)
132 handle_ref[0] = handle
133 return outputs
135 @_dynamo_disable
136 def _forward_hook(self, cell, inputs, outputs): # pylint: disable=R1710
137 """Execute forward hook."""
138 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD:
139 return
140 self._register_backward_pre_hook(outputs)
141 if self.scheduler_ctx.root_bp_state:
142 self._restore_forward_prefetch_after_recompute()
143 return
144 return self._hsdp_forward_hook(cell, inputs, outputs)
146 # pylint: disable=W0212
147 @_dynamo_disable
148 def _backward_pre_hook(self, grad):
149 """Execute backward pre hook."""
150 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD:
151 return grad
152 if self._is_root:
153 Variable._execution_engine.queue_callback(self._root_backward_hook)
154 self.scheduler_ctx.root_bp_state = True
155 self._hsdp_backward_pre_hook(self.cell, None)
156 return grad
158 @_dynamo_disable
159 def _root_backward_hook(self):
160 """Drain all DP pipelines, then run final TP reduction and apply gradients."""
161 logger.debug("hook=root_backward_hook enter module=%s", self.hsdp_state)
162 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers:
163 # let modules which are not triggered backward_hook launch backward communication.
164 hsdp_scheduler._backward_hook()
165 self.scheduler_ctx.root_bp_state = False
166 with torch.profiler.record_function(f"root_backward reduce:{self.hsdp_state.module_name}"):
167 logger.debug(
168 "hook=root_backward_hook action=final_reduce module=%s",
169 self.hsdp_state,
170 )
171 self._finalize_comm_fusion_reductions()
172 self._finalize_per_param_reductions()
173 self.launch_tp_replicate_reduce_and_apply()
175 def _finalize_comm_fusion_reductions(self) -> None:
176 """Drain the comm_fusion=True RS/AR pipeline."""
177 comm_ctx = self.scheduler_ctx.param_group_comm_ctx
178 if comm_ctx.all_reduce_param_group is not None:
179 logger.debug(
180 "hook=root_backward_hook wait=comm_fusion_all_reduce module=%s",
181 self.hsdp_state,
182 )
183 comm_ctx.all_reduce_param_group.wait_all_reduce_and_save_grad()
184 comm_ctx.all_reduce_param_group = None
185 if comm_ctx.pre_param_group is not None:
186 logger.debug(
187 "hook=root_backward_hook wait=comm_fusion_reduce_scatter module=%s",
188 self.hsdp_state,
189 )
190 comm_ctx.pre_param_group.wait_reduce_scatter_and_issue_all_reduce()
191 comm_ctx.pre_param_group = None
192 if comm_ctx.all_reduce_param_group is not None:
193 comm_ctx.all_reduce_param_group.wait_all_reduce_and_save_grad()
194 comm_ctx.all_reduce_param_group = None
196 def _finalize_per_param_reductions(self) -> None:
197 """Drain the module-tree-local comm_fusion=False RS/AR queues."""
198 # A fused root may own non-fused children, so always drain the tree queues.
199 last_all_reduce_groups = self.hsdp_state._wait_prev_reduce_scatter()
200 self.hsdp_state._wait_prev_reduce_scatter_without_all_reduce()
201 self.hsdp_state._issue_prev_fused_all_reduce(last_all_reduce_groups)
202 self.hsdp_state.wait_and_split_all_reduce_work_groups()
204 def launch_tp_replicate_reduce_and_apply(self) -> None:
205 """Run final TP replicate reductions and apply gradients for all states."""
206 for hsdp_scheduler in self.scheduler_ctx.all_hsdp_schedulers:
207 hsdp_state = hsdp_scheduler.hsdp_state
208 if hsdp_state is None:
209 continue
210 need_synchronize = False
211 for hsdp_param in hsdp_state.hsdp_params:
212 reduced_grad = hsdp_param.all_reduce_comm_ctx.all_reduce_output
213 if reduced_grad is None:
214 reduced_grad = hsdp_param.reduce_scatter_comm_ctx.reduce_scatter_output
215 if reduced_grad is None:
216 continue
217 hsdp_param.all_reduce_source_replicate_grad_inplace(
218 reduced_grad,
219 hsdp_state.reduce_op_type,
220 )
221 need_synchronize = hsdp_param.apply_reduced_grad(reduced_grad) or need_synchronize
222 hsdp_param.clear_all_reduce_output()
223 hsdp_param.clear_reduce_scatter_output()
224 hsdp_state._sync_current_stream_if_needed(need_synchronize)
226 @_dynamo_disable
227 def reset_iter_state(self) -> None:
228 """Reset Torch fully_shard iteration state after communication is complete."""
229 super().reset_iter_state()
230 self.hsdp_state.reset_iter_state()
231 comm_ctx = self.scheduler_ctx.param_group_comm_ctx
232 comm_ctx.pre_param_group = None
233 comm_ctx.all_reduce_param_group = None
235 @_dynamo_disable
236 def _backward_hook(self):
237 """Execute backward hook."""
238 if self.scheduler_state == FSDPSchedulerState.BACKWARD:
239 return
240 self._hsdp_backward_hook(self.cell, None, None)
242 # pylint: disable=W0613
243 @staticmethod
244 def _grouped_forward_pre_hook_skip(cell, args, kwargs) -> None:
245 """Override base ``(args, kwargs)`` return; ``nn.Module`` pre-hook uses ``None`` for no-op."""
246 return None
248 @staticmethod
249 def _grouped_forward_post_hook_skip(outputs) -> None:
250 """Override base output pass-through; forward hook uses ``None`` for no-op."""
251 return None
253 @_dynamo_disable
254 def _grouped_forward_pre_hook(self, cell, args, kwargs):
255 """Run the grouped FSDP pre-forward hook outside Dynamo tracing."""
256 return super()._grouped_forward_pre_hook(cell, args, kwargs)
258 def _register_forward_module_hook(self, mod, hook) -> None:
259 """Register forward hook; use ``always_call=True`` when supported (matches PyTorch FSDP)."""
260 sig = inspect.signature(mod.register_forward_hook)
261 if "always_call" in sig.parameters:
262 mod.register_forward_hook(hook, prepend=False, always_call=True)
263 else:
264 mod.register_forward_hook(hook, prepend=False)
266 def _register_forward_backward_hooks(self):
267 """Register module forward and backward hook on all managed modules."""
268 if self._fsdp_group_post_pending is None:
269 for mod in self.modules:
270 mod.register_forward_pre_hook(self._forward_pre_hook, with_kwargs=True)
271 mod.register_forward_hook(self._forward_hook)
272 return
273 for mod in self.modules:
274 mod.register_forward_pre_hook(self._grouped_forward_pre_hook, with_kwargs=True)
275 grouped_forward_hook = _dynamo_disable(self._make_grouped_forward_post_hook(mod))
276 self._register_forward_module_hook(mod, grouped_forward_hook)