上一篇把板子点亮,这一篇不整那些虚的,就干一件事:把 RKNN 环境老老实实搭起来,再用一套脚本把「模型转换 → 连板推理 → 量化对比」整条链路跑穿。
说句掏心窝子的话,玩瑞芯微这种带 NPU 的板子,大部分新手不是卡在硬件上,而是卡在工具链上——PC 端要有做模型转换的 RKNN-Toolkit2,板端要有做推理的运行时,中间还横着一个更基础的问题:板子得先稳定在线。WiFi 不会自动连、adb 每次重启都得串口手动拉起来,这种状态谈什么模型部署?所以这篇的顺序是:先把板子的「开机三件套」配好 → 再装 PC 工具链 → 最后七个脚本一条线跑通全程。踩过的坑全摊开讲,照着我走基本一次过。
一、先弄明白:模型是怎么"坐上"NPU 的
在动手之前,我强烈建议你把下面这个流程先看明白,不然只会对着命令复制粘贴,出了问题根本不知道查哪儿。
训练好的模型(PyTorch/TF等)
│ (电脑上一锤定音)
▼
转成中间格式 ONNX
│ (电脑上,用 RKNN-Toolkit2)
▼
生成板子认得的 .rknn 文件
│ (拷到板子上)
▼
板端用 rknn-toolkit-lite2 或 C API 加载推理
关键点有三条:
- 模型转换在 PC 上进行(叫"宿主侧"),用 Python 环境里的 RKNN-Toolkit2;
- 推理在板子上进行,用轻量版的运行时库(RKNN-Lite2 或 C 接口);
- 二者版本必须对应,安装时报错大概率就是版本没对上。
落到这块板子上,整套环境的连接拓扑长这样:
PC (WSL Ubuntu 22.04) OK1126B (RV1126B)
┌─────────────────────┐ WiFi 同网段 ┌──────────────────────────┐
│ RKNN-Toolkit2 │ ─── adb :5555 ──▶ │ adbd ──▶ rknn_server │
│ ├ 模型转换(离线) │ │ └─▶ NPU │
│ └ 连板调试推理 │ │ (librknnrt.so) │
└─────────────────────┘ └──────────────────────────┘
│ ▲
└──────────── USB 串口 (COM25, 仅调试急救) ────┘
两个容易想当然的点先纠正
- rknn_server 不监听任何 TCP 端口,它靠 adb 通道传数据。
- 板子和 PC 之间唯一的物理连接是一根 USB 串口线(救急用),跑模型走的是 WiFi 网络 adb。网络不通,一切免谈——这就是为什么前置配置要放在第一节。

二、前置第一课:开机三件套自启(WiFi / adbd / rknn_server)
这块板子出厂有个"惊喜":WiFi、adbd、rknn_server 三样全都没有开机自启。我一开始也被表象骗了——/etc/profile.d/adbd.sh 看着像启动脚本,其实只 export 了两个环境变量,一个进程都不会拉起;systemd 里那个 wpa_supplicant.service 用的是 -u 纯 dbus 模式,压根不读配置文件。所以以前每次断电重启,都得串口手动来一遍:
1. 手动拉取adb
【老办法——串口手动拉起 adbd(应急用)】
root@OK1126B-buildroot:~# ps | grep adbd
root@OK1126B-buildroot:~# export ADB_TCP_PORT=5555
root@OK1126B-buildroot:~# adbd &
[1] 1508
root@OK1126B-buildroot:~# adbd I 09-01 03:16:35 1508 1508 main.cpp:197] adbd listening on port 5555

一次两次无所谓,次数多了谁受得了?这次直接彻底办了:往 /etc/init.d/ 放两个开机脚本
另外提醒一句:持久配置别放 /tmp,那是内存盘,重启就没;这块板也没有 /data 分区,统一放 /etc(rootfs 本身是可写的 ext4)。
2. WiFi 开机自连
第一步,把 WiFi 凭据写进板子自带的 /etc/wpa_supplicant.conf:




第二步,写开机脚本 S98wifi.sh。
这里有个大坑必须重点说:我第一版脚本写完,重启死活不连,串口手动跑一遍却完全正常。查 journalctl 才水落石出——是时序竞争:脚本执行的那一刻,RTL8821CS 的驱动模块还没加载完(前后差 2 秒左右),ifconfig wlan0、wpa_supplicant、udhcpc 三条命令全部报 No such device 后败退。所以脚本里必须先等 wlan0 出现,再干活
【WiFi 开机自启脚本(板端)】
cat > /etc/init.d/S98wifi.sh << 'EOF'
#!/bin/sh
case "$1" in
start)
echo "Starting WiFi..."
n=0
while [ $n -lt 30 ]; do # 等驱动加载完,最多30秒
ifconfig wlan0 >/dev/null 2>&1 && break
sleep 1
n=$((n+1))
done
killall -q wpa_supplicant 2>/dev/null
killall -q udhcpc 2>/dev/null
ifconfig wlan0 up
wpa_supplicant -B -D nl80211 -i wlan0 -c /etc/wpa_supplicant.conf
udhcpc -b -i wlan0
;;
stop)
killall -q wpa_supplicant 2>/dev/null
killall -q udhcpc 2>/dev/null
;;
esac
exit 0
EOF
chmod 755 /etc/init.d/S98wifi.sh
小经验:串口终端里粘贴多行 heredoc 容易被吞行(我就栽在这,配置文件压根没写进去,脚本空转半天)。建议先用代码块1的办法把 adbd 手动拉起来,之后所有写文件操作都走 adb shell,稳定得多。

