rumblr Work in progressWIP

● The AI Primer · Lesson 3 · Part 1: how the model works inside

Optimizers

how weights take their steps downhill

You'll be able to explain SGD, momentum, Adam/AdamW, learning-rate warmup and decay

Members · open during launch 30 min8 figures and diagrams8 interactive
Guide is what to use and when. How it works builds it from scratch. Math & code adds the formulas and the Python.

The lesson in one minute

What you'll be able to explain

  1. Gradient descent: step against the slope, scaled by the learning rate.
  2. The learning rate is the most important knob: too high diverges, too low stalls.
  3. Momentum accumulates a velocity so consistent directions speed up and zig-zags cancel.
  4. Adam gives every weight its own step size (average gradient ÷ its typical size); AdamW decouples weight decay from that rescaling and is the transformer default.
  5. Transformers use warmup then cosine decay, and clip gradients by global norm.

Level 1

The practitioner's guide

Pick the run you are setting up; the guide below explains every choice it lights up.

In one sentence

An optimizer is the rule that turns "the slope of the loss here" into "the step every weight takes", and the handful of settings that come with it (learning rate, momentum, the two Adam betas, weight decay, warmup, clipping) are most of what a training recipe consists of.

When you need it

You need this the moment a training or fine-tuning job asks you for a learning rate, and again the first time a loss curve spikes, turns NaN, or flattens long before the model is any good. The tell: a config with lr, betas, weight_decay, warmup_steps and max_grad_norm copied from someone else's run, and a paper's training section that reads as Greek (β₁ = 0.9, β₂ = 0.95, ε = 10⁻⁵) rather than as choices. You don't need it to call a trained model, and a hosted fine-tuning API chooses the optimizer for you; what you set there is the learning rate and how long to train, which is still the most important choice on this page. One number from this lesson shows how much the rule matters: in a narrow valley, 100 steps of momentum reach a loss more than 10,000 times lower than 100 steps of plain gradient descent at the same learning rate.

Your options

The rules, plus two add-ons that ride on any of them, from the simplest to the one transformers use:

Option What it does What it guarantees What it costs Where it lives
Plain SGD Steps against the current batch's slope, scaled by the learning rate The simplest rule, nothing to tune but η The steepest direction caps η, so it creeps along gentle valley floors: loss 1.56 after 100 steps here Your training script, no extra memory
SGD with momentum Keeps a velocity: 90% of the last one plus the new slope Consistent directions speed up and zig-zags cancel: loss 6.5 × 10⁻⁵ after the same 100 steps One extra number per weight, and overshoot past the minimum before it settles The default for CNNs
Adam Gives every weight its own step: its average gradient divided by its typical gradient size; the first step is exactly η whatever the gradient One learning rate works across weights whose gradients differ by orders of magnitude Two extra numbers per weight; unreliable averages in the first steps; an L2 penalty added to its gradient loses its meaning Most training scripts
AdamW Adam, with weight decay applied straight to the weights instead of through the gradient λ means what it says: shrink by η × λ per step The same memory as Adam The transformer default; PyTorch's AdamW ships with lr 0.001, betas (0.9, 0.999), eps 10⁻⁸, weight_decay 0.01
Warmup then decay (a schedule on top) Ramps η from 0 to its peak, then lowers it along a cosine or a straight line to a floor No full-size steps while the weights are random and Adam's averages unsettled; a gentle finish into a good minimum Two more settings, warmup steps and total steps, so the run's length must be known up front The scheduler
Gradient clipping (on top) Rescales the whole gradient when its combined length exceeds a limit, direction unchanged A rare spike becomes a blip instead of a wrecked run One norm per step; it can hide a network that explodes every step One line before the optimizer step

How to choose

Start from the architecture, then from the size.

  • A transformer, pretraining or fine-tuning: AdamW, warmup, a cosine or linear decay, clipping at 1.0. That is the Llama 2 recipe (AdamW with β₁ = 0.9, β₂ = 0.95, ε = 10⁻⁵, 2000 warmup steps, cosine decay to 10% of the peak, weight decay 0.1, clipping 1.0) and the shape of Hugging Face's TrainingArguments defaults (AdamW, a linear schedule, max_grad_norm 1.0).
  • A convolutional network: SGD with momentum 0.9, still the default there.
  • The learning rate itself: the fastest one that doesn't blow up, found by trying a few. Bigger models take smaller rates (Llama 2 uses 3 × 10⁻⁴ for its 7B and 13B models and 1.5 × 10⁻⁴ for 34B and 70B), and fine-tuning takes smaller rates than pretraining (Hugging Face defaults to 5 × 10⁻⁵).
  • A published recipe you are reproducing: copy every setting, betas included. The transformer paper's β₂ = 0.98 and Llama 2's 0.95 are both deliberate departures from Adam's 0.999.
  • Whatever you pick, the learning rate is the single most important hyperparameter, and a schedule is part of it: the peak, the warmup and the total steps are one decision.

