最近在部署一个分割模型(DeepLabV3+,backbone是ResNet101),PyTorch 1.12直接转TensorRT(8.5.3)精度基本没问题,但中间加一步ONNX(opset 12)再转TRT,mIoU直接从0.78掉到0.61,主要错在边缘细节。我确认过ONNX模型的输出和PyTorch原模型是一致的,所以问题应该出在TRT解析ONNX图的时候。试过FP32、TF32、FP16,FP32也掉。有人说是Resize算子或者BN层折叠的问题?但我不太想换backbone。有没有人遇到过类似情况?最后是怎么定位到具体层或者解决的?感谢!
PyTorch转ONNX再转TensorRT,精度掉得离谱,有人遇到过吗?
全部回复
共 18 条我之前也踩过一模一样的坑,PyTorch直接转TRT没问题,中间过一道ONNX就崩,而且崩得毫无规律。后来我把ONNX的opset从12提到13或者更高,Resize算子的坐标变换模式在TRT解析时可能选错了实现,尤其是DeepLabV3+里那个双线性插值的上采样,对边缘影响特别大。你可以在转TRT前用ONNX Runtime跑一下那个ONNX模型,看看输出是不是真的和PyTorch一致,如果一致,那就锁定是TRT的层融合或算子映射问题。我建议你用trtexec的--dumpProfile或者--layerInfo看看每一层的输出,把TRT模型里所有Resize和Conv的权重导出来对比,重点检查BN层是不是被错误折叠了,因为有些时候TRT会把BN的epsilon参数搞丢。还有一招,你试试在ONNX里显式把Resize的mode改成nearest或者改成三线性插值做对照,如果精度恢复,那就是插值实现的差异。另外,既然FP32也掉,大概率不是精度模式的问题,你可以试一下onnx-simplifier过一遍,去掉一些冗余的Shape和Gather节点,有时候TRT对动态shape的图特别敏感。最后实在不行就绕开ONNX,直接用PyTorch导出成TorchScript再转TRT,虽然麻烦点,但至少不会死磕在这个解析问题上。
试试onnx转trt时把opset拉到13以上,之前我遇到过resize对齐方式不一样导致边缘崩的情况。
也可能是TRT对某些算子的实现用了低精度近似,先开强一致性模式跑一遍排除下。
这个坑我踩过,DeepLab系列转TRT后边缘崩掉大概率是Resize插值方式的问题。ONNX默认导出的Resize可能是nearest或者half_pixel对齐方式和原PyTorch不一致,TRT解析时又做了自己的处理,小目标分割上特别明显。你可以用polygraphy或者trtexec逐层dump对比一下ONNX和TRT中间输出,重点看ASPP后面那几个上采样层。我当时的解法是手动把Resize换成固定scale的Upsample并锁死坐标变换模式,mIoU基本就回来了。
边缘细节掉这么厉害,大概率是Resize插值方式在TRT里被改了。PyTorch转ONNX时如果用的是opset 12,Resize节点默认可能写成nearest或者half_pixel语义,但TRT早期版本对coordinate_transformation_mode支持很差,经常直接当asymmetric处理,分割模型边缘一塌糊涂就很合理。你可以先把ONNX里所有Resize的mode和coordinate_transformation_mode打出来,跟TRT解析后的层对比一下。另一个常见坑是BN折叠后卷积权重精度变化,尤其ResNet101这种深backbone,FP32下误差累积到后面也不小,但一般不会掉17个点这么夸张。建议用polygraphy或者trtexec逐层dump,把TRT输出和ONNX Runtime输出做cosine相似度,定位到第一个差异超过阈值的层。如果真是Resize,可以试着在导出ONNX前把插值换成固定尺寸的bilinear,或者用自定义插件替代。实在不行就保留PyTorch直转TRT那条路,没必要非绕ONNX。
你这个现象还挺典型的,ONNX那步输出对得上不代表TRT解析后图结构没变。我之前搞OCR分割也遇到过,最后发现是Resize在TRT里被降级成最近邻了,边缘直接糊掉。建议你拿polygraphy或者trtexec把每层输出dump出来,跟ONNX Runtime逐层对比,重点看Resize和插值那块。另外BN折叠在FP32下一般不会这么夸张,先排除算子映射问题吧。
这个坑我去年踩过,当时也是分割模型,ONNX那边输出对得严丝合缝,一进TRT就崩,最后发现是Resize算子在搞鬼。PyTorch转ONNX的时候如果Resize的mode是nearest,ONNX里可能会被拆成一堆Cast+Floor+Resize的组合,TRT解析时对坐标变换的rounding处理跟PyTorch不一致,边缘就糊了。你可以先把ONNX里Resize节点单独拎出来,用polygraphy或者onnxruntime跑一遍中间层输出,跟TRT同层对比,基本能定位到是哪几个节点精度对不上。BN折叠一般不会导致这么大的掉点,除非你的模型里有GN跟BN混着用,TRT对GN的折叠有时候会出问题。实在不行试试在转ONNX时把opset降到11,或者手动把Resize替换成TRT自带的插件,DeepLabV3+的ASPP里也有几个对精度敏感的算子,可以顺带查查。
我之前也踩过类似的坑,DeepLabV3+转到TRT之后边缘崩得厉害,最后发现是Resize算子的问题。ONNX里插值模式如果是half_pixel,TRT默认可能按asymmetric或者align_corners处理,双线性插值一偏边缘就糊了。你可以导出ONNX后拿netron看一下Resize节点的coordinate_transformation_mode,试着在转ONNX时固定成align_corners或者显式指定half_pixel,再对比TRT输出。另外BN折叠一般不会单独导致边缘掉这么多,更像是插值和后续concat的shape对齐出了偏差。
ONNX的Resize在TRT里插值模式经常被改,边缘掉点基本就是它,试试把opset降到11或者手动指定align_corners。
你这个掉点情况基本可以锁定在TRT解析ONNX时的图优化阶段,尤其是DeepLabV3+里那些空洞卷积和上采样路径。ONNX输出和PyTorch一致说明导出没问题,但TRT拿到ONNX后会做一层自己的图重写,Resize算子确实是个重灾区——opset 12里Resize的coordinate_transformation_mode如果和TRT默认行为对不上,边缘就会整体偏移一两个像素,分割任务对这种偏移特别敏感,mIoU掉十几个点完全可能。另外BN折叠在TRT里默认会做,如果某个BN的epsilon和PyTorch不一致,深层特征累积误差也会放大。我建议你先把ONNX用onnxsim过一遍,然后拿polygraphy跑逐层对比,把TRT每一层的输出和ONNX Runtime对齐,很快就能定位到是哪一层开始发散的。还有个偏方是转ONNX时把opset降到11试试,Resize的实现路径不一样,有时候反而能绕过去。实在不行就在ONNX里手动插一个自定义的Resize节点,把TRT那套默认逻辑顶掉。
这个坑我踩过,DeepLabV3+的ASPP里那些空洞卷积在ONNX导出时容易被拆成奇怪的子图,TRT解析后padding策略可能就变了,边缘最先遭殃。你可以先把ONNX用onnxsim跑一遍再转,有时候能消掉一些冗余节点,精度就回来一点。另外重点查Resize,opset12默认走的是half_pixel还是asymmetric要确认清楚,TRT对这两个的实现差异挺大的,分割边缘掉点十有八九是这里。定位的话建议分段dump,把backbone、ASPP、decoder分别导出单独比对,别整个模型一起转。我当时是decoder里的双线性上采样被TRT换成了最近邻,mIoU直接崩,后来手动插Resize节点指定模式才搞定。你也可以用polygraphy跑一下逐层对比,比盲猜快很多。
边缘细节掉多半是Resize插值方式变了,TRT对ONNX的Resize支持一直有坑,试试改成align_corners显式导出。
试试把ONNX的opset降到11再转,我之前Resize那块也是边缘崩,换opset就好了。
你提到ONNX输出和PyTorch一致,那基本能排除转换本身的问题,锅大概率在TRT解析ONNX那步。我遇到过类似的,最后定位到是Resize算子被TRT改成了最近邻插值,边缘直接糊掉,建议你把ONNX里所有Resize的mode和coordinate_transformation_mode打出来对一遍。另外BN折叠在TRT里默认会做,但如果有GN或者自定义norm就可能出岔子,可以试着用trtexec的--dumpLayerInfo看每层输出跟ONNX逐层比。还有个土办法是把ONNX切一半转TRT,二分法找哪层开始崩。
这个坑我踩过,而且也是分割模型,掉点几乎全在边界上,确实很像是图解析阶段把某些算子拆坏了。ONNX输出和PyTorch对齐只能说明导出那步没问题,但TRT在吃ONNX的时候会做自己的图优化和算子融合,Resize、Pad、还有插值相关的mode/coordinate_transformation参数经常被吃掉或者默认成别的,边缘就糊了。我当时用polygraphy逐层比对ONNX和TRT的中间输出,定位到是上采样那块插值方式变了,TRT把align_corners的行为处理得跟PyTorch不一致。另外BN折叠一般不会单独造成这么大幅度的mIoU下降,但如果它和Conv融合时遇到奇怪的输入布局,也可能连带把后面的Resize搞乱。建议你先拿polygraphy跑一下ONNX和TRT的逐层diff,别急着换backbone,大概率是某两三个算子的事。实在不行把Resize那段在ONNX里拆开或者换成TRT能正确解析的写法,通常能救回来。
你这情况八成是ONNX那步把Resize的坐标变换模式搞变了,PyTorch里align_corners和half_pixel行为对不上,TRT解析时默认按自己那套来,边缘自然糊。可以拿polygraphy逐层dump一下ONNX和TRT的中间输出,重点看Resize前后和最后一个上采样,差异一般就露出来了。我之前也是分割模型掉点,最后发现是opset 12里Resize的scales和sizes二选一被固定死了,改成显式sizes就回去了。实在不行试试opset 13或16,TRT 8.5对高版本解析反而更稳。
DeepLabV3+这种带空洞卷积和ASPP的结构,转ONNX时opset 12对Resize的坐标变换模式很容易被TRT解析偏掉,边缘细节崩掉基本就是这个原因。你可以把ONNX里所有Resize的coordinate_transformation_mode显式设成half_pixel或pytorch_half_pixel试试,别用默认的asymmetric。另外BN折叠在TRT里偶尔会把小batch的统计量算错,尤其在分割这种对边界敏感的任务上会被放大。定位的话建议用polygraphy逐层对比ONNX和TRT的中间输出,很快能锁定是哪个节点开始飘的。
这个坑我去年踩过,当时也是DeepLabV3+,不过backbone换成了MobileNetV2,现象几乎一模一样,ONNX输出对齐了但TRT就是边缘糊。后来定位下来是Resize算子在opset 12下被TRT解析成了最近邻插值,而PyTorch默认是双线性,边缘细节全丢了。你可以用polygraphy或者trtexec把每层输出dump出来,跟ONNX runtime的结果逐层比对,基本能锁定是哪一层开始偏的。另外BN折叠本身一般不会造成这么大掉点,但如果你的模型里有自定义的interpolate或者align_corners设置,TRT那边很容易默认关掉。还有个偏方是试试opset 11或者13,有时候换个版本TRT的parser行为就正常了。实在不行就在ONNX里手动插一个Resize节点,把mode和coordinate_transformation_mode写死,别让它自己猜。
这个思路不错,收藏了。