量化误差分析
本文主要记录一些模型量化出现精度问题时的分析思路以及背后的数学原理,聚焦于量化底噪和 gain 对于输出相对误差的影响,以及低秩分支量化对底噪的影响。
gain 的定义及其直观理解
我们知道一个各向同性(列向量两两正交且模长相等)的输入 \(x\in\mathbb{R}^{M\times K}\) 满足 \(x^\top x=\frac{\Vert x\Vert ^2_F}{K}I\),此时考虑任意线性变换 \(A\in\mathbb{R}^{N\times K}\),有
\[ \begin{align*} \Vert xA^\top\Vert _F^2&=\text{Tr}((xA^\top)^\top(xA^\top))\\ &=\text{Tr}(Ax^\top xA^\top)\\ &=\frac{\Vert x\Vert ^2_F}{K}\text{Tr}(AA^\top)\\ &=\frac{\Vert x\Vert ^2_F\Vert A\Vert _F^2}{K} \end{align*} \]
下面我们将 \(\Vert x\Vert _F\) 简写为 $x$,即:
\[ \Vert xA^\top\Vert =\frac{\Vert x\Vert \Vert A\Vert }{\sqrt{K}} \]
由此我们引出 gain 的定义:
\[ \begin{align*} \text{gain}(A)=\frac{\Vert xA^\top\Vert /\Vert x\Vert }{\Vert A\Vert /\sqrt{K}}=\frac{\text{激活}\ x\ \text{经过}\ A\ \text{的放大率}}{\text{随机方向经过}\ A\ \text{的平均放大率}}\tag{1} \end{align*} \]
显然当 \(x\) 各向同性时 \(\text{gain}(A)=1\)。一个 \(K=2\) 的小例子,假设 \(A\) 两个方向的奇异值分别为 1 和 10,则 \(\Vert A\Vert =\sqrt{1^2+10^2}\approx10.05\),基线放大率为 \(10.05/\sqrt2 \approx 7.11\),考虑三种情况:
- 激活全落在 \(v_1\):实际放大 1 倍,\(\text{gain}=1/7.11=0.141\)
- 激活全落在 \(v_2\):实际放大 10 倍,\(\text{gain}=10/7.11=1.41\)(\(=\sqrt2\),这是上限)
- 激活各向同性:\(\text{gain} = 1\)
可以看到,对于一个谱很平的矩阵(所有 \(\sigma\) 差不多)无论激活怎么放,gain 都会在 1 附近。下面我们继续推导 gain 如何帮助我们分析量化给输出带来的精度误差。
gain的值不是矩阵自身的特性,而是激活和矩阵共同决定的,所以必须用实际数据校准过程中的数值计算得到,才有分析价值。
量化误差的计算
设激活 \(x\in\mathbb{R}^{M\times K}\),权重 \(W\in\mathbb{R}^{N\times K}\),输出 \(y=xW^\top\)。
激活、权重量化后各自记为 \(\hat{x},\hat{W}\),对应的输出 \(\hat{y}=\hat{x}\hat{W}^\top\)。
量化导致的误差可以视作在原矩阵上叠加一个误差矩阵 \(E\),所以量化后的输出误差为:
\[ \begin{align*} \hat{y}-y&=(x+E_x)(W+E_W)^\top-xW^\top \\ &=E_xW^\top+xE^\top_W+\underbrace{E_xE_W^\top}_{\text{二阶可忽略}}\\ &=E_xW^\top+xE^\top_W \end{align*} \]
由于量化误差本质上是舍入造成的,所以 \(E\) 可被近似视作各向同性,因此:
\[ \begin{align*} \Vert E_xW^\top\Vert \approx\frac{\Vert E_x\Vert \Vert W\Vert }{\sqrt{K}}=\frac{\Vert E_x\Vert }{\Vert x\Vert }\cdot\frac{\Vert x\Vert \Vert W\Vert }{\sqrt{K}}=\varepsilon_x\cdot\frac{\Vert x\Vert \Vert W\Vert }{\sqrt{K}}\\ \Vert xE_W^\top\Vert \approx\frac{\Vert x\Vert \Vert E_W\Vert }{\sqrt{K}}=\frac{\Vert E_W\Vert }{\Vert W\Vert }\cdot\frac{\Vert x\Vert \Vert W\Vert }{\sqrt{K}}=\varepsilon_W\cdot\frac{\Vert x\Vert \Vert W\Vert }{\sqrt{K}} \end{align*} \]
其中 \(\varepsilon\) 表示量化对激活或权重本身造成的相对舍入误差。激活和权重的舍入误差互不相干,所以又有:
\[ \begin{align*} \Vert y-\hat{y}\Vert &=\sqrt{\Vert E_xW^\top\Vert ^2+\Vert xE_W^\top\Vert ^2}\notag\\ &=\sqrt{\varepsilon_x^2+\varepsilon_W^2}\cdot\frac{\Vert x\Vert \Vert W\Vert }{\sqrt{K}}\tag{2} \end{align*} \]
量化给最终输出带来的相对误差为 \(\text{err}=\frac{\Vert \hat{y}-y\Vert }{\Vert y\Vert }=\frac{\Vert \hat{y}-y\Vert }{\Vert xW^\top\Vert }\),由 (1),(2) 两式可得:
\[ \begin{align*} \text{err}=\sqrt{\varepsilon_x^2+\varepsilon_W^2}\cdot\frac{\Vert x\Vert \Vert W\Vert }{\Vert xW^\top\Vert \sqrt{K}}=\frac{\sqrt{\varepsilon_x^2+\varepsilon_W^2}}{\text{gain}(W)}\tag{3} \end{align*} \]
量化误差的分析思路
\(\text{err}\times\text{gain}(W)=\sqrt{\varepsilon_x^2+\varepsilon_W^2}\) 这个漂亮的公式能给误差分析工作带来很大的指导意义:在量化的实践中我们往往是已知一个量化方案的舍入误差的(底噪),所以只需要根据校准数据集的实际激活计算出待量化权重的 gain,以及最终量化结果的实际相对误差,就能根据这些数据判断整个量化流程是否在符合预期地 work。
比如,模型量化后实测结果不符合预期,排查时通过计算发现 \(\text{err}\times\text{gain}(W)\) 明显大于量化方案的预期底噪,这往往就能说明量化后的相对误差(\(\frac{\Vert E_W\Vert }{\Vert W\Vert }\))不全是舍入误差,很有可能是量化 pipeline 中引入了其他变量。
笔者在进行 svdquant 的过程中曾经发生过按照 sample 轴并行量化时不同 rank 减去了不同的低秩分支,但合并 checkpoint 时统一加回 rank0 的低秩分支,从而导致底噪数量级增加的事故,正是通过这种方式逐步排查发现的。
又比如,量化方案的底噪是相对固定的常数,所以 gain 越小的矩阵经过量化后最终输出的相对误差就会越大,这能告诉我们如果模型整体量化效果不佳,要对某些层保留精度,哪些模块是更需要优先保留高精度的。
低秩量化方案对底噪的影响
以 svdquant 为代表的低秩量化方案会把权重矩阵中奇异值最大的前若干个 rank 保留高精度,而只对残差 \(R\) 进行量化:
\[ \begin{align*} W&=L_1L_2+R\\ \text{Quant}(W)&=L_1L_2+\text{Quant}(R) \end{align*} \]
而且 svdquant 论文中的做法是高精度低秩分支使用未量化的 16bit 激活,所以此时 (2) 式对应改为:
\[ \begin{align*} \Vert y-\hat{y}\Vert &=\sqrt{\varepsilon_x^2+\varepsilon_R^2}\cdot\frac{\Vert x\Vert \Vert R\Vert }{\sqrt{K}}\tag{4} \end{align*} \]
- 式改为:
\[ \begin{align} \text{err}&=\sqrt{\varepsilon_x^2+\varepsilon_R^2}\cdot\frac{\Vert x\Vert \Vert R\Vert }{\Vert xW^\top\Vert \sqrt{K}}\notag\\ &=\frac{\sqrt{\varepsilon_x^2+\varepsilon_R^2}}{\text{gain}(W)}\cdot\frac{\Vert R\Vert }{\Vert W\Vert}\tag{5} \end{align} \]
从 (5) 式中我们不难看出,低秩分支的剥离本质上是给量化方案的底噪乘上了一个系数 \(\frac{\Vert R\Vert }{\Vert W\Vert }\)。而从奇异值分解的角度来看,这个系数和奇异值谱的关系是(假设低秩分支数为 32,所有 \(\sigma\) 已按降序排列):
\[ \begin{align*} \Vert W\Vert &=\sqrt{\sum_{i=1}^{K}\sigma_i^2}\\ F_{32}&=\frac{\sum_{i=1}^{32}\sigma_i^2}{\sum_{i=1}^{K}\sigma_i^2}\\ \frac{\Vert R\Vert }{\Vert W\Vert }&=\sqrt{1-F_{32}} \end{align*} \]
也就是说保留高精度的低秩分支所占能量(即奇异值的平方和)比例越高,svdquant 的量化底噪就会越低。且二者不是简单的线性关系:低秩分支能量占比为 80% 时,底噪约为低秩能量占比 20% 时的 50%——如果想让底噪降低 50%,需要把残差的能量占比降低到原有的 1/4,这通常需要保留多得多的 rank 在高精度,从而大幅降低量化的推理性能收益。
所以 svdquant 最有效的场景实际上是奇异值谱非常陡峭,前几个 rank 的能量占比远大于后续 rank 的情况,此时只需保留很少的低秩分支,即可大幅降低量化底噪。反之,对于谱很平的矩阵(常见于定位为精修层的深层 block),svdquant 的收益会非常有限。
当然,svdquant 会通过 smooth 人为创造陡奇异值谱,但 smooth 在 nvfp4 这类 group size 很小的方案中产生的作用也很有限,不过这对 group size 128(甚至 per-tensor)的 int4 W4A4 量化会非常有效。
总结
量化带来的误差背后有着深刻的数值分析和线性代数原理,了解这些概念对于实际量化工作的 bug 排查、收益评估有很重要的作用。
笔者认为这其中最关键的一环是理解公式 (3) 的应用方式。通过 err、gain、底噪这三者的数学关系,我们可以解释、预测每个矩阵量化后的误差情况,从而让整套量化 pipeline 工作在预期之中。