Ultralytics RKNNBackend 详解:在瑞芯微 NPU 上运行 YOLO 推理(.rknn 模型)
【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics
本文围绕 Ultralytics 仓库中 RKNNBackend 的 API 参考展开,逐方法解读RKNNBackend的load_model与forward实现,并结合 ultralytics/nn/backends/rknn.py、ultralytics/nn/backends/base.py 与 ultralytics/nn/autobackend.py 的源码,说明.rknn模型如何在 Rockchip(RK3588、RK3566 等)NPU 上完成加载、推理与元数据恢复,以及 INT8 量化模型的坐标还原细节,帮助你在 Rockchip 边缘设备上正确部署和调试 YOLO 推理链路。
RKNNBackend 类概览
RKNNBackend定义在 ultralytics/nn/backends/rknn.py 中,类文档字符串对其职责的表述是:
Rockchip RKNN inference backend for Rockchip NPU hardware. Loads and runs inference with RKNN models (.rknn files) using the RKNN-Toolkit-Lite2 runtime. Only supported on Rockchip devices with NPU hardware (e.g., RK3588, RK3566).
由此可以提炼出三条关键事实:
- 它只负责运行已导出的
.rknn模型,不负责模型转换(转换由rknn-toolkit2在 PC 侧完成,导出流程见 docs/en/integrations/rockchip-rknn.md); - 运行依赖RKNN-Toolkit-Lite2运行时(
rknnlite.api.RKNNLite),而非桌面端的rknn-toolkit2; - 只能在带 NPU 的 Rockchip 设备上运行(如 RK3588、RK3566),在 x86 PC 上会直接抛异常。
类结构上,它继承自 BaseBackend,后者是一个抽象基类,统一约定了所有推理后端必须实现的两个方法:
load_model(weight):从权重文件或模型目录加载推理运行时;forward(im):对输入张量执行一次前向推理。
BaseBackend.__init__会初始化一组公共属性(stride=32、names、task、batch=1、channels=3、end2end=False、metadata等),并在构造末尾自动调用load_model(weight),因此只要AutoBackend能路由到RKNNBackend,加载过程就会自动发生,无需用户显式调用。
运行环境前提:Rockchip 设备检测
load_model的第一步是环境自检(rknn.py L32-L33):
if not is_rockchip(): raise OSError("RKNN inference is only supported on Rockchip devices.")is_rockchip()实现于 ultralytics/utils/checks.py L1140-L1156,其判定逻辑是:
- 仅在Linux + ARM64环境下尝试检测(
LINUX and ARM64); - 读取设备树节点
/proc/device-tree/compatible,取最后一个逗号分隔的 SoC 标识,去掉空字符后取-之前的主型号; - 判断该主型号是否落在白名单
RKNN_CHIPS内。
白名单定义在 ultralytics/utils/init.py L84-L98:
RKNN_CHIPS = frozenset( { "rk3588", "rk3576", "rk3566", "rk3568", "rk3562", "rv1103", "rv1106", "rv1103b", "rv1106b", "rk2118", "rv1126b", } ) # Rockchip processors available for export也就是说,RKNNBackend支持的设备覆盖 RK35 系列主流芯片以及 RV11 系列工业级芯片;不在这份集合中的 Rockchip 芯片(哪怕有 NPU)会在设备检测阶段被拒绝,这是一个硬性的能力边界。
load_model:模型定位、运行时初始化与元数据恢复
通过设备检查后,load_model(rknn.py L22-L52)依次完成四步:
def load_model(self, weight: str | Path) -> None: if not is_rockchip(): raise OSError("RKNN inference is only supported on Rockchip devices.") LOGGER.info(f"Loading {weight} for RKNN inference...") check_requirements("rknn-toolkit-lite2") from rknnlite.api import RKNNLite w = Path(weight) if not w.is_file(): w = next(w.rglob("*.rknn")) self.model = RKNNLite() ret = self.model.load_rknn(str(w)) if ret != 0: raise RuntimeError(f"Failed to load RKNN model: {ret}") ret = self.model.init_runtime() if ret != 0: raise RuntimeError(f"Failed to init RKNN runtime: {ret}") self.apply_metadata(self.read_metadata(w))1. 依赖检查与延迟导入。check_requirements("rknn-toolkit-lite2")会在运行时自动校验(必要时安装)rknn-toolkit-lite2包;from rknnlite.api import RKNNLite是延迟导入,保证未安装该包的 PC 环境导入 Ultralytics 本身不会失败。
2. 权重路径解析:文件即目录皆可。入参weight既可以是.rknn文件路径,也可以是包含模型的目录。当传入的是目录(非文件)时,next(w.rglob("*.rknn"))会在目录树中递归找到第一个.rknn文件。这正好对接 Ultralytics 的导出产物约定:RKNN 导出的命名约定是yolo26n_rknn_model/目录(见 ultralytics/nn/autobackend.py L120 的格式表RKNN | *_rknn_model/),目录内同时包含<模型名>-<平台>.rknn与导出时写入的metadata.yaml。
3. 两步初始化并严格校验返回码。RKNN Lite 的load_rknn()(解析模型)与init_runtime()(初始化目标硬件运行时)各自返回状态码,任何一步非零都会抛出带状态码的RuntimeError,便于在设备上定位是“模型文件不合法”还是“NPU 运行时不可用”(例如设备缺少 RKNN 驱动时后者会失败)。
4. 元数据恢复。最后调用self.apply_metadata(self.read_metadata(w))。由于.rknn是二进制格式,BaseBackend.read_metadata(base.py L152-L192)走的是旁路 sidecar 文件分支:它在模型所在目录(或其上级目录)寻找metadata.yaml并解析。该文件由导出侧写入,参见 ultralytics/utils/export/rknn.py L82-L84:
if metadata: YAML.save(output_dir / "metadata.yaml", metadata)forward:输入格式约束与 NPU 推理
forward(rknn.py L54-L81)的文档字符串明确了输入约定:
im (torch.Tensor): Input image tensor inBHWC format, normalized to [0, 1].
方法体先做张量到运行时输入格式的转换:
h, w = im.shape[1:3] im = (im.cpu().numpy() * 255).astype("uint8") im = im if isinstance(im, (list, tuple)) else [im] y = self.model.inference(inputs=im)要点:
- BHWC + uint8:RKNN NPU 的推理输入是 HxWxC 的 8 位无符号整数,
[0,1]浮点张量乘以 255 后转为uint8;这与 NCHW/FP32 的 PyTorch、ONNX 后端形成鲜明对比。AutoBackend中通过self.nhwc = format in {"coreml", "saved_model", "pb", "edgetpu", "rknn"}(autobackend.py L263)标记了这一点,预测管线的预处理会据此在送入后端前完成通道布局转换; - 归一化由导出配置承担:导出侧
onnx2rknn固定写入了config = {"mean_values": [[0, 0, 0]], "std_values": [[255, 255, 255]], "target_platform": name}(export/rknn.py L74),即“除以 255”的归一化被烘焙进了 RKNN 模型本身,所以推理端只需喂原始像素值即可,两侧严格互补; - 批量输入:
inputs=im接受列表,RKNNLite.inference可对列表中的多帧做批量推理(批量能力取决于导出时的batch参数)。
INT8 量化模型的坐标还原
forward中最值得关注的是一段针对 INT8 导出的后处理(rknn.py L67-L81):
# INT8 exports use input-relative coordinates so a single per-tensor scale preserves class scores. if ( self.metadata.get("args", {}).get("quantize") == 8 and self.task in {"detect", "segment", "pose", "obb"} and not self.end2end ): kpt_start = 4 + len(self.names) # pose keypoints follow the box (4) and class-score (nc) channels for x in y: if x.ndim == 3: x[:, [0, 2]] *= w x[:, [1, 3]] *= h if self.task == "pose": x[:, kpt_start::3] *= w x[:, kpt_start + 1 :: 3] *= h这段逻辑的触发条件有三个,且都来自导出时嵌入的元数据(metadata.yaml→apply_metadata恢复为实例属性):
args.quantize == 8:模型是 INT8 量化导出的;- 任务属于
detect / segment / pose / obb:这些任务的原始输出里包含坐标通道; not self.end2end:非端到端 NMS 模型。从源码结构看,这与导出侧的限制一致——ultralytics/engine/exporter.py L681-L684 明确对rknn格式禁用end2end分支(“This export format does not support end2end models”),因此该条件在 RKNN 导出中实际恒为 True,保留它是为了与BaseBackend的通用语义对齐。
为什么需要这一步?注释解释了原因:INT8 导出采用“相对输入的坐标”(input-relative coordinates),这样单个 per-tensor 量化 scale 就不会破坏类别分数(class scores)。代价是 NPU 输出的框坐标是归一化到输入尺寸的比例值,forward需要把它们乘回真实像素值:x/y通道乘宽w,w/h通道乘高h。对pose任务,关键点紧跟在框(4 通道)与类别分数(nc通道)之后,因此用kpt_start = 4 + len(self.names)定位起点,再按每 3 个通道一组(x, y, conf)分别还原 x 与 y。
这段还原逻辑与导出侧一一对应:导出 INT8 图时,ultralytics/engine/exporter.py L1096-L1104 会在 ONNX 图中插入_NormalizeCoords节点(“Normalize coordinates by input size so RKNN's per-tensor INT8 scale preserves class scores”),两者共同构成“导出端归一化、推理端反归一化”的闭环。
AutoBackend 如何路由到 RKNNBackend
用户在 Ultralytics 中从不直接实例化RKNNBackend,入口是AutoBackend(ultralytics/nn/autobackend.py):
- 格式识别:
AutoBackend的_BACKEND_MAP将"rknn"映射到RKNNBackend(autobackend.py L167),文件命名约定为*_rknn_model/目录; - FP16 不支持:
fp16 &= format in {"pt", "torchscript", "onnx", "openvino", "engine", "triton"}(autobackend.py L215),RKNN 不在其中,设备端推理的精度由导出时的quantize决定; - 设备回落:非 PyTorch 系格式在 CUDA 不可用时会自动落回
cpu设备对象(autobackend.py L218-L224),而 RKNN 的实际执行硬件由RKNNLite.init_runtime()在 NPU 侧完成,device参数更多是管线层面的记账。
与导出侧的衔接:.rknn 目录是怎么来的
虽然本文主体是推理后端,但理解导出侧能完整解释后端的行为约定。导出函数onnx2rknn(ultralytics/utils/export/rknn.py L16-L84)的要点:
- 目标平台通过
name指定(默认"rk3588"),写入target_platform配置; - 平台精度限制:
rv1103 / rv1106 / rv1103b / rv1106b这四个 INT8-only 平台不做量化会直接报错,必须quantize=8(export/rknn.py L44-L48); - INT8 校准:
quantize=8时必须有校准图像列表文件(dataset),build(do_quantization=True, dataset=...)完成后导出<模型名>-<平台>.rknn,并把元数据以metadata.yaml旁路写入同一目录(export/rknn.py L80-L84)——这正是后端read_metadata读取的文件; - INT8 仅检测任务:ultralytics/engine/exporter.py L720-L725 规定 RKNN INT8 导出只支持
detect任务,其他任务请改用 FP16(quantize=16); - 批次扩展:
rknn_batch_size在加载 batch-1 的 ONNX 后由 RKNN Toolkit 扩展,所以export_rknn中会先截取self.im = self.im[:1](exporter.py L1535)。
导出入口的完整命令示例(含name、quantize参数)参见 docs/en/integrations/rockchip-rknn.md。
使用方式与仓库内的验证约束
在 Rockchip 设备上,只要把导出的yolo26n_rknn_model/目录拷贝到板端,Ultralytics 的预测/验证入口即可直接使用,例如:
from ultralytics import YOLO model = YOLO("yolo26n_rknn_model/") # 目录中自动 rglob 定位 .rknn results = model.predict("bus.jpg") # 推理走 RKNN NPU仓库自身也对 RKNN 运行环境做了硬性约束,可在 ultralytics/utils/benchmarks.py L170-L173 看到:
if export_format == "rknn": assert not isinstance(model, YOLOWorld), "YOLOWorldv2 RKNN exports not supported yet" assert LINUX, "RKNN only supported on Linux" assert not is_rockchip(), "RKNN Inference only supported on Rockchip devices"这三条 assert 给出了三条适用前提:YOLO-World v2 暂不支持 RKNN 导出;仅 Linux;且推理必须真的在 Rockchip 板卡上执行(该 benchmark 路径特意排除 Rockchip 环境,因为 PC 上既不能跑rknnlite也不应跑)。
适用范围与限制小结
综合文档与源码,RKNNBackend的能力边界可以归纳为:
| 维度 | 事实(依据) |
|---|---|
| 运行硬件 | 仅 Rockchip 带 NPU 设备,白名单RKNN_CHIPS(utils/init.py L84-L98) |
| 运行系统 | 仅 Linux + ARM64 路径下才会尝试设备检测(checks.py L1140-L1156) |
| 依赖 | rknn-toolkit-lite2运行时(rknn.py L36-L37) |
| 输入格式 | BHWC、[0,1]归一化张量,内部转 uint8 像素 |
| 精度 | 由导出决定(FP16/INT8),推理端不支持 FP16 开关(autobackend.py L215) |
| 元数据 | 依赖导出目录内的metadata.yamlsidecar(base.py L187-L190) |
| INT8 后处理 | detect/segment/pose/obb 的坐标乘回像素值,pose 关键点按kpt_start定位(rknn.py L67-L81) |
| 端到端 NMS | RKNN 格式导出时自动禁用end2end(exporter.py L681-L684) |
如果你在 Rockchip 板端调试 RKNN 推理遇到问题,排查顺序建议为:先确认 SoC 是否在RKNN_CHIPS白名单内 → 再确认metadata.yaml与.rknn同目录(否则task、names等元数据为空,后处理会退化)→ 最后关注init_runtime()的返回码以区分模型问题与 NPU 驱动/运行时问题。
参考文档:nn.backends.rknn API Reference(本类由 ultralytics/nn/backends/rknn.py 自动生成)。
【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考