Tononoff Journey

research · 13

how you could have invented muon from memory

The matrix trick hiding in plain sight that made AdamW look less inevitable.

AdamW is a comfortable default. Then someone remembers that a matrix is a transformation, and suddenly there is a suspiciously simple optimizer to explain.

Let's try to invent it. We have a model, some data, and enough calculus and linear algebra to be dangerous. How should we change the weights?

We'll start with the obvious answer, gradient descent. Then we'll notice that a matrix does more than store a list of numbers. Taking that seriously will lead us to Muon's update. The aim is to make each choice follow from the previous one, so the formula becomes something we can reconstruct.

The optional explanations are folded away. Open whichever bits you need; there is no entrance exam.

§A function, some data, and a loss

A model is a mathematical function fθ:X→Yf_\theta:\mathcal X\to\mathcal Y, with adjustable parameters θ\theta. We choose those parameters to approximate a relationship we do not know. In regression, call that relationship f∗f_*; after fitting, our estimate is f^=fθ^≈f∗\hat f=f_{\hat\theta}\approx f_*. The hat means "estimated from data".

This covers a line fθ(x)=ax+bf_\theta(x)=ax+b, a vision network, or an LLM. For an LLM, the output is a probability distribution over possible next tokens. We are estimating a conditional distribution, rather than assuming every input has one inevitable answer.

A loss turns "how well are we doing?" into a number to minimize. It doesn't have to be a distance in the formal mathematical sense. Here, it measures what we intuitively call distance or difference: how far the model's predictions are from what we want.

For regression, our training data consists of pairs (xi,yi)(x_i,y_i). The input is xix_i, the desired output is yiy_i, and the model predicts fθ(xi)f_\theta(x_i). For a language model, xtx_t is the observed token at position tt, and x<tx_{<t} is the preceding context. Two common losses are

Lreg(θ)=1N∑i=1N(fθ(xi)−yi)2,LLM(θ)=−1T∑t=1Tlog⁡pθ(xt∣x<t).\begin{aligned} L_{\mathrm{reg}}(\theta) &=\frac1N\sum_{i=1}^N\bigl(f_\theta(x_i)-y_i\bigr)^2,\\[10pt] L_{\mathrm{LM}}(\theta) &=-\frac1T\sum_{t=1}^T\log p_\theta(x_t\mid x_{<t}). \end{aligned}

The first is mean squared error over NN input/output pairs. The second is token cross-entropy over TT predicted tokens: it penalizes assigning low probability to the token that actually occurred. A lower loss means improvement on this objective; choosing a useful objective is our responsibility.

Why does low token probability give a large loss?

For a target distribution yy and predicted probabilities pp, cross-entropy is −∑jyjlog⁡pj-\sum_j y_j\log p_j. A one-hot target puts all its mass on the observed token, so only −log⁡p(observed token)-\log p(\text{observed token}) remains.

Giving the correct token probability 0.80.8 costs about 0.2230.223 nats. Giving it 0.20.2 costs about 1.6091.609. The negative logarithm makes confident mistakes expensive. Averaging this quantity over tokens gives the loss above.

Training moves θ\theta through a space with one dimension per parameter. We want a useful direction and a sensible distance. We get local information about the loss, not a map showing the best route to the bottom.

§First, take a step downhill

So far, θ\theta has meant all the model's adjustable parameters. To work out one update, focus on the weight matrix of a single layer, W∈Rm×nW\in\mathbb R^{m\times n}, and hold the other parameters fixed. We will write the loss as L(W)L(W) to show which parameters we are changing. It is the same training objective, viewed as a smaller optimization problem.

Once we have a rule for this matrix, we can apply it to other weight matrices too. Biases and other parameter shapes may use a different update rule. For now, assume L(W)L(W) is differentiable: small weight changes have a first-order effect that we can predict from the local slopes.

An optimizer adds a step DD, with the same shape as WW:

Wnew=W+D.W_{\mathrm{new}}=W+D.

Each DijD_{ij} says how much to change the corresponding weight WijW_{ij}. We keep uppercase WW and DD for the whole matrices throughout this derivation. In the small examples below, lowercase w1,w2w_1,w_2 denote individual weights.

The gradient matrix GG collects the partial derivatives, Gij=∂L/∂WijG_{ij}=\partial L/\partial W_{ij}. Each entry measures the local change in loss per tiny change in that weight, with the others held fixed. A positive slope says increasing the weight raises the loss locally; a negative slope says the opposite.

The gradient gives us slopes. To turn them into an actual step, choose a positive scale α\alpha, called the learning rate, and define

D=−αG.D=-\alpha G.

The minus sign reverses the slopes; α\alpha controls how far we move along them. This is gradient descent. Whether it lowers the actual loss still depends on how large a step we take.

Derivatives and partial derivatives, without the ceremony

For f(w)=w2f(w)=w^2, changing w=1w=1 by hh changes the output by 2h+h22h+h^2. Divide by hh to get the average slope 2+h2+h. As hh approaches zero, this approaches the derivative f′(1)=2f'(1)=2.

A shrinking secant interval reveals the derivativeSecant slope 2.020 for h=0.020 approaches the tangent slope 2.A derivative is a limiting slopeShrink the interval; keep the starting point fixed.11 + hw
f(1+h)−f(1)h=2+h\frac{f(1+h)-f(1)}h=2+h
h = 0.020secant slope = 2.020
f′(1)=lim⁡h→0(2+h)=2f'(1)=\lim_{h\to0}(2+h)=2
Blue: secant · dashed: tangent · curve: f(w) = w²
The moving point approaches w = 1. The blue secant measures the slope over a finite interval; the dashed tangent shows its limit. A partial derivative does the same experiment while holding the other weights fixed.
Shrink the interval and watch the slope settle.

A derivative measures slope. A second derivative measures how that slope changes, which is curvature. If every partial derivative is zero, we have a stationary point. It could be a minimum, a maximum, or a saddle. Zero does not come with a "best possible answer" certificate.

Work out a two-weight loss and its gradient

Let two predictions be y^1=w1\hat y_1=w_1 and y^2=2w2\hat y_2=2w_2, both with target zero. Half the sum of squared errors gives

L(w1,w2)=12[(y^1−0)2+(y^2−0)2]=12[w12+(2w2)2]=12(w12+4w22).\begin{aligned} L(w_1,w_2) &=\tfrac12\bigl[(\hat y_1-0)^2+(\hat y_2-0)^2\bigr]\\[10pt] &=\tfrac12\bigl[w_1^2+(2w_2)^2\bigr]\\[10pt] &=\tfrac12(w_1^2+4w_2^2). \end{aligned}

The 1/21/2 tidies the derivatives. The 44 comes from squaring the second prediction's factor of 22. In this two-weight example, collect the derivatives in a vector gg:

g=(∂L∂w1,∂L∂w2)=(12⋅2w1,12⋅8w2)=(w1,4w2).\begin{aligned} g&=\left(\frac{\partial L}{\partial w_1},\frac{\partial L}{\partial w_2}\right)\\[12pt] &=\left(\tfrac12\cdot2w_1,\tfrac12\cdot8w_2\right)\\[10pt] &=(w_1,4w_2). \end{aligned}

A real network chains layers together. An early weight affects later activations, then the prediction, then the loss. Backpropagation applies the chain rule through those layers to compute the same kind of partial derivatives.

Choosing DD gets harder when the loss has curved valleys, flat regions, saddles, or several minima. A downhill step can settle in a shallow basin even when a deeper one exists elsewhere.

The surface below has two minima. The first run settles in the higher one. Try the other basin to see a different result. You can also drag the graph to look around and zoom with the buttons.

Rotate a loss surface with two basins and follow computed gradient descentStep 15. Weights −0.816, 0.000. Loss 1.1010. A computed gradient path descends a three-dimensional surface with two basins. The floor trail shows parameter positions.w₁w₂localglobalStep 15 / 15 · loss 1.1010w = (−0.816, 0.000)D = −0.09 G
Height is loss; the floor axes are two weights. Orange follows computed gradient steps. The left basin contains a local minimum with positive loss; the right basin reaches the global minimum, zero. This small example shows why local slope information cannot promise the best solution.
Drag to rotate. Arrow keys also rotate; + and − zoom.
The exact loss behind the picture

The surface uses

Ldraw(x,y)=(x2−1)2+0.3(x−1)2+8y2.\begin{aligned} L_{\mathrm{draw}}(x,y)&=(x^2-1)^2\\[6pt] &\quad+0.3(x-1)^2+8y^2. \end{aligned}

Its gradient is (4x(x2−1)+0.6(x−1), 16y)(4x(x^2-1)+0.6(x-1),\,16y). The global minimum is (1,0)(1,0): all three nonnegative terms vanish there. There is also a strict local minimum at x=(−1−0.4)/2≈−0.816x=(-1-\sqrt{0.4})/2\approx-0.816, y=0y=0, separated from the deeper basin by a saddle.

