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 , its , 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 steps, and explained why large runs use . 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, is the parameters, the gradient at step , the learning rate, the weight decay, and Adam's first and second moments with decay rates and , and 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.
| what | precision | bytes per parameter |
|---|---|---|
| weights used in forward and backward | bf16 or fp16 | 2 |
| gradients | bf16 or fp16 | 2 |
| master weights | fp32 | 4 |
| Adam first moment | fp32 | 4 |
| Adam second moment | fp32 | 4 |
| total | 16 |
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.
| optimizer | state per parameter | notes |
|---|---|---|
| SGD | 0 | rarely used for Transformers |
| SGD with momentum | 1 number | |
| Adam, AdamW | 2 numbers | the default |
| 8-bit Adam | 2 numbers, 1 byte each | block-wise quantized moments (Dettmers et al. 2022) |
| Adafactor | about numbers for an matrix | factored second moment, no first moment |
| Lion | 1 number | momentum only |
| Shampoo | numbers per matrix, plus their inverse roots | expensive, amortized |
| Muon | 1 number | momentum only, AdamW for non-matrix parameters |
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.
AdamW at scale
AdamW's update, from §0.6, is
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 has a roughly constant size, say for a tensor with entries whose update has root-mean-square (typically 0.2 to 0.4 for Adam in practice). In high dimensions the update is nearly orthogonal to the current weights, so
Setting and using for small gives the equilibrium
In plain words, the weights grow or shrink until each step changes them by a relative amount of about . They get there on the timescale of steps that §0.6 found by unrolling the update, the same timescale on which they forget their initialization, about 33,000 steps for and . 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 should move when does.
The derivation also explains two practical choices. PyTorch's AdamW multiplies the decay by the learning rate, , so when the schedule lowers , decay weakens too, and the two are coupled when you tune them. Wortsman et al. (2023) found that independent weight decay, with not multiplied by , 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).
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 steps. The equilibrium does not care where you started, only about , , 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 the timescale is 33,000 steps.)
Beta2 and the sharper failure at scale
§0.6 explained why large runs use (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 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 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 , 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 , reduce the batch size, or adjust and .
Why epsilon matters at scale
exists to avoid dividing by zero. §0.6 noted one reason to make it larger, as Llama 2 did with . 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, , 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 , and then is no longer negligible. Per coordinate, the update is
so when , every update is halved, and for smaller gradients Adam turns into SGD with learning rate , 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 at scale and updates shrinking. Lowering to improved their 4.8B model, while raising it to made it diverge. OLMo 2 lowered from to for the same reason. The right 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.
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 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 with gradient , Adam stores a full matrix of second moments. Adafactor stores only its row sums and column sums , each kept as an exponential moving average of the corresponding sums of (plus a tiny ), and reconstructs
In plain words, guess each entry's second moment from how large its row is and how large its column is, which costs numbers instead of . For a 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 and a positive approximation is the generalized KL divergence (I-divergence),
Setting the derivative with respect to to zero,
and symmetrically , with and the row and column sums of . Summing the first equation over gives . Then
which is exactly Adafactor's . The objective is convex in and , so this stationary point is the minimum. When 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 (), saving another full copy. It clips each update to a root-mean-square of at most , , which stops the large steps that a stale second moment would otherwise produce (the same failure as above). And it uses an increasing decay rate, , 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 schedule and parameter-scaled steps (Chowdhery et al. 2022).
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 , compute the update , clip it to , and return (U_hat, r, c). Do not modify the inputs.
Lion, an optimizer found by search
Lion (Chen et al. 2023) was discovered by a symbolic search over programs, not designed by hand, and it is strikingly simple. With and ,
In plain words, take the sign of a blend of the momentum and the current gradient, so every coordinate moves by exactly , 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 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.
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.
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 matrix of gradient outer products, is hopeless for in the billions. Shampoo (Gupta et al. 2018) exploits the matrix structure of each layer instead. For a weight with gradient , it accumulates a left and a right statistic and preconditions on both sides,
In plain words, whiten the gradient along its rows with one small matrix and along its columns with another. The Kronecker product approximates the full-matrix statistic with numbers instead of .
There is a beautiful special case that connects Shampoo to the next optimizer. Drop the accumulation and use only the current gradient, with SVD . Then , , and
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 and , 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 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 is the spectral norm , its largest singular value, because 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 times its input,
where is the first-order change in the loss. Write . Then
because for unit vectors and . The bound is reached by , since then for every , and . 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 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 , then , and in general . So the iteration
keeps and fixed and applies the scalar polynomial 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 to 1.
The classic choice is the cubic , the Newton-Schulz iteration for the polar decomposition. Its properties are easy to check.
- 1 is a fixed point, .
- Convergence near 1 is quadratic. Writing , , so the error goes from to about .
- Every in increases toward 1, since there, and maps into . Values in are mapped into and then converge too, while values above go negative and diverge.
To keep every singular value in the safe range, first divide by the Frobenius norm, . Since , all singular values of are at most 1.
The cubic has one weakness. Near zero it only multiplies by per step, and normalizing by the Frobenius norm makes small singular values very small. A singular value of needs about 20 cubic steps to reach 1. Jordan's insight was that Muon does not need exactly . He chose a quintic with a large slope at zero, , so , and accepted that it does not converge to 1 but oscillates in a band around it. After 5 steps, every initial singular value from to 1 lands between about 0.68 and 1.13, so the result is with close to the identity, which works as well in practice.
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 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.
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 is the smaller one. Each step computes and . No SVD allowed.
The full algorithm, and making it scale
Putting it together, Muon's step for a matrix with gradient is
with momentum , Nesterov's look-ahead , five Newton-Schulz steps, and a shape-dependent scale . Two scales are in use. Jordan's original sets . 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 so the update has the same root-mean-square as a typical AdamW update. Since for an matrix has singular values equal to 1, its squared Frobenius norm is and
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 . 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 matrix () costs about FLOPs, at most , so five steps cost at most . The matrix's share of a training step with a batch of tokens (here is the batch size again, not the matrix dimension above) is FLOPs (forward and backward, §5.6), so the overhead is at most
For a model of width trained with a batch of 4 million tokens, that is 0.5 percent. This is the formula (with 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.
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 . 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.
Choosing an optimizer
| optimizer | state | strengths | weaknesses | used by |
|---|---|---|---|---|
| AdamW | 2 per parameter | robust, well understood, default hyperparameters transfer | memory, and pitfalls at scale | most frontier models (LLaMA, OLMo, DeepSeek-V3) |
| Adafactor | about per matrix | tiny state, update clipping | needs care (relative steps, clipping), slightly worse loss | T5, PaLM (unfactored) |
| Lion | 1 per parameter | memory, cheap sign updates | needs retuned learning rate and decay, sensitive to batch size | some vision and language runs |
| Shampoo, SOAP | per matrix plus eigenbases | fewest steps to a target loss | engineering, numerics, periodic expensive decompositions | AlgoPerf winner, research runs |
| Muon | 1 per matrix parameter | about 2 times the compute efficiency of AdamW in published scaling studies, cheap | needs AdamW for some parameters, whole matrices per device, newer | speedruns, Moonlight, Kimi K2 |
The honest summary for a practitioner is that AdamW with , on matrices only, and a small enough 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.
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
- Loshchilov & Hutter (2019). Decoupled Weight Decay Regularization. AdamW, and why L2 and weight decay differ for adaptive methods.
- Shazeer & Stern (2018). Adafactor, Adaptive Learning Rates with Sublinear Memory Cost. The factored second moment, update clipping, and relative step sizes.
- Chen et al. (2023). Symbolic Discovery of Optimization Algorithms. Lion, and how it was found by program search.
- Gupta et al. (2018). Shampoo, Preconditioned Stochastic Tensor Optimization, and Vyas et al. (2024), SOAP. Matrix preconditioning, and Adam in Shampoo's eigenbasis.
- Jordan et al. (2024). Muon, An optimizer for hidden layers in neural networks. The original write-up, with the quintic coefficients and the overhead analysis.
- Liu et al. (2025). Muon is Scalable for LLM Training. Weight decay, RMS matching, distributed Muon, and Moonlight.
- Bernstein & Newhouse (2024). Old Optimizer, New Norm, An Anthology. Steepest descent under different norms, connecting Adam, Shampoo, and orthogonalized updates.
- Wortsman et al. (2023). Small-scale proxies for large-scale Transformer training instabilities. Independent weight decay, the problem, and more, studied at small scale.