一个 8B 模型在 H100 上 batch 1 逐个生成 token,BF16 权重每步至少要读 4.77 ms;压成每 128 个一组的 INT4(W4A16),只要 1.45 ms。可同样的模型、batch 256 时,两者的下限只差 4.77 对 4.14 ms。只压权重换来的是 decode 少读的字节:batch 小时几乎按字节数提速,batch 大到算力受限就没了。训练后量化(PTQ)的几种做法,都是拿一小份校准(calibration)数据,让误差尽量不影响层的输出:GPTQ 把每一列的误差推给还没量化的列,AWQ 先按激活大小放大重要通道;要连激活一起压成 8 位,还得先用 SmoothQuant 这类变换,把激活的离群值挪到权重上。
待审核草稿。 这一篇还没有经过站主审阅,事实、取舍和措辞都可能修改;引用前请回到文中链接的一手资料核对。
格子、scale 和分组见上一篇格式与误差;roofline 和 decode 的 batch 账见性能。
先算字节:batch 1 为什么快三倍多
教学假设:8B 参数,其中 7.5B 在被量化的线性层里,0.5B 是输出层(lm_head),保持 BF16;GPU 按 H100 SXM5 的 3,352 GB/s 显存带宽、dense BF16 989.4 TFLOPS、dense FP8 1,978.9 TFLOPS 算(H100 白皮书 V1.04);先不算 KV cache。batch 1 时每一步的下限就是“权重字节 ÷ 带宽”:
| 方案 | 权重字节 | batch 1 每个 token 至少 | 相对 BF16 |
|---|---|---|---|
| BF16 | 16 GB | 4.77 ms | 1× |
| W8A8 FP8(输出层 BF16) | 8.5 GB | 2.54 ms | 1.88× |
| W4A16 g128(输出层 BF16) | 4.87 GB | 1.45 ms | 3.29× |
W4A16 的字节是 $7.5\times10^9\times4.125/8+0.5\times10^9\times2\approx4.87\times10^9$。不到 4 倍,是因为没量化的输出层占了 1 GB,scale 又多了 0.125 位:压得越狠,没压的那部分占比越大,这里已是 21%。
公开的实测:GPTQ(2022-10)把 OPT-175B 压到 3 bit,batch 1 生成 128 个 token,每个 token 在一张 A100 80GB 上 71 ms,FP16 要 5 张卡、230 ms,约 3.24 倍。他们的 kernel 做的是“量化矩阵乘全精度向量”,读权重时就地反量化,激活不量化;矩阵乘向量本来就受带宽限制,所以多出来的反量化计算藏得住。
batch 变大以后
W4A16 反量化以后仍然用 BF16 Tensor Core 算,所以它的算力上限和 BF16 一样;W8A8 FP8 的算力上限翻倍。每一步取 $\max(\text{字节}/\text{带宽},\ \text{运算}/\text{峰值})$,仍然不算 KV:
| batch | BF16:每步 · tokens/s | W4A16:每步 · tokens/s | W8A8 FP8:每步 · tokens/s |
|---|---|---|---|
| 1 | 4.77 ms · 210 | 1.45 ms · 689 | 2.54 ms · 394 |
| 16 | 4.77 ms · 3,352 | 1.45 ms · 11,019 | 2.54 ms · 6,310 |
| 64 | 4.77 ms · 13,408 | 1.45 ms · 44,076 | 2.54 ms · 25,239 |
| 128 | 4.77 ms · 26,816 | 2.07 ms · 61,838 | 2.54 ms · 50,477 |
| 256 | 4.77 ms · 53,632 | 4.14 ms · 61,838 | 2.54 ms · 100,954 |
| 512 | 8.28 ms · 61,838 | 8.28 ms · 61,838 | 4.40 ms · 116,405 |
W4A16 读权重只要 1.45 ms,而 batch $B$ 时的运算要 $B\times1.6\times10^{10}/989.4\times10^{12}$,两者在 $B\approx90$ 相等;过了这个点它就和 BF16 一样被算力卡住。BF16 和 FP8 的交点都在 $B\approx295$,但 FP8 的算力上限高一倍。这是峰值下的上限,真实 kernel 更早掉队:Marlin(2024-08)自报 batch 16–32 以内接近理想的 4 倍,之后逐渐变小,batch 128 时约 1.5 倍;Kurtic 等(2024-11,ACL 2025)在 vLLM 上的结论也是这个方向:同步、看延迟的部署里 W4A16 最省钱,异步连续组 batch 的吞吐场景里 W8A8 最好,交点取决于模型、硬件和任务。这张表没算 KV。加上每条请求各自的 KV,每步读的字节随 batch 增长,decode 更晚才进入算力受限,但权重只占读取量的一部分,提速被稀释:每条请求 4K token 的 BF16 KV、batch 64 时,W4A16 只快约 1.28 倍(11.7 对 15.0 ms),见性能。
最朴素的做法:逐个就近舍入(RTN)
RTN(round-to-nearest)就是上一篇的做法:每个权重单独舍入到最近的格子,不看数据。4 bit 加 g128 时它常常还行:AWQ 表 4 的 Llama-2 WikiText-2 困惑度(perplexity),7B 是 FP16 5.47、RTN 5.73、GPTQ 5.69、AWQ 5.60,70B 是 3.32、3.46、3.42、3.41,几种方法只差零点几。
位数再少、又不分组时,RTN 会崩。GPTQ 表 3 每行权重用一个 scale:OPT-175B 的 FP16 是 8.34,4 bit RTN 10.54(比 FP16 的 13B 还差),GPTQ 8.37;3 bit RTN 是 7.3×10³,GPTQ 8.68。RTN 吃亏在每个权重独立舍入:输入通道相关时,各自的误差会在输出里叠加。要知道哪些方向重要,就得看这一层实际收到的输入,这就是校准数据的用处。
GPTQ:量化一列,把误差推给还没量化的列
GPTQ 一层一层地解这个问题:
$$ \hat W=\arg\min_{\hat W}\ \big\|WX-\hat WX\big\|_2^2, $$
$W$ 是这一层的权重(每行一个输出),$X$ 是这一层在校准数据上的输入,每一列是一个 token。对一行权重 $w$,这是一个二次函数,Hessian 是 $H=2XX^\top$。它沿用 OBQ(Optimal Brain Quantization)的公式:把第 $q$ 个权重量化以后,其余还没量化的权重 $F$ 按下式更新,才能让输出误差最小:
$$ \delta_F=-\frac{w_q-\operatorname{quant}(w_q)}{[H_F^{-1}]_{qq}}\,\big(H_F^{-1}\big)_{:,q}. $$
手算一次。 一个输出、两个输入通道、4 个校准 token(教学数字):$x_1=[1,\ 2,\ -1,\ 0.5]$,$x_2=[1,\ 1.8,\ -1.2,\ 0.6]$,两个通道高度相关;$w=[0.62,\ 0.37]$;格子步长 0.25。
- $H_{12}=2\sum_t x_{1t}x_{2t}=2\times6.1=12.2$,$H_{22}=2\sum_t x_{2t}^2=2\times6.04=12.08$。
- RTN:$[0.5,\ 0.25]$。两个误差都是 +0.12,又乘在几乎一样的输入上,4 个 token 的输出误差是 $[0.24,\ 0.456,\ -0.264,\ 0.132]$,平方和 0.353。
- GPTQ:先量化 $w_1\to0.5$,误差 $e_1=0.12$。只剩一个权重时,上式化简为 $w_2\leftarrow w_2+e_1H_{12}/H_{22}=0.37+0.12\times1.0099\approx0.491$,再量化得 0.5。输出误差 $[-0.01,\ 0.006,\ 0.036,\ -0.018]$,平方和 0.00176,约为 RTN 的 1/200。
直觉是:$x_2\approx x_1$,所以 $w_2$ 多出来的一点正好补上 $w_1$ 少掉的那一点。在这个例子里,GPTQ 的结果也恰好是格子上的最优解(两维可以暴力枚举核对)。
X = [[1.0, 2.0, -1.0, 0.5],
[1.0, 1.8, -1.2, 0.6]]
w = [0.62, 0.37]
step = 0.25
def quant(v):
return step * round(v / step)
def output_error(w_hat):
total = 0.0
for t in range(4):
exact = w[0] * X[0][t] + w[1] * X[1][t]
approx = w_hat[0] * X[0][t] + w_hat[1] * X[1][t]
total += (exact - approx) ** 2
return total
h12 = 2 * sum(a * b for a, b in zip(X[0], X[1]))
h22 = 2 * sum(b * b for b in X[1])
rtn = [quant(w[0]), quant(w[1])]
q1 = quant(w[0])
gptq = [q1, quant(w[1] + (w[0] - q1) * h12 / h22)]
assert rtn == [0.5, 0.25] and gptq == [0.5, 0.5]
assert round(output_error(rtn), 4) == 0.3527
assert round(output_error(gptq), 5) == 0.00176
把 $x_2$ 改成和 $x_1$ 无关的数,补偿就几乎没用了:$H_{12}$ 接近 0。
从 OBQ 到能压 175B,GPTQ 改了三处:所有行按同一个固定顺序量化,共用一个 $H^{-1}$(大层上和 OBQ 逐个挑顺序差别很小);每 128 列一块,块做完再统一更新后面的列,避开显存带宽瓶颈;用 Cholesky 分解加上平均对角值 1% 的阻尼,保证数值稳定。校准数据是 C4 里随机取的 128 段、每段 2,048 token,一次只把一个 Transformer block 放进显存。一张 A100 80GB 上量化 OPT-175B 要 4.2 小时(表 2)。
AWQ:先看激活,把重要的通道放大
AWQ(2023-06,MLSys 2024 最佳论文)从一个实验出发(表 1,OPT-6.7B,INT3-g128,WikiText 困惑度):FP16 是 10.86,RTN 是 23.54;只把 1% 的通道留在 FP16,按激活大小挑,困惑度回到 11.39;按权重范数挑是 22.37,随机挑是 24.23。重要的权重要看它乘上的激活,不是看它自己有多大。
混合精度对硬件不友好,AWQ 改用缩放:把重要输入通道的权重乘以 $s>1$,对应的输入除以 $s$,数学上等价。量化器是 $Q(w)=\Delta\cdot\operatorname{Round}(w/\Delta)$,缩放后这个通道的误差变成 $\Delta'\cdot\operatorname{RoundErr}(ws/\Delta')\cdot x/s$。只要放大一个元素没有改变这一组的最大值,$\Delta'\approx\Delta$,这个通道的误差就缩小到约 $1/s$。论文表 2(OPT-6.7B)里 $s=2$ 最好,困惑度 11.92;$s=4$ 时 21.2% 的组的 $\Delta$ 变了,平均变大 21%,困惑度回升到 12.36。
手算一次。 一组 4 个权重 $w=[0.90,\ 0.20,\ -0.35,\ 0.50]$,各输入通道的平均激活幅值 $s_X=[1,\ 8,\ 1,\ 1]$,第 2 个通道是重要通道。对称 INT4,$\Delta=\max|w|/7$。教学假设各通道的输入独立、均值为 0、$\mathbb E[x_j^2]=s_{X,j}^2$,于是输出的均方误差约为 $\sum_j(w_j-\hat w_j)^2s_{X,j}^2$。
- 不缩放:$\Delta=0.9/7\approx0.1286$。0.20 被量化成 $2\Delta\approx0.257$,误差 0.057,乘上 8 以后平方是 0.209,占了总误差 0.2105 的绝大部分。
- 第 2 个通道乘 2:$0.4$ 仍小于 0.9,$\Delta$ 不变;$0.4/\Delta\approx3.11$ 舍入为 3,读回 0.386,除以 2 得等效权重 0.193,误差 0.007,总误差降到 0.0047。
AWQ 不手工挑 $s$,而是令 $s=s_X^{\alpha}$,在 $[0,1]$ 上取 20 个 $\alpha$ 网格搜索,让 $\|Q(W\operatorname{diag}(s))(\operatorname{diag}(s)^{-1}X)-WX\|$ 最小,再加上按 MSE 选的权重截断。在这个小例子里:
| $\alpha$ | 第 2 个通道的 $s$ | $\Delta$ | 加权误差 |
|---|---|---|---|
| 0 | 1 | 0.1286 | 0.2105 |
| 0.25 | 1.68 | 0.1286 | 0.0566 |
| 0.5 | 2.83 | 0.1286 | 0.0226 |
| 0.75 | 4.76 | 0.1359 | 0.0079 |
| 1 | 8 | 0.2286 | 0.0135 |
$\alpha=1$ 时 $0.2\times8=1.6$ 成了组内最大值,$\Delta$ 变大 1.78 倍,其他三个通道跟着变差。这就是要搜索的原因,而且舍入的偶然性会让误差随 $s$ 上下跳。AWQ 默认 g128,主攻 INT4 和 INT3;不用反向传播,也不做重建。论文报告它对校准数据要求更低(OPT-6.7B,INT3-g128):只用 16 段就好过用 192 段的 GPTQ;校准集和评测集来自不同领域(PubMed 与 Enron 互换)时,困惑度只升高 0.5–0.6,GPTQ 升高 2.3–4.9。
激活也要压:SmoothQuant 的 W8A8
只压权重救不了算力受限的阶段(prefill、大 batch)。要用 INT8 Tensor Core,激活也得是 8 位,而上一篇说过,激活的离群值固定在少数输入通道上,按输入通道给激活设 scale 又不能在 GEMM 之后乘回去。SmoothQuant(2022-11,ICML 2023)的做法是先做一个等价变换:
$$ Y=\big(X\operatorname{diag}(s)^{-1}\big)\big(\operatorname{diag}(s)W\big),\qquad s_j=\frac{\max|X_j|^{\alpha}}{\max|W_j|^{1-\alpha}}. $$
$X_j$ 是第 $j$ 个输入通道的激活(在校准数据上统计),$W_j$ 是 $W$ 的第 $j$ 行。$\alpha$ 是“迁移强度”:OPT 和 BLOOM 用 0.5,离群值更多的 GLM-130B 用 0.75;在 OPT-175B 的 LAMBADA 上,0.4–0.6 之间最好。$\operatorname{diag}(s)^{-1}$ 可以离线融进前一个 LayerNorm 或线性层,运行时没有额外开销。
手算一次。 3 个 token、3 个输入通道,第 2 个通道是离群通道(教学数字):
$$ X=\begin{bmatrix}0.8&-40&0.3\\-1.2&48&0.5\\0.6&-36&-0.9\end{bmatrix},\qquad W=\begin{bmatrix}0.8&-0.3\\0.2&0.1\\-0.5&0.6\end{bmatrix}. $$
各输入通道激活的最大绝对值是 $[1.2,\ 48,\ 0.9]$,$W$ 各行的最大绝对值是 $[0.8,\ 0.2,\ 0.6]$。取 $\alpha=0.5$,$s=[1.22,\ 15.5,\ 1.22]$。变换以后,平滑过的 $\hat X=X\operatorname{diag}(s)^{-1}$ 和 $\hat W=\operatorname{diag}(s)W$ 每个通道的最大值都变成 $\sqrt{\max|X_j|\cdot\max|W_j|}=[0.98,\ 3.10,\ 0.73]$。per-tensor INT8 量化激活时,步长从 $48/127\approx0.378$ 降到 $3.10/127\approx0.0244$,第 1 个通道的码值从 $[2,-3,2]$ 变成 $[27,-40,20]$,从约 3 个格子变成约 40 个。激活 per-tensor、权重 per-channel 时,$Y$ 的相对误差从 1.95% 降到 0.15%;激活改成 per-token,从 1.76% 降到 0.048%。代价是权重的第 2 行被放大了 15.5 倍,权重变得难量化一些,所以 $\alpha$ 不能取太大。
论文自报:在 OPT-175B、BLOOM-176B、GLM-130B 上,O1、O2 的 W8A8 与 FP16 准确率持平,最粗的 O3 在 BLOOM-176B、GLM-130B 上约掉 1%;MT-NLG 530B 也能跑 W8A8。摘要给的最大加速是 1.56 倍,显存约减半;粒度从 O1(per-token 动态)到 O3(per-tensor 静态)越粗越快。对照方案 LLM.int8() 把幅值超过 6 的离群维度拆出来用 FP16 乘,自报到 175B 都不掉精度、显存减半,但它自己测的端到端生成比 BF16 略慢(BLOOM-176B、batch 1:253 对 239 ms/token)。
几种做法放在一起
| 方法 | 压什么 | 要什么数据 | 主要思路 | 代价和风险 |
|---|---|---|---|---|
| RTN | 权重(也可以是激活) | 不要 | 逐个就近舍入 | 3 bit、不分组时会崩 |
| GPTQ | 权重 | 少量校准文本 | 用 $H^{-1}$ 把误差推给后面的列 | 可能贴合校准分布(AWQ 的跨领域实验) |
| AWQ | 权重 | 更少的校准文本 | 按激活放大重要通道 | 只调 scale 和截断,不改单个权重的舍入方向 |
| SmoothQuant | 权重和激活(W8A8) | 校准时统计激活最大值 | 把激活的难度挪给权重 | $\alpha$ 要调;权重变难量化 |
| LLM.int8() | W8A8,离群维度用 FP16 | 不要 | 混合精度分解 | 通常不比 FP16 快 |
| 旋转(如 QuaRot) | 权重、激活、KV 都到 4 bit | 视实现 | 正交旋转把离群值摊平,输出不变 | 多出旋转的计算;自报 Llama2-70B 困惑度最多多 0.47 |
| 量化感知训练(QAT) | 任意 | 训练数据和算力 | 训练时就模拟量化 | 要训练;Nagel 等指出它能支持更低位数 |
这些结果的边界
- 上面引用的精度数字大多是 WikiText-2 或 C4 上的困惑度,模型多是 OPT、BLOOM、Llama-2 这些较早的模型。GPTQ 自己也注明,校准数据取自 C4 训练集,所以它的 C4 困惑度不完全是零样本结果。任务准确率、逐题翻转和长上下文要另测,见怎样测出丢了多少。
- 提速数字是在特定 kernel、特定 GPU、batch 1 上测的;batch 一大优势就变小,换引擎版本要重测。
- 输出层、embedding 常常保持高精度,KV cache 也没压;长上下文时 KV 才是大头,见下一篇 FP8 推理与 KV cache 量化。在 4 bit 基座上继续训练(QLoRA、NF4)见 LoRA 与 QLoRA。
本页术语 2
- 校准 calibration
- 模型给出的置信度是否等于真实正确率
- 困惑度 perplexity
- exp(交叉熵),语言模型的常用指标