大模型量化系列(三):GPTQ — 把大模型权重量化到 3/4 bit
论文: GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers
作者: Elias Frantar, Saleh Ashkboos, Torsten Hoefler, Dan Alistarh
发表: ICLR 2023 | arXiv:2210.17323
一句话总结: GPTQ 证明了二阶补偿式 post-training weight quantization 可以扩展到大模型,让低比特 weight-only 量化从简单取整走向真正可用。
一、为什么 SmoothQuant 之后还需要 GPTQ
LLM.int8() 发现了大模型里的 activation outlier:少数 hidden dimension 会出现稳定且幅度极大的异常值,导致朴素 INT8 量化失效。它的处理方式是把 outlier channel 拆出来,用 FP16 单独计算。
SmoothQuant 则进一步问:能不能不保留 FP16 outlier 分支,而是把 activation 的量化难度迁移到 weight,让主要矩阵乘走更硬件友好的 W8A8 路线?
GPTQ 切入的是另一个问题。
如果 activation 不动,只把 weight 压到 3/4 bit,能不能仍然保持大模型的能力?
这个问题看似简单,但在 GPTQ 之前,答案并不乐观。
二、预备知识:Layer-wise Quantization 与 OBQ
GPTQ 属于 post-training quantization,也就是不重新训练模型,直接把全精度权重压到低比特。理解 GPTQ 之前,最重要的不是先看工程优化,而是先理解它继承的两个思想:layer-wise reconstruction 和 OBQ 的二阶补偿。
1. Layer-wise Quantization
原始权重是
量化后,权重变成
layer-wise quantization 的核心想法是:不直接优化整个模型最终的 loss,而是把模型拆成一层一层处理。对每一层,用少量 calibration data 跑出这一层的输入
写成目标函数就是:
这个式子里,
实际线性层通常一次处理多个 token、多个输出维度,所以输出差异更像一个矩阵。更严谨地写,也可以用 Frobenius 范数:
它表示对所有样本、所有输出维度上的误差做平方求和。
这个目标很重要,因为它改变了我们看待量化误差的方式。我们真正关心的不是每个 weight 自己被改了多少,而是这些 weight 在真实输入
2. 为什么 Hessian 会出现
这里的 Hessian 记作
对线性层的重构目标:
它的 Hessian 主要由输入 activation 的二阶统计量决定。按照本文
论文在介绍 OBQ 时按 row 处理权重,因此记号会写成
它的作用有两个。
第一,衡量某个 weight 量化后会造成多大影响。如果某个方向在
第二,描述不同 weight 之间的耦合关系。某个 weight 被量化后产生的误差,可能可以通过调整其他还没量化的 weight 来抵消。这个“该往哪里补偿”的方向,就是通过 Hessian inverse,也就是
所以在 GPTQ/OBQ 里,
3. RTN:最朴素的量化基线
有了 layer-wise 目标和 Hessian 的概念,再看 RTN 的问题就很清楚了。
RTN,也就是 round-to-nearest,是最直接的 weight quantization 方法。它的思路非常朴素:给定一个量化网格,把每个 weight 独立地映射到最近的量化值。
1 | 原始 weight: 0.37 |
这在 8-bit 时通常还可以。因为 8-bit 有 256 个状态,量化格点比较密,单个 weight 的误差不大,累积起来也未必立刻毁掉模型。
但到 4-bit,只有 16 个状态;到 3-bit,只有 8 个状态。格点变稀疏后,独立取整的问题就暴露出来了:RTN 只关心每个 weight 自己离哪个格点最近,却不关心整个 layer 的输出会发生什么。
换句话说,RTN 并没有显式优化 layer-wise reconstruction 目标,也没有利用 Hessian inverse 去补偿量化误差。某个 weight 被量化后造成的输出偏差,本来可能通过调整其他还没量化的 weight 抵消,但 RTN 完全没有这个过程。
4. OBQ:量化一个,补偿一批
OBQ,也就是 Optimal Brain Quantization,正是用这个 layer-wise 目标来做逐权重量化。它可以看成 GPTQ 的理论起点。
它的基本思路是:当某个 weight 从
直觉上,OBQ 每一步做四件事:
- 从还没量化的 weight 里,选择一个当前最适合量化的 weight。
- 把它映射到最近的量化格点。
- 计算这一步引入的量化误差。
- 根据 Hessian inverse,把误差补偿到剩余未量化的 weight 上。
把这个过程写成公式,会更接近论文里的描述。设
OBQ 会先选择量化代价最小的 weight:
然后对剩余 full-precision weights 做补偿更新:
第一个式子回答“下一步量化谁”:不只看取整误差本身,还要看这个误差在 Hessian 意义下的代价。第二个式子回答“量化之后怎么补”:沿着
所以 OBQ 和 RTN 的区别非常关键:
1 | RTN:每个 weight 独立找最近格点。 |
不过,原始 OBQ 的问题也很明显:它会对每一行 weight 独立做贪心量化,每一步还要维护和更新 Hessian inverse。对 ResNet-50 这种 2500 万参数模型,这类方法还能接受;但对 OPT-175B、BLOOM-176B 这种模型,逐权重贪心和频繁矩阵更新完全不可行。
所以 GPTQ 的核心贡献不是从零发明二阶补偿,而是把 OBQ 这套准确但昂贵的方法,改造成能跑在百亿、千亿参数模型上的算法。
三、GPTQ:把 OBQ 改造成大模型可用
有了前面的铺垫,GPTQ 的核心就很好理解了:它不是重新发明一种量化目标,而是保留 OBQ 的二阶补偿思想,同时把原始 OBQ 中最贵、最不稳定的部分改掉。
第一步是放弃逐权重贪心顺序。OBQ 会为每一行 weight 单独选择“当前最适合量化”的位置,这很精细,但代价太高。GPTQ 观察到,在大模型的大矩阵里,固定一个 column 顺序一起量化,效果和贪心顺序差距不大。这样所有 row 可以共享同一套未量化集合和 Hessian inverse 更新,复杂度立刻降下来。
第二步是做 lazy batch update。GPTQ 不在每量化一列之后就立刻更新后面所有 weight,而是按 block 处理,比如一次处理 128 个 columns。块内先完成递归更新,块处理完之后,再把累积误差统一应用到后面的 columns。这样理论目标没变,但矩阵操作更连续,更适合 GPU。
第三步是用更稳定的 Cholesky reformulation。OBQ/GPTQ 都依赖 Hessian inverse,直接反复更新容易积累数值误差。GPTQ 提前用 Cholesky decomposition 计算量化过程中真正需要的信息,并配合轻微 dampening,让算法在几十亿、上百亿参数模型上更稳定。
所以 GPTQ 的本质可以概括成一句话:
保留 OBQ 的二阶误差补偿,但放弃完整 OBQ 的昂贵贪心过程,把它改造成固定顺序、分块更新、数值稳定的大模型量化算法。
四、实验结果与部署意义
实验部分可以抓住三个结论。
第一,GPTQ 只用很少的 calibration data。论文使用 128 个来自 C4 的随机 2048-token 片段,不使用任务特定数据,仍然可以完成 one-shot quantization。
第二,GPTQ 在 175B 级别模型上仍然可运行。OPT-175B 和 BLOOM-176B 的量化都能在几小时内完成,这说明它不是只能用于小模型的精细 PTQ 方法。
第三,精度上 GPTQ 明显优于 RTN。4-bit GPTQ 基本接近 FP16;3-bit 虽然有损失,但仍然保持可用。相反,RTN 在 3-bit 下会直接崩溃,perplexity 会大幅恶化。
部署意义也来自这里:weight-only 低比特量化主要减少权重读取和显存占用。论文中 3-bit OPT-175B 可以放进一张 80GB A100,而 FP16 需要多张 GPU。decode 阶段往往受显存带宽限制,因此少读权重也能带来实际 latency 改善。
五、个人评价:真正的价值不是 4-bit,而是可扩展
GPTQ 的价值在于取舍:它没有追求完整 OBQ 的每一步最优,而是保留最关键的二阶补偿,把剩下的部分改造成 GPU 友好、数值稳定、可以在大模型上跑完的流程。
这也是为什么 GPTQ 到今天仍然有意义。很多后续系统不一定原封不动使用论文里的实现,但它留下了一个清晰判断:weight-only 低比特量化不能只做局部取整,必须考虑量化误差如何影响 layer output。