ONNX Runtime C++ 库

ONNX Runtime 为 C++ 提供了完整的推理 API,你可以通过集成它来在 C++ 项目中高效地运行 ONNX 模型。

一、如何获取 ONNX Runtime C++ 库

在你的 C++ 项目中使用 ONNX Runtime,主要有两种方式:

  • 使用预编译库(推荐):这是最简单的入门方式。你可以从 ONNX Runtime 官方网站的 Releases 页面下载适用于你平台(如 Windows、Linux、macOS)的 C/C++ 预编译库。这些库通常包含了核心的推理功能。
  • 从源码编译:如果你有特殊需求,比如需要支持特定的硬件(如 GPU、OpenVINO、NNAPI),或希望定制库的大小(例如为移动端进行精简),则需要从源码编译。官方 GitHub 仓库提供了详细的构建指南。例如,在 Linux 下启用 CUDA 支持的基本步骤是:bashgit clone --recursive https://github.com/microsoft/onnxruntime.git cd onnxruntime ./build.sh --config RelWithDebInfo --build_shared_lib --use_cuda

二、C++ API 的结构

ONNX Runtime 的 C++ API 是 C API 的一个“头文件-only”的封装,设计得比较现代,符合 C++ 的使用习惯。核心的 API 都包含在两个主要的头文件中:

  • onnxruntime_cxx_api.h:这是 C++ 开发主要使用的头文件。它定义了 Ort:: 命名空间下的所有 C++ 类,如 Env(环境)、Session(推理会话)、MemoryInfo(内存信息)、Value(张量)等。这些类利用 RAII(资源获取即初始化)机制自动管理内存,并通过抛出异常来处理错误,让代码更简洁安全。
  • onnxruntime_c_api.h:这是底层的 C API,提供了 OrtApi 结构体,包含所有以 Ort 开头的函数(如 OrtCreateSession)。C++ API 是基于此实现的。虽然可以直接使用 C API,但通常更推荐使用更方便的 C++ 封装。

三、C++ 基础推理流程

在 C++ 中使用 ONNX Runtime 进行模型推理,一般遵循以下几个典型步骤:

  1. 包含头文件:在你的代码中包含 ONNX Runtime 的头文件。cpp#include <onnxruntime_cxx_api.h> #include <vector> // ... 其他标准头文件
  2. 创建环境和会话选项:首先,创建一个 Ort::Env 对象来管理推理环境的日志和全局状态。然后,创建 Ort::SessionOptions 对象来配置会话,例如设置优化级别、线程数等。cppOrt::Env env(ORT_LOGGING_LEVEL_WARNING, "test"); Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); // 如果需要,可以在这里添加其他执行提供程序,如 CPU、CUDA 等
  3. 加载模型并创建会话:使用上一步创建的环境和选项,加载模型文件(.onnx)并创建 Ort::Session 对象。会话是执行推理的核心对象。cppconst char* model_path = "path/to/your/model.onnx"; Ort::Session session(env, model_path, session_options);
  4. 准备输入数据
    • 获取输入输出信息:通过 session.GetInputCount()session.GetInputName()session.GetInputTypeInfo() 等方法,动态获取模型期望的输入名称、维度(shape)和数据类型。
    • 构建输入张量:将你的数据(例如预处理后的图像数据)填充到 std::vector 中。然后,使用 Ort::Value::CreateTensor() 创建一个 ONNX Runtime 张量,你需要提供数据指针、数据大小、维度信息以及数据的内存信息。
  5. 运行推理:调用 session.Run() 方法,传入输入张量的名称和值,以及你想要获取的输出张量名称。函数会返回一个 std::vector<Ort::Value>,包含了推理结果。cppconst char* input_names[] = {"input"}; const char* output_names[] = {"output"}; std::vector<Ort::Value> input_tensors; // ... 创建并填充 input_tensors ... auto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_names, input_tensors.data(), 1, output_names, 1);
  6. 处理输出:从返回的 Ort::Value 对象中提取数据,并进行后续处理,例如解析分类结果或显示检测框。

四、完整示例

