# 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 ) ```