Follow the optimizer's own state names in universal checkpoints - #8609
Open
alanhuangyoo wants to merge 2 commits into
Open
alanhuangyoo wants to merge 2 commits into
alanhuangyoo wants to merge 2 commits into
Conversation
Conversion read Adam's `exp_avg` and `exp_avg_sq` by name, so a checkpoint written by any other optimizer could not be converted: Muon keeps `momentum_buffer` and nothing else, and `ds_to_universal` raised `KeyError: 'exp_avg'` before writing anything. Extraction now takes the names from the param group's own state, the merge takes them from the fragment files, and the ZeRO-3 loader takes them per sub-group - per group rather than per checkpoint, because DeepSpeed splits a Muon run into two param groups and one checkpoint therefore holds `momentum_buffer` for the matrices and Adam's pair for the rest. The ZeRO-3 loader also takes the step from whichever group saved one, since the Muon half has none. A state that is not shaped like the ZeRO partition now stops the conversion with a message naming it. ZeRO-1/2 give Muon a momentum buffer the size of the whole group, replicated on every rank, because Newton-Schulz needs the whole matrix; slicing that by partition offsets would write fragments that are the right size and hold the wrong rows. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
alanhuangyoo
requested review from
loadams,
tjruwase and
tohtana
as code owners
September 20, 2026 14:12
Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
alanhuangyoo
force-pushed
the
fix/ucp-optimizer-state-keys
branch
from
September 21, 2026 17:22
75d9b71 to
cfd14b5
Compare
This branch has not been deployed
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.
Fixes #8608.
The problem
Universal checkpoint conversion read Adam's state by name, so a checkpoint written by any other optimizer could not be converted. Muon keeps
momentum_bufferfor the matrices it orthogonalizes, sods_to_universalraisedKeyError: 'exp_avg'before writing anything — ZeRO-1/2 atextract_zero_shards, ZeRO-3 atextract_zero_shards_stage3.The same three names are hard-coded in two more places: the merge loops in
merge_tp_slices/merge_zero3_slices, and the ZeRO-3 loader, which also reads astepthat the Muon half does not have. The ZeRO-1/2 loader was already generic — it takes the names from the files it finds — which is why it needed no change.The change
Per group, not per checkpoint: DeepSpeed splits a Muon run into two param groups, so one checkpoint holds
momentum_bufferfor the matrices andexp_avg/exp_avg_sq/stepfor everything else. Reading the names once for the whole checkpoint sends the ZeRO-3 loader looking for0.bias/momentum_buffer.pt._SCALE_POWERSgainsmomentum_buffer: -1. It is only consulted for a tensor-parallel piece, where a parameter scaled byshas a gradient scaled by1/s, and the momentum buffer is gradient-like, asexp_avgis. A state name that is in no table stops the conversion with a message rather than being merged at an assumed scale.What this does not cover
ZeRO-1/2 with Muon. There the momentum buffer is the size of the whole group and replicated on every rank, because Newton-Schulz needs the whole matrix (
_muon_momentum_buffer,stage_1_and_2.py). Every other state is shaped like the partition, and the universal format stores state in the partition's layout, so slicing the buffer by partition offsets would write fragments that are the right size and hold the wrong rows. The conversion now refuses, naming the state:A refusal is not the end state, but it is better than a resume that looks successful. I am happy to map the replicated buffer in a follow-up, or here if you would rather have it in one piece.
autoep_universal.pycarries the same three names in its own paths and is untouched.Testing
On 2×H20, from the repo root:
tests/unit/checkpoint/test_universal_optimizer_states.py. Neither test passes on master: the conversion raisesKeyError: 'exp_avg'on the converting rank, which is also why the round trip hangs there rather than failing - the other rank is at the barrier behind it.test_muon_state_survives_a_universal_round_trip— ZeRO-3, a model with a 2-D weight and a norm so both halves are present. After converting and resuming, every state is element-wise equal to the state before the save.test_a_state_that_is_not_partition_shaped_stops_the_conversion— ZeRO-1, the replicated momentum buffer, expects theValueError.I could not run
tests/unit/checkpoint/test_universal_checkpoint.pylocally — itsbaseline_ws2fixture fails to resolve in my environment, on master as well as here — so that suite is down to CI.