Chapter 8 · Section 8.6 · 65 min

Optimizers for large models

Adam was published in 2014, and a decade later it, or its decoupled-weight-decay sibling AdamW, still trains most frontier models. That is remarkable for an algorithm designed when a large network had tens of millions of parameters. It also has a cost nobody worried about at the time. For every parameter, Adam keeps two extra numbers, so a 405-billion-parameter model needs about 3.2 terabytes just for Adam's state in float32, before counting the weights, the gradients, or a single activation.

Two things changed recently. Large runs exposed the parts of Adam that do not scale gracefully (its ϵ\epsilon, its β2\beta_2, how weight decay interacts with the learning rate), and a new optimizer, Muon, which replaces Adam's per-coordinate scaling with an orthogonalized matrix update, set speed records and was then scaled to a 16-billion-parameter model with about half the compute of AdamW. This section covers what Adam costs, how to run AdamW correctly at scale, the memory-saving alternatives (Adafactor, Lion), the matrix-preconditioning family (Shampoo, SOAP), and Muon, whose core, the Newton-Schulz iteration, we derive from scratch.

We build on §0.6, which derived Adam with its bias correction, implemented AdamW's decoupled weight decay, showed that weights forget on a timescale of 1/(ηλ)1/(\eta\lambda) steps, and explained why large runs use β2=0.95\beta_2 = 0.95. Here we take those as known and ask what changes at scale, and we use the number formats of §8.5 to count the bytes. Throughout, θ\theta is the parameters, gt\mathbf{g}_t the gradient at step tt, η\eta the learning rate, λ\lambda the weight decay, mt\mathbf{m}_t and vt\mathbf{v}_t Adam's first and second moments with decay rates β1\beta_1 and β2\beta_2, and ϵ\epsilon Adam's small constant.

What Adam costs in memory

§0.6 counted 16 bytes per parameter with everything in FP32 (weights, gradients, and two moments, 4 bytes each). Mixed precision changes the split but not the total, because the 16-bit working copy of the weights comes with an FP32 master copy (§8.5 explains why each copy has the precision it has). ZeRO's accounting (Rajbhandari et al. 2020) is the one everyone quotes.

whatprecisionbytes per parameter
weights used in forward and backwardbf16 or fp162
gradientsbf16 or fp162
master weightsfp324
Adam first moment m\mathbf{m}fp324
Adam second moment v\mathbf{v}fp324
total16

In plain words, Adam's two moments are half of the total, and the optimizer side (master weights plus moments) is three quarters. For a 7B model that is 112 GB, more than one 80 GB GPU, and for LLaMA 3 405B it is about 6.5 TB, the memory of 81 H100s before a single activation is stored. This is why distributed training shards optimizer state first (§10.3), and why every optimizer in this section is judged partly by its state.

optimizerstate per parameternotes
SGD0rarely used for Transformers
SGD with momentum1 number
Adam, AdamW2 numbersthe default
8-bit Adam2 numbers, 1 byte eachblock-wise quantized moments (Dettmers et al. 2022)
Adafactorabout (n+m)/(nm)(n + m)/(nm) numbers for an n×mn\times m matrixfactored second moment, no first moment
Lion1 numbermomentum only
Shampoon2+m2n^2 + m^2 numbers per n×mn\times m matrix, plus their inverse rootsexpensive, amortized
Muon1 numbermomentum only, AdamW for non-matrix parameters
Warm-up

Optimizer state memory

Write optimizer_state_numbers(shapes, optimizer) that returns the number of optimizer-state values (not bytes) for a model whose parameter tensors have the given shapes. Use these rules. "sgd" keeps nothing. "momentum" and "lion" keep one value per parameter. "adam" keeps two. "adafactor" keeps, for a 2-D tensor of shape (n, m), one row vector and one column vector, n + m values, and for any other tensor a full second moment, one value per parameter (no momentum). "muon" keeps one momentum value per parameter of every 2-D tensor and, for all other tensors, Adam's two values per parameter. Raise ValueError for an unknown name.

Hidden tests, hints, and AI help, from $1.99 a month.

AdamW at scale

AdamW's update, from §0.6, is

θt=θt−1−ηλ θt−1−η m^tv^t+ϵ,\theta_t = \theta_{t-1} - \eta\lambda\,\theta_{t-1} - \eta\,\frac{\hat{\mathbf{m}}_t}{\sqrt{\hat{\mathbf{v}}_t} + \epsilon},

with the decay applied directly to the weights rather than added to the gradient (Loshchilov & Hutter 2019). Three of its details behave differently at scale than in a textbook, and each one has caused real failures.

Weight decay sets an equilibrium, and a timescale

Why decay weights at all in a model that sees each token once and cannot overfit in the classical sense? Not mainly as a regularizer. With normalization layers, the function a layer computes barely depends on the scale of its weights (§5.2), so what weight decay really controls is the effective learning rate, the size of each update relative to the size of the weights.

