YOLOv11 部署到 Rockchip NPU 完整指南:ONNX 转换与 RKNN 量化实战
2026/9/23 12:16:28 网站建设 项目流程

简介:面向边缘智能设备部署需求,这款工具包围绕YOLOv11目标检测模型,打通了从模型导出到芯片端运行的完整链路。重点解决在瑞芯微平台上将开放神经网络交换格式模型转换为瑞芯微神经网络工具包模型的问题,内容覆盖环境配置指南、量化选项与推理加速建议,适合嵌入式视觉开发者及算法工程落地人员。压缩包共十五个文件,包括转换程序脚本、模型文件、测试图片、结果对比图、说明文档与使用手册,模型与文档分类清晰,整体体积约十四点二一兆字节。目前已有六十九人学习下载。资料非常注重实战,从环境准备、依赖安装到调用转换工具,每一步都配有界面截图和文字说明,能够显著降低入门门槛。量化部分给出了多种选项对比,帮助在精度与速度间取舍;附带的测试结果图片可以直观检验转换效果。对于需要在瑞芯微芯片上完成目标检测项目开发的工程师,这份资料提供了可复用的操作范式和排错思路。

1. 把 YOLOv11 塞进 Rockchip NPU:这条路到底卡在哪

我去年手头有个边缘巡检项目,模型用 YOLOv11 训练完,在服务器上 mAP 看着挺漂亮,一搬到 RK3588 上跑纯 CPU 推理,单帧 700 多毫秒,直接把客户劝退。后来把模型导出成 ONNX,再走 rknn-toolkit2 转成 RKNN 格式,配合 NPU 和 int8 量化,单帧掉到 30 毫秒以内——这个量级才是 Rockchip 部署该有的样子。这个标题给的工具,实际上就是把 YOLOv11 从 PyTorch 训练环境一路搬到 Rockchip 板子上的完整链路:训练好的权重导出 ONNX,ONNX 转 RKNN,期间还要解决环境配置、量化选项、精度损失和各种奇怪的算子报错。它不是那种"点一下生成"的黑匣子,而是需要你理解每一步在干什么、每个参数改了什么。适合谁看?准备把检测模型部署到 RK3588、RK3566 这类板子的嵌入式工程师,或者被"模型能跑但跑不动"折磨过的算法工程师。这套流程踩坑不少,但走通之后,后面换模型换板子都是顺水推舟的事。

2. 从 YOLOv11 导出 ONNX:转换链路的地基,导出格式决定后面顺不顺

2.1 为什么不能直接拿 PyTorch 权重转 RKNN,非要中间过一道 ONNX

很多第一次接触 Rockchip 部署的人,第一反应是"rknn-toolkit2 能不能直接吃 .pt 文件"。官方工具链里确实有 load_pytorch 这类入口,但实际用下来,我强烈建议你老老实实走 ONNX 中转。原因不复杂:rknn-toolkit2 对 ONNX 的算子覆盖最全,对 PyTorch 前向图的解析能力弱不少,尤其是 YOLOv11 里那些自定义模块、动态 shape、以及训练时带进来的无关节点,直接 load 容易在某个不起眼的算子上翻车,报错信息还看不懂。ONNX 相当于是把 PyTorch 动态图固化成了静态计算图,所有张量的 shape 和算子类型都明确,RKNN 解析起来省事,出问题也好定位——它报哪个算子不支持,你至少能在 Netron 里打开 ONNX 看到那个节点长什么样。

另外,ONNX 是一个跨平台载体,今天你转 RKNN,明天想试试 NCNN、OpenVINO 或者 TensorRT,同一份 ONNX 都能复用。我自己习惯的做法是:PyTorch 权重只作为"母版",任何部署格式都从 ONNX 派生的那份出发,这样模型在哪个平台上的行为差异,就能定位到转换工具本身,而不是源头就错了。

2.2 最小导出命令:用 YOLOv11 官方权重导出 ONNX 的三个关键开关

