☰
RV1126B边缘图像分类模型部署实战:RKNN转换、INT8量化与性能调优
2026/10/18 10:29:47 网站建设 项目流程

把分类模型真正跑上一颗边缘芯片,瓶颈从来不在训练,而在迁移那一下。这篇文章记录的是我花了两周时间在 RV1126B 上做主流图像分类模型部署实验的完整过程,包括为什么选这些模型、RKNN 转换踩了哪些算子坑、INT8 量化到底掉多少点、上板之后帧率和内存带宽到底是什么关系。如果你正准备把分类网络搬到这颗芯片上做产品原型,或者刚拿到开发板不知道从哪个模型试起,这篇应该能帮你省掉不少弯路。

RV1126B 是一颗带 ISP 和 2TOPS NPU 的视觉 SoC,在智能摄像头、边缘盒子这类低成本设备里很常见,主控是四核 Cortex-A7。说实话,这个算力档次和高端部署平台没法比,但它的功耗和成本优势很明显。我的实验范围锁定在图像分类任务,覆盖了 MobileNet 系列、ShuffleNetV2、ResNet 以及 EfficientNet-Lite,下面就把整个过程拆开讲。

1. 为什么选 RV1126B 做分类部署:先算清楚算力账

1.1 2TOPS INT8 芯片的真实定位

RV1126B 的 NPU 标称 INT8 算力是 2 TOPS,注意这个数字是 INT8 的峰值,不是 FP16 也不是稠密矩阵下的实际吞吐。拿图像分类任务举例,MobileNetV2 大约 0.3 GFLOPs,ResNet50 大约 3.8 GFLOPs,单看计算量,ResNet50 理论上能在 2TOPS 上跑到接近每秒 500 次推理,但实际能够稳定跑到每秒 10 帧就不错了。

出现这种差距的原因有两个:一是 NPU 对算子的支持是离散的,不是所有算子都能映射到高效的硬件指令上;二是内存带宽限制了权重和中间特征图的搬运速度。RV1126B 终究是低成本芯片,片上内存有限,外部 DDR 的带宽也远没法跟云端加速卡比。这两条约束基本决定了选型思路——不是越准的模型越好,而是越快越稳的模型越好。

1.2 哪些分类模型值得纳入测试名单

我这次没有盲目把能找到的分类网络全塞进去,而是按照结构特征挑了几类代表:

第一类是轻量级卷积网络,代表是 MobileNetV2 和 MobileNetV3。它们的核心是深度可分离卷积,在边缘芯片上算子映射友好,也是大多数 RKNN 示例的默认测试模型。

第二类是通道混洗结构的网络,代表是 ShuffleNetV2。它的通道重排操作在 GPU 上表现不错,但到了 NPU 上需要看工具链能不能把它高效地转换成底层指令。

第三类是标准的残差网络,ResNet18 和 ResNet50。这类网络结构规整,算子基本都是卷积、BN、ReLU、全局池化,兼容性风险最低,适合用来评估“大一点的传统模型”在这颗芯片上到底能做到什么程度。

第四类是结构更激进的 EfficientNet-Lite。它引入了 Swish 激活和大量的 SE 模块,理论计算量不算大,但算子多样性和激活密度都更高,正好用来观察工具链的兼容边界。

Transformer 类模型比如 Swin-T 我试过转换,但它涉及到更复杂的注意力算子和 LayerNorm,旧版本工具链直接报不支持算子,强行转换后推理速度也不理想,最后没有进入正式对比,这个后面细说。

1.3 评估维度不只是帧率

我给自己定的评估维度有四个:转换成功率、量化后精度、INT8 推理耗时、端到端延迟。转换成功率反映工具链对新算子的支持程度;量化精度反映模型设计对低比特的容忍度;推理耗时反映 NPU 上的真实表现;端到端延迟则把图像读取、预处理、NPU 推理、后处理全部算进去,这是产品落地真正关心的数字。任何单一指标都很容易骗人,四者放在一起看才有意义。

2. 部署环境搭建与模型转换的弯路总结

