Skip to content

AidLite C++ 接口文档 ​

💡注意 使用 Aidlite-SDK C++ 开发需要了解如下事项:

  • 编译时需要包含头文件,存放路径 /usr/local/include/aidlux/aidlite/aidlite.hpp
  • 链接时需要指定库文件,存放路径 /usr/local/lib/libaidlite.so

模型数据类型.enum DataType ​

对于 AidliteSDK 而言,会用来处理不同框架的不同模型,每个模型也会有不同的输入数据类型和不同的输出数据类型。调用某些接口时,需要设置模型的输入输出数据类型,就需要用到此数据类型的枚举。

成员变量名类型值描述
TYPE_DEFAULTuint8_t0无效数据类型
TYPE_UINT8uint8_t1Unsigned char 数据
TYPE_INT8uint8_t2Char 数据
TYPE_UINT32uint8_t3Unsigned int32 数据
TYPE_FLOAT32uint8_t4Float32 数据
TYPE_INT32uint8_t5Int32 数据
TYPE_INT64uint8_t6Int64 数据
TYPE_UINT64uint8_t7Unsigned int64 数据
TYPE_INT16uint8_t8Int16 数据
TYPE_UINT16uint8_t9Unsigned int16 数据
TYPE_FLOAT16uint8_t10Float16 数据
TYPE_BOOLuint8_t11Bool 数据

推理实现类型.enum ImplementType ​

成员变量名类型值描述
TYPE_DEFAULTuint8_t0无效 ImplementType 类型
TYPE_MMKVuint8_t1通过 MMKV 实现(现在没有相关的实现,后期可能移除)
TYPE_REMOTEuint8_t2远程推理实现
TYPE_FASTuint8_t2TYPE_REMOTE 的兼容别名,已废弃,不建议继续使用
TYPE_LOCALuint8_t3本地推理实现

💡注意

特别说明:

TYPE_LOCAL 与 TYPE_REMOTE 的区别在于:对于 Aplux 融合系统而言,前者的推理实现过程是在 Linux 环境本地完成的,无需数据的传输。而后者则是通过 IPC(进程间通信)的方式与 Android 环境实现数据交互,推理实现过程在 Android 环境完成的,故而会涉及到数据的传输。综上所述,TYPE_LOCAL 方式是用来完成推理过程的优选项。

REMOTE 兼容性与恢复 ​

本节适用于包含 REMOTE 兼容性修复的客户端。合并源码不会自动更新板卡上的软件包。

TYPE_REMOTE 通过进程间通信(IPC)调用 Android 后端。请同时核对客户端和 Android 服务的能力。C++ 枚举中存在某个版本,不代表旧服务支持该版本。

  • 张量信息:优先使用服务返回的信息。旧 QNN 服务可能不返回这些信息。此时,只能使用明确声明的单图 FP32 形状,走普通数据通路。请通过 Model::set_model_properties() 设置输入和输出。客户端不会推测原生数据类型、名称或量化参数。
  • 原生数据:is_native=true 需要真实张量信息。旧协议客户端支持 FP16、UINT8 和 FP32 原生传输。Android 后端也必须支持所选类型。不支持的类型会报错,不能将 INT8 缓冲区当作 FP32 传入。
  • 超时恢复:超时不代表远端没有执行。停止使用当前对象。释放对象后,创建新对象并重新加载模型。重新设置全部输入。不要在原对象上重试,旧回复可能仍在返回。
  • 输出生命周期:C++ 输出指针是共享内存视图。需要长期保存结果时,请先复制数据。不要在下一次模型操作或释放对象后继续使用原指针。
  • 线程使用:单次调用会串行处理。完整的“设置输入、执行、取输出”序列需要外部锁。独立任务可以使用独立对象。
  • 协议容量:旧协议的单条控制消息最多为 1008 字节。超长消息会报错。缩短请求内容,或使用支持更大消息的配套服务。

以下行为需要更新后的 Android 模型槽位管理服务。

  • 模型数量:服务返回 All remote model slots are in use 时,先释放不用的模型。然后创建新对象。正在运行的模型会保留自己的槽位。
  • 客户端断开:服务观察到首次心跳前,空闲宽限为 15 秒。之后的空闲宽限为 2 秒。正在执行的操作会先结束,再由工作线程归还槽位。超时不会立即取消 NPU 操作。
  • 声明形状:每个输入和输出保留各自声明的形状。不同张量可以有不同的维度数量。空形状表示标量。请确认所选后端支持该形状。
  • 线程数:使用 TFLite 时,可将 Config::number_of_threads 设为正数。更新后的服务会传递该值。默认值 -1 保留 SDK 默认设置。

Python 高层 AutoModel 和 InferenceSession 仍要求 LOCAL。本次传输修复不解除该限制。不要据此假定所有后端、动态形状或原生数据类型均已支持。

测试性能时,请使用相同模型和运行时。先检查结果,再比较端到端时延、尾部时延和 CPU 用量。进程调度和通信仍有开销,不能保证所有模型都接近本地推理速度。

一次完成输入、执行和输出 ​

使用包含新接口的配套 SDK 头文件和运行库时,可以调用 Interpreter::run()。此接口保留原有函数,默认不会改变现有代码的调用方式。

cpp
std::vector<Aidlux::Aidlite::RunInput> inputs{
    {0, input.data(), input.size() * sizeof(float), false}
};
std::vector<Aidlux::Aidlite::RunOutput> outputs{{0, false}};
if (interpreter->run(inputs, outputs) != 0) {
    // Report the failure. Do not retry automatically.
    return 1;
}
// outputs[0].data is a borrowed view; outputs[0].byte_size is its length.

示例假设 input 是当前后端输入契约要求的连续 FP32 缓冲区。其他模型应使用对应的数据类型。byte_size 表示字节数,不是元素数量。每个输入可独立选择 is_native。原生数据仍需符合后端的实际表示。

一次调用必须提供全部输入。输入按索引绑定,可以采用不同的排列顺序。输出列表可以选择所需输出,也可以为空。重复索引、错误长度和未加载的模型会报错。

run() 与同一解释器上的数据和生命周期操作会串行执行。返回的输出是借用视图。其有效期到下一次模型操作或对象释放为止。需要保留结果时,先复制数据。此接口不会延长 C++ 对象本身的生命周期。

