使用 70 GB 显存才能跑一次的 Llama-2-70B,量化后可以塞进 24 GB 的消费级显卡。这不是魔法,而是大语言模型(LLM)权重量化带来的实际收益。本文将深入讲解 GPTQ、AWQ、SmoothQuant、W4A16 等主流量化方案的原理与实战,帮助你在生产环境中实现 4 倍模型压缩而不显著牺牲精度。
一、为什么 LLM 量化与普通 CNN 不同
传统图像模型的后训练量化(PTQ)在 ResNet 上效果很好,但直接套用到 LLM 上时,困惑度(Perplexity)会急剧上升,生成结果质量显著下降。根本原因在于 LLM 激活分布中存在的幂律异常值(power-law outliers)。
在 Transformer 的各层中,激活张量的大部分元素集中在很小的区间内,但少量通道(通常是特定的几个 token 维度)会爆发出极大值。这些离群值(outliers)占据了动态范围的主体,如果采用传统的 per-tensor 对称量化,大量正常值就会被压缩到极少数的量化格子中,导致信息严重丢失。
有趣的是,权重比激活更容易量化。权重分布通常接近高斯,没有极端离群点;而激活中与输入内容强相关的通道则难以预测。因此,LLM 量化领域的核心思路是:
- 对权重激进地压到 4-bit(4x 压缩)
- 对激活保留 8-bit 或 16-bit,或想办法把量化难度从激活迁移到权重
压缩目标非常明确:把动辄 70 GB、130 GB 的 FP16 权重降到 20 GB、35 GB 以下,让单卡甚至笔记本都能完成推理。
二、GPTQ:一次性层内贪心量化
2.1 理论根基:Optimal Brain Quantization
GPTQ 的数学基础是 OBQ(Optimal Brain Quantization),其思想来源于"最优脑损伤"(Optimal Brain Damage):每次量化一个权重时,不仅要考虑该权重自身的舍入误差,还要衡量它对其它未量化权重的"影响",并补偿性地更新剩余权重。
形式化地说,对于一个层的权重矩阵 $W$,目标是最小化量化后的输出重建误差:
$$
\min_{\hat{W}} |WX - \hat{W}X|^2
$$
其中 $X$ 是该层的校准数据(calibration data,通常只需 128~256 条样本)。OBQ 通过二阶信息(Hessian 矩阵 $H = X X^T$)来决定先量化哪个权重,以及如何更新剩余权重。
2.2 GPTQ 的核心加速技巧
OBQ 的原始复杂度是 $O(d_{row} \cdot d_{col}^3)$,对于 LLM 中动辄 4096 x 4096 甚至更大的矩阵根本无法接受。GPTQ 做了三个关键近似:
- 逐行独立量化:把权重矩阵按输出行拆开,每行独立求解,将问题降为 $O(d_{col}^2)$。
- 固定量化顺序:不再动态挑选"最好"的权重先量化,而是按固定顺序(例如从左到右)依次处理。实验表明这对最终精度影响很小。
- Cholesky 预计算:对 Hessian 矩阵做 Cholesky 分解,避免每一步都重复求逆。这是 GPTQ 相比 OBQ 提速数百倍的关键。
直观理解,GPTQ 就是:
拿一小批校准数据,逐层、逐行地把 FP16 权重舍入到最近的 4-bit 网格点;每量化完一个权重,就用闭式公式更新同一行中尚未量化的权重,以补偿已引入的误差。
2.3 AutoGPTQ 实战
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer
model_id = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True)
# 准备校准数据(128~256 条即可)
examples = [
tokenizer("auto-gptq is an easy-to-use model quantization library.")
for _ in range(128)
]
quantize_config = BaseQuantizeConfig(
bits=4, # 4-bit 权重
group_size=128, # 分组量化,128 个权重共享一组缩放参数
desc_act=False, # 是否对激活降序排列(通常 False 更快)
)
model = AutoGPTQForCausalLM.from_pretrained(
model_id, quantize_config, torch_dtype="auto"
)
# 执行一次性量化
model.quantize(examples, batch_size=1)
# 保存
model.save_quantized("./llama-2-7b-gptq-4bit")
tokenizer.save_pretrained("./llama-2-7b-gptq-4bit")
group_size=128 的意思是每 128 个连续权重共用一对缩放因子(scale / zero-point),这也叫分组量化或block-wise quantization。group_size 越小,精度越高,但额外存储的开销也越大。
三、AWQ:激活感知的保护性量化
3.1 核心洞察:并非所有权重生而平等
AWQ(Activation-aware Weight Quantization)的作者观察到:虽然异常值出现在激活中,但造成这些异常值的"元凶"是与之相乘的少量权重通道。如果你把某些权重通道的重要性排序,会发现少数通道对最终输出质量具有不成比例的影响。
AWQ 的策略很巧妙:不是改变量化网格本身,而是对重要的权重通道施加一个微小的缩放因子,让量化后的舍入误差更多地落在不那么重要的通道上,从而保护"关键权重"。
3.2 与 QAT 的区别
值得注意的是,AWQ 不需要训练数据做反向传播。它通过分析激活幅值离线计算 per-channel scaling,整个过程是无梯度的后训练量化。这意味着:
- 不需要大量训练数据
- 不需要 GPU 做反向传播
- 量化速度快,通常几分钟内完成
3.3 AutoAWQ 实战
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_id = "meta-llama/Llama-2-7b-hf"
quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}
model = AutoAWQForCausalLM.from_pretrained(model_id, **quant_config)
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
# 准备校准数据
data = []
for _ in range(128):
data.append(tokenizer("AWQ quantization protects salient weight channels.")["input_ids"])
# 量化
model.quantize(tokenizer, quant_config=quant_config, calib_data=data)
model.save_quantized("./llama-2-7b-awq-4bit")
tokenizer.save_pretrained("./llama-2-7b-awq-4bit")
在大量公开评测中,AWQ 在相同 4-bit 压缩率下通常比 GPTQ 的困惑度低 5%~15%,尤其在代码生成和数学推理等对精度敏感的任务上优势更明显。
四、SmoothQuant:把量化难度从激活迁移到权重
GPTQ 和 AWQ 都聚焦于"权重量化",但激活依然保持 FP16 或 BF16。SmoothQuant 则提出了一个数学洞察:可以在不损失信息的前提下,把激活的量化难度"平滑"地迁移到权重上。
4.1 数学原理
对于一个线性层 $Y = XW$,引入一个按通道的缩放向量 $s > 0$:
$$
Y = (X \cdot \text{diag}(s)^{-1}) \cdot (\text{diag}(s) \cdot W) = \tilde{X} \cdot \tilde{W}
$$
其中:
- $\tilde{X} = X \cdot \text{diag}(s)^{-1}$:把激活中难以量化的离群通道"压平",使其更适合 INT8 量化
- $\tilde{W} = \text{diag}(s) \cdot W$:权重大不了变得稍微难量化一点,反正权重本来就比激活好量化
$s$ 的选择原则是:让迁移后的激活和权重都比较好量化。经验上,$s$ 通常取激活通道均值的某个幂次:
$$
s_j = \max(|X_j|)^\alpha / \max(|W_j|)^{1-\alpha}
$$
其中超参数 $\alpha$ 通常取 0.5,对激活较强的模型可以偏向 0.75。
4.2 实现效果
SmoothQuant 使 LLM 能够稳定地跑在 W8A8(8-bit 权重 + 8-bit 激活)配置上,相比原始 FP16:
- 模型体积减半
- 推理速度在 A100 上提升约 1.5~1.8x
- 精度损失极小(通常不到 1% 的困惑度上升)
它已经集成到 FasterTransformer 和 TensorRT-LLM 中,是 NVIDIA GPU 上高吞吐推理的推荐方案之一。
五、W4A16 与其它主流格式
5.1 W4A16 的含义
W4A16 表示:权重Weight 以 4-bit 存储,激活Activation 和 KV-Cache 以 16-bit(FP16/BF16)计算。这是目前消费级 GPU 上最常见的部署形态:
- 权重被压缩 4 倍,节省显存和 PCIe 带宽
- 计算仍使用原生 FP16/BF16,不需要自定义 CUDA kernel 做 INT4 乘加(硬件不支持 INT4 乘加)
5.2 常见量化格式对比
| 格式 | 说明 | 位宽 | 典型框架 | 特点 |
|---|---|---|---|---|
| GPTQ | 层内贪心重建量化 | W4A16 | AutoGPTQ, text-generation-inference | 通用性强,支持大量模型 |
| AWQ | 激活感知保护量化 | W4A16 | AutoAWQ, vLLM | 同压缩率下精度更高 |
| GGUF/GGML | llama.cpp 原生格式 | Q4_0, Q5_K_M, Q8_0 | llama.cpp, ollama, KoboldCpp | CPU 推理首选,跨平台 |
| EXL2 | ExLlama2 专有格式 | 混合 2~8 bit | ExLlama2 | 支持自适应混合位宽,显存利用率高 |
| NF4 | Normal Float 4 | W4A16 (QLoRA) | bitsandbytes, PEFT | 用于微调而非部署,分布最优的 4-bit 类型 |
| SmoothQuant | 平滑迁移量化难度 | W8A8 | FasterTransformer, TensorRT-LLM | 吞吐最高,需显卡支持 INT8 GEMM |
5.3 NF4 与 GGUF 补充
NF4(Normal Float 4)是 QLoRA 提出的 4-bit 数据类型。与均匀分布的 INT4 不同,NF4 的量化格子按照标准正态分布的累积分布函数(CDF)逆映射来分配,使得对符合高斯分布的权重来说信息熵损失最小。NF4 主要用于微调场景(QLoRA),而非推理部署。
GGUF 是 llama.cpp 的新一代模型格式(代替旧的 GGML)。它将 tokenizer、超参数、权重全部打包进一个二进制文件。量化变体极其丰富:
Q4_0:最简单的 4-bit,速度快但精度一般Q5_K_M:5-bit 混合精度,平衡速度和质量Q8_0:8-bit,几乎无损,适合对质量要求极高的场景
六、bitsandbytes:LLM.int8() 与 8-bit Adam
6.1 LLM.int8() 混合精度分解
bitsandbytes 的 LLM.int8() 是 LLM 量化的先驱工作之一。它发现了一个关键现象:异常值只集中在极少数的隐藏维度上(通常不到 1% 的通道)。于是 LLM.int8() 采用混合精度分解:
- 对不含异常值的矩阵块,使用标准的 INT8 GEMM
- 对包含异常值的列/行,退回到 FP16 进行计算
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_8bit=True,
llm_int8_threshold=6.0, # 超过此阈值的值视为异常值
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto",
)
这种方法在 8-bit 下几乎能做到零精度损失,但相比 4-bit 量化,显存节省只有 2 倍而非 4 倍。
6.2 何时用 8-bit,何时用 4-bit?
| 场景 | 推荐方案 |
|---|---|
| 显存极度紧张(如单卡 24GB 跑 70B 模型) | GPTQ / AWQ 4-bit |
| 追求最高生成质量,显存充裕 | LLM.int8() 或 Q8_0 |
| 需要继续微调(LoRA/QLoRA) | bitsandbytes NF4 + LoRA |
| 追求极致吞吐,部署在 A100/H100 集群 | SmoothQuant W8A8 |
| 在 macOS / CPU 上本地运行 | GGUF Q5_K_M / Q4_0 |
七、工程实战:从量化到部署
7.1 用 vLLM 部署 GPTQ/AWQ 模型
vLLM 的 PagedAttention 与量化权重配合,可以在单卡上获得惊人的吞吐:
from vllm import LLM, SamplingParams
# GPTQ 模型
llm = LLM(
model="TheBloke/Llama-2-7B-GPTQ",
quantization="gptq", # 或 "awq"
dtype="float16",
gpu_memory_utilization=0.9,
)
sampling_params = SamplingParams(temperature=0.7, max_tokens=256)
outputs = llm.generate(["量化后的模型推理速度显著提升。"], sampling_params)
7.2 transformers 直接加载
from transformers import AutoModelForCausalLM, AutoTokenizer
# GPTQ
model = AutoModelForCausalLM.from_pretrained(
"./llama-2-7b-gptq-4bit",
device_map="auto",
)
# AWQ
model = AutoModelForCausalLM.from_pretrained(
"./llama-2-7b-awq-4bit",
device_map="auto",
)
对于 GPTQ 模型,如果本地加载时遇到 auto-gptq 相关错误,通常需要先 pip install auto-gptq;AWQ 同理需要 pip install autoawq。
7.3 显存对比(Llama-2-70B)
| 配置 | 权重显存 | KV-Cache(batch=1, 4K context) | 总显存占用 | 适配显卡 |
|---|---|---|---|---|
| FP16 | 140 GB | ~20 GB | ~160 GB | 2x A100 80GB |
| GPTQ / AWQ W4A16 | ~40 GB | ~20 GB | ~60 GB | 1x A100 80GB |
| GGUF Q4_K_M | ~40 GB | ~20 GB | ~60 GB | CPU / 单卡 |
| SmoothQuant W8A8 | ~70 GB | ~10 GB | ~80 GB | 1x A100 80GB |
可以看到,4-bit 量化让 70B 模型从专业级 8 卡集群降低到了单卡可运行的范围。
八、精度、速度、显存的全局权衡
以下汇总了本文涉及的所有主流方案,方便快速选型:
| 方法 | 位宽 | VRAM 节省 | 推理速度 | 精度损失 | 推荐框架 |
|---|---|---|---|---|---|
| FP16/BF16 基线 | W16A16 | 1x | 1.0x | 0 | PyTorch, vLLM |
| LLM.int8() | W8A16 | ~2x | 0.9x | 极低 | transformers, bitsandbytes |
| SmoothQuant | W8A8 | ~2x | 1.5~1.8x | 低 | TensorRT-LLM, FT |
| GPTQ | W4A16 | ~4x | 0.8~1.0x | 中低 | AutoGPTQ, TGI, vLLM |
| AWQ | W4A16 | ~4x | 0.8~1.0x | 低 | AutoAWQ, vLLM |
| EXL2 | W2~8A16 | 2~8x(自适应) | 0.8~1.0x | 可调 | ExLlama2 |
| QLoRA (NF4) | W4A16 | ~4x | 0.8x | 中(微调用) | bitsandbytes + PEFT |
| GGUF Q4_0 | W4A16 | ~4x | CPU 可用 | 中等 | llama.cpp |
| GGUF Q5_K_M | W5A16 | ~3.2x | CPU 可用 | 低 | llama.cpp |
选型建议
- 个人开发 / 消费级显卡(RTX 4090 24GB):优先 AWQ 或 GPTQ W4A16,配合 vLLM 获得高吞吐
- 苹果 MacBook / CPU 本地运行:用
llama.cpp的 GGUF Q5_K_M,兼顾质量与速度 - 数据中心批量推理(A100/H100):SmoothQuant W8A8 + TensorRT-LLM,吞吐最高
- 低资源微调:QLoRA(NF4 + LoRA),在 16 GB 显存上微调 65B 模型
- 极致压缩:EXL2 可以在不重要的层压到 2
3 bit,重要层保留 45 bit
结语
大模型量化已经从学术研究变成了推理部署的标配技术。GPTQ 和 AWQ 的普及,让 70B 参数模型也能在单张消费级显卡上流畅运行;SmoothQuant 则为数据中心的高吞吐场景提供了 W8A8 方案;llama.cpp 的 GGUF 生态让 LLM 真正走到了边缘设备和普通笔记本上。
在实际选型时,不必追求"最先进",而应根据硬件限制、精度要求、吞吐目标三者综合权衡。4-bit 权重量化已经相当成熟,AWQ 在多数场景下是新项目的推荐起点;如果只是想在本地笔记本跑一跑开源模型,直接下载社区已经量化好的 GGUF 文件即可。量化技术的持续进步,正在将"大模型"变得越来越"小",也越来越触手可及。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。