• ONNX:Linux系统中使用onnxruntime


    一、基本介绍

    微软联合Facebook等在2017年搞了个深度学习以及机器学习模型的格式标准–ONNX,旨在将所有模型格式统一为一致,更方便地实现模型部署。现在大多数的深度学习框架都支持ONNX模型转出并提供相应的导出接口。

    ONNXRuntime(Open Neural Network Exchange)是微软推出的一款针对ONNX模型格式的推理框架,用户可以非常便利的用其运行一个onnx模型。ONNXRuntime支持多种运行后端包括CPU,GPU,TensorRT,DML等。可以说ONNXRuntime是对ONNX模型最原生的支持,只要掌握模型导出的相应操作,便能对将不同框架的模型进行部署,提高开发效率。

    利用onnx和onnxruntime实现pytorch深度框架使用C++推理进行服务器部署,模型推理的性能是比python快很多的。

    1、下载

    GitHub下载地址:

    https://github.com/microsoft/onnxruntime/releases

     Release ONNX Runtime v1.9.0 · microsoft/onnxruntime · GitHub

    onnxruntime-linux-x64-1.9.0.tgz 

    2、解压

    下载的onnxruntime是直接编译好的库文件,直接放在自定义的文件夹中即可。在CMakeLists.txt中引入onnxruntime的头文件、库文件即可。

    1. # 引入头文件
    2. include_directories(......../onnxruntime/include)
    3. # 引入库文件
    4. link_directories(......../onnxruntime/lib)

    二、Pytorch导出.onnx模型

    首先,利用pytorch自带的torch.onnx模块导出 .onnx 模型文件,具体查看该部分pytorch官方文档,主要流程如下:

    1. import torch
    2. checkpoint = torch.load(model_path)
    3. model = ModelNet(params)
    4. model.load_state_dict(checkpoint['model'])
    5. model.eval()
    6. input_x_1 = torch.randn(10,20)
    7. input_x_2 = torch.randn(1,20,5)
    8. output, mask = model(input_x_1, input_x_2)
    9. torch.onnx.export(model,
    10. (input_x_1, input_x_2),
    11. 'model.onnx',
    12. input_names = ['input','input_mask'],
    13. output_names = ['output','output_mask'],
    14. opset_version=11,
    15. verbose = True,
    16. dynamic_axes={'input':{1,'seqlen'}, 'input_mask':{1:'seqlen',2:'time'},'output_mask':{0:'time'}})

    torch.onnx.export参数在文档里面都有,opset_version对应的版本很重要,dynamic_axes是对输入和输出对应维度可以进行动态设置,不设置的话输入和输出的Tensor 的 shape是不能改变的,如果输入固定就不需要加。

    导出的模型可否顺利使用可以先使用python进行检测

    1. import onnxruntime as ort
    2. import numpy as np
    3. ort_session = ort.InferenceSession('model.onnx')
    4. outputs = ort_session.run(None,{'input':np.random.randn(10,20),'input_mask':np.random.randn(1,20,5)})
    5. # 由于设置了dynamic_axes,支持对应维度的变化
    6. outputs = ort_session.run(None,{'input':np.random.randn(10,5),'input_mask':np.random.randn(1,26,2)})
    7. # outputs 为 包含'output'和'output_mask'的list
    8. import onnx
    9. model = onnx.load('model.onnx')
    10. onnx.checker.check_model(model)

    如果没有异常代表导出的模型没有问题,目前torch.onnx.export只能对部分支持的Tensor操作进行识别,详情参考Supported operators,对于包括transformer等基本的模型都是没有问题的,如果出现ATen等问题,你就需要对模型不支持的Tensor操作进行改进,以免影响C++对该模型的使用。

    三、模型推理流程

    总体来看,整个ONNXRuntime的运行可以分为三个阶段:

    • Session构造;
    • 模型加载与初始化;
    • 运行;

    1、第1阶段:Session构造

    构造阶段即创建一个InferenceSession对象。在python前端构建Session对象时,python端会通过http://onnxruntime_pybind_state.cc调用C++中的InferenceSession类构造函数,得到一个InferenceSession对象。

    InferenceSession构造阶段会进行各个成员的初始化,成员包括负责OpKernel管理的KernelRegistryManager对象,持有Session配置信息的SessionOptions对象,负责图分割的GraphTransformerManager,负责log管理的LoggingManager等。当然,这个时候InferenceSession就是一个空壳子,只完成了对成员对象的初始构建。

    2、第2阶段:模型加载与初始化

    在完成InferenceSession对象的构造后,会将onnx模型加载到InferenceSession中并进行进一步的初始化。

    2.1. 模型加载

    模型加载时,会在C++后端会调用对应的Load()函数,InferenceSession一共提供了8种Load函数。包读从url,ModelProto,void* model data,model istream等读取ModelProto。InferenceSession会对ModelProto进行解析然后持有其对应的Model成员。

    2.2. Providers注册

    在Load函数结束后,InferenceSession会调用两个函数:RegisterExecutionProviders()和sess->Initialize();

    RegisterExecutionProviders函数会完成ExecutionProvider的注册工作。这里解释一下ExecutionProvider,ONNXRuntime用Provider表示不同的运行设备比如CUDAProvider等。目前ONNXRuntimev1.0支持了包括CPU,CUDA,TensorRT,MKL等七种Providers。通过调用sess->RegisterExecutionProvider()函数,InferenceSession通过一个list持有当前运行环境中支持的ExecutionProviders。

    2.3. InferenceSession初始化

    即sess->Initialize(),这时InferenceSession会根据自身持有的model和execution providers进行进一步的初始化(在第一阶段Session构造时仅仅持有了空壳子成员变量)。该步骤是InferenceSession初始化的核心,一系列核心操作如内存分配,model partition,kernel注册等都会在这个阶段完成。

    1. 首先,session会根据level注册 graph optimization transformers,并通过GraphTransformerManager成员进行持有。
    2. 接下来session会进行OpKernel注册,OpKernel即定义的各个node对应在不同运行设备上的计算逻辑。这个过程会将持有的各个ExecutionProvider上定义的所有node对应的Kernel注册到session中,session通过KernelRegistryManager成员进行持有和管理。
    3. 然后session会对Graph进行图变换,包括插入copy节点,cast节点等。
    4. 接下来是model partition,也就是根运行设备对graph进行切分,决定每个node运行在哪个provider上。
    5. 最后,为每个node创建ExecutePlan,运行计划主要包含了各个op的执行顺序,内存申请管理,内存复用管理等操作。

    3、第3阶段:模型运行

    模型运行即InferenceSession每次读入一个batch的数据并进行计算得到模型的最终输出。然而其实绝大多数的工作早已经在InferenceSession初始化阶段完成。细看下源码就会发现run阶段主要是顺序调用各个node的对应OpKernel进行计算。

    四、代码

    和其他所有主流框架相同,ONNXRuntime最常用的语言是python,而实际负责执行框架运行的则是C++。

    下面就是C++通过onnxruntime对.onnx模型的使用,参考官方样例和常见问题写的模型多输入多输出的情况,部分参数可以参考样例或者查官方API文档。

    1、案例01

    BasicOrtHandler.h

    1. #include "onnxruntime_cxx_api.h"
    2. #include "opencv2/opencv.hpp"
    3. #include
    4. #define CHW 0
    5. class BasicOrtHandler {
    6. public:
    7. Ort::Value BasicOrtHandler::create_tensor(const cv::Mat &mat, const std::vector<int64_t> &tensor_dims, const Ort::MemoryInfo &memory_info_handler, std::vector<float> &tensor_value_handler, unsigned int data_format);
    8. protected:
    9. Ort::Env ort_env;
    10. Ort::Session *ort_session = nullptr;
    11. const char *input_name = nullptr;
    12. std::vector<const char *> input_node_names;
    13. std::vector<int64_t> input_node_dims; // 1 input only.
    14. std::size_t input_tensor_size = 1;
    15. std::vector<float> input_values_handler;
    16. // create input tensor
    17. Ort::MemoryInfo memory_info_handler = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    18. std::vector<const char *> output_node_names;
    19. std::vectorint64_t>> output_node_dims; // >=1 outputs
    20. const char*onnx_path = nullptr;
    21. const char *log_id = nullptr;
    22. int num_outputs = 1;
    23. protected:
    24. const unsigned int num_threads; // initialize at runtime.
    25. protected:
    26. explicit BasicOrtHandler(const std::string &_onnx_path, unsigned int _num_threads = 1);
    27. virtual ~BasicOrtHandler();
    28. protected:
    29. BasicOrtHandler(const BasicOrtHandler &) = delete;
    30. BasicOrtHandler(BasicOrtHandler &&) = delete;
    31. BasicOrtHandler &operator=(const BasicOrtHandler &) = delete;
    32. BasicOrtHandler &operator=(BasicOrtHandler &&) = delete;
    33. protected:
    34. virtual Ort::Value transform(const cv::Mat &mat) = 0;
    35. private:
    36. void initialize_handler();
    37. };

    BasicOrtHandler.cpp

    1. BasicOrtHandler::BasicOrtHandler(const std::string &_onnx_path, unsigned int _num_threads) : log_id(_onnx_path.data()), num_threads(_num_threads) {
    2. // string to wstring
    3. #ifdef LITE_WIN32
    4. std::wstring _w_onnx_path(lite::utils::to_wstring(_onnx_path));
    5. onnx_path = _w_onnx_path.data();
    6. #else
    7. onnx_path = _onnx_path.data();
    8. #endif
    9. initialize_handler();
    10. }
    11. void BasicOrtHandler::initialize_handler() {
    12. // set ort env
    13. ort_env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, log_id);
    14. // 0. session options
    15. Ort::SessionOptions session_options;
    16. // set op threads
    17. session_options.SetIntraOpNumThreads(num_threads);
    18. // set Optimization options:
    19. session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
    20. // set log level
    21. session_options.SetLogSeverityLevel(4);
    22. // GPU compatiable.
    23. // OrtCUDAProviderOptions provider_options;
    24. // session_options.AppendExecutionProvider_CUDA(provider_options);
    25. // #ifdef USE_CUDA
    26. // OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0); // C API stable.
    27. // #endif
    28. // 1. session
    29. ort_session = new Ort::Session(ort_env, onnx_path, session_options);
    30. // memory allocation and options
    31. Ort::AllocatorWithDefaultOptions allocator;
    32. // 2. input name & input dims
    33. input_name = ort_session->GetInputName(0, allocator);
    34. input_node_names.resize(1);
    35. input_node_names[0] = input_name;
    36. // 3. input names & output dimms
    37. Ort::TypeInfo type_info = ort_session->GetInputTypeInfo(0);
    38. auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
    39. input_tensor_size = 1;
    40. input_node_dims = tensor_info.GetShape();
    41. for (unsigned int i = 0; i < input_node_dims.size(); ++i) {
    42. input_tensor_size *= input_node_dims.at(i);
    43. }
    44. input_values_handler.resize(input_tensor_size);
    45. // 4. output names & output dimms
    46. num_outputs = ort_session->GetOutputCount();
    47. output_node_names.resize(num_outputs);
    48. for (unsigned int i = 0; i < num_outputs; ++i) {
    49. output_node_names[i] = ort_session->GetOutputName(i, allocator);
    50. Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
    51. auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();
    52. auto output_dims = output_tensor_info.GetShape();
    53. output_node_dims.push_back(output_dims);
    54. }
    55. }
    56. Ort::Value BasicOrtHandler::create_tensor(const cv::Mat &mat, const std::vector<int64_t> &tensor_dims, const Ort::MemoryInfo &memory_info_handler, std::vector<float> &tensor_value_handler, unsigned int data_format) throw(std::runtime_error) {
    57. const unsigned int rows = mat.rows;
    58. const unsigned int cols = mat.cols;
    59. const unsigned int channels = mat.channels();
    60. cv::Mat mat_ref;
    61. if (mat.type() != CV_32FC(channels)){
    62. mat.convertTo(mat_ref, CV_32FC(channels));
    63. } else{
    64. mat_ref = mat; // reference only. zero-time cost. support 1/2/3/... channels
    65. }
    66. if (tensor_dims.size() != 4) {
    67. throw std::runtime_error("dims mismatch.");
    68. }
    69. if (tensor_dims.at(0) != 1) {
    70. throw std::runtime_error("batch != 1");
    71. }
    72. // CXHXW
    73. if (data_format == CHW) {
    74. const unsigned int target_channel = tensor_dims.at(1);
    75. const unsigned int target_height = tensor_dims.at(2);
    76. const unsigned int target_width = tensor_dims.at(3);
    77. const unsigned int target_tensor_size = target_channel * target_height * target_width;
    78. if (target_channel != channels) {
    79. throw std::runtime_error("channel mismatch.");
    80. }
    81. tensor_value_handler.resize(target_tensor_size);
    82. cv::Mat resize_mat_ref;
    83. if (target_height != rows || target_width != cols) {
    84. cv::resize(mat_ref, resize_mat_ref, cv::Size(target_width, target_height));
    85. } else{
    86. resize_mat_ref = mat_ref; // reference only. zero-time cost.
    87. }
    88. std::vector mat_channels;
    89. cv::split(resize_mat_ref, mat_channels);
    90. // CXHXW
    91. for (unsigned int i = 0; i < channels; ++i){
    92. std::memcpy(tensor_value_handler.data() + i * (target_height * target_width), mat_channels.at(i).data,target_height * target_width * sizeof(float));
    93. }
    94. return Ort::Value::CreateTensor<float>(memory_info_handler, tensor_value_handler.data(), target_tensor_size, tensor_dims.data(), tensor_dims.size());
    95. }
    96. // HXWXC
    97. const unsigned int target_channel = tensor_dims.at(3);
    98. const unsigned int target_height = tensor_dims.at(1);
    99. const unsigned int target_width = tensor_dims.at(2);
    100. const unsigned int target_tensor_size = target_channel * target_height * target_width;
    101. if (target_channel != channels) {
    102. throw std::runtime_error("channel mismatch!");
    103. }
    104. tensor_value_handler.resize(target_tensor_size);
    105. cv::Mat resize_mat_ref;
    106. if (target_height != rows || target_width != cols) {
    107. cv::resize(mat_ref, resize_mat_ref, cv::Size(target_width, target_height));
    108. } else {
    109. resize_mat_ref = mat_ref; // reference only. zero-time cost.
    110. }
    111. std::memcpy(tensor_value_handler.data(), resize_mat_ref.data, target_tensor_size * sizeof(float));
    112. return Ort::Value::CreateTensor<float>(memory_info_handler, tensor_value_handler.data(), target_tensor_size, tensor_dims.data(), tensor_dims.size());
    113. }

    main.cpp

    1. const std::string _onnx_path="";
    2. unsigned int _num_threads = 1;
    3. //init inference
    4. BasicOrtHandler basicOrtHandler(_onnx_path,_num_threads);
    5. // after transform image
    6. const cv::Mat mat = "";
    7. const std::vector<int64_t> &tensor_dims = basicOrtHandler.input_node_dims;
    8. const Ort::MemoryInfo &memory_info_handler = basicOrtHandler.memory_info_handler;
    9. std::vector<float> &tensor_value_handler = basicOrtHandler.input_values_handler;
    10. unsigned int data_format = CHW; // 预处理后的模式
    11. // 1. make input tensor
    12. Ort::Value input_tensor = basicOrtHandler.create_tensor(mat_rs);
    13. // 2. inference scores & boxes.
    14. auto output_tensors = ort_session->Run(Ort::RunOptions{nullptr}, input_node_names.data(), &input_tensor, 1, output_node_names.data(), num_outputs);
    15. // 3. get output tensor
    16. Ort::Value &pred = output_tensors.at(0); // (1,n,c)
    17. //postprocess
    18. ...

    2、案例02
     

    1. #include
    2. #include
    3. #include
    4. int main(int argc, char* argv[]) {
    5. Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test");
    6. Ort::SessionOptions session_options;
    7. session_options.SetIntraOpNumThreads(1);
    8. session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
    9. #ifdef _WIN32
    10. const wchar_t* model_path = L"model.onnx";
    11. #else
    12. const char* model_path = "model.onnx";
    13. #endif
    14. Ort::Session session(env, model_path, session_options);
    15. // print model input layer (node names, types, shape etc.)
    16. Ort::AllocatorWithDefaultOptions allocator;
    17. // print number of model input nodes
    18. size_t num_input_nodes = session.GetInputCount();
    19. std::vector<const char*> input_node_names = {"input","input_mask"};
    20. std::vector<const char*> output_node_names = {"output","output_mask"};
    21. std::vector<int64_t> input_node_dims = {10, 20};
    22. size_t input_tensor_size = 10 * 20;
    23. std::vector<float> input_tensor_values(input_tensor_size);
    24. for (unsigned int i = 0; i < input_tensor_size; i++)
    25. input_tensor_values[i] = (float)i / (input_tensor_size + 1);
    26. // create input tensor object from data values
    27. auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    28. Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), 2);
    29. assert(input_tensor.IsTensor());
    30. std::vector<int64_t> input_mask_node_dims = {1, 20, 4};
    31. size_t input_mask_tensor_size = 1 * 20 * 4;
    32. std::vector<float> input_mask_tensor_values(input_mask_tensor_size);
    33. for (unsigned int i = 0; i < input_mask_tensor_size; i++)
    34. input_mask_tensor_values[i] = (float)i / (input_mask_tensor_size + 1);
    35. // create input tensor object from data values
    36. auto mask_memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    37. Ort::Value input_mask_tensor = Ort::Value::CreateTensor<float>(mask_memory_info, input_mask_tensor_values.data(), input_mask_tensor_size, input_mask_node_dims.data(), 3);
    38. assert(input_mask_tensor.IsTensor());
    39. std::vector ort_inputs;
    40. ort_inputs.push_back(std::move(input_tensor));
    41. ort_inputs.push_back(std::move(input_mask_tensor));
    42. // score model & input tensor, get back output tensor
    43. auto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_node_names.data(), ort_inputs.data(), ort_inputs.size(), output_node_names.data(), 2);
    44. // Get pointer to output tensor float values
    45. float* floatarr = output_tensors[0].GetTensorMutableData<float>();
    46. float* floatarr_mask = output_tensors[1].GetTensorMutableData<float>();
    47. printf("Done!\n");
    48. return 0;
    49. }

    编译命令:

    g++ infer.cpp -o infer onnxruntime-linux-x64-1.4.0/lib/libonnxruntime.so.1.4.0 -Ionnxruntime-linux-x64-1.4.0/include/ -std=c++11

    onnxruntime中Tensor支持的数据类型包括:

    1. typedef enum ONNXTensorElementDataType {
    2. ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED,
    3. ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, // maps to c type float
    4. ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8, // maps to c type uint8_t
    5. ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8, // maps to c type int8_t
    6. ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16, // maps to c type uint16_t
    7. ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16, // maps to c type int16_t
    8. ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32, // maps to c type int32_t
    9. ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64, // maps to c type int64_t
    10. ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING, // maps to c++ type std::string
    11. ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL,
    12. ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16,
    13. ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE, // maps to c type double
    14. ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32, // maps to c type uint32_t
    15. ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64, // maps to c type uint64_t
    16. ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64, // complex with float32 real and imaginary components
    17. ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128, // complex with float64 real and imaginary components
    18. ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 // Non-IEEE floating-point format based on IEEE754 single-precision
    19. } ONNXTensorElementDataType;

    其中需要注意的是使用bool型,需要从uint_8的vector转为bool型:

    1. std::vector<uint8_t> mask_tensor_values;
    2. for(int i = 0; i < mask_tensor_size; i++){
    3. mask_tensor_values.push_back((uint8_t)(true));
    4. }
    5. auto mask_memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    6. Ort::Value mask_tensor = Ort::Value::CreateTensor<bool>(mask_memory_info, reinterpret_cast<bool *>(mask_tensor_values.data()),mask_tensor_size, mask_node_dims.data(), 3);

    性能测试

    实际情况粗略统计,以transformer为例,onnxruntime-c++上的运行效率要比pytorch-python快2-5倍

    C++-onnx:用onnxruntime部署自己的模型_u013250861的博客-CSDN博客

    ONNX Runtime使用简单介绍_竹叶青lvye的博客-CSDN博客_onnxruntime 使用

    onnxruntime的c++使用_chencision的博客-CSDN博客_c++ onnxruntime

    onnxruntime C++ 使用(一)_SongpingWang的技术博客_51CTO博客

    OnnxRunTime的推理流程_hjxu2016的博客-CSDN博客_onnxruntime

    onnxruntime安装与使用(附实践中发现的一些问题)_本初-ben的博客-CSDN博客_onnxruntime安装

  • 相关阅读:
    Python Flask框架-开发简单博客-认证蓝图
    Git使用
    Python ‘list‘ object is not callable错误
    y93.第六章 微服务、服务网格及Envoy实战 -- Envoy配置(四)
    780. 到达终点;2360. 图中的最长环;1871. 跳跃游戏 VII
    ES6中数组的扩展
    SUSCTF2022 Misc-AUDIO&RA2
    《Linux》day3--shell语法(上)
    计算机毕业设计Java化妆品销售网站(源码+系统+mysql数据库+lw文档)
    Python面向对象编程
  • 原文地址:https://blog.csdn.net/u013250861/article/details/127829944