What it costs

Memory: momentum keeps one extra number per weight and Adam or AdamW keep two, so the optimizer state of a large model is twice the size of the weights it trains, which is a large part of why training needs more memory than serving. Compute: a few operations per weight per step, small beside the forward and backward passes. Time: warmup and decay need the total step count, so the budget is fixed before the run starts, and a run stopped halfway never reached its low learning rate. Quality: on this lesson's narrow valley, after 300 steps plain SGD sits at a loss of 3.7 × 10⁻³, Adam at 1.1 × 10⁻¹¹ and momentum at 1.8 × 10⁻¹²; the rule decides whether a fixed budget of steps gets there at all.

What breaks

  • Loss spikes, then NaN. The learning rate is too high: on a bowl with slope 2w, any η above 1 makes every step overshoot further than the last. Lower it, add warmup, clip.
  • Loss barely moves. Too low: at η = 0.001 the bowl's weight is still at 0.98 after ten steps. Raise it until training becomes unstable, then back off.
  • Weight decay that does nothing, or too much. An L2 penalty inside Adam gets divided by the typical gradient size, so λ = 0.1 and λ = 0.001 shrink a weight by the same amount. Use AdamW, where λ = 0.1 means 0.99 per step and 0.001 means 0.9999.
  • Divergence in the first hundred steps with Adam. No warmup: its averages are built from a handful of steps and the weights are random.
  • Overshoot. Momentum swings hard, across the valley and past the minimum, before it settles; the trajectory figure in Level 2 shows it. A smaller β or a smaller η calms it.
  • Clipping on every step. The gradient is exploding, not spiking; the fix is initialization or normalization (primer.ml.deep_nets), and clipping is hiding it.
  • A schedule cut short. Cosine decay reaches its floor only at the planned last step; stop early and the model never settled.

In the wild

Attention Is All You Need trained with Adam at β₁ = 0.9, β₂ = 0.98, ε = 10⁻⁹, a learning rate that rises linearly for 4000 warmup steps and then falls with the inverse square root of the step. Llama 2 used AdamW with the recipe above. PyTorch ships torch.optim.SGD (momentum as an argument), Adam and AdamW, and clips with torch.nn.utils.clip_grad_norm_, which measures the norm over all parameters "as if the norms of the individual gradients were concatenated into a single vector". Hugging Face's TrainingArguments defaults to AdamW (adamw_torch_fused on recent PyTorch), learning_rate 5e-5, weight_decay 0.0, betas 0.9 and 0.999, epsilon 1e-8, a linear schedule with no warmup and max_grad_norm 1.0. The rules come from Kingma and Ba (Adam, 2014), Loshchilov and Hutter (AdamW, 2017, and cosine annealing, 2016), Sutskever et al. (momentum, 2013) and Pascanu et al. (clipping, 2012), all linked at the end of the lesson.

Go deeper

Level 2 walks each rule down the same bowl with two-row tables you can check by hand, races SGD, momentum and Adam across a narrow valley, shows why an L2 penalty inside Adam stops meaning anything, draws the warmup-and-cosine curve, and clips (3, 4) down to (0.6, 0.8). If you only needed to fill in a config, you are done.

Level 2

How it works, from scratch

Every optimizer runs the same loop: feel the slope, turn it into a step, take it, repeat. This level builds each rule on a one-number valley you can check by hand, then lets them race across a valley that is not round.

Chapter 1

The idea: walking downhill in fog

Walk it yourself first; the formula below is the rule you used.

You're on a hillside in thick fog and want to reach the lowest point in the valley. You can't see the valley; you can only feel the slope under your feet. So you feel which way is downhill, take a step that way, and repeat. That is gradient descent. The hillside is the loss (how wrong the model is, for every possible setting of its weights), your position is the current weights, and the slope under your feet is the gradient: the list of slopes of the loss, one per weight. (See primer.ml.neural_net for how backprop measures it, and primer.notation for the symbols.) An optimizer is your rule for turning "the slope here" into "the step I take".

