Prhub

#51256 [BugFix] Reserve the bonus query slot in DFlash scheduling budget

原始 PR 作者 HF-001 合并时间 2026-08-13 11:50 文件变更 2 提交数 7 评论 21 代码增减 +59 / -10

执行摘要

修复 DFlash 调度预算少算 bonus query slot,并补全参数化测试

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。

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

讨论亮点

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。

实现拆解

  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 投机解码 modified 6.7
tests/test_config.py 配置模块 modified 5.35

关键符号

max_num_new_slots_for_drafting test_max_num_new_slots_for_drafting

关键源码片段

vllm/config/speculative.py core-logic

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

    @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-coverage

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

@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

评论区精华

use_dflash 并入增量条件 设计

dreamer-89 建议将 use_dflash() 并入 uses_draft_model() 的增量条件,保持一处调整:if self.uses_draft_model() or self.use_dflash(): slots_per_req += 1

结论:作者采纳并修改;后续 zixi-qi 的重构建议进一步将其拆分为单独分支。 · 已解决

参数化测试覆盖 K - 1 与 K 测试

dreamer-89 建议将测试参数化,同时覆盖普通并行投机(K - 1)与 DFlash(K),确保修复不改变其它投机路径的 slot 记账。

结论:作者补充 p-eagle 与 dflash 用例,最终扩展为 8 组参数化覆盖。 · 已解决

max_num_new_slots_for_drafting 逻辑重构 设计

zixi-qi 认为原逻辑 convoluted,建议每个 case 分开处理并在 docstring 中给出各算法预期输出示例,并提供参考实现(use_dflash 返回 K、parallel_drafting 内再分 PARD 与普通并行)。

结论:作者按参考实现重构,最终形态为逐算法分支 + 表格注释。 · 已解决

eagle3 默认 parallel_drafting 值 测试

zixi-qi 指出默认 eagle3 的 parallel_drafting=False,只有 p-eagle 设为 True 时才返回 7,要求测试覆盖所有情况,并指出 dflash 8、p-eagle 7 等期望值。

结论:作者新增完整 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 计算基准,参数化测试可长期复用。

调度预算计算变更 影响全部投机解码路径 核心配置属性

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论