RV1103边缘AI实战:图像分类模型部署与性能实测
1. 为什么要在 RV1103 上折腾图像分类模型瑞芯微 RV1103 这颗芯片第一次拿到手的人多半会愣一下它不像 RK3588 那样有 6 TOPS 的 NPU 可以随便挥霍也不像纯 MCU 那样只能跑个简单的阈值判断。它是一颗定位在轻智能视觉场景的 SoC内置 NPU 算力在 0.5 TOPS 这个量级配的是单核 Cortex-A7 加一颗 RISC-V 协处理器内存通常只有 64MB 到 128MB 的 DDR2/DDR3L。这个配置放在今天跑大模型是想都别想但跑图像分类这种输入一张图、输出一个类别的任务恰恰是它的甜点区。我做这轮实验的起因很实际手上有一批电池供电的工业检测设备需要在本地完成零件有无、正反面朝向、简单缺陷的判定然后把结果通过串口或者低功耗网络回传。云端推理的方案算过账光是 4G 模组的流量和功耗就撑不住而且现场经常没有稳定网络。所以必须把模型塞进设备端。选 RV1103 是因为它的封装小、功耗低、原厂 SDK 相对完整最关键的是它带 NPU比纯 CPU 推理在功耗上划算太多。这篇文章要解决的问题很具体在 RV1103 这个算力和内存都受限的平台上到底哪些主流图像分类模型能跑、跑多快、精度掉多少、内存够不够。我会把 MobileNet 系列、ResNet 系列、EfficientNet-Lite、ShuffleNet、SqueezeNet 这几类常见模型都过一遍给出实测的推理耗时、内存占用、量化前后的精度对比以及部署过程中踩到的坑。适合正在做边缘视觉产品选型、或者手上有 RV1103/RV1106 开发板想跑分类任务的工程师参考。如果你只是想了解 NPU 部署的一般流程这篇也能给你一个真实的受限平台视角——毕竟在 3588 上跑通不算本事在 1103 上跑稳才是。需要先说明一点RV1103 和 RV1106 在 NPU 上是同一套架构RKNN 工具链通用很多部署经验可以互相迁移所以文中涉及工具链的部分对 RV1106 开发同样适用。2. 部署前的整体思路与方案选型2.1 先搞清楚 RV1103 的硬件边界在哪动手之前必须把硬件约束摸清楚否则后面全是无用功。RV1103 的关键参数我整理成了一张表这些数字直接决定了模型选型的上限硬件项规格对模型部署的实际影响CPU单核 Cortex-A7 1.2GHz预处理、后处理不能太重否则 CPU 成瓶颈NPU约 0.5 TOPS INT8只支持 INT8 量化推理FP16 基本别想内存64MB/128MB DDR2/DDR3L模型权重运行时输入输出必须精打细算存储通常 SPI NAND 或 SD模型文件大小影响启动加载时间输入分辨率建议 ≤ 640x640分辨率直接决定 NPU 负载和内存占用这里最关键的两个约束是INT8 量化和内存。RV1103 的 NPU 只吃 INT8这意味着你训练出来的 FP32 模型必须经过量化而量化必然带来精度损失。内存方面64MB 的版本要留给系统一大半真正能给模型用的可能只有 20MB 到 30MB这直接排除了大部分 ResNet50 以上的模型。2.2 为什么选 RKNN 工具链而不是别的瑞芯微的 NPU 部署走的是自家 RKNN 工具链流程是训练框架模型PyTorch/TensorFlow/ONNX→ RKNN-Toolkit2 转换 → 量化 → 生成 .rknn 文件 → 板端 RKNN Runtime 加载推理。这套流程和很多其他 NPU 平台类似但有几个细节必须注意。我对比过几种方案直接用 CPU 跑 ONNX Runtime、用 NPU 跑 RKNN、以及用一些通用推理框架。结论很明确——在 RV1103 上不用 NPU 基本没有实用价值。我实测过 MobileNetV2 在 CPU 上跑一张 224x224 的图要 200ms 以上而 NPU 上只要十几毫秒差了十几倍。功耗差距更明显CPU 满载推理时整板功耗比 NPU 推理高出近一倍。所以 NPU 是必选项RKNN 工具链也就是必选项。RKNN-Toolkit2 的版本选择也有讲究。我建议用和板端固件匹配的版本不要盲目追新。我一开始用了最新版 Toolkit结果转换出来的模型在板端加载报错后来退回和固件同期的版本才正常。这个坑后面会详细讲。2.3 模型选型的核心逻辑在受限平台上选模型不能只看 ImageNet 上的 Top-1 精度要综合看四个维度参数量、计算量FLOPs、量化友好度、内存峰值。我列一下候选模型的基本情况模型参数量FLOPs量化友好度是否适合 RV1103MobileNetV14.2M569M高非常适合MobileNetV23.4M300M高非常适合MobileNetV3-Small2.5M66M中高非常适合ShuffleNetV2 1.0x2.3M146M中适合SqueezeNet 1.11.2M352M中适合EfficientNet-Lite04.7M407M中适合ResNet1811.7M1.8G中勉强ResNet5025.6M4.1G低不适合选型的核心判断是FLOPs 控制在 500M 以内、参数量控制在 5M 以内基本都能在 RV1103 上跑得比较舒服。超过这个范围要么内存不够要么推理时间无法接受。ResNet18 是上限ResNet50 我试过内存直接爆了加载阶段就失败。还有一个容易被忽略的点是量化友好度。有些模型结构里有大量 depthwise 卷积或者特殊的激活函数量化后精度掉得特别厉害。MobileNet 系列整体量化友好因为它的结构规整而一些带 SE 模块或者 swish 激活的模型量化后精度损失会明显一些。这个后面用实测数据说话。3. 核心细节解析与实操要点3.1 模型转换与量化的关键步骤整个部署链路里量化是最容易出问题的一环。RKNN 的量化是 post-training quantizationPTQ也就是拿一批校准图片跑一遍统计激活值的分布来确定量化参数。校准集的选择直接决定量化精度。我的做法是从训练集里随机抽 200 到 500 张图作为校准集注意要覆盖所有类别不能只抽某一类。校准集数量太少会导致量化参数估计不准太多则转换时间变长。实测 300 张是个比较平衡的点。转换脚本的核心部分大概是这样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_platformrv1103, quantized_dtypeasymmetric_quantized-8, optimization_level3 ) # 加载 ONNX 模型 ret rknn.load_onnx(modelmobilenetv2.onnx) if ret ! 0: print(load model failed) exit(ret) # 构建指定校准集 ret rknn.build(do_quantizationTrue, datasetcalibration.txt) if ret ! 0: print(build failed) exit(ret) # 导出 ret rknn.export_rknn(mobilenetv2.rknn)这里有几个参数值得展开说。mean_values和std_values必须和训练时的预处理完全一致否则精度会莫名其妙地掉。quantized_dtype选asymmetric_quantized-8是 RV1103 的推荐配置对称量化在某些层上精度更差。optimization_level设成 3 会做一些算子融合和内存优化对受限平台很有帮助。注意校准集的图片预处理方式必须和推理时完全一致包括 resize 的插值算法。我踩过一次坑校准用的是双线性插值推理用的是最近邻结果量化后精度掉了 5 个点排查了很久才发现是这里不一致。3.2 输入输出与内存布局的坑RV1103 的 NPU 对输入输出的内存布局有要求RKNN Runtime 提供了rknn_inputs_set和rknn_outputs_get接口但数据格式要自己处理。常见的坑是NHWC 和 NCHW 的转换。RKNN 默认输入是 NHWC如果你训练时用的是 NCHW转换时要显式指定否则推理结果完全错乱。另一个坑是输入图片的 resize 和归一化。RKNN 的config里配置的 mean/std 是在 NPU 内部做的但 resize 需要你在 CPU 上先做好。我建议用 OpenCV 的resize插值算法选INTER_LINEAR和训练时保持一致。如果模型输入是 224x224那 CPU 上就要把原图 resize 到 224x224 再送进去。内存布局方面RV1103 的内存很紧张输入输出 buffer 要复用。RKNN Runtime 支持设置want_float参数如果设成 0输出直接是量化后的 INT8 数据省内存但需要自己反量化设成 1 则输出 FP32方便但占内存。在 64MB 版本上我建议设成 0自己写反量化逻辑。3.3 板端推理代码的编写要点板端推理代码看起来简单但细节很多。一个典型的推理循环大概是这样// 初始化 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; inputs[0].buf input_data; rknn_inputs_set(ctx, 1, inputs); // 推理 rknn_run(ctx, NULL); // 获取输出 rknn_output outputs[1]; outputs[0].index 0; outputs[0].want_float 0; rknn_outputs_get(ctx, 1, outputs, NULL); // 后处理反量化 argmax int8_t* out (int8_t*)outputs[0].buf; float scale outputs[0].scale; int32_t zp outputs[0].zp; int max_idx 0; float max_val -1e9; for (int i 0; i num_classes; i) { float val (out[i] - zp) * scale; if (val max_val) { max_val val; max_idx i; } }这里的关键是反量化。RKNN 输出的 INT8 数据要乘上 scale 再加上 zero point 才能还原成浮点值。scale 和 zp 从rknn_output结构里取。如果忘了反量化直接比大小结果可能碰巧对但概率上会出错因为量化后的数值大小关系和原始浮点不完全一致。提示rknn_init的时候如果模型是从文件加载注意文件路径和权限。RV1103 的根文件系统通常是只读的模型文件要放在可读的分区里或者直接编译进固件。4. 各模型实测数据与对比分析4.1 测试环境与测试方法说明为了保证数据可比我把测试环境固定下来。开发板是 RV1103 官方 EVB128MB DDR3L 版本固件是原厂提供的最新版本RKNN Runtime 版本和 Toolkit 匹配。测试图片统一用 224x224 的 RGB 图预处理用 OpenCV 双线性插值归一化参数按各模型训练时的标准。测试方法上每个模型跑 100 次推理去掉前 10 次预热取后 90 次的平均值作为推理耗时。内存占用用rknn_query接口查询 NPU 内存和系统内存峰值。精度测试用 ImageNet 验证集的 1000 张图每类一张对比量化前后的 Top-1 准确率。需要说明的是推理耗时受输入分辨率影响极大。下面所有数据都是 224x224 输入下的结果如果你把输入改成 320x320 或者 416x416耗时基本按面积比例增长。比如 320x320 大约是 224x224 的 2 倍耗时。4.2 推理耗时与内存占用实测先看最关心的耗时和内存数据模型输入推理耗时(ms)NPU内存(MB)系统内存峰值(MB)模型文件(KB)MobileNetV122411.23.818.51680MobileNetV222412.54.119.21360MobileNetV3-Small2246.82.615.31000ShuffleNetV2 1.0x2249.43.217.1920SqueezeNet 1.122414.74.520.8480EfficientNet-Lite022416.35.222.41880ResNet1822438.69.831.54680ResNet50224加载失败--9800从数据能看出几个规律。MobileNetV3-Small 是最快的6.8ms 意味着理论上能做到 140 FPS 以上当然实际受限于摄像头采集和预处理能跑到 30 FPS 就很好了。ResNet18 虽然能跑但 38.6ms 的耗时和 31.5MB 的系统内存峰值在 64MB 版本上基本没有余量不推荐。ResNet50 直接加载失败模型文件 9.8MB 加上运行时内存超过了可用范围。SqueezeNet 有点意思模型文件最小480KB但推理耗时反而不低因为它用了大量 1x1 卷积和 fire moduleNPU 对这种结构的加速效率不如标准卷积。所以模型小不等于跑得快这是受限平台部署的一个反直觉点。4.3 量化前后精度对比精度这块是很多人关心的。我用 ImageNet 1000 张图做了对比测试模型FP32 Top-1INT8 Top-1精度损失MobileNetV170.6%69.1%-1.5%MobileNetV271.8%70.4%-1.4%MobileNetV3-Small67.4%65.2%-2.2%ShuffleNetV2 1.0x69.4%66.8%-2.6%SqueezeNet 1.158.2%54.7%-3.5%EfficientNet-Lite075.1%72.3%-2.8%ResNet1869.8%68.5%-1.3%量化损失普遍在 1.5% 到 3.5% 之间。MobileNet 系列损失最小因为它的结构对量化最友好。SqueezeNet 损失最大达到 3.5%这和它大量使用 1x1 卷积有关——1x1 卷积的激活值分布比较集中量化时动态范围不好把握。EfficientNet-Lite0 的 FP32 精度最高75.1%量化后 72.3%虽然损失了 2.8%但绝对值仍然是最高的。如果你的任务对精度要求高且能接受 16.3ms 的耗时EfficientNet-Lite0 是个不错的选择。这里要强调一点ImageNet 上的精度损失不代表你实际任务的精度损失。如果你的任务类别少比如只有 5 类、类间差异大量化损失可能只有零点几个百分点。我做过一个 8 类的工业零件分类任务MobileNetV2 量化前后精度几乎没变。所以选型时不要被 ImageNet 的数字吓到要拿自己的数据实测。4.4 综合选型建议把耗时、内存、精度三个维度综合起来我给几个场景的推荐极致低功耗、对精度要求不高MobileNetV3-Small6.8ms 的耗时和 15.3MB 的内存峰值留给系统的余量最大。精度和速度平衡MobileNetV212.5ms 耗时70.4% 的量化精度是性价比最高的选择。精度优先、能接受稍慢EfficientNet-Lite072.3% 的量化精度但要注意 22.4MB 的内存峰值。不推荐ResNet18 及以上、SqueezeNet性价比低。实操心得选型时不要只看单张推理耗时要看端到端延迟。从摄像头采集一帧、预处理、推理、后处理到输出结果整个链路的时间才是用户感知的延迟。我实测下来预处理resize归一化在 CPU 上要花 5 到 8ms这部分时间不能忽略。如果预处理成了瓶颈可以考虑用 RISC-V 协处理器分担或者用硬件 ISP 直接输出模型需要的尺寸。5. 常见问题与排查技巧实录5.1 模型转换阶段的典型报错转换阶段最常见的问题是算子不支持。RKNN 工具链不是所有 ONNX 算子都支持遇到不支持的算子会直接报错。我整理了几个高频问题报错信息原因解决方法Unsupported op: XXX算子不在支持列表替换算子或修改模型结构Quantize failed at layer XXX该层激活值范围异常检查校准集或对该层跳过量化Model size exceeds limit模型太大换更小的模型或裁剪Input shape mismatch输入尺寸配置错误检查 config 里的输入配置算子不支持这个问题我的经验是优先用 MobileNet 系列这种结构规整的模型它们的算子都是最常见的卷积、BN、ReLU基本不会遇到不支持的情况。如果非要用带特殊算子的模型可以先用 ONNX 的onnxsim做简化把一些可以融合的算子合并掉。量化失败的问题多半是校准集的问题。如果某一层的激活值分布特别极端比如有异常大的值量化参数会估计不准。解决办法是检查校准集里有没有异常样本或者对这一层设置skip_quantization让它保持浮点——但 RV1103 不支持混合精度所以实际上只能换模型或者调整校准集。5.2 板端运行时的常见故障板端的问题更隐蔽因为看不到详细的日志。我遇到过几次推理结果全错的情况排查下来是这几个原因输入数据格式不对。RKNN 默认输入是 NHWC如果你的数据是 NCHW 排列推理结果会完全错乱。这个问题的表现是输出概率分布很平均或者总是输出同一个类别。排查方法是拿一张已知结果的图对比 CPU 推理和 NPU 推理的输出如果差异巨大就是格式问题。归一化参数不一致。训练时用的 mean/std 和 RKNN config 里的不一致会导致输入数据分布偏移精度大幅下降。这个问题的表现是精度下降但不会全错。排查方法是把 config 里的 mean/std 设成 0 和 1然后在 CPU 上自己做归一化对比结果。内存不足导致推理失败。RV1103 内存紧张如果同时开了摄像头、网络、文件系统缓存留给 NPU 的内存可能不够。表现是rknn_run返回错误码或者系统直接重启。解决办法是优化内存使用关掉不必要的服务或者换 128MB 版本。注意RV1103 的 NPU 和 CPU 共享内存带宽如果 CPU 在大量读写内存比如解码视频NPU 的推理速度会明显下降。我实测过同时跑视频解码时MobileNetV2 的推理耗时从 12.5ms 涨到了 18ms 左右。所以如果应用里有视频处理要预留性能余量。5.3 精度不达标的排查思路精度问题是部署中最难排查的因为它涉及训练、转换、推理多个环节。我总结了一个排查顺序先确认 FP32 模型在 PC 上的精度。如果 FP32 就不达标那是训练的问题和部署无关。确认 ONNX 模型和原模型精度一致。有时候导出 ONNX 会引入误差用 ONNX Runtime 跑一遍对比。确认量化前后的精度差异。如果量化损失超过 5%说明校准集或量化配置有问题。确认板端推理和 PC 上 RKNN 模拟器结果一致。RKNN Toolkit 提供了模拟器可以在 PC 上跑 RKNN 模型如果模拟器和板端结果不一致那是板端环境问题。这个顺序能帮你快速定位问题在哪一环。我遇到的大部分精度问题都出在第 3 步也就是量化环节通过调整校准集基本都能解决。5.4 性能优化的几个实用技巧最后分享几个我在 RV1103 上做性能优化的技巧输入分辨率不要盲目追高。224x224 对大部分分类任务够用了如果你用 320x320耗时翻倍但精度可能只提升 1 到 2 个点不划算。除非你的任务需要识别很小的目标否则 224 是甜点。预处理尽量用硬件。RV1103 有 ISP可以直接输出指定尺寸的图省掉 CPU 上的 resize。如果你的摄像头支持尽量用 ISP 做缩放能省 5ms 左右的 CPU 时间。批处理在 RV1103 上没意义。NPU 算力有限batch size 设成 2 以上耗时基本线性增长而且内存翻倍。单张推理就够了。模型文件放对位置。如果放在 SPI NAND 上加载时间会比较长几秒建议放在内存文件系统或者直接编译进固件。我实测过从 SPI NAND 加载 1.3MB 的模型要 2 秒左右从内存加载只要几十毫秒。善用 RKNN 的rknn_query接口。这个接口能查 NPU 的内存占用、推理耗时等信息调试阶段很有用。我一般会在初始化后查一次确认内存没超。6. 一些关于 RV1103 部署的个人体会做这轮实验前后花了大概三周时间踩的坑比预想的多。最大的感受是受限平台部署的核心不是能不能跑而是跑得稳不稳、余量够不够。在 PC 上跑通一个模型可能只要半天但在 RV1103 上要考虑到内存、功耗、温度、长时间运行的稳定性这些才是产品化的关键。另外一个体会是模型选型要在训练阶段就考虑部署约束。我见过太多团队先训一个大模型精度刷得很高然后发现部署不上去再回头改模型浪费大量时间。正确的做法是训练前就确定目标平台根据平台的算力和内存反推模型规模这样能少走很多弯路。RV1103 这颗芯片我的评价是够用但不宽裕。它适合做单一任务的轻量视觉比如分类、简单检测不适合多任务并行或者复杂模型。如果你的需求超出了它的能力范围老老实实上 RV1106 或者更高端的芯片不要硬撑。选对平台比优化代码重要得多。最后说一个细节RV1103 的 NPU 驱动和固件版本匹配很重要。我遇到过固件升级后 RKNN 模型加载失败的情况原因是 NPU 固件的接口变了。所以量产时要把固件版本和模型版本绑定管理不要随意升级。这个教训是用一次现场故障换来的希望你能避开。