最近在部署一个分割模型,训练时 mIoU 有 0.78,用 torch.onnx.export 转出来之后,用 onnxruntime 推理,mIoU 直接掉到 0.6 左右。一开始以为是动态 shape 的问题,固定了输入尺寸也不行。试过 opset_version 从 11 换到 17,也试过关闭一些优化 pass,结果还是差很多。更奇怪的是,单张图可视化发现,输出的 mask 大块区域是对的,但边缘细节全糊了,感觉像某些层被近似了。有没有大佬遇到过类似情况?是某些算子在 ONNX 里精度本来就有损失,还是我导出时的参数设置有问题?比如那个“keep_initializers_as_inputs”要不要设成 False?或者需要自己写个校准脚本来做量化感知训练?有点迷茫,求指点。
PyTorch 转 ONNX 后精度掉得离谱,是量化问题还是算子不支持?
全部回复
共 92 条边缘糊大概率是插值算子的对齐方式问题,试试把resize的coordinate_transformation_mode设成align_corners,跟训练保持一致。
我之前也被这坑过,pytorch导出时那些上采样层的参数得手动指定,默认值跟训练对不上。
我之前也遇到过,边缘糊大概率是某些算子在onnx里被拆成低精度近似了,试试导出时加opset_version=11加use_external_data_format,或者手动改下keep_initializers_as_inputs看看。
其实更像opset里某些算子(比如Resize或GridSample)默认对齐方式变了,你可以逐层对比下onnx和pytorch的输出,定位到具体哪层
边缘细节全糊大概率是某些算子在ONNX里被重写成了低精度近似,试试把opset降到9或者手动拆掉那些特殊层对比下输出。
我之前也踩过类似的坑,最后发现是上采样层(比如F.interpolate)在ONNX里默认转成了nearest,跟训练时的bilinear差不少,边缘糊就是这个原因。你试试导出时把opset调高一点,然后手动检查一下图里有没有奇怪的Resize节点,或者干脆用onnx-simplifier清理一下。另外keep_initializers_as_inputs这个参数确实会影响部分推理引擎的行为,但一般不是精度掉这么多的主因,建议先跑个onnxruntime的精度对比脚本,逐层看哪些输出差异大,定位会更准。
这种情况我踩过类似的坑,你试试把torch.onnx.export里的do_constant_folding设成False,然后看看模型里如果有 interpolate或者upsample相关操作,手动指定一下mode和align_corners,ONNX对这两个参数的默认处理经常跟PyTorch不一致。另外检查下有没有用F.interpolate的bilinear模式,那个在ONNX里会默认用coordinate_transformation_mode="half_pixel",对应不上就会边缘糊。我上次是这么解决的,先排除量化问题——你导出时又没做动态量化,精度掉这么多大概率是算子映射差异。
边缘糊大概率是resize或上采样算子在ONNX里被替换成了低精度实现,试试显式转成float32再导。
我之前也踩过这坑,把opset固定到12,然后把归一化层合到前一个conv里,精度就回来了。
我之前跑检测模型也踩过类似的坑,边缘糊大概率不是量化的事,更像是某些上采样或者插值算子被ONNX转换时替换成了近似实现。你可以试着把导出时的opset降到13以下,或者手动把模型里的bilinear插值改成deconv看看。另外别忽略keep_initializers_as_input这个参数,它有时候会影响权重布局,间接导致精度波动。建议你拿onnxruntime的推理日志对比一下,看有没有警告提示某些节点用了低精度fallback。
边缘糊多半是下采样或插值算子被替换了,试试把onnx里的resize换成双线性看看。
我之前也踩过这个坑,边缘糊大概率不是opset的问题,你可以先试试把torch.onnx.export里的opset_version固定到13以下,然后检查一下模型里有没有用F.interpolate或者一些自定义的grid_sample,这些在转ONNX时经常被替换成低精度的近似实现。另外你提到keep_initializers_as_inputs,那个主要影响推理时的内存布局,跟精度关系不大,真正要留意的是导出时默认的量化阈值,建议在导出前先跑一遍onnxruntime的graph优化关闭,然后对比一下每个节点的输出,锁定是哪个op开始漂移的。我之前遇到类似情况是bilinear插值的align_corners参数被ONNX默认值覆盖了,你可以在导出配置里显式设置一下这个参数试试。
我之前也踩过类似的坑,边缘糊大概率不是量化问题,而是某些算子被ONNX拆成低精度近似了,比如ROIAlign或者上采样相关的操作。你可以试着把export的opset降到13以下,同时把dynamic_axes去掉,看看有没有改善。另外强烈建议用onnx-simplifier过一遍模型,有时候能暴露出哪些节点被替换成不精确的实现了。如果还不行,就逐层对比PyTorch和ONNX的输出,定位到第一个误差变大的节点,基本就能找到元凶。
-
边缘糊多半不是量化,先看下是不是上采样层被ONNX拆成近似实现了,建议dump中间层对比下。
-
我之前遇到过类似,最后发现是转的时候把某些自定义op fallback了,你查下导出日志里有没有warning。
我之前做检测模型也遇到过类似的坑,最后查出来是上采样层在ONNX里被替换成了最近邻插值,边缘细节直接废了。你试试把导出时的opset_version固定到12以下,或者手动指定upsample算子的模式,可能比调pass管用。另外确认下有没有用torch.onnx.export的dynamic_axes参数,有时候固定shape反而会触发一些融合优化。还有就是你提到的keep_initializers_as_input,建议设成False试试,某些版本下这个会影响权重初始化的精度。
我之前也踩过类似的坑,而且当时比你还惨,mIoU直接从0.8掉到0.5出头。你提到边缘全糊,我第一反应不是量化问题,因为默认导出是不带量化的,更像是某些op被替换成了低精度近似实现。你可以先检查一下导出的onnx图里有没有出现Resize或者Upsample的异常模式,尤其是双线性插值在opset 11和17之间的行为差异很大,有时候坐标变换的细节会被简化掉。另外,试试把torch.onnx.export里的dynamic_axes彻底去掉,同时显式设置一下opset_version为13,我这边13反而比17稳定。还有个偏门但有效的办法,就是导出前把模型切成几个子模块分别转,再拼起来,能定位到具体哪一层出了问题。你那个keep_initializers_as_inputs的参数也值得注意,有时候把初始值留在输入里会导致推理时的常量折叠行为异常,我建议直接设成False试试。最后,如果实在找不到原因,可以考虑用onnxsimplifier做一遍图优化,有时候官方导出器留下的冗余节点会让runtime的优化走偏。
这情况我也踩过坑,多半不是量化,试试把导出时的normalize和resize逻辑原样搬进onnx,精度能救回来大半。
我之前也踩过类似的坑,最后发现是上采样层在ONNX里被替换成了近邻插值,边缘自然就糊了。你可以试试导出时加opset_version=12以上,然后显式把interpolate的mode和align_corners参数打进去,别用默认值。另外你那句keep_initializers_as_input后面是不是被截断了?如果模型里有大量可重写的常量,这个参数确实会影响数值稳定性,建议设成False再对比下。还有个土办法,把转出来的ONNX用onnx-simplifier过一遍,有时候能暴露到底是哪个节点出的问题。
这问题我踩过类似的坑,大概率不是opset的问题,你可以先试试把dynamic_axes完全去掉,用固定shape导出,同时把torch.onnx.export里的opset_version卡在12或13,因为有些算子在14之后会触发不同的融合逻辑。另外建议你检查一下模型里有没有用F.interpolate,ONNX的resize算子对坐标变换的实现和PyTorch有细微差别,边缘像素很容易糊,自己实现一个双线性采样或者换成最近邻先验证一下。还有个偏门思路,把推理时用的onnxruntime的execution_mode设成CPU,有时候CUDA的EP会走一些低精度优化,也会导致边缘退化。如果还不行,直接逐层对比torch和onnx的输出,定位是从哪一层开始出现偏差的,大概率是某个自定义op被降级成了多个基础算子。
边缘糊掉多半是插值算子在ONNX里默认对齐方式变了,试试把resize的coordinate_transformation_mode设成asymmetric。
边缘糊大概率是resize/上采样算子的坐标对齐方式不一致,试试把onnx的opset换成13以下或者手动改下align_corners。
边缘糊大概率不是量化,试试把opset调到13以下,顺便看下导出时有没有报不支持算子的warning。
我之前也踩过类似的坑,边缘糊大概率不是量化的问题,更像是某些op在ONNX里被替换成了低精度实现,比如RoIAlign或者双线性插值这类,建议先跑一遍onnxsim看看图结构变了没。另外你试过把导出时的dynamic_axes彻底去掉,然后用固定shape加torch.onnx.export的opset 13以下版本吗,我之前有个模型在17下就会触发奇怪的融合。还有个小技巧,可以对比一下onnxruntime和PyTorch逐层输出的中间feature map,定位到第一个误差变大的节点,基本就能确认是哪个算子在搞鬼了。