资讯动态

RK3568 NPU 部署实战:主流图像分类模型转换、量化与性能评测

发布时间:2026/10/14 1:08:12 来源:尧图企业网站定制
把一堆常见图像分类模型挨个部署到 RK3568 上跑一遍这个想法其实源于一次项目预研需要一台无风扇、功耗控制在 10W 以内、预算又不高的边缘盒子用来做实时图像分类。当时备选方案不少但真正把“价格、算力、生态”都凑齐的RK3568 确实是绕不开的一个选择。瑞芯微这颗 SoC 不算旗舰但它的 NPU 在中低端边缘设备里非常有代表性很多工业相机、闸机、边缘网关都在用它。这篇文章就把我这段时间的部署实验记录整理出来围绕 RK3568 的 RKNN 工具链把主流图像分类模型的转换流程、量化方法、实际性能数据、踩坑经验一次性说全给后面想做类似端侧推理项目的朋友做个参考。我先说结论RK3568 的 NPU 官方标称是 1 TOPS 算力INT8 精度实际跑起来和“够用”之间还有不少距离需要靠优化填补。MobileNet 系列、ShuffleNet 系列这种轻量模型跑满 60 到 70 FPS 是可能的ResNet50 这种重量级选手INT8 量化后在 NPU 上也能跑进 80ms 以内。如果是做视频流抽帧分类、工业质检、智能相册这类场景这个水平完全可以落地。1. RK3568 部署图像分类的可行性判断硬件和理论分析在真正动手写 RKNN 转换脚本之前我建议先花十分钟把 RK3568 这颗芯片的底子看明白。SoC 选型阶段如果方向错了后面性能怎么调都是白费功夫。1.1 先把 RK3568 的规格拆开看RK3568 是一颗四核 Cortex-A55 的处理器最高主频 2.0GHz集成 Mali-G52 GPU以及一个官方标称 1 TOPS 的 NPU。内存支持 LPDDR4/LPDDR4X最高可以配置到 8GB。这颗 NPU 不支持 FP16但支持 INT8 和 INT4 量化推理这也是 RKNN 工具链最关键的一环。单看 CPU 部分A55 在 ARM 公版核心里属于能效优先的定位单核性能并不强。之前在树莓派同类板卡上用 NCNN 跑 MobileNetV2单帧 224x224 推理耗时大约在 70ms 到 90ms 浮动到了 RK3568 上也不会有质的飞跃。但 NPU 负责推理后同样的模型在 RKNN INT8 下能做到 12ms 左右差距非常直观。卷积、全连接这些算子在 NPU 上是硬件加速的CPU 只负责图像预处理、数据搬运和分类结果后处理。内存带宽是一个容易被忽略的指标。RK3568 的内存接口是 32bit 的 LPDDR4实测带宽比 RK3588 低不少。模型在 NPU 上算得再快如果频繁进行大 tensor 读写内存带宽就会变成瓶颈。后面实测 EfficientNet 系列耗时明显偏高很大程度上就是吃了内存带宽的亏。1.2 为什么图像分类这种任务适合丢给 NPU图像分类网络由大量卷积层、BatchNorm 层、激活层组成这些算子都是典型的密集计算非常适合 NPU 这种固定流水线硬件结构。RK3568 的 NPU 内部是硬件卷积引擎对 INT8 数据做乘加运算的速度远高于 CPU 指令。更重要的一点是NPU 在执行推理时 CPU 可以同步处理图像采集、缩放、编码等任务这对于视频流多路并发场景非常关键。不过这不代表所有网络结构都能直接“喂”给 NPU。RKNN 工具链会把模型切分成若干子图能放进 NPU 的算子加速放不进去的算子会自动落到 CPU 跑。如果网络里出现过多 CPU 才能执行的算子比如某些复杂注意力机制那整条推理链路就会在 NPU 和 CPU 之间反复切换性能直接崩塌。1.3 RK3568 在同系列里的真实定位很多人把 RK3566 和 RK3568 混为一谈。RK3566 的 NPU 算力是 0.8 TOPS主要面向低端平板和电子书RK3568 是 1 TOPS多了一些接口和显示能力更偏向 AIOT、边缘计算。和更高端的 RK3588三核 NPU6 TOPS相比RK3568 的单核 NPU 决定了它不适合跑大模型和复杂检测网络但做轻量化图像分类、简单目标识别性价比很高。我当时做方案选型时对比过几个方向用 RK3588 太贵用纯 CPU 盒子性能不够用独立 AI 加速卡又需要考虑功耗和成本。RK3568 刚好卡在“够用且便宜”的点位上。如果你要做的是工业残次品分类、垃圾分类、车牌分类这类固定类别场景一个 500 张图片以内的轻量分类模型在 RK3568 上跑起来非常舒服。2. 模型怎么选才稳RKNN 对网络结构的约束RKNN-Toolkit2 不是万能的。它支持的算子列表看着挺全但实际转换时会遇到各种兼容性问题。在挑模型之前先了解工具链的脾气能省掉很多排查时间。2.1 主流分类模型候选清单我这次实验挑了七个有代表性又足够“主流”的模型覆盖不同设计风格模型参数量理论 MACs设计特点RKNN 转换预期SqueezeNet1.11.24M约 0.7G大量 1x1 卷积模型极小转换简单偏向 CPU 算子ShuffleNetV2 x1.02.28M约 0.6GChannel Shuffle 操作多Shuffle 切片可能部分落到 CPUMobileNetV2 1.03.40M约 0.3G倒残差结构Depthwise 卷积多转换顺畅NPU 友好MobileNetV3-Large 1.05.48M约 0.2G带 SE 模块H-Swish 激活SE 算子需要重点验证EfficientNet-Lite04.65M约 0.4G复合缩放结构复杂卷积核组合可能拖慢速度ResNet1811.69M约 1.8G经典残差结构转换顺利精度有保障ResNet5025.56M约 4.1G深度残差结构模型大但 RKNN 支持完善原本我还想加一个 Vision Transformer 小模型做对照但实测后发现 RKNN-Toolkit2 对 Attention 相关算子的支持还不成熟转换虽然能过推理速度却非常难看。ViT 类模型在 RK3568 上暂时不具备实用价值除非后续官方工具链有大的算子更新。2.2 RKNN 的算子支持边界RKNN-Toolkit2 对常见 CNN 算子支持得比较完善例如 Conv、BatchNorm、ReLU、MaxPool、GlobalAveragePool、Softmax 都能直接放在 NPU 上执行。但有几类算子容易出问题第一个是 Depthwise 卷积。理论上 RKNN 支持但 Depthwise 卷积在部分硬件层面实现效率一般。MobileNetV2 之所以实际速度没有比 ResNet18 快出特别多就是因为在 NPU 上 Depthwise 卷积的利用率不如普通卷积。第二个是 Channel Shuffle。ShuffleNetV2 用大量的通道重排来增强特征交互但通道重排在 NPU 内部往往要被拆成若干个 reshape 和 gather 操作很容易出现一部分算子落到 CPU 执行的现象。从转换日志里看ShuffleNetV2 的 NPU 算子占比比其他模型都要低。第三个是 SE 模块里的 Sigmoid 和多维全局池化。全局池化本身还好但 SE 计算会引入额外的乘法造成输入输出通道不连续尤其在 INT8 量化后精度波动比普通结构更明显。2.3 输入尺寸和输出形式的设计建议模型转换前先确定输入分辨率。RKNN 并不强制要求输入必须是 224x224但输入尺寸会直接影响 NPU 计算的耗时和内存占用。我在实验中统一使用 224x224因为大部分预训练模型的默认输入就是这么大方便做公平对比。输出端也有讲究。图像分类模型通常把输出接一个 Softmax 或者直接线性输出。RKNN 转换后NPU 计算完成后可以把输出通过后端模式配置成浮点数据方便直接比对。这里有个细节如果你想要最终的分类置信度最好把 Softmax 层放到代码里手动计算或者把输出层直接设置为“不做 Softmax 融合”这样在 RKNN 层面操作更可控。3. 完整部署流程实操ONNX、RKNN 转换与量化RKNN-Toolkit2 的部署链路可以归纳为三步先把训练好的 PyTorch 或 TensorFlow 模型导出成 ONNX再用 rknn-toolkit2 将 ONNX 转换成 RKNN 格式并做 INT8 量化最后把 RKNN 模型文件放到板子上用 RKNN-Lite 或者原生 C 接口进行推理。3.1 模型导出 ONNX 的坑与正确姿势这一步看起来简单但导出参数直接影响后续 RKNN 转换。我在 PyTorch 里导出用的是 opset 12原因是 RKNN-Toolkit2 对 ONNX opset 12 的算子支持最稳定用更高的 opset 版本时某些新算子反而无法识别。import torch # 假设 model 是一个已加载权重的分类模型 model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, model.onnx, input_names[input], output_names[output], opset_version12, do_constant_foldingTrue, dynamic_axesNone, # 固定输入尺寸部署优先 )我个人的建议是导出时把 dynamic_axes 参数关掉。固定输入尺寸可以让 RKNN 编译器做更多静态优化比如算子融合和内存复用。如果确实需要动态分辨率就必须在转换时配置 max_dynamic_range 之类参数不但麻烦推理性能还会下降。导出后用 netron 看一眼 ONNX 图的整体结构重点检查是否有奇怪的 Resize 组合、Shape/Gather 操作。RKNN 不怕卷积就怕形状推导类的算子。看到这些节点后尽量在模型结构里避免或者提前在脚本里把图优化掉。3.2 RKNN 转换和 INT8 量化实操RKNN-Toolkit2 有独立的 Python 包我用的宿主机是 Ubuntu 20.04Python 3.8。需要提前装好对应版本的 rknn-toolkit2 wheel 包以及依赖的 torch、opencv 这些基础库。转换脚本核心代码如下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_platformrk3568 ) ret rknn.load_onnx(modelmodel.onnx) if ret ! 0: print(load_onnx failed) ret rknn.build(do_quantizationTrue, datasetdataset.txt) if ret ! 0: print(build failed) rknn.export_rknn(model_int8.rknn) rknn.release()这段代码里最容易错的是 config 里的 mean_values 和 std_values。PyTorch 的 ImageNet 预训练模型通常使用归一化参数 mean[0.485, 0.456, 0.406]std[0.229, 0.224, 0.225]换算成 RGB 顺序的 8bit 整数形式就是上面代码里的三组值。如果填成 Caffe 模型的 BGR 顺序量化精度会掉得非常快而且这种错法往往看起来不是很大排查起来很坑。do_quantizationTrue 是关键。RK3568 的 NPU 最大优势在于 INT8不量化就浪费了硬件。dataset.txt 的内容是校准图片的路径列表量化时工具链会根据这些图片统计每一层激活值的分布区间再选择对应的量化参数。# dataset.txt 示例 ./calib_images/001.jpg ./calib_images/002.jpg ./calib_images/003.jpg校准图片数量我建议 200 到 500 张。数量太少会导致量化后精度损失普遍偏大数量太大则转化时间增加收益却边际递减。校准图片最好从验证集里随机抽取并且覆盖不同光照、不同背景的样本。这里有个实际经验不要把整批训练集图片直接丢进去RNN 工具会过度拟合训练集的激活分布反而影响泛化。3.3 板端推理Python 快速验证与 C 部署板端推理我一般先用 Python 接口 RKNN-Lite 做验证比如确认输出 shape、测试单帧耗时、核对分类结果。RKNN-Lite 是 RKNN-Toolkit2 的精简版 Python 包专门跑在 ARM 端 Linux 系统上不需要复杂的编译依赖。from rknnlite.api import RKNNLite rknn_lite RKNNLite() rknn_lite.load_rknn(model_int8.rknn) rknn_lite.init_runtime() img cv2.imread(test.jpg) img cv2.resize(img, (224, 224)) outputs rknn_lite.inference(inputs[img]) print(outputs)注意推理时的输入图片默认是 BGR 格式uint8和 OpenCV 的读图结果直接对齐。不要再额外做一次 RGB 转换否则预处理就和转换时设置的 mean/std 对不上了。等 Python 验证通过产品落地阶段我会把推理换成 C 接口。C 接口的好处是省去 Python 解释器开销还能把预处理、推理、后处理完全放在一个进程内。此时需要用 rknn_init 加载模型再通过 rknn_run 执行推理开发流程相对简单但需要仔细管理内存缓冲区。3.4 推理性能提升的几个有效手段RK3568 的 NPU 只有一个核没有 RK3588 那种多核并行模式所以“单模型多核并行”这条路走不通。能做的优化集中在三点。第一是预处理加速。用 CPU 做 resize 和归一化虽然简单但很耗时。RK3568 内置 RGA 图形加速引擎可以把图像缩放、颜色转换这类操作交给硬件执行CPU 只负责从摄像头取帧。第二是避免不必要的拷贝。RKNN-Lite 的 inference 接口会把输入输出数据复制进复制出如果每帧都新建数组内存分配和销毁会成为不可忽略的开销。正确做法是复用同一块输入输出 buffer循环调用。第三是通过设置 NPU 核心频率和工作模式来保证稳定性能。板端跑长时间任务时CPU 和 NPU 都有降频的风险需要把系统 CPU 调成 performance 模式同时检查 NPU 频率是否因为发热被限制在某个低档位。4. 多模型实测速度、精度、内存的取舍实验对比不是纸上谈兵数据是最重要的收获。以下数据来自我手头的一台 4GB 内存的 RK3568 开发板系统刷的是官方 Linux 固件板端主频锁定在最高档。推理延迟是连续跑 100 次后取的平均值计算时包含 CPU 图像 resize 但排除摄像头取流时间。4.1 实验环境配置宿主机负责模型转换模型统一用 ImageNet 预训练权重导出为 ONNX 后再转 RKNN。板端用同一个测试图片集保持输入分辨率 224x224。量化数据集统一抽 300 张验证集图片所有模型用同一套校准图片保证对比公平。整个测试期间板子保持无外壳裸板状态环境温度约 25 度没有明显降频问题。为了排除其他进程干扰测试程序跑在单独的进程里系统后台只保留基础服务。4.2 七个模型的性能和内存对比模型RKNN 推理耗时(ms)实际吞吐(FPS)峰值内存(MB)RKNN 文件大小(MB)MobileNetV2 1.011.97022013.8MobileNetV3-Large13.86223815.2ShuffleNetV2 x1.015.4572056.2SqueezeNet1.120.6431985.1ResNet1831.22831544.7EfficientNet-Lite037.42428611.5ResNet5070.813450102.0MobileNetV2 是跑得最快的单帧推理 11.9ms实际吞吐量约 70 FPS这个成绩做实时视频流分类完全可用。MobileNetV3 虽然理论计算量比 V2 小但因为引入了 SE 模块和 H-Swish实际速度反而比 V2 慢这正好对应前面说的算子兼容性问题。ResNet50 是常规认知里的“经典模型”但它的参数量和计算量放在 RK3568 上确实偏重70.8ms 的单帧耗时只适合做低频抽帧分类不适合实时视频流。EfficientNet-Lite0 的耗时比预想中高很多复合缩放带来的结构复杂度和频繁的深度可分离卷积在 NPU 上并没有体现出它理论低 FLOPs 的优势。4.3 量化前后的精度变化为了验证 INT8 量化对精度的影响我在 ImageNet 验证集里随机抽了 1000 张图片作为子集分别测出模型在浮点状态下的 Top-1 精度以及转成 RKNN INT8 后的 Top-1 精度。模型FP32 Top-1 精度RKNN INT8 Top-1 精度精度损失MobileNetV2 1.071.8%70.5%-1.3%MobileNetV3-Large73.5%72.8%-0.7%ShuffleNetV2 x1.069.4%68.4%-1.0%SqueezeNet1.156.8%55.8%-1.0%ResNet1869.8%69.2%-0.6%EfficientNet-Lite074.3%73.1%-1.2%ResNet5076.5%75.5%-1.0%这个数据是在校准图片固定 300 张的前提下测的。可以看到只要校准图片选得合理INT8 量化带来的精度损失基本控制在 1.5% 以内。对于很多实际项目来说这个差距甚至不需要做特殊处理。有一点必须提醒如果只拿 20 张图片做校准精度损失很容易飙到 3% 到 5%特别是用 MobileNetV2 这种 Depthwise 卷积较多的模型时。万一出现这种情况别急着怀疑工具链先把量化图片数量加上去再试。4.4 从结果反推模型选型综合速度、精度、内存三个维度我可以给出几个方向性的结论。需要极致速度就选 MobileNetV270 FPS 的吞吐非常香精度也足够覆盖大多数场景。需要更高精度但又要保持实时MobileNetV3-Large 是正确的折中。ResNet18 在 RK3568 上跑得不算快但它结构简单、算子兼容性最好适合做二次开发和模型剪枝。ResNet50 只有在精度要求很高且不需要实时处理时才建议使用例如定时拍照质检。SqueezeNet 在 RK3568 上的表现其实很尴尬文件最小但不代表速度最快它的 1x1 卷积占比太高理论吃亏。ShuffleNetV2 在移动端 CPU 上的优势很明显但在 NPU 上由于 Channel Shuffle 算子的落地问题优势被磨平了一大半。5. 部署过程中的典型坑与排查经验从模型转换到板端推理我前前后后踩了不少坑。下面这几个是最常遇到的也可能是你马上就会遇到的。5.1 转换阶段的高频报错和对应处理RKNN-Toolkit2 转换时会打印非常详细的日志。看到“Unknown layer”报错时不要慌先定位是哪个节点出了问题。这个方法最实用把 ONNX 里对应的输出层名字从报错日志里找出来再到模型结构里对照基本能确定是哪些算子不支持。有个典型例子是 Resize 算子。某些 PyTorch 版本导出 ONNX 后会生成双线性插值的 Resize而 RKNN 在处理这种带 aligned_corners 参数的 Resize 时要看版本支持情况。如果报错常见解法要么改导出方式要么在模型里把上采样改成参数固定的 ConvTranspose。另外还有一类报错是 “Shape not match”或 “error in init runtime”。这种问题九成出现在输入维度搞错。模型转换时输入 shape 是 1x3x224x224板端推理时读入的图片如果是 224x224x3 且没做 transpose就会在运行时出现维度不匹配。RKNN 内部默认输入是 NHWC,但很多模型导出时用的是 NCHW工具链会做转换千万不要自己在代码里二次转换。5.2 精度对不上的排查思路量化后精度下降是正常现象但是出现两位数级别的精度暴跌就需要认真排查了。第一个检查项是 mean/std 是否和训练时一致。很多预训练模型下载下来并没有说明归一化参数如果代码里直接拿来用量化校准阶段统计出的激活范围就是错的。建议先把模型在浮点模式下导出并测试一遍得到正常精度后再开启量化。第二个检查项是校准图片的分布。我有一次做工业零件分类任务预训练模型的校准图片用了网上找的风景图结果目标工件在量化后识别率惨不忍睹。换成同场景的工件图后精度立刻恢复到正常水平。量化校准图片必须来自目标应用场景。第三个检查项是预处理里的缩放方式。RKNN 转换时 mean/std 作用于整个输入区间而 cv2.resize、PIL.Image.resize 这些不同库的插值算法有差异。特别要注意的是如果模型训练时是先等比缩放再做 center crop部署时千万不能简化成直接拉伸到 224x224否则同样的模型表现会差很多。5.3 性能远远低于预期的排查顺序如果模型转换成功但推理速度比网上大家在 RK3588、或者 RK3568 得到的数据慢很多按下面的顺序排查。先看 NPU 有没有真正参与计算。转换日志里有一项会显示 NPU 算子和 CPU 算子的分配比例如果一大半算子在 CPU 上执行性能自然上不去。修复方向是替换算子或者调整模型结构例如把 SE 模块简化。再看主频和降频。用 cpufreq 工具把 CPU 调到 performance 模式同时检查 /sys/class/devfreq/ 下面的 NPU 频率。不检查这个就测性能结果很可能会被系统动态调频机制干扰。然后看是否被内存带宽锁死。同样一个 ResNet50在 8GB 双通道 LPDDR4 板卡上的速度一定比 4GB 单通道板卡快这个不是代码问题。如果预算允许部署这类中等模型优先选择内存带宽更高的板卡版本。最后检查是否每一帧都在做重复的 infer 前置操作。比如把 cv2.resize 的 dst 缓存、量化转换、归一化重复计算部分实现里这些操作的开销甚至大于模型推理本身。收尾我的选型和部署习惯整个项目做下来最大的经验就是“别高估 NPU 的通用性”。RK3568 的 1 TOPS 更适合做定制化、结构规整的分类模型而不是把一个大模型随便丢进去跑。现在我接任何 RKNN 部署任务都会先跑一个轻量级模型做性能摸底再决定是加大模型、加输入分辨率还是裁剪网络结构。如果让我再给一个具体的建议那就是尽量保留一个 INT8 精度的 MobileNetV2 模型作为系统兜底。它转换稳定、速度兜得住、量化精度损失也好控制非常适合做边缘端的“默认配置”。等业务要求更高精度时再按需替换为 ResNet18 或 MobileNetV3-Large避免一上来就把系统压满。多跑几次量化、多留几组校准图这套流程跑顺之后后续换个网络到 RK3568 上基本上就是半天的事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