AWQ: protect the weights that matter

A weight that meets a large activation does more damage when it’s rounded. AWQ finds those weights by looking at activations, and scales them up before quantizing so they get relatively finer levels, without any mixed precision.

Key idea
Salient weight channels chosen by activation size, per-channel scaling before quantization, a search over one exponent α
Real world
An invoice where every unit price is rounded to the nearest £10. For an item bought once, that’s fine. For screws bought 1,000 at a time at £0.40 each, rounding to £0 loses £400. Price them per box of 100 instead (£40 a box, 10 boxes) and the same rounding costs at most £50.
In an LLM
In a 4-bit Llama layer, about 1% of input channels see activations far larger than the rest. Keeping just those weights in 16-bit recovers most of the lost quality. AWQ gets the same effect while keeping every weight 4-bit: it multiplies those channels’ weights by a factor s and divides their inputs by s.

Not all weights are equal

The error a rounded weight causes in the output is its rounding error times the input it multiplies:

output error from weight j=(wj−w^j)×xj\text{output error from weight } j = (w_j - \hat w_j) \times x_j

So a weight that always meets a large input xjx_j matters far more than one that meets a small input. AWQ (activation-aware weight quantization) measures this directly: run calibration text through the model, and call an input channel salient if its average activation magnitude is large. In LLMs a small fraction of channels, around 1%, stand out.

The paper’s first experiment is striking: keep just those 1% of weight channels in FP16 and quantize the rest to 4 bits, and most of the quality lost by round-to-nearest comes back. Choosing the 1% by weight size instead barely helps. It’s the activations that tell you what matters.

Scaling instead of mixed precision

Keeping some weights in 16-bit and the rest in 4-bit is awkward for hardware: kernels want every weight in a group stored the same way. AWQ uses the same identity as SmoothQuant instead. For a salient input channel jj:

wjxj=(s⋅wj)⋅xjsw_j x_j = (s \cdot w_j) \cdot \frac{x_j}{s}

Multiply the weight by s>1s > 1 before quantizing and divide its input by ss. The rounding error of s⋅wjs \cdot w_j is about the same absolute size as before (the grid step barely changes as long as s⋅wjs \cdot w_j isn’t the largest weight in its group), but after dividing by ss, its effect on the output shrinks by a factor of ss. The division of the input is folded into the previous operation, so it’s free at run time.

Push ss too far and s⋅wjs \cdot w_j becomes the largest weight in its group. Then it sets the group’s scale, the grid gets coarser for every other weight, and the error goes back up. There is a sweet spot.

Try it

how much each input channel adds to the output error
c1
c2
c3
c4
c5
c6
c7
c8
row 6 on its 4-bit grid: ticks are the 15 levels, red lines the rounding error
4-bit with scalingkeep c3 in 16-bit (mixed precision)
output error vs no scaling100%
keeping c3 in 16-bit10%
best s here×2.13
Without scaling, channel c3 (amber) causes most of the error even though its weights are ordinary: every tiny rounding mistake on them is multiplied by an activation 8× larger than usual. Drag s up.
A 4-bit layer: 256 output rows, 8 input channels, one scale per row. Channel c3 carries activations 8× larger than the others. Error is the expected squared error of the layer’s output, as a percentage of the error with no scaling.
example · pricing per box

The invoice rounds unit prices to the nearest £10. Screws cost £0.40 and you buy 1,000: rounded to £0, the invoice is £400 short. Re-list them as boxes of 100 at £40 a box, 10 boxes. Rounding £40 to £40 costs nothing here, and even a bad case (£45 → £50) is £5 a box, £50 in total. Same purchase, same rounding rule; you just moved a factor of 100 from the quantity to the price. That’s AWQ’s ss: move a factor from the activation into the weight, where the rounding happens.

example · one salient channel

In the widget, channel c3 sees activations 8× larger than the others. Without scaling it causes most of the layer’s output error, although its weights are ordinary. Scaling it by about 2.5 cuts the total error to under 40% of where it started, and every weight is still a 4-bit integer with one scale per group. Push past about ×5 and the layer becomes worse than with no scaling at all. Keeping c3 entirely in 16-bit (the dashed line) does better still, but that needs a second storage format inside the same layer.

Searching for s

