# PR #51256 完整报告

- 仓库：`vllm-project/vllm`
- 标题：[BugFix] Reserve the bonus query slot in DFlash scheduling budget
- 合并时间：2026-08-13 11:50
- 原文链接：http://prhub.com.cn/vllm-project/vllm/pull/51256

---

# 执行摘要

- 一句话：修复 DFlash 调度预算少算 bonus query slot，并补全参数化测试
- 推荐动作：值得精读：改动虽小，但命中调度预算与投机解码核心配置路径，最终实现把『按算法逐 case 分支 + docstring 对照表』的模式做到清晰可维护，适合作为后续新增投机算法时 slot 计算的参考基准。应重点关注 zixi-qi 的重构建议与参数化测试设计，它们把单点修复提升为可维护、可验证的配置语义。

# 功能与动机

PR body 说明：通用 parallel-drafting 计算只给 DFlash 预留 K - 1 个 slot，导致调度预算每个请求少一个 slot。以 max_num_batched_tokens = 2048、max_num_seqs = 256、num_speculative_tokens = 8 为例，旧计算允许 max_num_scheduled_tokens = 2048 - 7 × 256 = 256，但满 DFlash batch 需要 256 × (8 + 1) = 2304 个 query token，超过 max_num_batched_tokens。DFlash 每请求有 K + 1 个 query tokens，净扩张为 K 个 slot，而非通用 parallel-drafting 的 K - 1。

# 实现拆解

1. 根因定位：在 vllm/config/speculative.py 的 max_num_new_slots_for_drafting 属性中，旧实现把 parallel_drafting 一律按 K - 1 计算，再对 draft_model 加 1，没有区分 DFlash 的 bonus query 结构。
2. 属性重写与文档化：按算法显式分支——use_dflash() 返回 K；parallel_drafting 且 uses_draft_model()（PARD）返回 K；普通并行（P-EAGLE、DSpark）返回 K - 1；串行 draft model 返回 1；其余（EAGLE3、MTP、N-gram）返回 0。docstring 增加 8 行算法对照表，明确每种算法与 parallel_drafting 组合的预期 slot 数。
3. 测试配套：tests/test_config.py 新增参数化测试 test_max_num_new_slots_for_drafting，以 num_speculative_tokens = 8 构造 8 组 (method, parallel_drafting, expected_slots) 用例，覆盖全部算法组合，防止 DFlash 修复影响其它投机路径。
4. 演进过程：首版仅在旧逻辑上把 use_dflash() 并入 uses_draft_model() 的增量条件；经 review 建议后重构为逐算法分支形式，期间两次 merge 同步 main 分支，最终由 jeejeelee 与 dreamer-89 approve 合入。

关键文件：
- `vllm/config/speculative.py`（模块 投机解码；类别 source；类型 core-logic；符号 max_num_new_slots_for_drafting）: 核心修复文件：重写 max_num_new_slots_for_drafting 属性，DFlash 从 K - 1 改为返回 K，并按算法分支重构，docstring 附 8 行算法对照表。
- `tests/test_config.py`（模块 配置模块；类别 test；类型 test-coverage；符号 test_max_num_new_slots_for_drafting）: 新增参数化测试 test_max_num_new_slots_for_drafting，覆盖 8 种算法与并行组合，锁定 slot 计算行为，防止修复 DFlash 时回归其它投机路径。

关键符号：max_num_new_slots_for_drafting, test_max_num_new_slots_for_drafting

## 关键源码片段

### `vllm/config/speculative.py`

核心修复文件：重写 max_num_new_slots_for_drafting 属性，DFlash 从 K - 1 改为返回 K，并按算法分支重构，docstring 附 8 行算法对照表。

```python
    @property
    def max_num_new_slots_for_drafting(self) -> int:
        """返回每个请求在投机解码时额外需要预留的调度 slot 数。

        调度器预算已经为每个 decode 请求预留 1 个 query slot。
        设 K 为 num_speculative_tokens，各算法与额外 slot 数的对应关系如下：

        ================== ============ ================= =================
        Algorithm          Method       parallel_drafting Additional slots
        ================== ============ ================= =================
        EAGLE3             eagle3       No                0
        P-EAGLE            eagle3       Yes               K - 1
        DFlash             dflash       Yes               K
        DSpark             dspark       Yes               K - 1
        MTP                mtp          No                0
        N-gram             ngram        No                0
        Draft model        draft_model  No                1
        PARD               draft_model  Yes               K
        ================== ============ ================= =================
        """
        num_draft_tokens = self.num_speculative_tokens

        if self.use_dflash():
            # DFlash 每请求包含 1 个 bonus query 与 K 个 masked query，
            # 调度器只预留 1 个 query slot，因此还需额外预留 K 个 slot。
            return num_draft_tokens

        if self.parallel_drafting:
            if self.uses_draft_model():
                # PARD 不截断已有输入，K 个 query 位置都需要新 slot。
                return num_draft_tokens

            # 普通并行投机（P-EAGLE、DSpark）复用已有 query，
            # 只有 K - 1 个 masked query 需要新 slot。
            return num_draft_tokens - 1

        if self.uses_draft_model():
            # 串行 draft model 的输入保留 1 个未截断 token，
            # 因此每请求需要 1 个额外 slot。
            return 1

        # EAGLE3、MTP、N-gram 等串行方法复用现有 query，无需额外 slot。
        return 0

```

