课程 10 · 执行层

一张图的三种录法

nano-vllm 的 capture_cudagraph 只能录 decode。vLLM 把「录图」做成了三种模式,还能一边编译一边录——这一课讲清 CUDAGraphMode 到底在权衡什么。

约 55 分钟 源码:vllm/v1/executor/ · vllm/v1/worker/ · vllm/config/compilation.py 前置:06、08

2026-11-08 源码校正

本页已按本地固定commit修正相关描述。三框架对照 · 带行号的离线证据 · 核查与勘误详情。

1Executor:谁来跑这一批

Executor部署形态典型场景
UniProcExecutor单进程单卡本地调试、小模型
MultiprocExecutor本机多进程单机 TP / PP
RayExecutorV2跨机、Ray 集群多机、弹性伸缩

三者对上层暴露同一个接口:execute_model(scheduler_output) 和 sample_tokens()。调度器完全不知道底下有多少张卡——这是 vLLM 能把「单卡」和「千卡」用同一份调度代码跑起来的原因。

2Worker 里发生了什么

execute_model(scheduler_output)
  ├─ 把 num_scheduled_tokens 展开成每个 token 的位置
  ├─ 更新 block table / slot mapping(和 nano-vllm 一模一样)
  ├─ 选 attention backend,跑前向
  └─ 返回 hidden states 或 logits

它和 nano-vllm 的 ModelRunner.run() 是同一件事,只是规模不同:

nano-vllmvLLM Worker
模型Qwen3 一个文件数百个架构的注册表 + 权重加载器
注意力写死 FlashAttention可插拔 backend(见下节)
图一种录法、只录 decode三种模式、可按 decode / mixed 分别配置
并行TPTP / PP / DP / EP
KV一个大张量多 group、可跨节点传输

Attention backend 为什么要插件化

vllm/v1/attention/backends/ 下有 flash_attn、flashinfer、triton、flex_attention、composite、cpu_attn 等十余个实现。原因:

后端在启动时根据「模型 + 硬件 + 配置」自动选取,接口统一。你在 nano-vllm 里看到的 flash_attn_varlen_func / flash_attn_with_kvcache 两种调用,就是 FlashAttention backend 内部的两种路径。

3CUDA Graph 的三种模式

vllm/config/compilation.py · L130–140class CUDAGraphMode(enum.Enum):
    NONE = 0
    PIECEWISE = 1
    FULL = 2
    FULL_DECODE_ONLY = (FULL, NONE)
    FULL_AND_PIECEWISE = (FULL, PIECEWISE)

最后两个是「组合模式」,元组的第一位用于 decode 批次,第二位用于 mixed 批次(即含 prefill 的批次)[compilation.py L142–152]。

模式怎么录适用
NONE不录,纯 eager调试、不支持的算子
PIECEWISE把模型切成若干「静态片段」,每段各录一张图;attention 等动态算子留在图外prefill / mixed 批次
FULL完整model forward作为图单位backend支持的decode或prefill/mixed桶
FULL_DECODE_ONLYdecode 用 FULL,mixed 用 NONE保守配置
FULL_AND_PIECEWISEdecode 用 FULL,mixed 用 PIECEWISE一种混合策略,收益需测量

为什么 prefill 也能录图(nano-vllm 做不到)

nano-vllm 的 prefill 每轮 shape 都变(token 总数不定),所以只能 eager。vLLM 的办法是 分段:

[embedding] [norm] [qkv] [attention ✗ 动态] [o_proj] [mlp] [norm]
   └────── 录成图 ──────┘        └─── eager ───┘  └── 录成图 ──┘

把 attention 切成图与图之间的「洞」,其余静态部分照录。这样即使总 token 数不同,每个片段的 shape 也可以是固定的(按 token 数分桶)。这就是 piecewise 的核心思想。

一句话区分

FULL捕获完整model forward;backend支持时也可以处理non-uniform prefill/mixed。PIECEWISE切分模型,动态部分可留在图外。二者都需遵守捕获形状和地址合约,通常结合分桶与padding。

4核对模型、logits与采样的真实边界

nano-vllm capture只包含self.model,compute_logits在图外。此vLLM提交的常见GPUModelRunner路径同样在_model_forward返回后选取sample_hidden_states,再compute_logits。因此不能依据FULL名字断言logits一定入图;见实际forward边界。

nano-vllm将输入值拷入预分配graph缓冲;vLLM还通过attention metadata、backend能力与模式调度协调更多模型/设备路径。应逐个核对缓冲字段,而非依据张量个数推断性能。

5并行的四种字母

缩写切什么通信vLLM 里的位置
TP每层权重每层 all-reducedistributed/parallel_state.py
PP层段边界传激活pipeline parallel 调度
DP整个模型副本无(除非 MoE)DPEngineCoreProc
EPMoE 专家all-to-allMoE 层 + DeepEP 类通信库

注意 vLLM 的 DP 是在 EngineCore 进程级做的:每个 DP rank 有自己的调度器和 worker 组,前端把请求分发过去。nano-vllm 完全没有这一层——它只有 TP。

6练习 A · 给场景选模式

下面三个场景,你会选哪种 CUDAGraphMode?

  1. 线上服务的 decode 占 90% 时间,prefill 偶尔来一条长 prompt。
  2. 你在调试一个自定义 attention 算子,需要能断点、打印中间张量。
  3. 离线批量打分(只用 prefill,不做生成)。
展开:参考答案

① FULL_AND_PIECEWISE可作为候选配置,随后在固定负载上与其他支持模式对照;不能预先保证收益最大。

② NONE:图内无法断点,调试必须退回 eager。

③ 先查backend的FULL能力和本轮shape桶:支持时prefill也可FULL,否则评估PIECEWISE或NONE。用固定负载测量后选择。

7练习 B · 闭卷测验

8面试复述卡

「vLLM 的 CUDA Graph 怎么做的?」

展开:30 秒版本

「runtime有NONE、PIECEWISE、FULL;策略按batch形态分派模式。FULL不是仅decode,是否覆盖prefill/mixed看backend。按真实调用边界区分model forward、logits和采样,再检查捕获桶与padding。」详见29课。

可以问:「分段图的边界怎么定?」——和 torch.compile 的 graph break 是同一个问题,值得单独一节课,如果你要深入编译方向我可以加。