Skip to content
NLEN
Illustration: Quantization in production: from 16-bit to 4-bit precision

Quantization in production: from 16-bit to 4-bit precision

By Ivo Donker — compiled with AI assistance (Claude & Gemini)

In the core canon article on the shift toward compact architectures, the analysis of small language models and efficient inference, it was already made clear that more compact parameters structurally reduce AI operating costs. Where model distillation and pruning intervene in the architecture itself, quantization directly modifies the numerical representation of weights and activations. In modern production pipelines, running unquantized 16-bit floating-point formats (FP16 or BF16) for large-scale LLM workloads is rarely defensible economically or operationally. By reducing weights to 8-bit or 4-bit precision, density per GPU doubles or quadruples.

Yet reducing numerical precision is not a free optimization. As we descend from 16-bit floating point to 8-bit integers and subsequently to 4-bit quantization methods, a fundamental tension arises between memory savings, compute throughput, mathematical rounding errors, and task-specific degradation. This article dissects the precise mechanics of modern post-training quantization (PTQ) techniques, their interaction with hardware architectures, the implications for the KV cache, and the methodologies required to measurably manage quality loss in production environments.

The mathematical foundation: linear transformations and rounding noise

Quantization transforms a continuous or high-precision set of numbers into a discrete, low-precision number space. In a standard neural network, weights are typically stored in 16-bit floating point (IEEE 754 FP16 or Brain Floating Point BF16). An FP16 value consists of 1 sign bit, 5 exponent bits, and 10 mantissa bits, covering a broad dynamic range. In uniform affine quantization to, for example, an 8-bit integer (INT8), we map this dynamic range onto a scale of 256 integers (-128 to 127 for signed integers, or 0 to 255 for unsigned integers).

The standard mathematical formula for uniform quantization relies on two crucial parameters: a scale factor (scale, $S$) and a zero-point offset (zero-point, $Z$). The transformation from a real-valued weight $x$ to an integer value $q$ is defined as:

q = clip(round(x / S) + Z, q_min, q_max)

To approximate the original value during matrix multiplications (dequantization), we apply the inverse transformation:

x_approx = S * (q - Z)

When the zero-point $Z$ equals zero, this is referred to as symmetric quantization. This significantly simplifies hardware-level multiplications, as no additional offset term needs to be processed during tensor dot-product calculations. Asymmetric quantization, on the other hand, uses a dynamic zero-point ($Z \neq 0$), which is valuable when weight distributions are skewed around zero, as is often the case with activations following a ReLU or GeLU layer.

The loss of representational capacity, also known as quantization noise or rounding noise, is determined by the distance between consecutive quantization steps ($\Delta = S$). The fewer bits available, the larger $\Delta$ becomes and the more information is lost in the fine-grained tails of the weight distribution.

Weight distributions and the challenge of 'outlier features'

In language models up to 7 billion parameters, the weight matrices typically exhibit a more or less predictable Gaussian bell-shaped distribution. However, as models grow larger (especially beyond the 6.7B and 13B thresholds), a striking emergent phenomenon occurs: the emergence of systematic numerical outliers (emergent outlier features). In specific transformation layers, a fraction of the hidden dimensions (often less than 0.1% of the channels) exhibits activation values up to 100 times larger than the average activation magnitude.

When we apply a uniform matrix-wide scale factor $S$ to a layer with extreme outliers, that single outlier forces an enormously large step size $\Delta$. As a result, all the remaining 99.9% of normal weights are compressed into just a handful of discrete values around zero, leading to a catastrophic collapse of the model's language proficiency and contextual coherence.

To neutralize this phenomenon without excessive memory overhead, modern quantization methods break down the tensors into finer-grained structures:

PTQ versus QAT: two different paths to compression

There are two fundamentally different routes to reduce a model's precision: Post-Training Quantization (PTQ) and Quantization-Aware Training (QAT).

Post-Training Quantization (PTQ) is applied to an already fully trained base or instruction model. Here, the weights are frozen and converted using a representative calibration set of text data (usually 128 to 512 context blocks). The calibration set serves to observe the dynamic ranges of activations across layers and determine the optimal scale factors using techniques such as Mean Squared Error (MSE) minimization or Hessian curvature calculations. PTQ requires relatively little compute and can be completed within minutes to a few hours on a single GPU.

Quantization-Aware Training (QAT) integrates rounding errors directly into the training process or fine-tuning phase. Because the discrete rounding function $\text{round}(x)$ has a zero derivative and blocks gradient descent via backpropagation, QAT utilizes a Straight-Through Estimator (STE). During the forward pass, weights are virtually quantized to simulate noise, while during the backward pass, unrounded real gradients flow through to the FP32 master weights. At extremely low bitrates (such as 2-bit and 3-bit), QAT yields significantly better perplexity scores than PTQ, but comes with substantial training costs and data requirements.

