• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = {&paramIndicesValues, 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, &paramIndices, &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