3. 量化方法
好,咱们今天聊聊量化的具体玩法。说白了,量化就是把模型里那些浮点数(FP32)硬塞进INT8的小盒子里。怎么塞?这里头就有讲究了。
我个人习惯把量化方法分成两个维度来理解:一个是缩放方式(对称 vs 非对称),另一个是粒度(逐层 vs 逐通道)。这两个维度组合起来,基本就覆盖了90%以上的INT8落地场景。
3.1 对称量化 vs 非对称量化
先看缩放方式。你想想看,一个FP32的数值范围是[-∞, +∞],但INT8只能表示[-128, 127]或者[0, 255]。怎么映射?
对称量化,就是让零点(zero point)固定在0。也就是说,FP32的0对应INT8的0。这样做的好处是计算简单,没有零点偏移,卷积、矩阵乘这些操作可以直接用INT8算,不用额外处理零点。
但问题来了——如果模型权重分布不对称怎么办?比如某个卷积核的权重全在正数区间,对称量化就会浪费掉INT8的负数部分。我在项目中遇到过这种情况,一个层的权重范围是[0.01, 0.5],对称量化后有效精度直接砍半。
非对称量化就灵活多了。它允许零点偏移,FP32的0可以映射到INT8的任意值。这样就能充分利用INT8的整个表示范围。代价呢?计算时多一步零点减法,硬件实现稍微复杂一点。
核心区别一句话:
- 对称量化:零点固定为0,计算快,适合权重分布对称的场景
- 非对称量化:零点可偏移,精度高,适合激活值分布不对称的场景
嗯,这里要注意:激活值(activation)通常是非对称的(比如ReLU后全是正数),所以激活量化我建议用非对称。权重呢?大部分预训练模型的权重分布接近对称,用对称量化就够了。
3.2 逐层量化 vs 逐通道量化
接下来是粒度问题。说白了就是:一个层里,我用一个缩放因子管所有通道,还是每个通道各管各的?
逐层量化,就是整个层共享一个scale和zero point。实现简单,硬件友好,内存占用小。但问题也很明显——如果某个通道的数值范围特别大,其他通道就得跟着吃亏。
举个例子:一个卷积层有64个输出通道,其中63个通道的权重范围是[-0.1, 0.1],但第64个通道范围是[-1.0, 1.0]。逐层量化时,scale会被第64个通道拉大10倍,其他63个通道的精度就白白损失了。
逐通道量化,就是每个通道独立计算scale和zero point。精度提升很明显,尤其对深度可分离卷积、分组卷积这类结构。代价呢?需要存储更多的scale参数,硬件设计也更复杂。
我的经验:
我曾经在一个MobileNetV3的量化项目里,逐层量化掉点2.3%,换成逐通道量化后只掉点0.4%。所以对于轻量级模型,逐通道量化几乎是必选项。
3.3 四种组合怎么选?
把上面两个维度组合起来,就是四种方案:
| 组合 | 权重量化 | 激活量化 | 典型场景 |
|---|---|---|---|
| 对称+逐层 | ✅ 常用 | ❌ 不推荐 | 大模型、硬件受限场景 |
| 对称+逐通道 | ✅ 推荐 | ❌ 不推荐 | 通用场景,精度敏感 |
| 非对称+逐层 | ❌ 不推荐 | ✅ 可用 | 激活值分布极不对称 |
| 非对称+逐通道 | ✅ 可用 | ✅ 推荐 | 高精度要求,硬件支持 |
避坑指南:
我曾经在一个项目里对权重用了非对称+逐通道量化,结果硬件不支持零点偏移的权重计算,推理速度反而慢了30%。所以选方案前,一定先确认你的推理芯片支持什么。
3.4 知识体系总览
下面这张图帮你理清思路:
3.5 实战建议
说了这么多,到底怎么选?我给出一个通用策略:
- 权重量化:优先对称+逐通道。精度损失小,硬件实现也简单。
- 激活量化:优先非对称+逐层。如果精度不够,再升级到逐通道。
- 特殊层处理:第一层和最后一层建议保留FP32,或者用更高精度量化。
一句话总结:
对称量化省计算,非对称量化保精度。逐层量化省资源,逐通道量化保精度。具体选哪个,看你的硬件和精度要求。