Worked example on the simplest possible valley, the bowl f(w) = w², whose slope at w is 2w. Start at w = 1 with step size 0.1:

step w slope 2w step taken 0.1 × slope new w
0 1.0 2.0 0.2 0.8
1 0.8 1.6 0.16 0.64

Each step keeps 80% of w, sliding smoothly toward the bottom at 0.

Figure 1 · Diagram

Reading it: every optimizer runs this loop; they differ only in the diamond. Plain gradient descent's rule looks only at the current slope. Momentum and Adam also keep a small memory of past slopes (the dotted box) and use it to choose a better step.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the weights at step
the step counter 0, 1, 2, …
"eta", the learning rate (step size) 0.1
the loss
"nabla L", the gradient: the slope of the loss at the current weights, one number per weight

In words: "the next weights are the current weights minus the learning rate times the slope of the loss where we stand."

With the numbers: ; .

Level 3: in Python
w, eta = 1.0, 0.1
# ∇L for the bowl L = w²
def grad_L(w): return 2 * w
for t in range(2):
    # w_(t+1) = w_t - η ∇L(w_t)
    w = w - eta * grad_L(w)
    print(round(w, 2))  # → 0.8 0.64

"Stochastic" gradient descent (SGD) means the slope is estimated from a small random batch of examples instead of the whole dataset: noisier, but thousands of times cheaper per step. descend_bowl runs the table above; SGD is the general version.

Why it matters every model you've heard of was trained by a descendant of this one line. The variants below exist because real loss landscapes are not round bowls.

Chapter 2

The learning rate: how long a stride?

In the fog, stride length is everything. Tiny shuffling steps are safe but you'll be walking all night. Giant leaps overshoot the valley floor and land you higher up the opposite slope; keep leaping and you climb out of the valley altogether.

Worked example on the bowl w², where each step multiplies w by (1 − 2η):

learning rate η multiplier 1 − 2η w after 1, 2, 3 steps what happens
0.001 0.998 0.998, 0.996, 0.994 stalls: 10 steps only reach 0.980
0.1 0.8 0.8, 0.64, 0.512 smooth progress
0.5 0 0, 0, 0 lands on the bottom in one step
1.1 −1.2 −1.2, 1.44, −1.728 overshoots further each time: diverges

Figure 2 · Drawn from the lesson's code

0 5 10 15 20 25 30 step 1 0 − 1 2 1 0 − 1 0 1 0 − 8 1 0 − 6 1 0 − 4 1 0 − 2 1 0 0 1 0 2 1 0 4 1 0 6 loss w² (log scale) Learning rate on the bowl f(w) = w² η = 0.001 η = 0.1 η = 0.45 η = 1.1

Over 30 steps, rate 0.001 barely lowers the loss, 0.1 falls steadily, 0.45 plunges below the chart within six steps, and 1.1 climbs as every step overshoots

Reading it: each line is the loss (log scale) over 30 steps for one learning rate. The flat line near the top is 0.001: technically improving, practically stuck. 0.1 falls steadily. 0.45 falls fastest: its line ends at step 5, because one step later the loss is down to 10⁻¹², the floor of the chart. 1.1 climbs the whole way: every step makes things worse. On a real model you can't compute the perfect rate, so you look for the fastest one that doesn't blow up.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the slope of at 2.0 at
the factor each step multiplies by −1.2 when

In words: "on this bowl, one step multiplies the weight by one minus twice the learning rate; if that factor's size is above 1, the weight grows instead of shrinking."

With the numbers: : , so .

Level 3: in Python
def three_steps(eta, w=1.0):
    out = []
    for t in range(3):
        # w - η·2w = (1 - 2η) w
        w = (1 - 2 * eta) * w
        out.append(round(w, 3))
    return out
three_steps(1.1)  # → [-1.2, 1.44, -1.728]
three_steps(0.1)  # → [0.8, 0.64, 0.512]
# η = 0.001 after 10 steps: barely moved
round(0.998 ** 10, 3)  # → 0.98

In code: descend_bowl is the same loop with the learning rate as an argument; call it with each rate in the table to reproduce every row.

