news 2026/10/28 1:08:59

RV1126B边缘NPU实战:8大图像分类模型RKNN部署与量化对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RV1126B边缘NPU实战:8大图像分类模型RKNN部署与量化对比

1. 为什么要在 RV1126B 上折腾图像分类模型

瑞芯微 RV1126B 这颗芯片在边缘视觉圈子里热度一直不低。它属于 RV11 系列,内置独立 NPU,主打低功耗、小体积、带 ISP 和视频编解码能力的视觉 SoC,常见于 IPC、门禁面板、智能门锁、工业质检相机这类产品。很多做算法的朋友拿到板子第一件事,就是想把手里训好的图像分类模型塞进去跑一跑,看看帧率、精度、功耗到底能不能满足产品定义。

但真上手就会发现,从 PyTorch 的.pth到能在板子上跑起来的.rknn,中间隔着一整套工具链、量化策略和算子适配的坑。更麻烦的是,图像分类模型家族太庞杂了——ResNet、MobileNet、EfficientNet、ViT、ConvNeXt、ShuffleNet、RepVGG……每个网络的结构特点不同,对 NPU 的算子支持、量化友好度、内存占用都不一样。网上关于 RK3588 部署 YOLOv8 的资料一抓一大把,但专门针对 RV1126B 做图像分类模型横向对比的实验记录却少得可怜。

这篇内容就是把我这段时间在 RV1126B 上跑各类主流图像分类模型的完整实验过程整理出来。包括工具链版本选择、模型转换流程、量化校准集怎么准备、每个模型的实测帧率和精度损失、踩过的算子不支持问题,以及最终给出的一套选型建议。适合正在做边缘视觉产品选型、或者手里已经有 RV1126B 板子想跑分类任务的工程师参考。不管你是刚接触 RKNN 工具链的新手,还是已经部署过检测模型想扩展到分类的老手,应该都能从里面找到有用的东西。

2. 实验环境与工具链搭建

2.1 硬件与软件版本锁定

做嵌入式部署实验,第一原则就是版本锁定。RKNN 工具链的版本兼容性出了名的敏感,1.4 和 1.5 之间某些算子的行为都可能不一样。我这次实验用的环境如下:

项目版本/型号说明
目标芯片RV1126B内置 NPU,算力约 2TOPS(INT8)
开发板官方 EVB4GB DDR,带 MIPI 摄像头接口
转换主机Ubuntu 20.04 x86_64建议用物理机或完整虚拟机
Python3.8RKNN-Toolkit2 对 3.9+ 支持不稳
RKNN-Toolkit21.6.0注意不是 Toolkit1,RV1126B 用 Toolkit2
RKNPU2 Runtime1.6.0板端推理库,需与 Toolkit 版本对齐
交叉编译工具gcc-arm-linux-gnueabihf板端 C 程序编译用

这里要特别强调一点:RV1126B 和老的 RV1126 在工具链上是有区别的。RV1126 早期用的是 RKNN-Toolkit1,而 RV1126B 需要走 RKNN-Toolkit2 的路线。如果你照着老教程装 Toolkit1,转换阶段就会直接报 target 不支持。我一开始就踩了这个坑,折腾了半天才发现是工具链代际问题。

提示:安装 RKNN-Toolkit2 时,建议用 conda 建一个干净的 Python 3.8 环境,不要和系统 Python 混用。它依赖的 numpy、onnx、torch 版本都比较挑,混装很容易出现undefined symbol之类的报错。

2.2 板端运行环境准备

板端需要部署 RKNPU2 的 runtime 库。官方固件里通常已经带了librknnrt.so,但版本可能和你的 Toolkit 不匹配。我的做法是直接从 Toolkit2 的发布包里找到对应版本的runtime/RKNPU2/Linux/librknn_api/armhf-uclibc或者armhf-glibc目录,把librknnrt.so推到板子的/usr/lib/下替换。

判断板子用的是 uclibc 还是 glibc,可以用ldd --version看,或者直接看固件里已有的库。RV1126B 官方 Buildroot 固件一般是 uclibc,如果你自己用 Debian 根文件系统则是 glibc。选错了库,运行时会报cannot find librknnrt.so或者更隐蔽的段错误。