REMOTE 客户端只在服务明确声明 FUSED_RUN 能力后,才使用一次 IPC(进程间通信)请求完成整次推理。旧服务使用原有的输入、执行和输出步骤。缺少可选接口的旧插件也使用原有步骤。能力检查本身不会执行模型。

调用开始后发生错误时,模型可能已经执行。客户端不会自动重试,也不会在错误后再走一次旧通路。超时后须按前述恢复流程创建新对象。

当前 REMOTE 整次推理要求固定的、可验证的张量长度。它不支持切换运行时图。Android SDK 不支持的类型会明确报错,不会当作 FP32 解释。此限制不表示所有模型都必须使用 FP32。高层 Python 的 REMOTE 限制仍保留。

REMOTE 共享内存资源 ​

重复推理时,复用已加载的解释器。不再使用模型时,先结束对输出指针的访问。然后调用 destroy(),并检查返回值。

IPC 共享内存修复需要更新配套软件包。更新 Linux 客户端的 IPC 库。更新 Android 服务的 IPC 库及 JNI 组件。只更新一端,不能修复另一端的资源泄漏。合并源码不会自动更新板卡软件包。

更新后的 IPC 库会回收对象持有的内存映射和句柄。连接失败也会关闭临时句柄。停止旧式描述符服务时,库会等待工作线程退出。其他对象持有的独立映射仍然有效。

此修复不增加模型格式或数据类型支持。对比性能时,使用配套客户端、服务和运行时。先检查推理结果,再比较完整调用的时延。

REMOTE 模型信息协商 ​

配套更新后的客户端和 Android 服务会在创建模型时协商协议。旧端没有声明新能力时,客户端使用旧协议。现有 C++ 调用顺序保持不变。

  1. 创建模型并加载。新协议的加载回复只返回执行状态。
  2. 客户端通过 get_model_info 读取完整信息,并缓存结果。读取发生在加载阶段。
  3. 后续输入、执行和输出调用使用共享内存。它们不会重复查询模型信息。

新信息接口的单条回复最多为 4 MiB。它保留图分组、张量名称、索引、元素数量、运行时数据类型和形状。旧协议的控制区仍为 1008 字节。旧客户端不能读取多图信息或超出控制区的信息。

这些字段来自后端 SDK。它们描述转换后的运行时张量,不能替代 AIMO 中的原始模型输入输出契约。服务不会推测原始布局、量化比例或偏移量。Java SDK 的当前接口没有提供这些字段。用户应按原始模型的契约准备数据,不应统一改为 FP32。旧 QNN 普通通路的 FP32 回退规则仅适用于上节说明的旧服务。

服务在模型工作线程中读取信息。其他线程只读取不可变缓存。后端不提供某类信息时,回复会明确标记不可用。此时,不能根据空列表推测数据类型或量化参数。信息查询不会占用推理控制区。

新协议为每个模型分配一个实例标识。模型释放后,旧标识不能查询或调用复用该槽位的新模型。该标识用于避免串用模型,不是访问授权机制。

当前 C++ 张量信息接口使用无符号形状。收到负数动态维度时,客户端会报告不支持,而不会将它转换为巨大正数。传递多图描述也不表示 REMOTE 已支持切换图。高层 Python 接口仍要求 LOCAL。

选择独立的后端目录 ​

使用支持 AIDLITE_BACKEND_LIBRARY_DIR 的核心库时,可以为进程选择独立的后端目录。此设置适用于 Linux 客户端插件。测试新后端时,可以保留已安装的插件。

  1. 将配套后端库放入独立目录。使用绝对路径。

  2. 使用配套的 SDK 头文件和核心库。将后端依赖放入动态链接器的搜索路径。

  3. 启动测试程序。例如:

    sh
    env AIDLITE_BACKEND_LIBRARY_DIR="$PWD/test-backends" ./infer

在进程启动前设置此变量。未设置时,核心库使用原有默认目录。空值、相对路径或缺少插件会使初始化失败。指定目录中的插件加载失败时,核心库会报告错误。

此变量只选择客户端插件。使用 REMOTE 时,仍须核对 Android 服务的版本和能力。不同版本的后端应使用独立进程进行对比。

模型框架类型.enum FrameworkType ​

由于 AidliteSDK 整合了多种深度学习推理框架,所以在使用过程中,需要设置当前使用哪个框架的模型,就需要设置所需推理框架的类型枚举。

成员变量名类型值描述
TYPE_DEFAULTuint8_t0无效 FrameworkType 类型
TYPE_SNPEuint8_t1SNPE1.x(dlc) 模型
TYPE_TFLITEuint8_t2TFLite 模型
TYPE_RKNNuint8_t3RKNN 模型
TYPE_QNNuint8_t4QNN 模型
TYPE_SNPE2uint8_t5SNPE2.x(dlc) 模型
TYPE_NCNNuint8_t6NCNN 模型
TYPE_MNNuint8_t7MNN 模型
TYPE_TNNuint8_t8TNN 模型
TYPE_PADDLEuint8_t9Paddle 模型
TYPE_MSuint8_t10MindSpore 模型
TYPE_ONNXuint8_t11onnx 模型
TYPE_QNN216uint8_t101QNN2.16 模型
TYPE_SNPE216uint8_t102SNPE2.16 模型
TYPE_QNN223uint8_t103QNN2.23 模型
TYPE_SNPE223uint8_t104SNPE2.23 模型
TYPE_QNN229uint8_t105QNN2.29 模型
TYPE_SNPE229uint8_t106SNPE2.29 模型
TYPE_QNN231uint8_t107QNN2.31 模型
TYPE_QNN236uint8_t108QNN2.36 模型
TYPE_QNN240uint8_t109QNN2.40 模型

💡注意

特别说明:

  • 如果用户环境同时安装有 AidLite-QNN216/AidLite-QNN223/AidLite-QNN231 等后端实现软件包中的多个,而代码中 FrameworkType 设置为 TYPE_QNN 的话(即没有明确指定使用那个特定版本的 QNN 后端),那么实际运行的 QNN 后端应该是 QNN 版本最高的后端(即优先选择 AidLite-QNN231 来完成推理)。SNPE2的处理同理如是。
  • 同一个程序中不能同时使用不同版本的 QNN 后端软件包,就是说如果程序中先指定了 TYPE_QNN216 来完成推理,那么就不能再使用 TYPE_QNN223/TYPE_QNN231 来做模型推理。SNPE2的处理同理如是。

