3.3.9 · D2Deep Learning Frameworks

Visual walkthrough — Mixed precision training

2,322 words11 min readBack to topic

We are unpacking the central trick from Mixed precision trainingloss scaling — and the FP32 master-weight partner that makes it safe. Prerequisites we lean on: 1.3.07-Numerical-stability (why small numbers vanish), 4.2.03-Tensor-cores (why we want FP16 at all), and 3.3.05-Learning-rate-schedules (where comes from).


Step 1 — What a floating-point number actually is

WHAT. A number in a computer is stored as three pieces: a sign (plus or minus), an exponent (which power-of-two "bucket" the number lives in), and a mantissa (the fine detail inside that bucket). Think of a ruler: the exponent picks which segment of the ruler you're standing on, the mantissa picks the tick within that segment.

WHY. Everything about mixed precision comes from one fact: FP16 has fewer bits than FP32, so both its reach (biggest/smallest number) and its resolution (smallest gap between neighbours) shrink. We have to see those two shrinkages before anything else makes sense.

PICTURE. The number line below is drawn in log-space. Blue marks the range FP32 can reach; orange marks the much narrower range of FP16. Notice orange stops on the small-number side well before the interesting gradients live.

Figure — Mixed precision training

Step 2 — Where a gradient comes from, and why some are tiny

WHAT. A gradient is "how much the loss changes when we nudge weight ." The backward pass builds it by the chain rule, multiplying one small factor per layer:

  • — the single loss number at the top of the network.
  • — the activation (output) of layer .
  • each fraction — "how layer 's output moves when its input moves."

WHY. Each factor is often less than . Multiply many sub- numbers and the product shrinks exponentially with depth. That is why early-layer gradients land around — right in the danger zone we drew in Step 1.

PICTURE. Watch the green gradient value slide left (smaller) as we move from the last layer toward the first. The orange FP16 floor is a cliff; gradients that fall off it become exactly .

Figure — Mixed precision training

Step 3 — The trick: multiply the loss, and let the chain rule spread it

WHAT. Pick a scalar (say ) and train on a scaled loss instead of .

WHY here, why this tool? We want to move every gradient up into FP16's range. Why scale the loss and not each gradient? Because the chain rule is linear in the top factor:

  • — one multiply, at the very top of the graph.
  • pulls straight through the derivative because it's a constant — every gradient in every layer comes out multiplied by automatically, for the price of a single multiplication.

That is the elegance: one nudge at the top, and the whole backward pass lifts together.

PICTURE. Same gradients as Step 2, but each is now . The green dots slide right, clearing the orange floor.

Figure — Mixed precision training

Step 4 — Unscale before you touch the weights

WHAT. After the backward pass we hold . Before updating anything we divide it back:

WHY. The scaling was a temporary lift to survive FP16. The optimizer must see the real gradient, or the effective learning rate would silently be times too big. We do this division in FP32, where the number now sits comfortably.

PICTURE. A round-trip: the gradient rides up by through the FP16 backward pass (orange arc, safe above the floor), then comes back down by into FP32 (blue arc) landing on its true value.

Figure — Mixed precision training

Step 5 — Why we still need FP32 master weights

WHAT. Even with a healthy gradient, the update can be far tinier than the weight itself. Keep the authoritative weights in a FP32 master copy; only cast a FP16 copy for the fast forward/backward math.

WHY. The update rule is

  • — current weight, often near (so FP16 resolution near it is ).
  • (the Greek letter "eta") — the learning rate: a small fixed number that sets how big a step we take, e.g. . (It comes from your learning-rate schedule.)
  • — the gradient at step .
  • — the actual step; with this is .

Adding to a number near in FP16 rounds right back to : the step is smaller than the gap between FP16 neighbours. This is a different failure from Step 2 — not the gradient underflowing, but the sum swallowing the update.

PICTURE. Left panel: in FP16 the update arrow is shorter than the tick spacing — snaps back. Right panel: in FP32 the ticks are dense enough that the same update actually moves .

Figure — Mixed precision training

Step 6 — The full loop, and the edge cases

WHAT. Chain the pieces into one training step:

cast down

multiply by S

divide by S in FP32

FP32 master weights

FP16 weights

forward pass FP16

loss L

scaled loss

backward pass FP16

scaled grads

true grads

optimizer step

WHY / edge cases. Two things can go wrong at the extremes, and both have fixes:

  • Overflow (too much scale). If is too big, a large gradient times exceeds FP16's ceiling () and becomes inf/nan. Fix: dynamic loss scaling — when you see inf/nan, skip this step and halve . If many steps pass cleanly (e.g. 2000), double to reclaim headroom. auto-tunes to each model. (Halving and doubling keep a power of two, preserving the exact round-trip from Step 4.)
  • Zero / degenerate gradient. A genuinely-zero gradient () stays under any scale () — scaling can't invent signal that isn't there, and that's correct: those weights shouldn't move.
  • BF16 escape hatch. Batch normalization statistics and softmax sums can overflow FP16's small range. BF16 has FP32's full range (just coarser mantissa), so on hardware that supports it you often skip loss scaling entirely — the floor problem never appears.

PICTURE. The dynamic-scaling controller as a sawtooth: climbs by doubling while stable, drops by halving on each overflow, hovering just under the danger line.

Figure — Mixed precision training

The one-picture summary

Figure — Mixed precision training

One gradient's journey: born tiny (below the FP16 floor) → lifted by into the safe FP16 band → carried through the backward pass → dropped by in FP32 → added to an FP32 master weight where even a step counts. Fast math in FP16, careful bookkeeping in FP32.

Recall Retell the whole thing in plain words (Feynman check)

FP16 is a short ruler: it can't measure very tiny things (they read as zero) and its ticks are far apart (tiny changes disappear). Gradients — especially in early layers — are exactly those tiny things. So before the backward pass I multiply the loss by a big number ; because the chain rule carries constants straight through, every gradient comes out times bigger and now fits on the ruler. I pick as a power of two so the multiply and later divide are exact exponent shifts. Right before I change the weights I divide by to get the honest gradient back, and I do that in FP32, the long precise ruler. I also keep the real weights in FP32 the whole time, because a weight update can be smaller than an FP16 tick and would otherwise vanish. If ever gets so big that gradients overflow to infinity, I halve it and try again; if things stay calm, I double it to keep the most headroom. BF16 skips the lifting trick because it already has FP32's reach.

Recall

Why scale the loss instead of each layer's gradient? ::: One multiply at the top; the chain rule multiplies every downstream gradient by that same constant automatically. What are the two different FP16 failures? ::: (1) small gradients underflow to zero below the FP16 floor; (2) small weight updates get swallowed because they're below the spacing near the weight. What is the smallest normal FP16 value, and the smallest subnormal? ::: Normal ; subnormal . Why must the loss scale be a power of two? ::: Multiplying/dividing by only shifts the exponent, leaving the mantissa untouched — the scale/unscale round-trip is then exact. What does dynamic loss scaling do on an overflow? ::: Skip the step and halve ; double after a long stable run. Why does BF16 often need no loss scaling? ::: It shares FP32's exponent range, so gradients don't underflow — only precision is reduced.


Related: Hinglish version · 3.3.10-Gradient-checkpointing · 5.1.02-Model-parallelism