跳转至

GPTQ 从零推导:4-bit 权重量化为什么能“量化一列,补偿一列”

⏱ 约 102 分钟

本文整理于2026年08月07日晚上十点

前言

最近在学习大模型相关的量化算法,从LLM.int8()ZeroQuant、再到SmoothQuant,一路追到GPTQ,在GPTQ的推导上花了小一周的晚上时间,写下此文记录一下,方便后续也对此感兴趣的读者。

本文只需要读者学过矩阵乘法、逆矩阵、正定矩阵等线性代数基础知识。我们将从“一个浮点数怎样变成 4-bit”开始,完整推到 GPTQ 的 blockwise 伪代码;关键计算均标出输入、输出 shape 和含义。 本文聚焦经典 W4A16 GPTQ:权重保存为 4-bit,计算时激活仍用 FP16/BF16。真实 Linear 层若含偏置,本教程假设偏置保持不变,因此它在量化前后的输出差中抵消。

一、读前准备:一个数怎样变成 4-bit

1.1. 4-bit 存的不是任意小数,而是 16 个编号之一

4 个二进制位最多表示 \(2^4=16\) 个不同编号。本文采用无符号码

\[ c\in\{0,1,\ldots,15\}. \]

给定正的量化步长(scale)\(\Delta_q>0\) 和整数零点(zero point)\(z_q\),常见的仿射量化写成

\[ \begin{aligned} &c=\operatorname{clip}\!\left( \operatorname{round}\!\left(\frac{w}{\Delta_q}\right)+z_q,\ 0,\ 15 \right),\\ &q=\mathcal Q(w)=\Delta_q(c-z_q). \end{aligned} \]

这两行分别做两件事:

  1. 编码:把浮点权重 \(w\) 映射为整数码 \(c\);模型文件中真正打包保存的是 \(c\)
  2. 反量化:把 \(c\) 对应回量化网格上的浮点值 \(q\);GPTQ 做误差补偿时使用的是 \(q\)

\(\Delta_q=0.25,z_q=8,w=0.62\) 为例:

\[ \frac{0.62}{0.25}=2.48 \xrightarrow{\operatorname{round}}2 \xrightarrow{+z_q}c=10 \xrightarrow{\text{反量化}}q=0.25(10-8)=0.50. \]
步骤 输入 输出 含义
除以 scale \(w=0.62,\Delta_q=0.25\),均为标量 \(2.48\),标量 用“网格间隔”为单位表示权重
round 并加零点 标量 \(2.48,z_q=8\) \(c=10\),4-bit 整数码 选择最近的合法编号
clip 整数码 \(c\in[0,15]\) 超出范围时饱和到端点
反量化 \(c=10,\Delta_q=0.25,z_q=8\) \(q=0.50\),标量 该编号在实数轴上代表的值

\(\Delta_q\) 决定相邻网格点的距离;\(z_q\) 让整数码 \(z_q\) 精确代表实数 \(0\)clip 表示超出可表示范围的数只能落在最左或最右端点。若一组权重范围约为 \([w_{\min},w_{\max}]\),一种粗略的 min-max 拟合是

\[ \Delta_q\approx\frac{w_{\max}-w_{\min}}{15},\qquad z_q\approx\operatorname{clip}\!\left(\operatorname{round}\!\left(-\frac{w_{\min}}{\Delta_q}\right),0,15\right). \]

真实量化器还会处理对称网格、异常值和常数区间。本文把“怎样拟合网格”和“怎样用二阶信息补偿舍入误差”分开:推导补偿时,当前权重可用的网格视为已经确定。

1.2. PTQ、W4A16 和校准数据分别是什么

术语 小白版解释
PTQ(post-training quantization) 模型训练完成后再量化,不重新训练整个模型
W4A16 权重用 4-bit 码保存;输入激活在计算时仍是 FP16/BF16
校准数据 少量有代表性的真实输入,只用来观察各层收到什么向量,不需要标签,也不更新权重
激活 一条输入运行到某层时,该层实际收到的数值向量;它不是 token ID
反量化权重 4-bit 码按 scale/zero 还原出的网格值 \(q\);它通常不等于原权重 \(w\)

普通 Linear 层把输入向量 \(x\in\mathbb R^d\) 映射为

\[ y=Wx+b_{\mathrm{bias}},\qquad W\in\mathbb R^{m\times d},\quad y,b_{\mathrm{bias}}\in\mathbb R^m. \]

运行几条代表性文本,记录到达这一层的 \(N\) 个输入向量,并把它们并排放成 \(X\in\mathbb R^{d\times N}\)。偏置 \(b_{\mathrm{bias}}\) 不变时,原层与量化层的输出差是

\[ (\hat W X+b_{\mathrm{bias}}\mathbf 1^\top) -(WX+b_{\mathrm{bias}}\mathbf 1^\top) =(\hat W-W)X. \]

所以后文只写 \(Y=WX\)。GPTQ 的问题不是“怎样训练 \(W\)”,而是“把 \(W\) 换成只能落在 16 级网格上的 \(\hat W\) 后,怎样让这批输入看到的输出尽量不变”。

1.3. 为什么公式里没有量化 \(X\)

因为本文推导的是经典 W4A16 GPTQ,而不是 W4A8 或 W4A4:W4 表示权重被压到 4-bit,A16 表示激活仍用 FP16/BF16,不再额外投影到 INT8/INT4 网格。数学上把捕获到的这些 16-bit 数值记作 \(X\in\mathbb R^{d\times N}\);写 \(\mathbb R\) 只是为了使用矩阵代数,并不表示计算机真的用“无限精度实数”。工程上常把 \(X\) 以 FP16/BF16 捕获,再用 FP32 累积 \(XX^\top\)

1.4. GPTQ 工作脉络

先用四句话概括整篇文章:

  1. 量化当前工作权重,得到最近网格点 \(q\)
  2. 计算被舍掉的残差 \(\rho=w_{\mathrm{work}}-q\)
  3. 已量化坐标不再动,让尚未量化的坐标根据校准输入共同吸收这份残差。
  4. 冻结当前坐标,继续处理下一列;所有输出行可以并行做同一件事。

把一个线性层写成

\[ Y=WX. \]

GPTQ 的出发点不是让每个 \(W_{rj}\) 单独离原值最近,而是让层输出的变化最小:

\[ \min_{\hat W}\;\frac1N\lVert WX-\hat W X\rVert_F^2, \qquad \text{s.t. }\hat W_{rj}\in\mathcal G_{r,\operatorname{grp}(j)}. \tag{1} \]

这里 \(\mathcal G_{r,\operatorname{grp}(j)}\) 是第 \(r\) 个输出行、列 \(j\) 所属量化组可用的标量网格,\(\hat W\) 是反量化后的近似权重。\(\|\cdot\|_F^2\) 表示把矩阵中每个元素的平方加起来,\(1/N\) 表示再按校准位置数取平均。

若直接枚举 \(md\) 个权重各自的 16 种选择,需要比较 \(16^{md}\) 个矩阵,实际不可行。GPTQ 因此按输入特征列向前推进,每一步同时处理 \(W_{:,j}\) 中的 \(m\) 个权重。“量化一列”并不是让整列共用一个量化值:每个权重仍按自己所在输出行和量化组的网格分别舍入。某个网格点一旦选定,其他尚未量化的浮点权重应该怎样移动,就有一个精确公式。所以最终结果很聪明,但不保证是式 (1) 所有可能 INT4 矩阵中的全局最优解。

下面先固定一个输出行 \(r\),只研究这一行的权重;其他输出行做完全相同的计算,工程上可以并行执行。

先把“自由权重”翻译成人话:当前正在处理的权重,以及后面还没有量化的权重,暂时都能移动;已经量化完成的权重则不许再动。把仍能移动的坐标集合记为 \(\mathcal F\),其中共有 \(k\) 个权重,并把这些位置临时编号为 \(0,\ldots,k-1\)

现在处理 \(\mathcal F\) 中局部编号为 \(p\) 的权重。它的当前工作值是 \(w_{\mathrm{work},p}\),最近的量化网格点是 \(q_p\)

\[ \text{舍入差额:}\qquad \rho_p=w_{\mathrm{work},p}-q_p, \qquad q_p=\mathcal Q(w_{\mathrm{work},p}). \]

例如 \(w_{\mathrm{work},p}=0.62\)\(q_p=0.50\),便有 \(\rho_p=0.12\)。为了让当前权重恰好落到 \(0.50\),它自己必须移动

\[ \delta_p=q_p-w_{\mathrm{work},p}=-\rho_p=-0.12. \]

问题只剩下:其他 \(k-1\) 个权重各自应该移动多少,才能尽量抵消这次舍入对层输出的影响?

现在先把 \(G_F\in\mathbb R^{k\times k}\) 当作一张由校准输入 \(X\) 算出的 \(k\times k\) 数字表。取它的第 \(p\) 列,再除以第 \(p\) 个对角数,就得到各权重随当前权重一起移动的比例:

\[ \text{联动比例} =\frac{(G_F)_{:,p}}{[G_F]_{pp}} \in\mathbb R^{k}. \]

后文会证明,这张表是阻尼后代价矩阵的逆;在这里先把它看成一张补偿路线图即可。

完整更新为

\[ \delta^*=-\frac{\rho_p}{[G_F]_{pp}}(G_F)_{:,p}. \tag{2} \]

加入阻尼后 \(G_F\) 是正定矩阵,因此 \([G_F]_{pp}>0\),这里不会除以 0。

这条式子可以分三步读:

计算 输入 shape 输出 shape 小白版含义
取第 \(p\)\((G_F)_{:,p}\) \(G_F:[k,k]\) \([k,1]\) 取出所有自由权重与当前权重的关系
除以 \([G_F]_{pp}\) \([k,1]\) 除以标量 \([k,1]\) 把当前坐标自己的比例归一化为 1
乘以 \(-\rho_p\) 标量乘 \([k,1]\) \(\delta^*:[k,1]\) 让当前权重移动正确距离,其余权重按比例补偿

为什么它一定能把当前权重送到 \(q_p\)?只看输出向量的第 \(p\) 个元素:

\[ [\delta^*]_p =-\frac{\rho_p}{[G_F]_{pp}}[G_F]_{pp} =-\rho_p =q_p-w_{\mathrm{work},p}. \]

分母和同一列的第 \(p\) 个元素正好约掉了。其他任意坐标 \(i\ne p\) 则移动

\[ [\delta^*]_i =-\rho_p\frac{[G_F]_{ip}}{[G_F]_{pp}}, \]

移动多少由它与第 \(p\) 个坐标的联动关系决定。例如 \(k=2\),当前处理第一个坐标,即 \(p=0\);若

\[ \rho_p=0.12,\qquad (G_F)_{:,p}= \begin{bmatrix}1\\-1/2\end{bmatrix}, \qquad [G_F]_{pp}=1, \]

那么

\[ \delta^* =-0.12 \begin{bmatrix}1\\-1/2\end{bmatrix} = \begin{bmatrix}-0.12\\+0.06\end{bmatrix}. \]

第一个数把当前权重从 \(0.62\) 减到 \(0.50\);第二个数让另一个尚未量化的权重增加 \(0.06\),主动接住一部分输出误差。

这样补偿以后,这一步无法再降低的最小代价为

\[ \Delta\ell_{\lambda,p}^*=\frac{\rho_p^2}{2[G_F]_{pp}}. \tag{3} \]

先把符号逐个翻译:

  • \(\Delta\) 表示“这一步新增加的”;
  • \(\ell\) 表示我们正在尽量压低的输出误差代价;
  • \(\lambda\) 表示为了数值稳定而加入的阻尼;
  • 星号 \(*\) 表示其他自由权重已经按最佳方式补偿后得到的最小值。

现在可以先把式 (3) 当成一个“这次舍入有多难补救”的分数:

  1. 分子是 \(\rho_p^2\):舍入距离越大,代价越高;距离变成 2 倍,平方代价会变成 4 倍。
  2. 分母里的 \([G_F]_{pp}\) 越大,表示把当前权重移动同样距离后,能够达到的最小代价越低。原因可能是这个输入方向本身不敏感,也可能是其他自由权重容易补偿。

无阻尼时,式 (3) 就是按 token 平均的单行层重建损失增量;有阻尼时,它是带稳定项的代理代价。式 (2)、(3) 在这里只是先建立直觉,后文会先用二维配方法,再用拉格朗日乘子把每一步完整推出。

这里先把存储量算清楚。只计算裸权重、忽略 scale/zero 等元数据时:

存储格式 每参数位数 70B 参数的理想存储量 相对 FP16 的权重压缩
FP32 32 bit 280 GB 0.5x
FP16 / BF16 16 bit 140 GB 1x
INT8 8 bit 70 GB 2x
INT4 4 bit 35 GB 4x

所以 INT4 是相对 FP32 的 \(8\times\) 压缩、相对 FP16 的 \(4\times\) 压缩。实际模型还要存储分组 scale、zero point、层元数据,打包和对齐也会带来额外开销,因此不能把 \(35\,\text{GB}\) 当作最终显存占用。

难点在于量化误差不是孤立地发生在一个标量上。若只做 round-to-nearest(RTN),定义

\[ \Delta W = \hat W - W, \]

则层输出误差是

\[ \hat Y-Y = (\hat W-W)X = \Delta W X. \tag{4} \]

同样大小的 \(\Delta W_{ij}\),在不同的输入方向上,可能几乎没有影响,也可能被 \(X\) 放大。更关键的是:多个权重误差会在输出中相加、抵消或放大。只最小化每个元素的 \(|\Delta W_{ij}|\),并不等于最小化 \(\lVert \Delta W X\rVert_F\)

这正是 GPTQ 的入口:校准输入 \(X\) 决定什么方向的误差昂贵,二阶信息决定剩余权重怎样共同吸收误差。

1.5. 统一记号与 shape 约定

本文固定一个 Linear 层。为了让矩阵乘法不来回转置,使用下列约定:

符号 shape 含义
\(d\) 标量 输入特征维度,\(d=d_{\mathrm{in}}\)
\(m\) 标量 输出特征维度,\(m=d_{\mathrm{out}}\)
\(n_b\) 标量 校准序列总数;它是 \(A\) 的第一维,不是 DataLoader 跑了多少批
\(T\) 标量 为简化记号,假设每条校准序列保留的 token 数相同
\(N\) 标量 有效 token 位置总数;定长无 padding 时 \(N=n_bT\)
\(W\) \([m,d]\) 浮点权重矩阵
\(W_{\mathrm{work}}\) \([m,d]\) 量化过程中的浮点工作副本;未处理列会不断接收补偿
\(\hat W\) \([m,d]\) 反量化后的近似权重矩阵
\(K\) \([m,d]\) 低比特整数码,4-bit 时每元素属于 16 个码之一
\(A\) \([n_b,T,d]\) 框架在该层捕获的原始输入激活
\(X\) \([d,N]\) \(A\) 展平 token 维后再转置的校准矩阵
\(Y=WX\) \([m,N]\) 原始层在校准输入上的输出
\(H\) \([d,d]\) 单个输出行对应的 Hessian
\(G=H_\lambda^{-1}\) \([d,d]\) 阻尼后 Hessian 的逆;它描述可补偿性
\(R\) \([d,d]\) \(G=R^\top R\) 中的上三角 Cholesky 因子
\(b\) 标量 GPTQ 的列 block 大小,论文默认 \(128\)
\(g\) 标量 量化 group size;\(g=-1\)\(g=d\) 表示整行共用网格
\(n_g\) 标量 每行的 group 数;\(g=-1\) 时为 \(1\),否则为 \(\lceil d/g\rceil\)
\(\Delta_q\) 单个行/组内为标量 量化网格步长(scale);下标 \(q\) 用来和损失增量 \(\Delta\ell\) 区分
\(q=\mathcal Q(w_{\mathrm{work}})\) 与输入相同 当前量化器返回的反量化网格值
\(\rho=w_{\mathrm{work}}-q\) 与输入相同 全文统一使用的“被量化舍掉的残差”

1.6. 单行推导与实现列的区别

矩阵 \(W\) 的第 \(r\) 行是一个长度为 \(d\) 的行向量 \(W_{r,:}\)。在推导单行时,为了使用标准二次型,把它转写为列向量:

\[ w_r := W_{r,:}^\top \in \mathbb R^d. \]

完整参数空间中的 \(w\)\(\delta\)\([d,1]\) 列向量;若只看当前 \(k\) 个自由坐标,它们的自由部分则是 \([k,1]\)。标准基写成粗体 \(\mathbf e_p\),同样随所在空间取 \([d,1]\)\([k,1]\)。实现中一次处理所有输出行时,W_work[:, j] 又是长度为 \(m\) 的列。这两种“列”来自不同维度:

记号 shape 它沿着哪一维
单行参数 \(w_r\) \([d]\) 一个输出神经元对所有输入特征的权重
实现中的 W_work[:, j] \([m]\) 一个输入特征连接到所有输出神经元的当前工作权重

GPTQ 恰恰利用了这一点:数学上每个 \(w_r\) 独立,工程上所有行能同时处理同一个输入特征列 \(j\)

本文的特征列与伪代码统一使用 0-based 索引\(j=0,1,\ldots,d-1\)。例如长度为 4 的向量中,“第二个坐标”是 \(p=1\),对应

\[ \mathbf e_1=\begin{bmatrix}0&1&0&0\end{bmatrix}^\top. \]

二、从层输出重建得到 Hessian

2.1. 把校准激活整理成 \(X\)

Transformer 中某个 Linear 层的输入通常由框架给成:

\[ A \in \mathbb R^{n_b\times T\times d}. \]

把前两维合并,并转置:

\[ X=\operatorname{reshape}(A,[N,d])^\top \in \mathbb R^{d\times N}. \tag{5} \]
计算 输入 shape 输出 shape 含义
捕获激活 \(A:[n_b,T,d]\) 同左 多个 batch、多个 token 的层输入
合并 token 维 \([n_b,T,d]\) \([N,d]\) 每一行变成一个 token 位置
转置 \([N,d]\) \(X:[d,N]\) 每一列变成一个 token 位置,适配 \(WX\)

下图把从校准输入到 INT4 权重的全链路串起来。之后的每个公式都会落在图中的某一个节点。

一层 Linear 的 GPTQ 数据流

图 1:单层 GPTQ 的数据流。绿色是张量输入/产物,蓝色是统计与变形,橙色是参数和阻尼,紫色是二阶量化计算。

2.2. 层重建目标

原始输出与量化后输出分别是:

\[ Y=WX\in\mathbb R^{m\times N},\qquad \hat Y=\hat W X\in\mathbb R^{m\times N}. \]

使用“按 token 平均、对输出通道求和”的平方重建误差:

\[ \mathcal L(\hat W) = \frac1N\lVert WX-\hat W X\rVert_F^2 = \frac1N\lVert (W-\hat W)X\rVert_F^2. \tag{6} \]