加速硬件类型.enum AccelerateType ​

对于每个深度学习推理框架而言,可能会支持运行在不同的加速设备上(如 SNPE/QNN 模型运行在 DSP 设备,RKNN 模型运行在 NPU 设备),所以在使用过程中,需要设置当前模型期望运行在哪个设备,就需要使用此加速硬件枚举。

成员变量名类型值描述
TYPE_DEFAULTuint8_t0无效 AccelerateType 类型
TYPE_CPUuint8_t1CPU 加速运行
TYPE_GPUuint8_t2GPU 加速运行
TYPE_DSPuint8_t3DSP 加速运行
TYPE_NPUuint8_t4NPU 加速运行

日志级别.enum LogLevel ​

AidliteSDK 提供有设置日志相关信息的接口(后续会介绍),如果需要指定 AidliteSDK 当前使用哪个日志级别,就需要使用此日志级别枚举。

成员变量名类型值描述
INFOuint8_t0消息
WARNINGuint8_t1警告
ERRORuint8_t2错误
FATALuint8_t3致命错误

设备信息类.class DeviceInfo ​

从 2.3.1 开始,Aidlite 通过 DeviceInfo 描述底层可用推理设备的信息,目前主要用于 QNN DSP 场景下的设备查询与自定义设备配置。

成员变量列表 ​

成员变量名类型默认值描述
versionuint32_t0X00020301DeviceInfo 结构版本号
iduint32_t0设备 ID
typeuint32_t0设备类型编号,由后端解释
cores_numuint32_t1设备包含的核心数量
cores_idstd::vector<uint32_t>空设备核心 ID 列表

输入输出信息类.class TensorInfo ​

每个模型都会有各自的输入输出,对于开发者不那么熟悉的模型而言,开发过程中可能需要查询该模型的输入输出 Tensor 信息,以方便开发者做模型推理的前后处理操作。Aidlite 为开发者提供了查询模型输入输出详细信息的接口,以及用于存储输入输出详细信息的 TensorInfo 数据类型。

成员变量列表 ​

TensorInfo 对象用于存储模型输入输出 Tensor 的详细信息,其中就包含如下的这些参数:

成员 version
类型 uint32_t
默认 0X00020205
描述 此成员用于标识 TensorInfo 版本,如果以后需要扩展更多信息时,会更新版本号
成员 index
类型 uint32_t
默认 无默认值
描述 Tensor 的索引值
成员 name
类型 std::string
默认 无默认值
描述 Tensor 的名称字符串
成员 element_count
类型 uint64_t
默认 0
描述 Tensor 中数据元素的数量(非字节数量)
成员 element_type
类型 enum DataType
默认 DataType::TYPE_DEFAULT
描述 Tensor 中数据元素的类型
成员 dimensions
类型 uint32_t
默认 0
描述 Tensor 中数据元素的维度值
成员 shape
类型 std::vector< uint64_t >
默认 无默认值
描述 Tensor 中数据元素的 shape 描述

模型类.class Model ​

在创建推理解释器之前,需要设置具体模型的相关详细参数。Model 类主要用于记录模型的文件信息、结构信息、运行过程中模型相关内容。

创建空实例对象.create_instance() ​

当前头文件还提供了一个无参重载,用于先创建一个空的 Model 实例,再由调用方在后续流程中补充模型路径或属性信息。

API create_instance
描述 构造一个不携带模型文件路径的 Model 实例对象
参数 void
返回 如果为 nullptr 值,说明函数构建对象失败;否则就是 Model 对象的指针

创建实例对象.create_instance() ​

想要设置模型相关的详细信息,当然就需要先有模型实例对象,此函数用于创建 Model 实例对象。如果模型框架(如 snpe、tflite 等)只需一个模型文件,则调用此接口。

API create_instance
描述 通过传递模型文件的路径名称,构造 Model 类型的实例对象
参数 model_path:模型文件的路径名称
返回 如果为 nullptr 值,说明函数构建对象失败;否则就是 Model 对象的指针
cpp
// 使用当前路径下 inceptionv3_float32.dlc 文件创建模型对象,返回值为空则报错
Model* model = Model::create_instance("./inceptionv3_float32.dlc");
if(model == nullptr){
    printf("Create model failed.\n");
    return EXIT_FAILURE;
}

创建实例对象.create_instance() ​

想要设置模型相关的详细信息,当然就需要先有模型实例对象,此函数用于创建 Model 实例对象。如果模型框架(如 ncnn、tnn 等)涉及两个模型文件,则调用此接口。

API create_instance
描述 通过传递模型文件的路径名称,构造 Model 类型的实例对象
参数 model_struct_path:模型结构文件的路径名称
model_weight_path:模型权重参数文件的路径名称
返回 如果为 nullptr 值,说明函数构建对象失败;否则就是 Model 对象的指针
cpp
// 使用当前路径下 ncnn 的两个模型文件创建模型对象,返回值为空则报错
Model* model = Model::create_instance( "./mobilenet_ssd_voc_ncnn.param", 
     "./mobilenet_ssd_voc_ncnn.bin");
if(model == nullptr){
    printf("Create model failed.\n");
    return EXIT_FAILURE;
}

设置模型属性.set_model_properties() ​

模型实例对象创建成功之后,需要设置模型的输入输出数据类型和输入输出 tensor 数据的 shape 信息。

