Prhub

#29464 Fix EAGLE draft hidden dim extraction and centralize spec helpers

原始 PR 作者 merrymercy 合并时间 2026-06-28 12:48 文件变更 10 提交数 3 评论 5 代码增减 +190 / -173

执行摘要

修复 EAGLE draft hidden dim 提取并集中规格函数

PR body指出:部分架构中读取fc.in_features来获取draft hidden dim是不正确的,需要改用config驱动的方式。同时原有hidden_size_for/dtype_for类方法散落在多个类中,导致重复和容易出错。集中化后统一从model_runner的config中派生,避免手动维护。

该PR值得精读,因为它展示了如何通过集中化辅助函数消除重复逻辑并修复隐蔽的维度错误。设计决策如使用config驱动而非模型层属性是更稳健的做法。此外,prefill CUDA graph runner中input_embeds slot的注册注释清晰地解释了multimodal路径的需求。建议团队后续补充相关测试。

讨论亮点
  1. 删除注释:merrymercy在review中指出prefill_cuda_graph_runner.py中新加的注释过于冗余,要求删除(commit中已执行)。
  2. 硬编码整数:在prefill_cuda_graph_runner.pyreplay_layer_forward使用了硬编码整数1作为参数索引,merrymercy要求改为inspect.signature动态获取参数位置(commit中已修复)。
  3. follow-up清理:PR body末尾提到init_disaggregationmodel_config和fallback block已过时,建议简化忽略get_draft_kv_pool的第二个返回值(未在当前PR中处理,留待后续)。

实现拆解

  1. 新增集中式辅助函数:在python/sglang/srt/speculative/eagle_utils.py中新增get_draft_input_from_target_hidden_dimget_draft_recurrent_hidden_state_spec。前者根据config(EAGLE3 aux模式等)计算目标hidden states宽度,后者返回draft循环时需要的hidden states尺寸和dtype。两者均从draft的model_runner读取config,成为单一事实来源。

  2. 删除散落的类方法:在python/sglang/srt/speculative/eagle_info.py中删除EagleDraftInput.hidden_size_fordtype_for以及EagleDraftExtendInput.hidden_size_fordtype_for,同时删除辅助函数_draft_runner_of。这些功能全部由新函数替代。

  3. 更新调用方:修改eagle_worker_v2.pyeagle_draft_cuda_graph_runner.pyeagle_draft_extend_cuda_graph_runner.pymulti_layer_eagle_worker_v2.pymulti_layer_eagle_draft_extend_cuda_graph_runner.py,将原有对hidden_size_for/dtype_for的调用替换为get_draft_recurrent_hidden_state_specget_draft_input_from_target_hidden_dim

  4. 修复prefill CUDA graph runner:在prefill_cuda_graph_runner.py中,为breakable backend注册multimodal input_embeds slot,使得captured graph在multimodal batch replay时能正确填充vision embeddings。同时将draft hidden dim的计算从尝试读取fc.in_features改为统一使用get_draft_input_from_target_hidden_dim

  5. 清理scheduler中的分散逻辑:在python/sglang/srt/managers/scheduler.pyinit_disaggregation中,将原来内联的hidden size/dtype分支提取为使用get_draft_recurrent_hidden_state_spec,消除了重复的三元表达式。

  6. 附加风格调整:使用PEP 617的parenthesized context manager语法(with (... , ...):)改写多处context manager。

文件 模块 状态 重要度
python/sglang/srt/speculative/eagle_utils.py 推测解码 modified 7.93
python/sglang/srt/speculative/eagle_info.py 推测解码 modified 7.65
python/sglang/srt/model_executor/runner/prefill_cuda_graph_runner.py 执行器 modified 7.39
python/sglang/srt/managers/scheduler.py 调度器 modified 6.71
python/sglang/srt/speculative/eagle_worker_v2.py 推测解码 modified 6.35
python/sglang/srt/speculative/eagle_draft_extend_cuda_graph_runner.py 推测解码 modified 6.16
python/sglang/srt/speculative/multi_layer_eagle_worker_v2.py 推测解码 modified 6.04
python/sglang/srt/speculative/eagle_draft_cuda_graph_runner.py 推测解码 modified 5.4
python/sglang/srt/speculative/multi_layer_eagle_draft_extend_cuda_graph_runner.py 推测解码 modified 5.35
python/sglang/srt/model_executor/runner/decode_cuda_graph_runner.py 执行器 modified 5.28

关键符号

get_draft_input_from_target_hidden_dim get_draft_recurrent_hidden_state_spec hidden_size_for dtype_for

关键源码片段

python/sglang/srt/speculative/eagle_utils.py core-logic

集中化的 hidden states 尺寸 / 类型解析函数所在文件,新增了两个核心函数,替换了原有分散的类方法。

def get_draft_input_from_target_hidden_dim(model_runner: ModelRunner) -> int:
    """Width of the target hidden states fed into the draft model.    This is the single source of truth and is derived entirely from config:
    for EAGLE3 aux mode the draft consumes `num_aux` concatenated target
    layers (each `target_hidden_size` wide); every other arch consumes the
    per-layer `spec_hidden_size`.    Do NOT read this off a draft projection's `in_features` (e.g. an `fc`
    layer): that width is arch-specific.    Note: read entirely from the *draft* `model_runner`'s config. The non-aux
    branch assumes the draft's `spec_hidden_size` equals the target hidden
    width fed to the draft (true for standard EAGLE, where the draft mirrors
    the target hidden size); aux mode reads the explicit `target_hidden_size`.
    """
    model_config = model_runner.model_config
    hf_config = model_config.hf_config
    eagle_config = getattr(hf_config, "eagle_config", None) or {}
    get_eagle_config = (
        eagle_config.get
        if isinstance(eagle_config, dict)
        else lambda key, default=None: getattr(eagle_config, key, default)
    )
    use_aux = get_eagle_config("use_aux_hidden_state", True)
    spec_algorithm = model_runner.spec_algorithm
