Lesson 12 of 13

Quantization: smaller numbers

Storing weights in 8 or 4 bits instead of 16 cuts a model's memory by half or more. See how scales, groups, and outliers decide what that costs in accuracy.

Advanced18 min

Builds on Inside a real language model and Where your AI runs (AI from zero)

In this lesson you will

  • Compare float32, float16, bfloat16, int8, and 4-bit formats by range and precision
  • Quantize numbers with absmax and zero-point scaling, and bound the rounding error
  • Explain why outliers hurt and how per-group scales contain the damage
  • Compute a model's memory at each precision, and read a measured case where quantization went wrong

Llama 2 7B has 6.74 billion parameters. Stored as 16-bit numbers, its weights take 13.5 GB, more memory than most laptop graphics cards have. Stored in 4 bits, they take about 3.4 GB and fit. And as the KV cache lesson showed, generating text one token at a time is usually limited by how fast weights can be read from memory, so smaller weights are also faster weights.

Storing numbers in fewer bits is called QuantizationStoring a model's numbers in fewer bits, for example as 8-bit or 4-bit integers with a scale, to save memory and speed up inference at some cost in accuracy.Open in glossary. It always loses something. This lesson is about how much, and why.

Number formats

Training usually stores numbers as Floating pointA way to store real numbers as a sign, an exponent that sets the scale, and a fraction that sets the precision. float32 and float16 are the common 32-bit and 16-bit IEEE 754 formats.Open in glossary: a sign, an exponent that sets the scale, and a fraction that sets the precision within that scale. More exponent bits give more range; more fraction bits give finer steps.

FormatBitsSign, exponent, fractionLargest valueGap just above 1
float32321, 8, 233.4⋅10383.4 \cdot 10^{38}2−232^{-23}
float16161, 5, 1065,5042−102^{-10}
bfloat16A 16-bit floating point format with float32's 8 exponent bits and only 7 fraction bits: the same range as float32, less precision than float16. Designed at Google Brain and widely used for training.Open in glossary161, 8, 73.4⋅10383.4 \cdot 10^{38}2−72^{-7}
int88integers −128-128 to 127127127 times a scaleone scale
4-bit integer4integers −8-8 to 777 times a scaleone scale

The last column is the precision: the next number after 1 is 1+2−10≈1.0011 + 2^{-10} \approx 1.001 in float16 but 1+2−7≈1.0081 + 2^{-7} \approx 1.008 in bfloat16 (largest values are rounded). float16 and bfloat16 both use 16 bits but split them differently. float16 is more precise but overflows above 65,504. bfloat16, designed at Google Brain, keeps float32’s 8 exponent bits, so it has the same range with less precision. That is why it is popular for training, where gradients and activations can span huge ranges. Newer hardware also supports 8-bit floating point formats (Micikevicius et al., 2022).

Integers have no exponent at all: 8 bits give 256 evenly spaced values, 4 bits give 16. To store a weight like 0.0137 as an integer, you need a scale that says what one step is worth.

Scales: absmax and zero-point

The simplest recipe, Absmax quantizationQuantization that sets the scale from the largest magnitude in a block, so that value maps to the largest integer. Levels are symmetric around zero.Open in glossary, uses the largest magnitude in a block of numbers to set the scale, so that it maps to the largest integer:

s=max⁡i∣xi∣2b−1−1,qi=round⁡ ⁣(xis),x^i=qi ss = \frac{\max_i |x_i|}{2^{b-1} - 1}, \qquad q_i = \operatorname{round}\!\left(\frac{x_i}{s}\right), \qquad \hat{x}_i = q_i\, s

With b=8b = 8 bits, the largest magnitude becomes ±127\pm 127. The model stores the integers qiq_i and one scale ss, and computes with x^i\hat{x}_i. Rounding to the nearest level means every error is at most half a step: ∣xi−x^i∣≤s/2|x_i - \hat{x}_i| \le s / 2.

Absmax puts its levels symmetrically around zero. If the values are all positive, as activations after a ReLU are, half the levels are wasted. Zero-point quantizationQuantization that maps a block's actual range, from its minimum to its maximum, onto the integers, with an integer offset marking where zero lands. Suits values that are not centered on zero.Open in glossary maps the actual range [min⁡,max⁡][\min, \max] onto 0,…,2b−10, \dots, 2^b - 1 with a scale and an integer offset zz that marks where zero lands:

s=max⁡−min⁡2b−1,z=round⁡ ⁣(−min⁡s)qi=round⁡ ⁣(xis)+z,x^i=(qi−z) s\begin{aligned} s &= \frac{\max - \min}{2^b - 1}, \qquad z = \operatorname{round}\!\left(-\frac{\min}{s}\right) \\ q_i &= \operatorname{round}\!\left(\frac{x_i}{s}\right) + z, \qquad \hat{x}_i = (q_i - z)\, s \end{aligned}

(The range is widened to include 0 if needed, so that zero is stored exactly.)