AWQ doesn’t tune each channel’s ss separately. It uses one formula with one knob:

sj=∣xj∣ˉ αs_j = \bar{|x_j|}^{\,\alpha}

where ∣xj∣ˉ\bar{|x_j|} is channel jj’s average activation magnitude on the calibration data. α=0\alpha = 0 means no scaling; larger α\alpha scales salient channels more. AWQ tries a grid of values of α\alpha between 0 and 1 for each layer, quantizes, measures the layer’s output error on calibration data, and keeps the best. No gradients, no weight updates beyond the scaling.

That simplicity is a feature. Because AWQ never fits the weights to the calibration set (unlike GPTQ, which adjusts weights to cancel errors on it), it is less sensitive to which calibration data you use, and generalises well to other domains and to instruction-tuned and multimodal models.

In practice

  • AWQ is weight-only: weights in 4-bit (usually group size 128), activations stay in 16-bit (W4A16). It speeds up generation because decoding is limited by reading weights from memory: see what to quantize.
  • AWQ checkpoints are served by vLLM, SGLang, TGI and TensorRT-LLM. The original AutoAWQ library is no longer maintained; its functionality moved into llm-compressor.
  • AWQ and GPTQ can be combined: apply AWQ’s scaling first, then GPTQ’s error feedback. Many toolkits offer both as steps in one recipe.

Where this shows up

  • The same scaling identity used for activations: LLM.int8() and SmoothQuant.
  • Error feedback instead of scaling: GPTQ.
  • llama.cpp’s importance matrix also weights errors by activation size: GGUF.
going deeper

Why scaling helps

For a group of weights quantized with step Δ=max⁡k∣wk∣/qmax⁡\Delta = \max_k |w_k| / q_{\max}, the error on weight jj is Δ⋅ϵj\Delta \cdot \epsilon_j, where ϵj\epsilon_j is the rounding error in units of the step (roughly uniform on [−12,12][-\tfrac12, \tfrac12]). Its contribution to the output is Δ⋅ϵj⋅xj\Delta \cdot \epsilon_j \cdot x_j. After scaling,

Q(s⋅wj)⋅xjs−wjxj=Δ′⋅ϵj′⋅xjsQ(s \cdot w_j) \cdot \frac{x_j}{s} - w_j x_j = \Delta' \cdot \epsilon'_j \cdot \frac{x_j}{s}

If scaling one weight doesn’t change the group maximum, Δ′=Δ\Delta' = \Delta and ϵ′\epsilon' has the same distribution as ϵ\epsilon, so the expected error on that channel falls by a factor of ss. Once s⋅wjs \cdot w_j exceeds the group maximum, Δ′=s∣wj∣/qmax⁡\Delta' = s|w_j| / q_{\max} grows with ss and every other weight in the group gets worse.

AWQ minimises the output error of each linear layer on calibration inputs XX:

s∗=arg⁡min⁡s  ∥Q(Wdiag⁡(s))(diag⁡(s)−1X)−WX∥,s=sXα,  α∈[0,1]s^* = \arg\min_{s}\; \big\Vert Q\big(W \operatorname{diag}(s)\big)\big(\operatorname{diag}(s)^{-1} X\big) - W X \big\Vert, \qquad s = s_X^{\alpha},\; \alpha \in [0, 1]

with sXs_X the per-channel average activation magnitude. The paper searches 20 values of α\alpha. Weight clipping (shrinking each group’s maximum slightly, as in outliers and clipping) is searched the same way.

In papers

Related

  • gptq - Don’t round each weight on its own. Round one column, see how much the layer’s output moved, and nudge the weights you haven’t rounded yet to cancel it. A little calibration data says how.
  • llm-int8 - Large models have a few activation channels that are huge in every token. LLM.int8() pulls them out and keeps them in 16-bit; SmoothQuant moves the problem from activations into weights, where it is easy. Both make 8-bit matrix multiplies work.
  • outliers - LLMs contain a few huge values among millions of small ones, especially in activations. Clipping the range trades a little damage to the outliers for a much finer grid for everything else. Where to clip is a calibration problem.
  • choosing - Which format for which machine, which tool makes it, and how to check you haven’t broken anything. Rules of thumb, commands, and a picker that does the arithmetic.