最近在部署一个分割模型,训练时 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 条遇到过类似情况,当时排查下来是上采样层(比如bilinear插值)在ONNX里被替换成了非标准实现,边缘细节直接拉胯。你可以试着把导出时的opset固定到12以下,或者手动把模型里的F.interpolate换成conv_transpose试试,我这么改完精度基本就回来了。
另外记得检查一下导出时有没有把batch维度锁死,有时候动态轴没指定对也会导致推理时统计信息错乱。你那个keep_initializers_as_inputs参数如果设成False,某些常量折叠优化会改变数值路径,建议设成True再对比下。如果还不行,就用onnxsim精简一下图结构,再逐层对比中间张量的cos相似度,定位到具体是哪一层开始漂移的。
遇到过类似的,但不是分割模型,是检测头那边转完精度掉一截。后来查出来是上采样那块,F.interpolate默认的bilinear在ONNX里映射到Resize时,coordinate_transformation_mode和training时不完全一致,边缘像素就是差在这。你试试导出前把插值方式显式指定成align_corners=False,或者干脆换成nearest验证下是不是这个原因。另外keep_initializers_as_inputs那个参数确实会影响图结构,但一般不太导致精度崩,你可以先排除掉它。
我之前也被这个坑过,最后定位到是InstanceNorm或者BatchNorm在eval模式下转ONNX,某些opset版本会把均值方差折叠成常量,但折叠精度是float32的,如果你的模型里混合了float16或者某些敏感层,边缘就容易被抹平。你可以试着把模型里所有norm层换成GroupNorm试试,或者导出时加一句torch.onnx.export的operator_export_type设为ONNX_ATEN_FALLBACK看看报不报warning,有些算子会悄悄走ATen fallback精度就不对了。
我之前也踩过类似的坑,分割模型转ONNX后边缘糊多半不是量化问题,而是某些上采样或插值算子在ONNX里的实现跟PyTorch不完全一致。你可以先检查一下模型里有没有用F.interpolate,特别是align_corners这个参数,ONNX对它的支持有时候会悄悄改变行为。另外建议你用onnxruntime的CPU和CUDA各跑一遍对比下,如果结果有差异那就是算子实现问题。我之前是把双线性上采样换成反卷积或者直接用ONNX的Resize算子重写那部分才解决的,你可以试试看。
遇到过类似的,但不是分割是检测,转完onnx后bbox回归精度掉了不少。后来查出来是upsample和某些归一化层在onnxruntime里走的实现和pytorch不完全一致,尤其是边缘像素的插值方式有细微差别,视觉上就是边缘糊。
你试试把模型里的双线性插值换成最近邻或者显式用grid_sample,看误差是否缩小。另外检查一下有没有用F.interpolate的align_corners参数,这个在onnx里经常被默认成False,容易踩坑。
还有那个keep_initializers_as_input,建议设成False看看,有时候会引入额外的输入节点干扰推理。如果还不行,可以逐层对比输出,定位到具体哪个节点开始偏差变大的,比盲目调opset效率高。
遇到过类似的情况,不过我是做检测模型的,转完ONNX后边界框回归精度掉了不少。你提到固定shape和换opset都没用,那大概率不是动态维度的问题,我怀疑是某些算子在转换时被重写成了低精度或者近似实现,尤其是涉及到上采样、插值或者softmax这类操作。边缘细节糊掉这个现象很典型,我上次排查时发现是F.interpolate在ONNX里默认被映射成了nearest模式,跟PyTorch里的bilinear行为差很多,你可以检查一下导出的图里这个算子。另外keep_initializers_as_inputs这个参数确实会影响最终图的拓扑结构,但它一般只影响推理时的输入输出,不太会导致精度突变,倒是可以试试把导出时的do_constant_folding关掉,我见过有些fold操作把数值精度搞崩的。还有个思路是你直接在onnxruntime里逐层对比中间tensor,跟PyTorch的feature map做差值,这样能定位到是哪一层开始分叉的,比盲调参数高效得多。如果最后定位到是某些算子不兼容,可以考虑用onnx-simplifier或者手动把那一层替换成自定义op,我之前就是这么解决的。
我之前也踩过类似的坑,不过不是分割模型,是检测头那边。你提到边缘全糊,我第一反应不是量化,因为默认导出其实不会动权重精度,更像是有算子被替换成了低精度实现或者融合出了问题。你可以先查一下onnxruntime的log,开verbose模式看有没有warning提示某些节点fallback到CPU或者用了低精度kernel,我那次就是有个InstanceNorm被莫名替换成了实验性实现,精度直接崩。另外你说的keep_initializers_as_inputs那个参数,我建议你试一下设成False,有时候保留初始值作为输入会导致graph结构变化,反而触发一些不稳定的优化路径。还有一个偏门但很有效的排查方法:用onnxruntime的C API逐个跑中间节点,对比PyTorch中间tensor的差异,锁定第一个误差放大的层。我猜你可能是bilinear上采样或者某些grid_sample类算子被转成了若干个基础op的组合,精度损失在多次插值里累积了。如果实在找不到,可以试试用onnx-simplifier过一遍,有时候冗余的shape运算会误导优化器。最后,如果模型不大,也可以考虑干脆转成TensorRT或者用torchscript直接部署,省心很多。
遇到过类似的,但不是分割模型,是检测头那边转完直接丢小目标。你这个边缘糊掉更像是上采样或者 interpolate 算子的问题,ONNX 里某些 resize 模式默认对齐方式跟 PyTorch 不一致,尤其是 align_corners 没显式指定的话,转出来很容易踩坑。建议先排查一下模型里有没有用 F.interpolate,把 mode 和 align_corners 都固定了再导一次试试。另外 keep_initializers_as_inputs 那个参数一般影响不大,除非你后续要做量化,不然可以先不管它。
我之前也踩过类似的坑,不过不是分割模型,是检测头那边。你提到大块区域对、边缘糊,这个现象其实挺典型的,我怀疑大概率不是量化问题,因为ONNX默认导出是浮点,除非你显式开了dynamic quantization。更可能的是某些算子被替换成了低精度近似实现,比如一些上采样或者反卷积在ONNX里的实现跟PyTorch原生不完全一致,尤其是align_corners这类参数,稍微差一点边缘就会很敏感。你试过opset 17,但有没有对比过onnxruntime和PyTorch的逐层输出?建议你把模型拆开,把可疑的层(比如F.interpolate、nn.Upsample、还有那些带epsilon的归一化)单独拎出来跑一遍,看数值差异是不是从这里开始的。另外keep_initializers_as_inputs这个参数,我记得在某些版本里如果设成False,会导致权重被折叠进常量,理论上不影响精度,但如果你用了自定义导出脚本,有可能把一些buffer误处理了。我上次最后发现是BatchNorm的momentum在导出时被重置了,导致推理时统计量不对,你可以检查下BN层是不是被fuse了,有时候融合反而会引入数值误差。还有个小建议,试试用torch.onnx.export里的dynamo模式,PyTorch 2.0以后那个新导出路径对算子的保真度高一些,虽然有时候慢,但精度问题排查起来省事很多。
我之前也碰到过类似的情况,最后发现是上采样层(比如F.interpolate)在ONNX里被转换成了不同的实现,导致边缘细节丢失。你可以试试把模型里的双线性插值换成转置卷积,或者导出时加上torch.onnx.export的dynamic_axes参数再对比下输出。另外如果用了自定义算子,ONNX Runtime的CPU实现精度可能和PyTorch的CUDA不一样,建议先检查下是不是所有层的输出都能对齐。
边缘糊了大概率是插值算子的锅,试试显式指定resize的坐标变换模式,onnx默认的half_pixel和torch里对齐方式差挺多的。
我之前也被这个坑过,转的时候把opset拉高到13以上,然后重写一下forward里的interpolate,基本能对齐。
我之前也栽过这坑,试试把插值层的mode换成nearest,或者排查下有没有用自定义op,多半是算子映射的锅。
这问题我上周刚踩过,边缘糊大概率不是量化,而是ONNX的某些算子融合把细节丢了。你可以先试试把torch.onnx.export里的do_constant_folding设成False,我之前这么搞直接救回来0.1的mIoU。另外你用的什么上采样方式?如果是F.interpolate,ONNX里bilinear的实现跟PyTorch的align_corners默认值有细微差异,这也会导致边缘对不齐。建议你导出时把input_names和output_names显式定义好,然后对比一下ONNX和PyTorch输出的逐像素差值,看看是不是集中在边缘区域。如果还不行,试试用onnx-simplifier过一遍,有时候能消除一些冗余的精度损失节点。
我之前跑检测模型也踩过类似的坑,最后发现是上采样层(比如F.interpolate)在ONNX里被重写成了不同的实现,边缘细节就崩了。你可以试试把导出时的opset_version固定到15以下,然后显式用ONNX的Resize算子替换掉双线性插值,看看mIoU能不能回来。另外,如果用了custom op,检查下onnxruntime是不是真的支持,否则可能走了降级路径。还有个笨办法,逐层对比pytorch和onnx的输出tensor,定位到第一个差异大的层,基本就能锁定问题。
这情况太典型了,多半不是量化,你查查上采样和插值类算子,ONNX 对 align_corners 支持容易出坑。
这问题我踩过类似的坑,边缘糊大概率不是量化,而是某些算子在onnx里被重写成了低精度近似实现,尤其是上采样和反卷积附近。你可以先试试把导出时的opset固定到12以下,同时把torch.onnx.export里的do_constant_folding关掉,对比一下输出差异。另外强烈建议用onnxruntime的推理结果和pytorch逐层对比中间tensor,定位到具体是哪一层开始漂移,比瞎试参数高效得多。我之前是卡在grid_sample上,换成最近邻插值就正常了,你可以往这个方向排查下。
我之前也踩过类似的坑,最后定位到是上采样层的问题,尤其是双线性插值在ONNX里不同opset下的实现会有细微差别,边缘像素容易出偏差。你可以试试把模型里的interpolate换成最近邻或者转置卷积验证一下,如果精度回来了就基本锁定原因了。另外别忽略那个keep_initializers_as_inputs,设成False有时能减少图结构里的冗余节点,对精度也有影响。还有就是你导出前最好确认下模型是不是在eval模式,dropout和BN层没冻结的话,推理结果飘也很正常。
我之前也踩过类似的坑,分割模型边缘糊大概率不是量化问题,而是某些上采样或插值算子在ONNX里被替换成了近似实现,尤其是align_corners参数不一致的时候。你可以先导出时加个torch.onnx.export的operator_export_type参数试试,或者用onnxruntime的graph optimization level调到0看看能不能定位到具体节点。另外建议对比一下onnx和torch输出在每一层的中间feature map差异,这样能快速锁定是哪几个op出了问题。我之前是换成了ONNX支持的Resize模式才解决的,你可以检查下模型里有没有用F.interpolate。
这情况我碰到过,多半是某些上采样或特殊算子被替换成低精度实现,试试加onnx::optimizer关掉fuse_bn和eliminate_deadend。
这情况我也踩过,别急着怪量化,先查下有没有用上grid_sample或插值,ONNX对这类算子支持挺坑的。
遇到过类似的,先别急着怪量化,试试把导出时的opset版本固定到13以下,有些算子在ONNX里的实现确实精度有损。