The animation recomputes the gradient at each step, using α=0.09\alpha=0.09. The same function drives the surface and the path. Moving between iterates is only a visual aid; the optimization steps are discrete. This is a two-weight illustration, not a claim that shallow local minima are the main difficulty in every large network.

§Turn many slopes into one score

We have a candidate step DD, but its entries only tell us how much to change the weights. They do not tell us whether those changes help. For that, we need to know how the loss responds. That is the information in GG.

Hold every weight except WijW_{ij} fixed. Recall that Gij=∂L/∂WijG_{ij}=\partial L/\partial W_{ij} is the local slope: loss change per unit change in this weight, in the limit of a tiny move. Near our current weights, we approximate the curve by a straight line. On a straight line, change in height is slope times horizontal movement. Here that becomes

predicted loss change≈GijDij.\text{predicted loss change}\approx G_{ij}D_{ij}.

For example, if Gij=3G_{ij}=3 and we increase that weight by Dij=0.01D_{ij}=0.01, the predicted loss change is 3×0.01=0.033\times0.01=0.03. Moving it by −0.01-0.01 instead predicts −0.03-0.03, a decrease. A negative gradient entry reverses which direction helps. The same weight change can help or hurt depending on its slope.

When several weights change together, differentiability tells us that their first-order effects add. This does not assume the weights act independently: the slopes depend on the whole current matrix, and the linear approximation leaves a remainder. We will make that approximation explicit in the next section. For now, our combined score is

∑i,jGijDij=⟨G,D⟩F.\sum_{i,j}G_{ij}D_{ij}=\langle G,D\rangle_F.

This is the ordinary dot product of the entries, keeping them arranged as matrices. Its name is the Frobenius inner product. For any two matrices AA and BB of the same shape, we write

⟨A,B⟩F=∑i,jAijBij=tr⁡(A⊤B).\langle A,B\rangle_F=\sum_{i,j}A_{ij}B_{ij} =\operatorname{tr}(A^\top B).

Here A⊤A^\top is the transpose, which swaps rows and columns. tr⁡\operatorname{tr} denotes the trace, the sum of a square matrix's diagonal entries. The whole expression returns one number.

What does the trace notation mean?

For a square matrix CC, the trace is the sum of its diagonal entries:

tr⁡(C)=∑jCjj.\operatorname{tr}(C)=\sum_j C_{jj}.

For example,

C=[302−2],tr⁡(C)=3+(−2)=1.\begin{aligned} C&=\begin{bmatrix}3&0\\2&-2\end{bmatrix},\\[10pt] \operatorname{tr}(C)&=3+(-2)=1. \end{aligned}

The entry 22 below the diagonal contributes nothing. Trace takes a square matrix and returns one scalar.

Why does it appear in our score? The jjth diagonal entry of A⊤BA^\top B is the dot product of column jj of AA with column jj of BB:

(A⊤B)jj=∑iAijBij,tr⁡(A⊤B)=∑j∑iAijBij.\begin{aligned} (A^\top B)_{jj}&=\sum_i A_{ij}B_{ij},\\[10pt] \operatorname{tr}(A^\top B)&=\sum_j\sum_i A_{ij}B_{ij}. \end{aligned}

Adding those diagonal entries counts every matching pair once. That gives the same inner product we started with.

We will also need a way to measure the size of a step. Using the same entries, define the Frobenius norm:

∥A∥F=∑i,jAij2.\|A\|_F=\sqrt{\sum_{i,j}A_{ij}^2}.

It is the Euclidean length we would get by listing all the entries in a vector. This length and our score are related: the difference between the squared lengths of A+BA+B and A−BA-B isolates their inner product.

⟨A,B⟩F=∥A+B∥F2−∥A−B∥F24.\boxed{\langle A,B\rangle_F =\frac{\|A+B\|_F^2-\|A-B\|_F^2}{4}.}

This is the real polarization identity.

Where does the polarization formula come from?

Start with the norm's definition. Squaring it removes the square root, and each entry of A+BA+B is Aij+BijA_{ij}+B_{ij}. Apply the ordinary identity (a+b)2=a2+2ab+b2(a+b)^2=a^2+2ab+b^2 to each entry:

∥A+B∥F2=∑i,j(Aij+Bij)2=∑i,jAij2+∑i,jBij2+2∑i,jAijBij=∥A∥F2+∥B∥F2+2⟨A,B⟩F.\begin{aligned} \|A+B\|_F^2&=\sum_{i,j}(A_{ij}+B_{ij})^2\\[10pt] &=\sum_{i,j}A_{ij}^2+\sum_{i,j}B_{ij}^2\\[6pt] &\quad+2\sum_{i,j}A_{ij}B_{ij}\\[10pt] &=\|A\|_F^2+\|B\|_F^2\\[6pt] &\quad+2\langle A,B\rangle_F. \end{aligned}

The last line simply recognizes the two squared norms and the inner product from their definitions. Replacing BB by −B-B changes the sign of the cross term:

∥A−B∥F2=∥A∥F2+∥B∥F2−2⟨A,B⟩F.\begin{aligned} \|A-B\|_F^2&=\|A\|_F^2+\|B\|_F^2\\[6pt] &\quad-2\langle A,B\rangle_F. \end{aligned}

Subtract the second expansion from the first. The squared norms cancel, and the cross terms leave 4⟨A,B⟩F4\langle A,B\rangle_F. Dividing by four gives the formula above.

§Predict whether a step will lower the loss

Our sum of slopes times changes looks like a useful score. Now we need to connect it to the quantity we actually care about, the loss after taking the step.

A differentiable function is locally approximated by its value plus a linear change. Applied to the weights, that linear change is exactly ⟨G,D⟩F\langle G,D\rangle_F. This is the first-order Taylor expansion:

L(W+D)≈L(W)+⟨G,D⟩F.\boxed{L(W+D)\approx L(W)+\langle G,D\rangle_F.}

The first term is our current loss. The second is the predicted change. A more negative inner product means a larger predicted decrease. The approximation is local, so the step size still matters.

Why does the local slope predict a change in loss?

Taylor approximates a function near a point using derivatives there. For a sufficiently smooth scalar function,

f(x+h)=f(x)+f′(x)h+f′′(x)2!h2+⋯+f(N)(x)N!hN+RN(h).\begin{aligned} f(x+h)&=f(x)+f'(x)h\\[8pt] &\quad+\frac{f''(x)}{2!}h^2+\cdots\\[8pt] &\quad+\frac{f^{(N)}(x)}{N!}h^N+R_N(h). \end{aligned}

The remainder RNR_N records the error. If the next derivative is continuous and bounded in magnitude by BB along the interval, ∣RN(h)∣≤B∣h∣N+1/(N+1)!|R_N(h)|\leq B|h|^{N+1}/(N+1)!. Keeping only the value and slope gives the first-order approximation.

For f(x)=x2f(x)=x^2 at x=1x=1, the tangent predicts 1+2h1+2h and the exact value is 1+2h+h21+2h+h^2. Watch the terms appear with their geometric meaning.

A parabola, its tangent line and the Taylor approximation errorAt x=1.15, the actual value is 1.322, the tangent predicts 1.300, and their gap is 0.0225. Halving h quarters the gap.Value, slope, then the missing pieceHalve h. The error drops to one quarter.1hx
f(1+h)=f(1+h)=11+2h+2h+h2+h^2
h = 0.150tangent = 1.300actual = 1.322gap h² = 0.0225curvetangenterror
First locate the value, then build the tangent prediction, then reveal the gap to the curve. Halving h halves the linear change but quarters the error h².
Value, slope, and the part the tangent misses.

For matrices, restrict the loss to a line, ϕ(t)=L(W+tD)\phi(t)=L(W+tD). The chain rule gives

ϕ′(0)=∑i,j∂L∂WijDij=⟨G,D⟩F.\phi'(0)=\sum_{i,j}\frac{\partial L}{\partial W_{ij}}D_{ij} =\langle G,D\rangle_F.

That is where Frobenius enters: it packages the sum of slopes times changes. Differentiability gives the precise statement

L(W+D)=L(W)+⟨G,D⟩F+R(D).\begin{aligned} L(W+D)&=L(W)+\langle G,D\rangle_F\\[6pt] &\quad+R(D). \end{aligned}

The remainder becomes small relative to the step:

R(D)∥D∥F⟶0as D→0.\frac{R(D)}{\|D\|_F}\longrightarrow0\quad\text{as }D\to0.

A quadratic error bound needs extra smoothness, such as a continuous Hessian nearby. For our earlier quadratic, the remainder is exactly 12(d12+4d22)\tfrac12(d_1^2+4d_2^2). An oversized step can let this positive remainder overwhelm the predicted decrease.

