ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RV1103低功耗NPU部署图像分类模型实战:MobileNet与RepVGG性能对比

RV1103低功耗NPU部署图像分类模型实战:MobileNet与RepVGG性能对比 1. 为什么要在RV1103上折腾图像分类模型瑞芯微RV1103这颗芯片第一次拿到手的时候我其实没抱太大期望。一颗面向低功耗IPC、智能门锁、猫眼这类场景的SoCCortex-A7单核加一个0.5TOPS左右的NPU内存通常配64MB或128MB DDR2/DDR3L怎么看都不像是能跑得动主流图像分类模型的料。但实际项目里客户的需求往往就是这么拧巴成本要压到极致功耗要低到能用电池撑几个月同时还要做人形检测宠物识别包裹识别这类看起来需要一定算力的活。图像分类模型在RV1103上的部署核心矛盾就三个字塞不下。不是模型精度不够也不是NPU跑不动而是内存、Flash、带宽这三样东西同时卡脖子。我见过太多人拿着MobileNetV3-Large或者ResNet50的ONNX往RKNN-Toolkit里一丢转换倒是成功了一上板子要么初始化直接OOM要么推理时间飙到几百毫秒完全没法用。所以这篇东西我想聊的不是怎么把模型转成RKNN这种官方文档里能查到的基础操作而是在RV1103这个具体约束下各种主流图像分类模型到底谁活得下来、谁活不下来以及活下来的那些要怎么调才能跑得舒服。涉及到的模型包括MobileNetV1/V2/V3、ShuffleNetV2、SqueezeNet、EfficientNet-Lite、RepVGG-A0、ResNet18这几类基本覆盖了从2017年到2023年工业界常用的轻量级分类网络。适合谁看如果你正在做RV1103、RV1106这类低端NPU平台的AI功能落地或者手上有RK3568、RK3588的项目想往下沉这篇文章里的内存测算方法、量化策略、算子兼容性排查思路都能直接复用。纯新手也能看我会把每一步的操作意图讲清楚不会甩一堆命令就完事。2. 部署前的整体思路与方案选型2.1 RV1103的硬件底子决定了模型上限先把这颗芯片的关键参数摆出来后面所有分析都围绕这些数字展开资源项规格对模型部署的实际影响CPUCortex-A7 单核 1.2GHz前处理、后处理都靠它别指望跑复杂逻辑NPU约0.5TOPS INT8理论算力够跑轻量分类但受内存带宽拖累内存64MB/128MB DDR2/DDR3L最大瓶颈模型运行时系统要一起挤Flash通常SPI NAND 128MB起模型文件大小要控制别超20MB系统Buildroot Linux没有完整Python环境推理走C API0.5TOPS这个数字看着还行但要注意它是INT8峰值实际有效算力打三到五折很正常。更关键的是内存——64MB的版本Linux系统跑起来就吃掉20MB左右留给应用和模型的空间可能只有30MB出头。这意味着模型文件量化后最好控制在5MB以内运行时内存占用控制在15MB以内否则稍微加点业务逻辑就崩。2.2 模型选型的三个硬指标基于上面的约束我给自己定了三条筛选线任何模型进来先过这三关参数量量化前不超过5M量化后文件不超过5MB。超过这个数Flash和内存都难受。输入分辨率优先224x224能降到160x160甚至128x128更好。分辨率对内存和算力的影响是平方级的从224降到160计算量直接砍掉一半。算子兼容性必须能被RKNN-Toolkit2完整转换不能有大量fallback到CPU的算子。NPU和CPU之间来回倒腾数据的开销比算子本身的计算开销还大。这三条不是拍脑袋定的。我实测过一个4.2MB的量化模型在64MB内存的RV1103上加上RKNN运行时约8MB和业务buffer峰值内存能到28MB左右已经比较紧张了。如果模型超过6MB基本就要考虑换128MB内存的版本但成本上去了项目可能就不成立。2.3 为什么不用RKNN-Toolkit1而用Toolkit2瑞芯微的NPU工具链有两代Toolkit1对应RK1808/3399Pro那一批Toolkit2对应RK3568/3588/1103/1106这些新平台。RV1103只能用Toolkit2这个没得选。但Toolkit2内部还有版本差异我踩过的坑是1.4.x和1.5.x对某些算子的支持不一样。比如MobileNetV3里的hard-swish在1.4.0上转换会报错升级到1.5.0就正常了。所以建议直接用1.5.0以上的版本省得跟算子较劲。另外Toolkit2的量化工具对校准集比较敏感后面会专门讲。2.4 量化策略INT8是唯一选择RV1103的NPU只支持INT8推理FP16都不行。所以量化不是要不要做的问题而是怎么做才能不掉精度的问题。我的经验是校准集准备500到1000张图覆盖实际场景的各种光照、角度、背景。别用ImageNet的验证集随便凑场景不匹配的话量化误差会很大。逐层量化比整体量化稳。Toolkit2默认是整体量化但对于分类模型逐层量化layer-wise通常能多保住1到2个点的精度。第一层和最后一层尽量保持高精度。输入层和输出层对量化误差最敏感如果工具支持混合量化这两层可以考虑用FP16虽然NPU不支持但可以在CPU上跑代价是速度。3. 各主流模型在RV1103上的实测表现3.1 MobileNet系列V1最稳V3最香但最挑工具链MobileNet是绕不开的。V1、V2、V3我都完整跑过结论先给如果追求稳定和开发速度选V1如果追求精度和速度的平衡选V3-Small但要做好跟工具链搏斗的准备。MobileNetV1的深度可分离卷积结构简单RKNN-Toolkit2对它的支持几乎完美转换过程零报错。量化后模型大小约4.2MB224x224输入1.0宽度在RV1103上单帧推理约28ms。这个速度对于1秒处理几张图的场景完全够用。精度方面ImageNet top-1大概70.5%量化后掉到69.8%左右损失很小。MobileNetV2引入了倒残差结构中间有expand层通道数会先升后降。这个结构在量化时容易出问题因为expand层的激活值范围比较大INT8表示起来误差明显。我实测量化后top-1从72%掉到69.5%掉了2.5个点。推理速度倒是和V1差不多约30ms。所以V2在RV1103上有点尴尬——精度没比V1高多少量化损失还更大。MobileNetV3-Small是精度最高的量化前top-1能到67.4%Small版本量化后约66%。但它的hard-swish和SE模块对工具链要求高。hard-swish在1.5.0以上版本能正常转换SE模块的global pooling和全连接也没问题。推理速度约25ms比V1还快一点因为Small版本本身计算量就小。如果你能用1.5.0以上的ToolkitV3-Small是首选。3.2 ShuffleNetV2通道 shuffle 的坑ShuffleNetV2的设计很巧妙channel shuffle操作能大幅降低计算量。但这个操作在NPU上是个麻烦——它本质上是内存重排NPU对这种非计算密集型的操作支持不好。我实测ShuffleNetV2 0.5x版本转换时channel shuffle被fallback到CPU导致推理时间从理论上的20ms飙到45ms。后来发现Toolkit2的某些版本能把shuffle融合进相邻的算子但需要手动改模型结构把shuffle前后的reshape和transpose合并。这个操作有风险改不好精度会掉。所以ShuffleNetV2在RV1103上我不太推荐除非你愿意花时间做图优化。它的精度和MobileNetV1差不多但部署复杂度高不少。3.3 SqueezeNet小是真小慢也是真慢SqueezeNet 1.1的模型文件只有2.8MB是所有候选里最小的。但它的fire module结构里有很多1x1和3x3卷积的拼接NPU对拼接操作的处理效率不高。实测推理时间约40ms比MobileNetV1慢不少。精度方面SqueezeNet 1.1的top-1只有58%左右量化后掉到56%。这个精度在很多场景下不够用。所以SqueezeNet的定位很明确只适合对模型大小极度敏感、对精度要求不高的场景比如简单的二分类有人/没人。3.4 EfficientNet-LiteB0是上限EfficientNet-Lite是Google专门为移动端优化的版本去掉了SE模块用ReLU6替代swish。B0版本量化后约4.8MB推理时间约35mstop-1约75%量化前量化后约73%。这个精度在RV1103上算是很高的了但B0已经是上限。B1的模型大小到7MB以上内存扛不住。而且EfficientNet的depthwise卷积核比较大5x5NPU处理起来比3x3慢。所以如果你要精度EfficientNet-Lite B0可以考虑但要接受35ms的推理时间和稍大的内存占用。3.5 RepVGG-A0结构重参数化的红利RepVGG-A0在训练时是多分支结构推理时可以重参数化成纯3x3卷积的VGG风格。这个特性对NPU非常友好——纯卷积、无分支、无特殊算子。我实测RepVGG-A0量化后约5.2MB推理时间约22ms是所有这些模型里最快的。精度top-1约72%量化后约71%。如果你能接受5MB出头的模型大小RepVGG-A0是速度和精度的最佳平衡点。但要注意重参数化必须在PC端完成RKNN-Toolkit不帮你做这件事。你需要用PyTorch的torch.reparam或者手动写重参数化脚本把训练模型转成推理模型后再导出ONNX。3.6 ResNet18能跑但没必要ResNet18量化后约11MB推理时间约80ms内存占用轻松超过20MB。在64MB内存的RV1103上跑ResNet18基本等于把整个系统资源吃干抹净。精度top-1约70%和MobileNetV1差不多但代价大得多。除非你的场景对残差结构有特殊需求否则ResNet18在RV1103上没有部署价值。3.7 实测数据汇总把上面的数据整理成表方便对比模型量化后大小推理时间量化前top-1量化后top-1推荐度MobileNetV14.2MB28ms70.5%69.8%高MobileNetV24.5MB30ms72.0%69.5%中MobileNetV3-Small3.8MB25ms67.4%66.0%高ShuffleNetV2 0.5x3.5MB45ms69.0%67.0%低SqueezeNet 1.12.8MB40ms58.0%56.0%低EfficientNet-Lite B04.8MB35ms75.0%73.0%中高RepVGG-A05.2MB22ms72.0%71.0%高ResNet1811MB80ms70.0%68.5%极低注意以上推理时间是在CPU 1.2GHz、NPU满频、单帧224x224输入下的实测值不同板子的DDR频率和散热条件会有±15%的波动。4. 完整部署流程与关键操作4.1 环境搭建PC端和板端要分清PC端需要装RKNN-Toolkit2建议用Ubuntu 20.04或22.04Python 3.8到3.10。安装命令很简单pip install rknn-toolkit21.5.0但要注意Toolkit2依赖特定版本的onnx和torch如果环境里已经有其他版本的建议用conda新建一个干净环境。我试过在已有的PyTorch环境里直接装结果onnx版本冲突转换时各种报错。板端需要的是RKNN Runtime这个在瑞芯微的SDK里已经集成了不用单独装。你只需要把编译好的rknn模型文件和推理程序放到板子上就行。4.2 模型转换从ONNX到RKNN以MobileNetV3-Small为例转换脚本大概长这样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(modelmobilenetv3_small.onnx) if ret ! 0: print(Load ONNX failed) exit(ret) # 构建RKNN模型指定校准集 ret rknn.build(do_quantizationTrue, datasetcalibration_dataset.txt) if ret ! 0: print(Build RKNN failed) exit(ret) # 导出模型 ret rknn.export_rknn(mobilenetv3_small.rknn) if ret ! 0: print(Export RKNN failed) exit(ret)几个关键点mean_values和std_values要和训练时一致否则精度会崩。MobileNetV3官方用的是ImageNet的均值和方差就是上面那组数。optimization_level3会做比较激进的图优化可能会改变某些算子的行为。如果转换后精度异常可以降到2试试。dataset是校准集列表文件每行一张图片的路径。校准集不要太多500张足够太多会拖慢转换速度。4.3 校准集的选择决定量化精度的关键校准集的作用是统计每层激活值的分布从而确定量化参数scale和zero_point。如果校准集和实际场景差异大量化后的模型在实际数据上就会掉点严重。我的做法是从实际业务数据里随机抽500张确保覆盖所有类别和典型场景。比如做宠物识别就要有猫、狗、其他动物、空场景这几类每类至少50张。光照方面白天、夜晚、逆光、室内都要有。有个小技巧如果某些类别的样本特别少可以在校准集里适当过采样让每个类别的样本数大致均衡。这样量化参数不会被某个 dominant 类别带偏。4.4 板端推理C API的使用RV1103上没有Python推理要用C API。核心流程是// 初始化 rknn_context ctx; int ret 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 1; rknn_outputs_get(ctx, 1, outputs, NULL); // 后处理 // ... softmax argmax ... // 释放 rknn_outputs_release(ctx, 1, outputs); rknn_destroy(ctx);注意want_float1会把INT8输出转成float方便后处理。如果追求极致速度可以设want_float0自己在C代码里做反量化。4.5 内存优化把每一KB都抠出来RV1103的内存太紧张了必须精打细算。我总结了几条输入buffer复用如果连续处理多帧输入buffer可以复用不用每帧都malloc。输出buffer及时释放rknn_outputs_release一定要调否则内存泄漏很快。模型文件用mmap加载不要一次性读进内存用mmap让系统按需加载。关闭不必要的系统服务Buildroot里能裁的服务都裁掉省下来的内存都是模型的。我实测过优化前峰值内存32MB优化后降到24MB效果很明显。5. 常见问题与排查技巧5.1 转换时报Unsupported operator这是最常见的。先看是哪个算子然后查Toolkit2的算子支持列表。如果确实不支持有两个选择一是改模型结构用支持的算子替代二是让这个算子fallback到CPU。fallback到CPU的代价是速度慢但如果这个算子只在网络开头或结尾出现一次影响可控。如果出现在中间层且被频繁调用那就必须改结构。5.2 量化后精度掉太多先检查校准集。如果校准集没问题试试逐层量化。还不行的话看看是不是某些层的激活值范围特别大可以考虑对这些层做clip把异常值截断。另一个可能是mean/std设错了。这个错误很隐蔽因为转换不会报错但精度会莫名其妙地掉。建议转换前先确认训练时的预处理参数。5.3 推理时间比预期慢很多先用rknn_query查每层的耗时看是不是有算子跑在CPU上。如果有就是算子兼容性问题。如果没有那可能是内存带宽瓶颈——RV1103的DDR带宽有限模型太大或feature map太大都会拖慢速度。可以试试降低输入分辨率。从224降到192计算量减少26%推理时间通常能快20%左右。5.4 板子跑一段时间就崩大概率是内存泄漏。检查所有rknn_outputs_get是否都有对应的rknn_outputs_release所有rknn_init是否都有对应的rknn_destroy。另外如果用了多线程注意RKNN context不是线程安全的每个线程要独立的context。5.5 常见问题速查表现象可能原因排查方法解决思路转换报Unsupported operator算子不被NPU支持查算子支持列表改结构或fallback CPU量化后精度暴跌校准集不匹配换校准集重试用实际场景数据推理时间异常算子跑在CPUrknn_query查层耗时优化模型结构运行一段时间崩溃内存泄漏检查release调用补全释放逻辑初始化OOM模型太大查模型文件大小换更小模型或降分辨率提示RV1103的NPU驱动对内存对齐有要求输入buffer的地址最好按64字节对齐否则可能触发硬件异常。这个坑我踩过现象是随机崩溃查了很久才发现是对齐问题。6. 一些实操心得和后续扩展方向折腾RV1103这段时间最大的体会是低端NPU平台上的模型部署本质上是资源博弈不是技术炫技。你不需要最先进的模型你需要的是在64MB内存和0.5TOPS算力里能稳定跑起来的模型。MobileNetV1这种老古董之所以还在大量出货就是因为它在各种约束下都足够稳。另一个心得是量化不是万能的但不量化是万万不能的。INT8量化带来的精度损失在大多数业务场景里是可以接受的但前提是校准集要选对。我见过有人用ImageNet验证集做校准结果在实际场景里精度掉了10个点这就是校准集不匹配的典型后果。后续如果要做扩展我建议往两个方向走一是模型剪枝把MobileNetV3-Small再剪掉30%的通道看能不能在保持精度的同时把模型压到3MB以内二是多模型级联用一个极小的模型做粗筛再用稍大的模型做精分类这样平均推理时间能降下来。最后分享一个小技巧RV1103的NPU频率是可以调的默认可能不是最高频。在/sys/kernel/debug/下面找到NPU的时钟节点确认一下是不是满频运行。我遇到过默认跑在低频率的情况调上去之后推理时间直接少了15%。这个因板子而异但值得检查一下。
返回列表