Technical note

BM1684X上将YOLOv8量化为INT8BModel

为了把YOLOv8部署到BM1684X,这次使用TPU-MLIR完成了从ONNX到BModel的转换。F16模型可以直接编译,INT8则要多做一次校准;全INT8精度不理想时,再用qtable把敏感层保留为浮点计算。

这次只转换了少量模型,目标是跑通工具链并比较F16和INT8,没有覆盖其他检测、跟踪和人脸模型。TPU-MLIR版本当时没有记录,命令中的参数应以所用SDK附带的文档为准。

参考

从ONNX生成MLIR

先准备固定输入尺寸的ONNX模型、一张测试图片和校准图片目录,工作目录使用以下名称:

workspace/
├── detector.onnx
├── image/
│   └── sample.jpg
└── calibration/
    ├── 0001.jpg
    └── ...

转换时把YOLOv8的0~255RGB输入缩放到0~1,并生成后续比对需要的测试结果:

model_transform.py \
  --model_name detector \
  --model_def detector.onnx \
  --input_shapes '[[1,3,640,640]]' \
  --mean 0.0,0.0,0.0 \
  --scale 0.0039216,0.0039216,0.0039216 \
  --keep_aspect_ratio \
  --pixel_format rgb \
  --test_input image/sample.jpg \
  --test_result detector_top_outputs.npz \
  --mlir detector.mlir

这里的预处理必须和训练、ONNX推理及最终应用保持一致。--keep_aspect_ratio会在缩放时保留宽高比并补边;如果原来的推理代码采用直接拉伸,这个参数也要随之调整,不能只照搬示例。

命令还会生成detector_in_f32.npz,它是同一张测试图片经过预处理后的输入,后面的F16和INT8编译都用它做数值比对。

先生成F16基线

F16不需要校准表,可以直接从MLIR生成BM1684X使用的BModel:

model_deploy.py \
  --mlir detector.mlir \
  --quantize F16 \
  --processor bm1684x \
  --test_input detector_in_f32.npz \
  --test_reference detector_top_outputs.npz \
  --model detector_1684x_f16.bmodel

先保留F16结果很有必要。它既是部署候选,也能把“模型转换本身的问题”和“INT8量化带来的误差”分开。

可以用model_runner.py检查BModel输出:

model_runner.py \
  --input detector_in_f32.npz \
  --model detector_1684x_f16.bmodel \
  --output detector_f16_outputs.npz

model_deploy.py的相似度检查和输出张量统计只能发现明显的数值异常,不能代替完整数据集上的mAP、Precision和Recall评估。

生成校准表和INT8模型

这次探索使用了100张校准图片:

run_calibration.py detector.mlir \
  --dataset calibration \
  --input_num 100 \
  -o detector_cali_table

校准图片应来自实际输入分布。100张只是这次跑通流程时的取值,不是固定标准;TPU-MLIR当前文档给出的常见范围是100至1000张,正式模型仍要根据数据复杂度和精度结果调整。

使用校准表生成全INT8对称量化模型:

model_deploy.py \
  --mlir detector.mlir \
  --quantize INT8 \
  --calibration_table detector_cali_table \
  --processor bm1684x \
  --test_input detector_in_f32.npz \
  --test_reference detector_top_outputs.npz \
  --tolerance 0.85,0.45 \
  --model detector_1684x_int8.bmodel

--tolerance控制转换过程中的相似度检查门限,不代表最终检测精度。模型成功生成之后,仍要使用带标签的数据集进行评估。

全INT8不够时使用混合精度

TPU-MLIR用qtable记录哪些算子保留为F32、F16或BF16。当前版本能够为YOLO结构自动识别部分不适合INT8的后处理算子,也可以通过search_qtable继续搜索敏感层。

搜索命令同时指定校准表和qtable的输出名称,避免后续部署时串错文件:

run_calibration.py detector.mlir \
  --dataset calibration \
  --input_num 100 \
  --inference_num 10 \
  --search search_qtable \
  --processor bm1684x \
  --quantize_table detector_qtable \
  --calibration_table detector_search_cali_table

不同TPU-MLIR版本对搜索后更新校准表的文件名处理并不完全一致。有的版本会另外生成new_cali_table.txt,因此下一步不能凭文件名猜测:先查看搜索日志,确认最终qtable和更新后校准表的实际路径,再传给model_deploy.py

SEARCH_CALI_TABLE=new_cali_table.txt

model_deploy.py \
  --mlir detector.mlir \
  --quantize INT8 \
  --processor bm1684x \
  --calibration_table "$SEARCH_CALI_TABLE" \
  --quantize_table detector_qtable \
  --test_input detector_in_f32.npz \
  --test_reference detector_top_outputs.npz \
  --tolerance 0.85,0.45 \
  --model detector_1684x_mix.bmodel

部署阶段如果引用了另一个临时qtable文件,命令仍可能成功,却不能证明模型使用的是刚完成搜索的配置。这里最好统一命名,并在编译前查看两个表的路径和内容。

这次的精度和耗时

同一检测模型的测试结果如下。数据集规模、TPU-MLIR版本和板卡运行状态没有完整记录,所以只比较这组测试内部的相对变化。

模型mAP@0.5mAP@0.5:0.95PrecisionRecallF1推理(ms)换算FPS
BModel FP320.94390.66200.90430.96990.935938.026.4
BModel F160.94400.66200.90370.96990.935614.171.1
BModel INT8方案A0.91560.61280.88640.97060.926611.686.5
BModel INT8方案B0.92420.62500.90080.96710.932811.586.7
BModel INT8自动pattern0.92420.62500.90080.96710.932811.686.6

F16相对FP32把推理耗时从38.0ms降到14.1ms,精度指标基本不变。三份INT8结果的耗时都在11.5ms左右,其中方案B和自动pattern的mAP@0.5为0.9242,比F16低0.0198;F1只低0.0028。

方案A和方案B对应两次混精度调整,但当时没有把具体qtable差异记录下来,无法从结果反推出是哪几层带来了改善。自动pattern与方案B得到相同精度,说明在这个模型上没有必要仅为了“手工调过”而保留更复杂的配置。

现有结果足以支持继续使用F16,并把INT8作为有条件的优化选项。真正上线INT8之前,还需要在目标板卡上用真实图片复查预处理、后处理和检测结果;这部分当时没有完成,不能把转换成功直接当成部署验证通过。