27届大模型岗面试准备(九):模型量化实战——INT8/INT4/GPTQ/AWQ 原理、误差账与手
27届大模型岗面试准备(九):模型量化实战——INT8/INT4/GPTQ/AWQ 原理、误差账与手写模拟量化
前八篇把训练侧(预训练、SFT、RLHF、PEFT)讲完了,从这篇开始进入推理部署侧。部署侧的第一道面试题几乎必然是量化:「7B 模型怎么塞进一张 12GB 的消费级显卡?」「GPTQ 和 AWQ 有什么区别?」「量化后精度掉了怎么排查?」这些问题看似工程向,实际上面试官想考的是你对数值表示、误差来源和校准逻辑的理解深度。这篇按「为什么要量化 → 数值格式 → 四种主流方案 → 手写模拟量化 → 面试追问」的顺序拆完。
一、先算一笔账:为什么量化是部署的第一杠杆
一个 7B 参数的模型,不同精度下的权重体积:
- FP32:7B × 4 Byte = 28 GB
- FP16/BF16:7B × 2 Byte = 14 GB
- INT8:7B × 1 Byte = 7 GB
- INT4:7B × 0.5 Byte = 3.5 GB
注意这只是权重。推理时还有 KV Cache(下一篇细讲)和激活值的开销,所以 FP16 的 7B 模型在 16GB 显卡上跑长上下文会直接 OOM。INT4 量化把权重压到 3.5GB,留给 KV Cache 的空间翻了三倍——这就是为什么 llama.cpp、Ollama 这些端侧方案默认都用 4bit。
除了显存,量化的第二个收益经常被忽略:大模型推理的 decode 阶段是访存瓶颈(memory-bound),不是算力瓶颈。每生成一个 token 都要把全部权重从显存搬到计算单元一次,权重体积减半,搬运时间近似减半,token 生成速度就上去了。面试时能主动说出「量化加速的本质是缓解访存带宽压力」,比只会背「省显存」高一个档次。
二、数值格式:从 FP16 到 INT4 到底丢了什么
量化的本质是把高精度浮点数映射到低比特整数网格上。最常用的是对称线性量化:
q = round(x / s), s = max(|x|) / (2^(b-1) - 1)
其中 s 是缩放因子(scale),b 是比特数。反量化时 x̂ = q × s,x - x̂ 就是量化误差。两个关键概念:
粒度(granularity):scale 按什么范围计算。整个张量共用一个 scale 叫 per-tensor,每一行(输出通道)一个叫 per-channel,每连续 128 个元素一组叫 per-group。粒度越细,对离群值越鲁棒,但需要存的 scale 越多。INT4 方案基本都用 group size 64/128。
离群值(outlier):LLM 的激活值中存在少量幅值极大的维度(可以比中位数大几十倍),这是 LLM 量化和 CNN 量化最大的区别。一个离群值会把 scale 撑得很大,导致其余正常值全部被挤在少数几个整数格子里,精度崩掉。几乎所有 LLM 量化方案的核心创新都是在处理离群值——记住这句话,四种方案的对比就有了主线。
三、四种主流方案对比
| 方案 | 比特 | 量化对象 | 需要校准数据 | 核心思想 | 精度保持 | 典型场景 |
|---|---|---|---|---|---|---|
| LLM.int8() | W8A8 | 权重+激活 | 否 | 离群维度拆出来走 FP16,其余走 INT8 矩阵乘 | 几乎无损 | bitsandbytes 一行加载 |
| SmoothQuant | W8A8 | 权重+激活 | 是(少量) | 把激活的离群幅值「平移」给权重,让两边都好量化 | 高 | 服务端 INT8 全链路加速 |
| GPTQ | W4A16 | 仅权重 | 是(128~1024 条) | 逐列量化,用 Hessian 信息把误差补偿到未量化列 | 高 | 显存受限的 GPU 推理 |
| AWQ | W4A16 | 仅权重 | 是(少量) | 按激活幅值找出 1% 显著权重通道,量化前放大保护 | 高,长尾任务更稳 | 端侧/边缘部署,vLLM 支持好 |
三条面试常考的横向结论:
- W8A8 和 W4A16 是两条路线:前者激活也量化,能用 INT8 TensorCore 拿到计算加速,适合大 batch 服务端;后者只压权重、计算仍在 FP16 做,赢在显存和访存,适合小 batch、decode 为主的场景。追问「为什么 W4A16 计算没变快还是更快」,答案就是第一节说的 memory-bound。
- GPTQ 是「误差补偿」流派:量化第 i 列产生的误差,按 Hessian 逆矩阵加权摊到还没量化的列上,让后面的列「替它还债」。数学上源自 OBS(Optimal Brain Surgeon)剪枝理论。
- AWQ 是「重要性保护」流派:观察到只有约 1% 的权重通道对输出影响巨大(对应激活幅值大的通道),量化前给这些通道乘一个放大系数 s,量化后在对应输入除回 s,数学等价但让重要权重占据更多整数格子。AWQ 不做梯度/Hessian 计算,量化速度快,且不容易过拟合校准集。
四、手写模拟量化:把 GPTQ 的核心逻辑跑出来
面试手撕环节不会让你写 CUDA kernel,但可能让你写「模拟量化」(fake quantization:量化再反量化,观察误差)。下面这段纯 NumPy 代码实现了 per-group 对称量化,并演示 GPTQ 风格的逐列误差补偿与朴素 RTN(round-to-nearest)的对比:
import numpy as np
def quantize_rtn(W, bits=4, group_size=64):
"""朴素 RTN:逐组独立量化,不做任何补偿"""
Wq = np.zeros_like(W)
qmax = 2 ** (bits - 1) - 1 # int4 对称: [-7, 7]
for g in range(0, W.shape[1], group_size):
blk = W[:, g:g + group_size]
s = np.abs(blk).max(axis=1, keepdims=True) / qmax + 1e-8
Wq[:, g:g + group_size] = np.round(blk / s) * s # 量化+反量化
return Wq
def quantize_gptq_style(W, X, bits=4, group_size=64):
"""GPTQ 风格:逐列量化,把误差按相关性补偿给未量化列
W: [out, in] 权重矩阵 X: [n, in] 校准激活"""
W = W.copy().astype(np.float64)
qmax = 2 ** (bits - 1) - 1
H = X.T @ X + 1e-2 * np.eye(W.shape[1]) # Hessian ≈ 2X^TX(阻尼防奇异)
Hinv = np.linalg.inv(H)
for g in range(0, W.shape[1], group_size):
blk_end = min(g + group_size, W.shape[1])
s = np.abs(W[:, g:blk_end]).max(axis=1, keepdims=True) / qmax + 1e-8
for j in range(g, blk_end):
w = W[:, j]
q = np.clip(np.round(w / s[:, 0]), -qmax, qmax) * s[:, 0]
err = (w - q) / Hinv[j, j] # 本列量化误差
W[:, j] = q
if j + 1 < W.shape[1]: # 摊给后续所有未量化列
W[:, j + 1:] -= np.outer(err, Hinv[j, j + 1:])
return W.astype(np.float32)
if __name__ == "__main__":
rng = np.random.default_rng(0)
W = rng.normal(0, 0.02, (256, 512)).astype(np.float32)
W[:, rng.choice(512, 5, replace=False)] *= 8 # 人为注入离群通道
X = rng.normal(0, 1.0, (128, 512)).astype(np.float32)
Y = X @ W.T # 原始输出作为基准
for name, Wq in [("RTN", quantize_rtn(W)),
("GPTQ-style", quantize_gptq_style(W, X))]:
rel = np.linalg.norm(X @ Wq.T - Y) / np.linalg.norm(Y)
print(f"{name:12s} 输出相对误差: {rel:.4%}")
在我机器上跑出来 RTN 的输出相对误差约是 GPTQ 风格的 1.5~2 倍。两个值得在面试里点出的细节:一是 GPTQ 优化的目标不是「权重误差最小」而是「层输出误差最小」,所以必须要校准数据来估计 Hessian;二是代码里的阻尼项(1e-2 * I)对应 GPTQ 论文里的 dampening,没有它 Hessian 求逆经常数值爆炸——这也是实际量化工具里 damp_percent 参数的来源。
五、精度掉了怎么排查:一套可复述的方法论
「量化后 benchmark 掉了 3 个点怎么办」是高频追问,给一套顺序化的排查框架:
- 先定位层:逐层替换(一层量化其余保持 FP16),用少量样本测 PPL,找出误差贡献最大的层。经验上首尾几层和 down_proj 最敏感,很多方案默认跳过 lm_head 不量化。
- 调粒度:group size 从 128 降到 64/32,scale 存储开销略增,离群鲁棒性明显提升。
- 换校准集:校准数据要贴近推理分布。用 C4 校准却拿去跑中文客服,掉点大概率是分布不匹配,换成业务语料重新量化。
- 混合精度兜底:把最敏感的 1~2 层保持 FP16 或 INT8,其余 INT4,显存多花几百 MB 换回精度。
- 升级方案:RTN → GPTQ/AWQ → 带微调的量化(QAT 或 QLoRA 式「量化+适配器补偿」)。
最后一个常见追问:「量化和上一篇讲的 QLoRA 什么关系?」答:QLoRA 的 NF4 是训练时把冻结基座压缩以省显存,反向传播时反量化回 BF16 计算,LoRA 分支始终高精度;GPTQ/AWQ 是推理时量化,目标是部署。一个管训练成本,一个管推理成本,两者可以串联——QLoRA 训完合并权重,再做 GPTQ 量化上线。
下一篇(A10)讲推理加速的另一半:KV Cache、PagedAttention 与 vLLM,和本篇的量化拼在一起,才是完整的推理优化答卷。
更多推荐


所有评论(0)