大模型推理引擎vLLM(39):CUDAGraph模式以及CUDAGraphWrapper和BreakableCUDAGraphWrapper问题整理

目录

[1 PIECEWISE、FULL、FULL_AND_PIECEWISE、FULL_DECODE_ONLY区别](#1 PIECEWISE、FULL、FULL_AND_PIECEWISE、FULL_DECODE_ONLY区别)

[2 CUDAGraphWrapper和BreakableCUDAGraphWrapper](#2 CUDAGraphWrapper和BreakableCUDAGraphWrapper)

[2.1 CUDAGraphWrapper backend](#2.1 CUDAGraphWrapper backend)

​编辑

[2.2 BreakableCUDAGraphWrapper backend](#2.2 BreakableCUDAGraphWrapper backend)

[2.3 为什么kimi k3的piecewise必须用breakable的后端](#2.3 为什么kimi k3的piecewise必须用breakable的后端)

[3 cudagraph的步长和档位](#3 cudagraph的步长和档位)


1 PIECEWISE、FULL、FULL_AND_PIECEWISE、FULL_DECODE_ONLY区别

它们只决定一件事:decode 用哪种录像,prefill 或混合 batch 用哪种录像。

这里的「prefill」在代码里叫 mixed,包括纯 prefill,也包括一个 batch 里既有 prefill 又有 decode。纯 decode、每条请求 query 长度相同,才走 decode 那一列。

运行时真正只有三种录像:NONE(不录)、PIECEWISE(attention 在外)、FULL(attention 在内)。后面两个带下划线的名字,是把这三种分别派给 decode 和 prefill。

配置 decode prefill / 混合 batch
PIECEWISE PIECEWISE:attention 在图外,eager;前后的计算是小图 同样是 PIECEWISE
FULL FULL:整段 forward 一张图,attention 也在里面 prefill 也要求整段 forward 进一张图;变长 attention 很多后端根本不支持放进 FULL,所以通常不用这个模式,
FULL_DECODE_ONLY FULL 不录,整段 eager
FULL_AND_PIECEWISE FULL FULL_AND_PIECEWISE。这是 vLLM 默认

2 CUDAGraphWrapper和BreakableCUDAGraphWrapper

2.1 CUDAGraphWrapper backend

CUDAGraphWrapper 是 018、021 里唯一的一套。切分发生在编译期。torch.compile 把 forward 追踪成一张 FX 图,在 splitting_ops 上剪开。

编译期先做一步,而且只为 prefill 做。torch.compile 把 forward 追踪成 FX 图,在 attention 上剪开。剪完之后,一层变成三截:左边的 norm、qkv 是一块子图,attention 自己留在剪口上,右边的 o_proj、MoE 是另一块子图。每块子图包一个标记为 PIECEWISE 的 CUDAGraphWrapper。prefill 时调度器宣布 PIECEWISE,就播这两块小图,attention 现场跑。

decode 不走这些剪口。已经剪开的模型最外面再套一个标记为 FULL 的 CUDAGraphWrapper。纯 decode 时调度器宣布 FULL,这个外层 wrapper 用一次 torch.cuda.graph() 把整段 forward 录成一张大图。里面的小图 wrapper 这次对不上模式,不再各自录像。它们的计算连同 attention 一起被外层这张大图录进去。所以 decode 的 attention 在图里。

编译期在 attention 上剪开,只给 prefill 的 PIECEWISE 用。decode 的 FULL 是外层另外一套录像,把剪口盖住,attention 也在那张大图里。两套开关,一次只用一套。

每个 wrapper 先看调度器这次宣布的运行模式。对不上就只把计算往下传,自己不录像。所以 prefill 和 decode 是两份已经录好的东西,一次请求只用一份。

Prefill 或混合 batch。 调度器宣布 PIECEWISE。外层那个标记为 FULL 的 wrapper 对不上,不录,只是调用 forward。里面的子图对得上,各自播一张小图。attention 落在剪刀上,哪张小图都不收它,所以现场发 kernel,这就是 eager。一次 prefill 的一层是:

小图(norm / qkv) → attention,eager → 小图(o_proj / MoE)

attention 必须留在外面,是因为 prefill 里每条请求的 query 长度、KV 读到哪都不一样。CUDA Graph 要求录像时 kernel 的启动次数和显存地址都固定,换一套长度不能重放。两边的矩阵乘可以把 token 数 pad 成固定大小,所以能录。

纯 decode。 每条请求的 query 长度相同,不开推测解码时就是 1 个 token,attention 的形状也能固定。调度器宣布 FULL。外层 wrapper 对得上,用一次 torch.cuda.graph() 把整段 forward 录成一张大图,attention 也在这张图里面。里面那些 PIECEWISE 的小 wrapper 对不上,不再单独录像,它们的计算被外层这张大图一起录走。一层是:

一张大图:norm / qkv → attention → o_proj / MoE

如果配置不是默认,而是整个服务都设成 PIECEWISE,那 decode 也走上面 prefill 那条,attention 仍然在图外。FULL_DECODE_ONLY 则是 decode 走这张大图,prefill 不录,整段 eager。两种后端都一样,因为这是模式决定的,不是后端决定的。

2.2 BreakableCUDAGraphWrapper backend

BreakableCUDAGraphWrapper 是这套 Kimi 代码新加的,018、021 里没有。KimiK3、KimiLinear、DeepSeek-V4、Inkling、MiniMax-M3 这些模型没有走原来的 @support_torch_compile,启动时如果没手动设置,就会自动 VLLM_USE_BREAKABLE_CUDAGRAPH=1,同时把 torch.compile 关掉。整个模型只在外面包一层,不在编译期切图。attention 上挂了 @eager_break_during_capture。第一次真正跑 forward 时,碰到这个装饰器才决定剪不剪。

一层都可以看成:

norm / qkv 投影 → attention → o_proj / MoE

没有编译,也没有事先切好的子图。录像发生在第一次跑某个 batch 形状的 forward 时。装饰器看的也是调度器这次宣布的运行模式。

Prefill 或混合 batch,模式是 PIECEWISE。 捕图过程中碰到 attention,就做三件事:结束当前这段 graph,attention 在 CPU 上 eager 跑,再开始下一段 graph。录出来的不是一张图,而是一条播放列表:

replay 第 1 段(norm / qkv) → eager 跑 attention → replay 第 2 段(o_proj / MoE)

以后每次同样形状的 prefill 就按这个顺序播。eager 那段必须写进捕图时固定的那块显存,后面的 graph 段读的才是同一块地址。效果和经典 PIECEWISE 一样,attention 在图外,只是小图不是编译切好的,是跑的时候剪出来的。

纯 decode,模式是 FULL。 同一个装饰器看到模式是 FULL,不剪。attention 的 GPU 操作留在当前这段 capture 里,和前后的计算录在一起。一层接近经典 FULL 的那张大图:

一段连续的 capture:norm / qkv → attention → o_proj / MoE

管线仍然是 breakable 自己的 capture_begin / capture_end,不是经典那种用 torch.cuda.graph() 把整个 forward 包一次。FULL_DECODE_ONLY 的 prefill 两边都不录。服务若整体设成 PIECEWISE,decode 在 breakable 里也会每层 attention 都剪一刀。

2.3 为什么kimi k3的piecewise必须用breakable的后端

3 cudagraph的步长和档位

CUDA Graph 要求一次录进去的 kernel 形状固定。vLLM 不会为每一种 token 数各录一张图,而是先定一组档位,启动时按这些档位各录一张。运行时把这一步的 token 数向上补到已经录过的、不小于它的那一档,再重放对应的图。

档位按 token 数计,不是按请求数。普通解码每个请求 1 个 token,两者一样。开了 MTP 之后每个请求带 1 + MTP步数 个 token,档位跟着变大。

不手写列表时,档位按固定步长铺到上限:

  • 先放 1、2、4
  • 从 8 到 256,步长 8
  • 从 256 到上限,步长 16

上限由 --max-cudagraph-capture-size 决定。不传时是 min(max_num_seqs × 每个请求的解码 token 数 × 2, 512),再和 max_num_batched_tokens 取较小值。命令行里没有单独的步长参数,8 和 16 写在生成逻辑里。想改步长,用 --cudagraph-capture-sizes 把每一档都列出来,自动生成就不再用。

max_num_seqs=128、没开 MTP 时,上限是 256,整份列表 35 档。PIECEWISE 用这 35 档。FULL 解码只留不超过 请求数上限 × 每请求 token 数 的那些,也就是不超过 128,剩下 19 档:1, 2, 4, 8, 16, ..., 128。日志里的 FULL: 2/19 是从大到小的第二张,对应 120 个 token。

运行时只补到下一档。12 个 token、下一档是 16,就补到 16,不会一直补到最大那档。超过最大档位则这一步不用 CUDA Graph。

多 DP 时还有一次对齐。每个 DP 先按自己的 token 数升到档位上,再在 DP 之间取最大值,所有 DP 都补到这个最大值,这样各卡进同一张图,token 数也一致。

相关推荐
Light Gao2 天前
从 LoRA 到 vLLM:大模型训练与企业部署名词通识(图解版)
vllm
数据与后端架构提升之路2 天前
四个模型,四条路:一份来自生产环境的 LLM 部署横评与选型框架
gpu算力·vllm·h100
缘友一世3 天前
GLM5.3 flash cybersec本地部署[5]:生产运维与故障恢复
ubuntu·vllm·glm5.3 flash
缘友一世4 天前
GLM5.3 flash cybersec本地部署[4]:基准测试与复现
vllm·glm5.3 flash
rjc_lihui5 天前
NCCL,Tokio ,vLLM ,Vue 3在线书籍
vllm
GPUStack5 天前
一张 A800 80GB,跑通 Qwen-Image-2.1:GPUStack 部署、生成与图像编辑实战
人工智能·开源·github·vllm·大模型部署·gpustack
缘友一世5 天前
GLM5.3 flash cybersec本地部署[0]:环境准备与首次启动
vllm·glm5.3 flash
DongQiShanRen7 天前
裁决台账双向互校(中):五向一致性链——②向解读与③向实现
人工智能·深度学习·自然语言处理·集成学习·vllm
智码看视界7 天前
开源大模型每日追踪:小米 MiMo-V2.6-Pro,登顶开放权重第一(超过 GLM-5.3 、Kimi K3)
agent·vllm·moe·全模态·小米大模型·mimo-v2.6
零基础1238 天前
DeepSeek V4.1 Flash (Batch) 的性能测试与应用
经验分享·python·语言模型·vllm