常见的做法是直接用 ultralytics 库的 export 接口导出,不建议自己手写 torch.onnx.export,因为 YOLOv11 的检测头里有不少细节,比如多尺度输出、anchor-free 解码逻辑,手写容易漏。最小可用的导出脚本如下:

from ultralytics import YOLO # 加载训练好的权重,注意用训练完的 best.pt,而不是 last.pt model = YOLO("runs/detect/train/weights/best.pt") # 导出 ONNX model.export( format="onnx", # 导出格式 imgsz=640, # 输入尺寸,必须与训练时一致 opset=12, # ONNX opset 版本,RKNN 对 12 支持最稳 simplify=True, # 用 onnxsim 简化计算图 dynamic=False, # 固定静态 shape,RKNN 对动态 shape 支持有限 half=False, # 导出 FP32,量化放到 RKNN 阶段做 )

这段代码里我标了几个关键参数,逐个说明。imgsz 必须和训练时保持一致,如果训练用了 640,导出也必须是 640,后面转 RKNN 时还要再填一次,三处不一致就会出幺蛾子。opset 我固定给 12,别看 ultralytics 支持更新版本,rknn-toolkit2 对 opset 13 以上的某些算子解析有历史遗留问题,opset 12 是保守但稳的选择。simplify 建议打开,它会把计算图中的冗余节点折叠掉,比如同一算子重复出现、shape 操作链过长,这些在 RKNN 转换时都可能变成"不支持"的报错来源。dynamic 必须设成 False,Rockchip NPU 的输入是固定形状的,转出来就是死 shape,动态 shape 就算转成功,板子上也没法高效跑。half 这里先不开,int8 量化的活儿统一放到 RKNN 转换阶段,导出时保持 FP32 精度最保险。

2.3 导出后必须做的一次体检:用 onnxruntime 验证 ONNX 输出和 PyTorch 输出一致

导出成功不等于导出正确。我在 RKNN 转换上栽过最大的跟头,就是 ONNX 本身就导错了,结果后面所有排查都走偏。所以导出 ONNX 之后,我习惯先用 onnxruntime 跑一遍推理,拿同一张测试图跟 PyTorch 模型的输出对比,确认差异在可接受范围内再继续。

import onnxruntime as ort import numpy as np import cv2 from ultralytics import YOLO # 用 PyTorch 模型跑一次 pt_model = YOLO("runs/detect/train/weights/best.pt") img = cv2.imread("test.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results_pt = pt_model(img_rgb, imgsz=640, verbose=False) # 用 ONNX 模型跑一次 sess = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) input_name = sess.get_inputs()[0].name input_shape = sess.get_inputs()[0].shape # 预处理与 ultralytics 保持一致:letterbox + 归一化 from ultralytics.data.augment import LetterBox lb = LetterBox((640, 640), auto=True, stride=32) img_letterbox = lb(image=img_rgb) img_norm = img_letterbox.astype(np.float32) / 255.0 img_tensor = np.transpose(img_norm, (2, 0, 1))[None, ...] outputs_onnx = sess.run(None, {input_name: img_tensor}) # 对比主要输出 shape 和数值范围 print("PyTorch 输出形状:", len(results_pt[0].boxes)) print("ONNX 输出形状:", [o.shape for o in outputs_onnx])

这段脚本的核心逻辑是:同一张图分别喂给 PyTorch 模型和 ONNX 模型,对比检测结果的数量和整体置信度分布。注意 ultralytics 内部的预处理是 letterbox + 归一化到 0~1,你手动用 onnxruntime 跑的时候必须复现这套预处理,否则数值对不上,不是模型转换的问题,是你输入的问题。我一般只看两个指标:检测框数量是否一致,以及最高置信度是否在同一个量级。对比理想情况是检测框数量完全相同,置信度差异在 1e-3 量级以内。如果框数量都对不上,那不用往下走了,回头检查导出参数,多半是 imgsz 或 opset 出了问题。

3. rknn-toolkit2 环境配置:Python 版本、依赖冲突和校准集,一个都不能少

3.1 版本矩阵先行:Python 版本和 rknn-toolkit2 版本怎么对齐