Frobenius 范数只是逐元素平方和:若 \(M\in\mathbb R^{m\times N}\),则

\[ \|M\|_F^2=\sum_{r=0}^{m-1}\sum_{t=0}^{N-1}M_{rt}^2. \]

因此式 (6) 可以读成:“把每个输出通道、每个校准位置的输出差平方后全部相加,再除以位置数 \(N\),寻找使它最小的量化权重。”它不是再除以 \(m\) 的逐元素 MSE。原论文常写不带 \(1/N\) 的版本;两者只差一个正的常数,若阻尼也按同一尺度调整,最优补偿方向不变。

计算 输入 shape 输出 shape 含义
\(WX\) \([m,d]\cdot[d,N]\) \([m,N]\) 原始层的校准输出
\(\hat W X\) \([m,d]\cdot[d,N]\) \([m,N]\) 量化层的校准输出
差与 Frobenius 范数 \([m,N]\) 标量 所有输出通道、所有 token 位置的总误差

2.3. 为什么问题能拆成 \(m\) 个独立的行问题

\(\Delta W=\hat W-W\)。把 \(\Delta W\) 的第 \(r\) 行转为列向量 \(\delta w_r\in\mathbb R^d\),则

\[ \begin{aligned} \mathcal L &=\frac1N\lVert \Delta W X\rVert_F^2\\ &=\frac1N\sum_{r=0}^{m-1}\lVert \delta w_r^\top X\rVert_2^2\\ &=\sum_{r=0}^{m-1}\delta w_r^\top\underbrace{\left(\frac1NXX^\top\right)}_{[d,d]}\delta w_r. \end{aligned} \tag{7} \]

这一步非常重要:不同输出行之间没有交叉项。因此可以把每一行当成一个独立的长度 \(d\) 参数向量来优化;但它们共享同一个 \(XX^\top\),因为所有行看的是同一批输入 \(X\)

也可以用迹写成:

\[ \mathcal L=\frac1N\operatorname{tr}\left(\Delta WXX^\top\Delta W^\top\right). \]

这只是在压缩记号,实际含义仍然是式 (7) 的逐行求和。

2.4. Hessian 从哪里来

固定某一个输出行,省略行下标。令当前权重变化为 \(\delta\in\mathbb R^d\),则该行的重建损失是

\[ \ell(\delta)=\frac1N\lVert \delta^\top X\rVert_2^2 =\frac1N\delta^\top XX^\top\delta. \tag{8} \]

如果熟悉微积分,可以对 \(\delta\) 求一阶、二阶导:

\[ \nabla_\delta\ell(\delta)=\frac2NXX^\top\delta, \qquad H=\nabla^2_\delta\ell(\delta)=\frac2NXX^\top. \tag{9} \]
计算 输入 shape 输出 shape 含义
\(XX^\top\) \([d,N]\cdot[N,d]\) \([d,d]\) 输入特征两两共同出现的统计量
\(H=2XX^\top/N\) \([d,d]\) \([d,d]\) 改动一行权重时的精确曲率矩阵
\(v^\top Hv\) \([1,d]\cdot[d,d]\cdot[d,1]\) 标量 沿方向 \(v\) 改权重的二阶代价

注意两个常被混淆的事实。

  1. 对式 (8) 这个层重建目标\(H=2XX^\top/N\) 是精确 Hessian,不是近似,也不需要假设原模型训练到了极小点。因为式 (8) 本身就是二次函数。
  2. 在整个网络的原始训练 loss 上谈 Hessian/Fisher 时,事情会复杂得多。GPTQ 在这里没有直接求那个巨大网络 loss 的 Hessian,而是求这个局部重建问题的 Hessian。

不熟悉多元微积分也不影响后面的阅读:式 (8) 已经直接告诉我们,误差由矩阵 \(XX^\top\) 构成的二次型衡量。“Hessian”在本文里只是这个二次型前面的矩阵名称。

\(\delta=0\) 处,\(\nabla\ell(0)=0\) 恒等成立,因为目标 \(WX\) 正是由原始 \(W\) 构造出来的。于是

\[ \ell(\delta)=\frac12\delta^\top H\delta \tag{10} \]

不仅是 Taylor 二阶近似,而是精确等式。

2.5. 本文只需要的优化工具箱

后面只会反复使用三个线性代数事实:

  1. 若对任意非零 \(v\) 都有 \(v^\top Mv>0\),就称对称矩阵 \(M\) 正定,记作 \(M\succ0\)
  2. \(M\succ0\) 时,\(\frac12x^\top Mx\) 像一个只有一个谷底的碗;在仿射约束下,它也只有一个最小点。
  3. 配方法给出更强的证明。若能写成下面的形式,则第二项对所有 \(x\) 都不小于 0,并且只有 \(x=x^*\) 时为 0,所以 \(x^*\) 是唯一全局最小值:

    \[ f(x)=f(x^*)+\frac12(x-x^*)^\top M(x-x^*), \]
  4. 正定矩阵的主子矩阵及其逆仍然正定。主子矩阵的证明只需把它的任意非零向量 \(u\) 在未选坐标上补 0,得到 \(v\ne0\),于是 \(u^\top M_{FF}u=v^\top Mv>0\)。对逆矩阵,令 \(z=M^{-1}y\ne0\),便有 \(y^\top M^{-1}y=z^\top Mz>0\)

拉格朗日乘子只是把“找满足约束的谷底”压缩成一个线性方程组。核心推导会先用配方法把最小值直接看出来,再给出拉格朗日写法作为核对;读者不需要预先学过优化课程。

2.6. \(H\) 的直觉:校准数据在给误差方向标价

对任意方向 \(v\in\mathbb R^d\)

\[ v^\top Hv=\frac2N\lVert v^\top X\rVert_2^2. \tag{11} \]

如果校准输入经常沿着 \(v\) 方向有很大分量,那么沿 \(v\) 改权重会显著改变输出,代价就高;若 \(v^\top X\) 很小,那个方向对校准集几乎不可见,代价就低。

现实中 \(X\) 常常列数不足、特征相关或秩亏,因此 \(H\) 可能不可逆。GPTQ 使用阻尼:

\[ H_\lambda=H+\lambda I_d,\qquad \lambda>0. \tag{12} \]

为什么加上任意 \(\lambda>0\) 就能保证正定?对任意非零 \(v\)

\[ \begin{aligned} v^\top H_\lambda v &=v^\top Hv+\lambda v^\top v\\ &=\frac2N\|v^\top X\|_2^2+\lambda\|v\|_2^2\\ &>0. \end{aligned} \]

第一项可能因为数据看不见方向 \(v\) 而等于 0,但第二项一定严格大于 0。直觉上,阻尼是在所有方向上加一点“基础代价”,既让矩阵可逆,也避免后续除以极小数。

从这里开始要区分两件事。若 \(\lambda=0\)\(H\) 可逆,后文的二次型就是式 (6) 的精确层重建损失;实际 GPTQ 通常以 \(H_\lambda\) 做补偿,每一步连续更新精确最小化相应的阻尼条件子问题:

\[ \ell_\lambda(\delta) =\frac12\delta^\top H_\lambda\delta =\ell(\delta)+\frac\lambda2\lVert\delta\rVert_2^2. \]

这不会改变“往哪个方向补偿”的基本机制,却会改变数值损失和补偿大小。离散落点仍按既定顺序贪心选择,完整 GPTQ 不保证全局最小化 \(\ell_\lambda\)。最终模型好不好,仍要回到裸的层重建误差或语言模型困惑度上验证。

三、标准 GPTQ 的全貌

这一章只负责建立全局印象,不在推导前塞入实现细节。读者先知道标准 W4A16 GPTQ 大致按什么顺序处理一个 Linear 层,接下来的章节再逐一回答“为什么这样补偿”和“代码怎样高效实现”。

先看一行总流程:

校准文本 \(\longrightarrow\) 捕获当前层输入 \(X\) \(\longrightarrow\) 配置量化器与网格策略 \(\longrightarrow\) 构造 \(H\) 并处理死特征 \(\longrightarrow\) 排列处理顺序并加入阻尼 \(\longrightarrow\) 预计算补偿表 \(R\) \(\longrightarrow\) 逐列“拟合或选择网格、舍入、算残差、补偿” \(\longrightarrow\) 每个 block 合并写回 \(\longrightarrow\) 反置换 \(\longrightarrow\) 按部署格式打包。

这里先知道“需要选择一种网格策略”即可;整行网格、static groups 和 dynamic groups 的具体拟合时机属于工程细节,放到后文实现章节再区分。

3.1. 输入与最终输出

对一个 Linear 层,GPTQ 接收:

输入 shape 含义
浮点权重 \(W\) \([m,d]\) 要量化的原始 Linear 权重
校准激活 \(X\) \([d,N]\) \(N\) 个代表性 token 到达这一层时的输入
bit 数 标量 本文固定为 4,因此每个权重有 16 个整数码可选
group size \(g\) 标量 多少个连续权重共享一套 scale/zero;它会改变量化网格
block size \(b\) 标量 多少列攒在一起做一次块外矩阵更新;固定顺序和网格时只改变计算组织
阻尼比例 标量 给 Hessian 加多少数值稳定项

其中,\(d\) 是每个 token 输入向量的长度;\(N\) 是进入该层的全部有效 校准 token 数,不是 batch size。比如 3 条文本去掉 padding 后分别有 \(4,2,3\) 个 token,则 \(N=9\)\(X:[d,9]\),而 \(X_{:,t}:[d,1]\) 就是第 \(t\) 个 token 到达该层时的输入向量。

要把“GPTQ 算法的结果”和“部署文件中的存法”分开看:

阶段 输出 shape 含义
GPTQ 数学核心 反量化权重 \(Q=\hat W\) \([m,d]\) 经典算法直接构造的量化近似;每个元素已经落在某个 4-bit 网格点上
量化器/部署工具链 逻辑整数码 \(K\) \([m,d]\) 每个位置是 \(0,\ldots,15\) 中的一个码,此时还没有按字节或机器字压紧
量化器/部署工具链 scale、zero 通常为 \([m,n_g]\) 把整数码还原成网格值所需的元数据
dynamic groups 与 act-order 联用时 group_id \([d]\) 告诉部署端每个原始输入列应使用哪一组 scale/zero
打包之后 packed codes 由 kernel 布局决定 理论最低存储量约为 \(md/2\) byte;实际常按 int32、对齐和分块布局保存

3.2. 标准 GPTQ 的十步

这一小节只给读者一张路线图,暂时不证明公式:

  1. 第 1 步:准备少量有代表性的无标签校准文本,让浮点模型正常前向运行;
  2. 第 2 步:捕获这些文本的每个有效 token 到达当前 Linear 层时的输入,并按列整理成 \(X:[d,N]\)
  3. 第 3 步:配置 4-bit 量化器,确定整行或分组共享 scale/zero 的网格策略;
  4. 第 4 步:由校准输入计算 \(H=2XX^\top/N:[d,d]\),得到输入特征之间的二阶关系;
  5. 第 5 步:处理校准集中从未出现的死特征,并按需要加入阻尼和确定列处理顺序;
  6. 第 6 步:由阻尼 Hessian 预计算逆 Hessian \(G\) 及其 Cholesky 因子 \(R\),把后续每一列所需的补偿系数提前准备好;
  7. 第 7 步:建立浮点工作副本 \(W_{\mathrm{work}}:[m,d]\) 和保存最终量化结果的缓冲区;
  8. 第 8 步是 GPTQ 的核心:算法按既定顺序取出当前工作列 \(w_j=W_{\mathrm{work}}[:,j]:[m]\),注意这里读的是已经接收过前面误差补偿的工作值,而不是原始 \(W[:,j]\);量化器让这一列的每个输出行分别落到自己的 4-bit 网格点,得到整数码 \(c_j:[m]\) 和反量化值 \(q_j:[m]\);随后计算本列舍弃的残差 \(\rho_j=w_j-q_j:[m]\),再依据 \(G\)(高效实现中使用 \(R\))描述的输入特征关系,把这份残差分配给尚未量化的列,使当前列精确变成 \(q_j\),而后续列的工作值相应改变;最后冻结当前列,再从补偿后的下一列继续重复,所以 GPTQ 与普通逐元素舍入最关键的区别就是“量化当前列以后,让未量化列主动接住误差”。接下来的核心推导正是要证明:在当前量化点已经选定时,这种补偿为什么是二次重建目标下的最优补偿;
  9. 第 9 步:每完成一个 block,就把此前暂存的块外补偿合并成一次矩阵乘法写回尾部;
  10. 第 10 步:所有列处理完后恢复原列顺序,保存或打包 4-bit 码及 scale/zero,验证层重建误差,再继续量化下一层。

3.3. 放大第 8 步

十步流程中,第 1--7 步都在准备数据、量化网格和补偿系数,第 9 步只是把许多补偿合并成更快的矩阵乘法,第 10 步负责整理输出;真正决定 GPTQ 数学结构的是第 8 步反复执行的列循环,它可以压缩成一句话:

读当前工作列 \(\rightarrow\) 落到 4-bit 网格 \(\rightarrow\) 计算舍入残差 \(\rightarrow\)\(R\) 把残差传给尚未量化的列 \(\rightarrow\) 冻结当前列。

接下来的“核心推导”暂时不讨论 group、block 和代码,只研究第 8 步中的一个问题:当前列已经选定量化点后,为什么按逆 Hessian 的一列给其余权重分配补偿,恰好能使层输出重建代价最小? 得到这个数学答案以后,后文再分别解释逆 Hessian 更新、Cholesky 预计算和 lazy block update 怎样实现同一件事。

四、核心推导:固定一个坐标时怎样得到最优补偿

4.1. 先看第一次补偿到底做了什么

\[ w_{\mathrm{work}}= \begin{bmatrix}0.62\\0.62\end{bmatrix},\qquad G= \begin{bmatrix}1&-1/2\\-1/2&1\end{bmatrix}. \]

人为指定先量化第 0 个坐标。它从 \(0.62\) 落到 \(q_0=0.50\),所以 \(\rho_0=0.62-0.50=0.12\)。完整更新是

\[ -\frac{\rho_0}{G_{00}}G_{:,0} =-0.12 \begin{bmatrix}1\\-1/2\end{bmatrix} =\begin{bmatrix}-0.12\\+0.06\end{bmatrix}. \]

结果为

\[ w_{\mathrm{work}}^{\mathrm{new}} =\begin{bmatrix}0.50\\0.68\end{bmatrix}. \]

第一项 \(-0.12\) 把当前坐标锁到网格点;第二项 \(+0.06\) 才是其他坐标接住的补偿。后续量化必须处理工作值 \(0.68\),不能回头再量化原值 \(0.62\)。这里的第 0 列顺序是人为指定的,不是公式推导出来的。

4.2. 当前子问题:只固定一个坐标,其余都能动

设仍自由的坐标集合为 \(\mathcal F\),大小为 \(k=|\mathcal F|\)。这里的“坐标”就是当前输出行中权重所在的全局列号,不是权重的数值。

先看一个具体例子。假设这一行共有 \(d=5\) 个权重,当前处理状态如下:

全局列号 权重 当前状态 是否属于 \(\mathcal F\) 在自由子问题中的局部编号
0 \(w_0\) 已量化并冻结
1 \(w_1\) 已量化并冻结
2 \(w_2\) 本轮正要量化 0
3 \(w_3\) 尚未量化,可以补偿 1
4 \(w_4\) 尚未量化,可以补偿 2

因此

\[ \mathcal F=\{2,3,4\},\qquad k=|\mathcal F|=3. \]

只取这些仍能移动的位置,工作权重和本轮更新分别是

\[ w_{\mathrm{work},\mathcal F} = \begin{bmatrix} w_{\mathrm{work},2}\\ w_{\mathrm{work},3}\\ w_{\mathrm{work},4} \end{bmatrix} :[3,1], \qquad \delta_{\mathcal F} = \begin{bmatrix} \Delta w_2\\ \Delta w_3\\ \Delta w_4 \end{bmatrix} :[3,1]. \]

这里 \(\Delta w_2\) 表示“全局第 2 列的更新量”,其余两个同理。把自由部分取出以后,后面的通用证明会把 \(\delta_{\mathcal F}\) 简写成 \(\delta\),并按自由集合内部重新编号为 \(0,1,2\)

完整的 \(H_\lambda\)\([5,5]\),而自由子问题只保留全局行、列 \(2,3,4\)

\[ H_F =(H_\lambda)_{\mathcal F,\mathcal F} = \begin{bmatrix} (H_\lambda)_{22}&(H_\lambda)_{23}&(H_\lambda)_{24}\\ (H_\lambda)_{32}&(H_\lambda)_{33}&(H_\lambda)_{34}\\ (H_\lambda)_{42}&(H_\lambda)_{43}&(H_\lambda)_{44} \end{bmatrix} :[3,3]. \]

这一轮处理的是全局第 2 列,但它在 \(\mathcal F=\{2,3,4\}\) 中排第一个,所以局部编号是 \(p=0\)。于是

\[ \mathbf e_p=\mathbf e_0= \begin{bmatrix}1\\0\\0\end{bmatrix}, \qquad \mathbf e_p^\top\delta_{\mathcal F}=\Delta w_2. \]

约束 \(\mathbf e_p^\top\delta_{\mathcal F}=-\rho_p\) 只把 \(\Delta w_2\) 固定为当前权重必须移动的距离;\(\Delta w_3,\Delta w_4\) 仍可自由选择,用来补偿输出误差。本轮结束后,全局第 2 列也被冻结,下一轮便有 \(\mathcal F=\{3,4\}\)

一般地,自由部分的阻尼 Hessian 及其逆记为

\[ H_F=(H_\lambda)_{\mathcal F,\mathcal F}\in\mathbb R^{k\times k}, \qquad G_F=H_F^{-1}\in\mathbb R^{k\times k}. \]

当前要量化 \(\mathcal F\) 中局部编号为 \(p\) 的坐标。先对当前工作值量化,再定义残差:

\[ q_p=\mathcal Q(w_{\mathrm{work},p}), \qquad \rho_p=w_{\mathrm{work},p}-q_p. \tag{13} \]

本次完整更新记为 \(\delta\in\mathbb R^k\)。为了让当前坐标从 \(w_{\mathrm{work},p}\) 变成 \(q_p\),必须满足

\[ \delta_p=q_p-w_{\mathrm{work},p}=-\rho_p. \]

\(\mathbf e_p\in\mathbb R^k\) 为第 \(p\) 个标准基向量,则问题是

\[ \begin{aligned} \min_{\delta\in\mathbb R^k}\quad &\frac12\delta^\top H_F\delta\\ \text{s.t.}\quad &\mathbf e_p^\top\delta=-\rho_p. \end{aligned} \tag{14} \]

约束只固定 \(\delta_p\),没有要求另外 \(k-1\) 个分量为零;它们正是用来补偿的自由度。又因为 \(H_F\succ0\),式 (14) 是严格凸二次问题,所以只会有一个全局最小解。

