Transformer 解剖:从 Attention 到推理系统

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

作者 杨艺韬 · 4,365 字

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

第 14 章我们看到 Decode 阶段的瓶颈是 HBM 带宽——每生成一个 token 都要把整个模型权重从 HBM 拉一遍。Llama-3 70B 在 FP16 下是 140 GB,单卡 H100 的 HBM 带宽 3.35 TB/s——一次 forward 至少 42 ms。这是「物理学的下限」。

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

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

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

读完这章你能:

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

LLM 训练时用的标准格式是 FP16 或 BF16——每参数 16 位 = 2 字节。这是个相对精确的浮点表示——FP16 能精确表示约 1024 个不同的小数值(在某个区间内)。

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

研究表明,不需要。一个训练好的 LLM 的参数分布大致是高斯型——绝大多数参数集中在 -1 到 1 之间,少数极端值散落在外面。如果你看具体值的精度,只需要分辨大约 16-256 个不同等级就足够保留模型能力。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),让两者大致等价:

xFP16sxintx_{\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 byte(INT8)而不是 2 byte(FP16)。

16.2 量化的几个维度

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

维度 1:量化什么?

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

维度 2:什么时候量化?

主流大模型几乎全部用 PTQ——重训 70B 模型成本几百万美元,PTQ 几小时跑完。

维度 3:粒度多细?

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

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

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

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

16.3 INT8 量化:W8A8 与 SmoothQuant

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

朴素 W8A8 的算法:

# 权重量化(per-channel)
scale_w = w.abs().max(dim=-1, keepdim=True) / 127
w_int8 = (w / scale_w).round().clamp(-127, 127).to(torch.int8)

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

# INT8 矩阵乘法(GPU Tensor Core 支持)
y_int32 = torch.matmul(x_int8, w_int8.T).to(torch.int32)

# 反量化
y_fp16 = y_int32.to(torch.float16) * scale_a * scale_w

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

激活 outlier 现象

研究发现(Dettmers et al., LLM.int8(),NeurIPS 2022),LLM 的激活分布不是正常的钟形分布——少数(约 0.1%)激活值是远超其他值的「outlier」(绝对值大几十倍)。

flowchart LR
  subgraph "激活 outlier"
    NORMAL[99.9% 的激活<br/>绝对值 < 6]
    OUT[0.1% 的激活<br/>绝对值 > 60]
  end
  NORMAL --> Q[量化时缩放因子<br/>被 outlier 撑大]
  OUT --> Q
  Q --> BAD[绝大多数值精度被牺牲<br/>质量大幅下降]

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

LLM.int8() 的解决方案

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

  1. 检测每层激活中的 outlier 列(通过统计阈值)
  2. outlier 列:FP16 计算
  3. 非 outlier 列:INT8 计算
  4. 最后合并

这种方案在 OPT、BLOOM 等早期开源大模型上几乎无损。但代价是计算路径分裂、kernel 实现复杂。

SmoothQuant 的精妙

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

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

Y=XW=(Xdiag(s)1)(diag(s)W)=XWY = X \cdot W = (X \cdot \text{diag}(s)^{-1}) \cdot (\text{diag}(s) \cdot W) = X' \cdot W'

选合适的 ss,让 XX'(缩放后的激活)outlier 减小,WW'(缩放后的权重)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 让 W8A8 在主流大模型上能做到「与 FP16 精度几乎无差」(< 0.5% 困惑度损失),同时显存减半、HBM 带宽减半。这是今天 W8A8 部署的事实标准。

16.4 INT4 量化:GPTQ 与 AWQ

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

GPTQ:基于 Hessian 的逐列量化

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

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

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

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

直观感受:每量化一列就「调整下一列以弥补这一列的损失」——把误差像击鼓传花一样传下去,最终所有列的整体误差最小。

GPTQ 的代价:

GPTQ 是早期 INT4 量化的代表,至今仍是最常用的方案之一。bitsandbytes、auto-gptq 等库都实现了它。

AWQ:基于权重显著性

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

AWQ 的关键 insight:

  1. 少数显著(salient)通道对模型质量贡献最大——这些通道对应的激活值大、影响输出多
  2. 如果只对显著通道做轻度缩放、对其他通道做激进 INT4 量化,整体质量损失可以非常小

具体算法:

  1. 用校准数据测量每个通道的「激活显著性」(activation magnitude)
  2. 给每个通道选一个缩放因子 ss——显著通道大 ss(让权重更精细),不显著通道小 ss
  3. 然后做标准 INT4 量化

AWQ 的优势:

AWQ 在 2024 年成为 INT4 量化的事实标准——Llama / Mistral / Qwen 的 INT4 版本几乎都用 AWQ。

GPTQ vs AWQ 实测对比

在 Llama-2 7B 上的典型困惑度(PPL)对比(数值越低越好):

方案 WikiText-2 PPL C4 PPL 量化时间
FP16(基线) 5.47 7.26
GPTQ INT4 5.59 7.41 2 小时
AWQ INT4 5.55 7.36 10 分钟
朴素 INT4 7.21 9.50 几秒

可以看到:

16.5 FP8:Hopper 时代的新格式

NVIDIA H100 / B200 GPU 引入了新的硬件支持:FP8 浮点——8 位浮点格式,比 INT8 表达能力更强。

FP8 有两种格式:

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 用起来比 INT8 简单——直接 cast,质量损失很小。但 FP8 需要硬件支持(H100、H200、B200、A100 不支持)。

DeepSeek-V3 是首个全 FP8 训练的 frontier 模型——权重、激活、梯度的大部分计算都用 FP8。这把训练成本打到了同等模型的一半。

未来趋势:FP8 取代 INT8 + FP4 取代 INT4。NVIDIA Blackwell(B200)已经原生支持 FP4,H100 / H200 通过算法模拟也能用——硬件演化方向已经确定。

16.6 不同方案的对照表

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

方案 权重 激活 KV 显存压缩 硬件支持 质量损失 适用
FP16(基线) FP16 FP16 FP16 通用 0 训练 / 推理基线
BF16 BF16 BF16 BF16 A100+ 几乎无 取代 FP16 训练
W8A8 (SmoothQuant) INT8 INT8 FP16/INT8 T4+ < 0.5% PPL 中等并发推理
W8A16 INT8 FP16 FP16 通用 < 0.3% PPL 简单部署
W4A16 (GPTQ) INT4 FP16 FP16 T4+ 1-2% PPL 高密度部署
W4A16 (AWQ) INT4 FP16 FP16 T4+ < 1.5% PPL 替代 GPTQ
W4A8 INT4 INT8 INT8 A100+ 2-3% PPL 极致部署
FP8 (E4M3) FP8 FP8 FP8 H100+ < 0.3% PPL 新硬件最优
FP4 FP4 FP4 FP4 B200 1-2% PPL 下一代主流

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

16.7 量化的硬件依赖

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

Tensor Core 算力对照(H100 SXM 单卡,TFLOPS):

精度 TFLOPS 相对 FP16
FP32 67 0.07×
TF32 989
BF16 / FP16 989
FP8 (E4M3) 1979
INT8 1979
INT4 3958
FP4 (B200) 7916

理论上 INT4 矩阵乘比 FP16 快 4 倍——但实际推理瓶颈在 HBM 带宽(memory-bound),加速因子接近「权重大小压缩比」(也就是 4 倍)。

消费卡支持

结论:选量化方案要考虑目标硬件。给 4090 用户部署:FP8 是最优选;给 3090 用户:INT4 + AWQ;给 V100:W8A8 + SmoothQuant。

16.8 一个端到端例子

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

Step 1:选量化方案

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

INT4 后只有 35 GB——单卡 H100 80GB 能装!剩 45 GB 给 KV Cache,能撑大量并发。

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)
"

