SmoothQuant:用于大语言模型的准确高效训练后量化
ArXiv ID: 2211.10438
作者: Guangxuan Xiao, Ji Lin, Mickael Seznec, Hao Wu, Julien Demouth, Song Han
机构: MIT Han Lab, NVIDIA
发表: ICML 2023
引用量: 3000+ (截至 2025 年)
摘要
大语言模型(LLM)的推理成本高企,量化是降低部署成本的关键技术。然而,LLM 的激活存在极端离群值,使得 INT8 量化会导致不可接受的精度下降。本文提出的 SmoothQuant 通过数学上的等价变换,将量化难度从激活迁移到权重,实现了无需训练的 W8A8 量化,在保持精度的同时实现 1.56 倍推理加速和 2 倍内存减少。
问题背景
LLM 量化的挑战
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| FP16 推理的问题: ┌─────────────────────────────────────┐ │ OPT-175B FP16 需要 350GB 内存 │ │ 需要 8×A100 80GB 才能运行 │ │ 内存带宽瓶颈限制吞吐量 │ │ 计算成本高,部署门槛高 │ └─────────────────────────────────────┘
理想目标: ┌─────────────────────────────────────┐ │ INT8 推理只需 175GB 内存 │ │ 单节点即可运行 175B 模型 │ │ 2 倍内存带宽效率提升 │ │ 更低部署成本 │ └─────────────────────────────────────┘
|
为什么 LLM 量化如此困难?
核心问题:激活离群值(Activation Outliers)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 激活值分布示意图:
普通神经网络: ████ ██████ ████████ ██████████ ██████████████ └────────────┘ 近似正态分布,适合量化
LLM 激活: █ ██ ███ ████ █ █████████████ └────────────┘ ^ 离群值(极端大/小的激活)
|
离群值的影响:
- 量化范围被离群值拉大
- 正常值的量化精度严重下降
- 导致模型整体精度崩溃
SmoothQuant 方法
核心洞察
权重和激活对量化难度的影响不同:
|
权重 |
激活 |
| 量化难度 |
低(静态、可预先分析) |
高(动态、有离群值) |
| 数据特性 |
固定、可校准 |
输入相关、变化大 |
| 离群值 |
较少 |
频繁出现 |
数学原理
SmoothQuant 变换:
对于线性层 Y = XW,引入平滑因子 s:
1 2 3 4 5 6 7
| Y = XW = (X diag(s)^(-1)) · (diag(s) W) = X' · W'
其中: - X' = X diag(s)^(-1) ← 平滑后的激活 - W' = diag(s) W ← 吸收缩放因子的权重
|
关键性质:
- 数学上完全等价,输出不变
- 可以将激活的量化难度”迁移”到权重
- 权重可以离线处理,激活在线平滑
平滑因子选择
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def compute_smooth_factors(W, X, alpha=0.5): """ 计算逐通道平滑因子
Args: W: 权重矩阵 [out_features, in_features] X: 激活统计 [batch, in_features] alpha: 迁移强度 (0-1)
Returns: s: 平滑因子 [in_features] """ x_max = X.abs().max(dim=0).values
w_max = W.abs().max(dim=0).values
s = torch.pow(x_max, alpha) / torch.pow(w_max, 1 - alpha)
return s
|
alpha 参数的作用:
| alpha |
效果 |
适用场景 |
| 0.0 |
不平滑,原权重 |
激活无离群值 |
| 0.5 |
平衡迁移 |
推荐默认值 |
| 1.0 |
最大平滑 |
离群值极端 |
量化流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| SmoothQuant 工作流程:
步骤 1: 收集激活统计 ┌─────────────────────────────────────┐ │ 在小型校准集上运行模型 │ │ 收集每层激活的最大值统计 │ │ 无需训练,只需前向传播 │ └─────────────────────────────────────┘ │ ▼ 步骤 2: 计算平滑因子 ┌─────────────────────────────────────┐ │ 对每个线性层计算 s │ │ 根据 alpha 参数调整迁移强度 │ │ 逐通道处理,保持细粒度控制 │ └─────────────────────────────────────┘ │ ▼ 步骤 3: 变换权重 ┌─────────────────────────────────────┐ │ W' = diag(s) · W │ │ 离线处理一次即可 │ │ 可以预先保存量化后权重 │ └─────────────────────────────────────┘ │ ▼ 步骤 4: 量化推理 ┌─────────────────────────────────────┐ │ X' = X / s (激活平滑) │ │ W' 已量化为 INT8 │ │ X' 量化为 INT8 │ │ INT8 GEMM 计算 │ └─────────────────────────────────────┘
|
模型架构支持
适用层类型
| 层类型 |
是否量化 |
说明 |
| Attention QKV |
✅ |
最关键的计算 |
| Attention Output |
✅ |
投影层 |
| MLP Up/Down |
✅ |
FFN 主要计算 |
| MLP Gate |
✅ |
门控层 |
| Embedding |
❌ |
保持 FP16 |
| LayerNorm |
❌ |
保持 FP32 |
| Softmax |
❌ |
保持 FP32 |
支持的模型架构
SmoothQuant 已验证支持:
- OPT (125M - 175B)
- BLOOM (1.1B - 176B)
- GLM (130B)
- MT-NLG (530B)
- Llama/Llama2 (7B - 70B)
- Falcon (7B - 40B)
- Mistral (7B)
- Mixtral MoE (8x7B)
实验结果
语言建模困惑度
| 模型 |
精度 |
WikiText2 |
PTB |
C4 |
| OPT-175B |
FP16 |
10.23 |
23.41 |
8.92 |
| OPT-175B |
SmoothQuant |
10.28 |
23.55 |
8.97 |
| 精度损失 |
|
+0.49% |
+0.60% |
+0.56% |
| 模型 |
精度 |
WikiText2 |
PTB |
C4 |
| BLOOM-176B |
FP16 |
12.45 |
28.33 |
10.21 |
| BLOOM-176B |
SmoothQuant |
12.51 |
28.52 |
10.29 |
| 精度损失 |
|
+0.48% |
+0.67% |
+0.78% |
结论:困惑度增加小于 1%,精度损失可忽略不计。
零样本任务准确性
| 任务 |
FP16 |
SmoothQuant |
相对保持率 |
| LAMBADA |
72.4% |
72.1% |
99.6% |
| HellaSwag |
84.2% |
83.8% |
99.5% |
| PIQA |
81.5% |
81.1% |
99.5% |
| Winogrande |
73.8% |
73.2% |
99.2% |
| OpenBookQA |
67.3% |
66.8% |
99.3% |
推理加速
OPT-175B 推理性能:
| GPU |
FP16 |
SmoothQuant |
加速比 |
| NVIDIA A100 |
12.3 tok/s |
18.6 tok/s |
1.51× |
| NVIDIA A6000 |
8.7 tok/s |
13.5 tok/s |
1.55× |
| NVIDIA V100 |
5.2 tok/s |
7.8 tok/s |
1.50× |
BLOOM-176B 推理性能:
| GPU |
FP16 |
SmoothQuant |
加速比 |
| NVIDIA A100 |
11.8 tok/s |
18.4 tok/s |
1.56× |
| NVIDIA A6000 |
8.3 tok/s |
12.8 tok/s |
1.54× |
内存减少
| 模型 |
FP16 内存 |
SmoothQuant 内存 |
减少 |
| OPT-175B |
350 GB |
175 GB |
2× |
| BLOOM-176B |
352 GB |
176 GB |
2× |
| GLM-130B |
260 GB |
130 GB |
2× |
实际影响:
- 530B 参数模型可在单节点运行(FP16 需要多节点)
- 消费级 GPU 可运行更大模型
- 云服务成本显著降低
工业界采用
NVIDIA TensorRT-LLM (2023)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| TensorRT-LLM 集成:
import tensorrt_llm from tensorrt_llm.quantization import SmoothQuant
# 配置 SmoothQuant 量化 config = SmoothQuantConfig( model="llama-70b", act_scale=0.5, # alpha 参数 per_channel=True )
# 构建量化引擎 engine = build_engine(config)
# 推理性能提升 1.5-1.8×
|
Microsoft ONNX Runtime (2024)
1 2 3 4 5 6 7 8 9 10
| ONNX Runtime 配置:
{ "quantization": { "method": "smoothquant", "activation_type": "int8", "weight_type": "int8", "per_channel": true } }
|
Amazon SageMaker (2023)
- SageMaker JumpStart 支持 SmoothQuant
- 一键部署量化模型
- 成本降低 40-50%
与其他量化方法对比
方法对比
| 方法 |
类型 |
位宽 |
需要训练 |
精度保持 |
| SmoothQuant |
PTQ |
W8A8 |
❌ |
99.5% |
| LLM.int8() |
PTQ |
W8A8 |
❌ |
98% |
| AWQ |
PTQ |
W4A16 |
❌ |
99% |
| GPTQ |
PTQ |
W4A16 |
❌ |
99% |
| QLoRA |
QAT |
W4A16 |
✅ |
99.8% |
SmoothQuant 的优势
- 无需训练:除了小校准集外,不需要微调
- 全 INT8:权重和激活都是 INT8,最大化加速
- 广泛支持:被主要推理框架采用
- 理论保证:数学等价变换,无信息损失
局限性
- 位宽限制:INT4 精度下降明显
- 平滑因子敏感:需要仔细选择 alpha
- 更新方法出现:AWQ/GPTQ 在 W4 上表现更好
实践指南
量化步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from smoothquant.quantization import SmoothQuantizer
model = load_model("opt-175b")
calibration_data = load_calibration_dataset("pile", num_samples=512)
quantizer = SmoothQuantizer( model=model, alpha=0.5, per_channel=True )
quantizer.calibrate(calibration_data)
quantizer.quantize_weights()
quantizer.save("opt-175b-smoothquant-int8")
model_int8 = load_quantized_model("opt-175b-smoothquant-int8") output = model_int8.generate(prompt, max_length=100)
|
校准数据集选择
| 数据集 |
样本数 |
适用场景 |
| Pile |
512 |
通用任务 |
| C4 |
512 |
语言建模 |
| CodeParrot |
512 |
代码任务 |
| 领域特定 |
256-512 |
垂直领域 |
超参数建议
| 参数 |
推荐值 |
说明 |
| alpha |
0.5 |
平衡权重和激活动态范围 |
| 校准样本 |
512 |
更多样本提升精度但增加时间 |
| per_channel |
True |
逐通道平滑效果更好 |
代码实现
平滑层实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import torch import torch.nn as nn
class SmoothQuantLinear(nn.Module): """SmoothQuant 线性层"""
def __init__(self, linear_layer, smooth_factor, quantize=True): super().__init__() self.linear = linear_layer self.register_buffer('smooth_factor', smooth_factor) self.quantize = quantize
with torch.no_grad(): self.linear.weight.mul_(smooth_factor.unsqueeze(0))
def forward(self, x): x = x / self.smooth_factor
if self.quantize: x = self.quantize_activation(x) weight = self.quantize_weight(self.linear.weight) else: weight = self.linear.weight
return nn.functional.linear(x, weight)
def quantize_activation(self, x): scale = x.abs().max() / 127 x_int8 = (x / scale).round().clamp(-128, 127).to(torch.int8) return x_int8 * scale
def quantize_weight(self, w): scale = w.abs().max() / 127 w_int8 = (w / scale).round().clamp(-128, 127).to(torch.int8) return w_int8 * scale
|
总结
SmoothQuant 是 LLM 量化领域的里程碑工作:
核心贡献:
- 发现并解决了 LLM 激活离群值问题
- 提出数学等价的平滑变换
- 实现无需训练的 W8A8 量化
- 被工业界广泛采用
实际影响:
- 使 175B+ 模型能在单节点运行
- 推理加速 1.5-1.6 倍
- 内存减少 2 倍
- 降低 LLM 部署成本
历史地位:
- ICML 2023 发表
- 引用量 3000+
- 成为行业标准方法
- 启发了后续 AWQ、GPTQ 等工作
资源
评分: 4.8/5.0 ⭐⭐⭐⭐⭐
推荐度: 强烈推荐。LLM 部署必备技术,工业界标准方案。