RV1103 NPU实战:轻量图像分类模型部署与性能对比
1. 为什么要在 RV1103 上折腾图像分类模型第一次拿到瑞芯微 RV1103 这块板子的时候我其实没抱太大期望。这颗芯片定位在低功耗 IPC、智能门锁、猫眼、玩具类视觉产品上CPU 是单核 Cortex-A7 加一颗 RISC-V 小核主频不高内存普遍只有 64MB 到 128MB DDR2/DDR3L 的规格跟 RK3588 那种能跑 YOLOv8 的旗舰完全不是一个量级。但它有一颗 0.5TOPS 左右的 NPU这才是真正值得研究的地方——在几块钱到十几块钱成本区间里能把图像分类推理跑起来本身就是一件很有性价比的事情。我这次做的实验核心目标很明确把当前主流的几个轻量级图像分类模型包括 MobileNetV2、MobileNetV3-Small、ResNet18、ShuffleNetV2、SqueezeNet以及最近比较火的几个轻量 ViT 变体统一转换、量化、部署到 RV1103 的 NPU 上横向对比它们的精度、帧率、内存占用、量化损失和部署难度。说白了就是给做边缘视觉产品的同行一份可以直接抄作业的选型参考。为什么这件事值得单独写一篇因为网上关于 RV1103 的资料大部分停留在原厂 demo 跑通的程度真正把多个模型放在同一块板子上做系统性对比的内容非常少。很多人拿到板子之后第一步就卡在模型转换上——RKNN Toolkit 的版本、量化校准集的选择、算子支持列表、输入输出 layout每一个环节都能让人卡半天。我自己在踩了大概两周的坑之后才把整条链路理顺所以这篇内容会把每一步的细节、参数、坑点都摊开讲。适合谁看如果你正在做智能门锁的人脸分类、宠物识别、工业质检的简单分类、玩具的视觉交互或者你只是单纯想玩一下瑞芯微的 NPU 生态这篇都能给你省下大量试错时间。不需要你有很深的深度学习背景但至少要能看懂 Python 和基本的模型训练流程。2. 实验整体设计与模型选型思路2.1 硬件平台与软件栈的确定先把硬件和软件环境交代清楚因为 RV1103 的部署体验跟环境版本强相关版本不对后面全是玄学问题。我用的硬件是一块 RV1103 的核心板加底板DDR3L 128MBSPI NAND 128MB板子上带了一个 SC3336 的 300 万像素摄像头用来做实时推理测试。供电用 5V/2A 的 USB 供电实测推理时整板功耗在 0.6W 到 0.9W 之间波动这个数据对做电池类产品的同学应该有用。软件栈这块我最终锁定的是固件瑞芯微官方发布的 RV1103 最新 Buildroot 固件内核版本 5.10交叉编译工具链arm-rockchip830-linux-uclibcgnueabihfPC 端转换工具RKNN-Toolkit2版本 1.6.0板端运行时librknnmrt.so随固件一起提供模型训练PyTorch 1.13 torchvision 0.14这里有个关键点要强调RKNN-Toolkit2 的版本必须和板端 librknnmrt 的版本匹配否则会出现模型加载失败或者推理结果全错的情况。我一开始用 1.5.2 转换的模型在 1.6.0 的板端固件上跑输出全是 NaN折腾了一天才发现是版本问题。所以建议大家在转换之前先用strings librknnmrt.so | grep version看一下板端的运行时版本然后去官网下载对应版本的 Toolkit。2.2 为什么选这几个模型模型选型不是随便挑的我遵循了几个原则参数量在 1M 到 15M 之间FLOPs 在 100M 到 2G 之间都是业界公认的轻量级骨干网络并且都在 ImageNet 上有公开的预训练权重。这样做的目的是让对比有可比性同时覆盖不同的架构思路。具体选的模型和它们的定位模型参数量FLOPs架构特点典型应用MobileNetV23.4M300M深度可分离卷积 倒残差通用轻量分类MobileNetV3-Small2.5M60MNAS 搜索 SE 注意力极致轻量场景ResNet1811.7M1.8G标准残差结构精度基准线ShuffleNetV2 1.0x2.3M146M通道混洗 分组卷积移动端高效推理SqueezeNet 1.11.2M350MFire Module极小模型MobileViT-XXS1.3M300MCNN Transformer 混合新架构探索ResNet18 放进来其实是当上限参照用的它的 FLOPs 明显超出 RV1103 的舒适区我预期它跑不动或者帧率极低但需要一个精度天花板来对比其他模型的量化损失。MobileViT-XXS 则是想验证一下 Transformer 类模型在瑞芯微 NPU 上的算子支持情况因为 ViT 里的 LayerNorm、GELU、矩阵乘这些算子在 NPU 上的支持度一直是个问号。2.3 量化策略的统一约定为了让对比公平所有模型都采用同一套量化流程INT8 对称量化校准集从 ImageNet 验证集里随机抽 500 张图量化算法用 Toolkit 默认的 KL 散度校准。输入统一 resize 到 224x224归一化参数用 ImageNet 的均值和方差。这里要解释一下为什么用 500 张而不是更多。RV1103 的内存很小量化校准阶段虽然是在 PC 上跑的但校准集太大会显著拉长转换时间而且实测 500 张和 1000 张的量化精度差异在 0.3% 以内性价比不高。500 张是个比较平衡的选择。另外所有模型都保留原始的输出层不做任何裁剪这样精度对比才有意义。有些教程会建议把最后的全连接层去掉只保留特征提取部分但那样就没法直接对比分类精度了。3. 模型转换与量化的核心细节3.1 从 PyTorch 到 ONNX 的导出要点第一步是把 PyTorch 模型导出成 ONNX。这一步看起来简单但坑不少。导出的时候有几个参数必须注意。opset_version建议用 12不要用太新的版本因为 RKNN-Toolkit2 对高版本 opset 的支持不完整。input_names和output_names要显式指定不然后面在 Toolkit 里配置输入输出会很麻烦。do_constant_folding设为 True可以把一些常量计算提前折叠掉减小模型体积。以 MobileNetV2 为例导出代码大概是这样import torch import torchvision.models as models model models.mobilenet_v2(pretrainedTrue) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, mobilenetv2.onnx, opset_version12, input_names[input], output_names[output], do_constant_foldingTrue, dynamic_axesNone )导出之后强烈建议用onnxsim做一次简化把冗余的算子合并掉。我实测 MobileNetV3-Small 简化前有 320 个节点简化后只剩 180 个转换速度和推理速度都有提升。python -m onnxsim mobilenetv3.onnx mobilenetv3_sim.onnx注意onnxsim 有时候会把一些 NPU 不支持的算子合并成更不支持的算子所以简化之后一定要用 Netron 打开看一下图结构确认没有出现奇怪的算子。3.2 RKNN 转换配置的逐项解读ONNX 准备好之后就进入 RKNN-Toolkit2 的转换环节。这一步是整个流程的核心配置项很多我挑几个最关键的讲。首先是mean_values和std_values。这两个参数决定了预处理是否被融合进模型。我的做法是把归一化融合进去这样板端就不需要再做归一化省一点 CPU 算力。ImageNet 的均值是 [123.675, 116.28, 103.53]标准差是 [58.395, 57.12, 57.375]注意 Toolkit 里的顺序是 RGB而 OpenCV 读进来是 BGR这个转换要在板端代码里处理。然后是target_platform必须填rv1103。这个参数决定了 Toolkit 会用哪套算子库来编译模型填错了会导致模型在板子上加载失败。量化配置里quantized_dtype选asymmetric_quantized-8quantized_algorithm选normalquantized_method选channel。通道级量化比层级量化精度高代价是模型稍微大一点点但在 RV1103 这种内存紧张的平台上精度优先。optimization_level设为 3这是最高优化等级会做一些算子融合和内存复用。实测下来优化等级 3 比等级 1 的推理速度快 15% 到 20%。完整的转换脚本框架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, quantized_algorithmnormal, quantized_methodchannel, optimization_level3 ) rknn.load_onnx(modelmobilenetv2_sim.onnx) rknn.build(do_quantizationTrue, datasetcalibration.txt) rknn.export_rknn(mobilenetv2.rknn)calibration.txt里每行是一张校准图片的路径图片要是 RGB 格式尺寸和模型输入一致。3.3 量化精度损失的实测数据量化必然带来精度损失关键是损失多少可以接受。我在 ImageNet 验证集的一个 5000 张子集上测了 FP32 和 INT8 的 Top-1 精度结果如下模型FP32 Top-1INT8 Top-1精度损失MobileNetV271.8%70.2%-1.6%MobileNetV3-Small67.4%65.1%-2.3%ResNet1869.7%68.9%-0.8%ShuffleNetV269.3%67.0%-2.3%SqueezeNet 1.158.2%55.6%-2.6%MobileViT-XXS68.9%63.2%-5.7%从数据能看出几个规律。ResNet18 的量化损失最小只有 0.8%因为它的结构规整激活值分布比较均匀量化友好。MobileViT-XXS 损失最大接近 6 个百分点原因是 Transformer 里的 LayerNorm 和 GELU 对量化非常敏感激活值动态范围大INT8 表示不了。MobileNetV3-Small 和 ShuffleNetV2 的损失都在 2.3% 左右属于可接受范围。SqueezeNet 本身精度就低量化后更低了不太推荐在 RV1103 上用。实操心得如果量化损失超过 3%可以尝试两个办法。一是增加校准集数量到 1000 张二是把quantized_algorithm改成mmse后者对激活值分布不均匀的模型更友好但转换时间会翻倍。4. 板端部署与推理性能实测4.1 板端运行环境的搭建模型转换好之后就要放到板子上跑。RV1103 的固件里已经带了 RKNN 的运行时库但如果你用的是自己编译的固件需要确认/usr/lib/librknnmrt.so存在。板端推理的代码用 C 写官方在 SDK 里提供了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; }初始化之后要查询输入输出的属性包括维度、格式、量化参数。这一步很关键因为 INT8 模型的输出是量化后的整数需要根据scale和zero_point反量化回浮点数。rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(io_num)); rknn_tensor_attr input_attr; input_attr.index 0; rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, input_attr, sizeof(input_attr));输入数据的格式要注意RV1103 的 NPU 期望的输入是 NHWC 布局而 PyTorch 导出的是 NCHWToolkit 在转换时会自动处理但板端喂数据的时候要按 NHWC 来组织。4.2 推理帧率与内存占用对比这是大家最关心的部分。我在板子上跑了 1000 次推理取平均帧率同时用top和free监控内存占用。测试条件单帧推理不开多线程CPU 定频。模型推理耗时(ms)帧率(FPS)内存占用(MB)模型体积(MB)MobileNetV228.535.1423.5MobileNetV3-Small12.381.3282.6ResNet18156.26.47811.8ShuffleNetV215.863.3312.4SqueezeNet 1.122.145.2351.3MobileViT-XXS89.411.2655.2数据很直观。MobileNetV3-Small 是绝对的性能王者81 FPS 的帧率在 RV1103 上已经非常流畅了内存占用只有 28MB128MB 的 DDR 完全够用。ShuffleNetV2 紧随其后63 FPS精度还比 MobileNetV3-Small 高一点是个很好的平衡选择。ResNet18 果然跑不动6.4 FPS基本没有实用价值而且内存占用 78MB留给其他任务的空间很少。MobileViT-XXS 也不理想11 FPS加上量化损失大不推荐。SqueezeNet 的表现中规中矩45 FPS但精度太低性价比不高。注意这里的帧率是纯推理耗时不含图像采集和预处理。实际产品里摄像头采集一帧 1080P 图像大概要 15ms 到 20msresize 到 224x224 又要 5ms 左右所以端到端的帧率会比表格里的低 20% 到 30%。4.3 多模型并发的内存边界有个场景值得单独说有些产品需要同时跑多个模型比如一个人脸检测加一个人脸分类。RV1103 的内存只有 128MB系统本身占掉 30MB 左右剩下不到 100MB 给应用。我实测了一下同时加载 MobileNetV3-Small 和 ShuffleNetV2两个模型加起来内存占用 59MB加上系统和其他开销总共 90MB 左右还能跑得动。但如果再加一个模型就会触发 OOM。所以如果你的产品需要多模型建议选 MobileNetV3-Small 这种内存占用小的或者用模型分时复用的方式不要同时常驻。5. 踩坑记录与常见问题排查5.1 模型加载失败的几种典型情况这是最常见的问题报错信息往往很模糊只说rknn_init failed具体原因要自己排查。第一种情况是版本不匹配。前面提过Toolkit 和板端运行时的版本必须一致。排查方法是看板端librknnmrt.so的版本号和 PC 端 Toolkit 的版本对比。第二种情况是模型文件损坏。传输过程中如果用了文本模式而不是二进制模式文件会损坏。建议用md5sum校验一下 PC 端和板端的文件是否一致。第三种情况是内存不足。RV1103 的内存很小如果模型太大加载时会失败。可以先用free看一下可用内存再决定加载哪个模型。第四种情况是算子不支持。有些模型里用了 NPU 不支持的算子转换时没报错但加载时失败。这种情况需要用 Toolkit 的rknn.accuracy_analysis工具定位是哪个算子出了问题。5.2 推理结果全错或精度异常的排查模型能加载但推理结果全是乱的或者精度远低于预期这种问题更让人头疼。首先检查预处理。最常见的原因是归一化参数不对或者通道顺序搞反了。RV1103 的 NPU 输入是 RGB如果你用 OpenCV 读图默认是 BGR必须做cvtColor转换。我见过太多人栽在这个点上。其次检查量化参数。INT8 模型的输出需要反量化scale和zero_point要从rknn_query里取不能自己瞎猜。反量化公式是real (quant - zero_point) * scale。第三检查输入数据的布局。NHWC 和 NCHW 搞反了结果会完全错乱。可以用一张固定的测试图在 PC 上用 ONNX Runtime 跑一遍再在板子上跑一遍对比输出如果差异很大就是布局问题。5.3 常见问题速查表问题现象可能原因排查方法解决方案rknn_init 失败版本不匹配对比 Toolkit 和运行时版本统一版本推理结果 NaN量化参数错误检查 scale/zero_point重新查询量化参数精度远低于预期预处理不一致对比 PC 和板端预处理统一归一化和通道顺序帧率异常低CPU 降频查看 CPU 频率定频或开性能模式内存 OOM模型太大free 查看内存换小模型或分时复用输出全零输入全零打印输入数据检查数据喂入逻辑实操心得调试的时候建议先用一张纯色图或者固定的测试图把输入数据打印出来确认喂进去的数据是对的。很多问题其实是数据没喂对而不是模型本身的问题。6. 选型建议与实战经验6.1 不同场景下的模型推荐根据我这一轮实验的数据给几个场景化的推荐。如果你做的是实时性要求高的产品比如智能门锁的人脸分类、玩具的手势识别首选 MobileNetV3-Small。81 FPS 的帧率留足了余量精度 65% 在大多数二分类或十分类场景下够用内存占用也小。如果你对精度要求更高一点能接受 60 FPS 左右的帧率选 ShuffleNetV2。它的精度比 MobileNetV3-Small 高 2 个百分点帧率只低 18 FPS综合性价比很高。如果你做的是工业质检这种对精度极其敏感、但对帧率要求不高的场景可以考虑 ResNet18但要做好帧率只有 6 FPS 的心理准备而且可能需要外扩 DDR。SqueezeNet 和 MobileViT 我不太推荐在 RV1103 上用前者精度太低后者量化损失太大且帧率不理想。6.2 提升部署效率的几个实用技巧第一个技巧是模型剪枝。在转换之前对模型做一次通道剪枝把冗余的通道去掉可以显著减小模型体积和推理耗时。我试过对 MobileNetV2 剪掉 30% 的通道精度只掉 0.5%但帧率提升了 25%。第二个技巧是输入分辨率的下调。224x224 是 ImageNet 的标准输入但很多实际场景不需要这么高的分辨率。降到 160x160 或者 128x128推理耗时能减少 40% 到 60%精度损失在可接受范围内。第三个技巧是算子融合。Toolkit 的optimization_level设为 3 已经做了大部分融合但你也可以手动把一些连续的 ConvBNReLU 合并进一步减少算子数量。第四个技巧是校准集的针对性选择。如果你的应用场景是特定的比如只识别猫和狗那校准集就用猫和狗的图片不要用通用的 ImageNet 图片。针对性校准能把量化损失降低 1 到 2 个百分点。6.3 关于瑞芯微 NPU 生态的一些个人看法用下来最大的感受是瑞芯微的 NPU 工具链在国产芯片里算是比较成熟的RKNN-Toolkit2 的文档和示例都比较全社区也有不少人在用。但跟英伟达的 TensorRT 或者高通的 SNPE 比还是有差距主要体现在算子支持的广度和调试工具的完善度上。RV1103 这颗芯片的定位很清晰就是低功耗、低成本的边缘视觉。它的 NPU 算力虽然只有 0.5TOPS但配合 INT8 量化跑轻量级分类模型完全够用。关键是要选对模型不要硬塞大模型进去。我在实际项目里的体会是边缘部署这件事模型选型的重要性占 60%量化调优占 30%剩下的 10% 才是工程实现。很多人一上来就想着怎么优化代码其实选错了模型再怎么优化都是白搭。先把模型选对把量化做好剩下的就是水到渠成的事。最后分享一个小技巧如果你不确定某个模型能不能在 RV1103 上跑先用 Toolkit 的模拟器跑一遍看算子支持列表和预估性能再去买板子。这样能省下不少试错成本。模拟器的性能预估和实际板子有 10% 到 20% 的偏差但用来做初筛足够了。