第4章:模型量化(Quantization)——FP32/FP16/INT8/INT4精度对比、PTQ与QAT、YOLO量化踩坑记录
模型量化,说白了就是给模型“减重降频”。
你想想看,YOLO模型在GPU上跑FP32,精度高但体积大、速度慢。到了手机、摄像头、边缘盒子这些端侧设备,算力和带宽都有限,必须把模型从“大胖子”变成“精干小伙”。
我个人习惯把量化比作“压缩图片”——你愿意牺牲多少画质,来换取更小的文件体积和更快的加载速度?
4.1 精度格式对比:FP32 / FP16 / INT8 / INT4
先看一张表,把四种主流精度格式的差异说清楚。
| 精度格式 | 位宽 | 表示范围 | 相对FP32精度损失 | 典型推理加速比 | 适用场景 |
|---|---|---|---|---|---|
| FP32 | 32-bit | ~3.4e38 | 基准 | 1x | 训练、高精度服务器推理 |
| FP16 | 16-bit | ~6.5e4 | 极小(<0.5% mAP) | 1.5x - 2x | GPU/部分NPU推理 |
| INT8 | 8-bit | -128 ~ 127 | 较小(1%-3% mAP) | 2x - 4x | 端侧主流部署格式 |
| INT4 | 4-bit | -8 ~ 7 | 较大(3%-8% mAP) | 4x - 8x | 极端资源受限场景 |
FP16基本是“无损”的。我在项目中做过对比,YOLOv8s从FP32转FP16,mAP只掉了0.2%,但显存占用直接砍半。如果你用的是NVIDIA Jetson系列,FP16是首选。
INT8才是端侧的主力。大部分手机芯片、NPU(比如瑞芯微、地平线、高通)都原生支持INT8。代价是精度会掉1-3个百分点,但换来2-4倍的推理速度提升。
INT4呢?嗯,这里要注意。INT4的精度损失已经比较明显了,而且很多硬件不支持原生INT4计算,需要做特殊的“打包”处理。我建议除非是像智能门锁、低端MCU这种实在挤不出算力的场景,否则别碰INT4。
核心结论:端侧部署,INT8是“甜点精度”。FP16适合GPU场景,INT4只适合极端情况。
4.2 训练后量化(PTQ) vs 量化感知训练(QAT)
量化方法主要有两条路:PTQ和QAT。我刚开始做量化时,觉得PTQ简单粗暴,直接转就行。后来踩了坑才发现,事情没那么简单。
4.2.1 训练后量化(PTQ)
PTQ,就是模型训练完成后,直接用少量校准数据(calibration data)跑一遍,统计出每层激活值的分布范围,然后算出缩放因子(scale)和零点(zero point),把FP32的权重和激活值映射到INT8。
PTQ的优点是:
- 快——不需要重新训练,几分钟到几小时搞定。
- 简单——不需要改训练代码。
但缺点也很明显:
- 精度损失不可控——尤其是对小目标、低置信度的检测框,量化后可能直接消失。
- 对校准数据敏感——校准集选不好,量化后精度崩盘。
我在项目中遇到过,用100张图片做校准,量化后mAP掉了5%。换成500张覆盖各种场景的图片,只掉了1.5%。校准数据的选择,比你想象的重要得多。
4.2.2 量化感知训练(QAT)
QAT,是在训练过程中“模拟”量化效果。具体做法是:在forward时插入伪量化节点(FakeQuantize),把权重和激活值先量化再反量化,让模型学会适应量化带来的误差。
QAT的优点是:
- 精度高——通常能恢复到FP32精度的98%以上。
- 鲁棒性强——对校准数据不敏感。
但代价是:
- 慢——需要重新训练,通常要额外训练几个epoch。
- 复杂——需要修改训练代码,调参也更麻烦。
我的建议:先跑PTQ,如果精度损失在可接受范围内(比如<2% mAP),直接用PTQ。如果精度损失过大,再上QAT。不要一上来就QAT,浪费时间。
4.3 YOLO量化踩坑记录
这部分是我最想分享的。量化看起来简单,但YOLO模型结构特殊,坑特别多。我曾经在一个项目中,花了两周时间才把量化后的YOLOv5s调通。
坑1:校准数据选择不当
校准数据必须覆盖目标场景的分布。比如你检测的是行人,校准集里全是汽车,那量化后行人检测率直接掉一半。我建议校准集至少包含500张图片,且与验证集分布一致。
坑2:BN层融合顺序
量化前必须先把BN层融合到卷积层里。如果顺序搞反了,量化后的精度会莫名其妙地差。正确的流程是:先BN融合,再量化。
坑3:检测头(Head)的量化敏感度
YOLO的检测头包含很多小卷积层和激活函数,对量化特别敏感。我试过把整个模型量化后,检测头输出全是NaN。解决办法是:对检测头部分使用更高的精度(比如FP16),或者对检测头单独做QAT。
坑4:非对称量化 vs 对称量化
YOLO的权重分布通常是对称的,用对称量化(symmetric quantization)效果更好。但激活值分布往往是非对称的(比如ReLU后全是正数),用非对称量化(asymmetric quantization)更合适。很多量化工具默认用对称量化,导致激活值量化误差大。
重要提醒:量化后的模型一定要在目标硬件上做端到端测试。PC上跑出来的精度,不代表在手机或NPU上也能达到。我曾经被这个坑过,PC上mAP只掉了1%,上板后直接掉了5%。
4.4 量化流程与工具链
下面这张图是我总结的YOLO量化标准流程,你可以直接照着做。
4.5 代码实战:YOLOv8 PTQ量化示例
下面是一个用ONNX Runtime做PTQ量化的简化示例。我习惯用这种方式快速验证量化效果。
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
# 加载FP32模型
model_fp32 = 'yolov8s.onnx'
model_int8 = 'yolov8s_int8.onnx'
# 执行动态量化(PTQ)
quantized_model = quantize_dynamic(
model_input=model_fp32,
model_output=model_int8,
weight_type=QuantType.QInt8 # 权重用INT8
)
print(f"量化完成,模型保存至:{model_int8}")
这个代码跑完,模型体积会缩小到原来的1/4左右。但注意,这只是动态量化,只量化了权重,激活值还是FP32。如果想做全INT8量化,需要用静态量化,需要提供校准数据。
小技巧:量化后一定要检查模型输出是否正常。可以跑几张测试图片,对比FP32和INT8模型的输出张量,看看最大误差是否在合理范围内(通常<0.1)。
4.6 量化后的性能评估
量化完成后,别急着部署。先做三件事:
- 精度评估:在验证集上跑mAP,对比FP32和量化后的差异。
- 速度测试:在目标硬件上测FPS,看是否达到预期。
- 稳定性测试:连续跑1000帧,看是否有内存泄漏或输出异常。
我曾经在一个项目中,量化后的模型在PC上跑得好好的,上板后每跑200帧就崩一次。查了两天,发现是NPU驱动对INT8的某些操作有bug。嗯,硬件兼容性问题,也是量化落地的一大坑。
量化不是终点,而是端侧部署的起点。把这一步走稳了,后面的优化才能事半功倍。