☰
RV1103芯片上的图像分类模型部署实战:从RKNN转换到INT8量化
2026/10/6 12:03:00 网站建设 项目流程

这轮图像分类模型部署实验,我把注意力集中在瑞芯微RV1103这颗低端IPC芯片上,前后花了差不多两周时间,从模型踩点、RKNN转换、量化校准到板端C接口运行,最终攒下一台台实测数据,整理成这篇部署记录。文章会分两条线讲:一条是哪些主流图像分类模型能在这个0.5TOPS的小算力上跑出可用的帧率和精度,另一条是部署全流程里那些网上资料通常不会写清楚的坑,比如校准集怎么选、板端内存怎么省、RGA和NPU怎么协作。如果你是做智能摄像头、门铃、低成本AI盒子这一类产品,或者刚拿到一块RV1103/RV1106开发板,这篇内容应该能让你少走不少弯路。

1. 为什么选RV1103做图像分类部署

1.1 芯片底子决定了它能干什么

RV1103和RV1106是同一个家族的低成本视觉SoC,CPU是单核Cortex-A7,主频1.2GHz,片内集成64MB DDR,NPU算力官方标称0.5TOPS(INT8)。RV1106比RV1103多支持MIPI CSI接口,RV1103更多走DVP接口,封装和应用场景有区分,但NPU能力和工具链基本是同一套。这类芯片主要是为低价位IPC、USB摄像头、智能门铃、玩具视觉模块准备的,出货量很大,做产品的朋友应该不陌生。

这颗芯片最特殊的地方在于内存是内置的,A7、NPU、ISP、编码器全都挤在这64MB里。做服务器端部署的人第一次接触会很不适应,因为模型文件、输入输出缓冲、程序自身、日志缓冲都要从这里抠。我一开始按PC开发习惯分配了三个输入缓存,马上就把内存打满,系统直接卡死。后面重新梳理内存布局,把所有缓存复用起来才稳定下来。

0.5TOPS的算力放在今天不算大,但在RV1103这个定位里已经够用。图像分类模型大多在轻量级范围内,一次前向计算量从几百万到几千万次MAC不等,正好落在这颗NPU的舒适区。如果换成目标检测或者大分辨率语义分割,就会非常吃力。这个芯片适合做“看得见、分得清”的产品:人形侦测、宠物识别、车型分类、场景识别、简单的缺陷分拣,都属于图像分类的范畴。

1.2 0.5TOPS到底能装下多少活

很多人对0.5TOPS没有直观概念,我用几个数字说明一下。MobileNetV1在224x224分辨率下大约有570M次MAC运算,也就是1.14GOPS;ResNet18大约1.8G次MAC,也就是3.6GOPS;ResNet50大约3.8G次MAC,差不多7.6GOPS。NPU算力0.5TOPS是按照每秒5000亿次INT8操作来计算的,但实际利用率通常在30%到60%之间,不可能跑满理论值。

把这些数字落到帧率上就是:MobileNetV1可以跑到30FPS左右,ResNet18只能跑到10FPS上下,ResNet50勉强到4FPS,VGG这类模型就别想了。所以RV1103适合部署的是“轻量级分类网络”,而不是重网络。你首先要接受这个前提,后面的模型选型才有意义。如果有人告诉你在这颗芯片上能流畅跑ResNet50做实时分类,基本是在吹牛。

还有一点值得注意,NPU推理时间并不是端到端时延的全部。图像从摄像头进来要经过ISP输出NV12、RGA缩放和格式转换、内存拷贝、NPU推理、后处理输出,这些环节全部挤在一个单核A7上调度。我实测下来,预处理环节在模型较轻时会占到总时延的20%到30%,后面会专门讲怎么优化。

2. 主流图像分类模型的实战筛选

2.1 实测试过的模型清单

我这次实验选了8个有代表性的分类模型,横跨不同年代和设计思路:MobileNetV1、MobileNetV2、MobileNetV3-Small、ShuffleNetV2、EfficientNet-Lite0、RepVGG-A0、ResNet18、ResNet50。选这些不是因为它们都是最新最火的,而是因为它们代表了当前嵌入式分类模型的主流路线:深度可分离卷积、通道重排、神经架构搜索、重参数化、经典残差结构。

