4. 对称量化与非对称量化:公式推导、零点作用与实战对比

量化,说白了就是把模型里那些浮点数(FP32)给“压缩”成低精度的整数(比如 INT8)。但怎么压缩?直接截断肯定不行,精度会崩。这里就有两个流派:对称量化和非对称量化。我刚开始接触量化时,也在这两个概念上绕了很久,今天咱们一次性把它讲透。

4.1 对称量化:简单粗暴,但有个坑

对称量化的核心思想很简单:让浮点数的 0 和量化后的 0 严格对齐。也就是说,你输入数据的正负范围,被对称地映射到量化整数的正负范围上。

它的数学公式长这样:

Q = round( S * R )

其中,R 是原始浮点数,Q 是量化后的整数,S 是缩放因子(Scale)。

反量化公式就是:

R = Q / S

你看,这里没有零点(Zero Point)。为什么?因为对称量化强制零点为 0。这意味着,如果你的浮点数范围是 [-10.0, 10.0],那么量化到 INT8(范围 [-128, 127])时,-10.0 对应 -128,10.0 对应 127。

缩放因子 S 怎么算?

很简单,取浮点数值的最大值:

S = max( abs(R_max), abs(R_min) ) / Q_max

对于 INT8,Q_max 通常是 127(或者 127.0,避免整数除法)。

我在项目中遇到过一个问题:如果模型权重分布非常不均匀,比如大部分值在 [-0.1, 0.1] 之间,但有几个离群点跑到 10.0 了。用对称量化,S 会被那个 10.0 拉得很大,导致 [-0.1, 0.1] 这个密集区间的量化精度严重损失。说白了,就是被“一颗老鼠屎坏了一锅粥”。

4.2 非对称量化:更灵活,但多一个参数

非对称量化就是为了解决上面那个问题而生的。它不强制零点对齐,而是引入了一个 零点(Zero Point, ZP) 来偏移映射关系。

公式变成:

Q = round( S * R + ZP )

反量化:

R = (Q - ZP) / S

这里,ZP 是一个整数,它代表浮点数 0 在量化后对应的整数值。举个例子,如果浮点数范围是 [2.0, 10.0],量化到 [0, 255](UINT8)。那么浮点数的 0 并不在范围内,但 ZP 会负责把整个范围“平移”到量化空间里。

零点(Zero Point)的作用是什么?

说白了,零点就是用来处理那些“不对称”的数据分布的。比如 ReLU 激活函数的输出全是非负数,用对称量化会浪费一半的量化范围(负数部分),而用非对称量化就能把整个 [0, 255] 范围都用上。

缩放因子 S 的计算也变了:

S = (R_max - R_min) / (Q_max - Q_min)

零点 ZP 的计算:

ZP = round( Q_min - R_min / S )

嗯,这里要注意,ZP 计算出来后,通常要 clamp 到 [Q_min, Q_max] 范围内,防止溢出。

4.3 实战对比:什么时候用哪个?

光讲理论没意思,咱们直接看对比。我整理了一个表格,方便你快速决策:

特性 对称量化 非对称量化
零点 固定为 0 需要计算和存储
计算复杂度 低(少一次加减法) 稍高(多一次 ZP 操作)
适用场景 权重(通常对称分布) 激活值(如 ReLU 输出)
精度损失 数据不对称时损失大 能更好适应数据分布
硬件支持 大部分硬件原生支持 部分硬件需要额外处理

我个人习惯是:权重用对称量化,激活值用非对称量化。为什么?因为权重经过训练后,分布通常接近以 0 为中心的正态分布,对称量化不会浪费太多范围。而激活值,尤其是 ReLU 后的,全是非负数,非对称量化能更好地利用量化精度。

我曾经踩过的坑:

有一次我图省事,对全模型都用对称量化。结果模型精度掉了 3 个点。排查了半天,发现是某个层的激活值分布严重偏斜,对称量化把大部分值都挤到了几个量化 bin 里。换成非对称量化后,精度立马回来了。所以,别偷懒,该用非对称的地方一定要用。

4.4 知识体系:一张图看懂量化选择

为了让你更直观地理解,我画了一张流程图。它展示了从数据分布到量化策略选择的完整逻辑:

量化策略选择流程图 输入数据分布 数据是否以0为中心对称? 是 对称量化 否 非对称量化 S = max(|R|) / Q_max ZP = 0 适用:权重、正态分布数据 S = (R_max - R_min) / (Q_max - Q_min) ZP = round(Q_min - R_min / S) 适用:ReLU激活、偏斜分布数据 量化参数 (S, ZP) 确定 注意:ZP需clamp

你想想看,这张图其实就概括了咱们今天讲的核心。从数据分布出发,判断是否对称,然后选择对应的量化策略,最后计算出 S 和 ZP。就这么简单。

4.5 实战代码:手算一个例子

光说不练假把式。咱们手算一个例子,加深理解。

假设有一组浮点数激活值:[0.5, 1.2, 3.8, 5.0, 7.1, 9.3],要量化到 UINT8(范围 [0, 255])。

对称量化:

R_max = 9.3, R_min = 0.5
S = max(|9.3|, |0.5|) / 127 = 9.3 / 127 ≈ 0.0732
Q = round(0.0732 * R)
Q_values = [0, 0, 0, 0, 1, 1]  // 大部分值被压缩到0附近,精度损失严重

非对称量化:

R_max = 9.3, R_min = 0.5
S = (9.3 - 0.5) / (255 - 0) = 8.8 / 255 ≈ 0.0345
ZP = round(0 - 0.5 / 0.0345) = round(-14.49) = -14  // clamp到0
ZP = 0
Q = round(0.0345 * R + 0)
Q_values = [0, 0, 0, 0, 0, 0]  // 还是不行?

等等,这里有个问题。因为 R_min 是 0.5,不是 0,所以 ZP 计算出来是负数,但 UINT8 的 Q_min 是 0,所以 ZP 被 clamp 到 0 了。这导致非对称量化退化为对称量化。

实战经验:

如果数据的最小值离 0 很远,非对称量化才能真正发挥作用。比如数据范围是 [2.0, 10.0],ZP 会是一个正数,把整个范围平移到量化空间里。所以,别看到非对称就觉得一定比对称好,得看数据。

好了,对称量化和非对称量化的核心内容就这些。记住,没有银弹,根据你的数据分布和硬件特性,选择合适的策略才是王道。