Adaptive Optimizers
One learning rate fails when layers see very different gradient sizes. Adaptive methods give each parameter its own step size from its own recent gradients.
Why Does This Exist?
Picture fine-tuning a text classifier. The embedding layer's gradients trickle through while the final layer's gradients arrive loud, easily tens of times apart in size. You get one global learning rate to serve both. Set it for the loud layer and the quiet one barely moves for epochs. Set it for the quiet one and the loud one jumps past anything useful on the first steps.
That mismatch is the whole reason this family exists. Scale makes it hurt more: two state vectors per parameter at 4 bytes each turns 7 billion parameters into about 56 billion bytes of optimizer state before weights or gradients take a byte. So the choice of how you track per-parameter history stops being trivia and starts deciding whether the run fits at all.
This page maps that family: where AdaGrad's accumulation stalls, how RMSprop fixes it by forgetting, how Adam pairs the fix with momentum, and which answer fits which cost, Adafactor for memory, Lion for compute, LAMB for very large batches. The full Adam update with bias correction and AdamW lives in Adam and AdamW. The loop all of these plug into lives in Gradient Descent.
The one idea you need first is that loop's step. Parameters move against the gradient by a global rate : minus times , repeated. Momentum improves it by averaging recent gradients so consistent direction stacks up. Adaptive methods attack the other half of the problem, the size of the step per parameter, and they stack with a global schedule such as Learning Rate Scheduling.
Think of It Like This
A coach who sets stride length per runner
You coach a squad with one whistle for everyone. Your sprinters take huge strides, your beginners take tiny ones, and one shouted cadence cannot suit both. So you watch each runner's recent strides and adjust: you shorten the overstrider's allowance and lengthen the beginner's, until one whistle means about the same effort everywhere.
The whistle is the global learning rate. Each runner's recent stride record is the second moment. The adjustment is the division. Sparse features, the runners who rarely show up, get longer early strides, which is exactly AdaGrad's old insight about rare words.
Where it stops: you can see fatigue and terrain, while the optimizer sees only squared numbers with no idea what they mean. And your notebook says nothing about the cost of remembering every stride, which is where Adafactor's compression and Lion's direction-only steps come in below.
How It Actually Works
Every member shares one move: divide each parameter's gradient by a measure of how large that parameter's gradients have been running. Quiet parameters get boosted, loud ones get held back, and one global finally suits the whole model. The second moment is that measure, an average of squared gradients kept separately for every parameter.
The shared rule
Keep one running average of the squared gradient per parameter:
Here is the gradient at step , is the second moment after seeing it, and is a decay between 0 and 1, often 0.9, that sets how fast old squares are forgotten. Then step with the gradient divided by its own recent size:
Here holds the parameters at step , is the single global learning rate, and is a tiny guard, typically , that keeps you from dividing by something near zero when a parameter has sat still. A quiet parameter owns a small , so the division boosts it. A loud one owns a large , so the same division shrinks it.
From AdaGrad to RMSprop to Adam
AdaGrad came first and never forgets. It sums every squared gradient, , and steps times over plus . Rare features start with a near-zero sum, so their early steps are large, which made AdaGrad loved for sparse text data. But the sum only grows, so on a long dense run every step shrinks toward zero and training stalls mid-run. Good opener, poor finisher.
RMSprop fixes the stall by swapping the sum for the moving average above, with around 0.9. Old squares fade, the denominator tracks recent size instead of all-time size, and steps stop decaying. One state vector per parameter, no momentum, still a sane pick when memory is tight and you doubt momentum buys much.
Adam adds the first moment, the momentum average with its own decay usually 0.9, alongside the second moment with usually 0.999. Direction comes from , scale from , and both get bias correction for their zero start before the division. Two states per parameter. That correction and the AdamW decoupling are worked fully in Adam and AdamW, so this page treats Adam as the default the rest of the family is measured against.
Adafactor, Lion, and LAMB answer different costs
Adafactor answers memory. For an by weight matrix, Adam's two states are two full by arrays. Adafactor keeps one row factor of length plus one column factor of length and rebuilds each entry's scale from their outer product. Take a 4096 by 4096 layer in float32: Adam holds 2 times 4096 times 4096 times 4 bytes, about 134.22 MB, while the factors hold 4096 plus 4096 times 4 bytes, about 0.0328 MB, roughly 4096 times smaller. Coarser history, far cheaper. Reach for it when optimizer state is what stops the run fitting.
Lion answers per-step compute. It keeps one momentum-like average and steps by its sign rather than its size, so every parameter moves the same distance and only direction differs, with decay applied outside the sign. One state, cheaper arithmetic, but the magnitude information Adam keeps is gone. Treat a switch as a fresh tuning job, never a drop-in.
LAMB answers very large batches. It takes an Adam-style raw update for each layer with weights and rescales it by the layer's trust ratio, the norm of over the norm of , so every layer moves in proportion to its own size no matter how the giant batch pushed the raw gradients. Same direction Adam chose, different distance per layer.
The trade is plain: adaptivity buys you one rate that works across wildly scaled layers, and charges memory, extra arithmetic, and sometimes a little generalization on vision tasks where tuned SGD with momentum still wins. Skip the family on small clean convex fits, where plain descent plus a schedule is simpler and every bit as good. The shared assumption is that recent squared size predicts the right scale right now. It fails when gradients spike suddenly against a stale denominator or wake from near zero, which is when and warmup earn their keep, see Learning Rate Scheduling.
Worked example: two gradients, one step size
Fix , , , and take one RMSprop step from for a quiet parameter with and a loud one with , twenty times larger. Square them: 0.01 and 4.0. Average against zero: becomes 0.1 times 0.01, which is 0.001, and 0.1 times 4.0, which is 0.4. Square-root them: about 0.0316 and about 0.6325.
A 20 times gap in gradients becomes the same 0.032 step after the division, which is the picture above. Note the first-step overshoot: bias correction would divide each by 0.1 and land both steps at exactly 0.01, so the uncorrected 0.032 runs about 3.16 times large. That early sizing error is why Adam and AdamW corrects before dividing.
Code
This snippet replays the worked example so you can check the equal steps yourself:
import math
def rms_step(g: float, v_prev: float = 0.0, beta: float = 0.9, lr: float = 0.01, eps: float = 1e-8) -> tuple[float, float]: v = beta * v_prev + (1.0 - beta) * g * g # recent squared size step = lr * g / (math.sqrt(v) + eps) # scaled step for this param return v, step
for g in (0.1, 2.0): v, step = rms_step(g) print(f"g {g:.1f} v {v:.4f} step {step:.4f}") # -> g 0.1 v 0.0010 step 0.0316 # -> g 2.0 v 0.4000 step 0.0316Both print 0.0316. Change beta to 1.0 and watch the second call shrink on repeat steps: that is AdaGrad's stall in one line.
Watch Out For
Swapping the optimizer without re-tuning the rate
Symptom: you switch Adam to Lion at the same learning rate and the run looks broken within an epoch, so Lion gets blamed and rolled back. Lion's sign-based step moves every parameter the same distance, which behaves nothing like Adam's magnitude-scaled step at the same nominal number, and LAMB's layer rescaling shifts the scale again.
Swap the optimizer, re-tune the rate. Treat it as a fresh tuning job: search the rate from scratch on a short horizon, check the first few hundred steps before judging, and only then compare quality. The optimizer name is not the only thing you changed.
Buying memory savings for the wrong bottleneck
Symptom: the run still dies with out-of-memory after switching to Adafactor, and the switch reads as a failure. Adafactor shrinks optimizer state only. If the binding constraint was activations, the KV cache, or the weights themselves, none of that moved, so the same limit hits at the same step.
Profile which part of the memory budget is binding before picking the fix aimed at it. If state dominates, Adafactor helps a lot. If activations dominate, you want checkpointing, a smaller micro-batch, or shorter context, not a new optimizer.
The Quick Version
- Adaptive methods divide each gradient by its own recent size, so one global rate suits layers whose gradients differ tens of times over.
- AdaGrad sums all past squares and stalls on long runs; RMSprop swaps the sum for a forgetting average and keeps one state.
- Adam pairs that scale with a momentum direction plus bias correction, holding two states, which is why it is the default.
- Adafactor rebuilds the second moment from row plus column factors, about 4096 times smaller than full state on a 4096 by 4096 layer.
- Lion steps by the sign of one average, cheaper per step but blind to magnitude, so it needs its own tuned rate.
- LAMB rescales each layer's update by its weight norm over its update norm to hold together at very large batches.