Lensa ML
Lensa ML

Quantization

Step 1 of 6: Why Models Don't Fit

Bits per weight decide the memory bill

Bits per weight16 bits (fp16)
Weights of the 7B chat model against two GPU memory sizes24 GB GPU 10.5 GB left13.5 GB80 GB GPU 66.5 GB left13.5 GB0204060806.74B weights × 2 bytes = 13.5 GB
Weight memory
13.5 GB
Fits on 24 GB
Yes
Smaller than fp32
2×

Half precision halves the weights to 13.5 GB. It fits on a 24 GB card with 10.5 GB left for the KV cache. This is the usual serving format and the baseline for quantization.

Drag Bits per weight through 32, 16, 8 and 4 and watch the weight bar against the two GPU outlines. The rule is memory = parameters × bits ÷ 8, so 6.74B weights at 4 bits come to about 3.4 billion bytes. The GB labels here are decimal gigabytes (10^9 bytes). The KV cache adds more: about 0.5 MB per token in fp16, so one 4,096-token conversation takes 2.1 GB. Real 4-bit files are also a little larger than the bar, because each group of weights stores a scale.

Step 2 of 6: Number Formats

fp32, fp16, bf16, int8 and int4 bit by bit

Value to store (log scale)0.1
signexponentmantissaintegerfp3200111101110011001100110011001101fp1600101110011001101+5+10, max 65504bf1600111101110011011+8+7, max 3.39e+38int8256 levels, -128 to 127int416 levels, -8 to 7FormatStored valueResultfp320.100000001roundedfp160.0999755859roundedbf160.100097656rounded
fp32 relative error
1.5e-8
fp16 relative error
2.4e-4
bf16 relative error
9.8e-4

Inside fp16's range, both 16-bit formats round to the nearest grid point. fp16 spends 10 bits on the mantissa and errs by 2.4e-4; bf16 has 7 and errs by 9.8e-4. Integers have no exponent at all, so int8 and int4 need a separate scale.

Drag Value to store, or click a preset, and read the bits each float format writes. A normal float means (−1)^s × 1.m × 2^(e − bias), with bias 127 for fp32 and bf16 and 15 for fp16. The mantissa width sets the relative spacing between neighbors: 2^−10 ≈ 0.1% for fp16 and 2^−7 ≈ 0.8% for bf16, and rounding errs by at most half of that. bf16 is the top half of an fp32 number, designed at Google for TPU training, which is why converting fp32 to bf16 only drops mantissa bits.

Step 3 of 6: Floats to Integers

Absmax scaling and rounding

Bitsint4
64 synthetic weights with the 15 int4 levels (orange lines)−0.0520+0.052Each weight: original (grey dot) and dequantized q × scale (orange tick)+0−
Scale
0.0074
Levels
15
Mean abs error
0.0019
Max error
0.0037

int4 gives 15 levels and uses 12 of them. Rounding moves each weight by at most half a step, scale ÷ 2 = 0.0037. The worst weight here uses 99% of that bound.

Drag Bits from 2 to 8 and compare the mean error with the scale. Rounding error is near uniform within half a step, so the mean error sits near scale ÷ 4: at int4 the scale is 0.0074 and the mean error is 0.0019. Symmetric absmax wastes one code: int8 can store −128, but the rule never produces it. Asymmetric quantization adds a zero point, q = round(w ÷ s) + z, so a 0 to 6 range after a ReLU uses every level. Weights are close to symmetric, so they rarely need one.

Step 4 of 6: The Outlier Problem

One large weight wastes the levels

Outlier size (× typical max)8.0×
Group size16 weights
Per-tensor: one scale for all 64 weights (int4)outlier 8.0× (off scale)Per-group: one scale per 16 weights (int4)outlier 8.0× (off scale)
Per-tensor error
0.0123
Per-group error
0.0036
Scale overhead
+1 bit
Levels used (per-tensor)
3 of 15

The 8.0× outlier stretches the per-tensor scale, so the other 63 weights collapse onto 3 levels. Per-group scaling confines the damage to the outlier's group of 16 and cuts the error 3.4×, at 1 extra bit per weight.

Drag Outlier size to 20× and step Group size from 8 to 64 to see where the per-group error catches up with per-tensor. Dettmers et al. found that transformers above about 6.7B parameters develop a few hidden dimensions with activations far larger than the rest. LLM.int8() keeps those dimensions in fp16 and multiplies everything else in int8. Weight-only methods take the group route instead. GPTQ and AWQ commonly use groups of 128, where one fp16 scale costs 16 ÷ 128 = 0.125 extra bits per weight.

Step 5 of 6: Weights vs Activations

Weight-only int4 against W8A8

Batch size (sequences decoded together)1
W4A16: weight-only int4int4 weightsdequantize to fp16fp16 × fp16 matmul (fp16 x)W8A8: weights and activations in int8int8 weightsint8 × int8 matmul into int32 sumsTime per decode step at batch 1 on an illustrative GPUfp16memory-bound6.74 ms0.04 msW4A16memory-bound1.69 ms0.04 msW8A8memory-bound3.37 ms0.02 msread weights: bytes ÷ 2 TB/smath: 2 × params × batch ÷ peak
fp16 step
6.74 ms
W4A16 step
1.69 ms
W8A8 step
3.37 ms
Tokens/s, fastest
593