rknn-toolkit2 这工具说好听点叫"对版本敏感",说难听点就是版本强迫症。我见过太多人在环境配置这一步就放弃了,报错信息五花八门,但根因往往就一个:Python 版本不兼容。当前 Rockchip 官方推荐的 rknn-toolkit2 是 1.x 系列,它要求 Python 版本通常落在 3.8 到 3.10 之间,3.11 以上大概率装不上或者运行时报错。另外它依赖的 onnx、numpy 版本也有讲究,onnx 不能装太新,1.13 到 1.15 之间比较稳,numpy 不要超过 1.24,否则会有编译兼容问题。

我一般用 conda 单独建一个环境,跟项目开发环境隔离,避免 rknn-toolkit2 的依赖把训练环境的包搞乱了。建环境的完整流程如下:

# 创建 Python 3.8 环境,rknn-toolkit2 目前最稳的版本 conda create -n rknn_env python=3.8 conda activate rknn_env # 安装 rknn-toolkit2 核心包,注意指定版本号 pip install rknn-toolkit2==1.5.2 -i https://pypi.org/simple # 锁定关键依赖版本,防止 pip 自动升级 pip install onnx==1.14.1 pip install numpy==1.24.3 pip install onnxruntime==1.15.1 pip install opencv-python==4.8.1.78 # 验证安装 python -c "from rknn.api import RKNN; print('rknn-toolkit2 OK')"

版本问题比很多新手想得更致命。rknn-toolkit2 1.5.2 和 1.6.0 的 API 行为就可能有差异,比如某些版本的 build 接口多了参数,某些版本的 load_onnx 对输入 shape 的处理方式不同。我自己的原则是:选定一个版本组合之后,用 conda env export 把环境导出一份 yaml 存档,下次换机器直接 conda env create -f,不要每次都裸 pip install。另外强调一下,rknn-toolkit2 本身只负责在 PC 上完成模型转换和模拟推理,它不部署到板子上,板子上真正跑的是 RKNN Runtime 的 C 库。所以你 PC 上装的 rknn-toolkit2 只要能用就行,和板子上的版本不必完全一致,但建议尽量对齐,后面 5.4 我会专门讲版本不匹配的坑。

3.2 校准集准备:int8 量化的弹药库不是随便找几张图就完事

RKNN 的 int8 量化,简单说就是根据一个输入样本集,统计每一层激活值的数值分布,然后把 FP32 的浮点权重映射到 int8 的 256 个整数值。这个样本集,也就是校准集的质量,直接决定量化后模型的精度保留程度。这个环节没有硬性保证:校准集不够代表真实部署场景,模型 quantize 完看起来跑得欢,一上真实场景检测框就开始乱飘。

准备校准集我有几个硬性要求。第一,数量在 50 到 200 张之间,太少统计出来的分布不稳定,太多转换时间成倍增加,收益不显著。第二,必须覆盖所有典型场景,比如户外巡检模型,白天、逆光、黄昏、雨雾各占一部分,不要全是同一时段拍的。第三,尽量避免用训练集的原图做校准集——模型在训练集上过拟合了,激活值分布会偏低偏差,不代表真实推理时的分布。我的习惯是从验证集里随机抽一部分,再混入少量没见过的实拍图。第四,图片尺寸统一缩放到模型的输入尺寸 640x640,不需要做精细的预处理,rknn-toolkit2 会在 build 时根据你配置的 mean/std 做归一化。

还有一个容易被忽略的点:校准集不要用压缩率过高的 JPEG。压缩伪影会改变高频区域的像素分布,进而影响激活值的统计,虽然影响幅度不大,但量化本身就在刀尖上跳舞,能少一个误差源就少一个。

3.3 在 PC 上模拟推理:先把模型跑通了再上板,省一半调试时间

rknn-toolkit2 提供了一套 PC 端模拟推理能力,不需要板子也能看到 RKNN 模型对一张输入图给出的推理结果。这个能力在开发阶段极其好用,因为板子调试要搭交叉编译环境、传文件、看日志,一轮循环十几分钟,而在 PC 上模拟只需要几秒钟。我最常干的事情是:先转换 RKNN,然后立刻在 PC 上做模拟推理,把检测框画出来看一眼,如果这一步就错了,那都不用上板。

