最近在搞一个部署项目,把训练好的YOLOv5模型转成ONNX,用onnxruntime跑了一下,发现输出的检测框置信度整体偏低,有些原本能检出的目标直接丢了。对比了torch和onnx的输入输出,数据预处理都是一样的,也关掉了amp。查了日志,提示有些算子(比如Focus和SiLU)被拆成了多个小算子,不确定是不是这个原因。有没有大佬遇到过类似情况?是算子兼容性问题,还是转换时需要设置opset版本或者dynamic_axes?另外,用onnx-simplifier简化后会不会有改善?希望有经验的朋友指点一下,实在不想从头用TensorRT重写。
PyTorch转ONNX后推理结果和原模型差很多,是量化问题还是算子不支持?
全部回复
共 64 条之前跑过类似的坑,YOLOv5转ONNX确实容易在Focus和SiLU上出问题,但置信度整体掉这么多大概率不是量化,而是opset版本太低导致算子拆解后精度损失。建议先把opset设到12以上,dynamic_axes按官方文档配好,尤其注意输出层的维度。onnx-simplifier可以试,但别指望它能解决精度问题,更可能是把冗余节点清掉后速度提升。另外检查一下转ONNX时有没有把模型设成eval模式,BatchNorm层在训练和推理状态下的行为差异也会导致这种偏差。
我之前也踩过类似的坑,YOLOv5转ONNX后置信度掉一截,大概率不是量化的问题,因为你说关了amp,那基本就是算子拆解带来的数值精度损耗。Focus和SiLU被拆开确实会引入额外计算,但更关键的是opset版本,我之前用opset=11就明显比12差,建议你直接上opset=17试试,onnxruntime对高版本支持已经很稳了。dynamic_axes这块,如果batch size和输入尺寸固定,就别开动态维度,开了反而可能触发某些算子的非优化路径。onnx-simplifier可以跑一下,它能把Focus这种结构合并成切片加卷积,但我遇到过simplify后反而把某些常量折叠出精度问题的情况,所以简化后一定要重新对比输出。另外你也可以检查下转ONNX时是否默认把模型里的half权重转成了float32,如果原模型是FP16训练,这种转换本身就会掉点。要是实在排查不出来,可以试着把输出层单独拎出来对比中间特征图,看到底是哪一层开始偏差变大的。最后提个醒,YOLOv5的检测头里有grid生成逻辑,ONNX导出时如果用了固定shape,某些解码步骤会被常量折叠,导致坐标偏移,这个也会影响置信度。
先检查下opset版本吧,YOLOv5用11以上会好很多,simplifier对Focus拆解没太大帮助。
我之前也踩过这坑,把onnx的输入输出shape固定住别用dynamic试试,置信度低多半是opset兼容性问题。
我之前也踩过类似的坑,YOLOv5转ONNX后置信度掉一截,八成不是opset版本的问题,而是Focus和SiLU被拆解后,某些中间节点的数值精度在fp32下都有微妙差异,尤其当输入分辨率不是默认640时会更明显。建议你先别急着上simplifier,那个对算子兼容性帮助有限,倒是可以试试把opset调到13以上,然后明确设置dynamic_axes给batch和宽高,有时候静态shape会触发onnxruntime的图优化走偏。另外你提到关掉了amp,但训练时如果用了混合精度,权重里可能残留一些半精度参数,导出前最好先转成纯fp32再保存,我上次就是卡在这。还有个土办法,你可以在onnxruntime里把execution_mode设为ORT_SEQUENTIAL,关掉并行优化,看置信度是否回升,能帮你判断是不是算子融合导致的数值漂移。如果实在不行,干脆手写个自定义op把Focus和SiLU合并回去,网上有现成代码,比从头用TensorRT省事很多。
之前跑过类似的坑,YOLOv5转ONNX置信度掉点大概率不是量化问题,Focus和SiLU被拆分后,如果opset版本低于11,某些算子的数值精度确实会受影响,建议先试试opset=12或13,同时把dynamic_axes设好。onnx-simplifier主要解决的是冗余结构,对精度帮助有限,但可以配合onnxruntime的graph优化选项一起试。另外可以逐层对比torch和onnx的输出,定位是哪个节点开始偏差变大的,这样比瞎猜效率高。实在不行,检查下输入数据的归一化方式是否在导出时被意外改动,我之前就栽在这上面。
我之前也踩过这个坑,YOLOv5转ONNX置信度掉下来大概率不是量化问题,因为onnxruntime默认是fp32推理。SiLU和Focus被拆算子通常不影响数值,但如果你用的opset版本太低(比如9或10),某些层的计算精度或融合方式确实会变,建议直接设opset=17再导一次对比下。dynamic_axes对检测头输出影响不大,但如果你转的是带NMS的版本,那很可能是自定义算子没注册导致的数值偏差,可以试试关掉NMS纯看模型输出。onnx-simplifier有时候能解决奇怪的图优化问题,但别指望它修复精度差异,建议先用onnxruntime的graph optimization level调到0排除优化干扰。如果以上都试过还不行,拿几个固定输入对比中间层输出,定位到具体哪一层开始分叉,一般就能找到原因了。
遇到过类似情况,多半不是量化问题,YOLOv5转ONNX时Focus和SiLU被拆开确实容易导致数值精度漂移,尤其是置信度这种敏感输出。建议先检查一下onnxruntime的opset版本,至少设到12以上,然后dynamic_axes对batch和长宽都配好,不然形状固定后某些层行为会变。onnx-simplifier可以试试,但别指望它解决精度问题,它主要优化图结构。我之前用torch.onnx.export时加了opset_version=12和dynamic_axes之后,置信度偏差从0.2缩到0.05以内,你可以先跑个纯torch和onnx的输出对比,看是整体偏移还是个别样本异常,这样能更快定位是算子还是图结构的问题。
之前跑过类似的坑,YOLOv5转ONNX大概率是Focus和SiLU被拆解后精度抖动,尤其是SiLU在低精度下误差会被放大。你试试把opset设到12以上,然后转换时加上dynamic_axes,能缓解不少。另外onnx-simplifier建议跑一下,它会把冗余的reshape和concat合并掉,但注意别开fold_const,有时候反而会把动态shape搞坏。还有个偏方,转之前把模型里所有BN层和卷积层融合一下,能减少算子拆分带来的累积误差。
我之前也踩过这个坑,YOLOv5转ONNX置信度掉得离谱,后来发现主要是opset版本太低导致SiLU被拆成一堆子图,数值精度有损耗。你可以试试opset设成12以上,然后dynamic_axes只对batch维度开,别全开,有时候这能救回来。onnx-simplifier确实有用,但别指望它解决本质问题,它只是把图简化,不改数值逻辑。建议你先把onnx和torch的输出逐层对比一下,看到底是哪个节点开始偏差的,这样能快速定位是算子问题还是精度问题。另外,如果实在不行,可以考虑用onnxruntime的CUDA EP跑一下,有时候CPU和GPU的算子实现也有差异。
我之前也踩过这个坑,YOLOv5转ONNX后置信度掉一截大概率不是量化问题,因为你是直接转的FP32对吧。Focus和SiLU被拆解后,如果onnxruntime对某些子图优化不到位,数值精度确实会漂移,尤其是SiLU用公式近似时。建议先试试opset=11或12,然后dynamic_axes只对batch维度开,别全开,有时候维度动态化会让ORT选错kernel。onnx-simplifier能合并掉很多冗余reshape,对推理速度有帮助,但数值误差不一定能救回来,我之前用simplify后反而更糟。另外可以对比一下onnx和torch的逐层输出,定位到底从哪个节点开始偏差变大,这样比瞎猜高效。实在不行,YOLOv5官方那个export.py带的优化选项都试一遍,默认的不一定是最稳的。
我之前也踩过这个坑,YOLOv5转ONNX置信度掉一截大概率不是量化的问题,因为默认fp32导出不会做量化,更像是Focus和SiLU被拆解后某些子图在ORT上精度有细微差异。建议先试试opset=12或者13,同时把dynamic_axes设成正确的batch和尺寸,有时候固定shape反而会触发奇怪的优化。onnx-simplifier可以跑一下,但主要解决冗余节点,对精度影响可能有限,更关键的是检查下导出的onnx里是否有大量Reshape/Transpose混在一起,那会改变数据排布导致输出偏差。另外可以对比一下ONNX和PyTorch在相同输入下的中间层输出,定位第一个差异出现的层,基本就能锁定是哪个算子的问题了。
这个情况八成是opset版本太低导致算子拆解后精度丢失,试试opset=17加dynamic_axes,simplifier也能帮上忙。
之前调yolov5也踩过这坑,重点检查下Focus的转换方式,手动改下网络结构可能比调参更直接。
八成是opset版本太低搞的,试试设成17再导出,Focus拆解不影响结果。
simplifier基本没用,重点检查下dynamic_axes和SiLU的精度差异。
我之前也踩过类似的坑,YOLOv5转ONNX后置信度掉一截,多半不是量化的问题,因为你说了amp已经关掉,那基本可以排除精度损失。你提到的Focus被拆成小算子,这个其实很常见,PyTorch里Focus就是个切片加concat,转ONNX后变成多个Slice和Concat节点,理论上不影响数值,但onnxruntime在优化图时可能会对某些中间张量的内存布局做调整,导致细微的浮点差异,虽然通常不至于让目标直接丢,但如果你的模型本身过拟合或者阈值卡得紧,这点误差就可能放大。opset版本确实值得查一下,建议直接用opset 12以上,太老的版本对SiLU这类激活函数的支持不完整,有时会隐式替换成近似的实现。dynamic_axes一般不影响精度,只影响输入尺寸灵活性,除非你在导出的batch size和实际推理时不匹配。onnx-simplifier可以试,它能合并很多冗余算子,有时候能消除那些因为拆解产生的数值扰动,但也不是百分百有效,我遇到过一次simplify后反而更糟的情况。另外你可以做个对比实验,把ONNX的输出用numpy存下来,和PyTorch的逐层输出对一下,看差异到底从哪一层开始出现的,这样定位更快。如果实在搞不定,也别急着上TensorRT,先试试用onnxruntime的CUDA execution provider跑一下,有时候CPU和GPU的算子实现细节也会导致差异。
我之前也踩过这个坑,YOLOv5转ONNX后置信度掉一截,多半不是量化问题,而是opset版本太低导致Focus被拆成Slice+Concat后精度丢失。你试试把opset设到12以上,然后dynamic_axes只给batch维度,另外SiLU在旧版onnxruntime里确实会走fallback路径,建议换onnxruntime-gpu最新版。onnx-simplifier可以跑一下,主要是清理多余节点,但对精度影响不大,如果还不行就检查下输入输出的归一化方式是不是被transpose搞乱了。
大概率是opset版本低了,导出时把opset拉到13以上,dynamic_axes设好,简化器能解决一部分问题。
我之前也踩过类似的坑,YOLOv5转ONNX后置信度掉得离谱,最后定位到是opset版本太低导致Focus被拆成Slice和Concat时数值精度没保住。你可以先试试opset=12以上,而且dynamic_axes最好把batch和height、width都设上,不然onnxruntime会固定输入尺寸,可能触发隐式resize。另外SiLU这个激活函数在旧版onnx里确实会被拆成Sigmoid和Mul,理论上数学等价,但浮点运算顺序变了就可能引入误差,尤其低置信度目标对这种微小差异特别敏感。onnx-simplifier建议跑一下,它能合并一些冗余算子,有时候还能把Focus直接优化成Conv的等价形式,我那次跑完简化后误差立刻小了一个数量级。不过还有个容易忽略的点,你导出时有没有把model.eval()和torch.no_grad()都加上?我见过有人因为没关训练模式导致BatchNorm参数被动态更新,那误差会更大。如果这些都试了还不行,建议你直接对比每一层输出的余弦相似度,定位到具体哪个节点开始漂移,而不是只看最终结果。TensorRT其实不用怕,它现在对ONNX支持挺成熟的,但先把ONNX调对再转,省得后面排查更头疼。
遇到过,Focus和SiLU被拆解后精度掉很正常,先试下opset13加dynamic_axes,simplifier对这类问题帮助不大。
大概率是opset版本太低导致算子拆解后精度漂移,试试opset=17同时开dynamic_axes,simplifier对YOLOv5帮助不大。
之前遇到过类似情况,Focus改成卷积后误差就没了,SiLU一般没事,重点检查下预处理和归一化有没有被ONNX图优化改掉。
建议先看下onnx输出的logits分布,大概率是opset版本把SiLU拆坏了,试试12以上版本。