首页
/ Ultralytics YOLO26 端到端 NMS-Free 目标检测:输出格式、导出兼容性与迁移实践指南

Ultralytics YOLO26 端到端 NMS-Free 目标检测:输出格式、导出兼容性与迁移实践指南

2026-09-06 22:34:11作者:舒璇辛Bertina

YOLO26 的检测类模型(检测、实例分割、姿态估计、OBB)默认采用端到端推理:模型直接输出最终检测结果,不再需要 NMS(非极大值抑制)后处理步骤。本文基于 Ultralytics 仓库中的官方指南与源码实现,完整解析 YOLO26 的双头架构、新输出格式 (N, 300, 6)、各导出格式的端到端兼容性、量化对端到端分支的影响,以及从 YOLOv8/YOLO11 迁移时的全部注意事项。读完后,你既能判断自己的推理代码是否需要改动,也能定位导出格式中 NMS 自动回退的底层原因。

为什么 YOLO26 要移除 NMS

传统 YOLO 模型(YOLOv8YOLO11)的原始输出是数千个重叠候选框,必须经过独立的 NMS 步骤才能过滤出最终结果。这带来三个部署层面的问题:

  • 额外延迟:NMS 需要遍历所有候选框做两两 IoU 计算,是推理管线中不可忽略的一环;
  • 导出图复杂化:NMS 依赖动态控制流,不同硬件平台上的导出行为难以保持一致;
  • 跨平台不一致:不同推理引擎对 NMS 的实现细节存在差异,可能导致结果漂移。

YOLO26 以端到端(End-to-End)检测解决了上述问题:由 one-to-one 检测头内部完成 top-k 筛选与去重,直接输出最终检测结果。官方文档给出的收益数据是:YOLO26n 在 CPU ONNX 推理(Intel Xeon CPU @ 2.00 GHz)上比 YOLO11n 最高快 43%(见 YOLO26 模型页)。该设计同时服务于 YOLOE-26 开放词汇模型,使其在类别动态变化时仍保持快速推理。

双头架构:one-to-one 与 one-to-many

YOLO26 在训练阶段使用双头架构:两个检测头共享同一 backbone 和 neck,但输出方式不同。

检测头 用途 检测输出 后处理
One-to-One(默认) 端到端推理 (N, 300, 6) 仅置信度阈值过滤
One-to-Many 传统 YOLO 输出 (N, nc + 4, 8400) 需要 NMS

上表以检测任务为例:N 是批次大小,nc 是类别数(如 COCO 的 80),8400 是 imgsz=640 下的 anchor 总数。其他任务在 one-to-one 输出上追加任务专属数据:

任务 端到端输出 追加数据
检测 (N, 300, 6)
实例分割 (N, 300, 6 + nm) + proto (N, nm, H, W) nm 个掩码系数(默认 32)
姿态估计 (N, 300, 57) 17 个关键点 × 3(x, y, 可见性)
OBB (N, 300, 7) 旋转角

训练时两个头同时运行——one-to-many 头提供更丰富的学习信号,one-to-one 头学习产出干净、不重叠的预测。推理与导出时默认只激活 one-to-one 头,每图最多 300 个检测,格式为 [x1, y1, x2, y2, confidence, class_id]

源码印证:Detect 头的实现

配置文件中可以看到 end2end 是模型级参数,在 yolo26.yaml 中显式声明:

nc: 80 # number of classes
end2end: True # whether to use end-to-end mode
reg_max: 1 # DFL bins

Detect 头实现 中,end2end=True 时通过 copy.deepcopy 复制出一套独立的 one-to-one 卷积分支:

if end2end:
    self.one2one_cv2 = copy.deepcopy(self.cv2)
    self.one2one_cv3 = copy.deepcopy(self.cv3)

forward 中的关键逻辑(head.py)印证了双头行为:训练时同时计算两个头的预测;推理时只走 one-to-one 分支,并经 postprocess 完成 top-k 截断:

preds = self.forward_head(x, **self.one2many)
if self.end2end:
    x_detach = [xi.detach() for xi in x] if self.training else x  # detach keeps one2one out of the backbone
    one2one = self.forward_head(x_detach, **self.one2one)
    preds = {"one2many": preds, "one2one": one2one}
