显存不够时,第一个被想到的手段通常是量化:把 16 位浮点换成 8 位甚至 4 位整数,权重体积直接砍半再砍半。听起来像白捡,但它是一笔交易——用一点精度换显存和带宽。
入门要搞清的不是"哪种量化方法最强",而是三件事:它到底省掉了什么、误差从哪里来、怎么判断这一刀砍得值不值。方法名字(GPTQ、AWQ、SmoothQuant、bitsandbytes 等)是后面的事,先有这套判断框架,才不会被"4bit 无损"这类说法带偏。
1. 量化的本质
量化就是把连续的浮点数值映射到有限的整数格子上,再配一个缩放因子还原:
量化: q = round(x / scale) + zero_point
反量化: x' ≈ (q - zero_point) × scale
bit 与字节:字节数 = 位宽 / 8 # 16bit→2,8bit→1,4bit→0.5
因为格子有限,落不进格子的信息就丢了,这就是量化误差的唯一来源。所有方法(分组、逐通道、混合精度、校准)都是在决定"格子怎么切、切多细、哪里切细一点"。
2. 量化省的是什么,不省什么
| 省掉 | 不省 |
|---|---|
| 权重占用的显存 | 总浮点运算量(INT4 不会让矩阵乘变成 4 倍速) |
| 每步 decode 要读的字节数 | KV Cache(除非单独做 KV 量化) |
| 从显存到计算单元的搬运时间 | 激活峰值(通常还在,甚至因为算得快而更集中) |
| 从磁盘/网络加载的传输量 | 调度、排队、框架开销 |
关键推论:收益主要在带宽受限的场景。decode 每步都要把权重完整读一遍,权重变小 → 读得快 → 每 token 更快、能塞更多请求。而在 prefill 这种算力受限的阶段,量化带来的提速往往有限,甚至因为反量化开销而略慢。
权重显存 = 参数量 × 位宽 / 8
显存下降比例 = 1 − 量化位宽 / 原始位宽
decode 单步读取量 ≈ 权重字节(该项随位宽线性下降)
3. 几种常见形态的入门对照
| 形态 | 权重精度 | 激活精度 | 显存收益 | 大致适用 |
|---|---|---|---|---|
| FP16 / BF16 | 16bit | 16bit | 1×(基线) | 显存充裕、精度敏感 |
| INT8 仅权重(W8A8 之外的 W8) | 8bit | 16bit | 约 2× | 保守方案,质量风险小 |
| INT8 权重+激活(W8A8) | 8bit | 8bit | 约 2×,另有算力收益 | 需要硬件 INT8 算力支持 |
| INT4 仅权重(W4A16) | 4bit | 16bit | 约 3–4×(实际不到 4×) | 显存紧张的 decode 场景 |
| KV Cache 量化 | — | KV 单独降精度 | 按 KV 精度比例 | 长上下文、高并发的关键手段 |
"仅权重"与"权重+激活"的差别值得单独说清:
- 仅权重量化(weight-only):只把权重压小,计算时反量化回高精度再做乘加。收益是显存与读取带宽,实现简单,精度风险相对可控。
- 权重+激活量化(W8A8 等):权重和激活都压小,可用整数张量核直接算,收益还包括算力利用率。代价是需要校准激活的分布,对离群值更敏感,实现与硬件支持要求更高。
入门选型的朴素建议:先把显存问题解决(仅权重),再考虑算力问题(权重+激活)。不要一上来就同时动两头,出问题无法归因。
4. 量化误差从哪里来
| 来源 | 机制 | 典型表现 |
|---|---|---|
| 离群值 | 少数极大值把缩放因子撑大,其余值被挤到很少的格子上 | 整体精度崩,个别通道失真严重 |
| 通道尺度差异 | 不同通道数值范围差几个量级,共用一组 scale 时小通道被抹平 | 特定能力退化(如数字、代码) |
| 分组粒度太粗 | 一组共享一个 scale,组内分布差异大 | 误差随组大小上升 |
| 校准数据不匹配 | 用通用语料统计,实际流量是长文档/代码 | 线上比离线差,离线测不出来 |
| 层敏感度不同 | 某些层(如首尾层、norm)对精度格外敏感 | 局部退化,整体困惑度看不出 |
对应的入门解法各一句:逐通道或小分组切细格子;混合精度把敏感层留在高精度;校准集贴真实分布;用真实流量抽样验证。
5. 校准的作用与最小做法
校准就是用一小批有代表性的数据跑一遍前向,统计每一层激活(或权重)的数值范围,据此定 scale。没有校准的对称量化只能靠猜范围,离群值一来就崩。
最小可用做法(伪代码,接口名按所用框架核对):
# 1) 准备校准集:几十到几百条,必须贴近真实流量(长度分布、领域、语言)
calib = load_jsonl("CALIB_SET.jsonl")[:NUM_CALIB]
# 2) 跑前向,只统计分布,不更新任何权重
with torch.no_grad():
for batch in calib:
model(**tokenize(batch)) # hook 里累计每层 min/max 或分位数
# 3) 由统计量生成 scale(逐通道 / 分组),再执行量化导出
# 4) 立刻做一次冒烟:同一条 prompt,量化前后各跑一遍,比对输出
三个必须遵守的点:校准集不能只用训练集(会高估效果);不能太少(几十条起,且要覆盖长度上界);不能只覆盖短文本(长上下文场景要单独放长样本)。
6. 量化后可能出现的现象
| 现象 | 说明 | 怎么发现 |
|---|---|---|
| 困惑度上升 | 最常见,幅度取决于位宽与模型 | 固定评测集算困惑度,与基线比 |
| 特定任务退化 | 数学、代码、结构化输出(JSON)最容易先坏 | 分任务跑小评测集 |
| 长上下文更差 | KV 量化或长序列累积误差 | 按不同上下文长度分段测 |
| 输出格式不稳 | 该出 JSON 时漏括号、该停时不停 | 结构化输出成功率统计 |
| 重复与截断 | 分布尾部被压平 | 人工抽读 + 重复率指标 |
| 多语言退化 | 小语种在量化后最先掉 | 分语言抽样 |
注意一个反直觉的点:困惑度只涨一点点,任务表现可能掉很多。因为困惑度是全 token 平均,而任务失败往往由少数关键 token 决定。所以困惑度只能当筛查,不能当验收。
7. 评估量化的三个层次
| 层次 | 做什么 | 回答的问题 | 成本 |
|---|---|---|---|
| 1 困惑度 | 固定语料算 PPL,与基线对比 | 有没有整体性损伤 | 低 |
| 2 任务集 | 跑自己的任务评测集,分任务看 | 哪类能力退化、退化多少 | 中 |
| 3 真实流量抽样 | 从线上日志抽真实请求,人工/规则比对 | 用户实际会不会遇到问题 | 高,但最可信 |
三层缺一不可,且顺序不能颠倒:先用便宜的筛查,再用贵的验收。只做第 1 层就上线,是量化翻车最常见的路径。
# 验收口径建议写成可比较的形式
基线:FP16 在 TASK_SET 上的通过率 = A
候选:INT4 在 TASK_SET 上的通过率 = B
门槛:B ≥ A − 允许退化幅度,且任一子任务不得低于 A 的某个下限
另加:真实流量抽 N 条人工比对,格式错误率不得上升
具体的允许退化幅度由业务定,不要抄别人的数字——它对不同任务差别极大。
8. 混合精度与逐通道量化
- 逐通道量化(per-channel):给每个输出通道单独一个 scale,而不是整个张量共用一个。一句话:把格子切细,代价是元数据变多。
- 混合精度(mixed precision):把敏感层(首尾层、norm、部分 attention)留在高精度,其余层量化。一句话:把精度预算花在敏感的地方。
这两个手段可以叠加,也是"4bit 几乎无损"这类说法背后的真实原因——它们通常不是纯 4bit,而是 4bit 加上若干层保精度。
9. 什么时候不该量化
# 量化决策清单:五项全过才值得动手
[ ] 显存确实不够,且已排除并发配置与长度设置的问题
[ ] 有可复跑的评测集,且存着高精度基线结果
[ ] 推理引擎支持目标量化格式(不支持的格式等于白量化)
[ ] 能取到贴近真实流量的校准样本
[ ] 有回滚路径:高精度权重还留着
| 情况 | 理由 |
|---|---|
| 显存本来就充裕 | 换来的收益是余量,代价是质量风险,不划算 |
| 任务精度敏感(法律、医疗、金融计算) | 退化难以用平均指标兜住 |
| 没有评估集 | 无法判断退化,等于盲改 |
| 延迟瓶颈在 prefill 或调度 | 量化帮不上,白付质量代价 |
| 硬件/引擎不支持该量化格式 | 会退回低效路径,甚至更慢 |
| 长上下文是核心场景,且未做 KV 侧验证 | KV 量化对长序列的影响需要单独测 |
10. 四个常见误解
| 误解 | 实际 |
|---|---|
| 量化一定掉点很多 | 仅权重量化在多数模型上退化有限,但必须自己测,不能引用别人的结论 |
| 量化能提速所有场景 | 主要帮带宽受限的 decode;prefill 与调度瓶颈不受益 |
| 只在训练集/通用集上验证就够 | 分布不匹配时线上会更差,必须用真实流量抽样 |
| 量化后不做端到端回归 | 单测通过不代表流式输出、结构化解析、多轮对话都正常 |
11. 排错:五种量化相关的故障
| 现象 | 原因 | 处理 |
|---|---|---|
| 加载报错,缺 scale 或格式不匹配 | 量化格式与引擎不兼容 | 按引擎支持的格式重新导出,不要手工改文件 |
| 能跑但输出乱码/重复 | 反量化路径错误或分组配置不符 | 用官方脚本重导,逐层比对 |
| 长 prompt 时明显变差 | KV 精度或长序列累积误差 | 只量化权重,KV 保持高精度 |
| 吞吐没提升甚至下降 | 瓶颈不在带宽,或走了低效 kernel | 先定位瓶颈在 prefill 还是 decode |
| 离线好、线上差 | 校准集与真实流量不匹配 | 用线上抽样重做校准集 |
12. 边界:量化解决不了的问题
| 问题 | 为什么量化无效 |
|---|---|
| 模型本身装不进最小的卡 | 位宽有下限,装不下就是装不下 |
| 并发能力不足 | KV Cache 是主因,需单独做 KV 量化或降并发 |
| 首 token 太慢 | prefill 算力问题,量化收益有限 |
| 调度与排队造成的长尾 | 与精度无关 |
| 需要更强能力 | 量化只会更弱,不会更强 |
13. 自测
- 量化省掉的是显存还是算力?为什么 decode 受益明显而 prefill 受益有限?
- "仅权重量化"和"权重+激活量化"各解决什么问题?为什么建议先动前者?
- 列出量化误差的三个来源,并各给一个入门级缓解手段。
- 校准集为什么不能用训练集?长度分布上要注意什么?
- 为什么困惑度只涨一点,任务表现可能掉很多?三层评估分别回答什么问题?
- 有哪几种情况本来就不该量化?各说出理由。