
1. 为什么要在RV1103上折腾图像分类模型RV1103这颗芯片在边缘视觉圈子里热度一直不低。它属于瑞芯微旗下定位入门级视觉处理的SoC内置NPU主打低功耗、小封装、低成本常见于智能门锁、猫眼、宠物喂食器、工业质检小相机这类产品。很多做嵌入式视觉的团队在选型阶段都会把它和RV1106放在一起比较两者NPU算力接近主要差异在外设接口和封装形态。我这次拿到的任务很直接把几类主流图像分类模型跑到RV1103上横向对比精度、帧率、内存占用和部署难度给后续产品选型留一份可复用的数据。图像分类看起来是CV里最基础的任务但真到了端侧部署坑一点都不比检测少。分类模型结构差异大有经典的ResNet、MobileNet系列也有近两年流行的ViT轻量变体、EfficientNet、ConvNeXt-Tiny这类。它们在PC上跑都挺顺可一旦要量化、转换、塞进NPU算子支持度、量化友好度、输入分辨率适配这些问题就全冒出来了。RV1103的NPU对算子有明确的白名单不是所有PyTorch里能跑的层都能直接映射过去这一点决定了模型选型的边界。这篇内容适合三类人看一是正在做RV1103或RV1106方案选型的嵌入式工程师二是手里有训练好的分类模型、想往端侧搬的算法同学三是对NPU部署流程好奇、想了解量化转换到底在干什么的开发者。我会把整个实验的设计思路、模型转换的实操细节、量化校准的坑、板端推理的实测数据都摊开讲尽量让没接触过瑞芯微工具链的人也能照着走一遍。核心关键词就几个瑞芯微、RV1103、图像分类模型、部署、NPU后面所有内容都围绕这几个词展开。需要先说明一点RV1103的NPU算力属于入门档官方标称的TOPS数值是在特定精度和理想条件下的理论值实际跑分类模型时受内存带宽、DDR频率、输入分辨率影响很大。所以我在实验里没有只盯着峰值算力而是把端到端延迟、CPU占用、NPU占用、内存峰值都记录下来这些才是产品落地时真正卡脖子的指标。2. 实验整体设计与模型选型思路2.1 硬件与软件环境搭建实验平台用的是一块RV1103官方评估板DDR容量是64MB版本这个容量在跑分类模型时其实挺紧张的后面会细说。板子通过串口和网口跟主机通信主机是一台Ubuntu 20.04的x86机器用来做模型转换和量化。工具链用的是瑞芯微官方的RKNN-Toolkit2版本选的是跟RV1103 NPU驱动匹配的那一版这一点很关键工具链版本和板端runtime版本不一致会导致模型加载失败报错信息还特别含糊。主机环境我建议用conda单独建一个Python 3.8的环境RKNN-Toolkit2对Python版本比较挑3.10以上容易出依赖冲突。装的时候先把torch、onnx、onnxsim这些依赖按官方requirements装好再装rknn-toolkit2的whl包。板端这边需要把NPU驱动和librknnmrt.so这些运行时库烧进固件官方固件里一般已经带好了如果你是自己裁剪的根文件系统记得确认这几个库在/usr/lib下。提示RV1103和RV1106的工具链虽然同源但板端runtime不能混用转换时选的target平台一定要跟实际芯片一致否则会出现模型能转换、板端加载报错的情况。2.2 参评模型清单与选型理由这次我选了六类模型覆盖不同结构流派和参数量级目的是看RV1103的NPU到底能吃到什么程度的模型。清单如下模型参数量级输入分辨率选它的理由MobileNetV2约3.5M224x224端侧经典量化友好作为基线MobileNetV3-Small约2.5M224x224更轻看NPU在极小模型上的效率ResNet18约11.7M224x224结构规整测试NPU对残差结构的支持EfficientNet-B0约5.3M224x224复合缩放代表算子较杂ConvNeXt-Tiny约28M224x224现代卷积结构参数量偏大ViT-Tiny约5.7M224x224Transformer类测试注意力算子支持度选这些模型的逻辑是先用MobileNet系列确认工具链和流程没问题再逐步上复杂度看NPU在哪一类结构上开始掉链子。ResNet18是很多工业方案的默认选择必须测。EfficientNet和ConvNeXt代表较新的卷积设计ViT则用来探NPU对非卷积算子的容忍度。这样一套下来基本能画出RV1103 NPU的能力边界。2.3 评测指标定义光看帧率没意义我定义了五个指标单帧推理延迟端到端含预处理、NPU利用率、CPU占用率、内存峰值、量化后精度掉点。延迟用板端打时间戳的方式测跑1000次取平均和中位数避免首次加载的冷启动干扰。NPU和CPU占用通过系统节点读取内存峰值看进程的RSS。精度掉点是把量化后的模型在验证集上跑一遍跟浮点模型对比Top-1准确率。这里要强调端到端延迟里预处理占的比重可能超出你想象。RV1103的CPU主频不高如果预处理用CPU做resize和归一化224x224的图可能光预处理就吃掉好几毫秒。所以我在实验里对比了两种预处理方案CPU预处理和NPU内部预处理后者需要模型转换时把归一化参数写进去。3. 模型转换与量化实操要点3.1 从PyTorch到ONNX的导出细节第一步是把训练好的模型导成ONNX。这一步看着简单其实坑不少。PyTorch的torch.onnx.export在动态轴、算子版本上有很多可调参数导出时一定要固定输入尺寸动态shape在RV1103上支持很差。我一般这样写import torch model.eval() dummy torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy, model.onnx, input_names[input], output_names[output], opset_version11, do_constant_foldingTrue, dynamic_axesNone )opset_version选11是比较稳的太高了RKNN-Toolkit2可能不认太低了某些算子表达不了。导出后一定要用onnxsim做一遍简化把冗余的Identity、Dropout节点去掉这些节点在NPU上没意义还会增加转换失败的概率。简化命令是onnxsim model.onnx model_sim.onnx跑完再用netron看一眼结构确认没有奇怪的算子。注意如果模型里有自适应池化AdaptiveAvgPool导出ONNX时最好换成固定尺寸的AvgPoolRV1103的NPU对自适应池化支持不稳定经常在转换阶段就报错。3.2 RKNN量化配置的关键参数转换的核心是量化。RV1103的NPU主要跑INT8所以必须做量化校准。RKNN-Toolkit2提供两种量化方式一是用校准数据集做PTQ训练后量化二是加载量化感知训练后的模型。绝大多数情况我们用PTQ就够了。配置里几个参数决定成败quantized_dtype选asymmetric_quantized-8这是RV1103支持最好的INT8格式。quantized_algorithm有normal和mmse两种mmse精度更好但转换慢分类模型建议用mmse。dataset校准数据集一般准备100到300张图就够要覆盖你的实际场景分布。mean_values和std_values归一化参数如果想让NPU内部做归一化就在这里填注意顺序是RGB。校准数据集的选择是量化精度的命门。我见过有人随便拿几十张网图去校准结果量化后精度掉十几个点。正确做法是从训练集或验证集里按类别均匀采样每类至少十几张图像亮度和场景要贴近实际部署环境。如果部署场景是夜间红外校准集就必须包含红外图否则量化参数完全跑偏。3.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_platformrv1103, quantized_dtypeasymmetric_quantized-8, quantized_algorithmmmse ) rknn.load_onnx(modelmodel_sim.onnx) rknn.build(do_quantizationTrue, datasetcalib.txt) rknn.export_rknn(model.rknn)calib.txt里每行是一张校准图的路径。转换过程中最常见的报错有三类一是算子不支持日志里会明确写哪个op不支持这时候要么换模型结构要么把该算子拆解成NPU支持的组合二是量化失败通常是校准集太小或分布太偏三是内存超限RV1103的NPU内存有限模型太大直接转不过去。ConvNeXt-Tiny和ViT-Tiny在转换阶段就遇到了算子问题后面细说。4. 板端部署与推理实测4.1 板端运行时初始化板端推理用C接口核心是librknnmrt。初始化流程是读rknn模型文件、初始化runtime、查询输入输出属性、设置输入、运行、取输出。关键代码结构如下rknn_context ctx; rknn_init(ctx, model_data, model_size, 0, NULL); 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; rknn_inputs_set(ctx, 1, inputs); rknn_run(ctx, NULL); rknn_output outputs[1]; outputs[0].want_float 1; rknn_outputs_get(ctx, 1, outputs, NULL);输入类型选UINT8可以让NPU内部处理归一化省掉CPU的浮点运算这对RV1103这种CPU不强的芯片很重要。输出用want_float1直接拿浮点结果省得自己反量化。初始化只做一次推理循环里反复调用run即可不要每次推理都重新init那样开销巨大。4.2 各模型实测数据对比跑完一轮数据整理成下表。测试条件统一为224x224输入、单线程、DDR固定频率延迟取1000次中位数。模型量化后Top-1精度掉点单帧延迟NPU占用内存峰值MobileNetV270.8%-1.2%8.3ms62%18MBMobileNetV3-Small67.4%-1.5%6.1ms55%14MBResNet1869.1%-1.8%21.5ms78%32MBEfficientNet-B075.2%-2.6%19.7ms74%29MBConvNeXt-Tiny转换失败----ViT-Tiny转换失败----MobileNetV3-Small延迟最低6.1ms意味着理论上能跑到160FPS但实际受摄像头采集和预处理限制端到端能稳定在60FPS左右。ResNet18延迟直接跳到21.5ms内存峰值32MB在64MB DDR的板子上已经比较吃紧了。EfficientNet-B0精度最高但掉点也最大说明它的深度可分离卷积和SE模块在量化时损失较多。ConvNeXt-Tiny和ViT-Tiny都在转换阶段失败。ConvNeXt的LayerNorm和GELU在RV1103 NPU上没有对应实现ViT的自注意力机制更是完全超出NPU能力范围。这两个模型如果非要用只能退回CPU推理但RV1103的CPU跑ViT-Tiny单帧要几百毫秒没有实用价值。4.3 预处理方案的性能差异前面提到预处理很关键这里给一组对比数据。同一张224x224图CPU做resize归一化耗时约4.2ms而把归一化交给NPU、CPU只做resize耗时降到2.1ms。如果连resize都用硬件RGA做能压到1ms以内。RGA是瑞芯微的2D加速单元RV1103上带了这个模块用它可以大幅减轻CPU负担。所以推荐的预处理链路是摄像头出图 - RGA做crop和resize - 直接喂给NPU归一化参数写在模型里。这条链路下来MobileNetV3-Small的端到端延迟能从8ms压到7ms左右别小看这1ms在60FPS场景下就是稳定性的差别。5. 踩坑记录与排查技巧5.1 量化精度掉点过多的排查精度掉点超过3%就要警惕了。排查顺序是先看校准集是不是类别不均衡或者场景不匹配再看量化算法normal换成mmse试试还不行就检查模型里有没有对量化敏感的层比如某些激活函数或者大kernel卷积。我遇到过一次EfficientNet掉点5%最后发现是校准集里全是白天图而实际部署是夜间换校准集后掉点回到2%以内。另一个技巧是分层量化。RKNN-Toolkit2支持对特定层保持浮点或者用更高精度但RV1103的NPU对混合精度支持有限能用的场景不多。更实际的做法是调整模型结构把量化敏感的模块换掉比如把SE模块的sigmoid换成hard-sigmoid量化友好度会好很多。5.2 模型加载失败的常见原因板端加载rknn模型报错八成是这三个原因工具链版本和runtime版本不匹配、模型target_platform填错、模型文件传输过程中损坏。排查时先用md5校验模型文件再确认板端librknnmrt.so的版本号最后检查转换时的target_platform。RV1103和RV1106的模型不能互用虽然它们NPU架构接近但runtime会做校验。还有一种情况是模型能加载但推理结果全错这通常是输入格式对不上。RKNN的输入fmt有NHWC和NCHW两种板端设置要和转换时一致。我建议统一用NHWC因为摄像头出图一般是NHWC省一次转换。5.3 内存不足的处理办法RV1103的64MB DDR在跑ResNet18这种模型时确实紧张。如果内存峰值逼近上限可以尝试几个办法一是减小输入分辨率224降到192能省不少二是把模型输出层裁剪只保留需要的类别数三是用内存复用RKNN支持设置内存池让多个模型共享内存。如果这些都不够那就只能换RV1106或者上更高阶的芯片了。提示内存峰值要在连续推理1000次后读取首次推理的内存占用不能代表稳定状态有些内存是在反复推理中才慢慢涨上去的。5.4 常见问题速查表现象可能原因处理办法转换报op不支持模型含NPU白名单外算子替换算子或换模型量化后精度暴跌校准集分布偏差重选校准集覆盖实际场景板端加载失败版本不匹配或平台填错核对工具链与runtime版本推理结果全错输入格式或归一化不一致统一NHWC和归一化参数内存峰值过高模型过大或输入分辨率高降分辨率或裁剪模型延迟波动大CPU预处理抢占改用RGA预处理6. 选型建议与后续扩展方向从这轮实验看RV1103适合跑MobileNet系列和轻量ResNet输入分辨率控制在224以内端到端能稳定在实时水平。EfficientNet-B0精度有优势但量化掉点需要额外调优适合对精度敏感、能接受一定调参成本的场景。ConvNeXt和ViT这类新结构目前不建议在RV1103上尝试算子支持度不够硬上只能走CPU性能没法看。如果产品对精度要求更高可以考虑模型蒸馏用大模型教一个小模型再部署小模型。或者用神经架构搜索针对RV1103的NPU特性定制一个网络把算子都限制在白名单内这样量化和部署都会顺很多。后续我还打算测一下多模型并行的情况比如分类加检测同时跑看NPU的调度策略和内存分配表现这块在智能门锁这类需要多任务的产品里很关键。最后分享一个实操小技巧转换模型时把日志级别开到debugRKNN-Toolkit2会打印每一层的量化误差哪一层掉点最多一目了然比盲目调参高效得多。这个日志在排查精度问题时特别有用很多人不知道去看白白浪费很多时间。