skip to content

Search

← The Engine Room

Part II — Making Training Work (2014–2020)

Mixed Precision

FP32, FP16, BF16, FP8 and loss scaling: halving the bytes doubles the model.

2017–2024 15 min read 4 papers

It is October 2017. Every network this book has followed so far — AlexNet in 2012 (Chapter 2), the optimizers that trained it (Chapter 5) — has trained in 32-bit floating point, the width GPUs happened to compute in by default. A team at NVIDIA publishes a way to run most of that arithmetic in 16 bits instead, cutting memory roughly in half and lifting throughput on the new Tensor Core hardware, provided the lost dynamic range is actively compensated for rather than ignored. That compensation, called loss scaling, opens a seven-year argument over how few bits a number needs to carry a gradient — an argument that runs through bfloat16, then 8-bit floats, and ends, for now, with frontier models pretrained natively in 8 bits.

Where We Were

Every training run in this book up to 2017 used single-precision floating point, FP32: 32 bits per number, split into 1 sign bit, 8 exponent bits, and 23 mantissa (fraction) bits. That split gives roughly seven decimal digits of precision and a dynamic range running from about 1.18×10−381.18 \times 10^{-38} up to about 3.4×10383.4 \times 10^{38} for normal values. Weights, activations, gradients, and the momentum and variance buffers Adam keeps per parameter (Chapter 5) all lived at that width, because early GPU deep learning had no cheaper practical option: the arithmetic units doing the actual multiply-adds computed natively in FP32.

That began to change on the hardware side before it changed in training practice. In 2016, NVIDIA’s Pascal architecture — specifically the Tesla P100 — added dedicated FP16 arithmetic that ran at roughly twice the throughput of FP32 on the same chip, by packing two FP16 values into paths originally built for one FP32 value. In May 2017, NVIDIA’s Volta architecture went further with the Tesla V100, introducing Tensor Cores: dedicated units that multiply 4×4 matrices in FP16 and accumulate the result in FP32, delivering roughly twelve times the tensor throughput of Pascal. The hardware to train in half the bits, at higher speed, now existed. What was missing was a way to use it without losing accuracy.

Plain FP16 arithmetic buys its compactness by shrinking the exponent field from 8 bits to 5 and the mantissa from 23 bits to 10. That is a much narrower dynamic range than FP32’s: a largest finite value of 65,504 and a smallest normal value around 6.1×10−56.1 \times 10^{-5}. Gradients in a deep network routinely span more orders of magnitude than that window covers, especially in later layers where activations and their gradients tend to be smaller. A gradient value that falls below FP16’s smallest representable magnitude does not raise an error — it silently rounds to zero, a failure called underflow, and the corresponding weight simply stops updating. An activation or gradient that grows too large overflows to infinity instead. Both failures are quiet: the first visible symptom is usually degraded accuracy discovered well after the fact, not a crash at the moment it happens. That was the problem sitting in front of anyone who wanted to use Volta’s new Tensor Cores for something more than storage.

The hardware to train in half the bits arrives before the way to use it safely Tensor Cores ship in May 2017; the recipe that keeps FP16 gradients from vanishing ships five months later. Grey monospace text names the chapter or team behind each step. 2012 AlexNet Trains in FP32 on two GPUs Chapter 2 2012 2014 Adam FP32 accumulators throughout Chapter 5 2014 2016 Pascal P100 FP16 arithmetic at 2x FP32 throughput NVIDIA 2016 May 2017 Volta V100 Tensor Cores, FP16 multiply FP32 accumulate NVIDIA May 2017 Oct 2017 Mixed precision Loss scaling makes FP16 usable Micikevicius et al. Oct 2017 hardware, or a run that used it software, still full precision the recipe that made lower precision safe
Tensor Core hardware ships in May 2017; the recipe that stops FP16 gradients from silently rounding to zero ships five months later. PNG ↓

The Breakthrough