The 4-bit Revolution: AWQ, GPTQ, and bitsandbytes dissected

In modern production clusters, 4-bit PTQ dominates the landscape. Three specific implementations have shaped the market in recent years:

Method Optimization type Target Inference speed VRAM reconstruction
GPTQ (Layer-wise) Second-order Taylor / Inverse Hessian Weights (W4A16) High (with custom GEMM kernels) No reconstruction overhead
AWQ (Activation-aware) Salience preservation on activation data Weights (W4A16) Very high (optimized for throughput) No reconstruction overhead
NF4 / QLoRA (bitsandbytes) Information-theoretic quantile format Weights (W4A16) Moderate (high dequantization overhead) FP32/BF16 adapter combination
SmoothQuant / FP8 Scale migration from activation to weight Weight + Activation (W8A8 / FP8) Maximum (native tensor core support) Minimal compute overhead

GPTQ builds upon the classic Optimal Brain Surgeon algorithm. It processes a weight matrix layer by layer and updates the remaining unquantized weights every time a row is rounded, leveraging the inverse Hessian of the activations. This actively compensates for the introduction of rounding errors in earlier columns.

AWQ (Activation-aware Weight Quantization) recognizes that not all weights are created equal. By observing activation magnitude during calibration, AWQ identifies the top 1% most critical weights ('salient weights'). Rather than leaving these weights in 16-bit (which would cause memory fragmentation), AWQ computes a per-channel mathematical scaling factor that dampens activation size and proportionally scales up the weight. As a result, the most important signal carriers fall within the most representative portion of the 4-bit grid without disrupting the matrix structure.

NF4 (NormalFloat 4), introduced with QLoRA, moves away from the uniform linear grid. Because normalized neural network weights follow a standard normal distribution with mean 0 and variance $\sigma$ almost exactly, NF4 distributes the 16 available 4-bit discrete state points such that each quantile contains exactly the same probability mass. This minimizes the theoretical information loss per parameter.

Hardware mechanics and inference engines in practice

A persistent misconception about quantization is that it automatically halves computation time. On modern hardware, reality is more complex. To understand where the gains come from, we need to look at the bottleneck of LLM generation: Memory Bandwidth Bound vs. Compute Bound.

During the generation phase (autoregressively emitting token by token), the GPU must load the entire set of model parameters for every generated token from the slower High Bandwidth Memory (HBM/VRAM) into the fast on-chip Static RAM (SRAM) registers. Because the arithmetic operation per weight is minimal at batch size 1 (a single multiplication per value), the Tensor Core spends most of its time waiting for data transfer. We call this memory bandwidth bound.

A 4-bit model (W4A16) reduces the required data transfer across the memory bus by a factor of four. The GPU loads 4-bit integers into its registers, dequantizes them on-the-fly to FP16/BF16 in a handful of simple arithmetic clock cycles, and then executes the standard dot-product computation. Because the memory bus was the absolute bottleneck, this technique yields a dramatic increase in tokens per second per stream.

For those who want to dive deeper into the hardware components, the in-depth report on AI chips and hardware developments provides insight into how modern processors handle transistor density and memory bandwidths. At the software level, this requires a specialized server architecture; in the review of vLLM versus Ollama for production environments the performance differences of such inference engines are dissected down to the kernel level.

The overlooked bottleneck: KV-cache quantization

When scaling production systems to tens of thousands of concurrent users or processing long documents (32k to 128k context), the primary expansion risk for GPU memory is not weight memory, but the Key-Value (KV) cache For every token in the context, the model must retain the attention vectors of all preceding layers in VRAM.

The memory usage of the standard FP16 KV-cache can be calculated exactly using the formula:

Geheugen_KV (bytes) = 2 * n_layers * n_heads * d_head * n_ctx * batch_size * 2_bytes

For a standard 70B parameter model with 80 layers, Grouped-Query Attention (8 key-value heads of dimension 128), and a context of 16,384 tokens, the KV-cache of a single request consumes approximately 5.37 GB of VRAM. With a modest batch of 16 concurrent requests, the KV-cache alone swallows 85 GB of memory — more than the capacity of an entire 80GB enterprise GPU.

This is where KV-cache quantization (FP8, INT8, or INT4) relief. By asymmetrically quantizing the key and value vectors per token to 8-bit or 4-bit, the memory footprint of active sessions can be reduced by 50% to 75% without noticeable degradation in attention quality. To further optimize the interplay between context size and memory management, it is worth studying how context caching reduces memory and token costs, as this directly combines with KV-cache compression.

