Muon silently discards the param groups it is given - #8440
Open
alanhuangyoo wants to merge 1 commit into
Open
Conversation
Which half of Muon a parameter belongs to is a property of the parameter, so _configure_basic_optimizer has to build its own groups. It did that by flattening model_parameters into one list and rebuilding two groups from the config, which discards whatever the incoming groups set. Every other optimizer here receives model_parameters unchanged, so for them a group's own lr and weight_decay reach the optimizer. The consequence is the no-weight-decay-on-biases-and-norms grouping that most training recipes use. Passing the usual two groups, wd 0.1 and 0.0: AdamW -> lr=1.0e-03 wd=0.1 lr=1.0e-04 wd=0.0 Muon -> lr=5.0e-04 wd=0.01 lr=5.0e-04 wd=0.01 Everything falls back to the config values and nothing is reported, so parameters the user excluded from weight decay are decayed anyway. Split each incoming group into its Muon and Adam halves and carry that group's settings onto both. Settings resolve most-specific-last: the config's shared value, then muon_lr / adam_lr, then the group's own. A plain parameter list is unchanged, names included. Also raise on a parameter with no use_muon attribute rather than logging an error and then failing on p.use_muon two lines later. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
alanhuangyoo
requested review from
loadams,
tjruwase and
tohtana
as code owners
September 6, 2026 13:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Muon's param groups are built by DeepSpeed rather than taken from the caller, because which half of Muon a parameter belongs to is a property of the parameter. Doing that flattened away whatever the incoming groups set.
What happens
_configure_basic_optimizercollapsedmodel_parametersinto one list and rebuilt two groups from the config:The comment says MoE/EP, and for those groups it is harmless — the MoE identity rides on
param.group_nameandsplit_params_into_different_moe_groups_for_optimizerrebuilds them afterwards. But the same line eats a caller's own groups, and every other optimizer here receivesmodel_parametersunchanged, so for them a group'slrandweight_decayreach the optimizer.The pattern this breaks is the one in most training recipes: no weight decay on biases and norms. Passing the usual two groups,
wd 0.1and0.0,lr 1e-3and1e-4, against a config oflr 5e-4, weight_decay 0.01:Everything falls back to the config values. The parameters the caller excluded from weight decay are decayed at 0.01 anyway, and nothing says so.
The change
Split each incoming group into its Muon and Adam halves and carry that group's settings onto both. Settings resolve most-specific-last: the config's shared value, then
muon_lr/adam_lr, then whatever the group itself sets.After:
Also raise on a parameter with no
use_muonattribute. Today that logs an error and then dies two lines later onp.use_muonwith anAttributeError, so the helpful message is followed by an unhelpful traceback.Backward compatibility
A plain parameter list — the common case, and every existing test — is unchanged, group names included. Verified byte-for-byte against master:
Names only gain a prefix when there is more than one incoming group, which is required anyway: MoE regrouping keys its buckets by
name, so two groups must not collide on one. Nothing in the repo reads'muon-params'/'adam-params'.muon_lr/adam_lrstill override the sharedlr, and now lose to a group that sets its ownlr, which is the same precedence every other optimizer has.Tests
tests/unit/runtime/zero/test_muon_param_groups.py, 11 cases. Ten call the grouping directly (it is a staticmethod, so they need no engine and no GPU): the historical names and values for a plain list,muon_lr/adam_lroverriding, a group keeping its own weight decay and its own lr, a group without an lr still takingmuon_lr, a mixed group splitting in two with both halves keeping its settings, distinct names across groups, each half getting only the keys its optimizer accepts, frozen parameters left out, and the untagged-parameter error.The eleventh is the regression test: a real
deepspeed.initializewith the two-group no-decay pattern. On master it fails withand passes here with
[0.0, 0.1]. The other ten exercise a helper that does not exist on master, so they fail there for the trivial reason; the end-to-end one is the one that fails for the right reason.yapf and flake8 clean.
Related: #7657 asked for different learning rates for the Muon and Adam halves, which
muon_lr/adam_lrcovers at the config level; this makes the general per-group form work too. #7713 is a different way the configured Muon lr fails to reach the optimizer (an LR scheduler broadcasting one scalar over both groups) and is not addressed here.