Repository navigation
feat(optim): Muon symmetric-GEMM Newton-Schulz + orthogonalize expert stacks in place - #3873
Closed
faresobeid wants to merge 1 commit into
Closed
faresobeid wants to merge 1 commit into
faresobeid wants to merge 1 commit into
Conversation
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.
Summary
Two Muon optimizer-step speedups in
trainer/optim. They help SFT, RL, and the planned pretraining data mode. There are no config changes and no loss changes, and the param groups and checkpoint layout are unchanged.newton_schulz_symmetric). Dion's dense NS is kept, except thatX·Xᵀandb·A + c·A·Arun on QuACK'sgemm_symmetric. That kernel computes one triangle and mirrors it. Thea·X + B·Xstep runs onaddmm/baddbmmwith the scalar folded in. QuACK's symmetric GEMM has existed sincequack-kernels0.4.1, which is the current lower bound, so the pin is unchanged. The fork pinS1ro1/quack@f62009fis 0.6.5. It supports SM80/90/100/110/120. The code falls back to Dion's densezeropower_via_newtonschulz5in these cases: CPU, no QuACK, pre-SM80, or a matrix dimension not divisible by 8 (QuACK needs 16-byte aligned rows).Muon(dion.Muon)subclass sends a parameter to a local update (muon_update_batch_asyncwithworld_size=1) when it is a 3D+ DTensor whose shard placements all split dim 0. In that case each rank already holds whole[fan_out, fan_in]matrices, and NS treats the leading axis as a batch. Everything else still uses Dion's all-to-all path.(_StridedShard(0) on dp_shard_mod_ep=1, Shard(0) on ep=8). Dion's padding then zero-pads each rank's 16-expert shard up to all 128 experts and runs NS on that. Every rank does 8× the needed expert NS work. Dion's logged NS input is(128, 1536, 2048); the local path's is(16, 1536, 2048).Results (Qwen3-30B-A3B, random init, 1×8 H200, FSDP +
epas listed)Optimizer-step wall time is measured with the real
setup_model+setup_optimizer, random grads, andtorch.cuda.synchronize. Each number is the max over ranks, averaged over steady steps 4–8.NS microbenchmark on one H200 (ms per call; relative Frobenius error vs an fp32 NS with the same coefficients):
QuACK is closer to the fp32 reference than the dense bf16 path, because the alpha/beta epilogue runs in fp32. QuACK and the dense path differ from each other by about 1e-2 relative (max abs ≤ 3e-3). That is the size of bf16 NS noise.
First-step update equivalence vs
main, as||Δupdate|| / ||update||, max over ranks, layers 1 and 40, for ep ∈ {1, 2, 8}:Tests
tests/unit/train/test_muon.py(gpu): symmetric NS is at least as accurate as Dion's dense NS against an fp32 reference, on 2D (wide and tall) and 3D shapes. Passes on H200.pytest -m "not gpu" tests/unit/orchestrator tests/unit/test_configs.py tests/unit/train/test_muon.py: 291 passed, 3 deselected.ruff check/ruff format --checkare clean.Notes
use_triton=True, which is also symmetric) is within a few percent of QuACK at most shapes. QuACK is faster on large or batched matrices. I used QuACK because it is already a dependency and it is more accurate.optim/muon.pywith aMuon(DionMuon)subclass that overridesstep. The two combine directly: its streamed path calls_create_muon_tasks, which this PR overrides. Whichever lands second rebases trivially. refactor(trainer): share RL/SFT loop setup, resume step, trace and step logging #3851 touchessetup_optimizer's signature but not the lines changed here. There are no conflicts with refactor(configs): one trainer-common config base for TrainerConfig and SFTConfig #3849 / Chore/remove transformers modeling #3833 / refactor(lora): single-adapter LoRA layers, drop modules_to_save #3855 / refactor(trainer): add iter_moe_blocks for the MoE layer lookup #3843.