把这个唯一最优解记为 \(\delta^*\)。由于最优解首先必须是式 (14) 的可行解,它当然也要满足同一个硬约束,因此

\[ \boxed{\delta_p^*=-\rho_p}. \]

这里的星号只表示“在所有可行更新中代价最小的那一个完整向量”;它没有改变第 \(p\) 个分量必须把当前权重送到 \(q_p\) 这一事实。真正需要优化的,是其余 \(k-1\) 个分量\(\delta_{\mathcal T}^*\) 应该怎样移动,才能最有效地补偿这次舍入。

对象 输入 shape 输出 shape 含义
量化 \(w_{\mathrm{work},p}\) 标量 \(q_p\)\(\rho_p\),均为标量 选择网格点并记录残差
\(\mathbf e_p^\top\delta\) \([1,k]\cdot[k,1]\) 标量 取出必须等于 \(-\rho_p\) 的第 \(p\) 个更新量
\(\delta^\top H_F\delta\) \([1,k]\cdot[k,k]\cdot[k,1]\) 标量 本次更新造成的条件二次代价
\(G_F=H_F^{-1}\) \([k,k]\) \([k,k]\) 把代价矩阵转成可补偿方向

4.3. 只用线性代数和配方法求解

先把后文会反复使用的字母固定下来。\(\mathcal F\) 保存的是当前自由坐标的全局列号,而 \(p\) 是它们在自由子问题中的局部编号。为了不把两种编号混在一起,定义自由子问题的局部索引集

\[ \mathcal I_F=\{0,1,\ldots,k-1\}, \qquad \mathcal T=\mathcal I_F\setminus\{p\}, \qquad |\mathcal T|=k-1. \]

例如前面的 \(\mathcal F=\{2,3,4\}\) 中,当前全局第 2 列的局部编号是 \(p=0\)。此时 \(\mathcal I_F=\{0,1,2\}\)\(\mathcal T=\{1,2\}\),对应的全局列才是 \(\{3,4\}\)。因此本节\(H_F\)\(G_F\)\(\delta\) 下标中的 \(p\)\(\mathcal T\) 都是局部索引

下面先讨论 \(k\ge2\),也就是至少还有一个坐标能参与补偿的情况。若 \(k=1\)\(\mathcal T\) 为空且根本没有补偿变量;唯一可行更新就是\(\delta^*=-\rho_p\)。此时 \(G_F=1/h_{pp}\),所以代价直接为\(\rho_p^2h_{pp}/2=\rho_p^2/(2[G_F]_{pp})\),与下面的最终公式一致。

下面要把 \(p\) 对应的行和列临时写在最前面。为了不暗中偷换向量顺序,把这个动作明确写成置换矩阵。把集合 \(\mathcal T\) 中互异的局部索引按原顺序列为 \((t_1,\ldots,t_{k-1})\),定义

\[ P=[\mathbf e_p,\mathbf e_{t_1},\ldots,\mathbf e_{t_{k-1}}] \in\mathbb R^{k\times k}. \]

\(P\) 的每行、每列恰有一个 1,所以 \(P^\top P=PP^\top=I\)\(P^\top\) 把原向量换成 \((p,\mathcal T)\) 顺序,\(P\) 则把它换回原局部顺序。先只定义重排后的坐标向量

\[ \bar\delta:=P^\top\delta. \]

由于 \(PP^\top=I\),这个定义也等价于\(\delta=P\bar\delta\)。现在把它代回原来的二次代价,而不是另外猜一个新的 Hessian:

\[ \begin{aligned} \frac12\delta^\top H_F\delta &=\frac12(P\bar\delta)^\top H_F(P\bar\delta)\\ &=\frac12\bar\delta^\top \underbrace{(P^\top H_FP)}_{\text{同一个二次型在新坐标下的系数矩阵}} \bar\delta. \end{aligned} \]

因此,一旦选择了坐标变换\(\bar\delta=P^\top\delta\),为了让重排前后的二次代价完全相同,新坐标下的矩阵就被唯一诱导

\[ \boxed{\bar H_F:=P^\top H_FP}. \]

它不是额外假设,也不是用校准数据重新计算了一次 Hessian;它只是同一个 \(H_F\)\((p,\mathcal T)\) 顺序同时重排行和列。若直接对新坐标下的二次函数求 Hessian,也会得到

\[ \nabla_{\bar\delta}^2 \left[\frac12(P\bar\delta)^\top H_F(P\bar\delta)\right] =P^\top H_FP=\bar H_F. \]

shape 上,\([k,k][k,k][k,k]\to[k,k]\);并且对任意非零 \(z\)\(z^\top\bar H_Fz=(Pz)^\top H_F(Pz)>0\),所以重排不会改变正定性。现在才能把它按“当前坐标 \(p\) 在前、其余坐标 \(\mathcal T\) 在后”严格分块:

\[ \bar H_F= \begin{bmatrix} h_{pp}&h_{p\mathcal T}\\ h_{\mathcal T p}&H_{\mathcal T} \end{bmatrix}, \qquad \bar\delta= \begin{bmatrix} \delta_p\\ \delta_{\mathcal T} \end{bmatrix} = \begin{bmatrix} -\rho_p\\ \delta_{\mathcal T} \end{bmatrix}. \tag{15} \]

各个块的 shape 和含义如下。由于 \(H_F\) 对称,\(\bar H_F\) 也对称,\(h_{p\mathcal T}=h_{\mathcal T p}^\top\)

符号 shape 含义
\(h_{pp}=[H_F]_{pp}\) 标量 当前坐标 \(p\) 自己的二次代价系数
\(h_{p\mathcal T}\) \([1,k-1]\) 坐标 \(p\) 与其余自由坐标的耦合行
\(h_{\mathcal T p}\) \([k-1,1]\) 上一行的转置,即耦合列
\(H_{\mathcal T}=(H_F)_{\mathcal T,\mathcal T}\) \([k-1,k-1]\) 去掉 \(p\) 后,剩余自由坐标的 Hessian
\(\delta_{\mathcal T}\) \([k-1,1]\) 待求的补偿量

置换不改变正定性:任取 \(x\ne0\),有\(x^\top\bar H_Fx=(Px)^\top H_F(Px)>0\),所以\(\bar H_F\succ0\)。再确认稍后出现的 \(H_{\mathcal T}^{-1}\) 确实存在。任取非零向量 \(z\in\mathbb R^{k-1}\),按照\((p,\mathcal T)\) 分块顺序在最上面补一个 0,得到\(y=[0;z]\in\mathbb R^k\),于是

\[ z^\top H_{\mathcal T}z =y^\top\bar H_Fy >0. \]

所以 \(H_{\mathcal T}\succ0\),它必然可逆。

现在把式 (15) 代入目标。因为 \(\delta=P\bar\delta\)\(PP^\top=I\),换序前后的二次型数值完全相同。先做右侧的矩阵乘法,再做左侧的行向量乘法:

\[ \begin{aligned} f(\delta) &=\frac12\delta^\top H_F\delta\\ &=\frac12\bar\delta^\top\bar H_F\bar\delta\\ &=\frac12 \begin{bmatrix}-\rho_p&\delta_{\mathcal T}^\top\end{bmatrix} \begin{bmatrix} -h_{pp}\rho_p+h_{p\mathcal T}\delta_{\mathcal T}\\ -h_{\mathcal T p}\rho_p+H_{\mathcal T}\delta_{\mathcal T} \end{bmatrix}\\ &=\frac12\left( h_{pp}\rho_p^2 -\rho_p h_{p\mathcal T}\delta_{\mathcal T} -\rho_p\delta_{\mathcal T}^\top h_{\mathcal T p} +\delta_{\mathcal T}^\top H_{\mathcal T}\delta_{\mathcal T} \right)\\ &=\frac12\left( h_{pp}\rho_p^2 -2\rho_p h_{p\mathcal T}\delta_{\mathcal T} +\delta_{\mathcal T}^\top H_{\mathcal T}\delta_{\mathcal T} \right). \end{aligned} \tag{16} \]

最后一步把两个交叉项合并了。依赖 Hessian 的对称性 \(h_{\mathcal T p}=h_{p\mathcal T}^\top\),使用标量的转置等于 自身:

\[ \begin{aligned} \delta_{\mathcal T}^\top h_{\mathcal T p} &=\delta_{\mathcal T}^\top h_{p\mathcal T}^\top &&\text{(因为 }h_{\mathcal T p}=h_{p\mathcal T}^\top\text{)}\\ &=(h_{p\mathcal T}\delta_{\mathcal T})^\top &&\text{(因为 }(AB)^\top=B^\top A^\top\text{)}\\ &=h_{p\mathcal T}\delta_{\mathcal T} &&\text{(因为它的 shape 是 }[1,1]\text{)}. \end{aligned} \]

因此式 (16) 中两个交叉项才可以合并为 \(-2\rho_p h_{p\mathcal T}\delta_{\mathcal T}\)

接下来配方。候选“中心”

\[ \underbrace{\rho_p}_{\text{标量}} \underbrace{H_{\mathcal T}^{-1}}_{[k-1,k-1]} \underbrace{h_{\mathcal T p}}_{[k-1,1]} \longrightarrow[k-1,1] \]

\(\delta_{\mathcal T}\) 的 shape 相同。把下面的平方项一行一行展开:

\[ \begin{aligned} &\left(\delta_{\mathcal T} -\rho_pH_{\mathcal T}^{-1}h_{\mathcal T p}\right)^\top H_{\mathcal T} \left(\delta_{\mathcal T} -\rho_pH_{\mathcal T}^{-1}h_{\mathcal T p}\right)\\ ={}&\delta_{\mathcal T}^\top H_{\mathcal T}\delta_{\mathcal T} -\rho_p\delta_{\mathcal T}^\top h_{\mathcal T p} -\rho_p h_{p\mathcal T}\delta_{\mathcal T} +\rho_p^2h_{p\mathcal T}H_{\mathcal T}^{-1}h_{\mathcal T p}\\ ={}&\delta_{\mathcal T}^\top H_{\mathcal T}\delta_{\mathcal T} -2\rho_p h_{p\mathcal T}\delta_{\mathcal T} +\rho_p^2h_{p\mathcal T}H_{\mathcal T}^{-1}h_{\mathcal T p}. \end{aligned} \]

这里用到了 \(H_{\mathcal T}^{-1}\) 仍然对称。最后一项的 shape 是 \([1,k-1][k-1,k-1][k-1,1]\to\) 标量。把它移到等号另一边,再代回式 (16),便得到完整的配方结果:

\[ \begin{aligned} f(\delta) ={}&\frac12 \left(\delta_{\mathcal T} -\rho_pH_{\mathcal T}^{-1}h_{\mathcal T p}\right)^\top H_{\mathcal T} \left(\delta_{\mathcal T} -\rho_pH_{\mathcal T}^{-1}h_{\mathcal T p}\right)\\ &+\frac{\rho_p^2}{2} \left( h_{pp}-h_{p\mathcal T}H_{\mathcal T}^{-1}h_{\mathcal T p} \right). \end{aligned} \tag{17} \]

第二行与 \(\delta_{\mathcal T}\) 无关,是一个常数。第一行是正定二次型,因此总是大于等于 0,并且只有括号内的向量为 0 时才能等于0。所以唯一最优补偿是

\[ \boxed{ \delta_{\mathcal T}^* =\rho_pH_{\mathcal T}^{-1}h_{\mathcal T p} }. \tag{18} \]

到这里,配方法已经在 Hessian 的分块记号下求出了补偿量。为了和后面小节以及 GPTQ 的实际计算直接对应,还需要把 \(H_{\mathcal T}^{-1}h_{\mathcal T p}\) 改写成逆 Hessian \(G_F\) 的第 \(p\) 列。

\(G_F=H_F^{-1}\)。重排后的逆矩阵是

\[ \bar G_F :=\bar H_F^{-1} =P^\top G_F P = \begin{bmatrix} [G_F]_{pp}&(G_F)_{p,\mathcal T}\\ (G_F)_{\mathcal T,p}&(G_F)_{\mathcal T,\mathcal T} \end{bmatrix}. \]

等号 \(\bar H_F^{-1}=P^\top G_F P\) 可以直接相乘验证:

\[ \bar H_F(P^\top G_F P) =(P^\top H_F P)(P^\top G_F P) =P^\top H_FG_F P =I. \]

这里 \([G_F]_{pp}\) 是标量,\((G_F)_{\mathcal T,p}\)\([k-1,1]\) 列向量。由于 \(H_F\succ0\),它的逆也正定。具体地,任取 \(x\ne0\) 并令 \(z=G_Fx\),则 \(z\ne0\)\(x=H_Fz\),因此

\[ x^\top G_Fx =x^\top z =z^\top H_Fz >0. \]

所以 \(G_F\succ0\),进而 \([G_F]_{pp}=\mathbf e_p^\top G_F\mathbf e_p>0\) ;下面的除法不会除以 0。

\(\bar H_F\bar G_F=I\) 的左下块得到一个 \([k-1,1]\) 向量等式:

\[ \begin{aligned} h_{\mathcal T p}[G_F]_{pp} +H_{\mathcal T}(G_F)_{\mathcal T,p}&=0,\\ H_{\mathcal T}(G_F)_{\mathcal T,p} &=-h_{\mathcal T p}[G_F]_{pp},\\ (G_F)_{\mathcal T,p} &=-[G_F]_{pp}H_{\mathcal T}^{-1}h_{\mathcal T p},\\ H_{\mathcal T}^{-1}h_{\mathcal T p} &=-\frac{(G_F)_{\mathcal T,p}}{[G_F]_{pp}}. \end{aligned} \tag{19} \]

第一行的 shape 是

\[ [k-1,1]\cdot\text{标量}+[k-1,k-1][k-1,1] \longrightarrow[k-1,1]. \]

后面三行只是在等式两边依次左乘 \(H_{\mathcal T}^{-1}\),再除以正标量 \([G_F]_{pp}\)

把式 (19) 代入式 (18):

\[ \delta_{\mathcal T}^* =-\frac{\rho_p}{[G_F]_{pp}}(G_F)_{\mathcal T,p}. \]

再与式 (14) 后已经明确的硬约束 \(\delta_p^*=-\rho_p\) 上下拼起来,先得到 \((p,\mathcal T)\) 顺序下的完整更新:

\[ \begin{aligned} \bar\delta^* &= \begin{bmatrix} -\rho_p\\ -\dfrac{\rho_p}{[G_F]_{pp}}(G_F)_{\mathcal T,p} \end{bmatrix}\\ &=-\frac{\rho_p}{[G_F]_{pp}} \begin{bmatrix} [G_F]_{pp}\\ (G_F)_{\mathcal T,p} \end{bmatrix}\\ &=-\frac{\rho_p}{[G_F]_{pp}}\bar G_F\mathbf e_0. \end{aligned} \]

这里 \(\mathbf e_0\) 是重排空间中的第一个标准基向量;因为 \(P\) 的第一列是 \(\mathbf e_p\),所以 \(P\mathbf e_0=\mathbf e_p\)。现在乘 \(P\) 恢复原局部顺序:

\[ \begin{aligned} \delta^* &=P\bar\delta^*\\ &=-\frac{\rho_p}{[G_F]_{pp}}P\bar G_F\mathbf e_0\\ &=-\frac{\rho_p}{[G_F]_{pp}} P(P^\top G_F P)\mathbf e_0\\ &=-\frac{\rho_p}{[G_F]_{pp}}G_F(P\mathbf e_0)\\ &=-\frac{\rho_p}{[G_F]_{pp}}G_F\mathbf e_p\\ &=\boxed{ -\frac{\rho_p}{[G_F]_{pp}}(G_F)_{:,p} }. \end{aligned} \]

重排空间中的拼接是“标量 \([1,1]\) + 列向量 \([k-1,1]\) \(\longrightarrow\) 完整列向量 \([k,1]\)”;乘置换矩阵 \(P:[k,k]\) 以后,shape 仍为 \([k,1]\),只是元素回到原来的局部位置。若把下一节的标量记号提前写成

\[ \mu:=\frac{\rho_p}{[G_F]_{pp}}, \]

那么同一个答案就是 \(\delta^*=-\mu G_F\mathbf e_p\):这分别对应第 3 节的式 (20) 和式 (21)。

这就是第 4 节的式 (22),并不是只说明“方向大致相同”,而是把比例系数也精确求了出来。

最后还要把最小代价推完。由式 (17)、(18),最优点处的平方项为0,因此

\[ f(\delta^*) =\frac{\rho_p^2}{2} \left( h_{pp}-h_{p\mathcal T}H_{\mathcal T}^{-1}h_{\mathcal T p} \right). \]

括号里的量看起来仍然只有 Hessian。利用 \(\bar H_F\bar G_F=I\) 的左上块,并代入式 (19),可以把它改写成逆 Hessian 的对角元:

\[ \begin{aligned} h_{pp}[G_F]_{pp} +h_{p\mathcal T}(G_F)_{\mathcal T,p}&=1,\\ h_{pp}[G_F]_{pp} -[G_F]_{pp}h_{p\mathcal T}H_{\mathcal T}^{-1}h_{\mathcal T p} &=1,\\ [G_F]_{pp} \left( h_{pp}-h_{p\mathcal T}H_{\mathcal T}^{-1}h_{\mathcal T p} \right)&=1. \end{aligned} \]

这里左上块等式的两项分别是“标量乘标量”和 \([1,k-1][k-1,1]\),输出都为标量,所以才能相加并等于标量 1。

所以

\[ \boxed{ f(\delta^*) =\frac{\rho_p^2}{2[G_F]_{pp}} }. \]

这正是第 4 节的式 (23)。至此,配方法已经完整推出了后文的两个最终答案:最优完整更新式 (22),以及它造成的最小损失增量式(23)。换用第 4 节的损失记号,就是\(f(\delta^*)=\Delta\ell_{\lambda,p}^*\)

4.4. 直接求出最优补偿

这一节不是又提出了一个新问题,而是用论文中更常见的拉格朗日乘子法,重新求解式 (14)。上一节按照同一套 \(p\)\(\mathcal T\)\(H_{\mathcal T}\)\(G_F\) 记号分块,并用配方法推出了式 (22)、(23);这一节不需要把 \(p\) 写在分块矩阵的第一格,能够直接在原来的局部编号中得到“逆 Hessian 第 \(p\) 列”的公式,也为上一节提供一条独立核对路线:

\[ \min_{\delta\in\mathbb R^k} \frac12\delta^\top H_F\delta, \qquad \text{s.t.}\quad \mathbf e_p^\top\delta=-\rho_p. \]

先把问题翻译下:

  • 当前权重必须移动 \(-\rho_p\),才能从 \(w_{\mathrm{work},p}\) 精确落到网格点 \(q_p\)
  • 其余 \(k-1\) 个自由权重可以跟着移动,用来抵消输出误差;
  • 我们要在所有满足“第 \(p\) 个更新量等于 \(-\rho_p\)”的方案中,找出二次代价最小的那一个。

