# PR #27959 完整报告

- 仓库：`sgl-project/sglang`
- 标题：[Spec] Remove the DFLASH V1 worker path
- 合并时间：2026-06-12 05:37
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/27959

---

# 执行摘要

- 一句话：移除 DFLASH V1 worker 路径，统一 V2
- 推荐动作：建议所有 DFLASH 开发者阅读此 PR，理解 V1 路径删除的原因和 V2 worker 的统一行为。特别是环境变量 `SGLANG_ENABLE_SPEC_V2` 语义的变更需要注意。该 PR 是架构清理的良好范例。

# 功能与动机

根据 PR 描述，V1 worker 路径已不再需要，V2 worker 已稳定且功能完备。通过删除 V1 代码和 `SGLANG_ENABLE_SPEC_V2` 门控，可以简化架构、消除条件分支、减少维护负担，并使 DFLASH 的行为与 EAGLE 等其他推测解码算法一致。

# 实现拆解

1. **清空 `dflash_worker.py` 中的 V1 专有方法**：移除了 `_prepare_for_speculative_decoding`、`_append_target_hidden_to_draft_kv`、`forward_batch_generation` 和 `_to_int32_device_tensor` 方法。更新了类文档字符串为 `Shared DFLASH infrastructure (draft model, draft KV materialization)`。同时清理了不再使用的导入，如 `GenerationBatchResult`、`ForwardBatch`、`ForwardMode`、`DFlashDraftInput` 等。
2. **精简 `dflash_info.py`**：删除了 `DFlashDraftInput` 数据类（及其 `__post_init__`、`get_spec_adjust_token_coefficient`、`filter_batch`、`merge_batch` 方法），以及函数 `_compute_paged_keep_slots` 和 `DFlashVerifyInput.verify` 方法。现在该文件只保留 `DFlashVerifyInput` 类及其 V2 所需的方法（如 `prepare_for_v2_verify`、`generate_attn_arg_prefill`）。
3. **消除 `spec_info.py` 中的环境变量门控**：修改了 `supports_spec_v2` 方法，使其无条件返回 `True`（对于 DFLASH）。修改了 `create_worker` 方法，直接返回 `DFlashWorkerV2`，不再有条件分支选择 V1 或 V2 worker。
4. **更新 `speculative_hook.py` 中的配置逻辑**：移除了 V1 相关的警告日志。现在，当 `SGLANG_ENABLE_SPEC_V2` 为 0 时，仅设置 `disable_overlap_schedule=True` 以选择同步非重叠路径，不再区分 V1/V2 worker。
5. **调整 `dflash_worker_v2.py` 类注释**：更新了 `DFlashWorkerV2` 的文档字符串，说明它同时支持重叠和非重叠调度（与 EAGLE 一致）。
6. **更新测试文件**：在 `test_decode_bookkeeping_ownership.py` 中移除了对已删除 `DFlashVerifyInput.verify` 方法的引用（3 行测试数据）。

关键文件：
- `python/sglang/srt/speculative/dflash_worker.py`（模块 推测解码；类别 source；类型 dependency-wiring；符号 _prepare_for_speculative_decoding, _append_target_hidden_to_draft_kv, forward_batch_generation, _to_int32_device_tensor）: 核心文件，移除了 V1 专有方法，大幅精简了 DFLASH worker 类
- `python/sglang/srt/speculative/dflash_info.py`（模块 推测解码；类别 source；类型 dependency-wiring；符号 _compute_paged_keep_slots, DFlashDraftInput, __post_init__, get_spec_adjust_token_coefficient）: 关键数据结构和函数的重组，删除了 V1 的 `DFlashDraftInput` 和 `verify` 方法
- `python/sglang/srt/speculative/spec_info.py`（模块 推测解码；类别 source；类型 dependency-wiring）: 移除了 DFLASH V1 的环境变量门控，直接返回 V2 worker
- `python/sglang/srt/arg_groups/speculative_hook.py`（模块 配置；类别 source；类型 core-logic）: 移除了 V1 相关的警告日志，调整环境变量处理逻辑
- `python/sglang/srt/speculative/dflash_worker_v2.py`（模块 推测解码；类别 source；类型 core-logic）: 类注释更新，反映统一 worker 角色
- `test/registered/unit/spec/test_decode_bookkeeping_ownership.py`（模块 测试；类别 test；类型 test-coverage）: 移除对已删除 `DFlashVerifyInput.verify` 方法的测试引用

关键符号：_prepare_for_speculative_decoding, _append_target_hidden_to_draft_kv, forward_batch_generation, _to_int32_device_tensor, _compute_paged_keep_slots, DFlashDraftInput.__post_init__, DFlashDraftInput.get_spec_adjust_token_coefficient, DFlashDraftInput.filter_batch, DFlashDraftInput.merge_batch, DFlashVerifyInput.verify, DFlashWorker._greedy_sample_from_vocab_parallel_head

## 关键源码片段

### `python/sglang/srt/speculative/dflash_info.py`

关键数据结构和函数的重组，删除了 V1 的 `DFlashDraftInput` 和 `verify` 方法

