最近在试着用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 条动态shape这块确实是2.0 compile的老大难,我试过把max_length设成固定值+左padding,配合torch._dynamo的dynamic=False能稳不少。inductor第二次挂多半是缓存命中了错误的重编译,试试设TORCH_LOGS=recompiles看看是不是在反复触发guard,或者干脆用mode=max-autotune牺牲点编译时间换稳定。7B模型的话也可以考虑把KV cache部分单独拿出来不用compile,只编译decoder核心层,效果会好很多。
动态shape配compile确实坑多,试试max_length固定+padding到同一长度,能避开大部分设备不一致的报错。
遇到过一模一样的报错,后来发现是把position_ids和attention_mask传到不同device上了,建议先检查一下model.forward里所有输入tensor的device是否统一,尤其当用了padding时,attention_mask的shape会动态变,稍微不注意就跨了。关于动态shape,我的经验是torch.compile在dynamic=True下确实能跑,但性能提升会打折扣,而且有些算子还是会回退到eager,所以如果你能接受固定长度,比如把输入都pad到某个上限(像2048),稳定性会好很多,推理速度也比动态模式快。另外inductor后端那个“第一次过第二次挂”的毛病,我猜是CUDA graph捕获时遇到了新的shape导致缓存冲突,你可以试试torch._dynamo.config.capture_scalar_outputs=True,或者干脆把编译模式改成mode="reduce-overhead",虽然graph重编译会慢一点,但至少不容易崩。还有个偏方,如果实在搞不定,可以对非关键层用torch.compile,输出层和采样部分保持原样,这样报错范围小很多。最后想说,7B模型纯用PyTorch做优化其实不如vLLM或TensorRT-LLM省心,如果只是自己玩,换个框架可能更香。
同款问题,我上次跑chatglm也是这个报错,后来把padding到固定长度(比如512的倍数)就稳了。动态shape跟inductor的优化思路确实冲突,它很多fusion都是基于静态shape做的,你试试把max_length设死,或者用torch._dynamo的dynamic=True参数,但那个好像对某些算子支持还不完善。第一次能过第二次挂我怀疑是缓存或者内存复用的问题,建议开个新进程跑一次看看,或者直接换回eager模式对比下速度损失,有时候编译收益没那么大真不值得折腾。
说实话我也被这东西折磨过一阵,后来发现torch.compile对动态shape确实不友好,你试试在compile的时候加上mode="reduce-overhead"或者直接给输入搞个固定的max_length,虽然浪费点显存但至少能稳定跑起来。另外那个跨设备报错我怀疑不光是shape问题,可能是模型里某些op没标device,你检查下有没有用nn.DataParallel或者手动.to(cuda)的地方。inductor抽风太正常了,我后来干脆用onnxruntime或者TensorRT做推理,compile这功能现阶段还是适合训练场景多一点。你要是非得用,建议固定batch size和seq len,别让它动态分配。
动态shape建议直接pad到固定长度,或者试试torch._dynamo的mark_dynamic标记,报错会少很多。
动态shape确实是compile的大坑,我之前跑chatglm也遇到过类似的报错,后来是把padding到固定长度(比如512的倍数)才稳下来。你可以试试在编译时加上mode="reduce-overhead"或者用torch._dynamo.config.suppress_errors=True先看warning,至少能定位到具体是哪个op跨设备。另外inductor第一次和第二次行为不一致大概率是缓存的问题,试试设TORCHINDUCTOR_CACHE_DIR或者每次跑之前清一下缓存。还有个偏方,就是只compile decode阶段,prefill保持eager,这样能避开很多动态shape的麻烦。
遇到动态shape确实容易踩这个坑,我试过把max_length设成固定值然后padding到那个长度,compile基本就稳了,代价是显存多占一点但推理省心很多。另外你可以试试给torch.compile传dynamic=True参数,有些场景能缓解跨设备问题,但7B模型上我试过偶尔还是抽风。inductor后端第一次能过第二次挂我也遇到过,后来把mode改成max-autotune反而稳定些,虽然编译时间长了点。建议你先固定长度跑通,再慢慢调动态支持,别一上来就追求完美方案。
讲真你这个报错我太熟了,之前调stable diffusion的compile也踩过一模一样的坑,动态shape在inductor下就是个炸药包。我后来发现torch.compile对完全静态的shape最友好,哪怕你padding到固定最大长度,只要每个batch的shape完全一致,它就能把graph优化得比较彻底,但代价就是显存和算力浪费挺心疼的。你要是非得上动态shape,试试在compile的时候传dynamic=True,然后配合torch._dynamo.config的dynamic_shapes参数,虽然能过但性能提升会打折扣,而且某些算子还是会莫名触发recompile,第一次慢第二次挂的情况我也遇到过,感觉是graph guard的缓存策略在搞鬼。还有个野路子,把模型里那些导致跨设备的操作(比如自己写的自定义attention)尽量用原生nn.Module替代,有时候是自定义算子没标注设备导致编译器瞎猜。另外你可以考虑用torch._inductor.compile.get_backend加个debug参数看看具体是哪个节点崩的。不过说实话,现在大模型推理想稳定用compile,最省心的还是直接上vLLM或者TensorRT-LLM那套,PyTorch原生compile更适合研究阶段跑通流程,生产环境还是别太较真。
这问题我也踩过,动态shape在compile下确实容易触发跨设备检查,尤其是padding之后某些算子内部索引会跑偏。我现在的做法是分桶,把输入长度按区间固定成几个档位,每个桶单独compile,虽然编译次数多点但稳定很多。inductor第一次过第二次挂我也遇到过,怀疑是缓存和cudagraph的交互问题,可以试试把mode改成max-autotune或者关掉cudagraphs,至少目前我这样跑没再翻车。另外你检查下attention mask有没有跟着pad一起传到设备上,有时候是mask的device没对齐。
这个问题我前两天刚踩过,动态shape配inductor确实容易触发跨设备的bug,尤其带padding的时候。我现在的做法是直接给模型套个固定最大长度的wrapper,超出就截断,配合torch._dynamo.mark_dynamic可能好点。另外你可以试试把后端换成cudagraphs,有时候比inductor稳。第一次能跑第二次挂大概率是缓存和graph重编译的问题,清一下torch._dynamo的cache或者设个dynamic=True试试。
这问题我之前也踩过,动态shape在compile下确实容易触发设备检查的bug,尤其是padding后attention mask的形状变化。建议先用固定长度(比如把输入都截断到某个上限)跑通,确认能work再考虑动态方案,或者试试torch._dynamo的dynamic=True参数。另外inductor不稳定是常态,我后来换回默认后端,虽然慢点但至少不挂,你可以先求稳再求快。
这问题我踩坑踩太多了,动态shape目前确实是compile的硬伤,尤其是带padding的batch推理,跨device报错多半是某个子图被重编译后设备信息没同步。我现在是直接按最大长度固定输入,配合cudagraphs,虽然浪费点显存但至少稳定。inductor第二次挂那个我也遇到过,感觉是缓存和autotune的bug,试试torch._dynamo.config.suppress_errors=True能临时绕过,但根治还是得等官方修。另外7B模型如果实在要动态长度,可以试试把padding放在模型外面,或者用flash attention的变长接口,别让compile看到shape变化。
这问题我太有共鸣了,7B模型加动态shape基本就是踩坑重灾区。torch.compile现在对静态shape的优化确实最稳,你那个跨设备报错大概率是padding后某个分支没对齐导致的,建议先把max_length固定成你训练时的值,或者用mark_dynamic标记一下变化维度试试。我最近试下来,inductor后端配合fullgraph=True加dynamic=False反而比默认配置稳定不少,虽然牺牲点灵活性但至少不会第二次推理就炸。另外你试试把padding放在模型外面,用左padding或者右padding统一一下,有时候是tokenizer和模型内部的mask传播没同步才出这种玄学问题。还有个小技巧,编译前先跑一次warmup,让shape的guard缓存住,能减少不少stability的烦躁感。不过说实话,现在大模型推理真想省心,可能还是得靠vLLM或者TensorRT-LLM,PyTorch compile更适合研究阶段跑通,生产环境还是有点勉强。
说实话你这报错我太熟了,7B模型加动态shape基本是踩遍了所有坑。torch.compile目前对静态shape支持最好,你要是想省事,直接固定到最大长度比如2048做padding,虽然浪费点显存但至少能跑通。但你说inductor第二次挂这个,我怀疑是cudagraphs和动态shape的兼容性问题,可以试试torch.compile里加mode=“reduce-overhead”或者干脆关掉cudagraphs,或者把dynamic=True参数显式传进去,虽然慢点但稳定。还有个思路是分桶,把输入长度按区间切几个档位,每个档位单独编译一个graph,这样既不用全pad到最大,又能避开动态shape的雷。不过说实话,我现在大模型推理基本不用torch.compile了,直接vLLM或者TensorRT-LLM,省心太多,PyTorch原生编译还是更适合训练或者静态batch的场景。你要是非得用,建议升级到2.1以上,修了不少动态shape的bug,但别指望完全稳定,毕竟这功能对生产环境来说还是太新了。
这问题我也踩过,动态shape在compile下确实容易触发跨设备检查,因为graph模式会尝试缓存多个shape变体,有时候缓存没命中就给你整幺蛾子。我目前是直接限制输入长度到256并固定padding,虽然浪费点显存但至少稳了。inductor那个第一次过第二次挂的毛病我也遇到过,感觉是graph重编译时的缓存冲突,试试设torch._dynamo.config.cache_size_limit大一点,或者干脆用mode=“max-autotune”跑一遍看还会不会复现。另外你确认下是不是有if分支里创建了新的tensor没走统一device,这个在compile下特别容易漏。
torch.compile在动态shape上确实容易踩坑,尤其是padding后内部mask和位置编码那块,跨设备报错多半是graph break后某些tensor没跟着走。我试过把输入长度分桶,比如按32的倍数做padding,然后配合mark_dynamic或者设置dynamic=False,能稳不少。inductor那个第二次挂的问题我也遇到过,怀疑是cudagraph缓存和显存复用冲突,可以试试torch._inductor.config.triton.cudagraphs=False,或者干脆用compile的mode="max-autotune"但关掉cudagraphs。另外如果模型里有自定义op,建议先看下graph break点在哪,用torch._dynamo.explain输出报告,别硬调。
动态shape确实是compile的老大难,我这边之前调3B模型也踩过同样的坑,后来用torch._dynamo.config.suppress_errors=True先把动态shape的报错压下去,再配合padding到固定长度(比如64的倍数),基本能跑通。inductor后端第一次过第二次挂大概率是缓存或图优化的问题,可以试试torch._inductor.config.triton.cudagraphs=False关掉cudagraphs,有时候能解决。另外你也可以看看是不是某些op在编译后自动分派到了不同设备,手动用with torch.device('cuda')包一下试试。
动态shape确实是compile的硬伤,建议先用固定长度试试,或者开dynamic=True但得接受偶尔抽风。
动态shape确实是compile的老大难,我试过把max_length固定到256然后配合padding策略,基本能避开跨设备报错,代价是显存吃紧一点。inductor后端不稳定我也遇到过,后来干脆给关键模块单独compile,比如只编译attention部分,反而稳很多。你用的是哪种padding方式?如果是左侧padding的话,可以试试把padding侧改成右侧,有些算子对mask的隐式依赖会出这种怪问题。另外建议开一下mode=“max-autotune”,虽然编译时间长一点,但跑起来很少抽风。