A bicycle wheel is "true" when every spoke pulls the rim into a perfect circle. Muon's orthogonalization step does the same thing to a weight update — and you can watch it happen below, with the real coefficients, on a matrix you control.
Same goal — a well-scaled update — from two different units of analysis.
| Adam | Muon | |
|---|---|---|
| Unit of analysis | each scalar weight, independently | the whole 2D weight matrix, as one geometric object |
| What "well-scaled" means | each entry's step size matches its own gradient history | every direction the matrix can stretch gets an equal-size step — no direction dominates |
| Optimizer state / param | 2 buffers (m, v) | 1 buffer (momentum only) — ≈45% less optimizer memory for hidden weights |
| Where it's used | embeddings, output head, norms, biases — everything, in a hybrid setup | only 2D hidden weight matrices (attention/MLP projections); everything else still uses AdamW |
| Learning rate means | a per-entry step size | "spectral norm per update" — how far the matrix moves as a linear operator |
Reported results: 1.35× faster NanoGPT speedrun wallclock, a 1.5B-parameter model hitting its target loss in 10h vs 13.3h for AdamW, at 0.7% computational overhead for the orthogonalization step itself.
A matrix maps the unit circle to an ellipse — its two axis lengths are its singular values. "Orthogonalize" means: push both axis lengths to 1, turning the ellipse back into a circle. That's a wheel going "true."
Tip: "Already true" keeps both spokes exactly equal at every step — but even it doesn't land exactly on the dashed rim at step 5. Rescaling by the matrix's overall size is part of what the iteration has to fix, even for a perfectly round wheel.
g(σ)=3.4445σ−4.7750σ³+2.0315σ⁵ — every singular value bounces along this SAME fixed curve, independently, once per step. Dashed diagonal = "no change" (g(σ)=σ).Try it: drag the step slider to 12 on any preset. The coefficients (3.4445, -4.7750, 2.0315) were tuned to maximize how fast values approach 1 early on — not to converge exactly, ever. Keller Jordan's own code comment: this produces "something like US'V^T where S' is diagonal with S'ᵢᵢ ~ Uniform(0.5, 1.5)" — a deliberately loose approximation, traded for finishing in 5 cheap matmuls instead of running a slow, exact SVD.
What actually runs inside Muon.step(), in order — verbatim from muon.py.
Everything above the orthogonalization line is ordinary SGD-with-momentum. The only new ingredient is the line in the middle — and that line is exactly what the two panels above just showed you happening, step by step.
U, V) at all — only the axis lengths (Σ) change, step by step, via the same fixed polynomial curve.