§Decide how far we are willing to move

We can now compare steps by their predicted change in loss. But the prediction only uses local slopes. We choose a size limit so we stay close enough for it to be useful. A huge downhill step can overshoot, raise the actual loss, and send training bouncing around. The tangent line will happily recommend it anyway.

Call the limit η>0\eta>0, our step budget. We choose both its size and how to measure it; a limit alone does not guarantee convergence. Measuring size with the Frobenius norm gives

min⁡∥D∥F≤η⟨G,D⟩F.\min_{\|D\|_F\leq\eta}\langle G,D\rangle_F.

Read this as: among all steps within the budget, choose the one with the most negative predicted loss change. The gradient GG is fixed at our current weights; the step DD is what we get to choose.

To see the geometry, imagine listing the matrix entries in vectors. For nonzero GG and DD, call the angle between those vectors φ\varphi:

⟨G,D⟩F=∥G∥F∥D∥Fcos⁡φ.\langle G,D\rangle_F=\|G\|_F\|D\|_F\cos\varphi.

The quantity ∥D∥Fcos⁡φ\|D\|_F\cos\varphi is the signed projection of the step onto the gradient direction. Multiplying by ∥G∥F\|G\|_F turns that displacement into a predicted loss change. Moving with the gradient gives a positive score; moving against it gives a negative one. A perpendicular step scores zero, which predicts no first-order improvement. We want the most negative score our budget allows.

Cauchy–Schwarz says ∣⟨G,D⟩F∣≤∥G∥F∥D∥F|\langle G,D\rangle_F|\leq\|G\|_F\|D\|_F. Taking the negative side of that bound and applying our budget,

⟨G,D⟩F≥−∥G∥F∥D∥F≥−η∥G∥F.\langle G,D\rangle_F \geq-\|G\|_F\|D\|_F \geq-\eta\|G\|_F.

For G≠0G\neq0, choose DF=−ηG/∥G∥FD_F=-\eta G/\|G\|_F. Substitution gives ⟨G,DF⟩F=−η∥G∥F\langle G,D_F\rangle_F=-\eta\|G\|_F, exactly the lower bound. There is our minus sign and our best direction.

Ordinary SGD uses −αG-\alpha G: the same direction, with step length α∥G∥F\alpha\|G\|_F instead of a fixed η\eta.

Our budget so far measures the combined size of the changes to the matrix entries. There is another useful question: how much will this update change what the layer outputs?

To see the difference, feed the same input vector xx into the layer before and after the update. Here xx means the activations entering this layer. For the linear part of the layer, call the output zz:

zbefore=Wx,zafter=(W+D)x,=Wx+Dx,zafter−zbefore=Dx.\begin{aligned} z_{\mathrm{before}}&=Wx,\\[8pt] z_{\mathrm{after}}&=(W+D)x,\\[8pt] &=Wx+Dx,\\[8pt] z_{\mathrm{after}}-z_{\mathrm{before}}&=Dx. \end{aligned}

The old output WxWx cancels. An unchanged bias would cancel too. What remains, DxDx, is exactly the change caused by updating the weights. Even in one dimension, this distinction matters: changing a weight from 2 to 2.1 changes the output for input 3 from 6 to 6.3. The weight changed by 0.1; the output changed by 0.1×3=0.30.1\times3=0.3.

So we can choose a budget based on the update's effect on the layer's outputs. We need it to cover different inputs, not just one example. That leads to the next question: which input direction does DD change the most?

§Measure the largest output change

Keep the same loss prediction, but measure the update by its largest output change per unit of input length.

We must fix the input length before comparing directions. Otherwise, simply doubling xx doubles DxDx, even though the update DD has not changed. Try every input with length 1 and measure the resulting change DxDx. The largest length we find is the spectral norm, also called the Euclidean operator norm:

∥D∥op=max⁡∥x∥2=1∥Dx∥2.\|D\|_{\mathrm{op}}=\max_{\|x\|_2=1}\|Dx\|_2.

Here ∥x∥2\|x\|_2 is Euclidean vector length (the size of the vector, x12+⋯+xn2\sqrt{x_1^2+\cdots+x_n^2}). Fixing it at 1 lets us compare directions fairly.

Sweep unit inputs to find the spectral normUnit input (0.707,0.707) maps to (1.414,1.414), with output length 2.000. The maximum is 2.Which unit input gets stretched the most?The longest output has length 2.input x · length 1output DxD
D=[1.50.50.51.5]D=\begin{bmatrix}1.5&0.5\\0.5&1.5\end{bmatrix}
‖Dx‖₂ = 2.0002
x∗=12[11],∥D∥op=2x_*={1\over\sqrt2}\begin{bmatrix}1\\1\end{bmatrix},\quad\|D\|_{\rm op}=2
A unit input sweeps around the circle while its image sweeps an ellipse. For this D, the longest image has length 2, attained at x = (1, 1)/√2 and its negative. The animation then settles on one maximizing direction.
Keep the input length fixed. Search over its direction.

A spectral budget ∥D∥op≤η\|D\|_{\mathrm{op}}\leq\eta guarantees ∥Dx∥2≤η∥x∥2\|Dx\|_2\leq\eta\|x\|_2 for every input. It controls this layer's worst output change per unit input. It does not, by itself, control the whole network's loss curvature.

For a diagonal update, the two budgets are easy to see:

D=diag⁡(d1,d2)=[d100d2],D=\operatorname{diag}(d_1,d_2) =\begin{bmatrix}d_1&0\\0&d_2\end{bmatrix},
∥D∥F=d12+d22,∥D∥op=max⁡(∣d1∣,∣d2∣).\begin{aligned} \|D\|_F&=\sqrt{d_1^2+d_2^2},\\[10pt] \|D\|_{\mathrm{op}}&=\max(|d_1|,|d_2|). \end{aligned}

Set η=1\eta=1. Each point in the animation is a candidate update (d1,d2)(d_1,d_2). Shading marks allowed steps; the outline is the budget limit. Frobenius gives a disk, and the spectral norm gives a square.

Find the best allowed step by sliding an equal-score line to the budget boundaryExample 3. 4. Last contact: this is the best allowed update. The blue arrow is minus the gradient: a direction of decreasing predicted loss, with arbitrary drawn length. The orange dot is the candidate update; its matrix and score change as it moves. The dashed line joins equal scores.Example 3 · budget η = 1
G=[200−1]G=\begin{bmatrix}2&0\\0&-1\end{bmatrix}
4. Last contact: this is the best allowed updateFrobenius
d12+d22≤1\sqrt{d_1^2+d_2^2}\leq1
d₁d₂−gBest update D*−0.8940.0000.0000.447score = −2.24
Spectral
max⁡(∣d1∣,∣d2∣)≤1\max(|d_1|,|d_2|)\leq1
d₁d₂−gBest update D*−1.0000.0000.0001.000score = −3.00
−g: direction, not step lengthD: the update being considereddashed: same predicted change
Read the stages in order: allowed steps, the −g direction, decreasing score, then the last contact. The blue arrow is a direction guide. The orange point is the candidate update; its matrix and score change together. Both panels use the same gradient and budget η = 1.
Start at score zero. Follow the blue arrow toward lower scores.

Write the score as c(d1,d2)=g1d1+g2d2c(d_1,d_2)=g_1d_1+g_2d_2. The blue arrow points along −g=(−g1,−g2)-g=(-g_1,-g_2), where this score decreases. Its drawn length is just for readability. The orange dot is a candidate update DD, and the dashed line passes through all updates with the same score as that dot.

Start at the origin: no weight change, score zero. Now slide the dashed line in the arrow's direction. More negative scores mean larger predicted decreases. Stop when the line is about to lose contact with the allowed region. The final orange dot is the best allowed update. The arrow tells us which way is better; the boundary decides how far we can go.

Keep our loss prediction in view:

