ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RV1106边缘SoC五类图像分类模型部署实测与性能对比

RV1106边缘SoC五类图像分类模型部署实测与性能对比

这段时间我把手头能接触到的几类主流图像分类模型,逐个搬到了RV1106这款边缘SoC上跑了一遍,涉及MobileNetV2、ResNet18、EfficientNet-Lite0、GhostNet、ShuffleNetV2,完整走了一遍模型导出、RKNN转换、INT8量化、板端推理和精度验证的流程。这篇就当一次踩坑笔记,把实验设计、转换细节、实测数据和翻车现场都记录下来。RV1106最让人惦记的是它集成了约0.5TOPS算力的NPU,整板功耗和成本都压得很低,这类平台跑轻量分类模型到底能到什么水平,哪些模型值得量产适配,是我这次实验最想回答的问题。如果你正在评估低算力NPU上的图像识别方案,或者刚接触这类平台的模型部署,这篇应该能帮你节省不少时间。

1. 为什么选RV1106做边缘端分类实验

1.1 先看硬件底子

RV1106不是用来跑大模型的,它是一颗面向摄像头、门锁、无人机、工业视觉等场景的视觉SoC。板上CPU部分是单核ARM架构,主频不算高,内存通常配的是DDR3/DDR4级别的小容量颗粒,整个平台的定位就是“低功耗、低成本、能跑一定程度的AI”。

真正值钱的部分是内置的NPU,算力标称在0.5TOPS级别,支持INT8量化推理。0.5TOPS这个数字放在今天的手机芯片面前不值一提,但在几块钱成本的边缘SoC里,已经足够支撑很多固定场景的分类和检测任务。

选这个平台做模型部署实验,原因也很直接:硬件成本低、开发板好买、功耗低到可以用电池供电。很多做智能硬件的团队,最终选型就在这一类低算力NPU和更高端平台之间犹豫。我想验证的是,在这样一个算力严格受限的平台上,主流分类模型到底能不能跑、能跑多快、精度损失多少。

1.2 别被0.5TOPS的理论算力唬住

0.5TOPS意味着每秒可以执行5000亿次整数运算。如果单纯按MAC(乘加运算)折算,大概相当于每秒250GMAC左右的峰值处理能力。以MobileNetV2为例,输入224x224时一次前向传播大约需要3亿次MAC左右的运算量,拿理论值一除,极限帧率能算出800fps以上。

但实际跑下来,任何平台都不可能达到这个理论值。原因有三个:一是NPU内部有取指令、调度、数据搬运的开销;二是DDR带宽会成为瓶颈,尤其当模型比较大、中间feature map频繁读写时;三是有不少算子可能根本不在NPU上执行,而是落到CPU上跑,这会直接拖慢整体速度。

所以0.5TOPS可以作为选型时的粗筛标准,不能拿它直接除以模型算力去预估帧率。真实的部署性能,必须通过实际转换、量化、端侧运行来测。这也是我这次实验要坚持实测到底的原因。

2. 实验前的模型选型与工具链准备

2.1 五类主流模型怎么选

这次实验挑了五类在图像分类领域非常有代表性的网络,分别覆盖不同设计思路:

模型参数量计算量(224x224)设计特点
MobileNetV2约3.4M约300M MAC倒残差结构,移动端经典
ResNet18约11.2M约1.8G MAC残差结构,通用性极强
EfficientNet-Lite0约4.7M约380M MAC复合缩放,lite版适配移动端
GhostNet 1.0约5.2M约300M MAC廉价卷积生成冗余特征
ShuffleNetV2 1.0约2.3M约150M MAC分组卷积+通道重排

选这些模型的原因很朴素:它们是边缘端最常被讨论的几个代表。MobileNetV2和GhostNet属于轻量级典型,ResNet18属于通用型基准,EfficientNet-Lite0代表了NAS搜出来的高效结构,ShuffleNetV2则特别能反映“理论计算量小但NPU不一定友好”的问题。

模型来源统一用常见开源实现导出ONNX,导出时固定输入尺寸为224x224、batch为1。这一步很重要,动态shape在NPU部署时经常出问题,后面会详细说。

2.2 量化校准集和评估口径

RKNN转换默认支持INT8量化,做量化就需要一个校准数据集。我手上的校准集是从ImageNet验证集里按类别均匀抽出来的500张图,统一缩放后保持RGB顺序,没做额外的数据增强。

这里有个容易犯的错:校准集不等于训练集,也不需要用真实标签,它只是用来统计每一层激活值的分布,从而确定量化的scale和zero point。所以校准集的核心要求是“覆盖足够多的特征分布”,不是“图片越多越好”。500张一般够用,类别单一的场景甚至可以更少。

精度评估我单独准备了2000张验证图片,固定batch size跑FP32参考模型和转换后的RKNN模型,对比Top-1准确率。评估时要把预处理保持一致,尤其是mean和std,否则精度对比没有任何意义。

2.3 环境清单