这一节要严格回答三个问题:

  1. 最优的完整更新向量 \(\delta^*:[k,1]\) 是什么?
  2. 为什么答案会用到逆 Hessian \(G_F=H_F^{-1}\) 的第 \(p\) 列?
  3. 为什么算出的驻点一定是唯一全局最小点,而不只是某个局部候选点?

已知量、未知量和目标可以整理成:

对象 shape 已知/未知 含义
\(H_F\) \([k,k]\) 已知,且 \(H_F\succ0\) 自由权重变化的二次代价矩阵
\(\mathbf e_p\) \([k,1]\) 已知 只选出第 \(p\) 个分量
\(\rho_p\) 标量 已知 当前权重到量化点的舍入残差
\(\delta\) \([k,1]\) 待求 当前权重的强制移动与其他权重的补偿
\(\mu\) 标量 待求的辅助量 为了同时满足最小代价和等式约束引入的乘子

4.4.1 为什么会出现拉格朗日乘子

目标函数

\[ f(\delta)=\frac12\delta^\top H_F\delta \]

像一个只有一个谷底的碗,因为 \(H_F\succ0\)。约束

\[ \mathbf e_p^\top\delta=-\rho_p \]

表示只能在“第 \(p\) 个分量已经固定”的平面上移动。可行的小移动 \(h:[k,1]\) 必须满足

\[ \mathbf e_p^\top h=0, \]

因为它不能再次改变第 \(p\) 个分量。

在最优点 \(\delta^*\),沿任何可行方向 \(h\) 都不应再有一阶下降。更具体地说:若方向导数小于 0,沿 \(h\) 走一点就能继续降低代价;若方向导数大于 0,那么沿同样可行的反方向 \(-h\) 走一点就能降低代价。因此最优点必须满足

\[ h^\top H_F\delta^*=0 \qquad \text{对所有满足 }\mathbf e_p^\top h=0\text{ 的 }h. \]

也就是说,目标函数的梯度 \(H_F\delta^*\) 必须垂直于约束平面中的所有方向。这个平面的法向量就是 \(\mathbf e_p\),所以必有某个标量 \(\mu\) 使

\[ H_F\delta^*=-\mu\mathbf e_p. \]

拉格朗日乘子法只是把这条“谷底梯度必须垂直于约束平面”的条件系统地写出来。

4.4.2 建立拉格朗日函数并逐项求导

先把约束移到等号左边:

\[ g(\delta)=\mathbf e_p^\top\delta+\rho_p=0. \]

定义拉格朗日函数

\[ \mathcal J(\delta,\mu) =\frac12\delta^\top H_F\delta +\mu(\mathbf e_p^\top\delta+\rho_p). \]

分别对 \(\delta\)\(\mu\) 求偏导。由于 \(H_F=H_F^\top\)

\[ \nabla_\delta\!\left(\frac12\delta^\top H_F\delta\right) =\frac12(H_F+H_F^\top)\delta =H_F\delta. \]

约束项的梯度为

\[ \nabla_\delta\!\left[ \mu(\mathbf e_p^\top\delta+\rho_p) \right] =\mu\mathbf e_p, \]

而对 \(\mu\) 求导会原样取回约束:

\[ \frac{\partial\mathcal J}{\partial\mu} =\mathbf e_p^\top\delta+\rho_p. \]

令两组导数都等于 0,得到一阶最优条件

\[ \begin{cases} H_F\delta+\mu\mathbf e_p=0,\\ \mathbf e_p^\top\delta=-\rho_p. \end{cases} \]

也可以把它写成一个标准的 KKT 线性方程组。KKT 是 Karush-Kuhn-Tucker 的缩写;在这里只有等式约束,所以它只是把“梯度条件”和“原约束”上下拼成一个线性方程组,不需要额外的优化知识:

\[ \underbrace{ \begin{bmatrix} H_F&\mathbf e_p\\ \mathbf e_p^\top&0 \end{bmatrix}}_{[k+1,k+1]} \underbrace{ \begin{bmatrix} \delta\\ \mu \end{bmatrix}}_{[k+1,1]} = \underbrace{ \begin{bmatrix} 0_{k\times1}\\ -\rho_p \end{bmatrix}}_{[k+1,1]}. \]

4.4.3 解出 \(\delta\)\(\mu\)

由第一行

\[ H_F\delta+\mu\mathbf e_p=0. \]

因为 \(H_F\succ0\),所以 \(H_F\) 一定可逆。两边左乘

\[ G_F=H_F^{-1}:[k,k], \]

便得到

\[ \begin{aligned} G_FH_F\delta+\mu G_F\mathbf e_p&=0,\\ \delta&=-\mu G_F\mathbf e_p. \end{aligned} \tag{20} \]

式 (20) 右侧的关键矩阵乘法是

\[ G_F\mathbf e_p: \qquad [k,k][k,1]\longrightarrow[k,1]. \]

外面的标量 \(-\mu\) 只缩放数值,不改变输出 shape。

为什么 \(G_F\mathbf e_p\) 正好是 \(G_F\) 的第 \(p\) 列?因为 \(\mathbf e_p\) 除了第 \(p\) 个位置为 1,其余位置全是 0。矩阵乘以它时,

\[ G_F\mathbf e_p =\sum_{j=0}^{k-1}(G_F)_{:,j}[\mathbf e_p]_j =(G_F)_{:,p}. \]

现在把式 (20) 代回约束:

\[ \begin{aligned} \mathbf e_p^\top\delta &=\mathbf e_p^\top(-\mu G_F\mathbf e_p)\\ &=-\mu\,\mathbf e_p^\top G_F\mathbf e_p\\ &=-\rho_p. \end{aligned} \]

其中

\[ \mathbf e_p^\top G_F\mathbf e_p=[G_F]_{pp}, \]

因为左边的 \(\mathbf e_p^\top\) 又从第 \(p\) 列中取出了第 \(p\) 个元素。因此

\[ -\mu[G_F]_{pp}=-\rho_p \quad\Longrightarrow\quad \mu=\frac{\rho_p}{[G_F]_{pp}}. \tag{21} \]

这个除法一定有定义:\(H_F\succ0\) 推出 \(G_F=H_F^{-1}\succ0\),而 \(\mathbf e_p\ne0\),所以

\[ [G_F]_{pp} =\mathbf e_p^\top G_F\mathbf e_p >0. \]

\(\mu\) 代回式 (20),就得到下一小节将要框出的完整更新公式。它的第 \(p\) 个分量会精确等于 \(-\rho_p\),其余分量则是代价最小的补偿。

4.4.4 用开头的二维数值例子核对

回到本章开头的二维例子。此时

\[ G_F= \begin{bmatrix} 1&-1/2\\ -1/2&1 \end{bmatrix}, \qquad p=0, \qquad \rho_p=0.12. \]

先求乘子:

\[ \mu=\frac{\rho_p}{[G_F]_{pp}} =\frac{0.12}{1} =0.12. \]

再代入式 (20):

\[ \begin{aligned} \delta^* &=-\mu G_F\mathbf e_0\\ &=-0.12 \begin{bmatrix} 1&-1/2\\ -1/2&1 \end{bmatrix} \begin{bmatrix}1\\0\end{bmatrix}\\ &=-0.12 \begin{bmatrix}1\\-1/2\end{bmatrix} = \begin{bmatrix}-0.12\\+0.06\end{bmatrix}. \end{aligned} \]

第一个数把当前权重送到量化点,第二个数就是另一个自由权重接住的补偿,与本章开头直接写出的结果完全一致。

4.4.5 严格证明它是唯一全局最小点

只解出一阶方程还不够;现在证明它确实是全局最优解。

设上面求出的可行点为 \(\delta^*\)。任取另一个满足同一约束的可行点 \(\tilde\delta\),令

\[ h=\tilde\delta-\delta^*. \]

因为二者的第 \(p\) 个分量都等于 \(-\rho_p\)

\[ \mathbf e_p^\top h =\mathbf e_p^\top\tilde\delta -\mathbf e_p^\top\delta^* =-\rho_p-(-\rho_p) =0. \]

\(\tilde\delta=\delta^*+h\) 代入目标函数并完整展开:

\[ \begin{aligned} f(\tilde\delta) &=\frac12(\delta^*+h)^\top H_F(\delta^*+h)\\ &=\frac12(\delta^*)^\top H_F\delta^* +h^\top H_F\delta^* +\frac12h^\top H_Fh. \end{aligned} \]

由驻点方程 \(H_F\delta^*=-\mu\mathbf e_p\)

\[ h^\top H_F\delta^* =-\mu h^\top\mathbf e_p =0. \]

因此

\[ f(\tilde\delta)-f(\delta^*) =\frac12h^\top H_Fh. \]

\(\tilde\delta\ne\delta^*\) 时,必有 \(h\ne0\)。又因为 \(H_F\succ0\)

\[ \frac12h^\top H_Fh>0. \]

所以任何其他可行点的代价都严格大于 \(f(\delta^*)\)。这就证明了:

式 (20)、(21) 求出的 \(\delta^*\) 不只是一个驻点,而是约束问题唯一的全局最小解。

4.5. 最优完整更新与最小损失增量

式 (20)、(21) 沿两条路线得到了同一个完整更新。统一写成:

\[ \boxed{ \delta^* =-\frac{\rho_p}{[G_F]_{pp}}(G_F)_{:,p} }. \tag{22} \]

这里继续沿用第 2 节的局部索引定义:\(\mathcal I_F=\{0,1,\ldots,k-1\}\)\(\mathcal T=\mathcal I_F\setminus\{p\}\)。因此 \(\mathcal T\) 表示自由子问题中除 \(p\) 外的其余 \(k-1\) 个局部位置。在这套记号下,“完整更新”包含两部分:

\[ \boxed{ \delta_p^*=-\rho_p, \qquad \delta_{\mathcal T}^* =-\frac{\rho_p}{[G_F]_{pp}}(G_F)_{\mathcal T,p} }, \]

第一项把当前坐标强制移到 \(q_p\);第二项才是其他自由坐标的补偿。

损失增量也逐行算清楚。由驻点方程 \(H_F\delta^*=-\mu\mathbf e_p\) 和约束 \(\mathbf e_p^\top\delta^*=-\rho_p\)

\[ \begin{aligned} \Delta\ell_{\lambda,p}^* &=\frac12(\delta^*)^\top H_F\delta^*\\ &=-\frac12\mu(\delta^*)^\top\mathbf e_p\\ &=\frac12\mu\rho_p\\ &=\boxed{\frac{\rho_p^2}{2[G_F]_{pp}}}. \end{aligned} \tag{23} \]

式 (22) 给出更新向量,式 (23) 给出最小代价。下面分别从约束、无耦合边界和非负性验证它们,而不是只把公式再复述一遍。

验证 1:当前权重确实落到了量化点。 取式 (22) 的第 \(p\) 个分量:

\[ [\delta^*]_p =-\frac{\rho_p}{[G_F]_{pp}}[G_F]_{pp} =-\rho_p. \]

因此

\[ w_{\mathrm{work},p}+[\delta^*]_p =w_{\mathrm{work},p}-\rho_p =q_p, \]

恰好满足最初的硬约束,而不是近似满足。

验证 2:没有坐标耦合时,不应凭空出现补偿。\(G_F\) 是对角矩阵,那么对任意 \(i\ne p\) 都有 \([G_F]_{ip}=0\),所以

\[ [\delta^*]_i =-\frac{\rho_p}{[G_F]_{pp}}[G_F]_{ip} =0. \]

这时只有当前权重移动,其他权重不动,符合“各坐标互不影响”的边界情况。

验证 3:最小代价不可能为负。 因为 \(G_F\succ0\),所以 \([G_F]_{pp}>0\);又因为 \(\rho_p^2\ge0\),故

\[ \Delta\ell_{\lambda,p}^* =\frac{\rho_p^2}{2[G_F]_{pp}} \ge0. \]

三项验证分别检查了式 (22) 的可行性、补偿方向的合理性和式 (23) 的符号。

在 GPTQ 已给定的共享列顺序中,式 (23) 衡量当前这一步的条件代理代价。不要把 \([G_F]_{pp}\) 简单叫作“重要性”:在 \(|\rho_p|\) 相同时,它越大,式 (23) 越小,说明改变该坐标越容易被其他自由坐标补偿。

五、多轮之后为什么还能重复使用同一个纯二次问题

5.1. 这一节解决的问题

第一次量化之前,工作权重就是原始权重,总改变量为 0,所以损失天然是

\[ \ell_\lambda(\delta) =\frac12\delta^\top H_\lambda\delta. \]

但完成第一轮以后,当前工作权重已经包含“强制舍入”和“其他权重补偿”,不再等于原始权重。设对某个输出行而言,

\[ w_{\mathrm{work}}=w_{\mathrm{original}}+\zeta, \qquad \zeta:[d,1], \]

其中 \(\zeta\) 是此前所有轮累计产生的总改变量。下一轮若再增加一个更新,直接展开损失时通常会出现一个与新更新成一次关系的交叉项。

为了不用读者翻回前文,第一轮求解的式 (14) 完整抄写如下:

\[ \begin{aligned} \min_{\delta\in\mathbb R^k}\quad &\frac12\delta^\top H_F\delta,\\ \text{s.t.}\quad &\mathbf e_p^\top\delta=-\rho_p. \end{aligned} \qquad\text{(式 (14))} \]

这里的 \(\delta:[k,1]\) 是第一轮在当前 \(k\) 个自由坐标上施加的完整更新。进入后续轮次后,为了不把“历史累计变化 \(\zeta\)”和“本轮准备新增的变化”混在一起,本文把本轮新增量改记为

\[ u:[k,1]. \]

\(u\) 与式 (14) 中的 \(\delta\) 扮演同一个角色,只是名字变了:若本轮开始时自由部分是 \(w_{\mathrm{work},\mathcal F}\),施加更新后就是

\[ w_{\mathrm{work},\mathcal F}^{\mathrm{new}} =w_{\mathrm{work},\mathcal F}+u. \]

其中 \(u_p\) 是当前待量化坐标新增的变化,仍必须把该权重送到量化点, 所以

\[ u_p=q_p-w_{\mathrm{work},p}=-\rho_p, \qquad \mathbf e_p^\top u=-\rho_p. \]

因此,这一节真正需要回答的是:

\(\zeta\ne0\) 时,为什么下一轮仍能把式 (14) 中的 \(\delta\) 换成 本轮新增量 \(u\),继续求解 \(\min_u\tfrac12u^\top H_Fu\)\(\text{s.t. }\mathbf e_p^\top u=-\rho_p\)?从累计状态展开后本来会 出现的一次项,究竟为什么等于 0?

如果不能证明这一点,就不能把第一轮的式 (14) 想当然地重复用于第二轮、第三轮。

下面的证明针对给定的当前量化点 \(q_p\),并假设每轮按式 (22) 在精确算术下完成补偿。

5.2. 先把“本来会出现的一次项”完整展开

设本轮开始时:

对象 shape 含义
\(\mathcal P\) $ \mathcal P
\(\mathcal F\) $k= \mathcal F
\(\zeta\) \([d,1]\) 当前工作权重相对原始权重的累计变化
\(u\) \([k,1]\) 本轮准备新增到自由坐标上的更新;它对应式 (14) 中的 \(\delta\),不包含历史累计量 \(\zeta\)
\(S_{\mathcal F}\) \([d,k]\) 把自由向量 \(u\) 放回完整 \(d\) 维空间的位置矩阵

在完整的 \(d\) 维坐标中,本轮结束后的累计变化因此是

\[ \zeta^{\mathrm{new}}=\zeta+S_{\mathcal F}u:[d,1]. \]

位置矩阵只是一个“把短向量塞回原位置”的工具。例如 \(d=5\)\(\mathcal F=\{3,4\}\) 时,

\[ S_{\mathcal F} = \begin{bmatrix} 0&0\\ 0&0\\ 0&0\\ 1&0\\ 0&1 \end{bmatrix} :[5,2], \qquad S_{\mathcal F} \begin{bmatrix}u_0\\u_1\end{bmatrix} = \begin{bmatrix}0\\0\\0\\u_0\\u_1\end{bmatrix} :[5,1]. \]

所以冻结坐标得到的更新量都是 0,只有自由坐标能改变。

对单个输出行,阻尼二次代价为

\[ \ell_\lambda(x)=\frac12x^\top H_\lambda x. \]

从当前累计改变量 \(\zeta\) 出发,再施加 \(S_{\mathcal F}u\),损失增量是

\[ \begin{aligned} &\ell_\lambda(\zeta+S_{\mathcal F}u)-\ell_\lambda(\zeta)\\ &=\frac12(\zeta+S_{\mathcal F}u)^\top H_\lambda(\zeta+S_{\mathcal F}u) -\frac12\zeta^\top H_\lambda\zeta\\ &=\zeta^\top H_\lambda S_{\mathcal F}u +\frac12u^\top S_{\mathcal F}^\top H_\lambda S_{\mathcal F}u\\ &=\underbrace{ \bigl(S_{\mathcal F}^\top H_\lambda\zeta\bigr)^\top u }_{\text{一次项}} +\frac12u^\top H_Fu, \end{aligned} \]

从第二行到第三行用到了 \(H_\lambda=H_\lambda^\top\):展开后出现的两个交叉项互为转置且都是标量,所以合并成

\[ \frac12\zeta^\top H_\lambda S_{\mathcal F}u +\frac12u^\top S_{\mathcal F}^\top H_\lambda\zeta =\zeta^\top H_\lambda S_{\mathcal F}u. \]

其中

\[ H_F =S_{\mathcal F}^\top H_\lambda S_{\mathcal F} :[k,k]. \]

一次项中的向量

\[ g_{\mathcal F} =S_{\mathcal F}^\top H_\lambda\zeta =\bigl(H_\lambda\zeta\bigr)_{\mathcal F} :[k,1] \]

就是当前损失对所有自由坐标的梯度。一般情况下它并不自动等于 0;下面要证明 GPTQ 的最优补偿恰好让它在每轮开始时保持为 0。

5.3. 要维持的不变量

需要证明的不变量是

\[ \boxed{ \bigl(H_\lambda\zeta\bigr)_{\mathcal F}=0 }. \]

它的通俗含义是:

已冻结的权重虽然不能再动,但在所有仍可移动的权重方向上,当前工作权重已经没有任何一阶下降空间。

例如 \(\mathcal F=\{3,4\}\) 时,它只要求梯度的第 3、4 个分量为 0。已冻结坐标上的梯度可以不为 0,因为那些方向已经不允许移动。

一旦这个不变量成立,上节展开式中的一次项就是

\[ \underbrace{ \bigl(H_\lambda\zeta\bigr)_{\mathcal F}^\top }_{[1,k]} \underbrace{u}_{[k,1]} =0, \]

