Follow-up to #8367 / #8384, opened at @delock's suggestion so the layout question has its own place to be discussed.
What is not covered today
#8384 tags a projection for per-head Newton-Schulz by reading the head count from AutoTPMeta and the per-head width from the config. That covers standard attention and MLA. Two head-blocked matrices in the very models #8384 was tested against are left on the full-matrix path, because their head geometry is not in the fields the tagger reads.
Kimi-K3's linear-attention layers. inference-optimization/Kimi-K3-0.40B is a hybrid: linear_attn_config gives full_attn_layers: [4, 8] and kda_layers: [1, 2, 3, 5, 6, 7], so 2 of its 8 language layers are MLA and 6 are linear attention with convolution. Instantiated, 318 Muon parameters, 4 tagged — both MLA layers, correctly:
| layer kind |
parameter |
shape |
today |
| MLA (layers 4, 8) |
q_b_proj |
(768, 256) |
per-head, 8 × 96 |
| MLA (layers 4, 8) |
kv_b_proj |
(1024, 128) |
per-head, 8 × 128 |
| KDA (6 layers) |
q_proj / k_proj / v_proj |
(256, 1024) |
full-matrix |
The KDA projections are genuinely head-blocked. From the model's own code:
# modeling_kimi_k3_linear.py
self.head_dim = config.linear_attn_config["head_dim"] # 32
self.num_heads = config.linear_attn_config["num_heads"] # 8
projection_k_size = self.head_k_dim * self.num_k_heads
self.q_proj = nn.Linear(self.hidden_size, projection_k_size, bias=False)
8 × 32 = 256, which is the shape. They are rejected only because num_attention_heads (8) and head_dim (74) give 8 × 74 = 592, so the shape check declines rather than the tagger recognising them. The head count happens to be right and the width happens to be wrong.
GLM-5.2's sparse-attention indexer. inference-optimization/GLM-5.2-0.8B-A0.8B has indexer.wq_b at (512, 512), blocked on dim 0 by index_n_heads 8 × index_head_dim 64:
# modeling_glm_moe_dsa.py
self.wq_b = nn.Linear(self.q_lora_rank, self.n_heads * self.head_dim, bias=False)
Its count is in index_n_heads, which AutoTPMeta does not read. The indexer's other two matrices are not candidates: wk is (64, 2048), a single shared K with no head axis, and weights_proj is (8, 2048), one row per head.
The questions this needs answered first
- Does Muon Split apply to linear-attention heads in K3, or is the split specific to full attention?
- Does it apply to the DSA indexer in GLM-5, or only to the attention proper?
Both are model questions rather than implementation ones, which is why #8384 declines rather than guessing. If either answer is yes, the code change is small; if the answer is no, the current behaviour is already correct and this issue closes.
If the answer is yes
The general fix is probably to read the head count off the owning module rather than adding config keys one architecture at a time. Every case above carries it as an attribute:
| module |
attributes |
KimiLinearAttention |
num_heads, head_dim (from linear_attn_config) |
GlmMoeDsaIndexer |
n_heads, head_dim (from index_*) |
| MLA / standard attention |
num_heads, and a width derivable per kind |
Walking named_modules() to map a parameter to its owner and asking the module is architecture-agnostic, and the shape confirmation in #8384 stays as the guard. It is a larger change than #8384 and would want its own review, which is why it is not folded in there.
Not in scope
Whether per-head helps on these layers is a separate question from whether the split is well-defined on them. The measurements in #8384 show the mechanism only has something to act on once heads have differentiated: on trained checkpoints the gradient carries a 6–8x median head-norm imbalance that the whole-matrix path reduces to ~3x and per-head takes to ~1.1x, while both mini checkpoints ship untrained weights whose heads are interchangeable.
I am happy to take this if it is wanted.
Follow-up to #8367 / #8384, opened at @delock's suggestion so the layout question has its own place to be discussed.
What is not covered today
#8384 tags a projection for per-head Newton-Schulz by reading the head count from
AutoTPMetaand the per-head width from the config. That covers standard attention and MLA. Two head-blocked matrices in the very models #8384 was tested against are left on the full-matrix path, because their head geometry is not in the fields the tagger reads.Kimi-K3's linear-attention layers.
inference-optimization/Kimi-K3-0.40Bis a hybrid:linear_attn_configgivesfull_attn_layers: [4, 8]andkda_layers: [1, 2, 3, 5, 6, 7], so 2 of its 8 language layers are MLA and 6 are linear attention with convolution. Instantiated, 318 Muon parameters, 4 tagged — both MLA layers, correctly:q_b_projkv_b_projq_proj/k_proj/v_projThe KDA projections are genuinely head-blocked. From the model's own code:
8 × 32 = 256, which is the shape. They are rejected only because
num_attention_heads(8) andhead_dim(74) give 8 × 74 = 592, so the shape check declines rather than the tagger recognising them. The head count happens to be right and the width happens to be wrong.GLM-5.2's sparse-attention indexer.
inference-optimization/GLM-5.2-0.8B-A0.8Bhasindexer.wq_bat (512, 512), blocked on dim 0 byindex_n_heads8 ×index_head_dim64:Its count is in
index_n_heads, whichAutoTPMetadoes not read. The indexer's other two matrices are not candidates:wkis (64, 2048), a single shared K with no head axis, andweights_projis (8, 2048), one row per head.The questions this needs answered first
Both are model questions rather than implementation ones, which is why #8384 declines rather than guessing. If either answer is yes, the code change is small; if the answer is no, the current behaviour is already correct and this issue closes.
If the answer is yes
The general fix is probably to read the head count off the owning module rather than adding config keys one architecture at a time. Every case above carries it as an attribute:
KimiLinearAttentionnum_heads,head_dim(fromlinear_attn_config)GlmMoeDsaIndexern_heads,head_dim(fromindex_*)num_heads, and a width derivable per kindWalking
named_modules()to map a parameter to its owner and asking the module is architecture-agnostic, and the shape confirmation in #8384 stays as the guard. It is a larger change than #8384 and would want its own review, which is why it is not folded in there.Not in scope
Whether per-head helps on these layers is a separate question from whether the split is well-defined on them. The measurements in #8384 show the mechanism only has something to act on once heads have differentiated: on trained checkpoints the gradient carries a 6–8x median head-norm imbalance that the whole-matrix path reduces to ~3x and per-head takes to ~1.1x, while both mini checkpoints ship untrained weights whose heads are interchangeable.
I am happy to take this if it is wanted.