一、为什么需要量化?——从一次“翻车”说起
我记得刚入行那会儿,接手了一个视觉检测项目。模型在PC上跑得飞快,精度也漂亮。结果一部署到嵌入式设备上,直接“翻车”——内存爆了,推理速度慢得像幻灯片。
你想想看,一个MobileNetV2,FP32精度,大概14MB。听起来不大对吧?但嵌入式设备的RAM可能只有几十MB,还得跑系统、跑应用、跑其他任务。模型一加载,系统直接OOM。
这就是量化的第一个价值:减少模型体积。
量化能把模型体积压缩到原来的1/4
FP32 → INT8:每个参数从4字节变成1字节,体积直接砍掉75%。
第二个价值,是加速推理。嵌入式芯片通常有专用的INT8计算单元,算力是FP32的2-4倍。我做过一个对比测试:同样的卷积层,INT8推理速度比FP32快了将近3倍。说白了,量化就是让硬件“说人话”——它天生擅长处理整数,你非给它浮点数,它反而要“翻译”半天。
第三个价值,是降低功耗。少读数据、少做计算,电池就能多撑一会儿。做边缘设备的朋友都懂,功耗有时候比精度还重要。
我的经验:量化不是万能的。如果你的模型本身很小(比如<1MB),量化的收益有限。但一旦模型超过5MB,量化几乎是必选项。
二、量化的数学原理——对称量化 vs 非对称量化
量化说白了,就是用一个低精度的整数去“近似”一个高精度的浮点数。数学上很简单:
量化公式:q = round(r / S) + Z
反量化公式:r = (q - Z) * S
其中:
r = 原始浮点数
q = 量化后的整数
S = 缩放因子(scale)
Z = 零点偏移(zero point)
这里有两个关键参数:S 和 Z。不同的量化策略,本质上就是怎么算这两个值。
2.1 对称量化
对称量化的特点是:零点Z=0。也就是说,浮点数的0,量化后还是0。
公式简化为:
q = round(r / S)
r = q * S
S怎么算?很简单:
S = max(|r_max|, |r_min|) / (2^(b-1) - 1)
其中b是量化位数,INT8的话b=8,分母就是127。
举个例子:
假设浮点数范围是[-3.0, 3.0]
S = 3.0 / 127 ≈ 0.0236
浮点数1.5 → q = round(1.5 / 0.0236) = round(63.56) = 64
反量化:r = 64 * 0.0236 = 1.5104
误差:0.0104
对称量化的特点:
- 实现简单,硬件友好
- 适合权重分布对称的情况(比如经过BN层的权重)
- 如果数据分布不对称,会浪费量化范围
2.2 非对称量化
非对称量化允许零点Z不为0。说白了,就是可以“平移”整个量化范围,让量化区间更好地覆盖实际数据分布。
S和Z的计算:
S = (r_max - r_min) / (2^b - 1)
Z = round(-r_min / S)
还是那个例子:
假设浮点数范围是[-1.0, 3.0]
S = (3.0 - (-1.0)) / 255 = 4.0 / 255 ≈ 0.0157
Z = round(-(-1.0) / 0.0157) = round(63.69) = 64
浮点数1.5 → q = round(1.5 / 0.0157) + 64 = round(95.54) + 64 = 160
反量化:r = (160 - 64) * 0.0157 = 96 * 0.0157 = 1.5072
误差:0.0072
你看,同样的数据,非对称量化的误差更小。为什么?因为它把量化范围“对准”了数据的实际分布。
我个人的习惯:激活值用非对称量化,权重用对称量化。因为激活值通常分布不对称(比如ReLU之后全是非负),而权重经过训练后往往是对称的。
2.3 两种方案的对比
| 对比项 | 对称量化 | 非对称量化 |
|---|---|---|
| 零点Z | 固定为0 | 可偏移 |
| 实现复杂度 | 低 | 中 |
| 硬件支持 | 广泛 | 部分支持 |
| 数据分布适应性 | 对称分布好 | 任意分布好 |
| 典型应用 | 权重量化 | 激活值量化 |
三、量化精度损失分析——到底会掉多少点?
这是大家最关心的问题。量化一定会损失精度,但损失多少,取决于几个因素。
3.1 精度损失的来源
量化误差主要来自三个方面:
- 舍入误差:round操作本身就有误差,最大误差是0.5个量化步长
- 截断误差:超出量化范围的值被截断,这部分误差可能很大
- 累积误差:多层量化误差叠加,越深的网络误差越大
我曾经踩过的坑:有一次量化一个检测模型,单层测试误差只有0.1%,但整个网络跑下来,mAP掉了5个点。查了半天才发现,是某一层的激活值分布特别宽,截断误差被放大了。后来我调整了那个层的量化范围,问题就解决了。
3.2 如何评估精度损失
我一般用三个指标:
- 余弦相似度:量化前后特征图的相似度,一般要>0.99
- KL散度:分布差异,越小越好
- 任务指标:比如分类的Top-1准确率、检测的mAP
实际项目中,我建议先跑一个快速验证:
# 伪代码示例
def evaluate_quantization_loss(model, calib_data, test_data):
# 1. 收集每层的激活值分布
fp32_activations = collect_activations(model, calib_data)
# 2. 计算量化参数
scales, zero_points = compute_quant_params(fp32_activations)
# 3. 模拟量化推理
int8_outputs = simulate_quant_inference(model, scales, zero_points, test_data)
# 4. 对比精度
fp32_outputs = model(test_data)
loss = compute_metric(fp32_outputs, int8_outputs)
return loss
3.3 典型精度损失范围
| 模型类型 | INT8量化精度损失 | 备注 |
|---|---|---|
| 分类模型(ResNet等) | 0.1% - 0.5% | 通常可接受 |
| 检测模型(YOLO等) | 0.5% - 2% | 需仔细调参 |
| 分割模型(UNet等) | 1% - 3% | 边界区域敏感 |
| 轻量模型(MobileNet等) | 1% - 5% | 本身冗余少,量化更敏感 |
我的建议:如果精度损失超过2%,先别急着调量化参数。回头看看模型本身——是不是某些层数值范围特别大?是不是有异常值?很多时候,问题出在模型训练阶段,而不是量化阶段。
四、本章知识体系
下面这张图,是我自己总结的量化知识框架。你看一眼,就能把今天的内容串起来。
这张图把今天的内容分成了三大块。左边是“为什么”,中间是“怎么做”,右边是“会怎样”。做项目的时候,我习惯按这个顺序来思考:先想清楚要不要量化,再选方案,最后评估影响。
一句话总结:量化不是魔法,但它是嵌入式部署最实用的优化手段。选对方案、控制好误差,你就能在资源受限的设备上跑出接近浮点精度的效果。
公众号:蓝海资料掘金营,微信deep3321