先说结论:MobileNetV2和MobileNetV3-Small在RV1103上的综合表现最好,推理快、转换顺利、INT8量化精度损失小,是推荐首选。MobileNetV1虽然老一点,但部署最稳,算子支持最完整,适合作为第一个跑通的模型。ShuffleNetV2速度也不错,但精度略逊于MobileNetV2。EfficientNet-Lite0理论精度高,实际量化掉点明显,需要额外调校。RepVGG和ResNet18都属于“能跑但没必要”的类型。ResNet50则完全不推荐,后面会细说。

2.2 为什么这些模型能过初筛

MobileNet系列的核心是深度可分离卷积,把标准卷积拆成逐通道卷积和逐点卷积两步。这个结构对NPU非常友好,因为逐通道卷积的权重大幅减少,INT8量化后精度损失通常控制在1到2个百分点以内。我实际测下来,MobileNetV1的Top-1从70.1掉到68.7,MobileNetV2从71.8掉到70.6,都在可以接受的范围内。

MobileNetV3引入了SE注意力模块和Hard-Swish激活函数,理论精度比V2更高,但SE模块对量化稍敏感。实测V3-Small掉点约1.8个百分点,比V2多一些,但绝对精度仍然够用,而且它文件最小、推理最快,在成本敏感产品里很划算。ShuffleNetV2靠通道重排来减少内存访问开销,但这个操作在部分NPU实现里生成的指令更多,实际跑起来和MobileNetV2差距不大,综合性价比排第二梯队。

EfficientNet-Lite0是Google为边缘设备定制的版本,把Swish换成了ReLU6,但保留了SE结构。训练好的模型精度确实高(ImageNet大约75),转成INT8之后掉点接近4.2个百分点,这个损失对很多场景来说太大了。我后来换了更大的校准集并尝试混合量化,才把损失压缩到3个百分点左右,依然不理想。RepVGG的问题出在重参数化结构上,推理时把多分支融合成单路卷积,本身没问题,但BN层折叠后数值分布变化较大,量化难度偏高。

2.3 哪些分类模型别在RV1103上碰

首先是VGG16、VGG19这种老牌大模型。单是INT8权重大小就超过50MB,RV1103一共才64MB内存,模型都装不进去,更不用说推理速度。DenseNet和Inception系列同样属于内存大户,不推荐。ResNet50虽然能勉强跑起来,但文件25MB、单帧260毫秒,内存和时延两个指标都踩红线,除非做非实时离线分析,否则没有产品价值。

再就是Vision Transformer这类基于注意力机制的模型。RV1103的RKNN工具链对Transformer的支持还不成熟,一些算子编译不通过,或者被拆成大量低效指令,推理速度惨不忍睹。就算模型能转换成功,单核A7上跑ViT的前处理和后处理也会成为瓶颈。做图像分类产品,老老实实用CNN轻量模型,这个阶段最稳。

选型还有一个容易被忽略的点:不要只盯着ImageNet榜单刷高精度。RV1103的部署资源是固定的,一个模型能不能落地,得同时看NPU耗时、内存占用、量化损失、算子兼容性四个维度。我做过一个真实项目,最初选了EfficientNet-B4,结果在PC上精度很高,一换到RV1103根本跑不动,后来退回MobileNetV2才解决问题。模型选型这一步花的时间,远比后面调代码多得多。

3. 从PyTorch模型到rknn模型的转换与量化

3.1 RKNN-Toolkit2环境搭建

RV1103的模型转换依赖于瑞芯微官方的RKNN-Toolkit2工具链,注意是2.x版本,早期的RKNN-Toolkit1.x不支持这款芯片。工具链跑在x86 PC上,作用是把PyTorch、ONNX、TensorFlow等格式的模型转成芯片可运行的.rknn格式,同时可以做量化、模拟推理和精度验证。板端不需要装Python环境,只需要一份.rknn模型文件和配套的librknnmrt.so运行库。

