Quantization
推理优化 · 2022–2026 · vLLM e740bad

LLM 推理量化:权重、激活与 KV cache 的三本账

解码阶段每生成一个 token,都要把权重从显存搬到计算单元;瓶颈常常是带宽,不是 FLOPs。 weight-only 只压权重,W8A8 连激活一起压,FP8 用指数位在 8 bit 内保留比 INT8 更大的动态范围。 压到几位不是关键,粒度、scale 和校准集才决定压完还能不能答对。

AWQ · GPTQ · FP8 Formats · SmoothQuant AWQ ↗ GPTQ ↗ FP8 ↗ SmoothQuant ↗ vLLM @ e740bad ↗
TL;DR · 30 秒抓住核心

weight-only(W4A16)只量化权重,激活仍是 FP16。解码是 memory-bound,所以它能直接减少每 token 的权重读取量; 但 GEMM 仍是 FP16,KV cache 也不受益。AWQ 和 GPTQ 都属于这一类。

W8A8把激活也压到 INT8 或 FP8,才能用整数/FP8 tensor core。难点是激活离群值:SmoothQuant 用 per-channel 缩放把离群值从激活迁移到权重; FP8 用 E4M3 的 3 位尾数和 448 的最大值,再配 per-tensor、per-channel 或 block scale。

AWQ按激活幅度挑关键通道,做等价缩放;GPTQ用校准输入的 Hessian 做逐层误差补偿。 两者都不训练,但都需要校准数据,且默认只改权重。

KV cache 量化是另一笔账:FP16 到 FP8 直接把每 token 的 KV 字节减半,长上下文和高并发 batch 的收益比权重更直接; 代价是 scale 校准与 attention kernel 支持。

448
E4M3 的最大正规数
E5M2 到 57344,但尾数只有 2 位
6.24
Llama-2-7B INT3-g128 的 WikiText2 PPL
同表 FP16 为 5.47,GPTQ 为 6.43
0.03PPL
OPT-175B 4-bit GPTQ 与 FP16 的 WikiText2 差
8.37 − 8.34
01

解码先搬 140 GB 权重,再谈算力

先分清两类瓶颈:权重读取和 KV cache 读取;量化动的是字节数,不是 FLOPs。

自回归解码每次只生成一个 token,batch=1 时矩阵乘法的右侧只有一行向量。AWQ 在 RTX 4090 上做的 roofline 分析指出, 这个阶段的算术强度约为 1 FLOP/byte,而 4090 的峰值算力与带宽之比是 165 FLOP/byte,因此生成阶段是内存带宽受限, 权重访问量比激活访问量高几个数量级(AWQ §4.1,Figure 3)。

以 70B 参数、FP16 权重为例:70 × 109 × 2 B = 140 GB(十进制)。若显存带宽为 1 TB/s, 单次读取全部权重的理论时间下界是 140 GB ÷ 1000 GB/s = 140 ms;换成 INT4 权重则是 35 GB,下界 35 ms。 这还没算 KV cache、计算和 kernel 开销,但它说明了为什么量化首先是一种显存与带宽手段。

解码时间下界 ≈ 每 token 读取的权重字节 ÷ 显存带宽
batch=1、忽略计算与 KV 时的粗略下界;真实延迟由 kernel、并行度和缓存共同决定

KV cache 是第二本账。GPTQ 论文给过一个具体数字:OPT-175B 在 3-bit 权重下约 63 GB(含保持 FP16 的 embedding 与输出层), 若把 2048 个 token 的全部 K/V 历史存下来,还要再加约 9 GB(GPTQ §5)。长上下文或大 batch 时, KV cache 会从“附带开销”变成主要占用。

第三个问题是量化误差。均匀量化的步长 Δ 由张量(或通道/组)的最大绝对值决定; 激活里只要少数通道的离群值达到普通值的约 100 倍,Δ 就会被它们撑大,其余通道只剩很少的有效量化级 (SmoothQuant §3,Figure 2)。SmoothQuant 在 OPT-175B 上报告:在 WinoGrande、HellaSwag、PIQA、LAMBADA 四个零样本任务上, FP16 的平均准确率是 71.6%,而朴素的 INT8 per-tensor / per-token 激活量化分别只有 32.3% / 31.7% (SmoothQuant Table 1;§06 表 6 的 7 任务平均是另一组数字)。 SmoothQuant 也指出权重分布更平坦,激活才是量化难点(SmoothQuant §3)。