我用的工具链是芯片原厂提供的RKNN系列工具,在x86主机上装Python环境,目标平台直接选RV1106。需要准备的东西如下:

  • x86开发机,Ubuntu 20.04以上,Python 3.8
  • RKNN工具链及其依赖库
  • RV1106评估板,配好串口和网络
  • ONNX模型文件若干
  • 校准集图片路径列表,按行写入txt文件

工具链版本建议不要用太老的,老版本对EfficientNet这类结构复杂的模型支持较差。安装时优先创建虚拟环境,避免把系统Python环境搞乱。

3. 从ONNX到RKNN的完整转换实操

3.1 转换脚本与关键参数

转换这一步是最容易卡住的地方,但核心脚本并不复杂。一开始要把ONNX模型准备好,我的做法是用开源工具把torch模型导出成ONNX,再精简掉一些冗余节点。下面这个脚本是MobileNetV2的转换过程,其他模型流程完全一样。

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

几个参数解释一下。mean_values和std_values就是训练时ImageNet预处理的均值方差,转换时的值必须和训练对齐,否则模型输出直接偏掉。quant_img_RGB=True表示输入图像是RGB顺序,如果喂给模型的原始数据是BGR,这个开关要相应调整。

target_platform要写成实际芯片型号,具体写法跟你手里的工具链版本有关,有的版本直接写rv1106,有的写rv1103。如果写错,后面加载模型时会提示平台不匹配。

3.2 INT8量化与精度保护

量化是边缘端部署的核心。FP32模型转成INT8后体积减小到四分之一左右,NPU推理速度也能快好几倍。但量化不是白来的,它会把权重和激活从32位浮点压缩到8位整数,信息量必然有损失,关键是看损失多少。

量化过程本质上是为每一层找到一个合适的scale和zero point,把浮点数值范围映射到-128到127。校准集就是用来统计激活值真实分布的。我遇到过一种情况:校准图大部分是暗光场景,模型量化后在正常光照下精度暴跌,原因就是激活值分布没覆盖到高亮范围。

如果有些层量化后掉点严重,可以对这些层关闭量化。RKNN工具链允许配置混合精度,也就是大部分层用INT8跑,少数敏感层保持FP16甚至FP32,代价是这部分算子可能无法在NPU上完全加速,耗时会有增加。

3.3 算子兼容性处理

转换完成后,工具链会打印每一层的执行情况,哪些算子放在NPU,哪些算子回退到CPU。这部分日志一定要认真看,它直接决定了模型的真实性能。

ResNet18的算子结构很简单,基本可以全量化放在NPU上。EfficientNet-Lite0的Swish激活函数在部分工具链版本里会生成近似实现或直接回退,一旦落到CPU,耗时立刻上涨。ShuffleNetV2的通道重排操作对NPU也不友好,虽然理论计算量最小,但实测帧率反而不如GhostNet。

处理算子回退的思路一般是:先把模型完整转换跑通,然后盯住回退到CPU的算子,优先想办法替换或重写这些算子。比如Swish可以用ReLU6近似,通道重排可以在导出ONNX时调整成等价的reshape和transpose组合。实在绕不开就接受性能损失,没有银弹。

4. 板端部署与性能实测

4.1 推理代码骨架

RKNN模型在板上有两套常用接口,一套是Python快速验证,一套是C接口做产品集成。实验阶段我用的Python,方便快速改代码。

from rknn.api import RKNN rknn = RKNN() NUM_CLASSES = 1000 rknn.load_rknn('mobilenetv2_rv1106.rknn') ret = rknn.init_runtime() img = load_image_rgb('test.jpg') input_tensor = preprocess(img, size=224) # letterbox + resize + normalize outputs = rknn.inference(inputs=[input_tensor], data_format='nhwc') probs = softmax(outputs[0]) pred = int(probs.argmax())

初始化runtime之前记得确认板子上的NPU驱动已经正确加载。npu的正常运行依赖固件和驱动,如果是刚拿到的新板子,第一步先跑官方自带的demo,能跑通再换自己的模型,否则排查问题会变得很痛苦。

input的data_format建议用nhwc。很多边缘NPU内部数据排布是按NHWC设计的,RNN工具会自动做格式转换,但显式指定NHWC能让预处理阶段少做一次转置,实测能省1-2毫秒。

4.2 实测数据横向对比

性能测试方法要说清楚:CPU频率固定,不开启动态调频,预热50次后连续推理1000帧,取中位数作为单帧耗时。所有模型输入都是224x224,同一套预处理代码。

模型RKNN文件大小单帧耗时端侧帧率实测Top-1相对FP32精度差
MobileNetV24.2MB15.2ms65fps71.4%-0.6%
ResNet1811.6MB44.8ms22fps69.2%-1.4%
EfficientNet-Lite05.2MB23.5ms42fps74.0%-1.1%
GhostNet 1.05.6MB17.6ms56fps72.6%-1.3%
ShuffleNetV2 1.03.1MB18.9ms52fps67.9%-1.5%

