最近在调一个LLM的推理代码,模型是7B的,用PyTorch 2.0的torch.compile优化后,反而比eager模式慢了20%左右。我用的还是默认模式,没加任何特殊配置。看官方博客说compile能提速,但实测下来完全相反。网上查了下,有人说需要配合CUDA graphs,有人说是动态shape导致的recompile开销。想问问各位,实际生产环境里大家真的在开compile吗?还是说只对特定模型尺寸/批大小有效?另外我的输入长度是变化的,是不是这个原因?有没有什么经验性的选择标准,比如什么条件下才值得花时间调compile?谢谢。
PyTorch 2.0的compile到底该不该用?用了反而变慢了咋回事
全部回复
共 14 条动态shape确实是compile的杀手,recompile一次够eager跑好几轮了。我这边在服务端固定max_len和batch,配合cudagraphs之后7B大概能快15%,但调参过程挺折腾的。建议你先用torch.compile的mode="reduce-overhead"试试,如果还慢就检查下是否频繁触发guard重新编译,或者干脆只在解码阶段用,预填充保持eager。
动态shape确实是compile的大敌,recompile开销直接吃掉收益,建议固定长度padding再试。
生产环境我基本只用eager,compile目前更适合静态shape的CV模型或大batch推理。
说实话你这个情况我太熟了,7B模型推理场景下compile反而不划算的案例真不少见。动态shape确实是最大的坑,我这边之前测过,只要输入长度一变,recompile的开销直接吃掉所有优化收益,尤其是默认模式下的inductor对动态shape处理得很笨重。CUDA graphs确实能缓解一部分,但那是另一套优化路径了,跟compile叠一起调起来特别费劲。我自己的经验是,compile目前更适合静态shape、高吞吐的训练场景,或者batch size固定且足够大的推理,比如服务端批量请求那种。你要是LLM在线推理,输入长度天然就是变长的,不如先把精力放在KV cache、continuous batching或者量化上,这些收益来得更直接。另外可以试试torch.compile的mode参数,比如reduce-overhead,有时候比默认模式好不少,但前提还是得把动态shape那块用pad或者bucket处理掉。反正我的结论是,不是所有模型都值得折腾compile,7B这个量级,除非你愿意花一两天专门做shape固定和配置调优,否则eager模式加其他优化可能更省心。
动态shape确实是compile的大坑,recompile开销直接吃光收益,建议先固定长度试试。
生产环境还是eager稳,compile主要适合静态shape的CV模型,LLM场景真没必要硬上。
动态shape确实是compile的大坑,recompile开销直接吃光收益,建议先固定长度试试。
我这边7B以下模型开compile基本都亏,13B以上配合静态shape才明显提速。
说实话你这个问题太典型了,7B模型在变长输入下compile反而变慢,我这边也踩过同样的坑。动态shape确实是最大的杀手,torch.compile默认会做shape特化,一旦输入长度变了就得重新编译,那个开销比你省下的那点kernel时间多得多。我自己的经验是,如果能把padding到固定长度,或者用max_seq_len截断,compile的收益才体现得出来,否则真不如eager省心。
另外你提到CUDA graphs,这俩确实是配合着用的,但也不是所有算子都支持graph capture,像有些自定义op或者动态控制流直接会打断capture,反而更糟。我目前在生产环境里只对那种batch固定、序列长度也固定的场景开compile,比如离线批量推理,线上实时服务基本都关着。
还有个比较隐蔽的点,7B这种规模其实已经能吃到不少memory-bound的瓶颈了,compile对compute-bound的算子优化更明显,对小模型或者大batch的GEMM可能效果更好。你要是想试,可以先用torch.compile的mode="reduce-overhead"看看,再配合静态shape,但别指望默认配置能直接白嫖提速。最后说一句,官方博客那个benchmark多半是理想shape加小模型,参考价值有限。
动态shape确实是compile的大坑,recompile开销直接吃掉收益,建议先固定长度试试。
说实话你这情况我太熟了,之前我在一个6B模型上试compile也是这德行,后来发现动态shape绝对是头号杀手。你输入长度一变,CUDA graph缓存就失效,每次都得重新编译,那开销比省下来的计算时间还大。我后来用torch.compile的mode="reduce-overhead"配合固定padding到某个上限,才勉强把延迟压下来,但也就持平eager,没想象中那么神。生产环境里我们其实只对batch size固定、序列长度也基本固定的场景开compile,比如那种批量跑短文本的任务,其他情况都老老实实eager。你要是真想用,建议先试试把输入padding到固定长度,或者用capture cuda graph的接口手动管一下,但说实话收益不确定,得看你的batch size够不够大。另外7B这种规模,显存带宽可能才是瓶颈,compile主要优化计算图,对memory-bound的算子帮助有限,这也是个思路。你要是主要吃显存带宽,那还不如去调调num_workers和pin_memory,可能提升更直接。我个人的经验是,compile这玩意适合那种算子小而多的模型,像CV里的小网络,大模型上除非你能把shape完全锁死,否则别抱太大期望。
动态shape确实是compile的大坑,我这边试过只要序列长度一变,recompile的开销直接吃掉所有收益。7B这个规模如果batch不大,eager反而稳,建议你先把padding固定到某个长度或者用static shape试试。另外生产环境我基本不开,除非是那种超长稳定推理的离线任务,在线服务延迟抖动受不了。
动态shape确实是compile的杀手,我这边跑生成式模型时把padding都固定成最大长度后,速度才勉强追上eager。你试过给compile传mode="max-autotune"加cudagraphs吗?7B模型可能显存够但graph捕获的开销反而拖后腿。另外生产环境我基本只对CV模型开compile,NLP里变长输入太常见了,recompile一次够你跑好几轮推理了。
变慢20%算正常,我之前测过13B模型,compile在batch size小于8时基本没收益,甚至负优化。你试试把输入padding到固定长度,或者用torch._dynamo的mark_dynamic标记可变维度,有时候能减少recompile次数。不过说实话,现在这版本对动态shape的支持还是太勉强,除非你追求极致性能,不然eager省心多了。
我倒是好奇你用的什么显卡?我A100上compile提升明显,但V100上就反过来了。另外7B推理是不是有KV cache的buffer在变?那玩意儿如果跟着序列长度变,compile会疯狂重新编译。要不你试试把输入长度固定成训练时的值,或者干脆用bettertransformer库,比compile省事多了,效果还稳定。
你这个问题我上周刚踩过坑,最后发现是tokenizer那边生成了不同的attention mask,导致graph每次都在变。你
动态shape确实是compile的杀手,我这边跑代码生成模型也踩过坑,输入token长度一变,recompile开销直接吃掉所有收益。后来我干脆把padding到固定长度,配合torch.compile的mode="reduce-overhead",才勉强比eager快个10%左右。不过说实话,生产环境里我大部分场景还是关掉的,除非是那种batch size和序列长度都特别规整的离线推理任务,不然调优成本实在不划算。你试试把输入pad到固定长度或者用static shape模式看看,说不定有惊喜。
说实话你这情况我太熟了,7B模型动态输入长度,compile的recompile开销真的能把收益全吃掉。我自己的经验是,compile对静态shape和固定batch size效果最明显,一旦输入长度乱跳,它每换一个shape就得重新编译一遍图,这个开销比省下的那点kernel launch时间还贵。
你试试把padding到固定长度,或者用torch.compile的dynamic=True参数,虽然官方说支持动态shape,但实际效果还是不如静态。另外你这20%的变慢也可能跟GPU型号有关,A100上可能收益明显,消费级卡上反而会吃亏。
我生产环境里现在只在CV模型上开compile,NLP这边除非是纯推理且输入长度严格固定,否则一律eager。CUDA graphs确实能配合着用,但配置成本高,收益对7B这种规模来说不够看,不如先量化或者换更小的模型。
建议你先用torch.profiler跑一遍,看是不是真在recompile上花时间,如果是的话就把max_length定死,或者干脆放弃compile算了。毕竟调这玩意的时间,够你优化好几个别的地方了。
说实话你这情况我太熟了,7B模型加上变长输入,compile大概率是帮倒忙的。动态shape每一次变化都可能触发重新编译,那个开销比省下的那点kernel fusion时间高得多,尤其你用的还是默认模式,没做任何图模式优化。我自己的经验是,compile真正能吃到红利的地方在于固定shape、大batch、并且模型里有大量重复的elementwise操作,比如attention和MLP的叠加,这时候融合效果才明显。你要是输入长度实在没法固定,可以试试把padding到固定长度,或者用torch._dynamo的dynamic参数稍微调一下,但说实话收益不一定大。生产环境里我见过有人为了compile专门改推理逻辑,最后提速不到10%,反而增加了部署复杂度,所以我现在基本只对纯静态shape的模型开compile。另外你提到CUDA graphs,这两个确实经常搭配用,但前提是shape不变,否则graph捕获也会出问题。建议你做个简单的profiling,看下到底是recompile占了时间还是kernel本身变慢了,再决定要不要继续折腾。
动态shape基本就是compile的灾难现场,想提速得先固定输入长度或者用torch.compile的dynamic=True调参,不然recompile开销直接吃掉收益。