Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why → how

Why Adam beat plain SGD for LLMs

Vision models are mostly trained with SGD + momentum. Transformers are almost always trained with Adam or AdamW. Why did one optimizer win one regime and lose the other?

AI & ML intermediate Apr 29, 2026 · updated Aug 25, 2026 · 15 min read

On this page

The picture version

Six pictures for a reader who has never trained anything. The prose below fills in the seams the pictures skip.

1 · The problem

The recipe that trains your image model stalls on your text model.

a small image classifier the recipe you copied from a tutorial loss steps it just works the same recipe, on text same optimizer, same schedule, different data drops, then crawls steps Turn the one dial down — it crawls slower. Turn it up — it blows up.
Nothing is broken. The same training recipe that works on images stalls on a small transformer over text, and the single dial you are allowed to turn makes it worse in both directions.

2 · Where the mismatch lives

One row of the model sees a word constantly. Another sees it twice.

the model’s word table the … fifty thousand rows … aardvark nudge nudge a big correction, every single step a sliver, and only when the word appears seen ten thousand times an hour seen twice in the whole book The one dial multiplies both of these by the same amount. a bigger dial doesn’t close the gap — it scales it
Common words hand their part of the model a large correction on every step; rare words hand theirs a sliver. A single step size has to serve both — and multiplying two very different numbers by the same amount leaves them just as different.

3 · Why there is no good setting

The two demands don’t overlap anywhere on the dial.

what the steep parts tolerate any bigger and they overshoot and diverge what the rare row needs any smaller and it never moves at all tiny step size huge step size no setting lives here
This is the whole failure: the step size that keeps the steep parts of the model stable is far below the one the rare rows need. You are not tuning badly — there is nothing to tune to. Averaging past nudges doesn’t help either: the average of a tiny number is still tiny.

4 · The trick

Stop setting one dial. Give every number its own, and let it set itself.

one fader per number in the model below each fader: how big that number’s own recent nudges were big recent nudges → small fader, and the other way round step = nudge ÷ its own usual size the direction still comes from the nudge itself dividing by the usual size is the new part: it cancels out how loud that number happens to be aardvark now moves as far as the nobody set that; it fell out of the numbers This is Adam. Everything else in it is bookkeeping.
Adam keeps a running record of how big each number’s own nudges usually are and divides its step by that. The step stops caring about scale — roughly, not exactly, since several details in the real formula break the cancellation.

5 · What it costs

The running record is bigger than the model it’s about.

what you store, per number the weight itself — 4 bytes this step’s nudge — 4 bytes average nudge — 4 bytes average nudge squared — 4 bytes Adam’s bookkeeping 8 of the 16 bytes a 70-billion-number model 280 GB the model 560 GB Adam’s bookkeeping alone — twice the model This is the bill that doesn’t fit on your GPU, and the reason slimmer versions exist. the field pays it because, on a transformer, the plain method is slower for the same compute
Per-parameter adaptivity has to be stored per parameter. For a 70-billion-parameter model that is about 560 GB of bookkeeping, roughly twice the size of the weights — which is exactly why memory-thrifty variants and state-sharding systems were invented.

6 · Keep this card

The whole thing on one index card.

Adam = plain gradient descent + one step size per number + set by that number’s own history and the half that isn’t in the algorithm at all: a model whose numbers live on wildly different scales
Picture to keep: one master volume knob for the whole orchestra versus a knob per player, each set automatically by how loud that player has been. Adam didn’t win by being a better optimizer — it won because text transformers produce that mismatch of scales. Change the landscape — an image model, or much smaller batches — and the advantage shrinks.

Why it exists

You have a training recipe that works. A small image classifier, SGD with momentum, a learning-rate schedule you copied from a tutorial. The loss goes down, the accuracy goes up. So you point the same recipe at your first small transformer over text — same optimizer, same schedule, different data. The loss drops for a few hundred steps and then crawls. You lower the learning rate and it crawls slower. You raise it and it blows up.

You probably assume that’s a bug in your code, or that the model needs more data. It’s usually neither. Swap SGD for Adam, change nothing else, and the same model trains. That small transformer over text is the running example for the rest of this post — in particular, one row of its embedding table: the row for a rare token like aardvark.

