一、为什么需要量化?——从一次“翻车”说起

我记得刚入行那会儿,接手了一个视觉检测项目。模型在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)

这里有两个关键参数:SZ。不同的量化策略,本质上就是怎么算这两个值。

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 精度损失的来源

量化误差主要来自三个方面:

  1. 舍入误差:round操作本身就有误差,最大误差是0.5个量化步长
  2. 截断误差:超出量化范围的值被截断,这部分误差可能很大
  3. 累积误差:多层量化误差叠加,越深的网络误差越大

我曾经踩过的坑:有一次量化一个检测模型,单层测试误差只有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%,先别急着调量化参数。回头看看模型本身——是不是某些层数值范围特别大?是不是有异常值?很多时候,问题出在模型训练阶段,而不是量化阶段。

四、本章知识体系

下面这张图,是我自己总结的量化知识框架。你看一眼,就能把今天的内容串起来。

模型量化基础 - 知识体系 为什么需要量化 量化数学原理 精度损失分析 减少模型体积 加速推理 降低功耗 对称量化 非对称量化 S和Z的计算 舍入误差 截断误差 累积误差 核心要点 量化是嵌入式部署的必修课 | 对称量化适合权重,非对称量化适合激活值 精度损失可控,但需要仔细分析和调参

这张图把今天的内容分成了三大块。左边是“为什么”,中间是“怎么做”,右边是“会怎样”。做项目的时候,我习惯按这个顺序来思考:先想清楚要不要量化,再选方案,最后评估影响。

一句话总结:量化不是魔法,但它是嵌入式部署最实用的优化手段。选对方案、控制好误差,你就能在资源受限的设备上跑出接近浮点精度的效果。


公众号:蓝海资料掘金营,微信deep3321