所以问题不是“压到 8 bit 还是 4 bit”,而是:压哪些张量、用多细的粒度、scale 怎么定、在哪里反量化。 这四件事决定了精度、显存和吞吐的三角关系。

02

三条路线:只压权重、压权重加激活、用 FP8 压两者

三条路线的字节数、kernel 和精度风险不同,不能只看 bit 数。

W4A16

weight-only

AWQ / GPTQ

权重 4-bit(也有 3-bit),激活保持 FP16/BF16。checkpoint 里存 packed 整数、每组的 scale 和可选的 zero-point; kernel 在 GEMM 主循环里把权重反量化成 FP16/BF16,再与同精度激活相乘。

vLLM 的 AutoAWQConfig 暴露 weight_bits、group_size、zero_point (auto_awq.py:188-202);AutoGPTQConfig 暴露 weight_bits、group_size、 desc_act、is_sym(auto_gptq.py:112-156)。两者都可走 Marlin 等 WNA16 kernel。

收益:权重显存约降为 1/4;解码的权重读取量同步下降。不改变:激活与 KV cache 的字节数;GEMM 仍按 FP16/BF16 计算(权重本身已有量化误差)。

W8A8

INT8 权重 + 激活

SmoothQuant 等

权重和激活都量化到 INT8,才能用整数 GEMM。激活的难点是离群值:per-tensor 和 per-token 的 scale 都会被少数大通道支配,而非离群通道的有效量化级可能只剩 2–3 个(SmoothQuant §3)。

SmoothQuant 的观察是:离群值固定在少数通道上,且同一通道在不同 token 间幅度相近。它用一个 per-channel 缩放 s 把激活除以 s、权重乘以 s,数学上等价,却把量化难度从激活迁移到权重。

收益:权重和激活都减半;在支持 INT8 GEMM 的硬件上,整数 GEMM 吞吐更高。代价:静态激活 scale 需要校准集,动态 per-token 要在运行时统计;per-channel 激活量化本身与 INT8 GEMM 不兼容。

FP8

W8A8 浮点

E4M3 / E5M2

FP8 用 8 位浮点表示,权重和激活都可以是 FP8。E4M3 有 4 位指数、3 位尾数,最大正规数 448; E5M2 有 5 位指数、2 位尾数,最大 57344,动态范围更大但精度更粗(FP8 Formats Table 1)。

vLLM 文档推荐的 FP8 W8A8 配方是权重 static per-channel、激活 dynamic per-token, 简单的 RTN 即可,不需要校准数据(docs/features/quantization/llm_compressor/fp8.md:79-84)。 其线性层目前只支持 float8_e4m3fn(fp8.py:226-228)。

收益:权重显存减半,FP8 tensor core 可用;vLLM 文档称在 H100/MI300x 上可让显存需求约减半,并带来最高 1.6× 吞吐提升(docs/features/quantization/llm_compressor/fp8.md:3-6)。代价:需要 Ada/Hopper 及以上。

三条路线的“8 bit”不是同一件事:W4A16 的 4 bit 只作用于权重;INT8 W8A8 的 8 bit 同时作用于权重和激活; FP8 的 8 bit 是浮点编码,scale 只负责把张量搬进可表示区间,尾数位数由格式决定。比较时先看量化对象,再看 bit 数。

03

粒度决定误差,scale 决定动态范围

per-tensor 最省,group-wise 最准;AWQ 调缩放,GPTQ 补误差,FP8 选指数位。

3.1 三种粒度:一个 scale、每通道一个、每组一个

对称均匀量化的基本式是 q = round(w / Δ),反量化是 ŵ = q · Δ; 非对称量化再加 zero-point z。步长 Δ 由量化范围决定:

Δsym = max|w| / (2N−1 − 1)     Δasym = (max(w) − min(w)) / (2N − 1)
N 是位宽;对称量化常配 zero-point=0,非对称量化用整数零点平移

粒度不是免费的。以 INT4、G=128、FP16 scale、4-bit zero 为例,每组 128 个权重额外带 16 + 4 = 20 bit, 平均每权重多 0.156 bit;若 zero-point 用 FP16 存,则多 0.25 bit。组再小,元数据占比继续上升。 这部分在 §04 的显存表里没有计入,后文会单独说明。