The split shows up across the literature too. CNN-era vision papers — ResNets, the original ImageNet results, the workhorse 2010s recipes — are SGD + momentum with a hand-tuned schedule. Transformer papers are Adam or its cousin AdamW: Llama, GPT-style pretraining, fine-tuning, the open-source recipes, all Adam-flavored. Almost nobody trains a serious LLM with plain SGD — recent work has started revisiting that, but it’s the exception, not the default. (Modern ViT-style vision recipes have largely moved to AdamW too, which is a good hint that this is an architecture story more than an images-vs-text one.)

That should feel weird. SGD is the simpler algorithm. Adam stores two extra fp32 buffers per parameter — for a 70B-parameter model, ~560 GB of optimizer state, roughly 2× the size of the fp32 weights themselves. Why is the expensive, more complicated optimizer the one that won the regime where compute is most precious?

Not because Adam is a strictly better optimizer. Adam is better at the specific shape of the loss landscape transformers produce on text — and “one global step size for every parameter” is exactly the assumption that shape punishes.

Why it matters now

Try to full-fine-tune a 7B model on a 24 GB consumer GPU and you meet this directly: the weights are only ~14 GB in bf16, but Adam’s two fp32 moment buffers add ~56 GB on their own. The optimizer, not the model, is what doesn’t fit. That arithmetic is why three separate things exist:

The short answer

Adam = SGD + per-parameter learning rate that adapts to each parameter's gradient history

Picture to keep: SGD is one master volume knob for the whole orchestra; Adam gives every player their own knob, set automatically by how loud that player has been recently — so the quiet oboe (your aardvark embedding row) stops getting drowned out by the brass. The analogy covers step size only: unlike a mixing desk, Adam’s knobs move every step and are set by the gradients themselves, and nothing in it decides which direction a parameter should move.

SGD uses one global learning rate for every parameter. Adam keeps a running estimate of how big each parameter’s gradient typically is, and shrinks the step for parameters whose gradients are large or noisy while letting parameters with small gradients take bigger steps. That single change — making the effective step size per-parameter and adaptive — is the whole difference. For loss landscapes where different parameters live on wildly different scales (like transformers), it turns out to matter a lot.

How it works

Naive attempt: one step size for everything. Plain gradient descent is one rule:

w ← w − η · g

where g is the gradient of the loss with respect to w and η is the learning rate. Same η for every parameter in the model — the attention weights, the LayerNorm scales, and the aardvark embedding row all move by η times whatever gradient they happened to receive.

Why it breaks. Those parameters do not receive comparably-sized gradients. Because aardvark is rare, the gradient reaching its embedding row is tiny compared to the one reaching the. With a single η, you are forced into a compromise: pick η large enough that the rare rows learn anything and the frequent, sharply-curved directions overshoot and diverge; pick η small enough to keep those stable and the rare rows barely move. That is exactly the “lower it and it crawls, raise it and it blows up” symptom from the hook. Momentum doesn’t fix it — averaging gradients over time smooths the trajectory, but a running average of a tiny gradient is still a tiny gradient.

The fix: normalize each parameter by its own gradient scale. Adam tracks two extra running averages per parameter — m (the gradient’s running mean) and v (the gradient’s running mean squared) — and applies:

w ← w − η · m̂ / (√v̂ + ε)

(the hats mean bias-corrected — early in training the running averages start at zero and need rescaling, which is a startup detail, not the idea.)

The two halves do different jobs. m̂ smooths the direction, the way momentum does. √v̂ in the denominator is the new part: divide each parameter’s step by the typical size of its own recent gradients, and the step becomes approximately invariant to that parameter’s gradient scale — a parameter that always sees small gradients now takes a step comparable to one that always sees large gradients. The aardvark row stops being drowned out, and the sharp directions stop dictating the global η. It’s a per-parameter rescaling that emerges from the training data — no human has to set it. (“Approximately” is doing real work: ε, the bias correction, weight decay, and gradient statistics that shift during training all break exact invariance.)

That sounds like a small detail. For transformers on text, it isn’t — and the “why” splits into three explanations that don’t rule each other out.

Why this matters more for transformers than for CNNs

The remaining question is why the single-η compromise hurts a transformer so much more than a convnet. Three lines of explanation in the literature each name a different way that compromise fails; they aren’t mutually exclusive, and none is fully settled. Treat this section as a working synthesis, not a verdict.

1. The Hessian is “block heterogeneous.”