Here is the derivation. Adam's normalized update ut=m^t/(v^t+ϵ)\mathbf{u}_t = \hat{\mathbf{m}}_t/(\sqrt{\hat{\mathbf{v}}_t}+\epsilon) has a roughly constant size, say ∥u∥2≈Pρ2\lVert\mathbf{u}\rVert^2 \approx P\rho^2 for a tensor with PP entries whose update has root-mean-square ρ\rho (typically 0.2 to 0.4 for Adam in practice). In high dimensions the update is nearly orthogonal to the current weights, so

∥θt∥2≈(1−ηλ)2∥θt−1∥2+η2Pρ2.\lVert\theta_t\rVert^2 \approx (1 - \eta\lambda)^2\lVert\theta_{t-1}\rVert^2 + \eta^2 P\rho^2 .

Setting ∥θt∥=∥θt−1∥\lVert\theta_t\rVert = \lVert\theta_{t-1}\rVert and using (1−ηλ)2≈1−2ηλ(1-\eta\lambda)^2 \approx 1 - 2\eta\lambda for small ηλ\eta\lambda gives the equilibrium

∥θ∥eq2≈ηPρ22λ,RMS(θ)RMS(ηu)≈12ηλ.\lVert\theta\rVert^2_{\text{eq}} \approx \frac{\eta P\rho^2}{2\lambda}, \qquad \frac{\text{RMS}(\theta)}{\text{RMS}(\eta\mathbf{u})} \approx \frac{1}{\sqrt{2\eta\lambda}} .

In plain words, the weights grow or shrink until each step changes them by a relative amount of about 2ηλ\sqrt{2\eta\lambda}. They get there on the timescale of 1/(ηλ)1/(\eta\lambda) steps that §0.6 found by unrolling the update, the same timescale on which they forget their initialization, about 33,000 steps for η=3×10−4\eta = 3\times10^{-4} and λ=0.1\lambda = 0.1. Wang and Aitchison (2024) argue that this timescale, as a fraction of the run, is the quantity to hold fixed when changing model or dataset size, which tells you how λ\lambda should move when η\eta does.

The derivation also explains two practical choices. PyTorch's AdamW multiplies the decay by the learning rate, θ←θ(1−ηλ)\theta \leftarrow \theta(1 - \eta\lambda), so when the schedule lowers η\eta, decay weakens too, and the two are coupled when you tune them. Wortsman et al. (2023) found that independent weight decay, θ←θ(1−λ′)\theta \leftarrow \theta(1 - \lambda') with λ′\lambda' not multiplied by η\eta, made the final loss less sensitive to the learning rate in their small-scale stability study. And it explains what not to decay. Gains of normalization layers and biases have no free scale to control, so decaying them only drags them away from useful values. OLMo 2 also stopped decaying the embedding matrix, as one of a set of changes made for training stability (Team OLMo 2024).

Python · cell 1

Both runs end at the predicted RMS of about 0.031, one growing from a tiny initialization and one shrinking from a large one, and both get most of the way there within a few multiples of the timescale 1/(ηλ)≈3,3331/(\eta\lambda) \approx 3{,}333 steps. The equilibrium does not care where you started, only about η\eta, λ\lambda, and the size of the updates. (We used a learning rate ten times larger than a typical LLM's so the cell runs quickly. At η=3×10−4\eta = 3\times10^{-4} the timescale is 33,000 steps.)

Beta2 and the sharper failure at scale

§0.6 explained why large runs use β2=0.95\beta_2 = 0.95 (GPT-3, LLaMA, OLMo, DeepSeek) rather than Adam's default 0.999. The second moment then averages over about 20 steps instead of 1,000, so when gradients suddenly grow, the denominator v^\sqrt{\hat{\mathbf{v}}} catches up in tens of steps rather than hundreds, and the update cannot stay far above its usual size for long. At scale there is a second failure that a short memory alone does not prevent.

Molybog et al. (2023) described it in runs from 7B to 546B parameters. Some layers, typically early ones, have gradients smaller than ϵ\epsilon for long stretches, so their updates are tiny and their gradients become strongly correlated from step to step. When a rare batch pushes those gradients above ϵ\epsilon, the updates jump to roughly sign-sized values all at once, in a coordinated direction, and the loss spikes. Their list of remedies reads like a stability checklist (§8.8). Skip the batch, lower the learning rate, lower ϵ\epsilon, reduce the batch size, or adjust β1\beta_1 and β2\beta_2.

Why epsilon matters at scale

ϵ\epsilon exists to avoid dividing by zero. §0.6 noted one reason to make it larger, as Llama 2 did with 10−510^{-5}. It puts a floor under the denominator, so a parameter whose gradient is tiny and noisy cannot take full-size steps on noise. At scale the opposite danger takes over. The default, 10−810^{-8}, was chosen when per-coordinate gradients were much larger than that. In a large model trained with a large batch, the root-mean-square gradient of a single weight can fall to around 10−810^{-8}, and then ϵ\epsilon is no longer negligible. Per coordinate, the update is

m^v^+ϵ≈m^v^⋅11+ϵ/v^,\frac{\hat m}{\sqrt{\hat v} + \epsilon} \approx \frac{\hat m}{\sqrt{\hat v}}\cdot\frac{1}{1 + \epsilon/\sqrt{\hat v}},

so when v^≈ϵ\sqrt{\hat v} \approx \epsilon, every update is halved, and for smaller gradients Adam turns into SGD with learning rate η/ϵ\eta/\epsilon, whose steps are tiny because the gradients themselves are, and which no longer adapts to each coordinate's scale. Wortsman et al. (2023) observed exactly this, with gradient RMS approaching 10−810^{-8} at scale and updates shrinking. Lowering ϵ\epsilon to 10−1510^{-15} improved their 4.8B model, while raising it to 10−610^{-6} made it diverge. OLMo 2 lowered ϵ\epsilon from 10−510^{-5} to 10−810^{-8} for the same reason. The right ϵ\epsilon depends on the gradient scale, which shrinks with batch size and model size, so a value inherited from a smaller run is worth checking against the logged gradient RMS.

Python · cell 2

You implemented AdamW in Exercise 0.6d, and §0.6's PyTorch block puts matrices and 1-D tensors in separate parameter groups so only the matrices are decayed. At scale the same code carries three extra decisions, all from this section. Decide whether the decay is multiplied by the learning rate (PyTorch's default) or independent of it. Add the embedding to the no-decay group if you follow OLMo 2. And set ϵ\epsilon from the gradient RMS you actually observe, not from a default.

