最近把之前写的一个图像分类项目从PyTorch 1.13迁移到2.0,听说torch.compile能白嫖加速,就试着给模型加了个@torch.compile。结果发现训练速度反而慢了20%,报错还一堆,比如什么“dynamic shape not supported”。我用的就是标准ResNet50,输入尺寸固定,数据加载也没啥花活。是不是我姿势不对?还是说这玩意儿只对某些特定场景有效?求有经验的老哥指点一下,到底该怎么用compile才能不翻车?
PyTorch 2.0的torch.compile到底能不能直接加速老项目?踩坑了
全部回复
共 152 条小模型和短训练周期确实容易得不偿失,建议试试max-autotune模式,或者干脆把compile关掉用老版本。
我之前也踩过一模一样的坑,ResNet50这种CNN其实不太适合无脑加compile,尤其是batch size小的时候,图编译和算子融合的开销反而比省下的时间还多。建议你先试试torch.compile(model, mode="reduce-overhead"),然后把dynamic=True去掉,固定输入shape再跑跑看。另外老项目里如果用了自定义loss或者数据增强里有些随机操作,很容易触发graph break,这也会拖慢速度。我这边实际测下来,compile对Transformer或者大batch的CNN收益才明显,小模型真不如直接开AMP和channels_last来得实在。
同款踩坑路过,ResNet50这种CNN在CPU bound操作少的情况下,compile收益本来就不明显,反而图编译和shape推导的开销会吃掉那点优化。你试试把torch.compile的mode改成max-autotune,或者用torch._dynamo.mark_dynamic标注一下输入维度,大概率能解决报错。另外老项目里如果有自定义loss或数据增强里用了python控制流,也会触发graph break,建议先profile看看有没有大量recompile。我最后是只在推理阶段开了compile,训练还是关掉,速度反而稳定提升了15%。
老项目直接套compile容易负优化,先试试关闭dynamic和把batchsize固定,或者用torch.compile的mode=max-autotune看看。
说实话你遇到的这个问题太典型了,torch.compile对老项目真不是无脑加的。我自己的经验是,它默认走的是inductor后端,编译过程中会做很多图优化,但如果你代码里有任何稍微动态一点的操作,比如Python层面的list推导、或者某个tensor的shape是运行时算出来的,哪怕输入尺寸固定,它也会保守地放弃优化甚至直接报错。你那个ResNet50按理说很标准了,但慢20%很可能是因为编译开销没摊薄——训练步数少、batch不够大的话,第一次编译和graph break的代价远大于收益。我建议你先用torch.compile(model, mode="reduce-overhead")试试,或者干脆打开torch._dynamo.config.suppress_errors看看具体是哪个算子导致回退。另外别忘了设置torch._dynamo.config.cache_size_limit,默认限制有时候会让模型被重复编译。如果项目里有自定义loss或者数据增强里用了随机操作,那基本告别纯静态图了,不如只对backbone部分做compile,或者干脆用torch.compile(disable=True)跑旧路径对比一下。还有一个坑是CUDA graph和amp混用,你如果开了混合精度,记得把gradscaler的scale更新也放到compiled区域外面。
老项目直接无脑compile确实容易翻车,可以先试试torch.compile(mode="reduce-overhead"),再把batch size调大点看看。
我第一批跑也慢,多跑几个epoch看下,另外检查下有没有开mode=reduce-overhead,默认模式对训练收益不大。
这问题太真实了,我上个月也踩了一遍。torch.compile对静态shape的CNN其实加速有限,主要强在transformer这类计算密集模型上,而且老项目里但凡有个动态list或者条件分支就得炸。建议先试试用torch.compile(model, mode="reduce-overhead")配合fullgraph=True看能不能跑通,不行就只compile瓶颈层,别整个模型一把梭。另外检查下CUDA版本和triton是不是匹配,我更新完triton后速度才正常。
别急着上compile啊,先看下你训练循环里有没有用torch.cuda.amp或者自定义loss,这俩经常和graph break打架。我试过ResNet50,batch size 64以下基本没收益,反而显存占用翻倍,只有大batch时才有明显提速。而且你输入尺寸固定但dataloader里如果有个随机resize,哪怕最终尺寸一样,也会被判定为dynamic shape。建议先用torch._dynamo.config.log_level=logging.INFO看看具体哪里break了。
其实你这种情况大概率是图模式没处理好,老项目里很多隐式操作比如list append或者shape依赖都会打断编译。我建议先跑一下torch._dynamo.explain(model)看下break原因,然后把数据加载里to(device)挪到compile外面。另外2.0刚出时triton对A100
踩坑经历基本一致,我这边也是ResNet50,加了compile之后不光没提速,显存还涨了一截。后来翻了下torch.compile的文档和issue,发现它默认的mode是reduce-overhead,这个模式会做算子融合和CUDA graph捕获,但对显存和编译开销要求比较高,小batch或者显存紧张的时候反而容易负优化。你可以试试mode="max-autotune"或者mode="default",前者会花更多时间找最优kernel,后者更保守,有时候反而比默认模式快。另外dynamic shape那个报错,虽然你输入尺寸固定,但如果你的DataLoader里有drop_last=False,最后一批可能尺寸不一样,或者模型里有view、reshape导致shape推导不明确,建议把输入用torch.zeros跑一遍warmup,或者给模型加个torch._dynamo.config.suppress_errors=True先跑通看看。说实话,torch.compile对训练场景的加速幅度本来就没推理那么夸张,尤其ResNet这种老模型,CNN算子已经优化得很彻底了,收益主要还在Transformer或者大模型上。如果你的项目不是特别吃算力瓶颈,不如先把精力放在AMP混合精度和DataLoader的num_workers调优上,那俩收益更明显。不过既然升了2.0,也可以试试把compile包在eval模式下做推理,那个场景我试下来确实有15%-20%的提升,训练的话就得慢慢调了。
踩过一样的坑,compile对固定shape的CNN收益不大,反而图优化开销吃掉了收益,试试关掉dynamic和mode=max-autotune。
torch.compile这东西真不是无脑加的,我一开始也跟你一样直接套上去,结果显存爆了还慢得离谱。后来翻了下文档才发现,它对动态shape特别敏感,你虽然输入尺寸固定,但如果你在loss或者forward里有任何tensor维度在运行时变化,哪怕只是batch size最后一批不够整数,它都可能触发recompile,那开销比省下来的还大。建议你先用torch.compile(model, mode="reduce-overhead")这个模式试试,然后把dynamo的日志打开看看有没有graph break,我那次就是有个自定义的mask操作导致图被切成了好几段,编译直接白给。另外老项目里如果用了很多原地操作或者Python控制流,比如if tensor.item() > 0这种,基本必炸,要么改成torch.where,要么干脆把compile只包住纯CNN那部分,别整个模型一起上。还有个坑是跟AMP混用的时候,autocast和compile的顺序有讲究,我之前是先开autocast再compile就报错,反过来就正常了。说实话,ResNet50这种经典结构在2.0上gain不大,我试过能提升10%都算不错,但遇到瓶颈在数据加载或者CPU预处理的项目,compile基本帮不上忙,还不如把num_workers调高。你如果想要稳定加速,不如先试试torch.backends.cudnn.benchmark=True加上channels_last的memory format,这俩老技巧对CNN效果更直接,而且零风险。
torch.compile对固定shape的ResNet50按理说是最理想场景了,但慢20%大概率是踩了CUDA graph或inductor的缓存优化坑,你试试把mode改成max-autotune或者关掉dynamic=True看看。我这边之前跑检测模型也遇到类似情况,后来发现是dataloader的num_workers和compile的编译进程抢资源,把workers调低一档就正常了。另外报错dynamic shape的话,检查下有没有哪个层输出了不定长,比如adaptive avgpool的输出维度。实在不行就只在forward里包个torch.compile装饰器,别整个model都上,有些自定义loss或hook会干扰图优化。
torch.compile对固定shape的CNN收益本来就不大,小模型反而亏在编译开销上,试试mode="max-autotune"或只compile瓶颈层。
说实话你这情况我太熟了,刚出2.0那会儿我也是兴冲冲给老项目套上compile,结果跟你一模一样,慢20%都算好的,我那直接OOM。后来我翻了下源码才明白,torch.compile本质上是给训练大模型、动态图或者有复杂控制流的场景准备的,像ResNet这种结构规整的CNN,本身已经能被cudnn和autocast优化得差不多了,compile反而要花额外时间做graph capture和codegen,前期开销直接吞掉那点收益。你如果真想用,建议把backend从默认的inductor换成cudagraphs试试,或者至少开一下mode="reduce-overhead",有时候能救回来一点。另外报错那个dynamic shape,八成是你dataloader里有个什么tensor的batch维度被当成了动态的,哪怕是固定的,也可能因为某个view或者reshape操作让编译器误判,可以试试给输入张量加torch.jit.is_scripting那种静态断言,或者干脆用torch._dynamo.config.dynamic_shapes=False强制关掉。不过说实话,老项目要真想提速,不如先去检查数据预处理和混合精度,我换了AMP之后直接快了1.5倍,比折腾compile省心多了。
这问题我碰到过,ResNet50这种静态图模型按理说该是compile最友好的场景。你试试把torch.compile放到整个训练循环外面,或者用mode="reduce-overhead"参数,有时候默认模式在短batch训练下反而更慢。另外报错dynamic shape大概率是数据加载里某些tensor维度没写死,检查下有没有用.item()或者列表转tensor的地方。我自己的经验是batch size加大到64以上,compile的收益才明显,小batch下纯亏。
还有一点,编译第一次跑有预热时间,你如果只跑了几个epoch对比,那个慢20%可能全是编译开销。建议跑满一轮再比较,或者用torch._dynamo的profiler看下到底卡在哪。老项目迁移最烦的就是这种隐性坑,但调好了确实能快个15%-30%。
训练阶段别开compile,尤其是BN和动态loss的场景,推理时配合cudagraphs才明显。
先把torch._dynamo的报错关掉,用mode=max-autotune试试,小batch反而更慢很正常。
torch.compile这玩意儿真不是无脑加的,我一开始也跟你一样直接套,结果跑起来比原来还慢,后来查了才知道它对batch size和输入维度特别敏感,你虽然固定了尺寸,但可能代码里有个隐式的动态操作,比如list拼接或者python原生循环,它一检测到dynamic shape就直接放弃优化甚至走回退路径,反而多了开销。建议你先用torch.compile(..., mode="reduce-overhead")试试,再把数据loader的num_workers调大点,有时候编译开销跟数据加载瓶颈叠加了,看起来就像负优化。另外ResNet50这种CNN其实算子都比较规整,理论上该有收益的,你可以看看是不是把编译作用到了整个model而不是只包住forward,或者检查一下有没有跟torch.jit.script或者amp混用,我遇到过跟混合精度一起用的时候报奇怪的错误。还有一个坑是第一次跑会有编译预热,如果你的epoch数少、每步又很快,那前期那点时间占比就很高,感觉上就慢了,最好先跑个50步热身再计时。反正我最后是只在推理阶段开了compile,训练还是关了,收益明显但训练那点提升不值得折腾。
torch.compile这玩意儿真不是无脑加的,我试过几次,小模型和动态shape场景基本是负优化,ResNet这种CNN反而容易踩到图模式转换的坑。你检查下有没有开torch._dynamo的日志,报错里那些dynamic shape提示一般是因为数据加载器里混了非tensor操作,试试把dataloader的num_workers调低或者干脆用静态batch。另外2.0刚出那会儿的版本bug挺多,建议升到2.1+,编译模式选reduce-overhead可能比默认的max-autotune更稳。要是项目不急着上生产,可以先只用torch.compile做推理验证,训练还是跑原版,加速效果其实没那么玄乎。
说实话你碰到的这个情况我也遇到过,当时差点把torch.compile拉黑。后来看了下源码和issue才发现,这玩意儿对“静态shape”的执着比想象中严苛,你虽然输入尺寸固定,但如果在data pipeline里有个啥tensor维度被当成动态处理了,比如label的shape变化或者自定义loss里有个view操作,它就会疯狂回退到eager模式,那性能自然就崩了。我建议你先用torch.compile(model, dynamic=True)跑一遍,虽然会牺牲一点加速,但至少能确认是不是shape的问题;另外把torch._dynamo.config.suppress_errors设为True,让它直接跳过报错的子图,看看纯图模式下的真实收益。我个人测下来,ResNet50这种CNN在batch size够大(比如64以上)且GPU利用率高的时候,compile能带来15%-25%的提升,但小batch或者CPU bound的场景基本没戏。还有个小坑是别在第一次forward前就计时,因为compile有编译开销,得先warmup几十个iteration再比速度。如果你主要瓶颈在数据加载或者CPU预处理,那提速有限,不如先把pin_memory和num_workers调好。总之这功能不是银弹,但调对了场景确实香,建议你多试几组配置再决定要不要保留。
老项目直接上compile确实容易踩坑,建议先关掉动态shape再试,或者只compile推理部分。
先用torch.compile默认参数跑通再说,训练阶段很多时候反而得不偿失,ResNet50这种经典网络收益不大。