1# Neural Network Runtime设备接入指导
2
3## 概述
4
5### 功能简介
6
7NNRt(Neural Network Runtime,神经网络运行时)是面向AI领域的跨芯片推理计算运行时,作为中间桥梁连通上层AI推理框架和底层加速芯片,实现AI模型的跨芯片推理计算。
8
9NNRt开放了设备接口,芯片厂商通过设备接口将专有加速芯片接入NNRt,从而实现与OpenHarmony社区生态的对接。以下内容将介绍芯片如何接入NNRt。
10
11### 基本概念
12在开发前,开发者需要先了解以下概念,以便更好地理解全文内容:
13
14- HDI(Hardware Device Interface):OpenHarmony硬件设备接口,定义系统中跨进程通信的接口,实现服务间的跨进程通信。
15- IDL(Interface Description Language):接口描述语言,是HDI接口的语言格式。
16
17### 约束与限制
18- 系统版本:OpenHarmony主干版本。
19- 开发环境:Ubuntu 18.04及以上。
20- 接入设备:具备AI计算能力的芯片。
21
22### 运作机制
23NNRt通过HDI接口实现与设备芯片的对接,由HDI接口实现跨进程通信。
24
25**图1** NNRt架构图
26
27![架构图](./figures/nnrt_arch_diagram.png)
28
29整个架构主要分为三层,AI应用在应用层,AI推理框架和NNRt在系统层,设备服务在芯片层。AI应用如果要使用AI专用加速芯片进行模型推理,需要经过AI推理框架和NNRt才能调用到底层AI专用加速芯片,NNRt就是负责适配底层各种AI专用加速芯片的中间层。NNRt开放了标准统一的HDI设备接口,众多AI专用加速芯片都可以通过HDI接口接入OpenHarmony。此外NNRt也开放了标准统一的接口对接上层各种AI推理框架。
30
31程序运行时,AI应用、AI推理框架、NNRt都运行在用户进程中,底层AI芯片设备服务运行在HDI服务进程中。NNRt根据HDI接口实现了HDI Client,服务端也需要根据HDI接口实现HDI Service,两者通过OpenHarmony标准的HDF子系统实现跨进程通信。
32
33## 开发指导
34
35### 场景介绍
36当需要将一款AI加速芯片接入NNRt的时候,可以参考下文。下文以RK3568芯片为例,展示RK3568 CPU如何通过NNRt的V2.0版本HDI接口接入NNRt,并完成AI模型推理。V1.0版本HDI接口接入NNRt的流程与此类似。
37> 依赖说明:该教程展示的RK3568 CPU接入NNRt并没有真正实现CPU的驱动,而是借用了MindSpore Lite的runtime和CPU算子,因此会依赖MindSpore Lite的动态库以及头文件。实际开发时并不需要依赖MindSpore Lite的任何库或者头文件。
38
39### 开发流程
40AI专用加速芯片接入NNRt的整体流程如下:
41
42**图2** AI专用加速芯片接入NNRt流程
43
44![开发流程](./figures/nnrt_dev_flow.png)
45
46### 开发步骤
47AI芯片设备HDI服务开发者具体可通过以下步骤实现AI专用加速芯片对接NNRt:
48#### 生成HDI头文件
49开源社区下载OpenHarmony的代码,编译drivers_interface部件,生成HDI接口的头文件。
50
511. [下载源码](../get-code/sourcecode-acquire.md)。
52
532. 进入OpenHarmony源码根目录,编译NNRt的IDL接口文件(以RK3568产品为例):
54    ```shell
55    ./build.sh --product-name rk3568 –ccache --build-target drivers_interface_nnrt
56    ```
57
58    编译完成后,会在```out/rk3568/gen/drivers/interface/nnrt/v2_0```目录下生成C++类型的HDI头文件。若需要生成C类型的头文件,请在编译之前使用如下命令对```drivers/interface/nnrt/v2_0/BUILD.gn```文件中的```language```配置项进行设置。
59
60    ```shell
61    language = "c"
62    ```
63
64    生成头文件目录如下所示:
65    ```text
66    out/rk3568/gen/drivers/interface/nnrt
67    └── v2_0
68        ├── drivers_interface_nnrt__libnnrt_proxy_2.0_external_deps_temp.json
69        ├── drivers_interface_nnrt__libnnrt_stub_2.0_external_deps_temp.json
70        ├── innrt_device.h                        # 设备接口头文件
71        ├── iprepared_model.h                     # 编译AI模型对象头文件
72        ├── libnnrt_proxy_2.0__notice.d
73        ├── libnnrt_stub_2.0__notice.d
74        ├── model_types.cpp                       # AI模型结构定义实现文件
75        ├── model_types.h                         # AI模型结构定义头文件
76        ├── nnrt_device_driver.cpp                # 设备驱动实现参考样例
77        ├── nnrt_device_proxy.cpp
78        ├── nnrt_device_proxy.h
79        ├── nnrt_device_service.cpp               # 设备服务端实现参考样例
80        ├── nnrt_device_service.h                 # 设备服务端头文件
81        ├── nnrt_device_stub.cpp
82        ├── nnrt_device_stub.h
83        ├── nnrt_types.cpp                        # 数据类型定义实现文件
84        ├── nnrt_types.h                          # 数据类型定义头文件
85        ├── node_attr_types.cpp                   # AI模型算子属性定义实现文件
86        ├── node_attr_types.h                     # AI模型算子属性定义
87        ├── prepared_model_proxy.cpp
88        ├── prepared_model_proxy.h
89        ├── prepared_model_service.cpp            # 编译AI模型对象服务端实现参考样例
90        ├── prepared_model_service.h              # 编译AI模型对象服务端头文件
91        ├── prepared_model_stub.cpp
92        └── prepared_model_stub.h
93    ```
94
95#### 实现HDI服务
96
971. 进入OpenHarmony源码根目录,在```drivers/peripheral```目录下新建开发目录```nnrt```,用于HDI服务开发。开发目录结构如下所示:
98    ```text
99    drivers/peripheral/nnrt
100    ├── bundle.json
101    ├── v2_0
102        ├── BUILD.gn                                  # 代码编译脚本文件
103        └── hdi_cpu_service                           # 自定义目录
104            ├── BUILD.gn                              # 代码编译脚本文件
105            ├── include
106            │   ├── nnrt_device_service.h             # 设备服务端头文件
107            │   ├── node_functions.h                  # 非必须,由具体实现决定
108            │   ├── node_registry.h                   # 非必须,由具体实现决定
109            │   └── prepared_model_service.h          # 编译AI模型对象服务端头文件
110            └── src
111                ├── nnrt_device_driver.cpp            # 设备驱动实现文件
112                ├── nnrt_device_service.cpp           # 设备服务端实现文件
113                ├── nnrt_device_stub.cpp              # 非必须,由具体实现决定
114                ├── node_attr_types.cpp               # 非必须,由具体实现决定
115                ├── node_functions.cpp                # 非必须,由具体实现决定
116                ├── node_registry.cpp                 # 非必须,由具体实现决定
117                └── prepared_model_service.cpp        # 编译AI模型对象服务端实现文件
118    ```
119
1202. 实现设备驱动,无特殊需求可直接使用IDL文件编译生成的```nnrt_device_driver.cpp```文件,否则根据具体驱动开发。
121
1223. 实现服务接口,可参考```nnrt_device_service.cpp```和```prepared_model_service.cpp```实现文件,接口定义可以参考[NNRt的HDI接口定义](https://gitee.com/openharmony/drivers_interface/tree/master/nnrt)123
1244. 编译驱动和服务的实现文件为共享库。
125
126    在```drivers/peripheral/nnrt/v2_0/hdi_cpu_service/```下新建```BUILD.gn```文件,文件内容如下所示,相关参数配置内容可参考[Build教程](https://gitee.com/openharmony/build)127
128    ```shell
129    import("//build/ohos.gni")
130    import("//drivers/hdf_core/adapter/uhdf2/uhdf.gni")
131
132    ohos_shared_library("libnnrt_service_2.0") {
133      include_dirs = []
134      sources = [
135        "src/nnrt_device_service.cpp",
136        "src/node_functions.cpp",
137        "src/node_registry.cpp",
138        "src/prepared_model_service.cpp",
139        "src/shared_buffer_parser.cpp",
140        "src/validation.cpp",
141      ]
142
143      external_deps = [
144        "c_utils:utils",
145        "drivers_interface_nnrt:libnnrt_stub_2.0",
146        "hdf_core:libhdf_utils",
147        "hilog_native:libhilog",
148        "ipc:ipc_core",
149      ]
150
151      install_images = [ chipset_base_dir ]
152      subsystem_name = "hdf"
153      part_name = "drivers_peripheral_nnrt"
154    }
155
156    ohos_shared_library("libnnrt_driver") {
157      include_dirs = []
158      sources = [ "src/nnr_device_driver.cpp" ]
159      deps = [ ":libnnrt_service_2.0" ]
160
161      external_deps = [
162        "c_utils:utils",
163        "drivers_interface_nnrt:libnnrt_stub_2.0",
164        "hdf_core:libhdf_host",
165        "hdf_core:libhdf_ipc_adapter",
166        "hdf_core:libhdf_utils",
167        "hdf_core:libhdi",
168        "hilog_native:libhilog",
169        "ipc:ipc_core",
170      ]
171
172      install_images = [ chipset_base_dir ]
173      subsystem_name = "hdf"
174      part_name = "drivers_peripheral_nnrt"
175    }
176
177    group("hdf_nnrt_service") {
178      deps = [
179        ":libnnrt_driver",
180        ":libnnrt_service_2.0",
181      ]
182    }
183    ```
184
185    将```group("hdf_nnrt_service")```添加到```drivers/peripheral/nnrt/v2_0/BUILD.gn```文件中,以便在更上目录层级就能引用。
186    ```shell
187    if (defined(ohos_lite)) {
188      group("nnrt_entry") {
189        deps = []
190      }
191    } else {
192      group("nnrt_entry") {
193        deps = [ "./hdi_cpu_service:hdf_nnrt_service" ]
194      }
195    }
196    ```
197
198    新建```drivers/peripheral/nnrt/bundle.json```用于定义新增的```drivers_peripheral_nnrt```部件。
199    ```json
200    {
201      "name": "drivers_peripheral_nnrt",
202      "description": "Neural network runtime device driver",
203      "version": "4.0",
204      "license": "Apache License 2.0",
205      "component": {
206        "name": "drivers_peripheral_nnrt",
207        "subsystem": "hdf",
208        "syscap": [""],
209        "adapter_system_type": ["standard"],
210        "rom": "1024KB",
211        "ram": "2048KB",
212        "deps": {
213          "components": [
214            "c_utils",
215            "hdf_core",
216            "hilog_native",
217            "ipc"
218          ],
219          "third_part": [
220            "bounds_checking_function"
221          ]
222        },
223        "build": {
224          "sub_component": [
225            "//drivers/peripheral/nnrt/v2_0:nnrt_entry"
226          ],
227          "test": [
228          ],
229          "inner_kits": [
230          ]
231        }
232      }
233    }
234    ```
235
236#### 声明HDI服务
237
238  在对应产品的uhdf hcs配置文件中声明NNRt的用户态驱动与服务。例如针对RK3568,服务需要在```vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs```文件中新增如下配置:
239  ```text
240  nnrt :: host {
241      hostName = "nnrt_host";
242      priority = 50;
243      uid = "";
244      gid = "";
245      caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"];
246      nnrt_device :: device {
247          device0 :: deviceNode {
248              policy = 2;
249              priority = 100;
250              moduleName = "libnnrt_driver.z.so";
251              serviceName = "nnrt_device_service";
252          }
253      }
254  }
255  ```
256> 注意:修改hcs文件需要删除out目录重新编译,才能生效。
257
258#### 配置host进程用户ID和组ID
259  对于新增的nnrt_host进程的场景,需要配置对应进程的用户ID和组ID。 进程的用户ID在文件```base/startup/init/services/etc/passwd```中配置,进程的组ID在文件```base/startup/init/services/etc/group```中配置。
260  ```text
261  # 在base/startup/init/services/etc/passwd新增
262  nnrt_host:x:3311:3311:::/bin/false
263
264  # 在base/startup/init/services/etc/group新增
265  nnrt_host:x:3311:
266  ```
267
268#### 配置SELinux
269
270OpenHarmony已经开启SELinux特性,需要对新增的进程和服务配置相应的SELinux规则,用于运行host进程访问某些资源、发布HDI服务等。
271
2721. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/public/hdf_service_contexts```文件中新增配置:
273    ```text
274    # 新增配置
275    nnrt_device_service        u:object_r:hdf_nnrt_device_service:s0
276    ```
277    > ```nnrt_host```为[声明HDI服务](#声明hdi服务)步骤中配置的进程名称,下同。
278
2792. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/public/hdf_service.te```文件中新增配置:
280    ```text
281    # 新增配置
282    type hdf_nnrt_device_service, hdf_service_attr;
283    ```
284
2853. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/public/hdfdomain.te```文件中新增配置:
286    ```text
287    # 新增配置
288    neverallow { domain -hdfdomain -sadomain } { hdfdomain -nnrt_host -allocator_host -hdf_public_domain }:binder call;
289    ```
290
2914. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/public/type.te```文件中新增配置:
292    ```text
293    # 新增配置
294    type nnrt_host, hdfdomain, domain;
295    ```
296
2975. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/hdf_devmgr.te```文件中新增配置:
298    ```text
299    # 新增配置
300    allow hdf_devmgr nnrt_host:binder { call transfer };
301    allow hdf_devmgr nnrt_host:dir { search };
302    allow hdf_devmgr nnrt_host:file { open read write };
303    allow hdf_devmgr nnrt_host:process { getattr };
304    ```
305
3066. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/init.te```文件中新增配置:
307    ```text
308    # 新增配置
309    allow init nnrt_host:process { rlimitinh siginh transition };
310    ```
311
3127. 在```base/security/selinux/sepolicy/ohos_policy/startup/init/public/chipset_init.te```文件中作如下修改:
313
314    找到chipset_init这一行:
315    ```text
316    allow chipset_init { light_host input_user_host wifi_host camera_host power_host audio_host }:process { rlimitinh siginh transition };
317    ```
318    在host列表中增加nnrt_host:
319    ```text
320    allow chipset_init { light_host input_user_host wifi_host camera_host power_host audio_host nnrt_host }:process { rlimitinh siginh transition };
321    ```
322
3238. 新建```nnrt_host.te```配置文件:
324    ```shell
325    # 创建nnrt文件夹
326    mkdir base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt
327
328    # 创建vendor文件夹
329    mkdir base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt/vendor
330
331    # 创建nnrt_host.te文件
332    touch base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt/vendor/nnrt_host.te
333    ```
334
3359. 将所需的权限写入```nnrt_host.te```文件中:
336    ```text
337    allow nnrt_host dev_hdf_kevent:chr_file { ioctl };
338    allow nnrt_host hilog_param:file { read open map };
339    allow nnrt_host sh:binder { transfer };
340    allow nnrt_host samgr:binder { call };
341    allow nnrt_host dev_ashmem_file:chr_file { open };
342    allow nnrt_host dev_unix_socket:dir { search };
343    allow nnrt_host hdf_device_manager:hdf_devmgr_class { get };
344    allow nnrt_host hdf_nnrt_device_service:hdf_devmgr_class { add get };
345    allow nnrt_host dev_console_file:chr_file { read write };
346    allow nnrt_host debug_param:file { read open map };
347    allow nnrt_host sa_device_service_manager:samgr_class { get };
348    allow nnrt_host hdf_devmgr:binder { call transfer };
349    allow nnrt_host hdf_nnrt_device_service:binder { call };
350    allow nnrt_host sysfs_devices_system_cpu:file { read open getattr };
351    allow sh hdf_nnrt_device_service:hdf_devmgr_class { add get };
352    allow sh hdf_hci_interface_service:hdf_devmgr_class { get };
353    allow sh nnrt_host:dir { getattr search };
354    allow sh nnrt_host:file { open read };
355    allow sh nnrt_host:process { getattr };
356    allow sh nnrt_host:binder { call };
357    allow sh nnrt_host:fd { use };
358    ```
359
36010. 由于SELinux是白名单访问的权限机制,需要根据实际权限需求配置。将服务启动之后,可通过以下dmesg命令查看avc告警,
361avc告警会给出缺少的权限。SELinux的配置也可以参考[OpenHarmony SELinux子系统的说明](https://gitee.com/openharmony/security_selinux/blob/master/README.md)362    ```shell
363    hdc_std shell
364    dmesg | grep nnrt
365    ```
366
367#### 配置部件编译入口
368以RK3568产品为例:
369```shell
370vim //productdefine/common/inherit/chipset_common.json
371```
372在```"subsystems"```, ```"subsystem":"hdf"```, ```"components"```中添加:
373```shell
374{
375  "component": "drivers_peripheral_nnrt",
376  "features": []
377}
378```
379
380#### 删除out目录并编译整个系统
381```shell
382# 删除out目录
383rm -rf ./out
384
385# 编译
386./build.sh --product-name rk3568 –ccache --jobs=4
387```
388
389
390### 调测验证
391服务开发完成后,可以使用XTS用例验证基本功能和兼容性。开发者可通过以下步骤进行验证:
392
3931. 编译NNRt的hats用例,用例在```test/xts/hats/ai/nnrt/hdi```目录下。
394    ```shell
395    # 进入hats目录
396    cd test/xts/hats
397
398    # 编译hats测试用例
399    ./build.sh suite=hats system_size=standard product_name=rk3568
400
401    # 回到代码根目录
402    cd -
403    ```
404    编译好的测试用例可执行文件```HatsHdfNnrtFunctionTest```会输出到```out/rk3568/suites/hats/testcases/```下。
405
4062. 将测试用例push到RK3568设备的```/data/local/tmp/```目录下。
407    ```shell
408    # 将测试用例可执行文件推送到设备上,HatsHdfNnrtFunctionTest是测试用例可执行文件。
409    hdc_std file send out/rk3568/suites/hats/testcases/HartsHdfNnrtFunctionTest /data/local/tmp/
410
411    # 给测试用例可执行文件加上权限。
412    hdc_std shell "chmod +x /data/local/tmp/HatsHdfNnrtFunctionTest"
413    ```
414
4153. 执行用例并查看结果。
416    ```shell
417    # 执行测试用例
418    hdc_std shell "/data/local/tmp/HatsHdfNnrtFunctionTest"
419    ```
420
421    测试报告显示已通过47个用例,说明所有hats用例已执行成功,服务已通过兼容性测试。
422    ```text
423    ...
424    [----------] Global test environment tear-down
425    Gtest xml output finished
426    [==========] 47 tests from 3 test suites ran. (515 ms total)
427    [  PASSED  ] 47 tests.
428    ```
429
430### 开发实例
431完整Demo代码可以参考[社区实现](https://gitee.com/openharmony/ai_neural_network_runtime/tree/master/example/drivers)432
4331. 进入OpenHarmony源码根目录,在```drivers/peripheral```路径下创建```nnrt```目录,拷贝NNRt源码路径```foundation/ai/neural_network_runtime```下的```example/driver/nnrt/v2_0```目录到```drivers/peripheral/nnrt```路径下。
434    ```shell
435    cp -r example/drivers/nnrt/v2_0 drivers/peripheral/nnrt
436    ```
437
4382. 在```drivers/peripheral/nnrt```下补充```bundle.json```文件,```bundle.json```的写法参考本教程上面[开发步骤](#开发步骤)中的[实现HDI服务](#实现hdi服务)章节。
439
4403. 由于Demo依赖MindSpore Lite CPU算子,因此需要添加MindSpore Lite依赖文件:
441    - 在OpenHarmony源码根目录执行以下命令编译MindSpore Lite动态库。MindSpore源码在OpenHarmony源码根目录third_party/mindspore位置下。
442      ```shell
443      # 编译mindspore动态库
444      ./build.sh --product-name rk3568 -ccaache --jobs 4 --build-target mindspore_lib
445      ```
446    - 在```drivers/peripheral/nnrt/v2_0```下创建```mindspore```目录,用于存放mindspore动态库和头文件。
447      ```shell
448      mkdir drivers/peripheral/nnrt/v2_0/mindspore
449      ```
450    - 将MindSpore源码中```mindspore-src/source/include```目录拷贝到```drivers/peripheral/nnrt/v2_0/mindspore```目录下。
451      ```shell
452      cp third_party/mindspore/mindspore-src/source/include drivers/peripheral/nnrt/v2_0/mindspore
453      ```
454    - Demo还依赖mindspore的```schema```文件:
455      ```shell
456      # 创建mindspore_schema目录
457      mkdir drivers/peripheral/nnrt/v2_0/hdi_cpu_service/include/mindspore_schema
458
459      # 从third_party目录拷贝mindspore schema文件
460      cp third_party/mindspore/mindspore-src/source/mindspore/lite/schema/* drivers/peripheral/nnrt/v2_0/hdi_cpu_service/include/mindspore_schema/
461      ```
462    - 将编译好的OpenHarmony的MindSpore Lite动态库拷贝到```mindspore```目录下。
463      ```shell
464      # 在drivers/peripheral/nnrt/v2_0/mindspore下创建mindspore目录
465      mkdir drivers/peripheral/nnrt/v2_0/mindspore/mindspore
466
467      # 从out目录将mindspore动态库拷贝到drivers/peripheral/nnrt/v2_0/mindspore/mindspore468      cp out/rk3568/package/phone/system/lib/libmindspore-lite.so drivers/peripheral/nnrt/v2_0/mindspore/mindspore/
469      ```
4704. 其他配置请参考本教程上面的[开发步骤](#开发步骤)章节。
471