The Hessian of a transformer’s loss has very different curvature in different parameter blocks: the attention weights, the MLP weights, the embedding table, and the LayerNorm scales all live on different scales. A single global learning rate is forced to compromise: small enough for the sharpest block, which is wasteful for the flat ones. Adam is coordinate-wise adaptive, but in practice that ends up giving different parameter blocks usefully different effective scales. “Why Transformers Need Adam: A Hessian Perspective” (Zhang et al., NeurIPS 2024, arXiv 2402.16788) argues that block-wise Hessian heterogeneity is a key reason SGD struggles on transformers and that this heterogeneity is much milder in CNNs. They argue a cause; I wouldn’t read it as the settled cause.

2. Token frequencies are heavy-tailed.

Natural-language tokens follow something Zipf-shaped: a small set of very common tokens (the, ,, ., of) and a long tail of rare ones like aardvark. If you train with SGD’s single global learning rate, the gradient signal at the output layer is dominated by frequent tokens. Rare tokens make tiny contributions to the average gradient, so under plain SGD the loss on rare-token classes goes down much more slowly than on frequent ones. Adam divides by the per-parameter √v, which tends to be smaller for coordinates that repeatedly receive smaller gradients — so rare-token directions get amplified instead of drowned out. Kunstner et al.’s “Heavy-Tailed Class Imbalance and Why Adam Outperforms Gradient Descent on Language Models” (NeurIPS 2024, arXiv 2402.19449) makes this argument with a deliberately designed empirical study.

3. SGD’s update directions are too sharp.

A complementary line of work looks at the directional sharpness of the update — how much the loss curves along the direction you’re stepping in. Pan & Li’s “Toward Understanding Why Adam Converges Faster Than SGD for Transformers” (arXiv 2306.00204) argues that SGD’s update steps land in much sharper directions than Adam’s on transformers, that this is driven by a few coordinates with poorly behaved curvature, and that Adam’s coordinate-wise scaling is essentially a directional-sharpness reduction. Their coordinate-wise-clipping experiments support the idea that a small set of badly scaled coordinates drives much of the gap — which is a claim about where the gap lives, not a drop-in replacement for Adam.

The seams

Where the textbook story gets less clean:

You started with Adam = SGD + per-parameter learning rate. What did this post add to the why? — + a loss landscape whose parameters live on wildly different scales, which is the half that isn’t in the algorithm at all. The best current working explanation is not that Adam is a better optimizer, but that transformer training on text produces gradients spread across orders of magnitude, and a single global step size cannot serve them all at once. Change the landscape — a CNN on images, or, per the 2025 work, a much smaller batch size — and the advantage shrinks.

So the answer to the fear you opened with: your code probably wasn’t broken, and the model probably didn’t need more data. You were asking one number to be the right step size for a parameter that sees the ten thousand times an hour and a parameter that sees aardvark twice. It can’t be.

The transfer rule worth keeping: expect Adam-style methods to help whenever gradient scales are badly mismatched across parameters, and expect the gap to narrow when the training regime changes enough that a well-tuned SGD can cope.

Check yourself

Before you go — imagine two blocks of your small transformer whose gradients are consistently 1000× apart in magnitude, step after step. Which optimizer treats them more differently, SGD or Adam?

Answer

SGD. Its update is η · g, so the block with the larger gradients takes steps 1000× bigger — it either diverges or forces you to drop η for the whole model, which starves the other block. Adam divides each block’s step by its own √v̂, which is also about 1000× apart, so the two ratios m̂/√v̂ come out comparable and both blocks move at a similar rate. That approximate per-coordinate scale-invariance is the mechanism, and it’s the block-heterogeneity story above stated in one sentence.

And one trade-off: your 7B model trains fine with AdamW but you’re out of VRAM. Someone suggests switching to SGD + momentum to free the optimizer state. What do you actually save, and what do you risk?

Answer

Momentum keeps one buffer per parameter instead of Adam’s two, so it halves the optimizer state — in the fp32 accounting above (4 bytes weights + 4 gradients + 8 state) that’s 4 bytes of 16, about a quarter of the total. Real, but not dramatic. The risk is the whole post: you also give up the per-parameter scaling, and on a transformer that usually shows up as slower convergence per unit of compute, so you may spend more GPU-hours than you saved GPU-bytes. The cheaper trades are the ones designed for exactly this — 8-bit Adam or Adafactor keep the adaptivity and shrink the state, and ZeRO/FSDP shard it across devices instead of dropping it.

Going deeper