归一化 Hadamard、分块结构以及符号和排列的组合,提供了多种重新分配坐标能量的方式。对线性层同时变换激活与权重,可以保持精确算术下的输出。量化却依赖各个坐标的幅值、共享尺度的分组和有限的重建网格:同一个精确内积,换一套坐标计算,可能产生另一种舍入误差。

因此,为一层选择变换时,需要同时给出该层的激活、权重和量化规则。首先要明确误差如何进入输出,再考察量化分组怎样影响误差;有了评价标准,就可以用校准数据比较候选变换,进一步学习变换参数。当正交混合的自由度仍然受限时,通道缩放还可以调节激活与权重之间的幅值分配。

等价的精确计算,为什么产生不同量化误差

从一个线性层确定评价对象

将 M 行输入送入一个具有 K 个输入通道、N 个输出通道的线性层,写为

X\in\mathbb R^{M\times K},\qquad
W\in\mathbb R^{N\times K},\qquad
Y=XW^T\in\mathbb R^{M\times N}.

X 的一行可以是一个语言 token 或图像位置的特征,W 的一行给出一个输出通道的系数。令正交矩阵 R\in\mathbb R^{K\times K} 满足 RR^T=I_K,将激活与权重共同参与求和的 K 个通道变换为

X_R=XR,\qquad W_R=WR,\qquad
X_RW_R^T=XRR^TW^T=XW^T.

这个恒等式确定了比较基准:各个候选 R 对应同一个参考输出 Y。若线性层有偏置,可以在各条路径的乘积后加上相同偏置。量化放在 X_R、W_R 形成之后,接下来要观察编码与重建怎样改变这个乘积。

四维输入的完整计算

取 M=1、K=4、N=2:

x=[8,0,0,0],\qquad
W=\begin{bmatrix}1&2&3&4\\-1&1&-1&1\end{bmatrix}.

原结果是 xW^T=[8,-8]。

记 H_K 为元素取 \pm1、满足 H_KH_K^T=KI_K 的 Hadamard 矩阵,归一化后为 Q_K=H_K/\sqrt K。本例使用四阶 Sylvester 构造:

Q_4=\frac12\begin{bmatrix}
1&1&1&1\\
1&-1&1&-1\\
1&1&-1&-1\\
1&-1&-1&1
\end{bmatrix}.

使用归一化 Sylvester 矩阵 Q_4 后:

xQ_4=[4,4,4,4],\qquad
WQ_4=\begin{bmatrix}5&-1&-2&0\\0&-2&0&0\end{bmatrix}.

逐个输出核对:

4\times5+4\times(-1)+4\times(-2)+4\times0=8,
4\times0+4\times(-2)+4\times0+4\times0=-8.

原输入的能量集中在一个通道,变换后分到四个通道;权重的坐标也随之改变。内积保持,来自这两项变化的配对。

量化之后,误差从哪里进入

精确乘法中,两侧的旋转恰好抵消。量化插在旋转与乘法之间,舍入和截断会改变传入乘法的数值;同一个内积于是可以在不同坐标中产生不同的量化误差。

为区别于 Hadamard 矩阵 Q_K,将量化加重建写成 \mathcal Q。例如,一个对称均匀量化器用整数 q\in[-q_{\max},q_{\max}] 编码:

q(z)=\operatorname{clip}\!\left(\operatorname{round}(z/s),-q_{\max},q_{\max}\right),
\qquad \mathcal Q(z)=s\,q(z).

s 是步长,也称 scale;round 将数值取到最近的整数,clip 将结果限制在表示范围内。本文采用 q_{\max}=7 的对称整数网格作为例子,scale 可按一行或一个小组的最大绝对值除以 7 来选择。\mathcal Q 的公式描述编码后重建出的数值,实际实现可以保存整数及 scale,在矩阵乘或输出缩放时使用它们。

继续上面的四维例子,为每行独立选择 scale。输入 [4,4,4,4] 用 s_A=4/7,可以精确重建。第一行变换后权重 [5,-1,-2,0] 用 s_W=5/7,整数码及重建值为:

q_W=[7,-1,-3,0],\qquad
\mathcal Q_W([5,-1,-2,0])=[5,-5/7,-15/7,0].

因此第一个输出变成 4(5-5/7-15/7)=60/7\approx8.5714。正交改写本身保留了结果,数值舍入改变了结果。旋转的作用需要由量化后的误差来衡量。

令 E_A=\mathcal Q_A(X_R)-X_R、E_W=\mathcal Q_W(W_R)-W_R,量化后的乘积可展开为:

\widehat Y-Y
=E_AW_R^T+X_RE_W^T+E_AE_W^T.