Why it matters the learning rate is the single most important hyperparameter. Too high and training diverges or bounces (loss spikes, NaNs); too low and it takes forever or settles somewhere poor. The steepest direction of the landscape sets the ceiling: on a bowl with slope 2w, any η above 1 diverges.

The chart above for four rates, now for any rate, drawn on the bowl itself.

Chapter 3

Momentum: a heavy ball instead of a cautious hiker

Now imagine a long, narrow valley: steep walls on both sides, a gentle slope along the floor. A cautious hiker who only reads the local slope zig-zags from wall to wall and barely moves along the floor. A heavy ball rolling down the same valley behaves differently: its sideways bouncing cancels out, while the gentle downhill pull along the floor keeps adding up, so it builds speed exactly where you want it. That accumulated speed is momentum.

Worked example on the bowl w², learning rate 0.1, momentum β = 0.9:

step slope g = 2w velocity v = 0.9·v + g new w = w − 0.1·v
1 2.0 2.0 1.0 − 0.2 = 0.8
2 1.6 0.9 × 2.0 + 1.6 = 3.4 0.8 − 0.34 = 0.46

After two steps plain descent is at 0.64; momentum is already at 0.46.

Figure 4 · Diagram

Reading it: the velocity box mixes the new slope with 90% of the old velocity, and that velocity (not the raw slope) sets the step. Follow the loop back: next time, this velocity becomes the "old velocity". Slopes that keep pointing the same way pile up; slopes that flip sign every step (the walls) mostly cancel.

Figure 3 · Drawn from the lesson's code

−8 −6 −4 −2 0 2 x (gentle direction) −1.0 −0.5 0.0 0.5 1.0 y (steep direction) 100 steps on the narrow valley ½(x² + 100y²) SGD (η = 0.015) momentum (η = 0.015, β = 0.9) Adam (η = 0.3) minimum

After 100 steps from (-8, 1), plain SGD has only crept to x = -1.8 along the valley floor, while momentum and Adam reach the minimum after some overshoot

Reading it: the ellipses are contour lines of the valley ½(x² + 100y²), the start is at the left and the minimum is the star at the centre. Plain SGD (its learning rate capped by the steep walls) creeps along the floor and after 100 steps is still far from the star. Momentum swings hard: across the valley to y = −1.1 (further out than it started) and past the star to x ≈ 1.9, before the swings die down and it settles 0.003 from the minimum. Adam, which rescales each direction separately, takes a steadier line: it dips to y ≈ −0.5, overshoots to x ≈ 0.6, and ends 0.02 away. Both beat plain SGD by far; momentum's overshoot is the price of the speed it builds.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the gradient (slope) at step 2.0, then 1.6
the velocity: a running, fading sum of past gradients 2.0, then 3.4
"beta", how much old velocity is kept each step (0 to 1) 0.9
learning rate 0.1

In words: "the velocity is 90% of the previous velocity plus the new slope, and the weights move by the learning rate times the velocity."

With the numbers: ; .

Level 3: in Python
w, v, beta, eta = 1.0, 0.0, 0.9, 0.1
for t in range(2):
    # the slope of w² here
    g = 2 * w
    # v_t = β v_(t-1) + g_t
    v = beta * v + g
    # w_(t+1) = w_t - η v_t
    w = w - eta * v
    print(round(v, 2), round(w, 2))  # → 2.0 0.8 3.4 0.46

In code: momentum_on_bowl runs the two-row table; SGD with a nonzero momentum keeps its velocity between calls to SGD.step. narrow_valley is the valley in the figure (rosenbrock is a harder, banana-shaped one), and run walks any optimizer across a landscape and records its path.

Why it matters real loss surfaces are full of narrow valleys. On the valley above, 100 steps of momentum reach a loss over 10,000× lower than 100 steps of plain SGD at the same learning rate. Momentum is still the default for training CNNs.

The race above, with the momentum and the clock in your hands.

Chapter 4

Adam: a separate stride for every direction

Compare the two strides yourself first; Adam's formulas will then read as how it keeps them even.

Back in the narrow valley, what you'd really like is short steps across the steep walls and long strides along the gentle floor. Adam does exactly that: it keeps, for every single weight, a running average of the slope (the direction, like momentum) and a running average of the squared slope (how big that weight's slopes typically are), then divides the first by the square root of the second. Every weight ends up taking steps of roughly the same size, the learning rate, whatever the scale of its gradient.

