Skip to content

Commit 528339f

Browse files
committed
refactor(mfsdp): clarify Muon update phases and types
Signed-off-by: Jingyue Wu <jingyuew@nvidia.com>
1 parent 38b4c4c commit 528339f

1 file changed

Lines changed: 74 additions & 39 deletions

File tree

  • megatron/core/distributed/fsdp/src/megatron_fsdp/experimental

‎megatron/core/distributed/fsdp/src/megatron_fsdp/experimental/muon.py‎

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class FsdpMuon(Muon):
5252
including in optimizer parameter-group dictionaries.
5353
"""
5454

55+
dp_mesh: DeviceMesh
5556
_fsdp_groups: list[FsdpParameterGroup]
5657
_owner_maps: list[dict[int, int]]
5758

@@ -65,12 +66,12 @@ def __init__(self, params: ParamsT, dp_mesh: DeviceMesh, **kwargs: Any) -> None:
6566

6667
# Preserve discovery order: communication must be posted identically on every rank.
6768
self._fsdp_groups = []
68-
seen = set()
69+
seen: set[FsdpParameterGroup] = set()
6970
for group in self.param_groups:
7071
for param in group["params"]:
7172
if param.ndim != 2:
7273
continue
73-
fsdp_group = get_containing_parameter_group(param)
74+
fsdp_group: FsdpParameterGroup | None = get_containing_parameter_group(param)
7475
assert fsdp_group is not None
7576
if fsdp_group not in seen:
7677
seen.add(fsdp_group)
@@ -79,14 +80,14 @@ def __init__(self, params: ParamsT, dp_mesh: DeviceMesh, **kwargs: Any) -> None:
7980

8081
def _validate_param_groups(self) -> None:
8182
"""Validate optimizer parameters and the FSDP buffers that contain them."""
82-
seen = set()
83+
seen: set[FsdpParameterGroup] = set()
8384
for group in self.param_groups:
8485
if group["weight_decay"] != 0:
8586
raise NotImplementedError("FsdpMuon does not support nonzero weight decay yet.")
8687
for param in group["params"]:
8788
if param.ndim != 2:
8889
continue
89-
fsdp_group = get_containing_parameter_group(param)
90+
fsdp_group: FsdpParameterGroup | None = get_containing_parameter_group(param)
9091
if fsdp_group is None:
9192
raise ValueError("FsdpMuon requires parameters sharded by MFSDP v2.")
9293
if fsdp_group in seen:
@@ -117,13 +118,13 @@ def step(self, closure: Callable[[], float] | None = None) -> None:
117118
for param in group["params"]:
118119
if param.ndim != 2 or param.grad is None:
119120
continue
120-
state = self.state[param]
121+
state: dict[str, Any] = self.state[param]
121122
if not state:
122123
state["momentum_buffer"] = torch.zeros_like(param)
123124
param_to_group_index[param] = group_index
124125

125126
for fsdp_group, owners in zip(self._fsdp_groups, self._owner_maps, strict=True):
126-
group_indices = {
127+
group_indices: dict[int, int] = {
127128
i: param_to_group_index[fp.sharded]
128129
for i, fp in enumerate(fsdp_group.fsdp_parameters)
129130
if fp.sharded in param_to_group_index
@@ -139,56 +140,90 @@ def _step_group(
139140
group_indices: dict[int, int],
140141
tensor_to_owner: dict[int, int],
141142
) -> None:
142-
"""Update a buffer using a tensor-index-to-optimizer-group-index mapping."""
143-
buffer = fsdp_group.main_weight
143+
"""Gather momentum, orthogonalize on owners, and scatter updates.
144+
145+
Args:
146+
fsdp_group: FSDP buffers and parameters to update.
147+
group_indices: Active indices into ``fsdp_group.fsdp_parameters`` mapped
148+
to indices into ``self.param_groups`` for their optimizer settings.
149+
tensor_to_owner: The same active tensor indices mapped to global owner ranks.
150+
"""
151+
buffer: DBuffer = fsdp_group.main_weight
152+
momentum_inputs: DBuffer = self._compute_momentum_inputs(fsdp_group, group_indices)
153+
this_rank: int = self.dp_mesh.get_rank()
154+
full_updates: dict[int, torch.Tensor] = {
155+
i: torch.empty(buffer.layout.tensor_shapes[i], dtype=buffer.dtype, device=buffer.device)
156+
for i, owner in tensor_to_owner.items()
157+
if owner == this_rank
158+
}
159+
gather(momentum_inputs, full_updates, tensor_to_owner=tensor_to_owner)
160+
self._orthogonalize_updates(fsdp_group, group_indices, full_updates)
144161

145-
# Compute the local momentum inputs.
146-
inputs = DBuffer(buffer.mesh, buffer.placements, buffer.layout, buffer.dtype, buffer.device)
162+
local_updates: DBuffer = DBuffer(
163+
buffer.mesh, buffer.placements, buffer.layout, torch.float32, buffer.device
164+
)
165+
scatter(full_updates, local_updates, tensor_to_owner=tensor_to_owner)
166+
self._apply_local_updates(fsdp_group, group_indices, full_updates, local_updates)
167+
168+
def _compute_momentum_inputs(
169+
self, fsdp_group: FsdpParameterGroup, group_indices: dict[int, int]
170+
) -> DBuffer:
171+
"""Update sharded momentum and build the inputs for orthogonalization."""
172+
buffer: DBuffer = fsdp_group.main_weight
173+
momentum_inputs: DBuffer = DBuffer(
174+
buffer.mesh, buffer.placements, buffer.layout, buffer.dtype, buffer.device
175+
)
147176
for i, group_index in group_indices.items():
148-
group = self.param_groups[group_index]
149-
param = cast(DTensor, fsdp_group.fsdp_parameters[i].sharded)
177+
group: dict[str, Any] = self.param_groups[group_index]
178+
param: DTensor = cast(DTensor, fsdp_group.fsdp_parameters[i].sharded)
150179
assert isinstance(param.grad, DTensor)
151-
local = inputs.get_tensor_view(i)
152-
if local.numel() == 0:
153-
continue
154-
momentum = self.state[param]["momentum_buffer"].to_local()
155-
grad = param.grad.to_local().to(momentum.dtype).reshape(momentum.shape)
180+
local_input: torch.Tensor = momentum_inputs.get_tensor_view(i)
181+
momentum: torch.Tensor = self.state[param]["momentum_buffer"].to_local()
182+
grad: torch.Tensor = param.grad.to_local().to(momentum.dtype).reshape(momentum.shape)
183+
# EO's OrthogonalizedOptimizer.step implements momentum inline, without a helper.
156184
momentum.lerp_(grad, 1 - group["momentum"])
157-
update = grad.lerp(momentum, group["momentum"]) if self.nesterov else momentum
158-
local.copy_(update.reshape(local.shape))
185+
update: torch.Tensor = (
186+
grad.lerp(momentum, group["momentum"]) if self.nesterov else momentum
187+
)
188+
local_input.copy_(update.reshape(local_input.shape))
189+
return momentum_inputs
159190

160-
# Gather and orthogonalize full matrices on their owners.
161-
this_rank = self.dp_mesh.get_rank()
162-
full_updates = {
163-
i: torch.empty(buffer.layout.tensor_shapes[i], dtype=buffer.dtype, device=buffer.device)
164-
for i, owner in tensor_to_owner.items()
165-
if owner == this_rank
166-
}
167-
gather(inputs, full_updates, tensor_to_owner=tensor_to_owner)
191+
def _orthogonalize_updates(
192+
self,
193+
fsdp_group: FsdpParameterGroup,
194+
group_indices: dict[int, int],
195+
full_updates: dict[int, torch.Tensor],
196+
) -> None:
197+
"""Replace this rank's gathered inputs with full orthogonalized updates."""
168198
with eo_utils.fp32_matmul_precision(self.fp32_matmul_prec):
169199
for i, update in full_updates.items():
170-
param = fsdp_group.fsdp_parameters[i].sharded
171-
group = self.param_groups[group_indices[i]]
172-
kwargs = {k: v for k, v in group.items() if k != "params"}
200+
param: torch.nn.Parameter = fsdp_group.fsdp_parameters[i].sharded
201+
group: dict[str, Any] = self.param_groups[group_indices[i]]
202+
kwargs: dict[str, Any] = {k: v for k, v in group.items() if k != "params"}
173203
full_updates[i] = self.orthogonalize(param, update.float(), **kwargs).contiguous()
174204

