Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / torch / fully_shard / scheduler.py: 60%
149 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +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 inspect
17import torch
18from typing import List
19from torch.autograd import Variable
20from torch.utils._pytree import tree_flatten, tree_unflatten
21from hyper_parallel.core.dtensor.dtensor import DTensor
22from hyper_parallel.tools.logging import get_logger
23from hyper_parallel.core.fully_shard.hsdp_scheduler import HSDPSchedulerV2, FSDPSchedulerState
24from hyper_parallel.core.fully_shard.utils import FSDPMeshInfo, DDPMeshInfo, HSDPMeshInfo
25from hyper_parallel.platform.torch.fully_shard.hook_function import PostBackwardFunction
26from hyper_parallel.platform.torch.fully_shard.state import TorchHSDPStateV2
27from hyper_parallel.platform.torch.fully_shard.param_group import get_comm_ctx
28from hyper_parallel.platform import get_platform
30logger = get_logger("FSDP")
33class TorchHSDPSchedulerV2(HSDPSchedulerV2):
34 """TorchHSDPScheduler is used to implement optimizer level."""
36 def __init__(self, *args, **kwargs):
37 """Initialize TorchHSDPSchedulerV2 and register forward/backward hooks."""
38 super().__init__(*args, **kwargs)
40 def _register_hooks(self):
41 """Register hooks."""
42 self._register_forward_backward_hooks()
44 def _init_platform(self):
45 """Initialize the platform."""
46 # pylint: disable=C0415
47 from hyper_parallel.platform.torch.platform import TorchPlatform
48 self.platform = get_platform()
49 if not isinstance(self.platform, TorchPlatform):
50 raise ValueError(f"TorchHSDPSchedulerV2 expect TorchPlatform, but got type: {type(self.platform)}")
52 def _new_cell_state(self):
53 """Create a new cell state for torch."""
54 params = self._get_managed_params()
55 if self.mesh is None:
56 compat_meshes = [
57 param.device_mesh for param in params if isinstance(param, DTensor)
58 ]
59 compat_mesh = compat_meshes[0] if compat_meshes else None
60 if compat_mesh is None:
61 raise ValueError(
62 "Cannot build fully_shard compatibility mesh_info "
63 "without a DTensor parameter mesh."
64 )
65 compat_mesh_hash = compat_mesh.to_hash()
66 for param_mesh in compat_meshes[1:]:
67 if param_mesh.to_hash() != compat_mesh_hash:
68 raise ValueError(
69 "fully_shard compatibility mode requires all DTensor parameters to share the same mesh."
70 )
71 self.mesh_info = DDPMeshInfo(mesh=compat_mesh, replicate_mesh_dim=0)
72 elif self.mesh.ndim == 1:
73 self.mesh_info = FSDPMeshInfo(mesh=self.mesh, shard_mesh_dim=0)
74 elif self.mesh.ndim == 2:
75 self.mesh_info = HSDPMeshInfo(mesh=self.mesh, shard_mesh_dim=1, replicate_mesh_dim=0)
76 else:
77 raise ValueError(
78 "fully_shard only supports explicit 1D DP/FSDP meshes or 2D HSDP meshes. "
79 f"Got mesh.ndim={self.mesh.ndim}."
80 )
81 self.hsdp_state = TorchHSDPStateV2(
82 self.modules, self.mesh_info, self.config, self.platform, self.device
83 )
85 def _register_post_backward_hook(self, args, kwargs):
86 """Wrap forward args/kwargs through PostBackwardFunction to register backward hook."""
87 if not torch.is_grad_enabled():
88 return args, kwargs
89 args_list, args_spec = tree_flatten(args)
90 kwargs_list, kwargs_spec = tree_flatten(kwargs)
91 args_kwargs_list = list(args_list) + list(kwargs_list)
92 inp_tensor_indices: List[int] = []
93 inp_tensors: List[torch.Tensor] = []
94 for i, obj in enumerate(args_kwargs_list):
95 if torch.is_tensor(obj) and obj.requires_grad:
96 inp_tensor_indices.append(i)
97 inp_tensors.append(obj)
98 if len(inp_tensors) == 0:
99 return args, kwargs # no tensors that require gradients
100 processed_tensors = PostBackwardFunction.apply(self, *inp_tensors)
101 for inp_tensor_idx, processed_tensor in zip(inp_tensor_indices, processed_tensors):
102 args_kwargs_list[inp_tensor_idx] = processed_tensor
103 args_list = args_kwargs_list[: len(args_list)]
104 kwargs_list = args_kwargs_list[len(args_list) :]
105 args = tree_unflatten(args_list, args_spec)
106 kwargs = tree_unflatten(kwargs_list, kwargs_spec)
107 return args, kwargs
109 def _forward_pre_hook(self, cell, args, kwargs):
110 """Execute forward pre hook and set up backward hook."""
111 args, kwargs = self._hsdp_forward_pre_hook(cell, args, kwargs)
112 return self._register_post_backward_hook(args, kwargs)
114 def _register_backward_pre_hook(self, outputs):
115 """Register gradient hooks on all requires-grad outputs to trigger backward pre hook."""
116 flat_outputs, _ = tree_flatten(outputs)
117 for output in flat_outputs:
118 if isinstance(output, torch.Tensor) and output.requires_grad:
119 handle_ref = [None]
120 # pylint: disable=C0103, W0102
122 def wrapper_for_backward_pre_hook(grad, _handle_ref=handle_ref):
123 """Remove this hook after it fires to prevent accmulation"""
124 handle = _handle_ref[0]
125 if handle is not None:
126 handle.remove()
127 return self._backward_pre_hook(grad)
128 # pylint: enable=C0103, W0102
129 handle = output.register_hook(wrapper_for_backward_pre_hook)
130 handle_ref[0] = handle
131 return outputs
133 def _forward_hook(self, cell, inputs, outputs): # pylint: disable=R1710
134 """Execute forward hook."""
135 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD:
136 return
137 self._register_backward_pre_hook(outputs)
138 if HSDPSchedulerV2.root_bp_state:
139 self._restore_forward_prefetch_after_recompute()
140 return
141 return self._hsdp_forward_hook(cell, inputs, outputs)
143 # pylint: disable=W0212
144 def _backward_pre_hook(self, grad):
145 """Execute backward pre hook."""
146 Variable._execution_engine.queue_callback(self._root_backward_hook)
147 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD:
148 return grad
149 HSDPSchedulerV2.root_bp_state = True
150 self._hsdp_backward_pre_hook(self.cell, None)
151 return grad
153 def _root_backward_hook(self, force_reduce=False):
154 """Finalize gradient reduction for the outermost HSDP module after backward.
156 ``apply_final_reduce`` selects between two cases, distinguished by whether
157 this unit's forward input was differentiable:
159 * input ``requires_grad=False`` (the common case): no ``PostBackwardFunction``
160 is inserted on the input, so ``scheduler_state != BACKWARD`` and this hook
161 owns the finalization -- it drains the pending reductions (comm_fusion=True
162 via ``CommContext``; comm_fusion=False via the last module's reduce_scatter
163 + allreduce) and applies the per-parameter gradients.
164 * input ``requires_grad=True`` (a boundary case where the unit is fed a
165 differentiable activation from an enclosing graph): the input's
166 ``PostBackwardFunction`` drives ``scheduler_state == BACKWARD``, so the
167 natural path does not finalize here. ``force_reduce`` lets a caller that
168 owns that backward boundary demand the drain happen now rather than have it
169 deferred a step.
171 ``root_bp_state`` -- whether the top-level root module's backward is still in
172 flight, used to gate forward prefetch during activation recompute -- is
173 independent of the reduce branch: it is cleared only by the root module's own
174 hook, keyed on ``_is_root``.
175 """
176 logger.debug("hook=root_backward_hook enter module=%s", self.hsdp_state)
177 apply_final_reduce = self.scheduler_state != FSDPSchedulerState.BACKWARD
178 self._backward_hook()
179 if self._is_root:
180 HSDPSchedulerV2.root_bp_state = False
181 if apply_final_reduce or force_reduce:
182 with torch.profiler.record_function(f"root_backward reduce:{self.hsdp_state.module_name}"):
183 logger.debug(
184 "hook=root_backward_hook action=final_reduce module=%s",
185 self.hsdp_state,
186 )
187 # Drain any pending async fused reduction from the last module's backward
188 comm_ctx = get_comm_ctx()
189 # Drain any pending pipelined HSDP reductions (comm_fusion=True)
190 if comm_ctx.all_reduce_param_group is not None:
191 logger.debug(
192 "hook=root_backward_hook wait=comm_fusion_all_reduce module=%s",
193 self.hsdp_state,
194 )
195 comm_ctx.all_reduce_param_group.wait_all_reduce_and_apply_grad()
196 comm_ctx.all_reduce_param_group = None
197 if comm_ctx.pre_param_group is not None:
198 logger.debug(
199 "hook=root_backward_hook apply=comm_fusion_reduce_scatter module=%s",
200 self.hsdp_state,
201 )
202 comm_ctx.pre_param_group.apply_fusion_reduced_grad()
203 comm_ctx.pre_param_group = None
205 # Process the last module's reduce_scatter and allreduce (comm_fusion=False)
206 if TorchHSDPStateV2.pre_all_reduce_groups:
207 for group in TorchHSDPStateV2.pre_all_reduce_groups:
208 logger.debug(
209 "hook=root_backward_hook wait=pre_reduce_scatter group_size=%s module=%s",
210 len(group.hsdp_params),
211 self.hsdp_state,
212 )
213 # Wait reduce_scatter
214 for hsdp_param in group.hsdp_params:
215 hsdp_param.reduce_scatter_output()
216 hsdp_param.clear_reduce_scatter_output()
217 # Accumulate existing gradients (from previous mini steps) to fused_buffer
218 # This is for gradient accumulation scenario
219 # where previous mini steps used pre_reduce_scatter_params.
220 # The gradients in sharded_param.grad are reduce_scatter results (not allreduced)
221 group.accumulate_existing_grads_to_buffer()
222 # Issue allreduce
223 logger.debug(
224 "hook=root_backward_hook launch=fused_all_reduce group_size=%s module=%s",
225 len(group.hsdp_params),
226 self.hsdp_state,
227 )
228 group.issue_async_allreduce()
229 TorchHSDPStateV2.pending_all_reduce_groups.append(group)
230 TorchHSDPStateV2.pre_all_reduce_groups.clear()
232 # Apply gradients for params without all_reduce needs
233 self.hsdp_state.reduce_scattered_params()
234 # Finally, wait all allreduce and apply gradients
235 TorchHSDPStateV2.delay_apply_reduce_grads(self.hsdp_state.device)
237 # Handle user config replicated_param
238 self.hsdp_state.reduce_params()
241 def _backward_hook(self):
242 """Execute backward hook."""
243 if self.scheduler_state == FSDPSchedulerState.BACKWARD:
244 return
245 self._hsdp_backward_hook(self.cell, None, None)
247 # pylint: disable=W0613
248 def _grouped_forward_pre_hook_skip(self, cell, args, kwargs) -> None:
249 """Override base ``(args, kwargs)`` return; ``nn.Module`` pre-hook uses ``None`` for no-op."""
250 return None
252 def _grouped_forward_post_hook_skip(self, outputs) -> None:
253 """Override base output pass-through; forward hook uses ``None`` for no-op."""
254 return None
256 def _register_forward_module_hook(self, mod, hook) -> None:
257 """Register forward hook; use ``always_call=True`` when supported (matches PyTorch FSDP)."""
258 sig = inspect.signature(mod.register_forward_hook)
259 if "always_call" in sig.parameters:
260 mod.register_forward_hook(hook, prepend=False, always_call=True)
261 else:
262 mod.register_forward_hook(hook, prepend=False)
264 def _register_forward_backward_hooks(self):
265 """Register module forward and backward hook on all managed modules."""
266 if self._fsdp_group_post_pending is None:
267 for mod in self.modules:
268 mod.register_forward_pre_hook(self._forward_pre_hook, with_kwargs=True)
269 mod.register_forward_hook(self._forward_hook)
270 return
271 for mod in self.modules:
272 mod.register_forward_pre_hook(self._grouped_forward_pre_hook, with_kwargs=True)
273 self._register_forward_module_hook(mod, self._make_grouped_forward_post_hook(mod))