### `tests/test_config.py`

新增参数化测试 test_max_num_new_slots_for_drafting，覆盖 8 种算法与并行组合，锁定 slot 计算行为，防止修复 DFlash 时回归其它投机路径。

```python
@pytest.mark.parametrize(
    ("method", "parallel_drafting", "expected_slots"),
    [
        # K = 8，覆盖全部 8 种算法与并行组合：
        # 串行方法（eagle3、mtp、ngram）不需要额外 slot；
        # 普通并行（p-eagle、dspark）复用已有 query，只需 K - 1 = 7 个；
        # DFlash 与 PARD 每请求需要 K = 8 个额外 slot；
        # 串行 draft model 保留 1 个未截断 token，需要 1 个。
        pytest.param("eagle3", False, 0, id="eagle3"),
        pytest.param("eagle3", True, 7, id="p-eagle"),
        pytest.param("dflash", True, 8, id="dflash"),
        pytest.param("dspark", True, 7, id="dspark"),
        pytest.param("mtp", False, 0, id="mtp"),
        pytest.param("ngram", False, 0, id="ngram"),
        pytest.param("draft_model", False, 1, id="draft-model"),
        pytest.param("draft_model", True, 8, id="pard"),
    ],
)
def test_max_num_new_slots_for_drafting(
    method: str, parallel_drafting: bool, expected_slots: int
):
    # 用 ngram 作为占位模型构造 SpeculativeConfig，
    # 再直接覆写 method 与 parallel_drafting 字段以覆盖全部组合。
    speculative_config = SpeculativeConfig(
        model="ngram",
        num_speculative_tokens=8,
    )
    speculative_config.method = method
    speculative_config.parallel_drafting = parallel_drafting

    assert speculative_config.max_num_new_slots_for_drafting == expected_slots

```

# 评论区精华

dreamer-89 在 vllm/config/speculative.py 上建议将 use_dflash() 并入现有增量条件，保持一处调整；作者采纳后，zixi-qi 进一步指出原逻辑 convoluted，建议每个 case 分开处理并在 docstring 中给出各算法的预期输出示例，该参考实现成为最终方案。zixi-qi 还强调默认 eagle3 的 parallel_drafting=False，只有 p-eagle 会返回 7，建议测试覆盖所有情况，最终促成 8 组参数化用例。全部 review 意见均已解决，两个 reviewer 最终 APPROVED。

- use_dflash 并入增量条件 (design): 作者采纳并修改；后续 zixi-qi 的重构建议进一步将其拆分为单独分支。
- 参数化测试覆盖 K - 1 与 K (testing): 作者补充 p-eagle 与 dflash 用例，最终扩展为 8 组参数化覆盖。
- max_num_new_slots_for_drafting 逻辑重构 (design): 作者按参考实现重构，最终形态为逐算法分支 + 表格注释。
- eagle3 默认 parallel_drafting 值 (testing): 作者新增完整 8 组参数化用例覆盖所有算法组合。

# 风险与影响

- 风险：调度器联动风险：max_num_new_slots_for_drafting 影响调度预算与 max_num_scheduled_tokens 计算，DFlash 预算由 K - 1 提升为 K 后单步 batch token 数会相应增加，属于修正性变化，需关注与 async scheduling、chunked prefill 的交互，PR 已通过 CI。回归风险：重构覆盖所有投机方法路径，EAGLE3、P-EAGLE、DSpark、MTP、N-gram、串行 draft model、PARD 的返回值与旧逻辑等值，且 8 组参数化测试锁定行为。兼容性风险：无 API、协议或模型权重变更，纯内部预算计算修正。文档风险：docstring 中的算法对照表若未来新增投机算法未同步更新会产生误导。
- 影响：用户侧：DFlash 用户调度预算修正，避免 batch token 数超出 max_num_batched_tokens 导致的排队或性能回退；其它投机算法行为不变。系统侧：调度器预算计算更精确，投机解码路径的 slot 语义被文档化。团队侧：确立按算法分支 + 对照表的配置风格，为后续新增投机算法提供可扩展的 slot 计算基准，参数化测试可长期复用。
- 风险标记：调度预算计算变更 , 影响全部投机解码路径 , 核心配置属性

# 关联脉络

- PR #47808 [Spec Decode] DSpark confidence-scheduled verification: 同属 v1 投机解码体系，DFlash 与 DSpark 在 use_eagle() 判断中共用路径，且二者都依赖 max_num_new_slots_for_drafting 决定调度预算；本 PR 确立了 DFlash 返回 K、DSpark 保持 K - 1 的差异化语义。