API set_model_properties
描述 设置模型的属性,设置模型的输入/输出数据 shape 和数据类型
参数 input_shapes:输入 tensor 的 shape 数组,二维数组结构
input_data_type:输入 tensor 的数据类型,DataType 枚举类型
output_shapes:输出 tensor 的 shape 数组,二维数组结构
output_data_type:输出 tensor 的数据类型,DataType 枚举类型
返回 0 表示设置成功,非 0 表示失败
注意 REMOTE 普通数据通路需要声明输入输出信息。SNPE/QNN 普通通路使用 DataType::TYPE_FLOAT32。其他后端使用模型声明的数据类型。原生 QNN 数据要求真实张量信息,详见 REMOTE 兼容性与恢复。
cpp
// 数据类型使用前述 DataType 枚举,输入输出 shape 是二维数组
std::vector<std::vector<uint32_t>> input_shapes = {{1,299,299,3}};
std::vector<std::vector<uint32_t>> output_shapes = {{1,1001}};
model->set_model_properties(input_shapes, DataType::TYPE_FLOAT32, 
    output_shapes, DataType::TYPE_FLOAT32);

获取模型路径.get_model_absolute_path() ​

如果模型框架(如 SNPE、TFLite 等)只涉及一个模型文件,在构建 Model 对象没有异常的情况下,会将传入的模型文件参数转换为绝对路径形式的文件路径。

API get_model_absolute_path
描述 用于获取模型文件的绝对路径
参数 void
返回 Model 对象所对应的模型文件路径字符串

获取模型路径.get_model_struct_absolute_path() ​

如果模型框架(如 NCNN、TNN 等)涉及两个模型文件,在构建 Model 对象没有异常的情况下,会将传入的模型结构文件参数转换为绝对路径形式的文件路径。

API get_model_struct_absolute_path
描述 用于获取模型结构文件的绝对路径
参数 void
返回 Model 对象所对应的模型结构文件路径字符串

获取模型路径.get_model_weight_absolute_path() ​

如果模型框架(如 NCNN、TNN 等)涉及两个模型文件,在构建 Model 对象没有异常的情况下,会将传入的模型权重参数文件参数转换为绝对路径形式的文件路径。

API get_model_weight_absolute_path
描述 用于获取模型权重参数文件的绝对路径
参数 void
返回 Model 对象所对应的模型权重参数文件路径字符串

配置类.class Config ​

在创建推理解释器之前,除了需要设置 Model 具体信息之外,还需要设置一些推理时的配置信息。Config 类用于记录需要预先设置的配置选项,这些配置项在运行时会被用到。

创建实例对象.create_instance() ​

想要设置运行时的配置信息,当然就需要先有配置实例对象,此函数用于创建 Config 实例

API create_instance
描述 用于构造 Config 类的实例对象
参数 void
返回 如果为 nullptr 值,说明函数构建对象失败;否则就是 Config 对象的指针
cpp
// 创建配置实例对象,返回值为空则报错
Config* config = Config::create_instance();
if(config == nullptr){
    printf("Create config failed.\n");
    return EXIT_FAILURE;
}

成员变量列表 ​

Config 对象用于设置运行时的配置信息,其中就包含如下的这些参数:

成员 accelerate_type
类型 enum AccelerateType
默认 AccelerateType.TYPE_CPU
描述 加速硬件的类型
成员 implement_type
类型 enum ImplementType
默认 ImplementType.TYPE_DEFAULT
描述 用于区分后端实现推理功能的不同环境
注意 开发者可以不用显式设置此配置项,
AidliteSDK 会根据当前的系统环境、模型框架等因素,适配得出更优的 ImplementType 类型
成员 framework_type
类型 enum FrameworkType
默认 FrameworkType.TYPE_DEFAULT
描述 底层推理框架的类型
成员 number_of_threads
类型 int8_t
默认 -1
描述 线程数,大于 0 有效
注意 当且仅当 framework_type 为 TFLITE 时,可以选择性设置此配置项
成员 snpe_out_names
类型 字符串数组
默认 空
描述 模型输出节点的名称列表
注意 当且仅当 framework_type 为 SNPE 时,需要设置此配置项
成员 is_quantify_model
类型 int32_t
默认 0
描述 是否为量化模型,1 表示量化模型
注意 当且仅当 implement_type 为 REMOTE 时,需要设置此配置项
成员 remote_timeout
类型 int32_t
默认 -1
描述 接口超时时间(毫秒,大于 0 有效)
注意 当且仅当 implement_type 为 REMOTE 时,可以选择性设置此配置项
成员 qnn_shared_buffer
类型 int32_t
默认 0
描述 是否启用 QNN shared buffer,目前主要用于 QNN DSP 场景
成员 batch_invoke_size
类型 uint32_t
默认 1
描述 后端 batch 推理配置参数,目前主要用于 QNN DSP 场景
成员 backend_extension_config
类型 std::string
默认 空字符串
描述 后端推理框架扩展配置文件,目前主要用于 QNN DSP 场景
成员 custom_device_info
类型 std::vector<DeviceInfo>
默认 空
描述 自定义设备信息列表,用于指定后端 QNN DSP 推理时实际使用的设备集合
成员 reserved1
类型 int32_t
默认 0
描述 预留字段,当前无业务语义,调用方无需主动设置

💡注意

Config 中 remote_timeout 与历史字段 fast_timeout 共用同一块联合体内存;其中 fast_timeout 在头文件中已标记为废弃,仅保留兼容用途,新的代码应统一使用 remote_timeout。

cpp
config->framework_type = FrameworkType::TYPE_QNN;
config->accelerate_type = AccelerateType::TYPE_DSP;
// 按需设置其他配置项
// ... ...

上下文类.class Context ​

用于存储执行过程中所涉及的运行时上下文,就包括 Model 对象和 Config 对象,后续可能会扩展运行时的上下文数据。

构造函数.Context() ​

API Context
描述 构造 Context 实例对象
参数 model:Model 实例对象指针
config:Config 实例对象指针
返回 正常:Context 实例对象

💡注意

特别说明,参数 model 和 config 两个指针指向的内存空间,必须是堆空间上开辟的。构造 Context 成功之后,它们生命周期将会被 Context 接管( Context 释放时,会自动释放 model 和 config 对象),故而开发者无需再手动释放两者的空间,否则会报错重复释放对象。

获取Model成员变量.get_model() ​

API get_model
描述 获取 context 管理的 Model 对象指针
参数 void
返回 Model 对象的指针

获取Config成员变量.get_config() ​

API get_config
描述 获取 context 管理的 Config 对象指针
参数 void
返回 Config 对象的指针

解释器类.class Interpreter ​