下一轮才会重新变成纯二次问题。现在严格证明这个不变量会被每一轮更新保留下来。

5.4. 数学归纳法严格证明不变量

第一步:证明第一次量化前成立。

第一次量化前还没有修改任何权重,所以

\[ \zeta=0 \quad\Longrightarrow\quad \bigl(H_\lambda\zeta\bigr)_{\mathcal F}=0. \]

因此归纳基础成立。

第二步:写出归纳假设。

假设某一轮开始时,全局自由列集合为 \(\mathcal F=\{f_0,\ldots,f_{k-1}\}\),并固定处理顺序 \((f_0,\ldots,f_{k-1})\)。归纳假设是

\[ \bigl(H_\lambda\zeta\bigr)_{\mathcal F}=0. \]

自由子问题的局部索引集仍记为 \(\mathcal I_F=\{0,1,\ldots,k-1\}\)。本轮选择局部位置 \(p\in\mathcal I_F\),它对应的全局列号是

\[ j=f_p. \]

量化完成后,全局列 \(j\) 被冻结。为了同时区分“旧矩阵里的局部 位置”和“完整 \(d\) 维空间里的全局列号”,分别定义

\[ \mathcal T=\mathcal I_F\setminus\{p\}, \qquad \mathcal F^+=\mathcal F\setminus\{j\}, \qquad |\mathcal T|=|\mathcal F^+|=k-1. \]

这里必须把两套编号说死:

集合 编号属于哪里 元素取值 生命周期
\(\mathcal F^+\) 原始权重矩阵的全局列号 原始范围 \(\{0,\ldots,d-1\}\) 中的列号 是下一轮真正保存和使用的自由列集合
\(\mathcal T\) 本轮自由子问题 \(H_F:[k,k]\) 内的临时局部位置 本轮范围 \(\{0,\ldots,k-1\}\) 中除 \(p\) 外的位置 只服务本轮的分块、取子向量和证明;进入下一轮后重新编号

从局部位置到全局列号的映射是

\[ \phi_{\mathcal F}:\mathcal I_F\to\mathcal F, \qquad \phi_{\mathcal F}(t)=f_t. \]

因此二者的准确关系不是“\(\mathcal T=\mathcal F^+\)”,而是

\[ \boxed{ \mathcal F^+ =\phi_{\mathcal F}(\mathcal T) =\{f_t\mid t\in\mathcal T\} }, \]

并且这些全局列号继承 \(\mathcal F\) 删除 \(j=f_p\) 后的相对顺序。

例如,若本轮按顺序保存的全局自由列为

\[ \mathcal F=(2,5,7,9), \qquad p=1,\qquad j=f_1=5, \]

那么

\[ \mathcal T=\{0,2,3\}\quad\text{(本轮临时局部位置)}, \qquad \mathcal F^+=(2,7,9)\quad\text{(原始全局列号)}. \]

这里绝不能把 \(\mathcal T=\{0,2,3\}\) 误读成原权重矩阵的第 \(0,2,3\) 列。进入下一轮后,\(\mathcal F^+=(2,7,9)\) 会重新建立新的 局部索引集

\[ \mathcal I_{F^+}=\{0,1,2\}. \]

所以旧的局部标签 \(\{0,2,3\}\) 到此失效;下一轮局部位置 \(0,1,2\) 才分别对应全局列 \(2,7,9\)

第三步:利用本轮最优补偿的驻点方程。

由归纳假设,5.2 节展开式中的一次项在本轮开始时等于 0,所以本轮确实重新化为式 (14) 的纯二次约束问题。令其最优完整更新为 \(\delta^*:[k,1]\),于是可以使用式 (20) 的驻点方程:

\[ H_F\delta^*=-\mu\mathbf e_p. \]

标准基向量 \(\mathbf e_p\) 只有第 \(p\) 个局部分量非零。把等式限制到 仍会保留的局部位置 \(\mathcal T\),右侧全部变为 0:

\[ \bigl(H_F\delta^*\bigr)_{\mathcal T} =\bigl(-\mu\mathbf e_p\bigr)_{\mathcal T} =0. \]

这一步表达的正是:本轮补偿完成后,除刚被冻结的局部位置 \(p\) (全局列 \(j\))以外,其余自由方向的一阶变化已经被消掉。

第四步:证明更新后的梯度仍在新自由集合上为 0。

把自由空间中的更新嵌回完整空间:

\[ \zeta' =\zeta+S_{\mathcal F}\delta^*, \qquad \zeta':[d,1]. \]

更新后,在下一轮的全局自由集合 \(\mathcal F^+\) 上,梯度为

