最近在试着用PyTorch 2.0的torch.compile优化一个7B的对话模型推理速度,但一跑就报“RuntimeError: Expected all tensors to be on the same device”,debug了半天发现是动态shape的问题。我的输入长度变化比较大,用padding后模型内部有些操作还是跨设备了。想问问大家,现在大模型用compile的最佳实践是什么?是不是必须固定输入长度,或者有什么配置能避免这种报错?另外,我试了用inductor后端,但有时候模型第一次推理能过,第二次就挂,感觉稳定性还是有点玄学。真诚求教,别笑我菜。
PyTorch 2.0 compile在LLM推理时总报错,是我打开方式不对吗?
全部回复
共 182 条这问题太真实了,我上周也在7B上踩了几乎一模一样的坑。动态shape确实是torch.compile目前最大的痛点之一,尤其LLM推理时那个KV cache的尺寸一变,inductor就容易生成跨设备的中间张量。我自己试下来,最稳的办法其实不是固定输入长度,而是把padding逻辑挪到模型外面,然后在compile的时候用mode="reduce-overhead"配合fullgraph=True,让整个forward图尽量保持静态。不过就算这样,第二次挂的玄学我也遇到过,后来发现是cudagraphs的缓存跟动态shape冲突了,把torch.compile的dynamic参数设成False反而能跑通,但代价就是变长输入的性能提升会打折扣。另外你试试把attention的mask改成布尔型而不是float型,有些报错是dtype不一致引起的。说实话现在这块文档太少,社区里也多是试错经验,我也还在摸索,如果你找到稳定的配置方案记得回来分享下。
说实话你这报错我太熟了,之前调一个3B模型也卡在动态shape上,后来发现torch.compile对可变长度序列的支持确实还比较糙,尤其跟attention mask配合的时候容易出幺蛾子。我现在的做法是干脆把padding固定到某个batch内的最大长度,然后用mark_dynamic标记那几个可变维度,虽然会牺牲一点显存和速度,但至少稳定。另外你提到第一次过第二次挂,这个我怀疑是cudagraphs或者缓存复用的问题,试试把mode设成reduce-overhead或者干脆用default,有时候玄学问题换个后端就好。还有个小技巧,可以试试把位置编码和attention的部分单独拎出来不compile,只compile那些shape完全固定的层,虽然麻烦点但能避开好多坑。现在官方文档里其实有关于dynamic shape的说明,建议翻翻那个torch._dynamo.mark_dynamic的用法,比瞎试强。至于inductor不稳定,我猜可能是跟你的CUDA版本或者flash-attention版本有冲突,有条件的话换个容器环境对比一下。
这问题我太有同感了,torch.compile对动态shape的支持确实还不太行,你那个跨设备报错八成是graph break之后某个子图被recompile到默认设备上去了。我自己的经验是,如果非要用compile跑LLM,至少得把输入长度固定成训练时的max_seq_len的倍数,比如128或256的整数倍,padding到那个值再喂进去,这样能减少很多动态分支。另外你可以试试把dynamic=True参数显式传进去,虽然官方说支持,但实际效果还是看具体模型结构,有些算子还是会炸。至于inductor后端第一次过第二次挂,我怀疑是cudagraphs缓存的问题,建议把mode设成reduce-overhead然后关掉triton的某些优化试试,或者直接退回eager模式只对attention部分做手动优化。说实话,现在这个阶段大模型推理要稳定,还是得靠vLLM或者TensorRT-LLM那套,PyTorch原生compile更适合固定shape的CV模型或者小batch场景。你7B模型如果显存够的话,不如直接试下flash-attention2加连续batch,效果可能比折腾compile来得实在。
torch.compile对动态shape确实不友好,我试过把max_length固定成训练时的长度,然后用左padding(pad到左侧)能绕过一部分跨设备问题,但代价是显存多吃了不少。inductor那个玄学bug我也遇到过,后来发现跟graph break有关,建议把模型里那些自定义attention里的python循环全改成torch原生算子,能稳定一点。你试试用torch._dynamo.config.suppress_errors=True先跑通,再慢慢排查哪层出问题,比直接裸compile好调。另外7B模型如果输入长度波动大,其实可以考虑不用compile,用vLLM或者把KV cache提前分配好,效果可能更直接。
这问题我踩过一模一样的坑,动态shape在compile下确实容易触发设备检查的bug。你试试把输入长度固定到某个上限(比如256/512),padding到那个值,然后配合torch._dynamo的dynamic=False,大概率能稳住。另外inductor第一次过第二次挂,多半是缓存和CUDA graph的兼容问题,可以设TORCHINDUCTOR_FORCE_DISABLE_CACHES=1跑跑看,虽然慢点但能验证是不是这个原因。我后来干脆换成了vLLM或TGI这类专门的推理框架,省心不少,7B模型用不上硬啃compile。
这问题我前段时间也踩过,动态shape确实是compile的大坑,你可以试试在输入处加个torch._dynamo.mark_dynamic,或者干脆把padding的长度固定成几个档位。inductor后端第二次挂很可能是缓存或者图捕获的问题,先关掉dynamic=True试试默认模式。另外7B模型建议直接看torch.compile的reduce-overhead模式,配合cudagraphs有时能绕开设备不一致的报错。
这问题太真实了,动态shape在compile下就是地狱难度。我试过把padding到固定长度,比如取batch里最大那个,然后配合torch._dynamo的dynamic=True参数,但说实话还是偶尔抽风。建议你干脆分桶处理,把输入长度相近的样本凑一起,每个桶内固定shape,这样至少能稳定跑起来。至于inductor第一次过第二次挂,可能是缓存没清理,试试torch._dynamo.reset()或者换回默认后端,有时候图模式比贪心模式更稳。
这问题我踩过一模一样的坑,动态shape在compile下确实容易触发跨设备检查,尤其7B这种模型里带conditional op的时候。我现在的做法是干脆把输入长度量化到几个固定档位,比如128、256、512,padding到最近档位,虽然浪费点显存但省心多了。另外inductor那个玄学稳定问题,试试把mode设成max-autotune然后加个torch._dynamo.config.suppress_errors=True,至少能让第一次跑过,第二次挂的bug大概率是cudagraph缓存没清干净,每次推理前手动清一下cache试试。
这问题我也踩过,动态shape确实跟compile八字不合,先固定长度试试,或者用torch._dynamo.config加个dynamic=True参数再调调。
我也踩过这个坑,动态shape在compile下确实容易触发设备不一致的报错,后来把输入长度按batch内最大值截断+固定max_len,基本就稳了。inductor第一次过第二次挂大概率是缓存和重新编译的边界问题,试试torch._dynamo.config.cache_size_limit调大点,或者干脆用mode="max-autotune"看看。另外7B模型如果显存不紧张,其实不用强行compile,flash-attention和KV cache的优化收益更直接,你可能也试过吧?
动态shape确实容易踩这个坑,试试把padding到固定长度或者用torch._dynamo的dynamic=True参数,能省不少事。
inductor老抽风的话,换个cudagraphs后端试试,我这跑7B稳多了。
这问题我也踩过,动态shape在compile下确实容易触发跨设备的隐式同步,尤其是padding后某些算子会悄悄把tensor搬到默认设备。建议先试torch.compile的mode=max-autotune加fullgraph=True,同时把输入长度上限定成固定值(比如模型最大长度),内部padding用左对齐,能省掉不少麻烦。稳定性方面,第二次挂可能是缓存key冲突,可以试试禁用shape specialization或者换个后端,比如ipex或triton的调优参数,别死磕inductor。另外可以看看生成时是否用了KV cache的静态分配,那个比动态分配稳很多。
动态shape这块确实是torch.compile目前最难受的点,尤其LLM推理场景下输入长度一变化,graph重新捕获就容易踩到跨设备的雷。我试过把padding打到固定长度(比如模型最大长度),然后配合mark_dynamic标记某些维度,但效果还是不稳,有时候优化了反而更慢。感觉跟具体模型结构关系挺大,像attention的mask操作和position id那块特别容易触发重编译。inductor后端那个第一次能过第二次挂的毛病我也遇到过,怀疑是缓存graph时没处理好某些tensor的device属性,试过加torch._dynamo.config.suppress_errors=True能跳过报错但性能就回退了。现在我自己是干脆分两套走,长序列用传统path,短序列才开compile,虽然麻烦但至少稳定。你有试过用static shape配合cudagraphs吗?我看有些人在用这个组合,好像比单纯inductor省心。另外7B模型其实也可以考虑下量化或者vLLM那套paged attention,说不定比死磕compile更实用。
动态shape在compile下确实容易踩坑,先试试固定到最大长度再配合padding_mask吧。inductor后端不稳定的话,可以看下cudagraphs关掉会不会好点。
动态shape确实是compile的大坑,我之前也踩过,把输入长度固定到最大再配合padding,虽然浪费点显存但至少能稳定跑。inductor那个第一次能过第二次挂的问题,我后来把mode调成max-autotune,再关掉一些重编译的优化项,稍微好点,但偶尔还是会抽风。听说现在可以试试给模型加个static cache之类的,或者干脆用vLLM那套,省心很多。你用的是哪个版本的torch?2.1之后对动态shape的支持好像有改进。
这问题我前两天刚踩过,动态shape在inductor下确实容易触发device guard的bug,尤其padding后attention mask的广播逻辑。我目前是粗暴地把输入长度分桶到几个固定档位,比如128、256、512,配合torch._dynamo.mark_dynamic标记关键张量,基本能稳住。另外你试试把fullgraph=True加上,有时候能提前暴露一些图外操作导致的跨设备问题。第二次挂的话,大概率是缓存复用时的shape specialization没清理干净,可以试试torch._dynamo.config.specialize_int=False,虽然会牺牲点性能但至少不崩。
这问题我也踩过,动态shape建议直接pad到固定长度,或者试试torch._dynamo.mark_dynamic。
inductor第二次挂大概率是缓存bug,换个reduce-overhead模式或者更新到2.1试试。
动态shape确实坑,我都是直接pad到固定长度再compile,省心不少。inductor稳定性就随缘吧,跑不通就换回eager模式。
试试把padding到固定长度,别用动态shape,compile对变长输入支持确实还不行。我上次也是折腾半天,最后锁死长度就稳了。
这问题我也踩过,动态shape在compile下确实容易触发设备不一致的报错,尤其是padding后mask参与计算的时候。建议先用固定长度跑通,再慢慢放宽到动态,或者试试给compile传dynamic=True参数,虽然性能会打点折扣但稳定不少。inductor那个偶发挂掉的情况我也遇到过,感觉跟图捕获时的缓存状态有关,可以试试每次推理前清一下torch.compile的缓存,或者干脆用reduce-overhead模式看看。另外7B模型其实也可以考虑换fschat的vLLM后端,省心很多。
动态shape确实容易踩坑,建议先固定到最大长度试试,或者用torch._dynamo的dynamic=True参数。第二次挂可能是缓存问题,清一下torch.compile的缓存目录再跑跑看。