Skip to content

量化 ​

标签
AI/infra/量化
AI/infra/推理引擎
字数
2649 字
阅读时间
11 分钟

量化是推理优化里唯一能同时降显存、降带宽、甚至提算力的手段。它的核心问题只有一个:用几位表示一个数。

这一篇按「W{A}{B}」这套命名法组织 —— A 是激活位宽、B 是权重位宽。这个记号比按方法名罗列清楚得多,因为它直接说明「动了哪一半」。

先看命名法 ​

记号做了什么代表方法
W16A16什么都没量(BF16 基线)—
W8A16 / W4A16只量权重,激活保持 BF16GPTQ、AWQ、bitsandbytes NF4
W8A8权重与激活都量到 INT8SmoothQuant、LLM.int8
W4A8权重 4 bit + FP8 激活TensorRT-LLM 支持
W4A4极致QuaRot、Atom

一条选型判据(比「哪个方法更好」有用得多):

权重侧优先看显存与带宽,激活侧优先看算力。

  • weight-only 在 memory-bound 场景赢 —— 单请求、长上下文(Decode 阶段就是这种形态,见 01-推理性能指标与瓶颈定位)
  • W8A8 在 compute-bound 场景赢 —— 大 batch、短上下文

所以「量化能不能加速」的答案是取决于你的瓶颈在哪一侧,不是一个固定结论。

W{A}{B} 这个记号直接说明「动了哪一半」,选型看瓶颈在哪一侧:

                       权重侧:优先看显存与带宽
                              │
        ┌─────────────────────┴─────────────────────┐
        ▼                                           ▼
   W4A16 / W8A16                               W8A8 / W4A8
   只量权重,激活保持 BF16                       权重与激活都量
        │                                           │
   在 memory-bound 场景赢                      在 compute-bound 场景赢
   单请求、长上下文                            大 batch、短上下文
   (Decode 阶段就是这种形态)                   (Prefill 阶段是这种形态)

  ⇒ 「量化能不能加速」的答案取决于你的瓶颈在哪一侧,不是固定结论

三种量化算法:GPTQ、AWQ 与 SmoothQuant ​

GPTQ(ICLR 2023) ​

基于二阶信息逐层量化。 用 Hessian 近似确定量化每个权重时的最优补偿策略,并把量化误差「传播」到后续权重中。该文报告可在数小时内把 OPT-175B / BLOOM-176B 压到 3–4 bit 而精度几乎不受影响 —— 它建立了 LLM 量化的工程基准,后续方法多以它为比较对象。

工程参数(vLLM 实现):

参数含义典型值
group_size多少权重共享一个 scale32 / 64 / 128
desc_act是否按激活顺序重排布尔
bits位宽4 或 8

AWQ(MLSys 2024 最佳论文) ​

核心发现:只需保护约 1% 的关键权重,就能实现近乎无损的 4-bit。 按激活值的大小识别重要权重,先用缩放因子保护它们再量化。

与 GPTQ 的差别是取向上的:

GPTQAWQ
依据二阶信息(Hessian 近似)激活统计
风格逐层二阶优化更直觉、更快、硬件更友好
group_size32–128通常更大(64–128)
精度一致性基准多数评测中更一致

当前 GPU 生产服务的首选 4-bit 方案是 AWQ + Marlin。

SmoothQuant(ICML 2023):把难度搬走 ​

Weight-only 之外的路线:权重与激活都量到 INT8(W8A8),难点是激活里的 outlier —— 少数极大值会把整个张量的量化尺度撑坏。

SmoothQuant 的解法是一次数学等价变换:把量化的难度从激活端迁移到权重端。

Y=(X⋅diag(s)−1)⋅(diag(s)⋅W)

两边相乘后仍然是原来的 XW,但左边的激活被缩小、右边的权重被放大 —— 激活更好量了,代价由权重承担(而权重本来就好量)。这条变换无需训练。

该文数据:OPT-66B 上精度损失 0.6%,推理加速 1.56×,显存减半。已在 TensorRT-LLM 与 vLLM 原生集成,是生产级 INT8 部署的标准路径。

SmoothQuant 的等价变换:把量化的难度从激活端迁到权重端

  难点:激活里有 outlier(少数极大值把整个张量的量化尺度撑坏)

    变换前: Y = X · W
                 ↑     ↑
              难量化  好量化

    等价变换:

      Y = ( X · diag(s)⁻¹ ) · ( diag(s) · W )
           └── 激活被缩小 ──┘   └── 权重被放大 ──┘

    两边相乘后仍然是原来的 XW ⇒ 数学等价,且无需训练
    ⇒ 激活更好量了,代价由权重承担(而权重本来就好量)

  该文数据:OPT-66B 上精度损失 0.6%,推理加速 1.56×,显存减半

KV Cache 量化 ​

与权重量化解耦,单独对 K/V 下手:KIVI 做到 2-bit;FP8 KV Cache 是 Hopper 上的常规选择。

它的收益直接来自 10-KV Cache 与推理优化 的结论:Decode 是带宽受限的,而 KV Cache 能占总显存的 50%–80%。

别把两者绑在一起量化

权重 INT4 与 KV bf16 是可以共存的(它们是解耦的)。该文在 2k 上下文评测,而 32k+ 时 INT4 权重 + 量化 KV 会在 needle-in-haystack 这类任务上明显退化。

判据:只有在目标上下文长度上实测过,才量化 KV。

低比特浮点:FP8 与 NVFP4 ​

格式硬件粒度
FP8(E4M3 / E5M2)Hopper、Ada(cc ≥ 8.9)、Blackwellper-tensor / per-token / per-group
NVFP4(FP4 E2M1)Blackwell(SM 10.0+)per-16 值 micro-block + per-tensor FP8 缩放

