GPTQ:基于近似二阶信息的大模型权重量化
GPTQ:基于近似二阶信息的大模型权重量化
为什么大模型需要量化?
大型语言模型(LLM)在自然语言处理任务中展现了惊人的能力,但其庞大的参数量也带来了高昂的推理成本。一个 70 亿参数的模型,以半精度(FP16)存储就需要约 14GB 显存,这直接限制了它在消费级硬件上的部署。模型量化正是为了解决这一痛点:将模型权重从高精度(如 FP16)转换为低精度(如 INT4),从而大幅减少模型体积和计算资源需求,同时尽可能保持模型性能。
然而,简单的逐层均匀量化在面对现代大模型时效果不佳,因为权重分布并非均匀,且每层的敏感度差异巨大。GPTQ(Generative Pre-trained Transformer Quantization)应运而生,它通过利用近似二阶导数信息,实现了对大规模 Transformer 模型的高效、高精度一次性权重量化。
GPTQ 的核心思想
GPTQ 的全称是 “Optimal Brain Quantization for Generative Pre-trained Transformers”,其灵感来源于网络剪枝中的 Optimal Brain Surgeon (OBS) 与 Optimal Brain Damage (OBD) 算法。核心思路是:
- 逐层量化:一次只处理 Transformer 的一层,以控制计算开销。
- 权重量化补偿:在将某个权重舍入到量化网格后,并不是直接丢弃量化误差,而是根据该权重对应行的 Hessian 逆来更新该行其他未量化的权重,从而补偿该权重量化引起的输出误差。
- 近似二阶信息:精确计算 Hessian 矩阵及其逆在大模型中不可行。GPTQ 使用基于校准数据的统计 Fisher 信息矩阵作为 Hessian 的近似,并采用高效的 Cholesky 分解与贪婪顺序量化,使得算法能扩展到上千亿参数的模型。
最终,GPTQ 能够在 4-bit 量化下,让大模型在多个任务上的性能损失极小,甚至无需微调。
预备知识:量化基础
在深入算法之前,理解两个关键概念:
对称与仿射量化
- 线性量化:将浮点数值 (x) 映射为整数 (q) 的公式为
[ q = \text{round}(x / s) + z ] 其中 (s) 为缩放因子(scale),(z) 为零点(zero-point)。对称量化时 (z=0),通常用于权重量化。 - 分组量化:将权重矩阵按通道或按固定大小(如128)分组,每组拥有独立的 scale 和 zero-point,能够更好地适应局部数值范围,提升量化精度。
量化误差与补偿
当我们将一个权重 (w_i) 量化为 (\hat{w}_i),会产生误差 (e_i = w_i - \hat{w}_i)。该误差会传递到该层的输出。简单地将该误差分摊给同行的其他权重,可以在理论上最小化输出扰动——这正是 GPTQ 所做的事情。
GPTQ 算法详解
GPTQ 对 Transformer 的每个线性层(如注意力中的 Q、K、V、O 投影以及 FFN 层)独立量化。其主要步骤分为三个阶段:
阶段一:构建 Hessian 逆近似
对于权重矩阵 (W \in \mathbb{R}^{d_{\text{out}} \times d_{\text{in}}}),设输入激活为 (X \in \mathbb{R}^{n \times d_{\text{in}}})(来自校准数据集的 (n) 个 token 的输入)。定义该层的输出误差期望(对整层而言)与 Hessian:
- 输出平方误差:(E = |WX - \hat{W}X|_F^2),可视为每一行输出向量的误差和。
- 对于权重矩阵的某一行 (w)(一个长度为 (d_{\text{in}}) 的向量),其 Hessian(二阶导数矩阵)近似为 (H = 2 X^T X)。实际中,GPTQ 在损失函数中加入了对所有权重的对称性,通常直接使用 (H \approx 2X^T X + \lambda I),并利用惰性批量更新技巧。
为了能按任意顺序更新权重,我们需要知道移除(量化)某个权重后,如何更新其他权重以补偿误差。OBS 给出的更新规则需要用到 Hessian 逆 (H^{-1})。直接求逆计算量太大,因此 GPTQ 采用如下策略:
- 在校准数据上计算 (X^T X)。
- 对该矩阵进行 Cholesky 分解,得到上三角矩阵,进而可以按行递推地求解某个权重量化时的补偿量,而无需完整计算逆矩阵。
- 结合权重的顺序进行逐步懒惰更新,使得每一步只处理当前权重及与其相关的行元素。
阶段二:贪婪顺序量化与懒惰批更新
理论上,我们可以并行量化整行,并通过解线性系统来更新所有未量化权重,但这需要 (O(d_{\text{in}}^3)) 计算量。GPTQ 做了关键的简化:
- 贪婪顺序:按照某种固定顺序(例如,按列索引递增)逐个量化权重。
- 列组批处理:将输入维度划分为若干组(group),每次只处理一组内的列。组内逐列量化,组间独立。这样每组内的 Hessian 子矩阵维度较小。
- 懒惰更新:量化某一列后,不立即更新该行后续所有列的权值,而是累积“期望的更新量”。当需要量化第 (j+1) 列时,再考虑它对前 (j) 列误差的全局补偿。实际上,GPTQ 通过保持对未量化权重的校正向量,并用 Cholesky 因子递推地计算更新。
这一连串的优化使得整体时间复杂度降为 (O(\max(d_{\text{in}} \cdot d_{\text{out}}^2, d_{\text{in}}^3))),对于典型的 Transformer 矩阵尺寸(如 d_in=4096, d_out=4096)完全可行,且可以扩展到超大规模。
阶段三:量化与反量化
GPTQ 通常采用分组量化(group-wise quantization)。具体做法:
- 将权重矩阵重塑为组内(例如每组 128 个元素),每个组独立计算 scale(和 zero-point)。
- 在校准过程中,按组为单位计算 Hessian 近似,并结合 OBS 补偿逻辑,直接输出量化后的整数权重和组的 scale。
- 推理时,只需将 INT4 权重乘以对应的 scale 即可恢复近似 FP16 值。
伪代码逻辑
下面以伪码形式总结整体过程:
for each linear layer in model:
W = layer.weight # shape: out_features x in_features
X = calibration_inputs # shape: n x in_features
H = 2 * X.T @ X + lambda * I
L = cholesky(H) # H = L @ L.T
# 按分组处理:每个组包含 group_size 列
for group_start in range(0, in_features, group_size):
group_end = min(group_start + group_size, in_features)
# 在该组内逐列量化
for col in range(group_start, group_end):
w_col = W[:, col]
# 量化当前列(包含 OBS 补偿)
delta = compute_compensation(L, W, col, quantized_weights)
W[:, col] = quantize(w_col + delta, scale)
# 更新 L 的分解以移除该列(懒惰方式)
update_cholesky_for_removed_column(L, col)
replace_layer_with_quantized(W_int, scales)
(实际实现更复杂,但上述流程抓住了核心。)
GPTQ 与其他量化方法对比
| 方法 | 所需数据 | 微调需求 | 量化精度 | 速度 | 典型应用 |
|---|---|---|---|---|---|
| GPTQ | 少量校准 | 无 | INT4/8 | 一次前向 | 大规模 LLM 部署 |
| LLM.int8() | 无 | 无 | INT8 | 推理时混合 | 异常值处理 |
| SmoothQuant | 少量校准 | 可能 | INT8 | 训练后平滑 | 可以保持精度 |
| AWQ | 少量校准 | 无 | INT4 | 一次前向 | 等效或优于 GPTQ |
| QLoRA 等 | 大量 | PEFT 微调 | INT4 | 多次训练 | 微调节约显存 |
GPTQ 的最大优势在于无需重新训练,只需少量校准样本(通常 128 个 2048 token 的序列)即可一次性完成整个模型的量化,非常适合快速部署。
动手实践:用 GPTQ 量化开源模型
得益于 auto-gptq 和 transformers 的集成,量化一个 Hugging Face 模型非常简单。以下示例量化一个 4-bit 的 LLaMA 模型。
环境准备
pip install auto-gptq transformers accelerate datasets
代码示例
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer
# 1. 定义量化配置
quantize_config = BaseQuantizeConfig(
bits=4, # 4-bit 量化
group_size=128, # 分组大小
desc_act=False, # 是否按激活值排序(可提高精度,但速度变慢)
)
# 2. 加载原始模型
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoGPTQForCausalLM.from_pretrained(model_name, quantize_config)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 3. 准备校准数据集(需要代表推理时的文本分布)
# 这里使用 wikitext 的少量样本
from datasets import load_dataset
dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train")
calibration_data = []
for i, example in enumerate(dataset):
if i >= 128:
break
encoded = tokenizer(example["text"], truncation=True, max_length=2048)
calibration_data.append(encoded["input_ids"])
# 4. 执行量化(这个过程可能需要几十分钟)
model.quantize(calibration_data)
# 5. 保存为 GPTQ 格式
model.save_quantized("./llama-2-7b-gptq-4bit")
tokenizer.save_pretrained("./llama-2-7b-gptq-4bit")
加载量化模型进行推理
model = AutoGPTQForCausalLM.from_quantized(
"./llama-2-7b-gptq-4bit",
device="cuda:0",
use_triton=False, # 可选,启用 triton 加速
)
tokenizer = AutoTokenizer.from_pretrained("./llama-2-7b-gptq-4bit")
prompt = "请解释什么是人工智能?"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0]))
GPTQ 的高级技巧
激活排序(desc_act)
GPTQ 论文中还提出了 激活感知排序(Activation-Aware Ordering)。在量化时,不是按列的自然顺序,而是根据输入激活的绝对值总和重新排序权重矩阵的列。这样可以先量化对输出影响较小的列,将误差补偿留给更敏感的列,进一步减小量化误差。启用方式为配置中 desc_act=True,但会略微增加量化时间和推理时的内存占用,需要根据情况权衡。
静态与动态分组
- 静态分组:量化时固定 group_size,推理时查找表简单。
- 动态分组:在某些实现中,scale 可以依赖输入 token 动态计算,但会增加推理延迟。GPTQ 默认使用静态分组,推荐 group_size=128。
支持的数据类型
GPTQ 支持多种整数位宽:2 bit、3 bit、4 bit、8 bit。4-bit 是最常见的平衡点。同时,scale 和 zero-point 也可使用 FP16 或 BF16 存储。
混合精度(EXL2 等)
GPTQ 的可扩展思路被 EXL2 等框架继承,通过混合不同位宽或使用查找表进一步提升性能。GPTQ 本身也可结合 GPTQ-for-LLaMa 的 CUDA 内核实现极快推理。
常见问题(FAQ)
Q: 量化后的模型精度下降严重吗?
A: 对于 4-bit GPTQ(group_size 128),困惑度通常仅上升 0.5~1.0 点,在常见基准(如 LAMBADA、HellaSwag)上准确率下降 1%~2% 以内,基本不影响下游任务。
Q: 校准数据需要多少?用什么数据?
A: 通常 128 个序列就足够。数据需要反映目标领域的文本分布,但如果只是通用场景,WikiText-2 或 C4 的随机样本即可。如果部署在特定领域(如医疗),使用相关语料会更好。
Q: GPTQ 和 Bitsandbytes 的 4-bit 量化有什么不同?
A: Bitsandbytes 采用 LLM.int8() 类似的混合量化,将输入分组动态量化,无需校准,但可能需要更多内存(由于混合精度开销)。GPTQ 是一次性静态量化,推理时内存占用更少,且速度更快。
Q: 能否在量化后用 LoRA 微调?
A: 可以,但需要支持量化基座上的 PEFT 框架,例如 peft + auto-gptq。由于量化权重是固定的,通常只训练额外的低秩适配器,这样可以大幅节省显存。
总结
GPTQ 通过近似二阶信息与顺序补偿策略,提供了一种高效且高精度的大模型权重量化方案。它的“一次量化,永久使用”特性特别适合希望快速将大模型部署到有限硬件环境的开发者。理解其背后的 Hessian 近似与 OBS 补偿机制,不仅有助于调优量化参数,也能帮助你在面对新型量化方法时触类旁通。
现在,你可以动手用 auto-gptq 量化自己下载的模型,体验一下模型体积缩小 4 倍、推理速度提升,同时几乎不损失性能的快感。