这三个项分别来自激活误差、权重误差和两者的交叉作用。它们还分别受到另一侧矩阵的加权,因此激活或权重的重建误差相同,最终输出误差也可能不同。选择变换时,需要评价两侧共同产生的结果。这里的编码误差与实际浮点计算中的舍入误差分别核对。

旋转块与量化组怎样共同影响误差

变换后的坐标确定了,编码还需要为这些数值选择尺度。一个块内共同参加混合的通道,是否也必须共用一个量化尺度?这涉及两个阶段的分组,应当分别定义。

一块变换可以对应多组量化

旋转块长 B 决定哪些输入一起参加加减。量化组长 G 决定哪些输出共享一个尺度。二者作用于计算的不同阶段:

X\xrightarrow{\;R\;}Z=XR
\xrightarrow{\;\text{按 }G\text{ 分组求尺度}\;}(q,s)
\xrightarrow{\;\text{重建}\;}\widehat Z.

例如 K=8、B=4、G=2 时,旋转分成两个四维块,量化分成四个二维组:

text
输出位置       0   1   2   3 | 4   5   6   7
旋转块         └──── B=4 ────┘ └──── B=4 ────┘
量化组         └ G=2 ┘└ G=2 ┘ └ G=2 ┘└ G=2 ┘

旋转时,每块中的四个数已经共同参与运算;随后把输出切成两组求尺度,并不会撤销前面的混合。在这种整除且边界对齐的安排中,每个旋转块包含 B/G 个量化组。推广到 K=4096,B=256,G=32,一行有 16 个旋转块、128 个量化组,每个旋转块对应八个尺度。

用八个数看尺度怎样改变误差

取

x=[4,3,2,1,\;0.4,0.3,0.2,0.1],\qquad
R=\operatorname{blkdiag}(Q_4,Q_4).

两块分别变换后得到

z=xR=[5,1,2,0,\;0.5,0.1,0.2,0].

下面固定这个旋转结果,只改变尺度的分组方式。采用对称均匀量化,整数范围为 [-7,7]。对每组 \mathcal G 定义

s_{\mathcal G}=\frac{\max_{j\in\mathcal G}|z_j|}{7},\qquad
q_j=\operatorname{round}(z_j/s_{\mathcal G}),\qquad
\widehat z_j=s_{\mathcal G}q_j.

全零组可直接编码成零,尺度按实现约定取一个正数。这个例子中的各组都有非零值,因此不需要额外处理。

若 G=8,整行共用 s=5/7,则

q=[7,1,3,0,\;1,0,0,0],
\qquad
\widehat z=[5,5/7,15/7,0,\;5/7,0,0,0].

前半行的最大值 5 决定了后半行的量化步长;0.1 和 0.2 都被舍入为零。若改成 G=2,四组的尺度分别是

s=[5/7,\;2/7,\;0.5/7,\;0.2/7],

于是

q=[7,1,7,0,\;7,1,7,0],
\qquad
\widehat z=[5,5/7,2,0,\;0.5,1/14,0.2,0].

例如 0.1 位于 [0.5,0.1] 这一组,使用步长 1/14,重建为 1/14\approx0.07143。两个方案在此例中的总平方误差分别为

\|\widehat z_{G=8}-z\|_2^2\approx0.19796,\qquad
\|\widehat z_{G=2}-z\|_2^2\approx0.08245.

更细的分组在这个例子中减少了误差,同时将尺度数量从一个增加到四个。实际系统需要把数值误差、尺度存储、计算开销和低比特算子的分组要求一起考虑。旋转块长与量化组长应分别写入模型配置。

块长、符号和排列共同确定 R,量化组长 G 则确定变换后哪些坐标共享尺度。比较候选 R 时,应先固定量化分组规则;若也希望调整 G,就把 (R,G) 作为一组配置,同时记录尺度数量和算子支持情况。这样可以区分误差变化究竟来自坐标混合,还是来自更多的量化尺度。

用校准数据比较候选变换

把选择标准写成输出误差

校准数据是一小组用于确定量化参数的代表性输入。对某个线性层,可以运行原模型并收集该层实际接收到的激活;这些激活与该层权重一起,构成比较变换所需的数据。对于图像、视频生成模型,输入还应覆盖准备使用的时间步或条件,因为同一层的激活分布可能随条件变化。

将收集到的校准输入组成 X\in\mathbb R^{M\times K},保持预训练权重 W 固定,先用浮点计算得到参考输出 Y=XW^T。对候选正交矩阵 R,同时变换激活与权重,再模拟部署时的量化:

\widehat Y(R)
=\mathcal Q_A(XR)\,\mathcal Q_W(WR)^T.