3.2 AWQ:按激活幅度保护关键通道,用等价缩放降低误差

AWQ 的起点是一个实验观察:LLM 的权重不是同等重要,少量关键权重对输出影响很大; 而“关键”应由激活分布判断,不是权重范数。AWQ Table 1 在 OPT-6.7B、INT3-g128 上比较了保留 1% 通道为 FP16 的效果: 按激活选,PPL 从 RTN 的 23.54 降到 11.39;按权重选是 22.37,随机选是 24.23。

直接保留 FP16 通道会破坏硬件友好的规则矩阵,AWQ 改用等价缩放:对输入通道乘 s, 同时把激活除以 s:

W′ = W · diag(s)     X′ = diag(s)−1 · X     W′X′ = WX
逐通道缩放不改变线性层输出;缩放可以融合进前一个算子,不增加运行时 kernel

缩放因子不是任意搜的。AWQ 把搜索空间压成一个参数 α:s = sXα, 其中 sX 是校准集上每个输入通道的平均激活幅度;然后最小化量化后的输出误差:

s* = arg mins ‖ Q(W · diag(s)) · (diag(s)−1X) − WX ‖22
Q 是权重量化函数;α 在 [0,1] 上网格搜索,按模型选最优值(AWQ §3.2,Eq.5)

AWQ 的缩放因子只依赖激活的平均幅度,不做回归或反向传播,因此校准集可以很小;代价是它仍然需要一小批能代表部署分布的校准样本。 vLLM 的 AWQ checkpoint 把 qweight、qzeros、scales 分开存, pack_factor = 32 / weight_bits,加载后先转成 GPTQ-like 标准布局,再交给 Marlin 等 kernel (auto_awq.py:198,424-461,512-525)。

3.3 GPTQ:逐层量化,用剩余权重的更新补偿舍入误差

GPTQ 把问题写成逐层的输出重建:给定一层权重 W 和校准输入 X,找量化权重 Ŵ 使 ‖WX − ŴX‖22 最小(GPTQ Eq.1)。它沿用 OBQ 的思路:一次量化一个权重, 然后更新所有尚未量化的权重,以补偿当前舍入造成的误差。

δF = − (wq − quant(wq)) · (H−1):,q / [H−1]qq     H = 2XXᵀ
F 是尚未量化的权重集合;H 是校准输入的二阶信息,δF 是对剩余权重的补偿更新(GPTQ Eq.2)

原始 OBQ 的复杂度是 O(drow · dcol3),无法扩展到十亿参数。 GPTQ 做了三处工程化:所有权重行使用同一固定顺序,把逆 Hessian 的更新从每权重一次降到每列一次; 以 B=128 列为一组做lazy batch-update;用 Cholesky 分解提高数值稳定性(GPTQ §4)。 最终它把 175B 模型的量化时间压到约 4 GPU 小时(GPTQ §1)。

和 AWQ 的区别:GPTQ 不改变激活,也不显式保护关键通道;它用校准输入的 Hessian 做误差补偿。 AWQ 则用激活幅度做等价缩放,让敏感通道在量化网格上更不容易被舍入。两者可以叠加,AWQ 论文 Table 9 报告了 AWQ 与 GPTQ 组合在更低 bit 下的结果。

3.4 FP8:指数位换动态范围,尾数位换精度

FP8 不是整数网格,它由符号位、指数位、尾数位组成。E4M3 的指数偏置是 7,最大正规数 448,最小正规数 2−6; E5M2 的偏置是 15,最大 57344,最小正规数 2−14。E5M2 的最大正规数是 E4M3 的 128 倍(57,344 ÷ 448 = 128),即高 7 个 binade,总 binade 数也更多(论文称 E5M2 有 32 个,E4M3 调整后 18 个), 但尾数只有 2 位,表示同样数量级时的相对误差更大(FP8 Formats Table 1 与 §3.1)。

属性E4M3E5M2
符号 / 指数 / 尾数1 / 4 / 31 / 5 / 2
指数偏置715
最大正规数44857,344
最小正规数2−62−14
无穷 / NaN无无穷,单一 NaN 编码有 ±inf 与多个 NaN 编码
表 1 · FP8 两种编码的可表示范围(FP8 Formats Table 1;数值按原表转录)

