Lesson 05 · 目标用时 25 分钟 · 固定源码版本

vLLM 源码里的 MTP 一轮调用链

把上一课的抽象流程贴到最新 vLLM:上一轮 draft 如何进入 scheduler,本轮 target 如何验证,以及本轮结束后下一轮 draft 从哪里来。

源码版本先钉死

本课所有路径和行号都对应 vLLM commit 3de4b2bf3c477513afff4a58680eb00d557bb53a(2026-07-22)。源码会继续变化,所以先看 commit,再看类名。

先预测

在 vLLM 的一次 iteration 中,哪句话更准确?

先画跨 iteration 的边界

固定一个请求:真实前缀是 我 喜欢。假设上一轮已经留下 draft [吃, 苹果, 。]。本课只追一个事实:这些 token 如何穿过 scheduler、GPU runner 和 EngineCore。

Iteration N 开始request.spec_token_ids 保存上一轮 draft。
本轮验证scheduler 调度 draft,target forward 计算验证 logits。
采样决定RejectionSampler 产出 accepted、correction 或 bonus。
Iteration N+1speculator proposal 写回新的 spec_token_ids

逐步跟源码走一遍

点击下一步时,只问“现在手里传的对象是什么”。每一步的源码链接都固定在本课版本。

步骤 1:配置决定“用哪种 proposer”

SpeculativeConfig.__post_init__ 看到 method="mtp" 且没有单独 draft model 时,会把 target 的模型名复制给 self.model,并对齐 quantization。这说明 MTP draft 结构可以来自同一个 target checkpoint。

vllm/config/speculative.py#L666-L705

源码地图压缩成一条线

request.spec_token_ids
  → scheduler.scheduled_spec_decode_tokens
  → V2 InputBatch / logits_indices
  → target forward + RejectionSampler
  → num_sampled, num_rejected
  → speculator.propose(...)
  → DraftTokensHandler
  → EngineCore.post_step
  → request.spec_token_ids(下一轮)

这里最容易混淆的是时间:request.spec_token_ids 不是“当前 target 刚生成的 token”,而是上一轮 proposal 留给下一轮验证的候选。验证和 proposal 在逻辑上相邻,但跨过了 iteration 边界。

收尾检查

在这条调用链中,num_rejected 最直接传给谁?

主阅读

打开 V2 sample_tokens,只做一个动作:沿着 num_rejected 向前追,直到 propose。然后再回到 本课速查页。下一课我们再拆 Triton rejection kernel;本课先把跨 iteration 的地图记牢。哪里看不懂,继续问我。