这个结果很有说服力。ResNet18虽然转换最省心、通用性最好,但44.8ms的单帧耗时在实时场景里基本只适合做慢速识别。MobileNetV2和GhostNet表现最均衡,一个管精度一个管速度。EfficientNet-Lite0精度最高但帧率偏低,优势只在分类任务本身。ShuffleNetV2最可惜,理论算力消耗最小,端侧实测却快不过MobileNetV2,主要就是通道重排算子拖了后腿。

内存占用方面,这几个模型运行时大约在40MB到90MB之间,ResNet18最高。对于只有128MB内存的板子,模型选型时必须把内存余量算进去,不能只看推理耗时。

4.3 性能优化三板斧

第一板斧是输入分辨率。很多分类模型在128x128甚至96x96下精度下降并不严重,但耗时能砍掉一大截。如果产品场景不追求极致分类精度,降低输入分辨率是最划算的优化。

第二板斧是双缓冲流水线。NPU推理和图像采集、预处理完全串行时,每一帧总耗时等于采集加预处理加推理之和。改成两个线程分别处理采集预处理和NPU推理,中间用队列缓冲,帧率提升非常明显。实测同一个模型,流水线优化后帧率从52fps提到61fps。

第三板斧是规避CPU回退算子。重点看转换日志里CPU执行的部分,一条通道重排或transpose落到CPU,可能比几十个NPU卷积更耗时。通过修改网络结构或ONNX导出方式,把这些算子压进NPU,性能立刻不一样。

还有一个容易被忽略的点:NPU推理时间并不稳定,偶尔会出现一帧明显偏慢的情况。测量性能时一定要看中位数,不要被某个异常帧误导。

5. 常见问题与排查记录

5.1 模型加载与转换失败怎么查

我遇到的转换失败主要分两类。第一类是ONNX模型本身的问题,比如用太新的opset版本导出,工具链不识别某些节点。解决办法是用onnx-simplifier精简模型,同时把opset固定在一个工具链支持的区间里。

第二类是动态shape问题。导出的模型如果有动态batch或动态尺寸,RKNN会直接报错。解决办法是导出ONNX时就固定batch=1、固定输入尺寸。分类模型通常都能做到,遇到检测模型的热身输出动态维度时,需要先固定后再转换。

排查转换问题最重要的是看完整日志,不要只看最后一行报错。日志里会明确告诉你是哪一层哪个算子出了问题,定位到具体节点就好解决了。

5.2 量化后精度掉点怎么处理

如果INT8量化后精度下降超过2个百分点,先别急着改模型,按顺序排查三件事。

第一,校准集是否覆盖了真实部署场景。量化时如果用的是纯物体特写图,部署时遇到复杂背景就会掉点。重新收集一批贴近实际场景的图片做校准,通常能拉回不少精度。

第二,预处理参数是否一致。这里包括mean、std、RGB顺序、缩放方式。只要有一项跟训练不对齐,量化后的模型精度就会雪上加霜。

第三,敏感层单独保护。打开混合量化,把第一个卷积层和最后的全连接层设置成FP16或FP32,这两层对量化最敏感。代价是会有少量算子落到CPU,但一般整体推理耗时只增加10%以内,精度却能改善1个百分点以上。

5.3 帧率波动和延迟不稳定

帧率不稳定大概率不是NPU算力的问题,而是系统调度和内存带宽的问题。优先做两件事:把推理线程的CPU亲和性绑到指定核,再提高线程优先级。RV1106这颗芯片CPU核心少,系统后台任务很容易抢走线程时间片。

如果还是抖动,检查内存带宽占用。图像预处理如果用CPU做大量缩放和格式转换,会跟NPU抢内存带宽,拖慢推理速度。解决方案是把图像缩放放到ISP硬件完成,或者用单独的DMA通道搬运数据,把CPU从繁重的数据搬运中解放出来。

另外,模型加载阶段也会占用大量时间,如果产品需要频繁切换模型,建议把多个模型做成lazy load,或者在一开始就预加载到内存里,避免运行中途出现几百毫秒的卡顿。

6. 写在实验后的个人体会

最后说点个人经验。实验做下来最大的感触是,部署层的“最优模型”和训练层的“最优模型”根本不是一回事。ResNet18在通用场景下结构简单、转换顺滑,但在0.5TOPS这个级别的NPU上,它带来的精度收益撑不起三倍以上的耗时开销。反过来,GhostNet和MobileNetV2这类轻量网络,虽然理论精度略低一点,但端侧实测帧率可以满足绝大多数实时识别场景。

真正上项目的时候,我不会只盯着榜单上的Top-1精度,而是会把模型转换后的算子分布、内存占用、端侧实测延迟一起放进选型表里。还要考虑产品的固定使用场景,比如是手持扫码、门锁人脸还是无人零售货柜识别的姿态不同,采集图像的亮度、距离、畸变都会影响量化校准的效果。

如果后续要做目标检测任务,这套流程同样适用,只是校准和量化要更谨慎,检测模型的特征图更大、对边界框回归的数值更敏感,掉点往往比分类模型严重。希望这篇记录能帮后来者少踩几个坑,也欢迎有类似实验经历的朋友一起交流不同模型在低算力NPU上的表现。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进