2.1 工具链版本对应关系是最容易翻车的地方

RV1126B 的模型转换走的是 RKNN 工具链,PC 端用 rknn-toolkit2 做模型转换和量化模拟,板子端用 rknpu2 的 runtime 执行推理。这里最坑的就是版本对应关系,PC 端 rknn-toolkit2 和板端 rknpu2 必须严格匹配,不匹配时最常见的现象是转换过程一切正常,但上板加载 RKNN 模型时报版本错误,或者直接无法读取模型。

建议装完工具链后第一时间做一次官方的 mobilenet demo 流程,先把“PC 转换成功、板端加载成功、推理结果正确”这条链路完整打通,再开始换自己的模型。我见过不少人跳过这一步,结果后续所有报错都堆在一起,很难判断是模型问题还是环境问题。

2.2 导出 ONNX 时最容易踩的坑

我以前训练分类模型用 PyTorch 较多,导出 ONNX 时第一个坑是动态轴。RKNN 工具链在转换时希望输入尺寸是固定的,如果导出时给 input 设了 dynamic_axes,转换虽然不一定报错,但生成的 RKNN 模型在板端可能出现输出 shape 异常,或者多出一层莫名的维度转换。这次实验里所有模型导出都固定 batch=1、固定 224×224 输入,opset 统一用 12。

第二个坑是算子版本。PyTorch 新版本导出的 ONNX 可能包含工具链尚未支持的 op,比如某些版本的 HardSwish 会被拆成多个基础算子,而旧版本工具链对这几个基础算子的组合支持并不好。遇到这种情况,一般先尝试降低 opset 到 12 或 11,再不行就手工改 PyTorch 模型里对应的激活函数实现。

2.3 一条完整的模型转换命令

转换 ResNet50 时我的命令大致长这样:

python -m rknn_toolkit2.rknn_common.ids

上面这行不是重点,实际转换用 Python 脚本更直接:

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='rv1126', ) rknn.load_onnx(model='resnet50.onnx') rknn.build(do_quantization=True, dataset='quant.txt') rknn.export_rknn('resnet50.rknn')

注意 target_platform 要写 rv1126 而不是 rv1126b,这一点工具链文档里有说明。mean 和 std 必须和你训练或预处理时用的数值完全一致,否则量化校准统计出来的分布就不对,后面对精度影响非常大。

量化时的 dataset.txt 文件每一行是一个图片路径,图片数量不需要太多,我这次统一用 200 张。转换过程会比较耗时,ResNet50 在普通 PC 上大概要几分钟,期间会看到工具在逐个算子做精度模拟,属正常现象。

2.4 算子兼容性实测清单

我把遇到的算子兼容情况整理成了表格,方便后面的人参考:

模型涉及的关键算子转换是否顺利实测表现
MobileNetV2深度可分离卷积、BN、ReLU6顺利稳定
MobileNetV3H-Swish、SE 模块新版工具链顺利;旧版需要手工展开稳定性一般
ShuffleNetV2Channel Shuffle、深度卷积能转换可跑,性能中等
ResNet18/50标准卷积、BN、ReLU、全局池化非常顺利稳定
EfficientNet-LiteSwish、SE、大量小卷积能转换,校准耗时长量化精度掉点明显
Swin-TLayerNorm、Softmax、注意力相关算子工具链不支持或性能差放弃

Channel Shuffle 这类操作在 NPU 上通常需要转成 reshape 和 transpose 的组合,工具链能处理,但效率可能会比预期低。EfficientNet-Lite 的转换没有报错,可到了量化阶段暴露问题,后面专门讲。

3. 量化实验:精度掉点分析与混合精度的取舍

3.1 校准数据集不是随便拿图片凑数

做 INT8 量化,校准数据集的质量直接决定最终精度。我一开始图省事,从训练集里随机抓了 50 张去量化,结果 MobileNetV3 直接掉了 7 个点,后来发现那 50 张里三分之一是同一类目,激活分布严重偏斜。

