Half-Precision
Half precision (FP16) stores numbers in 16 bits instead of FP32's 32, halving memory and speeding GPU math. Its limits, loss scaling, and FP16 inference.
Half precision, also called FP16, float16, or IEEE 754 binary16, is a 16-bit floating-point format with 1 sign bit, 5 exponent bits, and 10 fraction bits. Compared with standard 32-bit single precision (FP32), it halves the memory and bandwidth each number needs, and GPUs with Tensor Cores run FP16 matrix math many times faster than FP32. The trade-off is a narrow range, with a largest value of 65,504, and about three significant decimal digits. Neural networks tolerate that rounding well, which makes FP16 the standard precision for GPU inference and, combined with FP32 in mixed precision, a common way to speed up training.
How FP16 Compares with FP32#
FP32 spends 8 bits on the exponent and 23 on the fraction, so it spans roughly 10⁻³⁸ to 10³⁸ with about seven significant digits. FP16's 5 exponent bits limit normal values to between 6.1 × 10⁻⁵ and 65,504, with smaller subnormal values down to about 6 × 10⁻⁸ at reduced precision. Anything larger overflows to infinity and anything smaller rounds to zero. Converting a model shows both the benefit and the limits:
import torch
import torchvision
# ResNet-18 stores its weights in FP32 by default
model = torchvision.models.resnet18()
fp32_mb = sum(p.numel() * p.element_size() for p in model.parameters()) / 1e6
# Casting to FP16 stores every weight in 2 bytes instead of 4
model.half()
fp16_mb = sum(p.numel() * p.element_size() for p in model.parameters()) / 1e6
print(f"Weights: {fp32_mb:.1f} MB in FP32, {fp16_mb:.1f} MB in FP16")
# FP16 overflows above 65,504 and rounds to about 3 significant digits
x = torch.tensor([65504.0, 70000.0, 0.1])
print("FP16 values:", x.half().tolist())The weights shrink from 46.8 MB to 23.4 MB. In the second line, 65,504 survives as the largest FP16 value, 70,000 becomes inf, and 0.1 is stored as 0.0999755859375, the nearest value FP16 can represent.
Half Precision in Training#
Training entirely in FP16 loses accuracy or diverges for many models, because small gradients underflow to zero and weight updates that are tiny relative to the weights get rounded away. Mixed precision training fixes both: it keeps an FP32 master copy of the weights for the updates and runs the forward and backward passes in FP16, and it multiplies the loss by a scale factor so gradients stay inside FP16's range before they are unscaled in FP32. Frameworks adjust that factor automatically, as PyTorch's GradScaler does:
FP16 activations also halve the memory a batch takes during training, so the same GPU fits larger batches or higher-resolution inputs. On hardware that supports it, bfloat16 (BF16) is often preferred for training, because its FP32-sized exponent removes the need for loss scaling.
Half Precision for Inference#
Inference needs no gradients, and the activations of most vision models stay well within FP16's range, so converting a finished model to FP16 usually keeps its accuracy within a small margin of FP32 without the calibration data that INT8 model quantization requires. GPU engines such as TensorRT build FP16 engines that run on Tensor Cores, and embedded GPUs such as NVIDIA Jetson rely on it for real-time inference. CPUs gain less, because many lack fast FP16 arithmetic. Ultralytics YOLO trains on GPUs with FP16 mixed precision by default (amp=True) and uses quantize=16 for FP16 prediction and export, for example with YOLO26:
from ultralytics import YOLO
# Load the smallest YOLO26 detection model
model = YOLO("yolo26n.pt")
# Predict in FP16 on a GPU; on a CPU, PyTorch models run in FP32
results = model.predict("https://ultralytics.com/images/bus.jpg", quantize=16)
# Export an ONNX model that stores its weights in FP16
path = model.export(format="onnx", quantize=16)The exported file stores each weight in 2 bytes instead of 4, and the same quantize=16 argument produces FP16 models for TensorRT, OpenVINO, CoreML, and other export formats.
Half Precision vs. Related Formats#
- BFloat16 (BF16): The other 16-bit float, with 8 exponent and 7 fraction bits, which trades precision for FP32's range and is favored for training large models.
- FP8: 8-bit floating-point formats on recent NVIDIA GPUs that halve memory again, mostly used to train and serve large language models.
- INT8 quantization: Converts values to 8-bit integers with per-tensor or per-channel scales, which is faster and smaller than FP16 but needs calibration data or quantization-aware training to keep accuracy.
Where Half Precision Is Used#
- Edge AI: Detectors on Jetson modules and other embedded GPUs run in FP16 to reach camera frame rates within a few watts or tens of watts of power.
- Cloud inference: Serving in FP16 halves memory per model replica, so one GPU holds more replicas or larger batches.
- Large language models: A 7-billion-parameter model needs 28 GB for its weights in FP32 and 14 GB in FP16, so in FP16 it fits on a single 24 GB consumer GPU with room left for the KV cache.