把一个 FP16/BF16 张量搬进 FP8,需要一个 scale:scale = amax / maxFP8(amax = max|w|), 然后 wFP8 = clamp(w / scale, −maxFP8, maxFP8)。 E4M3 的 maxFP8 是 448,E5M2 是 57344。scale 可以是 per-tensor、per-channel, 也可以是 per-block;粒度越细,越能适配局部动态范围,但元数据越多。

vLLM 的 Fp8Config 支持 static/dynamic 两种激活方案,以及可选的 weight_block_size (fp8.py:92-135)。非 block 路径下,权重用 static per-tensor scale;动态激活在 CUTLASS 上走 per-token scale,否则走 per-tensor scale(fp8.py:266-274)。FP8 的线性层只支持 float8_e4m3fn,这是 torch._scaled_mm 的限制(fp8.py:226-228)。 E5M2 的动态范围更大,但 vLLM 的 FP8 线性层不走 E5M2;在 vLLM 里它主要出现在 KV cache 的 dtype 选项里 (docs/features/quantization/quantized_kvcache.md:39-41)。

04

权重账和 KV 账要分开算

70B 权重 FP16 是 140 GB;KV cache 每 token 只占几百 KiB,但会随上下文和 batch 线性增长。

权重显存的公式很直接:bytes = P × bw / 8,P 是参数量,bw 是每权重位数。 表 2 按 70B 参数、十进制 GB 计算,不计 scale、zero-point、embedding 和 lm_head。

权重格式bits / param70B 权重显存说明
FP16 / BF1616140 GB未量化基线
FP8 E4M3870 GBW8A8 或 W8A16
INT4435 GBweight-only,group scale
INT3326.25 GBAWQ 论文的极端设置;packing 与 kernel 支持更窄
表 2 · 70B 参数模型在不同权重格式下的理论显存(十进制 GB;未计 scale、zero-point、embedding、lm_head)

KV cache 的公式是 bytes/token = 2 × L × Hkv × dhead × bkv: 2 表示 K 和 V,L 是层数,Hkv 是 KV head 数,dhead 是每头维度, bkv 是每个元素的字节数。Llama-2-70B 的公开配置是 80 层、8 个 KV head、dhead=128 (config.json;GQA 让 KV head 数远小于 64 个 query head)。

上下文长度(batch=1)FP16 KVFP8 KV节省
4,096 token1.34 GB0.67 GB0.67 GB
32,768 token10.74 GB5.37 GB5.37 GB
131,072 token42.95 GB21.47 GB21.47 GB
表 3 · Llama-2-70B 的 KV cache 手算(80 层、8 个 KV head、head_dim 128、batch=1;十进制 GB)

长上下文时,KV 量化的绝对节省更大:4k 上下文下,FP16 KV 只有 1.34 GB,相比 140 GB 权重是零头; 到 128k,FP16 KV 已经到 42.95 GB,FP8 把它压到 21.47 GB。若 batch=16,所有 KV 数字再乘 16。

70B 权重
140.00 GB
KV cache
1.34 GB
合计
141.34 GB
KV / token
320 KiB
当前组合
FP16 权重 140.00 GB + FP16 KV 1.34 GB;合计 141.34 GB。KV 每 token 320 KiB。

权重固定按 70B 参数计算;KV 固定按 Llama-2-70B 的 80 层、8 个 KV head、head_dim=128 计算。 两个按钮组独立:weight-only 的 INT4 权重通常仍配 FP16 KV,FP8 W8A8 才可能同时用 FP8 KV。

手算对账:两组参数,JS 与手算同一套公式点开看每一步

组 A:70B 权重,FP16 / FP8 / INT4

  1. FP16:70 × 109 × 2 B = 140 × 109 B = 140 GB。
  2. FP8:70 × 109 × 1 B = 70 GB。
  3. INT4:70 × 109 × 0.5 B = 35 GB;不计 group scale 与 zero-point。