NVIDIA’s Micikevicius et al. answered the underflow-and-overflow problem with a small set of concrete rules rather than a new number format (Micikevicius et al., 2017). Their recipe kept an FP32 master copy of every weight, used only for the optimizer update; the forward and backward passes themselves ran in FP16, with weights cast down to FP16 at the start of each iteration. Because most of the memory and compute in a training step goes into activations and their gradients, not the master weights, this halved memory for the bulk of the tensors while keeping the numerically sensitive accumulation step in full precision. The paper reported the technique matching FP32 training accuracy, with no changes to the model or most hyperparameters, across convolutional networks, recurrent language models, and GANs, on models with over 100 million parameters — with roughly half the memory footprint of pure FP32 training.

The part of the recipe that made FP16 usable for the gradients themselves was loss scaling. Because a large share of a network’s gradients cluster in a numeric range below FP16’s representable floor, the paper’s fix was to multiply the loss by a scale factor before the backward pass, which — by linearity of differentiation — multiplies every gradient in the backward pass by that same factor, shifting the whole distribution up into FP16’s representable range. After the backward pass computes the scaled gradients, they are divided back down by the same factor before the FP32 master weights are updated, undoing the shift. The scale factor itself needs tuning: too small, and gradients still underflow; too large, and gradients overflow to infinity instead. NVIDIA’s original recipe used a fixed scale value; later implementations made this dynamic, adjusting the scale up when training ran cleanly for a stretch and dropping it immediately whenever an overflow was detected.

Loss scaling was a workaround for a format problem, and Google had already built a different format that mostly avoided the problem outright. Designed for the TPU line (Chapter 3), bfloat16 (BF16) also uses 16 bits, but splits them differently: 8 exponent bits — identical to FP32 — and only 7 mantissa bits, instead of FP16’s 5-exponent, 10-mantissa split. Because BF16’s exponent field matches FP32’s exactly, its dynamic range matches FP32’s too; the values that made FP16 overflow or underflow are, by construction, still representable in BF16, at the cost of coarser precision within whatever exponent range a value falls into. In May 2019, Kalamkar et al. published the first comprehensive empirical study of BF16 training, covering image classification, speech recognition, language modeling, generative networks, and industrial recommendation systems (Kalamkar et al., 2019). They reported that BF16 training matched FP32 accuracy in the same number of iterations with no hyperparameter changes — and, notably, without loss scaling, since BF16’s range already covered what FP32’s did. NVIDIA added native BF16 Tensor Core support with the A100 (Ampere), which shipped in May 2020, and BF16 became the more common default for large-model pretraining over the next several years: less numerically fragile than FP16, at the cost of the extra precision FP16’s wider mantissa carried.

The next halving needed a purpose-built format rather than a repurposed 16-bit one. In September 2022, Micikevicius and a larger group of co-authors from NVIDIA, Arm, Intel, and Qualcomm proposed two 8-bit floating-point encodings for both training and inference: E4M3, with 4 exponent bits and 3 mantissa bits, and E5M2, with 5 exponent bits and 2 mantissa bits (Micikevicius et al., 2022). The names describe the same exponent/mantissa trade-off FP16 and BF16 made at 16 bits, now compressed into 8: E4M3 trades range for precision (a maximum finite magnitude of 448, but finer steps between values), and E5M2 trades precision for range (a maximum finite magnitude of 57,344, close to FP16’s 65,504 because both share a 5-bit exponent field, but only 2 mantissa bits). The paper’s recommendation, followed by most later implementations, was to use E4M3 for weights and activations in the forward pass, where precision matters more, and E5M2 for gradients in the backward pass, where values swing across a wider range. Tested across CNNs, RNNs, and Transformers up to 175 billion parameters — GPT-3 scale — FP8 training matched the accuracy of 16-bit training, and the paper additionally showed the same formats worked for post-training quantization at inference time.

