3.3.9 · D4Deep Learning Frameworks

Exercises — Mixed precision training

3,159 words14 min readBack to topic

Before we start, three quick reminders so every symbol below is earned:

The blueprint below is not decoration — it is the map for every underflow/overflow argument on this page. Read it as instructed.

Figure — Mixed precision training

Level 1 — Recognition

L1.1 — Name the bits

Fill in the blanks: FP16 has 1 sign bit, 5 exponent bits and 10 mantissa bits. BF16 keeps FP32's 8 exponent bits but only 7 mantissa bits.

Recall Solution

FP16 = 1 + 5 + 10 = 16 bits. BF16 = 1 + 8 + 7 = 16 bits. Both are 16 bits total, but they spend those bits differently: FP16 buys precision (10 mantissa bits), BF16 buys range (8 exponent bits, matching FP32). That is why BF16 usually needs no loss scaling — its range already covers tiny gradients. Reveal: Why does BF16 rarely need loss scaling? ::: Its 8 exponent bits give it FP32's dynamic range, so gradients almost never underflow.

L1.2 — Which copy is which?

In mixed precision, which weight copy is used for the forward pass, and which one accumulates the update?

Recall Solution
  • Forward/backward pass uses the FP16 copy (fast on Tensor Cores).
  • The optimizer update happens on the FP32 master copy, which is then cast back to FP16 for the next step. The reason is that updates are tiny and cumulative; only FP32 has enough precision to not lose them (proven in L2.1).

Level 2 — Application

L2.1 — The vanishing update

A weight sits at . Learning rate , gradient . FP16 precision is . (a) What is the true update ? (b) If you update the weight in FP16, what value do you get, and why? (c) If you update the FP32 master, what value do you get, and why is that offset representable there?

Recall Solution

(a) . (b) The new weight should be . FP16 neighbours around are spaced by one ULP . Our offset is far below half that spacing, so round-to-nearest snaps back: the nearest representable value to is still . The update rounds away to zero — the weight never moves. (c) FP32 has a 23-bit mantissa, so just below its numbers are spaced by one ULP (the exponent bucket for values in ). Our offset is larger than that ULP (), so lands on — or rounds to — a genuine FP32 grid point distinct from . That is why FP32 keeps the update while FP16 loses it: the offset clears FP32's ULP but not FP16's. After 5000 such steps the master has drifted by , now large enough to change even the FP16 copy. This is exactly why the master weight exists.

L2.2 — Sizing the loss scale

Early-layer gradients cluster near . FP16's smallest normal value (the FTZ cliff) is . (a) Without scaling, what happens to these gradients under flush-to-zero? (b) Choose the smallest power of two that lifts to a comfortable . Compute the scaled gradient. Confirm the same works for .

Recall Solution

(a) , so the magnitude falls below the smallest normal. On FTZ hardware the subnormal region is treated as zero, so the gradient flushes to — that neuron gets no learning signal. (b) We want , i.e. . The smallest power of two is . Scaled gradient: — comfortably above both the cliff and our target, so it survives in FP16. For the negative case, : identical magnitude, opposite sign — the sign bit carries it, no special handling. After backprop we divide by to recover the true on the FP32 master.

L2.3 — Overflow check at the top

Loss scale . The largest gradient magnitude in the batch (before scaling) is . FP16's max is . Does scaling this gradient overflow, and does the sign matter?

Recall Solution

Scaled magnitude . That is far below , so no overflow — and this holds for both and since the representable magnitudes are sign-symmetric. Loss scaling only overflows when the largest magnitude times exceeds ; here we have huge headroom, meaning could be pushed higher. This is the logic dynamic loss scaling uses to increase when it's been safe for many steps.


Level 3 — Analysis

L3.1 — Memory accounting

A model has million parameters, trained with the Adam optimizer (2 FP32 state tensors per parameter: momentum + variance). (a) FP32-only footprint for weights + gradients + Adam state (ignore activations). (b) Mixed-precision footprint: FP16 weights + FP16 gradients + FP32 master weights + 2× FP32 Adam state. (c) Which scheme uses more memory for this non-activation part, and by how much?

Recall Solution

Bytes: FP32 = 4, FP16 = 2. . (a) Weights + grads + Adam = B MB. (b) FP16 weights + FP16 grads + FP32 master + Adam = MB. (c) They are identical (1600 MB) for the weight/gradient/optimizer part! Mixed precision does not save memory on this bookkeeping — it even adds the FP32 master copy. The real savings come from activations (halved in FP16), which dominate deep-network memory. This is the counter-intuitive truth the parent's Example 3 hides behind its totals.

L3.2 — Where does the speedup actually live?

A training step spends 90% of its time in matrix multiplies (GEMM) and 10% in elementwise ops (which get no Tensor Core speedup). GEMM runs 4× faster in FP16. (a) By Amdahl's law, what is the overall speedup? (b) What is the theoretical ceiling if GEMM became infinitely fast?

Recall Solution