组 B:Llama-2-70B 的 KV cache,4096 token,batch=1

  1. 每 token 元素数:2 × 80 × 8 × 128 = 163,840 个元素(K 和 V 各一半)。
  2. FP16 每 token:163,840 × 2 B = 327,680 B = 320 KiB。
  3. 4096 token:327,680 × 4096 = 1,342,177,280 B ≈ 1.34 GB;JS 也显示 1.34 GB。
  4. FP8 每 token:163,840 × 1 B = 163,840 B = 160 KiB;4096 token 为 671,088,640 B ≈ 0.67 GB,JS 也显示 0.67 GB。
  5. 合计:FP16 权重 + FP16 KV = 140 + 1.34217728 ≈ 141.34 GB; FP8 权重 + FP8 KV = 70 + 0.67108864 ≈ 70.67 GB;JS 分别显示 141.34 GB / 70.67 GB。

页面里的 JS 只做基本算术和 toFixed(2),没有随机数;上面显示的 1.34、0.67、141.34、70.67 GB 都能在模拟里逐位复现。 表 3 的 32k / 128k 行只是把 4096 换成对应上下文长度。

这个计算器没算什么简化与未覆盖分支
  • scale 与 zero-point 开销:INT4/INT3 的 group scale(通常 FP16)和 zero-point 会额外占显存;组越小,占比越高。表 2 与滑块只算纯权重位。
  • KV scale 与 block 元数据:per-tensor/per-head 的 k/v scale 和 block table 也占少量显存;这里只算 K/V 元素本身的字节。
  • 非权重参数:embedding、lm_head、norm 参数、bias 没有计入;它们可能保持 FP16。
  • 激活与临时缓冲:prefill 的激活、attention workspace、CUDA graph 捕获的缓冲没有计入。
  • KV 分配粒度:真实 vLLM 按 block 分配 KV,存在块内碎片和预留;这里按连续 token 数计算。
  • kernel 融合:weight-only 的反量化、scale 乘法、GEMM 是否融合,会改变实际吞吐;这里只算字节数,不预测延迟。
  • 并行与量化误差:tensor parallel、pipeline parallel 会切分权重和 KV;量化误差对 PPL 的影响也不在计算器里。
05

反量化发生在 kernel 里,不在显存里

weight-only 在 GEMM 主循环反量化权重;W8A8 先量化激活再进 tensor core;KV 在写入和读取 cache 时转换。

① 权重路径(weight-only) FP16 权重 checkpoint 离线量化 AWQ / GPTQ / FP8 INT4 / FP8 权重 + scales / zeros kernel 内反量化 unpack / cast → FP16 FP16 GEMM 权重不回写 DRAM ② 激活路径(W8A8) 激活 X(FP16) per-token 统计 动态量化 X INT8 / FP8 + scale INT8 / FP8 GEMM tensor core 输出反量化 × x_scale × w_scale FP16 输出 residual / norm ③ KV cache 路径 K/V 投影 FP16 写入时量化 k_scale / v_scale FP8 KV cache E4M3 或 E5M2 读取时反量化 或 FA3 保持 FP8 Attention softmax(QKᵀ)V 关键位置 weight-only:反量化在 kernel 内完成,低精度权重留在显存;激活不量化,GEMM 走 FP16/BF16。 W8A8:图示为动态方案,激活在每次前向量化;静态方案用校准 scale;输出用 x_scale 与 w_scale 反量化。 KV:K/V 写入 cache 时量化,读取时反量化;FA3 可在 FP8 域做 attention(docs/features/quantization/quantized_kvcache.md:7)。 图中实线是数据流;未画 scale 广播、zero-point 和 kernel 融合的细节。
图 1 · 量化/反量化发生在哪里。权重路径是离线量化 + 在线反量化;激活路径只有 W8A8 才需要; KV 路径在 cache 写入和读取两端各做一次转换。示意图,不是某个 kernel 的逐指令还原。

三条路径的共同点是:反量化尽量不把高精度权重/激活写回 DRAM。 AWQ 的 TinyChat 把反量化与矩阵乘法融合,避免中间结果来回搬(AWQ §4.1);GPTQ 也实现了一个 “量化矩阵 × 全精度向量”的 kernel,在需要时动态反量化权重(GPTQ §5)。vLLM 则按 checkpoint 和硬件选择 Marlin、CUTLASS、DeepGEMM 等后端(docs/features/quantization/llm_compressor/fp8.md:19-25)。