\[ \begin{aligned} \bigl(H_\lambda\zeta'\bigr)_{\mathcal F^+} &=\bigl(H_\lambda\zeta\bigr)_{\mathcal F^+} +\bigl(H_\lambda S_{\mathcal F}\delta^*\bigr)_{\mathcal F^+}\\ &=\underbrace{ \bigl(H_\lambda\zeta\bigr)_{\mathcal F^+} }_{0} +\bigl(H_F\delta^*\bigr)_{\mathcal T}\\ &=0+0\\ &=0. \end{aligned} \]

第二行的第一个 0 来自归纳假设,因为 \(\mathcal F^+\subset\mathcal F\)。第二项之所以能写成 \((H_F\delta^*)_{\mathcal T}\),是因为先在全部旧自由列上有

\[ \bigl(H_\lambda S_{\mathcal F}\delta^*\bigr)_{\mathcal F} =S_{\mathcal F}^\top H_\lambda S_{\mathcal F}\delta^* =H_F\delta^*. \]

再在左侧保留全局列 \(\mathcal F^+\)、在右侧保留与之对应的局部位置 \(\mathcal T\),就得到

\[ \bigl(H_\lambda S_{\mathcal F}\delta^*\bigr)_{\mathcal F^+} =\bigl(H_F\delta^*\bigr)_{\mathcal T}. \]

因此,“本轮开始时不变量成立”必然推出“下一轮开始时不变量仍成立”。结合第一次之前的归纳础,不变量对所有轮次都成立。

5.5. 回答本节开头的问题

下一轮的全局自由集合是 \(\mathcal F^+\)。令 \(S_{\mathcal F^+}:[d,k-1]\) 为对应的位置矩阵,并任取新的自由更新 \(v:[k-1,1]\)。由于刚刚证明

\[ \bigl(H_\lambda\zeta'\bigr)_{\mathcal F^+}=0, \]

所以

\[ \begin{aligned} \ell_\lambda(\zeta'+S_{\mathcal F^+}v)-\ell_\lambda(\zeta') &=\underbrace{ \bigl(H_\lambda\zeta'\bigr)_{\mathcal F^+}^\top v }_{0} +\frac12v^\top H_{\mathcal F^+}v\\ &=\frac12v^\top H_{\mathcal F^+}v. \end{aligned} \]

其中

\[ H_{\mathcal F^+} =S_{\mathcal F^+}^\top H_\lambda S_{\mathcal F^+} =(H_F)_{\mathcal T,\mathcal T}. \]

这就严格证明了:每轮开始时,一次项都会因为上一轮的最优补偿而消失,所以式 (14)--(23) 的纯二次问题结构可以重复使用。

但“结构相同”不等于“所有输入不变”。进入下一轮后:

  1. 全局自由集合从 \(\mathcal F\) 缩小为 \(\mathcal F^+\)
  2. 工作权重已经改变,新的舍入残差必须重新计算;
  3. Hessian 子矩阵从 \(H_F\) 变成 \(H_{\mathcal F^+}=(H_F)_{\mathcal T,\mathcal T}\)
  4. 因而逆 Hessian 也必须从 \(G_F\) 更新为 \(G_{\mathcal F^+}=H_{\mathcal F^+}^{-1}\)

第 4 点正好引出下一章:量化一个坐标以后,为什么必须更新逆 Hessian?

六、量化后为什么必须更新逆 Hessian

回到当前自由子问题的局部编号。局部索引集为\(\mathcal I_F=\{0,1,\ldots,k-1\}\);第 \(p\) 个局部坐标一旦固定,它就不再是自由变量。旧矩阵内仍然保留的局部位置是

\[ \mathcal T=\mathcal I_F\setminus\{p\}, \qquad |\mathcal T|=k-1. \]

若换回上一节的全局列号,这个局部集合对应的正是下一轮全局自由集合 \(\mathcal F^+\)

下一轮需要的是 \(H_{\mathcal T}=(H_F)_{\mathcal T,\mathcal T}\) 的逆,而不是旧逆矩阵随手截出来的一块:

\[ G_{\mathcal T}=H_{\mathcal T}^{-1}, \qquad\text{通常并不等于}\qquad (G_F)_{\mathcal T,\mathcal T}. \]

先用 \(2\times2\) 反例把区别钉死。设

\[ H_F=\begin{bmatrix}2&1\\1&2\end{bmatrix}, \qquad G_F=H_F^{-1}=\frac13\begin{bmatrix}2&-1\\-1&2\end{bmatrix}. \]

冻结第 0 个坐标后,真正剩下的 Hessian 是 \([2]\),所以新逆矩阵应为 \([1/2]\);但直接截取 \(G_F\) 右下角会得到 \([2/3]\),显然不对。正确的 rank-1 修正恰好给出

\[ \frac23-\frac{(-1/3)^2}{2/3}=\frac12. \]

下面把正在固定的坐标临时换到第一个位置。这个换序只为书写方便,不改变结论。把两个矩阵分块:

\[ H_F= \begin{bmatrix} \alpha & \beta^\top\\ \beta & C \end{bmatrix}, \qquad G_F=H_F^{-1}= \begin{bmatrix} \gamma & v^\top\\ v & V \end{bmatrix}. \tag{24} \]

其中 \(\alpha,\gamma\) 是标量,\(\beta,v\in\mathbb R^{k-1}\)\(C,V\in\mathbb R^{(k-1)\times(k-1)}\)。这里 \(C\) 正是冻结 \(p\) 后的 Hessian \(H_{\mathcal T}\),我们希望求 \(C^{-1}\)

利用 \(H_FG_F=I\) 的左下和右下分块:

\[ \beta\gamma+Cv=0,\qquad \beta v^\top+CV=I. \tag{25} \]

于是

\[ \begin{aligned} C\left(V-\frac{vv^\top}{\gamma}\right) &=CV-\frac{(Cv)v^\top}{\gamma}\\ &=(I-\beta v^\top)-\frac{(-\beta\gamma)v^\top}{\gamma}\\ &=I. \end{aligned} \]

所以

\[ \boxed{ G_{\mathcal T} =(G_F)_{\mathcal T,\mathcal T} -\frac{(G_F)_{\mathcal T,p}(G_F)_{p,\mathcal T}}{[G_F]_{pp}} }. \tag{26} \]

这就是 GPTQ 在冻结当前坐标后使用的“逆 Hessian 消元”,也称rank-1 downdate。它也可以先写成完整矩阵:

\[ \widetilde G_F =G_F-\frac{(G_F)_{:,p}(G_F)_{p,:}}{[G_F]_{pp}}, \qquad G_{\mathcal T}=(\widetilde G_F)_{\mathcal T,\mathcal T}. \tag{27} \]

外积会使被冻结坐标对应的行和列变成零;随后把它删掉即可。形状完全匹配:

计算 输入 shape 输出 shape 意义
\((G_F)_{:,p}\) \([k,1]\) \([k,1]\) 当前坐标的逆曲率列
\((G_F)_{:,p}(G_F)_{p,:}\) \([k,1]\cdot[1,k]\) \([k,k]\) 本轮消去的 rank-1 信息
式 (26) 的外积 \([k-1,1]\cdot[1,k-1]\) \([k-1,k-1]\) 剩余自由空间的校正
\(G_{\mathcal T}\) \([k-1,k-1]\) \([k-1,k-1]\) 下一轮真正应使用的逆 Hessian

这一步只由 \(X\)、阻尼和处理顺序决定,不依赖某一行的具体量化误差。因此 \(m\) 个输出行可以共享同一次消元,这正是 GPTQ 能并行化的地基。

七、GPTQ 块补偿所需的多约束通式(理论)

上一节只有一个硬约束:指定某个坐标必须移动 \(-\rho_p\)。现在把它推广到一组坐标。这节只讨论一个数学问题:既然这 \(s\) 个坐标必须发生指定变化,其他坐标应该怎样补偿,才能让二次代价最小?

7.1. 问题引入

当前自由子问题有 \(k\) 个局部坐标。设\(\mathcal B\subset\mathcal I_F\) 是含有 \(s\)互异局部索引的集合,并固定一个枚举顺序 \((b_1,\ldots,b_s)\)。于是

\[ s=|\mathcal B|, \qquad \mathcal T=\mathcal I_F\setminus\mathcal B. \]

这里 \(1\le s\le k\)\(\mathcal B\) 的枚举顺序\((b_1,\ldots,b_s)\) 也就是向量和矩阵切片的排列顺序。

对每个局部标准基向量 \(\mathbf e_{b_i}\),组成选择矩阵

\[ S_{\mathcal B} =[\mathbf e_{b_1},\ldots,\mathbf e_{b_s}] \in\mathbb R^{k\times s}. \]

它的作用只是“从一个 \(k\) 维向量中挑出 \(\mathcal B\) 的分量”:

\[ S_{\mathcal B}^{\top}\delta = \begin{bmatrix} \delta_{b_1}\\\vdots\\\delta_{b_s} \end{bmatrix} \in\mathbb R^{s\times1}. \]

后面的 \(a_{\mathcal B}\)\(q_{\mathcal B}\) 和矩阵切片都采用同一个枚举顺序,因此各分量始终一一对应。

若当前工作权重和已经选定的量化落点分别为\(w_{\mathrm{work},\mathcal B}\)\(q_{\mathcal B}\),那么必须发生的变化量

\[ a_{\mathcal B} :=q_{\mathcal B}-w_{\mathrm{work},\mathcal B} =-\rho_{\mathcal B} \in\mathbb R^{s\times1}. \]

其中\(\rho_{\mathcal B}:=w_{\mathrm{work},\mathcal B}-q_{\mathcal B}\in\mathbb R^{s\times1}\),各分量按\((b_1,\ldots,b_s)\) 排列。注意 \(a_{\mathcal B}\) 不是量化后的绝对权重 \(q_{\mathcal B}\),而是“从当前值走到落点要走多远”。因此问题的输入、输出是:

对象 shape 在问题中的角色
\(H_F\) \([k,k]\) 已知的二次代价矩阵
\(S_{\mathcal B}\) \([k,s]\) 已知的选坐标工具
\(a_{\mathcal B}\) \([s,1]\) 已知的 \(s\) 个强制变化量
\(\delta\) \([k,1]\) 待求的完整变化,包括补偿
\(S_{\mathcal B}^{\top}\delta\) \([s,k][k,1]\to[s,1]\) 必须等于 \(a_{\mathcal B}\)

于是精确问题是

\[ \begin{aligned} \min_{\delta\in\mathbb R^k}\quad &\frac12\delta^\top H_F\delta\\ \text{s.t.}\quad&S_{\mathcal B}^{\top}\delta=a_{\mathcal B}. \end{aligned} \tag{28} \]

用一句话说:\(\mathcal B\) 中的坐标不能讨价还价,它们的改变量已经给定;\(\mathcal T\) 中的 \(k-s\) 个坐标可以自由移动,专门用来减小损失。这个约束总是可行的,例如令被选坐标等于 \(a_{\mathcal B}\)、其余坐标等于 0;又因为 \(H_F\succ0\),可行集合上的严格凸二次函数只有一个全局最小点。若 \(s=1\)\(S_{\mathcal B}=\mathbf e_p\)\(a_{\mathcal B}=-\rho_p\),式 (28) 就退化为式 (14),所以它不是另一个互不相干的问题,而是单坐标结论的直接推广。

7.2. 一个可以手算的三维例子

\(k=3\),并设

\[ H_F= \begin{bmatrix} 2&1&0\\ 1&2&1\\ 0&1&2 \end{bmatrix}, \qquad \mathcal B=\{0,2\}, \qquad \mathcal T=\{1\}, \qquad a_{\mathcal B}=\begin{bmatrix}-1\\-1\end{bmatrix}. \]

可以把它想成:两个被固定的当前工作值都是 \(1\),量化器已经为它们选好了落点 \(0\),所以各自必须改变 \(-1\);这里故意选整数只是为了让手算更清楚。

这个 \(H_F\) 正定(它的三个顺序主子式是 \(2,3,4\)),因此确实是一个只有一个谷底的二次代价。选择矩阵为

\[ S_{\mathcal B}= \begin{bmatrix} 1&0\\ 0&0\\ 0&1 \end{bmatrix}:[3,2]. \]

\(\delta=[\delta_0,\delta_1,\delta_2]^\top\),则

\[ S_{\mathcal B}^{\top}\delta =\begin{bmatrix}\delta_0\\\delta_2\end{bmatrix} =\begin{bmatrix}-1\\-1\end{bmatrix}. \]

所以 \(\delta_0\)\(\delta_2\) 已经被固定,只有中间的 \(\delta_1\) 能 调节。写成 \(\delta=[-1,t,-1]^\top\),逐步计算:

\[ H_F\delta =\begin{bmatrix}t-2\\2t-2\\t-2\end{bmatrix}, \]
\[ \begin{aligned} \frac12\delta^\top H_F\delta &=\frac12 \begin{bmatrix}-1&t&-1\end{bmatrix} \begin{bmatrix}t-2\\2t-2\\t-2\end{bmatrix}\\ &=t^2-2t+2\\ &=(t-1)^2+1. \end{aligned} \]

平方项在 \(t=1\) 时为 0,所以

\[ \delta^*=\begin{bmatrix}-1\\1\\-1\end{bmatrix}, \qquad \Delta\ell^*=1. \]

这说明“补偿”不是口号:如果让中间坐标不动(\(t=0\)),代价是 \(2\);让它跟着移动 \(+1\),代价降到 \(1\)。两个被固定的坐标仍然 严格满足各自的变化量。

7.3. 通式、推导与具体计算步骤

我们直接从式 (28) 出发,先求出满足约束的最优 \(\delta\)

理论输入:

shape 含义
\(H_F\) \([k,k]\) 当前 \(k\) 个自由坐标的正定 Hessian
\(G_F=H_F^{-1}\) \([k,k]\) \(H_F\) 派生的逆 Hessian;用来传播补偿
\(S_{\mathcal B}\) \([k,s]\) 选择矩阵,抽出要同时固定的 \(s\) 个坐标
\(a_{\mathcal B}\) \([s,1]\) \(s\) 个坐标必须发生的变化量

纯数学问题把 \(a_{\mathcal B}\) 当作已知约束向量;\(G_F\) 是由\(H_F\) 求出的派生量,并不是另一个独立假设。工程中如何由量化落点和工作权重构造 \(a_{\mathcal B}\)

输出:

  1. 完整最优变化 \(\delta^*:[k,1]\)
  2. 这次变化的最小二次代价 \(\Delta\ell_{\mathcal B}^*\)(标量);
  3. 冻结 \(\mathcal B\) 后,剩余坐标 \(\mathcal T=\mathcal I_F\setminus\mathcal B\) 使用的 \(G_{\mathcal T}:[k-s,k-s]\)

从原问题开始推导。 式 (28) 是

\[ \begin{aligned} \min_{\delta\in\mathbb R^k}\quad &\frac12\delta^\top H_F\delta,\\ \text{s.t.}\quad &S_{\mathcal B}^{\top}\delta=a_{\mathcal B}. \end{aligned} \]

未知量只有 \(\delta:[k,1]\)。目标要求二次代价尽量小,约束要求 \(\mathcal B\) 中的 \(s\) 个坐标精确移动到指定位置。为了同时处理这\(s\) 条等式,给每条约束配一个乘子,组成\(\nu:[s,1]\)。拉格朗日函数为

\[ \mathcal J(\delta,\nu) =\frac12\delta^\top H_F\delta +\nu^\top(S_{\mathcal B}^{\top}\delta-a_{\mathcal B}). \]

分别对两个未知量求导。对 \(\delta\) 的梯度 shape 是 \([k,1]\);对\(\nu\) 求导,会原样取回 \(s\) 条约束:

\[ \begin{cases} H_F\delta+S_{\mathcal B}\nu=0,\\ S_{\mathcal B}^{\top}\delta=a_{\mathcal B}. \end{cases} \]

最优解必须同时满足这两行。先用第一行消去 \(\delta\)。左乘\(G_F=H_F^{-1}\),得到

\[ \delta=-G_FS_{\mathcal B}\nu. \]

这一步已经说明:最优 \(\delta\) 一定是 \(G_FS_{\mathcal B}\) 的各列的线性组合,只是组合系数 \(-\nu\) 还不知道。把它代回第二行:

\[ -S_{\mathcal B}^{\top}G_FS_{\mathcal B}\nu =a_{\mathcal B}. \]

现在 \(\Gamma\) 才自然出现:它就是上式中乘在 \(\nu\) 前面的系数矩阵。 定义

\[ \Gamma =S_{\mathcal B}^{\top}G_FS_{\mathcal B} =(G_F)_{\mathcal B,\mathcal B} \in\mathbb R^{s\times s}. \tag{29} \]

为什么最后一个等号成立?右乘 \(S_{\mathcal B}\)\(G_F\) 中选出\(\mathcal B\) 对应的 \(s\) 列,左乘 \(S_{\mathcal B}^{\top}\) 再选出同样的 \(s\) 行,所以结果正是 \(G_F\)\(\mathcal B\times\mathcal B\) 主子阵。整个乘法的 shape 是\([s,k][k,k][k,s]\to[s,s]\)

还不能直接写 \(\Gamma^{-1}\),必须先证明它可逆。对任意非零\(z\in\mathbb R^s\),选择矩阵的列互异,所以\(S_{\mathcal B}z\ne0\);又因为 \(G_F\succ0\),有

\[ z^\top\Gamma z =(S_{\mathcal B}z)^\top G_F(S_{\mathcal B}z)>0. \]

因此 \(\Gamma\succ0\),必然可逆。令

\[ y:=-\nu\in\mathbb R^{s\times1}, \]

刚才的方程就变成

\[ \Gamma y=a_{\mathcal B}, \qquad \Gamma:[s,s],\ a_{\mathcal B}:[s,1],\ y:[s,1]. \]

所以 \(y\) 不是凭空定义的算法变量,而是拉格朗日乘子的相反数。它的第 \(i\) 个分量说明要把 \(G_F\) 的第 \(b_i\) 列取多少。把\(y=-\nu\) 代回 \(\delta=-G_FS_{\mathcal B}\nu\),得到

\[ \delta^*=G_FS_{\mathcal B}y =G_FS_{\mathcal B}\Gamma^{-1}a_{\mathcal B}. \]

接着求最小代价,由\(H_FG_F=I_k\)

\[ H_F\delta^* =H_FG_FS_{\mathcal B}y =S_{\mathcal B}y. \]

再利用原约束\(S_{\mathcal B}^{\top}\delta^*=a_{\mathcal B}\)

\[ \begin{aligned} \frac12(\delta^*)^\top H_F\delta^* &=\frac12(\delta^*)^\top S_{\mathcal B}y\\ &=\frac12(S_{\mathcal B}^{\top}\delta^*)^\top y\\ &=\frac12a_{\mathcal B}^{\top}y\\ &=\frac12a_{\mathcal B}^{\top}\Gamma^{-1}a_{\mathcal B}. \end{aligned} \]

至此才得到完整通式:

\[ \boxed{ \delta^*=G_FS_{\mathcal B}\Gamma^{-1}a_{\mathcal B} }, \qquad \boxed{ \Delta\ell_{\mathcal B}^* =\frac12a_{\mathcal B}^{\top}\Gamma^{-1}a_{\mathcal B} }. \tag{30} \]

第一条的 shape 是\([k,k][k,s][s,s][s,1]\to[k,1]\)。第二条的 shape 是\([1,s][s,s][s,1]\to\) 标量。

任取另一个满足式 (28) 的可行解 \(\widetilde\delta=\delta^*+h\)。两者满足同一组约束,所以 \(S_{\mathcal B}^{\top}h=0\)。于是

\[ \begin{aligned} \frac12\widetilde\delta^\top H_F\widetilde\delta -\frac12(\delta^*)^\top H_F\delta^* &=h^\top H_F\delta^*+\frac12h^\top H_Fh\\ &=h^\top S_{\mathcal B}\Gamma^{-1}a_{\mathcal B} +\frac12h^\top H_Fh\\ &=(S_{\mathcal B}^{\top}h)^\top\Gamma^{-1}a_{\mathcal B} +\frac12h^\top H_Fh\\ &=\frac12h^\top H_Fh. \end{aligned} \]

只要 \(\widetilde\delta\ne\delta^*\),就有 \(h\ne0\);由\(H_F\succ0\),最后一项严格大于 0。因此式 (30) 给出的确实是式(28) 的唯一全局最小解

三个步骤整理如下:

  1. 计算 \(\Gamma=S_{\mathcal B}^{\top}G_FS_{\mathcal B}\)。它来自把 \(\delta=-G_FS_{\mathcal B}\nu\) 代回原约束后的系数矩阵;
  2. \(\Gamma y=a_{\mathcal B}\)。其中 \(y=-\nu\),是使 \(s\) 条硬约束 同时成立所需的混合系数;
  3. 计算 \(\delta^*=G_FS_{\mathcal B}y\),再计算 \(\Delta\ell_{\mathcal B}^*=\tfrac12a_{\mathcal B}^{\top}y\)。 前者把 \(s\) 个系数传播到全部 \(k\) 个坐标,后者是该可行解的最小 二次代价。

这三步只解决本轮的最优补偿和代价。冻结 \(\mathcal B\) 以后,下一轮究竟应使用哪个逆 Hessian,还需要从块矩阵关系单独推出,不能提前当作已知公式。下文先用三维例子核对本轮解,再完成这个推导。

现在用刚才的例子验证式 (30)。有

\[ G_F=H_F^{-1} =\frac14\begin{bmatrix} 3&-2&1\\ -2&4&-2\\ 1&-2&3 \end{bmatrix}, \qquad \Gamma=\frac14\begin{bmatrix}3&1\\1&3\end{bmatrix}, \qquad \Gamma^{-1}=\begin{bmatrix}\frac32&-\frac12\\-\frac12&\frac32\end{bmatrix}. \]

因此

\[ \Gamma^{-1}a_{\mathcal B}=\begin{bmatrix}-1\\-1\end{bmatrix}, \qquad G_FS_{\mathcal B}\Gamma^{-1}a_{\mathcal B} =G_F\begin{bmatrix}-1\\0\\-1\end{bmatrix} =\begin{bmatrix}-1\\1\\-1\end{bmatrix}, \]

并且 \(\frac12a_{\mathcal B}^{\top}\Gamma^{-1}a_{\mathcal B}=1\),与直接配方完全一致。

冻结 \(\mathcal B\) 后,剩余坐标是 \(\mathcal T\)。下一轮真正需要的逆 Hessian 不是简单截取,而是

\[ \boxed{ G_{\mathcal T}=(G_F)_{\mathcal T,\mathcal T} -(G_F)_{\mathcal T,\mathcal B}\Gamma^{-1}(G_F)_{\mathcal B,\mathcal T} }. \tag{31} \]

为了不把“Schur 补”当成一句黑箱术语,下面把式 (31) 的两行块乘法写出来。令

\[ P=[\mathbf e_{b_1},\ldots,\mathbf e_{b_s}, \mathbf e_{t_1},\ldots,\mathbf e_{t_{k-s}}]. \]

\(P\) 的列按\((\mathcal B,\mathcal T)\) 排列;于是 \(P^\top\) 把原坐标换成该顺序,则

\[ P^\top H_FP =\begin{bmatrix} H_{\mathcal B,\mathcal B}&H_{\mathcal B,\mathcal T}\\ H_{\mathcal T,\mathcal B}&H_{\mathcal T,\mathcal T} \end{bmatrix}, \qquad P^\top G_FP =\begin{bmatrix} \Gamma&(G_F)_{\mathcal B,\mathcal T}\\ (G_F)_{\mathcal T,\mathcal B}&(G_F)_{\mathcal T,\mathcal T} \end{bmatrix}, \]

为简写,下面把四个 Hessian 分块和四个逆 Hessian 分块直接用上下标表示;它们的 shape 依次是 \([s,s],[s,k-s],[k-s,s],[k-s,k-s]\)。又因为 \(G_F\) 对称,\((G_F)_{\mathcal T,\mathcal B}=(G_F)_{\mathcal B,\mathcal T}^{\top}\)。由\((P^\top H_FP)(P^\top G_FP)=I_k\),它的下左、下右两个分块分别是

\[ H_{\mathcal T,\mathcal B}\Gamma +H_{\mathcal T,\mathcal T}(G_F)_{\mathcal B,\mathcal T}^{\top} =0:[k-s,s], \qquad H_{\mathcal T,\mathcal B}(G_F)_{\mathcal B,\mathcal T} +H_{\mathcal T,\mathcal T}(G_F)_{\mathcal T,\mathcal T} =I_{k-s}:[k-s,k-s]. \]

用第一行消去\(H_{\mathcal T,\mathcal T}(G_F)_{\mathcal B,\mathcal T}^{\top}\)

\[ \begin{aligned} H_{\mathcal T,\mathcal T} \Big((G_F)_{\mathcal T,\mathcal T} -(G_F)_{\mathcal B,\mathcal T}^{\top} \Gamma^{-1}(G_F)_{\mathcal B,\mathcal T}\Big) &=H_{\mathcal T,\mathcal T}(G_F)_{\mathcal T,\mathcal T}\\ &\quad-H_{\mathcal T,\mathcal T} (G_F)_{\mathcal B,\mathcal T}^{\top} \Gamma^{-1}(G_F)_{\mathcal B,\mathcal T}\\ &=(I_{k-s} -H_{\mathcal T,\mathcal B}(G_F)_{\mathcal B,\mathcal T})\\ &\quad-(-H_{\mathcal T,\mathcal B}\Gamma) \Gamma^{-1}(G_F)_{\mathcal B,\mathcal T}\\ &=I_{k-s}. \end{aligned} \]

由于 \(H_{\mathcal T,\mathcal T}\) 正定,它可逆,所以括号内正是\(H_{\mathcal T,\mathcal T}^{-1}\);恢复原坐标顺序就得到式 (31)。因此 \(G_{\mathcal T}\) 的准确含义是

\[ H_{\mathcal T}G_{\mathcal T}=I_{k-s}, \]

即“冻结 \(\mathcal B\) 后剩余 Hessian 的逆”,不是旧矩阵\(G_F\) 的右下角直接截取。式 (31) 的修正项 shape 为\([k-s,s][s,s][s,k-s]\to[k-s,k-s]\)。在数值例中,

\[ G_{\mathcal T}=1- \begin{bmatrix}-\frac12&-\frac12\end{bmatrix} \Gamma^{-1} \begin{bmatrix}-\frac12\\-\frac12\end{bmatrix} =\frac12, \]

而剩余 Hessian 是 \([2]\),其逆正好也是 \([1/2]\)

把本节推导整理成完整的数学计算顺序。 前 6 步来自式 (29)、(30),第 7 步来自刚刚证明的式 (31):

  1. 把已知约束向量 \(a_{\mathcal B}:[s,1]\)\((b_1,\ldots,b_s)\) 的顺序排列;它表示这 \(s\) 个坐标各自必须发生的变化。
  2. \(G_F:[k,k]\) 抽取列块\((G_F)_{:,\mathcal B}:[k,s]\),并计算\(\Gamma=(G_F)_{\mathcal B,\mathcal B}:[s,s]\)
  3. \(\Gamma y=a_{\mathcal B}\)\([s,s]\,[s,1]\to[s,1]\)。因为 \(\Gamma\succ0\),解唯一。
  4. 计算 \(\delta^*=(G_F)_{:,\mathcal B}y\)\([k,s]\,[s,1]\to[k,1]\)。其中 \(\delta_{\mathcal B}^*\) 是硬约束指定的变化,\(\delta_{\mathcal T}^*\) 是最优连续补偿。
  5. 验证\(S_{\mathcal B}^{\top}\delta^*:[s,k][k,1]\to[s,1]\)等于 \(a_{\mathcal B}\),确认所有硬约束确实成立。
  6. 计算\(\Delta\ell_{\mathcal B}^*=\tfrac12a_{\mathcal B}^{\top}y\)\([1,s][s,1]\to\) 标量,得到本轮的最小二次代价。
  7. 若还要继续下一轮,解\(\Gamma Z=(G_F)_{\mathcal B,\mathcal T}\),其中\(Z:[s,k-s]\);再计算

    \[ G_{\mathcal T}=(G_F)_{\mathcal T,\mathcal T} -(G_F)_{\mathcal T,\mathcal B}Z. \]

修正项 shape 为 \([k-s,s][s,k-s]\to[k-s,k-s]\)

以上是从约束问题严格推出的数学顺序。量化器怎样逐列产生\(q_{\mathcal B}\),以及 \(\delta^*\) 怎样写回多行工作矩阵,仍放在下一章的工程语境中处理。

本节结论。 本节开头的问题是:当 \(\mathcal B\) 中的 \(s\) 个坐标必须发生指定变化 \(a_{\mathcal B}\) 时,其余坐标怎样补偿才最省代价?

  1. \(\Gamma=(G_F)_{\mathcal B,\mathcal B}\),解 \(\Gamma y=a_{\mathcal B}\)
  2. \(\delta^*=(G_F)_{:,\mathcal B}y\) 得到唯一全局最优补偿, 最小代价是 \(\tfrac12a_{\mathcal B}^{\top}y\)
  3. 冻结 \(\mathcal B\) 后,用式 (31) 得到下一轮真正需要的 \(G_{\mathcal T}\),不能直接截取旧 \(G_F\) 的剩余子块。

三维例子同时验证了约束、最优代价和下一轮逆 Hessian。以上仍是纯数学:它把 \(a_{\mathcal B}\) 当作已知量,并不要求 GPTQ 联合枚举一个块的量化组合。下一章负责说明 GPTQ 怎样逐列得到量化落点、构造 \(a_{\mathcal B}\),再把这个结论应用到所有输出行。

八、工程实现:GPTQ 怎样把二阶补偿变成高效矩阵计算

从本章开始进入工程语境:前一章的 \(a_{\mathcal B}\) 在这里由量化器实际选出的落点和工作矩阵计算出来;下面还会说明列顺序、Cholesky、block 和 lazy update 怎样把理论公式组织成可运行的 GPTQ。

8.1. 让所有输出行共享同一列顺序

GPTQ 为一个 Linear 层的所有 \(m\) 个输出行规定同一个输入特征处理顺序

\[ \pi=(\pi_0,\pi_1,\ldots,\pi_{d-1}). \]

尚未显式置换矩阵的写法中,第 \(j\) 步统一量化\((W_{\mathrm{work}})_{:,\pi_j}\);在后文代码采用的先置换矩阵写法中,同一列已经被搬到局部位置 \(j\),所以写成\((W'_{\mathrm{work}})_{:,j}\)。两种写法处理的是同一个原始输入特征\(\pi_j\),不能把原始列号 \(\pi_j\) 与置换后的局部位置 \(j\) 混用。所有行同时处理同一个输入特征,会带来三件直接结果:

  1. 所有输出行的自由集合完全一致,故共享 \(H_\lambda\)\(G\)、消元和 Cholesky 因子。
  2. 在置换后的局部坐标中,一列 \((W'_{\mathrm{work}})_{:,j}\in\mathbb R^m\) 可以一次量化,残差 向量也能一次补偿所有 \(m\) 行。
  3. 大量标量更新可以合并为外积和 GEMM;二阶预处理是 \(O(d^3)\),全部权重的补偿主循环是\(O(md^2)\),主导复杂度合起来约为 \(O(\max\{md^2,d^3\})\)

这里要把“最优”的范围说准确:GPTQ 在给定共享顺序、已经选定当前量化落点、其余坐标仍可连续移动的条件下,求式 (23) 的局部二次最优补偿;它不保证找到式 (1) 所有 INT4 组合中的全局最优解。

8.2. 把纯数学结论应用到多行工作矩阵

现在把前一章的多约束数学结论应用到所有输出行。令\(W_F\in\mathbb R^{m\times k}\) 表示当前 \(k\) 个自由列按处理顺序组成的工作矩阵视图;实际计算块记为 \(\mathcal B\),实际宽度为 \(b'\)。 这时把前一章的数学约束数具体取成

\[ s=b'. \]

在这个局部视图里,\(\mathcal B\) 表示当前剩余自由列中的前 \(b'\)个位置;它们对应的原始全局列标签另行保存,不能把两种索引混用。

一块数据如何流动

下面把前一章的数学变量落到代码,作为验证块入口/出口公式的清单。它说明输入输出和数据流,但经典 GPTQ 的高效实现通常用后面Cholesky 章的逐列、lazy 内核,不会在每个 block 重新做一次这个矩阵方程。这里 \(b'\) 是当前 block 的实际宽度,最后一个 block可能小于设定的 block size。

  1. 取块入口状态\(W_F^{\mathrm{in}}:[m,k]\)\(G_F:[k,k]\) 和当前列集合\(\mathcal B\)(宽度 \(b'\))。
  2. 在块内仍按列处理:每次把当前工作列\(w_j:[m,1]\) 交给量化器,得到对应落点 \(q_j:[m,1]\);得到后立即按单列补偿公式更新块内尚未处理的工作列,下一列从更新后的当前值继续量化。把所有已经确定的列按处理顺序拼成 \(Q_{\mathcal B}:[m,b']\)。这里不是一次枚举整块组合,而是逐列收集最终落点。
  3. 用块入口值计算残差\(D=(W_F^{\mathrm{in}})_{:,\mathcal B}-Q_{\mathcal B}:[m,b']\)
  4. \(\Gamma=(G_F)_{\mathcal B,\mathcal B}:[b',b']\),解矩阵方程\(\Gamma Z_D=D^\top\),输入输出\([b',b']\,[b',m]\to[b',m]\);实现中用线性方程求解器,不显式形成逆矩阵。
  5. 因为 \(G_F\) 对称,\(Z_D^\top=D\Gamma^{-1}:[m,b']\)。先计算补偿矩阵,再写出块出口:

    \[ \begin{aligned} \Delta W_F^{\mathrm{comp}} &=Z_D^\top(G_F)_{\mathcal B,:} :[m,b']\,[b',k]\to[m,k],\\ W_F^{\mathrm{out}} &=W_F^{\mathrm{in}}-\Delta W_F^{\mathrm{comp}} :[m,k]. \end{aligned} \]
  6. 验收两个结果:块内列\((W_F^{\mathrm{out}})_{:,\mathcal B}\) 应等于\(Q_{\mathcal B}:[m,b']\);块外尾部只发生补偿,shape 为\([m,k-b']\)。随后再把剩余列交给下一 block。

上面的清单先给出了数据流;下面专门证明其中第 4、5 步为什么等于前一章的通式。先再次强调计算顺序:GPTQ 在块内逐列运行量化器,得到已经选定的反量化落点\(Q_{\mathcal B}\in\mathbb R^{m\times b'}\);它不会用式 (28) 联合搜索整块量化点。只有这些落点确定以后,才定义相对块入口工作值的残差矩阵

\[ D=(W_F^{\mathrm{in}})_{:,\mathcal B}-Q_{\mathcal B} \in\mathbb R^{m\times b'}. \]

对第 \(r\) 个输出行,式 (28) 中规定的变化量是

\[ a_{\mathcal B}^{(r)} =Q_{r,\mathcal B}-(W_F^{\mathrm{in}})_{r,\mathcal B} =-D_{r,:}^{\top}\in\mathbb R^{b'\times1}. \]

把式 (30) 独立应用到 \(m\) 个输出行,再把这些行上下堆起来,得到

\[ \boxed{ W_F^{\mathrm{out}} =W_F^{\mathrm{in}} -D\,\Gamma^{-1}(G_F)_{\mathcal B,:} }. \tag{32} \]

这里

\[ \underbrace{D}_{[m,b']} \underbrace{\Gamma^{-1}}_{[b',b']} \underbrace{(G_F)_{\mathcal B,:}}_{[b',k]} \longrightarrow[m,k], \]

恰好与 \(W_F^{\mathrm{in}}\)\(W_F^{\mathrm{out}}:[m,k]\) 同 shape。这里明确从块入口状态算到块出口状态,不能把右侧误读成“已经完成块内更新后的 \(W_F\)”,否则会把同一补偿减两次。所有输出行的最小二次代理代价之和为

\[ \Delta\mathcal L_{\lambda,\mathcal B}^* =\frac12\operatorname{tr}\left(D\Gamma^{-1}D^\top\right). \tag{33} \]

其中 \(D:[m,b']\)\(\Gamma^{-1}:[b',b']\)\(D^\top:[b',m]\),所以\(D\Gamma^{-1}D^\top:[m,m]\),再取\(\operatorname{tr}:[m,m]\to\) 标量;这个标量等于 \(m\) 个输出行的二次代价之和。

本小节结论。 式 (32) 已经把单行解析解严格堆叠成了\(m\) 行的块入口到块出口公式;式 (33) 则把 \(m\) 行代价相加成一个标量。二者都要求 \(Q_{\mathcal B}\) 已由块内逐列量化确定,不能反过来把式 (32) 当作联合搜索量化点的公式。它们也还不是 lazy update 的实际内核;Cholesky 章的式 (47)--(49) 会证明,块末尾一次执行的\(ER_{\mathcal B\mathcal T}\) 正好等于式 (32) 的尾部切片。

8.3. act-order 本质上只是一次一致的置换

常见实现会把 \(\operatorname{diag}(H)\) 较大的输入特征放到前面处理,俗称 act-order。因为

\[ H_{jj}=\frac2N\sum_{t=0}^{N-1}X_{jt}^2, \]

对角元大表示第 \(j\) 个输入特征在校准数据中经常具有较大幅值。act-order 的直觉是:先处理这些敏感特征,让它们在剩余自由度最多时获得补偿。它是经验启发式,不是全局最优顺序的证明。

\(\Pi\in\{0,1\}^{d\times d}\) 是由 \(\pi\) 构成的置换矩阵,则

\[ W'=W\Pi^\top,\qquad X'=\Pi X,\qquad H_\lambda'=\Pi H_\lambda\Pi^\top. \tag{34} \]

形状是

\[ [m,d][d,d]\to[m,d],\qquad [d,d][d,N]\to[d,N],\qquad [d,d][d,d][d,d]\to[d,d]. \]

并且

\[ W'X'=(W\Pi^\top)(\Pi X)=WX. \tag{35} \]

所以它不是换了一个层,只是把“先处理哪个输入特征”的顺序显式搬到矩阵索引上。量化完成后,对\(\hat W'\) 做反置换即可回到原列顺序。注意:只排序 \(W\) 而忘记同样排序 \(H\)(或 \(X\))会让二阶统计量与权重列错位,是非常隐蔽的实现错误。

若同时使用 group quantization,必须先问一句:group 是在原始列顺序中预先拟合,还是在置换后的处理顺序中动态拟合?两种契约不能混用。

对 static groups,先在原始连续列上拟合各组的 scale/zero。置换后位置 \(j\) 对应原列 \(\pi_j\),所以量化时应选择

\[ \mathrm{static\_group}(j) =\left\lfloor\frac{\pi_j}{g}\right\rfloor. \]

这里的 \(j\)量化时的置换位置,不是部署时的原列号。把 \(K\)\(\hat W\) 反置换后,原列\(k\) 仍自然属于 \(\lfloor k/g\rfloor\) 组,因此部署端继续使用普通连续 group 布局,不需要额外的非平凡映射。这正是 static groups 能与 act-order 共用而不改变推理格式的原因。

对 dynamic groups,网格按置换后的连续处理位置 \(j\) 拟合。反置换后,原列 \(\pi_j\) 属于处理组 \(\lfloor j/g\rfloor\),所以必须保存

\[ \mathrm{group\_id}[\pi_j] =\left\lfloor\frac{j}{g}\right\rfloor, \qquad \mathrm{group\_id}\in\mathbb Z^d. \]

部署端必须支持这份映射,或把权重和元数据重新打包成等价布局;否则只反置换码矩阵会把权重和网格配错。

8.4. 4-bit 网格:量化器究竟返回什么

读前准备已经演示了一个标量。对某个输出行的某个 group,给定正 scale \(\Delta_q\) 和整数 zero point \(z_q\),同一公式写成

\[ \begin{aligned} c&=\operatorname{clip}\left( \operatorname{round}\left(\frac{w}{\Delta_q}\right)+z_q,\ 0,\ 15 \right),\\ q=\mathcal Q(w)&=\Delta_q(c-z_q). \end{aligned} \tag{36} \]

这里 \(c\) 是真正要打包存储的 4-bit 无符号码,\(q\) 是仍以 FP16/FP32 表示、供补偿计算使用的反量化值。某些硬件接口会把等价的 16 个码写成 \(-8,\ldots,7\);只要 scale、zero 和反量化公式同步,数学上是同一个有限网格。

对象 输入 shape 输出 shape 含义
当前列 \(w=W_{\mathrm{work}}[:,j]\) \([m]\) \([m]\) 所有输出行在第 \(j\) 个输入特征上的工作权重
量化器 \([m]\) \(c:[m]\)\(q:[m]\) 4-bit 码与反量化值 \(q=\mathcal Q(w)\)
scale、zero 通常按行/组统计 \([m,n_g]\) 每一行的每个 group 各有一组元数据
最终 \(K\) \([m,d]\) \([m,d]\) 每元素 4 bit 的整数码

论文中的补偿推导把当前量化网格视为已知。这个前提在 \(g=-1\)(一整行一个网格)或提前估计好的 static group 下最清楚。工程实现若使用 \(g>0\),有时会在每个 group 开始时根据当前工作权重重新估计 scale/zero;这会额外引入网格更新的启发式。不要把“固定网格下的代数等价”误读为“所有动态 group 实现都逐位相同”。

8.5. 最后分清三个矩阵角色

到这里可以把对象彻底分开:

名称 是否随输出行变化 是否在量化中变化 作用
\(X,H_\lambda,G,R\) 否,所有行共享 数值不变;列循环只读取相应尾部视图 输入统计与补偿系数
\(W_{\mathrm{work}}\) 是,shape 恒为 \([m,d]\) 尚未量化列会不断接收补偿
\(K,\hat W\) 已量化列只写一次 最终 INT4 码与反量化结果

读代码时最常见的混乱是把 \(W_{\mathrm{work}}\) 当成最终 \(\hat W\)。前者是“为了给后续列留出补偿空间”的浮点工作副本;一列被量化后,应把它的码和反量化值保存到输出,而不是在之后再次浮动修改它。

本章结论。 本章要回答的是“前面的单行数学公式怎样变成一层权重都能执行的计算”。答案分成四点:所有输出行共享同一个列顺序和同一套 \(H_\lambda,G\);每行仍使用自己的量化网格得到 \(q\);工作矩阵\(W_{\mathrm{work}}\) 接收补偿,而 \(K,\hat W\) 保存最终结果;act-order只改变处理位置,权重、Hessian 和 group 元数据必须使用同一套映射。式 (32) 给出了块入口到块出口的数学结果,下一章再把它改写成无需逐轮显式更新 \(G\) 的高效 Cholesky 计算。

九、Cholesky 形式:把重复消元变成一次预计算

前一章已经给出正确的块补偿公式,但若每量化一列都显式更新\(G^{(j)}\),计算和访存都会很重。本章只解决一个工程问题:怎样在不改变二阶补偿结果的前提下,预先计算一张可重复使用的“补偿表”?答案是把 \(G\) 分解为 \(R^\top R\)\(R\) 的尾块编码每一轮条件逆Hessian,\(R\) 的一行产生单列外积,多行缓存后产生块外 GEMM。

9.1. 上三角因子里已经包含了每一轮的消元结果

显式地按照式 (27) 反复更新 \(G\) 是正确的,但既耗时又容易积累数值误差。GPTQ 先对阻尼逆 Hessian 做上三角 Cholesky 分解:

\[ G=H_\lambda^{-1}=R^\top R, \qquad R\in\mathbb R^{d\times d}\text{ 为上三角矩阵}. \tag{37} \]

假设已经处理完前 \(j\) 列。把当前仍自由变量上的上三角尾部写成

\[ R_{j:,j:}= \begin{bmatrix} \tau & v^\top\\ 0 & R^+ \end{bmatrix}. \]

这里 \(\tau=R_{jj}>0\) 是标量,\(v:[d-j-1,1]\)\(R^+:[d-j-1,d-j-1]\);特意不用字母 \(r\),因为全文的 \(r\) 已表示输出行编号。

那么对应的当前逆 Hessian 为

\[ G^{(j)} =R_{j:,j:}^\top R_{j:,j:} = \begin{bmatrix} \tau^2 & \tau v^\top\\ \tau v & vv^\top+(R^+)^\top R^+ \end{bmatrix}. \tag{38} \]

\(G^{(j)}\) 的实际 shape 是 \([d-j,d-j]\);为便于和原矩阵、代码对齐,本文保留原始特征标签\(j,j+1,\ldots,d-1\)。因此后文写 \(G^{(j)}_{jj}\) 表示这个尾矩阵左上角元素;若改用局部数组下标,它就是 \(G^{(j)}_{0,0}\)

对第一个自由变量套用式 (26):

\[ \begin{aligned} G^{(j+1)} &=\left[vv^\top+(R^+)^\top R^+\right] -\frac{(\tau v)(\tau v)^\top}{\tau^2}\\ &=(R^+)^\top R^+. \end{aligned} \tag{39} \]

这就是关键。消去当前变量后,下一轮的 Cholesky 因子不需要重新算,它恰好就是 \(R\) 的右下角尾块。归纳地说:

\[ \boxed{ G^{(j)}=R_{j:,j:}^\top R_{j:,j:}. } \tag{40} \]

所以一个 \([d,d]\) 的 Cholesky 分解,预先编码了从第 0 列到第 \(d-1\) 列的所有逆 Hessian 消元状态。

9.2. 单列补偿如何变成一个外积

\(j\) 列量化前,定义所有输出行的原始量化误差

\[ \rho_j=W_{\mathrm{work}}[:,j]-Q_{:,j}\in\mathbb R^m. \]

由式 (40):

\[ G^{(j)}_{jj}=R_{jj}^2, \qquad G^{(j)}_{j,j:}=R_{jj}R_{j,j:}. \tag{41} \]

把 GPTQ 的单坐标二阶补偿写成所有输出行一起处理的行向量形式:

\[ \begin{aligned} W_{\mathrm{work}}[:,j:] &\leftarrow W_{\mathrm{work}}[:,j:] -\frac{\rho_j}{G^{(j)}_{jj}}G^{(j)}_{j,j:}\\ &= W_{\mathrm{work}}[:,j:] -\underbrace{\left(\frac{\rho_j}{R_{jj}}\right)}_{[m,1]} \underbrace{R_{j,j:}}_{[1,d-j]}. \end{aligned} \tag{42} \]

最后一行是外积,输出为 \([m,d-j]\)。它包含当前列:因为 \(R_{jj}/R_{jj}=1\),第 \(j\) 列恰好从\(W_{\mathrm{work}}[:,j]\) 变成 \(Q_{:,j}\);后面的列则收到补偿。

shape 作用
\(\rho_j\) \([m]\) 当前列量化前的误差,约定为 \(w-q\)
\(R_{jj}\) 标量 当前自由变量的 Cholesky 对角元
\(\eta_j=\rho_j/R_{jj}\) \([m]\) 代码中缓存的缩放残差;用希腊字母避免和标准基 \(\mathbf e_p\) 混淆
\(R_{j,j:}\) \([d-j]\) 当前列对自己和所有后续列的补偿系数
\(\eta_j[:,\mathrm{None}]\,R_{j,j:}[\mathrm{None},:]\) \([m,1]\cdot[1,d-j]\) \([m,d-j]\) 的列更新

单个输出行在这一步的阻尼代理损失增加为

\[ \Delta\ell_{\lambda,r,j} =\frac{\rho_{r,j}^2}{2R_{jj}^2}. \tag{43} \]

有些参考实现把 Cholesky 后的变量仍命名为类似 Hinv 的名字。概念上它此时是 \(R\),不是正在被显式更新的 \(G\);分母中的 \(R_{jj}^2\) 被拆成了一次除以 \(R_{jj}\) 和一次行外积。

把前面的二维例子也改用 \(R\) 核对一次:

\[ G= \begin{bmatrix}1&-1/2\\-1/2&1\end{bmatrix} =R^\top R, \qquad R= \begin{bmatrix} 1&-1/2\\ 0&\sqrt{3/4} \end{bmatrix}. \]

直接相乘可见 \(R^\top R=G\)。在 \(j=0\)\(\rho_0=0.12\) 时,\(\eta_0=\rho_0/R_{00}=0.12\),式 (42) 减去

\[ \eta_0R_{0,:}=0.12\begin{bmatrix}1&-1/2\end{bmatrix} =\begin{bmatrix}0.12&-0.06\end{bmatrix}, \]

所以两个工作权重分别改变 \(-0.12\)\(+0.06\),与配方法例子完全一致。消去第 0 个坐标后只剩 \(R_{11}=\sqrt{3/4}\),故下一轮的条件逆 Hessian 是 \(R_{11}^2=0.75\)

9.3. Lazy block update

令一个计算块从列 \(i\) 开始,实际长度为 \(b'=\min(b,d-i)\);块内索引记为 \(\mathcal B=[i,i+b')\),块外尾部记为 \(\mathcal T=[i+b',d)\)。把 Cholesky 因子分成

\[ R_{i:,i:}= \begin{bmatrix} R_{\mathcal B\mathcal B} & R_{\mathcal B\mathcal T}\\ 0 & R_{\mathcal T\mathcal T} \end{bmatrix}. \tag{44} \]

块内的列仍必须按顺序量化,因为第 \(j\) 列的工作值会受前面列补偿影响。每处理一列,保存

\[ \eta_j=\frac{\rho_j}{R_{jj}}\in\mathbb R^m. \]

把这 \(b'\) 个向量并排堆叠:

\[ E=[\eta_i,\eta_{i+1},\ldots,\eta_{i+b'-1}] \in\mathbb R^{m\times b'}. \tag{45} \]

块内立即做式 (42) 的局部版本;对块外尾部先不写。块处理完后,一次矩阵乘法就补齐所有延迟更新:

\[ \boxed{ W_{\mathrm{work}}[:,\mathcal T] \leftarrow W_{\mathrm{work}}[:,\mathcal T]-E R_{\mathcal B\mathcal T}. } \tag{46} \]

形状为

\[ [m,b']\cdot[b',d-i-b']\longrightarrow[m,d-i-b']. \]

为何它与逐列立即写到尾部相同?令

\[ D=(W_{\mathrm{work}}^{\mathrm{in}})_{:,\mathcal B}-Q_{\mathcal B} \]

是进入这个块时的块内工作权重与最终量化落点之差。块内逐列外积给出

\[ D=ER_{\mathcal B\mathcal B}. \tag{47} \]

\(b'=2\),把块内两列临时编号为 1、2(与全局列号无关),写

\[ E=[\eta_1,\eta_2], \qquad R_{\mathcal B\mathcal B} =\begin{bmatrix}r_{11}&r_{12}\\0&r_{22}\end{bmatrix}, \]

则式 (47) 逐列展开就是

\[ D_{:,1}=\eta_1r_{11}, \qquad D_{:,2}=\eta_1r_{12}+\eta_2r_{22}. \]

第二个等式明确展示了:第二列进入量化器之前,已经包含第一列带来的补偿,因此块内仍必须顺序处理。

这里的 \(G^{(i)}\) 指已经消去前 \(i\) 列后的条件逆 Hessian。把式(32) 套到当前计算块时,前文的符号具体对应为

\[ G_F=G^{(i)}, \qquad \Gamma=G^{(i)}_{\mathcal B\mathcal B}. \]

由式 (40) 可得

\[ G^{(i)}_{\mathcal B\mathcal B} =R_{\mathcal B\mathcal B}^\top R_{\mathcal B\mathcal B}, \qquad G^{(i)}_{\mathcal B\mathcal T} =R_{\mathcal B\mathcal B}^\top R_{\mathcal B\mathcal T}, \qquad \left(G^{(i)}_{\mathcal B\mathcal B}\right)^{-1} G^{(i)}_{\mathcal B\mathcal T} =R_{\mathcal B\mathcal B}^{-1}R_{\mathcal B\mathcal T}. \tag{48} \]

\(R\) 是正定矩阵 \(G\) 的 Cholesky 因子,所以它的对角元为正;上三角主块 \(R_{\mathcal B\mathcal B}\) 因而可逆,式 (48) 中的逆和下面的消去都有定义。于是式 (32) 中被减去的尾部项正好等于

\[ D\,\left(G^{(i)}_{\mathcal B\mathcal B}\right)^{-1} G^{(i)}_{\mathcal B\mathcal T} =ER_{\mathcal B\mathcal B}R_{\mathcal B\mathcal B}^{-1} R_{\mathcal B\mathcal T} =ER_{\mathcal B\mathcal T}. \tag{49} \]

因此,在量化网格固定、最终 \(Q_{\mathcal B}\) 已确定的前提下,lazy batching 是延迟写回,不是少算一项二阶补偿。它的价值在于把许多小的、访存不友好的尾部外积折叠为一次高吞吐矩阵乘法(GEMM)。浮点舍入次序或动态重拟合 group 网格时,不承诺逐 bit 相同。

GPTQ 单列更新与 Lazy Batch

图 2:第 \(j\) 列的并行 4-bit 量化。缩放残差 \(\eta_j\in\mathbb R^m\) 与 Cholesky 行尾形成外积,块内立即补偿;完成实际长度 \(b'=\min(b,d-i)\) 的 block 后,\(E\in\mathbb R^{m\times b'}\)\(R_{\mathcal B\mathcal T}\in\mathbb R^{b'\times(d-i-b')}\) 一次相乘并更新块外尾部。

9.4. 空间时间复杂度分析

把一层的输入维度记为 \(d\)、输出维度记为 \(m\)、校准 token 总数记为 \(N\)。主要成本如下:

阶段 主计算 时间复杂度 主要额外存储 解释
收集统计量 \(XX^\top\) \(O(Nd^2)\) \(O(d^2)\),或额外保存 \(X:[d,N]\) 可分批累加,不必永久保留所有 token
阻尼、求逆、Cholesky \(H_\lambda^{-1}\)\(R\) \(O(d^3)\) \(O(d^2)\) 通常是单层量化的二阶预处理
补偿主循环 外积/GEMM \(O(md^2)\) \(W_{\mathrm{work}}:[m,d]\)\(E:[m,b']\),且 \(b'\le b\) 所有输出行并行共享 \(R\)
打包权重 4-bit 码 \(O(md)\) \(md/2\) bytes,外加元数据 裸 INT4 的理论存储,不含对齐/scale/zero

例如 \(d=11008\) 时,一个 FP32 的 \([d,d]\) 矩阵约占 \(11008^2\times4\) bytes,即约 \(462\) MiB。若同时留着 \(H\)\(G\)\(R\),显存峰值很容易超过 1 GiB。实际实现常按层处理、及时释放中间矩阵,并尽量复用缓冲区。

这里的 \(O(d^3)\) 也解释了 GPTQ 不是“完全免费”的量化法。它之所以仍实用,是因为每个 Linear 层只做一次二阶预处理,而不是为每个输出行各自维护一套 \(d\times d\) 逆 Hessian;并且大模型推理会运行远多于一次量化。

本章结论。 Cholesky 形式没有更换 GPTQ 的优化目标,也没有省略补偿:式 (40) 说明一个 \(R\) 保存了所有后续条件逆 Hessian;式 (42)把单列补偿写成外积;式 (49) 证明 lazy block 的尾部 GEMM 与逐列立即写回在代数上相同。至此,前一章的正确块公式已经变成了实际 GPTQ 主循环可以高效执行的计算形式。

十、从公式到 blockwise 伪代码

下面是不可直接运行的 NumPy 风格教学伪代码。为了把数学主线和 group 元数据分开,它明确限定为 \(g=-1\):每个输出行只拟合一套固定网格,因此 scale, zero 的 shape 都是\([m,1]\)。若扩展到 \(g>0\),static groups 仍可使用普通连续 group 布局;dynamic groups 与act-order 联用时,则必须使用前面“act-order 本质上只是一次一致的置换”小节定义的 group_id:[d],或等价的重排元数据。

# W: [m, d], X: [d, N]
# Returns K:[m,d], scale:[m,1], zero:[m,1], W_hat:[m,d]
def gptq_linear_fixed_row_grid(
    W, X, quantizer, block_size=128, percdamp=0.01, perm=None
):
    m, d = W.shape
    N = X.shape[1]

    # Classic whole-row grid: fit once on the original row, then keep it fixed.
    scale, zero = quantizer.fit_rows(W)         # each [m, 1]
    Wwork = W.copy()                            # [m, d], floating work matrix
    H = (2.0 / N) * (X @ X.T)                  # [d, d]

    dead = (diag(H) == 0)                       # [d], unseen input features
    Wwork[:, dead] = 0                          # [m, n_dead]
    H[dead, dead] = 1                           # positive diagonal placeholders

    if perm is None:
        perm = arange(d)                        # or argsort(diag(H), descending=True)
    Wwork = Wwork[:, perm]                      # [m, d]
    H = H[perm, :][:, perm]                     # [d, d]

    eps = finfo(H.dtype).eps                    # positive scalar
    damp = max(percdamp * mean(diag(H)), eps)   # scalar > 0
    H = H + damp * eye(d)                       # [d, d], positive definite

    # Numerically safer than a generic inverse(H) in finite precision.
    L = cholesky(H, lower=True)                 # [d, d], H = L @ L.T
    G = cholesky_inverse(L)                     # [d, d], H^{-1}
    G = 0.5 * (G + G.T)                         # [d, d], remove roundoff asymmetry
    R = cholesky(G, upper=True)                 # [d, d], G = R.T @ R

    K = empty_code_matrix([m, d])               # [m, d], each entry in 0..15
    Q = empty_float([m, d])                     # [m, d], dequantized values

    for i in range(0, d, block_size):
        i2 = min(i + block_size, d)
        E = zeros([m, i2 - i])                  # [m, b'], scaled residuals

        for j in range(i, i2):
            code, q = quantizer.quantize(
                Wwork[:, j], scale[:, 0], zero[:, 0]
            )                                   # code:[m], q:[m]
            rho = Wwork[:, j] - q               # [m], work value minus q
            eta = rho / R[j, j]                 # [m], scaled residual

            K[:, j] = code
            Q[:, j] = q
            E[:, j - i] = eta
            Wwork[:, j:i2] -= eta[:, None] * R[j, j:i2][None, :]
                                                  # [m,1] * [1,i2-j] -> [m,i2-j]

        Wwork[:, i2:] -= E @ R[i:i2, i2:]       # [m,b'] @ [b',d-i2]

    K = undo_column_permutation(K, perm)        # [m, d]
    W_hat = undo_column_permutation(Q, perm)    # [m, d]
    return K, scale, zero, W_hat

dead 表示校准数据中从未出现的输入特征;它们对应 \(H\) 的零对角元。教学代码沿用经典实现思路,把相关工作权重置 0,并在 Hessian 对角线上放一个正占位值,使后续分解有定义。实际代码还应在 Cholesky 失败时提高阻尼并重试,而不是继续使用无效因子。

伪代码里几个看似神秘的写法只有切片和广播:

写法 输入 shape 输出 shape 含义
Wwork[:, j] Wwork:[m,d] [m] 取所有输出行的第 \(j\)
Wwork[:, j:i2] [m,d] [m,i2-j] 当前列到计算块末尾的视图
eta[:, None] eta:[m] [m,1] 插入一维,准备做外积
R[j,j:i2][None,:] [i2-j] [1,i2-j] 插入一维,准备做外积
undo_column_permutation [m,d]perm:[d] [m,d] 把 act-order 后的位置恢复为原特征列顺序

empty_code_matrix 只是“每个位置暂存一个 \(0\ldots15\) 码”的概念容器;真正部署时通常再把两个 4-bit 码压进一个 byte。块内更新包含当前列,是为了让工作矩阵当前列恰好变成 \(q\);而 \(Q\) 单独保存最终反量化值。块外更新等收集完 \(E\) 才执行,它与逐列写回尾部的数学结果相同。

伪代码位置 输入 shape 输出 shape 对应公式
\(X X^\top\) \([d,N]\cdot[N,d]\) \([d,d]\) 式 (9)
Cholesky\((G)\) \([d,d]\) \(R:[d,d]\) 式 (37)
量化当前列 当前列、scale、zero 均为 \([m]\) code、\(q:[m]\) 式 (36),每行使用自己的网格
块内外积 \([m,1]\cdot[1,i2-j]\) \([m,i2-j]\) 式 (42)
块外 GEMM \([m,b']\cdot[b',d-i2]\) \([m,d-i2]\) 式 (46)
反置换 \([m,d]\)perm:[d] \([m,d]\) 式 (34) 的逆操作

本节结论。 伪代码的控制流与公式是一一对应的:外层循环选择block,内层循环按顺序确定每列的 \(q\) 并立即更新块内列,块结束后用一次 GEMM 补齐块外尾部,最后反置换回原列顺序。\(W_{\mathrm{work}}\) 是可变的浮点工作副本,\(Q\)\(K\) 才是只写一次的最终量化结果;混淆这两类矩阵会直接破坏下一列的量化输入。

十一、一个完整的 16 级量化数值例子

前面的符号很多,下面用一个 \(m=2,d=2,N=3\) 的小层把全过程算到底。它刻意很小,但保留了 GPTQ 的两个关键现象:两个输出行共享同一个 \(G\),以及第一列的误差会改变第二列最终落到哪个 4-bit 点。

11.1. 输入、权重和 16 个量化点

设捕获到一个 batch 的三个 token 输入。为明确 batch 维,先写出 \(A[0,:,:]\)

\[ A[0,:,:]= \begin{bmatrix} 1&1\\ 0&1\\ 1&0 \end{bmatrix}, \qquad A\in\mathbb R^{1\times3\times2}. \]

按式 (5) 合并 token 维并转置:

\[ X= \begin{bmatrix} 1&0&1\\ 1&1&0 \end{bmatrix} \in\mathbb R^{2\times3}. \tag{50} \]

每一列确实是一个 token 的输入:分别为 \([1,1]^\top\)\([0,1]^\top\)\([1,0]^\top\)。浮点权重取为

\[ W= \begin{bmatrix} 0.62&0.62\\ -1.12&-0.62 \end{bmatrix} \in\mathbb R^{2\times2}. \tag{51} \]

本例故意固定一个教学用的仿射 INT4 网格:

\[ \Delta_q=0.25,\qquad z_q=8,\qquad c\in\{0,\ldots,15\}. \]

因此反量化点是

\[ \mathcal G =\{-2.00,-1.75,-1.50,-1.25,-1.00,-0.75,-0.50,-0.25, 0,0.25,0.50,0.75,1.00,1.25,1.50,1.75\}. \tag{52} \]

这里 scale 和 zero 的 shape 都是 \([m,1]=[2,1]\),即每个输出行只有一个 group;两行恰好使用相同数值。真实 GPTQ 会按行或按 group 估计它们,本例把网格预先固定,只让我们聚焦二阶补偿。

原始层输出为

\[ Y=WX= \begin{bmatrix} 1.24&0.62&0.62\\ -1.74&-0.62&-1.12 \end{bmatrix} \in\mathbb R^{2\times3}. \tag{53} \]
对象 shape 这个例子中的意义
\(A\) \([1,3,2]\) 1 条校准序列、3 个 token、2 个输入特征
\(X\) \([2,3]\) 3 个 token 位置的列向量
\(W\) \([2,2]\) 2 个输出通道各有 2 个权重
\(Y=WX\) \([2,3]\) 浮点层的基准输出
\(K\) \([2,2]\) 最后要存储的 4-bit 码

11.2. 先把 Hessian 和逆 Hessian 算出来

\[ XX^\top= \begin{bmatrix} 2&1\\ 1&2 \end{bmatrix}, \qquad H=\frac23XX^\top= \begin{bmatrix} 4/3&2/3\\ 2/3&4/3 \end{bmatrix}. \tag{54} \]

它的行和列都对应两个输入特征,不是两个输出行。这里 \(\det(H)=4/3>0\),因此为了手算清楚取 \(\lambda=0\)。求逆后

\[ G=H^{-1}= \begin{bmatrix} 1&-1/2\\ -1/2&1 \end{bmatrix}. \tag{55} \]

非对角项 \(-1/2\) 意味着两个输入特征在校准集上有关联,因而一个特征上的误差可以由另一个权重部分吸收。若这里是对角阵,下面的 \(+0.06\)\(-0.06\) 补偿将全部消失。

11.3. 量化第 0 列,并让第 1 列接住误差

本例预先指定处理顺序为第 0 列、再第 1 列;这不是由下面的公式自动选出的。第 0 列当前的两个工作权重是

\[ w=W_{\mathrm{work}}^{(0)}[:,0]=W_{:,0}= \begin{bmatrix} 0.62\\ -1.12 \end{bmatrix} \in\mathbb R^{2}. \]

按式 (36) 量化:

\[ K_{:,0}= \begin{bmatrix} 10\\4 \end{bmatrix}, \qquad q=Q_{:,0}= \begin{bmatrix} 0.50\\-1.00 \end{bmatrix}. \tag{56} \]

采用全文一致的误差约定 \(\rho=w-q\)

\[ \rho_0= \begin{bmatrix} 0.12\\-0.12 \end{bmatrix}, \qquad G_{00}=1,\qquad G_{1,0}=-\frac12. \tag{57} \]

对下一列的并行补偿是

\[ \begin{aligned} \Delta W_{\mathrm{work},:,1} &=-\frac{\rho_0}{G_{00}}G_{1,0}\\ &=- \begin{bmatrix}0.12\\-0.12\end{bmatrix} \left(-\frac12\right)\\ &= \begin{bmatrix}+0.06\\-0.06\end{bmatrix}. \end{aligned} \tag{58} \]

因此,第 0 列锁为 \(q\),第 1 列的工作值被改写:

\[ W_{\mathrm{work}}^{(1)}= \begin{bmatrix} 0.50&0.68\\ -1.00&-0.68 \end{bmatrix}. \tag{59} \]

这一步每个输出行的最小层重建损失增量是

\[ \Delta\ell_{0} =\frac{0.12^2}{2G_{00}} =0.0072. \tag{60} \]

与此同时,第 0 个输入特征已不再自由,必须消元:

\[ G^{(1)} =G_{1,1}-\frac{G_{1,0}G_{0,1}}{G_{00}} =1-\frac14 =0.75 \in\mathbb R^{1\times1}. \tag{61} \]

11.4. 量化已经补偿过的第 1 列

重点是:现在不能拿原始 \([0.62,-0.62]^\top\) 去 round,而要量化工作矩阵里的

\[ W_{\mathrm{work}}^{(1)}[:,1] = \begin{bmatrix} 0.68\\-0.68 \end{bmatrix}. \]

它们落到

\[ K_{:,1}= \begin{bmatrix} 11\\5 \end{bmatrix}, \qquad Q_{:,1}= \begin{bmatrix} 0.75\\-0.75 \end{bmatrix}, \qquad \rho_1= \begin{bmatrix} -0.07\\+0.07 \end{bmatrix}. \tag{62} \]

已经没有后续自由变量可补偿,故每个输出行增加

\[ \Delta\ell_1 =\frac{0.07^2}{2\times0.75} \approx0.0032667. \tag{63} \]

最终存储的整数码、元数据和反量化权重是

\[ K= \begin{bmatrix} 10&11\\ 4&5 \end{bmatrix} \in\{0,\ldots,15\}^{2\times2}, \qquad \hat W= \begin{bmatrix} 0.50&0.75\\ -1.00&-0.75 \end{bmatrix}. \tag{64} \]

11.5. 回到真实层输出验算,而不是只看权重差

\[ \hat Y=\hat W X= \begin{bmatrix} 1.25&0.75&0.50\\ -1.75&-0.75&-1.00 \end{bmatrix}, \]
\[ \hat Y-Y= \begin{bmatrix} 0.01&0.13&-0.12\\ -0.01&-0.13&0.12 \end{bmatrix}. \tag{65} \]

所以

\[ \frac1N\lVert\hat Y-Y\rVert_F^2 =\frac{0.0628}{3} \approx0.0209333. \tag{66} \]

它恰好等于二阶增量按行求和:

\[ 2\times(0.0072+0.0032667) \approx0.0209333. \tag{67} \]

作为对照,若完全忽略补偿,直接对原始 \(W\) 做最近邻 round:

\[ W_{\mathrm{RTN}}= \begin{bmatrix} 0.50&0.50\\ -1.00&-0.50 \end{bmatrix}, \qquad \frac1N\lVert W_{\mathrm{RTN}}X-WX\rVert_F^2 =0.0576. \tag{68} \]

在这个玩具例子中,GPTQ 的重建误差约为 RTN 的 \(0.3634\),即低约 \(63.7\%\)。特别看第一行第二列:原始 \(0.62\) 单独 round 会到 \(0.50\);它先因补偿变为 \(0.68\),随后量化到 \(0.75\)。GPTQ允许单个权重离原值看起来更远,只要整体 \(WX\) 更接近。

这个例子之所以能把式 (67) 与裸重建误差严格对上,是因为 \(H\) 满秩且取了 \(\lambda=0\)、网格也固定。真实大模型中通常会加入阻尼;那时应把式 (43) 当作阻尼代理的记账,并在量化结束后单独测量真实层误差和任务指标。

本例结论。 第 0 列的舍入误差先把第 1 列工作值从\([0.62,-0.62]^\top\) 推到 \([0.68,-0.68]^\top\),使它最终选择了不同的 4-bit 网格点;两行在两步中的条件代价全部相加,又与最终层输出重建误差严格相等。这同时验证了三件事:量化器读的是当前工作权重,不是原始权重;两个输出行共享补偿系数但分别量化;GPTQ 优先保持 \(WX\),而不是让每个 量化权重都尽量贴近原值。

参考资料

  1. 知乎文章:GPTQ 相关讲解:适合先建立直觉;本文补齐了其中容易被略过的条件最优、消元与 Cholesky 中间步骤。
  2. Frantar et al., GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers, ICLR 2023:层重建目标、共享列顺序、lazy block update 与 Cholesky 加速的原始来源。
  3. GPTQ 官方实现,固定提交 2d65066:阻尼、Cholesky、block 更新、act-order 和 group 行为的可执行对照。

读完后,最值得自己重新推一遍的是式 (26)、(42) 和 (49)。三式连起来,正好解释了 GPTQ 最核心的一句话:量化一列,剩下的列按输入统计主动接住误差。

评论

滚动到评论区附近时自动加载