最近在部署一个分割模型(DeepLabV3+,backbone是ResNet50),PyTorch里跑测试集mIOU有0.78,转成ONNX后拿onnxruntime验证精度几乎没掉,但用trtexec转成TensorRT FP16后,输出mask明显有噪点,边界也不对。我试过调整dynamic shape范围和batch size,也关了FP16用FP32,问题还是存在。有人说可能是某些算子在TRT里实现有差异,也有人让我检查一下预处理里的归一化是不是被融合掉了。有没有大佬踩过类似的坑?是不是必须得用onnx-simplifier或者手动改网络结构才行?求个排查思路,谢谢!
PyTorch转ONNX再转TensorRT,为啥推理结果和原模型对不上?
全部回复
共 12 条大概率是BN折叠和算子的显式对齐问题,先试下onnx-simplifier再逐层对比输出定位。另外查下预处理归一化是否被TRT强制融合了。
大概率是预处理被TRT优化时改了数值分布,试试把归一化层留在模型外面或用INT8校准下。
我之前也遇到过类似的,分割模型对数值敏感,TRT里头FP16的精度损失经常不是全局的,而是集中在某些层,特别是BN和反卷积附近。建议你先用trtexec导出每层输出对比下,定位到具体哪个节点开始漂移,别急着改网络结构。预处理归一化确实容易被融合掉,你检查下TRT的层里是不是多了一个不合理的scale操作,手动把输入改成归一化后的数据再试。另外onnx-simplifier能去掉些冗余转换节点,但解决不了算子实现差异,关键还是得看是不是某个特定算子(比如Resize的坐标变换模式)在TRT里有bug。
(换个角度补充)我猜你可能漏了TensorRT的算法选择问题,它默认会用一些激进优化,像卷积的winograd或者tactic搜索,对精度影响不小。试试在构建engine时设置builder_optimization_level为1,或者直接对输出mask做方差统计,看看是不是恒定偏移——如果是,那就是某个层的量化scale算错了。我之前用DeepLabV3+也出过类似噪点,后来发现是aspp里的空洞卷积被TRT重排了顺序,手动加个identity层就稳定了。你可以先跑个单张图,把中间特征dump出来跟onnxruntime对,差个1e-2以内基本就正常,别信trtexec的默认验证。
我之前做检测模型转TRT也碰到过类似情况,ONNX Runtime精度没问题但TRT就是输出怪。后来查了半天发现是BatchNorm层在TRT里被折叠的方式跟PyTorch不完全一致,数值上会有微小偏差,但分割任务对边界特别敏感,这点偏差就被放大了。你试试用polygraphy对比一下中间层的输出,定位到具体是哪一层开始差的,别上来就改网络结构。另外你说FP32也有问题,那大概率不是精度模式的事,更像是算子映射或者图优化导致的语义变化,比如Resize的坐标变换模式,TRT默认可能跟PyTorch的align_corners行为不同,这个坑特别隐蔽。预处理归一化被融合这事确实存在,但onnxruntime没掉精度说明ONNX本身没问题,问题更可能出在TRT的优化上。我建议你先固定输入尺寸,排除dynamic shape的干扰,再用onnx-simplifier过一遍,同时把TRT的layer可视化出来检查有没有奇怪的算子替换。如果还不行,可以试试把backbone里某些敏感操作(比如上采样)拆开写,强制TRT用特定实现。别急着手动改整个网络,先做逐层对比排查,通常能找到根因。
FP16下分割模型边界出噪点挺常见的,尤其是DeepLabV3+里的空洞卷积和上采样,TRT对某些padding方式处理跟PyTorch不完全一致。你可以先用polygraphy逐层比对ONNX和TRT的中间输出,定位到具体是哪个节点开始飘的,比盲目调shape有用。另外预处理那块也要确认下,TRT不会自动帮你做mean/std归一化,如果之前是靠ONNX图里的Sub/Div,转TRT时容易被优化掉。实在不行试试trtexec加--precisionConstraints=obey,强制关键层走FP32。
DeepLabV3+这类分割模型转TRT后边界变糊、出噪点,我第一反应就是空洞卷积和上采样那块出了问题。TRT对dilated conv的支持在某些版本里确实有坑,尤其是配合FP16时,padding方式可能和PyTorch不一致,导致特征图偏移一两个像素,分割边界就对不齐了。你可以先把backbone里的dilation关掉,用普通conv跑一遍对比,如果精度回来了那基本就锁定是这个原因。另外预处理那块也别放过,onnx里如果归一化是作为独立算子存在的,TRT有时会把它和前面的conv融合,但mean/std的顺序或者广播维度稍微对不上,输出就会整体偏。建议你用polygraphy或者trtexec的--dumpLayerInfo把每层输出和onnxruntime逐层比对,看从哪一层开始cosine similarity掉下去。onnx-simplifier值得跑一下,它能把一些冗余的transpose和reshape消掉,减少TRT解析时的歧义。至于手动改结构,先别急着动,大部分情况是算子版本和精度设置的问题,实在不行把上采样那块换成resize加conv的显式写法试试。
我之前也遇到过类似情况,最后发现是ResNet里的残差连接在TRT FP16下精度损失被放大了,尤其分割这种密集预测任务特别敏感。你可以先试试用polygraphy逐层对比ONNX和TRT的中间输出,定位到具体哪个节点开始跑偏。另外DeepLabV3+的ASPP里有空洞卷积,TRT对某些dilation配置的优化确实可能出问题,建议把TRT的tactic限制成跟ONNX一致的看看。onnx-simplifier值得跑一下,但多半不是它的事。
DeepLabV3+里的空洞卷积和ASPP在TRT里确实容易出问题,尤其是FP16下dilation rate大的层精度损失很明显。建议先把模型拆开逐层对比PyTorch和TRT的输出,定位到具体哪一层开始跑偏,别一上来就改结构。另外归一化那块也要确认下,TRT有时候会把预处理算子吞掉导致输入分布不对。onnx-simplifier可以试试,但更关键的是用polygraphy跑一下逐层对比,很快能看出问题出在哪。
这个坑我去年搞OCR模型的时候踩过一模一样的,最后发现是ResNet里的残差连接在TRT FP16下累加时精度损失被放大了,尤其是分割这种对像素级敏感的任务,边界噪点特别明显。你可以先确认下ONNX里的Resize或者插值算子是不是被TRT替换成了最近邻,DeepLabV3+的ASPP里空洞卷积和上采样对插值方式很敏感。另外预处理归一化被常量折叠掉这事确实存在,建议用polygraphy跑一下逐层对比,看哪一层输出开始崩。onnx-simplifier主要解决的是冗余算子和shape推导问题,对精度帮助有限,但可以试试把Conv和BN手动融合后再导出。还有个偏方是把TRT的builder optimization level调到0或1,有时候高优化级别会激进地重排计算顺序导致数值偏差。如果实在搞不定,可以用TRT的explicit precision把关键层强制锁FP32,其他层跑FP16,一般能救回来。
FP16对分割边界确实容易出噪点,先拿polygraphy逐层对比ONNX和TRT输出,定位是哪层炸的。
FP16出噪点挺常见的,尤其分割这种对边界敏感的活儿。你先别急着改结构,用polygraphy跑一下逐层对比,看是哪一层开始跟onnxruntime对不上,大概率是某个op在TRT里降精度降过头了。另外预处理那块也值得查,有些归一化如果是写在模型外面的,转TRT时容易漏掉或者被优化掉。实在不行把可疑层强制设成FP32再跑,能定位问题就好办。
FP16下有噪点挺常见的,你可以先用polygraphy跑逐层对比,看是哪一层开始误差爆掉的,一般ResNet50里的BN或者分割头附近的算子容易出问题。另外DeepLabV3+里的ASPP有大量空洞卷积,TRT对dilation的支持有时会偷偷降精度,建议把每个dilation conv单独拆出来验证。归一化那块也要确认下,如果预处理是在PyTorch外部做的,TRT引擎里没带进去,输入分布就偏了。onnx-simplifier可以先跑一遍,但别指望它解决所有问题,关键还是定位到具体算子。