Quantization of Neural Networks
A vision model that scores 76% top-1 accuracy on ImageNet is useless on a doorbell camera. This is true if it needs 400 MB of RAM and 30 watts to run. The weights and activations of a trained network are almost always stored as 32-bit floating-point numbers. Yet the actual information content needed to make correct predictions is far smaller. Quantization asks a simple engineering question: how few bits can represent each number in a network before accuracy collapses? The answer, surprisingly, is often 8 bits — sometimes 4 or even fewer. This comes with negligible loss in accuracy. It unlocks dramatic reductions in memory footprint, bandwidth, energy, and latency. This matters because inference, not training, dominates the lifetime compute cost of most deployed models. A recommendation system serving billions of queries per day pays an inference bill. A speech model running locally on a phone pays this bill too. So does a large language model served to millions of users. This bill is proportional to the number of bits moved and multiplied. Quantization directly attacks both terms. Fewer bits means less data movement — often the true bottleneck, since memory bandwidth is far scarcer than arithmetic throughput. Fewer bits also means cheaper integer arithmetic units on the chip. It composes naturally with other efficiency techniques such as Model Compression and Knowledge Distillation, pruning, and low-rank factorization. But it is the one technique that essentially every production inference stack — mobile, edge, and data-center — relies on. The core idea is to replace a continuous range of real numbers with a discrete grid of integers. These are related by an affine map with a scale and zero-point. The interesting engineering and research questions are all about how to choose that map. They also concern when to apply it — after training, or woven into training itself. And they concern how to handle the operations — matrix multiplies, convolutions, normalization, attention. This ensures the integer arithmetic faithfully approximates the original floating-point computation. It must do so without accumulating catastrophic error. This article gives a precise definition of the quantization map. It walks through post-training quantization (PTQ) and quantization-aware training (QAT) as algorithms with pseudocode. It derives the complexity and memory savings honestly. It works a numerical example by hand. It surveys the pitfalls that make quantization easy to get subtly wrong in practice.
- Quantization maps floating-point weights/activations to low-bit integers via an affine transform with a scale and zero-point, cutting memory and bandwidth by 2–8×.
- Post-training quantization (PTQ) calibrates the map on a small unlabeled dataset after training finishes — fast but can lose accuracy on sensitive layers.
- Quantization-aware training (QAT) simulates quantization during training using a straight-through estimator, letting the network adapt its weights to the discretization noise.
- Per-channel scales, symmetric ranges for weights, and careful handling of activation outliers are the difference between "works" and "silently degrades accuracy."
- Complexity gains come from smaller storage (bits per parameter) and cheaper integer MACs, not from fewer FLOPs — the operation count is unchanged.
- Modern LLM deployment leans heavily on 8-bit and 4-bit weight quantization (e.g., int8 weights, int4 with group-wise scales) to fit huge models into limited GPU memory.
Quantization trades numerical precision for memory, bandwidth, and energy by replacing floating-point tensors with an affine integer grid whose scale is chosen — statically after training or adaptively during training — to keep the error small enough that the network's learned function is preserved.
The idea
Formally, a quantizer maps a real value to an integer (for -bit unsigned quantization) using an affine (uniform) quantization scheme:
with scale and zero-point . The dequantized approximation is recovered by
When the scheme is called symmetric (the real range is centered at zero, natural for weights, which are typically roughly symmetric around zero); when it is asymmetric (useful for activations after a ReLU, which are one-sided and non-negative). The pair defines a piecewise-constant approximation of the identity function, and choosing them well is the entire game. Two design axes structure the space of methods. The first is granularity: one scale per tensor (per-tensor), one scale per output channel of a weight matrix or convolution filter (per-channel), or one scale per small group of elements (per-group, common in 4-bit LLM quantization). Finer granularity reduces quantization error because it lets each slice of the tensor use a range tailored to its own values, at the cost of storing more scale factors. The second axis is when calibration happens:
- Post-training quantization (PTQ) takes a fully trained floating-point model and computes for each tensor using statistics gathered from a small calibration set (a few hundred unlabeled examples), with no gradient updates to the weights. It is fast (minutes) and requires no labeled data or retraining infrastructure.
- Quantization-aware training (QAT) inserts fake-quantization operations into the forward pass during training (or fine-tuning), so the network experiences quantization noise on every mini-batch and its weights adapt to compensate. Gradients pass through the (non-differentiable) rounding operation via a straight-through estimator (STE). QAT is slower — it needs a training loop — but recovers accuracy that PTQ cannot when precision drops very low (4 bits or below) or when the network is unusually sensitive (small models, certain attention layers).
A third, related axis is the arithmetic that quantization targets: weight-only quantization (store weights as low-bit integers, but compute in higher precision after dequantizing on the fly — the dominant mode for memory-bound LLM inference) versus full integer quantization of both weights and activations, which additionally requires the accumulator and requantization steps described below to keep matrix multiplies entirely in integer arithmetic.
How it works
Step 1 — Choose the representable range. For a tensor , PTQ observes the empirical minimum and maximum () over the calibration set, or a tighter range chosen to minimize a distortion metric (mean-squared error or KL-divergence between the original and quantized histograms, as used in TensorRT-style calibration). Clipping the range tighter than the true min/max trades a larger error on outliers for a smaller step size on the bulk of the distribution — often a net win, since typical weight/activation distributions are heavy in the middle and outlier-diluted in the tails.
Step 2 — Compute scale and zero-point. For symmetric quantization to a signed -bit range :
For asymmetric quantization to an unsigned range :
Step 3 — Quantize. Apply the affine map and round-and-clip formula above to every element, producing an integer tensor plus the scalar (or per-channel vector) .
Step 4 — Requantize the matmul/conv output. If both weights (scale , zero-point , symmetric) and activations (scale , zero-point ) are quantized, an integer matmul produces a 32-bit accumulator whose value approximates up to zero-point cross-terms. This accumulator must be requantized — rescaled by and rounded — to produce the low-bit output activation for the next layer, where is that next tensor's scale.
Step 5 (QAT only) — Fake-quantize in the forward pass, straight-through in the backward pass. During training, replace each weight/activation tensor with inside the forward computation graph (so the network sees quantization noise and the loss reflects it), but define the backward pass as if inside the clipping range (gradient passes straight through) and zero outside it (clipped saturation blocks gradient flow, since those inputs did not affect the rounded output). ```
# ---------- Post-Training Quantization (PTQ) ---------- function CALIBRATE(model, calib_data, bits, granularity): stats = {} # tensor_name -> (min, max) per group hook all weight tensors and activation outputs for batch in calib_data: # a few hundred examples, no labels needed run model.forward(batch) # hooks record running min/max (or histogram) for tensor_name, values in stats.items(): if granularity == PER_TENSOR: groups = [values] elif granularity == PER_CHANNEL: groups = split_by_output_channel(values) for g in groups: s, z = compute_scale_zero_point(g.min, g.max, bits, symmetric = is_weight(tensor_name)) store(tensor_name, s, z) return quant_params function QUANTIZE_TENSOR(x, s, z, bits, signed): lo, hi = quant_range(bits, signed) # e.g. (-128, 127) for int8 signed q = round(x / s + z) return clip(q, lo, hi) function DEQUANTIZE_TENSOR(q, s, z): return s * (q - z) function PTQ_DEPLOY(model, quant_params, bits): for layer in model.layers: W_q = QUANTIZE_TENSOR(layer.weight, quant_params[layer].s_w, 0, bits, signed=True) store W_q instead of layer.weight # weights quantized once, offline return IntegerInferenceModel(model, quant_params)
# ---------- Quantization-Aware Training (QAT) ---------- function FAKE_QUANTIZE(x, s, z, bits, signed): lo, hi = quant_range(bits, signed) q = clip(round(x / s + z), lo, hi) # forward: real rounding + clipping return s * (q - z) # dequantized value, same shape as x
# custom gradient (straight-through estimator):
# d(FAKE_QUANTIZE(x)) / dx = 1 if lo <= round(x/s + z) <= hi (inside range)
# = 0 otherwise (saturated -> blocks gradient) function QAT_TRAIN(model, train_data, bits, epochs): insert FAKE_QUANTIZE nodes after every weight tensor and activation tensor initialize scales s from a short calibration pass (as in PTQ) for epoch in 1..epochs: for batch in train_data: x_q = FAKE_QUANTIZE(activations, s_x, z_x, bits, ...) W_q = FAKE_QUANTIZE(weights, s_w, 0, bits, signed=True) y = layer_op(x_q, W_q) # conv / matmul in float, on fake-quantized values loss = criterion(model.forward_rest(y), labels) grads = backward(loss) # STE makes this well-defined update(weights, grads) # weights updated in full precision periodically re-estimate s from batch statistics (EMA of min/max) return model # deploy by "freezing" final scales and truly quantizing, as in PTQ_DEPLOY ``` The two algorithms share the same mathematical machinery (`QUANTIZE_TENSOR`/`DEQUANTIZE_TENSOR`, scale/zero-point computation) but differ in when the network gets to react to the rounding error: PTQ computes the map once, after the fact; QAT lets gradient descent absorb the error into the weights themselves.
Complexity & correctness
Memory. A tensor with elements stored in bits instead of 32 bits occupies bytes instead of bytes — a factor reduction (8× for int4, 4× for int8), plus a small constant overhead for the per-channel or per-group scale/zero-point vectors, which is for group size and negligible when . For weight-only quantization of a model with parameters, storage drops from bytes to bytes, which is why 4-bit quantization of a 70-billion-parameter model shrinks it from roughly 280 GB to roughly 35–40 GB — the difference between "needs a multi-GPU server" and "fits on a single high-end GPU."
Time / throughput. Quantization does not reduce the number of multiply-accumulate (MAC) operations — a quantized convolution performs exactly the same arithmetic operations as its float counterpart. The speedup instead comes from two sources: (1) integer MACs are cheaper in silicon (smaller multiplier circuits, higher throughput per cycle on hardware with dedicated int8/int4 tensor cores) and (2) especially for memory-bound inference (small batch sizes, autoregressive decoding), the bottleneck is bytes moved from memory to compute, not FLOPs, so a 4× reduction in weight bytes can yield close to a 4× reduction in wall-clock latency even though FLOP count is unchanged. This distinguishes quantization sharply from pruning, which does reduce FLOPs.
Correctness / error analysis. The per-element quantization error of a uniform quantizer with step size satisfies (rounding to nearest introduces at most half a step of error), so the error is bounded by
This bound halves for every additional bit — the reason 8-bit quantization is nearly lossless for most vision/language models while 2-bit quantization is not: at over a typical weight range of , , small relative to typical weight magnitudes; at , , comparable to the weight values themselves. Errors from independent quantization of many weights partially cancel in a dot product (they behave like zero-mean noise under mild assumptions), so a layer's output error grows roughly as for -dimensional inner products rather than — this is the informal argument (a quantization-noise-as-additive-noise model, standard in signal processing) for why quantized networks with hundreds of input features per layer degrade gracefully rather than catastrophically at moderate bit-widths.
Why per-channel/per-group scales help — an invariant argument. Consider a weight matrix where different output channels have very different dynamic ranges (channel A's weights lie in , channel B's in ). A single per-tensor scale must cover the union of ranges, so is dictated by channel B, and channel A's small weights are quantized with a step size 40× too coarse — many of them round to exactly , destroying that channel's information. Per-channel scaling restores the invariant that each channel's local quantization error is bounded by its own local step size, decoupling channels that happen to share a tensor but not a distribution. This is why per-channel (or per-group) weight quantization is close to universal in practice, while per-tensor is only used when hardware constraints force it.
Why QAT recovers accuracy that PTQ cannot. PTQ treats quantization error as a fixed perturbation applied after optimization has already found a good floating-point minimum; there is no guarantee that this minimum is robust to that perturbation. QAT instead optimizes
directly, where is the fake-quantization operator — so gradient descent seeks a minimum that is good given the rounding noise, effectively regularizing weights toward being robust to their own discretization. The straight-through estimator is a biased gradient estimator (it is not the true gradient of the piecewise-constant `round`, which is zero almost everywhere and undefined at the jumps), but empirically it is a sufficiently good direction for optimization — the same phenomenon that makes STE work for binarized/ternary networks and for training with hard-threshold activations.
Tradeoffs, stated honestly. PTQ is cheap (no retraining, minutes of calibration) but can lose several accuracy points at 4 bits or below, and is fragile on models with large activation outliers (some transformer layers). QAT recovers most of that loss but costs a full (or partial) retraining run, needs labeled data, and adds engineering complexity (fake-quant nodes, STE, scale re-estimation schedules). Neither method reduces FLOPs; both are complementary to pruning and distillation, not substitutes for them.
Worked example
Consider a single output channel of a fully-connected layer with 5 observed float32 weights, to be quantized to signed int8 (, range ), symmetric per-channel:
Step 1 — range. .
Step 2 — scale (symmetric, signed 8-bit uses ):
Step 3 — quantize each value, , clipped to :
| (float) | clipped | ||
|---|---|---|---|
Step 4 — dequantize, :
| original | error | ||
|---|---|---|---|
Every error is at most , exactly matching the theoretical bound derived above; the largest error (0.0090) occurs for the value with the largest fractional rounding residue (), as expected.
Now suppose a second channel in the same weight matrix has values . If forced to share the first channel's per-tensor scale , every one of these values maps to — only 3–4 discrete levels are used to represent a channel whose values span barely 2% of the encoded range, and the relative error on values like is 85%. Computing a separate per-channel scale for this channel instead gives , spreading the channel across nearly the full int8 range and cutting its relative error by roughly 40×. This is the concrete mechanism behind the abstract claim in the complexity section: per-channel scaling is not a minor refinement, it is often the difference between a quantized model that matches float accuracy and one that does not.
Common pitfalls & traps
| Pitfall | Why it happens | Consequence |
|---|---|---|
| Per-tensor scale on a layer with wide inter-channel range variance | One scale must cover the widest channel | Small-magnitude channels collapse toward zero, losing signal |
| Ignoring activation outliers when calibrating | A few rare large activations (common in transformer LayerNorm inputs) dominate min/max | Step size becomes huge; typical activations get only a handful of levels |
| Applying int8 PTQ to a model with BatchNorm not yet folded | BatchNorm scale/shift not fused into preceding conv weights | Extra floating-point op reintroduces overhead and calibration mismatch |
| Using the true gradient of `round` instead of STE in QAT | `round` has zero gradient almost everywhere | No learning signal at all; weights never move |
| Re-quantizing already-quantized activations without re-estimating scale | Static scale from calibration stops matching evolving activation statistics during QAT | Accumulating clipping error, degraded accuracy over training |
| Quantizing the softmax output or attention scores naively | These are highly peaked, near 0/1 distributions | Severe underrepresentation of near-1 or near-0 probabilities |
| Comparing quantized-model accuracy on the same data used for calibration | Calibration set overlaps evaluation set | Overly optimistic accuracy estimate, doesn't generalize |
Pioneers & history
Quantization as a mathematical idea predates deep learning by decades: it is the same operation performed by every analog-to-digital converter, and its error analysis originates in classical information theory and digital signal processing. Claude Shannon's rate-distortion theory formalized the fundamental tradeoff between the number of bits used to represent a signal and the distortion incurred — the exact tradeoff neural network quantization exploits, decades later, for weights and activations instead of audio or image signals.
Origins · 1 minClaude Elwood Shannon and how this was discovered
Shannon's 1948 paper "A Mathematical Theory of Communication" and his subsequent work on rate-distortion theory established the theoretical limits of representing continuous signals with a finite number of discrete symbols — precisely the question quantization of neural network parameters answers empirically today. His framework of quantifying the tradeoff between bit-rate and reconstruction error underlies the informal "bits vs. accuracy" tradeoff curve that every quantization engineer traces out when choosing a bit-width for a deployed model.
Where this matters
Every major mobile inference framework — TensorFlow Lite, PyTorch Mobile/ExecuTorch, Core ML, ONNX Runtime — ships built-in int8 post-training and quantization-aware training pipelines, because on-device vision and speech models must fit in the memory and power budget of a phone or wearable. Data-center inference servers (NVIDIA TensorRT, Triton) use int8 and, on newer hardware, fp8 arithmetic to double or quadruple throughput per GPU for latency-sensitive services like search ranking and ad prediction. In the large-language-model era, weight-only 4-bit quantization methods (GPTQ, AWQ, bitsandbytes' NF4) are the enabling technology that lets 7B–70B parameter models run on a single consumer or prosumer GPU instead of requiring a multi-GPU cluster, directly determining which organizations can afford to serve or even experiment with these models. Speech recognition on embedded devices, autonomous-vehicle perception stacks under strict latency budgets, and federated-learning clients on low-power hardware all depend on the same affine quantization machinery described here. Quantization is also a natural complement to Model Compression and Knowledge Distillation and to pruning: a production pipeline commonly distills a large teacher into a smaller student, prunes redundant structure, and finally quantizes the result, stacking three largely orthogonal compression axes (parameters, sparsity, precision) into a single deployable artifact — a workflow directly connected to broader concerns in numerical precision and hardware-aware model design.
Ask the tutor: How would you decide, for a specific model and hardware target, whether 8-bit PTQ suffices or whether you need QAT at 4 bits?
Is quantization the same thing as pruning?
No. Pruning removes weights or structures (setting some parameters to exactly zero and often physically removing them), which reduces both the parameter count and the number of FLOPs performed. Quantization keeps every weight but represents each one with fewer bits, which reduces memory footprint and (on suitable hardware) arithmetic cost per operation, but leaves the total operation count unchanged. The two are complementary and are frequently applied together, along with distillation, in a single compression pipeline, since pruning attacks FLOPs and parameter count while quantization attacks bits-per-parameter and memory bandwidth.
Why do activations often need asymmetric quantization while weights use symmetric?
Weights, especially after typical initialization and training dynamics, tend to be roughly zero-centered and symmetric in distribution, so a symmetric range with zero-point wastes little dynamic range and simplifies the arithmetic (no zero-point cross-terms in the matmul). Activations after a ReLU or similar nonlinearity are strictly non-negative, so a symmetric scheme centered at zero would waste half the integer range on negative values that never occur. Asymmetric quantization with a nonzero places the integer grid exactly over the true one-sided range, at the cost of slightly more complex integer arithmetic during matmul/conv requantization (extra cross-terms involving must be computed and subtracted).
How low can bit-width go before accuracy collapses?
It depends heavily on the model, task, and quantization method. Standard 8-bit post-training quantization is close to lossless for most convolutional and transformer models. Weight-only 4-bit quantization with per-group scales and improved calibration methods (like GPTQ or AWQ) keeps large language models within roughly 1 point of perplexity/accuracy of the float baseline. Below 4 bits, and especially at 1–2 bits (binary/ternary networks), accuracy losses become substantial without extensive architecture-specific quantization-aware training, specialized loss terms, or restricting quantization to only the most robust layers while leaving sensitive layers (e.g., the first and last layers) in higher precision.
Does quantization-aware training always beat post-training quantization?
Not always, and not automatically. QAT typically yields equal or better accuracy than PTQ at any given bit-width because it lets the weights adapt to quantization noise, but it requires a training loop, labeled data, compute budget, and careful hyperparameter tuning (learning rate schedules interact with fake-quantization noise). For 8-bit quantization, PTQ is often already accurate enough that the extra cost of QAT isn't justified. QAT earns its cost primarily at aggressive bit-widths (4 bits and below) or on models known to be quantization-sensitive, where the accuracy gap between PTQ and full precision would otherwise be unacceptable.
What is "requantization" and why is it needed between layers?
When both a layer's weights and its input activations are quantized integers, an integer matrix multiply produces a wide accumulator (typically 32-bit) whose values are on a different, larger effective scale () than any single tensor's original scale. Requantization rescales this accumulator by and rounds it back down to the target bit-width ( being the scale chosen for the next layer's input), so that the pipeline of quantized layers stays in low-bit integer representation throughout, rather than needing to dequantize to float and requantize at every layer boundary. Handling this rescaling correctly (including any zero-point cross-terms from asymmetric quantization) is essential for both correctness and for realizing the hardware speed benefits of integer-only inference.
Can quantization be applied to gradients and optimizer states during training, not just weights/activations at inference?
Yes — this is an active and increasingly important variant, since training large models is itself expensive. Mixed-precision training already uses fp16/bf16 for most tensors while keeping a float32 "master copy" of weights for stable updates; more aggressive schemes quantize optimizer states (e.g., 8-bit Adam moments) to cut training memory substantially, which matters enormously for fitting large batch sizes or large models on limited GPU memory. This is a distinct problem from inference quantization, however, because training additionally needs to preserve small gradient updates accurately over many steps, so the tolerable error budget and the techniques used (dynamic loss scaling, stochastic rounding) differ from the calibration-based methods used for a frozen, trained model.
How does quantization interact with knowledge distillation?
They are complementary compression axes rather than competing techniques, and are frequently combined: distillation (see Model Compression and Knowledge Distillation) transfers a large teacher's learned function into a smaller student architecture, reducing parameter count and often architectural complexity, while quantization then reduces the bit-width used to store and compute that student's parameters. A common production pipeline is to distill first, quantize (PTQ or QAT) second, since starting from a smaller, already well-trained student model typically makes the final quantized model both smaller and more robust to the added quantization noise than quantizing a large teacher directly.