FP8 是 Ada 及以上 GPU 的默认精度选择 —— DeepSeek-V3/R1、Kimi-K2 等模型已原生支持 FP8 训练与推理。

激活量化的三种粒度(vLLM 实现里可配):

粒度特点
per-tensor整个张量一个 scale,内存开销最低
per-token每个 token 一个 scale,对 outlier token 更友好
per-block折中

精度要匹配,不要混搭

在 H100 上只开 --kv-cache-dtype fp8 而权重留 bf16,比两者单独都差约 10% 吞吐 —— 因为 K 投影的输出每一步都要被降位转换一次。

要开就一起开,或都不开。

Kernel 比算法更决定速度 ​

这一条最容易被忽略:同一份 AWQ 量化权重,换 kernel 能差 61%。

Marlin kernel 的做法是把反量化与矩阵乘法融合进一个 kernel —— 绕过 L1 走 async-copy、用共享内存双缓冲。它让 INT4 权重拿到接近理想的 4× 加速。

实测(Qwen2.5-32B,H200,batch=1,输入 256 / 输出 512 token):

方案吞吐(tok/s)相对基线
Baseline(BF16)4611.0×
Marlin-AWQ7411.61×
Marlin-GPTQ7121.54×
bitsandbytes(NF4)1680.36×
GGUF Q4_K_M930.20×

Marlin 的静默回退陷阱

vLLM 只在 group_size 匹配(128 或 -1)且 shape 对齐时才走 Marlin,否则回退到慢 3–4 倍的 kernel,而且不给警告。

症状是「我的 4-bit 模型比 fp16 还慢」。 查法是启动日志里有没有 Using Marlin kernel;没有就用 group_size=128 重量化。

表里 GGUF 那 93 tok/s 也是同一类问题的另一面 —— GGUF 是为 llama.cpp 设计的格式,在 vLLM 栈里没有优化过的 kernel。

同一份 AWQ 量化权重换 kernel 后的吞吐(Qwen2.5-32B / H200 / batch=1):

  1.0×  ┤ Baseline(BF16)      ████████████████ 461 tok/s
        │
  1.61× ┤ Marlin-AWQ           ██████████████████████████ 741
  1.54× ┤ Marlin-GPTQ          █████████████████████████ 712
        │
  0.36× ┤ bitsandbytes(NF4)   ██████ 168
  0.20× ┤ GGUF Q4_K_M          ███ 93
        └──────────────────────────────────────────▶ tok/s

  ⇒ 同一份权重,最好与最差差 8 倍 —— kernel 的选择比算法本身更要紧

  静默回退陷阱:vLLM 只在 group_size 匹配(128 或 -1)且 shape 对齐时才走
  Marlin,否则回退到慢 3–4 倍的 kernel,而且不给警告。
  症状是「我的 4-bit 模型比 fp16 还慢」;查法:启动日志里有没有
  Using Marlin kernel

vLLM 生态里的格式支持 ​

方法位宽加载方式硬件门槛
AWQ + MarlinW4A16--quantization awq(自动用 Marlin)Turing+
GPTQ / GPTQModelW4A16、W3--quantization gptqVolta+(兼容面最广)
compressed-tensorsW4A16 / W8A8 / FP8自动检测,无需 flagTuring – Blackwell
FP8(E4M3)W8A8 FP8--quantization fp8 或自动Ada / Hopper / Blackwell
INT8 W8A8W8A8compressed-tensors 自动Turing+
AutoRoundW4A16、INT2–4compressed-tensors 自动含 CPU
bitsandbytes NF4W4A16--quantization bitsandbytesVolta–Hopper
GGUFQ4–Q8插件实验性

compressed-tensors 是值得记的一条:它由 neuralmagic(Red Hat)与 vLLM 项目联合开发,把量化元数据存进 checkpoint 的 quantization_config 字段,vLLM 读 checkpoint 就自动加载 —— 不需要任何额外 flag。

配套的量化工具是 llm-compressor,一个工具覆盖 W4A16 / W8A8-INT8 / FP8:

python
from llmcompressor.transformers import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier

recipe = GPTQModifier(scheme="W4A16", targets="Linear", ignore=["lm_head"])
oneshot(model="Qwen/Qwen3-30B-A3B", dataset="open_platypus", recipe=recipe,
        output_dir="...", max_seq_length=2048, num_calibration_samples=512)

MoE 模型的特殊建议:在 cc ≥ 8.9 的卡上优先用 FP8 block-wise —— 它无需校准数据、官方支持最好。FP8 per-tensor 在 Qwen3-MoE 上有已知的维度不匹配问题。

五个坑 ​

坑表现与判据
长上下文退化该文只在 2k 评测;32k+ 时 INT4 在 needle-in-haystack 上掉 3–5 分。在目标上下文长度上实测
数学与代码任务格外敏感GSM8K / HumanEval 对权重噪声异常敏感(示例:fp16 55 分 → INT4-GPTQ 48 分);chat / 摘要几乎无损。要跑自己任务的评测,不能只看困惑度
校准集污染用 C4(99% 英文)校准中文模型 → MMLU 正常但中文输出乱码。多语言模型要用覆盖目标语言的混合校准集
LoRA 与量化混用QLoRA 的正确做法是 adapter 保持 bf16、只量 base。把 bf16 LoRA 合并进 GPTQ base 再重量化会退化
kernel 静默回退见上一节

量化「不必然加速」

硬件没有对应低精度 kernel、或计算图频繁做量化-反量化时,延迟可能不降反升。评估量化必须同时报模型大小、延迟、内存、精度四项变化。

这条在端侧同样成立 —— 见 07-端侧推理。

相关 ​

参考 ​

贡献者 ​

文件历史 ​