175-
# Scatter updates and apply each local shard.
176-
local_updates = DBuffer(
177-
buffer.mesh, buffer.placements, buffer.layout, torch.float32, buffer.device
178-
)
179-
scatter(full_updates, local_updates, tensor_to_owner=tensor_to_owner)
205+
def _apply_local_updates(
206+
self,
207+
fsdp_group: FsdpParameterGroup,
208+
group_indices: dict[int, int],
209+
full_updates: dict[int, torch.Tensor],
210+
local_updates: DBuffer,
211+
) -> None:
212+
"""Apply owned result slices and received updates to local parameter shards."""
213+
buffer: DBuffer = fsdp_group.main_weight
180214
for i, group_index in group_indices.items():
181-
param = cast(DTensor, fsdp_group.fsdp_parameters[i].sharded)
182-
weight = param.to_local()
215+
param: DTensor = cast(DTensor, fsdp_group.fsdp_parameters[i].sharded)
216+
weight: torch.Tensor = param.to_local()
183217
if weight.numel() == 0:
184218
continue
185219
if i in full_updates:
186220
# Scatter writes only remote-owned shards; owners keep their full result.
187-
offset = max(buffer.offset - buffer.layout.get_tensor_range(i).start, 0)
188-
update = full_updates[i].view(-1).narrow(0, offset, weight.numel())
221+
offset: int = max(buffer.offset - buffer.layout.get_tensor_range(i).start, 0)
222+
update: torch.Tensor = full_updates[i].view(-1).narrow(0, offset, weight.numel())
189223
else:
190224
update = local_updates.get_tensor_view(i)
191225
update = update.reshape(weight.shape).to(weight.dtype)
226+
# These hooks are inherited from EO's OrthogonalizedOptimizer, via Muon.
192227
self.pre_weight_update_fn_inplace(weight, update)
193228
weight.add_(update, alpha=-self.param_groups[group_index]["lr"])
194229
self.post_weight_update_fn_inplace(weight)

0 commit comments

Comments
 (0)