Transformer 解剖:从 Attention 到推理系统

第 16 章 量化实战:INT8 / INT4 / GPTQ / AWQ / FP8

作者 杨艺韬 · 6,024 字 · 发布于 · 更新于

第 14 章我们看到 Decode 阶段的瓶颈是 HBM 带宽——每生成一个 token 都要把整个模型权重从 HBM 拉一遍。Llama-3 70B 在 FP16 下是 140 GB,H100 的 HBM 带宽 3.35 TB/s——把权重扫一遍至少 42 ms(140 GB 装不进单卡 80 GB,实际要多卡 TP 分摊,但「读完全部权重才能出一个 token」这条约束不变)。这是「物理学的下限」。

但等一下——如果我们能把模型权重压缩成 35 GB(INT4)呢?读取时间立刻降到 10 ms——同样硬件下 token 速度的理论上限翻 4 倍。

这就是 量化(Quantization) 的核心价值:不改变模型结构,只是把每个参数用更少的位表示。FP16 → INT8 显存减半、INT4 减 4 倍、INT2 减 8 倍——直接对应 HBM 带宽节省,对应 Decode 加速。

但量化不是免费的。位数越少,每个参数的精度越低,模型质量必然损失一些。这一章我们要讲清楚:用多少位最划算?怎么把质量损失压到最小?哪些方案在哪些硬件上能跑?

读完这章你能:

  • 推导 FP16 → INT8 / INT4 的量化数学(缩放因子、零点、对称 vs 非对称);
  • 解释 GPTQ 和 AWQ 两大主流后训练量化(PTQ)方法的差异;
  • 理解 FP8(E4M3 / E5M2)和 INT8 的工程取舍;
  • 看懂 outlier 问题——为什么 SmoothQuant 必须存在;
  • 在面对一个新模型时知道选哪种量化(INT8 / INT4 / FP8)。

16.1 一个最朴素的问题:FP16 太精确了?

LLM 训练时用的标准格式是 FP16 或 BF16——每参数 16 位 = 2 字节。这是个相对精确的浮点表示——FP16 有 10 位尾数,在每个 [2k,2k+1)[2^k, 2^{k+1}) 区间内能精确表示 1024 个不同的值。

问题:神经网络真的需要这么高精度吗?

INT8 / INT4 量化的大量实践给出的答案是:不需要。一个训练好的 LLM 的参数分布大致是高斯型——绝大多数参数集中在 0 附近的窄区间里,少数极端值散落在外面。配上合适的缩放因子,分辨 16(INT4)到 256(INT8)个等级通常就足以保留模型的大部分能力。FP16 的 1024 级精度在大多数地方是「浪费」。

flowchart TB
  subgraph "FP16 (16 bit)"
    F1[每参数 2 byte<br/>~65536 个等级<br/>但大多数参数<br/>用不了这么多]
  end
  subgraph "INT8 (8 bit)"
    I1[每参数 1 byte<br/>256 个等级<br/>对大多数权重已足够]
  end
  subgraph "INT4 (4 bit)"
    I2[每参数 0.5 byte<br/>16 个等级<br/>边缘但仍可用]
  end
  subgraph "INT2 (2 bit)"
    I3[每参数 0.25 byte<br/>4 个等级<br/>质量明显损失]
  end

量化的核心数学:把一个 FP16 浮点数映射到一个低位整数,加上一个缩放因子(scale),让两者大致等价:

xFP16≈s⋅xintx_{\text{FP16}} \approx s \cdot x_{\text{int}}

其中 ss 是 FP16 的标量。xintx_{\text{int}} 占的位数(4、8、16 位)就是「量化精度」。

举个例子:一组权重值 [0.5, -0.8, 0.3, -1.2, 0.9]:

  • 找最大绝对值:1.2
  • 设 INT8 范围 -127 到 127(对称量化),缩放因子 s=1.2/127=0.00945s = 1.2 / 127 = 0.00945
  • 量化:每个值除以 s 取整 → [53, -85, 32, -127, 95]
  • 反量化:每个值乘以 s → [0.501, -0.803, 0.302, -1.200, 0.898]

