Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / platform / mindspore / fully_shard / scheduler.py: 94%
131 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 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 HSDP scheduler"""
16from typing import List
17import mindspore as ms
18from mindspore._c_expression import _DisableMsDispatchMode
19from mindspore.common.api import _pynative_executor
20from mindspore.utils._pytree import tree_flatten, tree_unflatten
21from hyper_parallel.tools.logging import get_logger
22from hyper_parallel.core.fully_shard.hsdp_scheduler import HSDPSchedulerV2, FSDPSchedulerState
23from hyper_parallel.core.fully_shard.hsdp_utils import get_dtensor_managed_mesh
24from hyper_parallel.platform.mindspore.fully_shard.hook_function import PostBackwardFunction
25from hyper_parallel.platform.mindspore.fully_shard.param_group import get_comm_ctx
26from hyper_parallel.platform.mindspore.fully_shard.state import MindSporeHSDPStateV2
27from hyper_parallel.core.fully_shard.utils import FSDPMeshInfo, HSDPMeshInfo, DDPMeshInfo
28from hyper_parallel.platform import get_platform
30logger = get_logger("FSDP")
33class MindSporeHSDPSchedulerV2(HSDPSchedulerV2):
34 """MindSpore HSDP scheduler.
36 List-unit grouped forward hooks use :class:`HSDPSchedulerV2` defaults for
37 ``_grouped_forward_pre_hook_skip`` / ``_grouped_forward_post_hook_skip`` (no overrides here).
38 """
39 def zero_grad(self) -> None:
40 """Zero grad."""
41 self.hsdp_state.zero_grad()
43 def _register_hooks(self):
44 """Register hooks."""
45 self._register_forward_backward_hooks()
47 def _init_platform(self):
48 """Initialize the platform."""
49 from hyper_parallel.platform.mindspore.platform import MindSporePlatform
50 self.platform = get_platform()
51 if not isinstance(self.platform, MindSporePlatform):
52 raise ValueError(f"MindSporeHSDPSchedulerV2 expect MindSporePlatform, but got type: {type(self.platform)}")
54 def _new_cell_state(self):
55 """Create a new cell state for mindspore."""
56 params = self._get_managed_params()
57 if self.mesh is None:
58 compat_meshes = [get_dtensor_managed_mesh(param) for param in params]
59 compat_meshes = [mesh for mesh in compat_meshes if mesh is not None]
60 compat_mesh = compat_meshes[0] if compat_meshes else None
61 if compat_mesh is None:
62 raise ValueError(
63 "Cannot build fully_shard compatibility mesh_info "
64 "without a DTensor parameter mesh."
65 )
66 compat_mesh_hash = compat_mesh.to_hash()
67 for param_mesh in compat_meshes[1:]:
68 if param_mesh.to_hash() != compat_mesh_hash:
69 raise ValueError(
70 "fully_shard compatibility mode requires all DTensor parameters to share the same mesh."
71 )
72 self.mesh_info = DDPMeshInfo(mesh=compat_mesh, replicate_mesh_dim=0)
73 elif self.mesh.ndim == 1:
74 self.mesh_info = FSDPMeshInfo(mesh=self.mesh, shard_mesh_dim=0)
75 elif self.mesh.ndim == 2:
76 self.mesh_info = HSDPMeshInfo(mesh=self.mesh, shard_mesh_dim=1, replicate_mesh_dim=0)
77 else:
78 raise ValueError(
79 "fully_shard only supports explicit 1D DP/FSDP meshes or 2D HSDP meshes. "
80 f"Got mesh.ndim={self.mesh.ndim}."
81 )
82 self.hsdp_state = MindSporeHSDPStateV2(
83 self.modules, self.mesh_info, self.config, self.platform, self.device
84 )
86 def _register_post_backward_hook(self, args, kwargs):
87 """Wrap forward args/kwargs through PostBackwardFunction to register backward hook."""
88 if not _pynative_executor.enable_grad():
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[ms.Tensor] = []
95 for i, obj in enumerate(args_kwargs_list):
96 if isinstance(obj, ms.Tensor) 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_spec, args_list)
107 kwargs = tree_unflatten(kwargs_spec, kwargs_list)
108 return args, kwargs
110 def _forward_pre_hook(self, cell, args, kwargs):
111 """Execute forward pre hook and set up backward hook."""
112 args, kwargs = self._hsdp_forward_pre_hook(cell, args, kwargs)
113 return self._register_post_backward_hook(args, kwargs)
115 def _register_backward_pre_hook(self, outputs):
116 """Register output hook to trigger backward pre hook."""
117 flat_outputs, _ = tree_flatten(outputs)
118 for output in flat_outputs:
119 if isinstance(output, ms.Tensor) and output._requires_grad:
120 output.register_hook(self._backward_pre_hook)
121 return outputs
123 def _forward_hook(self, cell, inputs, outputs):
124 """Execute forward hook."""
125 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD:
126 return
127 self._register_backward_pre_hook(outputs)
128 if HSDPSchedulerV2.root_bp_state:
129 self._restore_forward_prefetch_after_recompute()
130 return
131 return self._hsdp_forward_hook(cell, inputs, outputs)
133 # pylint: disable=W0212
134 def _backward_pre_hook(self, grad):
135 """Execute backward pre hook."""
136 _pynative_executor.queue_backward_final_callback(self._root_backward_hook)
137 if self.scheduler_state == FSDPSchedulerState.PRE_BACKWARD:
138 return grad
139 HSDPSchedulerV2.root_bp_state = True
140 self._hsdp_backward_pre_hook(self.cell, None)
141 return grad
143 # pylint: disable=W0613
144 def _root_backward_hook(self, force_reduce=False):
145 """Finalize the outermost backward: drain pending reductions and apply grads.
147 The drain is unconditional. Every step below is self-limiting -- the fused
148 groups are ``None``-guarded and ``reduce_params`` is an empty-queue no-op
149 when there is no pending work -- so running them on every invocation never
150 double-applies and preserves the invariant that a parameter's ``.grad`` is
151 either ``None`` or a fully reduced value. This mirrors torch FSDP2, whose
152 wait in ``_root_post_backward_final_callback`` is likewise not gated on the
153 per-group post-backward training state.
155 Gating the drain on ``scheduler_state != BACKWARD`` was unsafe for any unit that
156 acts as a root while being fed a differentiable activation: its input's
157 ``PostBackwardFunction`` drives ``scheduler_state == BACKWARD``, so the gate would
158 skip the drain and leak the last module's reduce-scatter into the next optimizer
159 step. This happens when ``fully_shard`` wraps only inner layers and not the root
160 module (each layer becomes its own root yet is fed a grad-requiring activation).
161 PP hit the same boundary and worked around it with ``force_reduce=True`` from
162 ``PipelineStage.execute_reduce_grad``; that call site keeps working -- the drain is
163 simply always performed now. PP per-micro-batch accumulation is unaffected because
164 each chunk's backward sets ``requires_gradient_sync=False``, leaving the reduce
165 queue empty here so this drain is a no-op until the explicit reduce step.
167 ``root_bp_state`` (top-level root backward in flight; gates forward prefetch during
168 activation recompute) is independent of the drain and is cleared only by the root
169 module's own hook, keyed on ``_is_root``.
170 """
171 logger.debug("hook=root_backward_hook enter module=%s", self.hsdp_state)
172 self._backward_hook()
173 if self._is_root:
174 HSDPSchedulerV2.root_bp_state = False
175 comm_ctx = get_comm_ctx()
176 if comm_ctx.all_reduce_param_group is not None:
177 logger.debug(
178 "hook=root_backward_hook wait=comm_fusion_all_reduce module=%s",
179 self.hsdp_state,
180 )
181 comm_ctx.all_reduce_param_group.wait_all_reduce_and_apply_grad()
182 comm_ctx.all_reduce_param_group = None
183 if comm_ctx.pre_param_group is not None:
184 logger.debug(
185 "hook=root_backward_hook apply=comm_fusion_reduce_scatter module=%s",
186 self.hsdp_state,
187 )
188 comm_ctx.pre_param_group.apply_fusion_reduced_grad()
189 comm_ctx.pre_param_group = None
190 # Step 1: Wait for previous reduce-scatter groups and get them for all-reduce
191 prev_groups = self.hsdp_state._wait_prev_reduce_scatter()
192 # Step 2: Accumulate and issue async all-reduce for previous groups
193 for group in prev_groups:
194 group.accumulate_existing_grads_to_buffer()
195 group.issue_async_allreduce()
196 MindSporeHSDPStateV2.pending_all_reduce_groups.append(group)
197 # Step 3: Wait/apply any remaining reduce-scatter for pure FSDP params
198 self.hsdp_state.reduce_scattered_params()
199 # Step 4: Wait for pending all-reduce groups and apply grads
200 MindSporeHSDPStateV2.delay_apply_reduce_grads()
201 # Step 5: Process any remaining all-reduce params (without fusion)
202 logger.debug(
203 "hook=root_backward_hook action=reduce_params module=%s",
204 self.hsdp_state,
205 )
206 self.hsdp_state.reduce_params()
208 def _backward_hook(self):
209 """Execute backward hook."""
210 if self.scheduler_state == FSDPSchedulerState.BACKWARD:
211 return
212 self._hsdp_backward_hook(self.cell, None, None)
214 @staticmethod
215 def _without_ms_dispatch_mode(hook):
216 """Run HSDP hook internals outside any outer MsDispatchMode."""
217 def wrapped_hook(*args, **kwargs):
218 with _DisableMsDispatchMode():
219 return hook(*args, **kwargs)
220 return wrapped_hook
222 def _register_forward_backward_hooks(self):
223 """Register module forward and backward hook on all managed modules."""
224 if self._fsdp_group_post_pending is None:
225 for mod in self.modules:
226 mod.register_forward_pre_hook(
227 self._without_ms_dispatch_mode(self._forward_pre_hook),
228 with_kwargs=True,
229 )
230 mod.register_forward_hook(self._without_ms_dispatch_mode(self._forward_hook))
231 return
232 for mod in self.modules:
233 mod.register_forward_pre_hook(
234 self._without_ms_dispatch_mode(self._grouped_forward_pre_hook),
235 with_kwargs=True,
236 )
237 mod.register_forward_hook(
238 self._without_ms_dispatch_mode(self._make_grouped_forward_post_hook(mod))
239 )