HB_ONNXRuntime 基础使用

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 可加速的预处理节点,依次完成:

  1. input_type_rtinput_type_train 数据类型的转换;
  2. (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()

您好,请问是不是可以理解为只要是特征图input_offset都应该设置为0?另外,input_offset=0的情况下,run和run_feature行为和输出结果有什么区别呢?

你好,我试验了HB_ONNXRuntime是在CPU上跑的,如果跑的是量化后的模型,比较慢,请问可以放到GPU上跑吗?HB_ONNXRuntime可以设置成GPU上运行吗?

你好,由于gpu版本未经过系统性的全量测试,因此并未在手册和文章中体现,如果有相关加速需求的话,可以尝试如下操作:

在docker中安装horizon-nn-gpu版本(ddk/package/host/ai_toolchain/horizon_nn_gpu-0.20.1-cp38-cp38-linux_x86_64.whl),

在代码中添加

sess = HB_ONNXRuntime(model_file=model)

sess.set_providers([‘CUDAExecutionProvider’])

尝试加速

你好,请问最新版本的oe有支持gpu版本的HB_ONNXRuntime了吗,还是仍然需要手动安装