Interpreter 类型的对象实例是执行推理操作的主体,用于执行具体的推理过程。创建解释器对象之后,所有的操作都是基于解释器对象来完成的,所以它是 AidliteSDK 绝对的核心内容。

创建实例对象.create_instance() ​

想要执行推理相关的操作,推理解释器肯定必不可少,此函数就用于构建推理解释器的实例对象。

API create_instance
描述 利用 Context 对象所管理的各种数据,构造 Interpreter 类型的对象
参数 context:Context 实例对象的指针
返回 为 nullptr 值则说明函数构建对象失败;否则就是 Interpreter 对象的指针
cpp
 // 使用 Context 对象指针来创建解释器对象,返回值为空则报错
Interpreter* aidlite_interpreter = Interpreter::create_instance(context);
if(aidlite_interpreter == nullptr){
    printf("Create Interpreter failed.\n");
    return EXIT_FAILURE;
}

初始化.init() ​

解释器对象创建之后,需要执行一些初始化操作(比如环境检查、资源构建等)。

API init
描述 完成推理所需的相关的初始化工作
参数 void
返回 0 值则说明初始化操作执行成功,否则非 0 值就说明执行失败
cpp
// 解释器初始化,返回值非0则报错
int result = aidlite_interpreter->init();
if(result != EXIT_SUCCESS){
    printf("sample : interpreter->init() failed.\n");
    return EXIT_FAILURE;
}

获取设备信息.get_device_info() ​

当前头文件新增了 get_device_info(std::vector<DeviceInfo>& device_info),用于查询后端实际可见的设备信息。该接口从 2.3.1 起支持,目前主要用于 QNN DSP 场景。

API get_device_info
描述 获取后端设备详情列表
参数 device_info:存储设备详情的数组
返回 0 值则说明获取成功,否则非 0 值就说明执行失败

加载模型.load_model() ​

解释器对象完成初始化操作之后,就可以为解释器加载所需的模型文件,完成模型加载工作。后续的推理过程就使用加载的模型资源。

API load_model
描述 完成模型加载相关的工作。
因为前述在 Model 对象中已经设置模型文件的路径,所以直接执行模型加载操作即可
参数 void
返回 0 值则说明模型加载操作执行成功,否则非 0 值就说明执行失败
cpp
// 解释器加载模型,返回值非0则报错
int result = aidlite_interpreter->load_model();
if(result != EXIT_SUCCESS){
    printf("sample : interpreter->load_model() failed.\n");
    return EXIT_FAILURE;
}

获取输入Tensor详情.get_input_tensor_info() ​

如果想要获取模型的输入 Tensor 的详细信息,可以使用此接口获取。

API get_input_tensor_info
描述 获取模型中输入 Tensor 的详细信息
参数 in_tensor_info:存储输入 tensor 详情的数组
返回 0 值则说明获取 Tensor 详情成功,否则非 0 值就说明执行失败
说明 此接口从 AidliteSDK 版本号 2.2.5 开始支持
cpp
// 获取模型的输入详情数据,返回值非0则报错
std::vector<std::vector<TensorInfo>> in_tensor_info;
result = aidlite_interpreter->get_input_tensor_info(in_tensor_info);
if(result != EXIT_SUCCESS){
    printf("interpreter->get_input_tensor_info() failed.\n");
    return EXIT_FAILURE;
}
for(int gi = 0; gi < in_tensor_info.size(); ++gi){
    for(int ti = 0; ti < in_tensor_info[gi].size(); ++ti){

        std::string tensor_shape_str = std::to_string(in_tensor_info[gi][ti].shape[0]);
        for(int si = 1; si < in_tensor_info[gi][ti].dimensions; ++si){
            tensor_shape_str += " ";
            tensor_shape_str += std::to_string(in_tensor_info[gi][ti].shape[si]);
        }

        printf("Input  tensor : Graph[%d]-Tensor[%d]-name[%s]-element_count[%ld]-element_type[%d]-dimensions[%d]-shape[%s]\n",
                gi, ti, in_tensor_info[gi][ti].name.c_str(), in_tensor_info[gi][ti].element_count, 
                (int)in_tensor_info[gi][ti].element_type, in_tensor_info[gi][ti].dimensions, tensor_shape_str.c_str());
    }
}

获取输出Tensor详情.get_output_tensor_info() ​

如果想要获取模型的输出 Tensor 的详细信息,可以使用此接口获取。

API get_output_tensor_info
描述 获取模型中输出 Tensor 的详细信息
参数 out_tensor_info:存储输出 tensor 详情的数组
返回 0 值则说明获取 Tensor 详情成功,否则非 0 值就说明执行失败
说明 此接口从 AidliteSDK 版本号 2.2.5 开始支持
cpp
// 获取模型的输出详情数据,返回值非0则报错
std::vector<std::vector<TensorInfo>> out_tensor_info;
result = aidlite_interpreter->get_output_tensor_info(out_tensor_info);
if(result != EXIT_SUCCESS){
    printf("interpreter->get_output_tensor_info() failed.\n");
    return EXIT_FAILURE;
}
for(int gi = 0; gi < out_tensor_info.size(); ++gi){
    for(int ti = 0; ti < out_tensor_info[gi].size(); ++ti){

        std::string tensor_shape_str = std::to_string(out_tensor_info[gi][ti].shape[0]);
        for(int si = 1; si < out_tensor_info[gi][ti].dimensions; ++si){
            tensor_shape_str += " ";
            tensor_shape_str += std::to_string(out_tensor_info[gi][ti].shape[si]);
        }

        printf("Output tensor : Graph[%d]-Tensor[%d]-name[%s]-element_count[%ld]-element_type[%d]-dimensions[%d]-shape[%s]\n",
                gi, ti, out_tensor_info[gi][ti].name.c_str(), out_tensor_info[gi][ti].element_count, 
                (int)out_tensor_info[gi][ti].element_type, out_tensor_info[gi][ti].dimensions, tensor_shape_str.c_str());
    }
}

设置输入数据.set_input_tensor() ​

执行推理操作之前,需要传入模型所需的输入 Tensor 数据 。设置输入数据之前,对于不同的模型,需要完成不同的前处理操作以适配具体模型。

