2. 量化类型:对称量化 vs 非对称量化,权重量化 vs 激活量化
好,咱们接着聊量化。上一章我们把量化的基本原理讲清楚了,说白了就是用更少的比特去近似表示原来的浮点数。但具体怎么近似?这里面就有门道了。我个人习惯把量化类型分成两个维度来看:一个是数据分布的处理方式(对称 vs 非对称),另一个是量化对象(权重 vs 激活)。这两个维度交叉组合,基本覆盖了你在边缘设备上会遇到的所有场景。
2.1 对称量化 vs 非对称量化
先说说对称量化。什么叫对称?就是量化后的整数范围关于0对称。比如int8的范围是[-128, 127],对称量化会把浮点数的0映射到整数的0上。你想想看,这样做的好处是什么?计算简单啊!因为零点就是0,做乘法加法的时候不用额外处理偏移量。
我在项目中遇到过这样一个场景:部署一个轻量级的人脸检测模型到Cortex-M4上。当时我图省事,直接用了对称量化。结果发现模型精度掉了将近5个点。排查了半天,问题出在哪?原来这个模型的权重分布并不对称,很多值都集中在正半轴。对称量化相当于把一半的量化范围浪费掉了。
对称量化的核心公式:
scale = max(|x_min|, |x_max|) / 127
quantized_value = round(x / scale)
注意这里没有zero_point,计算效率最高。
那非对称量化呢?说白了就是允许零点偏移。浮点数的0可以映射到整数范围内的任意一个值。这样做的好处是能更好地适配数据的实际分布。比如你的激活值全是正数,那非对称量化就能把整个整数范围都用上。
我的经验:对于ReLU后的激活值(全是非负的),非对称量化通常比对称量化多保留1-2个比特的有效精度。这个在低比特量化(比如4bit)时特别明显。
非对称量化的公式稍微复杂一点:
scale = (x_max - x_min) / 255
zero_point = round(-x_min / scale)
quantized_value = round(x / scale) + zero_point
嗯,这里要注意,zero_point的计算结果要钳位到[0, 255]范围内。我曾经见过一个同事忘了做钳位,结果量化后的值直接溢出,模型推理结果全乱了。
2.2 权重量化 vs 激活量化
接下来聊第二个维度:量化对象。权重和激活,虽然都是张量,但它们的统计特性差别很大。我建议你分开对待。
2.2.1 权重量化
权重有什么特点?训练完就固定了,不会变。这意味着我们可以离线做很多精细的优化。比如逐层量化、逐通道量化,甚至可以用更复杂的校准算法。
我个人习惯对权重使用对称量化。为什么?因为训练好的权重通常分布比较对称,而且权重的值范围相对稳定。我在做MobileNetV2的量化时做过对比:对称量化 vs 非对称量化,精度差异在0.1%以内。那当然选计算更简单的对称量化了。
避坑指南:我曾经在量化一个深度可分离卷积层时,发现某些通道的权重范围特别小(比如[-0.01, 0.01]),而其他通道的范围很大。如果对整个层使用同一个scale,小范围通道的权重会被直接量化成0。解决办法是使用逐通道量化(per-channel quantization),每个通道独立计算scale。
2.2.2 激活量化
激活值就麻烦多了。它随着输入变化,你没法提前知道它的精确分布。所以激活量化通常需要校准数据集来统计分布范围。
激活值的分布有几个特点:
- 范围变化大:不同层的激活值范围可能差好几个数量级
- 常有异常值:某些极端输入会产生很大的激活值
- 分布不对称:ReLU后的激活全是正数
对于激活量化,我建议使用非对称量化。特别是当激活值经过ReLU后,非对称量化能充分利用整数范围。我在量化一个语义分割模型时试过:对称量化导致分割边界出现大量噪点,换成非对称量化后,效果肉眼可见地改善了。
激活量化的校准方法:
- MinMax:直接用校准集上的最小值和最大值。简单,但容易被异常值带偏。
- Percentile:去掉两端的极端值,比如取99.9%分位数。我比较推荐这个。
- KL散度:最小化量化前后分布的KL散度。效果好,但计算量大。
2.3 四种组合的实际选择
好了,两个维度交叉一下,我们得到四种组合:
| 组合 | 权重 | 激活 | 适用场景 |
|---|---|---|---|
| 对称+对称 | 对称 | 对称 | 计算效率最高,适合对延迟要求极端的场景 |
| 对称+非对称 | 对称 | 非对称 | 我常用的组合,权重用对称省计算,激活用非对称保精度 |
| 非对称+对称 | 非对称 | 对称 | 很少用,除非权重分布极度不对称 |
| 非对称+非对称 | 非对称 | 非对称 | 精度最高,但计算复杂度也最高 |
我个人最常用的是对称权重量化 + 非对称激活量化。这个组合在大多数边缘设备上都能取得不错的平衡。当然,如果你的芯片有专门的硬件支持非对称计算(比如某些NPU),那全非对称也是可以的。
一个小技巧:在做量化时,不要一次性把所有层都量化了。我习惯逐层量化并验证精度。如果某一层量化后精度掉得厉害,就单独对这一层做非对称量化或者保留浮点。这种混合精度量化的策略,在实际项目中非常实用。
最后说一句,量化类型的选择没有银弹。我见过有人在树莓派上跑YOLOv5,对称量化效果就很好;也见过在STM32上跑语音唤醒模型,非对称量化才勉强达标。关键是要理解你的数据和硬件,然后做出合适的选择。