最近在调一个LLM的推理代码,模型是7B的,用PyTorch 2.0的torch.compile优化后,反而比eager模式慢了20%左右。我用的还是默认模式,没加任何特殊配置。看官方博客说compile能提速,但实测下来完全相反。网上查了下,有人说需要配合CUDA graphs,有人说是动态shape导致的recompile开销。想问问各位,实际生产环境里大家真的在开compile吗?还是说只对特定模型尺寸/批大小有效?另外我的输入长度是变化的,是不是这个原因?有没有什么经验性的选择标准,比如什么条件下才值得花时间调compile?谢谢。
PyTorch 2.0的compile到底该不该用?用了反而变慢了咋回事
全部回复
共 27 条动态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开销直接吃掉收益。
动态shape确实是compile的杀手,我这边跑生成式模型时把padding都固定住,配合cudagraphs才勉强有5%左右的提升,纯默认模式反而负优化很常见。另外7B这个规模,显存带宽可能才是瓶颈,compile主要省的是kernel launch开销,模型太小或太大收益都不明显。你要是输入长度实在变,可以试试用bucket把长度分档,每个档位单独compile,能减少recompile次数,但代码复杂度得自己权衡。
动态shape确实是compile的头号杀手,我试过把padding到固定长度后,速度立刻上来了,但显存又吃紧。你那个7B模型如果batch小的话,compile的kernel launch开销可能比省下的计算还多,建议试试把max_length固定或者用reduce_overhead参数开一下。另外生产环境我一般只在稳定shape的服务场景开,变长输入还是老实eager吧。你测过纯生成阶段和prefill阶段分开看耗时吗?有时候问题只出在某个子图上。
说实话你这个情况我太熟了,之前用7B模型做beam search的时候也遇到过一模一样的坑。动态shape对compile来说确实是致命伤,每次长度一变,graph就得重新编译或者做guard检查,这个开销在小batch下反而比省下的kernel launch时间还贵。我现在一般只在batch size固定且输入长度padding到固定值才开compile,效果立竿见影,但生成任务这种天生变长的场景基本放弃。另外一个经验是,compile对H100这种新架构的收益远大于A100,可能你卡比较老的话加速比就有限。还有一点,官方博客那些benchmark全是固定shape加连续内存访问的理想情况,实际LLM推理里attention的mask、cache的更新都会让graph变得复杂,有时候编译出来的kernel还不如手写的eager快。你要是想继续试,可以开mode="reduce-overhead"配合cudagraphs,但记得把padding逻辑改到模型外面,让输入shape尽量稳定,我试过大概能追平eager,但想再快就得动算子融合了。说到底,生产环境里还是得拿自己的真实负载跑一遍profile,别只看官网的营销数字,我身边真正大规模上compile的人其实不多,多数还是靠vLLM或者FasterTransformer那套。
说实话我觉得你这情况挺典型的,7B模型在推理场景下torch.compile不一定是稳赚不赔的买卖。动态shape确实是最大嫌疑,我这边之前试过输入长度从128到2048随机跳,compile每次遇到新shape都要重新编译,那个开销比省下来的计算时间还夸张,后来干脆用padding把长度固定到最接近的bucket才好一点。另外你只开默认模式可能也吃亏,我自己的经验是得配合mode=reduce-overhead加上cudagraphs才有效果,但cudagraphs又要求静态shape,这套组合拳打下来调参成本真的不低。目前我在生产里只对那种batch固定、序列长度也固定的场景开compile,像在线服务那种不规则请求我反而更信赖eager加torch.inference_mode,省心还稳定。你要是主要被动态shape卡着,不妨先试试把输入pad到固定长度或者用静态bucket,看看能不能把recompile次数压下去。至于官方博客说的提速,我怀疑他们benchmark多半是固定shape的CV或者CNN模型,LLM这种自回归场景其实不太适合直接套用那些结论。
动态shape确实是compile的坑,recompile开销直接吃掉收益,我试过固定长度后快了不少。
要不要开还得看场景,LLM推理这种变长输入建议别硬上compile。
动态shape这个点你基本说到根上了,torch.compile最怕的就是输入长度频繁变,每次变都可能触发重新编译,那开销比省下来的计算还大。我之前测过NLP任务,把padding到固定长度后compile才勉强有正收益,但收益也就10%出头,还得搭上显存翻倍。7B这种规模模型,显存带宽瓶颈可能比计算瓶颈更明显,compile主要优化算子融合和内核调度,对带宽密集型场景帮助有限,甚至可能因为额外内存布局转换拖慢速度。生产环境里我见过真开compile的,基本都是服务端固定batch size、固定序列长度,而且用CUDA graphs把图给固化下来,同时配合减少CPU launch开销,才有明显提升。你要是推理场景输入变化大,建议要么用vLLM这类专门优化过的框架,要么干脆把compile关掉,手动做点算子融合也比瞎折腾compile靠谱。另外注意下你用的是哪个GPU,A100/H100上的表现和消费级卡完全两码事,有些编译优化在消费卡上反而触发奇怪的kernel选择。反正别盲目信官方博客那些benchmark,他们测的往往是理想化场景,实际工程里坑很多。