70B 模型量化大约 1-2 小时(A100 / H100 单卡)。

Step 3:用 vLLM 部署

pip install vllm

vllm serve llama-3-70b-awq \
    --quantization awq \
    --kv-cache-dtype fp8 \
    --max-model-len 32768 \
    --gpu-memory-utilization 0.95

注意配合 --kv-cache-dtype fp8 使用 FP8 KV Cache(H100 上原生支持,进一步省 KV 显存)。

Step 4:质量验证

跑一组评测(如 MMLU、HellaSwag)对比原始 FP16 vs INT4-AWQ 的分数差距。一般在 1% 以内。

Step 5:性能 benchmark

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

16.9 量化的工程经验

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

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

INT4 量化在 128K 长上下文上会显示出累积误差。建议:

经验 3:W8A8 和 W4A16 的比较

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

经验 4:FP8 是未来

如果你的硬件是 H100+ / B200+,优先选 FP8——不需要 outlier 处理、量化时间短、质量损失最小。

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

量化主要服务于推理。训练时即使是 FP8 训练(DeepSeek-V3)也是「以全 FP16 为参照、动态调整」——不是简单地把所有计算 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 分离"]

每一层都贡献 2-4× 优化,叠加起来从一个朴素 PyTorch 实现到 vLLM 集群是 100-1000× 的吞吐提升。量化是其中关键一环——它把 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 比 GPTQ 简单 10 倍且质量略好。
  5. FP8 是 H100+ 时代的新主流——比 INT8 表达能力强、不需要 outlier 处理。DeepSeek-V3 用 FP8 完成训练。
  6. 不同硬件支持不同量化——选方案要看目标硬件能跑什么。
  7. 量化是推理优化的一层,不是全部——和 GQA / Flash Attention / KV Cache 等技术叠加才能发挥威力。
  8. 质量损失通常 < 2%——主流 INT4 方案对大模型几乎无损。

下一章我们看推理优化的另一个独立维度——投机解码(Speculative Decoding)。它用一个小模型「赌」出多个候选 token,让大模型一次验证多个、绕开 Decode 的逐 token 限制——又一种 4-5× 的加速。

延伸阅读