量化

从 scale 与舍入走到算法

核心解释

量化用有限的可表示值近似原来的张量。位宽决定可用多少种编码,格式决定这些编码表示什么数。先看均匀整数仿射量化:步长 s>0s>0 决定格点间距,整数零点 zz 决定哪个编码表示实数零。[1]

q=clip⁡ ⁣(R(x/s)+z,qmin⁡,qmax⁡)x^=s(q−z)\begin{aligned}q&=\operatorname{clip}\!\left(R(x/s)+z,q_{\min},q_{\max}\right)\\\hat{x}&=s(q-z)\end{aligned}

这里 RR 是舍入规则,clip 把结果限制在整数范围内。选 scale 时,要在范围覆盖与格点间距之间取舍。Min-max 覆盖观测范围,裁剪方法允许损失部分尾部值,换取更细的步长。按通道、token 或 group 共享参数,会改变这个取舍。[2]

舍入也需要精确定义。最近值舍入要处理恰好位于中点的情况,ONNX 的整数量化使用 nearest-even。随机舍入按距离分配概率。AdaRound 则根据层输出误差学习舍入方向。三者解决的问题不同。[3] [4]

量化算法关心误差怎样进入模型。GPTQ 补偿逐步量化产生的误差,AWQ 搜索权重通道缩放,SmoothQuant 平衡激活与权重的范围。省略偏置,只量化权重时,可以用下面的层输出重建目标理解这些选择,其中 W^\widehat W 受所选量化表示约束。[5] [6]

min⁡W^∥XW⊤−XW^⊤∥F2\min_{\widehat W}\left\|XW^{\top}-X\widehat W^{\top}\right\|_F^2

训练还要处理不可导的舍入与持续变化的张量分布。QAT 在前向模拟量化,反向使用近似梯度。FP8、FP4 训练则需要选择实际数值格式、scale 粒度与更新时机。推理部署时,还要检查累加精度、重新量化和后端支持,才能把存储节省转化为运行收益。[7] [8] [9]

资料

QA

为什么有的实现除以 scale,有的却乘以 scale?

先看反量化公式。若 x^=s(q−z)\hat{x}=s(q-z),ss 表示一个整数间隔对应的实数步长,量化时除以它。若代码保存 α=1/s\alpha=1/s,量化时就乘以 α\alpha。

Transformer Engine 的 FP8 文档同时展示了这两个方向:反量化乘以 ss,转换前乘以 Fmax⁡/amax⁡F_{\max}/a_{\max}。只对照变量名,容易把比例看反。

Min-max 的 scale 和 zero-point 到底怎么算?

对于保留精确零值的整数仿射量化,先令 a=min⁡(xmin⁡,0)a=\min(x_{\min},0)、b=max⁡(xmax⁡,0)b=\max(x_{\max},0)。整数范围是 [qmin⁡,qmax⁡][q_{\min},q_{\max}]。区间非退化时,用下面的公式计算步长与整数零点。

把零纳入范围,可以避免全正或全负输入得到范围外的零点。零点取整后,两端通常不能同时精确对齐。全零张量需要实现规定的正 scale 回退值,不能直接除以零。TorchAO 还会用 eps 限制最小 scale。

s=b−aqmax⁡−qmin⁡z=clip⁡ ⁣(qmin⁡−RNE⁡(a/s),qmin⁡,qmax⁡)\begin{aligned}s&=\frac{b-a}{q_{\max}-q_{\min}}\\z&=\operatorname{clip}\!\left(q_{\min}-\operatorname{RNE}(a/s),q_{\min},q_{\max}\right)\end{aligned}

对称 INT8 的 scale 应该除以 127、128,还是 127.5?

先确定实际使用的整数集合。若使用 [−127,127][-127,127],以 s=amax⁡/127s=a_{\max}/127 覆盖两侧。INT8 的完整集合是 [−128,127][-128,127],它在零的两侧并不对称。

TorchAO 的 SYMMETRIC 映射按区间宽度的一半计算,此时分母为 127.5。SYMMETRIC_NO_CLIPPING_ERR 则分别检查正负端点后取较大步长。在 z=0z=0 且正负极值都为 amax⁡a_{\max} 时,单用 amax⁡/128a_{\max}/128 会让正端点超出范围。

覆盖所有极值,为什么不一定误差最小?

一个很大的离群值会拉大步长,让大量小值落到同一个量化格点。收窄范围会损失尾部,却可能降低其余元素的舍入误差。Min-max、百分位、MSE 和基于直方图的校准,选择范围时优化的目标不同。