API set_input_tensor
描述 设置推理所需的输入 tensor 的数据
参数 in_tensor_idx/in_tensor_name:输入 tensor 的索引值/名称字符串
input_data:输入 tensor 的数据存储地址指针
可选参数 is_native:是否将传入数据视为原生后端数据格式;为 `false` 时按 SDK 默认逻辑做量化/反量化实现,为 `true` 时直接按原始的模型数据格式传入
返回 0 值则说明设置输入 Tensor 成功,否则非 0 值就说明执行失败
说明 1. 参数 `in_tensor_name` 从版本号 2.2.5 开始支持
2. 当前头文件同时提供带 `is_native` 参数和不带该参数的两组重载
3. 对于 SNPE/QNN 两个推理框架来说,输入的数据通常按 float32 处理;若调用原生格式接口,则需要用户自行保证数据格式与后端要求一致 。
cpp
// 设置推理的输入数据,返回值非0则报错
// int result = aidlite_interpreter->set_input_tensor(0, input_tensor_data);
int result = aidlite_interpreter->set_input_tensor("in_tensor_name", input_tensor_data);
if(result != EXIT_SUCCESS){
    printf("interpreter->set_input_tensor() failed.\n");
    return EXIT_FAILURE;
}

执行推理.invoke() ​

设置输入数据之后,接下来的步骤自然就是对输入数据执行推理运行过程。

API invoke
描述 执行推理运算过程
参数 void
返回 0 值则说明推理过程执行成功,否则非 0 值就说明执行失败
cpp
// 执行推理运行操作,返回值非0则报错
int result = aidlite_interpreter->invoke();
if(result != EXIT_SUCCESS){
    printf("sample : interpreter->invoke() failed.\n");
    return EXIT_FAILURE;
}

获取输出数据.get_output_tensor() ​

推理完成之后,就需要将推理得到的结果数据取出来。取出结果数据之后,就可以对这些结果数据进行处理,判断结果是否正确。

API get_output_tensor
描述 推理成功之后,获取推理结果数据
参数 out_tensor_idx/out_tensor_name:输出 tensor 的索引值/名称字符串
output_data:该值是指针变量的地址,函数内部重置该指针变量的值
output_length:该结果输出 tensor 的数据量(字节数)
可选参数 is_native:是否直接返回后端原生格式输出;为 `false` 时按 SDK 默认逻辑做量化/反量化实现,为 `true` 时直接按原始的模型数据格式返回数据缓冲区视图
返回 0 值则说明获取输出 Tensor 执行成功,否则非 0 值就说明执行失败
说明 1. 参数 `out_tensor_name` 从版本号 2.2.5 开始支持
2. `output_length` 表示输出 Tensor 数据的字节数量
3. 当前头文件同时提供带 `is_native` 参数和不带该参数的两组重载
4. 对于 SNPE/QNN,默认接口通常返回 float32 语义的数据;如果使用原生格式接口,则需要按后端真实输出格式自行解析。
cpp
// 获取推理结果数据,返回值非0则报错
float* out_data = nullptr;
uint32_t output_tensor_length = 0;
// int result = aidlite_interpreter->get_output_tensor(0, (void**)&out_data, &output_tensor_length);
int result = aidlite_interpreter->get_output_tensor("out_tensor_name", (void**)&out_data, &output_tensor_length);
if(result != EXIT_SUCCESS){
    printf("interpreter->get_output_tensor() failed.\n");
    return EXIT_FAILURE;
}

资源释放.destroy() ​

不再使用模型时,调用 destroy()。检查返回值。

如果 destroy() 失败,停止在此解释器上执行推理。保留解释器对象。对于 LOCAL,处理后端报告的问题后,再调用 destroy() 重试清理。清理开始后,普通模型操作会报错。此规则也适用于初始化失败后仍有待清理资源的情况。

如果 REMOTE 服务报告原生资源清理失败,停止向该上下文发送请求。更新后的服务会保留该模型槽位,因为原生资源可能仍在使用。停止其他模型任务,检查后端错误。重启后端服务,再创建新的解释器。对已关闭的远程上下文重试 destroy(),不能收回服务端保留的槽位。模型 worker 启动前清理失败时,也遵循此规则。

不要把清理失败当作清理成功。后端持续拒绝释放资源时,析构过程不能保证收回全部资源。原有低层接口在初始化前或成功释放后再次调用 destroy(),仍返回失败。

API destroy
描述 完成必要的释放操作
参数 void
返回 0 值则说明执行释放操作成功,否则非 0 值就说明执行失败
cpp
// 执行解释器释放过程,返回值非0则报错
int result = aidlite_interpreter->destroy();
if(result != EXIT_SUCCESS){
    printf("sample : interpreter-> destroy() failed.\n");
    return EXIT_FAILURE;
}

解释器创建类.class InterpreterBuilder ​

统一的解释器 Interpreter 对象的创建函数,通过此类来创建所需的解释器对象。

💡注意

头文件中还保留了一组历史拼写接口 build_interpretper_*,它们均已标记为废弃,仅用于兼容旧代码。文档以下只描述当前推荐使用的 build_interpreter_* 系列接口。

构建解释器.build_interpreter_from_path() ​

构建推理解释器对象,可以提供不同的参数,最简单的就是只提供模型文件的路径和名称。如果模型框架(如 SNPE、TFLite 等)只需一个模型文件,则调用此接口。

API build_interpreter_from_path
描述 通过模型文件的路径名称,直接创建对应的解释器对象
参数 path:模型文件的路径名称
返回 返回值为 Interpreter 类型的 unique_ptr 指针。
如果为 nullptr 值,则说明函数构建对象失败;否则就是 Interpreter 对象的指针

构建解释器.build_interpreter_from_path() ​

构建推理解释器对象,可以提供不同的参数,最简单的就是只提供模型文件的路径和名称。如果模型框架(如 NCNN、TNN 等)涉及两个模型文件,则调用此接口。

API build_interpreter_from_path
描述 通过模型文件的路径名称,直接创建对应的解释器对象
参数 model_struct_path:模型结构文件的路径名称
model_weight_path:模型权重参数文件的路径名称
返回 返回值为 Interpreter 类型的 unique_ptr 指针。
如果为 nullptr 值,则说明函数构建对象失败;否则就是 Interpreter 对象的指针