Worked example: on its very first step, Adam moves a weight by exactly the learning rate (0.01 here), no matter whether that weight's gradient is 1000, 1 or 0.001. (With bias correction, m̂ = g and v̂ = g², so the step is 0.01 × g / |g| = 0.01.)

Figure 5 · Diagram

Reading it: the gradient feeds two running averages. The top path remembers direction; the bottom path remembers magnitude. Both start at zero, so for the first few steps they're too small, and the "hat" boxes correct for that. The final box divides direction by typical size, which turns every weight's step into roughly η in the right direction.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example (first step, g = 1000)
this weight's gradient at step 1000
running average of the gradient ("first moment")
running average of the squared gradient ("second moment")
how much of the old averages to keep 0.9, 0.999
raised to the step number
"m-hat, v-hat": the bias-corrected averages 1000,
square root
"epsilon", a tiny number to avoid dividing by zero
learning rate 0.01

In words: "keep a running average of the gradient and of its square, correct both for starting at zero, then step by the learning rate times the average gradient divided by its typical size."

With the numbers: , , step .

Level 3: in Python
import math
beta_1, beta_2, eta, eps, t = 0.9, 0.999, 0.01, 1e-8, 1
def first_step(g):
    # m_1, starting from m_0 = 0
    m = beta_1 * 0 + (1 - beta_1) * g
    # v_1, starting from v_0 = 0
    v = beta_2 * 0 + (1 - beta_2) * g ** 2
    # undo the pull toward zero
    m_hat = m / (1 - beta_1 ** t)
    v_hat = v / (1 - beta_2 ** t)
    return eta * m_hat / (math.sqrt(v_hat) + eps)
# m_1, v_1
round((1 - beta_1) * 1000, 6), round((1 - beta_2) * 1000 ** 2, 6)  # → (100.0, 1000.0)
# the same step every time
[round(first_step(g), 6) for g in (1000, 1, 0.001)]  # → [0.01, 0.01, 0.01]

In code: Adam keeps the two running averages and the step count for every weight and applies the five formulas in Adam.step; adam_first_step shows the first step is always the learning rate.

Why it matters Adam is forgiving: one learning rate works across weights whose gradients differ by orders of magnitude, which is the norm in transformers (embeddings, attention, layer norms all behave differently). On the valley above it reaches a loss below 10⁻¹⁰ in 300 steps while plain SGD is still around 10⁻³.

Chapter 5

Weight decay, and why AdamW exists

Weight decay is a gentle leash that pulls every weight a little toward zero each step, so the model prefers small, smooth weights over large, spiky ones (a form of regularization; see primer.ml.regularization). The classic way to add it was an L2 penalty: add λw to the gradient. With plain SGD that is the same as shrinking the weight. With Adam it isn't, because Adam divides the whole gradient, penalty included, by its typical size. The leash's strength gets rescaled away.

Worked example: one step with zero loss-gradient, so only the decay acts. w = 1, learning rate 0.1.

method decay λ = 0.1 decay λ = 0.001
AdamW (decoupled) 1 − 0.1 × 0.1 = 0.99 1 − 0.1 × 0.001 = 0.9999
Adam with L2 in the gradient 0.9 0.9 (the same!)

With L2 inside Adam, a 100× weaker penalty shrinks the weight exactly as much: λ has stopped meaning what it says.

Figure 6 · Diagram

Reading it: on the left, the decay term joins the gradient before Adam's rescaling, so it gets divided by √v̂ like everything else and its strength is distorted. On the right (AdamW), the decay bypasses the rescaling and shrinks the weights directly, so λ means exactly "shrink by η·λ per step".
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
"lambda", the weight-decay strength 0.1
how much the leash pulls this step
Adam's usual step direction 0 here (no loss gradient)

In words: "take Adam's normal step, then separately shrink every weight by learning rate times decay times the weight."

With the numbers: .

Level 3: in Python
# no loss gradient: Adam's step is 0
w, eta, adam_step = 1.0, 0.1, 0.0
for lam in (0.1, 0.001):
    # AdamW: shrink by η λ w
    print(round(w - eta * adam_step - eta * lam * w, 4))  # → 0.99 0.9999
for lam in (0.1, 0.001):
    g = lam * w
    print(round(w - eta * g / abs(g), 4))  # → 0.9 0.9