还要区分权重重建误差与层输出误差。输入分布会放大某些方向上的权重误差。GPTQ 使用输入二阶信息,OmniQuant 则学习裁剪与等价变换。

Per-channel、per-token 和 group size 分别沿哪里共享 scale?

设 W∈RO×IW\in\mathbb{R}^{O\times I},前向为 Y=XW⊤Y=XW^{\top}。按输出通道量化权重时,每一行共享一个 scale。常见 grouped weight quantization 则把每行沿输入特征维切成若干组。Per-token 激活量化通常让每个 token 的特征向量共享参数。

读实现时要同时检查张量布局、分组轴和 scale 的广播形状。GPTQ 的 blocksize 控制误差补偿的计算分块,groupsize 控制量化参数共享范围,两个数值相同也不代表同一件事。

“四舍五入”能准确描述 round 吗?负数怎么办?

不能只写四舍五入。Round-to-nearest 还要指定中点规则。Nearest-even 把恰好位于中间的值舍入到偶数:0.5→00.5\to0、1.5→21.5\to2、2.5→22.5\to2,负数对应为 −0.5→0-0.5\to0、−1.5→−2-1.5\to-2、−2.5→−2-2.5\to-2。

向零截断、向下取整和中点远离零是不同规则。例如 −1.8-1.8 向零得到 −1-1,向下得到 −2-2。ONNX 的整数 QuantizeLinear 24 规定 nearest-even,复现时应明确写出该规则。

整数 zero-point 可以随便移进 round 里面吗?

在 nearest-even 下,中点处不总是等价。取 x/s=0.5x/s=0.5、z=1z=1:先舍入再相加得到 0+1=10+1=1,先相加再舍入得到 RNE⁡(1.5)=2\operatorname{RNE}(1.5)=2。整数平移改变了两个候选数的奇偶性。

ONNX QuantizeLinear 24 的文字定义对 x/sx/s 做 nearest-even,再加整数零点。核验导出或手写 kernel 时,要检查中点样本、除法精度和实际运算顺序。

随机舍入是上下各一半吗?它为什么用于训练?

通常所说的无偏随机舍入按距离分配概率。若相邻可表示值为 l<x<ul<x<u,向上概率是 (x−l)/(u−l)(x-l)/(u-l),向下概率是 (u−x)/(u−l)(u-x)/(u-l)。因此在概率精确、没有饱和等附加误差时,条件期望等于输入。

它让小于半个间隔的更新仍有机会保留下来,缓解小更新不断被舍掉的问题。单次结果仍有误差,范围外饱和也不满足这条无偏结论。Transformer Engine 的 NVFP4 训练配方将它用于梯度量化。

E[SR⁡(x)∣x]=lu−xu−l+ux−lu−l=x\mathbb{E}[\operatorname{SR}(x)\mid x]=l\frac{u-x}{u-l}+u\frac{x-l}{u-l}=x

GPTQ 里面也在 round,它和直接 RTN 差在哪里?

RTN 逐个把当前权重映射到最近格点。GPTQ 也调用这样的量化器,但每量化一部分,就用输入二阶信息调整尚未量化的权重,补偿已经引入的输出误差。

因此后续 round 接收到的权重已经改变。作者实现中的 quant.py 负责 scale、零点与舍入,gptq.py 负责 Cholesky 分解和误差更新。分开读这两部分,更容易看清算法增加了什么。

AWQ 和 SmoothQuant 中的 scale,都是量化步长吗?

它们的通道变换系数先改变浮点张量的分布。令 DD 为输入特征维上的正对角矩阵,省略偏置时,有下面的等价关系。随后,量化器再为变换后的张量计算自己的 scale。

SmoothQuant 用激活与权重的通道极值平衡 W8A8 的量化难度。AWQ 用激活统计搜索权重通道缩放,目标是改善低比特权重量化。AWQ 的“保护重要权重”通过缩放实现,并不要求把这部分权重继续保存为 FP16。

XW⊤=(XD−1)(WD)⊤XW^{\top}=(XD^{-1})(WD)^{\top}

LSQ、AdaRound、AutoRound 和 GSQ 分别学习什么?

LSQ 在量化感知训练中学习步长,并设计对应的梯度估计与缩放。AdaRound 在固定量化网格上,用校准数据学习权重向上还是向下舍入。

AutoRound 链接的 SignRound 方法联合优化舍入与裁剪,降低块输出重建误差。GSQ 则用 Gumbel-Softmax 松弛网格选择,并联合优化 group scale。阅读算法时,先找它固定了什么、更新了什么,以及损失计算在哪个输出上。

INT4、FP4 和 NF4 都是 4 bit,可以共用量化公式吗?

