1# NNRt设备开发指导 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推理框架和NNRt才能调用到底层专用加速芯片,而NNRt就是负责适配底层各种专用加速芯片,它开放了标准统一的设备接口,众多的第三方芯片设备都可以通过HDI接口接入OpenHarmony。 30 31程序运行时,AI应用、AI推理框架、NNRt都在用户进程中,底层设备服务在服务进程中,NNRt根据HDI接口实现了HDI Client,服务端也需要根据HDI接口实现HDI Service,使得两个进程间能够跨进程通信。 32 33## 开发指导 34 35### 场景介绍 36当需要将一款AI加速芯片接入NNRt的时候,可以参考下文。下文以RK3568芯片为例,展示RK3568 CPU如何通过HDI接口接入NNRt,并完成AI模型推理。 37> 依赖说明:该教程展示的RK3568 CPU接入NNRt并没有实际去写CPU的驱动,而是借用了MindSpore Lite的CPU算子,故会依赖MindSpore Lite的动态库以及头文件,实际开发时并不需要依赖MindSpore Lite的任何库或者头文件。 38 39### 开发流程 40专用加速芯片接入NNRt的整体流程如下: 41 42**图2** 专用加速芯片接入NNRt流程 43 44![开发流程](./figures/nnrt_dev_flow.png) 45 46### 开发步骤 47开发者具体可通过以下步骤实现芯片对接NNRt: 48#### 生成HDI头文件 49开源社区下载OpenHarmony的代码,编译drivers_interface部件,生成HDI接口的头文件。 50 511. [下载源码](../get-code/sourcecode-acquire.md)。 52 532. 编译接口IDL文件。 54 ```shell 55 ./build.sh --product-name productname –ccache --build-target drivers_interface_nnrt 56 ``` 57 > productname为产品名称,此处为RK3568。 58 59 编译完成后,在```out/rk3568/gen/drivers/interface/nnrt```目录下生成HDI头文件,默认语言类型为C++。若需要生成C类型的头文件,请在编译之前使用如下命令对```drivers/interface/nnrt/v1_0/BUILD.gn```文件中的```language```配置项进行设置。 60 61 ```shell 62 language = "c" 63 ``` 64 65 生成头文件目录如下所示: 66 ```text 67 out/rk3568/gen/drivers/interface/nnrt 68 └── v1_0 69 ├── drivers_interface_nnrt__libnnrt_proxy_1.0_external_deps_temp.json 70 ├── drivers_interface_nnrt__libnnrt_stub_1.0_external_deps_temp.json 71 ├── innrt_device.h # 设备接口头文件 72 ├── iprepared_model.h # 编译AI模型对象头文件 73 ├── libnnrt_proxy_1.0__notice.d 74 ├── libnnrt_stub_1.0__notice.d 75 ├── model_types.cpp # AI模型结构定义实现文件 76 ├── model_types.h # AI模型结构定义头文件 77 ├── nnrt_device_driver.cpp # 设备驱动实现参考样例 78 ├── nnrt_device_proxy.cpp 79 ├── nnrt_device_proxy.h 80 ├── nnrt_device_service.cpp # 设备服务端实现参考样例 81 ├── nnrt_device_service.h # 设备服务端头文件 82 ├── nnrt_device_stub.cpp 83 ├── nnrt_device_stub.h 84 ├── nnrt_types.cpp # 数据类型定义实现文件 85 ├── nnrt_types.h # 数据类型定义头文件 86 ├── node_attr_types.cpp # AI模型算子属性定义实现文件 87 ├── node_attr_types.h # AI模型算子属性定义 88 ├── prepared_model_proxy.cpp 89 ├── prepared_model_proxy.h 90 ├── prepared_model_service.cpp # 编译AI模型对象服务端实现参考样例 91 ├── prepared_model_service.h # 编译AI模型对象服务端头文件 92 ├── prepared_model_stub.cpp 93 └── prepared_model_stub.h 94 ``` 95 96#### 实现HDI服务 97 981. 在drivers/peripheral目录下新建开发目录,用于HDI服务开发,开发目录结构如下所示: 99 ```text 100 drivers/peripheral/nnrt 101 ├── BUILD.gn # 代码编译脚本文件 102 ├── bundle.json 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. 实现设备驱动,无特殊需求可直接使用步骤1中生成的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/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_1.0") { 133 include_dirs = [] 134 sources = [ 135 "src/nnrt_device_service.cpp", 136 "src/prepared_model_service.cpp", 137 "src/node_registry.cpp", 138 "src/node_functions.cpp", 139 "src/node_attr_types.cpp" 140 ] 141 public_deps = [ "//drivers/interface/nnrt/v1_0:nnrt_idl_headers" ] 142 external_deps = [ 143 "hdf_core:libhdf_utils", 144 "hiviewdfx_hilog_native:libhilog", 145 "ipc:ipc_single", 146 "c_utils:utils", 147 ] 148 149 install_images = [ chipset_base_dir ] 150 subsystem_name = "hdf" 151 part_name = "drivers_peripheral_nnrt" 152 } 153 154 ohos_shared_library("libnnrt_driver") { 155 include_dirs = [] 156 sources = [ "src/nnr_device_driver.cpp" ] 157 deps = [ "//drivers/peripheral/nnrt/hdi_cpu_service:libnnrt_service_1.0" ] 158 159 external_deps = [ 160 "hdf_core:libhdf_host", 161 "hdf_core:libhdf_ipc_adapter", 162 "hdf_core:libhdf_utils", 163 "hiviewdfx_hilog_native:libhilog", 164 "ipc:ipc_single", 165 "c_utils:utils", 166 ] 167 168 install_images = [ chipset_base_dir ] 169 subsystem_name = "hdf" 170 part_name = "drivers_peripheral_nnrt" 171 } 172 173 group("hdf_nnrt_service") { 174 deps = [ 175 ":libnnrt_driver", 176 ":libnnrt_service_1.0", 177 ] 178 } 179 ``` 180 181 将```group("hdf_nnrt_service")```添加到```drivers/peripheral/nnrt/BUILD.gn```文件中,以便在更上目录层级就能引用。 182 ```shell 183 if (defined(ohos_lite)) { 184 group("nnrt_entry") { 185 deps = [ ] 186 } 187 } else { 188 group("nnrt_entry") { 189 deps = [ 190 "./hdi_cpu_service:hdf_nnrt_service", 191 ] 192 } 193 } 194 ``` 195 196 新建```drivers/peripheral/nnrt/bundle.json```用于定义新增的```drivers_peripheral_nnrt```部件。 197 ```json 198 { 199 "name": "drivers_peripheral_nnrt", 200 "description": "Neural network runtime device driver", 201 "version": "3.2", 202 "license": "Apache License 2.0", 203 "component": { 204 "name": "drivers_peripheral_nnrt", 205 "subsystem": "hdf", 206 "syscap": [""], 207 "adapter_system_type": ["standard"], 208 "rom": "1024KB", 209 "ram": "2048KB", 210 "deps": { 211 "components": [ 212 "ipc", 213 "hdf_core", 214 "hiviewdfx_hilog_native", 215 "c_utils" 216 ], 217 "third_part": [ 218 "bounds_checking_function" 219 ] 220 }, 221 "build": { 222 "sub_component": [ 223 "//drivers/peripheral/nnrt:nnrt_entry" 224 ], 225 "test": [ 226 ], 227 "inner_kits": [ 228 ] 229 } 230 } 231 } 232 ``` 233 234#### 声明HDI服务 235 236 在对应产品的uhdf hcs配置文件中声明用户态驱动与服务,例如针对RK3568服务需要在```vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs```文件中新增如下配置: 237 ```text 238 nnrt :: host { 239 hostName = "nnrt_host"; 240 priority = 50; 241 uid = ""; 242 gid = ""; 243 caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; 244 nnrt_device :: device { 245 device0 :: deviceNode { 246 policy = 2; 247 priority = 100; 248 moduleName = "libnnrt_driver.z.so"; 249 serviceName = "nnrt_device_service"; 250 } 251 } 252 } 253 ``` 254> 注意:修改hcs文件需要删除out目录重新编译,才能生效。 255 256#### 配置host进程用户ID和组ID 257 对于新增的nnrt_host进程的场景,需要配置对应进程的用户ID和组ID。 进程的用户ID在文件```base/startup/init/services/etc/passwd```中配置,进程的组ID在文件```base/startup/init/services/etc/group```中配置。 258 ```text 259 # 在base/startup/init/services/etc/passwd新增 260 nnrt_host:x:3311:3311:::/bin/false 261 262 # 在base/startup/init/services/etc/group新增 263 nnrt_host:x:3311: 264 ``` 265 266#### 配置SELinux 267 268OpenHarmony已经开启SELinux特性,需要对新增的进程和服务配置相应的SELinux规则,用于运行host进程启动访问某些资源、发布HDI服务。 269 2701. 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/type.te```文件中配置nnrt_host进程安全上下文,新增配置如下: 271 ```text 272 # 新增配置 273 type nnrt_host, hdfdomain, domain; 274 ``` 275 > nnrt_host为上文配置的进程名称。 276 2772. 由于SeLinux是白名单访问的权限机制,需要根据实际权限需求配置。将服务启动之后,可通过以下dmesg命令查看avc告警, 278avc告警会给出缺少的权限。SeLinux的配置也可以参考[OpenHarmony SeLinux子系统的说明](https://gitee.com/openharmony/security_selinux/blob/master/README.md)。 279 ```shell 280 hdc_std shell 281 dmesg | grep nnrt 282 ``` 283 2843. 新建nnrt_host.te配置文件,将权限配置到nnrt_host.te文件中。 285 ```shell 286 # 创建nnrt文件夹 287 mkdir base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt 288 289 # 创建vendor文件夹 290 mkdir base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt/vendor 291 292 # 创建nnrt_host.te文件 293 touch base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt/vendor/nnrt_host.te 294 ``` 295 2964. 将所需的权限写入nnrt_host.te文件中,比如: 297 ```text 298 allow nnrt_host dev_hdf_kevent:chr_file { ioctl }; 299 allow nnrt_host hilog_param:file { read }; 300 allow nnrt_host sh:binder { transfer }; 301 allow nnrt_host dev_ashmem_file:chr_file { open }; 302 allow sh nnrt_host:fd { use }; 303 ``` 304 305#### 配置部件编译入口 306以RK3568产品为例: 307```shell 308vim //productdefine/common/inherit/chipset_common.json 309``` 310在"subsystems", "subsystem":"hdf", "components"中添加: 311```shell 312{ 313 "component": "drivers_peripheral_foo", 314 "features": [] 315} 316``` 317 318#### 删除out目录并编译整个系统 319```shell 320# 删除out目录 321rm -rf ./out 322 323# 编译 324./build.sh --product-name rk3568 –ccache --jobs=4 325``` 326 327 328### 调测验证 329服务开发完成后,可以使用XTS用例验证基本功能和兼容性,开发者可通过以下步骤进行验证: 330 3311. 编译NNRt的hats用例,用例在```test/xts/hats/hdf/nnrt```目录下。 332 ```shell 333 # 进入hats目录 334 cd test/xts/hats 335 336 # 编译hats测试用例 337 ./build.sh suite=hats system_size=standard --product-name rk3568 338 339 # 回到代码根目录 340 cd - 341 ``` 342 编译好的测试用例会输出到相对代码根目录的```out/rk3568/suites/hats/testcases/HatsHdfNnrtFunctionTest```路径下。 343 3442. 将测试用例push到设备上。 345 ```shell 346 # 将测试用例可执行文件推送到设备上,HatsHdfNnrtFunctionTest是测试用例可执行文件。 347 hdc_std file send out/rk3568/suites/hats/testcases/HartsHdfNnrtFunctionTest /data/local/tmp/ 348 349 # 给测试用例可执行文件加上权限。 350 hdc_std shell "chmod +x /data/local/tmp/HatsHdfNnrtFunctionTest" 351 ``` 352 3533. 执行用例并查看结果。 354 ```shell 355 # 执行测试用例 356 hdc_std shell "/data/local/tmp/HatsHdfNnrtFunctionTest" 357 ``` 358 359 所有hats用例执行成功,可以看到测试报告显示已通过47个用例,说明服务通过了兼容性测试。 360 ```text 361 ... 362 [----------] Global test environment tear-down 363 Gtest xml output finished 364 [==========] 47 tests from 3 test suites ran. (515 ms total) 365 [ PASSED ] 47 tests. 366 ``` 367 368### 开发实例 369完整Demo代码可以参考[社区实现](https://gitee.com/openharmony/ai_neural_network_runtime/tree/master/example/drivers)。 370 3711. 拷贝```example/driver/nnrt```目录到```drivers/peripheral```路径下。 372 ```shell 373 cp -r example/driver/nnrt drivers/peripheral 374 ``` 375 3762. 补充bundle.json文件到```drivers/peripheral/nnrt```,bundle.json参考本教程上面的[开发步骤](#开发步骤)章节。 377 3783. 由于Demo依赖MindSpore Lite CPU算子,故需要添加MindSpore Lite依赖文件。 379 - 下载MindSpore Lite的头文件,[MindSpore Lite 1.5.0](https://ms-release.obs.cn-north-4.myhuaweicloud.com/1.5.0/MindSpore/lite/release/linux/mindspore-lite-1.5.0-linux-x64.tar.gz)。 380 - 在```drivers/peripheral/nnrt```目录下新建mindspore目录,用于存放mindspore依赖库和头文件。 381 ```shell 382 mkdir drivers/peripheral/nnrt/mindspore 383 ``` 384 - 解压mindspore-lite-1.5.0-linux-x64.tar.gz文件,将```runtime/include```目录拷贝到```drivers/peripheral/nnrt/mindspore```目录下。 385 - Demo还依赖mindspore的schema文件。 386 ```shell 387 # 创建mindspore_schema目录 388 mkdir drivers/peripheral/nnrt/hdi_cpu_service/include/mindspore_schema 389 390 # 拷贝mindspore schema文件 391 cp third_party/mindspore/mindspore/lite/schema/* drivers/peripheral/nnrt/hdi_cpu_service/include/mindspore_schema/ 392 ``` 393 - 编译MindSpore Lite的动态库,并将动态库放到mindspore目录下。 394 ```shell 395 # 编译mindspore动态库 396 ./build.sh --product-name rk3568 -ccaache --jobs 4 --build-target mindspore_lib 397 398 # 将mindspore动态库 399 mkdir drivers/peripheral/nnrt/mindspore/mindspore 400 401 # 将mindspore动态拷贝到drivers/peripheral/nnrt/mindspore/mindspore。 402 cp out/rk3568/package/phone/system/lib/libmindspore-lite.huawei.so drivers/peripheral/nnrt/mindspore/mindspore/ 403 ``` 4044. 其他配置请参考本教程上面的[开发步骤](#开发步骤)章节。