Adafactor, a second moment in factored form

Adafactor (Shazeer & Stern 2018) attacks Adam's memory directly. For a weight matrix W∈Rn×m\mathbf{W} \in \R^{n\times m} with gradient G\mathbf{G}, Adam stores a full n×mn\times m matrix V\mathbf{V} of second moments. Adafactor stores only its row sums r∈Rn\mathbf{r} \in \R^n and column sums c∈Rm\mathbf{c} \in \R^m, each kept as an exponential moving average of the corresponding sums of G2\mathbf{G}^2 (plus a tiny ϵ1=10−30\epsilon_1 = 10^{-30}), and reconstructs

V^=rc⊤1⊤r,V^ij=ri cj∑krk.\hat{\mathbf{V}} = \frac{\mathbf{r}\mathbf{c}^\top}{\mathbf{1}^\top\mathbf{r}}, \qquad \hat V_{ij} = \frac{r_i\,c_j}{\sum_k r_k}.

In plain words, guess each entry's second moment from how large its row is and how large its column is, which costs n+mn + m numbers instead of nmnm. For a 4096×40964096\times 4096 matrix, that is 8,192 numbers instead of 16.8 million.

Why this particular rank-one guess? Because it is the best one under the right notion of error.

∂ The factored estimate is the rank-one fit that minimizes the I-divergence

Second moments are positive, so a natural error between V\mathbf{V} and a positive approximation ab⊤\mathbf{a}\mathbf{b}^\top is the generalized KL divergence (I-divergence),

D(V ∥ ab⊤)=∑i,j[Vijlog⁡Vijaibj−Vij+aibj].D(\mathbf{V}\,\|\,\mathbf{a}\mathbf{b}^\top) = \sum_{i,j}\Big[V_{ij}\log\frac{V_{ij}}{a_ib_j} - V_{ij} + a_ib_j\Big].

Setting the derivative with respect to aia_i to zero,

∂D∂ai=∑j(−Vijai+bj)=0⟹ai=∑jVij∑jbj=ri1⊤b,\frac{\partial D}{\partial a_i} = \sum_j\Big(-\frac{V_{ij}}{a_i} + b_j\Big) = 0 \quad\Longrightarrow\quad a_i = \frac{\sum_j V_{ij}}{\sum_j b_j} = \frac{r_i}{\mathbf{1}^\top\mathbf{b}},

and symmetrically bj=cj/(1⊤a)b_j = c_j/(\mathbf{1}^\top\mathbf{a}), with rir_i and cjc_j the row and column sums of V\mathbf{V}. Summing the first equation over ii gives (1⊤a)(1⊤b)=∑ijVij=1⊤r(\mathbf{1}^\top\mathbf{a})(\mathbf{1}^\top\mathbf{b}) = \sum_{ij}V_{ij} = \mathbf{1}^\top\mathbf{r}. Then

aibj=ri1⊤b⋅cj1⊤a=ricj1⊤r,a_ib_j = \frac{r_i}{\mathbf{1}^\top\mathbf{b}}\cdot\frac{c_j}{\mathbf{1}^\top\mathbf{a}} = \frac{r_ic_j}{\mathbf{1}^\top\mathbf{r}},

