1. 为什么要在RV1106上折腾图像分类模型
第一次拿到RV1106这块板子的时候,我其实没太当回事。一颗面向低功耗IPC和边缘视觉场景的SoC,内置NPU,官方标称算力在0.5TOPS这个量级,看起来就是给简单检测任务用的。但真正上手之后才发现,这颗芯片的定位比想象中微妙得多——它便宜、功耗低、集成度高,非常适合做常驻式的图像分类任务,比如工业质检里的良品/次品初筛、智能门禁里的人形/非人形判断、农业场景里的果实成熟度分级这类不需要跑大模型的活儿。
问题在于,网上关于RV1106的资料极度碎片化。官方文档讲的是工具链怎么用,社区帖子讲的是怎么把某个特定模型跑起来,但很少有人系统性地对比过:到底哪些主流图像分类模型能在RV1106上跑得动、跑得好、跑得稳?MobileNet系列、ResNet系列、EfficientNet系列、ShuffleNet系列,这些在服务器和手机端已经被验证过无数次的模型,放到RV1106的NPU上会是什么表现?量化之后精度掉多少?推理延迟能不能满足实时性要求?内存占用会不会爆?
我花了大概三周时间,把手上能拿到的几个主流分类模型挨个在RV1106上部署了一遍,从模型转换、量化、编译到板端推理,踩了不少坑,也积累了一些在官方文档里找不到的经验。这篇文章就是把这整个过程拆开来讲清楚,包括工具链的选型逻辑、量化参数的取舍、板端推理的代码结构,以及不同模型在实际场景下的表现差异。如果你手上正好有RV1106的板子,或者正在评估用哪颗芯片做边缘分类任务,这些内容应该能帮你省掉不少试错时间。
2. RV1106的NPU到底能吃什么样的模型
2.1 算力账不能只看TOPS数字
RV1106的NPU标称0.5TOPS,但这个数字是INT8精度下的理论峰值。实际能跑出多少有效算力,取决于模型的算子结构、内存带宽占用、以及数据搬运的效率。我实测下来,一个结构规整的MobileNetV2,在输入分辨率224x224的情况下,单帧推理大概在15到20毫秒之间,换算下来有效算力大概在理论值的40%到50%。这个折扣率在边缘芯片里算正常水平,但如果模型里有大量非标准算子,折扣会更狠。
这里有个容易被忽略的点:NPU的算力单位是TOPS,但模型的计算量单位是FLOPs或者MACs。一个1G MACs的模型,在0.5TOPS的NPU上理论最快是2毫秒,但实际因为内存访问、算子调度、数据格式转换的开销,往往要乘以3到5倍的时间系数。所以在选模型的时候,不能只看FLOPs,还要看模型的算子构成是否对NPU友好。
2.2 算子支持列表决定了模型的上限
RV1106的NPU对算子的支持是有明确边界的。卷积、深度可分离卷积、全连接、池化、BN、ReLU这些基础算子支持得很好,但像Swish、Mish、GELU这类激活函数,以及某些变体的注意力机制,要么不支持,要么需要拆解成多个基础算子来模拟,效率会大幅下降。
这就直接决定了模型选型的范围。MobileNetV2用的是ReLU6,NPU原生支持,转换过程很顺。EfficientNet用的是Swish,虽然工具链能通过算子替换把它转成近似实现,但精度会掉,而且推理速度明显变慢。ShuffleNetV2的通道混洗操作在NPU上需要额外的数据重排,也会增加延迟。ResNet系列因为结构规整、全是标准卷积,反而是NPU最友好的那一类,但参数量和计算量偏大,在RV1106上跑起来比较吃力。
我整理了一个简单的算子友好度对照表,基于实际转换和推理的体验:
| 模型系列 | 核心算子 | NPU友好度 | 转换难度 | 推理延迟(224输入) |
|---|---|---|---|---|
| MobileNetV2 | 深度可分离卷积+ReLU6 | 高 | 低 | 15-20ms |
| MobileNetV3 | 深度可分离卷积+SE+HardSwish | 中 | 中 | 25-35ms |
| ResNet18 | 标准卷积+ReLU | 高 | 低 | 45-60ms |
| ResNet50 | 标准卷积+ReLU | 中 | 低 | 120-180ms |
| ShuffleNetV2 | 深度可分离卷积+通道混洗 | 中低 | 中高 | 30-45ms |
| EfficientNet-B0 | MBConv+Swish+SE | 低 | 高 | 60-90ms |
这个表里的延迟数据是在RV1106默认频率下、单线程推理、输入分辨率224x224的条件下测的,不同板子的内存带宽和散热条件会有差异,但相对关系是稳定的。
2.3 内存带宽是隐藏的瓶颈
RV1106的NPU有自己的一块专用内存区域,但模型权重和中间特征图的数据搬运还是要走系统总线。当模型参数量超过一定阈值,或者中间特征图的通道数太大时,内存带宽就会成为瓶颈。我实测发现,当单层卷积的输入通道数超过256、输出通道数超过512时,推理时间的增长就不是线性的了,会有明显的非线性上升。
这也是为什么MobileNet系列在RV1106上表现最好——它的深度可分离卷积把通道间的计算拆开了,单次计算的数据量小,对带宽的压力也小。而ResNet50这种每层都是大通道标准卷积的模型,带宽压力就很大,延迟直接飙到120毫秒以上,基本没法做实时推理。
3. 从PyTorch到板端:模型转换的完整链路
3.1 工具链的获取和环境的搭建
RV1106的模型转换工具链是官方提供的一套基于Docker的镜像,里面包含了模型解析、量化、编译的完整工具。我建议直接在Ubuntu 20.04的物理机或者虚拟机上装Docker,然后把工具链镜像拉下来跑。不要试图在Windows上折腾,工具链里的很多脚本是bash写的,Windows下的兼容性问题会浪费你大量时间。
环境准备的具体步骤:
- 安装Docker,确保版本在20.10以上,因为工具链镜像用到了一些新的镜像格式特性。
- 从官方渠道获取工具链镜像文件,通常是一个tar包,用
docker load导入。 - 启动容器的时候,把工作目录挂载进去,方便在宿主机和容器之间交换文件。命令大概是这样:
docker run -it --name rv1106_toolchain \ -v /home/yourname/rv1106_workspace:/workspace \ rv1106_toolchain:latest /bin/bash- 进入容器后,先跑一下工具链自带的示例,确认环境没问题。示例通常是一个简单的分类模型,转换完之后可以用模拟器跑推理,验证整个链路是通的。
这里有个坑要注意:工具链镜像里的Python版本和某些依赖库的版本是固定的,不要试图在容器里升级或者安装新的包,很容易把环境搞崩。如果需要额外的Python库来处理模型,在宿主机上装,然后把处理好的模型文件挂载进去。
3.2 模型导出:ONNX是必经之路
不管你的模型是在PyTorch还是TensorFlow里训练的,最终都要先导出成ONNX格式,工具链才能解析。PyTorch导出ONNX的代码很标准:
import torch import torchvision.models as models model = models.mobilenet_v2(pretrained=True) model.eval() dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, "mobilenet_v2.onnx", input_names=["input"], output_names=["output"], opset_version=11 )opset_version建议用11,这是工具链支持得最好的版本。用更高的版本可能会遇到算子解析失败的问题,用更低的版本又可能缺少某些必要的算子定义。
导出之后,强烈建议用ONNX Runtime在PC上先跑一遍推理,确认模型的输出和PyTorch原版是一致的。这一步能帮你排除掉大部分导出阶段的问题,比如某些算子被错误地简化了、输入输出的维度对不上等等。
3.3 量化:精度和速度的平衡点
量化是RV1106部署里最关键的环节。工具链支持INT8量化,能把模型大小压缩到原来的四分之一,推理速度也能提升两到三倍。但量化会带来精度损失,损失多少取决于模型的鲁棒性和量化策略。
工具链的量化流程通常是这样的:先准备一批校准数据,大概100到500张图片,要能覆盖实际场景里的各种情况。然后工具链会用这些数据统计每一层激活值的分布,计算出合适的量化参数。校准数据的质量直接决定了量化后的精度,如果校准数据不能代表实际场景,量化后的模型在实际数据上可能会崩得很厉害。
我一般会准备三类校准数据:正常场景的样本、边界情况的样本(比如光照很暗、目标很小)、以及一些负样本。每类大概100张左右,混在一起做校准。这样量化出来的模型泛化能力会好很多。
量化之后一定要做精度对比。工具链通常会提供一个模拟器,可以在PC上跑量化后的模型,输出和浮点模型的对比结果。我一般会看两个指标:Top-1准确率的下降幅度,以及输出向量的余弦相似度。准确率下降在1%以内、余弦相似度在0.99以上,基本就可以接受。如果下降太多,就要考虑调整量化策略,比如对某些敏感层保持浮点精度,或者增加校准数据的多样性。
3.4 编译和板端部署
量化完成之后,工具链会把模型编译成RV1106 NPU能执行的二进制格式。编译的时候需要指定目标芯片型号和推理模式,RV1106对应的参数在工具链文档里有说明。编译产物通常包括一个模型文件和一个配置文件,配置文件里描述了输入输出的格式和预处理参数。
板端推理的代码结构一般是这样的:初始化NPU上下文、加载模型文件、准备输入数据、执行推理、获取输出结果。官方提供了C语言的API,封装得还算清晰。我一般会写一个简单的C程序来做推理测试,核心逻辑大概是这样:
// 初始化NPU rknn_context ctx; rknn_init(&ctx, model_path, 0, 0, NULL); // 准备输入 rknn_input inputs[1]; inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; inputs[0].size = input_size; 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].want_float = 1; rknn_outputs_get(ctx, 1, outputs, NULL);这段代码看起来简单,但实际部署的时候有几个细节要注意。输入数据的格式要和模型训练时保持一致,包括归一化参数、通道顺序、数据类型。如果训练时用的是RGB、归一化到0到1之间,板端也要做同样的处理。输出数据的解析也要和训练时的后处理逻辑对齐,特别是分类任务,要确认输出的向量顺序和类别标签的对应关系。
4. 不同模型在RV1106上的实测表现
4.1 MobileNetV2:最稳的选择
MobileNetV2是我在RV1106上跑得最顺的模型。转换过程几乎没有遇到算子不支持的问题,量化后的精度损失也很小。在一个100类的小型分类任务上,浮点模型的Top-1准确率是92.3%,量化后是91.8%,只掉了0.5个百分点。推理延迟稳定在15到20毫秒之间,完全能满足实时性要求。
这个模型最大的优势是结构规整,深度可分离卷积和ReLU6都是NPU原生支持的算子,不需要任何额外的转换处理。而且参数量只有3.4M左右,模型文件量化后不到1MB,对存储空间也很友好。
在实际场景里,我用MobileNetV2做了一个工业零件的缺陷分类任务,输入分辨率224x224,三类输出(合格、划痕、变形)。在RV1106上跑,单帧推理18毫秒左右,加上图像采集和预处理的时间,整体处理速度能到30帧以上。精度方面,量化后的模型在测试集上的准确率是94.1%,比浮点模型低了0.7个百分点,完全在可接受范围内。
4.2 MobileNetV3:精度更高但代价不小
MobileNetV3在ImageNet上的表现比V2好,但在RV1106上部署的代价也更大。主要问题出在HardSwish激活函数和SE模块上。HardSwish虽然可以拆解成多个基础算子来模拟,但拆解之后推理速度会明显变慢。SE模块里的全局池化和通道注意力计算,在NPU上也需要额外的数据搬运。
我实测下来,MobileNetV3-Small在RV1106上的推理延迟是25到35毫秒,比V2慢了将近一倍。量化后的精度损失也比V2大,在一个细粒度分类任务上,浮点模型准确率89.5%,量化后掉到了87.2%,损失了2.3个百分点。这个损失幅度在细粒度分类任务上已经比较明显了。
如果你的场景对精度要求很高,而且能接受30毫秒左右的延迟,MobileNetV3可以考虑。但如果对实时性要求更严格,或者任务本身对精度的敏感度不高,MobileNetV2是更务实的选择。
4.3 ResNet18:结构友好但计算量大
ResNet18的结构对NPU非常友好,全是标准卷积和ReLU,转换过程没有任何障碍。量化后的精度损失也很小,基本在0.5个百分点以内。问题在于计算量。ResNet18的FLOPs是MobileNetV2的将近三倍,在RV1106上跑起来延迟明显更高。
我实测的延迟是45到60毫秒,具体取决于输入分辨率和板子的散热情况。如果板子温度上来了,NPU会降频,延迟会进一步增加。在持续推理的场景下,ResNet18的帧率大概在15到20帧之间,做实时视频分析会比较吃力。
不过ResNet18有一个优势:它的特征提取能力比MobileNet系列强,在一些需要细粒度识别的任务上,精度优势明显。如果你的场景不需要高帧率,比如每隔几秒处理一帧,ResNet18是可以考虑的。
4.4 ShuffleNetV2和EfficientNet:不太推荐
ShuffleNetV2的通道混洗操作在NPU上需要额外的数据重排,这个操作在CPU上很轻量,但在NPU上会打断计算流水线,导致推理效率下降。我实测的延迟是30到45毫秒,比MobileNetV2慢,精度又没有明显优势,性价比不高。
EfficientNet-B0的问题更严重。Swish激活函数在NPU上没有原生支持,工具链虽然能通过算子替换来模拟,但模拟出来的结果和原版有偏差,量化后的精度损失也比较大。我实测的延迟是60到90毫秒,精度损失在3个百分点以上。在RV1106这个算力级别的芯片上,EfficientNet的复杂度有点超纲了。
4.5 实测数据汇总
把几个模型的实测数据整理在一起,方便对比:
| 模型 | 浮点准确率 | 量化准确率 | 精度损失 | 推理延迟 | 模型大小(量化后) |
|---|---|---|---|---|---|
| MobileNetV2 | 92.3% | 91.8% | 0.5% | 15-20ms | 0.9MB |
| MobileNetV3-Small | 89.5% | 87.2% | 2.3% | 25-35ms | 1.2MB |
| ResNet18 | 93.1% | 92.6% | 0.5% | 45-60ms | 11MB |
| ShuffleNetV2 | 90.2% | 88.9% | 1.3% | 30-45ms | 1.4MB |
| EfficientNet-B0 | 91.8% | 88.5% | 3.3% | 60-90ms | 2.1MB |
这个表里的准确率数据是在一个自建的100类分类数据集上测的,不同数据集上的绝对数值会有差异,但相对关系基本稳定。
5. 部署过程中踩过的坑和排查思路
5.1 量化后精度暴跌的排查链路
有一次我部署一个自定义的轻量分类模型,量化之后精度从90%多直接掉到了60%多,基本没法用。排查过程大概花了半天时间,最后定位到问题出在量化校准数据的分布上。
我的排查步骤是这样的:先用工具链的模拟器跑量化模型,看每一层的输出和浮点模型的差异。发现前面几层差异很小,但从某一层开始,输出差异突然变大。然后去看那一层的激活值分布,发现校准数据在这一层的激活值范围很窄,而实际测试数据的激活值范围要宽得多。量化参数是根据校准数据算出来的,范围窄了,实际数据一进来就溢出了,精度自然崩了。
解决办法是重新准备校准数据,增加样本的多样性,特别是把那些激活值分布比较极端的样本加进去。重新量化之后,精度恢复到了89%左右,只比浮点模型低了1个多百分点。
这个坑给我的教训是:校准数据不能随便找几张图凑数,一定要能代表实际场景的数据分布。如果实际场景的数据分布很宽,校准数据也要覆盖到这些边界情况。
5.2 输入预处理不一致导致的精度问题
另一个常见的坑是板端预处理和训练时不一致。训练的时候可能用的是RGB通道、归一化到0到1、然后减均值除方差。板端如果忘了做某一步,或者顺序搞错了,精度就会莫名其妙地下降。
我一般会在板端推理代码里加一个调试输出,把预处理后的输入数据的前几个像素值打印出来,和PC上训练时的预处理结果对比。如果对不上,就逐项检查:通道顺序对不对、归一化参数对不对、数据类型对不对。这个检查花不了几分钟,但能避免很多莫名其妙的精度问题。
5.3 NPU内存不足的处理
RV1106的NPU专用内存是有限的,如果模型太大或者中间特征图太多,可能会遇到内存不足的问题。表现是推理的时候报错,或者推理结果异常。
遇到这种情况,有几个处理方向:一是减小输入分辨率,特征图的大小会按平方比例缩小;二是简化模型结构,减少通道数或者层数;三是检查是否有内存泄漏,比如每次推理都重新申请内存但没有释放。
我遇到过一次内存不足的问题,最后发现是推理循环里没有释放输出buffer,跑了几百帧之后内存就满了。加上释放逻辑之后就正常了。
5.4 板端推理的性能调优
如果推理速度不达预期,可以从几个方向调优。首先是输入分辨率,从224降到192或者160,延迟会明显下降,精度损失通常在一个百分点以内。其次是模型剪枝,把一些冗余的通道去掉,参数量和计算量都能降下来。最后是NPU频率,如果散热条件允许,可以适当提高NPU的工作频率,但要注意温度控制。
我一般会先试输入分辨率,这是最简单也最有效的手段。如果降分辨率之后精度还能接受,就不需要动模型结构了。
6. 选型建议和实际场景的匹配
6.1 按场景选模型
不同的应用场景对模型的要求不一样。如果是做实时视频分析,帧率要求高,MobileNetV2是最稳妥的选择。如果是做静态图片分类,对延迟不敏感,可以用ResNet18来换取更高的精度。如果场景特别简单,比如只有两三个类别,甚至可以自己设计一个更轻量的网络,不需要用这些通用模型。
我整理了一个简单的选型参考:
| 场景类型 | 推荐模型 | 理由 |
|---|---|---|
| 实时视频分类(>25fps) | MobileNetV2 | 延迟低,精度够用 |
| 静态图片分类(精度优先) | ResNet18 | 精度高,延迟可接受 |
| 极低功耗常驻任务 | MobileNetV2(降分辨率) | 功耗低,可长期运行 |
| 细粒度分类 | ResNet18或MobileNetV3 | 特征提取能力强 |
| 多类别简单分类 | 自定义轻量网络 | 针对性强,效率高 |
6.2 量化策略的取舍
量化策略没有万能方案,要根据任务特点来定。如果任务对精度很敏感,比如医疗影像分类,可以考虑对最后几层保持浮点精度,只量化前面的特征提取层。如果任务对速度要求高,可以全量化,接受一点精度损失。
校准数据的准备也很关键。我一般会准备至少300张校准图片,覆盖各种光照条件、目标大小、背景复杂度。如果实际场景的数据分布比较集中,校准数据也可以相应集中一些。
6.3 后续扩展的方向
RV1106的NPU虽然算力有限,但通过合理的模型设计和优化,还是能覆盖不少实际需求的。如果后续有更高算力的需求,可以考虑同系列更高端的芯片,工具链和部署流程基本一致,迁移成本很低。
另外,RV1106支持多模型级联,可以先跑一个轻量的检测模型把目标框出来,再跑一个分类模型对目标进行分类。这种级联方式能在有限算力下实现更复杂的任务。
我在实际项目里用MobileNetV2做分类,配合一个简单的人形检测模型,在RV1106上实现了人形检测加属性分类的完整流程,整体帧率能到20帧左右,效果还不错。关键是要把两个模型的计算量控制好,不要让NPU过载。
整体来说,RV1106在边缘图像分类任务上是一个性价比很高的选择,前提是选对模型、做好量化、调好预处理。MobileNetV2是目前最稳的起点,其他模型可以根据具体需求来评估。部署过程中遇到问题,优先排查量化校准数据和预处理一致性,这两个地方出问题的概率最大。