这里 \mathcal Q_A 和 \mathcal Q_W 都包含舍入、截断和按 scale 重建的过程,输出仍可用浮点数表示。因而可以直接比较 \widehat Y(R) 与参考输出 Y。一个便于理解的逐层目标是

\mathcal L(R)
=\frac{1}{MN}\left\|\widehat Y(R)-Y\right\|_F^2,
\qquad RR^T=I_K.

\|\cdot\|_F^2 表示把矩阵中所有元素的平方相加。这一目标先计算该层的量化输出误差,再对 MN 个输出元素取平均;使用多批校准数据时,还可以对各批误差求平均。

这一目标把前面的三项误差合并到同一个输出中。若使用同一批输入和同一组量化规则,就可以直接比较不同 R 的损失。激活峰值、权重峰值和各自的重建误差仍有助于分析原因,最终选择则应与希望控制的误差对象一致。

三个候选矩阵的完整比较

固定权重与三行校准输入:

W=\begin{bmatrix}1&2&3&4\\-1&1&-1&1\end{bmatrix},\qquad
X=\begin{bmatrix}
8&0&0&0\\
4&3&2&1\\
1&-1&1&-1
\end{bmatrix}.

取三个候选矩阵:

R^{(0)}=I_4,\qquad R^{(1)}=Q_4,\qquad
R^{(2)}=DQ_4,\qquad D=\operatorname{diag}(1,1,1,-1).

Q_4 采用前面的四阶 Sylvester 矩阵,D 固定翻转第四个输入通道的符号。三个候选都保持精确内积。分别变换 X 与 W 后,激活每行、权重每行各用一个 scale,均采用最大绝对值除以 7 的尺度规则和 [-7,7] 的整数网格。由此得到:

候选变换 精确计算的输出均方误差 加入量化后的输出均方误差 \mathcal L
I_4 0 1.054561
Q_4 0 0.165001
DQ_4 0 1.054561

在这三行输入和指定量化规则下,Q_4 的输出误差最小。这个结论只对应当前比较条件;更换权重、输入或尺度分组以后,需要重新计算损失。表中的差异也说明,固定 Hadamard 与增加符号翻转的组合应当作为候选接受评价,变换的名称本身无法决定哪一个误差更小。

有限候选集 \mathcal C 上的选择可以写成

R_* = \mathop{\arg\min}_{R\in\mathcal C}\mathcal L(R).

实际候选可以来自不同块长、块内矩阵、符号向量或排列。多级结构 R_1PR_2 也可以固定组合形式,再用数据选择其中的矩阵与排列;这里 R_1,R_2 为同维正交变换,P 为排列矩阵,行向量依次经过第一次混合、重排和第二次混合。例如,根据异常值所在通道调整块内混合,并把大幅值通道重新分配到不同块。结构规定可执行的运算,校准数据决定在这个范围内采用哪一组参数。

让比较条件与部署条件对应

每个候选都要按同一套量化规则重新生成 scale 和重建值。例如,规则都是“每组最大绝对值除以 7”,具体 scale 随变换后的数值重新计算。如果沿用另一个候选的尺度,比较中就混入了不匹配的量化参数。

对于固定结构的比较,应保持位宽、数值格式、分组方式、裁剪策略和评价数据一致;若部署采用低精度尺度,还应在比较时模拟尺度本身的编码。保留原始坐标 I_K 作为候选,可以衡量引入变换在当前配置下带来的变化。

校准完成后,用另外留出的输入检验误差,才能观察所选参数对新输入的适应情况。若准备按时间步或条件使用不同变换,应分别定义各组的校准目标,并让运行时的激活变换与对应的权重准备方式匹配。多组参数所增加的存储和执行成本,也属于选择条件。

有限候选集易于比较,但其覆盖范围由事先列出的结构和参数决定。若希望根据误差连续调整混合系数,就需要把 R 写成可优化参数的函数。

从选择候选到学习正交变换

将变换写成 R(\theta),其中 \theta 表示待优化参数,就可以沿用校准损失 \mathcal L 来调整它:

\theta_* = \mathop{\arg\min}_{\theta}\mathcal L(R(\theta)),
\qquad R(\theta)R(\theta)^T=I_K.

损失怎样传到变换参数

参数变化沿下面的路径影响损失:

text
变换参数 → R → XR 与 WR → 模拟量化 → 输出 Ŷ(R) → 与参考 Y 比较

在精确运算中,去掉量化后总有 (XR)(WR)^T=Y。因此,学习过程关心的是舍入、截断以后出现的差别。仅优化浮点线性层的重建误差,无法在这些等价的正交矩阵之间作出有效选择。采用梯度法时,还要为舍入操作指定反向传播的近似规则;具体的校准算法会规定该规则以及更新方式。