which is exactly Adafactor's V^\hat{\mathbf{V}}. The objective is convex in log⁡a\log\mathbf{a} and log⁡b\log\mathbf{b}, so this stationary point is the minimum. When V\mathbf{V} really is rank one, the estimate is exact.

Adafactor made three more changes, each to save memory or add stability. It drops the first moment altogether (β1=0\beta_1 = 0), saving another full copy. It clips each update to a root-mean-square of at most d=1d = 1, U^=U/max⁡(1,RMS⁡(U)/d)\hat{\mathbf{U}} = \mathbf{U}/\max(1, \operatorname{RMS}(\mathbf{U})/d), which stops the large steps that a stale second moment would otherwise produce (the same failure as β2=0.999\beta_2 = 0.999 above). And it uses an increasing decay rate, β^2,t=1−t−0.8\hat\beta_{2,t} = 1 - t^{-0.8}, so the average covers more steps as training goes on, and a relative step size, the learning rate times the RMS of the parameter itself. T5 was trained with Adafactor, and PaLM used Adafactor without factorization, keeping its β2\beta_2 schedule and parameter-scaled steps (Chowdhery et al. 2022).

Core

Adafactor's factored second moment

Implement adafactor_step(G, r, c, beta2, eps1=1e-30, d=1.0) for one matrix gradient G of shape (n, m) with row state r of shape (n,) and column state c of shape (m,). Update r = beta2 * r + (1 - beta2) * (G**2 + eps1).sum(axis=1) and c likewise with axis=0, form V^=rc⊤/∑iri\hat{\mathbf{V}} = \mathbf{r}\mathbf{c}^\top/\sum_i r_i, compute the update U=G/V^\mathbf{U} = \mathbf{G}/\sqrt{\hat{\mathbf{V}}}, clip it to U^=U/max⁡(1,RMS⁡(U)/d)\hat{\mathbf{U}} = \mathbf{U}/\max(1, \operatorname{RMS}(\mathbf{U})/d), and return (U_hat, r, c). Do not modify the inputs.

Hidden tests, hints, and AI help, from $1.99 a month.

Lion (Chen et al. 2023) was discovered by a symbolic search over programs, not designed by hand, and it is strikingly simple. With β1=0.9\beta_1 = 0.9 and β2=0.99\beta_2 = 0.99,

ct=β1mt−1+(1−β1)gt,θt=θt−1−η(sign⁡(ct)+λθt−1),mt=β2mt−1+(1−β2)gt.\mathbf{c}_t = \beta_1\mathbf{m}_{t-1} + (1-\beta_1)\mathbf{g}_t, \qquad \theta_t = \theta_{t-1} - \eta\big(\operatorname{sign}(\mathbf{c}_t) + \lambda\theta_{t-1}\big), \qquad \mathbf{m}_t = \beta_2\mathbf{m}_{t-1} + (1-\beta_2)\mathbf{g}_t .

In plain words, take the sign of a blend of the momentum and the current gradient, so every coordinate moves by exactly η\eta, then update the momentum with a slower average. Two different betas mean that the direction used now leans more on the current gradient than the momentum that is carried forward does.

Lion keeps only one state tensor instead of Adam's two, which saves 4 of the 16 bytes per parameter of mixed-precision training. Because every coordinate of a sign update has magnitude 1, while Adam's update has an RMS around 0.2 to 0.4, Lion needs a learning rate 3 to 10 times smaller than AdamW's and a weight decay 3 to 10 times larger to keep ηλ\eta\lambda similar. The paper reported up to 5 times less pretraining compute for vision Transformers on JFT and solid results on language modeling. Sign-based updates are also cheap to communicate across GPUs, since one bit per coordinate carries the direction.

Core

One Lion step

Implement lion_step(theta, g, m, lr, beta1=0.9, beta2=0.99, wd=0.0) returning (new_theta, new_m) exactly as in the equations above. Use np.sign, so a coordinate whose blend is exactly zero does not move except for weight decay.

Hidden tests, hints, and AI help, from $1.99 a month.

Shampoo and SOAP, preconditioning with matrices

Adam rescales each coordinate separately, which is a diagonal preconditioner. It ignores correlations between coordinates. The ideal of full-matrix AdaGrad, which would precondition with the inverse square root of the full P×PP\times P matrix of gradient outer products, is hopeless for PP in the billions. Shampoo (Gupta et al. 2018) exploits the matrix structure of each layer instead. For a weight W∈Rm×n\mathbf{W} \in \R^{m\times n} with gradient Gt\mathbf{G}_t, it accumulates a left and a right statistic and preconditions on both sides,

