最近在调一个LLM的推理代码,模型是7B的,用PyTorch 2.0的torch.compile优化后,反而比eager模式慢了20%左右。我用的还是默认模式,没加任何特殊配置。看官方博客说compile能提速,但实测下来完全相反。网上查了下,有人说需要配合CUDA graphs,有人说是动态shape导致的recompile开销。想问问各位,实际生产环境里大家真的在开compile吗?还是说只对特定模型尺寸/批大小有效?另外我的输入长度是变化的,是不是这个原因?有没有什么经验性的选择标准,比如什么条件下才值得花时间调compile?谢谢。
PyTorch 2.0的compile到底该不该用?用了反而变慢了咋回事
全部回复
共 27 条说实话动态shape这块确实是compile的大坑,我之前试过带padding的batch推理,recompile开销直接能把收益吃干净。你要是能把输入长度固定或者用bucket把长度分档,再配合cudagraphs,效果会明显不一样。另外7B这种规模,显存带宽可能才是瓶颈,compile主要优化计算,内存密集场景收益本来就有限。我现在的经验是,只有形状固定且batch够大(至少32+)才值得折腾,否则eager模式省心多了。
输入长度老变的话recompile确实要命,先固定shape或padding到统一长度试试。我们7B以下模型开compile收益也不大,大batch才划算。
动shape这个点基本就说到根子上了,torch.compile对静态shape的优化最狠,一变长就得重新编译,来回折腾的开销比省下来的还多。我之前试过把输入padding到固定长度,配合cudagraphs,小模型上确实有提升,但7B这种规模显存本来就紧,padding浪费的空间反而让batch size降了,最后收益被抵消。
另外你用的是默认模式,默认的max-autotune其实没开,很多算子融合和内核选择都没走到最优路径。可以先试试mode="reduce-overhead",这个对推理场景更友好,但前提是你的输入shape得稳。如果实在变长,建议用dynamic=True或者干脆锁到几个常见长度,把recompile次数控制住。
生产环境我见到的用法大多是只在服务端固定了batch和seq_len的离线推理场景用compile,在线那种延迟敏感的反而少人开,因为编译一次要几秒到十几秒,冷启动直接崩。还有个坑是某些自定义算子或者动态控制流,compile会直接报错或者退化成eager,等于白开。
你要是图省事,就先别纠结compile,用半精度加KV cache优化可能收益更直接。真要用,建议先profile看瓶颈在哪,如果已经是memory bound,compile再牛也救不了。最后问一句,你用的是torch2.0的哪个小版本?2.1和2.2对动态shape的处理改进挺大的,升级说不定就好了。
动态shape基本就是compile的命门,recompile开销比收益还大,建议先固定长度试试。
变长输入确实容易触发重编译,先固定shape测下baseline,再决定值不值得开。
变长输入确实是compile翻车的高发场景,你这不是错觉。torch.compile默认走的是dynamic shape那套,seq_len一变就容易触发guard失败然后重新编译,7B模型本来就大,recompile的代价直接吃掉甚至倒贴收益。我自己的经验是先把mode设成reduce-overhead试试,再配合mark_dynamic把batch和seq维度标出来,很多时候能稳下来。另外你可以看下TORCH_LOGS=graph_breaks之类的东西,看看是不是图被切得稀碎,如果break太多那还不如别开。生产里我们其实用得挺挑的,固定shape、固定batch、反复跑同一种推理的service开compile收益明显,像chat这种输入长度飘忽的,往往是开之前先做padding分桶或者干脆eager。至于CUDA graphs,它和compile是能叠加,但对动态shape更敏感,得先解决recompile问题再说。建议你拿真实流量跑个benchmark,别只看单条,compile的价值基本都在稳态吞吐上。
动态shape确实是compile的坑,每次变长都触发recompile,开销全搭进去了。我这边7B模型固定输入长度后compile能快15%左右,但一上变长就崩。建议先用dynamic=True试试,或者把常见长度分桶。生产环境目前还是慎开,调试成本不低。