```python
# python/sglang/srt/speculative/dflash_info.py (head) - 仅保留 DFlashVerifyInput

@dataclass
class DFlashVerifyInput(SpecInput):
    """Inputs for a target-model verify forward in DFlash.

    The verify forward is run with `ForwardMode.TARGET_VERIFY` so that the target
    model returns logits for all tokens in the block, enabling accept-length
    computation.
    """

    draft_token: torch.Tensor
    positions: torch.Tensor
    draft_token_num: int
    # Kept for compatibility with attention backends that gate tree metadata by `topk > 1`.
    # DFLASH verify is linear (non-tree), so this is always 1.
    topk: int = 1
    # Custom attention "allow mask" for TARGET_VERIFY in backends that require it.
    # Semantics follow SGLang speculative conventions: True means the (q, k) pair is allowed.
    custom_mask: torch.Tensor | None = None
    capture_hidden_mode: CaptureHiddenMode = CaptureHiddenMode.FULL

    # Shape info for padding (e.g., DP attention / CUDA graph).
    num_tokens_per_batch: int = -1

    def __post_init__(self):
        super().__init__(spec_input_type=SpecInputType.DFLASH_VERIFY)
        if self.num_tokens_per_batch == -1:
            # 如果没有显式指定，则使用 draft_token_num 作为每批 token 数
            self.num_tokens_per_batch = int(self.draft_token_num)

    def get_spec_adjust_token_coefficient(self) -> Tuple[int, int]:
        return self.draft_token_num, self.draft_token_num

    def prepare_for_v2_verify(
        self,
        batch: ScheduleBatch,
        target_worker: "TpModelWorker",
    ) -> tuple[ForwardBatch, bool]:
        """Prepare a DFLASH verify forward batch for overlap scheduling.

        The caller computes and stores `batch.out_cache_loc` before this
        method is called. This helper only packages the verify forward and
        pre-initializes either CUDA-graph replay metadata or eager attention
        metadata so the actual forward can run with `skip_attn_backend_init=True`.
        """
        batch.input_ids = self.draft_token
        batch.spec_info = self
        batch.forward_mode = (
            ForwardMode.IDLE
            if batch.forward_mode.is_idle()
            else ForwardMode.TARGET_VERIFY
        )
        batch.capture_hidden_mode = self.capture_hidden_mode
        verify_forward_batch = ForwardBatch.init_new(batch, target_worker.model_runner)

        can_run_cuda_graph = bool(
            target_worker.model_runner.decode_cuda_graph_runner
            and target_worker.model_runner.decode_cuda_graph_runner.can_run(
                verify_forward_batch
            )
        )
        if can_run_cuda_graph:
            target_worker.model_runner.decode_cuda_graph_runner.replay_prepare(
                verify_forward_batch
            )
        elif not batch.forward_mode.is_idle():
            target_worker.model_runner.attn_backend.init_forward_metadata(
                verify_forward_batch
            )

        return verify_forward_batch, can_run_cuda_graph

```

### `python/sglang/srt/speculative/spec_info.py`

移除了 DFLASH V1 的环境变量门控，直接返回 V2 worker

```python
# python/sglang/srt/speculative/spec_info.py (head) - create_worker 中 DFLASH 分支简化

def create_worker(
    self, server_args: ServerArgs
) -> Optional[Union[Type[BaseSpecWorker], Type[TpModelWorker], Type[NGRAMWorker]]]:
    assert (
        not self.is_none()
    ), "Cannot create worker for NONE speculative algorithm."

    if self.is_dflash():
        # V2 worker 同时驱动重叠和非重叠调度（关闭重叠时由调度器同步运行），与 EAGLE 一致
        from sglang.srt.speculative.dflash_worker_v2 import DFlashWorkerV2
        return DFlashWorkerV2

    if self.is_frozen_kv_mtp():
        from sglang.srt.speculative.frozen_kv_mtp_worker_v2 import FrozenKVMTPWorkerV2
        return FrozenKVMTPWorkerV2

    # EAGLE / EAGLE3 / STANDALONE / MULTI_LAYER 始终使用 V2 worker
    if self.is_eagle() and server_args.enable_multi_layer_eagle:
        from sglang.srt.speculative.multi_layer_eagle_worker_v2 import MultiLayerEagleWorkerV2
        return MultiLayerEagleWorkerV2
    elif self.is_eagle():
        from sglang.srt.speculative.eagle_worker_v2 import EAGLEWorkerV2
        return EAGLEWorkerV2
    # ... 其他分支不变

```

# 评论区精华

PR 没有 review 评论，作者自行合并。在 Issue 评论中，作者触发了针对 DFLASH 相关测试的重跑，所有测试均通过。

- 暂无高价值评论线程

# 风险与影响

- 风险：该 PR 删除了 884 行代码，V2 路径的行为保持不变，但仍然存在以下风险：
 - **回归风险**：任何依赖 V1 worker 或 `SGLANG_ENABLE_SPEC_V2=0` 原有 V1 行为的部署，在更新后可能会因工作器选择逻辑改变而出现意外行为（尽管 PR 声称 V2 同步模式等效）。
 - **测试覆盖**：虽然相关测试通过，但难以保证所有边缘场景（如自定义 draft window size、paged mode 组合）都已被覆盖。
 - **环境变量语义变化**：`SGLANG_ENABLE_SPEC_V2` 不再控制 worker 类型，仅控制是否启用重叠调度，文档可能需要同步更新。
 - 影响：
 - **用户影响**：低。无 API 变化，但环境变量 `SGLANG_ENABLE_SPEC_V2` 的行为有所调整，用户需注意其含义已变为仅控制重叠调度。
 - **系统影响**：中。代码量显著减少，架构更简洁，便于后续维护和演进。DFLASH 的代码路径与 EAGLE 统一，降低了特殊处理的需要。
 - **团队影响**：积极。减少技术债务，新开发者更容易理解 DFLASH 的实现。
 - 风险标记：大量代码删除 , 环境变量语义变化 , 回归风险

# 关联脉络

- 暂无明显关联 PR