4、权重量化与激活量化:权重量化的策略、激活量化的挑战、动态范围与静态范围
好,咱们进入第四章。这一章聊的是量化里最核心的两个对象——权重和激活值。
很多人刚开始学量化,觉得不就是把32位浮点数砍成8位整数嘛,有什么难的?
嗯,我当年也是这么想的。直到我在一个边缘设备上跑模型,发现精度直接掉了5个点,才意识到这里面的门道有多深。
今天咱们就把权重量化和激活量化掰开揉碎了讲清楚。
4.1 权重量化的策略
权重是什么?是模型训练好的参数,是固定的。你训练完了,权重就定在那了,不会因为输入图片不同而改变。
这就带来一个好处——我们可以离线分析权重的分布,然后选择最合适的量化策略。
我个人习惯把权重量化策略分成三类:
- 对称量化:量化范围关于0对称。比如权重范围是[-1.0, 1.0],量化到[-127, 127]。
- 非对称量化:量化范围不关于0对称。比如权重范围是[-0.8, 1.2],那就用零点偏移来映射。
- 逐通道量化:每个卷积核单独算自己的缩放因子,而不是整个层共用一套。
你可能会问,这三种策略到底怎么选?
我直接说结论:对于大多数卷积网络,逐通道对称量化是性价比最高的选择。
为什么?
因为权重分布通常是对称的,而且不同通道的分布差异很大。我在项目中遇到过,一个卷积层有64个通道,有的通道权重范围是[-0.1, 0.1],有的却是[-0.5, 0.5]。如果整个层共用一套量化参数,小范围的通道几乎要被噪声淹没了。
来看一段伪代码,展示逐通道量化的核心逻辑:
# 逐通道对称量化
for channel in range(num_channels):
w_ch = weights[channel, :, :, :]
scale = max(abs(w_ch)) / 127.0
q_ch = round(w_ch / scale)
q_ch = clamp(q_ch, -127, 127)
quantized_weights[channel, :, :, :] = q_ch
scales[channel] = scale
这里要注意,scale是每个通道独立计算的。推理时,反量化也要按通道来。
4.2 激活量化的挑战
激活量化比权重量化难得多。
为什么?因为激活值是动态的。你输入一张猫的图片和一张狗的图片,中间层的激活值分布可能完全不同。
我刚开始做激活量化时,犯过一个低级错误——直接用训练集的激活值分布来定量化范围。结果验证集上跑得好好的,一到测试集就崩了。
后来我才明白,激活量化的核心挑战有三个:
- 分布不稳定:不同输入产生的激活值范围差异很大。
- 异常值问题:偶尔会出现几个特别大的激活值,把量化范围撑得很大。
- 逐层累积误差:前一层的量化误差会传递到下一层,越往后误差越大。
举个例子,假设某层激活值99%都在[-3.0, 3.0]之间,但偶尔有个值跑到10.0。如果你按[-10.0, 10.0]来量化,那大部分值的精度就浪费了。如果你按[-3.0, 3.0]来量化,那个10.0就会被截断,产生巨大误差。
怎么解决?
我建议用校准数据集来统计激活值的分布。具体做法是:
- 拿几百张有代表性的图片跑一遍模型
- 记录每一层激活值的最大值、最小值、百分位数
- 根据这些统计量来设定量化范围
4.3 动态范围与静态范围
这两个概念很多人搞混,我简单说清楚。
静态范围:量化参数在推理前就定好了,推理过程中不变。
动态范围:量化参数在推理时根据实际输入动态调整。
说白了,静态范围是「一刀切」,动态范围是「看人下菜碟」。
来看一个对比表格:
| 特性 | 静态范围 | 动态范围 |
|---|---|---|
| 计算开销 | 低(参数固定) | 高(每次推理都要算) |
| 精度 | 一般 | 较高 |
| 硬件友好度 | 高 | 低 |
| 适用场景 | 边缘设备、实时推理 | 服务器端、精度优先 |
我个人在边缘设备上几乎只用静态范围。为什么?因为动态范围需要在推理时计算最大值和最小值,这个操作在CPU上很慢,在NPU上甚至不支持。
但静态范围也有坑。你想想看,如果校准集和实际部署场景的数据分布不一致,那静态范围就选不准了。
我遇到过最典型的情况:一个目标检测模型,校准集用的是白天的街景,部署时却遇到了大量夜间场景。结果激活值分布完全变了,量化后的模型几乎没法用。
怎么避免?
- 校准数据集要尽可能覆盖部署场景
- 如果场景变化太大,考虑用运行时统计来动态调整量化范围
- 实在不行,就回退到FP16推理,虽然慢一点但至少不会崩
权重是静态的,适合离线分析和逐通道量化。
激活值是动态的,需要校准数据集来统计分布。
静态范围省计算,动态范围保精度。
在边缘设备上,优先用静态范围,但一定要做好校准数据集的覆盖。
嗯,这一章的内容就到这里。下一章咱们会讲量化感知训练,也就是怎么让模型在训练时就「学会」量化带来的误差。到时候我会分享一个我在项目里踩过的坑——训练时精度很高,量化后直接崩了,后来发现是伪量化操作没加对位置。
到时候细聊。