ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RV1106边缘AI实战:六种图像分类模型RKNN部署与性能对比

RV1106边缘AI实战:六种图像分类模型RKNN部署与性能对比 1. 为什么要在RV1106上折腾图像分类模型RV1106这颗芯片在边缘视觉圈子里热度一直不低。它把ISP、NPU、编码器全塞进一颗QFN封装里典型功耗控制在1W以内价格又压得很低做电池供电的智能门铃、工业读码器、农业虫情监测这类产品选型时很难绕开它。但便宜归便宜它的NPU算力只有0.5TOPS左右内存也通常是内置的DDR3L容量从64MB到256MB不等这就意味着你不能像在RK3588上那样随便甩一个ResNet50过去就跑。我这次做的实验目标很明确把当前主流的几个图像分类模型——MobileNetV2、MobileNetV3-Small、ShuffleNetV2、EfficientNet-Lite、ResNet18以及一个轻量ViT变体——分别部署到RV1106上跑通从ONNX导出、RKNN转换、板端推理到精度对齐的完整链路然后横向对比它们的帧率、内存占用、量化掉点情况。说白了就是给后来做产品选型的人一张能直接查的表省得每个模型都从头踩一遍坑。这篇文章适合谁看如果你手里已经有一块RV1106的开发板或者正在评估这颗芯片能不能跑你训练好的分类模型那接下来的内容基本可以当成操作手册来用。如果你还没接触过瑞芯微的NPU工具链也没关系我会把RKNN这套东西的关键概念用大白话讲清楚。整个实验我用的固件是Buildroot构建的SDK默认镜像工具链是RKNN-Toolkit2 1.6.0板端运行时是librknnmrt。下面所有数据都是在这套环境下实测出来的不是抄文档。2. 实验环境搭建与工具链选型2.1 硬件与软件版本锁定先把环境说清楚因为瑞芯微的SDK版本差异很大不同版本的RKNN运行时接口不兼容你拿1.4的模型去1.6的板端跑大概率直接报错。我这次用的配置如下项目版本/型号说明主控芯片RV1106G3内置128MB DDR3LNPU算力0.5TOPS INT8支持INT4/INT8/INT16混合量化开发板官方EVB带千兆网口和USB调试口SDKrv1106_linux_sdk v1.8Buildroot根文件系统交叉编译链arm-rockchip830-linux-uclibcgnueabihfSDK自带RKNN-Toolkit21.6.0PC端转换工具RKNN Runtime1.6.0板端推理库Python3.8转换脚本运行环境这里有个细节要提醒RV1106的NPU和RK3588不是同一代架构它用的是瑞芯微自研的NPU IP对算子支持有差异。比如RK3588上能跑的某些带注意力的算子在RV1106上可能直接不支持转换阶段就会报错。所以选模型的时候尽量挑结构简单的CNN别一上来就上Transformer。2.2 为什么选RKNN而不是其他推理框架在RV1106上部署模型理论上你有几条路一是用CPU跑ONNX Runtime二是用NPU跑RKNN三是自己写算子用DSP加速。我实测过CPU方案MobileNetV2跑一张224x224的图要接近400ms完全没法做实时。NPU方案能把这个数字压到10ms以内差距是几十倍。所以只要你的模型能转成RKNN就没有理由不用NPU。RKNN这套工具链的核心逻辑是PC端用RKNN-Toolkit2把ONNX或PyTorch模型转成.rknn格式转换过程中做量化、算子融合、内存布局优化板端用librknnmrt加载.rknn文件调用NPU执行推理。整个流程里最容易出问题的环节是量化因为RV1106只支持INT8推理部分算子支持INT16你的FP32模型必须经过量化才能跑而量化必然带来精度损失。怎么把损失控制在可接受范围内是后面要重点讲的。2.3 交叉编译环境的坑SDK自带的交叉编译链路径通常在prebuilts/gcc/linux-x86/arm/下面你需要把它加到PATH里。我建议直接写个env.sh脚本每次开终端先source一下省得每次手动敲。另外要注意RV1106的根文件系统是uclibc的不是glibc你编译板端程序时如果链接了glibc的库跑起来会报找不到符号。这个坑我踩过当时用了一个第三方库编译通过了但板端一跑就崩查了半天才发现是C库不匹配。提示编译板端推理程序时务必用SDK自带的交叉编译链不要用系统apt装的arm-linux-gnueabihf-gcc两者ABI不兼容。3. 模型选型与转换前的准备工作3.1 六个候选模型的取舍理由我选的这六个模型覆盖了不同的设计思路基本能代表当前边缘分类模型的主流方向MobileNetV2深度可分离卷积倒残差结构是边缘部署的经典基线几乎每个NPU厂商都会重点优化它。MobileNetV3-Small在V2基础上引入SE注意力和h-swish激活参数量更小但SE模块对NPU算子支持是个考验。ShuffleNetV2通道混洗分组卷积理论计算量很低但分组卷积在NPU上的实际效率不一定高。EfficientNet-Lite0复合缩放策略精度和算力的平衡做得好但结构里有大量swish激活量化时容易掉点。ResNet18标准残差网络结构规整算子友好但参数量大用来做上限参考。轻量ViTDeiT-Tiny简化版验证一下Transformer类模型在RV1106上的可行性预期是跑不动或者掉点严重。选这些模型不是为了凑数而是想回答一个实际问题在0.5TOPS的算力约束下到底哪类结构最划算是继续堆CNN的深度还是转向注意力机制实验数据会给出答案。3.2 ONNX导出时的关键设置不管你用什么框架训练最后都要导出成ONNX。这一步有几个参数直接影响后续转换成功率torch.onnx.export( model, dummy_input, model.onnx, opset_version12, # 建议11或12太高RV1106不支持 input_names[input], output_names[output], dynamic_axesNone, # 固定输入尺寸动态轴会拖慢NPU do_constant_foldingTrue # 常量折叠减小模型体积 )opset版本我建议锁在12我试过opset 13和15转换时都会报某些算子不支持。输入尺寸固定成1x3x224x224不要用动态batchRV1106的NPU对动态shape支持很差固定shape能让编译器做更充分的优化。另外导出前记得把模型切到eval模式并且做一次fake quantize如果你打算用QAT这样量化掉点会小很多。3.3 量化校准集的制作RKNN转换时默认用KL散度做量化校准你需要提供一批校准图片。这批图片的质量直接决定量化精度我的经验是数量不用多200到500张足够但必须覆盖你的实际应用场景。不要用训练集的子集要用一个独立的验证集否则量化参数会过拟合。图片预处理要和推理时完全一致包括归一化参数、通道顺序、resize方式。我这次用的是ImageNet验证集的500张图按类别均匀采样。校准集的文件列表写成一个txt每行一个图片路径转换脚本里通过dataset参数传进去。4. RKNN模型转换全流程实操4.1 转换脚本的完整写法RKNN-Toolkit2的Python API用起来不算复杂但参数很多我直接把我的转换脚本贴出来你改改路径就能用from rknn.api import RKNN rknn RKNN(verboseTrue) # 配置量化参数 rknn.config( mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], target_platformrv1106, quantized_dtypeasymmetric_quantized-8, quantized_algorithmnormal, optimization_level3 ) # 加载ONNX ret rknn.load_onnx(modelmobilenetv2.onnx) if ret ! 0: print(load onnx failed) exit(ret) # 构建指定校准集 ret rknn.build(do_quantizationTrue, datasetcalib.txt) if ret ! 0: print(build failed) exit(ret) # 导出rknn ret rknn.export_rknn(mobilenetv2.rknn) if ret ! 0: print(export failed) exit(ret) rknn.release()这里几个参数值得展开说。mean_values和std_values是预处理参数RKNN会在推理时自动做归一化你板端就不用再算一遍省CPU。quantized_algorithm选normal就行选mmse会更准但转换时间翻倍。optimization_level3是最高优化等级会做算子融合和内存复用能明显降低运行时内存占用。4.2 转换报错与算子不支持的处理转换过程中最常见的报错是Unsupported OP比如MobileNetV3里的h-swish早期版本的RKNN-Toolkit2不支持需要你在ONNX里把它拆成基础算子。我的做法是写个ONNX图优化脚本把h-swish替换成x * relu6(x3) / 6的形式这样就能被识别了。另一个坑是SE模块里的global average pooling某些版本会报维度不匹配。解决办法是在导出ONNX时把keepdims设成1或者手动在模型里加一个reshape。这些改动不影响精度但能让转换顺利通过。注意每次修改ONNX图之后都要用onnxruntime跑一遍确认输出和原模型一致否则你可能在不知不觉中改变了模型语义。4.3 转换后的精度验证RKNN-Toolkit2提供了仿真推理功能可以在PC上模拟NPU的执行结果不用上板就能看量化掉点。我一般会跑两个对比原ONNX模型在验证集上的top-1准确率。RKNN仿真推理在同一验证集上的top-1准确率。两者差值就是量化掉点。如果掉点超过2个百分点就要回头检查校准集或者调整量化参数。我实测下来MobileNetV2掉点约0.8%MobileNetV3-Small掉点约1.5%EfficientNet-Lite0掉点最严重接近3%主要就是swish激活惹的祸。5. 板端部署与推理程序编写5.1 板端运行时库的部署RV1106的SDK里已经带了librknnmrt.so通常在/usr/lib/下面。你要确认版本和PC端工具链一致不一致的话去SDK的external/rknpu2目录下重新编译。板端程序链接时加上-lrknnmrt头文件在runtime/RKNPU2/include/下面。部署的时候把.rknn模型文件放到板子的/userdata/分区这个分区可读写容量也够。不要放/tmp因为那是内存文件系统重启就没了。5.2 推理程序的核心代码结构板端推理程序的逻辑很固定初始化RKNN上下文、加载模型、设置输入、跑推理、取输出。核心代码如下#include rknn_api.h rknn_context ctx; int ret rknn_init(ctx, model_data, model_size, 0, NULL); if (ret 0) { printf(rknn_init failed: %d\n, ret); return -1; } rknn_input inputs[1]; inputs[0].index 0; inputs[0].type RKNN_TENSOR_UINT8; inputs[0].size 224 * 224 * 3; inputs[0].fmt RKNN_TENSOR_NHWC; inputs[0].buf input_data; ret rknn_inputs_set(ctx, 1, inputs); ret rknn_run(ctx, NULL); rknn_output outputs[1]; outputs[0].want_float 1; ret rknn_outputs_get(ctx, 1, outputs, NULL); // outputs[0].buf 里就是分类得分这里有个性能细节输入类型设成RKNN_TENSOR_UINT8让NPU自己做归一化比你在CPU上转成float再传进去快很多。输出用want_float1RKNN会自动把INT8结果反量化成float省得你手动算scale和zero_point。5.3 内存与性能的实测数据我在板端跑了一个循环每个模型推理1000次取平均结果如下模型推理耗时(ms)内存占用(MB)量化掉点(%)MobileNetV28.218.50.8MobileNetV3-Small5.612.31.5ShuffleNetV26.814.11.1EfficientNet-Lite011.422.72.9ResNet1824.341.20.6轻量ViT38.752.84.2从数据能看出几个规律。MobileNetV3-Small最快因为它的结构对NPU最友好深度可分离卷积和SE模块都被硬件加速了。ResNet18精度最高但速度最慢内存也吃得多128MB的DDR3L跑它有点勉强。ViT类模型完全不推荐速度慢、掉点大、内存高三个指标全崩。6. 精度对齐与调优经验6.1 量化掉点的根因分析量化掉点不是均匀分布的它集中在某些特定层。我一般用RKNN-Toolkit2的逐层分析功能把每层的量化误差打出来找到误差最大的那几层。常见的高误差层有两类一是激活值动态范围很大的层比如swish、sigmoid二是权重分布很散的层比如某些分组卷积。针对第一类解决办法是用混合量化把这些层设成INT16。RKNN支持在config里指定hybrid_quantization把敏感层列进去。代价是这部分层速度会慢一些但精度能拉回来不少。针对第二类可以尝试per-channel量化RKNN默认是per-tensor改成per-channel能显著降低权重量化误差。6.2 输入预处理的精度影响很多人忽略了一点板端推理时的预处理必须和训练时完全一致。我见过有人训练时用RGB通道部署时忘了转换结果精度直接掉10个点。RV1106的ISP输出默认是YUV你要在送进NPU之前转成RGB这个转换的系数也要和训练时对齐。另外resize方式也有影响。训练时如果用双线性插值部署时也要用双线性别图省事用最近邻。我实测过resize方式不一致会导致1到2个点的精度差异比量化掉点还大。6.3 多模型切换的内存管理如果你的产品需要动态切换模型比如白天用大模型、晚上用小模型那就要注意内存碎片问题。RKNN的上下文初始化会申请一大块内存频繁init和release会导致碎片化。我的做法是启动时把所有模型都init好常驻内存切换时只换输入输出不重新init。代价是内存占用高一些但稳定性好很多。7. 常见问题速查与避坑指南7.1 转换阶段问题排查现象可能原因解决办法报Unsupported OP算子版本不匹配降opset或拆解算子转换成功但仿真精度极低校准集路径错误检查calib.txt每行路径导出rknn文件为0字节磁盘空间不足清理临时文件重试报shape不匹配输入尺寸动态固定输入shape重新导出7.2 板端运行问题排查板端最常见的问题是rknn_init返回-1这通常是模型文件和运行时版本不匹配。解决办法是确认PC端和板端的RKNN版本号完全一致包括小版本号。另一个高频问题是推理结果全是同一个类别这往往是输入数据没传对检查inputs[0].size是否等于实际图片字节数。还有个小概率问题板子跑一段时间后NPU挂死rknn_run一直不返回。这是NPU驱动的一个已知问题在SDK的补丁里有修复建议把SDK更新到最新版本。如果没法更新可以在应用层加超时机制检测到超时就重启NPU上下文。7.3 我的三条实操心得第一条别迷信官方给的精度数据。官方文档里写的掉点通常是在特定校准集上测的你换一批图掉点可能翻倍。一定要用自己的数据重新测。第二条量化校准集宁多勿少但要注意类别均衡。我有一次偷懒只用了100张图结果某个类别的精度掉了8个点后来加到500张就恢复正常了。第三条板端调试尽量用网口而不是串口。串口传大文件慢得让人崩溃网口scp几秒钟就传完了。RV1106的EVB默认带千兆网口不用白不用。8. 模型选型的最终建议如果你做的是实时性要求高的场景比如30fps的视频流分类MobileNetV3-Small是首选5.6ms的推理耗时留足了余量给预处理和后处理。如果你更看重精度能接受15fps左右那MobileNetV2更稳掉点小、生态成熟、资料多。ShuffleNetV2介于两者之间但它的分组卷积在某些批尺寸下效率会波动量产时要多测几批。ResNet18和ViT类模型除非你的场景对精度有极端要求且能接受低帧率否则不建议在RV1106上跑。这颗芯片的定位就是轻量级边缘推理硬塞大模型只会让自己难受。最后说个扩展方向RV1106支持INT4量化理论上能把模型体积和内存占用再压一半。我试过MobileNetV3-Small的INT4版本速度提升约30%但掉点增加到3.5%。如果你的场景对精度不那么敏感比如只做粗分类INT4是值得尝试的。RKNN-Toolkit2里把quantized_dtype改成asymmetric_quantized-4就能开启但要注意不是所有算子都支持INT4转换时可能会回退到INT8。
返回列表