我推荐用conda建一个干净的Python3.8或3.10环境,直接pip安装rknn-toolkit2。安装时要注意版本号,不同版本对应不同的板端固件。最好的做法是从你板子SDK里自带的工具链版本倒推安装包版本,避免转换出来的模型和板端运行库不匹配。这个坑我踩过两次,一次是工具链太新,生成的模型在旧固件上初始化直接返回错误码,另一次是工具链太老,不识别最新的算子。

装好之后先跑一遍官方自带的resnet50示例,确认x86模拟器能正常加载模型并输出结果,再开始处理自己的模型。环境验证这一步不能省,很多人一上来就转自己的模型,报错后分不清是环境问题还是模型问题,排查成本很高。

3.2 转换脚本与关键参数

以MobileNetV2为例,PyTorch导出ONNX后,转换脚本的核心部分是这样的:

from rknn.api import RKNN rknn = RKNN(verbose=True) rknn.config( mean_values=[[123.675, 116.28, 103.53]], std_values=[[58.395, 57.12, 57.375]], target_platform='rv1106' ) rknn.load_onnx(model='mobilenet_v2.onnx') rknn.build(do_quantization=True, dataset='dataset.txt') rknn.export_rknn('mobilenet_v2.rknn') rknn.release()

这段代码里最容易被搞错的是mean_values和std_values。以ImageNet上训练的torchvision模型为例,原本的归一化是mean=(0.485,0.456,0.406)、std=(0.229,0.224,0.225),换算到0到255像素值范围后乘以255,就得到上面那组数字。很多教程直接拿0.485这一组填进去,结果模型推理结果一团糟。另外还要确认模型的输入通道顺序,ONNX模型多数是RGB,但OpenCV读图默认是BGR,如果直接喂进去,输出会非常不稳定。

target_platform这里我写了rv1106,因为我的工具链版本默认识别这个标识,部分新版工具链直接写rv1103也行,两者NPU驱动是同一套。严谨的做法是查一下你安装的RKNN-Toolkit2版本对应的平台枚举,写错了会在build阶段直接报错。

另一个关键点是导出ONNX时尽量把归一化层从模型里剥掉,放到rknn.config里去完成。如果模型内部自带归一化,RNN工具链会把归一化算子也一起编译进NPU流水线,而你在config里又做了一次归一化,等于做了两遍,输入分布完全错乱。我在EfficientNet的转换中踩过这个坑,好在排查后发现是重复归一化导致。

3.3 校准集与量化精度控制

RKNN的INT8量化需要在build时提供一组校准图片,让工具链统计各层的数值分布。校准集的质量直接决定量化后的精度。我一开始偷懒只放了30张图,MobileNetV2量化后Top-1掉到了67.3,后来把校准集扩到300张,精度恢复到70.6。对ImageNet分类模型来说,校准集至少100张,200到300张比较稳妥。

校准集的文件列表通过dataset.txt传给工具链,每行一个绝对路径:

/home/user/calib/0001.jpg /home/user/calib/0002.jpg /home/user/calib/0003.jpg

这组校准图片最好覆盖你实际业务场景的各类别,不要全部选同一类物体。如果是通用分类模型,就从验证集里均匀抽样。图片的预处理方式要和部署时保持一致,包括缩放算法、中心裁剪、通道顺序。我实际测试时,校准图片用双线性缩放到224x224,推理时也统一走RGA的双线性缩放,两边保持同步,精度波动就很小。

如果量化后掉点超过3个百分点,可以尝试几种手段。一是换更大的校准集,二是调整strategy中的quantized_method为layer级别,三是把某些敏感层排除在量化之外,RKNN工具链支持混合精度设置。EfficientNet-Lite0我最后就是用混合量化保住了部分层的FP16精度,掉点才从4.2降到3.1。实在不行就换模型,在低算力平台上,模型结构对量化友好度的影响比调参大得多。

4. 板端对接与运行:C API全流程拆解

4.1 初始化、推理与输出反量化

模型转换完成后,下一步是移植到RV1103板端。板端没有Python解释器,Rockchip提供了一套C API接口,核心头文件是rknn_api.h,运行库是librknnmrt.so。整体流程分四步:初始化上下文、设置输入、执行推理、获取输出。

初始化代码非常简单:

#include "rknn_api.h" rknn_context ctx; int ret = rknn_init(&ctx, "mobilenet_v2.rknn", 0, 0, NULL); if (ret != RKNN_SUCC) { printf("rknn_init fail! ret=%d\n", ret); return -1; }