上述损失用于解释单层的计算关系。完整模型还可以用最终任务损失来选择旋转。例如,SpinQuant 第 3.2 节 使用含量化操作的模型在校准集上的任务损失,并保持预训练权重参数固定。此时,待优化旋转通过许多层的量化结果,最终影响模型输出。

直接学习一个稠密正交矩阵

损失函数规定了选择标准,参数化方式则规定能够选择哪些矩阵。最直接的做法是保存并更新整张矩阵,同时维持正交约束。一个 K\times K 矩阵有 K^2 个存储位置,但正交矩阵的各个元素受到行向量长度与两两内积的共同约束:

\sum_{j=1}^{K}r_{ij}^2=1,
\qquad
\sum_{j=1}^{K}r_{ij}r_{\ell j}=0\quad(i\ne\ell).

前一类条件保证每行长度为 1,后一类保证不同的行互相垂直。局部独立的约束共有 K(K+1)/2 个,所以连续自由度为

K^2-\frac{K(K+1)}2=\frac{K(K-1)}2.

例如,二维平面旋转由一个角度确定;四维正交矩阵有 6 个连续自由度。若直接保存并更新整张矩阵,仍需 K^2 个数。更新算法还必须维持正交条件,否则前面的等价公式就失去了依据。

SpinQuant 用保持正交性的 Cayley 更新优化部分旋转,并区分可以并入权重的变换与需要在线执行的变换。这里需要分别核算校准时的参数量和推理时的额外计算量。具体变换能否并入权重,取决于它在模型中的位置以及相邻运算的性质。

若一个稠密矩阵可以提前乘入权重,部署时使用的是变换后的权重。若必须在运行时显式计算 xR,普通稠密乘法每个输入向量需要 O(K^2) 次算术操作。后一种位置更适合考虑带有快速算法的结构化矩阵。

用二维旋转组成可学习蝶形

为了减少需要保存和调整的参数,可以将候选矩阵限制为若干小正交因子的乘积。最小的非平凡因子只作用于两个通道,并由一个角度确定。定义

G(\theta)=
\begin{bmatrix}
\cos\theta&-\sin\theta\\
\sin\theta&\cos\theta
\end{bmatrix},
\qquad G(\theta)G(\theta)^T=I_2.

本文采用行向量右乘,因此一对输入的具体计算是

[a,b]G(\theta)
=[a\cos\theta+b\sin\theta,\;-a\sin\theta+b\cos\theta].

角度控制两个通道混合的比例。\theta=0 时保持原值;\theta=\pi/4 时,输出为 [(a+b)/\sqrt2,(-a+b)/\sqrt2]。每一对通道都可以拥有自己的角度。

从二维旋转到一整层矩阵

下面采用 K=2^m、m\ge1 的一种蝶形构造,通道编号从 0 开始。每层 B_\ell 都是 K\times K 矩阵,包含 K/2 个互不重叠的二维旋转。 构造时,先将矩阵元素全部置零,再选定各对通道。对于一对 (p,q),把 G(\theta) 按照行列顺序 (p,q) 填到第 p、q 行与第 p、q 列的交叉位置。这样,只有这一对通道互相参与计算;一层中的全部通道对合起来覆盖 K 个位置。

先取 K=4。第一层配对 (0,1)、(2,3),分别使用角度 \alpha,\beta,于是

B_1=
\begin{bmatrix}
\cos\alpha&-\sin\alpha&0&0\\
\sin\alpha&\cos\alpha&0&0\\
0&0&\cos\beta&-\sin\beta\\
0&0&\sin\beta&\cos\beta
\end{bmatrix}
=\operatorname{blkdiag}\bigl(G(\alpha),G(\beta)\bigr).

两个二维旋转沿对角线排列,各自混合两个相邻通道。第二层改为配对 (0,2)、(1,3),分别使用角度 \gamma,\delta,得到

B_2=
\begin{bmatrix}
\cos\gamma&0&-\sin\gamma&0\\
0&\cos\delta&0&-\sin\delta\\
\sin\gamma&0&\cos\gamma&0\\
0&\sin\delta&0&\cos\delta
\end{bmatrix}.

其中,第 0、2 行与第 0、2 列交叉处的子矩阵是 G(\gamma),第 1、3 行与第 1、3 列交叉处的子矩阵是 G(\delta)。两个旋转仍然互不重叠,只是参与同一旋转的通道在原始编号中相隔一个位置。

在行向量右乘约定下,整体计算为

R=B_1B_2,\qquad xR=(xB_1)B_2.

