Skip to content
AI360XpertBeta

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.

Two parameters with gradients 20 times apart get the same 0.032 step when each gradient is divided by its own recent size
Two parameters with gradients 20 times apart get the same 0.032 step when each gradient is divided by its own recent size

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 θ\theta move against the gradient gg by a global rate η\eta: θ\theta minus η\eta times gg, 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 η\eta 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:

vt=βvt−1+(1−β)gt2with v0=0v_t = \beta v_{t-1} + \left(1 - \beta\right) g_t^2 \quad \text{with } v_0 = 0

Here gtg_t is the gradient at step tt, vtv_t is the second moment after seeing it, and β\beta 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:

θt+1=θt−ηgtvt+ϵone update per parameter\theta_{t+1} = \theta_t - \eta \frac{g_t}{\sqrt{v_t} + \epsilon} \quad \text{one update per parameter}

Here θt\theta_t holds the parameters at step tt, η\eta is the single global learning rate, and ϵ\epsilon is a tiny guard, typically 10−810^{-8}, that keeps you from dividing by something near zero when a parameter has sat still. A quiet parameter owns a small vt\sqrt{v_t}, so the division boosts it. A loud one owns a large vt\sqrt{v_t}, so the same division shrinks it.

From AdaGrad to RMSprop to Adam

AdaGrad came first and never forgets. It sums every squared gradient, Gt=Gt−1+gt2G_t = G_{t-1} + g_t^2, and steps η\eta times gtg_t over Gt\sqrt{G_t} plus ϵ\epsilon. 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 β\beta 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 mtm_t with its own decay β1\beta_1 usually 0.9, alongside the second moment with β2\beta_2 usually 0.999. Direction comes from mtm_t, scale from vtv_t, 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 nn by mm weight matrix, Adam's two states are two full nn by mm arrays. Adafactor keeps one row factor of length nn plus one column factor of length mm 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 uu for each layer with weights ww and rescales it by the layer's trust ratio, the norm of ww over the norm of uu, 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 ϵ\epsilon and warmup earn their keep, see Learning Rate Scheduling.

Worked example: two gradients, one step size

Fix η=0.01\eta = 0.01, β=0.9\beta = 0.9, ϵ=10−8\epsilon = 10^{-8}, and take one RMSprop step from v0=0v_0 = 0 for a quiet parameter with g=0.1g = 0.1 and a loud one with g=2.0g = 2.0, twenty times larger. Square them: 0.01 and 4.0. Average against zero: vv 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.

∣Δq∣=0.01×0.10.001≈0.032quiet param\left| \Delta_q \right| = 0.01 \times \frac{0.1}{\sqrt{0.001}} \approx 0.032 \quad \text{quiet param} ∣Δl∣=0.01×2.00.4≈0.032loud param\left| \Delta_l \right| = 0.01 \times \frac{2.0}{\sqrt{0.4}} \approx 0.032 \quad \text{loud param}

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 vv 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.0316

Both 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.