另外,板端跑推理建议把 CPU 调频策略设为 performance,避免降频导致帧率波动:

echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

NPU 的时钟也可以查一下,cat /sys/kernel/debug/rknpu/clock之类的节点能看到当前频率。有些固件默认 NPU 频率不是最高档,手动拉满能提升 10% 到 20% 的推理速度。

2.3 模型转换的整体流程

整个链路可以概括成四步:训练框架导出 ONNX,ONNX 转 RKNN,量化校准,板端推理验证。听起来简单,但每一步都有细节。

第一步导出 ONNX 时,要注意 opset 版本。RKNN-Toolkit2 1.6.0 对 opset 11 到 13 支持最好,opset 17 以上有些算子会解析失败。我一般统一用 opset 12 导出。

第二步转 RKNN 时,config里的mean_values和std_values要和训练时的预处理对齐。很多人精度掉得厉害,就是因为这里没对齐——训练时用的是 ImageNet 的 mean/std,转换时却填了 0 到 1 的归一化,那结果肯定崩。

第三步量化校准是重头戏。RV1126B 的 NPU 主要跑 INT8,量化校准集的质量直接决定精度损失。我后面会专门讲怎么选校准集。

第四步板端验证,除了看输出对不对,还要用rknn.query看每层的耗时分布,定位瓶颈层。

3. 参评模型选型与结构特点

3.1 参评模型清单

这次实验我选了 8 个有代表性的图像分类模型,覆盖了从轻量到中等规模、从 CNN 到 Transformer 的不同架构路线:

模型参数量输入尺寸架构特点典型场景
MobileNetV23.4M224x224深度可分离卷积移动端首选
MobileNetV3-Small2.5M224x224NAS + SE 模块极致轻量
ShuffleNetV2 1.0x2.3M224x224通道混洗低算力设备
ResNet1811.7M224x224残差连接经典基线
ResNet5025.6M224x224瓶颈残差精度优先
EfficientNet-B05.3M224x224复合缩放精度效率平衡
RepVGG-A08.3M224x224重参数化训练推理解耦
ViT-Tiny5.7M224x224自注意力Transformer 路线

选这些模型的逻辑是:既要覆盖实际产品中最可能用到的轻量网络,也要看看 Transformer 类模型在 RV1126B 这种小 NPU 上到底能不能跑。ViT-Tiny 是我特意加进来的,因为现在很多新项目想上 Transformer,但边缘 NPU 对注意力的支持普遍不好,需要实测验证。

3.2 各模型对 NPU 的友好度预判

在动手之前,其实可以根据架构特点预判一下哪些模型会顺利、哪些会踩坑。

深度可分离卷积是 MobileNet 系列的核心,RKNN 对 depthwise conv 的支持一直不错,但要注意某些版本的量化对 depthwise 层不友好,会出现精度骤降。ShuffleNetV2 的通道混洗操作在 ONNX 里通常被拆成 reshape + transpose + reshape,这些算子在 NPU 上要么被支持,要么会 fallback 到 CPU,需要看具体实现。

ResNet 系列的残差加法(Add)是标准算子,支持没问题,但 ResNet50 的参数量和计算量对 RV1126B 来说偏大,帧率可能不理想。

EfficientNet 的 SE 模块里有全局平均池化和乘法,全局池化在 NPU 上一般支持,但要注意它的 Swish 激活函数。Swish 在早期 RKNN 版本里不支持,需要替换成 ReLU 或者用近似。1.6.0 版本对 Swish 的支持有所改善,但仍需实测。

RepVGG 的重参数化在推理时会把多分支合并成单个 3x3 卷积,所以转换到 RKNN 的其实是合并后的结构,理论上非常友好。但前提是你在导出 ONNX 前就完成了重参数化,否则多分支结构会增加转换复杂度。

ViT 是最不确定的。自注意力涉及大量的矩阵乘、softmax、layer norm,这些算子在 NPU 上的支持程度参差不齐。LayerNorm 在很多 NPU 上不支持,会 fallback 到 CPU,导致速度极慢。我预判 ViT-Tiny 大概率跑不顺,但还是要实测确认。

4. 模型转换实操与量化校准

4.1 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, "mobilenetv2.onnx", opset_version=12, input_names=["input"], output_names=["output"], do_constant_folding=True )