In code: Adam implements both recipes: with decoupled decay it is AdamW, otherwise it adds the L2 penalty to the gradient. one_decay_step runs the table.

Why it matters AdamW is the default optimizer for transformers. The fix was a one-line change that made weight decay behave predictably and improved generalization.

The table above, over thirty steps and every strength of leash.

Chapter 6

Warmup and cosine decay: easing on and off the gas

Think of driving an unfamiliar car. You ease onto the accelerator at first, because you don't yet know how it responds. You cruise at speed for most of the trip. Near the destination you slow gradually and glide into the parking spot. Transformers are trained the same way: the learning rate warms up linearly from 0 to its peak, then decays along a cosine curve toward a small floor.

Worked example with peak 0.001, 100 warmup steps, 1,000 steps total:

step phase learning rate
50 halfway through warmup 0.0005
100 end of warmup 0.001 (peak)
550 halfway through decay 0.0005
1000 end the floor (e.g. 0.00001)

Figure 7 · Drawn from the lesson's code

0 200 400 600 800 1000 training step 0.0000 0.0002 0.0004 0.0006 0.0008 0.0010 learning rate Linear warmup, then cosine decay end of warmup

The learning rate ramps straight up from 0 to 0.001 over 100 steps, then falls along a half cosine, passing 0.0005 at step 550 and ending near 0.00001

Reading it: the horizontal axis is the training step and the vertical axis the learning rate. The short straight ramp on the left is warmup. Then the curve rolls over the top, falls fastest in the middle and flattens as it approaches the floor, like half a cosine wave. The dots are the table above.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the learning rate at step 0.0005 at
the peak learning rate 0.001
the floor 0 (or 0.00001)
number of warmup steps 100
total steps 1000
progress through the decay, from 0 to 1
cosine: 1 at 0, 0 at , −1 at
pi, ≈ 3.1416 (half a turn, in radians)

In words: "during warmup the rate climbs in a straight line to the peak; after that it follows half a cosine wave from the peak down to the floor."

With the numbers: step 550: progress 0.5, , so .

Level 3: in Python
import math
eta_max, eta_min, T_w, T = 0.001, 0.0, 100, 1000
def eta(t):
    if t < T_w:
        # the straight ramp
        return eta_max * t / T_w
    # 0 to 1 through the decay
    progress = (t - T_w) / (T - T_w)
    return eta_min + (eta_max - eta_min) * (1 + math.cos(math.pi * progress)) / 2
[round(eta(t), 6) for t in (50, 100, 550, 1000)]  # → [0.0005, 0.001, 0.0005, 0.0]

In code: warmup_cosine returns the learning rate for any step: the straight ramp during warmup, then the half cosine down to the floor.

Why it matters at the very start, Adam's averages are unreliable and the weights are random, so a full-size step can wreck them; warmup avoids early divergence. The slow finish lets the model settle into a good minimum instead of bouncing around it.

The curve above, with the warmup, the floor and the stopping point in your hands.

Chapter 7

Gradient clipping: a speed limiter

Drag an arrow past the limit first; the formula below is what you'll see it do.

Occasionally one bad batch produces an enormous gradient, a sudden cliff in the fog. Taking a full step along it could throw the weights far from anywhere useful. Clipping is a speed limiter: if the step would be longer than a set limit, shorten it to the limit, keeping its direction.

Worked example: the gradient (3, 4) has length √(3² + 4²) = 5. With a limit of 1, scale it by 1/5 to get (0.6, 0.8), length 1, same direction. A gradient of (0.3, 0.4) has length 0.5, under the limit, so it's left alone.

Figure 8 · Diagram

Reading it: first measure the length of all the gradients together, as if they were one long list. If it's within the limit, nothing happens. If not, every gradient is multiplied by the same factor, which shortens the step without changing its direction.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
every gradient, treated as one long list (3, 4)
one entry of that list 3, 4
the norm (length) of : square every entry, add, take the square root 5
the clipping limit 1
the smaller of the two

In words: "if the gradient's length exceeds the limit, scale it down so its length equals the limit; otherwise leave it alone."

With the numbers: .

Level 3: in Python
import math
def clip(g, c):
    # ‖g‖ = √(Σ g_i²)
    norm = math.sqrt(sum(g_i ** 2 for g_i in g))
    # shrink only if too long
    scale = min(1, c / norm)
    return [round(g_i * scale, 2) for g_i in g]