可以看到反量化后的值和原值非常接近,但每个权重只用了 1 byte(INT8)而不是 2 byte(FP16)。

16.2 量化的几个维度

「量化」这个词在不同上下文下指的是不同事。要分清下面几个维度:

维度 1:量化什么?

  • 权重量化(Weight Quantization):模型参数(W)量化
  • 激活量化(Activation Quantization):每层输入 / 输出(A)量化
  • KV Cache 量化(第 15 章已讲)

通常用 WnAm 表示「权重 n 位 + 激活 m 位」:

  • W16A16:FP16 训练 / 推理基线
  • W8A8:权重和激活都 INT8
  • W4A16:权重 INT4,激活保持 FP16
  • W4A4:权重和激活都 INT4

维度 2:什么时候量化?

  • PTQ(Post-Training Quantization):训练完之后再量化。优点是简单(不重训);缺点是质量损失稍大。
  • QAT(Quantization-Aware Training):训练时就模拟量化,模型适应量化。优点是质量好;缺点是要重训,成本高。

大模型部署以 PTQ 为主——QAT 要把量化放进训练或大规模微调里重跑,成本远高于 PTQ;而 PTQ 在单卡上几小时内就能量化完一个百亿级模型(GPTQ 论文报告 175B 模型约 4 个 GPU 小时)。

维度 3:粒度多细?

  • per-tensor:整个张量用一个缩放因子。最粗,质量最差。
  • per-channel(权重每个输出通道一行一个)/ per-token(激活每个 token 一行一个):每行一个独立的缩放因子。中等。
  • per-group(group-wise):把一行再切成每 N 个相邻元素一组,每组一个缩放因子。最细,质量最好。

更细的粒度让缩放因子更贴合数据分布,但额外存储缩放因子也是开销。主流方案:W4 用 group_size=128 的 group-wise 量化,A8 用 per-token 量化。

维度 4:对称还是非对称?

  • 对称量化(symmetric):缩放范围对称(-127 到 127)
  • 非对称量化(asymmetric):加一个零点(zero point)偏移,能更精确表示有偏分布

非对称对激活更友好(激活分布常常偏正),对称对权重够用。

把这几个维度组合起来,就有几十种「量化方案」——但工程上真正主流的就 4-5 种,下面分别讲。

16.3 INT8 量化:W8A8 与 SmoothQuant

最简单的量化是 W8A8——权重和激活都 INT8。每参数从 2 byte 降到 1 byte,显存和 HBM 读取直接减半。

朴素 W8A8 的算法:

# w: (out, in)   x: (tokens, in)
# 权重量化(per-output-channel):注意用 amax,Tensor.max(dim=...) 返回的是
# (values, indices) 具名元组,直接拿去除会报错
scale_w = w.abs().amax(dim=-1, keepdim=True) / 127        # (out, 1)
w_int8 = (w / scale_w).round().clamp(-127, 127).to(torch.int8)

# 激活量化(per-token)
scale_a = x.abs().amax(dim=-1, keepdim=True) / 127        # (tokens, 1)
x_int8 = (x / scale_a).round().clamp(-127, 127).to(torch.int8)

# INT8 矩阵乘法(概念示意)。注意 torch.matmul 本身不接受 int8 输入,
# 真跑要用 torch._int_mm 或推理框架自带的 INT8 GEMM kernel
y_int32 = torch._int_mm(x_int8, w_int8.T)                 # (tokens, out)

# 反量化:scale_w 是 (out, 1),要转成 (1, out) 才能和 (tokens, out) 广播
y_fp16 = y_int32.to(torch.float16) * scale_a * scale_w.reshape(1, -1)

但朴素 W8A8 在 LLM 上有个致命问题:激活 outlier。

激活 outlier 现象