这里有几个容易忽略的点。do_constant_folding=True能把 BN 层折叠进卷积,减少算子数量,对 NPU 友好。model.eval()必须调用,否则 BN 和 Dropout 会以训练模式导出,结果完全不对。

对于 RepVGG,导出前必须调用它提供的repvgg_model_convert或者手动做重参数化,把训练时的多分支结构合并成推理时的单路 3x3 卷积。如果直接导出训练结构,ONNX 里会有一堆分支,转换时算子数量翻倍,而且量化误差会累积。

对于 ViT,导出时要注意位置编码的处理。有些实现里位置编码是动态生成的,导出后会变成一堆奇怪的算子。建议用固定位置编码的版本,或者把位置编码作为常量固化进去。

4.2 RKNN 转换配置详解

ONNX 转 RKNN 的核心代码如下:

from rknn.api import RKNN rknn = RKNN(verbose=True) 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', optimization_level=3 ) rknn.load_onnx(model='mobilenetv2.onnx') rknn.build(do_quantization=True, dataset='calibration.txt') rknn.export_rknn('mobilenetv2.rknn')

mean_values和std_values这里填的是 ImageNet 的标准值,对应训练时transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])。注意 RKNN 的 mean/std 是按 0 到 255 像素值算的,所以要把 0 到 1 的 mean 乘以 255,std 也乘以 255。这个换算如果搞错,精度会掉得莫名其妙。

quantized_dtype选asymmetric_quantized-8是 RV1126B 的推荐配置。对称量化在某些层上精度不如非对称,特别是经过 ReLU 之后激活值全非负的情况,非对称量化能更好利用 INT8 的动态范围。

optimization_level=3会启用更激进的图优化,包括算子融合、常量折叠等。但有时候过于激进会导致某些层被错误融合,如果发现精度异常,可以降到 2 试试。

4.3 量化校准集怎么准备才靠谱

量化校准是决定 INT8 精度损失的关键。校准集的目的是让工具统计每层激活值的动态范围,从而确定量化参数。校准集选得不好,量化后的模型精度可能掉十几个点。

我的经验是校准集要满足三个条件:数量够、分布对、预处理一致。

数量上,官方建议 100 到 500 张。我实测下来,200 张左右是个比较平衡的点,再多收益递减。太少(比如 20 张)会导致统计不充分,某些层的动态范围估计偏差大。

分布上,校准集要覆盖你实际应用场景的各类样本。如果你做的是工业质检,校准集就应该是各种缺陷和正常样本的混合,而不是随便拿 ImageNet 的图片凑数。领域不匹配的校准集是精度损失的最大元凶。

预处理上,校准集的图片预处理必须和推理时完全一致。包括 resize 方式、crop 策略、归一化参数。我一般直接复用验证集的 dataloader,把预处理后的张量存成 npy 或者直接喂图片路径列表。

校准集文件calibration.txt就是一个纯文本,每行一张图片的路径:

/data/calib/img_001.jpg /data/calib/img_002.jpg ...

注意:校准图片的尺寸不需要和模型输入一致,RKNN 会自己做 resize。但如果你在 config 里设置了 mean/std,工具会用这个参数处理校准图。所以校准图和推理图的预处理链路要一致。

4.4 转换过程中的算子报错处理

转换时最常见的报错就是算子不支持。RKNN-Toolkit2 在build阶段会打印每个算子的映射情况,不支持的算子会标红。

我遇到过的典型问题:

MobileNetV3 的 hard-swish 激活,在 1.6.0 里部分支持,但某些情况下会被拆解成多个算子,影响效率。解决办法是在训练时就用 ReLU 替换 hard-swish,或者接受它的拆解。

EfficientNet 的 Swish,1.6.0 支持但需要确认。如果报不支持,可以用torch.nn.SiLU的近似替换,或者改用 EfficientNet-Lite 版本,它把 Swish 换成了 ReLU6,对 NPU 友好得多。

ViT 的 LayerNorm,这是最致命的。RV1126B 的 NPU 不支持 LayerNorm,会 fallback 到 CPU。一旦有层跑在 CPU 上,整个推理就会被拖慢,因为 NPU 和 CPU 之间的数据搬运开销很大。实测 ViT-Tiny 转换能成功,但推理速度惨不忍睹。