Lt=Lt−1+GtGt⊤∈Rm×m,Rt=Rt−1+Gt⊤Gt∈Rn×n,Wt=Wt−1−η Lt−1/4GtRt−1/4.\mathbf{L}_t = \mathbf{L}_{t-1} + \mathbf{G}_t\mathbf{G}_t^\top \in \R^{m\times m}, \qquad \mathbf{R}_t = \mathbf{R}_{t-1} + \mathbf{G}_t^\top\mathbf{G}_t \in \R^{n\times n}, \qquad \mathbf{W}_t = \mathbf{W}_{t-1} - \eta\,\mathbf{L}_t^{-1/4}\mathbf{G}_t\mathbf{R}_t^{-1/4} .

In plain words, whiten the gradient along its rows with one small matrix and along its columns with another. The Kronecker product L1/2⊗R1/2\mathbf{L}^{1/2}\otimes\mathbf{R}^{1/2} approximates the full-matrix statistic with m2+n2m^2 + n^2 numbers instead of m2n2m^2n^2.

There is a beautiful special case that connects Shampoo to the next optimizer. Drop the accumulation and use only the current gradient, with SVD G=UΣV⊤\mathbf{G} = \mathbf{U}\boldsymbol\Sigma\mathbf{V}^\top. Then L=UΣ2U⊤\mathbf{L} = \mathbf{U}\boldsymbol\Sigma^2\mathbf{U}^\top, R=VΣ2V⊤\mathbf{R} = \mathbf{V}\boldsymbol\Sigma^2\mathbf{V}^\top, and

L−1/4GR−1/4=UΣ−1/2U⊤ UΣV⊤ VΣ−1/2V⊤=UV⊤.\mathbf{L}^{-1/4}\mathbf{G}\mathbf{R}^{-1/4} = \mathbf{U}\boldsymbol\Sigma^{-1/2}\mathbf{U}^\top\,\mathbf{U}\boldsymbol\Sigma\mathbf{V}^\top\,\mathbf{V}\boldsymbol\Sigma^{-1/2}\mathbf{V}^\top = \mathbf{U}\mathbf{V}^\top .

In plain words, Shampoo without memory replaces the gradient by its orthogonal part, keeping its singular vectors and setting every singular value to 1. Hold on to that, because it is exactly what Muon computes.

Shampoo's practical problems are cost and numerics. Inverse fourth roots of large matrices need an eigendecomposition, which is slow and unstable in low precision, so implementations recompute them only every few dozen or hundred steps and run them in float32 on the side. A distributed implementation of Shampoo won the external-tuning track of the AlgoPerf training-algorithms benchmark in 2024. SOAP (Vyas et al. 2024) simplified the idea further. Rotate the gradient into the eigenbasis of Shampoo's L\mathbf{L} and R\mathbf{R}, run ordinary AdamW there, and rotate back, refreshing the eigenbasis only every 10 steps or so. At 360M and 660M parameters with a 2-million-token batch, SOAP needed over 40 percent fewer iterations and over 35 percent less wall-clock time than AdamW.

Muon, orthogonalized updates

Muon (Jordan et al. 2024, "MomentUm Orthogonalized by Newton-Schulz") applies to each 2-D weight matrix of the hidden layers. It keeps a momentum buffer, as SGD with momentum would, and replaces the update by its orthogonal part UV⊤\mathbf{U}\mathbf{V}^\top before applying it. Embeddings, the output head, and all 1-D parameters (gains, biases) are trained with AdamW as usual.

Why orthogonalize

There are two answers, one empirical and one geometric.

The empirical one comes from looking at updates. Jordan observed that the updates SGD-momentum and Adam produce for Transformer weight matrices have very high condition numbers. They are close to low rank, dominated by a few directions, so a few directions in weight space get almost all of the learning while many others, possibly important, get almost none. Orthogonalizing sets every singular value to 1 and gives the rare directions the same step size as the dominant ones.

The geometric answer explains why this is a principled choice. Gradient descent is steepest descent under the Euclidean norm, the direction that decreases the loss fastest per unit of step length. Change the norm and you change the algorithm. For a linear layer, the natural norm on an update ΔW\Delta\mathbf{W} is the spectral norm ∥ΔW∥op\lVert\Delta\mathbf{W}\rVert_{\text{op}}, its largest singular value, because ∥ΔWx∥≤∥ΔW∥op∥x∥\lVert\Delta\mathbf{W}\mathbf{x}\rVert \le \lVert\Delta\mathbf{W}\rVert_{\text{op}}\lVert\mathbf{x}\rVert bounds how much the layer's output can change for any input (Bernstein & Newhouse 2024). So ask for the step that decreases the linearized loss the most while changing no output by more than η\eta times its input,

min⁡ΔW  ⟨G,ΔW⟩subject to∥ΔW∥op≤η,\min_{\Delta\mathbf{W}}\;\langle\mathbf{G}, \Delta\mathbf{W}\rangle \quad\text{subject to}\quad \lVert\Delta\mathbf{W}\rVert_{\text{op}} \le \eta,

