Prhub

#43241 [Model Runner V2][Spec Decode] Add Gemma4 MTP support

原始 PR 作者 TheEpicDolphin 合并时间 2026-06-04 08:51 文件变更 14 提交数 1 评论 27 代码增减 +1243 / -942

执行摘要

重构推测解码器架构,支持 Gemma4 MTP

PR body 明确指出:Gemma4 MTP 目前 MRV2 不支持,但已通过子类方式在 MRV1 中添加(#41745)。为在 MRV2 中支持 Gemma4 MTP,需增加常量位置、KV 共享等特性,同时重构架构以支持更多 draft 模式。

建议合并后要求补充 Gemma4 MTP 的 E2E 测试;值得精读 AutoRegressiveSpeculator 的 hooks 设计和 Gemma4Speculator 的 KV 共享实现。

讨论亮点
  • gemini-code-assist[bot] 报告 init_attn_backend 返回值解包错误(4 值解包为 3 变量),TheEpicDolphin 确认已修复。
  • benchislett 质疑当前设计是否偏离之前讨论的提案(参数化 propose),TheEpicDolphin 解释 hooks 方式更灵活。
  • benchislett 询问测试覆盖,TheEpicDolphin 承诺后续 PR 添加测试。
  • depthfirst-app[bot] 警告 autoregressive/speculator.py 中存在未解决的合并冲突标记,最终已解决。
  • chaunceyjiang 引用 #46786 建议参考。

实现拆解

  1. 在 speculator.py 中引入 BaseSpeculator 抽象基类和 DraftModelSpeculator 通用基类,承载缓冲区、DP 配置、模型加载、attention 元数据构建等通用逻辑,并声明 load_draft_model 扩展点。
  2. 新建 autoregressive/speculator.py,实现 AutoRegressiveSpeculator,封装自回归 draft 循环(CUDA graph 捕获、propose、prefill+decode),提供可覆写的 hooks:advance_draft_positions、model_returns_tuple、load_draft_model、sample_draft。
  3. 将原 EagleSpeculator 精简为 AutoRegressiveSpeculator 的子类,仅覆写 load_draft_model 调用 load_eagle_model。
  4. 新增 MTPSpeculator(用于 DeepSeek 等标准 MTP),覆写 model_returns_tuple 返回 False,load_draft_model 同 Eagle。
  5. 新增 Gemma4Speculator,覆写 advance_draft_positions 返回 False(常量位置)、model_returns_tuple 返回 True,在 load_draft_model 中创建 KV 共享、保留 TRITON_ATTN 后端、共享 embedding。
  6. 配套变更:将 eagle/cudagraph.py 重命名为 autoregressive/cudagraph_utils.py,类名改为 PrefillSpeculatorCudaGraphManager / DecodeSpeculatorCudaGraphManager;调整 attention 后端以支持 KV 共享;更新 init.py 和 model_runner.py 中的导入和工厂函数。
文件 模块 状态 重要度
vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py 推测解码 added 9.25
vllm/v1/worker/gpu/spec_decode/speculator.py 推测解码 added 9.2
vllm/v1/worker/gpu/spec_decode/gemma4/speculator.py 推测解码 added 9.04
vllm/v1/worker/gpu/spec_decode/eagle/speculator.py 推测解码 modified 8.93
vllm/v1/worker/gpu/spec_decode/mtp/speculator.py 推测解码 added 7.86
vllm/v1/worker/gpu/spec_decode/autoregressive/cudagraph_utils.py 推测解码 renamed 7.53
vllm/v1/worker/gpu/spec_decode/__init__.py 推测解码 modified 6.43

关键符号

BaseSpeculator.init_cudagraph_manager BaseSpeculator.capture BaseSpeculator.propose DraftModelSpeculator.__init__ DraftModelSpeculator.load_draft_model DraftModelSpeculator.load_model AutoRegressiveSpeculator.__init__ AutoRegressiveSpeculator.advance_draft_positions AutoRegressiveSpeculator.model_returns_tuple AutoRegressiveSpeculator.init_cudagraph_manager AutoRegressiveSpeculator.capture AutoRegressiveSpeculator.propose AutoRegressiveSpeculator.sample_draft Gemma4Speculator.advance_draft_positions Gemma4Speculator.model_returns_tuple Gemma4Speculator.load_draft_model Gemma4Speculator._create_draft_vllm_config Gemma4Speculator._setup_gemma4_kv_sharing Gemma4Speculator._share_embeddings EagleSpeculator.load_draft_model MTPSpeculator.model_returns_tuple MTPSpeculator.load_draft_model PrefillSpeculatorCudaGraphManager.capture DecodeSpeculatorCudaGraphManager.capture

关键源码片段

vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py core-logic

核心新增文件,定义了 AutoRegressiveSpeculator 类,实现自回归 draft 循环和可覆写的 hooks,是重构的基石。

class AutoRegressiveSpeculator(DraftModelSpeculator):
    def __init__(self, vllm_config: VllmConfig, device: torch.device):
        super().__init__(vllm_config, device)
        self.hidden_states = torch.zeros(
            self.max_num_tokens, self.hidden_size, dtype=self.dtype, device=device
        )
        self.current_draft_step = torch.tensor(0, dtype=torch.int64, device=device)
        self.last_token_indices = torch.zeros(
            self.max_num_reqs, dtype=torch.int64, device=device
        )
        # 如果 draft 模型支持 multimodality ,分配对应缓冲区
        self.supports_mm_inputs = MULTIMODAL_REGISTRY.supports_multimodal_inputs(
            self.draft_model_config
        )
        if self.supports_mm_inputs:
            self.inputs_embeds = torch.zeros(
                self.max_num_tokens, self.hidden_size, dtype=self.dtype, device=device
            )
        self.prefill_cudagraph_manager: PrefillSpeculatorCudaGraphManager | None = None
        self.decode_cudagraph_manager: DecodeSpeculatorCudaGraphManager | None = None
​
    @property
    def advance_draft_positions(self) -> bool:
        """
        Whether to increment positions and seq_lens between draft steps.        True for Eagle/standard MTP (each step produces new KV).
        False for Gemma4 MTP (Q-only, shares target KV, constant positions).
        """
        # 默认返回 True ,子类 Gemma4Speculator 覆写为 False
        return True
​
    @property
    def model_returns_tuple(self) -> bool:
        """
        Whether the draft model's forward() returns a tuple.        True: returns (last_hidden_states, hidden_states) — Eagle, Gemma4 MTP.
        False: returns a single tensor used for both — standard MTP (DeepSeek).
        """
        return True
​
    def init_cudagraph_manager(self, cudagraph_mode: CUDAGraphMode) -> None:
        # 初始化 draft prefill 的 CUDA graph 管理器(对应 draft 步骤 0)
        self.prefill_cudagraph_manager = PrefillSpeculatorCudaGraphManager(
            self.vllm_config, self.device, cudagraph_mode,
            self.num_speculative_steps + 1,
        )
        # PIECEWISE 模式不支持 draft decode ,因此降级为 NONE 或 FULL_DECODE_ONLY
        if cudagraph_mode.decode_mode() == CUDAGraphMode.FULL:
            cudagraph_mode = CUDAGraphMode.FULL_DECODE_ONLY
        else:
            cudagraph_mode = CUDAGraphMode.NONE
        # 初始化 draft decode 的 CUDA graph 管理器(draft 步骤 > 0)
        self.decode_cudagraph_manager = DecodeSpeculatorCudaGraphManager(
            self.vllm_config, self.device, cudagraph_mode, decode_query_len=1,
        )
vllm/v1/worker/gpu/spec_decode/gemma4/speculator.py core-logic

新增 Gemma4 专用推测器,实现了 constant positions、KV 共享、保留注意力后端等关键特性。

class Gemma4Speculator(AutoRegressiveSpeculator):
    @property
    def advance_draft_positions(self) -> bool:
        # Gemma4 MTP 是 Q-only ,不生成新 KV ,所以位置和 seq_lens 保持不变
        return False
​
    @property
    def model_returns_tuple(self) -> bool:
        # forward 返回 (draft_hidden, backbone_hidden) ,proposer 使用
        return True
​
    def load_draft_model(
        self,
        target_model: nn.Module,
        target_attn_layer_names: set[str],
    ) -> nn.Module:
        # 构建 draft 的配置,保留 target 的注意力后端
        draft_vllm_config = self._create_draft_vllm_config()
        with set_model_tag("eagle_head"):
            draft_model = get_model(
                vllm_config=draft_vllm_config,
                model_config=self.speculative_config.draft_model_config,
                load_config=self.speculative_config.draft_load_config,
            )
        # 建立 draft 层与 target 层的 KV 共享
        self._setup_gemma4_kv_sharing(draft_model, target_attn_layer_names)
        # 共享 embedding 权重
        self._share_embeddings(draft_model, target_model)
        return draft_model
​
    def _create_draft_vllm_config(self) -> VllmConfig:
        # 保留 target 强制使用的 TRITON_ATTN 后端,因为 Gemma4 有异构 head 维度
        # (sliding 层 head_dim=256, global 层 head_dim=512),必须用 TRITON_ATTN
        draft_model_config = self.speculative_config.draft_model_config
        draft_vllm_config = replace(self.vllm_config, model_config=draft_model_config)
        target_backend = self.vllm_config.attention_config.backend
        if target_backend is not None:
            draft_vllm_config = replace(
                draft_vllm_config,
                attention_config=replace(
                    draft_vllm_config.attention_config, backend=target_backend
                ),
            )
        return draft_vllm_config

评论区精华

init_attn_backend 返回值解包错误 正确性

gemini-code-assist[bot] 指出 init_attn_backend 返回 4 个值但代码解包为 3 个变量,将导致 ValueError 。

结论:TheEpicDolphin 确认已修复。 · 已解决

架构设计讨论:是否采用参数化 propose 方法 设计

benchislett 评论 'this doesn't follow the design we discussed previously. This seems to follow the existing MRV1 design... Did you change your mind about what you think the right design looks like?'

结论:TheEpicDolphin 解释了使用 hooks 方式的优势,认为当前设计更适合。 · 已解决

测试覆盖要求 测试

benchislett 询问 'Do we have any test coverage that we can rely on here? Should we start parameterizing some of the E2E specdec tests for both MRV1 and MRV2 coverage?'

结论:TheEpicDolphin 回复 'I'll follow up with a PR to add/enable tests for MRV2 gemma4 MTP' · acknowledged

合并冲突残留警告 正确性

depthfirst-app[bot] 指出文件中存在未解决的合并冲突标记,将导致 SyntaxError 。

结论:未直接回复,但推测已解决(PR 最终被合并)。 · 已解决

风险与影响

  1. 解包错误已修复,但若合并其他分支再次引入需警惕(speculator.py 中的 init_attn_backend 调用)。
  2. KV 共享逻辑(_setup_gemma4_kv_sharing)映射 draft 层到 target 层的规则正确性直接影响推理结果,需额外测试。
  3. 常量位置假设(advance_draft_positions=False)与标准 MTP 不同,若模型未正确配置可能导致序列崩溃。
  4. 缺少测试覆盖,重构后回归风险较高,尤其是合并冲突残留问题。
  5. 注意力后端强制 TRITON_ATTN(Gemma4)可能与其他后端冲突,需验证环境兼容。

对用户:Gemma4 模型可使用 MRV2 进行推测解码,获得与 MRV1 相当的加速效果。对系统:speculator 架构更清晰,便于后续扩展新 draft 模式。对团队:维护压力略有上升,但插件式 hooks 降低新增模型的门槛。对代码库:新增约 1.2k 行,删除约 0.9k 行,净增约 300 行,主要集中于 spec_decode 子模块。

解包错误已修复 合并冲突残留 缺少测试覆盖 KV 共享正确性风险 常量位置假设

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论