ShuffleNetV2 的通道混洗,在 ONNX 里是 reshape + transpose + reshape。RKNN 对 transpose 的支持有限,某些维度组合会报错。如果遇到,可以尝试用optimization_level=2让它保留原始结构,或者手动改写网络把 shuffle 换成 1x1 卷积(会损失一些精度但能跑)。

5. 板端推理实测与性能对比

5.1 测试方法与指标定义

板端测试我写了一个统一的 C++ 推理程序,用 RKNPU2 的 C API,避免 Python 解释器开销影响帧率测量。测试流程是:加载模型、预热 10 次、连续推理 100 次取平均耗时。

测的指标有三个:单帧推理耗时(ms)、Top-1 精度(在 ImageNet 验证集子集上)、内存占用(通过/proc/节点读取)。

精度测试用的是 ImageNet 验证集里随机抽的 5000 张图,对比 FP32 原模型和 INT8 RKNN 模型的 Top-1 准确率差异。

5.2 各模型实测数据

实测数据如下表(RV1126B,NPU 满频,单核推理):

模型推理耗时(ms)帧率(FPS)FP32精度INT8精度精度损失内存(MB)
MobileNetV28.212271.8%70.9%-0.9%18
MobileNetV3-Small5.617867.4%66.1%-1.3%14
ShuffleNetV2 1.0x6.116469.4%67.8%-1.6%15
ResNet1814.37069.8%69.0%-0.8%32
ResNet5038.72676.1%75.2%-0.9%68
EfficientNet-B016.56177.1%75.3%-1.8%28
RepVGG-A011.88572.4%71.6%-0.8%24
ViT-Tiny156.3674.5%70.2%-4.3%45

几个观察:

MobileNetV3-Small 是最快的,178 FPS,精度 66.1%,适合对速度极度敏感、精度要求不高的场景。MobileNetV2 速度稍慢但精度更高,是更均衡的选择。

ResNet18 的精度和 MobileNetV2 接近,但速度慢了一倍,参数量也大,在 RV1126B 上性价比不如 MobileNet 系列。

ResNet50 精度最高(75.2%),但 26 FPS 对于很多实时场景已经不够用了,除非你的场景允许低帧率。

EfficientNet-B0 精度 75.3%,和 ResNet50 相当,但速度快了一倍多,内存也小很多。这是这次实验里精度效率比最好的模型。不过它的量化损失 1.8% 略高,说明 Swish 和 SE 模块的量化还是有点影响。

RepVGG-A0 表现中规中矩,85 FPS,精度 71.6%,量化损失只有 0.8%,说明重参数化后的单路卷积对量化非常友好。

ViT-Tiny 是最惨的,156ms 一帧,只有 6 FPS,而且量化损失高达 4.3%。原因就是 LayerNorm 和 softmax 跑在 CPU 上,NPU 利用率极低。这个结果基本宣判了 ViT 类模型在 RV1126B 上的死刑,除非你能找到完全 NPU 友好的 Transformer 变体。

5.3 精度损失的原因分析

量化损失的大小和模型结构强相关。从数据看,纯卷积、激活函数简单的模型(ResNet、RepVGG)量化损失最小,在 1% 以内。带复杂激活(Swish、hard-swish)和注意力机制的模型损失较大。

MobileNetV3 的 hard-swish 和 SE 模块是损失来源。SE 模块里的全局池化和 sigmoid 在量化后动态范围压缩,导致通道权重估计不准。

EfficientNet 的 Swish 是主要问题。Swish 在负半轴不是单调的,量化时容易在零点附近产生较大误差。

ViT 的损失最大,除了 LayerNorm 跑 CPU 导致的数值不一致,softmax 的量化也是问题。softmax 输出是概率分布,动态范围很小,INT8 量化后分辨率不够,注意力权重失真。

一个通用的经验:激活函数越简单、越单调,量化越友好。ReLU 是最友好的,ReLU6 次之,Swish 和 GELU 较差,softmax 最差。

6. 常见问题排查与避坑经验

6.1 转换阶段问题速查

