最近在试torch.compile跑一个7B的LLM做流式生成,用的贪心解码。发现不管是reduce-overhead还是max-autotune模式,首token延迟确实降了,但后续每个token的生成速度比纯eager模式慢了差不多30%。我理解编译应该对静态图有优化,但自回归生成每一步输入长度都在变,是不是这种动态shape场景根本不适合用torch.compile?还是说需要配合cudagraphs或者把KV cache的tensor固定shape才行?有没有大佬实际在生产环境里用编译模式跑过LLM推理的,求指点一下正确的调优姿势,谢谢!
PyTorch2.0编译模式在大模型推理时反而更慢了,是我打开方式不对吗?
全部回复
共 27 条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,编译模式目前对自回归生成真没那么友好。