6.6 KiB
6.6 KiB
GPTQ与AWQ
GPTQ(General Purpose Post-Training Quantization)
GPTQ是一种针对LLM的后训练量化方法,由Frantar等人于2023年提出,主要用于将模型权重从FP16量化到INT4/INT3,同时保持较好的模型质量。
核心思想:逐层量化与权重反解
GPTQ的核心是权重反解(Weight Dequantization)技术,逐层处理而非一次性量化:
量化流程
输入: 预训练好的FP16模型权重 W ∈ R^(n×m)
输出: 量化权重 W_q ∈ INT^n×m,兼反量化辅助信息
对每一层(Column-wise):
1. 量化当前层权重为INT
2. 计算量化误差
3. 将误差补偿回后续未量化列
4. 迭代处理所有列
权重反解原理
GPTQ利用了神经网络的权重冗余性:通过精确计算每个权重列的量化误差,并将误差补偿("反解")到后续列,可以最小化整体量化误差。
# GPTQ 伪代码
def gptq_quantize(W, bits=4):
n, m = W.shape
W_quant = torch.zeros_like(W, dtype=torch.int8)
H = W.T @ W # Hessian矩阵近似
for col in range(m):
# 量化当前列
w_col = W[:, col]
scale = w_col.abs().max() / (2**(bits-1) - 1)
w_q = (w_col / scale).round()
# 计算量化误差
error = w_col - w_q * scale
# 将误差补偿到后续列
# 使用Hessian信息指导误差分配
if col < m - 1:
W[:, col+1:] -= error @ H[col, col+1:] / H[col, col]
W_quant[:, col] = w_q
return W_quant, scales
Cholesky分解加速
原始GPTQ对每一列都需要矩阵求逆,计算量极大。优化版本使用Cholesky分解预处理:
# Cholesky分解预处理
L = torch.cholesky(H, upper=False)
L_inv = torch.inverse(L)
# 后续处理时可以直接用 L_inv
将预处理复杂度从 O(m² n) 降低到 O(m³),对于大模型效果显著。
GPTQ性能指标
| 量化精度 | 权重大小压缩 | 精度损失(Benchmark) |
|---|---|---|
| FP16 | 1x | 基线 |
| INT4 | ~4x | <2% (部分任务) |
| INT3 | ~5.3x | 3-5% |
| INT2 | ~8x | >10% (显著) |
AWQ(Activation-aware Weight Quantization)
AWQ是由Lin等人在2024年提出的量化方法,核心洞察是:并非所有权重对模型精度同等重要,量化误差对模型影响不同,需要根据激活值分布来决定量化策略。
核心思想:激活感知
AWQ发现,在LLM中,只有约1%的权重(对应较大的激活值)贡献了大部分模型性能。保护这些重要权重可以显著减少量化误差。
量化方法
def awq_quantize(W, X, bits=4):
"""
W: 权重矩阵 (out_features, in_features)
X: 校准激活值 (batch, in_features)
"""
# 计算每个权重通道的重要性
importance = (X.abs().mean(dim=0)) # per-channel 重要性
# 找出最重要权重(top-k)
k = int(W.shape[1] * 0.01) # 1% 保护比例
idx = torch.topk(importance, k).indices
# 对重要权重使用更高精度
W_protected = W[:, idx]
W_quantized = quantize(W[:, idx], bits=bits) # 保持高精度
# 其他权重正常量化
W_normal = quantize(W[:, idx], bits=bits)
return W_quantized, W_normal, scales
与GPTQ的对比
| 特性 | GPTQ | AWQ |
|---|---|---|
| 量化依据 | 基于Hessian(梯度敏感性) | 基于激活值分布 |
| 关注点 | 权重本身的重要性 | 权重对激活值的影响 |
| 保护策略 | 误差补偿 | 跳过/高精度保留 |
| 精度(INT4) | 较好 | 更优(部分场景) |
| 速度 | 较慢(逐列处理) | 较快 |
AWQ实现要点
# AWQ校准流程
1. 收集激活值统计:X = 模型中某层的输入
2. 计算重要性:I = |X|.mean(dim=0)
3. 保护最重要权重:W[:, top_k_idx] 保持FP16
4. 量化剩余权重:W[:, others] → INT4
5. 反量化计算:恢复FP16用于推理
LLM.int8()方法
LLM.int8()是Bitwise BLAS提出的INT8推理方法,解决大模型(>6B参数)INT8量化精度下降严重的问题。
离群值问题
研究发现,LLM的激活值中存在系统性离群值(Outliers):约0.1%的维度有远大于平均值的激活值,这些离群值如果被量化会引入巨大误差。
激活值分布: 大部分值在 [-1, 1],但某些维度在 [10, 100]
量化后: INT8范围 [-128, 127],离群值被截断,误差巨大
混合精度分解
LLM.int8()将计算分解为两部分:
def llm_int8_linear(x, w, bias=None):
# x: (batch, seq, hidden)
# w: (out_features, in_features) INT8
# Step 1: 分离离群值维度
x_normal = x[:, :, normal_dims] # INT8处理
x_outlier = x[:, :, outlier_dims] # FP16处理
# Step 2: INT8矩阵乘法(正常维度)
y1 = F.linear(x_normal, w_normal) # INT8 → INT32 → FP16
# Step 3: FP16矩阵乘法(离群值)
y2 = F.linear(x_outlier, w_outlier) # FP16
# Step 4: 结果融合
y = y1 + y2 + bias if bias else y1 + y2
return y
离群值检测
def find_outlier_dims(X, threshold=2.5):
"""检测离群值维度"""
# 计算每个维度的重要性(绝对值统计)
importance = X.abs().mean(dim=(0, 1)) # (hidden,)
# 超过阈值的维度标记为离群值
outlier_mask = importance > threshold * importance.median()
return outlier_mask
LLM.int8()效果
| 模型 | INT8精度损失 | 内存节省 | 速度提升 |
|---|---|---|---|
| LLaMA-7B | <0.5% | ~50% | ~1.4x |
| LLaMA-13B | <0.5% | ~50% | ~1.4x |
| LLaMA-65B | <1% | ~50% | ~1.3x |
与GPTQ/AWQ的关系
- GPTQ → 解决权重压缩问题,但未处理激活离群值
- AWQ → 通过激活感知优化权重分配,间接缓解离群值问题
- LLM.int8() → 直接处理离群值问题,提供完整INT8推理方案
实践建议
量化方法选择
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 4-bit推理 | AWQ | 精度更好 |
| 8-bit推理 | LLM.int8() | 离群值处理成熟 |
| 快速部署 | GPTQ | 工具链完善 |
| 内存极度受限 | GPTQ 3-bit | 压缩比高 |
量化工具
# GPTQ (AutoGPTQ库)
pip install auto-gptq
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(...).quantize(quantization_config)
# AWQ (vLLM集成)
pip install vllm
from vllm import LLM
llm = LLM(model, quantization="awq")
# LLM.int8() (Transformers集成)
model = AutoModelForCausalLM.from_pretrained(
..., load_in_8bit=True, llm_int8_threshold=6.0
)