1# Neural Network Runtime对接AI推理框架开发指导 2 3## 场景介绍 4 5Neural Network Runtime作为AI推理引擎和加速芯片的桥梁,为AI推理引擎提供精简的Native接口,满足推理引擎通过加速芯片执行端到端推理的需求。 6 7本文以图1展示的`Add`单算子模型为例,介绍Neural Network Runtime的开发流程。`Add`算子包含两个输入、一个参数和一个输出,其中的`activation`参数用于指定`Add`算子中激活函数的类型。 8 9**图1** Add单算子网络示意图 10 11 12## 环境准备 13 14### 环境要求 15 16Neural Network Runtime部件的环境要求如下: 17 18- 开发环境:Ubuntu 18.04及以上。 19- 接入设备:系统定义的标准设备,系统中内置AI硬件驱动并已接入Neural Network Runtime。 20 21由于Neural Network Runtime通过Native API对外开放,需要通过Native开发套件编译Neural Network Runtime应用。在社区的每日构建中下载对应系统版本的ohos-sdk压缩包,从压缩包中提取对应平台的Native开发套件。以Linux为例,Native开发套件的压缩包命名为`native-linux-{版本号}.zip`。 22 23### 环境搭建 24 251. 打开Ubuntu编译服务器的终端。 262. 把下载好的Native开发套件压缩包拷贝至当前用户根目录下。 273. 执行以下命令解压Native开发套件的压缩包。 28 ```shell 29 unzip native-linux-{版本号}.zip 30 ``` 31 32 解压缩后的内容如下(随版本迭代,目录下的内容可能发生变化,请以最新版本的Native API为准): 33 ```text 34 native/ 35 ├── build // 交叉编译工具链 36 ├── build-tools // 编译构建工具 37 ├── docs 38 ├── llvm 39 ├── nativeapi_syscap_config.json 40 ├── ndk_system_capability.json 41 ├── NOTICE.txt 42 ├── oh-uni-package.json 43 └── sysroot // Native API头文件和库 44 ``` 45## 接口说明 46 47这里给出Neural Network Runtime开发流程中通用的接口,具体请见下列表格。 48 49### 结构体 50 51| 结构体名称 | 描述 | 52| --------- | ---- | 53| typedef struct OH_NNModel OH_NNModel | Neural Network Runtime的模型句柄,用于构造模型。 | 54| typedef struct OH_NNCompilation OH_NNCompilation | Neural Network Runtime的编译器句柄,用于编译AI模型。 | 55| typedef struct OH_NNExecutor OH_NNExecutor | Neural Network Runtime的执行器句柄,用于在指定设备上执行推理计算。 | 56| typedef struct NN_QuantParam NN_QuantParam | Neural Network Runtime的量化参数句柄,用于在构造模型时指定张量的量化参数。 | 57| typedef struct NN_TensorDesc NN_TensorDesc | Neural Network Runtime的张量描述句柄,用于描述张量的各类属性,例如数据布局、数据类型、形状等。 | 58| typedef struct NN_Tensor NN_Tensor | Neural Network Runtime的张量句柄,用于设置执行器的推理输入和输出张量。 | 59 60### 模型构造接口 61 62| 接口名称 | 描述 | 63| ------- | --- | 64| OH_NNModel_Construct() | 创建OH_NNModel类型的模型实例。 | 65| OH_NN_ReturnCode OH_NNModel_AddTensorToModel(OH_NNModel *model, const NN_TensorDesc *tensorDesc) | 向模型实例中添加张量。 | 66| OH_NN_ReturnCode OH_NNModel_SetTensorData(OH_NNModel *model, uint32_t index, const void *dataBuffer, size_t length) | 设置张量的数值。 | 67| OH_NN_ReturnCode OH_NNModel_AddOperation(OH_NNModel *model, OH_NN_OperationType op, const OH_NN_UInt32Array *paramIndices, const OH_NN_UInt32Array *inputIndices, const OH_NN_UInt32Array *outputIndices) | 向模型实例中添加算子。 | 68| OH_NN_ReturnCode OH_NNModel_SpecifyInputsAndOutputs(OH_NNModel *model, const OH_NN_UInt32Array *inputIndices, const OH_NN_UInt32Array *outputIndices) | 指定模型的输入和输出张量的索引值。 | 69| OH_NN_ReturnCode OH_NNModel_Finish(OH_NNModel *model) | 完成模型构图。| 70| void OH_NNModel_Destroy(OH_NNModel **model) | 销毁模型实例。 | 71 72 73### 模型编译接口 74 75| 接口名称 | 描述 | 76| ------- | --- | 77| OH_NNCompilation *OH_NNCompilation_Construct(const OH_NNModel *model) | 基于模型实例创建OH_NNCompilation类型的编译实例。 | 78| OH_NNCompilation *OH_NNCompilation_ConstructWithOfflineModelFile(const char *modelPath) | 基于离线模型文件路径创建OH_NNCompilation类型的编译实例。 | 79| OH_NNCompilation *OH_NNCompilation_ConstructWithOfflineModelBuffer(const void *modelBuffer, size_t modelSize) | 基于离线模型文件内存创建OH_NNCompilation类型的编译实例。 | 80| OH_NNCompilation *OH_NNCompilation_ConstructForCache() | 创建一个空的编译实例,以便稍后从模型缓存中恢复。 | 81| OH_NN_ReturnCode OH_NNCompilation_ExportCacheToBuffer(OH_NNCompilation *compilation, const void *buffer, size_t length, size_t *modelSize) | 将模型缓存写入到指定内存区域。 | 82| OH_NN_ReturnCode OH_NNCompilation_ImportCacheFromBuffer(OH_NNCompilation *compilation, const void *buffer, size_t modelSize) | 从指定内存区域读取模型缓存。 | 83| OH_NN_ReturnCode OH_NNCompilation_AddExtensionConfig(OH_NNCompilation *compilation, const char *configName, const void *configValue, const size_t configValueSize) | 为自定义硬件属性添加扩展配置,具体硬件的扩展属性名称和属性值需要从硬件厂商的文档中获取。 | 84| OH_NN_ReturnCode OH_NNCompilation_SetDevice(OH_NNCompilation *compilation, size_t deviceID) | 指定模型编译和计算的硬件,可通过设备管理接口获取。 | 85| OH_NN_ReturnCode OH_NNCompilation_SetCache(OH_NNCompilation *compilation, const char *cachePath, uint32_t version) | 设置编译模型的缓存目录和版本。 | 86| OH_NN_ReturnCode OH_NNCompilation_SetPerformanceMode(OH_NNCompilation *compilation, OH_NN_PerformanceMode performanceMode) | 设置模型计算的性能模式。 | 87| OH_NN_ReturnCode OH_NNCompilation_SetPriority(OH_NNCompilation *compilation, OH_NN_Priority priority) | 设置模型计算的优先级。 | 88| OH_NN_ReturnCode OH_NNCompilation_EnableFloat16(OH_NNCompilation *compilation, bool enableFloat16) | 是否以float16的浮点数精度计算。 | 89| OH_NN_ReturnCode OH_NNCompilation_Build(OH_NNCompilation *compilation) | 执行模型编译。 | 90| void OH_NNCompilation_Destroy(OH_NNCompilation **compilation) | 销毁编译实例。 | 91 92### 张量描述接口 93 94| 接口名称 | 描述 | 95| ------- | --- | 96| NN_TensorDesc *OH_NNTensorDesc_Create() | 创建一个张量描述实例,用于后续创建张量。 | 97| OH_NN_ReturnCode OH_NNTensorDesc_SetName(NN_TensorDesc *tensorDesc, const char *name) | 设置张量描述的名称。 | 98| OH_NN_ReturnCode OH_NNTensorDesc_GetName(const NN_TensorDesc *tensorDesc, const char **name) | 获取张量描述的名称。 | 99| OH_NN_ReturnCode OH_NNTensorDesc_SetDataType(NN_TensorDesc *tensorDesc, OH_NN_DataType dataType) | 设置张量描述的数据类型。 | 100| OH_NN_ReturnCode OH_NNTensorDesc_GetDataType(const NN_TensorDesc *tensorDesc, OH_NN_DataType *dataType) | 获取张量描述的数据类型。 | 101| OH_NN_ReturnCode OH_NNTensorDesc_SetShape(NN_TensorDesc *tensorDesc, const int32_t *shape, size_t shapeLength) | 设置张量描述的形状。 | 102| OH_NN_ReturnCode OH_NNTensorDesc_GetShape(const NN_TensorDesc *tensorDesc, int32_t **shape, size_t *shapeLength) | 获取张量描述的形状。 | 103| OH_NN_ReturnCode OH_NNTensorDesc_SetFormat(NN_TensorDesc *tensorDesc, OH_NN_Format format) | 设置张量描述的数据布局。 | 104| OH_NN_ReturnCode OH_NNTensorDesc_GetFormat(const NN_TensorDesc *tensorDesc, OH_NN_Format *format) | 获取张量描述的数据布局。 | 105| OH_NN_ReturnCode OH_NNTensorDesc_GetElementCount(const NN_TensorDesc *tensorDesc, size_t *elementCount) | 获取张量描述的元素个数。 | 106| OH_NN_ReturnCode OH_NNTensorDesc_GetByteSize(const NN_TensorDesc *tensorDesc, size_t *byteSize) | 获取基于张量描述的形状和数据类型计算的数据占用字节数。 | 107| OH_NN_ReturnCode OH_NNTensorDesc_Destroy(NN_TensorDesc **tensorDesc) | 销毁张量描述实例。 | 108 109### 张量接口 110 111| 接口名称 | 描述 | 112| ------- | --- | 113| NN_Tensor* OH_NNTensor_Create(size_t deviceID, NN_TensorDesc *tensorDesc) | 从张量描述创建张量实例,会申请设备共享内存。 | 114| NN_Tensor* OH_NNTensor_CreateWithSize(size_t deviceID, NN_TensorDesc *tensorDesc, size_t size) | 按照指定内存大小和张量描述创建张量实例,会申请设备共享内存。 | 115| NN_Tensor* OH_NNTensor_CreateWithFd(size_t deviceID, NN_TensorDesc *tensorDesc, int fd, size_t size, size_t offset) | 按照指定共享内存的文件描述符和张量描述创建张量实例,从而可以复用其他张量的设备共享内存。 | 116| NN_TensorDesc* OH_NNTensor_GetTensorDesc(const NN_Tensor *tensor) | 获取张量内部的张量描述实例指针,从而可读取张量的属性,例如数据类型、形状等。 | 117| void* OH_NNTensor_GetDataBuffer(const NN_Tensor *tensor) | 获取张量数据的内存地址,可以读写张量数据。 | 118| OH_NN_ReturnCode OH_NNTensor_GetFd(const NN_Tensor *tensor, int *fd) | 获取张量数据所在共享内存的文件描述符,文件描述符fd对应了一块设备共享内存。 | 119| OH_NN_ReturnCode OH_NNTensor_GetSize(const NN_Tensor *tensor, size_t *size) | 获取张量数据所在共享内存的大小。 | 120| OH_NN_ReturnCode OH_NNTensor_GetOffset(const NN_Tensor *tensor, size_t *offset) | 获取张量数据所在共享内存上的偏移量,张量数据可使用的大小为所在共享内存的大小减去偏移量。 | 121| OH_NN_ReturnCode OH_NNTensor_Destroy(NN_Tensor **tensor) | 销毁张量实例。 | 122 123### 执行推理接口 124 125| 接口名称 | 描述 | 126| ------- | --- | 127| OH_NNExecutor *OH_NNExecutor_Construct(OH_NNCompilation *compilation) | 创建OH_NNExecutor类型的执行器实例。 | 128| OH_NN_ReturnCode OH_NNExecutor_GetOutputShape(OH_NNExecutor *executor, uint32_t outputIndex, int32_t **shape, uint32_t *shapeLength) | 获取输出张量的维度信息,用于输出张量具有动态形状的情况。 | 129| OH_NN_ReturnCode OH_NNExecutor_GetInputCount(const OH_NNExecutor *executor, size_t *inputCount) | 获取输入张量的数量。 | 130| OH_NN_ReturnCode OH_NNExecutor_GetOutputCount(const OH_NNExecutor *executor, size_t *outputCount) | 获取输出张量的数量。 | 131| NN_TensorDesc* OH_NNExecutor_CreateInputTensorDesc(const OH_NNExecutor *executor, size_t index) | 由指定索引值创建一个输入张量的描述,用于读取张量的属性或创建张量实例。 | 132| NN_TensorDesc* OH_NNExecutor_CreateOutputTensorDesc(const OH_NNExecutor *executor, size_t index) | 由指定索引值创建一个输出张量的描述,用于读取张量的属性或创建张量实例。 | 133| OH_NN_ReturnCode OH_NNExecutor_GetInputDimRange(const OH_NNExecutor *executor, size_t index, size_t **minInputDims, size_t **maxInputDims, size_t *shapeLength) |获取所有输入张量的维度范围。当输入张量具有动态形状时,不同设备可能支持不同的维度范围。 | 134| OH_NN_ReturnCode OH_NNExecutor_SetOnRunDone(OH_NNExecutor *executor, NN_OnRunDone onRunDone) | 设置异步推理结束后的回调处理函数,回调函数定义详见接口文档。 | 135| OH_NN_ReturnCode OH_NNExecutor_SetOnServiceDied(OH_NNExecutor *executor, NN_OnServiceDied onServiceDied) | 设置异步推理执行期间设备驱动服务突然死亡时的回调处理函数,回调函数定义详见接口文档。 | 136| OH_NN_ReturnCode OH_NNExecutor_RunSync(OH_NNExecutor *executor, NN_Tensor *inputTensor[], size_t inputCount, NN_Tensor *outputTensor[], size_t outputCount) | 执行同步推理。 | 137| OH_NN_ReturnCode OH_NNExecutor_RunAsync(OH_NNExecutor *executor, NN_Tensor *inputTensor[], size_t inputCount, NN_Tensor *outputTensor[], size_t outputCount, int32_t timeout, void *userData) | 执行异步推理。 | 138| void OH_NNExecutor_Destroy(OH_NNExecutor **executor) | 销毁执行器实例。 | 139 140### 设备管理接口 141 142| 接口名称 | 描述 | 143| ------- | --- | 144| OH_NN_ReturnCode OH_NNDevice_GetAllDevicesID(const size_t **allDevicesID, uint32_t *deviceCount) | 获取对接到Neural Network Runtime的所有硬件ID。 | 145| OH_NN_ReturnCode OH_NNDevice_GetName(size_t deviceID, const char **name) | 获取指定硬件的名称。 | 146| OH_NN_ReturnCode OH_NNDevice_GetType(size_t deviceID, OH_NN_DeviceType *deviceType) | 获取指定硬件的类别信息。 | 147 148 149## 开发步骤 150 151Neural Network Runtime的开发流程主要包含**模型构造**、**模型编译**和**推理执行**三个阶段。以下开发步骤以`Add`单算子模型为例,介绍调用Neural Network Runtime接口,开发应用的过程。 152 1531. 创建应用样例文件。 154 155 首先,创建Neural Network Runtime应用样例的源文件。在项目目录下执行以下命令,创建`nnrt_example/`目录,并在目录下创建 `nnrt_example.cpp` 源文件。 156 157 ```shell 158 mkdir ~/nnrt_example && cd ~/nnrt_example 159 touch nnrt_example.cpp 160 ``` 161 1622. 导入Neural Network Runtime。 163 164 在 `nnrt_example.cpp` 文件的开头添加以下代码,引入Neural Network Runtime。 165 166 ```cpp 167 #include <iostream> 168 #include <cstdarg> 169 #include "neural_network_runtime/neural_network_runtime.h" 170 ``` 171 1723. 定义日志打印、设置输入数据、数据打印等辅助函数。 173 174 ```cpp 175 // 返回值检查宏 176 #define CHECKNEQ(realRet, expectRet, retValue, ...) \ 177 do { \ 178 if ((realRet) != (expectRet)) { \ 179 printf(__VA_ARGS__); \ 180 return (retValue); \ 181 } \ 182 } while (0) 183 184 #define CHECKEQ(realRet, expectRet, retValue, ...) \ 185 do { \ 186 if ((realRet) == (expectRet)) { \ 187 printf(__VA_ARGS__); \ 188 return (retValue); \ 189 } \ 190 } while (0) 191 192 // 设置输入数据用于推理 193 OH_NN_ReturnCode SetInputData(NN_Tensor* inputTensor[], size_t inputSize) 194 { 195 OH_NN_DataType dataType(OH_NN_FLOAT32); 196 OH_NN_ReturnCode ret{OH_NN_FAILED}; 197 size_t elementCount = 0; 198 for (size_t i = 0; i < inputSize; ++i) { 199 // 获取张量的数据内存 200 auto data = OH_NNTensor_GetDataBuffer(inputTensor[i]); 201 CHECKEQ(data, nullptr, OH_NN_FAILED, "Failed to get data buffer."); 202 // 获取张量的描述 203 auto desc = OH_NNTensor_GetTensorDesc(inputTensor[i]); 204 CHECKEQ(desc, nullptr, OH_NN_FAILED, "Failed to get desc."); 205 // 获取张量的数据类型 206 ret = OH_NNTensorDesc_GetDataType(desc, &dataType); 207 CHECKNEQ(ret, OH_NN_SUCCESS, OH_NN_FAILED, "Failed to get data type."); 208 // 获取张量的元素个数 209 ret = OH_NNTensorDesc_GetElementCount(desc, &elementCount); 210 CHECKNEQ(ret, OH_NN_SUCCESS, OH_NN_FAILED, "Failed to get element count."); 211 switch(dataType) { 212 case OH_NN_FLOAT32: { 213 float* floatValue = reinterpret_cast<float*>(data); 214 for (size_t j = 0; j < elementCount; ++j) { 215 floatValue[j] = static_cast<float>(j); 216 } 217 break; 218 } 219 case OH_NN_INT32: { 220 int* intValue = reinterpret_cast<int*>(data); 221 for (size_t j = 0; j < elementCount; ++j) { 222 intValue[j] = static_cast<int>(j); 223 } 224 break; 225 } 226 default: 227 return OH_NN_FAILED; 228 } 229 } 230 return OH_NN_SUCCESS; 231 } 232 233 OH_NN_ReturnCode Print(NN_Tensor* outputTensor[], size_t outputSize) 234 { 235 OH_NN_DataType dataType(OH_NN_FLOAT32); 236 OH_NN_ReturnCode ret{OH_NN_FAILED}; 237 size_t elementCount = 0; 238 for (size_t i = 0; i < outputSize; ++i) { 239 auto data = OH_NNTensor_GetDataBuffer(outputTensor[i]); 240 CHECKEQ(data, nullptr, OH_NN_FAILED, "Failed to get data buffer."); 241 auto desc = OH_NNTensor_GetTensorDesc(outputTensor[i]); 242 CHECKEQ(desc, nullptr, OH_NN_FAILED, "Failed to get desc."); 243 ret = OH_NNTensorDesc_GetDataType(desc, &dataType); 244 CHECKNEQ(ret, OH_NN_SUCCESS, OH_NN_FAILED, "Failed to get data type."); 245 ret = OH_NNTensorDesc_GetElementCount(desc, &elementCount); 246 CHECKNEQ(ret, OH_NN_SUCCESS, OH_NN_FAILED, "Failed to get element count."); 247 switch(dataType) { 248 case OH_NN_FLOAT32: { 249 float* floatValue = reinterpret_cast<float*>(data); 250 for (size_t j = 0; j < elementCount; ++j) { 251 std::cout << "Output index: " << j << ", value is: " << floatValue[j] << "." << std::endl; 252 } 253 break; 254 } 255 case OH_NN_INT32: { 256 int* intValue = reinterpret_cast<int*>(data); 257 for (size_t j = 0; j < elementCount; ++j) { 258 std::cout << "Output index: " << j << ", value is: " << intValue[j] << "." << std::endl; 259 } 260 break; 261 } 262 default: 263 return OH_NN_FAILED; 264 } 265 } 266 267 return OH_NN_SUCCESS; 268 } 269 ``` 270 2714. 构造模型。 272 273 使用Neural Network Runtime的模型构造接口,构造`Add`单算子样例模型。 274 275 ```cpp 276 OH_NN_ReturnCode BuildModel(OH_NNModel** pmodel) 277 { 278 // 创建模型实例model,进行模型构造 279 OH_NNModel* model = OH_NNModel_Construct(); 280 CHECKEQ(model, nullptr, OH_NN_FAILED, "Create model failed."); 281 282 // 添加Add算子的第一个输入张量,类型为float32,张量形状为[1, 2, 2, 3] 283 NN_TensorDesc* tensorDesc = OH_NNTensorDesc_Create(); 284 CHECKEQ(tensorDesc, nullptr, OH_NN_FAILED, "Create TensorDesc failed."); 285 286 int32_t inputDims[4] = {1, 2, 2, 3}; 287 auto returnCode = OH_NNTensorDesc_SetShape(tensorDesc, inputDims, 4); 288 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc shape failed."); 289 290 returnCode = OH_NNTensorDesc_SetDataType(tensorDesc, OH_NN_FLOAT32); 291 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc data type failed."); 292 293 returnCode = OH_NNTensorDesc_SetFormat(tensorDesc, OH_NN_FORMAT_NONE); 294 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc format failed."); 295 296 returnCode = OH_NNModel_AddTensorToModel(model, tensorDesc); 297 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Add first TensorDesc to model failed."); 298 299 returnCode = OH_NNModel_SetTensorType(model, 0, OH_NN_TENSOR); 300 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set model tensor type failed."); 301 302 // 添加Add算子的第二个输入张量,类型为float32,张量形状为[1, 2, 2, 3] 303 tensorDesc = OH_NNTensorDesc_Create(); 304 CHECKEQ(tensorDesc, nullptr, OH_NN_FAILED, "Create TensorDesc failed."); 305 306 returnCode = OH_NNTensorDesc_SetShape(tensorDesc, inputDims, 4); 307 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc shape failed."); 308 309 returnCode = OH_NNTensorDesc_SetDataType(tensorDesc, OH_NN_FLOAT32); 310 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc data type failed."); 311 312 returnCode = OH_NNTensorDesc_SetFormat(tensorDesc, OH_NN_FORMAT_NONE); 313 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc format failed."); 314 315 returnCode = OH_NNModel_AddTensorToModel(model, tensorDesc); 316 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Add second TensorDesc to model failed."); 317 318 returnCode = OH_NNModel_SetTensorType(model, 1, OH_NN_TENSOR); 319 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set model tensor type failed."); 320 321 // 添加Add算子的参数张量,该参数张量用于指定激活函数的类型,张量的数据类型为int8。 322 tensorDesc = OH_NNTensorDesc_Create(); 323 CHECKEQ(tensorDesc, nullptr, OH_NN_FAILED, "Create TensorDesc failed."); 324 325 int32_t activationDims = 1; 326 returnCode = OH_NNTensorDesc_SetShape(tensorDesc, &activationDims, 1); 327 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc shape failed."); 328 329 returnCode = OH_NNTensorDesc_SetDataType(tensorDesc, OH_NN_INT8); 330 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc data type failed."); 331 332 returnCode = OH_NNTensorDesc_SetFormat(tensorDesc, OH_NN_FORMAT_NONE); 333 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc format failed."); 334 335 returnCode = OH_NNModel_AddTensorToModel(model, tensorDesc); 336 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Add second TensorDesc to model failed."); 337 338 returnCode = OH_NNModel_SetTensorType(model, 2, OH_NN_ADD_ACTIVATIONTYPE); 339 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set model tensor type failed."); 340 341 // 将激活函数类型设置为OH_NN_FUSED_NONE,表示该算子不添加激活函数。 342 int8_t activationValue = OH_NN_FUSED_NONE; 343 returnCode = OH_NNModel_SetTensorData(model, 2, &activationValue, sizeof(int8_t)); 344 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set model tensor data failed."); 345 346 // 设置Add算子的输出张量,类型为float32,张量形状为[1, 2, 2, 3] 347 tensorDesc = OH_NNTensorDesc_Create(); 348 CHECKEQ(tensorDesc, nullptr, OH_NN_FAILED, "Create TensorDesc failed."); 349 350 returnCode = OH_NNTensorDesc_SetShape(tensorDesc, inputDims, 4); 351 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc shape failed."); 352 353 returnCode = OH_NNTensorDesc_SetDataType(tensorDesc, OH_NN_FLOAT32); 354 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc data type failed."); 355 356 returnCode = OH_NNTensorDesc_SetFormat(tensorDesc, OH_NN_FORMAT_NONE); 357 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set TensorDesc format failed."); 358 359 returnCode = OH_NNModel_AddTensorToModel(model, tensorDesc); 360 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Add forth TensorDesc to model failed."); 361 362 returnCode = OH_NNModel_SetTensorType(model, 3, OH_NN_TENSOR); 363 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Set model tensor type failed."); 364 365 // 指定Add算子的输入张量、参数张量和输出张量的索引 366 uint32_t inputIndicesValues[2] = {0, 1}; 367 uint32_t paramIndicesValues = 2; 368 uint32_t outputIndicesValues = 3; 369 OH_NN_UInt32Array paramIndices = {¶mIndicesValues, 1}; 370 OH_NN_UInt32Array inputIndices = {inputIndicesValues, 2}; 371 OH_NN_UInt32Array outputIndices = {&outputIndicesValues, 1}; 372 373 // 向模型实例添加Add算子 374 returnCode = OH_NNModel_AddOperation(model, OH_NN_OPS_ADD, ¶mIndices, &inputIndices, &outputIndices); 375 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Add operation to model failed."); 376 377 // 设置模型实例的输入张量、输出张量的索引 378 returnCode = OH_NNModel_SpecifyInputsAndOutputs(model, &inputIndices, &outputIndices); 379 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Specify model inputs and outputs failed."); 380 381 // 完成模型实例的构建 382 returnCode = OH_NNModel_Finish(model); 383 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "Build model failed."); 384 385 // 返回模型实例 386 *pmodel = model; 387 return OH_NN_SUCCESS; 388 } 389 ``` 390 3915. 查询Neural Network Runtime已经对接的AI加速芯片。 392 393 Neural Network Runtime支持通过HDI接口,对接多种AI加速芯片。在执行模型编译前,需要查询当前设备下,Neural Network Runtime已经对接的AI加速芯片。每个AI加速芯片对应唯一的ID值,在编译阶段需要通过设备ID,指定模型编译的芯片。 394 ```cpp 395 void GetAvailableDevices(std::vector<size_t>& availableDevice) 396 { 397 availableDevice.clear(); 398 399 // 获取可用的硬件ID 400 const size_t* devices = nullptr; 401 uint32_t deviceCount = 0; 402 OH_NN_ReturnCode ret = OH_NNDevice_GetAllDevicesID(&devices, &deviceCount); 403 if (ret != OH_NN_SUCCESS) { 404 std::cout << "GetAllDevicesID failed, get no available device." << std::endl; 405 return; 406 } 407 408 for (uint32_t i = 0; i < deviceCount; i++) { 409 availableDevice.emplace_back(devices[i]); 410 } 411 } 412 ``` 413 4146. 在指定的设备上编译模型。 415 416 Neural Network Runtime使用抽象的模型表达描述AI模型的拓扑结构。在AI加速芯片上执行前,需要通过Neural Network Runtime提供的编译模块来创建编译实例,并由编译实例将抽象的模型表达下发至芯片驱动层,转换成可以直接推理计算的格式,即模型编译。 417 ```cpp 418 OH_NN_ReturnCode CreateCompilation(OH_NNModel* model, const std::vector<size_t>& availableDevice, 419 OH_NNCompilation** pCompilation) 420 { 421 // 创建编译实例compilation,将构图的模型实例或MSLite传下来的模型实例传入 422 OH_NNCompilation* compilation = OH_NNCompilation_Construct(model); 423 CHECKEQ(compilation, nullptr, OH_NN_FAILED, "OH_NNCore_ConstructCompilationWithNNModel failed."); 424 425 // 设置编译的硬件、缓存路径、性能模式、计算优先级、是否开启float16低精度计算等选项 426 // 选择在第一个设备上编译模型 427 auto returnCode = OH_NNCompilation_SetDevice(compilation, availableDevice[0]); 428 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNCompilation_SetDevice failed."); 429 430 // 将模型编译结果缓存在/data/local/tmp目录下,版本号指定为1 431 returnCode = OH_NNCompilation_SetCache(compilation, "/data/local/tmp", 1); 432 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNCompilation_SetCache failed."); 433 434 // 设置硬件性能模式 435 returnCode = OH_NNCompilation_SetPerformanceMode(compilation, OH_NN_PERFORMANCE_EXTREME); 436 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNCompilation_SetPerformanceMode failed."); 437 438 // 设置推理执行优先级 439 returnCode = OH_NNCompilation_SetPriority(compilation, OH_NN_PRIORITY_HIGH); 440 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNCompilation_SetPriority failed."); 441 442 // 是否开启FP16计算模式 443 returnCode = OH_NNCompilation_EnableFloat16(compilation, false); 444 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNCompilation_EnableFloat16 failed."); 445 446 // 执行模型编译 447 returnCode = OH_NNCompilation_Build(compilation); 448 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNCompilation_Build failed."); 449 450 *pCompilation = compilation; 451 return OH_NN_SUCCESS; 452 } 453 ``` 454 4557. 创建执行器。 456 457 完成模型编译后,需要调用Neural Network Runtime的执行模块,通过编译实例创建执行器。模型推理阶段中的设置模型输入、触发推理计算以及获取模型输出等操作均需要围绕执行器完成。 458 ```cpp 459 OH_NNExecutor* CreateExecutor(OH_NNCompilation* compilation) 460 { 461 // 通过编译实例compilation创建执行器executor 462 OH_NNExecutor *executor = OH_NNExecutor_Construct(compilation); 463 CHECKEQ(executor, nullptr, nullptr, "OH_NNExecutor_Construct failed."); 464 return executor; 465 } 466 ``` 467 4688. 执行推理计算,并打印推理结果。 469 470 通过执行模块提供的接口,将推理计算所需要的输入数据传递给执行器,触发执行器完成一次推理计算,获取模型的推理结果并打印。 471 ```cpp 472 OH_NN_ReturnCode Run(OH_NNExecutor* executor, const std::vector<size_t>& availableDevice) 473 { 474 // 从executor获取输入输出信息 475 // 获取输入张量的个数 476 size_t inputCount = 0; 477 auto returnCode = OH_NNExecutor_GetInputCount(executor, &inputCount); 478 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNExecutor_GetInputCount failed."); 479 std::vector<NN_TensorDesc*> inputTensorDescs; 480 NN_TensorDesc* tensorDescTmp = nullptr; 481 for (size_t i = 0; i < inputCount; ++i) { 482 // 创建输入张量的描述 483 tensorDescTmp = OH_NNExecutor_CreateInputTensorDesc(executor, i); 484 CHECKEQ(tensorDescTmp, nullptr, OH_NN_FAILED, "OH_NNExecutor_CreateInputTensorDesc failed."); 485 inputTensorDescs.emplace_back(tensorDescTmp); 486 } 487 // 获取输出张量的个数 488 size_t outputCount = 0; 489 returnCode = OH_NNExecutor_GetOutputCount(executor, &outputCount); 490 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNExecutor_GetOutputCount failed."); 491 std::vector<NN_TensorDesc*> outputTensorDescs; 492 for (size_t i = 0; i < outputCount; ++i) { 493 // 创建输出张量的描述 494 tensorDescTmp = OH_NNExecutor_CreateOutputTensorDesc(executor, i); 495 CHECKEQ(tensorDescTmp, nullptr, OH_NN_FAILED, "OH_NNExecutor_CreateOutputTensorDesc failed."); 496 outputTensorDescs.emplace_back(tensorDescTmp); 497 } 498 499 // 创建输入和输出张量 500 NN_Tensor* inputTensors[inputCount]; 501 NN_Tensor* tensor = nullptr; 502 for (size_t i = 0; i < inputCount; ++i) { 503 tensor = nullptr; 504 tensor = OH_NNTensor_Create(availableDevice[0], inputTensorDescs[i]); 505 CHECKEQ(tensor, nullptr, OH_NN_FAILED, "OH_NNTensor_Create failed."); 506 inputTensors[i] = tensor; 507 } 508 NN_Tensor* outputTensors[outputCount]; 509 for (size_t i = 0; i < outputCount; ++i) { 510 tensor = nullptr; 511 tensor = OH_NNTensor_Create(availableDevice[0], outputTensorDescs[i]); 512 CHECKEQ(tensor, nullptr, OH_NN_FAILED, "OH_NNTensor_Create failed."); 513 outputTensors[i] = tensor; 514 } 515 516 // 设置输入张量的数据 517 returnCode = SetInputData(inputTensors, inputCount); 518 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "SetInputData failed."); 519 520 // 执行推理 521 returnCode = OH_NNExecutor_RunSync(executor, inputTensors, inputCount, outputTensors, outputCount); 522 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNExecutor_RunSync failed."); 523 524 // 打印输出张量的数据 525 Print(outputTensors, outputCount); 526 527 // 清理输入和输出张量以及张量描述 528 for (size_t i = 0; i < inputCount; ++i) { 529 returnCode = OH_NNTensor_Destroy(&inputTensors[i]); 530 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNTensor_Destroy failed."); 531 returnCode = OH_NNTensorDesc_Destroy(&inputTensorDescs[i]); 532 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNTensorDesc_Destroy failed."); 533 } 534 for (size_t i = 0; i < outputCount; ++i) { 535 returnCode = OH_NNTensor_Destroy(&outputTensors[i]); 536 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNTensor_Destroy failed."); 537 returnCode = OH_NNTensorDesc_Destroy(&outputTensorDescs[i]); 538 CHECKNEQ(returnCode, OH_NN_SUCCESS, OH_NN_FAILED, "OH_NNTensorDesc_Destroy failed."); 539 } 540 541 return OH_NN_SUCCESS; 542 } 543 ``` 544 5459. 构建端到端模型构造-编译-执行流程。 546 547 步骤4-步骤8实现了模型的模型构造、编译和执行流程,并封装成多个函数,便于模块化开发。以下示例代码将串联这些函数, 形成一个完整的Neural Network Runtime使用流程。 548 ```cpp 549 int main(int argc, char** argv) 550 { 551 OH_NNModel* model = nullptr; 552 OH_NNCompilation* compilation = nullptr; 553 OH_NNExecutor* executor = nullptr; 554 std::vector<size_t> availableDevices; 555 556 // 模型构造 557 OH_NN_ReturnCode ret = BuildModel(&model); 558 if (ret != OH_NN_SUCCESS) { 559 std::cout << "BuildModel failed." << std::endl; 560 OH_NNModel_Destroy(&model); 561 return -1; 562 } 563 564 // 获取可执行的设备 565 GetAvailableDevices(availableDevices); 566 if (availableDevices.empty()) { 567 std::cout << "No available device." << std::endl; 568 OH_NNModel_Destroy(&model); 569 return -1; 570 } 571 572 // 模型编译 573 ret = CreateCompilation(model, availableDevices, &compilation); 574 if (ret != OH_NN_SUCCESS) { 575 std::cout << "CreateCompilation failed." << std::endl; 576 OH_NNModel_Destroy(&model); 577 OH_NNCompilation_Destroy(&compilation); 578 return -1; 579 } 580 581 // 销毁模型实例 582 OH_NNModel_Destroy(&model); 583 584 // 创建模型的推理执行器 585 executor = CreateExecutor(compilation); 586 if (executor == nullptr) { 587 std::cout << "CreateExecutor failed, no executor is created." << std::endl; 588 OH_NNCompilation_Destroy(&compilation); 589 return -1; 590 } 591 592 // 销毁编译实例 593 OH_NNCompilation_Destroy(&compilation); 594 595 // 使用上一步创建的执行器,执行推理计算 596 ret = Run(executor, availableDevices); 597 if (ret != OH_NN_SUCCESS) { 598 std::cout << "Run failed." << std::endl; 599 OH_NNExecutor_Destroy(&executor); 600 return -1; 601 } 602 603 // 销毁执行器实例 604 OH_NNExecutor_Destroy(&executor); 605 606 return 0; 607 } 608 ``` 609 610## 调测验证 611 6121. 准备应用样例的编译配置文件。 613 614 新建一个 `CMakeLists.txt` 文件,为开发步骤中的应用样例文件 `nnrt_example.cpp` 添加编译配置。以下提供简单的 `CMakeLists.txt` 示例: 615 ```text 616 cmake_minimum_required(VERSION 3.16) 617 project(nnrt_example C CXX) 618 619 add_executable(nnrt_example 620 ./nnrt_example.cpp 621 ) 622 623 target_link_libraries(nnrt_example 624 neural_network_runtime 625 neural_network_core 626 ) 627 ``` 628 6292. 编译应用样例。 630 631 执行以下命令,在当前目录下新建build/目录,在build/目录下编译 `nnrt_example.cpp`,得到二进制文件 `nnrt_example`。 632 ```shell 633 mkdir build && cd build 634 cmake -DCMAKE_TOOLCHAIN_FILE={交叉编译工具链的路径}/build/cmake/ohos.toolchain.cmake -DOHOS_ARCH=arm64-v8a -DOHOS_PLATFORM=OHOS -DOHOS_STL=c++_static .. 635 make 636 ``` 637 6383. 执行以下代码,将样例推送到设备上执行。 639 ```shell 640 # 将编译得到的 `nnrt_example` 推送到设备上,执行样例。 641 hdc_std file send ./nnrt_example /data/local/tmp/. 642 643 # 给测试用例可执行文件加上权限。 644 hdc_std shell "chmod +x /data/local/tmp/nnrt_example" 645 646 # 执行测试用例 647 hdc_std shell "/data/local/tmp/nnrt_example" 648 ``` 649 650 如果样例执行正常,应该得到以下输出。 651 ```text 652 Output index: 0, value is: 0.000000. 653 Output index: 1, value is: 2.000000. 654 Output index: 2, value is: 4.000000. 655 Output index: 3, value is: 6.000000. 656 Output index: 4, value is: 8.000000. 657 Output index: 5, value is: 10.000000. 658 Output index: 6, value is: 12.000000. 659 Output index: 7, value is: 14.000000. 660 Output index: 8, value is: 16.000000. 661 Output index: 9, value is: 18.000000. 662 Output index: 10, value is: 20.000000. 663 Output index: 11, value is: 22.000000. 664 ``` 665 6664. 检查模型缓存(可选)。 667 668 如果在调测环境下,Neural Network Runtime对接的HDI服务支持模型缓存功能,执行完 `nnrt_example`, 可以在 `/data/local/tmp` 目录下找到生成的缓存文件。 669 670 > **说明:** 671 > 672 > 模型的IR需要传递到硬件驱动层,由HDI服务将统一的IR图,编译成硬件专用的计算图,编译的过程非常耗时。Neural Network Runtime支持计算图缓存的特性,可以将HDI服务编译生成的计算图,缓存到设备存储中。当下一次在同一个加速芯片上编译同一个模型时,通过指定缓存的路径,Neural Network Runtime可以直接加载缓存文件中的计算图,减少编译消耗的时间。 673 674 检查缓存目录下的缓存文件: 675 ```shell 676 ls /data/local/tmp 677 ``` 678 679 以下为打印结果: 680 ```text 681 # 0.nncache 1.nncache 2.nncache cache_info.nncache 682 ``` 683 684 如果缓存不再使用,需要手动删除缓存,可以参考以下命令,删除缓存文件。 685 ```shell 686 rm /data/local/tmp/*nncache 687 ``` 688