1. 为什么要在RV1126B上折腾图像分类模型
边缘端跑图像分类,这两年从"能不能跑"变成了"怎么跑得划算"。瑞芯微RV1126B这颗芯片在安防、工业质检、智能零售这些场景里出镜率很高,它自带NPU,标称算力对付ResNet、MobileNet这一档的分类网络绰绰有余。但"绰绰有余"是纸面参数,真把各种主流分类模型往上搬的时候,你会发现事情没那么简单——有的模型转换完精度掉得离谱,有的推理速度远低于预期,还有的干脆在量化阶段就报错退出。
我最近集中做了一轮实验,把常见的几类图像分类模型在RV1126B上挨个部署了一遍,从模型转换、量化、精度对齐到板端实测推理,全流程走通并记录了数据。这篇内容就是把这轮实验的完整过程、踩过的坑和最后的选型建议摊开来讲。适合正在做边缘AI落地、手里有RV1126B或者同类NPU平台、需要给分类任务选模型的开发者参考。不管你是刚接触RKNN工具链的新手,还是已经跑通过几个模型想横向对比的老手,应该都能从里面找到有用的东西。
先说清楚一个前提:RV1126B的NPU对算子是有选择性的,不是所有PyTorch里能跑的模型都能顺利转过去。所以整个实验的核心矛盾就一句话——在精度、速度、模型体积这三者之间,针对RV1126B的实际算子支持情况找平衡点。下面我按实验的实际推进顺序来展开。
2. 实验环境与工具链的搭建细节
2.1 硬件与软件版本清单
环境这块必须先交代清楚,因为RKNN工具链的版本兼容性是个大坑,不同版本之间API和算子支持差异明显。我这次用的配置如下:
| 项目 | 版本/型号 | 说明 |
|---|---|---|
| 目标芯片 | RV1126B | NPU核心,支持INT8量化推理 |
| 开发主机 | x86_64 Ubuntu 20.04 | 用于模型转换和量化 |
| RKNN-Toolkit2 | 1.6.0 | PC端转换工具 |
| RKNPU2 Runtime | 对应1.6.0 | 板端推理库 |
| PyTorch | 1.13.1 | 模型导出 |
| ONNX | 1.14.0 | 中间格式 |
| Python | 3.8 | 工具链依赖 |
这里有个经验:RKNN-Toolkit2的版本和板端runtime版本必须匹配,我一开始PC端用了1.6.0,板端固件里带的是1.5.x的runtime,结果模型加载直接失败,报的是版本不兼容。后来把板端固件刷到对应版本才通。这个坑很隐蔽,因为转换阶段一切正常,只有上板才暴露。
2.2 模型转换链路的选择逻辑
从训练框架到RV1126B,中间要经过格式转换。常见的链路有两条:
- PyTorch → ONNX → RKNN:最通用,绝大多数模型走这条
- PyTorch → RKNN(直接转换):工具链支持有限,容易出问题
我全程走的是ONNX中转。原因是ONNX作为中间表示,算子表达相对规范,而且RKNN-Toolkit2对ONNX的解析最成熟。直接转的话,遇到自定义算子或者动态shape很容易卡住。
导出ONNX的时候有几个参数必须注意。以torch.onnx.export为例:
torch.onnx.export( model, dummy_input, "model.onnx", opset_version=11, # 建议11或12,太高RKNN可能不支持 input_names=["input"], output_names=["output"], do_constant_folding=True, dynamic_axes=None # 固定shape,边缘端不需要动态 )opset_version我试过13和11,13在部分模型上会出现RKNN无法识别的算子,退回11就正常了。dynamic_axes一定要设成None,固定输入尺寸,因为NPU推理是静态图,动态shape会带来一堆麻烦。
2.3 量化校准集的准备
INT8量化是RV1126B发挥性能的关键,不量化的话FP16推理速度会慢一大截。量化需要校准集,这一步直接决定精度损失大小。
我的做法是从训练集里随机抽300到500张图做校准,覆盖所有类别。数量不用太多,但分布一定要均匀,如果校准集里某个类别的样本特别少,量化后那个类别的精度会明显偏低。我第一轮实验偷懒只用了100张,结果量化后整体精度掉了6个点,补到400张之后掉点控制在1.5个点以内。
校准集的预处理要和推理时完全一致——同样的resize、同样的归一化参数。这点很容易被忽略,有人校准用一套预处理,推理用另一套,量化出来的scale就偏了。
3. 主流分类模型的逐个部署实录
3.1 MobileNetV2:最省心的入门选择
MobileNetV2是我第一个跑通的模型,也是整个实验里最顺利的。它的深度可分离卷积结构对NPU非常友好,RKNN工具链原生支持,转换过程零报错。
转换脚本核心部分:
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_platform='rv1126b', quantized_dtype='asymmetric_quantized-8' ) rknn.load_onnx(model='mobilenetv2.onnx') rknn.build(do_quantization=True, dataset='calib.txt') rknn.export_rknn('mobilenetv2.rknn')mean和std这里用的是ImageNet的标准值,注意RKNN的config里mean_values是0-255量级的,不是0-1,这个和很多训练框架的习惯不一样,填错了精度会崩。
实测结果:输入224x224,INT8量化后模型体积约3.5MB,板端单帧推理耗时约8ms,Top-1精度相比原模型掉约1.2个点。这个表现对于大多数分类任务完全够用。
3.2 ResNet50:精度高但代价明显
ResNet50的精度确实好,但在RV1126B上跑起来就没那么舒服了。主要问题出在残差连接和部分卷积算子的融合上。
转换阶段倒是没报错,但量化后精度掉了将近4个点,这个幅度有点大。我排查了一圈,发现是残差分支的量化误差累积导致的。解决办法是开启混合量化,把敏感层保留为FP16:
rknn.config( ..., quantized_dtype='asymmetric_quantized-8', quantized_algorithm='normal', quantized_method='channel' # 按通道量化,比按层量化精度好 )把quantized_method从layer改成channel之后,精度损失降到2个点出头。代价是模型体积稍微大了一点,但可接受。
速度方面,ResNet50在RV1126B上单帧约35ms,比MobileNetV2慢了四倍多。模型体积量化后约25MB。所以ResNet50适合那种对精度要求高、但对实时性要求不苛刻的场景,比如离线质检。
3.3 EfficientNet系列:算子兼容性的重灾区
EfficientNet-B0是我这次实验里最折腾的一个。它的MBConv结构里用了SE模块(Squeeze-and-Excitation),涉及全局池化和一些动态操作,RKNN对这类算子的支持不完整。
第一次转换直接失败,报的是某个Gather算子不支持。我尝试了几种方案:
- 方案一:把SE模块替换成普通卷积,精度掉了3个点,但能跑通
- 方案二:升级工具链版本,部分算子在新版本里被支持了
- 方案三:换用EfficientNet-Lite版本,这个版本专门为边缘设备优化过,去掉了SE模块
最后我选了方案三,EfficientNet-Lite0转换顺利,量化后精度掉约1.8个点,单帧约12ms,模型体积约5MB。这个结果比魔改原版EfficientNet要划算得多。
这里的心得是:遇到算子不支持的模型,优先找官方或社区提供的边缘优化版本,而不是硬改网络结构。硬改往往牵一发动全身,精度和速度都不可控。
3.4 ShuffleNetV2与GhostNet:轻量化的两个方向
ShuffleNetV2的核心是通道混洗(channel shuffle),这个操作在RKNN里是通过reshape和transpose组合实现的,转换能过但会引入额外的内存搬运开销。实测单帧约10ms,精度掉约1.5个点,模型体积约2.8MB。
GhostNet用廉价操作生成冗余特征图,理论计算量很低。但它的Ghost模块里有一些分组卷积的变体,RKNN支持得一般。转换能过,量化后精度掉约2.2个点,单帧约9ms,模型体积约3MB。
这两个模型的表现介于MobileNetV2和EfficientNet-Lite之间,属于"能用但没有特别突出优势"的档位。如果MobileNetV2已经满足需求,没必要特意换这两个。
3.5 Vision Transformer类:现阶段不建议上
我抱着试试看的心态跑了一个小型的ViT变体。结论很直接:不推荐在RV1126B上部署Transformer类分类模型。
原因有几个:一是自注意力机制里的矩阵运算和softmax,RKNN支持得很勉强,转换过程各种报错;二是即使转换成功,注意力机制的计算模式对NPU的并行度利用很差,实测速度慢得离谱;三是量化后精度损失巨大,因为注意力权重对量化非常敏感。
所以这类模型现阶段还是留给GPU或者更高算力的NPU平台,RV1126B上老老实实用CNN。
4. 量化精度对齐的排查方法论
4.1 精度掉点的定位思路
量化后精度掉点是必然的,关键是搞清楚掉在哪里、能不能救。我的排查流程是这样的:
第一步,先在PC端用RKNN的仿真模式跑一遍量化后的模型,对比原模型在验证集上的逐样本输出。找出哪些样本的预测结果变了。
第二步,对这些"变脸"的样本做可视化分析,看它们的共同特征。我遇到过的情况是,量化后对低对比度图像的识别明显变差,这说明量化把某些细微的特征差异抹平了。
第三步,定位到具体的层。RKNN提供了逐层量化的精度分析工具,可以看每一层量化前后的输出差异。差异大的层就是敏感层。
第四步,对敏感层做处理——要么保留FP16,要么调整量化参数。
4.2 混合量化的实操配置
混合量化是救精度的主要手段。RKNN支持通过配置文件指定哪些层不量化:
rknn.config( ..., quantized_dtype='asymmetric_quantized-8', quantized_algorithm='normal', quantized_method='channel' ) # 在build时通过hybrid_quantization指定 rknn.build( do_quantization=True, dataset='calib.txt', rknn_batch_size=1 )实际操作中,我一般先把所有层都量化,看精度掉多少。如果掉点在2个点以内,基本可以接受,不动它。如果超过3个点,就把最后几层(通常是分类头附近的层)改成FP16,因为这些层对最终输出影响最直接。
注意:混合量化会让模型体积增大,因为FP16的层占的空间是INT8的两倍。所以不要一上来就大面积混合量化,要精准打击。
4.3 校准集质量对精度的影响
前面提过校准集数量的问题,这里再补充一个维度:校准集的难度分布。
如果你的校准集全是清晰、正对、光照良好的图片,那量化参数会偏向这种"简单"分布,遇到实际场景里的模糊、倾斜、暗光图片时精度就会崩。我的做法是在校准集里刻意混入一些困难样本——模糊的、遮挡的、光照不均的,占比大概20%左右。这样量化出来的模型鲁棒性明显更好。
这个技巧在工业质检场景特别有用,因为产线上的图片质量参差不齐,校准集如果太"干净",上线后精度会打脸。
5. 板端实测:速度、内存与功耗的真实数据
5.1 推理速度的测量方法
板端测速不能只看一次推理的时间,要跑多次取稳定值。我的做法是预热10次,然后连续跑100次,取平均和中位数。
测量代码大致这样:
import time # 预热 for _ in range(10): rknn.inference(inputs=[img]) # 正式测量 times = [] for _ in range(100): start = time.perf_counter() rknn.inference(inputs=[img]) times.append(time.perf_counter() - start) print("avg:", sum(times)/len(times)*1000, "ms") print("median:", sorted(times)[50]*1000, "ms")注意要排除第一次推理的初始化开销,那次通常会慢很多。
5.2 各模型实测数据汇总
把这次实验的主要数据整理成表,方便横向对比:
| 模型 | 量化后体积 | 单帧耗时 | 精度损失 | 算子兼容性 |
|---|---|---|---|---|
| MobileNetV2 | 3.5MB | 8ms | 1.2% | 优秀 |
| MobileNetV3-Small | 2.5MB | 6ms | 1.5% | 优秀 |
| ResNet50 | 25MB | 35ms | 2.1% | 良好 |
| EfficientNet-Lite0 | 5MB | 12ms | 1.8% | 良好 |
| ShuffleNetV2 | 2.8MB | 10ms | 1.5% | 中等 |
| GhostNet | 3MB | 9ms | 2.2% | 中等 |
| ViT变体 | - | - | - | 差 |
从这张表能看出几个规律:模型体积和耗时基本正相关,但不是线性的;精度损失和模型结构的复杂度有关,结构越"花哨"的模型量化越吃亏。
5.3 内存占用与多模型并存的考量
RV1126B的内存不算宽裕,如果要在同一块板上跑多个模型(比如一个检测加一个分类),内存就得精打细算。
单个模型推理时的峰值内存占用,MobileNetV2大约在30MB左右,ResNet50要到80MB以上。这个数字包括模型权重、中间特征图和输入输出缓冲。如果内存吃紧,优先选轻量模型,或者把多个模型分时复用。
我试过在RV1126B上同时加载MobileNetV2和一个轻量检测模型,内存是够的,但两个模型交替推理时会有上下文切换开销,整体吞吐比单独跑要低一些。所以如果业务允许,尽量把多个任务合并到一个模型里,或者用流水线方式错开推理。
6. 模型选型的决策框架与踩坑清单
6.1 按场景选模型的决策树
经过这轮实验,我总结了一个简单的选型逻辑:
如果你的场景对实时性要求极高(比如30fps以上),选MobileNetV3-Small或MobileNetV2,这两个是速度和精度的最佳平衡点。
如果精度优先、实时性可以放宽到10fps左右,选EfficientNet-Lite0,它在精度上比MobileNet系列有明显优势,速度也还能接受。
如果任务本身很简单(比如只有几个类别、类间差异大),ShuffleNetV2或GhostNet也够用,体积还更小。
ResNet50除非你有特殊理由,否则在RV1126B上不太推荐,性价比不高。
6.2 转换阶段的常见报错与处理
把这次遇到的报错整理一下,方便对照排查:
| 报错信息关键词 | 可能原因 | 处理方式 |
|---|---|---|
| Unsupported op: Gather | 模型含动态索引操作 | 换边缘优化版本或改结构 |
| Quantize failed | 校准集格式或数量问题 | 检查校准集预处理,增加数量 |
| Version mismatch | 工具链与runtime版本不一致 | 统一版本 |
| Shape inference error | 输入shape不固定 | 导出ONNX时固定shape |
| Out of memory | 模型太大或batch过大 | 减小batch,换轻量模型 |
6.3 几个容易忽略的实操细节
第一个细节:输入图像的预处理要在CPU上做,不要在模型里做。有些人喜欢把归一化甚至resize塞进模型,这样转换出来的模型会包含这些算子,NPU处理这些非卷积算子效率很低。正确做法是预处理在CPU端完成,模型只负责纯推理。
第二个细节:batch size设成1。边缘端推理基本都是单帧,设大了浪费内存还不会提速。RKNN的build参数里rknn_batch_size保持1。
第三个细节:量化后的模型要在真实数据上验证,不能只看验证集。验证集和实际场景的数据分布往往有差异,我遇到过验证集精度很好但上线后拉胯的情况,后来发现是实际场景的光照条件和验证集差太多。所以有条件的话,拿一批真实场景的数据做最终验证。
第四个细节:注意模型的输出层。有些模型的最后一层是softmax,量化后softmax的输出分布会变形。建议导出ONNX时把softmax去掉,让模型输出logits,softmax在CPU端做。这样量化对最终结果的影响更小。
7. 我在这轮实验里最想分享的几点体会
跑完这一整轮,最大的感受是:边缘端部署的功夫,一半在模型转换和量化上,一半在对芯片特性的理解上。很多人以为模型训练好了就万事大吉,实际上从训练完成到板端稳定运行,中间的工作量可能比训练还大。
MobileNetV2这类"老实"的模型之所以在RV1126B上表现最好,不是因为它们多先进,而是因为它们的算子结构规整、对量化友好。这提醒我们,做边缘部署选模型的时候,不能只看论文里的精度和FLOPs,还要看它的算子构成是否"NPU友好"。
另外,量化校准集的重要性怎么强调都不过分。我见过太多人随便抽几十张图做校准,然后抱怨精度掉得多。花点时间把校准集做扎实,比事后各种补救都管用。
最后说一个我自己的习惯:每部署一个新模型,我都会先在PC端仿真跑通、精度对齐之后,再上板。上板之后先测速度,再测精度,最后做长时间稳定性测试。这个流程看起来慢,但能避免很多返工。尤其是稳定性测试,跑个几小时看有没有内存泄漏或者精度漂移,这一步很多人省了,结果上线后出问题。
RV1126B这颗芯片的潜力是够的,关键看你怎么用。选对模型、做好量化、注意细节,它完全能撑起大多数边缘分类任务。