...
y = self._inference(preds["one2one"] if self.end2end else preds)

值得注意的是训练时用 detach() 把 one-to-one 分支与 backbone 解耦,从源码结构看其梯度主要由 one-to-many 头驱动。postprocess 方法(head.py)将原始预测 (batch, num_anchors, 4 + nc + extra) 转换为文档所述的 (batch, min(max_det, num_anchors), 6 + extra)——这就是"模型内部完成去重"的具体位置。而 fuse() 方法会直接移除 one-to-many 分支(self.cv2 = self.cv3 = None),这正是 model.fuse() 能"减小模型体积与 FLOPs"的源码依据;YOLO26 模型页 中也注明:预训练 checkpoint 参数/FLOPs 统计是 fuse 之后的数值。

其他任务头(SegmentPoseOBB)同样接受 end2end 参数(head.py),与文档"四种检测类任务均默认支持端到端"的说法一致。

需要修改代码吗?

使用 Ultralytics Python API 或 CLI:无需任何改动

如果你使用标准 Python APICLI,预测、验证、导出全部自动适配端到端模型,只把模型名换成 yolo26n.pt 即可:

from ultralytics import YOLO

# Load a YOLO26 model
model = YOLO("yolo26n.pt")

# Predict — no NMS step, no code changes
results = model.predict("image.jpg")
yolo predict model=yolo26n.pt source=image.jpg

使用自定义推理代码:需要适配新输出格式

如果你为 YOLOv8/YOLO11 写过自定义后处理(例如基于 ONNX Runtime 或 TensorRT 的推理管线),必须更新输出解析逻辑:

YOLOv8 / YOLO11 YOLO26(端到端)
检测输出 (N, nc + 4, 8400) (N, 300, 6)
框格式 xywh(中心 x、中心 y、宽、高) xyxy(左上 x、左上 y、右下 x、右下 y)
布局 每个 anchor 的框坐标 + 类别分数 [x1, y1, x2, y2, conf, class_id]
是否需要 NMS
后处理 NMS + 置信度过滤 仅置信度过滤

端到端模型下后处理被大幅简化,以 ONNX Runtime 为例:

import onnxruntime as ort

# Load and run the exported end-to-end model
session = ort.InferenceSession("yolo26n.onnx")
output = session.run(None, {session.get_inputs()[0].name: input_tensor})

# End-to-end output: (batch, 300, 6) → [x1, y1, x2, y2, confidence, class_id]
detections = output[0][0]  # first image in batch
detections = detections[detections[:, 4] > 0.25]  # confidence filter, no NMS

切换回 one-to-many 头(传统 NMS 输出)

如需传统 YOLO 输出格式(例如复用现有 NMS 后处理代码),可在 one-to-many 头可用时通过 end2end=False 切换:

from ultralytics import YOLO

model = YOLO("yolo26n.pt")

# Prediction with NMS (traditional behavior)
results = model.predict("image.jpg", end2end=False)

# Validation with NMS
metrics = model.val(data="coco.yaml", end2end=False)

# Export without end-to-end
model.export(format="onnx", end2end=False)
yolo predict model=yolo26n.pt source=image.jpg end2end=False
yolo val model=yolo26n.pt data=coco.yaml end2end=False
yolo export model=yolo26n.pt format=onnx end2end=False

default.yaml 中,end2end 是一个可选布尔参数(注释说明其适用于 YOLO26 与 YOLOv10 的 predict/val/export)。其默认值为 None(不强制),模型自身的 yaml 配置(end2end: True)生效;显式传入 False 才会切换到 one-to-many 路径。

导出格式兼容性

大多数导出格式原生支持端到端推理,包括 ONNXTensorRTCoreMLOpenVINOLiteRTMNN

以下格式不支持端到端,会自动回退到 one-to-many 头:NCNNRKNNPaddlePaddleExecuTorchIMXEdge TPUQualcomm QNN

回退意味着什么:导出到这些格式时,Ultralytics 会自动切换到 one-to-many 头并打印警告——你的推理管线需要像使用 YOLOv8/YOLO11 一样在端侧自行实现 NMS