研究发现(Dettmers et al., LLM.int8(),NeurIPS 2022),模型规模到 6.7B 左右起,LLM 的激活分布不再是正常的钟形分布——少数(约 0.1%)特征维度上会系统性地出现「outlier」,论文称其幅值可达其他维度的 20 倍。

flowchart LR
  subgraph "激活 outlier"
    NORMAL[约 99.9% 的特征维度<br/>幅值较小]
    OUT[约 0.1% 的特征维度<br/>幅值 ≥ 6(论文阈值)<br/>可达其他维度 20 倍]
  end
  NORMAL --> Q[量化时缩放因子<br/>被 outlier 撑大]
  OUT --> Q
  Q --> BAD[绝大多数值精度被牺牲<br/>质量大幅下降]

朴素量化的逻辑「按最大值算缩放」会让缩放因子被 outlier 撑大——绝大多数小值被量化到几乎全是 0,精度全部损失。

LLM.int8() 的解决方案

Dettmers 等人提出 混合精度:把 outlier 通道单独保留 FP16,其他通道用 INT8。具体地:

  1. 检测每层激活中的 outlier 列(论文取幅值阈值 6)
  2. outlier 列:FP16 计算
  3. 非 outlier 列:INT8 计算
  4. 最后合并

论文在 OPT 系列(最大 175B)上验证了 INT8 推理几乎不掉点,并用它跑 BLOOM-176B 推理。但代价是计算路径分裂、kernel 实现复杂。

SmoothQuant 的精妙

更聪明的方案是 SmoothQuant(Xiao et al., 2023):通过数学等价变换,把激活的 outlier「转移」到权重上。

直觉:在 Y=X⋅WY = X \cdot W 这个矩阵乘里,如果激活 XX 有大 outlier、权重 WW 比较平整,那么对每个通道引入一个缩放 ss:

Y=X⋅W=(X⋅diag(s)−1)⋅(diag(s)⋅W)=X′⋅W′Y = X \cdot W = (X \cdot \text{diag}(s)^{-1}) \cdot (\text{diag}(s) \cdot W) = X' \cdot W'

选合适的 ss,让 X′X'(缩放后的激活)outlier 减小,W′W'(缩放后的权重)outlier 略增——但权重的分布原本就比较平整,能容忍一些放大;激活的 outlier 反而被压平。

flowchart LR
  subgraph "原始"
    X1["X<br/>有大 outlier"]
    W1["W<br/>分布平整"]
  end
  subgraph "SmoothQuant 之后"
    X2["X'<br/>outlier 被分摊给 W"]
    W2["W'<br/>略有 outlier 但仍可量化"]
  end
  X1 -.等价变换.-> X2
  W1 -.等价变换.-> W2
  X2 --> QQ["X' 和 W' 都能 INT8 量化<br/>质量几乎无损"]
  W2 --> QQ

SmoothQuant 论文在 OPT-175B、BLOOM-176B、GLM-130B 等模型上报告 W8A8 的精度「与 FP16 几乎无差」,同时最高 1.56× 加速、显存减半。它是 W8A8 部署最常用的预处理之一。

16.4 INT4 量化:GPTQ 与 AWQ

INT4 把每参数压到 0.5 byte——比 FP16 减 4 倍。但 INT4 只有 16 个等级,朴素量化质量损失严重。要让 INT4 可用,需要更聪明的算法。

GPTQ:基于 Hessian 的逐列量化

GPTQ(Frantar et al., ICLR 2023)的核心想法:量化是有顺序的,每量化一列,根据它的误差去补偿后面未量化的列。

具体地,对一个权重矩阵 W∈Rm×nW \in \mathbb{R}^{m \times n},逐列量化 W:,1,W:,2,…,W:,nW_{:,1}, W_{:,2}, \dots, W_{:,n}。每量化一列 W:,iW_{:,i},会引入一个误差 δi=W:,i−W^:,i\delta_i = W_{:,i} - \hat{W}_{:,i}(量化前减量化后)。GPTQ 用这个误差去修正后面未量化的列:

W:,j′←W:,j+αij⋅δi,j>iW_{:,j}' \leftarrow W_{:,j} + \alpha_{ij} \cdot \delta_i, \quad j > i

其中 αij\alpha_{ij} 是从 Hessian 矩阵推出来的修正系数——它告诉我们「未量化的列 jj 应该如何调整,以最小化整体重建误差」。

直观感受:每量化一列就「调整后面所有未量化的列以弥补这一列的损失」——把误差像击鼓传花一样传下去,尽量压低整层输出的重建误差(这是贪心近似,不保证全局最优)。

GPTQ 的代价:

  • 需要一份校准数据(calibration data)——一小批文本(通常几十到几百条),用来估算 Hessian
  • 量化耗时:论文报告 OPT-175B / BLOOM-176B 在单张 A100 上约 4 个 GPU 小时,几十亿参数的模型只要几分钟到几十分钟

GPTQ 是早期 INT4 量化的代表,至今仍是最常用的方案之一。auto-gptq 等库实现了它,vLLM 等推理框架能直接加载 GPTQ checkpoint(bitsandbytes 走的是另一路:LLM.int8() 与 NF4,不做 GPTQ)。

AWQ:基于权重显著性

AWQ(Lin et al., MLSys 2024)走了一条不同的路:不是所有权重一样重要,量化时应该保护那 1% 最重要的权重。

AWQ 的关键 insight:

  1. 少数显著(salient)通道对模型质量贡献最大——这些通道对应的激活值大、影响输出多
  2. 量化前把显著通道的权重按比例放大、对应激活同比例缩小(数学上等价),再让所有通道统一做 INT4 量化,显著通道的相对量化误差就会变小,整体质量损失可以非常小

具体算法:

  1. 用校准数据测量每个通道的「激活显著性」(activation magnitude)
  2. 给每个通道选一个缩放因子:s=sXαs = s_X^{\alpha},sXs_X 是该通道的平均激活幅值,α∈[0,1]\alpha \in [0, 1] 用网格搜索选——于是显著通道 ss 大(权重被放大、量化得更精细),不显著通道 ss 小
  3. 然后做标准的分组 INT4 量化:y≈(x⋅diag(s)−1)⋅Q(diag(s)⋅W)y \approx (x \cdot \text{diag}(s)^{-1}) \cdot Q(\text{diag}(s) \cdot W),其中 Q(⋅)Q(\cdot) 是分组量化

AWQ 的优势:

  • 不需要估 Hessian、不需要逐列误差补偿,量化流程简单得多、跑得也快得多
  • 推理时基本不需要额外计算(激活侧的 diag(s)−1\text{diag}(s)^{-1} 可融进前一个算子,如 LayerNorm 或前一层线性层)
  • AWQ 论文报告,在 Llama / Llama-2 上质量与 GPTQ 相当或略优,且所需校准集更小、对校准集分布更不敏感

AWQ 与 GPTQ 是今天最常见的两种 INT4 预量化 checkpoint 格式,Hugging Face 上 Llama / Mistral / Qwen 的 INT4 版本多以这两种格式发布。

GPTQ vs AWQ 实测对比

以 Llama-2 7B / WikiText-2 为例,下表取自 AWQ 论文 Table 4(group_size=128,困惑度越低越好;GPTQ-R 是带列重排的 GPTQ):

方案 INT4 g128 PPL INT3 g128 PPL 校准方式
FP16(基线) 5.47 5.47 —
朴素 RTN(round-to-nearest) 5.73 6.66 不需要校准
GPTQ 5.69 6.43 逐层估 Hessian、逐列误差补偿
GPTQ-R 5.63 6.42 同上,加列重排
AWQ 5.60 6.24 只统计每通道平均激活幅值 + 网格搜索 α\alpha

