Mixed Precision
Mixed precision trains neural networks with 16-bit math and FP32 master weights, cutting memory use and training time. Covers loss scaling, BF16, and AMP.
Mixed precision is a training technique that runs most of a neural network's math in a 16-bit floating-point format, either FP16 (half precision) or BF16, while keeping a 32-bit (FP32) master copy of the model weights and running numerically sensitive operations in FP32. 16-bit values take half the memory and bandwidth of FP32, and the Tensor Cores in modern GPUs multiply them far faster, so mixed precision shortens training and fits larger batch sizes in memory while matching FP32 accuracy. The recipe described by Micikevicius et al. in 2018, with FP32 master weights, FP16 arithmetic, and loss scaling, is what deep learning frameworks now automate as Automatic Mixed Precision (AMP).
How Mixed Precision Works#
Three pieces let backpropagation and weight updates in 16-bit training match the accuracy of FP32 training:
- FP32 Master Weights: The optimizer applies each update to an FP32 copy of the weights. A tiny update added directly to an FP16 weight would often round away, because FP16 keeps only about three significant decimal digits.
- Per-Operation Precision: An autocast context runs matrix multiplications and convolutions in 16-bit, where Tensor Cores give the biggest speedup, and keeps reductions, softmax, and the loss in FP32.
- Loss Scaling: FP16 cannot represent magnitudes below about 6 × 10⁻⁸, so many small gradients would underflow to zero. Multiplying the loss by a scale factor before backpropagation shifts every gradient into FP16's range, and the gradients are divided by the same factor before the weight update.
In PyTorch AMP, torch.autocast handles the per-operation casting and GradScaler handles loss scaling:
import torch
from torch import nn
# Use FP16 on an NVIDIA GPU and BF16 on CPU
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.float16 if device == "cuda" else torch.bfloat16
model = nn.Linear(512, 10).to(device) # master weights stay in FP32
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
scaler = torch.amp.GradScaler(device, enabled=dtype == torch.float16) # loss scaling for FP16 only
x = torch.randn(64, 512, device=device)
with torch.autocast(device_type=device, dtype=dtype):
out = model(x) # the matrix multiply runs in 16-bit
loss = out.float().pow(2).mean()
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
print(f"Output: {out.dtype}, weights: {model.weight.dtype}, gradients: {model.weight.grad.dtype}")On CPU the snippet prints torch.bfloat16 for the layer output and torch.float32 for both the weights and their gradients; on an NVIDIA GPU the output is torch.float16. The 16-bit math and the FP32 master weights coexist in one training step.
PyTorch's GradScaler adjusts the scale dynamically. It starts at 65,536, halves the scale and skips the step whenever the scaled gradients contain inf or NaN values, and doubles the scale after 2,000 consecutive steps without overflow, so the factor settles just below the point where gradients overflow.
FP16, BF16, and Lower Precisions#
FP16 spends 5 bits on the exponent and 10 on the mantissa, so it is precise but overflows above 65,504. BF16 keeps FP32's 8 exponent bits, covering the same range up to about 3.4 × 10³⁸, with only 7 mantissa bits, so it needs no loss scaling at the cost of coarser rounding. BF16 runs on NVIDIA GPUs from the Ampere generation onward, on Google TPUs, and on many recent CPUs, and it is the usual training format for large language models. The same idea continues to FP8 on Hopper and Blackwell GPUs through NVIDIA's Transformer Engine, and to FP4 formats such as NVFP4 on Blackwell.
Mixed Precision in Practice#
The main practical risk with AMP is a model or GPU that produces NaN losses in 16-bit. Ultralytics YOLO26 training enables AMP by default with amp=True, first comparing FP32 and AMP predictions from a YOLO26n model on CUDA GPUs and falling back to FP32 if they disagree or if the GPU belongs to a series known to produce NaN losses under AMP, such as the GTX 16 series. Setting amp="bf16" switches to BF16 autocast with loss scaling turned off.
Mixed precision is widely used wherever training is memory- or time-bound. Teams building autonomous vehicles train object detection models on millions of high-resolution frames, where halving activation memory allows larger images and batches. In medical image analysis, 3D CT and MRI volumes often exceed GPU memory in FP32, and 16-bit activations make segmentation models on whole volumes trainable.
Mixed Precision vs. Related Concepts#
- Model Quantization: Quantization converts a trained model to low-precision integers such as INT8 for faster inference, while mixed precision speeds up training and keeps floating-point weights.
- Quantization-Aware Training: QAT simulates integer rounding during training so the model keeps its accuracy after INT8 deployment.
- Half Precision: Half precision names the FP16 number format itself, and mixed precision is the training technique that combines it with FP32. Training purely in FP16, without FP32 master weights or loss scaling, loses small updates to rounding and small gradients to underflow, and often fails to converge.










