
1. 为什么要在 RK3568 上折腾图像分类模型RK3568 这颗芯片在边缘计算圈子里热度一直不低。四核 Cortex-A55 加一颗 0.8 TOPS 的 NPU支持 INT8 量化推理功耗控制在几瓦级别价格又相对友好做智能门禁、工业质检、零售货柜这类场景的开发者基本都会把它列入候选。但真到落地阶段问题就来了手头训好的图像分类模型五花八门ResNet、MobileNet、EfficientNet、ViT 各有各的结构到底哪个能在 RK3568 上跑得又快又准官方文档给的数据往往只覆盖少数几个经典网络实际选型时还是得自己动手测一遍。我最近花了大概两周时间把手上常见的几类图像分类模型在 RK3568 上完整跑了一轮部署实验从模型转换、量化、板端推理到精度和速度对比踩了不少坑也积累了一些官方文档里不会写的经验。这篇文章就是把整个实验过程、数据结果和实操心得整理出来给正在做类似选型的同行一个参考。不管你是刚接触 RKNN 工具链的新手还是已经用过 RK3588 想了解 RK3568 差异的老手应该都能从中找到有用的东西。需要提前说明的是实验平台是一块标准的 RK3568 开发板4GB 内存版本系统是官方 Ubuntu 20.04 固件RKNN-Toolkit2 用的是 1.6.0 版本NPU 驱动版本 0.8.2。不同固件和工具链版本可能会有差异具体数据仅供参考但方法论是通用的。2. 实验整体设计与模型选型思路2.1 为什么选这几类模型图像分类模型发展到今天大致可以分成几个流派。一类是以 ResNet 为代表的经典卷积网络结构规整、算子标准对 NPU 友好一类是以 MobileNet、ShuffleNet 为代表的轻量级网络专为移动端设计参数量和计算量都压得很低还有一类是以 EfficientNet 为代表的复合缩放网络用 NAS 思路平衡深度、宽度和分辨率最后是近两年火起来的 ViT 系列基于注意力机制精度高但对算力和内存带宽要求也高。我选型的逻辑是每个流派挑一到两个有代表性的模型覆盖不同的参数量级和计算量级这样对比出来的结论才有参考价值。具体清单如下模型参数量输入尺寸流派选它的理由ResNet1811.7M224x224经典卷积结构标准作为基准线ResNet5025.6M224x224经典卷积大模型在 NPU 上的表现MobileNetV23.4M224x224轻量级移动端经典部署广泛MobileNetV3-Small2.5M224x224轻量级更极致的轻量化ShuffleNetV22.3M224x224轻量级通道混洗算子的兼容性EfficientNet-B05.3M224x224复合缩放NAS 结构的部署难度ViT-B/1686M224x224注意力机制验证 Transformer 可行性这个组合基本能覆盖大多数实际项目会遇到的模型类型。如果你手头的模型不在这个列表里也可以参照这个分类找到最接近的参考对象。2.2 实验要回答的三个核心问题整个实验围绕三个问题展开。第一是精度损失模型经过 INT8 量化后Top-1 准确率会掉多少不同结构的模型对量化的敏感度是否一样第二是推理速度单帧推理耗时多少毫秒能否满足实时性要求比如 30fps 需要单帧低于 33ms第三是部署难度模型转换过程中会遇到哪些算子不支持的问题需要多少手工干预这三个问题对应了实际选型时最关心的三个维度。精度决定能不能用速度决定好不好用部署难度决定要花多少人力。很多网上的评测只讲速度不讲精度或者只讲精度不讲部署成本参考价值有限。我这次尽量把三个维度都量化出来。2.3 测试数据集和评价标准精度测试用的是 ImageNet 验证集的子集5000 张图片覆盖 1000 个类别。之所以不用完整 50000 张是因为板端推理速度有限完整跑一遍时间太长。5000 张的采样保证了每个类别至少 5 张统计上足够反映精度趋势。评价指标用 Top-1 和 Top-5 准确率对比三个阶段的数值PyTorch 原始模型、ONNX 转换后模型、RKNN INT8 量化后模型。这样能清楚看到精度损失发生在哪个环节。速度测试用板端 NPU 推理预热 10 次后取 100 次推理的平均耗时排除首次加载和缓存的影响。3. 部署环境搭建与工具链配置3.1 PC 端环境准备RKNN 工具链分两部分PC 端的 RKNN-Toolkit2 负责模型转换和量化板端的 RKNPU2 运行时负责实际推理。PC 端建议用 Ubuntu 20.04 或 22.04Python 版本 3.8 到 3.10 之间。我一开始在 Windows 上试过工具链支持不完整量化功能经常报错后来换到 Ubuntu 才顺利。安装命令如下conda create -n rknn python3.9 conda activate rknn pip install rknn-toolkit21.6.0 pip install onnx1.14.0 pip install torch2.0.0 torchvision0.15.0这里有个坑要注意rknn-toolkit2 对 numpy 版本有要求如果环境里装了 numpy 2.x 会报兼容性错误需要降到 1.24 以下。另外 onnx 版本也不能太新1.15 以上某些算子解析会出问题我实测 1.14.0 最稳。板端环境相对简单官方固件已经带了 RKNPU2 运行时只需要确认驱动版本cat /sys/kernel/debug/rknpu/version如果输出类似RKNPU2: 0.8.2就说明驱动正常。如果版本太老建议刷最新固件老驱动对新算子的支持不全。3.2 模型导出与 ONNX 转换PyTorch 模型转 ONNX 是第一步。以 ResNet18 为例import torch import torchvision.models as models model models.resnet18(pretrainedTrue) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, resnet18.onnx, input_names[input], output_names[output], opset_version11, dynamic_axesNone )opset_version 建议用 11这是 RKNN 工具链支持最完整的版本。用 12 或 13 有时会遇到算子映射问题。dynamic_axes 设为 None 是因为板端推理输入尺寸固定动态轴反而会增加转换复杂度。导出后建议用 onnxsim 做一次简化去掉冗余算子pip install onnxsim onnxsim resnet18.onnx resnet18_sim.onnx这一步能减少后续量化时的算子数量提升转换成功率。我测过几个模型简化后 ONNX 文件大小平均减少 15% 左右转换时间也短一些。3.3 RKNN 模型转换与量化配置转换脚本的核心是量化配置。RKNN 支持两种量化方式一是用校准数据集做 PTQ训练后量化二是加载量化感知训练后的模型。实际项目里 PTQ 用得最多因为不需要重新训练。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_platformrk3568, quantized_dtypeasymmetric_quantized-8, optimization_level3 ) rknn.load_onnx(modelresnet18_sim.onnx) rknn.build(do_quantizationTrue, datasetcalibration.txt) rknn.export_rknn(resnet18.rknn)这里的 mean 和 std 必须和训练时用的预处理参数一致否则精度会掉得莫名其妙。校准数据集准备 200 到 500 张图片就够从训练集里随机采样要覆盖所有类别。我试过只用 50 张量化后精度掉了 3 个点加到 300 张后只掉 0.8 个点。optimization_level3是最高优化级别会做一些算子融合和内存优化但偶尔会导致转换失败。如果遇到问题可以降到 2 试试。4. 各模型部署实操与精度速度实测4.1 经典卷积网络ResNet 系列ResNet18 和 ResNet50 的转换过程最顺利所有算子都被原生支持没有遇到任何需要手工干预的地方。这得益于 ResNet 结构规整主要是 Conv、BN、ReLU、MaxPool、GlobalAvgPool 和全连接这些都是 NPU 的标准算子。精度方面ResNet18 原始 PyTorch 模型 Top-1 是 69.8%转 ONNX 后 69.7%基本无损INT8 量化后 68.9%掉了 0.9 个点。ResNet50 原始 76.1%量化后 75.2%掉了 0.9 个点。这个损失幅度在可接受范围内说明 ResNet 系列对 INT8 量化不敏感。速度方面ResNet18 单帧推理 12.3msResNet50 是 31.7ms。ResNet18 能轻松跑到 30fps 以上ResNet50 刚好卡在 30fps 边缘。如果对帧率要求高ResNet50 需要降输入分辨率或者做模型剪枝。注意ResNet 的 BN 层在量化时会被融合进 Conv这是自动完成的不需要手动处理。但如果你的模型里有自定义 BN 变体可能需要检查融合是否正确。4.2 轻量级网络MobileNet 与 ShuffleNetMobileNetV2 的转换也很顺利深度可分离卷积在 RK3568 NPU 上有专门优化。精度从原始的 71.9% 掉到 70.8%损失 1.1 个点。速度是亮点单帧只要 6.8ms能跑到 140fps 以上非常适合高帧率场景。MobileNetV3-Small 参数量更小但精度只有 67.4%量化后 66.2%。速度 4.2ms非常快。这个模型适合对精度要求不高但帧率要求极高的场景比如简单的有无检测。ShuffleNetV2 遇到了点麻烦。通道混洗Channel Shuffle算子在 ONNX 里是用 Reshape Transpose Reshape 组合实现的RKNN 工具链在量化时对 Transpose 的处理有点问题第一次转换报错。解决办法是在转换前用 onnx-simplifier 把这三个算子融合成一个自定义算子或者手动改写模型结构用等效的 1x1 卷积替代。我用了前者融合后转换成功。精度从 69.4% 掉到 67.8%损失 1.6 个点比 MobileNet 系列敏感一些。模型原始 Top-1量化后 Top-1精度损失单帧耗时帧率ResNet1869.8%68.9%-0.9%12.3ms81fpsResNet5076.1%75.2%-0.9%31.7ms31fpsMobileNetV271.9%70.8%-1.1%6.8ms147fpsMobileNetV3-Small67.4%66.2%-1.2%4.2ms238fpsShuffleNetV269.4%67.8%-1.6%5.9ms169fps4.3 复合缩放网络EfficientNet-B0EfficientNet-B0 的转换遇到了 Swish 激活函数的问题。Swish 是 x * sigmoid(x)ONNX 里拆成了 Sigmoid 和 Mul 两个算子。RKNN 对 Sigmoid 的支持没问题但量化时 Sigmoid 的输出范围不好确定导致精度损失偏大。第一次转换后精度从 77.1% 掉到 73.5%损失 3.6 个点明显比其他模型差。后来我查了 RKNN 的文档发现可以在 config 里指定quantized_algorithmmmse用最小均方误差算法做量化比默认的normal算法更适合有 Sigmoid 的网络。改完后精度恢复到 75.8%损失 1.3 个点和其他模型差不多了。速度方面EfficientNet-B0 单帧 18.5ms约 54fps。这个速度中规中矩但考虑到它的精度比 ResNet18 高不少性价比还是不错的。实操心得遇到精度损失异常大的模型先别急着换模型试试调整量化算法。mmse算法对包含 Sigmoid、Tanh 这类饱和激活函数的网络效果明显更好代价是量化时间会长一些。4.4 注意力机制ViT-B/16ViT 的部署是最折腾的。首先是模型太大86M 参数ONNX 文件 330MBRKNN 转换时内存占用很高我的 PC 有 16GB 内存都差点不够用。其次是结构问题ViT 的核心是 Multi-Head Attention涉及大量的 MatMul 和 Softmax这些算子在 NPU 上的效率远不如卷积。转换过程倒没有报错但量化后精度从 81.8% 掉到 76.2%损失 5.6 个点是所有模型里最严重的。分析原因主要是 Attention 里的 Softmax 输出分布比较集中INT8 量化后分辨率不够导致注意力权重失真。速度更是惨不忍睹单帧 187ms只有 5fps 左右。这个速度基本没法做实时推理只能用于离线批处理场景。模型原始 Top-1量化后 Top-1精度损失单帧耗时帧率EfficientNet-B077.1%75.8%-1.3%18.5ms54fpsViT-B/1681.8%76.2%-5.6%187ms5fpsViT 在 RK3568 上的表现说明Transformer 类模型目前还不适合这类边缘 NPU。除非你有极致的精度需求且能接受离线推理否则还是老老实实用卷积网络。5. 常见问题排查与避坑经验5.1 模型转换失败的高频原因转换失败是最常见的问题我整理了一个排查表报错信息原因解决办法Unsupported op: XXX算子不支持用 onnx-simplifier 融合或改写模型Quantize failed校准数据不足增加到 300 张以上覆盖所有类别Out of memory模型太大增加 PC 内存或分块转换Shape mismatch输入尺寸不匹配检查 config 里的输入尺寸设置Mean/std error预处理参数错误核对训练时的归一化参数其中算子不支持是最头疼的。RKNN 1.6.0 支持的算子列表在官方文档里有但实际转换时经常遇到列表里有但量化失败的算子。我的经验是遇到问题先试 onnx-simplifier能解决 80% 的情况。剩下的 20% 需要手动改写模型把不支持的算子用等效的支持算子替换。5.2 精度损失过大的调试思路精度损失超过 2 个点就需要排查了。我的调试顺序是先确认 ONNX 模型本身的精度是否正常排除导出问题再检查预处理参数是否一致然后尝试不同的量化算法最后考虑增加校准数据量。有一个容易被忽略的点是校准数据的分布。如果你用 ImageNet 的图片做校准但实际部署场景是工业相机拍的灰度图量化后的精度在实际场景里会掉得很厉害。校准数据一定要贴近实际部署场景这是很多教程不会强调的。5.3 板端推理性能优化技巧板端推理速度除了模型本身还受几个因素影响。一是 CPU 频率RK3568 默认是动态调频推理时会自动升频但如果散热不好会降频。建议加个散热片实测能稳定提升 10% 左右的帧率。二是内存带宽多个模型同时加载会争抢带宽建议串行推理。三是 NPU 核心数RK3568 只有一个 NPU 核心没法像 RK3588 那样多核并行。还有一个技巧是用 RKNN 的零拷贝接口避免数据在 CPU 和 NPU 之间来回拷贝。这个在官方例程里有示范能省 1 到 2ms 的拷贝时间对小模型来说占比不小。# 零拷贝推理示例 outputs rknn.inference(inputs[img], data_formatnhwc)用data_formatnhwc可以避免额外的格式转换前提是你的输入数据本来就是 NHWC 排列。5.4 模型选型的实战建议根据这次实验的数据我给几个场景的选型建议。高帧率简单场景选 MobileNetV3-Small4.2ms 的延迟基本没有对手。精度和速度平衡选 MobileNetV2 或 EfficientNet-B0前者更快后者更准。需要高精度且能接受 30fps 选 ResNet50。ShuffleNetV2 部署麻烦除非有特殊需求否则不推荐。ViT 系列目前不建议在 RK3568 上做实时推理。另外提醒一点这些数据是在 224x224 输入下测的。如果你把输入分辨率提高到 320x320 或 416x416推理时间会按面积比例增加。比如 MobileNetV2 从 224 提到 320耗时大概从 6.8ms 涨到 13ms 左右。选型时要把分辨率因素考虑进去。6. 实验数据汇总与横向对比把所有数据汇总到一张表里方便横向对比模型参数量原始 Top-1量化 Top-1精度损失耗时帧率部署难度MobileNetV3-Small2.5M67.4%66.2%-1.2%4.2ms238fps低ShuffleNetV22.3M69.4%67.8%-1.6%5.9ms169fps中MobileNetV23.4M71.9%70.8%-1.1%6.8ms147fps低ResNet1811.7M69.8%68.9%-0.9%12.3ms81fps低EfficientNet-B05.3M77.1%75.8%-1.3%18.5ms54fps中ResNet5025.6M76.1%75.2%-0.9%31.7ms31fps低ViT-B/1686M81.8%76.2%-5.6%187ms5fps高从这张表能看出几个规律。轻量级网络的精度损失普遍比大模型略高因为它们的特征表达能力本来就弱量化进一步压缩了信息量。ResNet 系列对量化最鲁棒损失都在 1 个点以内。EfficientNet 需要调量化算法才能达到同等水平。ViT 是特例精度损失和速度都不理想。性价比最高的几个选择追求速度选 MobileNetV3-Small追求平衡选 MobileNetV2 或 EfficientNet-B0追求精度选 ResNet50。ResNet18 有点尴尬精度不如 EfficientNet-B0速度不如 MobileNetV2除非你的项目已经在用 ResNet18 且不想换模型否则新项目不太推荐。7. 一些补充的实操细节校准数据集的制作有个小技巧。不要直接把图片路径写进 txt 文件而是先用脚本把图片统一 resize 到模型输入尺寸并保存这样转换时不用反复解码速度快很多。我一开始用原始图片300 张校准花了 8 分钟预处理后只要 2 分钟。import cv2 import os for img_name in os.listdir(calib_images): img cv2.imread(fcalib_images/{img_name}) img cv2.resize(img, (224, 224)) cv2.imwrite(fcalib_resized/{img_name}, img)板端部署时建议把 RKNN 模型文件放在 eMMC 而不是 SD 卡上加载速度快 3 倍左右。RK3568 的 eMMC 读取速度能到 200MB/s 以上SD 卡通常只有 40MB/s。模型文件虽然不大但加载时间也影响启动速度。还有一点是关于多线程的。RK3568 的 NPU 是单核的多个线程同时调用 NPU 不会提升吞吐量反而会因为上下文切换增加延迟。正确的做法是用一个推理线程加一个预处理线程预处理和推理流水线化这样能提升整体吞吐。我实测这种流水线方式比单线程串行快 20% 左右。最后说下温度问题。RK3568 满载推理时芯片温度能到 70 度以上如果不加散热会触发降频帧率掉 30% 都有可能。我加了一个 20x20mm 的铝散热片温度稳定在 55 度左右帧率波动很小。这个成本不到两块钱但效果立竿见影强烈建议加上。关于模型量化后的精度验证不要只看整体 Top-1还要看混淆矩阵。有些模型整体精度掉得不多但某些类别掉得很厉害如果这些类别恰好是你关心的那整体指标就有误导性。我在测试时发现 MobileNetV3-Small 在细粒度类别比如不同品种的狗上掉点明显比粗粒度类别多如果你的应用场景是细粒度分类选型时要特别注意。