vLLM 的两个入口参数值得对照:vllm/config/model.py:271-275 的 quantization 默认是 None,此时先读模型 config 里的 quantization_config,没有再按 dtype 加载; vllm/config/cache.py:238-250 的 cache_dtype 默认是 "auto",也就是 KV cache 跟随模型 dtype。要量化 KV,必须在 kv_cache_dtype 里显式指定 fp8_e4m3 或 fp8_e5m2 (docs/features/quantization/quantized_kvcache.md:39-41)。

KV 路径的粒度限制更明显:kv_cache.py 的 BaseKVCacheMethod 在 checkpoint 加载路径上只接受 per-tensor 的 k/v scale(kv_cache.py:135-138);per-attention-head 的 scale 需要 Flash Attention 后端和 llm-compressor 的校准流程(docs/features/quantization/quantized_kvcache.md:15-19)。默认不校准时,vLLM 把 scale 设为 1.0 (docs/features/quantization/quantized_kvcache.md:25-30),并在 FP8 E4M3 的 scale 仍为 1.0 时给出警告 (kv_cache.py:157-162)。

06

同一张表内可比,跨论文不可比

三组实验的模型、数据集、基线和指标不同;只在自己的表内比较,不把不同论文的数字拼成一条曲线。

先看 weight-only。表 4 汇总了 AWQ 在 Llama-2-7B / 70B 上的 WikiText2 perplexity(PPL,越低越好), 同一张表里同时跑 FP16、RTN、GPTQ、GPTQ-R 和 AWQ;INT3/INT4 的 group size 都是 128。

模型精度FP16RTNGPTQGPTQ-RAWQ
Llama-2-7BINT3-g1285.476.666.436.426.24
Llama-2-7BINT4-g1285.475.735.695.635.60
Llama-2-70BINT3-g1283.323.983.883.863.74
Llama-2-70BINT4-g1283.323.463.423.433.41
表 4 · AWQ Table 4:Llama-2 的 WikiText2 PPL(INT3/INT4,group size 128;同一模型同一数据集内比较)

表 4 里,Llama-2-7B INT3 的 AWQ 是 6.24,比 FP16 的 5.47 高 0.77;Llama-2-70B INT4 的 AWQ 是 3.41, 比 FP16 的 3.32 高 0.09。这里的 0.77 和 0.09 都是 PPL 绝对差。

再看 GPTQ。它在 OPT 系列上报告 WikiText2 PPL,表 5 取 13B 和 175B 两档,保留 FP16、RTN、GPTQ 的 4-bit 与 3-bit 结果。

模型方法bitsWikiText2 PPL
OPT-13BFP161610.13
OPT-13BRTN411.32
OPT-13BGPTQ410.31
OPT-13BRTN33,400
OPT-13BGPTQ311.61
OPT-175BFP16168.34
OPT-175BRTN410.54
OPT-175BGPTQ48.37
OPT-175BRTN37,300
OPT-175BGPTQ38.68
表 5 · GPTQ Table 3:OPT-13B 与 OPT-175B 的 WikiText2 PPL(RTN 与 GPTQ 同表比较;3-bit RTN 在 13B/175B 上分别为 3,400/7,300)

GPTQ 在 4-bit 上把 OPT-175B 从 8.34 拉到 8.37,只差 0.03;3-bit RTN 直接崩到 7,300, 而 GPTQ 3-bit 仍是 8.68。论文还指出一个反直觉趋势:更大的模型通常更容易量化, 但 OPT-66B 是例外,作者把原因归于该模型早期层存在大量 dead units(GPTQ §5 与脚注 2)。

最后看 W8A8。表 6 汇总了 SmoothQuant 在 OPT-175B 上的 7 个零样本任务平均准确率和 WikiText2 PPL; 朴素的 W8A8、ZeroQuant、LLM.int8() 与 SmoothQuant-O3 都在同一张表里。

方法平均准确率(7 任务)WikiText2 PPL
FP1666.9%10.99
W8A8(naive)35.5%93,080
ZeroQuant35.8%84,648
LLM.int8()66.7%11.10
SmoothQuant-O366.8%11.17
表 6 · SmoothQuant Table 3:OPT-175B 的 7 任务平均准确率与 WikiText2 PPL(W8A8 为朴素 per-tensor 基线;准确率越高越好,PPL 越低越好)

