Systems

Quantisation and outliers

Why int4 sometimes works and sometimes destroys a model. Watch a handful of outlier channels consume the entire numeric range, and watch per-channel scaling give it back.

Precision
Scheme
Outlier size 9.0x
WEIGHT RANGE PER CHANNELoutlier channeloutlier channeleverything else lives in hereORDINARY WEIGHTS, AND THE VALUES 4-BIT CAN REPRESENT-1.081.08synthetic weights with two hand-placed outlier channels, not a real tensor.every ordinary weight lands on one of just 3 values
0.50vs 2.00 fp16
Bytes per weight
3of 15
Levels used by bulk
0.1924
Mean error
0.8dB, bulk
Signal to noise

One scale for the whole matrix. The outlier channels set it, so ordinary weights get a fraction of the available levels and most of the precision is wasted on values that barely occur.

Quantisation is usually sold as a straight trade: fewer bits, less memory, slightly worse output. That framing hides the part that decides whether it works, which is what happens when one channel has a far wider range than its neighbours and they are forced to share a scale.

Symmetric quantisation picks a scale from the largest magnitude it has to represent, then divides the range into however many levels the bit width allows. With one scale for the whole tensor, the widest channel sets it. Everything else, which is the overwhelming majority, is left sharing the few levels nearest zero. Drop to int4 with a large outlier present and the entire bulk of the distribution can fall between two representable values, at which point those values are not approximated, they are erased.

That is what the lower panel draws. The red lines are the values the format can actually represent, and the histogram is where the values want to be. Under per-tensor scaling the lines are far apart and the histogram has nowhere to land. Switch to per-channel and the lines close up, because each channel now gets a scale matched to its own range and a narrow channel is no longer punished for sharing a tensor with a wide one. The third setting, mixed precision, does what production kernels do with the worst channels: keep them in a wider format and quantise everything else, which is a different bargain from making the whole tensor share a finer scale.

One clarification, because it is widely garbled. The famous emergent-outlier result is about ACTIVATIONS, not weights: a handful of hidden-state channels develop magnitudes far above the rest, and that is what makes naive int8 inference collapse. Weight distributions are comparatively well behaved, which is precisely why weight-only int4 works at all. The mechanism drawn here, a wide channel starving narrow ones under a shared scale, is the same in both cases, and it is the reason activation-aware methods identify important weights by the size of the activations that multiply them rather than by the size of the weights themselves.

Production schemes diverge from this picture in different directions rather than converging on it. GPTQ compensates for rounding error by adjusting the weights it has not quantised yet. AWQ rescales channels using activation statistics. The int4 files you actually download sit between the two schemes shown here: GGUF k-quants use small groups of 16 to 32 values inside a 256-value super-block, while NF4 uses 64-value blocks and a genuinely non-uniform codebook shaped to a normal distribution. Group-wise scaling is the dominant real practice.

The practical read: a bit width is not a quality setting on its own. Int4 with well-chosen groups can beat int8 with one scale for everything, and the difference is entirely in how the scale is chosen.

The maths

Symmetric quantisation
s  =  maxiwi2b11,w^  =  sclamp ⁣(round(ws),(2b11),2b11)s \;=\; \frac{\max_i |w_i|}{2^{b-1} - 1}, \qquad \hat{w} \;=\; s \cdot \text{clamp}\!\Big(\text{round}\big(\tfrac{w}{s}\big),\, -(2^{b-1}{-}1),\, 2^{b-1}{-}1\Big)

The scale s is set by the largest magnitude the format must represent. The red grid lines in the lower panel are the multiples of s: the only values a weight can become.

Why outliers are poison
stensor  =  woutlier2b11    maxwbulk2b11  =  schannels_{tensor} \;=\; \frac{|w_{outlier}|}{2^{b-1}-1} \;\gg\; \frac{\max |w_{bulk}|}{2^{b-1}-1} \;=\; s_{channel}

One scale for the whole tensor means the outlier sets the step for everyone. When s exceeds the entire spread of the ordinary weights, they all land on just three codes, minus one, zero and plus one, and most of the format’s bits describe values that never occur.

Round-trip error bound
w^w    s2for    wmaxiwi|\hat{w} - w| \;\le\; \frac{s}{2} \quad \text{for} \;\; |w| \le \max_i |w_i|

Error is bounded by half a step, so everything comes down to how small the step is for the weights you actually have. Per-channel scaling wins purely by shrinking s where the data is narrow.

Related terms

More visualisations

Building with language models?

These explainers come out of the work. If you want the same thinking applied to your own system, that is what I do.

See how I can help