量化技术基础:从FP32到INT8
各位同学,今天我们来聊聊量化。说实话,量化是我在低功耗推理引擎设计中,觉得最「划算」的一笔投资。你想想看,一个模型从FP32变成INT8,体积直接缩到四分之一,推理速度能翻倍甚至更多,功耗更是直线下降。我在做边缘端芯片的时候,没有量化,很多场景根本跑不动。
但量化也不是万能的。搞不好精度掉得你怀疑人生。我刚开始做量化时,就踩过不少坑。今天我把这些经验掰开揉碎了讲给你听。
为什么要把FP32变成INT8?
说白了,FP32太「奢侈」了。32位存一个数,精度是高,但计算单元大、内存带宽占用高、功耗也高。INT8只需要8位,计算单元小得多,内存搬运量也少。
我举个例子。一个卷积层,FP32需要做32位乘法累加,INT8只需要8位乘法。硬件上,8位乘法器的面积和功耗,大概是32位的十分之一不到。你想想看,一个模型几十层,这差距就出来了。
而且,很多场景其实不需要那么高的精度。人眼对图像分类的容忍度很高,语音识别也是。模型学到的知识,大部分都「冗余」了。量化就是把这些冗余去掉,只保留关键信息。
核心观点:量化不是「降级」,而是「精简」。用最小的代价,保留最大的价值。
线性量化 vs 非线性量化
量化分两大类:线性量化和非线性量化。我工作中90%的场景用的都是线性量化,因为它简单、硬件友好。
线性量化
线性量化,就是找一个比例因子(scale)和零点(zero point),把FP32的值映射到INT8的整数范围。公式很简单:
q = round(r / scale) + zero_point
其中r是原始浮点数,q是量化后的整数。反量化就是:
r = (q - zero_point) * scale
scale怎么算?就是浮点数的范围除以整数范围。比如浮点数范围是[-1.0, 1.0],INT8范围是[-128, 127],那scale就是(1.0 - (-1.0)) / (127 - (-128)) ≈ 0.0078。
我在项目中遇到过一个问题:如果浮点数分布不均匀,比如大部分值集中在0附近,但有几个离群点很大,那scale就会被拉大,导致量化精度损失严重。这时候就需要做「裁剪」(clipping),把离群点截断掉。
我的经验:做线性量化时,先看看数据的分布直方图。如果尾巴很长,建议先做裁剪,再算scale。我一般会裁剪掉0.1%到1%的极端值,效果往往更好。
非线性量化
非线性量化,说白了就是scale不是固定的。比如对数量化,把浮点数映射到对数空间,再量化。公式像这样:
q = round(log2(|r|) * factor)
非线性量化的好处是,能更好地适应非均匀分布的数据。比如权重分布是高斯分布,中间密两边疏,用线性量化中间部分精度不够,两边又浪费。非线性量化可以「按需分配」精度。
但缺点也很明显:硬件实现复杂。非线性量化需要查表或者特殊计算单元,不像线性量化那样直接用乘法累加器就能搞定。所以我个人习惯,除非精度实在保不住,否则优先用线性量化。
对称量化 vs 非对称量化
这个知识点,我当年花了整整一周才彻底搞明白。其实没那么复杂。
对称量化
对称量化,就是zero_point固定为0。浮点数的正负范围对称映射到整数的正负范围。公式简化成:
q = round(r / scale)
好处是计算简单,硬件实现省事。坏处是,如果浮点数分布不对称,比如全是正数,那负数那部分整数范围就浪费了。
举个例子。ReLU激活函数的输出全是非负数,范围[0, 6.0]。用对称量化,INT8范围[-128, 127],负数部分全浪费了,有效精度只有一半。
非对称量化
非对称量化,就是zero_point可以不为0。它能灵活地调整映射范围,把浮点数的实际范围完整映射到整数范围。
还是刚才那个ReLU的例子,范围[0, 6.0]。用非对称量化,我们可以把0映射到0,6.0映射到255,充分利用INT8的[0, 255]范围。精度直接翻倍。
| 类型 | zero_point | 适用场景 | 精度 | 硬件复杂度 |
|---|---|---|---|---|
| 对称量化 | 固定为0 | 权重(分布近似对称) | 中等 | 低 |
| 非对称量化 | 可调 | 激活值(分布不对称) | 高 | 中 |
避坑指南:我曾经在一个项目中,对权重用了非对称量化,结果推理精度反而下降了。后来发现,权重分布本来就是近似对称的,非对称量化引入的zero_point计算反而增加了误差。所以,权重我建议用对称量化,激活值用非对称量化。这是业界主流做法。
量化误差分析
量化一定会引入误差,关键是怎么控制它。误差主要来自三个方面:
- 舍入误差:round操作带来的。比如3.14159量化成3,误差0.14159。这个没法避免,但可以通过增加量化位数来减小。
- 裁剪误差:把超出范围的浮点数截断掉。比如最大值是10.0,但量化范围只到8.0,那2.0的信息就丢了。这个误差可以通过合理选择裁剪阈值来控制。
- 精度损失:量化后的数值分辨率变低了。FP32有约7位有效数字,INT8只有2-3位。这个误差在多层累积后会放大。
我常用的误差分析方法,是计算量化前后的「信噪比」(SNR)。公式很简单:
SNR = 10 * log10( sum(r^2) / sum((r - r_q)^2) )
其中r是原始浮点数,r_q是反量化后的值。SNR越高,说明量化损失越小。我一般要求SNR在30dB以上,低于这个值就要小心了。
一个小技巧:量化误差分析时,不要只看全局SNR。我习惯分层看,哪一层SNR低,就重点优化哪一层。有时候某一层的误差特别大,但后面几层能「纠正」回来。但也有时候,误差会逐层放大,导致最终结果完全不对。嗯,这个需要具体模型具体分析。
实战建议
说了这么多,最后给几条我自己的实战建议:
- 先做校准:量化前,用一小批数据跑一遍,统计每层的输入输出分布。这叫「校准」(calibration)。没有校准的量化,就像闭着眼睛开车。
- 逐层验证:量化完一层,就对比一下这层输出和FP32输出的差异。如果差异太大,及时调整量化参数。
- 混合精度:不是所有层都需要INT8。敏感层(比如第一层和最后一层)可以保留FP16甚至FP32。我做过一个模型,只把中间层量化成INT8,精度几乎没掉,速度却快了3倍。
- 量化感知训练:如果量化后精度掉得厉害,可以考虑在训练时就模拟量化误差。这叫「量化感知训练」(QAT)。虽然训练时间长了点,但效果确实好。
好了,量化技术的基础就讲到这里。下一章我们会深入具体的量化工具和框架,到时候我会拿一个实际的模型,手把手带大家做一遍量化。记得提前准备好环境。