27届大模型面试准备(十九):模型量化全攻略------INT8/INT4、GPTQ、AWQ、SmoothQuant 与 KV Cache 量化
上一篇《LoRA 与 PEFT 全攻略》讲的是"怎么用最少的可训练参数把模型调好",属于训练侧省显存。这一篇转到部署侧的省显存主线------量化(Quantization)。它和第十五篇《高效推理全攻略》是一对:那篇讲的是"调度和算子怎么快",这篇讲的是"数字表示怎么小"。量化几乎是所有推理面试的必问题,因为它同时牵扯数值分析、硬件指令集和工程权衡三层。本文按"为什么要量化 → 量化的数学 → PTQ 三大流派(GPTQ / AWQ / SmoothQuant)→ QAT → KV Cache 量化 → 落地选型与踩坑"的顺序展开,每节都给原理、公式骨架、代码片段,结尾给面试速答和高频追问清单。
一、先算一笔账:为什么必须量化
面试官问"为什么要量化",只回答"为了省显存"会显得很浅。真正的答案有三层,而且要能当场算出来。
1.1 显存账
一个 70B 参数的模型,权重按不同精度存储所需显存:
| 精度 | 每参数字节 | 70B 权重显存 | 能否单卡 A100-80G |
|---|---|---|---|
| FP32 | 4 | 280 GB | 不能 |
| FP16 / BF16 | 2 | 140 GB | 不能 |
| INT8 | 1 | 70 GB | 勉强(无余量给 KV Cache) |
| INT4 | 0.5 | 35 GB | 可以,还剩 45G 给 KV Cache |
这张表要能脱口而出。结论:INT4 是"70B 单卡可跑"的分水岭,这就是 INT4 量化在社区如此流行的直接原因。
1.2 带宽账(更关键,但常被忽略)
LLM 的 decode 阶段是**访存密集(memory-bound)**而非计算密集。每生成一个 token,都要把全部权重从 HBM 读进片上,算力其实是闲着的。
Roofline 视角:decode 阶段单 token 的算术强度
计算量 ≈ 2 × N_params FLOPs (每个参数一次乘一次加)
访存量 ≈ N_params × bytes_per_param
算术强度 = 2 / bytes_per_param
FP16: 2/2 = 1 FLOP/Byte
INT8: 2/1 = 2 FLOP/Byte
INT4: 2/0.5 = 4 FLOP/Byte
A100 的 ridge point ≈ 312 TFLOPS / 2 TB/s ≈ 156 FLOP/Byte
→ 1、2、4 都远低于 156,说明 decode 彻底卡在带宽上
所以有个重要推论:decode 阶段的速度近似与"权重字节数"成反比。FP16 换 INT4,权重体积减 4 倍,理论 decode 吞吐能提升接近 4 倍------这个提升不是来自算得快,而是来自读得少。这是面试的高分回答点。
1.3 成本账
显存小了,同一张卡就能塞更大的 batch 和更长的 KV Cache,单位 token 成本随之下降。量化本质上是在"精度损失"和"服务成本"之间做交易。
量化带来的三重收益
┌─────────────┬─────────────┬──────────────┐
│ 显存下降 │ 带宽下降 │ batch 变大 │
│ 4x (INT4) │ 4x │ 吞吐再上升 │
└──────┬──────┴──────┬──────┴──────┬───────┘
└─────────────┴─────────────┘
单 token 成本 ↓↓
代价:精度损失(可控)
二、量化的数学骨架
2.1 均匀仿射量化
主流量化都是均匀量化(uniform quantization) ,核心两个参数:缩放因子 scale(s)和零点 zero-point(z)。
量化: q = clamp( round(x / s) + z, q_min, q_max )
反量化: x̂ = s × (q - z)
其中对 INT8 对称量化:z = 0,q ∈ [-128, 127];INT8 非对称量化:z ≠ 0,q ∈ [0, 255]。
scale 的取法(min-max 校准):
对称: s = max(|x|) / (2^(b-1) - 1) # b=8 → 127
非对称: s = (max(x) - min(x)) / (2^b - 1)
z = round(-min(x) / s)
一段可直接跑的实现:
python
import torch
def quantize_tensor(x: torch.Tensor, num_bits: int = 8, symmetric: bool = True):
"""返回 (q, scale, zero_point)。symmetric=True 时 zero_point 恒为 0。"""
qmax = 2 ** (num_bits - 1) - 1 if symmetric else 2 ** num_bits - 1
qmin = -(2 ** (num_bits - 1)) if symmetric else 0
if symmetric:
scale = x.abs().max() / qmax
zp = torch.tensor(0, dtype=torch.int32)
else:
scale = (x.max() - x.min()) / (qmax - qmin)
zp = torch.round(qmin - x.min() / scale).to(torch.int32)
scale = torch.clamp(scale, min=1e-8)
q = torch.clamp(torch.round(x / scale) + zp, qmin, qmax)
return q.to(torch.int8), scale, zp
def dequantize_tensor(q, scale, zp):
return (q.to(torch.float32) - zp) * scale
2.2 量化粒度:per-tensor / per-channel / per-group
这是面试高频追问点。粒度越细,误差越小,但元数据(scale)开销越大。
权重矩阵 W [out_features, in_features]
per-tensor : 整个 W 共用 1 个 scale ------ 最省,误差最大
per-channel : 每个 out_channel 一个 scale ------ 常用于 INT8 权重
per-group(g=128): 每 128 个输入维度一组 scale ------ INT4 标配(GPTQ/AWQ 默认)
in_features = 4096, group_size = 128
→ 每个输出通道有 4096/128 = 32 个 scale
→ 额外开销: 32 × 2 bytes(FP16) / (128 × 0.5 bytes) = 每权重多 ~1 bit
→ 所以说"INT4 group-128"的等效位宽约 4.25 bit
结论要背下来:INT8 用 per-channel 就够;INT4 必须用 per-group(通常 g=128),否则精度崩。
2.3 W8A8 / W4A16 命名法
W 指权重(weight),A 指激活(activation)。
| 方案 | 含义 | 典型代表 | 适用场景 |
|---|---|---|---|
| W8A8 | 权重和激活都 INT8 | SmoothQuant、LLM.int8() | 追求吞吐,prefill 计算受益大 |
| W4A16 | 权重 INT4,激活仍 FP16 | GPTQ、AWQ | 追求显存和 decode 速度 |
| W4A8 | 权重 INT4,激活 INT8 | QoQ、部分定制方案 | 极致压榨,工程复杂 |
| W8A16 | 权重 INT8,激活 FP16 | bitsandbytes LLM.int8() | 保精度,实现简单 |
| FP8 (E4M3/E5M2) | 浮点 8 位 | H100 原生支持 | 精度最好的 8 位方案 |
关键判断:W4A16 只解决带宽问题,不解决算力问题------因为算之前要把 INT4 反量化回 FP16 再做 GEMM。所以 W4A16 对 decode(带宽瓶颈)提升巨大,对 prefill(算力瓶颈)几乎没提升,甚至因为反量化开销略慢。这个反直觉的点是面试区分度极高的答案。
三、量化难在哪:激活值的离群点
如果量化只是简单地缩放取整,就不会有这么多论文。真正的难点是 LLM 激活值中存在系统性离群点(outlier)。
某层 hidden_states 的分布示意(4096 维)
绝大多数通道: |x| ∈ [0, 3]
少数几个通道: |x| ∈ [50, 100] ← outlier channel,固定出现在特定维度
若 per-tensor 量化:
scale = 100 / 127 ≈ 0.787
→ 值为 0.5 的正常激活量化后 = round(0.5/0.787) = 1
→ 反量化回来 = 0.787,误差 57%!大量小信号被压成 0 或 1
这就是 LLM.int8() 论文的核心发现:当模型参数超过约 6.7B 时,会稳定出现幅值极大的离群特征维度,且这些维度对模型效果至关重要,直接裁掉会导致性能崩溃。
三种应对思路,正好对应后面三个流派:
离群点问题
│
├── 思路A:把离群点单独拎出来算 → LLM.int8() 混合精度分解
├── 思路B:把难度从激活转移到权重 → SmoothQuant 数学等价缩放
└── 思路C:干脆不量化激活,只量化权重,
但按激活重要性保护关键权重 → AWQ / GPTQ
四、PTQ 三大流派详解
PTQ(Post-Training Quantization,训练后量化)不需要重训,是工业界绝对主流。
4.1 LLM.int8():混合精度分解
最朴素也最好懂的方案。做法:把激活中超过阈值(通常 6.0)的离群通道挑出来,用 FP16 算;其余通道走 INT8。
X [batch, 4096] × W [4096, 11008]
1) 找出 X 中 |x| > 6.0 的列索引集合 O(通常 |O| < 10)
2) 拆分:
X_out = X[:, O] (FP16) W_out = W[O, :] (FP16)
X_int = X[:, ~O] (INT8) W_int = W[~O, :] (INT8)
3) Y = X_out @ W_out + dequant(X_int @ W_int)
优点:几乎零精度损失,实现简单(bitsandbytes 一行 load_in_8bit=True)。
缺点:慢。因为要动态检测离群点、拆分张量、跑两次 GEMM 再相加,实际吞吐往往比 FP16 还低。所以它适合"显存不够但不追求速度"的场景(比如本地跑大模型做实验),不适合线上服务。这个"能省显存但更慢"的坑必须记住。
4.2 SmoothQuant:把量化难度从激活转移到权重
核心洞察非常优雅:激活难量化(有离群点),权重好量化(分布平坦)。那能不能在数学上等价地把一部分"难度"从激活挪给权重?
原式: Y = X · W
引入对角缩放矩阵 diag(s):
Y = (X · diag(s)^-1) · (diag(s) · W)
└──── X̂ ────┘ └──── Ŵ ────┘
X̂ 的离群点被 s 除小了 → 好量化了
Ŵ 被 s 乘大了一点 → 稍微难量化,但权重本来余量足
缩放因子的取法引入超参 α(migration strength,通常 0.5):
s_j = max(|X_j|)^α / max(|W_j|)^(1-α)
α = 0 → 全部难度留在激活(等于不做)
α = 1 → 全部难度推给权重(权重会崩)
α = 0.5 → 两边均分,实践最优
关键点:diag(s)^-1 可以被融合进上一层的 LayerNorm 或前一个线性层的权重里,推理时零额外开销。这是它比 LLM.int8() 快的根本原因。
python
@torch.no_grad()
def smooth_ln_linear(ln, linears, act_scales, alpha=0.5):
"""把 LayerNorm 后面的若干 Linear 一起做 SmoothQuant 平滑。
act_scales: 校准得到的每通道激活最大绝对值 [hidden]"""
device, dtype = linears[0].weight.device, linears[0].weight.dtype
act_scales = act_scales.to(device=device, dtype=dtype)
# 权重侧每通道最大值:对所有共享输入的 linear 取并集
w_scales = torch.cat(
[fc.weight.abs().max(dim=0, keepdim=True)[0] for fc in linears], dim=0
).max(dim=0)[0].clamp(min=1e-5)
s = (act_scales.pow(alpha) / w_scales.pow(1 - alpha)).clamp(min=1e-5)
# 难度迁移:LN 除以 s(等价于激活除以 s),Linear 权重乘以 s
ln.weight.div_(s)
if getattr(ln, "bias", None) is not None:
ln.bias.div_(s)
for fc in linears:
fc.weight.mul_(s.view(1, -1))
SmoothQuant 是 W8A8 方案,因此它对 prefill 阶段(计算密集)有真实的算力加速------INT8 Tensor Core 吞吐是 FP16 的 2 倍。这一点和 W4A16 形成鲜明对比。
4.3 GPTQ:基于二阶信息的逐层误差补偿
GPTQ 走的是另一条路:只量化权重(W4A16),但量化时不是简单取整,而是逐列量化并把误差补偿到还没量化的列上。
它的理论基础是 OBQ(Optimal Brain Quantization),目标是最小化每层输出的重建误差:
目标: argmin_Ŵ || X·W - X·Ŵ ||²_F
用二阶泰勒展开,海森矩阵 H = 2·X^T·X
量化第 q 列后,对剩余列的最优补偿量:
δ_rest = - ( w_q - quant(w_q) ) / [H^-1]_qq × [H^-1]_{q, rest}
直观理解:"这一列我取整取多了,那就让后面还没量化的列少一点,把整体输出拉回来。" 这是一种贪心的误差重分配。
算法骨架:
python
# GPTQ 单层量化的核心循环(简化版,省略 Cholesky 与分块细节)
def gptq_quantize_layer(W, H_inv, group_size=128, bits=4):
"""W: [out, in],H_inv: [in, in] 海森逆的 Cholesky 上三角"""
Q = torch.zeros_like(W)
for i in range(W.shape[1]): # 按输入维度逐列
w = W[:, i].clone()
d = H_inv[i, i]
if i % group_size == 0: # 每组重新算 scale
scale, zp = find_params(W[:, i:i + group_size], bits)
q = quantize_column(w, scale, zp, bits) # 取整
Q[:, i] = q
err = (w - dequant(q, scale, zp)) / d # 归一化误差
# 关键:把误差按海森逆的相关性补偿给后面所有未量化列
W[:, i + 1:] -= err.unsqueeze(1) @ H_inv[i, i + 1:].unsqueeze(0)
return Q
工程要点:
-
需要少量校准数据(通常 128 条、每条 2048 token)来估计
H = X^T X。 -
海森矩阵可能奇异,实践中要加阻尼
H += λ·mean(diag(H))·I,λ 取 0.01。 -
采用"分块 + 惰性更新"(block size 128)避免逐列更新的巨大访存开销,这是 GPTQ 相对 OBQ 能跑得动 175B 的工程关键。
-
单卡量化 70B 大约 1~4 小时。
4.4 AWQ:按激活幅值保护关键权重
AWQ(Activation-aware Weight Quantization)的核心观察比 GPTQ 更简洁:并非所有权重同等重要,只有约 1% 的"显著权重"决定了模型效果,而判断显著性的依据不是权重自身大小,而是对应的激活幅值大小。
论文里的消融实验很有说服力:
| 保护策略 | INT3 下的 PPL |
|---|---|
| 不保护(全量化) | 显著劣化 |
| 按权重幅值挑 1% 保 FP16 | 几乎无改善 |
| 按激活幅值挑 1% 保 FP16 | 大幅改善,接近 FP16 |
但"混合精度保留 1% FP16"对硬件不友好(不规则内存访问)。AWQ 的巧思是:用逐通道缩放来等价实现"保护",而不真的保留 FP16。
对显著通道 j 的权重先放大 s 倍,量化后再在激活侧除回来:
Ŵ_j = quant(W_j · s_j) Y = (Ŵ_j / s_j) · X_j
放大后,W_j 在量化格点上占的相对误差变小 → 等效精度更高
s 的搜索:s = (mean|X_j|)^α,网格搜 α ∈ [0,1] 取重建误差最小
GPTQ vs AWQ 对比(面试必问):
| 维度 | GPTQ | AWQ |
|---|---|---|
| 核心思想 | 二阶误差补偿 | 激活感知的通道缩放 |
| 是否改权重数值 | 是(补偿会改后续列) | 是(等价缩放) |
| 校准数据依赖 | 较强,可能过拟合校准集 | 较弱,泛化更好 |
| 量化耗时 | 慢(小时级) | 快(分钟级) |
| 推理速度 | 相当 | 略快(kernel 更规整) |
| 精度 | 相当,个别任务 AWQ 更稳 | 相当,长尾/多语言场景更稳 |
一句话总结选型:追求快速量化和跨域泛化选 AWQ;已有成熟 GPTQ 流水线且校准数据贴合业务分布,GPTQ 也够用。目前社区(vLLM、SGLang)对两者支持都很完善。
五、QAT:量化感知训练
PTQ 在 INT4 以下(INT3/INT2)会明显掉点,这时需要 QAT(Quantization-Aware Training)------训练时就模拟量化误差。
核心是 伪量化节点(fake quant)+ 直通估计器(STE):
前向: x̂ = dequant(quant(x)) # 引入真实的取整误差
反向: ∂L/∂x = ∂L/∂x̂ · 1{qmin ≤ x/s ≤ qmax}
└──── STE:round 的梯度直接当作 1 ────┘
python
class FakeQuant(torch.autograd.Function):
@staticmethod
def forward(ctx, x, scale, qmin, qmax):
ctx.save_for_backward(x, scale)
ctx.qmin, ctx.qmax = qmin, qmax
q = torch.clamp(torch.round(x / scale), qmin, qmax)
return q * scale
@staticmethod
def backward(ctx, g):
x, scale = ctx.saved_tensors
# STE + 截断区间外梯度置零
mask = ((x / scale) >= ctx.qmin) & ((x / scale) <= ctx.qmax)
return g * mask, None, None, None
现实中大模型很少做全量 QAT(太贵),主流是 QLoRA 式的"量化基座 + LoRA 微调"------基座冻结在 NF4,只训 LoRA。这在第十八篇已详细讲过,两篇可以串起来回答:"QLoRA 本质是 PTQ 之后用 PEFT 把损失补回来,比真 QAT 便宜两个数量级。"
六、KV Cache 量化:长上下文场景的胜负手
权重量化解决的是"模型多大",但长上下文场景下,KV Cache 才是显存大头。
KV Cache 显存 = 2 × L × H_kv × d_head × S × B × bytes
以 Llama-3-70B (L=80, H_kv=8 GQA, d_head=128) 为例
batch=32, seq=32768, FP16:
2 × 80 × 8 × 128 × 32768 × 32 × 2 B ≈ 343 GB ← 比 140G 权重还大!
改 INT8: 171 GB 改 INT4: 86 GB
所以在长上下文 + 大 batch 场景,KV Cache 量化的收益比权重量化更大。
实践要点:
-
K 和 V 要区别对待 。K 有明显的通道级离群点(因为要和 Q 做点积、且叠加了 RoPE),V 的分布更平坦。因此常见做法是 K 用 per-channel 量化,V 用 per-token 量化 。
-
保留最近若干 token 为 FP16 (residual window,如最近 128 个),因为新 token 对注意力贡献最敏感。
-
INT8 KV Cache 基本无损,可放心上;INT4 KV Cache 在长文本检索类任务(大海捞针)上会掉点,要实测。
-
vLLM 中开启方式:
--kv-cache-dtype fp8(H100 上 FP8 KV 比 INT8 精度更好且有原生支持)。
python
# per-token 量化 V(每个 token 一个 scale),比 per-tensor 稳很多
def quant_kv_per_token(v: torch.Tensor, bits=8):
"""v: [B, H, S, D] → 沿最后一维求 scale,每个 (b,h,s) 一个 scale"""
qmax = 2 ** (bits - 1) - 1
scale = v.abs().amax(dim=-1, keepdim=True) / qmax # [B,H,S,1]
scale = scale.clamp(min=1e-8)
q = torch.clamp(torch.round(v / scale), -qmax - 1, qmax).to(torch.int8)
return q, scale
七、落地选型与真实踩坑
7.1 决策树
要不要量化?
│
显存够 & 延迟达标 ──否──> 直接 FP16/BF16,别自找麻烦
│是(显存/成本有压力)
▼
主要瓶颈是 decode 还是 prefill?
│ │
decode(生成长) prefill(输入长/高并发)
│ │
W4A16 (AWQ/GPTQ) W8A8 (SmoothQuant) 或 FP8
│ │
└──── 长上下文?────────┘
│是
叠加 KV Cache 量化 (FP8/INT8)
│
精度不达标?
│是
放宽到 INT8 权重,或 QLoRA 补偿微调
7.2 五个真实踩坑
坑一:只看 PPL 不看下游任务。 PPL 掉 0.1 看起来无所谓,但代码生成、数学推理、工具调用这类任务对量化极度敏感,可能掉 5~10 个点。评测必须覆盖真实业务任务,尤其是 JSON 结构化输出的合法率------量化后模型漏引号、括号不配对的概率会上升,这在 Agent 场景是致命的。
坑二:校准集选错。 GPTQ 用 wikitext 校准、却上线跑中文客服,分布不匹配会明显掉点。校准集应该从线上真实流量采样 128~512 条,这是成本极低、收益极大的一步。
坑三:以为 INT4 一定比 INT8 快。 前面算过,W4A16 在 prefill 阶段因为要反量化,可能比 W8A8 更慢。高并发短输出场景,W8A8 往往才是对的。
坑四:忽略 lm_head 和 embedding。 词表 15 万、hidden 8192 的 lm_head 就有 12 亿参数,占比不小;但它对量化敏感(直接决定输出分布)。通用做法是 lm_head 保持 FP16 不量化,其余全量化。
坑五:group_size 和硬件对不齐。 group_size 必须能被 kernel 的 tile 尺寸整除,随手改成 64 或 100 可能导致 kernel fallback 到极慢的通用实现。没有充分理由就用默认的 128。
7.3 一段可直接用的 vLLM 部署示例
bash
# AWQ INT4 权重 + FP8 KV Cache,长上下文高吞吐配置
python -m vllm.entrypoints.openai.api_server \
--model /models/Llama-3-70B-Instruct-AWQ \
--quantization awq_marlin \
--kv-cache-dtype fp8 \
--max-model-len 32768 \
--gpu-memory-utilization 0.92 \
--tensor-parallel-size 2
注意 awq_marlin 而不是 awq:Marlin 是专门优化的 W4A16 kernel,在 Ampere/Hopper 上比原始 AWQ kernel 快约 1.5~2 倍,能用就一定要用。
八、面试速答
Q:量化为什么能加速推理?
A:LLM 的 decode 阶段是访存密集型,每生成一个 token 都要把全部权重从 HBM 读一遍,算力是闲置的。量化把权重字节数减少 N 倍,访存量就减少 N 倍,decode 吞吐近似提升 N 倍。所以加速主要来自"读得少"而非"算得快"。但 prefill 是计算密集型,W4A16 因为需要反量化反而可能变慢,只有 W8A8/FP8 这类激活也量化的方案才能吃到 INT8 Tensor Core 的算力红利。
Q:LLM 量化最大的技术难点是什么?
A:激活值的系统性离群点。模型超过约 6.7B 后会稳定出现少数幅值极大(50~100 倍于常规值)的特征维度,且这些维度对效果至关重要。用 per-tensor 量化时,这些离群点会把 scale 撑大,导致绝大多数正常激活被压缩到极少的量化格点上,误差急剧放大。三种解法分别是 LLM.int8() 的混合精度分解、SmoothQuant 的难度迁移、AWQ/GPTQ 的只量化权重加重要性保护。
Q:GPTQ 和 AWQ 的区别?
A:GPTQ 基于二阶海森信息做逐列量化和误差补偿,把当前列的取整误差按相关性分摊给尚未量化的列,理论性强但量化慢(小时级)且对校准集分布较敏感。AWQ 基于"激活幅值大的通道对应的权重更重要"这一观察,通过逐通道缩放等价地保护关键权重,量化快(分钟级)、跨域泛化更好、kernel 更规整。精度上二者接近,工程上我倾向优先试 AWQ。
Q:什么是 W4A16、W8A8?分别适合什么场景?
A:W 是权重位宽,A 是激活位宽。W4A16 只压权重,收益在显存和带宽,适合长文本生成、显存吃紧、追求 decode 速度的场景,代表是 GPTQ/AWQ。W8A8 权重激活都压到 INT8,能真正调用 INT8 Tensor Core,对 prefill 这种计算密集阶段有实际算力加速,适合高并发短输出场景,代表是 SmoothQuant。H100 上还可以用 FP8,动态范围比 INT8 大,精度更好且硬件原生支持。
Q:SmoothQuant 的原理?为什么它不增加推理开销?
A:它利用 Y = (X·diag(s)^-1)·(diag(s)·W) 的数学等价性,把激活的量化难度按 s_j = max|X_j|^α / max|W_j|^(1-α)(α 通常 0.5)迁移一部分到权重上,让两边都变得好量化。之所以零开销,是因为 diag(s)^-1 这个缩放可以在离线阶段直接融合进前一层的 LayerNorm 权重或线性层权重里,推理时没有任何额外算子。
Q:INT4 量化为什么必须用 group-wise?
A:INT4 只有 16 个量化格点,表达能力极其有限。如果整个张量或整个通道共用一个 scale,一旦范围内有较大值,小值就会被大量压成同一个格点,信息损失严重。按 128 个输入维度分组各自算 scale,能让每组的动态范围贴合局部分布。代价是每 128 个 4-bit 权重要多存一个 FP16 scale(和 zero point),等效位宽从 4 升到约 4.25,这个开销完全值得。
Q:KV Cache 要不要量化?
A:长上下文加大 batch 时必须量化。70B 模型在 batch=32、seq=32K 下 KV Cache 能到 343GB,比 140GB 的权重还大,这时 KV 量化的收益远超权重量化。实践上 INT8/FP8 基本无损可以放心上;INT4 在大海捞针类长程检索任务上会掉点需要实测。工程细节是 K 和 V 要分开处理------K 因为叠加 RoPE 且要做点积,存在通道级离群点,宜用 per-channel;V 分布平坦,per-token 即可;另外保留最近 128 个 token 为高精度会更稳。
Q:量化后如何验证效果?
A:三层验证。第一层是 PPL(wikitext/c4)做快速冒烟,只能判断"有没有崩"。第二层是标准 benchmark(MMLU、GSM8K、HumanEval),重点看推理和代码这类对量化敏感的任务。第三层也是最重要的一层,是业务真实数据的端到端评测,Agent 场景还要专门统计 JSON 结构化输出合法率和工具调用参数准确率------量化后这两个指标的劣化往往比 PPL 明显得多,而它们直接决定线上能不能用。
九、高频追问清单
- 为什么模型规模超过 6.7B 后离群点问题突然变严重?和涌现现象有关系吗?
- GPTQ 的海森矩阵为什么可以用
X^T X近似?阻尼系数的作用是什么? - AWQ 论文中"按权重幅值挑 1% 保 FP16 几乎无效,按激活幅值挑就有效",如何解释?
- NF4(NormalFloat4)和普通 INT4 的区别?它的信息论依据是什么?
- 为什么 lm_head 通常不量化?如果一定要量化会有什么现象?
- FP8 的 E4M3 和 E5M2 分别用在哪?为什么前向用 E4M3、反向梯度用 E5M2?
- Marlin kernel 为什么比朴素 W4A16 kernel 快这么多?它做了哪些优化?
- 量化和 MoE 一起用有什么特殊问题?专家权重量化的难点在哪?
- 如果线上发现量化模型在某类 badcase 上明显劣化,你的排查路径是什么?
- 量化会不会放大模型的安全风险(比如更容易被越狱)?为什么?
下一篇(A20)会讲大模型评测体系------量化后"到底掉了多少点"这个问题,最终要靠一套可信的评测体系来回答。从标准 benchmark 的局限、数据污染的检测,到 LLM-as-Judge 的偏差校正和 Arena 式对战评测,把"怎么科学地说模型好不好"讲清楚。