2、量化原理精讲:对称量化 vs 非对称量化、量化公式推导、缩放因子与零点、量化粒度
好,咱们直接进入正题。量化这东西,说白了就是用低精度(比如 INT8)去近似高精度(FP32)的数值。为什么要这么干?因为 INT8 计算快、省显存,部署的时候真香。
但这里有个核心问题:怎么映射才最准? 我刚开始做量化的时候,以为就是把 FP32 的值除以一个数取整就完事了。后来发现,这里面门道深着呢。今天我就把对称量化、非对称量化、缩放因子、零点这些概念,掰开了揉碎了讲清楚。
2.1 量化公式:从 FP32 到 INT8 的数学桥梁
先看最核心的公式。不管什么量化方法,本质上都是这个线性映射:
Q = round( (R - Z) / S )
其中:
- R:原始的 FP32 浮点数
- Q:量化后的 INT8 整数
- S:缩放因子(Scale),一个浮点数
- Z:零点(Zero Point),一个整数
反量化公式就是反过来:
R = Q * S + Z
嗯,这里要注意。这个公式是所有量化的基础。你想想看,我们其实是在用一条直线去拟合 FP32 到 INT8 的映射关系。S 决定了这条线的斜率,Z 决定了这条线的偏移。
2.2 对称量化 vs 非对称量化
这两个概念,我当年花了整整一个下午才彻底搞明白。咱们直接看区别。
2.2.1 对称量化
对称量化的核心思想是:让 FP32 的 0 映射到 INT8 的 0。也就是说,零点 Z = 0。
公式简化成:
Q = round( R / S )
缩放因子 S 怎么算?很简单:
S = max( |R_max|, |R_min| ) / 127
这里用 127 而不是 128,是因为 INT8 的范围是 [-128, 127],我们通常把正负对称的边界留给 -128 和 127。我个人习惯用 127,因为这样能保证正负两边都能映射到。
对称量化的特点:
- 零点固定为 0,实现简单
- 适合权重(weights)量化,因为权重通常关于 0 对称
- 如果激活值(activations)全是正数(比如 ReLU 输出),对称量化会浪费一半的量化范围
我在项目中遇到过一个问题:用对称量化去量化 ReLU 的输出。ReLU 输出全是非负的,结果 INT8 的负数范围全浪费了,精度损失很大。后来换成非对称量化,效果立竿见影。
2.2.2 非对称量化
非对称量化允许 FP32 的 0 映射到 INT8 的任意整数 Z。公式就是最原始的那个:
Q = round( (R - Z) / S )
缩放因子 S 和零点 Z 的计算:
S = (R_max - R_min) / 255
Z = round( 0 - R_min / S )
这里用 255 是因为 INT8 有 256 个离散值。Z 的作用就是让 FP32 的 0 映射到 INT8 的某个整数上。
我的经验:非对称量化特别适合激活值。因为激活值往往分布不均匀,比如全是正数或者偏在某一边。用非对称量化能充分利用 INT8 的整个范围,精度更高。
说白了,对称量化是非对称量化的一种特例(Z=0)。选择哪种,取决于你的数据分布。
2.3 缩放因子与零点:量化的灵魂参数
S 和 Z 这两个参数,决定了量化的质量。我见过很多新手直接拿整个 tensor 的最大最小值去算 S 和 Z,结果精度惨不忍睹。
缩放因子 S:决定了量化的步长。S 越小,量化精度越高,但能表示的范围越小。S 越大,范围越大,但精度越低。这是个 trade-off。
零点 Z:用来对齐 FP32 的 0 和 INT8 的某个值。为什么这么重要?因为 0 在神经网络里是个特殊值,比如 padding、ReLU 的截断。如果 0 映射不准,这些操作就会出错。
避坑指南:我曾经在量化一个卷积层时,忘了检查零点 Z 是否在 INT8 范围内。结果 Z 算出来是 300,直接溢出。量化后的模型输出全是 NaN。所以,一定要确保 Z 在 [0, 255] 范围内(对于无符号 INT8)或 [-128, 127](对于有符号 INT8)。
2.4 量化粒度:per-tensor vs per-channel
量化粒度,说白了就是共享 S 和 Z 的范围有多大。
2.4.1 Per-tensor 量化
整个 tensor 共享一组 S 和 Z。比如一个形状为 [64, 3, 3, 3] 的卷积核,所有 64 个通道共用一个 S 和一个 Z。
优点:实现简单,计算开销小。
缺点:如果不同通道的数值范围差异很大,精度损失会很大。
我举个例子。一个卷积核的 64 个通道里,有些通道的值在 [-1, 1] 之间,有些在 [-100, 100] 之间。用 per-tensor 量化,S 会被大范围通道主导,小范围通道的精度就全丢了。
2.4.2 Per-channel 量化
每个通道独立计算 S 和 Z。还是那个 [64, 3, 3, 3] 的卷积核,每个输出通道都有自己的 S 和 Z。
优点:精度更高,能适应不同通道的数值分布。
缺点:需要存储更多的 S 和 Z,计算时也要做更多的处理。
实际部署建议:
- 权重:强烈建议用 per-channel 量化。我在 TensorRT 里部署时,per-channel 比 per-tensor 能提升 1-2 个点的精度。
- 激活值:通常用 per-tensor 量化。因为激活值的分布相对均匀,而且 per-channel 会引入额外的计算开销。
你想想看,为什么权重适合 per-channel?因为每个卷积核的数值范围可能完全不同。而激活值经过 BatchNorm 和 ReLU 后,分布往往比较规整。
2.5 总结与对比
咱们用一张表来总结一下:
| 特性 | 对称量化 | 非对称量化 |
|---|---|---|
| 零点 Z | 固定为 0 | 可调 |
| 实现复杂度 | 低 | 中 |
| 适用场景 | 权重(对称分布) | 激活值(非对称分布) |
| 精度 | 数据对称时高 | 数据非对称时更高 |
再来看量化粒度的对比:
| 粒度 | 精度 | 计算开销 | 存储开销 |
|---|---|---|---|
| Per-tensor | 低 | 低 | 低 |
| Per-channel | 高 | 中 | 中 |
好了,量化原理这部分就讲到这里。下一章咱们会深入 TensorRT 的 INT8 校准器,看看实际工程中怎么选这些参数。记住一句话:没有最好的量化方法,只有最适合你数据的量化方法。