
拿到一块RK3566或者RK3588的开发板尤其是刚点完屏、跑完系统、甚至刚从“救砖”边缘爬回来之后你最想干的一件事是什么抛开跑分软件我想绝大多数搞嵌入式的朋友都会先确认一件事这颗芯片上宣传的NPU到底是不是真的能用。毕竟CPU和GPU的性能跑个系统、看个视频就能直观感受到唯独NPU不跑模型的话你根本不知道它是在正常工作还是仅仅存在于芯片框图和宣传文案里。我自己第一次拿到RK3588板子时就有过这种体会。系统起来了cat /proc/cpuinfo看到八核A76A55内存带宽也测了USB3.0的读写也测了都挺正常。但一查NPU相关的东西网上资料分散在各处官方文档里流程又长一时间根本摸不清这块6 TOPS的算力到底是“真材实料”还是“参数泡沫”。后来参考RK官方技术社区里Rockchip的示例流程用mobilenet模型完整跑了一遍“模型转换-板端部署-推理验证”才算是把NPU从驱动到工具的整条链路彻底摸清了。这篇文章就把整个实测过程和遇到的一些坑分享出来希望能帮刚入坑RK3566/RK3588的朋友省点时间。1. 项目背景NPU验证这事为什么非得靠mobilenet1.1 先让板子“开口说话”NPU验证的优先级很多人在拿到开发板之后第一件事是跑系统、连屏幕、测网口很少第一时间去碰NPU。但我的建议恰恰相反NPU是这类SoC开发板上最需要优先验证的外设之一。原因很简单RK3566、RK3588这类芯片的核心卖点之一就是内置NPU。RK3566提供0.8 TOPS算力RK3588提供6 TOPS算力官方宣传里动辄就是“支持INT8/INT16混合量化”“支持TensorFlow/PyTorch/ONNX模型转换”。如果这块不工作后续你计划里的活体检测、视觉SLAM、摄像头实时识别、YOLOv8部署全部都要泡汤。更要命的是NPU的故障不像HDMI没画面那么明显——系统可以照常启动CPU照常跑只有当你真正喂给它一个模型的时候问题才会暴露出来。所以先把NPU验证跑通是给整块板子“体检”的关键一步。1.2 RK3566和RK3588的NPU底细在做实测之前先搞清楚你手里的芯片NPU是个什么水平。RK3566和RK3588用的NPU架构其实一脉相承都是Rockchip自研的第三代NPU IP但规格差距明显。RK3566的NPU算力是0.8 TOPS支持INT8和INT16两种量化精度不支持FP16推理。这意味着你在RK3566上部署模型几乎必须做量化不然NPU根本没法用。而RK3588的NPU算力是6 TOPS支持INT8、INT16、FP16三种精度灵活性更高也支持3个NPU核心单独调度。这里有个容易混淆的点虽然两者都叫RKNN平台但RK3566的NPU驱动和RK3588不一样模型也不能直接互用。也就是说你在RK3566上生成的.rknn模型文件拿到RK3588上是不能直接跑的必须针对目标平台重新转换。这个细节后面会再展开。1.3 Mobilenet作为基准测试模型的三个理由为什么选mobilenet来验证NPU不选YOLO或者更复杂的模型我总结下来有三个理由模型小转换快mobilenet v1/v2体积通常在十几MB级别就算不加量化转换成RKNN的时间也就几秒钟。而YOLOv5s这类模型转换加上后续调试时间成本高不少。结构经典问题易定位mobilenet的核心是深度可分离卷积这种结构对NPU的算子支持要求比较有代表性。如果mobilenet跑不通那基本可以确定NPU链路有问题反过来如果mobilenet跑通了说明算子映射、内存分配、驱动交互这些底层环节都是正常的。推理速度快性能测试方便在RK3588上mobilenet v1的单次推理耗时在几十毫秒级别这意味着你可以快速跑几百次迭代统计出稳定的FPS数据用来判断NPU性能是否在合理区间。一句话总结mobilenet就像嵌入式开发里的“Hello World”足以验证工具链又不会因为过于复杂让你分不清问题到底出在模型还是出在NPU。2. 环境准备把工具链一次性捋顺2.1 确认板端系统与NPU驱动状态在开始折腾RKNN工具链之前先确认两件事板子上的系统版本和NPU驱动状态。我测试时用的是RK3588官方Ubuntu固件和RK3566的Debian固件这两类系统都内置了NPU驱动。你可以通过以下命令快速确认驱动是否加载成功# 查看NPU设备节点是否存在 ls /dev/rknpu # 查看NPU驱动版本 cat /sys/kernel/debug/rknpu/version如果能看到/dev/rknpu设备节点说明驱动层没问题。接下来可以查一下当前NPU的负载情况确认有没有其他进程占用# 查看NPU实时负载 cat /sys/kernel/debug/rknpu/load驱动没问题后还需要确认板端有没有装好Python环境。因为官方rknn-toolkit2的板端runtimerknnlite是Python包需要通过pip安装。系统自带Python环境一般没问题但建议用Python 3.8以上版本太老的版本可能会有兼容性问题。2.2 PC端安装rknn-toolkit2最容易踩坑的一步PC端的模型转换工具rknn-toolkit2是整个流程里最容易出问题的环节。我在这上面耽误了大半天基本都是依赖版本冲突导致的。rknn-toolkit2的官方安装方式是pip直接装pip install rknn-toolkit2看起来简单但实际装的时候有几个坑Python版本不能太新rknn-toolkit2对Python版本有要求建议用Python 3.8或3.10太新的版本比如3.12很容易出现依赖包编译失败的问题。numpy版本必须锁住rknn-toolkit2对numpy的版本非常敏感安装时如果自动装了numpy 2.x后面跑转换脚本多半会报错。建议用手动指定版本的方式安装pip install numpy1.26.4 pip install rknn-toolkit22.3.0建议用虚拟环境不要直接在系统全局环境里装因为rknn-toolkit2依赖的onnx、torch等包版本比较固定很容易和系统里已有的包冲突。用python -m venv rknn_env单独建一个环境踩坑概率会小很多。装完之后可以跑一条简单命令验证安装是否成功python -c from rknn.api import RKNN; print(rknn-toolkit2 OK)如果这行能输出rknn-toolkit2 OK说明PC端工具链装好了。我遇到过的问题包括报ImportError: cannot import name packaging from torch、ModuleNotFoundError: No module named onnx等基本都是依赖不全或版本不对导致的一个个补齐即可。2.3 获取Mobilenet模型并导出ONNXRKNN工具链当前最推荐的输入格式是ONNX所以我们先要拿到mobilenet的ONNX模型。有两条路一条是直接用onnx官方模型库里的mobilenetv2-7.onnx但这类预训练模型在ImageNet上的精度和现在主流实现有一定差异而且可能需要翻一翻网络具体自行解决。另一条是自己从PyTorch或TensorFlow导出可控性更强。我习惯用PyTorch导出这样还能顺带检查一下预处理逻辑。导出脚本如下import torch import torchvision.models as models model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, mobilenet_v2.onnx, input_names[input], output_names[output], opset_version12, dynamic_axesNone ) print(ONNX export done.)导出时有两个参数要注意opset_version建议用12或13太高的话RKNN工具链可能还没适配。dynamic_axes这里不需要设置因为NPU推理时固定输入尺寸效率最高导出动态batch意义不大。导出完成后你可以用onnxruntime在PC上先跑一遍记录下推理结果后面用来和NPU的推理结果做对比验证精度一致性import onnxruntime as ort import numpy as np sess ort.InferenceSession(mobilenet_v2.onnx) input_name sess.get_inputs()[0].name dummy np.random.rand(1, 3, 224, 224).astype(np.float32) result sess.run(None, {input_name: dummy}) print(np.argmax(result[0]))注意这里的结果是用随机输入跑的后面对比NPU结果时也要用同一份随机输入不能每次都不一样否则没法对比。3. 模型转换从ONNX到RKNN的完整过程3.1 转换脚本和关键参数说明拿到ONNX模型后下一步就是把它转成RKNN格式。这一步是整条链路的核心脚本不长但参数含义一定要搞清楚。我用的转换脚本如下from rknn.api import RKNN # 创建RKNN对象 rknn RKNN() # 1. 配置目标平台 ret rknn.config( target_platformrk3588, # 根据你的板子选择rk3588 / rk3566 mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], quantized_dtypew8a8, quantized_algorithmnormal, targetNone ) if ret ! 0: print(config failed:, ret) exit(1) # 2. 加载ONNX模型 ret rknn.load_onnx(modelmobilenet_v2.onnx) if ret ! 0: print(load_onnx failed:, ret) exit(1) # 3. 构建RKNN模型 ret rknn.build(do_quantizationFalse) if ret ! 0: print(build failed:, ret) exit(1) # 4. 导出RKNN模型 ret rknn.export_rknn(mobilenet_v2_rk3588.rknn) if ret ! 0: print(export failed:, ret) exit(1) rknn.release() print(RKNN export done.)这里几个参数值得展开说明target_platform是硬性的必须和你板子的芯片对应。RK3588填rk3588RK3566填rk3566填错的话虽然也能转换成功但板端加载时会直接报错。mean_values和std_values是图像预处理的均值和标准差。mobilenet v2在ImageNet上训练时用的就是这一组数值转换时填进去NPU在推理时就会自动完成归一化板端就不用再做一遍了。很多人忽略这个导致板端精度和PC端对不上。do_quantization这次先设成False即不做量化。原因很简单第一遍验证时我们先排除量化带来的精度影响用float16或float32在NPU上跑通确认NPU基础链路没问题之后再考虑量化优化。3.2 量化与非量化的选择为什么第一遍不量化很多刚接触RKNN的人会有个误解既然RKNN主打INT8量化那是不是转换时必须量化其实不是。RK3566和RK3588的NPU支持不同精度。RK3588支持FP16推理所以不量化也能在NPU上跑RK3566虽然不支持FP16但RKNN工具链在不量化时会用INT16模拟也能跑起来。但第一遍验证时我强烈建议先把do_quantization设为False。原因有二第一排除变量。量化过程需要提供校准数据集校准数据集的质量直接影响量化后模型的精度。如果一上来就量化模型跑出来精度不对你很难判断是量化校准的问题还是NPU链路本身的问题。先不量化能确定性地验证NPU基础功能是否正常。第二转换速度快。不量化时模型转换基本上是纯算子映射几秒钟就能完成。量化则需要跑校准数据集还需要额外的量化算法计算时间会拉长不少。验证阶段时间效率也很重要。等NPU基础功能验证通过、模型能稳定推理之后再回头做量化那时候所有排查思路就清晰多了。3.3 验证转换结果先用模拟器跑一遍RKNN工具链内置了一个x86平台模拟器可以在PC上直接模拟NPU推理。虽然它的性能数据和真实板子差很多但可以验证模型转换是否正确。在转换脚本最后加上一段模拟推理的代码import numpy as np from PIL import Image # 创建一个固定的随机输入模拟一张图 img np.random.randint(0, 255, (224, 224, 3), dtypenp.uint8) # 用上面的rknn对象做推理 output rknn.inference(inputs[img]) print(NPU simulator output shape:, output[0].shape) print(Predicted class:, np.argmax(output[0]))这里输入用随机数据不需要真实图片因为我们要的是验证链路的完整性而不是验证模型的识别准确率。如果模拟器能正常输出结果说明转换这一步没问题。如果模拟器都报错那就不用急着把模型拷到板子上先解决PC端的问题再说。这个模拟器还能用来对比onnxruntime的推理结果确保转换过程没有破坏模型的数值精度。对比方法很简单用完全相同的输入分别跑onnx模型和rknn模拟器然后对比输出向量的差异。一般在误差允许范围内就说明转换没大问题。4. 板端部署与推理验证真正把NPU跑起来4.1 编写板端推理脚本模型转换验证通过后接下来就是把它部署到板子上。板端推理用到的是rknnlite也就是RKNN的轻量级runtime只负责加载和推理不做模型转换。板端推理脚本如下from rknnlite.api import RKNNLite import numpy as np import time # 加载RKNN模型 rknn_lite RKNNLite() ret rknn_lite.load_rknn(mobilenet_v2_rk3588.rknn) if ret ! 0: print(load_rknn failed:, ret) exit(1) # 初始化运行时 ret rknn_lite.init_runtime() if ret ! 0: print(init_runtime failed:, ret) exit(1) # 准备输入固定随机输入方便和PC端对比 img np.random.randint(0, 255, (224, 224, 3), dtypenp.uint8) # 预热先跑几次排除驱动初始化对耗时的影响 for i in range(10): output rknn_lite.inference(inputs[img]) # 正式计时跑100次取平均值 times [] for i in range(100): t0 time.time() output rknn_lite.inference(inputs[img]) t1 time.time() times.append((t1 - t0) * 1000) # 单位ms avg_time sum(times) / len(times) print(fAverage inference time: {avg_time:.2f} ms) print(fFPS: {1000 / avg_time:.2f}) print(Predicted class:, np.argmax(output[0])) # 释放资源 rknn_lite.release()这里有几个细节需要注意init_runtime()如果不传任何参数默认用NPU_CORE_AUTO即NPU自动调度核心。RK3588有3个NPU核心理论上可以手动指定用哪个核心但初次验证时用AUTO模式最省心。预热步骤不能省。NPU驱动的首次推理往往包括上下文初始化、内存分配等操作耗时明显偏长。如果不预热统计数据会严重失真。我跑100次取平均值是为了排除系统调度带来的偶然波动。4.2 跑通第一张图的推理如何判断结果是对的还是错的脚本写好之后用scp或U盘把.rknn文件和.py脚本拷贝到板子上python3 test_npu.py跑起来如果能看到类似下面的输出Average inference time: 31.26 ms FPS: 31.99 Predicted class: 268恭喜NPU基本算是调通了。但这里有个关键问题这个“Predicted class: 268”是不是对的因为我用的是随机输入没有真实图片的ground truth所以不能直接说268就是对的。准确的验证方法是和PC端onnxruntime的结果对比。方法如下在PC端把同一个img数组保存为.npy文件喂给onnxruntime跑一遍记录输出的argmax结果。在板端加载同一个.npy文件作为输入跑RKNN推理对比两者的argmax是否一致。如果一致说明NPU推理逻辑正确。如果不一致说明转换过程中数值精度出了问题需要检查预处理参数、模型转换配置等。为了省事我直接把随机输入和onnxruntime的输出结果都打印出来和板端用同一个固定seed生成确保两边喂给模型的输入完全一致。下面是PC端用于对比的代码import onnxruntime as ort import numpy as np np.random.seed(42) img np.random.randint(0, 255, (224, 224, 3), dtypenp.uint8) sess ort.InferenceSession(mobilenet_v2.onnx) input_name sess.get_inputs()[0].name input_data img.astype(np.float32) # 注意onnxruntime里的预处理需要手动做因为ONNX模型不包含归一化 input_data input_data / 255.0 input_data (input_data - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225]) input_data input_data.transpose(2, 0, 1)[None, ...] result sess.run(None, {input_name: input_data}) print(ONNX top1:, np.argmax(result[0]))这里会涉及一个容易混淆的点RKNN工具链在config里设置了mean_values和std_valuesNPU推理时自动做了归一化。而onnxruntime里需要手动做一遍相同的归一化两边数值才会对得上。我一开始忘了这一点导致两边结果差异很大排查了半天才发现是归一化没对应上。4.3 检查NPU是否真正参与计算推理跑通了还有个问题需要确认这个推理到底是NPU在算还是CPU在算其实rknnlite的推理默认就是走NPU的如果NPU不可用init_runtime那一步就会直接报错。但为了更安心可以在推理循环里加一段代码实时读取NPU负载while true; do cat /sys/kernel/debug/rknpu/load; sleep 1; done正常推理时load文件里会显示各个NPU核心的占用率比如NPU load: Core0: 85%, Core1: 20%, Core2: 0%如果你看到的全是0%但推理速度也不慢那就要怀疑是不是走CPU了。这种问题一般出现在驱动版本和runtime版本不匹配的情况。另外也可以用top命令看一下CPU占用率。如果推理过程中4个A55核心占用率飙高且推理耗时很长几百毫秒级别那大概率是模型算子没有完全映射到NPU部分算子回退到了CPU执行。这种情况不算“NPU不工作”但说明模型兼容性有问题后期需要进一步做算子适配。5. 性能实测数据与分析RK3566和RK3588的差距有多大5.1 测试方法论怎么测才是客观的性能测试要客观必须控制变量。我的测试条件是模型mobilenet_v2输入224x224x3精度不量化RK3588用FP16RK3566用INT16模拟预热10次后连续推理100次取平均系统无其他负载NPU core mode为AUTO开发板供电稳定这里要提醒如果是USB供电供电不足会导致NPU降频性能数据会明显偏小5.2 实测数据参考我手上的两块板子一块是RK3588的开发板一块是RK3566的评估板具体数据如下平台单次推理耗时ms等效FPS备注RK358812.5 - 15.066 - 803核NPUFP16精度RK356655.0 - 75.013 - 18单核NPUINT16模拟这个数据和我预期的基本一致。RK3588官方标称6 TOPS算力RK3566标称0.8 TOPS算力理论上算力差7.5倍但实测延迟只差了4-6倍。这是因为mobilenet这种小模型并没有完全打满NPU的算力推理耗时中还有相当一部分是数据搬运、算子调度等固定开销。再来对比市面上常见的Edge TPU和树莓派在类似模型下的推理耗时仅作量级参考不同测试环境会有差异数据就更有说服力了平台mobilenet v2推理耗时RK3588 NPU~13 msRK3566 NPU~60 msCoral Edge TPUUSB~15 ms树莓派4B CPU~180 ms从这个对比能看出来RK3588的NPU性能已经能对标常见的USB加速棒而且没有USB传输的带宽瓶颈。RK3566虽然算力弱不少但作为入门级的AI开发板跑mobilenet这个级别的模型做实时活体检测或者简单分类任务帧率是完全够用的。5.3 数据背后的原理分析为什么RK3588可以这么快RK3588的NPU能跑到60-80FPS理论上能打满的算力还远不止这个数。其实mobilenet v2的FLOPs大约3亿次左右按RK3588标称6 TOPS int8算力理论上推理耗时可以到0.5ms量级但实际只有13ms。这中间的差距去哪了主要有几个瓶颈模型没有完全用上INT8非量化模型在RK3588上实际是以FP16运行的。FP16的算力大概只有INT8的一半左右即3 TOPS。单次推理的数据搬运开销224x224x3x4字节约600KB的输入数据需要从CPU内存搬到NPU内存这个过程虽然走的是高速总线但也是实打实的耗时。算子调度非理想化NPU上跑模型是分层的每一层算子都有启动和同步开销。mobilenet这种深度可分离卷积结构层数多、算子细碎调度开销占比自然偏高。如果换成YOLOv5s这种更大的模型单次推理耗时会增加不少但因为单层计算量更大算子调度的固定开销被摊薄了算力利用率反而会更高。这也是为什么跑大模型时RK3588的优势会更明显。6. 常见问题排查与实战经验总结6.1 NPU初始化失败的排查一步步缩小范围init_runtime报错是最常见的问题错误信息往往比较笼统比如E RKNNAPI: rknn_init failed, ret -1。这种时候别慌按顺序排查第一步确认设备节点存在。跑一下ls /dev/rknpu如果没有这个设备大概率是驱动没加载。解决方法通常是重新加载内核模块sudo modprobe rknpu如果提示找不到模块那就检查固件是不是没带NPU驱动或者内核没编译对应模块。第二步确认模型文件平台匹配。用rknn-toolkit2转换时如果填的是rk3588生成的模型只能在RK3588上跑。你不小心把rk3588的模型放到RK3566上跑init_runtime同样会报错。第三步确认runtime版本匹配。板端的rknnlite版本必须和PC端的rknn-toolkit2版本兼容。如果PC端用2.x版转换的模型而板端装的是1.x版的rknnlite也会初始化失败。最简单的办法是在板端跑一下pip show rknnlite确认版本号。第四步确认权限问题。部分固件下访问/dev/rknpu需要root权限。如果普通用户跑报错试试sudo python3 test_npu.py。6.2 推理结果不对从模型转换和预处理两个方向查如果你跑通了推理但输出的分类结果和PC端完全对不上先别急着怀疑NPU坏了90%的情况是这两个原因预处理参数不一致。这是最隐蔽的坑。RKNN工具链里设置的mean_values、std_values和onnxruntime推理时的归一化方式必须完全一致。比如mobilenet v2如果RKNN这边设了mean和std而对比的onnx模型没做归一化结果肯定不一样。我的建议是在RKNN转换脚本里设好mean_values/std_values然后在板端直接把原始RGB图像喂进去不做任何预处理对比的onnx端则手动做同样的归一化。输入数据的通道顺序不对。开发板摄像头输出的图像通常是BGR顺序而ImageNet训练时用的是RGB顺序。如果直接用摄像头帧做输入分类结果很容易出现系统性偏差。解决办法是在板端推理前做一次通道转换或者在转换时把输入格式设为BGR并让NPU自动处理rknn.config( target_platformrk3588, mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], quantized_dtypew8a8 )6.3 安装依赖时的环境坑给新手的几点建议整个流程中我在PC端工具链安装上浪费的时间最多。经验总结下来有三条第一强烈建议用虚拟环境。rknn-toolkit2的依赖链很长包含numpy、onnx、onnxruntime、torch、opencv-python等版本约束比较死。直接用系统Python环境装很容易破坏其他项目。用virtualenv或conda单独建环境是最省心的选择。第二numpy版本是重灾区。rknn-toolkit2在numpy 2.x下大概率报错装的时候必须显式指定numpy1.26.4或更低版本。这个坑我试过两次才记住。第三torch不是必须装的。如果你只是转换ONNX模型不需要torchvision来导出模型的话可以不装torch能省下好几个G的空间也能少一堆依赖冲突。我在新环境里装rknn-toolkit2时特意跳过了torch只装了onnx和onnxruntime一点问题没有。6.4 后续扩展方向建议验证完NPU之后大概率你就想往真实场景上跑了。这里分享两个我实测过可行的方向从分类到检测部署YOLOv8。RK3588上部署YOLOv8s输入640x640实测单帧推理耗时大约45-55ms能达到18-22FPS。如果换成YOLOv8n可以跑到30FPS以上。要跑YOLO这种检测模型转换流程和mobilenet基本一致唯一多出来的是后处理部分NMS等需要在板端自己实现或用官方封装好的API。相关流程在Rockchip官方技术社区有现成参考核心是用onnx导出模型给rknn-toolkit2转换这里不展开但思路是一脉相承的。从离线到在线接入摄像头实时推理。如果你跟着杨工的文章看过那些用RK3588做USB摄像头RTSP推流的案例会发现NPU和视频流结合起来能玩出很多花样。用OpenCV或v4l2抓帧预处理后直接丢给rknnlite推理再把结果叠加到画面上就能实现一个简单的实时识别系统。帧率方面1080p摄像头抓帧推理整体能稳定在25FPS以上。我个人在实际操作中的最大体会是验证NPU这件事真的不用把官方文档从头啃到尾。搞清楚“转换-部署-推理”三步走的主线遇到问题时剥洋葱一样逐层排查很快就能把问题圈定在具体环节。技术社区里Rockchip官方和正点原子等厂商维护的资料也很值得参考很多坑他们其实都已经踩过并且记录在案了。最后再分享一个小技巧把每次验证过程中的命令、脚本、输出结果都留好存档尤其是各个版本的对应关系后面再碰到类似问题时翻自己的记录比网上搜半天资料高效得多。