因此先执行第一层,再执行第二层。第一层分别处理原通道 \{0,1\} 和 \{2,3\},第二层让这两组的输出继续参与混合。四个角度可以独立设置,也可以作为校准损失中的待优化参数。

以四维输入 x=[1,0,0,0] 为例,四个角度都取 \pi/4:

步骤 本层读取的通道对 四个输出分量
输入 — [1,0,0,0]
第一层 (0,1) 与 (2,3) [1/\sqrt2,-1/\sqrt2,0,0]
第二层 (0,2) 与 (1,3) [1/2,-1/2,-1/2,1/2]

第二层的第一对读到 [1/\sqrt2,0],产生 [1/2,-1/2],写回位置 0 和 2;另一对读到 [-1/\sqrt2,0],产生 [-1/2,1/2],写回位置 1 和 3。这样就能逐项得到表中的结果。输入和最终输出的平方和都为 1。

每层的配对规则与最后一层的形式

推广到 K=2^m,第 \ell 层取配对距离

d_\ell=2^{\ell-1},\qquad \ell=1,\ldots,m.

将 K 个通道按长度 2d_\ell 分组,每组前半部分与后半部分的对应位置两两配对。若 t 表示组号、j 表示半组内的位置,则

\begin{aligned}
p&=2d_\ell t+j,\qquad q=p+d_\ell,\\
t&=0,\ldots,\frac{K}{2d_\ell}-1,\qquad
j=0,\ldots,d_\ell-1.
\end{aligned}

本层共有 K/2 对通道。用 r=td_\ell+j 为这些通道对编号,第 r 对使用自己的角度 \theta_{\ell,r}。例如 K=8 时,共有三层:

层 配对距离 本层的通道对
B_1 1 (0,1),(2,3),(4,5),(6,7)
B_2 2 (0,2),(1,3),(4,6),(5,7)
B_3 4 (0,4),(1,5),(2,6),(3,7)

第一层的一般形式就是相邻二维旋转组成的块对角矩阵:

B_1=\operatorname{blkdiag}\left(
G(\theta_{1,0}),\ldots,G(\theta_{1,K/2-1})
\right).

当 K\ge4 时,第二层在每四个通道内部,采用上面四维 B_2 的结构;不同四通道组分别使用自己的两个角度。后续层依次扩大配对距离,每层中的一对通道都读取上一层的两个输出,再将旋转结果写回原来的两个位置。

最后一层的距离为 d_m=K/2,通道对为 (j,j+K/2)。令

\begin{aligned}
C_m&=\operatorname{diag}\left(
\cos\theta_{m,0},\ldots,\cos\theta_{m,K/2-1}\right),\\
S_m&=\operatorname{diag}\left(
\sin\theta_{m,0},\ldots,\sin\theta_{m,K/2-1}\right).
\end{aligned}

二者都是 K/2\times K/2 的对角矩阵。按前后两半通道划分行列,最后一层可以明确写成

B_m=
\begin{bmatrix}
C_m&-S_m\\
S_m&C_m
\end{bmatrix}.

例如,将进入这一层的行向量记为 [u,v],u、v 各含 K/2 个分量,则

[u,v]B_m=[uC_m+vS_m,\;-uS_m+vC_m].

由于 C_m,S_m 均为对角矩阵,第 j 个位置只与另一半的第 j 个位置配对。K=4 时,m=2,这个形式展开后就是前面的四维 B_2。

为什么每一层及其乘积都正交

每个二维旋转满足 G(\theta)G(\theta)^T=I_2,因此同一对通道对应的两行长度均为 1、内积为零。不同通道对对应的行使用互不重叠的列,它们之间的内积也为零。整层矩阵由此满足

B_\ell B_\ell^T=I_K.

最后一层还可以直接用分块乘法核对。对角矩阵 C_m,S_m 互相可交换,且每个对角位置满足 \cos^2\theta+\sin^2\theta=1,所以

\begin{aligned}
B_mB_m^T
&=\begin{bmatrix}
C_m^2+S_m^2&C_mS_m-S_mC_m\\
S_mC_m-C_mS_m&S_m^2+C_m^2
\end{bmatrix}\\
&=I_K.
\end{aligned}

将 m 层按执行顺序相乘,得到

R=B_1B_2\cdots B_m,
\qquad RR^T=I_K.

当 m=1 时,R=B_1,正交性已经由单层的结论给出。当 m\ge2 时,利用转置会反转各因子的顺序,可以逐层展开:

\begin{aligned}
RR^T
&=B_1B_2\cdots B_mB_m^T\cdots B_2^TB_1^T\\
&=B_1\cdots B_{m-1}
\underbrace{B_mB_m^T}_{I_K}
B_{m-1}^T\cdots B_1^T\\
&=\cdots=B_1B_1^T=I_K.
\end{aligned}