3. adbd + rknn_server 开机自启
同样的思路再来一个 S99rknn.sh:adbd 用环境变量指定监听 5555;rknn_server 不直接拉起,而是交给厂商自带的 start_rknn.sh 守护循环(server 意外挂了会自动重生,比裸拉一个进程可靠):
【adbd / rknn_server 开机自启脚本(板端)】
cat > /etc/init.d/S99rknn.sh << 'EOF'
#!/bin/sh
case "$1" in
start)
if ! pidof adbd >/dev/null 2>&1; then
ADB_TCP_PORT=5555 ADBD_SHELL=/bin/bash /usr/bin/adbd >/dev/null 2>&1 &
fi
if ! pidof start_rknn.sh >/dev/null 2>&1; then
/usr/bin/start_rknn.sh >/dev/null 2>&1 &
fi
;;
stop)
killall adbd 2>/dev/null
killall start_rknn.sh 2>/dev/null
killall rknn_server 2>/dev/null
;;
esac
exit 0
EOF
chmod 755 /etc/init.d/S99rknn.sh
4. 重启验收:约 40 秒后全自动就绪
reboot 一次,等半分钟左右,三项检查全过就算配置成功:
【代码块5:重启后验收(板端串口)】
ifconfig wlan0 # 有 inet addr:192.168.77.x → WiFi 自连 OK
pidof adbd # 有 PID → adbd 自启 OK
pidof rknn_server # 有 PID → rknn_server 自启 OK

PC 侧(WSL)再补一刀:
adb connect 192.168.77.104:5555 # 换成你板子的实际 IP
adb devices # 应显示 device 状态
adb shell id # 应返回 uid=0(root)

交代一个历史遗留问题:如果 adb root 之后连接掉线、跑模型时报 rknn_server abnormal / ERROR_PIPE -9,那是 buildroot 自带 adbd 的锅(不支持网络 root)。解决办法是换成官方 adbd,我把它做成了应急脚本 04b_fix_adbd.sh,见第六节。先做 2/3的自启配置,大多数情况就用不上 04b 了——但知道它在那儿,心里踏实。
三、电脑上装 RKNN-Toolkit2(脚本 01)
板子用的 NPU 是瑞芯微的,对应的转换工具叫 RKNN-Toolkit2。注意是带 2 的,老的那个 Toolkit(不带 2)是给老芯片用的,别下错。
官方建议用一个干净的 Python 虚拟环境,别污染电脑上别的工程。所谓 venv 就是一份独立的 Python 沙盒(我用的 Python 3.10,include-system-site-packages=false 完全隔离),依赖坏了删掉重建就行,系统 Python 毫发无伤——这对 RKNN 这种依赖版本卡得死的工具来说简直是刚需。
实际操作我全部固化进了 01_env_setup.sh:自动建 venv(~/rknn_env)、锁版本装依赖、装 wheel、最后自检,一条命令到底:
【环境一键搭建(PC 端 WSL)】
cd ~/rknn_manual
bash 01_env_setup.sh
# 预期输出:
# == python: Python 3.10.12 venv: /home/lxg/rknn_env ==
# rknn_toolkit2 已就绪, version = 2.3.2
# RESULT: ENV_READY
三个版本坑提前打预防针(都是实打实踩过的):
- onnx 必须锁 1.16.1:新版删了
onnx.mapping,转换阶段直接崩;
- setuptools 要 < 70:新版没有
pkg_resources,toolkit 一 import 就报错;
- wheel 认准 x86_64 + cp310:WSL 里手滑装成 arm64 包的话,安装阶段就 Architecture 不匹配。
另外验证别用 import rknn 这种写法——2.3.2 是 namespace 包结构,没有顶层模块,正确姿势是 from rknn.api import RKNN(我就被自己写的探针坑过一回)。

四、板端运行时:固件自带,只需确认
板子这头比想象的省事:rknn_server 在 /usr/bin、librknnrt.so 在 /usr/lib,固件出厂就带,不用装任何东西。(想走板端 Python 路线的话还有个 rknn-toolkit-lite2 可以 pip 装,但我们这套流程用不上——转换在 PC、调试走连板、量产走 C API,三板斧够用了。)
要做的只是"确认它活着",这步固化成 04_board_deploy.sh:连板、确认 root、检查 librknnrt / rknn_server / rknpu 设备节点,全过输出 BOARD_READY:
【代码块7:连板部署检查(PC 端)】
bash 04_board_deploy.sh
# 预期输出(S99 自启生效后 rknn_server 就是 running 状态):
# connected to 192.168.77.104:5555
# board user: root
# librknnrt: librknnrt.so
# rknn_server: running
# dev: rknpu
# RESULT: BOARD_READY

