最近在把一个语义分割模型(DeepLabV3+,backbone是ResNet50)从PyTorch转到TensorRT部署。流程是pt→onnx→trt,用trtexec转的FP16。本地测试单张图还行,但一跑完整验证集,mIoU直接掉了4个点,而且发现掉精度主要集中在暗部区域和小目标上。
PyTorch模型部署到生产环境,ONNX转TensorRT精度掉得离谱,求排查思路
全部回复
共 57 条我之前也踩过类似的坑,FP16在小目标和低对比度区域掉点基本是常态。建议先检查一下ONNX导出的精度,跟PyTorch对一遍输出的余弦相似度,排除是转换环节的问题。另外trtexec默认的FP16可能对某些层不太友好,试试在转引擎时加上--fp16 --fastmath=false,或者用TensorRT的官方量化工具做PTQ校准,用一小部分验证集做校准数据,能明显改善暗部区域的分布。还有个笨办法,就是把这些敏感层单独拉出来用FP32跑,虽然麻烦但定位问题很快。
之前跑过类似的坑,暗部和小目标掉点大概率是FP16的动态范围问题,建议先用trtexec加--fp16但关掉层融合试试,定位是不是某些层精度敏感。另外ONNX导出时把opset版本拉高到17以上,有些算子默认实现精度差挺多的。还有个笨办法,把输入图像先做归一化到0-1再转,有时候预处理方式在TRT里会被隐式改掉。实在不行就混合精度,给那几个关键层单独设FP32,代价是推理慢点但能保住mIoU。
遇到过类似的坑,当时也是FP16掉点,后来发现是ONNX导出时把一些LayerNorm和激活函数融合得有问题,转TRT后数值波动被放大了。你可以先试试用trtexec加--fp16但不开任何tactic,再对比一下是不是某些kernel选择的问题。暗部和小目标掉点很可能是通道数少的层在FP16下精度损失更敏感,建议用polygraphy检查一下每层输出的最大误差,定位到具体是哪几个节点出事。另外别忘了确认下ONNX里有没有动态shape或者Resize的坐标变换模式不一致,这个也会导致局部精度崩。
检查下onnx里有没有动态shape相关的op,TensorRT对这类处理容易出问题,暗部和小目标尤其敏感。
这个现象我太熟了,之前做检测模型也踩过同样的坑。你提到暗部区域和小目标掉点,基本可以锁定是FP16的动态范围问题,ResNet50里的BN层在转TRT时折叠后,对低灰度特征的敏感度会明显下降。建议先别急着上FP16,用trtexec加--fp16的同时,把--precisionConstraints和--calib试试,或者干脆先用FP32跑一遍全验证集,确认是转换精度损失还是优化器选择的问题。另外,ONNX导出时注意一下opset版本和Deformable Conv(如果你用了的话)的支持情况,之前遇到过算子被替换成低精度实现的情况。如果确认是FP16的问题,可以考虑对敏感层单独设置保持FP32,TRT的per-layer精度控制能救回来不少。还有个土办法,把输入图像做一下直方图均衡化再进模型,能缓解暗部区域的数值截断,但治标不治本。最后建议你对比一下ONNX Runtime和TRT的逐层输出,写个小脚本找出第一个误差变大的层,基本就能定位了。
FP16掉点集中在暗部和小目标,大概率不是TensorRT的锅,先检查下ONNX导出时有没有把归一化层或resize模式搞变了,我之前遇到过opset版本不同导致插值方式从bilinear变成nearest的情况。另外建议你用trtexec的--saveEngine和--loadEngine配合Polygraphy做个逐层对比,看看是哪个layer开始发散,暗部区域很可能是中间激活值太小,FP16下denormals处理不当造成的。如果确认是精度敏感层,可以试试给那几个算子单独开FP32,或者用per-channel量化替代默认的per-tensor,也能救回来不少。
暗部和小目标掉精度挺典型的,先查下onnx转trt时fp16的per-channel量化,试试给这几层单独开fp32。
建议先做个逐层精度对比,大概率是batchnorm折叠或resize插值在trt里实现不一样,暗部区域可以试下INT8+校准集。
暗部和小目标掉点基本就是FP16的动态范围问题,试试per-channel量化或者敏感层回退FP32吧。
之前跑检测模型也踩过类似的坑,FP16下小目标掉点基本是常态。建议先别急着调trt,把onnx的fp32和fp16精度对比一下,如果onnx这步就有问题那就是导出时候算子兼容性的锅。另外暗部区域掉点大概率是反卷积或者上采样层在fp16下的累积误差,试试给这些层单独设成fp32精度,trtexec里可以用--layerPrecision指定。还有个土办法,把输入图像做一下归一化方式的微调,有时候能缓解不少。
这问题太典型了,我上次做检测模型也踩过类似的坑。FP16掉点基本就三个方向,你先别急着调精度,把trtexec的日志翻出来看看有没有警告,特别是关于精度下降的layer。暗部区域和小目标掉点严重,大概率是反卷积或者上采样层在FP16下误差被放大了,你可以试着把这些层单独设成FP32,用setLayerPrecision接口强制指定,我上次这么干直接救回2个点。另外ONNX导出的时候注意一下opset版本,有些算子映射到TensorRT会选到精度不友好的实现,比如Resize的坐标变换模式,建议用align_corners=False配合onnxsim简化一下图。还有个骚操作,如果模型里有BatchNorm,转TRT时试试把BN层fold进卷积,有时候能减少中间计算的精度损失。实在不行就对比一下FP32的TRT和ONNX Runtime的输出,定位到具体是哪几个张量开始漂移的,别一上来就全图调。你用的trtexec版本是多少?我记得TensorRT 8.5以后对FP16的算子选择策略改过,换旧版本试试说不定也有惊喜。
试试per-channel量化,还有暗部区域先做直方图校准,大概率是FP16对低值敏感的问题。
遇到这种暗部区域和小目标掉点的情况,我第一反应就是FP16的精度瓶颈,尤其是ResNet50的BN层在转TRT时经常会被融合掉,导致数值分布跟原本的统计量对不上。你可以先试试用trtexec加--fp16的同时打开--stronglyTyped,或者手动把某些敏感层(比如第一个conv和最后的seg head)强制回FP32,看看mIoU能回来多少。另外,ONNX导出时如果opset版本太高,有些算子(像Resize的坐标变换模式)在TRT里会走不同的实现,暗部区域的插值误差会被放大,建议确认一下onnxruntime和TRT的插值行为是否一致。还有个坑是动态shape,如果你导出时用了动态输入,TRT会选一个保守的kernel,对暗部像素的卷积计算可能不是最优的,试试固定shape重新转一次对比。如果还不行,可以量化校准集重新生成一下,用验证集里暗部样本多的子集做校准,别用默认的1000张随机图。我之前也遇到过类似情况,最后是逐层dump输出对比,发现是某个残差连接的加操作在FP16下溢出,手动把那层设成FP32就解决了。你要是方便,可以先跑个层级别精度对比脚本,定位到具体哪几层掉得最狠,比盲调快很多。
我之前也踩过类似的坑,FP16在小目标和暗部区域特别容易崩。建议你先用trtexec跑一下层精度对比,重点看下ResNet50的BN层和激活函数输出,很多情况下是算子融合时精度丢失。另外试试ONNX导出时把opset版本调高到17以上,有些动态shape问题会导致TensorRT优化选错路径。还有个小技巧,暗部区域可以尝试在预处理时加个gamma校正,让模型输入分布更均匀,但得先确认是不是量化敏感导致的。
我之前也踩过类似的坑,FP16在小目标和暗部区域特别容易崩。建议先排除ONNX导出时的op融合问题,试试用onnx-simplifier过一遍,再对比一下ONNX的FP32和FP16精度,定位是转换哪一步掉的点。另外TensorRT的FP16对某些层(比如Resize、BatchNorm)特别敏感,可以考虑给这些层单独设成FP32精度,用trtexec的--layerPrecision选项试试。还有,验证集如果本身有大量低对比度样本,建议检查一下输入预处理是不是在转TRT前后有差异,比如归一化参数的精度变化。
我之前也踩过类似的坑,FP16在暗部区域特别容易丢精度,可以试试用trtexec加--fp16的同时开--stronglyTyped,或者对敏感层单独跑INT8校准。另外建议先对比一下ONNX和PyTorch的输出,看看误差是不是从ONNX导出那步就开始了,我之前发现过opset版本不对导致某些算子精度异常。小目标掉点的话,检查下是不是下采样倍数太高,TensorRT的池化层在边界处理上跟PyTorch有细微差别。
暗部和小目标掉点像是FP16精度问题,试试给那几个层单独开FP32或者用INT8量化校准看看。
我碰到过类似情况,用trtexec加--fp16后建议先检查下BN层和激活函数的精度敏感度,改用逐层精度控制能救回来不少。
我之前也踩过类似的坑,FP16转完暗部像素特别容易崩,你试试在ONNX导出的时候把动态轴固定住,还有TensorRT的层精度改成FP16+FP32混合模式,有时候能救回来。另外小目标掉点大概率是池化层和上采样在低精度下梯度信息丢了,可以单独给这几个层设成FP32跑一下对比。顺便问下你用的是TensorRT哪个版本?8.x和9.x对算子支持差别挺大的,换新版有时候能白捡精度。
我之前也踩过类似的坑,FP16对暗部和小目标的敏感度确实很致命,建议先检查下onnx导出时有没有把opset版本拉高,低版本有些算子会隐式降精度。另外trtexec默认的calibration可能没吃到足够多暗部样本,试试用你自己的验证集子集重新跑一遍calibration,或者直接换成INT8+熵校准,有时反而比FP16稳。还有个小细节,ResNet50里的BatchNorm在转换时容易出问题,可以冻结bn再导出对比下。如果还不行,就逐层dump TensorRT的中间输出,和onnx对一下,定位到具体是哪几个层崩了。
我前段时间也踩过类似的坑,FP16下暗部区域和小目标掉点大概率是动态范围不够导致的,可以试试给TensorRT的每通道或者每张图加个量化校准,别用默认的。另外ONNX导出时把opset版本拉高到17以上,有些算子会映射得更高效,精度损失也会小一点。你目前trtexec转的时候有没有开--fp16的同时保留IBN层?ResNet50的BN层在FP16下很容易出问题,建议先转成带explicit batch的ONNX,再在TRT里手动合并BN试试。如果还不行,可以对比下ONNX Runtime的FP16推理结果,能帮你定位是转换过程还是TRT引擎本身的问题。
FP16掉点先查下onnx里的归一化层是不是被折叠了,暗部精度差大概率是精度溢出。小目标问题建议试试trtexec加--fp16的per-tensor校准,或者干脆对敏感层单独保FP32。