表 6 说明 W8A8 的难点不在权重,而在激活:朴素 W8A8 的平均准确率从 66.9% 掉到 35.5%, SmoothQuant-O3 回到 66.8%,差 0.1 个百分点;PPL 从 10.99 变成 11.17,差 0.18。 LLM.int8() 也能保持精度,但它用 FP16 表示离群值,混合精度路径的开销另算。

吞吐方面,三篇论文/文档的口径不同,不能把数字直接横向比较:

weight-only 的收益前提是解码阶段 memory-bound。如果 batch 很大、prefill 很长,GEMM 变成 compute-bound, weight-only 的收益会被摊薄;W8A8/FP8 则还能利用 tensor core 的计算吞吐。精度损失也不是一个固定值: 它取决于模型、数据集、bit 数、group size 和校准集,表 4–6 只代表各自的实验设置。

07

能压不等于该压:离群值、校准集和 kernel 支持

量化失败通常不是 bit 数不够,而是粒度、校准分布或 kernel 不支持。

精度边界

离群值与校准集

激活离群值固定在少数通道,per-tensor/per-token 会被它们支配;per-channel 激活量化虽然准, 但与 INT8 GEMM 不兼容。SmoothQuant 的迁移强度 α 需要按模型调:论文在 OPT/BLOOM 上用 0.5, GLM-130B 因为离群值更多而用 0.75(SmoothQuant §5.1)。

AWQ 只需要统计激活幅度,校准集可以小;GPTQ 需要校准输入来估 Hessian。两者都对校准分布敏感, 但 AWQ 明显更鲁棒:分布偏移时 AWQ 的 PPL 仅升 0.5–0.6,GPTQ 升 2.3–4.9(AWQ Figure 8)。 校准集与部署分布偏离时,量化误差会上升。vLLM 的 KV cache 在不校准的时候把 scale 设为 1.0, FP8 E4M3 下可能出问题(docs/features/quantization/quantized_kvcache.md:25-30;kv_cache.py:157-162)。

系统边界

kernel 与硬件

FP8 计算需要 NVIDIA compute capability ≥ 8.9(Ada/Hopper/Blackwell);Turing/Ampere 只能走 weight-only W8A16 的 FP8 Marlin(docs/features/quantization/llm_compressor/fp8.md:15-17,24-25)。INT8 W8A8 在 Blackwell(≥10.0)上不支持, 文档建议改用 FP8(docs/features/quantization/llm_compressor/int8_w8a8.md:11-13)。

KV 的 per-head scale 只在 Flash Attention 后端可用,且需要 llm-compressor 校准; BaseKVCacheMethod 的 checkpoint 加载路径只接受 per-tensor 的 k/v scale(docs/features/quantization/quantized_kvcache.md:15-19; kv_cache.py:135-138)。group size、bit 数、packing 顺序也都受 kernel 限制。

KV 边界

KV 量化省的是另一块显存

KV 量化不减少权重显存,只减少 cache;它也不会让 GEMM 变快。它的收益在长上下文、大 batch 或高并发时最明显, 因为 KV 随 token 数和 batch 线性增长。FP8 KV 的 E4M3 只有 3 位尾数,K/V 的分布、scale 粒度和 attention 后端都会影响误差;per-tensor 是每个 K/V 张量各用一个 scale;per-head 粒度则走另一条路径(docs/features/quantization/quantized_kvcache.md:15-19)。

反过来,weight-only 不减少 KV cache;KV 占满时需要单独处理 KV。 两类量化解决的是不同的账。

使用边界

什么时候别用

需要严格可复现的数值、或模型对激活离群值特别敏感时,先做小规模评测再上线;不要假设某个 bit 数“通用”。 推断计算密集的 prefill、很大的 batch、或 kernel 不支持的 group size/bit 数下,量化的吞吐收益可能消失, 甚至因为反量化开销变慢。

表 4–6 都是特定模型、数据集和基线下的 PPL/准确率;换模型、换数据或换 kernel,数字可能不同,不能当成普遍保证。不同论文的基线和指标也不同,不能拼在一起比较。

一句话总结:weight-only 用 4-bit 权重换显存和带宽,W8A8/FP8 用 8-bit 权重加激活换 tensor core 吞吐, KV 量化用 8-bit cache 换长上下文容量。三者的收益来源不同,失败模式也不同;先确认瓶颈在哪一本账,再选量化对象。