Onnxruntime C api wrap instruction

Onnxruntime c接口说明及动态调用示例

背景:

需要onnx模型推理的功能,直接引用onnxruntime代码会引起编译问题。所以考虑动态加载onnxruntime的动态库完成。C++的接口依然需要源码依赖,所以考虑使用onnxruntime的c接口。

1.How to access Onnxruntime C API:

要访问c api,需要拿到 c api的函数指针,而onnxruntime 的所有capi定义在 一个结构体中:

https://github.com/microsoft/onnxruntime/blob/master/onnxruntime/include/onnxruntime/core/session/onnxruntime_c_api.h

上述仅随意列举了用到的函数指针,由于onnxruntime在c api的接口实现中将初始化一个全局静态OrtApi对象,因而这些函数指针并不需要一一获取,只用获取到这个OrtApi对象即可通过访问成员的方式使用对应的函数,极大方便了接口使用。而获取这个对象是通过一个OrtGetApiBase函数实现。见如下定义。

首先通过导出函数OrtGetApiBase函数获取指向一个全局静态结构体:OrtApiBase结构体的指针,该结构体内定义了获取包含所用到的函数指针的全局静态OrtApi结构体对象的函数指针GetApi。

因此可以通过如下代码

获取需要的OrtApi结构体对象,从而能访问所需的函数指针。

另外onnxruntime定义了一系列接口用到的结构体,以及对应的资源释放函数 ReleaseXXX。

2. How to Use the interface:

2.1 How to create session:

可以参考:https://github.com/microsoft/onnxruntime/blob/master/onnxruntime/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests.Capi/C_Api_Sample.cpp

有如下两个接口可以创建session:

这两接口区别是模型的传入类型不一样,由于这里我们不是从文件加载模型进行推理,所以这里采用CreateSessionFromArray接口。

a. const OrtEnv* env:

要使用该接口,需要先创建执行环境,

可以用该函数创建,参数就是日志级别和日志标示。

b. const void* model_data && size_t model_data_length:

model_data即模型二进制表示的指针,而要从ModelProto获取二进制表示,包括model_data_len是二进制表示的大小信息,都需要从ONNX protobuf的定义里找对应的函数,代码如下:

会话配置,可以通过下述接口获取,

至此,一个session便准备完毕。

2.2 How to use OrtValue

OrtValue是onnxruntime c api中最常见的类型,用来封装所有的数据传递结构。最主要的就是tensor,所以api中也提供了一系列相关的函数接口用于tensor相关的类型转换。

由于我们是在ONNX中调用onnxruntime的api,而且是用动态加载的方式,所以我们只用关心从onnx tensor 到OrtValue的相互转换,而OrtValue内部封装的是onnxruntime的tensor类型。

2.3 How to infer

主要是用这个接口:

OrtStatus*( * Run)( OrtSession* sess, const OrtRunOptions* run_options, const char* const* input_names,  const OrtValue* const* input, size_t input_len,
const char* const* output_names, size_t output_names_len, OrtValue** output);

3. How to Convert ONNX tensor to OrtValue

先说模型infer过程中首先会遇到onnx下input tensor转换成OrtValue的问题,

核心是调用

CreateTensorAsOrtValue / CreateTensorWithDataAsOrtValue 这两个函数进行OrtValue类型的tensor创建,这两个函数的区别一是是否需要由onnxruntime进行内存分配及其内存管理的职责。

由于input tensor 已经开辟并保持了输入数据,我们不需要onnxruntime重复开辟内存存放数据,因而我们是使用 CreateTensorWithDataAsOrtValue 函数去创建,对函数原型进行简化(去掉说明的宏)

OrtStatus*(* CreateTensorWithDataAsOrtValue)(const OrtMemoryInfo* info, void* p_data, size_t p_data_len, const int64_t* shape, size_t shape_len, ONNXTensorElementDataType type, OrtValue** out);

依次分析入参:

a. const OrtMemoryInfo* info:可以通过接口:

OrtStatus*(* CreateCpuMemoryInfo)(enum OrtAllocatorType type, enum OrtMemType mem_type1, OrtMemoryInfo** out);

获取,而CreateCpuMemoryInfo接口只需要传入两个枚举类型即可获取。

b. void* p_data: &&   size_t p_data_len:

此即原onnx tensor的data指针,由于onnx tensor的数据存放成员因tensor的类型不同而不同,所以我们需要一个辅助函数来获取该data实际存储的位置,

p_data_len就是 p_data指向的内存空间的大小,根据onnxruntime 的 api实现得知此大小是字节大小,并且是0字节对齐的。

所以函数内实现先根据 ONNX tensor的类型获取到存储数据的成员vector容器,从而获取数据指针及数据数量,并根据类型计算出总大小。

c. const int64_t* shape && size_t shape_len:

shape和shape_len及张量维度(通道大小)和维度数量(通道数量)信息,这个信息OrtValue 和 Onnx tensor的定义没有什么区别,所以直接获取:

input_tensors[i].sizes().data(),
input_tensors[i].sizes().size(),

d. ONNXTensorElementDataType type:

此参数即OrtValue对应tensor的元素类型信息,这里用一个函数进行对应枚举类型的转换

4. How to Convert OrtValue to ONNX tensor

infer得到的OrtValue 需要转成 ONNX tensor,onnxruntime并没有提供转换接口,需要从OrtValue中获取数据指针/大小/类型等信息构造onnx tensor

a.获取数据指针:

// This function doesn’t work with string tensor
// this is a no-copy method whose pointer is only valid until the backing
// OrtValue is free’d.
OrtStatus*( * GetTensorMutableData)( OrtValue* value, void** out);

参数不再赘述。

b. 获取tensor类型和维度信息:

首先通过GetTensorTypeAndShape获取类型和维度信息对象:

/**
* \param out Should be freed by OrtReleaseTensorTypeAndShapeInfo after use
*/
OrtStatus*(* GetTensorTypeAndShape)( const OrtValue* value, OrtTensorTypeAndShapeInfo** out);

传入即infer返回的output (OrtValue),传出OrtTensorTypeAndShapeInfo对象。

然后获取数据类型:

OrtStatus*( * GetTensorElementType)( const OrtTensorTypeAndShapeInfo*, enum ONNXTensorElementDataType* out);

拿到的类型信息再做一下转换:

接着获取数据维度信息,包括维度数量和维度信息:

OrtStatus*(* GetDimensionsCount)( const OrtTensorTypeAndShapeInfo* info, size_t* out);
OrtStatus*(* GetDimensions)( const OrtTensorTypeAndShapeInfo* info, int64_t* dim_values, size_t dim_values_length);

拿到上述信息后开始构造 ONNX tensor,这里还是要根据类型进行数据存储:

等上述过程全部结束,别忘了释放所有资源。

上述过程我简单写了个代码,放到GITHUB上:

代码地址:https://github.com/atp798/onnx_dynamic_load.git

0

发表评论