问题现象可能原因解决办法
报 target_platform 不支持用了 Toolkit1换 RKNN-Toolkit2
ONNX 解析失败opset 版本过高用 opset 11-13 重新导出
算子不支持标红激活函数或特殊算子替换激活或改写网络
量化后精度骤降校准集不匹配换领域匹配的校准集
build 阶段 OOM模型太大或优化级别过高降 optimization_level
转换成功但推理报错runtime 版本不匹配对齐 Toolkit 和 runtime 版本

6.2 板端运行问题排查

板端最常见的问题是librknnrt.so版本不匹配。表现是加载模型时返回错误码,或者推理结果全零。解决办法是确认 Toolkit 和 runtime 版本一致,用strings librknnrt.so | grep version看版本号。

另一个坑是输入数据的 layout。RKNN 默认输入是 NHWC,但 PyTorch 导出的是 NCHW。转换时 RKNN 会自动处理,但如果你在板端手动构造输入,要注意用 NHWC 顺序,否则结果完全错乱。

内存不足也是常见问题。RV1126B 的 DDR 有限,大模型加载后剩余内存可能不够跑其他任务。ResNet50 加载后占用 68MB,加上系统和其他进程,4GB DDR 其实还好,但如果你同时跑多路推理,就要算好内存预算。

6.3 提升精度的小技巧

如果量化后精度损失超过 2%,可以尝试这几个方法。

第一,混合量化。RKNN 支持对敏感层保持 FP16,其他层 INT8。通过hybrid_quantization配置,把量化损失大的层(比如 SE 模块、注意力层)设为 FP16。代价是模型变大、速度略降,但精度能拉回来不少。

第二,调整校准集。增加校准集数量到 500 张,或者针对性地补充难样本。难样本指的是模型容易分错的类别,这些样本的激活值分布更能代表边界情况。

第三,量化感知训练。如果模型是你自己训练的,可以在训练阶段就加入量化模拟,让模型适应 INT8 的数值范围。这是最彻底的方法,但需要重新训练。

第四,逐层分析。用rknn.query看每层的量化误差,定位损失最大的层,针对性处理。有时候问题就出在一两个层上,改掉就好了。

6.4 实操心得

我在整个实验里最大的体会是:不要迷信模型在服务器上的表现。ResNet50 在 ImageNet 上比 MobileNetV2 高 4 个点,但在 RV1126B 上,它的帧率只有 MobileNetV2 的五分之一,而实际产品里 4 个点的精度差异往往可以通过数据增强和微调补回来,但帧率不够就是硬伤。

另一个体会是校准集的重要性被严重低估。我一开始图省事,直接用 ImageNet 的验证集做校准,结果 MobileNetV3 的精度掉了 3 个点。后来换成和实际场景匹配的校准集,损失降到 1.3%。校准集不是走形式,它直接决定量化参数的质量。

还有一点,RV1126B 的 NPU 对 depthwise 卷积的加速比没有想象中高。理论上 depthwise 的计算量小,但 NPU 的并行度利用不充分,实际加速比不如普通卷积。所以 MobileNet 系列虽然 FLOPs 低,但在 RV1126B 上的帧率优势没有 FLOPs 差距那么大。

最后,如果你要做产品选型,我的建议是:优先考虑 MobileNetV2 或 RepVGG-A0,它们在速度、精度、量化友好度上最均衡。如果精度要求高且能接受 60 FPS 左右,EfficientNet-B0 是更好的选择。ViT 类模型在 RV1126B 上目前不实用,等下一代 NPU 或者用专门优化的 Transformer 变体再说。

这个实验后续还可以扩展的方向:一是测试更多轻量模型,比如 GhostNet、MobileOne;二是尝试 INT4 量化,看 RV1126B 是否支持以及精度如何;三是把分类模型作为 backbone,接检测或分割头,看整体 pipeline 的表现。这些等我后续有时间再补上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 1:08:42

多差速无人车编队控制:ROS/C++源码实现与调参实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:08:42

国产SiC MOSFET通过AEC-Q101与HV-H3TRB车规认证意味着什么

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:08:15

STM32F030C8T6+MRT311额温枪方案:硬件设计、软件流程与标定避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:08:13

嵌入式AI协同开发实战:用Codex类工具提效重复编码

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:07:52

万能芯片真相:FPGA如何实现可重构计算与异构集成

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:05:48

高分辨率远程摄像头如何越做越小?GMSL串行链路设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华