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!["Add单算子网络示意图"](figures/02.png) 11 12## 环境准备 13 14### 环境要求 15 16Neural Network Runtime部件的环境要求如下: 17 18- 系统版本:OpenHarmony master分支。 19- 开发环境:Ubuntu 18.04及以上。 20- 接入设备:OpenHarmony定义的标准设备,并且系统中内置的硬件加速器驱动,已通过HDI接口对接Neural Network Runtime。 21 22由于Neural Network Runtime通过OpenHarmony Native API对外开放,需要通过OpenHarmony的Native开发套件编译Neural Network Runtime应用。 23 24### 环境搭建 25 261. 打开Ubuntu编译服务器的终端。 272. 把Native开发套件压缩包拷贝至当前用户根目录下。 283. 执行以下命令解压Native开发套件的压缩包。 29```shell 30unzip native-linux-{版本号}.zip 31``` 32 33解压缩后的内容如下(随版本迭代,目录下的内容可能发生变化,请以最新版本的Native API为准): 34```text 35native/ 36├── build // 交叉编译工具链 37├── build-tools // 编译构建工具 38├── docs 39├── llvm 40├── nativeapi_syscap_config.json 41├── ndk_system_capability.json 42├── NOTICE.txt 43├── oh-uni-package.json 44└── sysroot // Native API头文件和库 45``` 46## 接口说明 47 48这里给出Neural Network Runtime开发流程中通用的接口,具体请见下列表格。 49 50### 结构体 51 52| 结构体名称 | 描述 | 53| --------- | ---- | 54| typedef struct OH_NNModel OH_NNModel | Neural Network Runtime的模型句柄,用于构造模型。 | 55| typedef struct OH_NNCompilation OH_NNCompilation | Neural Network Runtime的编译器句柄,用于编译AI模型。 | 56| typedef struct OH_NNExecutor OH_NNExecutor | Neural Network Runtime的执行器句柄,用于在指定设备上执行推理计算。 | 57 58### 模型构造相关接口 59 60| 接口名称 | 描述 | 61| ------- | --- | 62| OH_NNModel_Construct() | 创建OH_NNModel类型的模型实例。 | 63| OH_NN_ReturnCode OH_NNModel_AddTensor(OH_NNModel *model, const OH_NN_Tensor *tensor) | 向模型实例中添加张量。 | 64| OH_NN_ReturnCode OH_NNModel_SetTensorData(OH_NNModel *model, uint32_t index, const void *dataBuffer, size_t length) | 设置张量的数值。 | 65| 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) | 向模型实例中添加算子。 | 66| OH_NN_ReturnCode OH_NNModel_SpecifyInputsAndOutputs(OH_NNModel *model, const OH_NN_UInt32Array *inputIndices, const OH_NN_UInt32Array *outputIndices) | 指定模型的输入输出。 | 67| OH_NN_ReturnCode OH_NNModel_Finish(OH_NNModel *model) | 完成模型构图。| 68| void OH_NNModel_Destroy(OH_NNModel **model) | 释放模型实例。 | 69 70### 模型编译相关接口 71 72| 接口名称 | 描述 | 73| ------- | --- | 74| OH_NNCompilation *OH_NNCompilation_Construct(const OH_NNModel *model) | 创建OH_NNCompilation类型的编译实例。 | 75| OH_NN_ReturnCode OH_NNCompilation_SetDevice(OH_NNCompilation *compilation, size_t deviceID) | 指定模型编译和计算的硬件。 | 76| OH_NN_ReturnCode OH_NNCompilation_SetCache(OH_NNCompilation *compilation, const char *cachePath, uint32_t version) | 设置编译后的模型缓存路径和缓存版本。 | 77| OH_NN_ReturnCode OH_NNCompilation_Build(OH_NNCompilation *compilation) | 进行模型编译。 | 78| void OH_NNCompilation_Destroy(OH_NNCompilation **compilation) | 释放OH_NNCompilation对象。 | 79 80### 执行推理相关接口 81 82| 接口名称 | 描述 | 83| ------- | --- | 84| OH_NNExecutor *OH_NNExecutor_Construct(OH_NNCompilation *compilation) | 创建OH_NNExecutor类型的执行器实例。 | 85| OH_NN_ReturnCode OH_NNExecutor_SetInput(OH_NNExecutor *executor, uint32_t inputIndex, const OH_NN_Tensor *tensor, const void *dataBuffer, size_t length) | 设置模型单个输入的数据。 | 86| OH_NN_ReturnCode OH_NNExecutor_SetOutput(OH_NNExecutor *executor, uint32_t outputIndex, void *dataBuffer, size_t length) | 设置模型单个输出的缓冲区。 | 87| OH_NN_ReturnCode OH_NNExecutor_Run(OH_NNExecutor *executor) | 执行推理。 | 88| void OH_NNExecutor_Destroy(OH_NNExecutor **executor) | 销毁OH_NNExecutor实例,释放实例占用的内存。 | 89 90### 设备管理相关接口 91 92| 接口名称 | 描述 | 93| ------- | --- | 94| OH_NN_ReturnCode OH_NNDevice_GetAllDevicesID(const size_t **allDevicesID, uint32_t *deviceCount) | 获取对接到 Neural Network Runtime 的硬件ID。 | 95 96 97## 开发步骤 98 99Neural Network Runtime的开发流程主要包含**模型构造**、**模型编译**和**推理执行**三个阶段。以下开发步骤以`Add`单算子模型为例,介绍调用Neural Network Runtime接口,开发应用的过程。 100 1011. 创建应用样例文件。 102 103 首先,创建Neural Network Runtime应用样例的源文件。在项目目录下执行以下命令,创建`nnrt_example/`目录,在目录下创建 `nnrt_example.cpp` 源文件。 104 105 ```shell 106 mkdir ~/nnrt_example && cd ~/nnrt_example 107 touch nnrt_example.cpp 108 ``` 109 1102. 导入Neural Network Runtime。 111 112 在 `nnrt_example.cpp` 文件的开头添加以下代码,引入Neural Network Runtime模块。 113 114 ```cpp 115 #include <cstdint> 116 #include <iostream> 117 #include <vector> 118 119 #include "neural_network_runtime/neural_network_runtime.h" 120 121 // 常量,用于指定输入、输出数据的字节长度 122 const size_t DATA_LENGTH = 4 * 12; 123 ``` 124 1253. 构造模型。 126 127 使用Neural Network Runtime接口,构造`Add`单算子样例模型。 128 129 ```cpp 130 OH_NN_ReturnCode BuildModel(OH_NNModel** pModel) 131 { 132 // 创建模型实例,进行模型构造 133 OH_NNModel* model = OH_NNModel_Construct(); 134 if (model == nullptr) { 135 std::cout << "Create model failed." << std::endl; 136 return OH_NN_MEMORY_ERROR; 137 } 138 139 // 添加Add算子的第一个输入Tensor,类型为float32,张量形状为[1, 2, 2, 3] 140 int32_t inputDims[4] = {1, 2, 2, 3}; 141 OH_NN_Tensor input1 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR}; 142 OH_NN_ReturnCode ret = OH_NNModel_AddTensor(model, &input1); 143 if (ret != OH_NN_SUCCESS) { 144 std::cout << "BuildModel failed, add Tensor of first input failed." << std::endl; 145 return ret; 146 } 147 148 // 添加Add算子的第二个输入Tensor,类型为float32,张量形状为[1, 2, 2, 3] 149 OH_NN_Tensor input2 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR}; 150 ret = OH_NNModel_AddTensor(model, &input2); 151 if (ret != OH_NN_SUCCESS) { 152 std::cout << "BuildModel failed, add Tensor of second input failed." << std::endl; 153 return ret; 154 } 155 156 // 添加Add算子的参数Tensor,该参数Tensor用于指定激活函数的类型,Tensor的数据类型为int8。 157 int32_t activationDims = 1; 158 int8_t activationValue = OH_NN_FUSED_NONE; 159 OH_NN_Tensor activation = {OH_NN_INT8, 1, &activationDims, nullptr, OH_NN_ADD_ACTIVATIONTYPE}; 160 ret = OH_NNModel_AddTensor(model, &activation); 161 if (ret != OH_NN_SUCCESS) { 162 std::cout << "BuildModel failed, add Tensor of activation failed." << std::endl; 163 return ret; 164 } 165 166 // 将激活函数类型设置为OH_NN_FUSED_NONE,表示该算子不添加激活函数。 167 ret = OH_NNModel_SetTensorData(model, 2, &activationValue, sizeof(int8_t)); 168 if (ret != OH_NN_SUCCESS) { 169 std::cout << "BuildModel failed, set value of activation failed." << std::endl; 170 return ret; 171 } 172 173 // 设置Add算子的输出,类型为float32,张量形状为[1, 2, 2, 3] 174 OH_NN_Tensor output = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR}; 175 ret = OH_NNModel_AddTensor(model, &output); 176 if (ret != OH_NN_SUCCESS) { 177 std::cout << "BuildModel failed, add Tensor of output failed." << std::endl; 178 return ret; 179 } 180 181 // 指定Add算子的输入、参数和输出索引 182 uint32_t inputIndicesValues[2] = {0, 1}; 183 uint32_t paramIndicesValues = 2; 184 uint32_t outputIndicesValues = 3; 185 OH_NN_UInt32Array paramIndices = {¶mIndicesValues, 1}; 186 OH_NN_UInt32Array inputIndices = {inputIndicesValues, 2}; 187 OH_NN_UInt32Array outputIndices = {&outputIndicesValues, 1}; 188 189 // 向模型实例添加Add算子 190 ret = OH_NNModel_AddOperation(model, OH_NN_OPS_ADD, ¶mIndices, &inputIndices, &outputIndices); 191 if (ret != OH_NN_SUCCESS) { 192 std::cout << "BuildModel failed, add operation failed." << std::endl; 193 return ret; 194 } 195 196 // 设置模型实例的输入、输出索引 197 ret = OH_NNModel_SpecifyInputsAndOutputs(model, &inputIndices, &outputIndices); 198 if (ret != OH_NN_SUCCESS) { 199 std::cout << "BuildModel failed, specify inputs and outputs failed." << std::endl; 200 return ret; 201 } 202 203 // 完成模型实例的构建 204 ret = OH_NNModel_Finish(model); 205 if (ret != OH_NN_SUCCESS) { 206 std::cout << "BuildModel failed, error happened when finishing model construction." << std::endl; 207 return ret; 208 } 209 210 *pModel = model; 211 return OH_NN_SUCCESS; 212 } 213 ``` 214 2154. 查询Neural Network Runtime已经对接的加速芯片。 216 217 Neural Network Runtime支持通过HDI接口,对接多种加速芯片。在执行模型编译前,需要查询当前设备下,Neural Network Runtime已经对接的加速芯片。每个加速芯片对应唯一的ID值,在编译阶段需要通过设备ID,指定模型编译的芯片。 218 ```cpp 219 void GetAvailableDevices(std::vector<size_t>& availableDevice) 220 { 221 availableDevice.clear(); 222 223 // 获取可用的硬件ID 224 const size_t* devices = nullptr; 225 uint32_t deviceCount = 0; 226 OH_NN_ReturnCode ret = OH_NNDevice_GetAllDevicesID(&devices, &deviceCount); 227 if (ret != OH_NN_SUCCESS) { 228 std::cout << "GetAllDevicesID failed, get no available device." << std::endl; 229 return; 230 } 231 232 for (uint32_t i = 0; i < deviceCount; i++) { 233 availableDevice.emplace_back(devices[i]); 234 } 235 } 236 ``` 237 2385. 在指定的设备上编译模型。 239 240 Neural Network Runtime使用抽象的模型表达描述AI模型的拓扑结构,在加速芯片上执行前,需要通过Neural Network Runtime提供的编译模块,将抽象的模型表达下发至芯片驱动层,转换成可以直接推理计算的格式。 241 ```cpp 242 OH_NN_ReturnCode CreateCompilation(OH_NNModel* model, const std::vector<size_t>& availableDevice, OH_NNCompilation** pCompilation) 243 { 244 // 创建编译实例,用于将模型传递至底层硬件编译 245 OH_NNCompilation* compilation = OH_NNCompilation_Construct(model); 246 if (compilation == nullptr) { 247 std::cout << "CreateCompilation failed, error happened when creating compilation." << std::endl; 248 return OH_NN_MEMORY_ERROR; 249 } 250 251 // 设置编译的硬件、缓存路径、性能模式、计算优先级、是否开启float16低精度计算等选项 252 253 // 选择在第一个设备上编译模型 254 OH_NN_ReturnCode ret = OH_NNCompilation_SetDevice(compilation, availableDevice[0]); 255 if (ret != OH_NN_SUCCESS) { 256 std::cout << "CreateCompilation failed, error happened when setting device." << std::endl; 257 return ret; 258 } 259 260 // 将模型编译结果缓存在/data/local/tmp目录下,版本号指定为1 261 ret = OH_NNCompilation_SetCache(compilation, "/data/local/tmp", 1); 262 if (ret != OH_NN_SUCCESS) { 263 std::cout << "CreateCompilation failed, error happened when setting cache path." << std::endl; 264 return ret; 265 } 266 267 // 完成编译设置,进行模型编译 268 ret = OH_NNCompilation_Build(compilation); 269 if (ret != OH_NN_SUCCESS) { 270 std::cout << "CreateCompilation failed, error happened when building compilation." << std::endl; 271 return ret; 272 } 273 274 *pCompilation = compilation; 275 return OH_NN_SUCCESS; 276 } 277 ``` 278 2796. 创建执行器。 280 281 完成模型编译后,需要调用Neural Network Runtime的执行模块,创建推理执行器。执行阶段,设置模型输入、获取模型输出和触发推理计算的操作均围绕执行器完成。 282 ```cpp 283 OH_NNExecutor* CreateExecutor(OH_NNCompilation* compilation) 284 { 285 // 创建执行实例 286 OH_NNExecutor* executor = OH_NNExecutor_Construct(compilation); 287 return executor; 288 } 289 ``` 290 2917. 执行推理计算,并打印计算结果。 292 293 通过执行模块提供的接口,将推理计算所需要的输入数据传递给执行器,触发执行器完成一次推理计算,获取模型的推理计算结果。 294 ```cpp 295 OH_NN_ReturnCode Run(OH_NNExecutor* executor) 296 { 297 // 构造示例数据 298 float input1[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; 299 float input2[12] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}; 300 301 int32_t inputDims[4] = {1, 2, 2, 3}; 302 OH_NN_Tensor inputTensor1 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR}; 303 OH_NN_Tensor inputTensor2 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR}; 304 305 // 设置执行的输入 306 307 // 设置执行的第一个输入,输入数据由input1指定 308 OH_NN_ReturnCode ret = OH_NNExecutor_SetInput(executor, 0, &inputTensor1, input1, DATA_LENGTH); 309 if (ret != OH_NN_SUCCESS) { 310 std::cout << "Run failed, error happened when setting first input." << std::endl; 311 return ret; 312 } 313 314 // 设置执行的第二个输入,输入数据由input2指定 315 ret = OH_NNExecutor_SetInput(executor, 1, &inputTensor2, input2, DATA_LENGTH); 316 if (ret != OH_NN_SUCCESS) { 317 std::cout << "Run failed, error happened when setting second input." << std::endl; 318 return ret; 319 } 320 321 // 设置输出的数据缓冲区,OH_NNExecutor_Run执行计算后,输出结果将保留在output中 322 float output[12]; 323 ret = OH_NNExecutor_SetOutput(executor, 0, output, DATA_LENGTH); 324 if (ret != OH_NN_SUCCESS) { 325 std::cout << "Run failed, error happened when setting output buffer." << std::endl; 326 return ret; 327 } 328 329 // 执行计算 330 ret = OH_NNExecutor_Run(executor); 331 if (ret != OH_NN_SUCCESS) { 332 std::cout << "Run failed, error doing execution." << std::endl; 333 return ret; 334 } 335 336 // 打印输出结果 337 for (uint32_t i = 0; i < 12; i++) { 338 std::cout << "Output index: " << i << ", value is: " << output[i] << "." << std::endl; 339 } 340 341 return OH_NN_SUCCESS; 342 } 343 ``` 344 3458. 构建端到端模型构造-编译-执行流程。 346 347 步骤3-步骤7实现了模型的模型构造、编译和执行流程,并封装成4个函数,便于模块化开发。以下示例代码将4个函数串联成完整的Neural Network Runtime开发流程。 348 ```cpp 349 int main() 350 { 351 OH_NNModel* model = nullptr; 352 OH_NNCompilation* compilation = nullptr; 353 OH_NNExecutor* executor = nullptr; 354 std::vector<size_t> availableDevices; 355 356 // 模型构造阶段 357 OH_NN_ReturnCode ret = BuildModel(&model); 358 if (ret != OH_NN_SUCCESS) { 359 std::cout << "BuildModel failed." << std::endl; 360 OH_NNModel_Destroy(&model); 361 return -1; 362 } 363 364 // 获取可执行的设备 365 GetAvailableDevices(availableDevices); 366 if (availableDevices.empty()) { 367 std::cout << "No available device." << std::endl; 368 OH_NNModel_Destroy(&model); 369 return -1; 370 } 371 372 // 模型编译阶段 373 ret = CreateCompilation(model, availableDevices, &compilation); 374 if (ret != OH_NN_SUCCESS) { 375 std::cout << "CreateCompilation failed." << std::endl; 376 OH_NNModel_Destroy(&model); 377 OH_NNCompilation_Destroy(&compilation); 378 return -1; 379 } 380 381 // 创建模型的推理执行器 382 executor = CreateExecutor(compilation); 383 if (executor == nullptr) { 384 std::cout << "CreateExecutor failed, no executor is created." << std::endl; 385 OH_NNModel_Destroy(&model); 386 OH_NNCompilation_Destroy(&compilation); 387 return -1; 388 } 389 390 // 使用上一步创建的执行器,执行单步推理计算 391 ret = Run(executor); 392 if (ret != OH_NN_SUCCESS) { 393 std::cout << "Run failed." << std::endl; 394 OH_NNModel_Destroy(&model); 395 OH_NNCompilation_Destroy(&compilation); 396 OH_NNExecutor_Destroy(&executor); 397 return -1; 398 } 399 400 // 释放申请的资源 401 OH_NNModel_Destroy(&model); 402 OH_NNCompilation_Destroy(&compilation); 403 OH_NNExecutor_Destroy(&executor); 404 405 return 0; 406 } 407 ``` 408 409## 调测验证 410 4111. 准备应用样例的编译配置文件。 412 413 新建一个 `CMakeLists.txt` 文件,为开发步骤中的应用样例文件 `nnrt_example.cpp` 添加编译配置。以下提供简单的 `CMakeLists.txt` 示例: 414 ```text 415 cmake_minimum_required(VERSION 3.16) 416 project(nnrt_example C CXX) 417 418 add_executable(nnrt_example 419 ./nnrt_example.cpp 420 ) 421 422 target_link_libraries(nnrt_example 423 neural_network_runtime.z 424 ) 425 ``` 426 4272. 编译应用样例。 428 429 执行以下命令,在当前目录下新建build/目录,在build/目录下编译 `nnrt_example.cpp`,得到二进制文件 `nnrt_example`。 430 ```shell 431 mkdir build && cd build 432 cmake -DCMAKE_TOOLCHAIN_FILE={交叉编译工具链的路径}/build/cmake/ohos.toolchain.cmake -DOHOS_ARCH=arm64-v8a -DOHOS_PLATFORM=OHOS -DOHOS_STL=c++_static .. 433 make 434 ``` 435 4363. 执行以下代码,将样例推送到设备上执行。 437 ```shell 438 # 将编译得到的 `nnrt_example` 推送到设备上,执行样例。 439 hdc_std file send ./nnrt_example /data/local/tmp/. 440 441 # 给测试用例可执行文件加上权限。 442 hdc_std shell "chmod +x /data/local/tmp/nnrt_example" 443 444 # 执行测试用例 445 hdc_std shell "/data/local/tmp/nnrt_example" 446 ``` 447 448 如果样例执行正常,应该得到以下输出。 449 ```text 450 Output index: 0, value is: 11.000000. 451 Output index: 1, value is: 13.000000. 452 Output index: 2, value is: 15.000000. 453 Output index: 3, value is: 17.000000. 454 Output index: 4, value is: 19.000000. 455 Output index: 5, value is: 21.000000. 456 Output index: 6, value is: 23.000000. 457 Output index: 7, value is: 25.000000. 458 Output index: 8, value is: 27.000000. 459 Output index: 9, value is: 29.000000. 460 Output index: 10, value is: 31.000000. 461 Output index: 11, value is: 33.000000. 462 ``` 463 4644. 检查模型缓存(可选)。 465 466 如果在调测环境下,Neural Network Runtime对接的HDI服务支持模型缓存功能,执行完 `nnrt_example`, 可以在 `/data/local/tmp` 目录下找到生成的缓存文件。 467 468 > **说明:** 469 > 470 > 模型的IR需要传递到硬件驱动层,由HDI服务将统一的IR图,编译成硬件专用的计算图,编译的过程非常耗时。Neural Network Runtime支持计算图缓存的特性,可以将HDI服务编译生成的计算图,缓存到设备存储中。当下一次在同一个加速芯片上编译同一个模型时,通过指定缓存的路径,Neural Network Runtime可以直接加载缓存文件中的计算图,减少编译消耗的时间。 471 472 检查缓存目录下的缓存文件: 473 ```shell 474 ls /data/local/tmp 475 ``` 476 477 以下为打印结果: 478 ```text 479 # 0.nncache cache_info.nncache 480 ``` 481 482 如果缓存不再使用,需要手动删除缓存,可以参考以下命令,删除缓存文件。 483 ```shell 484 rm /data/local/tmp/*nncache 485 ``` 486 487## 相关实例 488 489第三方AI推理框架对接Neural Network Runtime的流程,可以参考以下相关实例: 490 491- [Tensorflow Lite接入NNRt Delegate开发指南](https://gitee.com/openharmony/ai_neural_network_runtime/tree/master/example/deep_learning_framework) 492 493