年初帮朋友把一个YOLO26检测模型从训练机搬到工控机上前后折腾了两个晚上最后发现问题根本不在模型权重而在导出时选的格式和参数没对齐。这件事让我一直想写点实在的内容YOLO26的训练调参已经被聊得足够多但“训练完之后的模型导出”这一环真正说透的人不多。这篇文章就聊这个——从权重整理、环境对齐、导出命令到导出后的精度验证和落地部署把YOLO26模型导出的完整链路捋一遍。不管你是刚用YOLO26做完计算机视觉大作业还是已经在做单相机测距、人员入侵检测这类实际项目只要模型训练完成后面绕不开“怎么把.pt文件变成能上线的推理引擎”这一步。文章覆盖ONNX、TensorRT、OpenVINO等常见导出格式也包含我在实践中趟过的坑和排查思路希望能帮你少走弯路。1. 模型导出这件事为什么会成为部署环节的主战场1.1 训练状态与部署状态本质上是两个世界很多人以为训练完模型就算完事导出只是跑一条命令。但实际部署时遇到的多数“翻车现场”根因都出在训练环境和部署环境的差异上。训练时模型跑在PyTorch里输入是任意batch的float32张量前向计算用的是GPU整个计算图可以随时修改还有EMA、数据增强、分布式训练这些附加逻辑。部署时模型跑在ONNX Runtime、TensorRT或者移动端推理引擎里输入输出接口固定张量精度可能是FP16甚至INT8计算图被高度优化很多训练时的“小动作”都不支持。导出就是在两个世界之间搭桥。桥搭得稳模型的精度和速度才能延续桥搭得糙训练时mAP再漂亮一上推理引擎就掉点甚至直接报错。我见过最典型的例子训练时输入做了复杂的Mosaic增强还在归一化阶段用了自定义的mean/std部署时却用最简单的letterbox加除以255。预处理一变模型看到的输入分布就不对了推理结果自然飘。1.2 写论文、做原型、上产线导出策略完全不一样YOLO26的“模型导出”没有一套通吃所有场景的做法因为不同目标对应的约束不同。如果是为了做实验验证或写计算机视觉大作业通常只需要导出一份ONNX用onnxruntime跑通就算完事重点是能复现训练时的精度表现。如果是做产品原型或实时演示比如摄像头推理、单相机测距输出实时距离那么TensorRTNVIDIA GPU或OpenVINOIntel平台是主力需要在延迟和精度之间做平衡导出FP16几乎成了默认选项。如果是做边缘端部署比如人员入侵检测设备、嵌入式板卡那么RKNN、TFLite、CoreML这类端侧格式更常见还可能要做INT8量化。这个阶段最核心的矛盾是模型体积、推理速度和精度三者的取舍。所以导出不是一条命令复制粘贴第一步是搞清楚你的部署目标是什么再决定用哪条技术路线。后面的操作细节都建立在“你想把模型送到哪里去”这个问题上。2. 导出前必须确认的三件事权重、环境与目标后端2.1 权重文件的选择别把训练状态当成推理状态Ultralytics训练YOLO系列模型后会在runs目录下生成weights文件夹里面有两个文件best.pt和last.pt。best.pt是验证集上mAP最高的权重last.pt是最后一个epoch结束时保存的权重。很多人直接拿last.pt导出这本身没问题但你要清楚自己拿的是哪个——如果你的训练过程有波动last.pt可能对应的是过拟合或退化阶段的模型导出的推理效果自然不如best.pt。另外如果你的训练流程里开了EMA指数移动平均Ultralytics默认在训练结束后会把EMA权重合并到best.pt里。但如果你用了自定义训练脚本模型可能还停留在“训练态”包含蒸馏头、辅助头、额外的分类分支等训练专用结构。导出前务必检查模型结构把无关分支冻结或移除保证导出结果是纯推理结构。2.2 环境匹配版本对齐是科学不是玄学这一步最容易忽略也最容易出问题。YOLO26的导出工具链依赖一串软硬件版本任何一环不对齐报错千奇百怪。这里给一张我在多个项目里验证过的版本配套表供参考组件推荐版本组合A推荐版本组合B说明Ubuntu LTS20.04 / 22.0422.04 / 24.04服务器和工控机的常见选择CUDA11.812.1 / 12.2取决于TensorRT和PyTorch的编译版本cuDNN8.68.9应与CUDA小版本匹配TensorRT8.5 / 8.610.x新版本对动态shape支持更好PyTorch2.0.x2.1.x / 2.2.x需与CUDA版本对应onnx1.141.15过低时部分算子导出失败onnxruntime1.151.17用于ONNX推理验证onnxsim0.4.3x0.4.36用于简化计算图版本对齐的关键不在于“越新越好”而在于“链路上互相认识”。特别是TensorRT它的engine文件几乎和CUDA/TensorRT版本强绑定A环境导出的engine拿到B环境直接加载通常都会报错。建议你在开始导出前用pip freeze和nvcc -V把当前环境记录到一个文件里。这样即使后面把环境折腾坏了也能快速恢复。2.3 目标后端怎么选一张表看完主流方案我整理了一个导出格式选型表列出不同格式对应的典型部署场景导出格式适合场景推理框架典型精度备注ONNX跨平台原型验证、云端CPU推理、中间过渡文件ONNX Runtime、OpenCV DNNFP32/FP16通用性最好TensorRT engineNVIDIA GPU服务器、Jetson嵌入式TensorRTFP16/INT8性能上限最高OpenVINOIntel CPU/核显、边缘盒子OpenVINO RuntimeFP32/FP16对Intel平台优化明显TFLiteAndroid移动端、树莓派TensorFlow LiteFP16/INT8移动端生态完善RKNNRK3588等瑞芯微平台RKNN ToolkitINT8/FP16国内边缘设备常用CoreMLiOS/macOS设备Core MLFP16Apple生态专属没有绝对最好只有和硬件绑定的“最合适”。我的个人建议是除非明确知道目标设备否则先导ONNX把模型验证流程跑通再做二次导出。ONNX相当于一个“通用中转格式”几乎所有部署框架都能从它继续转换排查问题也容易。3. 用Ultralytics导出YOLO26的完整操作与参数详解3.1 一条命令导出先跑通再调优Ultralytics把导出封装得很简单基本一条命令搞定。以导出ONNX为例yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 halfFalse simplifyTrue dynamicTrue opset12如果你用的是Python环境也可以这样from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) model.export( formatonnx, imgsz640, halfFalse, simplifyTrue, dynamicTrue, opset12 )执行完成后在best.pt同目录下会生成best.onnx。如果开了simplify日志里会显示ONNX简化过程如果开了dynamic控制台还会打印动态轴的说明比如Dynamic axes dict: images: {0: batch, 2: height, 3: width}这说明输入张量的batch、高、宽都是动态的。3.2 关键参数的含义与设置逻辑imgsz推理输入的分辨率。建议和训练时保持一致如果你在训练时用了640×640就不要在导出时改成1280否则本质上是跨分辨率推理精度会受影响。如果你想改善小目标检测就用1280或更大的imgsz重新推理验证而不是训练完直接改导出尺寸。half是否以半精度导出。导出FP16模型的好处是体积减半、推理变快特别是在TensorRT上收益明显。但如果你导的是ONNX并且后续要在CPU上跑建议先保持False避免一些不支持FP16的CPU环境出问题。simplify是否用onnxsim简化计算图。我建议一直开着它能合并冗余算子、消除常量节点很多兼容性问题在简化后直接消失。dynamic是否开放动态输入尺寸。如果是给服务端做可变分辨率输入可以开如果是边缘设备或固定分辨率场景建议关掉。动态尺寸会让部分TensorRT导出变得复杂性能也可能打折。opsetONNX算子集版本。一般12~17都可用。如果你的推理框架比较老选低版本opset更安全如果导出时遇到某些算子不兼容可以尝试提高opset版本。nms是否把NMS整合进模型。开启后导出的是端到端模型输出直接就是过滤后的预测框省掉外部后处理但灵活度降低且在一些边缘设备上支持不好。3.3 导出时的日志与报错快速解读日志出现“export success”导出成功模型路径已经打印。出现“WARNING ⚠️ no labels found in ...”通常是数据集路径问题不影响导出但会让人误以为训练集丢了。出现“RuntimeError: Exporting unknown operator ...”算子不支持多数是模型里用了自定义或过新的算子。解决办法是先开simplify再提高opset如果还不行就要考虑是不是用到了部署框架不支持的模块例如某些自定义注意力实现。出现“CUDA out of memory”导出时模型加载到显存同时会把计算图也放到显存小显存容易爆。可以换成CPU导出也就是在CPU环境里跑export通常时间会稍长但是能过。提示建议在干净的虚拟环境里导出。Ultralytics依赖较多如果和训练环境混在一起版本升级容易破坏已有依赖关系。4. 导出之后的验证别让精度损失悄悄溜过去4.1 输出张量的结构怎么读导出完成后第一步不是急着部署而是先看懂输出长什么样。YOLO26默认的ONNX输出通常是一个形状为1×(4类别数)×8400的张量以640×640输入为例其中8400是不同特征层上的候选框总数4是中心点x、y和宽高后面跟着每个类别的置信度。你可以用下面的脚本快速查看输出结构import onnx model onnx.load(best.onnx) for output in model.graph.output: print(output.name, [dim.dim_value for dim in output.type.tensor_type.shape.dim])如果你在导出时开启了nms参数输出会变成类似1×300×6的形状每个候选框包含x1、y1、x2、y2、置信度、类别编号部署端不用再写后处理。但要注意这种端到端模型改动后处理逻辑使用前务必确认你的推理框架支持。4.2 精度对比测试同一张图两套推理差多少部署前一定要做一次“同图对比”拿同一张测试图片分别用PyTorch原始模型和导出的模型推理比对输出结果的差异。我这里用的标准流程是固定一张或一小组代表性图像包含小目标、大目标、遮挡场景。原始PyTorch模型保存预测结果包含坐标和置信度。导出的模型ONNX/TensorRT加载同一张图预处理参数必须完全一致letterbox尺寸、填充颜色、归一化方式、通道顺序。用IoU和置信度偏差来量化差异。两个模型的输出框如果IoU大于0.5且置信度差异在0.05以内基本可以判定导出没有引入明显损失。# 伪代码示例比较两个模型在单张图上的输出 import numpy as np # pred1: PyTorch模型输出, pred2: 导出模型输出 # 每个检测: [x1, y1, x2, y2, conf, cls] def compute_iou(box1, box2): x1, y1, x2, y2 box1 x3, y3, x4, y4 box2 inter_x1 max(x1, x3) inter_y1 max(y1, y3) inter_x2 min(x2, x4) inter_y2 min(y2, y4) inter_area max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) area1 (x2 - x1) * (y2 - y1) area2 (x4 - x3) * (y4 - y3) union area1 area2 - inter_area return inter_area / union if union 0 else 0如果发现差异太大优先检查预处理而不是模型结构。我在第5章会专门展开预处理一致性问题。4.3 性能基准延迟、吞吐和预热缺一不可很多人只测单张图片的推理时间这不够。推理引擎普遍有“预热”行为头几次推理会包含显存分配、kernel编译等额外开销跑几十次之后才能代表真实性能。我建议这样测固定输入尺寸连续跑100次以上去掉前10次统计剩余次数的平均延迟。记录P50、P90延迟不要只看平均因为平均会把少数极慢的帧掩盖掉。如果有吞吐要求用多线程或多进程压测观察显存占用是否随并发上涨。对TensorRT记录engine构建时间、首次推理时间和稳定后推理时间三者完全不同。注意TensorRT的engine构建有时反而比ONNX推理还慢但那是构建开销不影响实际部署后的性能。不要在构建阶段就下“太慢”的结论。5. 部署实战中的坑算子兼容、动态尺寸与预处理一致性5.1 ONNX导出后算子不兼容的排查路径模型结构越复杂导出失败的概率越高。YOLO26如果加入了很多模块比如注意力机制、可变形卷积、自定义上采样导出时的兼容性问题就会集中暴露。我的排查路径通常是这样的第一步打开simplify导出会有一大批问题自动消失。onnxsim会折叠常量节点清理无效算子尤其是训练阶段引入的辅助操作比如梯度相关的节点。第二步逐级提高opset版本。有时候某个算子在新版opset里才支持比如某些注意力实现需要较高的opset。第三步用Netron可视化ONNX模型检查导出后是否还有奇怪的结构。Netron能看到算子的连接方式很多问题图片上看一眼就知道是哪里断了。第四步如果算子在ONNX里天然不兼容回到PyTorch模型里把检测头或主干中不兼容的部分替换成等价替代模块重新训练或重新导出。虽然麻烦但这是最稳妥的方案。5.2 动态尺寸与letterbox的配合问题如果你的导出开了dynamicTrue部署时又允许不同分辨率的输入那么预处理里的letterbox逻辑必须与训练时完全一致。YOLO系列训练的letterbox默认使用灰色114,114,114填充。某些部署代码会觉得灰色填充影响性能习惯用黑色或者白色填充结果就是模型输入和训练时的分布不一样目标位置和置信度都会跑偏。更隐蔽的坑是letterbox后图像的缩放比例。YOLO的letterbox通常把长边缩放到目标尺寸然后对短边进行填充。有些部署代码写反了把填充放在了长边方向预测框的位置就会整体偏移。这种问题在固定尺寸上不容易发现但在动态尺寸场景下会表现得非常明显。5.3 INT8量化别让校准集毁掉整个模型边缘设备上经常需要把模型压缩到INT8以换取更低的延迟和更小的内存占用。INT8量化的精度损失往往不是量化本身造成的而是校准集选得不对。校准集是量化过程中用来统计权重和激活值分布的数据集。如果你拿几十张背景单调的图片去做校准模型的激活值范围估计就会偏一旦部署环境中出现复杂场景激活值超出校准范围输出直接就崩了。我的经验是校准集至少500张最好覆盖目标类别、光照变化、不同距离、不同姿态的真实场景图。如果项目里暂时没有这么多真实数据也可以用公开数据集的子集顶一顶但上线前必须用真实场景图回归一遍精度。另外不是所有层都适合INT8量化。遇到对精度极其敏感的层可以在TensorRT里对这些层做精度白名单强制用FP32或FP16计算通常能救回不少精度。5.4 TensorRT engine不通用换GPU就要重新构建TensorRT的engine文件与目标GPU架构强绑定。你在RTX 4090上构建好的engine放到Jetson Orin上大概率加载失败版本组合不同也会报错。常见报错信息有“Engine could not be deserialized”“Mismatched TensorRT library version”“Could not find plugin ...”遇到这类问题别去改代码直接检查两个环境下的CUDA、TensorRT版本是否一致以及GPU架构是否相同。跨架构使用时最简单的做法是在目标机器上重新执行一次模型转换和engine构建不要图省事搬engine文件。我在实际项目中一般会写一个转换脚本放到部署包里这样到了现场跑一遍engine就在目标机器上生成了省去很多版本匹配的麻烦。6. 从导出到落地单相机测距与人员入侵检测两个真实案例6.1 单相机测距任务导出后配合深度信息或几何约束使用单相机测距是热搜词里很典型的应用方向。YOLO26负责目标检测和定位导出模型输出的边界框、类别和置信度只是测距的第一步。常见做法有两种。一种是基于已知目标尺寸的单目测距比如检测到人后根据人的平均身高、相机焦距和检测框高度通过针孔相机模型估算距离。这种情况下需要导出的模型保持较高的框回归精度如果目标被遮挡导致检测框高度不完整测距误差会很大。另一种是搭配单目深度估计模型把YOLO26检测到的目标框区域内的深度信息聚合输出一个稳定的距离值。在这种方案里导出的模型推理延迟和深度模型延迟会叠加所以我在NVIDIA Jetson上通常把YOLO26导出为FP16的TensorRT engine把单帧总延迟控制在几十毫秒内保证测距输出不会明显滞后于画面。6.2 人员入侵检测的边缘部署流程人员入侵检测这类任务核心指标是误报率和实时性。YOLO26检测到人员之后通常还要接跟踪器和电子围栏逻辑判断人员是否进入禁区。我在一个实际项目里的流程是视频流解帧缩放到模型输入尺寸预处理对齐训练时的letterbox参数。ONNX或TensorRT推理得到人员检测框和置信度。用ByteTrack做跟踪赋予每个目标稳定的ID。判断检测框中心或底部点是否落在电子围栏多边形内连续多帧命中才触发报警降低偶发误报。把模型导出成TensorRT INT8 engine后单路推理在边缘设备上能做到实时而且CPU占用很低因此可以在一台设备上同时跑多路视频流。如果导出时选错后端比如在无NVIDIA GPU的设备上选TensorRT那整个流程根本跑不起来。所以导出前对部署硬件的确认永远排在第一位。6.3 我日常都在用的快速调试工具链最后分享几个我高频使用的工具它们帮我节省了大量排错时间Netron可视化ONNX模型结构确认输入输出节点排查导出后是否多出可疑算子。onnxruntime导出ONNX后我通常会在onnxruntime里跑一次单图推理检测输出shape和数值是否正常。trtexecTensorRT自带的可执行工具用它构建engine并做性能基准测试比写Python脚本简单得多。nsys / ncuNVIDIA的性能分析工具定位GPU推理时是算子瓶颈还是数据搬运瓶颈。提示如果在TensorRT上遇到莫名其妙的精度问题我建议立刻回到ONNX验证一遍。ONNX没问题问题大概率出在TensorRT的优化选项或精度设置上ONNX也有问题那就要回头查PyTorch模型和预处理了。7. 导出和部署的后续扩展模型导出从来不是终点而是部署的真正起点。训练可以不断迭代但交付到现场跑的毕竟是导出的那份推理产物。如果你后续要继续开发我建议把导出流程脚本化训练完成后自动执行ONNX导出、TensorRT构建、精度回归和性能压测。这样每次训练新版本权重部署包都能在几分钟内生成完毕而不需要手动一条条敲命令。在我自己项目里这个脚本帮我省了不少事。以前每次更新模型都要花半天搭环境、调参数、验证精度现在全部自动化之后模型更新到部署上线的时间压缩到一顿饭的功夫。模型导出虽然看着不起眼但把这条链路打通项目迭代速度会有质的提升。最后再分享一个小技巧如果你的目标设备支持TensorRT不要在导出阶段直接跨越到TensorRT而是先导ONNX再用trtexec或Python脚本转engine。中间多一步不会浪费多少时间但排查问题时你手里会多一个稳定可信的中间参考点。这个习惯帮我解决了很多“部署端说不清、训练端不承认”的糊涂账。
