224 lines
6.6 KiB
Markdown
224 lines
6.6 KiB
Markdown
# 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利用了神经网络的权重冗余性:通过精确计算每个权重列的量化误差,并将误差补偿("反解")到后续列,可以最小化整体量化误差。
|
||
|
||
```python
|
||
# 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分解预处理:
|
||
|
||
```python
|
||
# 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%的权重(对应较大的激活值)贡献了大部分模型性能。保护这些重要权重可以显著减少量化误差。
|
||
|
||
### 量化方法
|
||
|
||
```python
|
||
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实现要点
|
||
|
||
```python
|
||
# 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()将计算分解为两部分:
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
### 离群值检测
|
||
|
||
```python
|
||
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的关系
|
||
|
||
1. **GPTQ** → 解决权重压缩问题,但未处理激活离群值
|
||
2. **AWQ** → 通过激活感知优化权重分配,间接缓解离群值问题
|
||
3. **LLM.int8()** → 直接处理离群值问题,提供完整INT8推理方案
|
||
|
||
## 实践建议
|
||
|
||
### 量化方法选择
|
||
|
||
| 场景 | 推荐方法 | 理由 |
|
||
|------|---------|------|
|
||
| 4-bit推理 | AWQ | 精度更好 |
|
||
| 8-bit推理 | LLM.int8() | 离群值处理成熟 |
|
||
| 快速部署 | GPTQ | 工具链完善 |
|
||
| 内存极度受限 | GPTQ 3-bit | 压缩比高 |
|
||
|
||
### 量化工具
|
||
|
||
```bash
|
||
# 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
|
||
)
|
||
``` |