clip([3, 4], c=1)  # → [0.6, 0.8]
# length 0.5: under the limit, left alone
clip([0.3, 0.4], c=1)  # → [0.3, 0.4]

In code: clip_by_global_norm measures the length of all gradients together and scales every one by the same factor when it exceeds the limit.

Why it matters large-model training runs almost always clip (a limit of 1.0 is common). It turns rare loss spikes from run-ending disasters into harmless blips. It's measured globally, across all layers together, so the update's direction is preserved (see primer.ml.deep_nets for exploding gradients).

Test yourself

6 questions

Answer each one out loud or on paper before you open it. If you can explain it, you know it.

Question 1What's the single most important hyperparameter, and what happens at each extreme?Think it through, then reveal

The learning rate. Too high: steps overshoot and training diverges or oscillates (loss spikes, NaNs). Too low: progress is so slow that training stalls or settles in a poor spot.

Question 2Why does momentum help in a narrow valley?Think it through, then reveal

Gradients across the valley flip sign every step and cancel in the velocity, while the small, consistent gradient along the valley accumulates, so the optimizer speeds up in the useful direction.

Question 3What does Adam's division by √v̂ achieve?Think it through, then reveal

It normalizes each weight's step by the typical size of its gradient, so every weight moves roughly η per step regardless of gradient scale. One learning rate then works for all parameters.

Question 4What's the difference between Adam with L2 and AdamW?Think it through, then reveal

With L2, the decay term is added to the gradient and then rescaled by Adam, so its effective strength varies per weight and λ loses its meaning. AdamW applies decay directly to the weights, outside the rescaling.

Question 5Why warm up the learning rate?Think it through, then reveal

At the start, weights are random and Adam's moment estimates are based on a handful of steps, so full-size updates can be wildly wrong and destabilize training. Ramping up gives the statistics time to settle.

Question 6What does gradient clipping protect against, and why clip the global norm?Think it through, then reveal

Rare huge gradients (exploding gradients, bad batches) that would throw the weights far off. Clipping the combined norm scales every tensor by the same factor, which preserves the update's direction.

Primary sources

The papers behind this lesson

Kingma & Ba, Adam: A Method for Stochastic Optimization (2014): Combined momentum with per-weight step sizes and bias correction into the optimizer most networks are trained with.

Read the annotated companion →The paper ↗

Loshchilov & Hutter, Decoupled Weight Decay Regularization (AdamW, 2017): Showed that L2 regularization and weight decay differ under Adam, and fixed it by decoupling the decay.

The paper ↗

Sutskever, Martens, Dahl & Hinton, On the importance of initialization and momentum in deep learning (ICML 2013): Demonstrated that well-tuned momentum makes plain SGD competitive on hard deep-network problems.

The paper ↗

Loshchilov & Hutter, SGDR: Stochastic Gradient Descent with Warm Restarts (2016): Introduced cosine learning-rate annealing, now the standard decay shape.

The paper ↗

Pascanu, Mikolov & Bengio, On the difficulty of training recurrent neural networks (2012): Analysed exploding gradients and proposed clipping the gradient norm.

The paper ↗

Researcher's shelf

Further reading

  • Sebastian Ruder, An overview of gradient descent optimization algorithms: https://arxiv.org/abs/1609.04747
  • Gabriel Goh, Why Momentum Really Works (Distill): https://distill.pub/2017/momentum/
  • Kingma & Ba, Adam: A Method for Stochastic Optimization (2014): https://arxiv.org/abs/1412.6980
  • Loshchilov & Hutter, Decoupled Weight Decay Regularization (AdamW, 2017): https://arxiv.org/abs/1711.05101
  • Loshchilov & Hutter, SGDR: Stochastic Gradient Descent with Warm Restarts (cosine schedules, 2016): https://arxiv.org/abs/1608.03983
  • Pascanu, Mikolov & Bengio, On the difficulty of training recurrent neural networks (gradient clipping, 2012): https://arxiv.org/abs/1211.5063
  • CS231n notes, Neural Networks Part 3 (parameter updates): https://cs231n.github.io/neural-networks-3/
  • PyTorch torch.optim docs: https://pytorch.org/docs/stable/optim.html

About this lesson. This is the illustrated edition of a lesson from the open-source AI Primer. Its text, figures and numbers are generated from the Primer's source at commit 048aeaa, so the two always agree: the explanation, the code that builds it and the tests that prove it.