跳到主要内容
推理优化

第4章:量化

掌握 W8A8(SmoothQuant)、INT4(GPTQ/AWQ)、KV Cache 量化和 FP8 量化的原理与选型决策

量化 SmoothQuant GPTQ AWQ FP8 KV Cache量化

量化是推理优化”用精度换显存 / 带宽 / 算力”的核心手段——一个 70B FP16 要 140 GB 显存,INT4 量化后只剩 35 GB,单卡就能跑;FP8 GEMM 算力是 BF16 的 2 倍。本章系统覆盖 W8A8(SmoothQuant)、INT4(GPTQ/AWQ)、KV Cache 量化、FP8 量化的原理与工程选型。

📑 目录


1. 量化基础概念

1.1 量化的数学

把 FP16 张量 xx 量化到 INT8:

xint=round(xs)+z,x^fp=(xintz)sx_{\text{int}} = \text{round}\left(\frac{x}{s}\right) + z, \quad \hat{x}_{\text{fp}} = (x_{\text{int}} - z) \cdot s
  • ss (scale):缩放因子
  • zz (zero point):零点(对称量化时为 0)

1.2 对称 vs 非对称

类型公式适用
对称s=max(x)/127s = \max(\|x\|) / 127权重(分布对称)
非对称s=(maxmin)/255s = (\max - \min) / 255, zz 调整激活(ReLU 后偏正)

1.3 量化粒度

粒度共享 scale 范围精度速度
Per-tensor整个 tensor 一个 scale最快
Per-channel每个输出通道一个 scale
Per-group每 g 个权重一个 scale(g=32/64/128)中等
Per-token (动态)每个 token 一个 scale(激活用)较慢

🌟 经验:权重 per-channel 或 per-group,激活 per-token,综合最佳。

1.4 PTQ vs QAT

方法全称流程适用
PTQPost-Training Quantization训练后用少量校准数据求 scale大模型主流(快、省)
QATQuantization-Aware Training训练时模拟量化,反传梯度精度敏感场景

LLM 几乎全部用 PTQ——QAT 对 100B+ 模型代价太高。


2. W8A8:SmoothQuant

2.1 直接量化激活的问题

LLM 激活有显著的 outlier(某些通道的值是其他通道的 100 倍以上),直接 per-tensor 量化会让大部分通道精度损失严重(scale 被 outlier 拉得太大)。

2.2 SmoothQuant 的等价变换

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

把激活的”难度”(outlier)转移到权重上——权重容易量化,激活变得 smooth。

scale sjs_j 的选择:

sj=max(Xj)α/max(Wj)1α,α[0,1]s_j = \max(|X_j|)^\alpha / \max(|W_j|)^{1-\alpha}, \quad \alpha \in [0, 1]

α=0.5\alpha = 0.5 是常用值,效果稳定。

2.3 收益

  • W8A8 整张图(GEMM 用 INT8 算力)
  • 显存:模型大小 ÷ 2
  • 算力:INT8 Tensor Core ≈ FP16 的 2×
  • 精度:LLaMA-7B 在 WikiText 困惑度上升 < 0.5%

2.4 用法

from vllm import LLM

llm = LLM(model="...", quantization="smoothquant", ...)

或预量化好的 GGUF / safetensors 文件:

llm = LLM(model="path/to/llama-7b-w8a8")

3. Weight-only INT4:GPTQ 与 AWQ

3.1 思路

只把权重量化到 INT4(激活仍 FP16),compute 时把 INT4 权重 dequant 回 FP16 再算:

INT4 权重(磁盘/显存)
   ↓ dequant 到 FP16
GEMM (FP16 × FP16)
  • ✅ 显存大幅降低(权重 ÷ 4)
  • ✅ HBM 带宽降低(memory bound 算子加速明显)
  • ❌ Compute 不加速(因为还是 FP16 GEMM)

适合 memory bound 场景:Decode 阶段、batch 小的服务

3.2 GPTQ:基于 Hessian 的逐层量化

逐层处理,基于二阶信息(Hessian)选 scale,使量化误差对最终输出的影响最小。

from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

quantize_config = BaseQuantizeConfig(
    bits=4, group_size=128, desc_act=False,
)
model = AutoGPTQForCausalLM.from_pretrained("Llama-7b", quantize_config)
model.quantize(calib_dataset)   # 几十条样本即可
model.save_quantized("Llama-7b-gptq-int4")

3.3 AWQ:基于 Activation 分布保护重要权重

观察:权重的重要性由对应的激活幅值决定——激活大的通道,对应权重的精度更重要。AWQ 给重要通道更宽的 scale 范围,精度更好。

from awq import AutoAWQForCausalLM
model = AutoAWQForCausalLM.from_pretrained("Llama-7b")
model.quantize(quant_config={"w_bit": 4, "q_group_size": 128})
model.save_quantized("Llama-7b-awq")

3.4 GPTQ vs AWQ

维度GPTQAWQ
精度中-高
速度(校准)较慢(算 Hessian)
推理速度一致一致
工业偏好新项目首选

3.5 Marlin Kernel:让 INT4 真正快

朴素的”INT4 dequant 到 FP16 再 GEMM”在小 batch 下反而比 FP16 慢,因为 dequant 开销太大。