构建解释器.build_interpreter_from_model() ​

构建推理解释器对象,除了提供模型文件路径之外,也可以提供 Model 对象,这样不仅可以设置模型文件的路径名称,还可以设置模型的输入输出数据类型和输入输出数据的 shape 信息。

API build_interpreter_from_model
描述 通过传入 Model 对象来创建对应的解释器对象。而 Config 相关涉及的参数都使用默认值
参数 model:Model 类型的对象,包含模型相关的数据
返回 返回值为 Interpreter 类型的 unique_ptr 指针。
如果为 nullptr 值,则说明函数构建对象失败;否则就是 Interpreter 对象的指针

💡注意

特别说明,参数 model 指针指向的内存空间,必须是堆空间上开辟的。构造 Interpreter 成功之后,它的生命周期将会被 Interpreter 接管( Interpreter 释放时,会自动释放 model 对象),故而开发者无需再手动释放两者的空间,否则会报错重复释放对象。

构建解释器.build_interpreter_from_model_and_config() ​

构建推理解释器对象,除了前述的方式,也可以同时提供 Model 对象和 Config 对象,这样不仅可以提供模型相关信息,还可以提供更多的运行时配置参数。

API build_interpreter_from_model_and_config
描述 通过传入 Model 对象和 Config 来创建对应的解释器对象
参数 model:Model 类型的对象,包含模型相关的数据
config:Config 类型的对象,包含一些配置参数
返回 返回值为 Interpreter 类型的 unique_ptr 指针。
如果为 nullptr 值,则说明函数构建对象失败;否则就是 Interpreter 对象的指针

💡注意

特别说明,参数 model 和 config 两个指针指向的内存空间,必须是堆空间上开辟的。构造 Interpreter 成功之后,它们生命周期将会被 Interpreter 接管( Interpreter 释放时,会自动释放 model 和 config 对象),故而开发者无需再手动释放两者的空间,否则会报错重复释放对象。

cpp
// 传入Model和Config对象来构建解释器,返回值为空则报错
std::unique_ptr<Interpreter>&& aidlite_interpreter = 
 InterpreterBuilder::build_interpreter_from_model_and_config(model, config);
if(aidlite_interpreter == nullptr){
    printf("build_interpreter failed.\n");
    return EXIT_FAILURE;
}

其他方法 ​

AidliteSDK 除了提供前述的那些推理相关的接口,也提供如下一些辅助接口。

获取SDK版本信息.get_library_version() ​

API get_library_version
描述 用于获取当前 AidliteSDK 的版本相关信息。
参数 void
返回 返回值为当前 AidliteSDK 的版本信息字符串

设置日志级别.set_log_level() ​

API set_log_level
描述 设置当前的最低日志级别,输出大于等于该日志级别的日志数据。
默认打印输出 WARNING 及以上级别的日志
参数 log_level:枚举类型 LogLevel 的取值
返回 默认返回 0 值

日志输出到标准终端.log_to_stderr() ​

API log_to_stderr
描述 设置日志信息输出到标准错误终端
参数 void
返回 默认返回 0 值

日志输出到文本文件.log_to_file() ​

API log_to_file
描述 设置日志信息输出到指定的文本文件
参数 path_and_prefix:日志文件的存放路径与名称前缀
also_to_stderr:标识是否同时输出日志到 stderr 终端,默认值为 false
返回 0 值则说明执行成功,否则非 0 值就说明执行失败

获取最近日志信息.last_log_msg() ​

API last_log_msg
描述 获取当前某个日志级别的最新日志信息,通常获取最新的错误信息
参数 log_level:枚举类型 LogLevel 的取值
返回 最新日志信息的存储地址指针

AidLite C++ 示例程序 ​

💡注意

使用 Aidlite-SDK 开发需要了解如下事项:

Aidlite 的功能实现分为两个部分,软件包 Aidlite-SDK 是为开发者提供接口的,而不同框架的推理功能则是由不同的后端软件包(如Aidlite-QNN236等)提供。

示例程序由后端推理软件包提供,示例程序随软件包安装,如 Aidlite-QNN236 的示例程序路径如下:

  • cpp 示例程序路径:/usr/local/share/aidlite/examples/aidlite_qnn236/cpp/
  • Python 示例程序路径:/usr/local/share/aidlite/examples/aidlite_qnn236/python/

以后端程序 Aidlite-QNN236 为例,其 CPP 示例程序大致包含如下几个部分:

初始化配置与模型加载 ​

如果开发者对模型推理有所了解,或者是看过 Aidlite API 接口文档,那么必然会知道,在真正实现推理操作之后,通常都需要设置一些关键信息,然后再为推理框架加载所需的模型文件。

cpp
// 此处可以使用不同数据精度的模型文件
if(acc_type == 1){
    model_path = "../../data/qnn_yolov5_multi/libcutoff_yolov5s_640_sigmoid_fp32.qnn236.aarch64.gcc9_4.so";
}else if(acc_type == 2){
    model_path = "../../data/qnn_yolov5_multi/libcutoff_yolov5s_640_sigmoid_fp32.qnn236.aarch64.gcc9_4.so";
}else if(acc_type == 3){
    model_path = "../../data/qnn_yolov5_multi/cutoff_yolov5s_640_sigmoid_w8a8.qnn236.ctx.bin";
}else if(acc_type == 4){
    model_path = "../../data/qnn_yolov5_multi/libcutoff_yolov5s_640_sigmoid_w8a8.qnn236.aarch64.gcc9_4.so";
}else{
    show_help_prompt();
    return EXIT_FAILURE;
}

// 创建 Model 对象实例,可以设置模型相关的信息
Model* model = Model::create_instance(model_path);
if(model == nullptr){
    printf("Create Model object failed.\n");
    return EXIT_FAILURE;
}

// 创建 Model 对象实例
Config* config = Config::create_instance();
if(config == nullptr){
    printf("Create Config object failed.\n");
    return EXIT_FAILURE;
}