#include <onnxruntime_cxx_api.h> #include <vector> #include <iostream> #include <exception> int main() { try { // 1. 创建推理环境(指定日志级别和名称) Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "example"); // 2. 配置会话选项 Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); // 线程数 // 若要使用 GPU,可在此添加 CUDA 执行提供程序(需编译时启用 CUDA) // OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0); // 3. 加载 ONNX 模型(请替换为你的模型路径) const std::string model_path = "linear_model.onnx"; Ort::Session session(env, model_path.c_str(), session_options); // 4. 获取模型输入/输出信息 Ort::AllocatorWithDefaultOptions allocator; // 输入信息 size_t num_inputs = session.GetInputCount(); std::vector<const char*> input_names; std::vector<Ort::AllocatedStringPtr> input_names_ptr; std::vector<std::vector<int64_t>> input_shapes; std::cout << "Number of inputs: " << num_inputs << std::endl; for (size_t i = 0; i < num_inputs; ++i) { auto name = session.GetInputNameAllocated(i, allocator); std::cout << "Input [" << i << "] name: " << name.get() << std::endl; input_names_ptr.push_back(std::move(name)); auto type_info = session.GetInputTypeInfo(i); auto tensor_info = type_info.GetTensorTypeAndShapeInfo(); auto shape = tensor_info.GetShape(); input_shapes.push_back(shape); std::cout << " shape: [ "; for (auto dim : shape) std::cout << dim << " "; std::cout << "]" << std::endl; } // 输出信息 size_t num_outputs = session.GetOutputCount(); std::vector<const char*> output_names; std::vector<Ort::AllocatedStringPtr> output_names_ptr; std::cout << "Number of outputs: " << num_outputs << std::endl; for (size_t i = 0; i < num_outputs; ++i) { auto name = session.GetOutputNameAllocated(i, allocator); std::cout << "Output [" << i << "] name: " << name.get() << std::endl; output_names_ptr.push_back(std::move(name)); } // 构建名称指针数组(用于 Run 接口) for (const auto& ptr : input_names_ptr) input_names.push_back(ptr.get()); for (const auto& ptr : output_names_ptr) output_names.push_back(ptr.get()); // 5. 准备输入数据(以第一个输入的 shape 为准) // 假设第一个输入形状为 [1, 10] 的 float 张量 const std::vector<int64_t>& first_input_shape = input_shapes[0]; size_t input_size = 1; for (auto dim : first_input_shape) input_size *= dim; std::vector<float> input_data(input_size); for (size_t i = 0; i < input_size; ++i) { input_data[i] = static_cast<float>(i); // 填充一些测试数据 } // 创建 CPU 内存信息 Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); // 创建输入张量 Ort::Value input_tensor = Ort::Value::CreateTensor<float>( memory_info, input_data.data(), input_data.size(), first_input_shape.data(), first_input_shape.size() ); // 6. 运行推理 std::vector<Ort::Value> input_tensors; input_tensors.push_back(std::move(input_tensor)); std::vector<Ort::Value> output_tensors = session.Run( Ort::RunOptions{nullptr}, input_names.data(), input_tensors.data(), input_tensors.size(), output_names.data(), output_names.size() ); // 7. 处理输出(假设输出为 float 张量) float* output_data = output_tensors[0].GetTensorMutableData<float>(); auto output_info = output_tensors[0].GetTensorTypeAndShapeInfo(); auto output_shape = output_info.GetShape(); size_t output_count = output_info.GetElementCount(); std::cout << "Output shape: [ "; for (auto dim : output_shape) std::cout << dim << " "; std::cout << "]" << std::endl; std::cout << "Output data: "; for (size_t i = 0; i < output_count; ++i) { std::cout << output_data[i] << " "; } std::cout << std::endl; } catch (const Ort::Exception& e) { std::cerr << "ONNX Runtime error: " << e.what() << std::endl; return -1; } catch (const std::exception& e) { std::cerr << "Standard error: " << e.what() << std::endl; return -1; } return 0; }
如何生成测试模型(Python)

上述示例需要一个 ONNX 模型。以下 Python 脚本可生成一个简单的线性模型 linear_model.onnx(输入 x 形状为 [1,10],输出 y 形状为 [1,10]):

import onnx from onnx import helper, TensorProto, numpy_helper import numpy as np # 创建随机权重和偏置 W = np.random.randn(10, 10).astype(np.float32) b = np.random.randn(10).astype(np.float32) # 定义输入输出 x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 10]) # 创建初始值 W_initializer = numpy_helper.from_array(W, name='W') b_initializer = numpy_helper.from_array(b, name='b') # 创建节点(Gemm 实现线性变换 y = x * W^T + b) node = helper.make_node('Gemm', inputs=['x', 'W', 'b'], outputs=['y']) # 构建图 graph = helper.make_graph([node], 'linear_graph', [x], [y], initializer=[W_initializer, b_initializer]) # 构建模型 model = helper.make_model(graph, producer_name='example') # 保存模型 onnx.save(model, 'linear_model.onnx') print("模型已保存为 linear_model.onnx")

运行该脚本生成模型文件,然后将 model_path 指向该文件。

编译与运行
Linux / macOS

假设 ONNX Runtime 安装在 /path/to/onnxruntime,编译命令如下:

bash

g++ -std=c++11 -I/path/to/onnxruntime/include \ -L/path/to/onnxruntime/lib -lonnxruntime \ example.cpp -o example

如果 ONNX Runtime 安装到了系统路径(如 /usr/local),可以简化:

bash

g++ -std=c++11 example.cpp -lonnxruntime -o example

运行前确保动态库路径可找到(Linux 上可设置 LD_LIBRARY_PATH):

bash