Marlin 是 INT4 × FP16 的高性能融合 kernel:

  • 避免显式 dequant,直接 fused 运算
  • batch=1-32 时 1.5-2× FP16 性能
  • 工业级实现,vLLM、TensorRT-LLM 都集成
llm = LLM(model="...-awq", quantization="awq_marlin")  # 启用 Marlin

4. KV Cache 量化:长上下文救星

4.1 动机

长上下文(32K-128K)场景,KV Cache 占显存的大头甚至超过权重。把 KV 从 BF16 量化到 INT8 / INT4 / FP8,显存直接 ÷ 2 / ÷ 4,并发能力翻倍。

4.2 KIVI:2-bit KV 量化

KIVI 论文证明:Key 用 per-channel 量化,Value 用 per-token 量化——这样可以做到 2-bit,精度损失 <1%。

量化方式KV 大小精度
BF16100%基线
INT850%-0.1%
INT425%-0.5%
KIVI 2-bit12.5%-0.8%

4.3 工程实现

# vLLM
llm = LLM(model="...", kv_cache_dtype="fp8")   # 或 "fp8_e5m2"

需要专门的 paged attention kernel 支持 mixed-precision——FlashInfer / vLLM 都已实现。


5. FP8:Hopper 时代的新宠

5.1 FP8 两种格式

格式指数尾数范围精度用途
E4M34310±2\sim 10^{\pm 2}较高权重 / 激活(forward)
E5M25210±5\sim 10^{\pm 5}较低梯度(范围更重要)

5.2 FP8 GEMM 优势

H100 的 FP8 Tensor Core 算力 = 1979 TFLOPS = 2× FP16。Decode 阶段可以:

  • 显存:权重 + KV 都 FP8,显存 ÷ 2
  • 算力:GEMM 速度 2×

5.3 动态 Scaling

FP8 范围窄,每个 tensor 的 scale 必须动态调整。NVIDIA Transformer Engine 自动处理:

import transformer_engine.pytorch as te
from transformer_engine.common.recipe import Format, DelayedScaling

fp8_recipe = DelayedScaling(fp8_format=Format.HYBRID)
with te.fp8_autocast(enabled=True, fp8_recipe=fp8_recipe):
    out = te_module(x)

5.4 vLLM FP8

llm = LLM(model="...-fp8", quantization="fp8", kv_cache_dtype="fp8")

DeepSeek-V3、LLaMA-3 都有官方 FP8 权重发布,直接用即可。


6. 选型决策与精度评估

6.1 决策树

目标?
├─ 单卡装下大模型(显存优先)
│   ├─ 略微精度损失可接受 → AWQ INT4
│   └─ 不能损失 → SmoothQuant W8A8 / FP8

├─ 加速 Decode(batch 小)
│   └─ AWQ + Marlin (memory bound 加速)

├─ 加速 Prefill / 大 batch (compute bound)
│   ├─ Hopper(H100) → FP8
│   └─ Ampere(A100) → SmoothQuant W8A8

├─ 长上下文(32K+)
│   └─ KV Cache 量化(INT8 / FP8 / KIVI)

└─ 多种叠加
    └─ 权重 INT4 + KV INT8 + FP8 GEMM(各自独立)

6.2 精度评估三件套

# 1. 困惑度 PPL(技术指标)
def calculate_ppl(model, dataset):
    total_loss, total_len = 0, 0
    for text in dataset:
        ids = tokenizer.encode(text, return_tensors='pt').cuda()
        with torch.no_grad():
            loss = model(ids, labels=ids).loss
        total_loss += loss.item() * ids.shape[1]
        total_len += ids.shape[1]
    return math.exp(total_loss / total_len)

# 2. Benchmark 集(MMLU / HumanEval / GSM8K)
# lm-eval-harness:https://github.com/EleutherAI/lm-evaluation-harness

# 3. 人工对比(关键!)
# 准备 50 条业务相关 prompt,FP16 vs 量化版结果并排,人工打分

🌟 量化的金科玉律:所有量化必须在你的目标场景上做精度验证,不要相信论文上的”无损”

6.3 实测对比模板

配置显存TTFTTPOTThroughputMMLU人工评价
FP16140 GB100ms30ms3000 tps65.25.0
W8A870 GB80ms25ms4500 tps64.84.9
AWQ INT435 GB95ms22ms5500 tps63.54.7
FP870 GB60ms18ms6000 tps65.05.0

✅ 自我检验清单

  • 量化数学:能写出对称量化的公式和 zero point 含义
  • 粒度选择:能解释为什么权重 per-channel / 激活 per-token 是最佳实践
  • SmoothQuant 直觉:能解释”把激活的难度转移到权重”的等价变换
  • GPTQ vs AWQ:能对比两者的核心差异,以及为什么 AWQ 是新项目首选
  • INT4 反直觉:能解释为什么 INT4 不一定比 INT8 快(dequant 开销)
  • Marlin 作用:能解释为什么 INT4 + Marlin 才能在小 batch 下真正加速
  • KV 量化:能解释为什么 Key per-channel + Value per-token 的 KIVI 设计
  • FP8 vs INT8:能列出 FP8 在 H100 上相对 INT8 的优势(算力、原生 Tensor Core 支持)
  • 选型实战:给一个”70B 单卡 80GB”的需求,能给出最优量化方案
  • 精度验证:能设计一套量化前后的对比评估方案

📚 参考资料

论文

工具