第 12 章 · KV 缓存与混合注意力Quantization

quantization

深入阅读 · 第 12 章 KV cache——把每个 weight 存得更省 bit,同一个 memory-bound 的 decode 就跑得更轻、更快。

本章的一切都指向同一个瓶颈:decode 是 memory-bound 的。每个 token 都要把整个模型从内存里重读一遍,而在 batch 1 时,这整趟扫描只换来一个 token。roofline 给它标了个数——你被钉在最左侧的 memory 悬崖上。quantization 是撬动这道悬崖最直接的一根杠杆,而且它不动模型结构:形状不变、前向过程不变——只是把每个 weight 存得更省 bit,一份落在更粗格点上的近似副本。每个 weight 的字节更少,就意味着要搬运的更少,memory-bound 的 decode 也就跑得更轻。

更少的 bit,更小的模型,更快的 decode

一个 bf16 的 weight——也就是本 demo 用的——占 2 字节。降到 fp8int8 就是 1 字节;int4 只占半字节。占用随着 bit 数直线下降,而因为 decode 是 memory-bound 的,搬运这些 weight 的时间也随之下降。但这不是干净的每级 2×:开销——要把它反量化回一个计算格式、bandwidth 用不满——意味着书里给的是更诚实的每降一个精度级别 +30–50%。逐个切换这些格式,看三根条——大小、速度、质量——一起移动:

quantization——每个 weight 用更少的 bit,更小更快
🔒 你在这里 · demo 用的是 bf16
bit 布局 · bytes/param2 bytes/param1exponent 8mantissa 7weight 占用(约 0.8B 参数)1.6 GB基准——demo 用的就是这个decode 速度(相对 bf16)1.0×基准(1.0×)——memory-bound decode质量序数 · 非测量

bf16≈ 无损——它就是训练精度

书中的脆弱度排序(由弱到强):weights < activations < KV cache < attention

机制——quantization 通过一个 scale factor(外加可选的 zero-point)把连续的 weight 吸附到离散的格点上:real ≈ scale × (q − zero_point)。weight 用 对称 格点,activation 用 非对称 格点;粒度可以是 per-tensor、per-channel 或 per-block。weight-only 方法 GPTQ(基于 Hessian)和 AWQ(名字带 activation,但它只是挑出需要保精度的显著 weight 通道)只量化 weight;SmoothQuantLLM.int8() 是 W8A8(weight activation 一起)—— SmoothQuant 把困难的动态范围从 activation 迁移到 weight,LLM.int8() 把罕见的 outlier 维度保留在 fp16。

对本 demo 要诚实:浏览器内的模型以 bf16 发布——完全没有 quantization。数据中心的服务常跑 fp8 / int8,而更激进的整数与低于 4-bit 的 quant(int4 及更低)多半是 本地推理 的做法(GGUF / Ollama / llama.cpp)。所以这里选 bf16 是一个站得住、且诚实标注的选择——但 同一个模型 的量化副本会小 ≈2×(int8)到 ≈4×(int4),decode 也会快上一截。

它怎么工作——又要付出什么

诀窍是一个 scale factor:一块 weight 被映射到一小撮整数档位上,再由一个共享的浮点数在计算时把它们重新缩放回去(real ≈ scale × (q − zero_point)——其中 zero_point只是把格点整体平移,让真实的 0 依然落在一个精确的码上;详见 number formats 子章节)。bit 越少,格点越粗,代价就是质量。书里把谁更耐受排了个序,由弱到强:weights < activations < KV cache < attention。像 GPTQ AWQ 这类 weight-only 方法只量化 weight,常常一路到 int4 都近乎无损;推进到 weight activation 一起(W8A8,例如 SmoothQuantLLM.int8())就更难,因为 activation 带着更狂野的 outlier。经验法则:fp8 / MXFP8 是 sweet spot——几乎察觉不到损失——而整数格式缺少动态范围,所以对质量敏感的工作你还是坚持浮点,并把低于 4-bit 当成悬崖。

KV cache 也能被量化

这不只关乎 weight。你在本章认识的 bf16 KV cache 本身也是一串字节,decode 每一步都要把它读回来——所以它同样是一个量化对象。它中等敏感(还是书里的那个排序):它的误差会逐 token 累积,因为每个缓存下来的 key/value 会被序列其后的全部内容复用。fp8 / MXFP8 在这里是稳妥之选;激进的整数 KV 量化才是质量开始打滑的地方。

对本 demo 的诚实脚注:浏览器内的模型以 bf16 运行,完全没有 quantization——最简单、保真度最高的选择。书中指出,整数 / 低于 4-bit 的 quant 多半是本地推理的做法(你为 Ollama 或 llama.cpp 下载的那些 GGUF 文件),与数据中心的服务有别。同一个模型的量化副本会小 ≈2×(int8)到 ≈4×(int4),decode 也会实打实地快上一截——这正是整章一直在指向的那根杠杆。