Accelerating Throughput: The Synergy with Speculative Decoding

Quantization can also be deployed strategically within advanced inference pipelines. A prime example is Speculative Decoding. Here, a small, compressed 'draft model' (such as a 4-bit quantized compact model) rapidly generates a series of 4 to 8 candidate tokens. The large, unquantized base model (the 'target model') then validates all these tokens in parallel in a single forward pass across the batch.

Because the draft model is quantized, it easily fits on the same GPU alongside the primary model and executes its steps with minimal latency. How this mechanism mathematically guarantees that the final token distribution remains completely identical to that of the target model is detailed extensively in the guide on speculative decoding and accelerating LLM inference.

VRAM Calculation: Theoretical Model vs. Production Reality

A common design pitfall in capacity planning is confusing bare parameter size with the total memory requirements of a running inference system. The total VRAM footprint ($M_{\text{total}}$) consists of four components:

M_totaal = M_parameters + M_kv_cache + M_activaties + M_runtime_overhead
Parameters Format Weight Memory KV-Cache (8k ctx, b=4) CUDA / Runtime Buffer Minimum GPU Size
8B FP16 (16-bit) 16.0 GB 2.1 GB ~2.5 GB 1x 24 GB (e.g., RTX 4090 / A10G)
8B W4A16 (4-bit) 4.8 GB 2.1 GB ~2.0 GB 1x 12 GB / 1x 16 GB
70B FP16 (16-bit) 140.0 GB 5.4 GB ~5.0 GB 2x 80 GB (H100 / A100)
70B W4A16 (4-bit) 38.5 GB 5.4 GB ~4.0 GB 1x 48 GB (A40 / RTX 6000) or 2x 24 GB
70B W4A4 / FP8 38.5 GB 2.7 GB (FP8 KV) ~3.5 GB 1x 48 GB (high batch concurrency)

As the table demonstrates, 4-bit quantization transforms the hardware requirements of a 70B parameter model from a multi-node or multi-GPU H100 cluster to a single enterprise card with 48 GB VRAM. This often reduces infrastructure costs by 60% to 80% per million tokens processed.

Degradation measurement and quality monitoring: how do you measure loss?

The core operational question is: what do we sacrifice in intelligence? Relying blindly on aggregated academic benchmarks (such as MMLU or GSM8k) often masks substantial quality pitfalls in specific production tasks.

The standard mathematical benchmark for quantization quality is Perplexity (PPL) on a standardized dataset such as WikiText-2 or C4. Perplexity measures how well the model predicts the next token in a representative sequence of text. A perplexity increase of less than 0.1 points generally indicates virtually lossless compression.

PPL = exp( - (1 / N) * sum( log P(x_i | x_<i) ) )

In practice, however, degradations occur selectively:

Continuous mapping of these quality shifts requires active monitoring. Read in the article on measuring drift in production environments which metrics and automated evaluation harnesses can be systematically deployed to detect degradation early.

Implementation: a robust AWQ conversion and loading pattern

Below is a concrete Python pattern illustrating how a model is quantized using the AutoAWQ library and subsequently loaded with optimized W4A16 GEMM kernels for high-throughput inference.

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_pad = "meta-llama/Llama-3.1-8B-Instruct"
quant_pad = "./Llama-3.1-8B-Instruct-AWQ-4bit"

# 1. Laad het ongekwantiseerde basismodel en tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_pad, use_fast=True)
model = AutoAWQForCausalLM.from_pretrained(
    model_pad, 
    low_cpu_mem_usage=True,
    use_cache=False
)

# 2. Kwantisatieconfiguratie: 4-bit, groepgrootte 128, GEMM-optimalisatie
quant_config = {
    "zero_point": True,
    "q_group_size": 128,
    "w_bit": 4,
    "version": "GEMM"
}

# 3. Uitvoeren van de kalibratie en quantisatie
# Hier wordt een representatieve tekstdataset benut om salient weights te lokaliseren
model.quantize(tokenizer, quant_config=quant_config)

# 4. Opslaan van de gewichten en configuratie voor productie
model.save_quantized(quant_pad)
tokenizer.save_pretrained(quant_pad)

print("Quantisatie voltooid. Model gereed voor vLLM of TGI implementatie.")

This model can then be served directly via the command-line interface within a production engine like vLLM, using native 4-bit kernel acceleration:

python3 -m vllm.entrypoints.openai.api_server \
  --model ./Llama-3.1-8B-Instruct-AWQ-4bit \
  --quantization awq \
  --dtype float16 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --port 8000

Decision framework for production teams

Selecting the right quantization level involves balancing budget, latency requirements, and quality margins. The rules of thumb below provide guidance when configuring your inference stack: