onnxruntime的c++使用_onnxruntime c++-程序员宅基地

技术标签: c++  深度学习  pytorch  分享  

onnxruntime的c++使用

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

版本环境

python:

pytorch == 1.6.0

onnx == 1.7.0

onnxruntime == 1.3.0

c++:

onnxruntime-linux-x64-1.4.0

使用流程

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

import torch
checkpoint = torch.load(model_path)
model = ModelNet(params)
model.load_state_dict(checkpoint['model'])
model.eval()

input_x_1 = torch.randn(10,20)
input_x_2 = torch.randn(1,20,5)
output, mask = model(input_x_1, input_x_2)

torch.onnx.export(model,
                 (input_x_1, input_x_2),
                 'model.onnx',
                 input_names = ['input','input_mask'],
                 output_names = ['output','output_mask'],
                 opset_version=11,
                 verbose = True,
                 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进行检测

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

import onnx
model = onnx.load('model.onnx')
onnx.checker.check_model(model)

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

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

#include <assert.h>
#include <vector>
#include <onnxruntime_cxx_api.h>

int main(int argc, char* argv[]) {
  Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test");
  Ort::SessionOptions session_options;
  session_options.SetIntraOpNumThreads(1);
  
  session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);

#ifdef _WIN32
  const wchar_t* model_path = L"model.onnx";
#else
  const char* model_path = "model.onnx";
#endif

  Ort::Session session(env, model_path, session_options);
  // print model input layer (node names, types, shape etc.)
  Ort::AllocatorWithDefaultOptions allocator;

  // print number of model input nodes
  size_t num_input_nodes = session.GetInputCount();
  std::vector<const char*> input_node_names = {"input","input_mask"};
  std::vector<const char*> output_node_names = {"output","output_mask"};
    
  std::vector<int64_t> input_node_dims = {10, 20};
  size_t input_tensor_size = 10 * 20; 
  std::vector<float> input_tensor_values(input_tensor_size);
  for (unsigned int i = 0; i < input_tensor_size; i++)
    input_tensor_values[i] = (float)i / (input_tensor_size + 1);
  // create input tensor object from data values
  auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
  Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), 2);
  assert(input_tensor.IsTensor());

  std::vector<int64_t> input_mask_node_dims = {1, 20, 4};
  size_t input_mask_tensor_size = 1 * 20 * 4; 
  std::vector<float> input_mask_tensor_values(input_mask_tensor_size);
  for (unsigned int i = 0; i < input_mask_tensor_size; i++)
    input_mask_tensor_values[i] = (float)i / (input_mask_tensor_size + 1);
  // create input tensor object from data values
  auto mask_memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
  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);
  assert(input_mask_tensor.IsTensor());
    
  std::vector<Ort::Value> ort_inputs;
  ort_inputs.push_back(std::move(input_tensor));
  ort_inputs.push_back(std::move(input_mask_tensor));
  // score model & input tensor, get back output tensor
  auto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_node_names.data(), ort_inputs.data(), ort_inputs.size(), output_node_names.data(), 2);
  
  // Get pointer to output tensor float values
  float* floatarr = output_tensors[0].GetTensorMutableData<float>();
  float* floatarr_mask = output_tensors[1].GetTensorMutableData<float>();
  
  printf("Done!\n");
  return 0;
}

编译命令 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支持的数据类型包括:

typedef enum ONNXTensorElementDataType {
  ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED,
  ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT,   // maps to c type float
  ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8,   // maps to c type uint8_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8,    // maps to c type int8_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16,  // maps to c type uint16_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16,   // maps to c type int16_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32,   // maps to c type int32_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64,   // maps to c type int64_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING,  // maps to c++ type std::string
  ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL,
  ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16,
  ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE,      // maps to c type double
  ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32,      // maps to c type uint32_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64,      // maps to c type uint64_t
  ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64,   // complex with float32 real and imaginary components
  ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128,  // complex with float64 real and imaginary components
  ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16     // Non-IEEE floating-point format based on IEEE754 single-precision
} ONNXTensorElementDataType;

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

std::vector<uint8_t> mask_tensor_values;
for(int i = 0; i < mask_tensor_size; i++){
	mask_tensor_values.push_back((uint8_t)(true));
}
auto mask_memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
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倍

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/baidu_34595620/article/details/112176278

智能推荐

Jenkins地址无法访问 http://localhost:8080/_搜索端口8080,内容包含jenkins的网站-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏4次。服务器上都配置完毕,jenkins启动成功后访问不到这个url检查一下你的端口是否开启firewall-cmd --list-ports如果发现没有jenkins的端口,否则加上firewall-cmd --permanent --zone=public --add-port=8080/tcp开启已经配置好的jenkins的端口,然后在重启防火墙,注意一定要重启防火墙!systemctl reload firewalld注意jenkins默认的是8080端口,修改的话要到指定目录下修改vi_搜索端口8080,内容包含jenkins的网站

谈谈中间件:AddSingleton AddScoped AddTransient 三者的差异-程序员宅基地

文章浏览阅读5.4k次。简介AddSingleton:此方法创建一个 Singletion 服务。首次请求时创建 Singleton 服务。然后,所有后续的请求中都会使用相同的实例。因此,通常每个应用程序只创建一次 Singleton 服务,并且在整个应用程序生命周期中使用该单个实例 AddScoped:此方法创建一个 Scoped 服务。在范围内的每个请求中创建一个新的 Scoped 服务实例。例如在 We..._addsingleton

Eastmoney-Spyder:股吧网页数据抓取分析(一)_股吧爬虫-程序员宅基地

文章浏览阅读1.6w次,点赞33次,收藏208次。股吧数据抓取分析_股吧爬虫

使用 ortp 发送原始 H.264 码流_ortp-warning-rtp_session_ts_to_t: use of unsupport-程序员宅基地

文章浏览阅读3.5k次,点赞3次,收藏11次。oRTP 是一个 RTP (Real-time Transport Protocol (RFC 3550)) 协议的库实现,它完全以 C 语言来实现,因此方便应用于各种不同的平台。本文分享用 oRTP 发送,以 Android 的 MediaCodec 编码出来的原始 H.264 码流,又称裸流的方法。H.264 码流MediaCode 以 H.264 编码格式编码之后的视频,是由一个一个的NALU_ortp-warning-rtp_session_ts_to_t: use of unsupported payload type 96.

【Linux基础操作】进程管理和服务器搭建_linux useradd 用户名称大小写-程序员宅基地

文章浏览阅读217次。目录1. 压缩包管理1>. 屌丝版:2>. 高富帅版:1). tar -- 不使用z/j参数 , 该命令只能对文件或目录打包2). rar -- 必须手动安装该软件3). zip --总结: 相同之处:2. 进程管理:1. 压缩包管理1>. 屌丝版:1). gzip – .gz格式的压缩包gzip *.txt //每个文件分别压缩 不会保留原始文件了 不能打包压缩gunzip *.txt//解压缩 还原文件2). bzip2 – .bz2格式的压缩包bzip2 *.txt /_linux useradd 用户名称大小写

史上最强Java学习路线分享_java最强学习目录-程序员宅基地

文章浏览阅读103次。转自:http://www.pinlue.com/article/2020/12/1609/3211419596458.html_java最强学习目录

随便推点

AUTO-INC锁和AUTO_INCREMENT在InnoDB中处理方式_autoinc_increment-程序员宅基地

文章浏览阅读8.4k次,点赞5次,收藏7次。AUTO-INC LocksAn AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values i_autoinc_increment

db2查最新值的前一天值_NBA 2K21 最新球员能力值公布-程序员宅基地

文章浏览阅读266次。2K21能力值 今天,NBA 2K21公布了最新的球员能力值,勒布朗-詹姆斯以98的能力值高居第一。排在第2到第6的依次是:扬尼斯-阿德托昆博(97)、詹姆斯-哈登(96)、凯文-杜兰特(95)、斯蒂芬-库里(95)和达米安-利拉德(95)。 接下来的东契奇的能力值达到94,之后的依次是:吉米-巴特勒(93)、凯里-欧文(90)和杰森-塔图姆(90)。 90..._威少2k21能力值

后处理——Bloom效果_后处理效果-程序员宅基地

文章浏览阅读822次。// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "Unity Shaders Book/Chapter 12/Bloom" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} ..._后处理效果

Linux镜像_ntp镜像-程序员宅基地

文章浏览阅读3.7k次。二、 Linux镜像2.1 下载安装镜像http://ftp.sjtu.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1503-01.iso注:此镜像为示例,具体实际镜像版本以实际要求为准2.2 创建一个空镜像#qemu-img create -f qcow2 [filename] 10G2.3 安装linux镜像#qemu-syst..._ntp镜像

宝塔面板加载docker安装青龙面板,最新青龙面板详细搭建教程_宝塔面板和青龙面板哪个好-程序员宅基地

文章浏览阅读1.4w次,点赞4次,收藏42次。docker安装青龙面板可以实现很多我们想要的功能,哈哈, 废话不多说了, 开始说方法。一、购买云服务器现在的云服务器真的是很便宜,各大主机厂商对待新手是如此的慷慨,老用户不如狗,要买就好狠狠地买上它三年。腾讯云:【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年74元购买完云服务器后安装 CentOS 系统版本的话 CentOS 7.6,当然你要用8.0或8.2更高的版本应该也可以。二、Xshell软件SSH连接终端连接工具很多,我这里用的是“堡塔远程工具”,个人觉得..._宝塔面板和青龙面板哪个好

HDU6092 Rikka with Subset-01背包dp-2017多校联盟5 第8题-程序员宅基地

文章浏览阅读303次。HDU6092 Rikka with Subset-01背包dp-2017多校联盟5 第8题As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some math tasks to practice. There is one of them:Yuta has npositive A1−Anand their sum is m. Then for each subset Sof

推荐文章

热门文章

相关标签