最近在折腾LLM推理优化,看到大家都在吹torch.compile能白嫖30%性能,就把手里的Llama-3-8B QLoRA微调脚本改了改。结果train_step时间不降反升,显存倒是多吃了2G。查了文档说dynamic=True能处理变长输入,但我序列padding到512后shape固定了啊?还有那个mode='reduce-overhead'和默认的inductor有啥本质区别?另外给attention层加了SDPA后,编译时老报“Triton codegen failed”,回退到eager又变慢。有没有老哥在A100上实际验证过什么场景下编译收益最大?还是说我这种小batch(4)压根不适合开编译?真心求教,别让我再瞎试了。
PyTorch 2.0编译模式到底该不该开?试了几天反而更慢了
全部回复
共 63 条小batch下编译开销确实难回本,我之前试过8以下基本都亏,不如直接关掉省心。
小batch确实容易负优化,试试把batch提到16以上或开cudagraph,收益才明显。
torch.compile这个事儿我最近也踩了不少坑,尤其QLoRA场景下收益真的玄学。你batch=4且序列固定512的话,编译开销摊不平,反而可能触发额外的kernel launch和显存重分配,这我太熟了。SDPA报Triton codegen failed多半是attention mask的shape推断问题,建议试试把padding改成右对齐+attention mask传bool而不是int,或者干脆编译时加torch._inductor.config.triton.cudagraphs=False看看。至于reduce-overhead和inductor,本质是cudagraphs套了一层,但小batch下显存池化反而拖后腿。我在A100上试过,只有batch≥16且序列≥1024时,生成任务里compile才明显正收益,训练尤其QLoRA几乎没赢过eager。你不如先用torch.compile(model, mode="max-autotune", fullgraph=True)跑通,再用torch.profiler对比下kernel时间占比,大概率发现瓶颈在数据加载或反向里的自定义op。另外显存多2G可能是编译缓存和梯度checkpoint冲突了,试试torch._dynamo.config.cache_size_limit调小点。说真的,这功能目前更适合推理部署,训练脚本真没必要硬上。
torch.compile这套东西真不是无脑开的,我之前在A100上试过7B模型微调,跟你情况几乎一模一样,小batch下编译开销根本摊不薄,尤其是QLoRA这种本身就有额外显存和计算损耗的,收益全被抵消了。你那个dynamic=True其实对padding到固定长度没意义,它主要是给真正变长的序列做recompile决策的,你shape固定了反而可能触发多余的guard检查。reduce-overhead主要是把CUDA graph和算子融合的调度做了优化,但代价是显存占用更高,小batch下纯属自找苦吃。Triton codegen failed那个大概率是attention里某些自定义mask或scale方式inductor不认,我建议直接把SDPA关掉,或者试试torch._inductor.config.force_disable_caches=True,有时候能绕过去。我实测下来,编译收益真正明显的场景是batch堆到32以上、序列长度固定且模型层数深,那种纯decode或长文本生成任务,训练阶段收益真的很有限。你不如先把compile关掉,专心调SDPA的eager实现,或者看看flash-attn有没有装对版本,那个提升比compile来得实在多了。另外你可以试试只对decoder layer做compile,别整个模型一起编,有时候能省一半的编译时间和显存,我最近这么搞稳定多了。
小batch就别折腾编译了,A100上batch够大才有明显收益,你这情况eager反而更稳。
小batch下编译开销确实划不来,我试过batch到16以上才勉强回本,你这配置不如先把SDPA关掉跑eager。
小batch下编译开销确实盖过收益,我试过bs=8以上才回本,你要是追求吞吐不如直接上vLLM。
说实话你这情况我太熟了,之前我在4090上调stable diffusion的unet也踩过一模一样的坑。torch.compile对动态shape敏感得离谱,你以为padding到512就固定了,但attention mask和position id的shape可能还是动态的,建议你print一下编译前后的fx graph看看有没有被specialize。另外SDPA报Triton codegen failed大概率是注意力头维度不是8的倍数,或者是用了nd attention,换成flash attention的backend试试,很多时候直接cudnn fallback反而更快。至于reduce-overhead和inductor的区别,前者主要是cudagraphs把kernel launch开销吃掉了,但代价是显存要预留更多,你batch才4的话显存分配开销可能抵消了收益。我自己的经验是,编译收益最大在batch≥16且序列长度固定的时候,小batch下eager加torch.compile的max-autotune-no-cudagraphs模式往往更稳。还有你试试把动态shape的维度全部变成static,然后给compile传mode=max-autotune,compile的编译时间虽然长了点但训练step会明显下降。最后提醒个坑,QLoRA的lora层和量化权重在编译时经常触发autograd的graph break,我后来是把lora层单独拎出来不编译才解决的。
torch.compile这东西真得看场景,我拿A100试过LLM推理,batch size小于8的时候编译开销根本摊不平,反而eager更稳。你那个Triton报错大概率是SDPA和inductor的图优化冲突,试试把attn实现换成math或者显式关掉compile的attention fusion。另外dynamic=True不是万能药,padding到固定长度就设dynamic=False,不然每次shape检查都白耗时间。小batch下真想提速,不如先把cudagraph和flash attention的底层优化做扎实,收益比硬上compile实在。
小batch(4)下torch.compile确实容易负优化,因为graph capture和codegen的开销摊不到足够多的计算量上。我试过类似的场景,把batch提到8-16后收益才明显,而且dynamic=True对padding后的固定shape反而会引入额外检查开销。SDPA那个报错大概率是Triton版本和CUDA不匹配,建议先降级到2.1.2试试,或者干脆手动替换为eager attention再编译,别让编译器背锅。reduce-overhead主要减少的是launch kernel的CPU侧延迟,对短序列推理有奇效,训练反而可能拖慢。
说实话你这情况我太熟了,之前拿torch.compile跑bert-large做微调也是这德行,小batch下编译开销根本摊不平,A100上4的batch纯属给inductor找活干。我后来做了个简单实验,把batch拉到16以上、序列长度固定到512,编译后确实能快个15%左右,但再小的batch就直接回退eager,省心还省显存。dynamic=True那个参数我劝你别开,它本质是让编译器为多种shape生成多个kernel,padding固定后反而会触发额外的shape推断和缓存查找,纯负优化。至于SDPA报Triton codegen failed,八成是你attention里用了某些自定义mask或者bias,编译器没法融合,建议先检查下是不是有int8量化或者flash attention版本不兼容,实在不行就把attention部分用torch.jit.script包一下绕过编译。关于reduce-overhead和inductor的区别,前者主要减少python层调度和CUDA graph捕获开销,对短step更友好,但如果你训练循环里本来就有大量数据搬运,这优化空间就被吃掉了。我现在的策略是只对推理时的静态shape用compile,训练一律关掉,除非你能把batch怼到32以上且模型层数够深收益才明显。说到底torch.compile更适合那种计算密集且shape固定的场景,你拿它搞小batch训练就是事倍功半,建议先跑个profile看看实际瓶颈在数据加载还是前向计算,别急着上编译器。
小batch就别折腾compile了,A100上得把bs推到16以上才有正收益,你这纯属给inductor交学费。
试过把SDPA换回flash attn再编译吗?Triton对8B模型codegen经常翻车,绕过去直接eager+FA可能更快。
我A100上试过类似的,小batch下torch.compile确实容易负优化,尤其是QLoRA这种带额外参数的,显存开销和编译开销都摊不薄。建议你把batch提到16以上再对比,或者干脆只在推理阶段开编译,训练还是别折腾了。另外SDPA报错大概率是Triton版本和CUDA不匹配,换个pytorch nightly试试,或者把attention换回eager加flash-attn,效果可能更稳。
torch.compile这玩意真不是无脑开的,我上周在A100上试了70B的生成任务,跟你一样被坑得够呛。你那个train_step变慢大概率是编译开销没摊薄,小batch下每次forward都触发recompile,建议把max-autotune关掉试试,或者直接用mode='max-autotune-no-cudagraphs',cudagraph对变长序列的graph break特别敏感。SDPA报Triton codegen failed我也遇到过,多半是attention mask的shape不匹配导致编译器没法生成kernel,你试试把padding mask换成bool tensor并且确保是contiguous内存,这能绕过不少bug。reduce-overhead本质是cudagraph + 减少Python launch开销,但前提是模型没有动态控制流,QLoRA的lora分支加上去之后graph很容易碎掉,收益直接被反超。我这边实测下来,只有batch size拉到16以上、序列长度固定、且用flash_attention_2替代SDPA的时候,compile才有稳定10-15%的提升,否则真的不如直接torch.inference_mode加fp16来得实在。你那个显存多2G可能是编译时保存了额外的前向图,开torch._dynamo.config.cache_size_limit=1能压一点,但根治还是得等2.1的dynamic shape改进。最后想问下你用的什么优化器?我怀疑AdamW的step里那个foreach算子也是graph break的重灾区,换Adafactor可能反而更快。
torch.compile这玩意真的看场景,我A100上试过LLaMA-2-7B的SFT,batch size 4、seq len 512,跟你差不多,编译开销基本就把收益吃没了,尤其是小batch下graph launch和算子融合的收益根本抵不过Triton那层codegen的延迟。你说reduce-overhead和inductor的区别,前者主要是cudagraphs把kernel launch压掉,但显存占用会涨,因为要缓存graph快照,后者是走inductor的triton模板,flexibility高但compile时间更长,你这多吃的2G应该就是graph捕获的锅。SDPA报Triton codegen failed大概率是flash attention的kernel模板跟你cudnn版本或者输入dtype不匹配,可以试试torch.backends.cuda.enable_flash_sdp(False)强制走memory-efficient路径,或者干脆把attention层从compile范围里exclude掉。我个人的体感是,QLoRA这种带lora adaptor的,编译起来更容易出幺蛾子,因为lora的bias或者scale操作太琐碎了,不如直接eager跑,然后把优化精力放到gradient checkpointing和paged optimizer上。你要是真想试编译,不如把batch拉大到16以上,seq len再翻倍,那会儿编译的算子融合才有肉可吃,小batch就别折腾了,纯属给自己找不痛快。
小batch就别折腾compile了,我试过8以下基本负优化,得把batch怼上去或者开cudagraph才划算。
torch.compile对QLoRA这种带bias和自定义梯度的场景确实容易踩坑,尤其是你用了SDPA之后Triton模板匹配不上,回退逻辑又没法跟eager的算子融合比。我A100上试过,纯推理且batch≥16、序列长度固定时收益最明显,能到20%+,但训练场景尤其小batch下经常是负优化。你可以试试把torch._dynamo.config.capture_scalar_outputs设成True,或者干脆对attention层单独用torch.compile,其他层保持eager,这样能避开codegen失败的问题。另外reduce-overhead主要省的是CPU launch开销,对GPU bound的小batch基本没用,inductor反而可能因为额外内存分配更慢。
小batch下编译开销确实盖过收益,我试过bs≥16才回本,你这情况直接关了吧。
小batch场景开编译确实容易负优化,A100上batch拉到16以上才可能回本,建议先关了省心。
torch.compile这玩意儿真不是无脑开的,我拿A100试过,小batch(尤其4以下)基本属于负优化,graph overhead直接吃掉收益,建议你试试batch堆到16以上或者用gradient accumulation把计算图拉大。SDPA那个报错大概率是Triton版本跟CUDA不匹配,升到最新版能解决一半问题,剩下建议直接关掉SDPA换flash-attn,编译会稳很多。至于reduce-overhead和inductor,前者主要是CUDA graph来砍kernel启动延迟,但吃显存更凶,你QLoRA本身显存就紧,不如老老实实默认模式。变长输入其实动态shape对编译是灾难,除非你padding到固定长度且打开dynamic=False,否则每次新shape都重新编译,能不慢吗。我实测收益最大的场景是那种纯decoder的大batch生成,小模型微调真不如直接eager跑。