可以看到:

  • 在 INT4 g128 这一档,连朴素 RTN 也只比基线高 0.26——分组(group_size)本身就贡献了很大一部分质量;GPTQ 与 AWQ 把增量再压到 0.13-0.22
  • 降到 INT3,RTN 与 AWQ 的差距才明显拉开,算法的价值在更低位宽上更突出
  • 具体数字随模型、校准集、group_size 而变,选型时以自己模型的实测为准

16.5 FP8:Hopper 时代的新格式

NVIDIA 从 Ada(sm_89,如 L40S、RTX 4090)与 Hopper(sm_90,H100)起引入了新的硬件支持:FP8 浮点——8 位浮点格式,动态范围比 INT8 大得多。

FP8 有两种格式:

  • E4M3(4 位 exponent + 3 位 mantissa + 1 位 sign):精度高但范围小,适合权重和激活
  • E5M2(5 位 exponent + 2 位 mantissa + 1 位 sign):范围大但精度低,适合梯度
flowchart LR
  FP16["FP16: 1+5+10<br/>范围 ±65k<br/>精度 1024 等级"]
  E4M3["FP8 E4M3: 1+4+3<br/>范围 ±448<br/>精度 8 等级 / 区间"]
  E5M2["FP8 E5M2: 1+5+2<br/>范围 ±57k<br/>精度 4 等级 / 区间"]
  INT8["INT8: 1+7<br/>范围 ±127<br/>线性 256 等级"]

FP8 vs INT8:

  • FP8 是浮点——动态范围大,对 outlier 更宽容(通常不需要 SmoothQuant 之类的预处理,但仍要配缩放因子)
  • INT8 是定点——精度均匀,需要 outlier 处理才能用好

实际上 FP8 用起来比 INT8 简单——算一个缩放因子把数值挪进 E4M3 的 ±448 范围再 cast,质量损失很小(不能不带 scale 直接 cast:PyTorch 里超出范围的值会变成 NaN,见《PyTorch》专栏第 20 章)。但 FP8 需要硬件支持:Ada(L40S / RTX 4090)与 H100 / H200 / B200 有原生 FP8 Tensor Core,A100 没有。

DeepSeek-V3 技术报告称,它首次在超大规模模型上验证了 FP8 混合精度训练的可行性——大部分 GEMM 走 FP8,但 embedding、输出头、MoE gating、归一化、attention 算子以及主权重、权重梯度与优化器状态仍保留在更高精度上。这是「混合精度」,不是「全 FP8」。

未来趋势(作者判断):FP8 取代 INT8 + FP4 取代 INT4。NVIDIA Blackwell(B200)已经原生支持 FP4 Tensor Core;更早的 GPU 没有 FP4 算力通路,FP4 权重只能先反量化再算,拿到的只是省显存、省带宽那一半收益。

16.6 不同方案的对照表

把所有量化方案放一张表里对比:

方案 权重 激活 KV 显存压缩 硬件支持 质量损失 适用
FP16(基线) FP16 FP16 FP16 1× 通用 0 训练 / 推理基线
BF16 BF16 BF16 BF16 1× A100+ 几乎无 取代 FP16 训练
W8A8 (SmoothQuant) INT8 INT8 FP16/INT8 2× T4+ 几乎无 中等并发推理
W8A16 INT8 FP16 FP16 2× 通用 几乎无 简单部署
W4A16 (GPTQ) INT4 FP16 FP16 约 4× T4+ 小(g128) 高密度部署
W4A16 (AWQ) INT4 FP16 FP16 约 4× T4+ 小(g128),与 GPTQ 相当 替代 GPTQ
W4A8 INT4 INT8 INT8 约 4× A100+ 比 W4A16 大 极致部署
FP8 (E4M3) FP8 FP8 FP8 2× Ada / H100+ 很小 新硬件最优
FP4 FP4 FP4 FP4 约 4× B200 需逐模型验证 下一代主流(预测)