One scale, or many?

A whole weight matrix can share one scale (per tensor), or each row, or each group of, say, 64 or 128 consecutive weights can have its own. Group-wise quantizationGiving each group of consecutive values, such as 64 or 128 weights, its own quantization scale, so one large value only affects its own group.Open in glossary costs a little storage, one scale per group, but each group’s step size fits its own values.

This matters because of OutlierA data point far from the pattern of the rest, from a rare event or a recording error. Some loss functions are pulled hard by outliers.Taught in Linear regression and lossOpen in glossary. One value far larger than the rest sets the scale for everything that shares it, and stretches the step between levels until ordinary values round to zero.

Squeeze the weights

Store 128 numbers in fewer bits. Circles are the original values, bars the values the model would compute with.

Synthetic data: 128 weights drawn from a normal distribution with standard deviation 0.02, a typical size for a trained layer; the outlier sets one of them to 0.4. Positive activations are absolute values of normal samples. "Typical" means the root mean square of the values without the outlier.

Values
Stored as
Values per scale
Method
Typical error (of a typical value)12%
Largest error (of a typical value)20%
Rounded to exactly zero20 of 128
Bits per value, with scales4.125
Llama 2 7B stored this way3.5 GB

Challenges

0 of 4 complete

  1. With one outlier among the weights, find a 4-bit setting that rounds more than half of the other weights to exactly zero. (not yet)
  2. Keep the outlier and 4-bit absmax, and bring the typical error under 30% of a typical weight by changing only how many values share a scale. (not yet)
  3. On positive activations at 4 bits, find the change that cuts the error by more than half without changing the group size. (not yet)
  4. Compare the two 16-bit float formats on the weights and select the one that stores them more precisely. (not yet)

Try this

  • On Weights at 4 bits with one scale for all 128, look at how the circles snap to the bars. Most errors are small, and 20 weights round to exactly zero.
  • Switch to Weights + one outlier. Before you look, guess how many of the other 127 weights survive. The outlier’s scale makes the step wider than nearly every weight.
  • With the outlier still there, change Values per scale to 16. Only the outlier’s own group suffers; the others recover.
  • Choose Positive activations and compare Absmax with Zero-point at 4 bits.
  • Compare float16 with bfloat16 on the weights. Both are 16 bits; one is about seven times more precise here.
Absmax quantization with and without groupsPython with NumPy
import numpy as np

def absmax(x, bits, group):
    """Quantize to signed integers with one scale per group, then dequantize."""
    qmax = 2 ** (bits - 1) - 1                    # 127 for 8 bits, 7 for 4 bits
    g = x.reshape(-1, group)
    scale = np.abs(g).max(axis=1, keepdims=True) / qmax
    q = np.round(g / scale)                       # the stored integers
    return (q * scale).reshape(-1)                # what the model computes with

rng = np.random.default_rng(0)
w = rng.normal(0, 0.02, size=128)                 # synthetic weights
typical = np.sqrt(np.mean(w ** 2))
w_out = w.copy()
w_out[45] = 0.4                                   # one outlier, 20 standard deviations out

for name, x in [("weights", w), ("with outlier", w_out)]:
    for bits, group in [(8, 128), (4, 128), (4, 16)]:
        err = absmax(x, bits, group) - x
        zeros = np.sum(absmax(x, bits, group) == 0)
        print(f"{name:12} {bits}-bit, {group:3} per scale: typical error "
              f"{np.sqrt(np.mean(err ** 2)) / typical:6.1%}, {zeros:3} rounded to zero")

# Memory for Llama 2 7B (6,738,415,616 parameters).
n = 6_738_415_616
for label, bits in [("float32", 32), ("float16", 16), ("int8", 8),
                    ("4-bit + a 16-bit scale per 128", 4 + 16 / 128)]:
    print(f"{label:31} {n * bits / 8 / 1e9:5.1f} GB")
weights      8-bit, 128 per scale: typical error   0.5%,   1 rounded to zero
weights      4-bit, 128 per scale: typical error   9.4%,  17 rounded to zero
weights      4-bit,  16 per scale: typical error   7.6%,  12 rounded to zero
with outlier 8-bit, 128 per scale: typical error   4.8%,   6 rounded to zero
with outlier 4-bit, 128 per scale: typical error  85.6%, 113 rounded to zero
with outlier 4-bit,  16 per scale: typical error  32.9%,  22 rounded to zero
float32                          27.0 GB
float16                          13.5 GB
int8                              6.7 GB
4-bit + a 16-bit scale per 128    3.5 GB

The demo’s numbers are synthetic, but the effect is the real one. In the demo, at 4 bits with a single scale, one outlier sends 113 of the other 127 weights to exactly zero, and the typical error jumps from 12% of a typical weight to 82%. Giving every 16 weights their own scale brings it back to 28%, because the outlier now spoils only its own group.

Where the outliers are

