最近在试着用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确实是compile的坑,试试把padding到固定长度或者用torch._dynamo.mark_dynamic标记下,会稳很多。
动态shape确实是compile的大坑,我试过在token生成阶段把max_seq_len固定成2048,padding到定长反而能跑通,虽然浪费点显存但胜在稳定。inductor那个时好时坏的问题我也遇到过,后来发现把mode设成max-autotune能缓解一点,但编译时间直接翻倍。另外你检查过attention mask和position id的device吗,有时候问题出在缓存KV时没显式指定device。
动态shape确实是torch.compile目前最头疼的坑之一,我试过在LLM推理时用mark_dynamic标记可变维度,但实际效果还是看运气。你那个跨设备报错我猜是padding后attention mask和位置编码的计算路径没对齐导致的,建议先检查一下是否有tensor显式调用了.cuda()或者device属性被硬编码。固定输入长度确实是绕开问题最稳妥的办法,但代价是浪费算力,尤其batch size和长度都波动的时候。我最近在试torch._dynamo.config里的dynamic_shapes=True参数,配合inductor的dynamic=True,小模型上稳定性好了点,但7B这种规模还是偶尔会崩。另外你提到第一次能过第二次挂,很可能是缓存了graph后遇到新shape触发重新编译,这时候内存峰值会突然飙高,可以考虑设置torch._dynamo.config.cache_size_limit调大一点,或者干脆用mode="max-autotune"看看会不会缓解。说实话这功能现在还是偏实验性质,生产环境我建议先别全量上,至少留个eager模式的fallback开关。
动态shape确实坑,建议先用固定长度跑通再优化,或者试试torch._dynamo的mark_dynamic标注一下。
说实话你这个报错我太熟了,之前调Llama的时候也被dynamic shape折磨过好几轮。torch.compile现在对静态shape确实友好得多,但7B这种规模本来输入长度就很难固定,硬padding到最大长度又浪费显存。你可以试试把padding做到固定长度后,在模型forward里把attention mask和position id都跟着调整好,跨设备的问题多半是某个分支没把tensor显式搬到对应device上,建议在compile之前先关掉cudnn benchmark跑一遍纯eager模式,看能不能复现。另外inductor后端不稳定我也有同感,第一次能过第二次挂经常是缓存或graph break导致的,可以试试torch._dynamo.config.suppress_errors=True临时跳过,但别指望根治。我目前比较稳的配置是先用torch.compile(mode="reduce-overhead")配合固定长度输入,再在推理服务层做动态batch和长度分桶,而不是让模型内部去处理变化。你如果非要用动态shape,可以看看官方文档里关于dynamic=True参数的说明,但说实话对7B这种规模效果还是有限。还有个思路是直接换vLLM或者TensorRT-LLM,它们对动态shape的支持比torch.compile成熟太多,别在torch这条路上死磕。
这问题我也踩过,动态shape在compile下确实容易触发device guard的bug,尤其padding后mask和位置编码那部分。我后来是直接把输入长度分桶,比如64、128、256这样,每个桶单独compile,效果稳定多了。还有inductor那边,如果第二次挂可以试试加torch._dynamo.config.capture_scalar_outputs=True,或者直接关掉动态shape支持,牺牲点灵活度换稳定。大模型推理现阶段真别太指望compile全自动优化,手动把静态化做彻底比啥都强。
说实话你这不菜,torch.compile在LLM推理上目前就是一堆坑,动态shape是最大的雷。我试过7B和13B,最后发现固定长度加padding到上限反而最稳,性能损失也就5%左右,但至少不会一跑就炸。你那个跨设备报错,八成是模型里某些操作(比如attention mask相关的)在compile时被graph捕获后,device判断逻辑走偏了,可以试试在compile前手动把input_ids和attention_mask都显式放到同一设备,再给model里加个assert。至于inductor第二次挂,我怀疑是cache reuse的问题,你试过torch._dynamo.config.suppress_errors=True吗?虽然治标不治本,但至少能看出是哪个节点在第二次编译时出问题。另外建议用mode="reduce-overhead"配合fullgraph=False,别一上来就追求全图优化,把关键模块(比如decoder layer)单独compile,其他部分保持eager,稳定性会好很多。还有,如果输入长度变化实在太大,可以考虑用静态shape但分桶处理,比如256、512、1024三个档位分别编译,这样既不用每次都重新compile,又能覆盖大多数场景。我现在基本就是这个思路,跑起来比纯eager快20%左右,虽然达不到官方吹的2倍,但至少不会半夜起来看日志了。
动态shape确实是compile的坑,建议先固定seq len跑通再优化,不然跨设备报错能折磨死人。
试过把padding到固定长度然后开fullgraph模式吗?我这边7B用这个基本稳了。
动态shape确实是compile的头号杀手,我试过把padding固定到最大长度然后配合torch._dynamo.mark_dynamic指定维度,能缓解不少跨设备问题。inductor那个第一次过第二次挂的毛病我也遇到过,感觉是CUDA graph缓存和autograd交互的坑,可以试试把mode调成reduce-overhead或者直接关掉cudagraphs。另外你检查下模型里有没有用python原生list做张量运算,那种地方经常被dynamo忽略导致设备错位。说实话目前大模型推理还是vLLM那套更稳,compile更适合固定batch的离线场景。
同款踩坑,动态shape在compile下确实容易触发device guard的误判,尤其是padding后某些算子会隐式访问到原先的batch维度。建议先试试把输入长度固定到上限(比如512),用mask处理,跑通后再考虑dynamic=True,但那个目前对inductor支持确实一般。另外第一次能过第二次挂很可能是缓存了错误的graph,可以试试torch._dynamo.mark_dynamic标记变量维度,或者干脆每次前向都重新compile。稳定性确实玄学,目前生产环境还是老老实实关compile用torch.inference_mode加半精度更省心。
这问题我踩过一模一样的坑,动态shape在compile下确实容易触发跨设备检查,后来我是把padding统一到固定长度(比如最大序列截断+左padding)才稳住的。inductor后端第一次过第二次挂我也遇到过,感觉跟缓存和shape特化有关,试试加mode="reduce-overhead"或者干脆关掉dynamic shape支持。另外如果只是推理,其实可以考虑用ONNX Runtime或者vLLM,没必要死磕compile,7B模型收益不一定明显。你用的是哪个版本的PyTorch?更新到2.1+之后感觉稳定性好了不少。
这问题我熟,之前调chatglm也踩过这坑。动态shape别硬刚,建议把max_seq_len设成固定值,比如512或1024的倍数,然后pad_sequence的时候直接用那个常量,至少能绕开一半报错。inductor稳定性确实看版本,2.1之后好很多,你要是还在2.0.1就赶紧升吧。另外试试把编译参数里mode设成reduce-overhead或者max-autotune,有时候默认配置对LLM不友好。你那个跨设备报错,八成是某个分支里生成了未指定的cuda tensor,建议在模型forward入口统一加个.to(device)兜底。
这问题我上周刚踩过,动态shape在compile下确实容易触发device断言,尤其带padding时。我现在的做法是分桶处理,把输入长度归到几个固定档位,每个桶单独compile,基本不报了。另外inductor跑LLM建议把mode设成reduce-overhead,然后关掉动态shape相关的优化,第一次能过第二次挂大概率是graph缓存没处理好,可以试试torch._dynamo的cache_limit调大点,或者干脆用fullgraph=True强制静态图。你用的什么版本的CUDA?有时候编译器和驱动版本不匹配也会出这种玄学问题。
试试把padding改成动态padding,或者给compile传个mode=max-autotune,能缓解不少。inductor确实玄学,建议小batch先跑通再上大模型。
动态shape确实是compile的坑,建议先试试torch._dynamo的dynamic=True参数,或者干脆把padding固定到某个上限值看能不能绕过去。
我上周也踩了inductor的坑,第二次推理挂多半是缓存问题,加个torch._dynamo.config.cache_size_limit=64试试,说不定能稳点。
这问题我前段时间也踩过,动态shape确实是compile的大坑,尤其带padding的时候,建议试试把padding到固定长度或者用torch._dynamo的dynamic=True参数,虽然会牺牲点性能但能省心不少。inductor不稳定我也遇到过,后来发现跟CUDA graph和缓存有关,可以把mode换成max-autotune-no-cudagraphs试试。另外你确认下是不是有某个分支在cpu上执行了,比如tokenizer或自定义op没标device,这种跨设备报错不一定全是shape的锅。
同款踩坑路过,动态shape在compile下确实容易触发device guard的误判,尤其是padding到不同长度时,attention mask的广播逻辑会有隐式跨设备操作。我后来是先用静态长度跑通,再把输入统一pad到最大长度加个mask,虽然浪费点显存但至少稳定了。inductor那个第一次过第二次挂的问题,我怀疑是缓存了错误的graph,试过清torch.compile的缓存目录或者换reduce-overhead模式会好点。另外你试试把dynamic=True参数加上,虽然慢一点但能减少这种玄学报错。
这问题我也踩过,动态shape在compile下确实容易触发跨设备检查,尤其是padding后attention mask的维度变化。我目前是先把输入长度分桶(比如128/256/512),每个桶单独compile,虽然牺牲点灵活性但稳定多了。inductor那个时好时坏的情况,建议试试把mode设成“max-autotune”或者关掉dynamic=False显式声明静态shape,能省掉不少玄学问题。另外如果模型里有自定义op,先确认下是不是它们没实现device guard,这锅有时候不全是compile的。
说实话你这个报错我太熟了,7B模型加动态shape基本就是给自己上强度。torch.compile对静态shape优化最狠,一旦输入长度飘忽不定,它内部的图优化就容易跟显存分配打架,跨设备报错多半是graph break之后某个子图把张量搬到了默认设备上。我建议你先别急着上compile,试试把padding后的长度量化到几个档位,比如128、256、512,然后每个档位单独做一次静态编译,这样既保留一点动态性,又能避开大部分雷区。
关于inductor第二次挂的问题,我猜是缓存了错误的kernel或者cudagraph重放时碰到了新的shape,你可以试试关掉mode='reduce-overhead',有时候这个模式对内存布局特别敏感。另外动态shape如果实在绕不开,考虑一下用torch._dynamo.config的dynamic_shapes=True参数,但别指望它能完全救你,该炸还是会炸。
我自己的经验是,能固定batch和seq_len就尽量固定,实在不行用vLLM或者TensorRT-LLM这种专门为推理设计的框架,它们对动态shape的容忍度高得多。PyTorch compile目前更适合炼丹而不是纯推理部署,尤其对7B这个量级,收益可能还没你debug花的时间多。要是你只是想提速,先试试torch.inference_mode加上半精度,说不定比compile省心。
这问题我熟,之前调llama的时候也被动态shape坑过。你试试把padding到固定长度,然后用mark_dynamic标记那几个可变维度,能绕开不少device check。inductor不稳定太正常了,我后来干脆在关键路径上关了compile,只在attention那块开,效果反而稳。
另外7B模型的话,建议看看是不是kv cache的预分配没跟上,跨设备报错有时候是缓存tensor和设备不匹配导致的。你要是实在折腾不动,先用CUDA graph或者直接上vLLM,省心很多。