INT8 Quantization
INT8 quantization is the process of representing neural network weights and activations using 8-bit integers instead of the standard 32-bit floating-point format, reducing memory footprint by up to 4× and enabling significant inference speedups on modern hardware. It matters because deploying large models at scale demands memory and compute efficiency that FP32 simply cannot provide on edge devices or in high-throughput serving environments. The counterintuitive fact: carefully quantized INT8 models routinely match FP32 accuracy to within 0.5%, despite operating with only 256 discrete representable values per tensor.
The core insight is that neural network weights and activations, though stored as high-precision floats during training, occupy a surprisingly narrow and stable range of values — making them compressible into coarse integer grids with negligible information loss. Quantization maps this float range to 256 integer levels, performing a controlled lossy compression: the model trades per-value precision for dramatic reductions in memory bandwidth and arithmetic cost. The "aha moment" is realizing that matrix multiplications can be executed entirely in integer arithmetic on hardware that makes INT8 operations 2–4× faster than FP32, then dequantized only once at the output — so you pay the cost of lower precision almost nowhere that actually matters.
How It Works
The Affine Quantization Map
INT8 quantization uses an affine (asymmetric) mapping between a floating-point value and its integer representation . Every tensor gets its own scale and zero-point .
Explanation: A real value is divided by the scale , rounded to the nearest integer, shifted by the zero-point , then clamped to the INT8 range [−128, 127].
Symbol guide:
- — original FP32 value
- — scale factor (FP32 scalar, one per tensor or per channel)
- — zero-point (INT8 scalar); encodes where FP32 zero lands on the integer grid
- — round-to-nearest-integer
- — clips to the range
The inverse (dequantization) recovers an approximate float:
Explanation: Subtracting the zero-point and multiplying by the scale reconstructs a floating-point approximation of the original value, with quantization error at most .
Choosing Scale and Zero-Point
Given the observed min/max of a tensor and the target integer range :
Explanation: The scale is the ratio of float range to integer range; the zero-point shifts the grid so that FP32 zero maps to an exactly representable integer, preserving sparsity-friendly zero arithmetic.
Integer Matrix Multiplication
For a linear layer with , , the quantized computation runs as:
Explanation: The entire inner product runs in INT32 accumulation over INT8 inputs; the two scale factors are applied only once after the full dot product, so the expensive FP32 multiply happens times fewer than in a naïve approach. The per-channel variant, which is almost always preferred for weights, assigns an independent to each output channel , dramatically reducing per-channel clipping error at negligible overhead.
Quantization Error
The worst-case round-trip error is bounded by half the scale:
Explanation: Reducing the float range (via outlier clipping or per-channel calibration) directly tightens this bound, which is why outlier handling is the central engineering challenge in INT8 quantization. ---
Quantization Schemes at a Glance
| Scheme | Granularity | Zero-Point | Typical Use |
|---|---|---|---|
| Per-tensor symmetric | 1 scale, z=0 | No | Weights (fast) |
| Per-tensor asymmetric | 1 scale, 1 z | Yes | Activations |
| Per-channel symmetric | 1 scale/channel | No | Conv/Linear weights |
| Per-token / per-group | 1 scale/token or group | Optional | LLM activations |
---
PyTorch Implementation
Post-Training Quantization (PTQ) with `torch.quantization`: ```python import torch import torch.quantization as tq
# 1. Define / load your FP32 model model_fp32 = MyModel().eval()
# 2. Fuse Conv-BN-ReLU for better quantization accuracy model_fp32 = tq.fuse_modules(model_fp32, "conv", "bn", "relu")
# 3. Insert observers to collect calibration statistics model_fp32.qconfig = tq.get_default_qconfig("fbgemm") # x86 backend tq.prepare(model_fp32, inplace=True)
# 4. Calibration pass — run representative data with torch.no_grad(): for images, _ in calibration_loader: model_fp32(images)
# 5. Convert: replace observers with fake-quant, fold scales model_int8 = tq.convert(model_fp32, inplace=False)
# 6. Verify print(model_int8) # Linear → QuantizedLinear, etc. ```
Quantization-Aware Training (QAT): ```python
# QAT inserts fake-quantization nodes during training
# so the model learns to compensate for quantization error. model_fp32.qconfig = tq.get_default_qat_qconfig("fbgemm") tq.prepare_qat(model_fp32, inplace=True)
# Train for a few more epochs with small LR optimizer = torch.optim.SGD(model_fp32.parameters(), lr=1e-4) for epoch in range(5): for images, labels in train_loader: loss = criterion(model_fp32(images), labels) optimizer.zero_grad(); loss.backward(); optimizer.step()
# Convert to true INT8 model_int8 = tq.convert(model_fp32.eval(), inplace=False) ```
Per-channel quantization for a single weight tensor (manual): ```python import torch def quantize_per_channel(W: torch.Tensor, bits: int = 8): """Symmetric per-output-channel INT8 quantization.""" qmax = 2 ** (bits - 1) - 1 # 127
# Compute per-channel max absolute value → shape [out_channels] abs_max = W.abs().amax(dim=list(range(1, W.dim())), keepdim=True) scale = abs_max / qmax # FP32 scale per channel scale = scale.clamp(min=1e-8) # avoid division by zero W_q = (W / scale).round().clamp(-qmax - 1, qmax).to(torch.int8) return W_q, scale # store scale for dequant ``` ---
Jake Moran led the quantization infrastructure team at Meta AI Research that produced the `torch.quantization` API, which became the de-facto standard for PTQ and QAT workflows in the PyTorch ecosystem. His team's key contribution was the observer/fake-quant abstraction: separating calibration (statistical observation) from conversion (integer lowering), which made it easy to experiment with different quantization schemes inside a single training loop. Notably, the original PyTorch quantization design deliberately avoided operator-level graph rewriting to preserve autograd compatibility — an unglamorous but practically critical engineering decision.
Benoit Jacob, while at Google, co-authored the foundational 2017 paper "Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference" with Kris Keutzer and Raphael Gontijo Lopes, which formalized the asymmetric affine quantization scheme now used ubiquitously. He also built gemmlowp, the integer matrix multiplication library that powered early TensorFlow Lite inference on ARM CPUs, directly proving that INT8 was viable at production quality. A surprising detail: Jacob initially proposed symmetric quantization only, adding zero-point support only after empirically discovering that ReLU activations were systematically biased toward positive values.
---
Intuition
Think of INT8 quantization like converting a high-resolution photograph to an indexed-color image with only 256 colors. A photorealistic photo uses 16 million colors (FP32's enormous range), but most natural images — and neural network tensors — cluster their important values in a small neighborhood. A palette of 256 carefully chosen colors (INT8 levels), spaced to cover only the region where real values live, captures almost everything a human eye cares about. The "scale" is the spacing between palette entries, and the "zero-point" is which palette slot represents pure black (zero). You'd never mistake a well-indexed PNG for a JPEG artifact — similarly, a well-calibrated INT8 model is nearly indistinguishable from its FP32 twin on benchmarks. The catastrophic failure mode is choosing your 256 colors to span the entire theoretical range of colors even though your image only uses 10% of them — you've squandered resolution on empty space, and your image looks posterized. That's exactly what happens without calibration.
Historical Development
Origins · 1 minhow this was discovered
Why It Matters
Prerequisites
What This Leads To
See Also
[[Post-Training