因子从中间开始逐层抵消。各层即使采用不同角度,结论仍然成立。学习过程只需更新角度,并由它们生成对应的正弦、余弦系数,就能在精确运算中保持正交性;浮点实现中会有相应的数值舍入误差。

每层有 K/2 个角度,总计 \frac K2\log_2K 个角度。实际执行时,按配对规则逐对更新即可,显式的 K\times K 矩阵用于说明和核对计算关系。每层处理 K/2 对数据,应用整套变换的算术成本为 O(K\log K)。这种逐层改变配对关系的计算图称为蝶形结构。ButterflyQuant 第 3.2 节 将这类可学习结构用于量化变换。

蝶形结构、Hadamard 与一般正交矩阵的关系

上面的角度可以连续调整,因而需要区分分层结构与固定的 Hadamard 系数。蝶形描述的是分层计算结构,Hadamard 还规定了具体系数。对于二维情形,两者的精确关系为

Q_2=
\frac{1}{\sqrt2}
\begin{bmatrix}1&1\\1&-1\end{bmatrix}
=G(\pi/4)
\begin{bmatrix}1&0\\0&-1\end{bmatrix}.

因此,使用这里定义的 G 时,还要对第二个输出取反,才能得到标准 Hadamard 的“和、差”顺序。这个符号因子也解释了 G 的行列式为 +1,而 Q_2 的行列式为 -1。构造多层 Hadamard 蝶形时,应把对应符号规则一并写入每层操作。

当各个角度可以学习时,计算图保留了快速的分层结构,最终矩阵的元素则可以具有不同绝对值。每层因子稀疏,乘积 R 可以是稠密矩阵。四维的两层蝶形只有 4 个角度,而一般四维正交矩阵有 6 个连续自由度;这个比较表明,固定层数与固定配对模式限制了可表示的矩阵范围。

随机角度还需配合具体的采样分布才能定义随机矩阵族。正交性只保证长度与内积保持,Haar 分布还要求整个正交群上的分布具有旋转不变性。学习蝶形时,最终参数由校准数据决定,可以直接用校准误差与在线成本评价这一选择。

用缩放扩展正交变换的选择范围

学习正交矩阵可以改变坐标方向,但仍保持每个向量的长度。如果某些激活通道的幅值显著大于对应权重通道,还可以在混合之前重新分配两侧的幅值。通道缩放提供了这一自由度:把激活的某个通道缩小,同时把权重的对应通道放大,便有机会继续保持内积。这个配对是否成立、随后怎样与旋转组合,可以先用两个数核对。

先看一个仅有缩放的内积

取一个输入行向量和一个权重行向量:

x=[8,0],\qquad w=[1,2],\qquad xw^T=8.

选择正对角矩阵

S=\operatorname{diag}(4,1),\qquad
S^{-1}=\operatorname{diag}(1/4,1).

将输入第一通道除以 4,同时将权重第一通道乘以 4:

xS^{-1}=[2,0],\qquad wS=[4,2].

新的内积为 2\times4+0\times2=8。原来输入的最大绝对值为 8,缩放后为 2;权重的最大绝对值则由 2 增大到 4。这个过程把第一通道的一部分幅值压力从激活转移到了权重。

量化时,这一转移会同时影响两侧的 scale、舍入和截断。选择 S 时需要考察激活与权重共同产生的输出误差。只观察激活最大值的下降,还不足以评价该选择。

缩放后再旋转,完整的配对是什么

对一般线性层,设

S=\operatorname{diag}(s_1,\ldots,s_K),\quad s_j>0,
\qquad R\in\mathbb R^{K\times K},\quad RR^T=I_K.

激活与权重分别采用

\widetilde X=XS^{-1}R\in\mathbb R^{M\times K},
\qquad
\widetilde W=WSR\in\mathbb R^{N\times K}.

按矩阵乘法从左到右理解,每个输入通道先除以 s_j,每个权重输入通道先乘以 s_j,然后两者使用同一个 R。验证时,先把权重一侧的转置完整展开:

\begin{aligned}
\widetilde X\widetilde W^T
&=(XS^{-1}R)(WSR)^T\\
&=XS^{-1}RR^TS^TW^T\\
&=XS^{-1}SW^T\\
&=XW^T.
\end{aligned}

其中,S^T=S 来自对角结构,RR^T=I_K 来自正交性。各因子按相邻次序消去,得到原来的输出。

把前面的二维例子继续右乘 Q_2:

\widetilde x=[2,0]Q_2=[\sqrt2,\sqrt2],
\qquad
\widetilde w=[4,2]Q_2=[3\sqrt2,\sqrt2].

于是