import numpy as np import cv2 from rknn.api import RKNN rknn = RKNN() # 加载 RKNN 模型 rknn.load_rknn("yolov11.rknn") # 初始化运行环境,target 为 None 表示 PC 模拟推理 ret = rknn.init_runtime(target=None) assert ret == 0, f"init_runtime failed: {ret}" # 读取并预处理图片 img = cv2.imread("test.jpg") img = cv2.resize(img, (640, 640)) # 模拟推理 outputs = rknn.inference(inputs=[img]) print(f"Inference done, output tensors: {len(outputs)}") for i, out in enumerate(outputs): print(f"Output {i}: shape={out.shape}, dtype={out.dtype}, range=[{out.min():.4f}, {out.max():.4f}]") # 记得释放资源 rknn.release()

模拟推理这一步能确认两件事:一是 RKNN 模型本身没转坏,输出数值的范围、形状和 ONNX 输出对得上;二是推理速度可以大致评估,虽然 PC 模拟跑的是 x86 的 NPU 模拟器,和板子真实 AI 性能有关系但不完全等价,但数量级上的问题能提前暴露,比如模型某些算子导致推理极慢的情况,PC 上也会有体现。我用这个流程排过很多问题,强烈建议养成"转换完先模拟推理再上板"的习惯,血泪经验告诉我,跳过这个阶段直接上板的项目,往往会在板子上多花几倍时间。

4. ONNX 转 RKNN 核心流程:量化选项、目标平台和导出形态一次说透

4.1 完整转换脚本:从 load_onnx 到 export_rknn 的每一步在干什么

环境搭好、ONNX 验证通过、校准集备好之后,就可以正式执行转换。下面是一份我用来最顺手的转换脚本骨架,注释里写清楚了每个参数的含义:

import numpy as np from rknn.api import RKNN # 1. 创建 RKNN 对象 rknn = RKNN() # 2. 配置模型输入和量化参数 ret = rknn.config( mean_values=[[0, 0, 0]], # 均值,YOLOv11 官方训练时用的是 0~1 归一化,所以均值填 0 std_values=[[255, 255, 255]], # 标准差,除以 255 是归一化关键 target_platform="rk3588", # 目标平台,后续板子的 NPU 架构 quantized_dtype="w8a8", # int8 量化,权重和激活都量化为 8bit quantized_algorithm="normal", # 量化算法,normal 是融合量化,min/max 是逐层量化 quantized_method="layer", # 量化粒度,可选 layer 或 channel,推荐 channel ) assert ret == 0, f"config failed: {ret}" # 3. 加载 ONNX 模型 ret = rknn.load_onnx( model="yolov11.onnx", input_size_list=[[1, 3, 640, 640]], # NCHW 格式,必须与导出时一致 ) assert ret == 0, f"load_onnx failed: {ret}" # 4. 构建 RKNN 模型,这一步会执行量化 ret = rknn.build( do_quantization=True, # 打开量化开关 dataset="./calib_dataset.txt", # 校准集文件列表 pre_compile=False, # 预编译,False 表示生成通用 RKNN,跨板型兼容 ) assert ret == 0, f"build failed: {ret}" # 5. 导出 RKNN 模型文件 ret = rknn.export_rknn("yolov11.rknn") assert ret == 0, f"export_rknn failed: {ret}" # 6. 释放资源 rknn.release()

整个脚本分六步,每一步都有明确职责。config 阶段是给 RKNN 编译器"交代背景":输入图片的像素范围是多少、目标是哪颗芯片、量化到什么精度。这里最容易出错的是 mean_values 和 std_values,很多人直接把训练时的归一化参数抄过来,但 YOLOv11 官方训练用的归一化就是除以 255,并不需要额外减均值,所以 mean 填 0、std 填 255 才是对的。如果训练时用了自定义的归一化,比如均值 [0.485, 0.456, 0.406],那么在这里也要对应填,推理时 RKNN 会自动执行预处理,不需要自己在板上再写一次。

