适用场景 :拿到一块新的 RK3588 + OpenHarmony 6.1 开发板,快速验证 NPU 推理是否可用。 验证结果:通过 (api 2.3.2 / drv 0.9.8 / mobilenet_v1 RK3588 版推理成功 → ALL TESTS PASSED)
0. 前置条件
| 项目 | 说明 |
|---|---|
| 开发板 | RK3588 + OpenHarmony 6.1 标准系统(厂商镜像,基于 rockchip BSP 内核) |
| 调试工具 | hdc (HarmonyOS Device Connector),能 hdc shell |
| PC 编译环境 | WSL2 (Ubuntu-22.04) + GCC linaro 7.5.0 aarch64 交叉编译器 |
| RKNN2 SDK | rknpu2/runtime/Linux/ 目录(含 librknnrt.so、rknn_api.h) |
| 测试模型 | rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn(必须 RK3588 版,见 10.1) |
GCC linaro 路径(参考):
makefile
D:\Projects\RKNN3\gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu\gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu
RKNN2 SDK 路径(参考):
makefile
D:\Projects\rknn-toolkit2\rknpu2
1. 板端 NPU 驱动检查
通过 hdc shell 连上板子,依次执行:
bash
hdc shell
bash
# 1.1 NPU 驱动版本
cat /sys/kernel/debug/rknpu/version
实际输出 :
RKNPU driver: v0.9.8✅(与 RK3576 完全一致,均为 rockchip BSP 内核自带)
bash
# 1.2 NPU 设备节点
ls -la /dev/dri/renderD128
实际输出 :
crw-rw-rw- 1 root graphics 226, 128 ...✅(DRM GEM 方式)
bash
# 1.3 板端 libc 类型确认
ls /lib/ld-musl* /lib/ld-linux* 2>/dev/null
实际输出 :
/lib/ld-musl-aarch64.so.1✅(OH 6.1 使用 musl libc,这是后续需要 glibc 兼容层的原因)
1.4 关于 dmesg(本次实测为空输出,属正常)
bash
dmesg | grep -i rknpu
实际输出 :空。不是驱动没加载 (1.1 的 version 节点已证明驱动在工作),而是 OH 6.1 设置了
kernel.dmesg_restrict=1,hdc shell 默认非 root 读不了 dmesg。若确需查看驱动加载日志:
bashhdc shell su 0 "dmesg | grep -i rknpu"结论:此项是兜底检查,非必须,空输出不影响后续流程。
如果驱动未加载
检查内核配置:
bash
dmesg | grep -i rknpu
zcat /proc/config.gz | grep RKNPU 2>/dev/null
若 NPU 驱动未编译进内核,需要重新编译 OH 内核并启用 RKNPU 驱动(参考 GitHub rockchip-linux/kernel develop-6.1/drivers/rknpu,RK3588 官方支持零改动,compatible = "rockchip,rk3588-rknpu")。
2. 在板端搭建 glibc 兼容环境
由于 librknnrt.so 用 GCC linaro 编译、依赖 glibc,而 OH 6.1 使用 musl,需要自带 glibc 运行时。
2.1 从 GCC 工具链提取 glibc 文件
⚠️ 实测重要经验 :hdc 连续快速
file send(间隔 1-3 秒)会把 USB 通道打挂(USBBulkCallback1 failed),与调用方式(PowerShell/cmd/Bash)无关。推送多个文件务必逐条执行、间隔 ≥5 秒、每条推完先验证大小再推下一条。最稳妥的方式是让用户在 cmd 窗口手动逐条推送(本次实测手动逐条全部成功)。详见 7.5。
在板端创建目录:
bash
hdc shell "mkdir -p /data/glibc"
在 cmd 窗口(Win+R → 输入 cmd 回车),进入 glibc 库目录后逐条推送(每次等输出 FileTransfer finish 再执行下一条):
cmd
cd /d D:\Projects\RKNN3\gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu\gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu\aarch64-linux-gnu\libc\lib
D:\hdc\hdc.exe file send ld-2.25.so /data/glibc/ld-2.25.so
D:\hdc\hdc.exe file send libc-2.25.so /data/glibc/libc-2.25.so
D:\hdc\hdc.exe file send libm-2.25.so /data/glibc/libm-2.25.so
D:\hdc\hdc.exe file send libdl-2.25.so /data/glibc/libdl-2.25.so
D:\hdc\hdc.exe file send libpthread-2.25.so /data/glibc/libpthread-2.25.so
D:\hdc\hdc.exe file send libstdc++.so.6.0.24 /data/glibc/libstdc++.so.6.0.24
D:\hdc\hdc.exe file send libgcc_s.so.1 /data/glibc/libgcc_s.so.1
D:\hdc\hdc.exe file send librt-2.25.so /data/glibc/librt-2.25.so
注意 :部分板子出厂
/data/glibc可能已带ld-2.25.so、libc-2.25.so(本次实测出厂就有),推送前先hdc shell "ls -la /data/glibc/"查缺,避免重复。
2.2 创建 SONAME 符号链接 + 修复 ld 执行权限
⚠️ 本次实测新坑(RK3576 文档未记录) :本板出厂
ld-2.25.so权限为 644(无执行位) ,若不修复,运行时动态链接器报Permission denied。务必先执行 2.2 节第 1 条chmod。
在板端 shell 中:
bash
# ★ 先修权限(本次实测必须,否则第 5 节报 Permission denied)
chmod 755 /data/glibc/ld-2.25.so
# 创建 7 个 SONAME 符号链接(libgcc_s.so.1 本身就是 SONAME,不用建)
cd /data/glibc
ln -sf ld-2.25.so ld-linux-aarch64.so.1
ln -sf libc-2.25.so libc.so.6
ln -sf libm-2.25.so libm.so.6
ln -sf libdl-2.25.so libdl.so.2
ln -sf libpthread-2.25.so libpthread.so.0
ln -sf libstdc++.so.6.0.24 libstdc++.so.6
ln -sf librt-2.25.so librt.so.1
2.3 验证 glibc 环境
bash
ls -la /data/glibc/
期望 :看到 8 个
*-2.25.so文件(全部 >0 字节)+ 7 个*.so.*符号链接,ld-2.25.so权限为 755。
3. 编译推理测试程序
3.1 源文件
源文件位于
rknpu2/rknn_oh_test.c,可以作为模板直接使用。核心调用链:rknn_init()→rknn_query()→rknn_inputs_set()→rknn_run()→rknn_outputs_get()→rknn_destroy()
3.2 在 WSL 中用 GCC linaro 编译
编译脚本 build_oh_test.sh 位于 rknpu2/ 目录,在 WSL 中执行:
bash
wsl bash /mnt/d/Projects/rknn-toolkit2/rknpu2/build_oh_test.sh
实际输出 :编译成功,生成
rknpu2/rknn_oh_test(aarch64 ELF)。
编译脚本做了什么:
- 修复 LTO plugin 符号链接缺失
- 在 sysroot 中用
cp创建 SONAME 别名(DrvFS 不支持 symlink) - 编译命令关键参数:
-Wl,-rpath-link,"$LIBC_LIB"--- 链接时找到 glibc 库-Wl,--dynamic-linker=/data/glibc/ld-linux-aarch64.so.1--- 运行时使用板端的 glibc ld-Wl,-rpath='$ORIGIN:/data/glibc:/data/local/tmp'--- 运行时库搜索路径
验证编译产物:
bash
wsl bash -c "file /mnt/d/Projects/rknn-toolkit2/rknpu2/rknn_oh_test"
wsl bash -c "readelf -l /mnt/d/Projects/rknn-toolkit2/rknpu2/rknn_oh_test | grep interpreter"
期望 :
ELF 64-bit LSB executable, ARM aarch64+Requesting program interpreter: /data/glibc/ld-linux-aarch64.so.1
3.3 编译常见问题
| 错误 | 原因 | 解决 |
|---|---|---|
fatal error: liblto_plugin.so not found |
GCC 工具链 LTO plugin 文件名不对 | ln -sf liblto_plugin.so.0.0.0 liblto_plugin.so |
cannot find -lpthread |
sysroot 缺少 SONAME 符号链接 | cp libpthread-2.25.so libpthread.so.0 |
cannot find -lstdc++ |
同上 | cp libstdc++.so.6.0.24 libstdc++.so.6 |
cannot find -lgcc_s |
同上 | cp libgcc_s.so.1 libgcc_s.so |
4. 推送二进制和模型到板端
4.1 推送文件
⚠️ 同 2.1 节经验:逐条推送、间隔 ≥5 秒、每条推完验证大小 。批量连续推必断线,宁可手动逐条。三条命令务必分开执行,中间用
hdc shell "ls -la ..."验证上一条结果。
在 cmd 窗口逐条执行(每次等 FileTransfer finish 再下一条):
cmd
D:\hdc\hdc.exe file send D:\Projects\rknn-toolkit2\rknpu2\rknn_oh_test /data/local/tmp/rknn_oh_test
D:\hdc\hdc.exe file send D:\Projects\rknn-toolkit2\rknpu2\examples\rknn_api_demo\model\RK3588\mobilenet_v1.rknn /data/local/tmp/mobilenet_v1.rknn
D:\hdc\hdc.exe file send D:\Projects\rknn-toolkit2\rknpu2\runtime\Linux\librknn_api\aarch64\librknnrt.so /data/local/tmp/librknnrt.so
注意:
- 模型必须是
model/RK3588/mobilenet_v1.rknn(本次实测 SDK 自带现成,不用 PC 端转换;RK3576 的模型在此不可用,见 10.1)- 若推送中断,板端会残留 0 字节 文件,重推即可
4.2 设置权限 + 验证文件就位
bash
hdc shell "chmod 755 /data/local/tmp/rknn_oh_test /data/local/tmp/librknnrt.so"
hdc shell "ls -la /data/local/tmp/rknn_oh_test /data/local/tmp/mobilenet_v1.rknn /data/local/tmp/librknnrt.so"
实际输出(参考大小):
rknn_oh_test:19128 字节mobilenet_v1.rknn:4693865 字节librknnrt.so:7726232 字节 全部非 0 字节即就位。
5. 运行推理测试
bash
hdc shell
bash
cd /data/local/tmp
/data/glibc/ld-linux-aarch64.so.1 \
--library-path /data/glibc:/data/local/tmp \
./rknn_oh_test mobilenet_v1.rknn
实际输出(2026-08-13 RK3588 实测)
ini
=== RKNN OH6.1 Test ===
[1] Loading model: mobilenet_v1.rknn
model size: 4693865 bytes
[2] rknn_init()...
OK, ctx=366611633648
[3] Query SDK version...
api: 2.3.2
drv: 0.9.8
[4] Query I/O count...
inputs: 1, outputs: 1
[5] Input tensor(s):
[0] input: INT8 NHWC dims=[1,224,224,3] n_elems=150528 size=150528
[6] Output tensor(s):
[0] MobilenetV1/Predictions/Reshape_1: FP16 NHWC dims=[1,1001] n_elems=1001 size=2002
[7] Running inference...
rknn_run OK
rknn_outputs_get OK
output[0] first 5: 0.0001 0.0003 0.0011 0.0000 0.0000
[8] Cleanup...
done.
=== ALL TESTS PASSED ===
6. 一键快速验证清单
按以下顺序逐项确认,全部 OK 即可判定 NPU 可用:
| # | 检查项 | 命令 | 通过标准 |
|---|---|---|---|
| 1 | NPU 驱动 | cat /sys/kernel/debug/rknpu/version |
输出 v0.9.x |
| 2 | 设备节点 | ls -la /dev/dri/renderD128 |
存在且权限正确 |
| 3 | glibc 环境 | ls -la /data/glibc/ld-2.25.so |
存在且有执行权限(755) |
| 4 | librknnrt.so | ls -la /data/local/tmp/librknnrt.so |
文件存在且 >0 字节 |
| 5 | 测试程序 | ls -la /data/local/tmp/rknn_oh_test |
文件存在且可执行 |
| 6 | 测试模型 | ls -la /data/local/tmp/mobilenet_v1.rknn |
文件存在(RK3588 版) |
| 7 | 推理运行 | 执行第 5 节命令 | ALL TESTS PASSED |
7. 常见问题排查
7.1 "No such file or directory"(即使文件确实存在)
原因 :动态链接器不匹配。OH 使用 /lib/ld-musl-aarch64.so.1,而程序编译时指定了 glibc 的 ld。
确认:
bash
readelf -l ./rknn_oh_test | grep interpreter
# 应输出: [Requesting program interpreter: /data/glibc/ld-linux-aarch64.so.1]
解决:必须用 glibc 的 ld 启动:
bash
/data/glibc/ld-linux-aarch64.so.1 --library-path /data/glibc:/data/local/tmp ./rknn_oh_test
7.2 "can't execute: Permission denied"(本次实测新坑)
现象 :执行第 5 节命令时报 /bin/sh: /data/glibc/ld-linux-aarch64.so.1: can't execute: Permission denied。
原因 :本板出厂 ld-2.25.so(符号链接 ld-linux-aarch64.so.1 指向的真实文件)权限为 644,无执行位。
解决:
bash
chmod 755 /data/glibc/ld-2.25.so
排查 :先看 ls -la /data/glibc/ld-2.25.so,确认权限位是否含 x。
7.3 rknn_init 返回错误
-1(RKNN_ERR_FAIL):检查模型文件完整性、检查/dev/dri/renderD128权限-4(RKNN_ERR_DEVICE_UNAVAILABLE):NPU 驱动未加载-6(RKNN_ERR_MODEL_INVALID):模型与 SDK 版本不匹配,确认使用对应 RK3588 的模型(RK3576 模型不兼容)
7.4 rknn_server "Address already in use"
原因 :/dev/usb-ffs/ 被 hdc 占用,rknn_server 的 NPUTransfer 组件无法绑定 USB 通道。
结论 :rknn_server 是给 PC↔板端 USB 通信用的,RK3588 内部 NPU 推理不需要 rknn_server,直接调用 librknnrt.so 即可。
7.5 hdc 连续推送断线(本次实测新坑)
现象 :连续快速 hdc file send(间隔 1-3 秒)推到中途卡死,hdc list targets 出现 Connect server failed,hdc 日志报 USBBulkCallback1 failed, ret:2/3 循环。
根因 :连续快速 file send 会把 USB 通道打挂,与调用方式无关(PowerShell、cmd.exe、Bash 均复现;用户手动逐条推送间隔几十秒全部成功)。
预防:
- 每条 hdc 命令间隔 ≥5 秒
- 每次
file send后先验证板端文件大小,再发下一条 - 批量推送让用户手动逐条执行(最可靠)
- 推送中断后板端残留 0 字节文件,重推该文件即可,不用全重推
恢复连接(设备断电重插后):
cmd
taskkill /F /IM hdc.exe
D:\hdc\hdc.exe start
D:\hdc\hdc.exe list targets
若端口 8710 被僵尸进程占用:netstat -ano | findstr 8710 → taskkill /F /PID <pid>。
7.6 程序段错误 (SEGV)
可能是 --library-path 路径不完整,检查:
bash
echo $LD_LIBRARY_PATH
# 确认 /data/glibc 和 /data/local/tmp 都在搜索路径中
8. 相关文件索引
| 文件 | 路径 | 用途 |
|---|---|---|
| 推理测试源码 | rknpu2/rknn_oh_test.c |
最小 RKNN 推理验证程序 |
| 编译脚本 | rknpu2/build_oh_test.sh |
WSL + GCC linaro 编译 |
| RKNN API 头文件 | rknpu2/runtime/Linux/librknn_api/include/rknn_api.h |
完整 C API 定义 |
| 运行时库 | rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so |
7.7MB,板端部署 |
| RK3588 模型 | rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn |
分类模型测试(RK3588 专用) |
| YOLOv5 模型 | rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn |
检测模型测试(如有) |
| RKNN 驱动源码 | GitHub rockchip-linux/kernel develop-6.1 分支 drivers/rknpu |
内核驱动(v0.9.8,厂商镜像已带) |
| Reusable Skill | ~/.workbuddy/skills/rknn2-oh-cross-compile/ |
可复用的 AI 辅助 skill(含本指南全部新坑) |
9. 架构要点备忘
scss
┌─────────────────────────────────────────────────────┐
│ ArkTS App (OH 6.1 / LLVM+musl) │
│ │ 未来通过 IPC/NAPI 调用 │
│ ▼ │
│ rknn_oh_test (GCC linaro / glibc) │
│ │ 直接链接 │
│ ▼ │
│ librknnrt.so (GCC linaro / glibc) │
│ │ ioctl │
│ ▼ │
│ /dev/dri/renderD128 → NPU 驱动 v0.9.8 │
│ │ │
│ ▼ │
│ RK3588 三核 NPU (3×2 TOPS = 6 TOPS) │
└─────────────────────────────────────────────────────┘
- 不要用 rknn_server:它依赖 USB-FFS,在 OH 上被 hdc 占用
- GCC linaro 编译:所有直接调 RKNN API 的代码必须用 GCC 编译
- 自带 glibc :
/data/glibc/是运行时基础,每块新板子都要先搭好(含 ld 权限修复) - RK3588 三核 :多模型并行/加速可通过
rknn_set_core_mask(ctx, RKNN_NPU_CORE_0_1_2)启用三核