\widetilde x\widetilde w^T
=\sqrt2\cdot3\sqrt2+\sqrt2\cdot\sqrt2=6+2=8.

这个例子包含三套坐标下的同一个内积:原始坐标、缩放后的坐标以及缩放再旋转后的坐标。加入量化以后,计算的是 \mathcal Q_A(\widetilde X)\mathcal Q_W(\widetilde W)^T,输出差异来自各套坐标中的量化误差。

操作顺序为什么必须写清楚

“先缩放,再旋转”把某个原始通道的数值先调节,然后混入多个新通道;“先旋转,再缩放”调节的是混合以后的通道。两种操作一般产生不同结果。仍用上面的 x 和 S:

xS^{-1}Q_2=[\sqrt2,\sqrt2],
\qquad
xQ_2S^{-1}=[\sqrt2,4\sqrt2].

因此,工程实现中的调用次序必须与公式一致。预先变换权重时也要使用同一套约定。能够折叠进权重的因子、必须在线执行的因子,以及量化发生在什么位置,都可以由完整乘积逐项确定。

从正交旋转推广到一般可逆变换

缩放与旋转的组合本身就是一个可逆矩阵。记 T=S^{-1}R,则

T^{-T}\equiv(T^{-1})^T=SR.

由此可以得到更一般的配对规则:对任意可逆 T\in\mathbb R^{K\times K},令激活右乘 T,权重右乘 T^{-T},就有

(XT)(WT^{-T})^T
=XT\,T^{-1}W^T
=XW^T.

正交矩阵满足 R^{-T}=R,因此两侧可以使用同一个矩阵。一般可逆矩阵则按逆转置关系配对,激活与权重使用的变换通常不同。

FlatQuant 第 3.1 节 在更大的可逆变换空间中学习量化前变换,并采用两个较小矩阵的 Kronecker 积降低参数存储与执行成本。论文将这一类方法称为仿射变换;上面的公式讨论其中通过可逆矩阵实现的线性部分。若将 T 写为 A\otimes C,其中 A\in\mathbb R^{a\times a}、C\in\mathbb R^{b\times b} 且 K=ab,可以把一行输入逐行排成 a\times b 的数组 Z,计算 A^TZC 后再逐行展开。这一分解同样适用于学习得到的一般可逆因子。

一般可逆变换扩大了选择范围,也可能把某些方向放大、把另一些方向压缩。它的条件数衡量最大与最小长度放大倍数之比;该比值越大,逆变换对数值误差就可能越敏感。比较固定 Hadamard、可学习正交变换和一般可逆变换时,需要同时考察量化输出误差、校准代价、数值稳定性,以及在线乘法的实际成本。

选择这些参数时,仍然以量化后的输出为评价对象。对缩放与旋转的组合,可以写成

\widehat Y(S,R)
=\mathcal Q_A(XS^{-1}R)\,\mathcal Q_W(WSR)^T,
\qquad
\mathcal L(S,R)=\frac{1}{MN}\|\widehat Y(S,R)-XW^T\|_F^2.

这个目标与正交变换的校准目标采用相同的输出比较方式,只是增加了正对角矩阵 S 的自由度。优化时保持 s_j>0 和 RR^T=I_K,并按实际量化规则重算两侧的重建值,就能评价幅值调节与坐标混合共同带来的变化。

用完整程序核对量化误差与候选选择

下面的程序只依赖 NumPy,可以独立运行。它保留显式矩阵,使坐标变换、量化重建和输出误差能够逐项对应正文。quant_group 沿最后一个维度分组,每组独立选择最大绝对值尺度;这里返回的是重建值,便于直接计算误差。

程序依次核对四维线性层的量化输出、量化组长的影响、三个候选的校准损失、可学习正交因子的组合,以及缩放与旋转的配对。学习部分核对参数化和正交关系;实际梯度更新需要另外定义量化算子的反向近似。

python
import numpy as np


def q_sylvester(n):
    if n <= 0 or n & (n - 1):
        raise ValueError("n must be a positive power of two")
    h = np.ones((1, 1), dtype=np.float64)
    while h.shape[0] < n:
        h = np.block([[h, h], [h, -h]])
    return h / np.sqrt(n)


def quant_group(x, group, qmax=7):
    x = np.asarray(x, dtype=np.float64)
    if group <= 0 or x.shape[-1] % group:
        raise ValueError("group must divide the last dimension")
    z = x.reshape(*x.shape[:-1], -1, group)
    peak = np.max(np.abs(z), axis=-1, keepdims=True)
    scale = np.where(peak > 0, peak / qmax, 1.0)
    code = np.clip(np.rint(z / scale), -qmax, qmax)
    return (code * scale).reshape(x.shape)

