最近在部署一个分割模型,训练时 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 条遇到过类似的,但不是分割模型,是检测头回归坐标的时候精度崩了。后来查出来是某些上采样或者插值算子在ONNX里的实现跟PyTorch不完全一致,尤其是align_corners这个参数,默认值两边就不一样,你检查下这个。另外keep_initializers_as_inputs那个参数建议设成False试试,有时候会把权重变成输入导致精度异常,虽然看着不相关但真有人踩过坑。边缘糊的话,也可能是转的时候把一些fused的bn层拆了,试试把模型设成eval模式再导出。
这个大概率是某些算子在ONNX里被重写了,试试把opset降到13以下,另外检查下有没有用到torchvision的插值算子,那个导出容易出问题。
大概率是某些算子在onnx里被重写了,比如插值或归一化,试试逐层对比中间输出定位下。
遇到过,多半是某些算子在ONNX里被替换成低精度实现了,试试导出时加opset_version=17配合dynamo模式,或者检查下preprocess的归一化参数有没有被错误折叠。
之前跑检测模型也碰到过类似情况,建议先看看是不是某些特殊算子被拆成了低精度近似实现,比如上采样或者插值类操作。你可以试着把导出时的opset版本固定到15以下,同时关掉onnx的graph优化,再用onnxruntime的log_severity_level调成0看看有没有warning提示具体哪个节点有问题。另外最好对比一下pytorch和onnx的逐层输出,定位到第一个差异大的层,我上次就是这么发现是resize模式导致的偏差。
大概率是某些上采样或插值算子不兼容,试试显式转成resize或者换opset版本对比下输出。
我遇到过类似的,边缘糊大概率不是量化问题,而是某些算子在ONNX里被拆成了低精度近似实现,尤其是上采样和反卷积这块。你试试把导出时的opset版本固定到12,然后显式关掉那个“eliminate_dead_connections”之类的pass,或者干脆用onnx-simplifier处理一下图结构。另外检查下有没有用到grid_sample这类算子,ONNX的算子实现和PyTorch原生行为经常有细微差异,尤其对坐标敏感的操作,边缘误差会被放大。要是方便的话,可以对比下ONNX模型和PyTorch模型逐层输出的最大误差,定位到具体哪一层开始漂移,这样比盲调参数高效得多。
遇到过类似的,但不是分割是检测模型,转完ONNX后边界框回归精度掉得厉害。你这个问题大概率不是量化,因为ONNX默认导出就是FP32,重点怀疑是某些上采样或者插值算子的实现差异,尤其是align_corners这种参数在ONNX里不同opset下行为可能不一致。建议你先把模型里所有涉及grid_sample、interpolate的层单独拎出来做输入输出对比,看看是不是在某个节点开始特征图就出现偏差。另外torch.onnx.export里有个dynamic_axes的配置,如果你输入输出都设了动态维度,即使固定了实际尺寸,某些算子也可能走不同的优化路径,试试只对batch维度设动态,其他全静态。还有个野路子,用onnx-simplifier过一遍,有时候能暴露出隐藏的精度坑。
遇到过类似的,但不是分割是检测,后来发现是插值层的align_corners在ONNX里默认参数对不上,尤其是上采样倍数大的时候边缘会糊,你检查下F.interpolate的配置。另外你提到keep_initializers_as_inputs,这个一般不影响精度,但可以试试把opset降到13以下,有些算子在更高版本会走不同的实现路径。还有个坑是batch norm在eval模式下导出时会被folding,但某些自定义实现会保留成训练模式,导致分布偏移,建议导出前确认模型已经切到eval且所有bn都冻结了。如果还不行,可以逐层对比onnx和pytorch的输出,定位到第一个差异大的节点,基本就能锁定问题算子。
遇到过类似的,但不是分割模型,当时是检测头那边掉点。你这情况我第一反应不是量化,因为onnx默认导出是fp32,量化得自己显式开,更像是某些op被替换成了低精度实现,比如InstanceNorm或者插值层的转换差异。建议先对比一下onnx和pytorch输出的逐层feature map,定位到具体哪一层开始漂移,另外试试把opset降到13以下,有些高版本op的近似算法反而更激进。边缘糊这个特征,我猜是上采样层或者边界相关的卷积被融合了,可以关掉graph优化里的fuse_bn和eliminate_deadend再跑一次看看。
我上次转Deeplab也碰到过类似情况,后来发现是batch norm在eval和train模式下导出的差异,你把模型切到eval再导出试试。另外可以检查下有没有用到grid_sample或者自定义op,这类算子在onnx里经常会被拆成近似实现,边界自然就糊了。如果确认不是这两点,可以试试用onnx-simplifier过一遍图,有时候冗余节点会导致精度异常。
我之前也踩过类似的坑,分割模型转ONNX后边缘模糊大概率不是量化问题,而是某些上采样或插值算子在ONNX里的默认模式跟PyTorch不一致,比如align_corners没显式传的话,导出时会用错误的坐标映射。你可以检查一下模型里有没有用F.interpolate或nn.Upsample,试试在导出时加上opset_version=12以上,并且在torch.onnx.export里显式指定opset的算子版本,或者试一下用onnxsim简化后对比。另外,keep_initializers_as_inputs这个参数我通常设False,避免多余输入干扰,但更关键的是把模型切成几段分别转,定位是哪一层开始崩的。