2、量化基本原理:对称量化 vs 非对称量化

好,咱们直接进入正题。量化这玩意儿,说白了就是用更少的比特去表示一个数值。你想想看,FP32 有 32 个比特,INT8 只有 8 个,信息量差了 4 倍。怎么把 32 位的信息塞进 8 位里?这就是量化的核心问题。

我个人习惯把量化理解成「拍照」。FP32 是高清原图,INT8 是压缩后的 JPEG。压缩得好,人眼看不出区别;压缩得不好,满屏马赛克。咱们今天要聊的对称量化和非对称量化,就是两种不同的「压缩算法」。

2.1 对称量化(Symmetric Quantization)

对称量化,名字里带「对称」,是因为它的量化范围关于零点对称。什么意思?就是 INT8 的 0 对应 FP32 的 0,两边正负范围一样大。

公式长这样:

Q = round(R / S)

这里 R 是原始的浮点数,S 是 Scale(缩放因子),Q 是量化后的整数。注意,没有 ZeroPoint,因为 Z = 0。

Scale 怎么算?很简单:

S = max(|R_max|, |R_min|) / 127

嗯,这里要注意。INT8 的范围是 [-128, 127],但对称量化通常只用 [-127, 127],把 -128 空出来。为什么?我后面会讲,先卖个关子。

核心特点:

  • 零点对齐:FP32 的 0 量化后还是 0
  • 计算简单:没有 ZeroPoint,乘加运算少一次加法
  • 适合权重:权重通常关于 0 对称分布

我在项目中遇到过一个问题。有一次量化一个卷积层,发现激活值全是正数,用对称量化硬怼,结果精度掉得一塌糊涂。为什么?因为对称量化把一半的量化范围浪费在了负数区域,而实际数据全在正数区。这就好比用一把尺子量 0 到 100 的长度,你却把刻度从 -50 开始标,精度自然差。

2.2 非对称量化(Asymmetric Quantization)

非对称量化就灵活多了。它允许零点偏移,INT8 的 0 不一定对应 FP32 的 0。

公式是:

Q = round(R / S) + Z

这里多了一个 Z,就是 ZeroPoint(零点偏移)。

Scale 和 ZeroPoint 的计算:

S = (R_max - R_min) / 255
Z = round(-R_min / S)

你看,S 的分母是 255,因为 INT8 非对称量化用满 [0, 255] 整个范围。Z 的作用就是把浮点数的 R_min 映射到 INT8 的 0。

个人经验:

我建议激活值用非对称量化。因为 ReLU 之后的激活值全是非负的,非对称量化能把整个量化范围都用在刀刃上。权重则用对称量化,因为权重分布通常关于 0 对称,用对称量化不会浪费范围。

2.3 量化公式详解:Q = round(R/S) + Z

咱们把这个公式拆开揉碎了讲。

第一步:R / S

这一步是把浮点数缩放到整数刻度上。S 越小,刻度越密,精度越高。S 越大,刻度越疏,精度越低。

第二步:round()

四舍五入取整。这一步会引入量化误差。round 是向最近整数取整,0.5 的情况 IEEE 754 规定是向偶数取整,但 TensorRT 里我印象中用的是向零取整。具体实现可能有差异,但原理一样。

第三步:+ Z

加上零点偏移。这一步把量化后的值平移到 INT8 的表示范围内。

反量化公式也很重要:

R = (Q - Z) * S

你看,反量化就是量化的逆过程。先减 Z,再乘 S。

避坑指南:

我曾经在反量化时忘记减 Z,结果推理结果全偏了。排查了半天才发现是 ZeroPoint 没处理好。记住,量化时加了 Z,反量化时一定要减回去。

2.4 Scale 和 ZeroPoint 的计算细节

咱们用个具体例子来算一遍。假设有一组浮点数:

R = [1.2, -0.5, 3.4, -2.1, 0.0, 5.6]

对称量化:

R_max = 5.6, R_min = -2.1
S = max(|5.6|, |-2.1|) / 127 = 5.6 / 127 ≈ 0.04409
Z = 0

量化结果:
Q(1.2) = round(1.2 / 0.04409) = round(27.22) = 27
Q(-0.5) = round(-0.5 / 0.04409) = round(-11.34) = -11
Q(3.4) = round(3.4 / 0.04409) = round(77.12) = 77
Q(-2.1) = round(-2.1 / 0.04409) = round(-47.63) = -48
Q(0.0) = round(0.0 / 0.04409) = 0
Q(5.6) = round(5.6 / 0.04409) = round(127.01) = 127

非对称量化:

R_max = 5.6, R_min = -2.1
S = (5.6 - (-2.1)) / 255 = 7.7 / 255 ≈ 0.03020
Z = round(-(-2.1) / 0.03020) = round(69.54) = 70

量化结果:
Q(1.2) = round(1.2 / 0.03020) + 70 = round(39.74) + 70 = 40 + 70 = 110
Q(-0.5) = round(-0.5 / 0.03020) + 70 = round(-16.56) + 70 = -17 + 70 = 53
Q(3.4) = round(3.4 / 0.03020) + 70 = round(112.58) + 70 = 113 + 70 = 183
Q(-2.1) = round(-2.1 / 0.03020) + 70 = round(-69.54) + 70 = -70 + 70 = 0
Q(0.0) = round(0.0 / 0.03020) + 70 = 0 + 70 = 70
Q(5.6) = round(5.6 / 0.03020) + 70 = round(185.43) + 70 = 185 + 70 = 255

你看,非对称量化把 -2.1 映射到了 0,5.6 映射到了 255,用满了整个 INT8 范围。而对称量化只用了 [-128, 127] 的一部分。

对比项 对称量化 非对称量化
零点 Z = 0 Z ≠ 0
INT8 范围 [-128, 127] 或 [-127, 127] [0, 255]
计算复杂度 低(无 Z 加法) 高(多一次加法)
适用场景 权重(对称分布) 激活值(非对称分布)
精度损失 数据非对称时损失大 数据非对称时损失小

2.5 实战中的选择策略

说了这么多理论,咱们聊聊实际怎么选。

我的建议:

  • 权重:用对称量化。权重分布通常关于 0 对称,用对称量化不会浪费范围。而且没有 ZeroPoint,卷积计算时少一次加法,速度更快。
  • 激活值:用非对称量化。尤其是 ReLU 之后的激活值,全是非负的,非对称量化能把 256 个刻度全用在正数区。
  • 特殊情况:如果激活值经过 tanh 或 sigmoid,输出范围是 [-1, 1] 或 [0, 1],这时候对称量化和非对称量化差别不大。我一般用对称量化,省事。

小技巧:

TensorRT 默认对权重用对称量化,对激活值用非对称量化。但你可以通过设置量化参数来覆盖默认行为。我建议除非你很清楚自己在做什么,否则别乱改。

好了,这一章的内容就到这儿。量化公式看着简单,但里面的门道不少。下一章咱们聊聊量化校准,看看怎么在实际数据上算出最优的 Scale 和 ZeroPoint。

本章要点回顾:

  • 对称量化:Z=0,范围对称,适合权重
  • 非对称量化:Z≠0,范围灵活,适合激活值
  • 量化公式:Q = round(R/S) + Z
  • 反量化公式:R = (Q - Z) * S
  • Scale 决定精度,ZeroPoint 决定偏移