Real models do have outliers, and they matter most in activations, the vectors flowing between layers. Dettmers et al. (2022) found that in transformers of around 6.7 billion parameters and more, a few hidden dimensions carry values far larger than the rest, consistently across layers. Standard 8-bit quantization, which lets those few values set the scale, lost much of these models’ accuracy. Their method, LLM.int8(), keeps those few dimensions in 16 bits and multiplies everything else, more than 99.9% of the values, in 8 bits; they report inference for models up to 175 billion parameters without loss of performance.

Better methods choose more than just a scale. GPTQ (Frantar et al., 2022) quantizes weights one column at a time and adjusts the remaining weights to compensate for each rounding error, using a little calibration data; the authors report 3 or 4 bits per weight for models with 175 billion parameters with negligible loss in accuracy.

What it costs in memory

Modelfloat32float16int84-bit with a 16-bit scale per 128
SmolLM2-135M (134.5 million)538 MB269 MB135 MB69 MB
Llama 2 7B (6.74 billion)27.0 GB13.5 GB6.7 GB3.5 GB
Llama 3 70B (70.6 billion)282.2 GB141.1 GB70.6 GB36.4 GB

These are weights only (GB here means 10910^9 bytes). Running a model also needs memory for activations and the KV cache, which can be quantized too. Scales are not free: the last column stores 4.125 bits per weight. QLoRA (Dettmers et al., 2023) stores the frozen base model in a 4-bit format and trains LoRA adapters on top; its 32-bit scales for blocks of 64 weights would add 0.5 bits per weight, so it quantizes the scales themselves, saving about 0.37 bits per parameter. With these tricks the authors fine-tuned a 65-billion-parameter model on a single 48 GB GPU.

A measured case: SmolLM2-135M on this site

The live model lab runs SmolLM2-135M in your browser. Smaller 8-bit and 4-bit versions of it are published, and would cut the download to half or less. Before choosing, this site compared each published version (as ONNX exports) with the full 32-bit model on the same 24 prompts, measuring how far each version’s next-token probabilities moved, with the KL divergenceA measure of how much one probability distribution differs from another, in bits or nats. It is zero only when the two are identical. In RLHF a KL penalty keeps the trained model close to where it started.Open in glossary in bits, and whether it picked the same most likely token.

  • The 16-bit version matched: KL 0.000 bits, and the same top token every time.
  • Every 8-bit and 4-bit export distorted the distribution badly: KL 0.7 to 1.2 bits, and the top token agreed on only 13 to 16 of the 24 prompts.
  • One example: after “Twinkle, twinkle, little”, the 8-bit version gives ” star” about 3% instead of 29%.

A lab about a model’s probabilities needs faithful weights, so the site’s SmolLM2 labs download the model’s original weights, which are 16-bit (bfloat16), and compute in 32-bit; their probabilities match a reference implementation in PyTorch to within a few millionths. This is one small model and a set of general-purpose exports; other methods, such as ones that use calibration data or keep sensitive layers in 16 bits, might well do better, and this site did not test them. The lesson is narrower: quantization changes a model’s outputs, by an amount you have to measure rather than assume.

Key ideas

  • Floating point formats trade range (exponent bits) against precision (fraction bits); bfloat16 keeps float32’s range with less precision than float16.
  • Integer quantization stores small integers and a scale. Absmax centers the levels on zero; zero-point fits them to the values’ actual range. Each error is at most half a step.
  • An outlier sets the scale for every value that shares it. Per-group scales contain the damage at the cost of a little storage.
  • Weight memory is parameters times bits divided by 8: Llama 2 7B takes 13.5 GB at 16 bits and about 3.5 GB at 4 bits with group scales.
  • Quantization changes outputs. On this site, every 8-bit and 4-bit export of SmolLM2-135M changed the top token on about a third to nearly half of 24 prompts, so the labs use the original 16-bit weights.

Check yourself

Pick an answer to see why it is right or wrong. Nothing is graded. Your first answer is saved in this browser so the question can come back for review.

1Llama 2 7B has about 6.74 billion parameters. Roughly how much memory do its weights take at 4 bits each, ignoring scales?
2With 8-bit absmax quantization, the largest value in a group is 2.54. What is the scale, and what integer stores the value 0.5?
3Why do per-group scales help when a tensor has one large outlier?

Progress is saved in this browser only.

Up nextHow a chat assistant works
Next
Transformers and LLMs
  1. 1Predicting the next token
  2. 2Before transformers: recurrent networks
  3. 3Attention, step by step
  4. 4Many heads and the causal mask
  5. 5Where words are
  6. 6The transformer block
  7. 7Inside a real language model
  8. 8How LLMs are trained
  9. 9Where training data comes from
  10. 10Fine-tuning and LoRA
  11. 11Generating fast: the KV cache
  12. 12Quantization: smaller numbers
  13. 13How a chat assistant works

Try "embedding", "softmax", "overfitting", or "backpropagation".