BM1684X上将YOLOv8量化为INT8BModel

本文由AI辅助编写

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

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

参考

从ONNX生成MLIR

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

1
2
3
4
5
6
7
workspace/
├── detector.onnx
├── image/
│ └── sample.jpg
└── calibration/
├── 0001.jpg
└── ...

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

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
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输出:

1
2
3
4
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张校准图片:

1
2
3
4
run_calibration.py detector.mlir \
--dataset calibration \
--input_num 100 \
-o detector_cali_table

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

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

1
2
3
4
5
6
7
8
9
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的输出名称,避免后续部署时串错文件:

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
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.5 mAP@0.5:0.95 Precision Recall F1 推理(ms) 换算FPS
BModel FP32 0.9439 0.6620 0.9043 0.9699 0.9359 38.0 26.4
BModel F16 0.9440 0.6620 0.9037 0.9699 0.9356 14.1 71.1
BModel INT8方案A 0.9156 0.6128 0.8864 0.9706 0.9266 11.6 86.5
BModel INT8方案B 0.9242 0.6250 0.9008 0.9671 0.9328 11.5 86.7
BModel INT8自动pattern 0.9242 0.6250 0.9008 0.9671 0.9328 11.6 86.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之前,还需要在目标板卡上用真实图片复查预处理、后处理和检测结果;这部分当时没有完成,不能把转换成功直接当成部署验证通过。