
在嵌入式设备上跑AI推理我相信很多朋友最先碰到的往往不是模型精度不够而是NPU根本不工作。就拿RK3568这块板子来说它内置的NPU算力不大但胜在能跑常见的分类、检测模型功耗又低非常适合放在边缘设备上做视频分析、OCR预识别或者简单的人形检测。可每次拿到一块新板子大概率都会在NPU驱动这个环节卡一下要么rknpu.ko加载失败要么/dev/rknpu节点不存在要么驱动装上了但用户态rknn_init一调用就返回-5。这篇文章就把我在RK3568上处理NPU驱动问题的完整思路、命令和解决办法整理出来从内核态驱动、设备树、用户态运行时三个层面逐个拆给正在调板的你一个可以直接照着做的排查路径。1. RK3568 NPU驱动的整体链路拆解1.1 三层结构硬件、内核模块、用户态运行时先说清楚RK3568的NPU是怎么被用起来的。它并不是一个简单的IP核光靠CPU访问寄存器是跑不了神经网络的。整套东西分三层最底层的NPU硬件本身在RK3568里占据了独立的内存映射地址空间有自己的时钟、电源域、IOMMU甚至还有独立的SRAM区域。它需要内核态程序去管理这一步交给内核驱动模块rknpu.ko。中间一层就是rknpu.ko。它在内核里注册成一个misc设备对外暴露一个设备节点一般是/dev/rknpu。这个模块的工作包括控制NPU的电源域和时钟、配置IOMMU做内存地址映射、把NPU从复位状态释放出来、以及接收用户态发过来的消息。可以把它理解成一座桥桥的一头是NPU硬件另一头是用户态程序。最上面一层是用户态运行时库也就是大家熟悉的RKNN系列工具链里带的librknnrt.so。你的应用程序调用rknn_init、rknn_run这类API时实际是运行时库通过open(/dev/rknpu)和ioctl与内核驱动打交道把计算图下发到NPU上执行。很多人会混淆“驱动”和“运行时库”。如果只是替换librknnrt.so那不算解决驱动问题只是换了用户态一端真正的“NPU驱动”一般指rknpu.ko这个内核模块以及和设备树里NPU节点相关的电源、内存配置。换句话说驱动解决的是“设备能不能被系统识别、能不能被访问”的问题运行时库解决的是“模型能不能在设备上跑起来”的问题。1.2 问题分层先定位再动手别一头扎进代码里围绕这个结构我把平时遇到的NPU问题粗暴地分成四类驱动模块层rknpu.ko没有加载或者加载时报错比如version magic mismatch、Unknown symbol。设备节点层/dev/rknpu不存在或者权限不对导致用户态打不开。运行时库层驱动装好了节点也在但librknnrt.so与驱动版本不匹配或者依赖的库缺失rknn_init失败。资源限制层驱动和库都正常但内存不够、CMA分配失败、电源域没起来导致NPU运行报错或者性能异常。实际排查时我的顺序是先看内核模块在不在再看设备节点在不在然后看内核日志有没有报错最后才轮到用户态问题。如果你一上来就盯着应用程序的报错代码看很容易绕弯路。因为rknn_init返回-5这个错误根因可能是驱动没加载也可能是权限不对还可能是运行时库和驱动版本不一致不先看内核态很难判断。2. 环境排查驱动装没装、能不能用的五个检查命令2.1 检查内核版本与驱动模块加载状态拿到一块RK3568板子先别急着编译任何东西先用三条命令确认当前环境uname -a lsmod | grep rknpu find /lib/modules/$(uname -r) -name *rknpu* 2/dev/nulluname -a相当重要后面的所有版本匹配都要以它为准。比如内核版本是4.19.219那驱动模块必须是用4.19.219同版本内核源码编译出来的否则modprobe大概率会报version magic不匹配。lsmod如果输出了rknpu说明模块已经加载。如果没有任何输出也先别慌可能是模块文件不在/lib/modules目录下或者depmod没有生成依赖索引。find命令就是用来确认模块文件存不存在的。这里有个坑很多SDK会把内核模块放在板卡根文件系统的/lib/modules目录里但有些精简rootfs只放了驱动没放modules.dep导致modprobe找不到设备名。遇到这个情况可以用depmod -a重新生成依赖。2.2 确认NPU设备节点和内核日志模块加载之后还要确认设备节点有没有自动创建ls -l /dev/rknpu*正常情况应该有一个主设备号10、次设备号120的字符设备。如果没有最常见的原因是驱动注册misc设备失败或者根文件系统里的udev/mdev规则没有覆盖到这个设备。此时要立刻刷内核日志dmesg | grep -iE rknpu|npu看到类似“rknpu: probe success”或者“rk-npu: registered”这样的日志说明驱动加载和设备注册都正常。如果什么都没看到先查模块是否真的加载了如果有报错比如“version magic”“Unknown symbol”或者“Failed to register misc device”记下来后面逐一拆解。另一个容易忽视的点是权限。很多开发阶段直接用root用户跑没问题但如果你用普通用户跑推理程序/dev/rknpu没有0666权限就会出现Permission denied。这种问题用udev规则解决在/etc/udev/rules.d/里加一条KERNELrknpu, MODE06662.3 用户态运行库版本核对确认内核态没毛病之后再到应用目录下检查运行时库ldd ./your_infer_app ls -l /usr/lib/librknnrt.so strings /usr/lib/librknnrt.so | grep -iE version|rknn | head -20重点看两件事第一你的程序链接到的librknnrt.so是不是实际存在于设备上第二库版本是否和驱动配套。官方RKNN套件的1.x版本和2.x版本在设备端运行时库的接口上差异很大驱动和库混搭会出现“编译时能过、运行时莫名失败”的诡异问题。我自己的经验是拿到板子之后先把板厂或者原厂SDK里自带的librknnrt.so原封不动地放到/usr/lib里不要因为自己编译了新版教程里的库就去替换除非你已经确认内核驱动也能跟上新版接口。驱动和运行时库永远是成对升级的。2.4 设备树里NPU节点是否存在最后一步检查设备树这一步经常被跳过去但很多诡异问题都是在这埋下的ls /proc/device-tree | grep -i npu cat /proc/device-tree/npufde40000/status 2/dev/null设备树节点里如果status字段是“disabled”或者整个npu节点都不存在那驱动probe时找不到对应platform_device内核模块就会静默成功加载但不创建设备节点。出现这种情况不要纠结驱动代码直接检查板级dts文件确认npu节点、电源域、IOMMU都已经使能。3. 解决驱动加载失败的三条主流路径3.1 最稳妥的方案使用SDK配套的预编译驱动如果手上有完整的Linux SDK里面带kernel源码和预编译镜像那最稳的办法是直接用SDK里的内核和模块不要自己东拼西凑。原厂SDK发布时内核、设备树、驱动模块、用户态工具链是同一套代码里出来的版本锁定编译选项一致基本不会出现vermagic冲突。操作思路大概是把SDK里编译好的boot.img或者内核镜像、dtb文件烧录到板卡。把SDK rootfs里/lib/modules/整个目录同步到板子根文件系统。重启后执行depmod -a再modprobe rknpu。这个方案没什么技术含量但它能最大程度排除人为干扰。如果你拿到一块别人调过的板子不知道之前刷过什么内核、什么驱动不要犹豫先重刷整套镜像这是最快的复位手段。我在实际项目中吃过大亏某块板卡为了修别的外设bug同事手动替换了设备树结果NPU消失了两天。最后对比SDK原始dtb才发现npu节点的power-domains被误删了。所以每次动设备树之前先备份原始dtb养成习惯。3.2 自己编译内核模块从源码到加载全流程当板卡的存量内核版本和SDK不同或者你需要给自定义内核集成NPU驱动时就得自己编译rknpu模块。这里有几个必须满足的前置条件运行板卡的内核版本与编译设备上用的内核源码版本严格一致。交叉编译工具链能够编译该内核版本一般用aarch64-linux-gnu-gcc。内核源码里的配置项CONFIG_ROCKCHIP_RKNPU要确认另外相关的IOMMU、devfreq、DMA CMA能力都要开启。编译时可以只编这个模块不需要编整个内核镜像export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- make menuconfig # 确认CONFIG_ROCKCHIP_RKNPU为y或m # 只编译模块 make Mdrivers/rknpu modules编译完成之后把rknpu.ko拷贝到目标板scp drivers/rknpu/rknpu.ko root板卡IP:/lib/modules/$(uname -r)/extra/ ssh root板卡IP depmod -a modprobe rknpu这里想强调的是vermagic问题。每个内核模块都带一个版本魔法由内核版本、编译时SMP/PREEMPT等配置决定。板卡上运行的内核和编译模块的内核只要有一点差异比如打开了PREEMPT_RT模块就加载不起来。最快排查方法是modinfo rknpu.ko | grep vermagic cat /proc/version两者里的字符串必须一致。如果实在不想因为这个原因反复调可以把内核重新编译成和模块匹配的版本但这属于改内核了工作量会大许多。更聪明的做法是直接使用板卡运行内核对应的内核源码不要用MAINLINE内核去编厂商SDK的驱动。3.3 DKMS应对内核升级后的驱动失效还有一种场景你的板卡升级rootfs之后内核也随之升级比如从4.19.x升到4.19.y原来手动扔到/lib/modules里的rknpu.ko在新内核下彻底失效。这时候可以引入DKMS机制让系统在新内核安装时自动重新编译驱动模块。在编译机上创建/usr/src/rknpu-2.0/目录放入驱动源码和dkms.confPACKAGE_NAMErknpu PACKAGE_VERSION2.0 BUILT_MODULE_NAME[0]rknpu DEST_MODULE_LOCATION[0]/kernel/drivers/rknpu AUTOINSTALLyes然后执行dkms add -m rknpu -v 2.0 dkms build -m rknpu -v 2.0 dkms install -m rknpu -v 2.0DKMS会在当前内核编译一次驱动并安装到/lib/modules/$(uname -r)/updates/dkms/下还要运行depmod。之后每换一个内核DKMS的hook都会自动对新内核重新编译。这个方案适合长期维护的板卡尤其适合那些定期更新内核安全补丁的ARM64设备。不过DKMS也不是万能的前提是运行内核里有对应的内核头文件包而且交叉编译环境能正确匹配。如果你的内核是板厂魔改过的不标准内核DKMS build那一步可能还是会失败这时候老老实实用SDK路线更靠谱。3.4 设备树补充电源域、IOMMU与内存预留RK3568的NPU驱动能否probe成功和设备树节点的完整程度关系极大。常见的dts片段会包含寄存器地址与中断比如reg 0x0 0xfde40000 0x0 0x40000。电源域引用power controller里的npu对应电源域。时钟引用npu core和bus时钟。内存预留在reserved-memory节点里给NPU预留一段连续物理内存比如no-map属性。IOMMU节点让NPU能通过IOMMU访问外部内存。如果只缺了最基础的中断、时钟驱动通常会在probe时直接返回错误如果缺了内存预留或IOMMU可能出现insmod成功但NPU异常跑不动或者内存分配失败。检查设备树时我习惯用dtc反编译现有dtbdtc -I fs -O dts /proc/device-tree -o current.dts grep -n npu current.dts注意/proc/device-tree是运行时展开的fdtuboot实际上也可能对dtb做过修改。如果发现npu节点status是disabled可以直接在dts源码里改成okay重新编译dtb烧录。如果发现整个节点都不存在那就得回到SDK的板级dts找基础dtsi确认是否 A 引用了公共的rk3568-npu.dtsi。4. 编译安装和验证的完整实操记录4.1 一次真实的驱动排障过程用一个典型场景来走一遍完整流程。假设手里有一块RK3568板卡刷了一个精简版Debian系统内核是4.19.219但SDK内核源码也是4.19.219驱动源码在SDK kernel/drivers/rknpu目录下。先做状态登记uname -r # 输出 4.19.219 lsmod | grep rknpu # 无输出 ls -l /dev/rknpu* # 无此类设备文件 dmesg | grep -i rknpu # 无输出这说明要么模块没装要么装了也没加载成功。再查模块文件find /lib/modules/$(uname -r) -name *rknpu*没有任何结果代表根文件系统里就没有驱动模块。这种情况下别怨系统老实去SDK里编译。4.2 交叉编译并安装模块我的编译主机是x86_64 Ubuntu环境工具链用的是aarch64-linux-gnu-gcc。进入SDK的kernel目录export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 确认config cat .config | grep RKNPU # CONFIG_ROCKCHIP_RKNPUm看到CONFIG_ROCKCHIP_RKNPUm说明驱动编译为模块符合预期。然后单独编译这个目录下的模块make Mdrivers/rknpu modules ls drivers/rknpu/rknpu.ko编译产出rknpu.ko后在板上创建标准模块目录拷贝并加载mkdir -p /lib/modules/$(uname -r)/extra cp rknpu.ko /lib/modules/$(uname -r)/extra/ depmod -a modprobe rknpu如果运气好dmesg会多出驱动probe的日志dmesg | grep -i rknpu # rknpu: ver1.0 # rknpu: 0x10000000,0x10000000 # rknpu: regulator_enable done # rknpu: clk_enable done # rknpu: rknpu probe success再查看设备节点ls -l /dev/rknpu # crw-rw---- 1 root root 10, 120 Jan 1 08:00 /dev/rknpu到这里驱动层面的问题已经解决了。4.3 让模块开机自动加载开发阶段手动modprobe没问题但产品化之后必须开机自动加载。最简单的办法echo rknpu /etc/modules-load.d/rknpu.conf这个文件里的内容是一个模块名initramfs或systemd会在启动阶段自动加载。如果你用的是传统init脚本记得确保depmod已经生成依赖否则启动时仍然找不到模块。4.4 用推理程序验证驱动是否全程可用驱动加载成功不意味着整条链路没问题还得跑一个简单的推理demo验证。我用官方RKNN套件里的分类示例把模型文件和图片放到板子上然后执行./rknn_classify_demo model.rknn test.jpg第一次跑起来的时候看到模型加载耗时几十毫秒推理耗时十几毫秒心里才踏实。如果在这里碰到rknn_init返回-5通常不是驱动问题而是库里没有打开/dev/rknpu的权限或者库与驱动不匹配。可以用strace看一下strace -f -e traceopenat ./rknn_classify_demo model.rknn test.jpg 21 | grep rknpu能找到open(/dev/rknpu)失败的记录就说明权限或者节点路径不对按2.2的udev规则处理。5. 常见问题速查表与避坑心得5.1 问题速查表我在不同板卡上碰到过的问题基本都集中在下面这张表里。每一行里的排查方法都是实际用过的可以直接照抄。问题现象可能原因排查与解决modprobe rknpu提示 module not found模块不在/lib/modules目录或depmod索引未生成find检查文件depmod -a后重试insmod rknpu.ko报version magic mismatch模块与运行内核编译版本不一致modinfo vermagic与/proc/version对比换配套源码重编insmod报Unknown symbol驱动依赖的内核符号缺失或依赖模块未加载dmesg看符号名先加载依赖模块或重编内核/dev/rknpu设备节点不存在驱动注册misc设备失败、设备树节点disableddmesg查misc注册失败检查设备树npu节点状态/dev/rknpu存在但权限不足用户态非root访问被拒加udev规则MODE0666rknn_init返回-5驱动未加载、权限不足、运行时库不匹配、节点打不开用strace跟踪openat检查驱动和库版本rknn_init成功但rknn_run报内存错误CMA连续内存不足或IOMMU未配置调整cmdline的cma大小dmesg查NPU内存申请日志驱动加载成功但NPU性能极低频率锁在最低档、散热降频、电源域异常查看/sys/class/devfreq下NPU频率检查供电和温度内核升级后驱动失效模块与新版内核不匹配使用DKMS或直接使用同一版本SDK重新编译模块5.2 避坑心得先说版本匹配。我有一次为了一个很小的算子支持把librknnrt.so换成了较新的版本结果驱动还是旧的rknn_init直接报“version mismatch”。后来老老实实把整个SDK里配套的驱动和库一起换一次就过了。这件事给我的教训是在RK3568这种半封闭的NPU生态里没必要追新要追求版本一致性。芯片原厂发布SDK时内部所有版本已经经过验证你单独升级任何一个环节都是在给自己挖坑。再看内存问题。RK3568的NPU对内存连续性有要求尤其是在跑稍微大一些的检测模型时。SDK默认cmdline里可能已经留了CMA区域但如果你的板卡rootfs修改过cmdline或者配置了较大的GPU显存内存NPU就可能在运行中途报分配失败。解决办法是给kernel cmdline加上cma大小cma128M但修改内核启动参数一定要谨慎加太大容易影响系统其他内存使用加太小又不够NPU模型切分。一个比较稳的调整建议是128M起步通过dmesg里NPU的拿内存日志反复试找到稳定的边界值。然后是设备树。很多开发者拿到新板卡第一件事就是刷一个通用Ubuntu镜像然后发现NPU怎么都不工作。你仔细查dmesg发现驱动压根没有probe再一看/proc/device-treenpu节点status是disabled甚至不存在。这不是驱动问题是设备树和内核镜像根本不配套。所以每次刷系统时内核镜像、dtb、rknpu模块、librknnrt.so四个东西一定是同一套SDK编译产物这是最快的排障方式。最后想分享一个小技巧在长期维护的多块板卡上我会写一个简单的诊断脚本放到/usr/local/sbin/下名字就叫rknpu_check.sh。内容就是前面2.1到2.4命令的组合#!/bin/bash echo kernel uname -r echo module lsmod | grep rknpu || echo module not loaded echo node ls -l /dev/rknpu 2/dev/null || echo node missing echo dmesg dmesg | grep -iE rknpu|npu | tail -20 echo library ls -l /usr/lib/librknnrt.so 2/dev/null || echo runtime lib missing每次遇到问题先把脚本跑一遍输出的信息已经能覆盖80%的驱动故障。这个脚本帮我在好几块板子上快速锁定了问题省掉了大量反复敲命令的时间。回到开头说的那个场景RK3568的NPU驱动问题绝大多数都不是真的“硬件坏了”而是版本、设备树、模块路径、权限这些系统工程问题。只要把链路拆开一层一层验证解决起来其实很快。希望这篇整理能帮你少走弯路把更多时间花在NPU应用本身。