load_onnx 阶段最关键的是 input_size_list,虽然 ONNX 图里本身有 shape 信息,但 rknn-toolkit2 仍然要求显式声明。这个列表的形式是 NCHW,N 固定为 1,C 是 3,H 和 W 与 imgsz 一致。如果导出 ONNX 时已经用动态 shape,这里填的 shape 就是后续模型运行时的固定输入尺寸,后面想改只能重新转换,没有后悔药。

4.2 量化选项对照:什么时候选 normal、什么时候选 min/max,什么时候干脆不量化

量化是 RKNN 转换里影响最大也最需要理解的一个环节。rknn-toolkit2 提供了 quantized_algorithm="normal" 和 "min/max" 两种量化算法,它们的差异在于如何确定每层激活值和权重的数值范围。normal 算法会综合多张校准图的统计信息,做一个全局优化,对精度保留更友好;min/max 则是对每个张量单独取最小值和最大值,计算量大一些,但对分布不均匀的层更精确。

用表格总结一下不同场景的推荐选择:

量化选项精度表现推理速度推荐场景
不量化(FP16)最接近原模型较 int8 慢 20%-30%精度敏感、模型本身很小
normal + layer 粒度中等大多数检测模型首选项
normal + channel 粒度较好略慢分布差异大的深度模型
min/max + channel最好最慢小模型、对精度损失零容忍

我自己的经验是:对 YOLOv11 这类检测模型,先试 normal + layer,这是转换速度最快也最不容易出岔子组合,跑完用验证集测 mAP。如果 mAP 掉点超过 2%,换成 normal + channel 威力加强;还是掉,再换 min/max + channel。一般 YOLOv11 在 int8 量化下 mAP 掉点在 0.5% 到 2% 之间属于正常范围,超过这个范围就要检查校准集或模型本身了。另外有两种情况我会选择不量化:一是模型在板子上推理时间已经满足需求,不需要压榨极致性能;二是模型的输出层数值范围异常宽,量化后置信度会明显偏低。在这种情况下,用 FP16 导出反而是更省事的选择,反正 Rockchip NPU 也支持 FP16 推理。

4.3 预编译开关 pre_compile 的取舍:跨板型兼容性和极致性能怎么平衡

build 阶段的 pre_compile 参数最容易被忽略,但它的影响很隐蔽。简单说,pre_compile=False 生成的是中间表示,板子上的 RKNN Runtime 在加载模型时再针对具体芯片做编译优化;pre_compile=True 则是在 PC 转换阶段就把针对 target_platform 的底层优化做完,板子上加载速度更快,推理性能也略好,代价是生成的 .rknn 文件绑定了特定芯片平台,不能跨板型使用。

标题里提到适用于 Rockchip 芯片部署,所以大部分人会在 rk3568 和 rk3588 之间纠结。我个人的习惯是:如果项目用到的板型固定,比如已经确定量产用 RK3588,那 pre_compile 直接开 True,能榨出一点性能是一点;如果还在评估阶段,手头有 RK3588 实验板、RK3576 评估板,那就先 False,让同一份 .rknn 在两种板子上都能跑,等确定了最终硬件再重新转一份开预编译的版本。

4.4 转换后的产物确认:rknn 文件、权重文件和推理日志分别代表什么

转换完成后你会看到一系列产物,除了主产物 .rknn 文件之外,build 过程还会生成一些中间文件。常见文件包括:

文件作用后续是否需要
yolov11.rknn最终交付的部署模型是,拷贝到板子
.weight 文件量化后的权重单独存储仅当 export 时选了"分离权重"模式
log/ 目录转换日志和算子映射报告排错时参考
中间失败快照构建失败时的图信息仅排错时使用