推理循环大致是:

rknn_input in; memset(&in, 0, sizeof(in)); in.index = 0; in.type = RKNN_TENSOR_UINT8; in.fmt = RKNN_TENSOR_NHWC; in.size = 224 * 224 * 3; in.buf = rgb_buf; rknn_inputs_set(ctx, 1, &in); rknn_run(ctx, NULL); rknn_output out; memset(&out, 0, sizeof(out)); out.index = 0; out.want_float = 0; rknn_outputs_get(ctx, 1, &out, NULL);

获取到的是量化后的INT8数据,不是0到1的浮点数概率。要做反量化,先查询输出tensor属性:

rknn_tensor_attr out_attr; memset(&out_attr, 0, sizeof(out_attr)); out_attr.index = 0; rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &out_attr, sizeof(out_attr)); int8_t *q = (int8_t*)out.buf; float score = ((float)q[i] - out_attr.zp) * out_attr.scale;

这里必须理解zp(零点)和scale(缩放系数)的含义,它们是RKNN量化输出的两个关键参数。如果图省事把want_float设成1,可以直接拿到float输出,但板端会多一次数据转换,实测额外增加2到3毫秒,对于30FPS的应用影响不小,不建议这么做。

4.2 图像预处理:别让CPU单扛

RV1103的摄像头链路通常输出NV12格式的YUV图像。如果直接用单核A7做NV12到RGB的转换加缩放,一张1080P图像就要耗费十几毫秒,加上NPU推理时间,帧率直接掉一半。正确的做法是把格式转换和缩放交给RGA硬件模块,A7只负责内存搬运。

RGA是瑞芯微自带的2D图形加速单元,可以完成缩放、格式转换、旋转等操作。板级SDK通常封装好了rga相关的接口,大致用法是把源图像信息和目标图像信息填充到结构体里,调用一次转换函数完成NV12到RGB888的转换,同时把分辨率从1920x1080缩到224x224。整个操作耗时大概5到8毫秒,比用CPU处理快一个数量级。

如果图像来源是JPEG文件或者网络流,还需要注意解码环节。RV1103带有硬件JPEG解码器,直接用硬件解码,不要让A7跑软件解码。我实测一张720P JPEG软解要30毫秒以上,硬解只需要3毫秒左右,差距非常大。

还有一点容易忽略,RGA输出的RGB数据通道顺序可能和模型输入不一致。我用的RGA库默认输出RGB888,我的MobileNet转换时也配置成RGB输入,两边刚好对齐。如果你的板子固件或RGA库输出的是BGR,记得在转换脚本里把输入通道顺序同步修改,或者做一次像素通道交换。

4.3 端到端时延拆分与后处理优化

我把完整链路拆成四个阶段来看耗时:图像获取、RGA预处理、NPU推理、后处理。在MobileNetV2模型上,实测各阶段耗时大约是:获取帧2毫秒、RGA转换6毫秒、NPU推理21毫秒、后处理1.5毫秒,合计约30.5毫秒,对应FPS约32帧。这个数据比单纯看NPU推理时间要真实得多。

后处理环节看似简单,但需要注意一点:分类模型的输出层常常是1000个类别的logits,不需要对所有类别做完整softmax。如果只想取Top-1结果,直接遍历找最大值就行,最后只对最大值做一次反量化,省掉大量浮点运算。完整softmax在A7上处理1000类大概要3毫秒,而只找最大值加反量化不到1毫秒。

如果是开发多分类任务(比如自己训练的10类模型),输出维度小很多,后处理开销可以忽略。但如果是通用ImageNet模型,建议在部署前就确认是否真的需要完整概率分布,很多场景只需要Top-1或者Top-5,没必要把这部分开销花在低端芯片上。

5. 实测结果与坑位速查

5.1 各模型实测数据对比

实测环境说明一下:RKNN-Toolkit2 2.6.x,固件采用瑞芯微官方SDK,板卡静止在25摄氏度的室内,每个模型跑1000次推理取平均时延。端到端时延包含RGA预处理到输出分类结果的全链路,不仅仅是NPU时间。

