Prhub

#29556 dflash: drop verify_done barrier; rely on scheduler WAR fallback

原始 PR 作者 hnyls2002 合并时间 2026-06-28 16:47 文件变更 3 提交数 1 评论 5 代码增减 +2 / -27

执行摘要

移除 DFLASH verify_don 自栅栏,依赖全局 WAR

PR body 指出,DFLASH 使用 verify_done 事件自保护 req_to_token 写入,每次调用 cudaEventSynchronize 约占用 2.1ms,在 profile 中重复 37 次,是 overlap 循环中最大的 CPU 阻塞来源。由于 seq_lens CPU 值已通过 FutureMap(#29232)传输,verify_done 的唯一剩余作用是写后读(WAR)栅栏,而该栅栏已由调度器的全局 _apply_war_barrier 提供——此前 DFLASH 通过 and not is_dflash() 被排除在外。因此,可以安全地移除 DFLASH 自定栅栏,使其复用调度器的全局机制。

值得精读,尤其是对 speculative decoding overlap 调度感兴趣的同学。该 PR 展示了如何通过移除重复的保护机制(自栅栏 vs 全局屏障)来减少 CPU 阻塞,同时通过依赖设备端流等待保持正确性。设计决策清晰——利用已有 plan_stream.wait_stream(schedule_stream) 传递依赖,避免额外添加。

讨论亮点

该 PR 的 review 仅由 gemini-code-assist[bot] 执行,其结论为“无需额外评论”,没有人类审核者提出争议或设计权衡。PR body 中作者已详细说明了变更前后的 WAR 排序依赖图,并通过注释指明 prepare_for_decode 中已有的 plan_stream.wait_stream(schedule_stream) 会传递依赖,无需额外添加。

实现拆解

变更拆解为以下三步:

  1. 启用全局 WAR 屏障:在 python/sglang/srt/managers/scheduler.pyrun_event_loop 方法中,将 _war_barrier_enabled 的条件由 (is_cuda() or ...) and not self.spec_algorithm.is_dflash() 简化为 is_cuda() or ...,移除了排除 DFLASH 的 and not is_dflash() 分支,使 DFLASH 也走全局 _apply_war_barrier 路径。
  2. 清理 verify_done 字段:在 python/sglang/srt/speculative/dflash_info_v2.py 中,从 DFlashDraftInputV2 dataclass 移除 verify_done: Optional[torch.cuda.Event] 字段,同时删除 create_idle_input 中对 verify_done=None 的赋值,以及 prepare_for_decode 中依赖 verify_done 的两段同步逻辑(verify_done.synchronize()plan_stream.wait_event(self.verify_done))。
  3. 移除 verify_done 的记录和传递:在 python/sglang/srt/speculative/dflash_worker_v2.py 中,从 _make_next_draft_input_prefill_make_next_draft_input_decode 两个方法中移除 verify_done 参数及其传入 DFlashDraftInputV2 构造函数的部分;同时移除 forward_batch_generation 方法中三处创建并记录 verify_done 事件(torch.cuda.Event(); ... .record())的代码。

该 PR 是纯性能优化,不涉及测试、配置或部署配套变更。

文件 模块 状态 重要度
python/sglang/srt/managers/scheduler.py 调度器 modified 5.8
python/sglang/srt/speculative/dflash_info_v2.py 推测解码 modified 5.73
python/sglang/srt/speculative/dflash_worker_v2.py 推测解码 modified 5.71

关键符号

Scheduler.run_event_loop DFlashDraftInputV2.prepare_for_decode DFlashWorkerV2._make_next_draft_input_prefill DFlashWorkerV2._make_next_draft_input_decode DFlashWorkerV2.forward_batch_generation

关键源码片段

python/sglang/srt/managers/scheduler.py core-logic

启用全局 WAR 屏障,移除 DFLASH 排除条件

# python/sglang/srt/managers/scheduler.py
class Scheduler:
    def run_event_loop(self) -> None:
        # ... 省略 MLX 分支 ...
        self.schedule_stream = self.device_module.Stream(priority=0)
        if self.device == "cpu":
            self.schedule_stream.synchronize = lambda: None # 对于 CPU 无操作
​
        # 全局 WAR 屏障将调度器下一个共享缓冲区写入与上一次前向读取对齐。
        # 此前 DFLASH 通过 and not is_dflash() 跳过了此屏障(因为它使用自己的 verify_done 事件),
        # 现在 verify_done 已被移除,因此 DFLASH 也使用此屏障。
        self._war_barrier_enabled = (
            is_cuda() or envs.SGLANG_ENABLE_WAR_BARRIER.get()
        ) # 移除了 and not self.spec_algorithm.is_dflash()
        with self.device_module.StreamContext(self.schedule_stream):
            dispatch_event_loop(self)
python/sglang/srt/speculative/dflash_info_v2.py core-logic

移除 verify_done 字段及相关同步逻辑

# python/sglang/srt/speculative/dflash_info_v2.py
@dataclass
class DFlashDraftInputV2(SpecInput):
    """跨 overlap 迭代传递的 draft 端状态(spec-v2)。"""
​
    # Legacy Eagle-shaped 字段 ; DFLASH 通过 FutureMap 传递,这些字段未使用。
    topk_p: torch.Tensor
    topk_index: torch.Tensor
    bonus_tokens: torch.Tensor
    new_seq_lens: torch.Tensor
    hidden_states: torch.Tensor
    # verify_done: Optional[torch.cuda.Event] = None # 已移除
    max_top_k: int = 1
    uniform_top_k_value: Optional[int] = None
    reserved_seq_lens_cpu: Optional[torch.Tensor] = None
    reserved_seq_lens_sum: Optional[int] = None
    # ... 其他字段
​
    def prepare_for_decode(self, batch: ScheduleBatch):
        """为下一步 DFLASH 步骤在共享 req_to_token 池中预留头部空间。"""
        plan_stream, plan_stream_ctx = _get_overlap_plan_stream(batch.device)
        # 已移除 : if plan_stream is None and self.verify_done is not None:
        # self.verify_done.synchronize()
​
        bs = batch.batch_size()
        if bs == 0:
            return
        self._ensure_prepare_length_buffers(bs, batch.device)
        # ... 内部逻辑
        # 已移除 : if plan_stream is not None and self.verify_done is not None:
        # plan_stream.wait_event(self.verify_done)
python/sglang/srt/speculative/dflash_worker_v2.py core-logic

移除 verify_done 事件的记录和传递

# python/sglang/srt/speculative/dflash_worker_v2.py
class DFlashWorkerV2:
    def _make_next_draft_input_prefill(
        self,
        *,
        bonus_tokens: torch.Tensor,
        seq_lens: torch.Tensor,
        # verify_done: Optional[torch.cuda.Event] = None, # 已移除
    ) -> DFlashDraftInputV2:
        bs = int(seq_lens.numel())
        device = bonus_tokens.device
        return DFlashDraftInputV2(
            topk_p=torch.empty((bs, 0), device=device, dtype=torch.float32),
            topk_index=torch.empty((bs, 0), device=device, dtype=torch.int64),
            bonus_tokens=bonus_tokens.to(dtype=torch.int64),
            new_seq_lens=seq_lens.to(dtype=torch.int64),
            hidden_states=torch.empty((bs, 0), device=device, dtype=torch.float16),
            # verify_done=verify_done, # 已移除
        )
​
    # _make_next_draft_input_decode 类似
​
    def forward_batch_generation(self, batch, on_publish=None):
        if batch.forward_mode.is_extend() or batch.is_extend_in_batch:
            # prefill 路径
            # ... 省略逻辑 ...
            batch_output.next_draft_input = self._make_next_draft_input_prefill(
                bonus_tokens=next_token_ids,
                seq_lens=batch.seq_lens,
            )
            # 已移除 : verify_done = torch.get_device_module(device).Event()
            # verify_done.record()
            # batch_output.next_draft_input.verify_done = verify_done
            return batch_output
        # decode/verify 路径类似

评论区精华

自动化 AI 审查无额外反馈 other

gemini-code-assist[bot] 评论指出功能对齐正确,没有需要解决的 review comments。

结论:无额外反馈,PR 可直接合并。 · 已解决

风险与影响

  1. 正确性风险:依赖调度器的全局 WAR 屏障替代 DFLASH 自定栅栏。若 _apply_war_barrierwait_stream 分支在特定硬件或配置下行为异常(如 CPU 端无真实等待语义),可能导致 req_to_token 读取到未完成写入的数据。但该屏障已用于非 DFLASH 路径,风险可控。
  2. 性能回归风险:启用全局 WAR 屏障后,DFLASH 路径每次迭代会增加一次 schedule_stream.wait_stream(forward_stream) 调用。但由于该调用是设备端操作(不阻塞 CPU),且仅发生在 wait_event 快速路径不可用时,整体性能影响应低于原 cudaEventSynchronize
  3. 兼容性风险:移除 verify_done 字段修改了 DFlashDraftInputV2 的接口,但该 dataclass 仅在 DFLASH 路径内部使用,外部不依赖,不构成兼容性问题。

对用户:无直接用户可见变化,但可降低 decode 延迟(特别在长序列、高并发场景下),提升吞吐表现。
对系统:消除 DFLASH 路径中最大的 CPU 阻塞点(约 78ms/profile),使 overlap 调度更高效,CPU 可用于更多调度计算。
对团队:简化了 DFLASH 代码,消除了一种特殊自定制同步机制,降低维护负担。后续可进一步通过 #29541(细粒度 read_done 事件)优化全局 WAR 屏障的等待粒度。

同步机制变更 核心路径变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论