五、七个脚本,把整条RKNN转换链路跑通
前面铺垫完,正餐来了。整套流程我整理成 1 个配置文件 + 7 个脚本(外加一个应急备胎),全放在 rknn_manual/ 目录。每个脚本干完活都会打一个 RESULT 标记,一眼就能判断这步过没过:
| 文件 |
干什么 |
预期输出 |
00_env.env |
唯一要改的配置:venv/SDK 路径、板子 IP |
— |
01_env_setup.sh |
装 / 自检 RKNN-Toolkit2 环境 |
ENV_READY |
02_small_check.sh |
手写最小模型,先验证转换链路(可选) |
SMALL_OK |
03_yolo_convert.sh |
YOLOv5s → RKNN,一次产出 INT8 + FP16 |
YOLO_CONVERT_DONE |
04_board_deploy.sh |
网络 adb 连板 + 板端检查 |
BOARD_READY |
05_yolo_infer.sh |
板端 NPU 推理,测耗时 / 张量统计 |
INFER_DONE |
06_yolo_decode.sh |
INT8 vs FP16 解码对比 + 逐框 IoU |
DECODE_DONE |
07_perf_cpu.sh |
推理期间 CPU / NPU 占用采样 |
CPU/NPU 采样结果 |
04b_fix_adbd.sh |
(备胎)adb root 掉线时换官方 adbd |
ADBD_SWAPPED |
执行顺序就一条线:
【全流程执行(PC 端)】
cd ~/rknn_manual
nano 00_env.env # 第一次跑只改这里:路径 + 板子 IP
bash 01_env_setup.sh # ① 环境
bash 02_small_check.sh # ② 可选:30 秒自检转换链路
bash 03_yolo_convert.sh # ③ YOLOv5s 转出 INT8 + FP16 两个 .rknn
bash 04_board_deploy.sh # ④ 连板
bash 05_yolo_infer.sh # ⑤ 板端推理
bash 06_yolo_decode.sh # ⑥ 量化对比
bash 07_perf_cpu.sh # ⑦ 性能采样
③ 转换:一次出两个模型,INT8 比 FP16 小了快一半。注意 .rknn 要输出到用户可写目录(脚本里已经处理了;别学我第一次让它往 SDK 只读目录里写,直接 PermissionError 教做人):
yolov5s-int8-rv1126b.rknn 7766904 bytes (INT8)
yolov5s-fp16-rv1126b.rknn 14895428 bytes (FP16)
RESULT: YOLO_CONVERT_DONE

⑤ 板端推理(bus.jpg,640×640,连板调试模式):
[INT8] NPU inference time = 32.147s
[FP16] NPU inference time = 44.857s
RESULT: INFER_DONE
说明:这个耗时是 PC 工具链经 adb 连板的调试模式耗时,含数据来回传输,不代表 NPU 极限——板端 C API 纯推理会快一个数量级,那是下一篇的内容。但做同条件对比已经够用:INT8 比 FP16 快约 30%,体积小约 48%。

⑥ 量化对比:两个模型对同一张图各检出 5 个目标(4 人 + 1 公交),逐框 IoU 全部 0.975以上——INT8 量化基本没伤筋动骨
===== INT8 : 5 detections (NPU 29.924s) =====
[person] conf=0.860 box=(480,241,559,525)
...
INT8[person] conf=0.860 <-> FP16[person] conf=0.848 IoU=0.975
RESULT: DECODE_DONE

⑦ 性能采样:推理期间 CPU 计算占用(us+sy)仅 1.5%,98.5% 时间空闲——计算全卸载给了 NPU,CPU 只管调度和搬数据。这版内核的 NPU 占用 debugfs 节点读出来恒 0(接口没刷新);NPU 频率全程 800MHz,但基线不推理时也是 800MHz,频率不随负载变化。

小结
这套环境搭完,我给你六个避坑清单,都是血泪:
- 版本必须对齐:PC 端 RKNN-Toolkit2、板端 librknnrt、固件里的 NPU 驱动,三者版本必须匹配。别贪新,用配套那套最稳(onnx 锁 1.16.1、setuptools 锁 <70)。
- Python 环境要隔离:别往系统 Python 里硬塞,一个 venv 解决所有烦恼,出事删了重建。
- 分清"转换在 PC,推理在板":两边的库千万别装反,装反了各种玄学报错。
- 开机三件套要先配:WiFi / adbd / rknn_server 这板子出厂全无自启,
/etc/init.d/ 里两个脚本一劳永逸;写 WiFi 脚本记得加驱动等待循环,不然必踩时序竞争的坑。
- adb root 掉线不是玄学:报
ERROR_PIPE / rknn_server abnormal 就是 buildroot adbd 不支持网络 root,换官方 adbd(04b 脚本)立愈。
- 持久配置放 /etc,别放 /tmp:/tmp 是内存盘,重启全没——血泪+1。
环境至此全部就绪,而且重启不丢:断电再来,40 秒后 adb connect 一下直接续上。地基打牢了
脚本:
附件:rknn_manu.zip