where ⟨G,ΔW⟩=tr⁡(G⊤ΔW)\langle\mathbf{G}, \Delta\mathbf{W}\rangle = \operatorname{tr}(\mathbf{G}^\top\Delta\mathbf{W}) is the first-order change in the loss. Write G=∑iσiuivi⊤\mathbf{G} = \sum_i \sigma_i\mathbf{u}_i\mathbf{v}_i^\top. Then

⟨G,ΔW⟩=∑iσi ui⊤ΔW vi  ≥  −∑iσi ∥ΔW∥op  ≥  −η∑iσi,\langle\mathbf{G}, \Delta\mathbf{W}\rangle = \sum_i\sigma_i\,\mathbf{u}_i^\top\Delta\mathbf{W}\,\mathbf{v}_i \;\ge\; -\sum_i\sigma_i\,\lVert\Delta\mathbf{W}\rVert_{\text{op}} \;\ge\; -\eta\sum_i\sigma_i ,

because ∣u⊤Av∣≤∥A∥op|\mathbf{u}^\top\mathbf{A}\mathbf{v}| \le \lVert\mathbf{A}\rVert_{\text{op}} for unit vectors u\mathbf{u} and v\mathbf{v}. The bound is reached by ΔW=−η∑iuivi⊤=−η UV⊤\Delta\mathbf{W} = -\eta\sum_i\mathbf{u}_i\mathbf{v}_i^\top = -\eta\,\mathbf{U}\mathbf{V}^\top, since then ui⊤ΔWvi=−η\mathbf{u}_i^\top\Delta\mathbf{W}\mathbf{v}_i = -\eta for every ii, and ∥UV⊤∥op=1\lVert\mathbf{U}\mathbf{V}^\top\rVert_{\text{op}} = 1. In plain words, the steepest step under the spectral norm is the gradient with all its singular values set to the same size. Muon applies this to the momentum rather than the raw gradient.

Newton-Schulz, orthogonalizing with matrix products

Computing UV⊤\mathbf{U}\mathbf{V}^\top with an SVD every step is slow on a GPU. Newton-Schulz iterations compute it with nothing but matrix multiplications, which GPUs do best, and in bfloat16.

The key fact is that odd matrix polynomials act on singular values independently. If X=UΣV⊤\mathbf{X} = \mathbf{U}\boldsymbol\Sigma\mathbf{V}^\top, then XX⊤X=UΣV⊤VΣU⊤UΣV⊤=UΣ3V⊤\mathbf{X}\mathbf{X}^\top\mathbf{X} = \mathbf{U}\boldsymbol\Sigma\mathbf{V}^\top\mathbf{V}\boldsymbol\Sigma\mathbf{U}^\top\mathbf{U}\boldsymbol\Sigma\mathbf{V}^\top = \mathbf{U}\boldsymbol\Sigma^3\mathbf{V}^\top, and in general (XX⊤)kX=UΣ2k+1V⊤(\mathbf{X}\mathbf{X}^\top)^k\mathbf{X} = \mathbf{U}\boldsymbol\Sigma^{2k+1}\mathbf{V}^\top. So the iteration

X←aX+b(XX⊤)X+c(XX⊤)2X\mathbf{X} \leftarrow a\mathbf{X} + b(\mathbf{X}\mathbf{X}^\top)\mathbf{X} + c(\mathbf{X}\mathbf{X}^\top)^2\mathbf{X}

keeps U\mathbf{U} and V\mathbf{V} fixed and applies the scalar polynomial φ(σ)=aσ+bσ3+cσ5\varphi(\sigma) = a\sigma + b\sigma^3 + c\sigma^5 to every singular value at once. In plain words, to orthogonalize a matrix we only need a scalar polynomial that, iterated, sends every number in (0,1](0, 1] to 1.

The classic choice is the cubic φ(σ)=32σ−12σ3\varphi(\sigma) = \tfrac32\sigma - \tfrac12\sigma^3, the Newton-Schulz iteration for the polar decomposition. Its properties are easy to check.

  • 1 is a fixed point, φ(1)=32−12=1\varphi(1) = \tfrac32 - \tfrac12 = 1.
  • Convergence near 1 is quadratic. Writing σ=1−δ\sigma = 1 - \delta, φ(1−δ)=1−32δ2+12δ3\varphi(1 - \delta) = 1 - \tfrac32\delta^2 + \tfrac12\delta^3, so the error goes from δ\delta to about 1.5 δ21.5\,\delta^2.
  • Every σ\sigma in (0,1)(0, 1) increases toward 1, since φ(σ)/σ=32−12σ2>1\varphi(\sigma)/\sigma = \tfrac32 - \tfrac12\sigma^2 > 1 there, and φ\varphi maps (0,1)(0, 1) into (0,1)(0, 1). Values in (1,3)(1, \sqrt3) are mapped into (0,1)(0, 1) and then converge too, while values above 3\sqrt3 go negative and diverge.