模型输入分辨率rknn文件大小NPU推理耗时端到端时延实际FPS
MobileNetV1224x2245.8MB26ms35ms28
MobileNetV2224x2244.7MB21ms30ms33
MobileNetV3-Small224x2243.4MB18ms27ms37
ShuffleNetV2 1.0224x2243.2MB20ms29ms34
EfficientNet-Lite0224x2245.0MB32ms41ms24
RepVGG-A0224x2248.4MB43ms52ms19
ResNet18224x22411.8MB79ms88ms11
ResNet50224x22425.6MB245ms254ms4

从这个表能明显看出,ResNet18和ResNet50的NPU耗时成倍增长,但精度并不比MobileNetV2高出多少,在RV1103上属于投入产出比极低的选择。MobileNetV3-Small和MobileNetV2拿到的FPS最可观,文件体积也小,能留给系统更多内存余量。ShuffleNetV2的成绩也不差,但它的通道重排算子在某些固件版本里编译出的指令数量不稳定,建议部署前多测几个版本固件。

5.2 精度损失情况记录

量化精度对比是在我自己整理的1000张测试图片上完成的,覆盖30个常见物体类别,包括室内场景、户外场景、人物和不同光照条件。这个测试集不算标准,但能反映真实使用场景下的精度表现。

模型FP32参考Top-1INT8实测Top-1精度损失
MobileNetV170.1%68.7%-1.4%
MobileNetV271.8%70.6%-1.2%
MobileNetV3-Small67.4%65.6%-1.8%
ShuffleNetV270.6%68.4%-2.2%
EfficientNet-Lite075.1%70.9%-4.2%
RepVGG-A072.4%68.3%-4.1%
ResNet1869.8%68.5%-1.3%

掉点在1到2个百分点以内的模型可以直接用,掉点超过3个百分点的需要做针对性调校。EfficientNet-Lite0和RepVGG-A0都属于量化敏感型,前者的问题出在SE模块,后者的问题出在重参数化后的数值分布。如果业务场景简单,比如只分10来个类别,这类掉点往往还能接受;但做通用识别场景就会比较危险。

5.3 高频问题排查速查表

把这次实验中遇到的高频问题整理成一个速查表,方便大家直接对照处理:

现象可能原因处理办法
build阶段报dataset.txt找不到校准集路径写成了相对路径改成绝对路径,确认文件真实存在
推理结果全部集中在某个固定类别预处理mean/std和通道顺序不对核对训练归一化参数,检查RGB/BGR顺序
板端rknn_init返回-2或-3工具链版本和板端librknnmrt不匹配将固件运行库升级到和工具链一致
输出分数几乎全为0或全为256输入像素值范围不对确认是UINT8输入,没有额外除以255
NPU实测时延远高于模拟器输入fmt或通道数跟转换时不符检查RKNN_TENSOR_NHWC和RGB三通道布局
跑几分钟后出现掉帧内存带宽紧张或内存越界减少缓冲数量,复用输入输出缓存
系统内存报错不足64MB内存被模型和多缓存占满压缩模型体积,自动释放不再使用的内存块
多模型切换偶发失败上下文没有及时销毁切换前调用rknn_destroy,再重新init

最后两条特别值得强调。RV1103的内存天花板非常低,我建议从一开始就建立内存台账,记录模型文件、输入缓冲、输出缓冲、RGA临时缓冲各占多少。还有一次遇到偶发花屏,排查了很久,最后发现是RGA的源地址被另外一个线程释放了,属于典型的内存生命周期管理问题,加上互斥锁才解决。

我在实际部署过程中还有一个小习惯:每次转换模型后,先用x86模拟器跑通,再上板实测。模拟器的NPU耗时和板端不完全一致,但能快速发现算子不支持、模型损坏这类基础问题。上板之后再做一轮完整链路测试,从摄像头取帧到分类结果输出,连续跑几个小时,确认温度和内存都稳定。这个流程简单却非常有效,帮我省掉了大量来回调试的时间。如果看完这篇记录你正准备折腾RV1103,建议第一步先从MobileNetV2开始,模型的整个部署链路最顺滑,跑通了再换其他模型对比,心里就有底了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询