量化与压缩
第 16 课:量化与压缩
上一课我们把推理的瓶颈定位到了显存与带宽。这一课回答一个更直接的问题:能不能让每个参数少占几个字节?量化的动作只有一个——用一把粗糙的尺子替掉高精度数字;但它牵出三个必须分开对待的对象——权重、激活、以及 KV Cache(把每个 token 算过的 Key/Value 存下来、后面直接复用不重算);还有一条被反复误用的质量指标(困惑度),以及一个绕不开的边界:什么时候压精度就够了、什么时候必须换一个更小的模型。本课只讲推理侧量化,训练侧的 FP8 已在第 14 课讲过。
② 用一个离群值算例解释"为什么 int8 几乎无损、int4 开始塌"
③ 说清权重 / 激活 / KV Cache 三种量化对象的差异,并算出 4-bit 能换回多少显存与吞吐、以及怎么评测才不骗自己
前几课我们把模型"服务出去"了:先看清推理被 KV 卡住(第 15 课)。这一课回到模型本身——能不能让每个参数少占几个字节。
量化的全部动作只有一句话:给一串浮点数定一把尺子,再把每个数挪到最近的刻度上——因为模型里每个 token 都要乘过这些权重矩阵,权重少占一个字节,显存与搬运量就少一分。位数决定尺子上有几格——4 bit 就是 16 格。这就像把一张高精度照片另存为低质量版本:格子越少,颜色越容易被"就近归并",丢掉的是细节(数字精度),保住的是轮廓(模型的行为)。
真正决定成败的不是"用了几个 bit",而是格距 s 怎么定:s 定小了,超出范围的值会被截断;定大了,绝大多数值又都挤在零附近。下面用一组具体权重,把这把尺子画出来。
| 数据格式 | 刻度数 | 取值范围 | 相对精度 |
|---|---|---|---|
| fp16 | 约 1,024 档(尾数 10 位) | −65,504 ~ 65,504 | ≈ 0.00049 |
| int8 | 256 | −128 ~ 127(通用范围,对称量化取 −127 ~ 127) | 1/256 ≈ 0.0039 |
| int4 | 16 | −8 ~ 7 | 1/16 = 0.0625 |
| 方案 | 扫过的范围 | 格距 s | 一个数的最大误差 s/2 |
|---|---|---|---|
| 对称(z = 0,用 −7 ~ 7) | [−1.6, 1.6] | 1.6 / 7 = 0.229 | 0.114 |
| 非对称(z = 9,用 0 ~ 15) | [−1.6, 1.2] | 2.8 / 15 = 0.187 | 0.093 |
| 8 个权重(示意) | 整层共用一个 s | 0.05 会被还原成 | 每 4 个分一组 | 0.05 会被还原成 |
|---|---|---|---|---|
| 前 4 个:0.05, 0.1, −0.08, 0.12 | 3.2 / 7 = 0.457 | round(0.109) = 0 → 0(丢光) | 0.12 / 7 = 0.0171 | round(2.92) = 3 → 0.051 ✓ |
| 后 4 个:3.0, −2.5, 2.8, 3.2 | 同上 0.457 | — | 3.2 / 7 = 0.457 | — |
量化有三个旋钮:位宽(决定刻度总数)、对称性(决定刻度怎么摆)、分组粒度(决定每套 s/z 管多少个数)。位宽决定上限,后两者决定实际能不能逼近这个上限。所以"4 bit"从来不是简单地把 16 个格子铺开——它是"非对称 + 分组"之后每个数尽可能贴近刻度的结果。代价是每组要多存一套 s 与 z:group = 128 时约 0.031 B/参数(折合 0.25 bit/参数),这是下张卡片里"4× 压缩为什么实际只有 3.8×"的来源。
上一张卡片说了量化是一把有损的尺子;这一张回答最常被问的那个问题——为什么位宽一降到 4 位就崩,8 位却几乎没事?
答案不在"4 比 8 少 4 个 bit"这个数量上,而在离群值上——因为尺子的格距是按最大值定的,最大值一离群,格距就被整条撑开。可以随便挑一个隐藏层通道,它的值大多安静地待在 0.1 ~ 2 之间,偶尔却蹦出一个 50。格距被这个 50 撑开后,绝大多数正常值的间距就被压缩到不足一格。8 位还有几十个格子可分,4 位只有 16 格——一格都不剩。而这个过程中,困惑度(perplexity)往往还看不出异常。
这可以类比成"用一把以公里为刻度的尺子去量一根头发":不是尺子不准,是它把全部分辨率都花在了那个最长的东西上。下面用具体数字把这条曲线算出来。
| 通道里的激活值 | x ÷ s(s = 50/127 = 0.394) | 取整 | 还原(× 0.394) | 误差 |
|---|---|---|---|---|
| 0.1 | 0.254 | 0 | 0 | 0.100 |
| 0.3 | 0.762 | 1 | 0.394 | 0.094 |
| 0.5 | 1.270 | 1 | 0.394 | 0.106 |
| 1.0 | 2.539 | 3 | 1.181 | 0.181 |
| 2.0 | 5.079 | 5 | 1.969 | 0.031 |
| 50.0 | 126.9 | 127 | 50.0 | 0 |
| 通道里的激活值 | x ÷ s(s = 50/7 = 7.143) | 取整 | 还原 | 结果 |
|---|---|---|---|---|
| 0.1 | 0.014 | 0 | 0 | 丢失 |
| 0.5 | 0.070 | 0 | 0 | 丢失 |
| 1.0 | 0.140 | 0 | 0 | 丢失 |
| 2.0 | 0.280 | 0 | 0 | 丢失 |
| 50.0 | 7.0 | 7 | 50.0 | 保留 |
| 路线 | 做法 | 换回什么 | 代价 |
|---|---|---|---|
| LLM.int8() 混合精度 | 约 0.1% 离群维度走 fp16,其余 99.9% 走 int8 | 8 bit 无损 | 多一条 fp16 计算路径,内核更复杂 |
| SmoothQuant(W8A8) | 数学等价地把激活难度搬到权重 | 权重+激活都 int8,矩阵核加速约 2× | 权重更难量化一点,需校准 |
| W4A16(只量化权重) | 根本不碰激活 | 权重显存与带宽降 4× | 省不了激活侧的计算加速 |
"int8 近无损、int4 开始掉"不是玄学,而是离群值撑开格距的必然结果:格距由最大值定,离群值把格距放大到约 18 倍(本卡离群值是正常最大值的 25 倍),正常值就被挤到不足一格。8 bit 靠格子多,勉强还能分出 4 档;4 bit 直接归零。所以真正的分界线不是"几个 bit",而是这一层有没有离群值、它比正常值大多少倍。反向证据同样重要:后续研究(arXiv:2405.20835)发现新模型(Llama-2/3、Mistral)的离群值已明显变温和,Mistral 7B 近乎免疫——别把 OPT 时代的老结论当定律。
上一张卡片说明离群值出在激活里;这一张把量化对象拆开——同样是"一组浮点数",权重、激活、KV Cache 的难度为什么天差地别?
分界线只有一个问题:这个数是什么时候才知道它有多大的——因为"能离线反复优化"的前提,正是提前知道数值。权重在训练完就固定了,我们可以离线拿校准数据反复试、慢慢优化;激活是每次前向才算出来的,同一个通道这次是 0.1、下次可能是 50,离线只能估个近似范围;KV Cache 更晚——它是运行时逐 token 长出来的,连"一共有多少个数"都要等序列走完才知道。这就像做菜:冷冻食材(权重)可以提前腌好慢慢炖,现杀的鱼(激活)得当场处理,而 KV Cache 相当于边吃边上的菜——你没法提前准备。
正因为难度不同,三种对象的量化方案、粒度、代价也不同,下面逐项对齐。
| 对象 | 数值何时确定 | 离线能否反复优化 | 离群值程度 | 典型方案 |
|---|---|---|---|---|
| 权重 | 训练后固定(静态) | 能(可反复校准、可分组搜索) | 静态显著通道,约 1%(此处指权重侧,与 Card 2 的激活离群不同) | W4A16(GPTQ / AWQ) |
| 激活 | 每次前向才算(动态) | 只能近似(校准集估范围) | 输入相关,可达 100× | W8A8(SmoothQuant) |
| KV Cache | 运行时逐 token 长出 | 只能按统计规律设计粒度 | K 有稳定离群通道、V 没有 | KIVI 2-bit(K 逐通道 / V 逐 token) |
| 方案 | 权重要几个字节 | 激活要几个字节 | 省显存 | 省算力 |
|---|---|---|---|---|
| bf16 基线 | 2 | 2 | 1.0× | 1.0× |
| W4A16 | 0.5 | 2(不动) | 约 3.8×(权重侧) | 1.0×(激活仍是 fp16 矩阵乘) |
| W8A8 | 1 | 1 | 约 2× | 约 2×(走 int8 矩阵核) |
| 张量 | 离群结构 | 该用哪种粒度 | 原因 |
|---|---|---|---|
| Key cache | 少数通道跨 token 持续偏大 | 逐通道(per-channel) | 把离群通道的宽范围限制在它自己的 scale 里,不污染其他通道 |
| Value cache | 没有稳定的通道结构 | 逐 token(per-token) | 注意力输出是 V 的加权和,逐 token 量化能限制误差传播 |
三种对象的困难排序由"数值何时确定"决定:权重(静态,最容易)< KV Cache(运行时,但有统计规律)< 激活(动态,离群最凶)。所以工业默认是 W4A16——只动最容易、收益又最大的权重,绕开最难的激活;想要算力加速才去做 W8A8(要付校准与 SmoothQuant 的功课);KV 量化则通常用 fp8 打底、KIVI 类方案做更低比特。这条排序也是下一张卡片"4 bit 换来多少"的前提。
上一张卡片把三种对象分开了;这一张算清最主流的方案——只量化权重(W4A16)——到底能换回多少显存与吞吐。
显存这笔账非常直白:权重显存 = 参数量 × 每个参数的字节数。bf16 是 2 字节,4 bit 是 0.5 字节,理论就是 4×。吞吐这笔账要用到第 15 课的结论:decode 是带宽受限的,每一步的耗时约等于"把权重读一遍 ÷ 显存带宽"——读得少,就快,就像搬砖时每次少搬几块,来回的趟数自然就少了。
但两笔账都要扣掉一个开销:分组量化每组要额外存一套 scale 与 zero-point(各 2 字节)。group = 128 意味着每 128 个权重多花 4 字节,折合约 0.031 B/参数(≈ 0.25 bit/参数)——这是"4× 压缩实际只有 3.8×"的来源。
| 项 | bf16 | int4(纯权重 0.5 B) | int4 + 分组常数 |
|---|---|---|---|
| 每参数字节 | 2 | 0.5 | 0.531 |
| 70B 权重(全模型) | 140 GB | 35 GB | 37.2 GB |
| TP = 2 每卡 | 70 GB | 17.5 GB | 18.6 GB |
| 相对 bf16 的压缩比 | 1.0× | 4.0× | 3.76× |
| 精度 | 每卡每步搬运 | 一步耗时(÷ H100 的 3.35 TB/s) | 单请求上限 | 相对 |
|---|---|---|---|---|
| bf16 | 70 GB | 20.9 ms | 约 48 token/s | 1.0× |
| int4 | 17.5 GB | 5.22 ms | 约 191 token/s | 4.0× |
| 来源 | 读数 | 条件 |
|---|---|---|
| 理论带宽上限 | 4.0× | 纯权重、忽略一切开销 |
| GPTQ 论文端到端 | A100 3.25× / A6000 4.5× | 175B、相对 FP16 |
| 含分组常数的显存 | 3.76× | group = 128 |
| 常见实测区间 | 约 2 ~ 3×(估算) | 经验区间,要扣反量化开销与内核效率 |
W4A16 的收益是不对称的:显存稳稳拿到约 3.8×,吞吐只在 decode 上兑现、且要打折到 2 ~ 3×。原因上一课已经给出——decode 带宽受限、prefill 算力受限。所以"该不该量化"要按业务问:如果是长输出、高并发(decode 主导),4-bit 收益最大;如果是长 prompt、短输出(prefill 主导),W4A16 主要帮你省显存,加速有限。
上一张卡片把 4 bit 的收益算清了;但"省了显存"只是半个答案——另一半是它掉了多少分,而这个"多少"极大程度上取决于你用什么尺子量。
困惑度低,只说明模型对"常见文本的下一个词"依然猜得准;它衡量不了模型会不会听懂指令、会不会编造事实、能不能在长上下文里找到关键信息。这些能力恰恰是量化最容易伤到的地方,因为它们依赖少数"关键权重/激活",而困惑度把损失平摊到了全部 token 上,把塌方稀释成了看不出的波纹。这就像一个学生的平均分:总分看着没变,但偏科可能已经很严重了。
所以真正的问题不是"困惑度涨了多少",而是"我的业务能力掉没掉"——下面用一个反差极大的例子说明。
| 例子 | 量化前 | 4-bit 后 | 变化 | 直观感受 |
|---|---|---|---|---|
| OPT-175B 困惑度(GPTQ 论文) | (论文基线) | — | +0.03 | 「几乎无损」 |
| Qwen-8B 的 MMLU(Qwen3 量化研究) | 74.7 | 69.3 | −5.4 分 | 「明显掉分」 |
| 指令跟随 / 幻觉检测(7B~405B 评测) | — | — | 退化最明显 | 「业务会出问题」 |
| 模型规模 | 4-bit 典型掉分(估算,需按具体模型实测) | 为什么 |
|---|---|---|
| 70B 及以上 | 通常 < 1 分 | 参数冗余大,量化误差被大量通道摊薄 |
| 7B ~ 13B | 数分 | 冗余中等,GPTQ 类方法开始吃力 |
| < 3B | 可达十几分 | 冗余最少,每个参数都更"关键" |
| 你的业务能力 | 该看什么 | 为什么不能只靠困惑度 |
|---|---|---|
| 通用对话质量 | MT-Bench 类(多轮对话打分基准,注意区分力有限) | 困惑度测不出"答非所问" |
| 知识与推理 | MMLU(多学科选择题基准)/ 数学基准 | 困惑度对少数难题的塌方不敏感 |
| 指令跟随 | IFEval 类(指令格式遵循评测) | 这是量化退化最明显的一类 |
| 长上下文 | LongBench / RULER(长文本检索与理解基准) | 困惑度通常在短文本上算,测不到长程检索 |
验收量化有一条铁律:困惑度是"及格线"而不是"验收线"。它涨一点点只能说明"没有灾难性崩坏",证明不了任务能力没退化。正确的做法是——先看困惑度排除崩坏,再用与业务对齐的任务基准(指令跟随 / 幻觉 / 长上下文)和量化前基线做对照。反向证据也要记住:新模型对量化越来越稳健(arXiv:2405.20835),老论文的"掉分幅度"不能直接套到今天的模型上。
上一张卡片讲了怎么验收量化;这一张回答最后一个决策问题——当你需要"更小更快"时,应该压精度,还是换一个更小的模型?
关键是把两条路改的东西分开:量化改的是"每个参数几个字节",蒸馏改的是"一共有几个参数"。这就像搬家:量化是把每件行李压缩打包(件数不变、每件更小),蒸馏是干脆换一批更少的行李(件数变少)。两者作用在不同维度上,所以原则上可以叠加。
判断顺序也很明确:先问"模型放得下吗、服务得起吗"——放得下就用量化,收益快、成本低;只有当量化到底也放不下、或延迟预算仍然超标时,才需要动用蒸馏。另外要分清作用对象:量化不只作用于权重,也能压激活与 KV cache——所以"要不要量化"和"要不要蒸馏"是两个独立的问题。下面把三条路线的账一起算。
| 手段 | 改变什么 | 什么时候做 | 典型压缩 | 代价 |
|---|---|---|---|---|
| 量化 | 每参数字节数(2 B → 0.5 B) | 部署时(几小时) | 约 4× | 掉 1 ~ 3 分,需评测兜底 |
| 蒸馏 | 参数个数(70B → 8B) | 训练时(一次完整训练) | 2 ~ 10× | 训练算力与数据 |
| 蒸馏 + 量化 | 两者叠加(相乘) | 先训后压 | 可到几十× | 两者代价都要付 |
| 目标 | 方案 | 权重显存 | 能否放下 |
|---|---|---|---|
| 单张 80 GB 卡 | 70B @ 4-bit | 35 GB | 可以(还要留 KV 的空间) |
| 单张 24 GB 卡 | 70B @ 4-bit | 35 GB | 不行(模型本身就超了) |
| 单张 24 GB 卡 | 8B @ bf16 | 16 GB | 可以 |
| 单张 8 GB 卡 | 8B @ 4-bit | 4 GB | 可以(先蒸馏再量化) |
| 诉求 | 量化能做到吗 | 该用什么 |
|---|---|---|
| 服务成本降下来 | 能(4× 显存、decode 加速) | GPTQ / AWQ |
| 延迟预算是硬上限 | 通常能(decode 加速) | 量化 + 引擎优化(第 18 课) |
| 要进手机 / 边缘设备 | 不够,参数量仍太大 | 蒸馏(+ 量化) |
| 要换掉模型的能力上限 | 不能(参数量的差距压不出来) | 蒸馏 / 换模型 |
决策顺序只有三步:① 先试量化(几小时、约 4×、掉 1 ~ 3 分)——它解决绝大多数"服务太贵"的问题;② 量化到底仍放不下、或延迟仍超标,再考虑蒸馏(一次训练、改参数量的量级);③ 两端都要,就先蒸馏再量化,两个乘数相乘。而这一切的前提是上一张卡片那句——量化之后必须用任务基准验收,不能只看困惑度。到这里,推理侧的显存与压缩讲完了;下一课我们把镜头拉远,看这些组件怎么被一个推理引擎组装起来。
最后一个常被混淆的概念:GGUF(GPT-Generated Unified Format,llama.cpp 的模型文件格式——把权重、分词器、元数据打包进一个文件,支持 2~8 bit 档位,Ollama、LM Studio 直接读取)属于部署格式,和上面讲的量化原理是两个层次:量化决定「每个权重用几位表示」,GGUF 决定「这些权重怎么装进一个文件、被谁读出来」——选量化方案时看精度与吞吐,选部署格式时看推理框架与硬件。