1. 为什么要在RK3566上折腾图像分类模型部署
第一次拿到RK3566这块板子的时候,我其实没太当回事。四核A55、Mali-G52、0.8Tops NPU,参数放在2024年看确实不算亮眼,但它的定位非常清晰——低功耗边缘计算盒子、智能门禁、工业质检终端、零售货柜识别,这些场景不需要跑大模型,但需要稳定、便宜、长期供货的芯片。图像分类恰好是这类场景里最高频的需求:判断产品有没有瑕疵、识别货架上的商品类别、区分人员是否佩戴安全帽,本质上都是分类任务。
问题在于,很多团队在PC上训练完模型,导出ONNX之后往板子上扔,发现要么跑不起来,要么速度慢得离谱,要么精度掉得莫名其妙。我前后在RK3566上部署过ResNet、MobileNet、EfficientNet、ViT、ConvNeXt、Swin Transformer这几类主流模型,踩过的坑足够写一本小册子。这篇文章就把整个实验过程拆开讲清楚:哪些模型适合上RK3566、RKNN工具链怎么配置、量化到底会掉多少精度、不同输入分辨率下帧率差多少、遇到算子不支持怎么绕过去。
适合谁看?如果你手上有RK3566的开发板,或者正在选型边缘AI芯片,又或者你已经跑通了PC端推理但卡在板端部署这一步,这篇内容应该能帮你省下至少两周的试错时间。我不讲空洞的理论,只讲我实际跑出来的数据和踩过的坑。
2. RK3566的NPU到底能吃下什么模型
2.1 硬件规格与算力边界
RK3566的NPU是瑞芯微自研的RKNPU2架构,标称0.8Tops INT8算力。这个数字要拆开看:它指的是INT8精度下的峰值算力,FP16大概只有一半,FP32基本不用指望。NPU内部有独立的MAC阵列和缓存,但内存带宽是共享的,LPDDR4/LPDDR4X的带宽直接决定了模型能不能跑满算力。
我实测下来,RK3566的NPU在跑轻量级模型时利用率能到70%以上,但跑大模型时瓶颈往往在内存带宽而不是算力。举个例子,ResNet50的参数量是25M左右,权重加载就要占不少带宽,输入分辨率一上去,特征图内存占用暴涨,NPU就开始等数据了。
| 硬件参数 | 规格 | 对部署的影响 |
|---|---|---|
| CPU | 四核Cortex-A55 @1.8GHz | 预处理和后处理的主要承担者 |
| NPU | 0.8Tops INT8 | 只支持INT8/INT16量化推理 |
| 内存 | LPDDR4/LPDDR4X | 带宽决定大模型的实际帧率 |
| 存储 | eMMC 5.1 / SD 3.0 | 模型加载速度受限于存储读取 |
2.2 RKNN工具链的版本选择
瑞芯微的RKNN-Toolkit2是PC端的模型转换工具,RKNN-Toolkit-Lite2是板端推理库。这里有个大坑:版本必须严格对应。我试过用RKNN-Toolkit2 1.5.0转换的模型,在1.4.0的板端库上直接报错,错误信息还特别模糊,只说不支持某个算子,实际上就是版本不匹配。
截至我写这篇文章的时候,稳定可用的组合是RKNN-Toolkit2 1.6.0配RKNN-Toolkit-Lite2 1.6.0,对应的NPU驱动版本是0.9.6以上。如果你用的是Buildroot或者Debian固件,先确认一下/dev/rknpu设备节点是否存在,没有这个节点说明NPU驱动没加载,后面所有操作都是白费。
注意:瑞芯微官网的固件下载页面里,RK3566的固件更新频率不高,建议直接找FAE要最新的SDK包,里面会附带匹配的RKNN库和驱动。
2.3 模型选型的核心原则
在RK3566上选模型,我总结了三条硬性标准:
第一,参数量控制在15M以内。超过这个数,模型加载时间会明显变长,而且推理时内存带宽压力大,帧率上不去。MobileNetV3-Small只有2.5M,ResNet18是11M,这两个是甜点区。
第二,避免动态shape。RKNN对动态输入的支持很有限,转换时最好固定输入尺寸。我试过用动态batch转换,结果板端推理时直接段错误。
第三,优先选有官方量化示例的模型。瑞芯微的GitHub仓库里提供了ResNet、MobileNet、YOLO系列的量化脚本,这些模型经过验证,量化后精度损失可控。冷门模型自己写量化配置,很容易掉点。
3. 从PyTorch到RKNN的完整转换流程
3.1 环境搭建与依赖安装
PC端我用的Ubuntu 20.04,Python 3.8。RKNN-Toolkit2对Python版本有要求,3.9以上有些依赖包会冲突。安装命令如下:
pip install rknn-toolkit2==1.6.0 pip install torch==1.13.1 torchvision==0.14.1 pip install onnx==1.14.0 onnxruntime==1.15.1板端需要安装RKNN-Toolkit-Lite2,这个通常包含在固件里,如果没有,可以从SDK包的runtime目录里找到whl文件手动安装。
pip install rknn_toolkit_lite2-1.6.0-cp38-cp38-linux_aarch64.whl安装完成后,跑一个简单的测试脚本确认NPU可用:
from rknnlite.api import RKNNLite rknn = RKNNLite() ret = rknn.load_rknn('test.rknn') ret = rknn.init_runtime() print('NPU初始化成功' if ret == 0 else 'NPU初始化失败')3.2 模型导出与ONNX转换
以ResNet18为例,从PyTorch导出ONNX:
import torch import torchvision.models as models model = models.resnet18(pretrained=True) model.eval() dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, 'resnet18.onnx', input_names=['input'], output_names=['output'], opset_version=11, dynamic_axes=None )这里有个细节:opset_version建议用11,RKNN对11的支持最稳定。用12或13有时候会遇到算子映射问题。dynamic_axes一定要设为None,固定shape。
导出后可以用onnxsim简化一下模型,去掉多余的算子:
onnxsim resnet18.onnx resnet18_sim.onnx3.3 RKNN量化配置与转换
量化是精度损失的主要来源。RKNN支持混合量化,但配置起来比较麻烦。我的做法是先跑一遍全INT8量化,看精度掉多少,如果掉超过3个百分点,再考虑对敏感层做混合量化。
from rknn.api import RKNN rknn = RKNN() rknn.config( mean_values=[[123.675, 116.28, 103.53]], std_values=[[58.395, 57.12, 57.375]], target_platform='rk3566', quantized_dtype='asymmetric_quantized-8', optimization_level=3 ) rknn.load_onnx(model='resnet18_sim.onnx') rknn.build(do_quantization=True, dataset='./dataset.txt') rknn.export_rknn('resnet18.rknn')dataset.txt里放的是量化校准图片的路径,一般准备200到500张就够了。图片要覆盖实际场景的各种情况,否则量化后的模型在特定场景下会崩。
实操心得:校准集里一定要包含一些极端样本,比如过曝、欠曝、模糊的图片。我试过只用清晰图片做校准,结果模型在暗光环境下识别率直接腰斩。
3.4 板端推理代码实现
板端推理的核心是预处理要和PC端量化时保持一致。RKNN的inference接口接受numpy数组,但要注意数据排布和归一化方式。
import numpy as np from rknnlite.api import RKNNLite import cv2 rknn = RKNNLite() rknn.load_rknn('resnet18.rknn') rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0) img = cv2.imread('test.jpg') img = cv2.resize(img, (224, 224)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) img = (img - [123.675, 116.28, 103.53]) / [58.395, 57.12, 57.375] img = np.expand_dims(img, axis=0) outputs = rknn.inference(inputs=[img]) pred = np.argmax(outputs[0]) print('预测类别:', pred)core_mask参数可以指定用哪个NPU核心,RK3566只有一个NPU核心,所以设不设都一样。但如果你用的是RK3588,这个参数就很重要了。
4. 主流模型在RK3566上的实测数据
4.1 测试环境说明
所有测试都在同一块RK3566开发板上进行,4GB LPDDR4X内存,eMMC存储,系统是Debian 11,NPU驱动版本0.9.6。测试图片统一用224x224分辨率,batch size为1。帧率取100次推理的平均值,排除第一次加载的冷启动时间。
4.2 分类模型帧率与精度对比
| 模型 | 参数量 | FP32精度 | INT8精度 | 帧率(FPS) | 模型大小 |
|---|---|---|---|---|---|
| MobileNetV3-Small | 2.5M | 67.7% | 66.2% | 142 | 2.8MB |
| MobileNetV3-Large | 5.4M | 75.2% | 73.8% | 89 | 5.6MB |
| ResNet18 | 11.7M | 69.8% | 68.1% | 52 | 11.8MB |
| ResNet50 | 25.6M | 76.1% | 74.3% | 21 | 25.2MB |
| EfficientNet-B0 | 5.3M | 77.1% | 74.9% | 63 | 5.5MB |
| ConvNeXt-Tiny | 28.6M | 82.1% | 79.4% | 14 | 28.3MB |
| ViT-Base | 86M | 81.8% | 不支持 | - | - |
| Swin-Tiny | 28.3M | 81.3% | 77.6% | 11 | 28.1MB |
几个关键发现:
MobileNetV3-Small是帧率王者,142FPS意味着单帧推理只要7ms,留给预处理和后处理的时间很充裕。但它的精度只有66%左右,适合对精度要求不高的场景。
ResNet18是平衡点,52FPS够用,精度68%比MobileNetV3-Small高一点,模型结构简单,量化后掉点少。
EfficientNet-B0的精度和帧率都不错,但它的深度可分离卷积在RKNN上优化得一般,实际帧率比理论值低。
ViT系列直接不用考虑,RKNN不支持Multi-Head Attention算子,转换阶段就报错。Swin-Tiny虽然能转,但帧率只有11FPS,而且量化后精度掉了近4个百分点。
4.3 输入分辨率对帧率的影响
很多人忽略了一点:输入分辨率对帧率的影响是非线性的。我拿ResNet18做了个测试:
| 输入分辨率 | 帧率(FPS) | 相对224x224的倍率 |
|---|---|---|
| 128x128 | 98 | 1.88x |
| 160x160 | 76 | 1.46x |
| 224x224 | 52 | 1.00x |
| 320x320 | 28 | 0.54x |
| 448x448 | 14 | 0.27x |
分辨率翻倍,计算量翻四倍,但帧率下降得更快,因为内存带宽成了瓶颈。如果你的场景不需要224x224,降到160x160能换来46%的帧率提升,精度可能只掉1到2个百分点。
4.4 量化精度损失的补偿策略
INT8量化后精度掉2到3个百分点是正常的,但如果掉超过5个点,就要考虑补偿。我常用的策略有三种:
第一种是混合量化,对精度敏感的首尾层保持FP16。RKNN支持在转换时指定某些层不量化,但配置起来比较繁琐,需要逐层分析。
第二种是量化感知训练,在训练阶段就模拟量化误差。这个方法效果最好,但需要重新训练模型,周期长。
第三种是校准集优化,增加校准图片的数量和多样性。我试过把校准集从200张增加到1000张,ResNet18的INT8精度从67.2%提升到68.1%,接近FP32水平。
注意:校准集不是越多越好,超过2000张后收益递减,而且转换时间会大幅增加。
5. 部署过程中遇到的典型问题与排查方法
5.1 算子不支持怎么办
RKNN对算子的支持是有限制的。我遇到过几种典型的不支持情况:
HardSwish算子在RKNN-Toolkit2 1.5.0之前不支持,MobileNetV3转换时会报错。解决办法是把HardSwish替换成ReLU6,精度会掉一点,但能跑起来。
LayerNorm在ViT和ConvNeXt里都有,RKNN对它的支持不稳定。ConvNeXt-Tiny转换时LayerNorm被映射成了其他算子,导致精度异常。后来我把LayerNorm换成了BatchNorm,重新训练后才正常。
GELU激活函数在EfficientNet里用到,RKNN支持但效率不高。可以替换成ReLU,精度损失在可接受范围内。
排查算子问题的流程:先用rknn.load_onnx加载模型,如果报错会提示哪个算子不支持;然后用Netron打开ONNX模型,找到对应的层;最后决定是替换算子还是换模型。
5.2 推理结果与PC端不一致
这个问题最让人头疼。板端推理结果和PC端ONNX Runtime的结果对不上,可能的原因有:
预处理不一致。PC端用PyTorch的transforms.Normalize,板端用numpy手动归一化,如果mean和std的数值精度不同,结果就会有偏差。建议把预处理参数写成常量,两边共用。
量化误差累积。INT8量化后,中间层的数值范围被压缩,误差会逐层放大。如果模型很深,最后输出的logits可能完全乱掉。解决办法是检查每一层的输出,找到误差最大的层,对它做混合量化。
输入数据排布错误。RKNN的inference接口默认接受NHWC格式,但PyTorch导出的是NCHW。虽然RKNN内部会做转换,但如果手动改了输入shape,可能会出错。
5.3 内存不足与段错误
RK3566的4GB内存看着不少,但系统占掉一部分,NPU驱动占掉一部分,留给模型推理的其实不多。跑ResNet50的时候,如果同时开多个线程,很容易触发OOM。
我遇到过最诡异的问题是段错误,程序跑着跑着就崩了,没有任何错误日志。后来用dmesg查看内核日志,发现是NPU驱动在内存分配失败后没有正确处理,直接导致了用户态进程崩溃。
解决办法:控制并发数,推理线程不要超过2个;用rknn.release()及时释放不用的模型;如果模型太大,考虑用core_mask分时复用。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 转换时报算子不支持 | RKNN版本低或算子确实不支持 | 升级RKNN或替换算子 |
| 板端初始化失败 | NPU驱动未加载 | 检查/dev/rknpu节点 |
| 推理结果乱码 | 预处理不一致 | 统一PC端和板端预处理 |
| 帧率远低于预期 | 内存带宽瓶颈 | 降低输入分辨率或换小模型 |
| 程序随机崩溃 | 内存不足或驱动bug | 减少并发,更新驱动 |
| 量化后精度暴跌 | 校准集不具代表性 | 增加校准集多样性 |
6. 实际项目中的选型建议与优化技巧
6.1 不同场景下的模型推荐
如果是智能门禁的人脸识别,推荐MobileNetV3-Small,帧率够高,精度够用,模型小加载快。人脸识别对帧率要求高,因为要连续检测。
如果是工业质检的瑕疵分类,推荐ResNet18或EfficientNet-B0。质检场景对精度要求高,帧率可以放宽到30FPS左右。EfficientNet-B0的精度比ResNet18高不少,但帧率低一些,看具体需求。
如果是零售货柜的商品识别,推荐MobileNetV3-Large。商品类别多,需要一定的特征提取能力,MobileNetV3-Large的75%精度基本够用,89FPS也能满足实时性。
ConvNeXt和Swin这些Transformer类模型,除非精度要求极高且能接受10FPS左右的帧率,否则不建议在RK3566上跑。
6.2 模型剪枝与蒸馏的实操
如果现有模型太大跑不动,可以考虑剪枝。我试过用torch-pruning对ResNet50做通道剪枝,剪掉30%的通道后,参数量降到18M,帧率从21FPS提升到35FPS,精度只掉了1.2个百分点。
知识蒸馏也很有效。用ResNet50当教师模型,MobileNetV3-Small当学生模型,蒸馏后的MobileNetV3-Small精度能从66%提升到71%,帧率不变。这个方法适合有训练资源的团队。
6.3 多模型并行推理的调度
有些场景需要同时跑多个模型,比如先检测人脸再分类表情。RK3566只有一个NPU核心,多模型只能串行执行。我的做法是用一个调度线程管理模型队列,按优先级分配推理时间。
如果两个模型都要求实时性,可以考虑把其中一个模型放到CPU上跑。RK3566的CPU性能不算差,跑MobileNetV3-Small能到15FPS左右,虽然比NPU慢很多,但能分担压力。
6.4 长期运行的稳定性保障
边缘设备通常要7x24小时运行,稳定性比性能更重要。我总结了几个保障措施:
加看门狗,定时检查推理线程是否卡死,卡死就重启进程。
限制内存增长,每次推理后手动释放中间变量,避免内存泄漏。
记录推理日志,包括帧率、耗时、内存占用,方便排查问题。
温度监控,RK3566长时间满载会发热降频,加个散热片能稳定不少。
我在实际项目里遇到过连续跑48小时后帧率从52FPS降到38FPS的情况,后来发现是散热没做好,NPU温度到了85度触发降频。加了散热片之后,连续跑一周帧率都很稳定。
最后分享一个小技巧:RKNN的inference接口支持批量输入,虽然RK3566的NPU算力有限,但batch size设为2的时候,总吞吐量比单张推理高15%左右。如果你的场景对单帧延迟不敏感,可以试试批量推理。