把一个 YOLOv8 工业缺陷检测模型转成 TensorRT INT8 之后,推理速度快了 40%,但 mAP@0.5 从 94.2% 掉到了 91.1%,整整 3 个点。产线那边说精度不够,不能上线。排查了将近三天,这篇文章记录完整的定位过程和解法。
问题背景
部署环境是 Jetson AGX Orin,GPU 是 Ampere 架构,TensorRT 版本 8.6。模型是 YOLOv8m,输入 640×640,FP32 baseline mAP@0.5 = 94.2%。
INT8 量化的原理是把 FP32 的权重和激活值映射到 INT8 范围(-127~127),需要一个校准数据集来统计激活值的分布,确定量化阈值。大多数精度损失问题,根源都在这一步。
第一步:排查校准数据集
最初我用了训练集里随机抽出的 500 张图做校准,想着反正都是同领域的数据。这里有个隐性问题:训练集的分布不等于产线实际分布。
产线上有几类特殊场景:强反光金属件、极暗环境、多目标密集排列——这些在训练集里比例很低,但在实际推理时占了相当大的比例。校准数据没覆盖到这些场景,导致这几类激活值的量化阈值定得不准,精度大幅下跌。
解法:重新构建校准集,从产线采集了 1000 张图,确保覆盖所有典型场景,每类至少 100 张。重新校准后 mAP 回升到 92.4%,但还不够。
第二步:逐层精度分析
校准集优化之后还有将近 2 个点的损失,说明问题不只是校准数据的问题。这时候需要定位是哪些层在量化时损失最大。
TensorRT 提供了 polygraphy 工具可以逐层比对 FP32 和 INT8 的输出差异:
polygraphy run model.onnx \
--trt --fp32 \
--data-loader-script calib_loader.py \
--save-results fp32_results.json
polygraphy run model.onnx \
--trt --int8 \
--calibration-cache calib.cache \
--data-loader-script calib_loader.py \
--save-results int8_results.json
polygraphy diff-results fp32_results.json int8_results.json \
--per-output-compare
分析结果发现,SPP(空间金字塔池化)模块之后的几个 Conv 层误差显著偏高,均方误差是其他层的 10 倍以上。原因是 SPP 输出的激活值动态范围非常大,INT8 的 256 个量化区间根本装不下。
第三步:混合精度策略
找到敏感层之后,解法就清晰了:让这些层保持 FP16,其余层用 INT8。TensorRT 支持在 builder 层面配置:
import tensorrt as trt
builder = trt.Builder(logger)
config = builder.create_builder_config()
# 开启 INT8 和 FP16
config.set_flag(trt.BuilderFlag.INT8)
config.set_flag(trt.BuilderFlag.FP16)
config.int8_calibrator = calibrator
# 对敏感层单独设置精度
network = builder.create_network(...)
# ... 构建网络后 ...
sensitive_layers = [
"spp_cv3_conv",
"spp_cv3_bn",
"c2f_after_spp_conv1",
]
for i in range(network.num_layers):
layer = network.get_layer(i)
if layer.name in sensitive_layers:
layer.precision = trt.DataType.HALF
layer.set_output_type(0, trt.DataType.HALF)
注意: 混合精度会增加不同精度层之间的数据转换开销,但对于敏感层集中的情况,这个开销通常远小于精度损失带来的业务代价。
最终结果对比
| 方案 | mAP@0.5 | 推理延迟 | vs FP32 加速比 |
|---|---|---|---|
| FP32 基线 | 94.2% | 28.6 ms | 1× |
| INT8 全量化(随机校准集) | 91.1% | 17.1 ms | 1.67× |
| INT8 全量化(产线校准集) | 92.4% | 17.1 ms | 1.67× |
| 混合精度(最终方案) | 93.6% | 19.8 ms | 1.44× |
最终精度与 FP32 只差 0.6 个点,推理速度提升了 44%。产线验收通过。
小结
INT8 量化精度掉点的排查路径:校准数据集 → 逐层误差分析 → 混合精度。大多数情况下,第一步就能解决大半问题。如果还不够,再用 polygraphy 找敏感层,做混合精度。暴力全 FP16 当然更省事,但如果部署目标是极致低延迟,混合精度是更好的平衡点。
如果你在用 Jetson 或者边缘 GPU 部署检测模型,遇到类似精度问题欢迎来聊。