首页 / 资讯中心 / 文章详情

YOLOv8 NMS为何拖累端侧性能?YOLO26无NMS后处理实现延迟腰斩

YOLOv8 NMS为何拖累端侧性能?YOLO26无NMS后处理实现延迟腰斩 ★ FEATURED ARTICLE
1. YOLOv8的NMS在端侧为什么这么“贵”1.1 NMS到底在做什么先讲一个很多人可能忽略的事实YOLOv8这类dense检测器模型最后输出的并不是“干净的几个框”而是一大堆冗余预测。拿一个640×640的输入来说主干网络会生成80×80、40×40、20×20三张不同尺度的特征图加起来一共8400个候选位置。每个候选位置都携带一组信息4个坐标值cx、cy、w、h和80个类别得分。也就是说模型一次推理会吐出一整块形状接近(1, 84, 8400)的张量。这8400个预测里绝大多数都指向同一个目标。比如画面里有一只猫可能周围几十个候选框都框住了它只是位置、大小、置信度略有差异。如果不做处理下游业务根本没法用同一个物体会被框几十次。NMS要做的事情就是把这些冗余结果合并成唯一一个框。它的逻辑说起来很简单先按类别置信度从高到低排序拿最高分的框作为基准把其他跟它交并比超过阈值的框全部抑制掉然后换下一个框继续。但“简单”只在概念层面成立。落到代码里它涉及排序、循环、IoU批量计算、动态阈值判断、结果拼接。每张图的候选框数量不固定每个类别的框数量也不固定于是整段逻辑是强动态的很难做到完全静态化。1.2 端侧设备的算力分布很不均衡为什么NMS在服务器上通常不是大问题在端侧却特别扎眼原因在于设备特性。服务器GPU跑NMS时可以把一批框的IoU计算并行化几百个框的矩阵运算对GPU来说几乎可以忽略整个后处理往往不到1毫秒。而端侧设备的算力结构完全不同NPU和DSP擅长卷积、矩阵乘这类规整计算但对排序、循环、动态内存分配、分支跳转这些操作非常不擅长。很多端侧推理引擎根本不支持在NPU里塞一个带循环的NMS算子只能把结果拿回CPU做。于是常见的情况是NPU跑完模型只花了十几毫秒然后数据拷贝回CPU再经过过滤、排序、IoU抑制这些步骤CPU后处理又要吃掉几毫秒甚至十几毫秒。我见过不少项目模型推理时间从40ms压到了20ms结果后处理还是傻大黑粗的纯Python实现端到端延迟根本压不下去。问题不在模型而在后处理。另外一个隐藏开销是内存分配。每帧推理都会动态创建候选框数组、置信度数组、索引数组NMS过程中还会频繁做拼接和删除操作。在内存带宽和分配器都很“抠”的嵌入式环境里这些操作带来的延迟和抖动可能比实际计算量更麻烦。1.3 给NMS算一笔延迟账用具体数字感受一下。假设640×640输入置信度阈值过滤后保留了200个框默认80个类别里实际有效的类别可能就十来个。每个类别内部需要两两计算IoU200个框两两组合大概是200×199/2约2万次IoU计算乘以有效类别数就是几十万次浮点运算和比较。这个运算量听起来不大但端侧CPU做起来并不轻松。用未经优化的Python脚本跑一帧搞不好要10ms以上改成C手写循环大概能做到2到5ms。如果开发板上只有一个中等性能的CPU核心在跑后处理同时前端还有相机采集和UI任务抢占资源实际开销会更难看。更关键的是NMS的计算量和候选框数量是平方关系。如果阈值调低了或者检测场景特别复杂候选框从200涨到500NMS耗时会成倍上涨。这就是为什么有些项目在演示Demo时一切正常一到真实场景帧率就掉得厉害。2. YOLO26去掉NMS的技术路线到底怎么回事2.1 从“一对多”到“一对一”标签分配才是关键很多人以为去掉NMS就是“推理时把这个函数删了”这是对无NMS检测器最大的误解。带NMS的检测器能工作是因为它在训练时允许一个目标对应多个候选框。比如一只猫可能被20个候选框同时预测为正样本网络通过大量重复学习把猫的特征吃透推理时再用NMS把20个框合并成一个。这是一种“训练时粗放、推理时精修”的设计。无NMS检测器的思路完全不同训练时就强迫网络对每个目标只给出一个预测。这需要改变标签分配策略用一对一匹配替代原来的一对多匹配。具体做法通常是用匈牙利算法在预测框和真实框之间做最优匹配让每个真实框只对应一个预测框其余预测都被视为负样本。这事情说起来容易做起来难。一对一分配意味着监督信号变稀疏了网络更难收敛对小目标也不够友好。所以后来端到端检测器加入了很多辅助手段比如denoising训练、额外的一对多辅助分支等。YOLO26这类模型的“无NMS”能成立本质上其实是把原本在推理阶段做的去重工作提前塞进了训练阶段让网络自己学会“抑制重复框”。2.2 输出形态的变化不再是8400个候选从结构上看无NMS检测器最直观的变化是输出形态变了。YOLOv8输出8400个候选无NMS模型输出的通常是一组固定数量的预测框比如300个或者128个。每个预测框可以看作一个可学习的“查询”经过特征交互之后的响应它从特征图里“找出”自己负责的那个目标直接回归坐标和类别。实现方式上有人会做成类似DETR的query attention结构也有人用更轻量的set prediction head。YOLO26这个命名大概率是冲着“YOLO系列新阶段”来的具体实现我不方便乱猜但从实测感受看它走的是更偏端侧友好的路线没有搬运完整Transformer那套重结构而是保留CNN主干在检测头部分引入query交互机制。不管底层怎么实现推理时的收益是确定的模型输出一个形状固定的张量比如(1, 300, 85)。后处理只需要做一次坐标缩放或者按需求过滤掉置信度极低的框这已经不是在“抑制重复框”而是“挑出有效结果”。整个过程没有排序、没有循环、没有动态内存分配非常规整可以高度静态化。2.3 去掉NMS不掉点靠的是损失设计兜底直接删掉NMS一定会掉点这一点基本没有悬念。真正的问题是掉多少以及能不能通过训练策略找补回来。无NMS模型的精度保障主要来自几个地方。第一是一对一匹配的质量匈牙利匹配用的代价矩阵需要精心设计比如分类损失、L1回归损失、IoU损失的权重配比直接影响匹配结果。第二是denoising训练也就是在训练时故意给一些query加噪声让网络学会修正偏移这能显著提升收敛稳定性和小目标召回。第三是损失函数的组合focal loss、GIoU loss、DFL这些组件同样需要适配。这些细节决定了“无NMS”到底是个噱头还是真本事。如果只是把NMS删了什么都不改测试集mAP大概率直接掉好几个点。YOLO26能被人拿出来讨论是因为它在端到端设计上把这一套补齐了至少在我自己的测试集上和YOLOv8的精度差距控制在可接受范围内。3. 延迟腰斩的收益来源与边界条件3.1 腰斩主要省在哪个环节把一份实测profile数据拆开看收益来源其实很清楚。参照我在一块中端开发板上的测试结果同样输入640×640INT8量化单线程CPU较多线程混合跑。YOLOv8这边预处理大概2ms模型推理17ms后处理8.4ms端到端27.4ms。YOLO26这边预处理2ms模型推理13.2ms后处理只剩0.6ms端到端15.8ms。整体差不多是腰斩。这中间有两部分收益。第一部分是模型本身的结构调整推理时间从17ms降到13.2ms。第二部分是后处理从8.4ms降到0.6ms这部分是纯省下来的。也就是说即便YOLO26的模型推理时间和YOLOv8完全一样只要后处理归零端到端延迟也能从27ms降到19ms降幅接近30%。再去掉冗余计算和更轻量的检测头才有可能做到真正的“腰斩”。所以看这类模型一定要分开看“网络推理时间”和“端到端延迟”。很多宣传材料喜欢拿端到端延迟说事因为数字更好看但只有拆开才知道钱省在哪儿、省了多少。3.2 端到端延迟是一个系统工程除了模型和后处理端到端延迟还包括预处理、内存拷贝、线程调度、业务逻辑几个部分。在端侧项目里这些环节往往互相影响。举个例子YOLOv8的NMS后处理在CPU上跑如果业务代码本身也在CPU上跑二者会抢算力。换成YOLO26之后CPU后处理几乎消失CPU负载明显下降这时候真正限制帧率的可能是图像解码和颜色空间转换。很多人在评测时只看模型推理时间忽略了真实业务线程结果上线后发现帧率提升没有预期那么高。另外延迟测量本身有讲究。第一次推理往往包含初始化、模型加载、内存池分配这个数据没有参考价值。需要先预热几帧等线程池和内存分配稳定之后再统计而且要连续跑至少几百帧取稳态值而不是取单次最好成绩。否则很容易被极端值误导。3.3 不是所有场景都适合切到YOLO26无NMS设计在以下场景收益最大高帧率实时视频流、机器人避障、无人机视觉这类任务对端到端延迟极其敏感多路摄像头并发检测每路省几毫秒乘上数量就很可观低算力MCU或者不带GPU的嵌入式设备后处理原本占据大块CPU资源业务逻辑需要低CPU占用的场景省掉后处理后CPU可以留给决策、通信、UI。但有些场景要谨慎。比如超大图切片检测如果一张图切成几十个patch分别推理无NMS模型在每个patch上的输出直接被硬编码成固定数量比如300个框而NMS方案可以根据patch内容动态调整候选数量这对计算资源是浪费。再比如目标密集到一塌糊涂的遥感或者细胞检测场景一对一匹配的上限可能不如“一对多NMS”能覆盖的密度高。这些地方需要实测不能只看卖点。4. 一份尽量真实的中端开发板实测对比4.1 测试口径先定标准再看数据我这边用的是一块常见的中端开发板具体型号就不报了反正不是什么旗舰SoC也没有独立GPU比较接近量产设备的实际算力水平。推理引擎用ONNX Runtime为主同时跑了一遍NCNN做交叉验证。模型都做了INT8量化输入分辨率统一为640×640。测试脚本统一了以下流程固定输入图像禁止随机噪声影响先跑20帧预热让线程池和内存池稳定之后连续跑200帧统计平均延迟预处理统一用同样的Resize和归一化代码不算进模型推理但算进端到端。目标只有一个尽量保证对比的是模型本身的差异而不是工具链差异。4.2 延迟、体积、内存、精度的直接对比下面这张表是我手头数据里的相对趋势具体数字会因为设备、引擎、量化方式不同而变化但整体差异方向是稳定的。对比维度YOLOv8YOLO26 无NMS模型大小INT8约11MB约9MB预处理耗时约2.0ms约2.0ms网络推理耗时约17.0ms约13.2ms后处理耗时约8.4ms约0.6ms端到端耗时约27.4ms约15.8ms内存峰值约220MB约170MB自建测试集mAP0.5相对值基准低0.3~0.6个百分点有意思的是模型体积只差了2MB左右说明YOLO26并没有把模型做得特别小收益主要来自结构设计和后处理清除。内存峰值的下降主要因为不需要在运行时动态创建候选框数组和索引数组这对内存紧张的场景比延迟改善更值钱。4.3 实测里几个反直觉的现象第一模型大小差距不大的情况下延迟差距却很大。原因是省掉动态后处理之后引擎可以更激进地做内存复用和算子融合这部分收益在纸面上看不出来。第二目标稀疏的场景下YOLO26的稳定性反而更好。YOLOv8有时会因为置信度阈值和NMS阈值的组合不够好导致同一个目标被框两次或者目标被漏掉。无NMS模型输出天然是“一个目标一个框”少了阈值调参的痛苦自建测试集上个别类别的召回率反而更高。第三开启NPU之后帧率的提升幅度往往比纸面延迟更大。因为无NMS模型的后处理非常规整可以留在NPU完成CPU几乎完全解放出来整条流水线的负载均衡变好了。5. 端侧部署YOLO26的实操避坑经验5.1 导出链路ONNX、算子兼容、动态Shape第一步是把训练好的模型导出成端侧引擎能跑的格式。这里有一个优势无NMS模型的导出链路通常比带NMS的模型更干净。YOLOv8如果想把NMS也一起导出到模型里常常需要拼一堆自定义节点不同引擎支持程度不一。YOLO26因为推理阶段没有NMS导出时就少了很多麻烦。主要需要注意的地方反而在query交互相关的算子上比如有些实现会用到矩阵转置、Gather、Scatter甚至有动态循环。这些算子在不同引擎上的优化程度差异很大导出之后一定要先用简化工具处理一遍把不必要的节点去掉。另外如果业务需要把后处理留在CPU做就不要把坐标映射、阈值过滤这些操作硬塞进模型里。我倾向于导出到特征层拿到原始输出张量然后在C侧做一次轻量解析。这样模型更纯粹后续换设备、换引擎时也更灵活。5.2 INT8量化检测头最容易掉坑无NMS模型量化时最让人头疼的是检测头部分。分类分支在推理时通常要经过sigmoid得到置信度再和阈值比较。量化之后置信度分布容易发生偏移原本0.45置信度的目标可能变成0.32直接被阈值过滤掉。回归分支也一样坐标的微小偏差在小目标上会被放大。我的做法是先用代表性足够强的校准集做全模型量化观察精度回退。如果检测头掉点明显就把检测头的某些层设置为保持FP16或FP32只对主干做INT8量化。虽然这样会牺牲一部分加速效果但换来的是稳定的检测精度在端侧项目里更划算。还有一个细节如果模型里有批量归一化层导出到端侧引擎前务必把它融合进卷积层。很多导出工具会自动处理但有些自定义结构不会导致推理时多出几个零散的算子延迟和误差都会上升。5.3 输出裁剪与内存复用进一步压榨性能无NMS模型虽然推理时输出固定数量的框但这个数量不一定都要用满。我实测过在普通安防和机器人场景一帧画面里的目标通常不超过10个。模型默认输出300个query纯属浪费。试着把输出数量从300裁剪到128模型推理时间又降了一截精度几乎没有变化。如果业务场景简单甚至可以压到64但要注意不能压过头否则密集场景会漏检。内存方面建议在初始化阶段就把输出缓冲区一次性分配好之后每一帧直接复用。不要小看这点优化在无NMS后处理的场景下内存分配甚至可能成为新的隐藏瓶颈。如果推理引擎允许把输入图像的缩放也放到硬件层面做或者用双缓冲轮流存放输入帧CPU侧的预处理压力会小很多。5.4 暂时切不过去怎么给YOLOv8的后处理提速并不是每个人都有条件立刻换模型。如果你现在还得继续用YOLOv8我给几个立竿见影的优化方向。第一先做Top-K截断。模型原始输出8400个候选大部分置信度极低可以在排序前只保留每类得分最高的前100个框NMS的运算量会显著下降。第二把逐类别循环改成矩阵化操作。一次性计算所有框之间的IoU矩阵用一次矩阵掩码来代替循环抑制代码跑起来会快很多。第三把置信度阈值调高一点。很多项目把阈值设在0.25实际到0.4也完全够用候选框数量少了NMS自然更快。另外一个思路是“后处理前置”把置信度过滤和坐标解码尽可能放到NPU上做只把少量候选框传回CPU。很多引擎都支持TopK、Gather这类算子只要不把带循环的NMS塞进去算子兼容性还是可以的。最后再分享一个我自己的实操习惯拿到任何新检测模型先不看它宣传的mAP而是把模型输出张量dump下来统计一下置信度分布、框数量分布、目标重叠情况。这个动作能帮你判断阈值该设多少、后处理是否值得做精简、该不该把NMS换掉。YOLOv8和YOLO26之间的选择也不妨用这个笨办法做一次“体检”数据会比宣传语诚实得多。
阅读完成 · 觉得有帮助?
咨询建站