最近在做一个端侧部署的小项目,模型是ResNet18改的,训练时用PyTorch,FP16大概2ms一张图。转成ONNX后用onnxruntime跑,CPU和GPU都试了,结果比PyTorch直接推理慢了将近一倍,简直离谱。我试过opset_version从11调到17,也试过optimize=True,甚至关了dynamic_axes,还是没改善。网上说ONNX有图优化,但我看日志好像很多fusion都没触发。有没有人遇到过类似情况?是不是我导出的时候漏了什么参数,或者runtime的session配置有问题?求指点,谢谢!
PyTorch转ONNX后推理速度反而变慢,是哪里设置不对吗?
全部回复
共 13 条这问题我当初也踩过坑,先说结论:大概率不是你导出参数的问题,而是onnxruntime的线程配置和pytorch的cudnn autotune差太多了。PyTorch推理时默认会用cudnn benchmark自动选最合适的卷积算法,但ONNX转出来之后,很多算子在ORT里走的是一套通用实现,尤其ResNet这种带shortcut的残差结构,如果onnxruntime没把conv+bn+relu这些完全融合,memory access模式会变差,速度掉一半很正常。你试试在session options里把intra_op_num_threads设成物理核心数,然后开enable_cpu_mem_arena,GPU的话看看是不是没走tensorrt或者cuda的ep,纯CUDA EP对某些pytorch导出的transpose布局处理很笨。另外有个很阴间的点:检查一下onnx模型里有没有多余的identity节点或者cast操作,有时候torch导出会自动插一堆没用的cast到float32再转回float16,这玩意会打断图优化。最后实在不行,可以试一下onnx-simplifier把模型过一遍,经常能删掉20%的冗余算子,速度会有肉眼可见的提升。
我之前也踩过这个坑,大概率不是导出参数的问题,而是onnxruntime的线程数和执行模式没调。你试试session options里把intra_op_num_threads设成物理核心数,然后跑CPU时关掉arena策略,GPU的话检查下是不是没走TensorRT EP,默认的CUDA EP对很多算子反而没优化。另外ResNet18这种小模型,ONNX的图优化有时候会把算子融合成更慢的batch norm+conv组合,你可以用onnxsim精简下再转,或者干脆对比下onnxruntime的disable_optimization选项,我这边当时关了优化反而快了一截。
试下onnxruntime的session加enable_all_optimization,另外你转的时候是不是没把模型切成推理模式?
试试用onnxruntime的profiling看看瓶颈在哪,有时候是内存拷贝或者线程数没设对。
FP16在onnxruntime里不一定自动开,得手动设session options,不然可能还在跑FP32。
FP16下PyTorch 2ms其实已经很快了,ONNX变慢一倍挺常见的,尤其ResNet这种带残差的结构。你试试导出时加do_constant_folding=True,然后runtime里把graph_optimization_level设成ORT_ENABLE_ALL,另外intra_op_num_threads手动设成物理核数,别用默认值。还有个坑是GPU上onnxruntime默认可能没开cudnn的benchmark,session options里加enable_cudnn_conv_algo_search会好不少。
我遇到过类似情况,后来发现是onnxruntime默认没开全部图优化,得手动设session_options.graph_optimization_level = ORT_ENABLE_ALL,光靠optimize=True不够。另外FP16在ONNX里不一定自动走TensorCore,得看provider是不是CUDAExecutionProvider且显式允许fp16。还有个坑是PyTorch那边可能用了channels_last或者cudnn benchmark,导出时这些布局信息丢了,ONNX就退回默认NCHW慢慢跑。你可以先对比下两边实际用的provider和优化后的graph节点数,基本能定位到问题。
你这种情况我也遇到过,ResNet18这种结构在ONNX里确实经常跑不过原生PyTorch。FP16在PyTorch里走的是cuDNN高度优化过的kernel,而onnxruntime的CUDA EP对fp16支持其实挺一般的,很多算子还是回退到fp32跑,中间来回转换反而更慢。建议你先用fp32导出对比一下,如果fp32下ONNX就不慢,那基本就是精度的问题。另外可以试试TensorRT EP,对ResNet这种backbone优化比默认CUDA EP好很多。
我之前也踩过这个坑,ONNX Runtime默认会开一些内存优化的策略,但有时候反而拖慢小模型的推理。你可以试试把session里的graph_optimization_level设成ORT_DISABLE_ALL,或者手动指定execution_mode为ORT_SEQUENTIAL,有时并行反而有调度开销。另外检查一下导出时是不是把BN融合进去了,没融合的话runtime会多走一层。还有个可能,你FP16导出后runtime是不是还在用FP32的kernel跑?
你这情况挺常见的,ONNX Runtime默认不一定开满优化。先确认下session里有没有设graph_optimization_level为ORT_ENABLE_ALL,还有intra_op_num_threads是不是默认值,CPU上线程数不对很容易拖慢。另外FP16在CPU上ORT支持一般,反而可能比FP32还慢,建议CPU上先跑FP32对比看看。还有导出时记得用torch.onnx.export的do_constant_folding=True,不然BN和Conv的融合基本不会触发。
FP16的PyTorch推理本来就有优化加持,ONNX runtime默认可能没开FP16,你试试用fp16模型跑,不然精度和速度都对不上。
ONNX在CPU上不一定比PyTorch快,尤其小模型,kernel launch和内存拷贝开销占大头,试试开IOBinding把数据直接怼到GPU上。
这个思路不错,收藏了。