export LD_LIBRARY_PATH=/path/to/onnxruntime/lib:$LD_LIBRARY_PATH ./example

Windows

使用 Visual Studio 开发人员命令提示符:

bash

cl /EHsc /I C:\path\to\onnxruntime\include example.cpp ^ /link /LIBPATH:C:\path\to\onnxruntime\lib onnxruntime.lib

运行前将 onnxruntime.dll 放在可执行文件目录或系统路径中。

说明
  • 代码自动获取输入张量的实际形状并分配数据,但假设数据类型为 float。如果你的模型输入是其他类型(如 int64),需要相应调整 CreateTensor 的模板参数和数据填充。
  • 如果模型有多个输入,需要为每个输入创建一个 Ort::Value 并放入 input_tensors 向量,同时确保输入名称顺序与模型一致。
  • 错误处理统一使用 try-catch,ONNX Runtime 的 C++ API 在出错时会抛出 Ort::Exception
  • 如需 GPU 支持,可在 session_options 中添加相应的执行提供程序(如 OrtSessionOptionsAppendExecutionProvider_CUDA),并确保 ONNX Runtime 库为 GPU 版本。

Read more

2024第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

2024第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

记录刷题的过程、感悟、题解。 希望能帮到,那些与我一同前行的,来自远方的朋友😉 大纲:  1、握手问题-(解析)-简单组合问题(别人叫她 鸽巢定理)😇,感觉叫高级了  2、小球反弹-(解析)-简单物理问题,不太容易想  3、好数-(解析)-简单运用分支计算  4、R 格式-(解析)-高精度,不是快速幂😉  5、宝石组合-(解析)-lcm推论(gcd、lcm结合)  6、数字接龙-(解析)-DFS(蓝桥专属、每年必有一道)  7、拔河-(解析)-定一端,动一端😎 题目: 1、握手问题 问题描述

By Ne0inhk
求最大公约数(gcd)与最小公倍数(lcm)【C/C++】

求最大公约数(gcd)与最小公倍数(lcm)【C/C++】

大家好啊,欢迎来到本博客( •̀ ω •́ )✧,我将带领大家详细的了解最大公约数的思想与解法。 一、什么是公约数 公约数,也称为公因数,是指两个或多个整数共有的因数。具体来说,如果一个整数能被两个或多个整数整除,那么这个整数就是这些整数的公约数。 例如,考虑整数12和18: * 12的因数有 :1, 2, 3, 4, 6, 12 * 18的因数有:1, 2, 3, 6, 9, 18 12和18的公约数是它们共有的因数,即:1, 2, 3, 6 附:lcm是最小公倍数 定理:a、b 两个数的最小公倍数(lcm)乘以它们的最大公约数(gcd)等于 a 和 b 本身的乘积。 如:gcd(

By Ne0inhk
C++ 入门必看:引用怎么用?inline 和 nullptr 是什么?

C++ 入门必看:引用怎么用?inline 和 nullptr 是什么?

目录 * 一、引用 * 1.1 引用的概念和定义 * 1.2 引用的特性 * 1.3 引用的使用 * 1.3.1 引用传参的使用 * 1.3.2 传引用返回的错误使用 * 1.3.3 传引用返回的正确使用 * 1.4 const引用 * 1.5 指针和引用的关系 * 二、inline * 三、nullptr * 总结 🎬 云泽Q:个人主页 🔥 专栏传送入口: 《C语言》《数据结构》《C++》《Linux》 ⛺️遇见安然遇见你,不负代码不负卿~ 在这篇文章开始之前,我想给大家推荐一个非常牛的人工智能学习网站。在近几年,大家也知道人工智能和 AI 技术的发展也是非常迅速,

By Ne0inhk
手撕哈希全家桶!unordered_map/set 底层 + 位图布隆过滤器----《Hello C++ Wrold!》(24)--(C/C++)

手撕哈希全家桶!unordered_map/set 底层 + 位图布隆过滤器----《Hello C++ Wrold!》(24)--(C/C++)

文章目录 * unordered系列关联式容器 * 哈希 * 哈希冲突 * 闭散列的模拟实现 * 开散列的模拟实现 * 哈希桶里面迭代器的模拟实现 * unordered_set的封装 * unordered_map的封装 * 位图 * 应用 * 布隆过滤器 * 布隆过滤器的模拟实现 * 哈希切割 * 哈希切割的应用 * 作业部分 unordered系列关联式容器 有unordered_mapunordered_setunordered_multimapunordered_multiset 这些是用哈希表是实现的 用法的话跟eg:set那种几乎相同 --接口都差不多 他们可以用范围for去遍历哈 跟set那些的区别: 1.unordered系列的容器的迭代器是单向迭代器 2.unordered系列中序遍历出来不是有序的 3.unordered系列的容器的性能比eg:set那些要稍微好些;但是升序或降序的数据插入的话,set好些其实 引申:比较性能要在release下面比较 哈希 哈

By Ne0inhk