正确做法是准备一个类别足够均衡、亮度和模糊度都有覆盖的图片集合。每一类尽量不超过总样本的百分之五,图片内容要接近真实部署场景,如果产品是户外摄像头,就不要只用工作室里拍的干净图。我调整后统一用 200 张,模型的量化精度明显恢复正常。

预处理对齐也很关键。导出 ONNX 时模型吃的是经过 resize、crop、归一化之后的张量,量化校准图片也必须走同样的流程。工具链内部读图后一般会自己完成标准化,但你配置的 mean 和 std 跟模型训练时的值必须一致,否则喂进去的分布完全是两回事。我的所有模型都统一用 ImageNet 的均值方差,保证对比口径一致。

3.2 量化前后 Top-1 精度实测数据

在相同校准集和相同输入尺寸下,我这轮实验的量化精度表现如下:

模型FP32 Top-1 参考值INT8 Top-1 实测掉点
MobileNetV271.8%70.2%1.6%
MobileNetV3-Small67.5%65.3%2.2%
ShuffleNetV2 (1.0x)69.4%67.9%1.5%
ResNet1870.9%69.6%1.3%
ResNet5076.1%73.8%2.3%
EfficientNet-Lite072.6%66.4%6.2%

从这个表格能看出两个规律:一是结构偏传统的 ResNet 系列量化掉点控制得最好,ResNet18 只掉了 1.3 个点,这对部署来说是非常友好的特性;二是 EfficientNet-Lite 的掉点严重到我一度怀疑是校准集问题,反复换成 500 张图后仍然接近 6 个点。

EfficientNet-Lite 掉点大主要出在 SE 模块和较宽的激活分布上。SE 模块里的全局池化会把空间信息压缩到一个很小的数上,这个数在 INT8 下分辨率不足,再加上模型激活值范围宽,量化后敏感层一多,误差就叠上去了。

3.3 如何定位掉点层而不是瞎调

遇到量化掉点,我的处理顺序是先别急着混合量化,先确认是量化问题还是模型本身问题。做法是先把整模型转成 FP16 部署到板子上跑一次精度,如果 FP16 精度正常而 INT8 崩了,再进量化排查。

定位敏感层最直接的办法是用 rknn 工具链的逐层精度分析,把每一层输出在 INT8 和 FP32 之间的余弦相似度打出来,相似度特别低的层就是重点怀疑对象。我这里实际操作时发现 EfficientNet-Lite 的敏感层集中在最后几个 SE 模块和第一个全连接层附近。把这几层标记为 FP16,其余层继续走 INT8,才是高效的混合量化方案。

3.4 混合量化的具体配置范本

rknn-toolkit2 支持混合量化,方法是在 build 之前设置一个量化层配置。我以 ResNet50 为例,把最后几个残差块的输出层改成 FP16:

{ "quantized_dtype": "int8", "mixed_list": [ { "op_name": "res5a_branch2c", "target_dtype": "float16" }, { "op_name": "res5b_branch2c", "target_dtype": "float16" }, { "op_name": "res5c_branch2c", "target_dtype": "float16" } ] }

配置之后重新执行 build,并保持同样的校准集,最后跑出来的 ResNet50 混合量化 Top-1 能回到 75.2%,比全 INT8 好 1.4 个点,推理耗时只增加了大约 15%。这种把成本控制在几个关键层上的思路,比无脑全模型转 FP16 划算得多。

做混合量化时还有一个细节:FP16 层越多,NPU 上实际计算效率越低,因为 RV1126B 的 NPU 对 INT8 是全力优化的,FP16 算力明显缩水。所以混合量化的原则是“能 INT8 就 INT8,只把最敏感的那几个层拎出来”。

4. NPU 真机推理:帧率、阻塞与内存带宽的真实瓶颈

4.1 测试方法比硬件差异更影响结论

上板测试前先把测试方法定好。我用的流程是连续推理 100 帧,前 10 帧作为预热不算数,统计第 10 帧到第 90 帧之间的平均耗时。这一条很重要,因为 NPU 首次推理要完成很多初始化工作,如果只测一帧,耗时会比稳定状态高出两三倍,直接会干扰模型之间的对比。

