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 进行模型推理,一般遵循以下几个典型步骤:
- 包含头文件:在你的代码中包含 ONNX Runtime 的头文件。cpp#include <onnxruntime_cxx_api.h> #include <vector> // ... 其他标准头文件
- 创建环境和会话选项:首先,创建一个
Ort::Env对象来管理推理环境的日志和全局状态。然后,创建Ort::SessionOptions对象来配置会话,例如设置优化级别、线程数等。cppOrt::Env env(ORT_LOGGING_LEVEL_WARNING, "test"); Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); // 如果需要,可以在这里添加其他执行提供程序,如 CPU、CUDA 等 - 加载模型并创建会话:使用上一步创建的环境和选项,加载模型文件(
.onnx)并创建Ort::Session对象。会话是执行推理的核心对象。cppconst char* model_path = "path/to/your/model.onnx"; Ort::Session session(env, model_path, session_options); - 准备输入数据:
- 获取输入输出信息:通过
session.GetInputCount()、session.GetInputName()、session.GetInputTypeInfo()等方法,动态获取模型期望的输入名称、维度(shape)和数据类型。 - 构建输入张量:将你的数据(例如预处理后的图像数据)填充到
std::vector中。然后,使用Ort::Value::CreateTensor()创建一个 ONNX Runtime 张量,你需要提供数据指针、数据大小、维度信息以及数据的内存信息。
- 获取输入输出信息:通过
- 运行推理:调用
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); - 处理输出:从返回的
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 版本。