q2, q4 = (q_sylvester(n) for n in (2, 4))

# 先检查精确配对,再计算加入量化后的输出。
x = np.array([[8., 0, 0, 0]])
w = np.array([[1., 2, 3, 4], [-1., 1, -1, 1]])
xr, wr = x @ q4, w @ q4
assert np.allclose(xr, [[4, 4, 4, 4]])
assert np.allclose(wr, [[5, -1, -2, 0], [0, -2, 0, 0]])
assert np.allclose(xr @ wr.T, x @ w.T)
y_hat = quant_group(xr, 4) @ quant_group(wr, 4).T
assert np.allclose(y_hat, [[60 / 7, -8]])

# 旋转结果固定,只改变共享 scale 的组长。
rb = np.kron(np.eye(2), q4)
x8 = np.array([4., 3, 2, 1, .4, .3, .2, .1])
z8 = x8 @ rb
assert np.allclose(z8, [5, 1, 2, 0, .5, .1, .2, 0])
err8 = np.sum((quant_group(z8, 8) - z8) ** 2)
err2 = np.sum((quant_group(z8, 2) - z8) ** 2)
assert np.allclose([err8, err2], [97 / 490, 101 / 1225])
print(f"G=8 squared error: {err8:.5f}")
print(f"G=2 squared error: {err2:.5f}")

# 在相同数据和量化规则下比较候选。
x_calib = np.array([[8., 0, 0, 0], [4., 3, 2, 1], [1., -1, 1, -1]])
w_calib = np.array([[1., 2, 3, 4], [-1., 1, -1, 1]])
y_ref = x_calib @ w_calib.T
candidates = {
    "I": np.eye(4),
    "Q4": q4,
    "D Q4": np.diag([1., 1, 1, -1]) @ q4,
}
scores = {}
for name, rotation in candidates.items():
    xa, wa = x_calib @ rotation, w_calib @ rotation
    assert np.allclose(xa @ wa.T, y_ref)
    y_quant = quant_group(xa, 4) @ quant_group(wa, 4).T
    scores[name] = np.mean((y_quant - y_ref) ** 2)
    print(f"{name} calibration MSE: {scores[name]:.6f}")
assert np.allclose(list(scores.values()),
                   [1.0545605997501013, .1650006941552131, 1.0545605997501])
best_name = min(scores, key=scores.get)
assert best_name == "Q4"
print(f"Selected candidate: {best_name}")

# 每层按指定的两两配对应用行向量 Givens 旋转。
theta = np.pi / 4
c, s = np.cos(theta), np.sin(theta)
givens = np.array([[c, -s], [s, c]])
assert np.allclose(givens @ np.diag([1., -1.]), q2)
butterfly = np.eye(4)
for pairs in (((0, 1), (2, 3)), ((0, 2), (1, 3))):
    layer = np.eye(4)
    for pair in pairs:
        layer[np.ix_(pair, pair)] = givens
    butterfly = butterfly @ layer
assert np.allclose(np.eye(4)[0] @ butterfly, [.5, -.5, -.5, .5])
assert np.allclose(butterfly @ butterfly.T, np.eye(4))

# 输入先除以通道尺度,权重先乘以通道尺度。
x, w = np.array([8., 0]), np.array([1., 2])
scale_matrix = np.diag([4., 1.])
inv_scale = np.diag([.25, 1.])
xt, wt = x @ inv_scale @ q2, w @ scale_matrix @ q2
assert np.allclose(xt, [np.sqrt(2), np.sqrt(2)])
assert np.allclose(wt, [3 * np.sqrt(2), np.sqrt(2)])
assert np.allclose(xt @ wt.T, x @ w.T)
assert np.allclose(x @ q2 @ inv_scale, [np.sqrt(2), 4 * np.sqrt(2)])

print("All quantization and transform-selection examples passed.")

预期输出:

text
G=8 squared error: 0.19796
G=2 squared error: 0.08245
I calibration MSE: 1.054561
Q4 calibration MSE: 0.165001
D Q4 calibration MSE: 1.054561
Selected candidate: Q4
All quantization and transform-selection examples passed.

这些结果核对了小矩阵的量化误差、候选比较和参数化关系。模型准确率与生成质量,需要在对应模型和独立评价数据上测量。

参考资料

  • DuQuant:数据相关的块内旋转、通道重排与多级组合。

  • SpinQuant:正交约束下的旋转学习,以及任务损失与变换参数的联系。

  • FlatQuant:一般可逆变换、Kronecker 参数化与量化校准。

  • TR-DQ:扩散模型中的时间步相关重排与旋转。

  • ButterflyQuant:可学习的结构化蝶形变换。