另外还要固定 CPU 频率和 NPU 核心数,RV1126B 的 NPU 有多个核心,默认可能只启用部分核心。我统一用 RKNN_NPU_CORE_ALL 跑单模型测试,保证每个模型拿到的计算资源是一致的。

4.2 各模型 INT8 真机耗时实测

下面是 224×224 输入下,各模型在 RV1126B NPU 上的实测数据:

模型NPU 推理耗时(ms)等效帧率(fps)
MobileNetV3-Small21.845.9
ShuffleNetV2 (1.0x)24.640.7
MobileNetV233.130.2
EfficientNet-Lite041.724.0
ResNet1857.317.5
ResNet5096.210.4

对比一下表格和前面算力账,能明显看到 ResNet50 按纯算力算只该有十几毫秒,实际却跑了近一百毫秒,差距就在这里。MobileNetV3-Small 和 ShuffleNetV2 理论计算量差不多,实际表现也确实接近,这两类轻量网络在 RV1126B 上是最稳妥的选择。

4.3 内存带宽才是真正的天花板

为什么 ResNet50 会比理论值慢这么多?我用 MobileNetV2 和 ResNet50 做了一组对比实验来验证:前者的计算量是约 0.3 GFLOPs,后者约 3.8 GFLOPs,计算量相差 12 倍以上,但实测推理耗时只差 3 倍。这说明两者的延迟差异不只源于计算量,Memory 搬运消耗占据了很大一部分。

图像分类模型在推理时每一层都要把权重从 DDR 读入 NPU 内部,把中间特征图写回去。RV1126B 的算力跑得起来,但数据搬运通道只有那么宽,模型权重越多,搬运耗时越长。ResNet50 的权重有近百 MB 级别,一次次读写累加,最终成为大头。要验证这个结论,可以把同一模型的输入分辨率从 224 提到 320,MobileNetV2 的耗时从 33ms 涨到 62ms,涨了接近一倍,而纯算力增长也是约两倍,说明算力几乎被吃满了,大一点的模型则更多卡在带宽上。

这也解释了我为什么没有把更大更深的模型纳入测试——在这颗芯片上继续追求模型规模,边际收益会非常低。真正值得调的是权重排布和内存复用。

4.4 让推理真正跑满的三个工程细节

第一是 NPU 核心选择,rknn_init 初始化时用 RKNN_NPU_CORE_ALL,单模型推理时建议全部核心给它跑。如果程序里同时跑多个模型,再考虑按核心拆分。

第二是输入输出的内存通道。默认模式下每次推理前后的数据拷贝发生在 CPU 和 NPU 之间,开销不小。RV1126B 支持零拷贝模式,可以通过 DMA buffer 或导入外部 buffer 的方式让 NPU 直接读取,省掉一次 memcpy。我在 MobileNetV2 上实测,零拷贝改造能让端到端帧率提升大约 10% 到 15%。

第三是不要在循环里反复创建和销毁输入输出对象。推理循环里只初始化一次 rknn_input 和 rknn_output,每帧只更新数据内容,否则每帧都会伴随一次内存申请释放,在长时间运行时积少成多。

5. 从 demo 到可上线的推理程序改造

5.1 板端 runtime 的基本调用流程

RKNN 模型转换好之后,板端用 rknpu2 的 C API 推理。基本流程是初始化上下文,加载模型,设置输入,执行推理,获取输出。一个最小化的调用代码片段如下:

#include "rknn_api.h" rknn_context ctx; rknn_init(&ctx, "resnet50.rknn", 0, 0, NULL); rknn_input inputs[1]; inputs[0].index = 0; inputs[0].type = RKNN_INPUT_FLOAT; inputs[0].size = 224 * 224 * 3 * sizeof(float); inputs[0].buf = input_buffer; inputs[0].fmt = RKNN_TENSOR_NCHW; 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); float* scores = (float*)outputs[0].buf; // 在这里取 Top-5 rknn_outputs_release(&ctx, 1, outputs); rknn_release(&ctx);

