ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RK3588 部署 YOLOv5s 全流程:从 ONNX 导出到 INT8 量化与 NPU 推理

RK3588 部署 YOLOv5s 全流程:从 ONNX 导出到 INT8 量化与 NPU 推理 1. 为什么我要在 RK3588 上折腾 YOLOv5s第一次拿到 RK3588 开发板的时候我脑子里想的其实很简单这玩意儿号称 6 TOPS 的 NPU 算力跑个 YOLOv5s 还不是手到擒来结果真正上手才发现从一块裸板到模型在 NPU 上稳定推理中间隔着的坑比我预想的多得多。这个系列我打算把整条链路完整记录下来从环境搭建、模型导出、量化校准到板端推理和性能调优每一步都写清楚我踩过什么、为什么这么选、有没有更好的替代方案。YOLOv5s 这个模型选得其实挺有讲究。它是 YOLOv5 系列里最小的版本参数量大约 7.2M权重文件 FP32 下也就 27MB 左右输入分辨率默认 640×640。对于 RK3588 这种边缘端芯片来说YOLOv5s 是一个甜点级的选择——精度够用速度能跑起来量化之后模型体积还能再压到 7MB 上下。如果你一上来就搞 YOLOv8m 或者更大的模型NPU 的带宽和内存调度会立刻成为瓶颈调试难度陡增。所以我的建议是先用 YOLOv5s 把整条链路跑通再考虑换模型。这个系列适合谁看如果你手里有一块 RK3588 或 RK3588S 的开发板想把自己的 PyTorch 模型部署到 NPU 上又不想被官方文档里那些零散的脚本和参数绕晕那这个系列就是写给你的。我会尽量把每个命令、每个配置文件的含义都讲透而不是甩一堆代码让你自己猜。整个系列会涉及 ONNX 导出、RKNN 工具链、INT8 量化、板端 C 推理、性能剖析这几个核心环节每一篇聚焦一个阶段最后串成一条完整的部署流水线。先说结论RK3588 跑 YOLOv5sINT8 量化后在 640×640 输入下单核 NPU 大概能做到 30-40 FPS三核齐开能上到 80 FPS 以上。但这个数字背后有一堆前提条件——模型结构要改、量化校准集要选对、RKNN 版本要匹配、板端内存要调优。这些细节我会在后续文章里逐个拆开讲。这一篇作为开篇主要把整体路线图、硬件选型、软件版本这些地基问题说清楚避免你后面返工。2. 整条部署链路的全景拆解2.1 从 PyTorch 到 NPU 的四个阶段很多人以为部署就是把模型转成 ONNX再转成 RKNN然后跑起来实际上中间有四个明显不同的阶段每个阶段的目标和工具都不一样。第一阶段是模型准备。你在 PyTorch 里训练好的.pt文件需要先导出成 ONNX。这一步看似简单但坑最多——算子版本、动态轴、输出节点命名任何一个不对后面 RKNN 转换就会报错。我的习惯是在导出前先把模型切到 eval 模式固定输入尺寸并且用torch.onnx.export时显式指定opset_version12这个版本对 RKNN 工具链的兼容性最好。第二阶段是模型转换与量化。ONNX 模型通过 RKNN-Toolkit2 转成.rknn格式。这里最关键的是量化——FP32 直接转过去也能跑但速度只有 INT8 的三分之一左右而且内存占用大。INT8 量化需要一份校准数据集通常从训练集里抽 100-300 张图就够了。校准集的选择直接决定量化后的精度损失这个我后面会专门写一篇讲。第三阶段是板端推理。.rknn模型通过 RKNN Runtime 在板子上加载执行。你可以用 Python 接口快速验证也可以用 C 接口做正式部署。Python 接口方便调试但性能有损耗C 接口性能最好但编译和内存管理要自己处理。第四阶段是性能调优。包括 NPU 核心绑定、内存零拷贝、多线程流水线、输入预处理加速等。这一步能把性能再压榨出 30% 以上但需要你对 RK3588 的硬件架构有一定理解。2.2 为什么不能跳过 ONNX 直接转 RKNN有人会问RKNN-Toolkit2 不是支持直接加载 PyTorch 模型吗为什么还要绕一圈 ONNX这个问题我专门试过。RKNN-Toolkit2 确实提供了load_pytorch接口但它对 PyTorch 版本、模型结构、算子实现都有严格限制稍微复杂一点的模型就会加载失败。而且 PyTorch 的算子在不同版本间行为不一致今天能转明天就报错非常不稳定。ONNX 作为中间格式好处是它是一个静态计算图算子定义明确版本可控。你导出一次 ONNX后面无论换什么工具链都能用。更重要的是ONNX 可以用 Netron 可视化你能清楚地看到每个节点的输入输出、算子类型、张量形状排查问题的时候一目了然。所以我的建议是老老实实走 ONNX 这条路别图省事。2.3 硬件与软件版本的选择逻辑RK3588 的 NPU 是瑞芯微自研的 RKNN 架构算力标称 6 TOPS但这是 INT8 的理论峰值。实际能跑出多少取决于模型结构、内存带宽、NPU 驱动版本。我用的板子是 16GB 内存版本NPU 驱动版本是 0.9.6RKNN-Toolkit2 用的是 2.0.0b0。这个组合是我试过最稳定的太新的版本有时候反而有兼容性问题。软件版本这块我要特别提醒RKNN-Toolkit2 的版本必须和板端 RKNN Runtime 的版本匹配。如果你在 PC 上用 2.0.0 转换模型板端 Runtime 是 1.6.0加载的时候大概率会报版本不兼容。所以第一步就是确认板端 Runtime 版本然后去下载对应版本的 Toolkit。查看板端版本的方法很简单cat /sys/kernel/debug/rknpu/version这条命令会输出 NPU 驱动的版本号。然后根据这个版本去瑞芯微的 GitHub 仓库找对应的 RKNN-Toolkit2 版本。别嫌麻烦版本对不上后面全是坑。3. 环境搭建PC 端与板端的双线准备3.1 PC 端 RKNN-Toolkit2 的安装细节RKNN-Toolkit2 跑在 x86 的 Linux 上官方推荐 Ubuntu 20.04 或 22.04。我一开始想用 Windows 的 WSL2结果发现 NPU 模拟器在 WSL 下跑不起来只能老老实实装了个 Ubuntu 双系统。如果你没有 Linux 环境建议直接用 Docker官方提供了镜像省去依赖问题。安装过程本身不复杂但有几个细节容易忽略。第一Python 版本必须是 3.8 到 3.10 之间3.11 以上会有依赖冲突。第二安装完rknn_toolkit2之后要验证一下能不能正常导入from rknn.api import RKNN rknn RKNN() print(rknn.version())如果这行代码报错大概率是 numpy 版本不兼容。RKNN-Toolkit2 对 numpy 版本很敏感我试过 numpy 1.24 会报AttributeError降到 1.21 就正常了。这种依赖问题官方文档里不会写只能自己踩。3.2 板端 Runtime 与驱动的一致性检查板端这边RKNN Runtime 通常已经预装在系统镜像里了。但如果你用的是自己烧的 Ubuntu 20.04 镜像可能需要手动安装。检查方法ls /usr/lib/librknnrt.so如果这个文件存在说明 Runtime 已经装好了。然后确认版本strings /usr/lib/librknnrt.so | grep -i version输出的版本号要和 PC 端 Toolkit 的版本对应。我遇到过板端 Runtime 是 1.5.0PC 端 Toolkit 是 2.0.0 的情况转换出来的模型加载时报RKNN_ERR_MODEL_INVALID折腾了半天才发现是版本问题。所以这一步一定要先做别等模型转完了才发现。3.3 交叉编译工具链的准备如果你打算用 C 做板端推理还需要准备交叉编译工具链。RK3588 是 ARM64 架构官方推荐用aarch64-linux-gnu-gcc。Ubuntu 下安装sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu然后编译 RKNN Runtime 的示例代码时需要指定CMAKE_C_COMPILER和CMAKE_CXX_COMPILER。这里有个坑RKNN Runtime 的库文件分librknnrt.so和librknn_api.so前者是运行时库后者是 API 库链接的时候两个都要加。我一开始只加了librknnrt.so编译能过但运行时报符号找不到查了半天才发现漏了 API 库。4. YOLOv5s 模型导出前的结构改造4.1 为什么原版 YOLOv5s 不能直接转YOLOv5s 的原版结构里有几个算子对 RKNN 不友好。最典型的是Focus层它用切片操作把输入特征图拆成四份再拼接这个操作在 ONNX 里会变成一堆Slice和ConcatRKNN 转换的时候容易出错。YOLOv5 从 v6.0 开始已经把Focus换成了Conv所以如果你用的是新版本这个问题自动解决了。另一个问题是输出层。YOLOv5 的检测头输出是三个不同尺度的特征图每个特征图经过卷积后输出[batch, 3*(5nc), h, w]的张量。这个结构本身没问题但如果你在导出 ONNX 的时候没有固定输出节点RKNN 转换后会多出一些无用的分支。我的做法是在export.py里显式指定--include onnx并且用--grid参数让输出直接是解码后的结果这样板端就不用再做后处理了。4.2 导出 ONNX 的关键参数导出命令看起来简单但参数选不对后面全是麻烦。我用的命令是这样的python export.py --weights yolov5s.pt --include onnx --img 640 --batch 1 --opset 12 --simplify这里有几个点要解释。--img 640是固定输入尺寸RKNN 不支持动态输入所以必须固定。--batch 1也是同理板端推理通常一次只处理一张图。--opset 12是我试过兼容性最好的版本opset 11 有些算子不支持opset 13 又太新。--simplify会用 onnx-simplifier 把模型里的冗余节点去掉这一步能显著减少转换时的报错。导出完成后用 Netron 打开.onnx文件检查三件事输入节点是不是[1, 3, 640, 640]输出节点是不是三个[1, 25200, 85]之类的张量中间有没有奇怪的Constant或Identity节点。如果输出节点名字是output、output1、output2那后面 RKNN 转换的时候要对应上。4.3 输出节点的命名与对齐RKNN 转换的时候需要指定输出节点名称如果名字对不上转换出来的模型输出就是错的。YOLOv5 默认的输出节点名是output、output1、output2但有时候 simplify 之后会变。我的习惯是在导出后手动改一下 ONNX 的输出节点名统一成output0、output1、output2这样后面写代码的时候不容易搞混。改名字可以用 onnx 的 Python APIimport onnx model onnx.load(yolov5s.onnx) for i, output in enumerate(model.graph.output): output.name foutput{i} onnx.save(model, yolov5s_renamed.onnx)这一步看起来多余但等你后面调试的时候会发现输出节点名对不上是最难排查的问题之一因为模型能跑只是结果不对你根本不知道哪里错了。5. 量化校准集的准备与常见误区5.1 校准集不是越多越好INT8 量化的原理是统计每一层激活值的分布然后计算一个缩放因子把 FP32 的数值映射到 INT8 的 [-128, 127] 区间。校准集的作用就是提供这些统计样本。很多人以为校准集越多越好其实不是。RKNN 官方建议 100-300 张图就够了太多反而会导致统计偏差因为不同场景的图片分布差异大混在一起会让缩放因子偏向某个极端。我的做法是从训练集里随机抽 200 张图确保覆盖所有类别然后单独放一个文件夹。校准的时候用dataset.txt列出图片路径每行一个。这里有个细节校准图片的预处理必须和推理时完全一致。如果你推理时用的是 letterbox 填充校准的时候也要用 letterbox否则统计出来的分布和实际推理时的分布对不上量化精度会掉得很厉害。5.2 校准过程中的精度监控RKNN-Toolkit2 在量化的时候会输出每一层的量化误差如果某一层的误差特别大说明这一层的激活值分布太分散可能需要调整。我一般会关注cosine similarity这个指标它衡量量化前后输出的相似度低于 0.99 就说明量化损失比较明显。如果发现精度掉得太多有几个调整方向。第一换校准集选更有代表性的图片。第二调整量化算法RKNN 支持normal和mmse两种mmse精度更高但速度慢一点。第三对特定层做混合量化把敏感层保持 FP16。这些技巧我后面会专门写一篇展开。5.3 量化后的模型验证量化完成后别急着上板子先在 PC 上用 RKNN 的模拟器跑一遍。RKNN-Toolkit2 提供了inference接口可以在 PC 上模拟 NPU 的行为。把量化后的模型加载进去跑几张测试图和 PyTorch 的结果对比一下。如果 mAP 掉得在 1-2 个百分点以内说明量化是成功的。如果掉得太多就要回去检查校准集或者量化参数。这一步很多人会跳过直接上板子跑结果发现精度不对又回来查浪费大量时间。我的建议是PC 端模拟器验证通过之前不要碰板子。模拟器虽然慢但能帮你排除掉大部分量化问题。6. 板端推理的两种路径与选型建议6.1 Python 接口快速验证的首选板端 Python 接口用起来很简单装好rknn_toolkit_lite2之后几行代码就能跑起来from rknnlite.api import RKNNLite rknn RKNNLite() rknn.load_rknn(yolov5s_int8.rknn) rknn.init_runtime(core_maskRKNNLite.NPU_CORE_0) outputs rknn.inference(inputs[img])这个接口适合快速验证模型能不能跑、输出对不对。但它的性能不是最优的因为 Python 和 C 之间的数据拷贝有开销而且 GIL 会限制多线程。我实测下来Python 接口的帧率大概只有 C 接口的 60% 左右。6.2 C 接口正式部署的必经之路C 接口性能最好但代码量也大。核心流程是加载模型、创建输入输出张量、设置 NPU 核心、执行推理、获取结果。RKNN 的 C API 设计得还算清晰但内存管理要自己处理。我建议先用官方示例代码跑通再逐步改成自己的结构。C 接口有一个关键优化点零拷贝。RKNN 支持把输入数据直接映射到 NPU 的内存空间避免 CPU 和 NPU 之间的数据拷贝。这个优化能提升 10-15% 的性能但需要你对内存对齐和缓冲区管理有了解。我后面会专门写一篇讲这个。6.3 多核 NPU 的调度策略RK3588 的 NPU 有三个核心可以单独使用也可以组合使用。core_mask参数控制用哪几个核心NPU_CORE_0单核功耗最低NPU_CORE_0_1双核性能翻倍NPU_CORE_0_1_2三核性能最高但功耗也最大实测下来YOLOv5s 在单核上大概 30-40 FPS三核能到 80 FPS 以上。但三核的调度有开销如果你的应用对延迟敏感单核反而更稳定。我的建议是先用单核跑通再根据实际需求决定要不要开多核。7. 我踩过的几个典型坑与排查思路7.1 模型转换报错Unsupported op这是最常见的问题原因是 ONNX 里有 RKNN 不支持的算子。排查方法是先用 Netron 找到报错的算子然后看能不能用等价算子替换。比如Hardswish在旧版本 RKNN 里不支持可以换成ReLU6或者SiLU。YOLOv5s 里主要涉及的是SiLU激活函数RKNN 2.0 以上版本已经支持了但如果你用的是 1.6 版本就需要把SiLU换成ReLU。7.2 推理结果全是乱码这个问题通常是输出节点对不上导致的。检查方法是在 PC 模拟器里跑一遍看输出张量的形状和数值范围。如果形状不对说明输出节点选错了如果数值范围不对说明量化有问题。我遇到过一次是输出节点名字写错了模型能跑但输出是随机的查了一下午才发现。7.3 板端加载模型报RKNN_ERR_MODEL_INVALID这个错误 90% 是版本不匹配。板端 Runtime 版本和 PC 端 Toolkit 版本必须一致差一个小版本都可能出问题。解决方法就是前面说的先查板端版本再下载对应的 Toolkit。别想着用新版本 Toolkit 转的模型去旧版本 Runtime 上跑大概率不行。7.4 帧率远低于预期如果帧率只有个位数先检查是不是用了 Python 接口换成 C 试试。如果还是慢检查 NPU 核心有没有正确设置core_mask是不是设成了单核。另外输入图片的预处理如果放在 CPU 上做也会拖慢整体速度。我的做法是把 resize 和归一化也放到 NPU 上用 RKNN 的预处理接口能省不少时间。8. 这个系列后续会写什么这篇开篇主要把整体路线和环境准备说清楚了。接下来我计划按这个顺序展开第二篇讲 ONNX 导出的完整流程和常见报错处理第三篇讲 INT8 量化的原理、校准集选择和精度调优第四篇讲 C 板端推理的完整代码实现和零拷贝优化第五篇讲多核调度和性能剖析包括怎么用perf和rknn_query看 NPU 利用率。如果时间允许还会加一篇讲 YOLOv8 在 RK3588 上的部署差异毕竟现在 YOLOv8 的呼声很高。最后分享一个我自己的习惯每次转换模型之前先把版本号、命令、参数、报错信息记在一个 markdown 文件里。部署这件事版本和参数的组合太多了今天能跑通的配置过两个月你肯定记不清。有个记录下次遇到同样的问题翻一下就能解决。这个系列的文章本质上就是我的这份记录整理出来的希望能帮你少走点弯路。
返回列表