At batch 1 every step reads all 6.74B weights to do very little math, so time tracks bytes. int4 weights read 4× fewer bytes than fp16, and W4A16 takes 1.69 ms against 6.74 ms.

Drag Batch size from 1 to 512 and watch each scheme switch from memory-bound to compute-bound. The switch happens where arithmetic intensity, FLOPs per byte read, reaches the GPU's peak ÷ bandwidth, 156 for fp16 math on this GPU. For fp16 decoding the intensity equals the batch size, because each 2-byte weight does 2 FLOPs per sequence. Prefill processes the whole prompt at once, so it is compute-bound even for one user. The bars ignore KV cache reads. W8A8 also needs the activation outliers handled, as SmoothQuant does by moving their scale into the weights.

Step 6 of 6: Accuracy vs Size

Output error of a quantized layer

Bitsint4
Group size32 weights
per-tensorper-group, g = 320.1%1%10%100%2345678bits per weight · y axis: ‖y − ŷ‖ / ‖y‖ (log)
Per-tensor error
42.4%
Per-group error
14.7%
Bits per weight
4.5
7B weights
3.8 GB

At int4, per-group scaling gives 14.7% output error against 42.4% per-tensor. Plain rounding still loses a lot here; GPTQ, AWQ and NF4 reach usable 4-bit models by fitting the rounding to calibration data or by using a better grid.

Set Bits to 4, then move Group size and compare the error with the bits-per-weight readout. Plain rounding treats every weight alike, and the methods used in practice do not. GPTQ quantizes one column at a time and adjusts the remaining columns to cancel the error, using second-order statistics from a small calibration set; it quantized 175B-parameter models in about four GPU hours. AWQ finds the roughly 1% of weight channels that see large activations and scales them up before rounding. NF4 places its 16 levels at quantiles of a normal distribution.

What is quantization in machine learning?

Quantization stores a model's numbers in fewer bits, for example 8-bit or 4-bit integers instead of 16-bit floats. Each block of weights keeps a scale, and a weight is saved as a small integer that is multiplied by that scale when the model runs. The model gets 2 to 4 times smaller than fp16 at the cost of a small, controlled rounding error.

Quantization is how large language models run on a single GPU, a laptop, or a phone. Serving systems load 4-bit or 8-bit versions of open models to fit more requests per card, and local tools such as llama.cpp ship nearly every model in quantized form. Because decoding one token at a time is limited by memory bandwidth, smaller weights also make generation faster. QLoRA also fine-tunes models on top of a frozen 4-bit base.

Key ideas

  • Weight memory is parameters × bits ÷ 8, so going from fp16 to int4 cuts it by 4×.
  • Floats trade range for precision through the exponent and mantissa split; bf16 keeps fp32's range with less precision.
  • Absmax quantization maps the largest weight to the top integer level and rounds the rest to a uniform grid.
  • One outlier stretches the scale for every weight that shares it, so per-group scales protect the rest at a small storage cost.
  • Weight-only quantization speeds up memory-bound decoding, while W8A8 also speeds up compute-bound work.
  • Methods such as GPTQ, AWQ and NF4 keep 4-bit models close to full precision where plain rounding falls short.

Common questions

What is the difference between post-training quantization and quantization-aware training?

Post-training quantization (PTQ) converts a finished model, sometimes with a few hundred calibration samples, and takes minutes to hours. Quantization-aware training (QAT) simulates the rounding during training so the weights adapt to it. QAT usually holds up better at very low bit widths, but it needs the training pipeline and much more compute, so most LLM releases use PTQ.

How much accuracy does 4-bit quantization lose?

With a good method such as GPTQ or AWQ, 4-bit weight-only models usually lose a small amount of perplexity and score close to fp16 on most benchmarks. 8-bit weights are close to lossless. Below 4 bits, quality drops faster, and smaller models suffer more than large ones because each weight carries more of the model's knowledge.

What do GGUF names like Q4_K_M mean?

GGUF is the model file format used by llama.cpp. Q4 means about 4 bits per weight, K marks the k-quant family, which stores small blocks of weights with their own scales inside larger super-blocks, and M is a medium mix that keeps some sensitive tensors at higher precision. The actual average is a little above the nominal bit count.

Can a quantized model be fine-tuned?

Not directly, because rounding to a few levels destroys small gradient updates. QLoRA solves this by freezing the 4-bit NF4 base and training LoRA adapters in 16-bit precision on top. The base is dequantized on the fly in each forward and backward pass, which lets a 65B model fine-tune on one 48 GB GPU.

Related explainers

References

  1. Frantar et al. (2022), "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers"
  2. Lin et al. (2023), "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration"
  3. Dettmers et al. (2023), "QLoRA: Efficient Finetuning of Quantized LLMs"
  4. Dettmers et al. (2022), "LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale"
  5. Nagel et al. (2021), "A White Paper on Neural Network Quantization"

Last updated