1# NNRt设备开发指导 2 3## NNRt开发概述 4 5### 功能简介 6 7Neural Network Runtime(NNRt, 神经网络运行时)是面向AI领域的跨芯片推理计算运行时,作为中间桥梁连通上层AI推理框架和底层加速芯片,实现AI模型的跨芯片推理计算。 8 9本文介绍芯片厂商如何在将专有加速芯片接入NNRt,接入OpenHarmony社区生态。 10 11### 基本概念 12在开发前,开发者需要先了解以下概念,以便更好地理解全文内容: 13 14- NNRt:Neural Network Runtime,神经网络运行时,是本指导主要介绍的部件。 15- OHOS:OpenHarmony Operating System,OpenHarmony操作系统。 16- HDI:Hardware Device Interface,硬件设备接口,是OHOS中系统组件与芯片组件通信的接口。 17- IDL:Interface Description Language,接口描述语言,是HDI接口的语言格式。 18 19### 约束与限制 20- 系统版本:OpenHarmony master分支。 21- 开发环境:Ubuntu 18.04及以上。 22- 接入设备:OpenHarmony定义的标准设备。 23 24### 运作机制 25NNRt通过HDI接口实现与设备芯片的对接,由HDI接口实现跨进程通信。 26 27**图1** NNRt架构图 28 29 30 31整个架构主要分为三层,AI应用在应用层,AI推理框架和神经网络运行时在系统层,设备服务在芯片层。AI应用要在专用加速芯片上完成模型推理,需要经过AI推理框架和神经网络运行时才能调用到底层的芯片设备,而神经网络运行时就是负责适配底层各种芯片设备,它开放了标准统一的南向接口,众多的第三方芯片设备都可以通过HDI接口接入OHOS。 32 33程序运行时,AI应用、AI推理框架、神经网络运行时都在同一个进程,底层设备服务在另一个进程,进程间是通过IPC的机制通信,神经网络运行时根据南向HDI接口实现了HDI Client,服务端也需要根据南向HDI接口实现HDI Service。 34 35## NNRt开发指导 36 37### 场景介绍 38下文以rk3568芯片为例,展示rk3568 CPU如何通过HDI接口接入NNRt,并完成AI模型推理。 39> 依赖说明:该教程展示的rk3568 CPU接入NNRt并没有实际去写CPU的驱动,而是借用了Mindspore-Lite的CPU算子,故会依赖MindSpore-Lite的动态库以及头文件,实际开发时并不需要依赖MindSpore-Lite的任何库或者头文件。 40 41### 开发流程 42适配操作的整体流程如下: 43 44**图2** NNRt适配流程 45 46 47 48### 开发步骤 49开发者具体可通过以下步骤在芯片侧对接NNRt: 501. 开源社区下载OpenHarmony的代码,编译drivers_interface部件,生成HDI接口的头文件。 51 - [下载源码](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md)。 52 - 编译接口IDL文件。 53 ```shell 54 ./build.sh --product-name rk3568 –ccache --build-target drivers_interface_nnrt 55 ``` 56 57 编译之后,可以在```out/rk3568/gen/drivers/interface/nnrt```目录下找到生成的头文件,默认生成C++头文件,若需要生成C头文件,则修改```drivers/interface/nnrt/v1_0/BUILD.gn```文件中的language。 58 ```shell 59 language = "c" 60 ``` 61 62 生成头文件目录如下所示: 63 ```text 64 out/rk3568/gen/drivers/interface/nnrt 65 └── v1_0 66 ├── drivers_interface_nnrt__libnnrt_proxy_1.0_external_deps_temp.json 67 ├── drivers_interface_nnrt__libnnrt_stub_1.0_external_deps_temp.json 68 ├── innrt_device.h # 设备接口头文件 69 ├── iprepared_model.h # 编译AI模型对象头文件 70 ├── libnnrt_proxy_1.0__notice.d 71 ├── libnnrt_stub_1.0__notice.d 72 ├── model_types.cpp # AI模型结构定义实现文件 73 ├── model_types.h # AI模型结构定义头文件 74 ├── nnrt_device_driver.cpp # 设备驱动实现参考样例 75 ├── nnrt_device_proxy.cpp 76 ├── nnrt_device_proxy.h 77 ├── nnrt_device_service.cpp # 设备服务端实现参考样例 78 ├── nnrt_device_service.h # 设备服务端头文件 79 ├── nnrt_device_stub.cpp 80 ├── nnrt_device_stub.h 81 ├── nnrt_types.cpp # 数据类型定义实现文件 82 ├── nnrt_types.h # 数据类型定义头文件 83 ├── node_attr_types.cpp # AI模型算子属性定义实现文件 84 ├── node_attr_types.h # AI模型算子属性定义 85 ├── prepared_model_proxy.cpp 86 ├── prepared_model_proxy.h 87 ├── prepared_model_service.cpp # 编译AI模型对象服务端实现参考样例 88 ├── prepared_model_service.h # 编译AI模型对象服务端头文件 89 ├── prepared_model_stub.cpp 90 └── prepared_model_stub.h 91 ``` 92 932. 实现HDI服务。 94 - 在drivers/peripheral目录下新建开发目录,用于HDI服务开发,开发目录结构如下所示。 95 ```text 96 drivers/peripheral/nnrt 97 ├── BUILD.gn # 代码编译脚本文件 98 ├── bundle.json 99 └── hdi_cpu_service # 自定义目录 100 ├── BUILD.gn # 代码编译脚本文件 101 ├── include 102 │ ├── nnrt_device_service.h # 设备服务端头文件 103 │ ├── node_functions.h # 非必须,由具体实现决定 104 │ ├── node_registry.h # 非必须,由具体实现决定 105 │ └── prepared_model_service.h # 编译AI模型对象服务端头文件 106 └── src 107 ├── nnrt_device_driver.cpp # 设备驱动实现文件 108 ├── nnrt_device_service.cpp # 设备服务端实现文件 109 ├── nnrt_device_stub.cpp # 非必须,由具体实现决定 110 ├── node_attr_types.cpp # 非必须,由具体实现决定 111 ├── node_functions.cpp # 非必须,由具体实现决定 112 ├── node_registry.cpp # 非必须,由具体实现决定 113 └── prepared_model_service.cpp # 编译AI模型对象服务端实现文件 114 ``` 115 116 - 实现设备驱动,无特殊需求可直接使用步骤1中生成的nnrt_device_driver.cpp文件,否则根据具体驱动开发。 117 - 实现服务接口,主要实现nnrt_device_service.cpp和prepared_model_service.cpp文件,接口定义可以参考```drivers/interface/nnrt```。 118 119 - 编译驱动和服务实现为共享库。 120 在```drivers/peripheral/nnrt/hdi_cpu_service/```下新建```BUILD.gn```文件,对驱动入口和服务实现编译为共享库。 121 122 ```shell 123 import("//build/ohos.gni") 124 import("//drivers/hdf_core/adapter/uhdf2/uhdf.gni") 125 126 ohos_shared_library("libnnrt_service_1.0") { 127 include_dirs = [] 128 sources = [ 129 "src/nnrt_device_service.cpp", 130 "src/prepared_model_service.cpp", 131 "src/node_registry.cpp", 132 "src/node_functions.cpp", 133 "src/node_attr_types.cpp" 134 ] 135 public_deps = [ "//drivers/interface/nnrt/v1_0:nnrt_idl_headers" ] 136 external_deps = [ 137 "hdf_core:libhdf_utils", 138 "hiviewdfx_hilog_native:libhilog", 139 "ipc:ipc_single", 140 "c_utils:utils", 141 ] 142 143 install_images = [ chipset_base_dir ] 144 subsystem_name = "hdf" 145 part_name = "drivers_peripheral_nnrt" 146 } 147 148 ohos_shared_library("libnnrt_driver") { 149 include_dirs = [] 150 sources = [ "src/nnr_device_driver.cpp" ] 151 deps = [ "//drivers/peripheral/nnrt/hdi_cpu_service:libnnrt_service_1.0" ] 152 153 external_deps = [ 154 "hdf_core:libhdf_host", 155 "hdf_core:libhdf_ipc_adapter", 156 "hdf_core:libhdf_utils", 157 "hiviewdfx_hilog_native:libhilog", 158 "ipc:ipc_single", 159 "c_utils:utils", 160 ] 161 162 install_images = [ chipset_base_dir ] 163 subsystem_name = "hdf" 164 part_name = "drivers_peripheral_nnrt" 165 } 166 167 group("hdf_nnrt_service") { 168 deps = [ 169 ":libnnrt_driver", 170 ":libnnrt_service_1.0", 171 ] 172 } 173 ``` 174 175 将```group("hdf_nnrt_service")```添加到```drivers/peripheral/nnrt/BUILD.gn```文件中。 176 ```shell 177 if (defined(ohos_lite)) { 178 group("nnrt_entry") { 179 deps = [ ] 180 } 181 } else { 182 group("nnrt_entry") { 183 deps = [ 184 "./hdi_cpu_service:hdf_nnrt_service", 185 ] 186 } 187 } 188 ``` 189 190 新建```drivers/peripheral/nnrt/bundle.json```用于定义新增的```drivers_peripheral_nnrt```部件。 191 ```json 192 { 193 "name": "drivers_peripheral_nnrt", 194 "description": "Neural network runtime device driver", 195 "version": "3.2", 196 "license": "Apache License 2.0", 197 "component": { 198 "name": "drivers_peripheral_nnrt", 199 "subsystem": "hdf", 200 "syscap": [""], 201 "adapter_system_type": ["standard"], 202 "rom": "1024KB", 203 "ram": "2048KB", 204 "deps": { 205 "components": [ 206 "ipc", 207 "hdf_core", 208 "hiviewdfx_hilog_native", 209 "c_utils" 210 ], 211 "third_part": [ 212 "bounds_checking_function" 213 ] 214 }, 215 "build": { 216 "sub_component": [ 217 "//drivers/peripheral/nnrt:nnrt_entry" 218 ], 219 "test": [ 220 ], 221 "inner_kits": [ 222 ] 223 } 224 } 225 } 226 ``` 227 2283. 声明HDI服务。 229 230 在对应产品的uhdf hcs配置文件中声明用户态驱动与服务,本例中rk3568对应在```vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs```文件中新增如下配置: 231 ```text 232 nnrt :: host { 233 hostName = "nnrt_host"; 234 priority = 50; 235 uid = ""; 236 gid = ""; 237 caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; 238 nnrt_device :: device { 239 device0 :: deviceNode { 240 policy = 2; 241 priority = 100; 242 moduleName = "libnnrt_driver.z.so"; 243 serviceName = "nnrt_device_service"; 244 } 245 } 246 } 247 ``` 248 > 注意:修改hcs文件后请删除out目录重新编译,才能生效。 249 2504. 配置host进程用户和组。 251 252 对于新增host进程的场景,需要新增配置对应进程的用户ID和组ID。 进程的用户ID在文件```base/startup/init/services/etc/passwd```中配置,进程的组ID在文件```base/startup/init/services/etc/group```中配置。 253 ```text 254 # 在base/startup/init/services/etc/passwd新增 255 nnrt_host:x:3311:3311:::/bin/false 256 257 # 在base/startup/init/services/etc/group新增 258 nnrt_host:x:3311: 259 ``` 260 完成上述所有配置后,全量编译版本后应该可以观察到新增host进程启动,也可以通过hilog输出检索新增的服务名称nnrt_interface_service观察到服务发布成功。 261 2625. SELinux配置。 263 264 OHOS已经开启SELinux特性,需要对新增的进程和服务配置相应的SELinux规则,用于运行host进程启动访问某些资源、发布HDI服务。对于调用者来说,也需要配置SELinux规则运行获取和调用某个HDI服务。 265 266 在```base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/type.te```文件中配置nnrt_host进程安全上下文,新增配置如下: 267 ```text 268 # 新增配置 269 type nnrt_host, hdfdomain, domain; 270 ``` 271 272 由于SeLinux是白名单访问的权限机制,需要根据实际权限需求配置,将服务启动起来之后,通过以下dmesg命令可能查看avc告警, 273 avc告警会给出缺少的权限,SeLinux的配置也可以参考[OpenHarmony SeLinux子系统的说明](https://gitee.com/openharmony/security_selinux/blob/master/README.md)。 274 ```shell 275 hdc_std shell 276 dmesg | grep nnrt 277 ``` 278 279 新建nnrt_host.te配置文件,将权限配置到nnrt_host.te文件中。 280 ```shell 281 # 创建nnrt文件夹 282 mkdir base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt 283 284 # 创建vendor文件夹 285 mkdir base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt/vendor 286 287 # 创建nnrt_host.te文件 288 touch base/security/selinux/sepolicy/ohos_policy/drivers/peripheral/nnrt/vendor/nnrt_host.te 289 ``` 290 291 然后再将所需的权限写入nnrt_host.te文件中,比如: 292 ```text 293 allow nnrt_host dev_hdf_kevent:chr_file { ioctl }; 294 allow nnrt_host hilog_param:file { read }; 295 allow nnrt_host sh:binder { transfer }; 296 allow nnrt_host dev_ashmem_file:chr_file { open }; 297 allow sh nnrt_host:fd { use }; 298 ``` 299 3006. 删除out目录编译整个系统。 301 ```shell 302 # 删除out目录 303 rm -rf ./out 304 305 # 编译 306 ./build.sh --product-name rk3568 –ccache --jobs=4 307 ``` 308 309 310### 调测验证 311服务开发完成后,可以使用XTS用例验证基本功能和兼容性,开发者可通过以下步骤进行验证: 3121. 编译NNRt的hats用例,用例在```test/xts/hats/hdf/nnrt```目录下,编译NNRt的hats用例。 313 ```shell 314 # 进入hats目录 315 cd test/xts/hats 316 317 # 编译hats测试用例 318 ./build.sh suite=hats system_size=standard --product-name rk3568 319 320 # 回到代码根目录 321 cd - 322 ``` 323 编译好的测试用例会输出到相对代码根目录的out/rk3568/suites/hats/testcases/HatsHdfNnrtFunctionTest路径下。 324 3252. 将测试用例push到设备上。 326 ```shell 327 # 将测试用例可执行文件推送到设备上,HatsHdfNnrtFunctionTest是测试用例可执行文件。 328 hdc_std file send out/rk3568/suites/hats/testcases/HartsHdfNnrtFunctionTest /data/local/tmp/ 329 330 # 给测试用例可执行文件加上权限。 331 hdc_std shell "chmod +x /data/local/tmp/HatsHdfNnrtFunctionTest" 332 ``` 333 3343. 执行用例并查看结果。 335 ```shell 336 # 执行测试用例 337 hdc_std shell "/data/local/tmp/HatsHdfNnrtFunctionTest" 338 ``` 339 340 所有hats用例执行成功,可以看到测试报告通过47个用例: 341 ```text 342 ... 343 [----------] Global test environment tear-down 344 Gtest xml output finished 345 [==========] 47 tests from 3 test suites ran. (515 ms total) 346 [ PASSED ] 47 tests. 347 ``` 348 349### 开发实例 350完整Demo代码可以参考[社区实现](../drivers/nnrt/)。 3511. 拷贝```example/driver/nnrt```目录到```drivers/peripheral```路径下。 352 ```shell 353 cp -r example/driver/nnrt drivers/peripheral 354 ``` 3552. 补充bundle.json文件到```drivers/peripheral/nnrt```,bundle.json参考本教程上面的[开发步骤](#开发步骤)章节。 3563. 由于Demo依赖MindSpore-Lite CPU算子,故需要添加MindSpore-Lite依赖文件。 357 - 下载MindSpore-Lite的头文件,[mindspore 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)。 358 - 在```drivers/peripheral/nnrt```目录下新建mindspore目录,用于存放mindspore依赖库和头文件。 359 ```shell 360 mkdir drivers/peripheral/nnrt/mindspore 361 ``` 362 - 解压mindspore-lite-1.5.0-linux-x64.tar.gz文件,将```runtime/include```目录拷贝到```drivers/peripheral/nnrt/mindspore```目录下。 363 - Demo还依赖mindspore的schema文件。 364 ```shell 365 # 创建mindspore_schema目录 366 mkdir drivers/peripheral/nnrt/hdi_cpu_service/include/mindspore_schema 367 368 # 拷贝mindspore schema文件 369 cp third_party/mindspore/mindspore/lite/schema/* drivers/peripheral/nnrt/hdi_cpu_service/include/mindspore_schema/ 370 ``` 371 - 编译MindSpore-Lite的动态库,并将动态库放到mindspore目录下。 372 ```shell 373 # 编译mindspore动态库 374 ./build.sh --product-name rk3568 -ccaache --jobs 4 --build-target mindspore_lib 375 376 # 将mindspore动态库 377 mkdir drivers/peripheral/nnrt/mindspore/mindspore 378 379 # 将mindspore动态拷贝到drivers/peripheral/nnrt/mindspore/mindspore。 380 cp out/rk3568/package/phone/system/lib/libmindspore-lite.huawei.so drivers/peripheral/nnrt/mindspore/mindspore/ 381 ``` 382 4. 其他配置请参考本教程上面的[开发步骤](#开发步骤)章节。