在 QNAP TS416(RK3568)上复用 QuMagie 容器的 NPU 运行自定义 RKNN 模型
声明 :本文由 AI 辅助整理,基于一次真实的设备调试过程。所有命令、报错和输出均来自实际终端记录,目标是提供一份足够详细的、可完整复现的参考文档。
目录
背景与目标
环境信息
第一阶段:认识 QuMagie 容器的启动架构
第二阶段:进入容器,发现 NPU 相关进程
第三阶段:解析 QuMagieCore.sh 启动脚本
第四阶段:解析 common.include
第五阶段:解析容器内 entrypoint 脚本
第六阶段:Python 侧调试——从 ModuleNotFoundError 到成功推理
第七阶段:独立容器复用宿主机 NPU
第八阶段:性能测试与遗留问题
关键结论与速查表
遗留问题与后续工作
1. 背景与目标 1.1 设备
设备型号 :QNAP TS416
SoC :Rockchip RK3568(NPU 算力约 1 TOPS)
系统 :QTS(QNAP 的 NAS 操作系统)
容器运行时 :Container Station,使用 system-docker 作为命令行接口
已安装的 QPKG :QuMagieCore(QNAP 的 AI 相册核心组件,带有 AI 图像识别能力)
1.2 目标 在不影响 QuMagie 原有功能 的前提下,复用设备的 NPU 资源,运行我们自己的 RKNN 模型(Python 接口),用于开发或测试目的。
1.3 挑战
QNAP 的 NPU 使用方式未公开文档化。
QuMagie 容器的启动脚本、模型路径、Python 环境都是未知的。
需要弄清楚 NPU 是如何被容器化访问的,才能在新的独立容器中复用。
2. 环境信息
项目
值
SoC
Rockchip RK3568
NPU 驱动版本
0.8.2
librknnrt.so 版本
2.3.0 (c949ad889d@2024-11-07T11:35:33)
rknn-toolkit-lite2 版本
2.0.0b0
Python 版本
3.8.10
镜像
qumagie-core-arm64:latest
容器名
aicore.container
容器 ID
cef730460997
QPKG 根目录
/share/CACHEDEV1_DATA/.qpkg/QuMagieCore
3. 第一阶段:认识 QuMagie 容器的启动架构 3.1 查看运行中的容器 1 2 3 4 [admin@TS416 ~]# system-docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b8277b6cc823 qumagie-core-arm64:latest "/bin/bash" 3 minutes ago Up 3 minutes rknn-test cef730460997 qumagie-core-arm64:latest "/root/daemon/entryp…" 3 weeks ago Up 3 weeks aicore.container
可见有两个容器:
rknn-test:之前实验时启动的临时容器。
aicore.container:QuMagie 的 AI 核心容器,已经运行了 3 周。
3.2 检查容器详情 1 system-docker inspect cef730460997
关键输出(节选):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 { "Id" : "cef7304609970c521c499478ae006a51cec8f8b5983e273ff435be9a5fa2ccce" , "Created" : "2026-08-31T15:29:14.96845285Z" , "Path" : "/root/daemon/entrypoint.sh" , "State" : { "Status" : "running" , "Pid" : 2055 } , "Image" : "sha256:ddf19ca7c584030390562464269a40b77bc922f90f14406a94a1eee02c7dc3bf" , "Name" : "/aicore.container" , "HostConfig" : { "Binds" : [ "/etc/localtime:/etc/localtime" , "/share/CACHEDEV1_DATA/.qpkg/QuMagieCore/daemon:/root/daemon" , "/share/CACHEDEV1_DATA/.qpkg/QuMagieCore/QuMagieCore/object:/tmp/iva" , "/share/CACHEDEV1_DATA/.qpkg/QuMagieCore/QuMagieCore/face:/tmp/face" , "/dev/bus/usb:/dev/bus/usb:ro" ] , "NetworkMode" : "host" , "RestartPolicy" : { "Name" : "always" } , "Privileged" : false , "Devices" : [ { "PathOnHost" : "/dev/dri/card0" , "PathInContainer" : "/dev/dri/card0" , "CgroupPermissions" : "rwm" } , { "PathOnHost" : "/dev/dri/renderD128" , "PathInContainer" : "/dev/dri/renderD128" , "CgroupPermissions" : "rwm" } , { "PathOnHost" : "/dev/dri/renderD128" , "PathInContainer" : "/dev/dri/renderD129" , "CgroupPermissions" : "rwm" } , { "PathOnHost" : "/dev/rga" , "PathInContainer" : "/dev/rga" , "CgroupPermissions" : "rwm" } ] , "DeviceCgroupRules" : [ "c *:* rw" ] , "CpuPeriod" : 100000 , "CpuQuota" : 200000 , "Ulimits" : [ { "Name" : "nofile" , "Hard" : 65535 , "Soft" : 65535 } ] } , "Config" : { "Hostname" : "TS416" , "Env" : [ "QNAP_QPKG=QuMagieCore" , "NPU_DEVICE=aarch64" , "ENABLE_SEMANTIC_SEARCH=1" , "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ] , "Cmd" : [ "/root/daemon/entrypoint.sh" ] , "Image" : "qumagie-core-arm64:latest" , "Labels" : { "org.opencontainers.image.ref.name" : "ubuntu" , "org.opencontainers.image.version" : "20.04" } } }
从 inspect 里能提取出的关键信息:
配置项
值
含义
网络模式
host
容器共享宿主机网络
启动命令
/root/daemon/entrypoint.sh
容器内 PID 1
环境变量
NPU_DEVICE=aarch64
非 RK3588 平台
环境变量
ENABLE_SEMANTIC_SEARCH=1
启用语义搜索
设备
/dev/dri/card0
GPU / DRM
设备
/dev/dri/renderD128
NPU / DRM render 节点
设备
/dev/dri/renderD128 → renderD129
RK3568 workaround (关键)
设备
/dev/rga
2D 图形加速
挂载
${QPKG_ROOT}/daemon → /root/daemon
QuMagie 应用文件
挂载
.../QuMagieCore/object → /tmp/iva
物体识别数据
挂载
.../QuMagieCore/face → /tmp/face
人脸识别数据
挂载
/dev/bus/usb → /dev/bus/usb:ro
USB 设备(只读)
CPU
period=100000, quota=200000
限 2 核
重启策略
always
挂了自动重启
3.3 需要注意的点
renderD128 被映射成 renderD129 :这是 RK3568 的典型 workaround。因为 RK3568 只有 renderD128,但 Rockchip 的某些用户态库会硬编码访问 renderD129,所以需要”伪装”一个出来。
网络是 host 模式 :这意味着容器和宿主机共享 localhost,npu_transfer_proxy 可以被跨容器访问。
--device-cgroup-rule="c *:* rw" :允许容器访问所有字符设备。
4. 第二阶段:进入容器,发现 NPU 相关进程 4.1 进入容器 1 2 [admin@TS416 ~]# system-docker exec -it cef730460997 bash root@TS416:/#
4.2 查看容器内进程 1 2 3 4 5 6 7 8 9 10 11 root@TS416:/# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 Aug31 ? 00:00:00 /bin/bash /root/daemon/entrypoint.sh root 8 1 0 Aug31 ? 00:12:47 ./npu_transfer_proxy root 16 1 0 Aug31 ? 00:00:02 ./inference root 18 1 0 Aug31 ? 00:01:29 ./aic_manager inference FcServer_mt root 19 1 0 Aug31 ? 00:01:30 ./FcServer_mt 3 root 28 19 0 Aug31 ? 00:00:00 [FcServer_mt] <defunct> root 32 1 0 Aug31 ? 00:00:01 ./FcServer_mt 3 root 232 0 1 17:35 pts/0 00:00:00 bash root 241 232 0 17:35 pts/0 00:00:00 ps -ef
4.3 进程解读
PID
进程
作用
1
/bin/bash /root/daemon/entrypoint.sh
容器主进程
8
./npu_transfer_proxy
NPU 用户态通信代理
16
./inference
AI 推理主程序(二进制)
18
./aic_manager inference FcServer_mt
AI 核心管理器
19
./FcServer_mt 3
特征计算服务(主进程)
28
[FcServer_mt] <defunct>
已退出但未回收的子进程
32
./FcServer_mt 3
aic_manager 拉起的第二个实例
关键发现 :npu_transfer_proxy 是 NPU 访问的核心,且是单实例 的。
4.4 查看容器内文件结构 1 2 3 4 5 root@TS416:/# ls bin boot dev etc home lib libjpeg.so.62 media mnt opt proc root run sbin srv sys tmp usr var root@TS416:/# ls /root/daemon/ entrypoint.sh faceLib quai quai_launcher.sh
4.5 检查 NPU 设备节点 1 2 3 4 5 6 root@TS416:/# ls -l /dev/dri/ /dev/rga crw------- 1 root root 10, 60 Sep 24 18:34 /dev/rga /dev/dri/: total 0 crw------- 1 root root 226, 128 Sep 24 18:34 renderD128
注意 :容器里只看到 renderD128,没有 renderD129(因为 inspect 显示 /dev/dri/renderD128 → /dev/dri/renderD129 是一个 workaround 映射,在本容器里的实际表现需要进一步确认)。
5. 第三阶段:解析 QuMagieCore.sh 启动脚本 5.1 脚本位置 1 2 3 DIR=$(/sbin/getcfg -f /etc/config/qpkg.conf QuMagieCore Install_Path) echo $DIR
5.2 关键函数:run_quai_container 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 run_quai_container (){ stop_quai_container prepare_quai_container cp "${QPKG_ROOT} /docker_scripts/entrypoint_arm_64.sh" "${QPKG_ROOT} /daemon/entrypoint.sh" chmod +x "${QPKG_ROOT} /daemon/entrypoint.sh" CPU_QUOTA=$(get_cpu_quota) log_usage "run aicore container with ${CPU_QUOTA} " local npu_device=$(get_npu_device) local enable_semantic_search=1 if [[ "${npu_device} " == "rk3588" ]]; then enable_semantic_search=0 fi ${DOCKER} run -e "QNAP_QPKG=${QPKG_NAME} " \ --name="${AICORE_CONTAINER_NAME} " -d --restart=always \ $(mount_hailo_devices) \ $(mount_rknn_devices) \ -v /dev/bus/usb:/dev/bus/usb:ro \ --device-cgroup-rule="c *:* rw" \ -v /etc/localtime:/etc/localtime \ -v "${QPKG_ROOT} /daemon:/root/daemon" \ -v "${OBJ_DATA_ROOT} :/tmp/iva" \ -v "${FACE_DATA_ROOT} :/tmp/face" \ --security-opt="systempaths=unconfined" \ -e NPU_DEVICE=${npu_device} \ -e ENABLE_SEMANTIC_SEARCH=${enable_semantic_search} \ --net=host \ --cpu-period=100000 --cpu-quota="${CPU_QUOTA} " \ ${IMAGE_REPOSITORY} :latest /root/daemon/entrypoint.sh return $? }
5.3 get_npu_device():判断 SoC 类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 get_npu_device (){ local device_compatible_node="/proc/device-tree/compatible" if [[ ! -f "${device_compatible_node} " ]]; then echo "aarch64" return fi if [[ $(grep -ic rk3588 "${device_compatible_node} " ) -ge 1 ]]; then echo "rk3588" else echo "aarch64" fi }
逻辑 :
有 rk3588 → 返回 rk3588(禁用语义搜索)
其他 → 返回 aarch64(启用语义搜索)
我们的 TS416 返回 aarch64,所以 ENABLE_SEMANTIC_SEARCH=1,与 inspect 一致。
6. 第四阶段:解析 common.include common.include 是 QuMagieCore.sh 引入的公共库,位于同一目录下。
6.1 关键变量定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 DOCKER="$(${GETCFG} -f ${CONF} container-station INSTALL_PATH) /bin/system-docker" QPKG_NAME="QuMagieCore" QPKG_ROOT="$(${GETCFG} ${QPKG_NAME} Install_Path -f ${CONF}) " DATA_ROOT="${QPKG_ROOT} /QuMagieCore" FACE_DATA_ROOT="${DATA_ROOT} /face" OBJ_DATA_ROOT="${DATA_ROOT} /object" AICORE_CONTAINER_NAME="aicore.container"
6.2 mount_rknn_devices():RKNN 设备映射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mount_rknn_devices (){ local ret_devices="" for device in /dev/dri/* do if [[ -c "${device} " ]]; then ret_devices="${ret_devices} --device $device :$device " fi done if [[ -c "/dev/dri/renderD128" && ! -c "/dev/dri/renderD129" ]]; then ret_devices="${ret_devices} --device /dev/dri/renderD128:/dev/dri/renderD129" fi if [[ -c "/dev/rga" ]]; then ret_devices="${ret_devices} --device /dev/rga:/dev/rga" fi echo "${ret_devices} " }
关键点 :
遍历 /dev/dri/*,所有字符设备都挂进容器。
如果只有 renderD128 ,则额外把 renderD128 映射成 renderD129。这是 RK3568 独有的 workaround。
/dev/rga 也一起挂进去。
6.3 get_cpu_quota():CPU 配额 1 2 3 4 5 6 7 8 9 10 11 12 13 get_cpu_quota (){ local cpu_cores=$(grep -c processor /proc/cpuinfo) local cpu_quota=$(($cpu_cores *100000 )) if [ $cpu_cores -gt 1 ]; then cpu_quota=$(($cpu_quota /2 )) else cpu_quota=50000 fi echo "${cpu_quota} " }
TS416 是 4 核,所以 cpu_quota = 4*100000/2 = 200000,与 inspect 一致。
7. 第五阶段:解析容器内 entrypoint 脚本 7.1 从宿主机获取 entrypoint_arm_64.sh 1 [admin@TS416 ~]# cat /share/CACHEDEV1_DATA/.qpkg/QuMagieCore/docker_scripts/entrypoint_arm_64.sh
7.2 脚本内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash ldconfig cd /usr/local/lib/rknn_env/rknnlite/3rdparty/platform-tools/ntp/linux-aarch64./npu_transfer_proxy & cd /root/daemon/quaipython3 -m build_link ./inference & cd /root/daemon/faceLib/bin/chmod +x *./aic_manager inference FcServer_mt & ./FcServer_mt 3
7.3 逐行解读
行
命令
作用
3
ldconfig
刷新动态库缓存,确保 librknnrt.so 可被找到
4-5
cd .../ntp/linux-aarch64 + ./npu_transfer_proxy &
启动 NPU 通信代理(后台)
7-8
cd /root/daemon/quai + python3 -m build_link
运行模型软链脚本
9
./inference &
启动 AI 推理主程序(后台)
11-12
cd /root/daemon/faceLib/bin/ + chmod +x *
准备人脸特征计算目录
14
./aic_manager inference FcServer_mt &
启动 AI 核心管理器(后台)
15
./FcServer_mt 3
前台运行特征计算服务(容器主进程)
注意最后一行没有 & ,所以 FcServer_mt 是前台进程,容器的主进程实际上是它。
7.4 build_link.pyc 反编译 原始的 build_link.py 被编译成了 build_link.pyc,用 uncompyle6 反编译:
1 2 pip3 install uncompyle6 uncompyle6 /path/to/build_link.pyc
反编译结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import platform, subprocess, osDEVICE_COMPATIBLE_NODE = "/proc/device-tree/compatible" def get_host (): system = platform.system() machine = platform.machine() os_machine = system + "-" + machine if os_machine == "Linux-aarch64" : try : with open (DEVICE_COMPATIBLE_NODE) as f: device_compatible_str = f.read() if "rk3588" in device_compatible_str: host = "RK3588" else : if "rk3568" in device_compatible_str: host = "RK356x" else : if "rk3566" in device_compatible_str: host = "RK356x" else : host = "other_arm64" except : print ("Read device node {} failed." .format (DEVICE_COMPATIBLE_NODE)) host = os_machine else : host = os_machine return host host = get_host() if os.path.exists("/root/daemon/quai/model/qumagiecore.rknn" ): subprocess.run(["rm" , "-f" , "/root/daemon/quai/model/qumagiecore.rknn" ]) elif host == "RK3588" : subprocess.run(["ln" , "-s" , "/root/daemon/quai/model/qumagiecore_rk3588.rknn" , "/root/daemon/quai/model/qumagiecore.rknn" ]) else : if host == "RK356x" : subprocess.run(["ln" , "-s" , "/root/daemon/quai/model/qumagiecore_rk356x.rknn" , "/root/daemon/quai/model/qumagiecore.rknn" ])
结论 :build_link.pyc 只是一个模型选择器 :
检测 SoC 类型
删除旧软链
根据 SoC 创建对应的软链(qumagiecore.rknn → qumagiecore_rk3588.rknn 或 qumagiecore_rk356x.rknn)
它跟 Python rknn 包完全无关 ,之前的猜测需要修正。
8. 第六阶段:Python 侧调试——从 ModuleNotFoundError 到成功推理 8.1 最初的尝试(错误) 1 from rknn.api import RKNN
报错:
1 ModuleNotFoundError: No module named 'rknn'
原因 :
rknn 是 PC 端模型转换工具 rknn-toolkit2 的包名。
板端推理用的是 rknn-toolkit-lite2,包名是 rknnlite 。
8.2 发现两个 Python 环境 1 2 3 4 5 6 7 8 root@TS416:/usr/local/lib# ls rknn_env rknn2_env root@TS416:/usr/local/lib/rknn_env/rknnlite# cat VERSION 1.7.7b2 root@TS416:/usr/local/lib/rknn2_env/rknnlite# cat VERSION 2.0.0b0
系统里存在两套 RKNN Python 环境。
8.3 使用正确类名和导入 1 2 3 4 5 import syssys.path.insert(0 , '/usr/local/lib/rknn2_env' ) from rknnlite.api import RKNNLiterknn = RKNNLite(verbose=True )
项
正确值
错误值
包名
rknnlite
rknn
类名
RKNNLite
RKNN
用途
板端推理
PC 端模型转换
8.4 undefined symbol 错误 用 rknn_env(1.7.7b2)时报错:
1 AttributeError: /usr/lib/librknnrt.so: undefined symbol: rknn_server_status
原因 :/usr/lib/librknnrt.so 是 2.3.0 版本,与 1.7.7b2 的 Python 包不匹配。
改用 rknn2_env(2.0.0b0)后,版本匹配(虽然 Python 包是 2.0.0b0,运行时库是 2.3.0,但接口兼容),错误消失。
8.5 第一次尝试独立容器(失败) 1 2 3 4 5 6 7 8 9 10 11 system-docker run -it --rm \ --name my-npu-app \ --net=host \ --device /dev/dri/renderD128:/dev/dri/renderD128 \ --device /dev/rga:/dev/rga \ --device-cgroup-rule="c *:* rw" \ --security-opt="systempaths=unconfined" \ -v /share/MyNPUProject:/app \ -v /proc/device-tree/compatible:/proc/device-tree/compatible:ro \ qumagie-core-arm64:latest \ bash
进入后尝试 init_runtime():
1 2 3 4 5 !!! It is detected that some necessary files are missing in the container. !!! When starting the container, please use the -v parameter to map the corresponding files in the host to the container. The reference parameters of run container are as follows: -v /dev/dri/renderD129:/dev/dri/renderD129 -v /proc/device-tree/compatible:/proc/device-tree/compatible
缺少两个东西 :
renderD129 的映射
/proc/device-tree/compatible 的挂载
8.6 修正后的独立容器(成功) 完整命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 system-docker run -it --rm \ --name my-npu-test \ --net=host \ --device /dev/dri/renderD128:/dev/dri/renderD128 \ --device /dev/dri/renderD128:/dev/dri/renderD129 \ --device /dev/rga:/dev/rga \ --device-cgroup-rule="c *:* rw" \ --security-opt="systempaths=unconfined" \ -v /share/MyNPUProject:/app \ -v /share/CACHEDEV1_DATA/.qpkg/QuMagieCore/daemon/quai/model:/mnt/model:ro \ -v /proc/device-tree/compatible:/proc/device-tree/compatible:ro \ qumagie-core-arm64:latest \ bash
关键新增 :
--device /dev/dri/renderD128:/dev/dri/renderD129
-v /proc/device-tree/compatible:/proc/device-tree/compatible:ro
8.7 完整推理脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import syssys.path.insert(0 , '/usr/local/lib/rknn2_env' ) from rknnlite.api import RKNNLiteimport numpy as npimport timeMODEL = '/mnt/model/qumagiecore_rk356x.rknn' rknn = RKNNLite(verbose=False ) ret = rknn.load_rknn(MODEL) print ('load_rknn:' , ret)ret = rknn.init_runtime() print ('init_runtime:' , ret)input_data = np.zeros((1 , 3 , 224 , 224 ), dtype=np.uint8) outputs = rknn.inference(inputs=[input_data]) print ('inference ok' )for i, o in enumerate (outputs): print (f' output[{i} ]: shape={o.shape} , dtype={o.dtype} ' ) rknn.inference(inputs=[input_data]) t0 = time.time() for _ in range (100 ): rknn.inference(inputs=[input_data]) t1 = time.time() print (f'avg: {(t1-t0)/100 *1000 :.2 f} ms, FPS: {100 /(t1-t0):.1 f} ' )rknn.release() print ('done' )
8.8 成功输出 1 2 3 4 5 6 7 8 9 load_rknn: 0 I RKNN: [18:53:42.972] RKNN Runtime Information, librknnrt version: 2.3.0 (c949ad889d@2024-11-07T11:35:33) I RKNN: [18:53:42.972] RKNN Driver Information, version: 0.8.2 I RKNN: [18:53:42.972] RKNN Model Information, version: 6, toolkit version: 2.0.0b0+9bab5682(compiler version: 2.0.0b0 (35a6907d79@2024-03-24T02:34:11)), target: RKNPU lite, target platform: rk3568, framework name: ONNX, framework layout: NCHW, model inference type: static_shape init_runtime: 0 inference ok output[0]: shape=(1, 420), dtype=float32 avg: 31.78 ms, FPS: 31.5 done
8.9 模型信息提取 从 RKNN 日志中可以读出模型的输入输出:
1 2 3 Feature Tensor Information Table ID 1 ConvRelu input INT8 NC1HWC2 (1,3,224,224) ← 输入 ID 58 OutputOperator output INT8 UNDEFINED (1,420) ← 输出
项
值
输入 shape
(1, 3, 224, 224)
输入 dtype
INT8(RKNNLite 接收 uint8)
输出 shape
(1, 420)
输出 dtype
float32(RKNNLite 自动反量化)
模型类型
MobileNetV2 风格骨干网络
分类头
420 维(非 ImageNet 1000 类,是 QuMagie 自定义)
9. 第七阶段:独立容器复用宿主机 NPU 9.1 为什么可以复用 npu_transfer_proxy 是单实例 守护进程,通过本地网络(localhost)与 AI 应用通信。容器使用 --net=host 后,容器内进程可以直接访问宿主机的 localhost,从而连接到 QuMagie 容器里的 npu_transfer_proxy。
架构图 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──────────────────────────────────────┐ │ 宿主机 (TS416) │ │ │ │ ┌────────────────────────────────┐ │ │ │ aicore.container │ │ │ │ ├─ npu_transfer_proxy (:xxxx) │ │ │ │ ├─ inference │ │ │ │ └─ FcServer_mt │ │ │ └────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────┐ │ │ │ my-npu-test (--net=host) │ │ │ │ └─ python3 RKNNLite │ │ │ │ ↓ │ │ │ │ 通过 localhost 连接 │ │ │ └────────────────────────────────┘ │ └──────────────────────────────────────┘
9.2 验证共用关系 在宿主机上:
1 system-docker exec aicore.container ps -ef | grep npu_transfer_proxy
可以看到 npu_transfer_proxy 进程。在你的新容器里,它不会出现(因为不共享 PID namespace),但 RKNNLite 通过 localhost 能连上它。
9.3 注意事项
不要在新容器里重复启动 npu_transfer_proxy ,会冲突。
性能是共享的 :QuMagie 高负载时,你的推理会变慢。
代理重启会断开连接 :aicore.container 重启后,你的推理进程需要重新 init_runtime()。
10. 第八阶段:性能测试与遗留问题 10.1 速度测试结果
输入类型
平均耗时
FPS
全零 np.zeros((1,3,224,224), uint8)
31.78 ms
31.5
随机 np.random.randint(0,255,(1,3,224,224), uint8)
31.82 ms
31.4
结论 :输入数据不影响速度,模型本身耗时约 31.8 ms。
10.2 与预期差距 Rockchip 官方基准测试中,MobileNetV2 在 RK3568 上可达 180 FPS(约 5.5 ms) 。
当前速度慢了近 6 倍。
10.3 排查 NPU 频率(未完成) 尝试查看 NPU 频率:
1 2 3 4 5 root@TS416:/# cat /sys/class/devfreq/fdab0000.npu/cur_freq cat : /sys/class/devfreq/fdab0000.npu/cur_freq: No such file or directoryroot@TS416:/# cat /sys/kernel/debug/clk/clk_summary | grep -i npu (空输出)
实际路径应为 /sys/class/devfreq/fde40000.npu/ ,这是 RK3568 的 NPU devfreq 节点,尚未验证。
10.4 可能的原因
原因
说明
状态
NPU 频率被限制
默认 governor 可能未拉到最高频
未排查
与 QuMagie 竞争
inference 进程持续占用 NPU
未排查
Python API 开销
ctypes 封装有额外开销
部分验证
模型量化质量
INT8 量化可能不够优化
无证据
10.5 可能的优化方向
调整 NPU 频率 :
1 2 3 cat /sys/class/devfreq/fde40000.npu/available_frequenciesecho userspace > /sys/class/devfreq/fde40000.npu/governorecho 800000 > /sys/class/devfreq/fde40000.npu/userspace/set_freq
暂停 QuMagie 容器,排除竞争 :
1 2 3 system-docker stop aicore.container system-docker start aicore.container
使用 C/C++ API :如果 Python 开销是瓶颈,可以改用 C++ 接口,直接调用 librknnrt.so。
11. 关键结论与速查表 11.1 Python 环境速查
项
正确值
错误值
环境路径
/usr/local/lib/rknn2_env
/usr/local/lib/rknn_env(旧版,版本不匹配)
包名
rknnlite
rknn
类名
RKNNLite
RKNN
VERSION
2.0.0b0
1.7.7b2
导入
from rknnlite.api import RKNNLite
from rknn.api import RKNN
11.2 容器启动参数速查
参数
作用
--net=host
复用宿主机的 npu_transfer_proxy
--device /dev/dri/renderD128:/dev/dri/renderD128
NPU 设备
--device /dev/dri/renderD128:/dev/dri/renderD129
RK3568 workaround
--device /dev/rga:/dev/rga
2D 图形加速
--device-cgroup-rule="c *:* rw"
字符设备访问权限
--security-opt="systempaths=unconfined"
允许访问 /proc/device-tree
-v /proc/device-tree/compatible:/proc/device-tree/compatible:ro
让 rknnlite 识别 SoC
-v /path/to/model:/mnt/model:ro
挂载模型目录
11.3 一键推理命令(复制即用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 system-docker run --rm \ --name my-npu-test \ --net=host \ --device /dev/dri/renderD128:/dev/dri/renderD128 \ --device /dev/dri/renderD128:/dev/dri/renderD129 \ --device /dev/rga:/dev/rga \ --device-cgroup-rule="c *:* rw" \ --security-opt="systempaths=unconfined" \ -v /share/CACHEDEV1_DATA/.qpkg/QuMagieCore/daemon/quai/model:/mnt/model:ro \ -v /proc/device-tree/compatible:/proc/device-tree/compatible:ro \ qumagie-core-arm64:latest \ python3 -c " import sys, time sys.path.insert(0, '/usr/local/lib/rknn2_env') from rknnlite.api import RKNNLite import numpy as np rknn = RKNNLite(verbose=False) ret = rknn.load_rknn('/mnt/model/qumagiecore_rk356x.rknn') print('load_rknn:', ret) ret = rknn.init_runtime() print('init_runtime:', ret) input_data = np.zeros((1, 3, 224, 224), dtype=np.uint8) outputs = rknn.inference(inputs=[input_data]) print('inference ok, output shape:', outputs[0].shape) t0 = time.time() for _ in range(100): rknn.inference(inputs=[input_data]) t1 = time.time() print(f'avg: {(t1-t0)/100*1000:.2f} ms, FPS: {100/(t1-t0):.1f}') rknn.release() print('done') "
11.4 运行自定义模型
将自己的 .rknn 模型(必须 target_platform=rk3568 )放到宿主机:
1 cp your_model.rknn /share/CACHEDEV1_DATA/.qpkg/QuMagieCore/daemon/quai/model/
修改上面的命令:
load_rknn('/mnt/model/your_model.rknn')
根据你的模型输入 shape 修改 np.zeros((1, 3, H, W))
12. 遗留问题与后续工作 12.1 未解决的问题
问题
现状
后续方向
推理速度慢(31.8 ms)
与官方 5.5 ms 差 6 倍
检查 NPU 频率、排除 QuMagie 竞争
NPU 频率节点未知
尝试的路径不对
找 /sys/class/devfreq/fde40000.npu/
Python API 开销
未量化
对比 C/C++ API 性能
与 QuMagie 共存
共享代理,资源竞争
高负载时考虑停 QuMagie
12.2 待验证的优化 1 2 3 4 5 6 7 8 cat /sys/class/devfreq/fde40000.npu/available_frequenciesecho userspace > /sys/class/devfreq/fde40000.npu/governorecho 800000 > /sys/class/devfreq/fde40000.npu/userspace/set_freq
12.3 长期方案
构建独立的 Docker 镜像,包含自己的 rknn-toolkit-lite2 和 npu_transfer_proxy。
在需要独占 NPU 时,临时停止 aicore.container。
生产环境考虑 C/C++ 实现推理核心,减少 Python 开销。
附录 A:关键文件路径速查
文件
路径
QPKG 控制脚本
${QPKG_ROOT}/QuMagieCore.sh
公共库
${QPKG_ROOT}/common.include
容器 entrypoint
${QPKG_ROOT}/daemon/entrypoint.sh
原始 entrypoint
${QPKG_ROOT}/docker_scripts/entrypoint_arm_64.sh
模型目录
${QPKG_ROOT}/daemon/quai/model/
RKNN 环境 1
/usr/local/lib/rknn_env/
RKNN 环境 2
/usr/local/lib/rknn2_env/
系统 RKNN 库
/usr/lib/librknnrt.so
NPU 代理
/usr/local/lib/rknn_env/rknnlite/3rdparty/platform-tools/ntp/linux-aarch64/npu_transfer_proxy
附录 B:术语表
术语
说明
RKNN
Rockchip Neural Network,Rockchip 的 NPU 推理框架
RKNNLite
板端轻量推理库,Python 包名 rknnlite
RKNN-Toolkit2
PC 端模型转换工具,Python 包名 rknn
npu_transfer_proxy
NPU 用户态通信代理,单实例守护进程
renderD128/129
DRM render 节点,NPU 通过它访问
RGA
Rockchip Graphics Accelerator,2D 图形加速
QPKG
QNAP Package,QNAP 的应用包
Container Station
QNAP 的容器管理平台
本文由 AI 辅助整理,基于 2026-09-24 的实际设备调试会话。 所有命令、报错和输出均来自终端记录,未做修改。 如需转载,请保留本声明。