To keep every singular value in the safe range, first divide by the Frobenius norm, X0=G/∥G∥F\mathbf{X}_0 = \mathbf{G}/\lVert\mathbf{G}\rVert_F. Since ∥G∥F2=∑iσi2≥σmax⁡2\lVert\mathbf{G}\rVert_F^2 = \sum_i\sigma_i^2 \ge \sigma_{\max}^2, all singular values of X0\mathbf{X}_0 are at most 1.

The cubic has one weakness. Near zero it only multiplies by φ′(0)=1.5\varphi'(0) = 1.5 per step, and normalizing by the Frobenius norm makes small singular values very small. A singular value of 10−310^{-3} needs about 20 cubic steps to reach 1. Jordan's insight was that Muon does not need exactly UV⊤\mathbf{U}\mathbf{V}^\top. He chose a quintic with a large slope at zero, (a,b,c)=(3.4445,−4.7750,2.0315)(a, b, c) = (3.4445, -4.7750, 2.0315), so φ′(0)=3.4445\varphi'(0) = 3.4445, and accepted that it does not converge to 1 but oscillates in a band around it. After 5 steps, every initial singular value from 10−210^{-2} to 1 lands between about 0.68 and 1.13, so the result is US′V⊤\mathbf{U}\mathbf{S}'\mathbf{V}^\top with S′\mathbf{S}' close to the identity, which works as well in practice.

Python · cell 3

This matrix has condition number 1,000, and after normalization its singular values span 0.0002 to 0.54. Five cubic steps barely move the small ones (only 21 percent reach the band from 0.68 to 1.2), and 25 cubic steps converge to UV⊤\mathbf{U}\mathbf{V}^\top exactly. Five quintic steps put 77 percent of the singular values in the band and lift the smallest from 0.0002 to about 0.1, a 500-fold boost, at a fifth of the cost of the 25 cubic steps. Explore the scalar maps below.

How do a few matrix products push every singular value toward 1?
Each Newton-Schulz step applies φ(σ) = aσ + bσ³ + cσ⁵ to every singular value. Left, where each starting singular value ends up after the chosen number of steps. Right, the map φ itself, with the path of one starting value.
Loading interactive figure…
Interview

Newton-Schulz orthogonalization

Implement newton_schulz(G, steps=5, coeffs=(3.4445, -4.7750, 2.0315), eps=1e-7) for a 2-D array G of any shape. Divide by the Frobenius norm plus eps. If G has more rows than columns, iterate on the transpose and transpose back, so the Gram matrix XX⊤\mathbf{X}\mathbf{X}^\top is the smaller one. Each step computes A=XX⊤\mathbf{A} = \mathbf{X}\mathbf{X}^\top and X←aX+(bA+cA2)X\mathbf{X} \leftarrow a\mathbf{X} + (b\mathbf{A} + c\mathbf{A}^2)\mathbf{X}. No SVD allowed.

Hidden tests, hints, and AI help, from $1.99 a month.

The full algorithm, and making it scale

Putting it together, Muon's step for a matrix W∈RA×B\mathbf{W} \in \R^{A\times B} with gradient G\mathbf{G} is

M←μM+(1−μ)G,N=(1−μ)G+μM,O=NS⁡5(N),W←W−η s O,\mathbf{M} \leftarrow \mu\mathbf{M} + (1 - \mu)\mathbf{G}, \qquad \mathbf{N} = (1 - \mu)\mathbf{G} + \mu\mathbf{M}, \qquad \mathbf{O} = \operatorname{NS}_5(\mathbf{N}), \qquad \mathbf{W} \leftarrow \mathbf{W} - \eta\,s\,\mathbf{O},

with momentum μ=0.95\mu = 0.95, Nesterov's look-ahead N\mathbf{N}, five Newton-Schulz steps, and a shape-dependent scale ss. Two scales are in use. Jordan's original sets s=max⁡(1,A/B)s = \sqrt{\max(1, A/B)}. The Moonshot team, scaling Muon up, found two changes crucial (Liu et al. 2025). They added decoupled weight decay, which plain Muon lacked, and chose ss so the update has the same root-mean-square as a typical AdamW update. Since UV⊤\mathbf{U}\mathbf{V}^\top for an A×BA\times B matrix has min⁡(A,B)\min(A, B) singular values equal to 1, its squared Frobenius norm is min⁡(A,B)\min(A, B) and

RMS⁡(UV⊤)=min⁡(A,B)AB=1max⁡(A,B)⟹s=0.2max⁡(A,B)\operatorname{RMS}(\mathbf{U}\mathbf{V}^\top) = \sqrt{\frac{\min(A, B)}{AB}} = \frac{1}{\sqrt{\max(A, B)}} \quad\Longrightarrow\quad s = 0.2\sqrt{\max(A, B)}