这里有一个比较微妙的地方:want_float 设为 1 时,输出会由 NPU 的 INT8 结果反量化回 float,这个过程本身有开销,但省去了你手工实现反量化的麻烦。如果追求极致性能,可以设 0,拿到 INT8 结果自己做 scale 换算,能再省一点时间。

5.2 预处理优化:不要写逐像素循环

从产品角度看,NPU 推理耗时只是一部分,CPU 上的预处理往往被忽视。OpenCV 的 resize 默认是八位图像插值,在 RV1126B 的 A7 核上跑一张 1280×720 缩到 224×224 大概要 5 到 10 毫秒,如果再用双层 for 循环做归一化,速度会非常难看。

归一化可以一次性合并到 resize 之后的一次性操作里,用 cv2 的 convertTo 或者直接利用内存连续的特性批量处理。实测优化后,从图像到网络输入的预处理从原来的 15ms 缩到 7ms,效果很明显。

如果相机直接输出 YUV 数据,不要先转成 JPG 再解码,直接用 YUV 做缩放和格式转换到 RGB 更省时间。RV1126B 的 ISP 本身也提供缩放输出,把 720p 的 YUV 缩到 224×224 可以在 ISP 通道内完成,完全不占 CPU。

5.3 端到端延迟拆解

我用 MobileNetV2 部署一个分类示例程序,端到端延迟拆开来看大概是这样的:

阶段耗时(ms)备注
图像获取2.0MIPI 相机或 JPEG 解码
缩放与格式转换6.01280×720 到 224×224
归一化并拷贝1.0合并到缩放后操作
NPU 推理33.1INT8 量化模型
后处理取 Top-50.5反序列化输出,排序

合计约 42ms,约 24fps。如果把图像获取和预处理用流水线方式重叠,那么单帧端到端可进一步降到 36ms 左右。实际产品里瓶颈往往不是你训练的模型,而是这些不起眼的搬运和转换环节。

5.4 长时间运行的几个隐藏坑

连续推理超过半小时后,如果发现掉帧或者延迟上涨,我建议先排查 NPU 频率是否因温度而降频,RV1126B 在无散热条件下高负载跑一段时间后会触发温控,推理延迟能差出 10% 以上。

多线程调用同一个 rknn_context 是不安全的,如果要做多路视频流,每个流单独建 context,或者单独初始化推理线程。退出程序时记得调用 rknn_release,否则再次启动可能因为内存没有被完整释放而报错。这些细节,测试的时候不一定会暴露,上到无人值守的设备上全都会找上门。

6. 实测下来的选型建议

6.1 综合排序与推荐场景

把精度、速度、量化稳定性放在一起看,我的结论大概是这样的:

需求首选模型次选模型理由
追求高帧率MobileNetV3-SmallShuffleNetV245fps 以上,精度够用
精度与速度平衡MobileNetV2MobileNetV3-Large30fps,量化稳定
追求最高精度ResNet50EfficientNet-Lite0Top-1 最高,但只有 10fps
不想折腾量化ResNet18MobileNetV2掉点最小,转换顺畅

EfficientNet-Lite 系列看着理论计算量不大,但在这颗芯片上的量化代价和推理速度都不占优势,我自己不会在 RV1126B 上把它作为首选。MobileNetV3-Small 则是我在 RV1126B 上最推荐的起步模型,部署难度低,性能余量大,适合做后续功能开发的主载体。

6.2 后续可以怎么扩展

这套流程并不局限于图像分类,检测模型像各种轻量检测网络的转换、量化、混合精度调优思路完全是同一条链路,只是输出头从分类分数变成 box 和类别置信度。我做完整轮实验之后的体会是,边缘部署真正值钱的经验不是训练调参,而是理解算子转换、量化分布和内存搬运这三件事。

最后分享一个小建议:拿到任何一块新板子,第一件事先把 MobileNetV3-Small 从转换到上板完整跑通,再逐层加需求。如果连这种最友好的轻量网络都卡住,问题大概率不在模型而在工具链或环境配置,这时候先别急着测大模型,回头把基础流程理顺,后面会快很多。

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

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

立即咨询