第 15 章 数值表示与量化对象¶
开场问题:W4A16 把权重压小了四倍,为什么大 batch 下反而比 bf16 还慢?
15.1 三个对象,三条瓶颈¶
"量化"这个词被用得太笼统,实际有三个互相独立的对象,省的东西完全不同:
- 权重量化:减少权重占的显存,以及 Decode 时每步要读的字节数。它打的是显存容量和带宽,正好对准 Decode 的瓶颈。
- 激活量化:让矩阵乘的两个输入都是低精度,从而能用上低精度的 tensor core。它打的是算力,主要收益在 Prefill 和大 batch。
- KV Cache 量化:减少每个 token 的 KV 字节数。它打的是显存容量,直接换来更大的并发和更长的上下文。
三者可以任意组合,但要各自算账。常见组合 W4A16(权重 4 bit、激活 16 bit)、W8A8、FP8 全量化,适用场景完全不同。
15.2 数字格式:范围和精度的交换¶
同样是 8 位,INT8 是均匀的整数网格,FP8 用几位当指数、几位当尾数,牺牲精度换动态范围。FP8 常见两种分配:E4M3(4 位指数 3 位尾数,范围小精度高,适合权重和激活)和 E5M2(范围大精度低,适合梯度)。
4 位这一层,除了 INT4,还有 2024 年之后硬件开始原生支持的 microscaling 格式(如 MXFP4、NVFP4),它们的思路是让一小组元素共享一个缩放因子,在极低位宽下保住动态范围。
但格式本身不产生加速。真正决定速度的是目标硬件有没有对应的原生指令、以及推理引擎有没有实现对应的高效 kernel。这一点在 15.5 节展开。
15.3 量化粒度¶
一个 scale 管多少个数,决定了精度和开销的平衡:
- per-tensor:整个张量一个 scale。最省、最快,但只要有一个离群值就会把整个网格拉坏。
- per-channel:每个输出通道一个 scale。权重量化的常见选择,开销很小,效果明显更好。
- per-group:每 32/64/128 个权重一组,各自一个 scale。INT4 的标配,因为 4 位只有 16 个刻度,必须靠细粒度 scale 才能用。
- per-token(激活):每个 token 一个 scale,在线计算。适配激活随输入变化的特性。
粒度越细,量化误差越小,但存储 scale 的额外开销和反量化的计算越多。这也是 INT4 模型实际大小往往大于"参数量除以 4"的原因。
15.4 KV Cache 量化的特殊性¶
KV 量化和权重量化有个本质区别:权重量化的误差是静态的、可以离线补偿的;KV 的误差会一路参与后续所有 token 的注意力计算。
它的收益很直接——KV 从 bf16 降到 FP8 就是并发翻倍(回到第 11 章,并发直接等于吞吐)。但质量评估必须专门做长上下文测试,因为误差在长序列上累积,短测试完全看不出来。实践中 FP8 KV 相对安全,INT4 KV 要谨慎,且通常对 K 和 V 采用不同策略(K 参与 softmax,对误差更敏感)。
15.5 为什么 W4A16 在大 batch 下会变慢¶
回到开场问题。W4A16 的实际执行是:权重以 INT4 存在显存里,计算前要反量化成 bf16,再做 bf16 的矩阵乘。
小 batch(Decode)时这很划算:瓶颈是读权重,读取量降到 1/4,反量化的那点计算完全被掩盖。
大 batch(Prefill 或高并发)时情况反转:瓶颈已经变成算力,权重读取不再是主要成本,而反量化是实打实增加的计算。此时 W4A16 不但没有收益,还比直接用 bf16 多了一道工序。如果目标硬件支持 FP8 或 INT8 的原生矩阵乘,W8A8 在这个工作点上会明显更好——因为它的矩阵乘本身就是低精度的,不需要先还原。
结论:量化方案要按目标工作点选。端侧和低并发场景选 weight-only 低位宽;数据中心高吞吐场景优先考虑有原生算子支持的 FP8/INT8 全量化。
核心问答¶
Q:INT4 一定比 INT8 快吗? 不一定。显存一定更省,速度取决于 batch、硬件指令支持和反量化开销。大 batch 下常常更慢。
Q:FP8 和 INT8 怎么选? 看硬件。数据中心新卡有 FP8 原生支持且动态范围更友好,通常首选;端侧 NPU 的 INT8 生态更成熟。
Q:KV 量化为什么要单独评测? 它的误差会持续参与注意力计算并在长序列上累积,短文本测试看不出问题。
Q:为什么 INT4 模型文件比"参数量÷4"大? 因为要额外存每个 group 的 scale(有时还有 zero point),group 越小这部分越多。