Half-precision floats train faster and use half the memory. They also can't represent a gradient like at all — it just rounds down to zero. So how does anyone train in FP16 without every small gradient silently vanishing?
This gradient is real — it's genuinely pushing a weight in a direction — but it's too small for the low-precision format to store. Cast it directly and it becomes exactly : not "small," gone. A gradient of means that weight gets no update at all.
Loss scaling multiplies the loss (and hence every gradient) by a constant before the backward pass, then divides the optimizer's update by the same constant afterward:
- — the true, full-precision gradient.
- — the loss scale, a constant chosen large enough to lift small gradients above FP16's representable range.
- — rounding to low precision, including flushing anything still too small to zero.
- — the gradient actually applied to the weight, after undoing the scale.
- Scale up before storage
is large enough to survive being rounded into FP16, even though alone wasn't.
- Scale back down before the update
Dividing by afterward recovers a value extremely close to the original — the update itself still happens in full precision, only the storage was ever low-precision.
Drag the scale slider up from . Below a threshold, the recovered gradient is a flat — the update is silently skipped. Cross it, and the true gradient comes back essentially exactly, every time, for every larger scale you try afterward.
- Without loss scaling
A true gradient of is smaller than the toy format's representable floor. Cast directly, it stores as exactly — that parameter stops learning for this step, and nothing downstream indicates why.
- With loss scaling at 2×
Scaled up to , the value survives casting. Divided back down by , it comes back out as — the exact true gradient, recovered.
- A second problem: the weight itself
Loss scaling protects gradients in flight, but the weight being updated has its own precision problem. A weight of stored directly in the toy low-precision format rounds to , and a tiny update of added to still rounds right back to . Applying that same update for 10 steps down two different paths:
- fp16-only: every update rounds away, so the stored weight never moves from
- full-precision master copy: the same 10 updates accumulate in full precision, reaching
A real, accumulated difference of about between the two paths.
Raise the loss scale until the recovered gradient matches the true gradient (5e-5) instead of underflowing to zero.
Mixed precision training isn't "just use smaller floats" — it's two separate fixes stacked together. Loss scaling keeps small-but-real gradients from being flushed to zero on the way through the backward pass. A full-precision master copy of the weights keeps small-but-real updates from vanishing on contact once they're applied. Drop either fix and FP16's speed and memory advantages come with silent, hard-to-diagnose training stalls. That's the last individual fix this part introduces — the closing chapter puts it back to back with normalization, warmup, and decoupled decay, all at once, on a single network that won't train without them.