量化-03-GPTQ

GPTQ

上文提到,OBQ 的致命痛点在于 “行依赖(Row-wise dependency)”

由于采用了贪心策略,权重矩阵每一行选出的最优量化顺序都不一样,导致 Hessian 逆矩阵 的更新无法在不同输出通道(行)之间共享,复杂度高达

GPTQ (Generative Pre-trained Transformer Quantization) 正是为了解决这个工程灾难。它在 OBQ 的数学基础上做了极其巧妙的近似和系统级优化,将复杂度成功降到了

GPTQ 主要做了以下四大核心优化


优化一:统一量化顺序

OBQ使用贪心选择量化顺序,实验表明这对LLM带来的收益微乎其微。

GPTQ 团队通过实验和理论发现:如果不去贪心地寻找每次误差最小的权重,而是强行规定对所有行使用相同的、固定的顺序(例如从左到右,按列 依次量化),最终的精度损失几乎可以忽略不计。

这一步为什么是破局的关键?

回顾一下 Hessian 矩阵的定义:

你会发现, 完全只依赖于输入激活值 ,和权重 没有任何关系!

因此,对于权重矩阵的每一行,初始的 是完全一模一样的。

在 OBQ 中,因为每行的量化顺序 不同,导致 的更新路径分叉了。

但在 GPTQ 中,既然规定了所有行的量化顺序完全一致(比如先量化第 1 列,再第 2 列),那么所有行在每一步所面临的 矩阵也是完全一致的!

推导结果:

我们只需要在整个层面上,维护和更新唯一一个 矩阵,而不是为 个通道维护 个矩阵。仅这一步,就把 Hessian 更新部分的复杂度从 暴降到了


优化二:Cholesky 分解重构

在固定了顺序之后,我们再来看看 的更新公式:

GPTQ 发现了一个数学等价性:这种每次减去一个由某列外积构成的秩 1 矩阵的操作,在数学上完全等价于对 进行 Cholesky 分解!

如果在开始量化之前,我们对初始的 Hessian 逆矩阵做 Cholesky 分解(因为加入阻尼后它是正定对称矩阵):

具体来说,在第 步量化时,用来更新其他未量化权重的比例向量 ,其实可以直接从提前计算好的 Cholesky 分解结果中读出来,而完全不需要像 OBQ 那样在循环里迭代更新矩阵。这不仅极大减少了计算量,还避免了由于数百次迭代相减带来的累积浮点误差,提升了数值稳定性。


优化三:工程优化:延迟批量更新

虽然数学上的计算量降下来了,但工程实现上还有内存带宽瓶颈

如果按照列 的顺序,每次量化一列,然后立刻去更新右边所有未量化的列,这在 GPU 上是非常低效的向量运算。更好的做法是大规模的矩阵乘法(GEMM)。

GPTQ 提出了 Lazy Batch-Updates 策略。

步骤推导:

  1. 将权重矩阵的列划分成大小为 的块(Block),比如

  2. 块内处理(贪心保留):在当前 Block 内部,依然按照列的顺序逐个量化。

    对于 Block 内的第 列,量化产生误差

    我们利用预处理好的 Cholesky 信息,算出它对应的更新向量

    此时,只更新当前 Block 内部右侧尚未量化的列,而不去碰 Block 外面的庞大权重。

  3. 块间更新(批量计算)

    当这 128 列(一个 Block)全部量化完毕后,我们将这 128 个误差向量拼成一个误差矩阵 ,将对应的 128 个更新向量拼成一个矩阵

    对于 Block 右侧所有极其庞大的剩余权重 ,使用一次密集的矩阵乘法进行全局更新:

通过这种batch更新,将大量零碎的内存读写转化为了一次高效的 GEMM 计算,加快计算速度。


优化四:加入 Hessian 阻尼

在实际的大模型校准中,用来计算 Hessian 矩阵的样本数往往远小于特征维度(比如仅仅用 128 条样本来校准 的权重)。

此时 会是一个半正定矩阵,不满足满秩条件,根本无法求逆。即使有微小的计算误差,也会导致求逆数值爆炸。

GPTQ 引入了一个工程上非常经典的操作:Hessian Damping。

在求逆之前,强制给对角线元素加上一个与对角线均值成比例的常数:

这里 通常取一个很小的值,比如 1%。

这个操作在稍微牺牲了一点点理论最优性的前提下,换来了极佳的数值稳定性,保证了后续 Cholesky 分解和求逆的顺利进行。


GPTQ 最终计算流程总结

我们来串一下,当拿到一个大模型的一层权重 时,GPTQ 是如何跑的:

  1. 收集数据:用少量校准数据通过该层,得到输入激活

  2. 计算并求逆

    计算

    加上阻尼:

    求逆(或直接进行 Cholesky 分解准备更新向量)。

  3. 分块执行量化(Lazy Update Loop)

    将所有的列分成大小为 的 Block。

    对每一个 Block:

    • (a) 在 Block 内部逐列进行量化。
    • (b) 计算该列量化带来的误差 ,并根据 的信息仅更新当前 Block 内部的后续列。
    • (c) 记录下该 Block 的所有更新指令。
    • (d) Block 处理结束后,调用 GPU 执行一次 GEMM 操作,将累积的误差一次性补偿给 中位于该 Block 右侧的所有剩余权重
  4. 结束:全部 Block 遍历完毕,输出量化后的权重

总结

OBQ 是理想主义的推导,它告诉了我们如何补偿误差能做到极限最优,但受困于行依赖导致的 复杂度。

GPTQ 是工程与数学结合的典范,它通过统一量化顺序打破了行依赖,通过 Cholesky 揭示数学本质减少了迭代误差,再通过 Lazy Batch-Updates 迎合了 GPU 的底层硬件特性。

最终,它将复杂度降到了真正的实用级别,这也是为什么如今开源社区(如 AutoGPTQ)几乎全都是基于 GPTQ 变体的核心原因。