​
    # 若非 EAGLE3 aux 模式,直接返回 spec_hidden_size
    if not (spec_algorithm is not None and spec_algorithm.is_eagle3() and use_aux):
        return model_config.spec_hidden_size
​
    # EAGLE3 aux: width = target_hidden_size * num_aux
    target_hidden = getattr(hf_config, "target_hidden_size", None)
    if target_hidden is None:
        target_hidden = model_config.hidden_size
    num_aux = getattr(hf_config, "num_aux_hidden_states", None)
    if num_aux is None:
        layer_ids = get_eagle_config("eagle_aux_hidden_state_layer_ids", None)
        if layer_ids is None:
            layer_ids = getattr(hf_config, "eagle_aux_hidden_state_layer_ids", None)
        num_aux = len(layer_ids) if layer_ids else 3
    return target_hidden * num_aux
​
​
def get_draft_recurrent_hidden_state_spec(
    model_runner: ModelRunner,
) -> tuple[Optional[int], Optional[torch.dtype]]:
    """Return hidden_states width/dtype carried between draft decode steps."""
    if model_runner.spec_algorithm.is_standalone():
        return None, None
    return model_runner.model_config.spec_hidden_size, model_runner.model_config.dtype
python/sglang/srt/model_executor/runner/prefill_cuda_graph_runner.py data-contract

修复 multimodal batch 的 input_embeds slot 注册,并统一使用新函数获取 draft hidden dim,是 multimodal 路径正确性的关键。

# 在 build_prefill_registry 调用中添加 source=self.buffers,
# 使得 multimodal 的 input_embeds slot 被注册到 buffer_registry 中
self.buffer_registry: CudaGraphBufferRegistry = build_prefill_registry(
    device=self.device,
    max_bs=self.max_bs,
    max_num_token=self.max_num_tokens,
    cache_loc_dtype=self._cache_loc_dtype(),
    is_multimodal=self.is_multimodal,
    hidden_size=self.model_runner.model_config.hidden_size,
    embed_dtype=self.model_runner.dtype,
    enable_mamba_track=self.mamba_track_enabled,
    # 注册 multimodal input_embeds slot(默认 True)。
    # 仅在 is_multimodal 时添加,纯文本模型不受影响。
    # tc_piecewise 和 breakable 后端都需要此 slot,
    # 否则 captured graph 会重新对 input_ids 做 embedding 而丢掉 vision embeddings。
    source=self.buffers,
)# 创建 static_draft_hidden_states 时,统一使用集中式函数
if (
    isinstance(self.backend, BreakableCudaGraphBackend)
    and model_runner.is_draft_worker
    and model_runner.spec_algorithm.is_eagle()
):
    hidden_dim = get_draft_input_from_target_hidden_dim(model_runner)
    with torch.device(self.device):
        self.static_draft_hidden_states = torch.zeros(
            (self.max_num_tokens, hidden_dim),
            dtype=self.model_runner.dtype,
        )

评论区精华

删除冗余注释 style

merrymercy 在 review 评论中指出 prefill_cuda_graph_runner.py 中新增的注释过于冗余,要求删除。

结论:相关注释已删除(在 commit 中体现)。 · 已解决

硬编码整数参数索引 设计

merrymercy 指出在 `replay_layer_forward` 中使用了硬编码整数 `1` 作为参数位置,应改用 `inspect.signature` 动态获取。

结论:已修改为使用 `inspect.signature` 获取实际参数位置。 · 已解决

scheduler 中废弃代码的后续清理 other

PR body 末尾 merrymercy 留言建议在 `init_disaggregation` 中简化 `get_draft_kv_pool` 的返回值处理,忽略第二个返回值并删除 fallback block。

结论:未在当前 PR 中处理,标记为 non-blocking cleanup,留待将来。 · 待处理

风险与影响

  1. 回归风险:涉及多个调用点替换,如果某个调用点未更新或新函数返回值与预期不符,可能导致draft hidden states尺寸错误,影响推测解码的正确性。
  2. 配置兼容性:新函数假设model_runner.model_config包含正确的spec_hidden_size等字段,若某些自定义模型配置缺少这些字段,可能引发异常。
  3. multimodal路径:prefill CUDA graph runner的input_embeds slot变更可能影响multimodal batch的图捕获和回放,若未正确配置可能导致推理错误。
  4. 缺少测试覆盖:本次变更未包含新增测试,集中化的逻辑和multimodal修复缺乏回归测试覆盖。

影响范围:所有使用EAGLE推测解码(包括EAGLE、EAGLE3、DFLASH等)的模型,特别是涉及hidden states传递的draft worker。同时影响multimodal模型的prefill CUDA graph路径。影响程度中等,因为修复了一个关键的正确性问题并清理了可维护性债务。无用户可见的功能变化(属内部重构+修复)。

核心路径变更 缺少测试覆盖 配置兼容性 multimodal 路径

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论