Let old total time : GEMM , elementwise . (a) New GEMM time . New total . Speedup . (b) As GEMM , total , speedup . The un-accelerated 10% caps you at no matter how fast Tensor Cores get — a classic Amdahl ceiling. See 4.2.03-Tensor-cores for what stays FP32.

L3.3 — Reading a dynamic-loss-scale log

A dynamic scaler starts at , halves on overflow, and doubles after clean steps. The log shows overflow at steps 3, 5, and 8, then no overflow for a long time. (a) What is right after step 8's overflow? (b) At step 2010 (2002 clean steps since step 8), what is ?

Recall Solution

(a) Start . Three halvings (steps 3, 5, 8): . (b) After 2000 clean steps since step 8, the scaler doubles once: . (Only one doubling — 2002 clean steps crosses the 2000 threshold exactly once.) .


Level 4 — Synthesis

L4.1 — Combine with gradient accumulation

You want an effective batch of 512 but only 128 fits per step, so you accumulate over 4 micro-batches (3.3.08-Gradient-accumulation). Loss scale . (a) Where in the pipeline do you apply — once per micro-batch, or once total? (b) After accumulating 4 scaled gradients, by what factor must you divide before the optimizer step to get the mean true gradient over 512 examples?

Recall Solution

(a) Apply to each micro-batch's loss before its backward pass — because each backward must keep its gradients out of the underflow zone. The scaling is linear, so scaled gradients simply add up: . (b) Accumulated value . To get the mean true gradient you divide by (unscale) and by the number of micro-batches (average): total divisor . If your per-micro-batch loss is already the mean over its 128 examples, dividing the sum of 4 by 4 gives the mean over 512. Order doesn't matter — just do both divisions before optimizer.step.

L4.2 — Batch norm in FP16?

2.4.06-Batch-normalization computes a mean and a variance across the batch, then normalises. Why is it standard to keep BatchNorm's statistics accumulation in FP32 even in a mixed-precision model?

Recall Solution

Variance is — a difference of two large, nearly-equal numbers. In FP16 (precision ) this catastrophic cancellation can produce garbage or even a tiny negative variance, and the following then blows up — a numerical-stability disaster. Summing many terms also loses small contributions once the running sum is large (the L2.1 phenomenon again). So the reductions (sum, mean, var) run in FP32; the cheap elementwise scale-and-shift can stay FP16. This is why frameworks whitelist/blacklist specific ops rather than casting the whole model.


Level 5 — Mastery

L5.1 — Design a scaling window

Profiling one model shows: smallest gradient magnitude , largest . FP16 usable (normal) range: . You want every gradient magnitude representable after scaling by a single power-of-two . (a) Lower bound on so clears the FTZ cliff. (b) Upper bound on so stays under the ceiling. (c) Is there a valid power-of-two ? If so, give one.

Recall Solution

(a) Need : . (b) Need : . (c) Valid window — enormous. Powers of two inside: up to . A safe choice is (check: ✓; ✓). Any in that range works for both signs of every gradient; dynamic scaling would settle somewhere here on its own.

The picture below shows exactly this: the gradient histogram as a bar, the FP16 window as a lane, and how multiplying by slides the bar into the lane.

Figure — Mixed precision training

L5.2 — When the window is empty

Suppose instead and . Repeat the bound calculation. What must you do?

Recall Solution

Lower bound: (as before). Upper bound: . Now we need and no valid exists. The gradient dynamic range itself, , is comparable to FP16's whole usable span () — in fact it exceeds it, so no single rigid slide fits both ends. Fixes: (1) switch to BF16, whose FP32-matching range needs no scaling; (2) use per-tensor / per-layer loss scales so early and late layers get different ; (3) reduce the huge at its source with gradient clipping or normalization. This is precisely the scenario that motivated BF16 hardware.

L5.3 — End-to-end sanity number

Put L2.1's logic on a schedule. With master-weight FP32 accumulation, from a cosine schedule (3.3.05-Learning-rate-schedules) currently at , gradient : compute the single-step update and confirm it is below FP16 precision (so the master is doing real work).

Recall Solution

Update . FP16 precision (one ULP at magnitude ) , and , so a direct FP16 update near a weight of order would round away entirely. The FP32 master (ULP just below ) just barely keeps a change — this is the razor's edge the whole technique balances on. Over a cosine schedule the update shrinks further as decays, making the master copy more essential late in training, not less.


Recall Quick self-audit before you leave
  • Master weights are FP32 because updates are smaller than FP16 precision (one ULP ≈ 1e-3) and would round away.
  • Loss scaling fixes underflow of tiny-magnitude gradients below the FTZ cliff (6.1e-5), not overflow.
  • Bigger risks overflow of the largest-magnitude gradients (over 65504).
  • Underflow/overflow depend only on magnitude; the sign bit rides along symmetrically.
  • The biggest memory saving in mixed precision comes from activations, not weights.
  • BF16 needs no loss scale because it has FP32's dynamic range.