INT4 配合固定 scale,得到等间隔的整数仿射网格。FP4 E2M1 是浮点格式,可表示值的间距并不均匀,转换要选最近的可表示浮点值。NF4 使用为近似正态权重设计的非均匀码本。

同样的位宽不代表相同的取值集合、缩放方式或 kernel。QLoRA 中的 double quantization 还会压缩量化常数,不能把 NF4 当成普通 INT4 加一个相同的 scale。

Scale 自己也会量化吗?为什么有的只允许 2 的幂?

会。MXFP8 用 E8M0 表示共享 scale,有限正值是 2 的幂。NVFP4 使用更细的块级 FP8 E4M3 scale,并结合张量级 FP32 scale。元素格式和 scale 格式需要一起看。

把理想 scale 转成受限格式又引入一次近似。若设计目标是覆盖 amax⁡a_{\max},且 scale 只能取 2 的幂,那么满足 sFmax⁡≥amax⁡sF_{\max}\ge a_{\max} 的最小值可写成下面的式子。它是这一覆盖条件的推导,不能代替所有 MX 实现的 scale 选择规则。

s=2⌈log⁡2(amax⁡/Fmax⁡)⌉(amax⁡>0)s=2^{\left\lceil\log_2(a_{\max}/F_{\max})\right\rceil}\quad(a_{\max}>0)

Current scaling 和 delayed scaling 到底差在哪一步?

Current scaling 先求当前张量的 amax,再计算缩放并转换。Delayed scaling 用历史 amax 决定这次缩放,同时记录本次 amax 供以后使用。

这个选择影响数据读取次数,也影响分布突然变化时的溢出或分辨率损失。Delayed 的历史长度、取最大值还是最近值、是否留 margin,都属于数值配方。按 token 或 block 计算 scale,则是另一个关于共享粒度的选择。

整数 GEMM 算完后,为什么还要再舍入一次?

省略偏置,且每个输入张量只用一个 scale 时,整数累加值为 Aij=∑k(qikX−zX)(qjkW−zW)A_{ij}=\sum_k(q^X_{ik}-z_X)(q^W_{jk}-z_W)。它对应的实数单位是 sXsWs_Xs_W。若输出也要量化,就需换成输出步长 sYs_Y 的单位。

实现常用定点乘法和移位近似 sXsW/sYs_Xs_W/s_Y,随后舍入并饱和。若 scale 沿归约维按组变化,就要按组缩放部分和,不能把所有缩放都合成末尾的单一常数。

qijY=clip⁡ ⁣(R ⁣(sXsWsYAij)+zY,qmin⁡,qmax⁡)q^Y_{ij}=\operatorname{clip}\!\left(R\!\left(\frac{s_Xs_W}{s_Y}A_{ij}\right)+z_Y,q_{\min},q_{\max}\right)

Round 几乎处处梯度为零,QAT 和 LSQ 怎么训练?

Fake quantization 在前向模拟量化和反量化,张量仍可保留浮点存储。反向用 straight-through estimator 等近似梯度,让权重或量化参数继续更新。它不是 round 的真实导数。

QAT 模拟目标低比特表示,与实际使用 FP8/FP4 算术的低精度训练是两个维度。QLoRA 又是另一种设置:冻结量化基座,通过它反传来训练 LoRA 参数。

校准数据只是在找最大值吗?权重与 KV cache 可以照搬吗?

校准可能用于统计范围,也可能用于估计层输入、优化舍入或最小化块输出误差。换数据会改变这些估计,所以应覆盖实际语言、任务和长度,并用独立样本检查效果。

权重是固定的,激活和 KV cache 随请求变化。KIVI 分别研究 key 与 value 的统计结构,采用不同的共享方向。不能仅因为权重量化表现好,就沿用同一粒度和参数处理所有缓存。

4 bit 为什么不是四倍省显存、四倍快?

权重还需要 scale、零点和布局信息。例如每 128 个 4-bit 权重共享一个 FP16 scale,不存零点且忽略对齐时,平均是 4+16/128=4.1254+16/128=4.125 bit/weight。更小的 group 会增加这部分开销。

执行速度还取决于解包、反量化、累加精度和 kernel。Decode 与 prefill 的矩阵形状不同,收益也可能不同。评估时把精度、总显存、prefill 延迟和 decode 吞吐放在同一配置下测量。

同一组量化参数,在不同后端的边界值上能否逐位一致?

待解

准备固定 scale、零点和分组,覆盖正负中点、饱和边界、极小值,再对照 PyTorch、ONNX 与实际 GPU kernel。记录乘倒数与直接除法、scale 存储精度,以及融合计算带来的差异。