表中「质量损失」只是量级上的定性排序,INT4 的具体数字见 16.4 节的论文表格;权重位宽之外,group-wise 的 scale / zero 还要额外占一点显存(g128 的 FP16 scale 约多 3%,见《PyTorch》专栏第 20 章),所以 INT4 实际略低于 4×。

实际工业部署的常见组合:

  • 训练:BF16(标准)或 FP8 混合精度(前沿,如 DeepSeek-V3)
  • 推理(性能优先):W4A16 + AWQ + INT8 KV
  • 推理(精度优先):W8A8 + SmoothQuant
  • 推理(H100+):FP8 全栈

16.7 量化的硬件依赖

不是所有 GPU 都支持所有量化:

Tensor Core 算力对照(H100 SXM 单卡,稠密吞吐,TFLOPS/TOPS;NVIDIA 数据表上带星号的数字是开稀疏后的两倍值):

精度 稠密吞吐 相对 BF16
FP32(非 Tensor Core) 67 0.07×
TF32 Tensor Core 495 0.5×
BF16 / FP16 Tensor Core 989 1×
FP8 (E4M3/E5M2) 1979 2×
INT8 1979 2×

注意两点:

  1. W4A16 的激活是 16 位,不论哪一代 GPU,矩阵乘都在 FP16 / BF16 Tensor Core 上算——INT4 权重在 kernel 里先反量化再乘,GEMM 本身不会变快。INT4 Tensor Core 只对 W4A4 这类「两边都是 4 位」的方案有意义,而 Hopper 的 Tensor Core 连这条 INT4 通路都没有(Turing、Ampere、Ada 有)。
  2. FP4 要到 Blackwell(B200)才是原生的,峰值吞吐约为 FP8 的两倍。

所以 INT4 在 H100 上的收益不来自算力,而来自 Decode 阶段是 memory-bound:权重体积压 4 倍,HBM 读取时间就压 4 倍。反过来,compute-bound 的 Prefill 阶段用 W4A16 是不会变快的。

这一点在 vLLM 的 AWQ 实现里写得非常直白:vllm-0.8.5/vllm/model_executor/layers/quantization/awq.py:174 有一条 FP16_MATMUL_HEURISTIC_CONDITION = x.shape[:-1].numel() >= 256——token 数一旦超过 256(也就是 prefill 或大 batch),它干脆先 awq_dequantize 把 INT4 权重解回 FP16、再走普通 torch.matmul;只有 token 数很少(decode)时才走融合的 awq_gemm。反量化再算比融合 kernel 更快,正说明这一档的瓶颈根本不在权重读取上。

消费卡支持:

  • RTX 4090 / 4070 等 Ada Lovelace 架构:支持 FP8、INT8、INT4
  • RTX 3090 / 3060 等 Ampere:支持 INT8、INT4,不支持 FP8
  • Turing(T4 / RTX 20 系):支持 INT8、INT4
  • Volta(V100):Tensor Core 只有 FP16 一档,INT8 只能走 CUDA Core 的 DP4A,收益有限

结论:选量化方案要考虑目标硬件。给 4090 用户部署:模型放得下时 FP8 是首选;给 3090 用户:INT4 + AWQ;给 V100:INT8 没有 Tensor Core 可用,W8A8 的算力收益有限,更适合只量化权重来省显存和带宽(注意 vLLM v0.8.5 的 AWQ 要求算力 ≥ 7.5,V100 是 7.0,只能走 GPTQ 这类 weight-only 路径,见 vllm-0.8.5/vllm/model_executor/layers/quantization/awq.py:54-56 与 vllm-0.8.5/vllm/model_executor/layers/quantization/gptq.py:91-92)。

16.8 一个端到端例子

最后给一个完整流程:用 vLLM 部署一个 INT4 量化的 Llama-3-70B。

Step 1:选量化方案

70B 模型 FP16 是 140 GB——单卡 H100 80GB 装不下。要么 4 卡 TP,要么量化到单卡。