这里需要特意说明一个常见翻车点:rknn.build 的 log 里会显示每个算子的映射情况,如果某个算子被标记为 "NOT SUPPORT" 或者 "FALLBACK",说明该算子没有跑到 NPU 上,而是回退到了 CPU。这种情况模型能转成功、能推理,但性能会大打折扣,YOLOv11 里如果 head 部分有自定义算子未被 NPU 支持,整体推理时间可能翻 3 到 5 倍。所以转换完我必做的一件事是:打开 log 目录下的 performance 相关日志,用 grep 搜一下 "NOT SUPPORT" 和 "CPU" 字样,确认所有算子都映射到了 NPU。这一步能省掉后面在板子上抓性能瓶颈的大量时间。

5. ONNX 转 RKNN 的 5 个高频翻车点:现象、根因和解决路径

5.1 转换时报 Unknown layer / Unsupported operator,YOLOv11 里的算子 RKNN 不认

现象:rknn.build 执行到一半,直接抛出一串类似 "Unknown layer xxx" 或 "Not supported operator: xxx" 的错误,常见出场角色是 HardSigmoid、GridSample、自定义 C2f 模块里的某些操作。

根因:YOLOv11 的网络结构比老版本复杂,引入了新的模块设计,而 rknn-toolkit2 对 ONNX 算子的支持列表是有限的,某些算子只出现在训练图里,对推理没有实际作用,但也有个别算子确实承担了核心计算。

解决路径:第一步,先用 onnxsim 已经简化过模型,排掉冗余算子。第二步,用 Netron 打开 ONNX 图,定位报错算子的位置,看它在哪个模块里。绝大多数情况下,报错发生在模型输出端的后处理部分,比如 DFL 解码或 NMS 相关结构,这些算子在 RKNN 侧都有等价实现,可以直接修改 ONNX 图,把不被支持的节点替换成 RKNN 支持的算子组合,比如把循环展开成矩阵运算。第三步,如果替换不了,只能调整导出方式,比如把 YOLOv11 的 detect head 拆开,只保留 backbone 和 neck 的导出,后处理放到板子的 C 代码里写。这一步操作量不小,但比陷入"算子不支持"的死胡同要有效得多。

5.2 int8 量化后模型精度崩了,检测框全在乱飘

现象:量化之前 mAP 有 0.85,量化之后掉到 0.6 甚至更低,检测框位置偏移明显,置信度普遍低于正常水平。

根因:优先级最高的是校准集与真实场景分布不一致。比如巡检项目校准集全是顺光环境,部署时碰到逆光场景就开始乱飘;其次是校准集数量太少,统计出来的激活值范围不具代表性;最后是某些层对量化极其敏感,比如检测 head 里的最后一层卷积,权重分布范围本来就窄,量化后信息丢失得最狠。

解决路径:先把校准集扩充到 100 张以上,确保覆盖所有典型场景;再用 4.2 的选项对比表,换成 channel 粒度量化;如果还不行,做混合量化——用 rknn.config 里的 custom_quantize_layers 参数指定某些层跳过量化、保持 FP16,通常优先保护 head 的最后一两层卷积。这个方法能让精度恢复大半,代价是这些层推理稍慢。

5.3 模拟推理结果全是零或 NaN,模型加载成功但输出不可用

现象:rknn.inference 正常返回,但 outputs 数组里面要么全是 0,要么是 nan,检测框一支都画不出来。

根因:常见原因有两个。第一个,config 阶段的 mean_values 和 std_values 写反了,输入图片进模型前被处理成了错误数值,激活值全部饱和或者归零。第二个,图片预处理尺寸和 input_size_list 不一致,比如脚本里 resize 成了 480x480,而 build 时声明的是 640x640,导致张量对齐错乱,算出来的结果就是垃圾。

解决路径:先用一张全黑图和一张全白图跑推理,观察输出数值范围,这能判断输入预处理是否正确;再打印一下送入 RKNN 的图片数组的 shape 和 dtype,确认是 uint8 还是 float32。排查顺序是预处理 -> config -> build,不要一上来就怀疑模型转换坏了。这是最容易自查的问题,但也很容易在忙乱中被忽略,建议每一步都打印关键张量的 shape。

5.4 板子上加载模型报版本错误,换个设备就起不来