这一行为的源码依据在 exporter.py:导出器检查模型是否具有 end2end 属性,对不支持 top-k 操作的格式直接置位 model.end2end = False 并记录警告:

if hasattr(model, "end2end"):
    if self.args.end2end is not None:
        model.end2end = self.args.end2end
    if fmt in {"rknn", "ncnn", "executorch", "paddle", "imx", "edgetpu", "qnn"}:
        # Disable the end2end branch for formats without top-k support
        model.end2end = False
        LOGGER.warning("This export format does not support end2end models, disabling the end2end branch.")

注意回退集合是硬编码在导出器中的,与本文档列出的格式清单一一对应(IMX 额外要求 nms=True 并自动设置,见 exporter.py)。

Hailo 的特殊处理

Hailo,导出器从加载的模型头而非参数判断输出路径:显式传入 end2end 会直接抛出 ValueErrorexporter.py)。因此:默认 YOLO26 检测模型保留 NMS-free 的 one-to-one 输出;而若 checkpoint 本身的头已被设为 end2end=False,则会按传统路径编译并配合 HailoRT 的 NMS。

量化与运行时版本会静默关闭端到端

TensorRTLiteRT 支持端到端,但以下情况会自动禁用该分支(各情况均打印警告并改导 one-to-many 头):

触发条件 原因(源码注释)
TensorRT 版本 < 8.5.0 旧版 TensorRT 不支持端到端模型所需的 Mod 算子(exporter.py
TensorRT 10.3.0 + quantize=8(JetPack 6) 该组合存在已知的 end2end 构建问题(exporter.py
LiteRT + quantize=8quantize="w8a16" 静态激活量化会破坏端到端的类别索引输出(exporter.py

换言之:如果你在 Jetson 上导出 TensorRT INT8 模型时看到端到端分支被禁用的警告,这不是配置错误,而是版本兼容性的保护机制——升级 TensorRT 到 8.5.0+ 或提高导出精度即可恢复端到端路径。

精度与速度的权衡

端到端检测在带来部署收益的同时,对精度的影响很小:

指标 端到端(默认) One-to-Many + NMS(end2end=False
COCO mAP 低 0.6–0.8 基线
后处理 仅置信度过滤 完整 NMS 管线
部署复杂度 最小 需要实现 NMS

具体数值上,五个检测规模中 one-to-one 头在 COCO 上损失 0.6–0.8 个 mAP——YOLO26n 为 40.9 → 40.1,YOLO26x 为 57.5 → 56.9——换来的是完全省去 NMS 这一趟。若你的场景以精度为绝对优先,用 end2end=False 回退到 one-to-many 头即可。更细的各规模基准(n/s/m/l/x)见 YOLO26 性能指标

从 YOLOv8 / YOLO11 迁移清单

升级现有项目到 YOLO26 时,按角色对照检查:

  • Ultralytics API / CLI 用户:无需任何改动,仅把模型名更新为 yolo26n.pt(或 yolo26n-seg.ptyolo26n-pose.ptyolo26n-obb.pt)。
  • 自定义后处理代码:更新输出解析——检测为 (N, 300, 6),分割/姿态/OBB 追加任务数据;同时注意框格式从 xywh 变为 xyxy
  • 导出管线:对照上文兼容性章节确认目标格式是否原生支持端到端。
  • TensorRT < 8.5.0:所有精度下端到端均被禁用——升级到 8.5.0+ 以保留端到端。
  • 量化导出:TensorRT 10.3.0(JetPack 6)+ quantize=8、LiteRT quantize=8quantize="w8a16" 会自动关闭端到端——改用更高精度导出可保留。
  • FP16 导出:若要求所有输出均为 FP16,请用 end2end=False 导出(原因见 Export 模式文档中 output0 为何保持 FP32 的说明)。
  • iOS / CoreML:端到端完全支持;若需要 Xcode Preview 功能,使用 end2end=False 配合 nms=True
  • 边缘设备(NCNN、RKNN):这些格式自动回退 one-to-many,端侧管线需自带 NMS。

FAQ

end2end=True 和 nms=True 可以同时使用吗?

不能,二者互斥。在端到端模型上设置 nms=True 导出时,会被强制改为 nms=False 并打印警告(源码见 exporter.py'nms=True' is not available for end2end models)。端到端头内部已完成重复框过滤,外部 NMS 没有必要。

end2end=False + nms=True 是合法组合——它把传统 NMS 烘焙进导出图。这对 CoreML 导出很有用:可以直接在 Xcode 中用 Preview 功能预览检测模型。

端到端模型中 max_det 参数控制什么?

max_det(默认 300,见 default.yaml)控制每图返回的最大检测数,推理和导出时均可调整:

model.predict("image.jpg", max_det=100)  # fewer detections
model.export(format="onnx", max_det=500)  # more detections for dense scenes

该值会被烘焙进导出图作为检测头的 top-k 参数,因此 max_det=500 会把输出张量拓宽到最多 (1, 500, 6)(以 anchor 数为上限)。这正对应源码中 get_topk_indexk = min(max_det, anchors) 逻辑(head.py)。

导出的 ONNX 输出 (1, 300, 6) 是正确的吗?

是。检测任务的端到端输出即:批次 1、最多 300 个检测、每个 6 个值 [x1, y1, x2, y2, confidence, class_id]。只需按置信度阈值过滤,无需 NMS。

其他任务的输出形状不同:

任务 输出形状 说明
检测 (1, 300, 6) [x1, y1, x2, y2, conf, class_id]
分割 (1, 300, 38) + (1, 32, 160, 160) 6 个框值 + 32 个掩码系数,外加原型掩码张量
姿态 (1, 300, 57) 6 个框值 + 17 关键点 × 3(x, y, 可见性)
OBB (1, 300, 7) 6 个框值 + 1 个旋转角

如何确认导出的模型是否端到端?

可以通过 Ultralytics Python API 或导出 ONNX 的元数据检查:

from ultralytics import YOLO

model = YOLO("yolo26n.onnx")
model.predict(verbose=False)  # run predict to setup predictor first
print(model.predictor.model.end2end)  # True if end-to-end is enabled
import onnxruntime as ort

session = ort.InferenceSession("yolo26n.onnx")
metadata = session.get_modelmeta().custom_metadata_map
print(metadata.get("end2end"))  # 'True' if end-to-end is enabled

导出器会把 end2end 状态写入导出模型元数据(exporter.py"end2end": getattr(model, "end2end", False))。两种检查回答的是不同问题:ONNX 元数据记录的是导出时使用的头,而 predictor.model.end2end 报告后端输出是否已含后处理(无需外部 NMS)。用 end2end=False, nms=True 导出的模型会同时输出 (1, 300, 6)——因为 NMS 被烘焙进了图——此时单看标志或输出形状都无法唯一判定所用头。

实例分割、姿态和 OBB 支持端到端吗?

支持。YOLO26 的四种检测类任务变体——检测、实例分割、姿态估计、OBB——默认支持端到端推理,且都提供 end2end=False 回退。各任务在基础检测输出上追加任务专属数据,yolo26n.ptyolo26n-seg.ptyolo26n-pose.ptyolo26n-obb.pt 的输出形状见上文"双头架构"一节的表格。

小结

  • 端到端检测是 YOLO26 的默认行为:使用 Ultralytics API/CLI 时零改动即可获益;只有自定义后处理管线需要适配 (N, 300, 6)xyxy 新输出并删除 NMS 步骤。
  • 绝大多数导出格式原生保留端到端输出;NCNN、RKNN、PaddlePaddle、ExecuTorch、IMX、Edge TPU、QNN 会自动回退 one-to-many 头,端侧需自备 NMS;TensorRT 与 LiteRT 在特定版本/量化条件下也会自动禁用端到端分支并打印警告。
  • 端到端头相对 one-to-many 头在 COCO 上的精度代价仅 0.6–0.8 mAP,换来的是部署管线简化与推理提速(YOLO26n 的 CPU ONNX 推理最高快 43%);精度优先场景用 end2end=False 回退。
  • end2endnms 互斥;max_det 控制烘焙进导出图的 top-k 上限;Hailo 则从模型头本身判定输出路径、不接受 end2end 参数。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388