// 配置一些运行时所需要的参数
config->framework_type = FrameworkType::TYPE_QNN236;
if(acc_type == 1){
    config->accelerate_type = AccelerateType::TYPE_CPU;
}else if(acc_type == 2){
    config->accelerate_type = AccelerateType::TYPE_GPU;
}else if(acc_type == 3){
    config->accelerate_type = AccelerateType::TYPE_DSP;
}else if(acc_type == 4){
    config->accelerate_type = AccelerateType::TYPE_DSP;
}else{
    show_help_prompt();
    return EXIT_FAILURE;
}

// 创建推理所需的解释器对象
std::unique_ptr<Interpreter>&& qnn_interpreter = InterpreterBuilder::build_interpreter_from_model_and_config(model, config);
if(qnn_interpreter == nullptr){
    printf("build_interpreter_from_model_and_config failed.\n");
    return EXIT_FAILURE;
}

// 完成资源初始化的操作
int result = qnn_interpreter->init();
if(result != EXIT_SUCCESS){
    printf("interpreter->init() failed.\n");
    return EXIT_FAILURE;
}

// 完成模型的加载操作
result = qnn_interpreter->load_model();
if(result != EXIT_SUCCESS){
    printf("interpreter->load_model() failed.\n");
    return EXIT_FAILURE;
}

在上述示例代码中,开发者可以提供 不同精度的模型文件(包括非量化的 float32 模型、量化的 int8 模型等) 来创建 Model 对象。相应的,开发者也需要为此指定不同的加速硬件,也就是需要设置 Config 对象的 accelerate_type 成员变量的值。利用 Model 对象和 Config 对象创建推理所需的解释器,然后即可完成所需的初始化、模型加载的操作。

输入数据的前处理 ​

一般来讲,模型推理所需的输入数据需要满足一定的要求,所以就需要对已有的输入数据做一些处理,使其可以被模型利用。

cpp
// 使用 Opencv 读取图片数据
cv::Mat frame = cv::imread(image_path);

// 输入图像的前处理操作
// ... ...

// 取得 Opencv Mat 对象所含数据的指针,并将其转为解释器 API 所需要的指针类型。
void* input_tensor_data = (void*)input_data.data;

前处理部分的内容究竟怎么实现,完全取决于开发者使用的模型需要什么样的输入数据。 示例程序中的前处理操作,就是针对我们示例程序提供的模型文件所适配的。

完成推理操作 ​

前处理操作完成之后,就可以将得到的数据传入给解释器,解释器就可以利用这些数据来完成推理任务,而在推理完成之后,开发者需要取出推理操作的结果数据。

cpp
// 将前处理完成后的数据传递给解释器对象
// result = qnn_interpreter->set_input_tensor(0,input_tensor_data);
result = qnn_interpreter->set_input_tensor("images",input_tensor_data);
if(result != EXIT_SUCCESS){
    printf("interpreter->set_input_tensor() failed.\n");
    return EXIT_FAILURE;
}

// 执行推理过程
result = qnn_interpreter->invoke();
if(result != EXIT_SUCCESS){
    printf("interpreter->invoke() failed.\n");
    return EXIT_FAILURE;
}

// 模型有三个输出 Tensor ,所以需要获取三个 Tensor 的输出数据。
uint32_t output_tensor_length_0 = 0;
// result = qnn_interpreter->get_output_tensor(0, (void**)&stride8, &output_tensor_length_0);
result = qnn_interpreter->get_output_tensor("_326", (void**)&stride8, &output_tensor_length_0);
if(result != EXIT_SUCCESS){
    printf("interpreter->get_output_tensor() 0 failed.\n");
    return EXIT_FAILURE;
}
printf("sample : interpreter->get_output_tensor() 0 length is [%d].\n", output_tensor_length_0);

uint32_t output_tensor_length_1 = 0;
// result = qnn_interpreter->get_output_tensor(1, (void**)&stride16, &output_tensor_length_1);
result = qnn_interpreter->get_output_tensor("_364", (void**)&stride16, &output_tensor_length_1);
if(result != EXIT_SUCCESS){
    printf("interpreter->get_output_tensor() 1 failed.\n");
    return EXIT_FAILURE;
}
printf("sample : interpreter->get_output_tensor() 1 length is [%d].\n", output_tensor_length_1);

uint32_t output_tensor_length_2 = 0;
// result = qnn_interpreter->get_output_tensor(2, (void**)&stride32, &output_tensor_length_2);
result = qnn_interpreter->get_output_tensor("_402", (void**)&stride32, &output_tensor_length_2);
if(result != EXIT_SUCCESS){
    printf("interpreter->get_output_tensor() 2 failed !\n");
    return EXIT_FAILURE;
}
printf("sample : interpreter->get_output_tensor() 2 length is [%d].\n", output_tensor_length_2);

前处理完成之后,就可以将输入数据给到解释器,由解释器完成推理任务,进而返回结果数据。

💡注意

需要说明的是,对于 SNPE/QNN 两个推理框架来说,输入输出的数据只能是 float32 数据类型。

不管模型本身的输入 Tensor 是什么精度的数据类型,使用 set_input_tensor() 接口时传入的数据必须在前处理阶段处理成 float32 类型的数据;同理,也不管模型本身的输出 Tensor 是什么精度的数据类型,使用 get_output_tensor() 接口时获取到的数据,也都是按 float32 数据类型存储的结果。

如果是其他的推理框架,那么前处理与后处理的数据类型,则与模型文件的输入输出 Tensor 的数据类型保持一致。

输出数据的后处理 ​

一般来讲,模型推理结束之后,输出的结果数据不会是我们最终想要的数据,还需要做进一步的加工才是开发者所需要的,这就是后处理操作。

cpp
// 输出数据的后处理操作
// ... ...

与前处理操作同理,后处理部分的内容究竟怎么实现,完全取决于开发者需要对推理结果数据做什么样的分析处理。 示例程序中的后处理操作,就是针对我们示例程序提供的模型文件所适配的。

资源释放 ​

当开发者不再需要 Aidlite 解释器来完成推理任务时,可以显示地完成资源释放操作。

cpp
result = qnn_interpreter->destroy();
if(result != EXIT_SUCCESS){
    printf("interpreter->destroy() failed.\n");
    return EXIT_FAILURE;
}

至此,已经展示了使用 Aidlite 做模型推理的全部流程,开发者只需要根据自己的模型做对应的修改,即可完成推理任务。