4、量化理论基础:对称量化与非对称量化、量化参数(scale/zero_point)、量化公式推导与反量化过程

好,咱们进入第四章。这一章是纯理论,但也是后面所有实战的根基。我见过不少同学,代码跑得飞起,一问他 scale 怎么算出来的,立马卡壳。嗯,这不行。量化参数搞不清楚,后面调精度、修 bug 就会像无头苍蝇。

说白了,量化就是把连续的浮点数,映射到离散的整数空间。为什么要这么干?因为端侧芯片的算力有限,内存也金贵。FP32 的模型跑在手机上,功耗和延迟都扛不住。INT8 计算,速度快 2-4 倍,内存省 75%。代价呢?精度会掉一点。我们的任务就是让这个代价最小化。

4.1 对称量化 vs 非对称量化

先看两种最基本的量化策略。我个人习惯,先看名字猜含义——对称,就是正负范围对称;非对称,就是可以不对称。

4.1.1 对称量化

对称量化,映射关系是 real_value = scale * quantized_value。注意,这里没有 zero_point。也就是说,浮点数的 0,量化后还是 0。正负范围完全对称。

举个例子,假设浮点范围是 [-10.0, 10.0],量化到 INT8(范围 -128 到 127)。那么 scale 就是 (10.0 - (-10.0)) / (127 - (-128)) = 20.0 / 255 ≈ 0.07843。量化后的值就是浮点数除以 scale,再四舍五入。

核心特点:

  • zero_point 固定为 0
  • 实现简单,计算量小
  • 如果浮点分布不对称(比如全是正数),会浪费一半的量化范围

我在项目中遇到过,用对称量化量化 ReLU 后的激活值。ReLU 输出全是非负的,但对称量化硬生生把负半轴也分配了码字。结果就是精度损失比预期大。后来我改用了非对称量化,效果立竿见影。

4.1.2 非对称量化

非对称量化,公式是 real_value = scale * (quantized_value - zero_point)。这里 zero_point 可以不为 0。它允许我们灵活地偏移量化范围,让浮点数的 0 映射到任意整数。

还是刚才的例子,浮点范围 [0.0, 10.0],量化到 INT8。这时候 scale = (10.0 - 0.0) / (127 - (-128)) = 10.0 / 255 ≈ 0.03922。zero_point 呢?我们希望浮点 0 映射到 INT8 的 0,所以 zero_point = 0 - (0.0 / scale) = 0。但等等,如果浮点范围是 [2.0, 12.0] 呢?

这时候 scale = (12.0 - 2.0) / 255 ≈ 0.03922。zero_point = 0 - (2.0 / 0.03922) ≈ -51。也就是说,浮点 0 映射到 INT8 的 -51。你看,zero_point 把整个量化窗口平移了。

我的建议:

对于权重,我通常用对称量化。因为训练好的权重分布往往接近对称。对于激活值,尤其是经过 ReLU 的,我强烈建议用非对称量化。别问为什么,问就是血的教训。

4.2 量化参数:scale 和 zero_point

这两个参数,是量化的灵魂。scale 决定了步长,zero_point 决定了偏移。我们详细拆解一下。

4.2.1 scale(缩放因子)

scale 是一个浮点数,表示量化后的每个整数步长,对应多少浮点数值。公式是:

scale = (r_max - r_min) / (q_max - q_min)

其中 r_max 和 r_min 是浮点数的最大值和最小值,q_max 和 q_min 是量化整数的最大值和最小值(比如 INT8 就是 127 和 -128)。

scale 越小,量化精度越高。但 scale 太小,可能导致量化后的整数范围溢出。这是个 trade-off。

4.2.2 zero_point(零点)

zero_point 是一个整数,表示浮点数 0 映射到哪个量化整数。公式是:

zero_point = q_min - (r_min / scale)

注意,zero_point 需要四舍五入到整数。而且,对于对称量化,zero_point 强制为 0。

避坑指南:

我曾经在量化一个模型时,发现推理结果全错。排查了半天,发现是 zero_point 计算时没有做 clamp。INT8 的 zero_point 必须在 [-128, 127] 范围内。如果计算出来是 -130,必须截断到 -128。否则反量化时直接崩掉。

4.3 量化公式推导与反量化过程

这部分,我建议你拿纸笔跟着推一遍。别嫌麻烦,推完你就再也不会忘了。

4.3.1 量化公式推导

我们想要一个线性映射:

r = S * (q - Z)

其中 r 是浮点数,q 是量化整数,S 是 scale,Z 是 zero_point。

已知两个端点:

  • 当 r = r_min 时,q = q_min
  • 当 r = r_max 时,q = q_max

代入公式:

r_min = S * (q_min - Z)
r_max = S * (q_max - Z)

两式相减:

r_max - r_min = S * (q_max - q_min)
S = (r_max - r_min) / (q_max - q_min)

然后求 Z:

Z = q_min - (r_min / S)

嗯,就是这么简单。但实际工程中,r_min 和 r_max 怎么选?是取训练集的最大最小值,还是取百分位?这属于校准方法,后面章节会细讲。

4.3.2 反量化过程

反量化,就是把量化后的整数,还原回浮点数。公式就是量化公式的逆运算:

r = S * (q - Z)

注意,反量化后的浮点数,和原始浮点数是有误差的。这个误差叫量化误差,主要来自四舍五入。

举个例子,假设 S=0.1,Z=0,原始浮点 r=3.14159。量化:q = round(3.14159 / 0.1) = round(31.4159) = 31。反量化:r' = 0.1 * (31 - 0) = 3.1。误差 = 3.14159 - 3.1 = 0.04159。

你看,误差就这么产生了。量化位宽越小,S 越大,误差越大。这就是为什么 INT4 比 INT8 精度差的原因。

关键点总结:

  • 量化:q = round(r / S) + Z
  • 反量化:r = S * (q - Z)
  • 对称量化:Z = 0
  • 非对称量化:Z ≠ 0

4.4 知识体系结构图

下面这张图,是我自己画的知识结构。你看一眼,就能把本章内容串起来。

量化理论基础 对称量化 非对称量化 量化参数 scale(缩放因子) zero_point(零点偏移) 公式推导 S = (r_max - r_min) / (q_max - q_min) Z = q_min - (r_min / S) 量化与反量化过程 量化:q = round(r / S) + Z | 反量化:r = S * (q - Z) 权重:对称量化 激活值:非对称量化 图:量化知识体系结构图

4.5 实战中的选择建议

说了这么多理论,最后给点实际建议。你在端侧芯片上做量化推理时,怎么选?

场景 推荐量化方式 原因
权重(Weight) 对称量化 权重分布通常对称,省去 zero_point 计算
激活值(Activation) 非对称量化 ReLU 后全为正数,非对称不浪费码字
偏置(Bias) FP32 或 INT32 偏置范围大,量化后精度损失严重
硬件有特殊指令 按硬件要求 有些芯片只支持对称量化,硬上非对称反而慢

个人经验:

我刚开始做量化时,总想着把所有层都量化到 INT8。后来发现,某些敏感层(比如第一层和最后一层)量化后精度掉得厉害。我的做法是:对这些层保持 FP32,其他层用 INT8。这叫混合精度量化,后面章节会详细展开。

好了,这一章的内容就到这里。量化理论是基础中的基础,你把它吃透了,后面看代码、调精度都会轻松很多。记住,scale 和 zero_point 不是随便算的,每一步都有物理意义。下次遇到量化 bug,先检查这两个参数对不对。


公众号:蓝海资料掘金营,微信deep3321