INT4 后线性层权重只有约 34 GB,再加上 g128 的 scale / zero(约 1.3 GB)和仍保留 FP16 的 embedding 与输出头(约 4.2 GB),总共约 40 GB——单卡 H100 80GB 能装!按 --gpu-memory-utilization 0.9 扣掉权重和激活,还剩约 30 GB 量级给 KV Cache;Llama-3 70B 每 token 的 KV 在 FP16 下是 320 KiB(第 15 章),FP8 下 160 KiB,约能装十几二十万个 token。

Step 2:用 AWQ 做量化

pip install autoawq

# 用 autoawq 库量化
python -c "
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = 'meta-llama/Meta-Llama-3-70B-Instruct'
quant_path = 'llama-3-70b-awq'
quant_config = {'zero_point': True, 'q_group_size': 128, 'w_bit': 4, 'version': 'GEMM'}

tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoAWQForCausalLM.from_pretrained(model_path)
model.quantize(tokenizer, quant_config=quant_config)
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)   # vLLM 加载时要从同一目录读 tokenizer
"

70B 模型的 AWQ 量化是小时级的活(视 GPU 与校准集而定)。注意 AutoAWQ 已于 2025 年停止维护并归档,官方建议迁移到 vLLM 项目的 llm-compressor(https://github.com/vllm-project/llm-compressor);上面的脚本只在它最后测试过的环境(Torch 2.6.0、Transformers 4.51.3)下有保证。

Step 3:用 vLLM 部署

pip install vllm

vllm serve llama-3-70b-awq \
    --kv-cache-dtype fp8 \
    --max-model-len 8192 \
    --gpu-memory-utilization 0.9

这里故意不写 --quantization awq:vLLM 会从 checkpoint 的量化配置自动识别 AWQ,满足条件时自动升级到更快的 awq_marlin kernel;显式写 --quantization awq 反而会强制走较慢的原始 AWQ kernel(vllm-0.8.5/vllm/model_executor/layers/quantization/awq_marlin.py:104-121,详见《vLLM》专栏第 13 章)。--max-model-len 不能超过模型自身的上下文长度,Llama-3 70B 是 8192,写 32768 会在启动时报错。--kv-cache-dtype fp8 开启 FP8 KV Cache,进一步省 KV 显存——但在 v0.8.5 的 V1 引擎里它只在 Hopper + FlashAttention 3 上可用(第 15 章)。

Step 4:质量验证

跑一组评测(如 MMLU、HellaSwag)对比原始 FP16 vs INT4-AWQ 的分数差距。对 70B 这个量级,g128 的 INT4 通常只掉一点点,但要以自己任务上的实测为准。

Step 5:性能 benchmark

测试 throughput(tokens/s)和 latency(ms/token)。INT4 相比 FP16 通常:

  • Decode 速度:上限是权重压缩比(约 4×),实测会被反量化开销、KV 读取和 kernel 效率打折扣
  • 单卡并发能力:权重腾出来的显存全部转给 KV Cache 池,能装的并发按腾出的显存等比例上升

16.9 量化的工程经验

经验 1:不是所有模型都量化友好

  • 训练充分(数据 / 步数足)的模型量化损失更小
  • 小模型(< 7B)量化损失大于大模型——参数少,每个都很关键
  • MoE 模型量化要格外留心——每个专家只被一小部分 token 激活,校准数据落到单个专家上的样本很少,逐专家的量化误差更难估准

经验 2:长上下文 + 量化要小心

INT4 量化在 128K 这类长上下文上可能暴露出短上下文评测看不到的累积误差。建议:

  • KV Cache 不量化或只 INT8(不 INT4)
  • 重要应用做完 NIAH 评估再上线

经验 3:W8A8 和 W4A16 的比较

  • W8A8:质量更好,但激活量化复杂(per-token、SmoothQuant)
  • W4A16:质量略差,但激活保 FP16,实现简单

工业上 W4A16 + AWQ 更主流,因为它在「质量损失/工程复杂度」上甜点更高。

经验 4:FP8 是未来

如果你的硬件是 Ada / H100+ / B200+,优先选 FP8——通常不需要 outlier 处理、量化时间短、在 8 位方案里质量损失最小。

经验 5:量化对训练动态没用

量化主要服务于推理。训练时即使是 FP8 训练(DeepSeek-V3)也是混合精度——主权重和优化器状态留在高精度、只有部分 GEMM 走 FP8,不是简单地把所有计算 cast 到 FP8。

16.10 把量化纳入完整推理 pipeline

量化是推理优化金字塔的一层,不是独立技术。完整的最优推理 pipeline 是:

flowchart TB
  ARCH["架构层<br/>GQA / MLA / SwiGLU"] --> Q
  Q["量化层<br/>FP8 / INT4 + AWQ"] --> K
  K["KV Cache 层<br/>PagedAttention + Prefix Caching + INT8 KV"] --> A
  A["Attention 内核<br/>Flash Attention 2/3"] --> S
  S["调度层<br/>Continuous Batching + Chunked Prefill"] --> D
  D["分布式层<br/>TP / PP / EP / PD 分离"]

每一层各自贡献一部分优化,叠加下来,从一个朴素 PyTorch 循环到调好的 vLLM 集群,吞吐差距可以达到数量级。量化是其中关键一环——它把 HBM 带宽这个最硬的瓶颈直接压下去 2-4 倍。

本章小结

  1. 量化的价值在 Decode 时显现——HBM 带宽 = 推理瓶颈,量化 = HBM 读取压缩。
  2. 量化的几个维度:权重 vs 激活 vs KV、PTQ vs QAT、对称 vs 非对称、粒度(per-tensor / channel / group / token)。
  3. W8A8 + SmoothQuant 是 INT8 部署的标配——通过等价变换把激活 outlier 转移到权重。
  4. W4A16 + GPTQ / AWQ 是 INT4 部署的标配——AWQ 不需要 Hessian、量化快得多,质量与 GPTQ 相当或略好。
  5. FP8 是 H100+ 时代的新主流——动态范围大、通常不需要 outlier 处理。DeepSeek-V3 用 FP8 混合精度完成训练。
  6. 不同硬件支持不同量化——选方案要看目标硬件能跑什么。
  7. 量化是推理优化的一层,不是全部——和 GQA / Flash Attention / KV Cache 等技术叠加才能发挥威力。
  8. 质量损失通常很小——AWQ 论文里 g128 的 INT4 AWQ 在 Llama / Llama-2 7B–70B 上 WikiText-2 困惑度只涨约 0.1,但要在自己的任务上实测。

下一章我们看推理优化的另一个独立维度——投机解码(Speculative Decoding)。它用一个小模型「赌」出多个候选 token,让大模型一次验证多个、绕开 Decode 的逐 token 限制——论文报告的单请求加速多在 2-4×,但大 batch 下会明显缩水。

延伸阅读

  • Dettmers et al., LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale, NeurIPS 2022(arXiv:2208.07339)——LLM.int8() 论文。
  • Xiao et al., SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models, ICML 2023(arXiv:2211.10438)——SmoothQuant。
  • Frantar et al., GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers, ICLR 2023(arXiv:2210.17323)——GPTQ。
  • Lin et al., AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration, MLSys 2024(arXiv:2306.00978)——AWQ。
  • Micikevicius et al., FP8 Formats for Deep Learning, 2022(arXiv:2209.05433)——E4M3 / E5M2 格式提案。
  • bitsandbytes 库: https://github.com/bitsandbytes-foundation/bitsandbytes
  • AutoAWQ(已归档,后继为 vLLM llm-compressor): https://github.com/casper-hansen/AutoAWQ
  • DeepSeek-V3 Technical Report——FP8 训练实战。
  • vLLM 量化文档: https://docs.vllm.ai/en/latest/quantization/