L(W+D)≈L(W)+⟨G,D⟩F⏟the change we minimize.\begin{aligned} L(W+D)&\approx L(W)\\[8pt] &\quad+\underbrace{\color{#b54328}{\langle G,D\rangle_F}}_{\text{the change we minimize}}. \end{aligned}

We changed which steps are allowed. The highlighted score is still the Frobenius inner product.

§Choose a step in each independent direction

The diagonal examples show what the new budget allows. We now need the best step for a general matrix, whose useful directions may not line up with the coordinate axes:

min⁡∥D∥op≤η⟨G,D⟩F.\min_{\|D\|_{\mathrm{op}}\leq\eta}\langle G,D\rangle_F.

The useful fact is that we can choose perpendicular input and output coordinates in which the matrix only stretches directions. This is the singular value decomposition, or SVD. It gives unit input directions viv_i, unit output directions uiu_i, and stretch factors σi\sigma_i, called singular values. In these coordinates, Gvi=σiuiGv_i=\sigma_i u_i.

We will use the SVD theorem without proving it here. It says that every real matrix, square or rectangular, can be written as UΣV⊤U\Sigma V^\top. You can accept that result for this derivation, or follow the construction in Gilbert Strang's SVD lecture. What matters for our optimizer is what the factors do.

For an m×nm\times n matrix, the full factorization has U∈Rm×mU\in\mathbb R^{m\times m}, Σ∈Rm×n\Sigma\in\mathbb R^{m\times n}, and V∈Rn×nV\in\mathbb R^{n\times n}. The middle factor has the same rectangular shape as the original matrix, with nonnegative entries on its diagonal and zeros everywhere else. UU and VV are orthogonal, so they preserve lengths.

Read UΣV⊤U\Sigma V^\top right to left: V⊤V^\top changes input coordinates, Σ\Sigma stretches each coordinate, and UU changes to output coordinates. Those coordinate changes can include reflections. The original matrix need not look diagonal at all.

For G=[3001]G=\begin{bmatrix}3&0\\0&1\end{bmatrix}, the useful directions are already the axes. It triples one and leaves the other unchanged, so U=V=IU=V=I and Σ=G\Sigma=G. A less conveniently aligned matrix needs the coordinate changes too.

SVD rotates and stretches a circle; the polar factor removes the stretchReplace both singular values with ones. A two-dimensional SVD with U a 30 degree rotation, singular values 2 and one half, and V transpose a minus 45 degree rotation. The current lengths of the two marked directions are 1.000 and 1.000. At the end both lengths are one and the polar factor preserves lengths.From a circle to an ellipse, and backReplace both singular values with onesu₁u₂
G=UΣV⊤ ⟶ Q=UV⊤G=U\Sigma V^\top\ \longrightarrow\ Q=UV^\top
Qvi=uiQv_i=u_i
Σ⟶I\Sigma\longrightarrow I
Lengths of the marked directions1.001.0012Dashed circle: length 1
Follow the marked input directions through Vᵀ, Σ, and U. This full-rank 2 × 2 example has stretches 2 and ½, with input and output rotations of 45° and 30°. The final stage previews what happens when both stretches become 1.
Follow the factors from right to left.

The animation gives us the directions; now we can write the score in those coordinates. The compact SVD keeps only the rr directions with positive stretches.

For nonzero GG, its compact SVD is

G=UrΣrVr⊤=∑i=1rσiuivi⊤.G=U_r\Sigma_rV_r^\top =\sum_{i=1}^r\sigma_i u_i v_i^\top.

Here rr counts the independent active directions, and Σr\Sigma_r stores the positive stretches on its diagonal. The columns of UrU_r and VrV_r are the output and input directions.

The largest σi\sigma_i is ∥G∥op\|G\|_{\mathrm{op}}. Combining all their squared lengths gives ∥G∥F=∑iσi2\|G\|_F=\sqrt{\sum_i\sigma_i^2}.

Unlike eigenvalue diagonalization, SVD uses separate input and output coordinates. That is why the same construction works for rectangular matrices too.

Now the score separates into those directions:

⟨G,D⟩F=∑i=1rσi ui⊤Dvi.\langle G,D\rangle_F=\sum_{i=1}^r\sigma_i\,u_i^\top Dv_i.

Read one term before adding them all. The vector DviDv_i is the output change caused by the update when the input is viv_i. The scalar ui⊤Dviu_i^\top Dv_i is its signed component along the unit output direction uiu_i. Positive means along uiu_i; negative means against it. The positive number σi\sigma_i weights that component in our score.

Our spectral budget limits the output change for every unit input. Each viv_i is one of those inputs, so

∥Dvi∥2≤∥D∥op∥vi∥2≤η.\|Dv_i\|_2\leq\|D\|_{\mathrm{op}}\|v_i\|_2 \leq\eta.

A signed component cannot have greater magnitude than the vector itself. A vector of length at most η\eta therefore cannot project farther than −η-\eta in the opposite direction. Written as inequalities,

ui⊤Dvi≥−∥Dvi∥2≥−η.u_i^\top Dv_i\geq-\|Dv_i\|_2\geq-\eta.

The most negative value occurs when Dvi=−ηuiDv_i=-\eta u_i: use the full allowed length and point exactly against uiu_i. Multiplying each bound by σi>0\sigma_i>0 preserves its direction. Adding gives

⟨G,D⟩F=∑i=1rσiui⊤Dvi≥∑i=1rσi(−η)=−η∑i=1rσi.\begin{aligned} \langle G,D\rangle_F &=\sum_{i=1}^r\sigma_i u_i^\top Dv_i\\[10pt] &\geq\sum_{i=1}^r\sigma_i(-\eta)\\[10pt] &=-\eta\sum_{i=1}^r\sigma_i. \end{aligned}

That is a lower bound. To prove it is the best attainable score, we still need one matrix that sends every active viv_i to −ηui-\eta u_i without breaking the budget. Choose

D∗=−ηUrVr⊤.\boxed{D_*=-\eta U_rV_r^\top.}

To check this, multiply the proposed update by one input direction viv_i. Let eie_i be the coordinate vector with a 1 in position ii and zeros elsewhere. The columns of VrV_r have unit length and are perpendicular to one another, so their dot products with viv_i give

Vr⊤vi=ei.V_r^\top v_i=e_i.

Multiplying a matrix by eie_i selects its iith column, so Urei=uiU_re_i=u_i. Substitute those two facts, one at a time:

D∗vi=(−ηUrVr⊤)vi=−ηUr(Vr⊤vi)=−ηUrei=−ηui.\begin{aligned} D_*v_i&=(-\eta U_rV_r^\top)v_i\\[8pt] &=-\eta U_r(V_r^\top v_i)\\[8pt] &=-\eta U_re_i\\[8pt] &=-\eta u_i. \end{aligned}

Thus this single update matrix sends every active input direction to the opposite output direction with length η\eta. Every term in the score reaches its lower bound.

It also respects the budget for combinations of inputs. Vr⊤V_r^\top extracts the coordinates in the active input directions without increasing length; UrU_r maps those coordinates into orthonormal output directions without changing their length. Multiplication by η\eta scales that length by η\eta. So ∥D∗x∥2≤η∥x∥2\|D_*x\|_2\leq\eta\|x\|_2 for every xx, with equality on each active viv_i.

We have both pieces: no allowed update can score lower, and this update achieves the bound. After factoring out the minus sign and step size, its effect is to replace the positive stretches (the singular values stored in Σr\Sigma_r) by ones:

UrΣrVr⊤⟼UrIrVr⊤=UrVr⊤.U_r\Sigma_rV_r^\top\longmapsto U_rI_rV_r^\top=U_rV_r^\top.

Call UrVr⊤U_rV_r^\top the polar factor, written polar⁡(G)\operatorname{polar}(G). It keeps the active input and output directions and replaces their positive stretches by 1. We have derived the matrix transformation at the center of Muon. Next we need to decide which gradient estimate to feed into it, and how to compute it cheaply.

§Give the direction some memory

So far we have treated GG as the direction to trust. In training, we usually compute it from a minibatch, a small sample of the data. Different samples give different gradients. Before applying the polar transformation, combine recent gradients into a steadier estimate MtM_t:

Mt=βMt−1+(1−β)Gt.M_t=\beta M_{t-1}+(1-\beta)G_t.

Start with M0=0M_0=0 and choose 0≤β<10\leq\beta<1. With β=0.8\beta=0.8, keep 80% of the old estimate and add 20% of the incoming gradient. The animation builds that sum.

β = 0.8, starting from M₀ = 0.
Momentum builds a weighted sum, with formulas synchronized to the arrowsBatch 12. Use the result as the next memory. Previous estimate 0.923, 0.128 times 0.8, plus incoming gradient 1.000, −0.900 times 0.2, gives 0.939, −0.077. Formula values are rounded to three decimals.Batch 12 · β = 0.8Use the result as the next memory
MtM_t==βMt−1\beta M_{t-1}++(1−β)Gt(1-\beta)G_t
≈\approx0.8[0.9230.128]0.8\begin{bmatrix}0.923\\0.128\end{bmatrix}++0.2[1.000−0.900]0.2\begin{bmatrix}1.000\\-0.900\end{bmatrix}
≈\approx[0.7390.103]\begin{bmatrix}0.739\\0.103\end{bmatrix}++[0.200−0.180]\begin{bmatrix}0.200\\-0.180\end{bmatrix}
MtM_t≈\approx[0.939−0.077]\begin{bmatrix}0.939\\-0.077\end{bmatrix}
entry 1entry 2memorythis batchnew estimateSecond entry over successive batches012 batches
Blue is the retained estimate; dark ink is the new contribution. Add them head to tail to get orange. The vectors list two diagonal entries; values are rounded to three decimals. The lower trace follows the second entry across batches.

Now put that memory on a loss surface. The dark path follows each noisy gradient; the blue path averages them before stepping. Both start at the same weights, use the same learning rate, and receive the same sequence of batch perturbations. Each computes its gradient at its own current position.

Compare noisy gradient descent and momentum on a three-dimensional loss surfaceStep 48 of 48. Raw gradients and momentum on the same quadratic loss. Noise on, momentum beta 0.8. Both use learning rate 0.14. Current losses 0.334 and 0.018.Same valley. Different memory.Step 48 / 48 · α = 0.14 · β = 0.8 · noise on
L(w1,w2)=12w12+6w22L(w_1,w_2)=\tfrac12w_1^2+6w_2^2
Height = lossminimumw₁w₂raw gradientmomentumMemory carries the path past w₁ = 0.Smoothing does not remove overshoot.Position along the valley, w₁0048 stepsLoss: raw 0.334 · memory 0.018Sideways travel: 16.68 vs 3.63
Height is loss; the two floor coordinates are weights. Sideways travel counts the total movement across the narrow valley. The lower plot shows the position along the valley: crossing below zero means passing the minimum in that direction. Both methods take 48 computed steps with α = 0.14. Smooth motion between steps is only a visual aid.
Drag to rotate. Compare noisy steps with their moving average.

Watch the dark path bounce across the valley while the blue path filters those changing directions. Then remove the noise and try longer memory. The blue path can pass the minimum because its stored direction takes time to reverse. Momentum reduces jitter in this example; it does not guarantee that we avoid overshooting. The learning rate and memory length both matter. Why Momentum Really Works explores this behavior in detail.

Use this momentum estimate in our update:

Wt+1=Wt−ηt polar⁡(Mt).\boxed{W_{t+1}=W_t-\eta_t\,\operatorname{polar}(M_t).}

Momentum followed by orthogonalization gives the idealized core of Muon. Its step optimizes the local score built from MtM_t, so it need not decrease the current minibatch's loss.

How much history is in that average, and why this order?

Expanding the recurrence gives Mt=(1−β)∑s=1tβt−sGsM_t=(1-\beta)\sum_{s=1}^t\beta^{t-s}G_s. Older gradients fade geometrically. The weights sum to 1−βt1-\beta^t, so starting at zero pulls early estimates toward zero.

Average first, then orthogonalize. These operations do not commute. In one dimension, averaging 1010 and −1-1 gives a positive number whose sign is +1+1. Averaging their signs gives zero. The sign function is the scalar version of the polar transformation.

So far, our optimizer has a clean formula and an SVD bill. Let's deal with the bill.

§Change the stretches without finding the directions

Computing a full decomposition at every training step would be expensive. But look at what we actually need from the momentum matrix:

M=UrΣrVr⊤,Q=UrIrVr⊤=UrVr⊤.\begin{aligned} M&=U_r\Sigma_rV_r^\top,\\[10pt] Q&=U_rI_rV_r^\top=U_rV_r^\top. \end{aligned}

The step from Σr\Sigma_r to IrI_r is the important choice here. Let's work through why we made it, starting with two numbers instead of the whole factorization.

Suppose the momentum matrix is diagonal, with stretches (singular values on the diagonal of Σr\Sigma_r) equal to 10 and 1. Its input and output directions are just the two coordinate axes. Set the allowed output-change budget to η=1\eta=1. We are choosing an update, so we may choose its length in each of those directions.

Recall our score: multiply each slope by its proposed change, then add. Here we use the momentum values 10 and 1 as averaged slopes. A more negative total is better according to that score. Consider these two choices:

Update along the two axesLargest output change for a unit inputScore using the momentum
−1-1 and −0.1-0.11110(−1)+1(−0.1)=−10.110(-1)+1(-0.1)=-10.1
−1-1 and −1-11110(−1)+1(−1)=−1110(-1)+1(-1)=-11

The first choice preserves the momentum's 10-to-1 ratio. The second moves farther along the weaker direction. Both obey the same budget, and the second has the more negative score. Keeping that ratio would give up an improvement we are allowed to take.

Why doesn't moving in both directions break the budget? Take any unit input x=(x1,x2)x=(x_1,x_2), so x12+x22=1x_1^2+x_2^2=1. The second update sends it to (−x1,−x2)(-x_1,-x_2), whose length is still 1. The input cannot have length 1 along both axes at once. That is why the spectral budget permits an update length of 1 along each axis. A Frobenius budget would instead measure the entries together as 12+12=2\sqrt{1^2+1^2}=\sqrt2 and reject this update when η=1\eta=1. These are different definitions of a small update.

Now connect those numbers to the general matrix. Recall that the SVD describes each input direction viv_i and its corresponding output direction uiu_i:

Mvi=σiui.Mv_i=\sigma_i u_i.

Here σi>0\sigma_i>0 is the original stretch. It tells us what MM does; it does not require our update DD to use that same length. Introduce ai≥0a_i\geq0 as the update length we choose in direction ii. To move against the momentum, we ask for

Dvi=−aiui.Dv_i=-a_i u_i.

This is our proposed update rule, not another property of the SVD. It means: when the input points along viv_i, the change in the layer's output points opposite uiu_i, with length aia_i. One matrix that does this in every active direction is

D=−Urdiag⁡(a1,…,ar)Vr⊤.D=-U_r\operatorname{diag}(a_1,\ldots,a_r)V_r^\top.

As in the multiplication we checked earlier, Vr⊤V_r^\top selects a coordinate, the diagonal matrix scales it by aia_i, and UrU_r turns it into uiu_i. For this choice of DD, the largest output stretch is max⁡iai\max_i a_i. The budget therefore says each aia_i may be at most η\eta.

The score also comes from earlier: we used ⟨G,D⟩F\langle G,D\rangle_F to predict the loss change. After introducing momentum, we use ⟨M,D⟩F\langle M,D\rangle_F to choose a step from the averaged slopes. This is a momentum-based score, not an exact prediction from the current gradient. Expanding it in the singular directions gives the same calculation as before:

⟨M,D⟩F=∑iσiui⊤Dvi=∑iσiui⊤(−aiui)=−∑iσiai.\begin{aligned} \langle M,D\rangle_F &=\sum_i\sigma_i u_i^\top Dv_i\\[8pt] &=\sum_i\sigma_i u_i^\top(-a_i u_i)\\[8pt] &=-\sum_i\sigma_i a_i. \end{aligned}

The last equality uses ui⊤ui=1u_i^\top u_i=1. Each positive σi\sigma_i multiplies a negative term, so increasing aia_i makes the score smaller. We can increase every aia_i to η\eta without exceeding the spectral budget. The earlier lower-bound argument shows this choice is optimal even among updates outside this proposed form.

All chosen lengths are now equal. Factoring out their common size η\eta leaves ones on the diagonal:

D∗=−ηUrIrVr⊤,Q=UrIrVr⊤.\begin{aligned} D_*&=-\eta U_rI_rV_r^\top,\\[8pt] Q&=U_rI_rV_r^\top. \end{aligned}

That is why we replace the positive stretches (the singular values stored in Σr\Sigma_r) by 1 in QQ. We keep the directions and choose new update lengths to fit our budget. The learning rate supplies the common size afterward. Only the middle factor becomes an identity; QQ still maps the input directions to their corresponding output directions and generally is not the identity matrix.

Newton–Schulz offers a way to approach this target using repeated matrix multiplications. Before deriving its update, we need two tools: a multiplication that changes the stretches without changing their directions, and a scalar rule that brings each stretch toward 1 and keeps it there.

Start with the multiplication. Let XX be our current approximation. The following identity changes its stretches without computing the directions explicitly.

If X=UrΣrVr⊤X=U_r\Sigma_rV_r^\top, then

XX⊤X=UrΣr3Vr⊤.XX^\top X=U_r\Sigma_r^3V_r^\top.

Multiplying by the transpose and then itself cubes every singular value while retaining the singular directions. That means

aX+bXX⊤X=Ur(aΣr+bΣr3)Vr⊤.aX+bXX^\top X=U_r(a\Sigma_r+b\Sigma_r^3)V_r^\top.

We can apply p(s)=as+bs3p(s)=as+bs^3 to all singular values using matrix multiplications alone. We never have to compute UrU_r or VrV_r. That leaves a scalar design problem: choose pp so repeating it brings positive stretches (singular values stored in Σr\Sigma_r) to 1.

Show the factors cancelling

The compact SVD has Vr⊤Vr=IrV_r^\top V_r=I_r and Ur⊤Ur=IrU_r^\top U_r=I_r. Expand the product:

XX⊤X=(UrΣrVr⊤)(VrΣrUr⊤)(UrΣrVr⊤)=UrΣrVr⊤Vr⏟IrΣrUr⊤Ur⏟IrΣrVr⊤=UrΣr3Vr⊤.\begin{aligned} &XX^\top X\\[8pt] &=(U_r\Sigma_rV_r^\top)(V_r\Sigma_rU_r^\top) (U_r\Sigma_rV_r^\top)\\[10pt] &=U_r\Sigma_r\underbrace{V_r^\top V_r}_{I_r} \Sigma_r\underbrace{U_r^\top U_r}_{I_r}\Sigma_rV_r^\top\\[10pt] &=U_r\Sigma_r^3V_r^\top. \end{aligned}

The same argument produces higher odd powers of Σr\Sigma_r. If a polynomial produces a negative value, that sign can be absorbed into a singular vector; the actual singular value is its magnitude. Our classical cubic below stays nonnegative on the interval we use.

§Make one the destination, and keep it there

Now we can work on one stretch at a time. If all the positive diagonal entries approach 1, then

Σr,k⟶Ir,Xk=UrΣr,kVr⊤⟶UrVr⊤=Q.\begin{aligned} \Sigma_{r,k}&\longrightarrow I_r,\\[10pt] X_k=U_r\Sigma_{r,k}V_r^\top&\longrightarrow U_rV_r^\top=Q. \end{aligned}

That is why 1 is the destination. Reaching it in the scalar calculation gives us the polar direction in the matrix calculation. We still need a rule for getting there.

Take one positive stretch s0s_0 and repeatedly feed the result of a function back into that same function:

s1=p(s0),s2=p(s1),…s_1=p(s_0),\qquad s_2=p(s_1),\qquad \ldots

In general, sk+1=p(sk)s_{k+1}=p(s_k). Here kk counts these inner iterations, not training steps. We want the numbers to approach 1. Once a number gets there, another iteration should leave it there, so we require p(1)=1p(1)=1. A value that a function leaves unchanged is called a fixed point.

Staying at the destination is only half the job. Nearby values also need to move toward it. For example, p(s)=12s+12p(s)=\tfrac12s+\tfrac12 sends 0.90.9 to 0.950.95, then 0.9750.975, halving the distance to 1 each time. But p(s)=2s−1p(s)=2s-1 sends 0.90.9 to 0.80.8, then 0.60.6. Both fix 1; only the first attracts nearby values. A fixed point can be a place you approach or a place you get kicked away from.

Watch a value go into the function, then come back as its next input

A fixed point is where y=p(s)y=p(s) meets y=sy=s. Orange goes vertically to evaluate the function, then horizontally to the diagonal to reuse the output. The number also moves into the formula above the graph, so the geometry and substitution tell the same story.

Plug in the number. Reuse the result.
An animated staircase compares attracting and repelling fixed pointsRepelling fixed point at 1. The error doubles at every step. Input 0.6000 moves into the fixed formula, which produces 0.2000. The output moves to the diagonal and is reused as the next input.Same fixed point, opposite behaviorThe error doubles at every step
p(s)=2s−1p(s)=2s-1
2×2\times
−1-1
p(0.6000)=p(0.6000)=
0.200000.50.511p(s)y = s
p(1)=1alwaysp(1)=1\quad\text{always}
s2=0.6000s_2=0.6000
s3=0.2000s_{3}=0.2000
∣ek+1∣=2∣ek∣|e_{k+1}|=2|e_k|
Error: eₖ = sₖ − 10.6000
Both maps fix 1. Starting at 0.9, p(s) = ½s + ½ halves the error; p(s) = 2s − 1 doubles it. Watch the input travel into the function, the arithmetic appear, and the result feed the next iteration.

We want the attracting behavior, with fast error reduction near the target. Write the current stretch as s=1+es=1+e, where ee is its error. The local approximation we used for the loss now tells us how the next error behaves. Assuming p(1)=1p(1)=1,

p(1+e)−1=p′(1)e+O(e2).p(1+e)-1=p'(1)e+O(e^2).

So p′(1)p'(1) multiplies the leading part of the error. A slope with magnitude below 1 shrinks nearby errors; choosing p′(1)=0p'(1)=0 removes that linear term altogether. For the cubic p(s)=as+bs3p(s)=as+bs^3 that our matrix multiplication can compute, the two requirements are

p(1)=1keeps the target fixed,p′(1)=0removes the linear error.\begin{aligned} p(1)=1&\quad\text{keeps the target fixed},\\[10pt] p'(1)=0&\quad\text{removes the linear error}. \end{aligned}

Substituting the cubic and its derivative gives

a+b=1,a+3b=0.\begin{aligned} a+b&=1,\\[8pt] a+3b&=0. \end{aligned}

Subtract the first equation from the second: 2b=−12b=-1. So b=−1/2b=-1/2 and a=3/2a=3/2. Putting these coefficients back into the scalar rule and its matrix counterpart gives the classical Newton–Schulz iteration:

p(s)=32s−12s3,Xk+1=32Xk−12XkXk⊤Xk.\begin{aligned} p(s)&=\tfrac32s-\tfrac12s^3,\\[10pt] X_{k+1}&=\tfrac32X_k-\tfrac12X_kX_k^\top X_k. \end{aligned}

Normalize a nonzero momentum matrix with X0=M/∥M∥FX_0=M/\|M\|_F. Since ∥M∥op≤∥M∥F\|M\|_{\mathrm{op}}\leq\|M\|_F, its singular values start in [0,1][0,1]. Repeated cubic steps bring each positive one toward 1 and keep zero at zero. Consequently, the middle factor approaches IrI_r, and XkX_k approaches Q=UrVr⊤Q=U_rV_r^\top. The scalar fixed point gives us the matrix direction we wanted. Zero stretches stay zero, so this also preserves the active subspace when the original matrix has missing directions.

Why do the iterates actually reach one?

For 0<s<10<s<1,

p(s)−s=12s(1−s2)>0,1−p(s)=12(1−s)2(s+2)≥0.\begin{aligned} p(s)-s&=\tfrac12s(1-s^2)>0,\\[10pt] 1-p(s)&=\tfrac12(1-s)^2(s+2)\geq0. \end{aligned}

The value increases without crossing 1. Its limit must be a fixed point; the only positive one in this interval is 1. The squared error factor gives quadratic convergence near 1. Very small starting values can still need many iterations to get close.

More generally, ∣p′(1)∣<1|p'(1)|<1 gives local attraction. Setting it to zero improves the local error reduction; it does not, on its own, prove that arbitrary starting values converge. That is why we checked the interval.

§Get a useful approximation in five steps

We now have an iteration that converges, but a tiny initial stretch can take many steps to get close to 1. Every training step contains this inner matrix calculation. We need to stop it somewhere, or we will spend the training budget polishing one update. The original Muon experiments found five inner steps sufficient for the models tested. Five is a compute-budget choice, not a convergence guarantee. They use this tuned quintic:

q(s)=3.4445s−4.7750s3+2.0315s5.q(s)=3.4445s-4.7750s^3+2.0315s^5.

Its slope at zero is 3.44453.4445, versus the cubic's 1.51.5, so small values initially grow faster. There is a catch: q(1)=0.701q(1)=0.701. One is not even a fixed point.

The bargain is fast early progress toward unit-sized stretches, with some error left over. Running this same quintic forever does not finish the job; it can keep oscillating. Our two cubic conditions do not derive these tuned coefficients. The Newton–Schulz notes explain this choice and the resulting oscillation.

Substitute each value into the cubic and quintic, then return the result to the graphStarting at 0.1, iteration 12. The cubic receives 1.000000 and returns 1.000000. The quintic receives 0.703558 and returns 1.110680. Both substitutions and their numerical terms are shown beside the trace.Start 0.1 · iteration 12 of 12Return each result to the graph1value sₖ0512 · k5-step budgetcubicquinticBand: 0.7 ≤ s ≤ 1.3quintic: in band at step 2cubic: in band at step 6Past 5: the quintic oscillates.
p(s)=1.5s−0.5s3p(s)=1.5s-0.5s^3
s11=s_{11}=
1.0000
≈1.5(1.0000)−0.5(1.0000)3\approx1.5(1.0000)-0.5(1.0000)^{3}
≈1.50000−0.50000\approx1.50000-0.50000
s12≈s_{12}\approx
1.0000
q(s)=3.4445s−4.775s3+2.0315s5q(s)=3.4445s-4.775s^3+2.0315s^5
s11=s_{11}=
0.7036
≈3.4445(0.7036)−4.775(0.7036)3\approx3.4445(0.7036)-4.775(0.7036)^{3}
+2.0315(0.7036)5+2.0315(0.7036)^{5}
≈2.42340−1.66292+0.35020\approx2.42340-1.66292+0.35020
s12≈s_{12}\approx
1.1107Substitutions are rounded; the recurrence uses full precision.
Starting at 0.1, the quintic first enters the shaded 0.7–1.3 band at step 2; the cubic takes 6. The band is an illustrative tolerance around 1. Rings mark the first arrival, and the vertical line marks the five-step compute budget. Each value travels into its polynomial and back to the graph. Keep going to see the difference between getting close early and converging. Try starts at 1 and 0 too. Arithmetic is displayed rounded and computed at full precision.
Substitute, calculate, and reuse each result.

A quintic q(s)=as+bs3+cs5q(s)=as+bs^3+cs^5 has three coefficients. We could impose q(1)=1q(1)=1 and q′(1)=0q'(1)=0, then use the remaining freedom to shape the interval. Muon's original coefficients make a different tradeoff. Polynomial design includes choosing the conditions, not only solving for the coefficients.

Show the matrix code and the practical optimizer details

Here is the five-step direction calculation in float32. The reference implementation uses bfloat16 for speed.

import torch

@torch.no_grad()
def muon_direction(M, steps=5, eps=1e-7):
    """Approximate polar direction for one finite 2D matrix."""
    assert M.ndim == 2
    X = M.float()
    transposed = X.shape[0] > X.shape[1]
    if transposed:
        X = X.T
    X = X / (X.norm() + eps)  # Frobenius norm; zero stays zero.
    a, b, c = 3.4445, -4.7750, 2.0315
    for _ in range(steps):
        A = X @ X.T
        X = a * X + (b * A + c * (A @ A)) @ X
    return X.T if transposed else X

Transposing a tall matrix makes XX⊤XX^\top smaller and cheaper to multiply; the return restores its shape. The quintic uses the same SVD algebra, adding a Σr5\Sigma_r^5 term.

The reference optimizer also uses a Nesterov-style blend Ht=(1−β)Gt+βMtH_t=(1-\beta)G_t+\beta M_t by default, applies matrix-shape scaling, and supports decoupled weight decay. The finite quintic output need not have spectral norm exactly 1.

The reference setup uses Muon for hidden weight matrices and another optimizer, such as AdamW, for embeddings, the output layer, and scalar or vector parameters. The function above computes the direction, not the full training recipe.

§Put the pieces together: one Muon step

We have enough parts. Time to assemble the optimizer.

At training step tt, backpropagation gives the gradient GtG_t. Blend it into the momentum, normalize that matrix, and run five polynomial steps. Then scale the result by the learning rate and subtract it from the weights:

Mt=βMt−1+(1−β)Gt,X0=Mt∥Mt∥F+ε,Ak=XkXk⊤,Xk+1=aXk+(bAk+cAk2)Xk,Wt+1=(1−ηtλ)Wt−ηtX5.\begin{aligned} M_t&=\beta M_{t-1}+(1-\beta)G_t,\\[10pt] X_0&=\frac{M_t}{\|M_t\|_F+\varepsilon},\\[10pt] A_k&=X_kX_k^\top,\\[10pt] X_{k+1}&=aX_k+(bA_k+cA_k^2)X_k,\\[10pt] W_{t+1}&=(1-\eta_t\lambda)W_t-\eta_tX_5. \end{aligned}

Run the recurrence for k=0,…,4k=0,\ldots,4, with a=3.4445a=3.4445, b=−4.7750b=-4.7750, and c=2.0315c=2.0315. The tiny ε\varepsilon prevents division by zero. The optional decay λ\lambda shrinks the old weights separately from the gradient calculation. Set it to zero to recover the update we derived.

This is a minimal Muon step. Practical variants can blend in the current gradient again with Nesterov momentum, scale the update for the matrix shape, or use different polynomial schedules. The reference implementation makes those choices explicit. The central operation stays the same: turn the momentum matrix into an approximate polar direction.

Follow one complete Muon matrix update from gradient to new weightsOperation 10: Add the step to the weights. Displayed matrix 0.9319, 0.5515, −0.2841, 0.9198. The unit circle is mapped by exactly this matrix. Dotted arrows show the exact SVD target, with its scale or weight addition applied at the final operations.10 / 10 · Add the step to the weightsOne matrix through the complete Muon stepSolid: images of v₁ and v₂Dotted: W − 0.1Q
Wt+1=Wt+DW_{t+1}=W_t+D
Wt=W_t=
1.0000.500−0.2501.000
Wt+1=W_{t+1}=
0.9320.551−0.2840.920stretches: 1.150, 0.882gap to dotted target: 0.0252
Q=UV⊤=[3/2−1/21/23/2]Q=UV^\top=\begin{bmatrix}\sqrt3/2&-1/2\\1/2&\sqrt3/2\end{bmatrix}
Exact SVD reference · gap measured with the Frobenius normUpdated weights need not be orthogonal.
Follow one matrix through memory, normalization, five quintic iterations, and the weight update. Solid arrows show what the current matrix does to the momentum’s two singular input directions v₁ and v₂. Dotted arrows show the exact target Qvᵢ = uᵢ, and the printed gap measures the remaining error. At the last two operations the reference becomes −0.1Q, then W − 0.1Q, so we compare like with like. The ellipse is the current image of the unit circle; the dashed circle has radius 1. Settings: β = 0.8, η = 0.1, λ = 0, no Nesterov blend.
Follow one matrix through the whole update.

Here Q=UV⊤Q=UV^\top is the exact polar factor of the momentum matrix, computed as a reference for the illustration. Muon computes X5X_5 instead. The solid arrows approach the dotted ones without landing exactly on them, because five quintic steps leave residual stretch.

Momentum chooses which matrix we trust. Normalization puts its stretches in range. The polynomial reshapes them, and the minus sign turns the result into a weight update. The updated weights themselves need not be orthogonal.

§Same gradients, different optimizers

We have assembled a complete Muon step. To see what its matrix transformation changes, give the same gradient sequence to three update rules.

SGD uses the gradient directly. Adam keeps moving averages of both gradients and their elementwise squares, then divides each coordinate by its own estimated scale. AdamW adds weight decay separately. Muon rescales the momentum matrix along its singular directions.

Let RtR_t denote the gradient-driven part before the learning rate and decay:

RtSGD=Gt,RtAdamW=m^t⊘(v^t+ε),RtMuon=X5≈polar⁡(Mt).\begin{aligned} R_t^{\mathrm{SGD}}&=G_t,\\[10pt] R_t^{\mathrm{AdamW}}&=\widehat m_t\oslash \left(\sqrt{\widehat v_t}+\varepsilon\right),\\[10pt] R_t^{\mathrm{Muon}}&=X_5\approx\operatorname{polar}(M_t). \end{aligned}

An entry is one number in the matrix, such as the number in row 1, column 2. The symbol ⊘\oslash means dividing each number by the number in the same position in another matrix. AdamW keeps a separate running scale for each position. For example, if two momentum entries are 4 and 1, and their estimated scales are 4 and 1, their contributions become 4/4=14/4=1 and 1/1=11/1=1. Those scales come from past squared gradients; this is an example of the arithmetic, not a fixed rule that all entries become 1.

That is entrywise scaling: rescaling each matrix entry separately. Muon instead uses the matrix's singular directions, which can involve several entries moving together. The next comparison asks whether that distinction changes the path through a loss function.

Compute SGD, AdamW and Muon updates from six identical gradientsSupplied gradient 6 of 6. Three computed steps. Different shapes.. SGD, AdamW and Muon compute different updates from the same gradient sequence. Ellipses use unit Frobenius norm for display; raw norms and matrix entries are shown.Gradient 6 / 6Three computed steps. Different shapes.
Gt=G_t=
1.0000.400−0.2001.300η = 0.1same inputsSGD
Rt=GtR_t=G_t
D =−0.100−0.0400.020−0.130‖D‖F = 0.170No momentum · no decay
AdamW
Rt=m^tv^t+εR_t={\widehat m_t\over\sqrt{\widehat v_t}+\varepsilon}
D =−0.0270.015−0.023−0.103‖D‖F = 0.110Momentum + decay λ = 0.1
Muon
Rt=X5R_t=X_5
D =−0.0750.008−0.015−0.102‖D‖F = 0.127Momentum + decay λ = 0.1
Shapes show D / ‖D‖F; the numbers show the actual step.Blue: image of (1, 0) · orange: image of (0, 1)
Replay the same six supplied gradient matrices through all three methods. Each panel shows the actual step D and its image of the unit circle. The images are divided by each step's Frobenius norm so their shapes fit the same scale; the unscaled norm is printed below. Watch the blue and orange basis arrows as well as the outline. This compares update rules, not training speed or final loss.
Same supplied gradients. Watch how each rule reshapes the update.

The first gradient has four positive entries. SGD keeps their relative sizes. AdamW's first bias-corrected gradient contribution is nearly the sign of each entry, so it is nearly rank one here. Muon's result keeps both matrix directions active. That difference comes from the rules, before we have trained anything.

The useful conclusion is that Muon gives weaker singular directions room to contribute instead of letting the strongest one dominate the update. That is what the rounder shape shows. It does not mean every step is smaller or gentler. These pictures normalize away the overall size; use the printed norms to compare size. The learning rate still decides how far we move, and a large rate can overshoot.

§Now give them a loss to minimize

The replay showed how the same gradients produce different steps. During training, those steps also change where the next gradient is evaluated. To see that feedback, give all three optimizers an actual loss and let each follow its own path.

Here is a quadratic loss with a narrow valley. In the two matrix directions shown below, it is

L(a,b)=12(40a2+b2).L(a,b)=\tfrac12(40a^2+b^2).

The bottom is at (0,0)(0,0). The loss curves forty times more sharply along aa than along bb. All three methods start at (1.5,1.5)(1.5,1.5), then recompute their gradients at their own new weights. Each curve is an actual optimization run.

Why compare two ways of writing this same valley? AdamW gives each matrix entry its own scale. We want to see whether its behavior changes when one useful movement is spread across several entries instead of stored in just one. Muon's rule uses singular directions, so this is a concrete way to compare what the two rules pay attention to.

Here aa measures movement across the steep side of the valley, and bb measures movement along its shallow side. The optimizer updates the entries of WW, not the letters aa and bb directly. We can encode those two coordinates in WW in either of these ways.

Separate entries. Put aa in the top-left entry and bb in the bottom-right entry. Hold a=1a=1 and increase only bb from 0 to 0.2:

[1000]⟶[1000.2].\begin{bmatrix}1&0\\0&0\end{bmatrix} \quad\longrightarrow\quad \begin{bmatrix}1&0\\0&0.2\end{bmatrix}.

Only the bottom-right number changes. AdamW's scale for that entry is based on gradients for the shallow movement alone.

Mixed entries. Store the same coordinates using W11=W22=(a+b)/2W_{11}=W_{22}=(a+b)/2 and W12=W21=(a−b)/2W_{12}=W_{21}=(a-b)/2. Make exactly the same change, a=1a=1 and b:0→0.2b:0\to0.2:

[0.50.50.50.5]⟶[0.60.40.40.6].\begin{bmatrix}0.5&0.5\\0.5&0.5\end{bmatrix} \quad\longrightarrow\quad \begin{bmatrix}0.6&0.4\\0.4&0.6\end{bmatrix}.

Now the shallow movement needs all four numbers to change: add 0.1 on the diagonal and subtract 0.1 off it. Increasing aa would instead add to all four entries. Each position therefore participates in both the steep and shallow movements. Its gradient history mixes their contributions, and AdamW's separate scale for that position cannot assign an independent scale to each movement.

The button Use separate entries for a and b switches from the second encoding to the first. It changes the formula for WW and reruns all three optimizers. It does not change L(a,b)L(a,b), so the bowl itself stays the same. Watch whether the optimizer follows the same path through that bowl. The formula and example matrix above the plots show the selected encoding; the matrices below show the actual weights during the run.

Both encodings start from a=b=1.5a=b=1.5, which gives W=1.5IW=1.5I in either case. That is why the starting weights look identical even though subsequent updates can differ. With the initial mixed encoding and learning rates, SGD bounces across the steep direction, AdamW makes slow progress along the shallow direction, and Muon gets farther down the valley in 80 steps. Use the switch to test how much of that result depends on the encoding.

Follow SGD, AdamW and Muon down a computed quadratic matrix lossActual matrix-loss optimization, step 80 of 80. The matrix basis mixes the two directions by 45 degrees. Current losses: SGD 0.0304436; AdamW 0.145701; Muon 0.0000156281. Every method recomputes its gradient from its own current weights.Training step 80 / 80Mixed matrix entries · basis rotated 45°How valley coordinates become matrix entries
W=12[a+ba−ba−ba+b]W=\frac12\begin{bmatrix}a+b&a-b\\a-b&a+b\end{bmatrix}
Same example point: a = 1, b = 00.5000.5000.5000.500The switch changes this map, not the valley axes.
L(a,b)=12(40a2+b2)L(a,b)=\tfrac12(40a^2+b^2)
-1-10011abstartminimum: L = 0Contours join points with equal loss.Loss · logarithmic scale10²110⁻⁴10⁻⁸04080training stepsSGD0.03044initial η = 0.04AdamW0.14570initial η = 0.08Muon1.56e−5initial η = 0.08Fresh gradients at every point.Try the basis and rate controls.λ = 0 · same rate schedule · different cost per stepThe same paths on the loss surfaceHeight = loss · drag to rotate · arrows also rotateminimumSGDAdamWMuonActual weight matrices at step 80SGD0.123−0.123−0.1230.123AdamW0.270−0.270−0.2700.270Muon−0.0030.0020.002−0.003
The left plot shows paths over equal-loss contours. The right plot tracks the actual loss on a logarithmic scale, so small values remain visible. Every point uses the same loss function shown above. Play the run, then try separating the two directions into different matrix entries or raising AdamW’s initial learning rate. Both changes can reverse the ranking. The third plot lifts these same paths onto the loss surface; height is the actual loss. Drag to rotate it, or use the arrow keys. Smooth motion connects computed training steps.
Follow three optimizers. Drag the surface to rotate.

Now press Use separate entries for a and b. The valley has the same shape, but its directions line up with the individual entries AdamW rescales. AdamW becomes much faster; SGD and Muon follow the same paths as before. Or keep the mixed basis and raise AdamW's learning rate to 0.320.32. It catches up and finishes below Muon. AdamW has declined our invitation to be deprecated.

Muon responds to the matrix's coupled directions, so rotating this example does not change its path in valley coordinates. That is a precise sense in which its update follows the matrix geometry. Calling it "more natural" is less useful than seeing which changes of coordinates leave its behavior intact.

There is only one minimum in this quadratic bowl. None of these paths demonstrates escaping a local minimum. At a point with zero gradient and no stored momentum, Muon also has no direction to follow. Momentum can carry an optimizer through a flat or turning region, but replacing singular values by one does not by itself guarantee escape.

The test answers the question we started with. Here, changing how the two movements are distributed across matrix entries changes AdamW's path, while SGD and Muon keep the same paths in the valley. AdamW scales those entries separately; Muon rescales the singular directions that combine them. The learning-rate control also shows that this difference does not establish a universal winner. We count training steps here; a Muon step does more work, so these curves do not measure wall-clock speed.

The matrix loss, gradients, and exact settings

The full problem has a 2×22\times2 weight matrix:

L(W)=12tr⁡(W⊤HW),G=HW,H=B[40001]B⊤.\begin{aligned} L(W)&=\tfrac12\operatorname{tr}(W^\top H W),\\[10pt] G&=HW,\\[10pt] H&=B\begin{bmatrix}40&0\\0&1\end{bmatrix}B^\top. \end{aligned}

The mixed basis uses B=12[1−111]B=\tfrac1{\sqrt2}\begin{bmatrix}1&-1\\1&1\end{bmatrix}; the aligned basis uses B=IB=I. All methods start at W0=1.5IW_0=1.5I. For these two choices of BB, their iterates stay in the slice W=Bdiag⁡(a,b)B⊤W=B\operatorname{diag}(a,b)B^\top. Substitution gives the two-dimensional loss plotted above. The code updates all four matrix entries, then reads off aa and bb.

SGD has no momentum and starts with learning rate 0.040.04. AdamW starts at 0.080.08 or 0.320.32, with β1=0.8\beta_1=0.8, β2=0.9\beta_2=0.9 and ε=10−8\varepsilon=10^{-8}. Muon starts at 0.080.08, with β=0.8\beta=0.8, five quintic iterations and ε=10−7\varepsilon=10^{-7}, without Nesterov or a shape multiplier. Every method has zero weight decay, so AdamW is equivalent to Adam in this example. Momentum histories start at zero; AdamW applies bias correction.

Each method scales its initial rate by the same schedule over 80 training steps:

ηt=η0[0.1+0.45(1+cos⁡πt80)].\eta_t=\eta_0\left[0.1+0.45\left(1+\cos\frac{\pi t}{80}\right)\right].

Here t=0,…,79t=0,\ldots,79. These settings make the geometric effect visible. The basis and learning-rate controls let us test how fragile any claim about the winner would be. The five inner polynomial iterations belong to each Muon training step; they are a different count from the 80 steps on the horizontal axis.

That is the optimizer we set out to invent. Use the local loss prediction to choose a direction, decide what "small" means for a matrix, remember recent gradients, and approximate the resulting polar transform cheaply. The remaining question is how well those choices work on the model we actually want to train.

§Where these choices came from

We reconstructed the update by choosing a loss prediction, an output-change budget, a gradient average, and a short polynomial calculation. That is a route to understanding the method. For its historical development and experimental results:

If I had to rebuild the argument, I'd start with DxDx: how much should one update be allowed to change the layer's output?

Sign in with GitHub and say something. Threads live in this repository's Discussions, so you keep your words and I keep no database.