4、模型量化基础:INT8/INT4量化原理与对内存占用的影响
说到边缘AI系统的内存优化,量化绝对是个绕不开的话题。我刚开始做嵌入式AI那会儿,模型动不动就几十上百兆,根本塞不进芯片里。后来接触了量化,才算是打开了新世界的大门。
说白了,量化就是把模型里那些高精度的浮点数,换成低精度的整数。你想想看,一个float32是4个字节,换成int8就变成1个字节,内存直接缩到四分之一。这还不算完,int4更是只要半个字节。嗯,代价就是精度会掉一些,但很多时候这个代价是完全可以接受的。
4.1 为什么要做量化?
先说说我个人的理解。模型在训练的时候,我们需要高精度来保证梯度更新的准确性,所以用float32甚至float64。但推理的时候呢?其实没那么娇气。
我在项目中遇到过这样一个场景:一个目标检测模型,float32版本要跑在树莓派上,帧率只有可怜的3帧。量化到int8之后,帧率直接飙到15帧,内存占用也从120MB降到了35MB。最关键的是,mAP只掉了0.8%。你说这买卖划不划算?
量化的好处主要有三点:
- 内存减半再减半:int8是float32的1/4,int4是1/8
- 带宽需求降低:从DDR搬数据的时间少了,推理自然就快了
- 计算效率提升:整数运算比浮点运算快得多,很多硬件还有专门的int8加速单元
核心观点:量化不是简单的精度取舍,而是用最小的精度损失,换取最大的性能收益。在边缘设备上,这往往是唯一的选择。
4.2 INT8量化原理
INT8量化,就是把float32的数值范围映射到[-128, 127]这个区间。怎么映射呢?核心就两个参数:缩放因子(scale)和零点(zero point)。
公式其实很简单:
q = round(r / scale) + zero_point
其中r是原始的浮点数,q是量化后的整数。反过来,反量化就是:
r = (q - zero_point) * scale
这里有个坑,我曾经踩过。scale的选择非常关键,选大了精度损失大,选小了容易溢出。我一般会先跑一遍校准数据集,统计出每一层激活值的最大值和最小值,然后根据这个来定scale。
量化的方式主要有两种:
- 对称量化:zero_point固定为0,适合权重这种正负对称的数据
- 非对称量化:zero_point可以不为0,适合激活值这种非对称分布的数据
我的经验:对于权重,我习惯用对称量化。对于激活值,尤其是ReLU之后的,用非对称量化效果更好。这个组合我在多个项目里验证过,精度损失最小。
4.3 INT4量化原理
INT4量化就更激进了。int4只有16个取值,从-8到7。你想想看,要把float32里几十亿个可能的值,压缩到16个格子里,这精度损失能不大吗?
但为什么还要用int4呢?因为有些场景下,精度不是第一位的。比如一些简单的分类任务,或者作为模型剪枝后的进一步压缩手段。
INT4量化的实现方式跟INT8类似,但有个关键区别:分组量化。因为单个数值的表示能力太弱了,所以需要把一组数值放在一起,共享同一个scale和zero_point。
// 伪代码示例:INT4分组量化
group_size = 32 // 每32个数值为一组
for each group in weights:
scale = (max(group) - min(group)) / 15
zero_point = -round(min(group) / scale)
for each value in group:
q = round(value / scale) + zero_point
q = clamp(q, -8, 7) // 限制在int4范围内
这里要注意,int4在硬件上并不像int8那么普及。很多芯片没有原生的int4计算单元,需要做额外的bit packing和解包操作。我曾经在一个项目里强行上int4,结果因为解包开销太大,速度反而没比int8快多少。
避坑指南:我曾经在STM32上尝试int4量化,发现内存是省了,但推理时间反而增加了。后来一查,原来是MCU没有int4指令集,所有运算都要拆成int8来做。所以,上int4之前,一定要确认你的硬件支不支持。
4.4 量化对内存占用的影响
这部分我直接上数据,大家看得更清楚。假设一个模型有1000万个参数:
| 精度 | 每个参数大小 | 总内存占用 | 相比float32节省 |
|---|---|---|---|
| float32 | 4 bytes | 40 MB | 0% |
| float16 | 2 bytes | 20 MB | 50% |
| int8 | 1 byte | 10 MB | 75% |
| int4 | 0.5 byte | 5 MB | 87.5% |
但这只是权重部分。别忘了,推理过程中还有中间激活值。这部分内存占用往往比权重还大。量化同样能压缩激活值,但要注意:激活值的分布是动态的,不像权重那样固定。
我个人的做法是:
- 先量化权重,这个基本无损
- 再量化激活值,但保留一些关键层的精度
- 最后用校准数据集跑一遍,看看精度是否达标
实际案例:我之前优化过一个语音识别模型,原始float32版本占用180MB。量化到int8后降到48MB,再配合int4量化部分层,最终只有28MB。成功塞进了一款只有32MB RAM的音频处理芯片里。
4.5 量化后的精度补偿技巧
量化一定会掉精度,但我们可以想办法把损失降到最低。这里分享几个我常用的技巧:
- 量化感知训练(QAT):在训练过程中模拟量化效果,让模型自己适应低精度。效果比训练后量化好很多,但需要重新训练。
- 逐通道量化:每个卷积核单独算scale,而不是整个层共享。精度能提升0.5-1%。
- 混合精度量化:敏感层用int8,不敏感层用int4。怎么判断敏感层?跑一遍逐层精度分析就知道了。
小技巧:如果你不想重新训练,可以试试偏置校正。量化后统计一下输出分布的偏移,然后调整偏置项。这个方法我试过多次,简单有效,精度能回来不少。
最后说一句,量化不是银弹。有些模型对精度极其敏感,比如医疗影像或者自动驾驶,量化后可能无法满足要求。但大多数边缘AI场景,比如智能家居、工业检测、可穿戴设备,量化都是最优解。
嗯,关于量化的基础就讲到这里。下一章我会深入讲量化工具链的使用,包括TensorRT、ONNX Runtime这些实际工具怎么配置量化参数。到时候再跟大家分享一些踩坑经验。