@@ -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