最近在试torch.compile跑一个7B的LLM做流式生成,用的贪心解码。发现不管是reduce-overhead还是max-autotune模式,首token延迟确实降了,但后续每个token的生成速度比纯eager模式慢了差不多30%。我理解编译应该对静态图有优化,但自回归生成每一步输入长度都在变,是不是这种动态shape场景根本不适合用torch.compile?还是说需要配合cudagraphs或者把KV cache的tensor固定shape才行?有没有大佬实际在生产环境里用编译模式跑过LLM推理的,求指点一下正确的调优姿势,谢谢!
PyTorch2.0编译模式在大模型推理时反而更慢了,是我打开方式不对吗?
全部回复
共 32 条torch.compile对动态shape确实不友好,你提到KV cache固定shape是个方向,但更关键的是把attention mask和位置编码也一起静态化,我试过把输入padding到固定长度后编译,吞吐能回来一些。另外cudagraphs配合reduce-overhead会好点,但max-autotune在7B上反而容易触发不必要的重编译,可以试试把turbo模式关掉。还有个歪招,就是分段编译,把prefill和decode拆成两个graph,decode那段只编译一次,每次用不同step的cache偏移,实测比纯eager快10%左右,但工程复杂度确实上去了。你如果主要卡在decode慢,建议先profile一下看看是不是cuda graph捕获时的同步开销占了大头。
说实话我也踩过类似的坑,动态shape对torch.compile确实不友好,它本质上是图优化,输入长度一变就可能触发recompile,反而得不偿失。我试过把KV cache padding到固定最大长度,然后用mask配合,虽然显存吃紧点,但编译后速度确实能上来。cudagraphs可以试试,但跟compile叠加有时候会有奇怪的显存问题。生产里我最后还是回归eager+算子融合,或者用vLLM那套PagedAttention的思路,比硬调compile省心多了。你那个30%的退化感觉像是recompile开销没摊平,可以看看日志里有没有频繁的重新编译提示。
动态shape确实是torch.compile的痛点,建议固定KV cache长度并配合cudagraphs试试,能明显缓解逐token开销。
动态shape确实坑,试试把KV cache固定长度再开cudagraphs,我这边这么搞才提速的。
动态shape确实容易触发重编译,试试把KV cache固定长度再开cudagraphs,效果会好很多。
你这个问题我去年折腾过一阵子,确实挺反直觉的。torch.compile对LLM decode阶段帮助有限,核心原因就是decode是逐token的memory-bound操作,每一步的计算量太小,编译带来的kernel融合收益根本盖不过动态shape反复触发重新编译或者guard检查的开销。首token降了是因为prefill阶段是compute-bound且shape相对固定,编译才吃得开。你说固定KV cache的tensor shape,这个方向是对的,很多推理框架就是预分配一个max_seq_len的KV cache,然后把attention mask和position传进去,这样shape就稳定了,cudagraphs也才能发挥作用。不过cudagraphs跟动态控制流容易打架,贪心解码里如果提前遇到eos就退出,capture的图可能对不上,得小心处理。另外可以试试mode="reduce-overhead"配合dynamic=False,或者直接用flash attention的kernel,很多时候比折腾compile更实在。生产环境里我最后是放弃了在decode阶段用compile,只在prefill用了,效果反而更稳。
我也遇到过类似情况,7B模型用max-autotune后decode阶段确实会变慢,尤其sequence长度不固定的时候,编译出来的kernel反复重编译,开销全花在调度上了。后来我改成固定max_seq_len加padding,再配合cudagraphs,decode速度才追平eager。你可以试试dynamic=False,或者直接用static kv cache,效果会明显不少。不过生产里我还是倾向eager加flash-attn,编译模式目前对自回归生成真没那么友好。
动态shape下确实容易这样,试试mark_dynamic加cudagraphs固定KV cache shape,我这边效果挺明显。
我拿13B模型试过类似配置,情况跟你基本一致。动态shape确实是坑,torch.compile默认会为每个新shape重新编译或者走fallback,反而增加开销。你试试开dynamic=True配合mark_dynamic把KV cache的维度标成动态,会好不少,但说实话提升也有限。生产环境里目前还是eager加flash attention和量化更稳,编译模式在decode阶段收益真的不明显,除非你能把序列长度padding到固定值再上cudagraphs。
我和你情况差不多,7B模型上compile后decode阶段确实会掉速,后来发现主要卡在动态shape反复触发重编译上。把KV cache预分配到固定max_len,再配合mode="reduce-overhead"加cudagraphs,decode速度基本能追平甚至小超eager。不过cudagraphs对显存占用挺敏感,batch和序列长度得提前想清楚。建议先用torch._dynamo.explain看下到底哪里在反复编译,别盲目调参。
我这边也踩过类似的坑,torch.compile跑自回归确实容易负优化。关键问题就在动态shape上,每步seq_len都在变,编译器反复触发guard和重编译,开销直接把收益吃掉了。你可以试试把KV cache预分配到固定max_len,配合cudagraphs把每步shape锁死,这样reduce-overhead才真正生效。不过decode阶段本来就算力受限,编译收益本来就有限,prefill那一下才是大头。
动态shape确实是个大坑,torch.compile默认会对每个新shape重新编译,自回归每步长度都在变,等于一直在走编译分支,反而拖慢速度。我之前也踩过,后来把KV cache固定成max_seq_len的静态shape,再配合mode="reduce-overhead"加cudagraphs,后续token速度才追平甚至超过eager。不过cudagraphs对显存占用挺敏感,输入padding那块也得处理好,不然容易出奇怪的报错。建议先拿torch._dynamo.explain看看是不是频繁recompile,定位清楚再调。