gives an RMS of 0.2, matching AdamW's typical 0.2 to 0.4. That lets Muon reuse AdamW's learning rate and weight decay, and the full update is W←W−η(0.2max⁡(A,B) O+λW)\mathbf{W} \leftarrow \mathbf{W} - \eta\big(0.2\sqrt{\max(A, B)}\,\mathbf{O} + \lambda\mathbf{W}\big). With these changes, Muon reached the same loss as AdamW with about 52 percent of the training FLOPs in their compute-optimal scaling experiments, and they trained Moonlight, a mixture-of-experts model with 3B active and 16B total parameters, on 5.7 trillion tokens with it.

What does Muon cost? Each Newton-Schulz step on an m×nm\times n matrix (m≤nm \le n) costs about 4m2n+2m34m^2n + 2m^3 FLOPs, at most 6m2n6m^2n, so five steps cost at most 30m2n30m^2n. The matrix's share of a training step with a batch of BB tokens (here BB is the batch size again, not the matrix dimension above) is 6mnB6mnB FLOPs (forward and backward, §5.6), so the overhead is at most

5⋅6m2n6mnB=5mB.\frac{5\cdot 6m^2n}{6mnB} = \frac{5m}{B}.

For a model of width m=4096m = 4096 trained with a batch of 4 million tokens, that is 0.5 percent. This is the formula Tm/BTm/B (with T=5T = 5 steps) from Jordan's write-up. The real costs are elsewhere. Newton-Schulz needs the whole matrix on one device, which clashes with sharded optimizers that split each matrix across GPUs (§10.3), and it adds latency even when its FLOPs are small. Moonshot's distributed implementation gathers each matrix, orthogonalizes it, and scatters the result.

Research

A Muon step with RMS matching and weight decay

Implement muon_step(W, G, M, lr, momentum=0.95, wd=0.1, nesterov=True) returning (new_W, new_M), following the equations above with the Moonshot scale s=0.2max⁡(A,B)s = 0.2\sqrt{\max(A, B)}. Update the buffer as M = momentum * M + (1 - momentum) * G. With nesterov=True the matrix to orthogonalize is (1 - momentum) * G + momentum * M (using the new M), otherwise it is M. Orthogonalize with the provided ns5, then apply decoupled decay and the scaled update, W * (1 - lr * wd) - lr * s * O.

Hidden tests, hints, and AI help, from $1.99 a month.

Choosing an optimizer

optimizerstatestrengthsweaknessesused by
AdamW2 per parameterrobust, well understood, default hyperparameters transfermemory, ϵ\epsilon and β2\beta_2 pitfalls at scalemost frontier models (LLaMA, OLMo, DeepSeek-V3)
Adafactorabout n+mn + m per matrixtiny state, update clippingneeds care (relative steps, clipping), slightly worse lossT5, PaLM (unfactored)
Lion1 per parametermemory, cheap sign updatesneeds retuned learning rate and decay, sensitive to batch sizesome vision and language runs
Shampoo, SOAPm2+n2m^2 + n^2 per matrix plus eigenbasesfewest steps to a target lossengineering, numerics, periodic expensive decompositionsAlgoPerf winner, research runs
Muon1 per matrix parameterabout 2 times the compute efficiency of AdamW in published scaling studies, cheapneeds AdamW for some parameters, whole matrices per device, newerspeedruns, Moonlight, Kimi K2

The honest summary for a practitioner is that AdamW with β2=0.95\beta_2 = 0.95, λ=0.1\lambda = 0.1 on matrices only, and a small enough ϵ\epsilon remains the safe default. Muon is the strongest new contender, with published wins at the 16B scale and adoption in at least one frontier-scale open model. Comparisons between optimizers are notoriously easy to get wrong (an untuned baseline, a different learning-rate schedule, a short run that favors one method), so treat any reported speedup as a claim about the tuning budget as well as the algorithm. Hyperparameters that transfer across model widths are their own topic (§9.5).

In PyTorch

PyTorch now ships Muon, so the real way is a few lines. Matrices of hidden layers go to Muon, everything else to AdamW, and we check one step of torch.optim.Muon against our NumPy Newton-Schulz.

🔥 In PyTorchfor a machine with PyTorch

The two agree to about the precision of bfloat16, which is the format PyTorch runs Newton-Schulz in. That the iteration is stable in bfloat16 at all is one of its practical virtues, since an SVD in bfloat16 would be useless.

Read more

Watch next

🎬 The Algorithm that Helps Machines Learn [AdamW] · Jia-Bin HuangA clean animated build-up from momentum and RMSProp to Adam and decoupled weight decay.
🎬 This Simple Optimizer Is Revolutionizing How We Train AI | Muon · Jia-Bin HuangMuon as orthogonalized momentum with Newton-Schulz, and how it was scaled to large language models.
🎬 Jeremy Bernstein - Depths of First Order Optimization · Cohere · starts at 37:15Bernstein builds the norm-based view that motivates Muon and connects it back to Adam and Shampoo.