2、量化误差来源分析:权重截断误差、激活值量化误差、舍入误差
好,咱们进入正题。量化这东西,说白了就是用更少的比特去表示原来的数值。那误差从哪来?我刚开始接触量化时,总觉得不就是把float转成int嘛,能有多大损失?结果第一次跑量化模型,精度直接掉了5个点,当时就懵了。
后来我花了一周时间,把整个量化流程的误差来源挨个排查了一遍。今天就把这些经验分享给你。量化误差主要来自三个地方:权重截断误差、激活值量化误差、还有舍入误差。咱们一个一个说。
2.1 权重截断误差
权重截断误差,说白了就是你把float32的权重强行塞进int8的盒子里,盒子装不下的部分就被砍掉了。嗯,这里要注意,不是所有权重都能被均匀地塞进去的。
我记得有一次部署一个BERT模型,量化后精度掉得离谱。排查了半天,发现是权重分布有个长尾——大部分权重集中在[-0.1, 0.1]之间,但有少数几个权重值到了0.8。如果按全局最大值0.8来定量化范围,那大部分权重的有效比特位就浪费了。
核心问题:权重分布不均匀时,截断误差会显著增大。
量化范围的选择直接影响截断误差。常见的做法有两种:
- MinMax量化:直接用权重的实际最小值和最大值。简单粗暴,但对离群点敏感。
- Percentile量化:去掉头尾的极端值,比如只保留99%的权重范围。我一般用这个方法,效果更稳定。
你想想看,如果权重分布是高斯形状,那MinMax和Percentile差别不大。但如果分布有长尾,Percentile明显更优。我在项目中遇到过这种情况:用MinMax量化后精度掉了2.3%,换成Percentile(去掉0.5%的极端值)后,精度只掉了0.4%。
我的习惯:先画个权重分布直方图,看看有没有离群点。有的话,优先用Percentile量化。
2.2 激活值量化误差
激活值量化误差比权重截断误差更棘手。为什么?因为权重是静态的,量化一次就固定了。但激活值是动态的,每次推理都不一样。
我刚开始做量化时,犯过一个低级错误:用训练集的激活值分布来校准量化参数。结果模型在测试集上跑,激活值分布变了,量化参数完全不匹配,精度直接崩了。
激活值量化的难点在于:
- 分布不稳定:不同输入产生的激活值分布差异很大
- 范围难确定:尤其是ReLU后的激活值,全是非负的,但最大值可能变化很大
- 对精度更敏感:激活值量化误差会逐层累积,越往后误差越大
举个例子,假设某层激活值范围是[0, 6.0],你用int8量化,步长就是6.0/255 ≈ 0.0235。但如果某个batch的激活值最大值变成了7.2,那超过6.0的部分全部被截断,误差就来了。
避坑指南:我曾经因为校准集选得太少(只有100张图),导致激活值范围估计不准。后来我改成用500张图做校准,效果好了很多。建议校准集至少覆盖500-1000个样本。
解决激活值量化误差,我常用的方法有:
- 滑动平均校准:在多个batch上统计激活值的最大值,取滑动平均
- KL散度校准:选择使量化前后分布差异最小的阈值
- 逐通道量化:每个通道单独计算量化参数,精度更高但计算量也更大
2.3 舍入误差
舍入误差,说白了就是你把一个连续值映射到离散的量化级别时,总会有个「四舍五入」的过程。这个误差不可避免,但可以控制。
常见的舍入方式有:
| 舍入方式 | 说明 | 误差特点 |
|---|---|---|
| Round-to-nearest | 四舍五入到最近的量化级别 | 误差均匀分布,期望为0 |
| Floor | 向下取整 | 误差始终为负,有偏 |
| Ceil | 向上取整 | 误差始终为正,有偏 |
| Stochastic rounding | 随机舍入,概率与距离成比例 | 期望无偏,但方差较大 |
我个人习惯用Round-to-nearest,因为它简单且无偏。但有一种情况要注意:当量化步长很大时,Round-to-nearest的误差可能会在某些层累积放大。
我记得有一次做8bit量化,模型精度一直差0.5%上不去。后来我把舍入方式从Round-to-nearest改成了Stochastic rounding,精度就上去了。为什么?因为Stochastic rounding的期望是无偏的,在训练过程中可以起到类似正则化的作用。
关键点:舍入误差虽然小,但经过多层累积后,影响不可忽视。尤其是在低比特量化(4bit、2bit)时,舍入误差会成为主要误差来源。
2.4 三种误差的对比
咱们把三种误差放在一起对比一下:
| 误差类型 | 来源 | 影响程度 | 常见解决方法 |
|---|---|---|---|
| 权重截断误差 | 权重值超出量化范围 | 高(尤其是离群点多时) | Percentile量化、逐层量化 |
| 激活值量化误差 | 激活值分布动态变化 | 高(逐层累积) | 滑动平均校准、KL散度校准 |
| 舍入误差 | 连续值到离散值的映射 | 中(低比特时高) | Stochastic rounding、更细的量化级别 |
你想想看,这三种误差不是独立存在的。权重截断误差会影响激活值的分布,激活值量化误差又会放大舍入误差。它们之间是相互耦合的。
所以我在做量化时,不会单独去优化某一个误差,而是整体考虑。比如:先用Percentile减少权重截断误差,再用KL散度校准减少激活值量化误差,最后用Stochastic rounding处理舍入误差。三步走下来,精度基本能恢复到原始模型的98%以上。
一个小技巧:量化后先跑一遍验证集,看看每层的输出和原始float模型的差异。哪层差异大,就重点优化哪层的量化参数。这叫「逐层诊断」,我每次量化必做这一步。
好了,关于量化误差的来源,咱们就聊到这。下一章我会讲如何用实际代码来诊断这些误差,到时候咱们手把手操作一遍。