Files
2026-05-16 17:16:51 +08:00

6.6 KiB
Raw Permalink Blame History

GPTQ与AWQ

GPTQGeneral 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% (显著)

AWQActivation-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的关系

  1. GPTQ → 解决权重压缩问题,但未处理激活离群值
  2. AWQ → 通过激活感知优化权重分配,间接缓解离群值问题
  3. 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
)