最近在给公司做LLM推理优化,看到PyTorch2.0的torch.compile宣称能提升30%-40%性能,就试了试。本地benchmark确实快了,但部署到生产环境(T4 GPU,batch动态变化)后发现两个问题:一是首次编译时间太长,大概要十几秒,预热的warmup策略没找到好的实践;二是配合vLLM或TensorRT-LLM时,compile反而和这些框架的CUDA graph冲突,偶尔报显存碎片错误。想请教各位,你们在生产环境里真的会用torch.compile吗?还是说只用来做训练?如果是推理,有没有和vLLM搭配成功的案例?或者说干脆别折腾,直接换ONNX或TensorRT更省心?刚接触这块,有点迷茫,求指点。
PyTorch2.0的compile到底该不该用在推理服务里?
全部回复
共 58 条说实话我试过之后就把compile从推理链路里摘出去了,T4这种卡上首次编译的十几秒对线上服务来说太致命了,warmup还得自己造轮子,不如直接砍掉。跟vLLM叠加那会儿我也踩过显存碎片,后来发现干脆让vLLM自己管CUDA graph,compile只留给训练或者离线批量推理用。如果你非要在推理里用,建议看看torch.compile的mode=reduce-overhead,但别指望它跟TensorRT-LLM能和平共处,至少我这边没成功案例。反正我现在是能走ONNX就走ONNX,动态batch用TensorRT的optimization profile比折腾compile省心多了。
说实话我之前在T4上也踩过同样的坑,torch.compile跟vLLM的CUDA graph抢显存这块真挺无解的,后来干脆直接用TensorRT了。不过要是batch动态变化特别频繁,TensorRT的显存池也未必比compile好到哪去。你试试把torch.compile的mode调成reduce-overhead,然后手动控制warmup的batch范围,别让它自动生成太多graph变体,可能会缓解碎片问题。但如果你主要跑LLM推理,我还是建议别折腾compile了,vLLM自己那套优化已经够吃透T4了,除非你用的是自定义算子。
说实话我之前在内部试过torch.compile配vLLM,T4上动态batch确实容易炸显存,后来干脆把compile只用在offline的预填充阶段,decode走vLLM原生的CUDA graph,这样冲突少很多。你提到的首次编译十几秒,如果服务能接受滚动发布,可以先在后台跑一次warmup再切流量,但确实没有特别优雅的解法。至于ONNX和TensorRT,小模型可能收益明显,但LLM这种动态shape场景,TensorRT的引擎构建和显存管理也有自己一套坑,未必比compile省心。我现在的结论是,除非你的模型结构特别规整且batch基本固定,否则别在线上赌compile的收益,训练端用用就得了。
我们这边试过torch.compile做推理,跟你的情况差不多,T4上动态batch确实容易炸显存,后来干脆只在固定shape的离线batch里用它,在线服务全换TensorRT了。vLLM那边本身就有自己的graph优化,再叠compile感觉是负优化,报错排查起来也麻烦。你要是追求稳定,建议直接上TensorRT,那点性能差距在生产里真没那么重要。
说实话我们团队也踩过一模一样的坑,T4上torch.compile的收益真没本地A100那么明显,动态batch下CUDA graph一冲突整个显存就乱掉。后来我们干脆把compile只用在offline的批量预处理上,比如embedding抽取和特征工程,线上推理全换成TensorRT了,省心不少。
vLLM那边官方其实不太推荐再叠一层torch.compile,因为PagedAttention自己管显存,你再让inductor去优化算子,两套内存调度逻辑容易打架。我们试过把compile限制在非attention的FFN部分,虽然能跑通,但收益也就10%出头,还得多维护一套图编译缓存,最后觉得不值。
倒是想问问你,有没有试过把动态shape固定成几个档位,比如batch=1/4/8,每个档位单独编译一次?我们这么折腾过,warmup时间能压到3-4秒,但换来的是显存碎片还是偶尔冒出来,后来就放弃了。可能PyTorch2.0这套还是更适合训练场景,推理生态里TensorRT和ONNX Runtime的图优化确实更成熟,尤其是T4这种老卡,CUDA core利用率吃不满的时候,compile的算子融合效果有限。
你要是真想省事,建议直接看ONNX Runtime的CUDA EP,配合dynamic axes设置,虽然前期写导出脚本也烦,但至少不会和vLLM打架。至于torch.compile,我目前只敢在内部测试环境跑跑,生产环境还是算了。
说实话你这情况我太懂了,T4上batch动态变化的时候torch.compile的优化策略基本就失效了,它静态shape假设太强,动态shape每次recompile反而更亏。我们之前也踩过这坑,后来干脆只在训练阶段用compile,推理直接走TensorRT,省心太多。vLLM那边本身就有自己的CUDA graph和page attention管理,你再叠一层compile,显存分配器互相打架是必然的,报错碎片还算轻的,严重时候直接CUDA OOM。我倒是见过有人用compile的mode=reduce-overhead配合固定batch size做过离线批处理,但生产环境没人敢这么玩。如果你非要在vLLM里用,可能得把torch.backends.cuda.graphs相关的参数全关掉,但那样性能提升也没剩多少了。说到底,推理这块成熟方案就是TensorRT或者ONNX Runtime,compile更适合训练加速或者动态图调试,别指望它一步到位替代传统优化。你那个warmup问题,网上有些用dummy input提前跑几十遍的土办法,但治标不治本,换batch还是得重新编译。要么就接受首次延迟,要么就换框架,没有第三条路。
我们这边试过一轮,结论是torch.compile更适合静态shape的离线批量推理,线上动态batch+多框架混用确实容易踩显存碎片。个人觉得别跟vLLM硬凑,要么纯TensorRT一条路走到黑,要么就干脆用原生PyTorch加CUDA graph手写优化,反而可控。warmup的话可以搞个固定shape的假请求先跑几轮,但治标不治本,编译开销省不掉。
说实话我觉得你踩的坑我基本都踩过,T4上torch.compile的收益真没宣传的那么香,尤其batch动态变化时,CUDA graph一重建成本全回来了。我这边最后是训练用compile,推理直接切TensorRT,省心太多,vLLM配onnxruntime都比硬凑compile稳。你要真想省显存碎片问题,试试固定batch size或者干脆关掉cudagraph,但那样性能提升就剩个位数了,不值当。
说实话我觉得T4这个卡上折腾compile性价比挺低的,动态batch直接把编译优化打回原形,省那点kernel launch时间还不够它重新shape的开销。之前我们试过固定batch+静态shape倒是能稳定提速,但线上流量根本不允许这么干。
跟vLLM的冲突我也踩过,最后发现不如直接把torch.compile特性关掉,让vLLM自己管CUDA graph反而省心。你提到的显存碎片问题八成是compile的缓存分配器和vLLM的paged memory打架了,这个目前真没啥优雅解法。
如果你主要跑LLM,我建议还是老老实实走TensorRT或者干脆用vLLM原生的continuous batching,别在compile上耗了。除非你模型结构特别规整、序列长度也基本固定,否则生产环境里那点收益真不值得冒这风险。
说实话我们这边也是踩了同样的坑,compile在小batch和固定shape下确实猛,但生产环境一上动态shape就原形毕露。现在基本就只在训练阶段用,推理还是老老实实TensorRT,虽然转换麻烦点但至少不会半夜被显存碎片报警叫起来。
vLLM那边我试过把单个算子抠出来手工compile再塞回去,效果不稳定而且维护成本太高,后来干脆放弃了。你要是实在想用,建议先确认下自己的batch上限和shape分布,如果能接受固定shape + 长预热,或许还能凑合,否则真心别折腾。
另外ONNX这边也有坑,T4上有些op不支持还得来回改图,但至少比跟CUDA graph打架强。你们有没有试过把compile和vLLM的版本都锁到某个特定组合?有时候是兼容性问题而非原理上冲突。
说实话我之前也被这个坑过,T4上动态batch加CUDA graph就是容易炸显存,后来干脆把compile关掉,直接用vLLM自带的优化,省心不少。对于LLM推理,个人感觉torch.compile更适合研究原型或小batch场景,生产上跟成熟框架拼优化有点吃力。不过你要是只跑单模型、固定shape的小服务,开compile配合手动内存池也许能省点显存,但收益真没宣传的那么大。现在主流做法还是ONNX或TensorRT做后端,尤其T4这种卡,TensorRT的算子融合比compile稳得多。
说实话我们团队也踩过类似的坑,T4上torch.compile的收益在动态batch下会被严重稀释,还不如直接上TensorRT稳。vLLM那边官方其实更推荐用PagedAttention自己的优化,compile进去反而容易把显存池搞乱。你要是真想省心,建议把精力放在算子融合和KV cache的显存复用上,比折腾compile性价比高多了。另外可以看看最新的torch.compile对dynamic shape的支持情况,2.1之后好像改善了不少,但生产环境我依然不敢直接用。
说实话我们试过一轮就放弃了,T4上那个编译时间加上动态batch,收益真心抵不过运维复杂度。现在生产环境基本是TensorRT和vLLM各管一段,compile只留在训练侧做图优化,推理这边压根不敢碰。你要是遇到显存碎片,八成是CUDA graph和compile的codegen在底层打架,这种问题查起来比性能损耗还头疼。
生产用compile真不如直接TensorRT,vLLM自己都管好graph了,叠buff只会添乱。
我们线上也踩过这个坑,torch.compile在训练侧收益挺明显,推理这边确实不太敢上。T4上动态batch加上vLLM的paged attention,compile基本等于给CUDA graph添乱,显存碎片报错不是偶然。后来我们推理直接走TensorRT-LLM或者ONNX Runtime,warmup可控,延迟也稳。compile目前更适合固定shape、自己手写serving的场景,跟成熟推理框架叠着用纯属自找麻烦。
我们线上也踩过类似的坑,torch.compile在固定shape下收益确实可观,但动态batch基本就是灾难,编译缓存命中率低得感人。后来推理侧直接切回TensorRT-LLM了,compile只留着训练用。vLLM那边其实自己做了CUDA graph,你再套一层compile纯属互相打架,没必要硬融。
我们组去年也踩过这个坑,说说实际感受。torch.compile在固定shape的CV模型上确实香,但LLM推理这边动态batch基本是常态,每次shape一变就触发recompile,那个编译开销在生产环境里根本扛不住。你提到的和vLLM冲突我也有耳闻,主要是vLLM自己已经用CUDA graph做了capture,compile再插一脚去改计算图,两边打架很容易出显存碎片。我们最后的做法是推理链路完全绕开compile,走TensorRT-LLM或者直接用vLLM自带的优化,训练侧倒是留着compile来加速。如果非要用,建议把dynamic shape的范围卡死,配合torch._dynamo的一些缓存策略,但说实话投入产出比不划算。ONNX那条路对LLM支持也不算友好,尤其是KV cache和自定义算子,转过去经常掉性能。所以结论就是:推理服务里compile目前还是个玩具,真上生产要么关掉,要么等PyTorch把dynamic shape这块彻底打磨好。
我们线上也踩过这个坑,compile和vLLM的CUDA graph基本是互斥的,强行一起用显存碎片问题很难绕开。后来干脆推理侧全走TensorRT-LLM,compile只留在训练和离线批量打分里用。动态batch场景下torch.compile的收益本来就不稳定,重编译开销经常把省下的那点时间又吃回去了。除非你的shape很固定、又能接受长预热,否则生产推理真没必要硬上。