1 使用场景
HB_ONNXRuntime 是地平线基于公版 ONNXRuntime 封装的一套 x86 端的 ONNX 模型推理库,除了支持 Pytorch、TensorFlow、PaddlePaddle 等各训练框架直接导出的 ONNX 原始模型[注1] 外,也支持地平线 PTQ 工具链转换过程中产出的各阶段 ONNX 模型[注2](不支持 .bin 模型)。下文将对各种情况分别举例说明。
[注1] Pytorch、TensorFlow、PaddPaddle 框架导出 ONNX 模型的示例可参考:
[注2] 地平线 PTQ 工具链转换产出物说明可参考: 4.1.1.6.4. 转换产出物解读
2 使用说明
2.1 原始 ONNX 模型推理
评测各训练框架导出的 ONNX 原始模型精度,可以帮助我们首先确认 ONNX 模型本身:
- 推理是否正常,否则后续喂给 PTQ 工具链做转换也会报错
- 精度是否正确,即和原训练框架模型推理结果是否一致或差异很小,以便排除 ONNX 模型导出环节存在问题
相比于公版 ONNXRuntime(参考代码请见附录),我们更建议直接使用地平线封装后的 HB_ONNXRuntime
进行推理,其数值和行为与公版完全一致,代码还可以在后续测试 PTQ 各阶段模型时实现复用。具体参考代码如下:
# 加载地平线依赖库
from horizon_tc_ui import HB_ONNXRuntime
def preprocess(input_name):
# 数据前处理,与训练框架中模型推理时保持一致
# 可以复用原始框架中的推理数据,并将其转换成numpy
# BGR->RGB、Resize、CenterCrop···
# HWC->CHW
# normalization
return data
def postprocess(model_output):
# 模型后处理
def main():
# 加载模型文件
sess = HB_ONNXRuntime(MODEL_PATH)
# 获取模型输入&输出节点名称
input_names = [input.name for input in sess.get_inputs()]
output_names = [output.name for output in sess.get_outputs()]
# 准备模型输入数据
feed_dict = dict()
for input_name in input_names:
feed_dict[input_name] = preprocess(input_name)
# 模型推理
outputs = sess.run_feature(output_names, feed_dict, input_offset=0)
# 后处理
postprocess(outputs)
if __name__ == '__main__':
main()
对比公版 ONNXRuntime,推理代码的差异有以下3处:
加载运行库
ONNX
import onnxruntime as rt
HB_ONNXRuntime
from horizon_tc_ui import HB_ONNXRuntime
加载模型文件
ONNX
sess = rt.InferenceSession(MODEL_PATH)
HB_ONNXRuntime
sess = HB_ONNXRuntime(MODEL_PATH)
推理模型
ONNX
outputs = sess.run(output_names, feed_dict)
HB_ONNXRuntime
outputs = sess.run_feature(output_names, feed_dict, input_offset=0)
2.2 PTQ各阶段模型推理
首先,我们需要理解 PTQ 各阶段模型的推理精度和测试意义:
***_original_float_model.onnx
浮点模型,只在原始 ONNX 模型前插入预处理节点
***_optimized_float_model.onnx
浮点模型,已完成图优化,其推理结果与 ***_original_float_model.onnx 一致,正常无需测试
***_calibrated_model.onnx
主要用于精度debug工具[注3],正常无需关注,下文不再涉及
***_quantized_model.onnx
定点模型,其推理结果与板端部署所用的 ***.bin 模型一致
[注3] 工具说明请参考: 4.1.2.11. 精度debug工具
以上各阶段模型的推理代码差异其实只在于 数据前处理 和 推理接口。
其中,数据前处理的基础操作与原始模型一致,但处理后的数据类型和 layout 会稍有差异,这与 yaml 文件中的部分配置项有关。地平线 PTQ 工具链支持在模型前端插入一个 BPU 可加速的预处理节点,依次完成:
input_type_rt
到input_type_train
数据类型的转换;- (data -
mean_value
) *scale_value
的数据归一化。
针对这四个参数的补充说明如下:
input_type_rt
模型在板端部署时拿到的推理数据类型,通常为 featuremap、nv12 或 gray:-
- featuremap:float32,非图像输入-
- nv12:uint8,XJ3/J5视频通路输出即为nv12-
- gray:uint8,灰度图,对应nv12的单y分量
input_type_train
模型训练时拿到的数据类型类型,通常为 RGB/BGR,gray 或 featuremap
mean_value &-
scale_value
数据前处理过程中的归一化操作,可以集成在模型中使用 BPU 加速,在推理 PTQ 产出的各阶段模型时需注意避免在推理代码中重复操作
需要注意的是,板端部署所用的 .bin 模型,其预处理节点能够实现数据类型的完整转换,而 ONNX 模型因为缺少部分板端执行硬件的配合,所以只会从 input_type_rt
对应的一种中间类型处理至 input_type_train
类型。每种 input_type_rt
对应的中间类型如下表所示,其中 _128
表示 uint8 数据减 128 后转换成 int8。
nv12
yuv444
rgb
bgr
gray
featuremap
yuv444_128
yuv444_128
RGB_128
BGR_128
GRAY_128
featuremap
数据预处理后的 layout(NCHW 或 NHWC),也会有一些情况区分,具体如下。为了避免出错,我们建议使用开源工具 Netron[注4] 直接可视化需要推理的 ONNX 模型进行确认:
- 对于 XJ3 工具链,图像输入模型的
***_quantized_model.onnx
模型都会被强制更改为 NHWC,其他模型保持和原始模型一致(通常为 NCHW) - 对于 J5 工具链,如果模型的
input_type_rt
配置为nv12
,那么***_quantized_model.onnx
模型会被强制更改为 NHWC,其他模型保持和原始模型一致(通常为 NCHW) - 对于 featuremap 输入的模型,无论是 XJ3 还是 J5 工具链,都会保持和原始模型一致
[注4] ONNX 可视化开源工具:Netron
综上,各阶段 ONNX 模型的差异整理如下,具体使用时对应修改本文 2.1 章节中的推理代码即可。
1)图像输入:
***_original_float_***
***_optimized_float_***
***_quantized_***
数据类型
input_type_rt 对应的中间类型,但不减 128,通过推理接口实现
数据 layout
同原始模型
同原始模型
XJ3
NHWC
J5-
输入类型为nv12
NHWC
J5-
输入类型非nv12
同原始模型
数据归一化
请配置在 yaml 中,推理代码中删除
推理接口
sess.run(output_names, feed_dict, input_offset=128)
2)Featuremap 输入:
***_original_float_***
***_optimized_float_***
***_quantized_***
数据类型
featuremap
数据 layout
同原始模型
数据归一化
请在前处理代码中直接完成,仅 channel=3 时可通过 yaml 配置
推理接口
sess.run_feature(output_names, feed_dict, input_offset=0)
3)图像 + Featuremap 混合多输入:
***_original_float_***
***_optimized_float_***
***_quantized_***
数据类型
input_type_rt 对应的中间类型,图像输入涉及 -128 的操作请在数据前处理中完成
数据 layout
同原始模型
数据归一化
图像请配置在 yaml 中,推理代码中删除;-
featuremap 请在前处理代码中直接完成,仅 channel=3 时可通过 yaml 配置
推理接口
sess.hb_session.run(output_names, feed_dict)
2.3 补充说明
在做模型精度验证时,大家还应了解到,nv12 相比于 RGB/BGR/YUV444 的数据空间更小,所以数据从 RGB/BGR/YUV444 转换至 nv12 时都会引入不可避免的微小误差且不可逆。通常情况下,这种误差对于模型的精度影响较小,但也有例外。以 input_type_rt: nv12
的模型推理为例,基于 opencv/skimage 读取的 JPEG 图片为 BGR/RGB,为了和板端行为保持一致,会先处理成 nv12,再转换至中间类型 yuv444。此时如果模型本身的鲁棒性较差,那么 BGR/RGB—>nv12
引入的误差就可能会对推理结果产生较大影响。因此,模型训练时还是应该尽量提高它的鲁棒性,从而降低这种微小误差的扰动影响。
3 附录
公版 ONNXRuntime 推理原始 ONNX 模型的参考代码如下:
import onnxruntime as rt
def preprocess(input_name):
# 数据前处理,与训练框架中模型推理时保持一致
# 可以复用原始框架中的推理数据,并将其转换成numpy
# BGR->RGB、Resize、CenterCrop···
# HWC->CHW
# normalization
return data
def postprocess(model_output):
# 模型后处理
def main():
# 加载模型文件
sess = rt.InferenceSession(MODEL_PATH)
# 获取模型输入&输出节点名称
input_names = [input.name for input in sess.get_inputs()]
output_names = [output.name for output in sess.get_outputs()]
# 准备模型输入数据
feed_dict = dict()
for input_name in input_names:
feed_dict[input_name] = preprocess(input_name)
# 模型推理
outputs = sess.run(output_names, feed_dict)
# 后处理
postprocess(outputs)
if __name__ == '__main__':
main()