现象:在 PC 上模拟推理一切正常,把 .rknn 文件拷到板子上,rknn_init 直接返回错误码,板子端 log 提示版本不匹配。

根因:rknn-toolkit2 转换出的模型内部带有版本信息,板子上运行的 librknnrt.so(RKNN Runtime 库)也有自己的版本号,两者不兼容时加载就失败。常见场景包括:PC 上装了新版 rknn-toolkit2 1.6.0,板子系统里的 RKNN Runtime 还是 1.4.0 时代的旧库。

解决路径:板子上先执行 rknn_server 或查询 librknnrt.so 的版本号,确定板端 runtime 版本;然后 PC 端安装对应版本的 rknn-toolkit2,重新转换一份模型。这条规则我把它当铁律:换板子前先查两端版本,PC 端 rknn-toolkit2 版本 >= 板端 RKNN Runtime 版本,但不要跨大版本。项目文档里固定记录两个环境的版本号,能省掉很多玄学排查时间。

5.5 模型转换成功、推理正常,但比预期慢 3 倍以上

现象:板端推理时间明显不达标,比如 RK3588 上 YOLOv11 预期 20 毫秒,实际跑了 60 毫秒甚至更多。

根因:最常见的是算子回退到 CPU 执行。前面 4.4 提过,log 里会标记哪些算子没有映射到 NPU,如果你没看 log 就直接部署,性能问题早埋下了。第二个常见原因是模型输入尺寸不是 NPU 友好尺寸,比如用了 640x672 这种不能被 16 整除的尺寸,NPU 做 padding 补齐时产生额外开销。

解决路径:打开转换时的 log 目录,搜索 "NOT SUPPORT" 或 "CPU Partition" 关键字,逐条解决回退的算子;同时检查输入尺寸是否能被 16 整除,YOLOv11 输入尺寸通常选 640 或 320,都能被 32 整除,问题不大,但只要改过就确认一下。第三个容易忽略的点是批量大小,板端推理时 rknn_inputs 的 batch 设置成 1 不会错,但如果模型本身支持 batch > 1,用 2 或 4 的 batch 往往能提升 NPU 吞吐,代价是增加延迟,需要权衡。通常我会把性能日志里耗时排名前 10 的层拉出来看一遍,心里就对瓶颈有数了。

6. 上板前的最后一道工序:推理速度验证、批处理调优和端到端自检清单

模型转好、量化调完、避坑踩完,最后这道工序是上板前的验证闭环。我每次上板前都做三件事:第一步,写一个最小推理脚本,在板子上连续跑 100 帧,统计平均耗时,特别注意跳过前 5 帧的预热时间——NPU 首次推理会加载模型到内存,这个时间和正常推理差一个量级,统计进去会显得板子性能很差,但实际是自欺欺人。第二步,用不同的 batch 做一轮吞吐测试,看 batch 从 1 调到 2 甚至 4 时,总耗时变化多少、平均每帧耗时是否下降。Rockchip NPU 对批处理的支持是实打实的,batch=1 时固定开销占比高,batch=4 时某些层可以并行计算,每帧平均耗时常常能再降 20% 左右,但这跟具体模型结构有关,所以必须实测。第三步,把检测输出跟 ONNX 推理的结果叠在一起画图——回传的检测框画出来的覆盖情况,直接告诉你量化后模型在真实场景下的行为漂移有多大。

我在 RKNN 转换上还有一个习惯性的检查:每次转换完,都留一份转换时的 config 参数和校准集清单,附在项目文档里。这样做最大的好处是,三个月后模型要迭代一版,重新转 RKNN 时能精确复现上一次的环境、参数和校准集,对比新旧模型的精度差异才横得起来。工具链版本变了、校准集换了、量化算法动了,任何一个变量都可能影响最终模型,没有记录就只能靠回忆排错,那是很折磨人的。希望这个从导出到部署的完整链路能帮你在 Rockchip 平台少走几趟弯路,把精力留在真正影响业务的调优上。

本文还有配套的精品资源,点击获取

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

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

立即咨询