FP8 needed dedicated silicon to be worth the added bookkeeping. NVIDIA’s H100 (Hopper), announced in March 2022, shipped fourth-generation Tensor Cores with native FP8 support and a Transformer Engine that dynamically recasts tensors between FP8 and 16-bit formats layer by layer. On data types both chips support, H100’s Tensor Cores deliver roughly twice A100’s per-SM throughput; FP8, a format A100 lacks entirely, pushes that to roughly four times A100’s per-SM throughput. A year and a half later, Peng et al. showed what that silicon could do at LLM scale: their FP8-LM framework extended FP8 beyond weights and activations to gradients, optimizer states, and distributed communication, applied in three incremental levels of adoption (Peng et al., 2023). Training a 175-billion-parameter GPT model on H100 hardware, FP8-LM reported a 39% reduction in real GPU memory usage and training 75% faster than the BF16-based Megatron-LM framework — and 37% faster than NVIDIA’s own Transformer Engine.

By December 2024, FP8 pretraining had reached frontier scale in a publicly documented model: DeepSeek-V3, a 671-billion-parameter mixture-of-experts model (Chapter 13), stored weights in FP8 and ran matrix multiplications in FP8 with FP32 accumulation, while keeping activations and gradients in BF16 and reserving FP32 for a handful of numerically sensitive operations. Its authors reported a relative loss error against a BF16 baseline consistently below 0.25% — within normal training-to-training variance — while contributing to a total reported training cost of $5.576 million across 2.788 million H800 GPU-hours. The seven years from a single fixed loss-scale value in one 2017 recipe to per-tensor, mixed-granularity scaling across a model with hundreds of billions of parameters is the arc this chapter traces.

Two ways down from FP32 converge before the next halving begins FP16 needs active correction; BF16 avoids the problem by construction; both feed the format that needed a purpose-built encoding. Grey monospace text is the year each format or framework shipped. FP32 baseline 8 exponent, 23 mantissa bits FP16 Needs loss scaling 2017 BF16 Wide exponent, no loss scaling needed 2019 FP8 E4M3 and E5M2 formats 2022 FP8 frameworks Weights, gradients, optimizer states 2023 671B at FP8 DeepSeek-V3 pretrains natively 2024
FP16 and BF16 are two different answers to the same narrowed exponent; both roads lead to the format that needed a bespoke design rather than a repurposed one. PNG ↓

The Mathematics

Every format in this chapter is an instance of the same binary floating-point representation, differing only in how the available bits are split between exponent and mantissa:

x=(−1)s (1+m) 2 e−bx = (-1)^{s}\,(1+m)\,2^{\,e-b}
SymbolMeaning
xxthe represented number — weight, activation, or gradient
sssign bit
mmmantissa fraction, built from the mantissa bits
eeexponent field, an unsigned integer
bbfixed exponent bias for the format

That trade-off, applied to the five formats this chapter covers, produces very different working ranges from the same 8, 16, or 32 bits:

FormatSignExponent bitsMantissa bitsMax finite magnitudeSmallest normal magnitudeNeeds loss scaling
FP321823≈3.4×1038\approx 3.4 \times 10^{38}≈1.18×10−38\approx 1.18 \times 10^{-38}No
FP16151065,504≈6.1×10−5\approx 6.1 \times 10^{-5}Yes
BF16187≈3.39×1038\approx 3.39 \times 10^{38}≈1.18×10−38\approx 1.18 \times 10^{-38}No
FP8 E4M3143448≈0.0156\approx 0.0156Yes
FP8 E5M215257,344≈6.1×10−5\approx 6.1 \times 10^{-5}Yes

FP16 and FP8 both need active range management precisely because they narrow the exponent field relative to FP32; BF16 does not, because it keeps FP32’s exponent field untouched and only narrows the mantissa.

Loss scaling, the technique that makes FP16 (and FP8) training usable despite that narrow exponent, is a three-step procedure: scale the loss up before backpropagation, let the scaling propagate through the gradients by linearity, then unscale before the weight update happens in FP32:

Ls=S⋅L(θ)gs=∇θLsθt+1=θt−η⋅gsS\begin{aligned} L_s &= S \cdot L(\theta) \\ g_s &= \nabla_\theta L_s \\ \theta_{t+1} &= \theta_t - \eta \cdot \dfrac{g_s}{S} \end{aligned}
SymbolMeaning
L(θ)L(\theta)true training loss, function of parameters θ\theta
SSscale factor applied before backpropagation
Ls,gsL_s, g_sscaled loss and the resulting scaled gradient
η\etalearning rate
θt,θt+1\theta_t, \theta_{t+1}FP32 master weights before and after the update

