Repository navigation
Conversation
…vention; raise for gated queries
…hook PreTrainedModelPrimeRL.muon_head_partitions(heads_per_block) raises by default; each model returns the blocks of all its attention head projections or raises. FlashAttention-based models (llama, qwen3, qwen3_moe, glm4_moe, minimax_m2, nemotron_h, laguna without output gate) and gpt-oss share qkv_head_partitions (packed qkv_proj or separate). Qwen3.5 and Qwen3.8 split q_proj per head as [query; gate]; AFMoE also splits its per-head gate_proj. DeepSeek V4, GLM-5 (glm_moe_dsa), gated Laguna and the Qwen3.5 VLM raise.
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.
What
New option
optim.heads_per_blockfor Muon (MuonConfig, shared by SFT, RL and pretraining). DefaultNone= today's behavior.heads_per_block = 1: each attention head's query, key and value rows are orthogonalized on their own (per-head Muon).2uses blocks of 2 heads, and so on.o_projand every other matrix stay as they are.qkvruntime fusion and with the unfused q/k/v projections. Packed weights keep the q/k/v boundaries.matrix_partitions, already used for fused weights). So FSDP/EP sharding works as before (the all-to-all rebuilds the whole matrix, then each block is orthogonalized), there is no extra communication, and therms_normLR adjustment uses each block's shape.heads_per_blockdoes not divide a projection's heads.Which models: each model decides through a hook,
PreTrainedModelPrimeRL.muon_head_partitions(heads_per_block). It returns the head blocks of all the model's attention projections, or raises. The default raises and names the model class, so a model is never covered only in part.qkv_head_partitionsinlayers/attn.py(packedqkv_projor separate q/k/v).q_proj([q_h; gate_h]), so a block is2 * head_dimrows per head. AFMoE: q/k/v plus its per-headgate_proj.glm_moe_dsa), Laguna with its output gate (g_proj), the Qwen3.5 VLM (vision encoder). Linear-attention layers (GatedDeltaNet, Mamba) are not softmax attention and are left as they are.glm_moe_dsa's implementation of the hook.Code:
muon_matrix_partitions()intrainer/optim/__init__.pybuilds the partitions (fused weights +model.muon_head_partitions(...)whenheads_per_blockis set). With the default it returns exactly the fused-weight partitions computed before.Why
Kimi K3 (https://arxiv.org/abs/2607.24653, section 2.5, "Per-Head Muon") orthogonalizes Q/K/V per head. They report more balanced heads and better stability at scale. GLM-5's "Muon Split" (https://arxiv.org/abs/2602.15763) does the same for MLA up-projections. Group Muon (https://arxiv.org/abs/2605.08933) finds head-wise faster early but overtaken late on GPT-2 small, with groups of heads best, hence
heads_per_block > 1.Evidence (pretraining, MoE d384 / L6 / 6 heads, 2 KV, E64 K6, Dolma 3, 537M tokens)
Pretrain study: https://github.com/snimu/pretrain-experiments/blob/main/experiments/init/2026-10-15-muon-per-head/report.md (private).
Default stays off.
Verification
muon_matrix_partitions()returns exactly the fused-weight partitions computed before, so Muon receives the same inputs.e4a6e0935), CPU check on meta-device models: for every covered model, withheads_per_block1 and 2 and with theqkvfusion where supported, the hook returns every q/k/v (and gate) weight of every attention layer, the blocks sum to each weight's rows, and each block hasheads_per_blockheads. The models listed under "Raise" raise, and a non-dividingheads_per_blockraises.0956a8858vse4a6e0935. With the default every logged metric is bitwise identical to main at all 5 steps (loss 8.464153, 14.507845, 6.840884, 12.007687, 7.399467).heads_per_block = 1/2reproduce the losses of the previous implementation (c48d13dd1): step 1 8.464153, step 5 7.101643 / 7.144592.0956a8858vs this branch atc48d13dd1(Muon, lr 2e-3, 1 H200, job 4394). With the default, every logged metric is bitwise identical to main at all 5 steps (loss 8.464153, 14.507845, 6.840884, 12.007687, 7.399467). Withheads_per_block = 1/2, step 1 is identical (8.464153, no update yet) and later steps differ (step 5 loss 7.1016 / 7.1446 vs 7.3995).