FP8’s exponent field is narrower still than FP16’s, so a single global scale factor chosen once at the start of training tends to be too coarse — the FP8 training frameworks that followed the 2022 formats paper instead recompute a scale per tensor, from that tensor’s own recent magnitude:

Sx=FP8max⁡max⁡(∣x∣)S_x = \dfrac{\mathrm{FP8}_{\max}}{\max(|x|)}

What It Enabled

Each format halving did not replace the one before it outright; FP16 and BF16 both remain in production use, and FP8 sits alongside both rather than eliminating them. What changed with each step was how much memory and communication bandwidth a model of a given accuracy required, and how large a model a fixed hardware budget could train at all.

TechniqueYearPrecisionReported resultModel scale tested
Mixed Precision Training2017FP16 + FP32 master weightsRoughly 2x memory reduction, matched FP32 accuracy100M+ parameters (CNN, RNN, GAN)
BF16 empirical study2019BF16, no loss scalingMatched FP32 accuracy, no hyperparameter changesFive domains, including language modeling
FP8 Formats for Deep Learning2022FP8 (E4M3 / E5M2)Matched 16-bit training qualityUp to 175B parameters
FP8-LM2023FP8 across weights, gradients, optimizer states39% less GPU memory, 75% faster than BF16 Megatron-LM175B-parameter GPT on H100
DeepSeek-V32024Native FP8 pretrainingRelative loss error under 0.25% vs. BF16 baseline671B-parameter MoE, $5.576M total training cost

Halving the bytes per parameter has consequences beyond a single GPU’s memory. Distributed data parallelism moves every gradient across the network once per step (Chapter 8); a model trained in FP16 or BF16 instead of FP32 roughly halves that communication volume for free, on top of whatever the memory savings do for how large a model or batch fits per device. The same halving compounds again inside ZeRO’s memory partitioning (Chapter 10), whose bytes-per-parameter accounting for weights, gradients, and optimizer states is directly a function of which precision each of those tensors is stored in. FlashAttention-3, released in July 2024, added Hopper-specific FP8 kernels to attention’s own matrix multiplications, extending this chapter’s numerics into the kernel layer covered in Chapter 12. And where this chapter’s formats change the arithmetic used during training, Chapter 15 covers a related but distinct problem: compressing an already-trained model’s weights, often to formats like int8 or 4-bit integers, purely to make inference cheaper.

The pattern of halving did not stop at 8 bits. NVIDIA’s Blackwell architecture (B200/GB200), announced in March 2024, added native FP4 support to its Tensor Cores, continuing the same exponent-and-mantissa trade-off one level further down. Whether that step follows the FP16 or the BF16 path — needing aggressive per-tensor scaling to stay usable, or finding a wider-range 4-bit split that avoids it — is a question for the hardware-and-model story told in full in Chapter 20.

Key Papers

PaperAuthorsYearLinkWhy it matters
Mixed Precision TrainingMicikevicius et al.2017arXiv:1710.03740Introduces FP32 master weights plus loss scaling, the recipe that made FP16 training on Tensor Core hardware usable without an accuracy loss
A Study of BFLOAT16 for Deep Learning TrainingKalamkar et al.2019arXiv:1905.12322First comprehensive empirical validation that BF16’s wider exponent, narrower mantissa split matches FP32 accuracy without loss scaling
FP8 Formats for Deep LearningMicikevicius et al.2022arXiv:2209.05433Defines the E4M3 and E5M2 8-bit float formats and shows FP8 matching 16-bit training quality up to 175B parameters
FP8-LM: Training FP8 Large Language ModelsPeng et al.2023arXiv:2310.18313Extends FP8 to gradients, optimizer states, and distributed communication, reporting large memory and speed gains at GPT-175B scale on H100

PM Lens

Papers referenced