Prhub

#26128 [core] Make spec_v2 `seq_lens_cpu` optional via backend `needs_cpu_seq_lens`; Triton opts out

原始 PR 作者 hnyls2002 合并时间 2026-05-30 04:00 文件变更 14 提交数 20 评论 5 代码增减 +176 / -32

执行摘要

使 spec_v2 seq_lens_cpu 按后端标志可选,Triton 跳过 CPU 同步

Triton 的 cuda-graph replay 从预分配缓冲区重建元数据,从不读取 seq_lens_cpu / seq_lens_sum,因此在全 Triton EAGLE3 路径上该同步是纯粹开销。

值得精读,展示了通过 per-backend 标志优雅控制同步路径的设计方法;关注 FutureMap 的异步模式及多组件协调方式。

讨论亮点

PR 描述中明确 TBO 和 piecewise CG 场景仍需 CPU seq_lens,目前强制同步并标记为 FIXME;Triton 后端选择退出后,下游多处需增加 None 守卫,增加了分支复杂性,但架构清晰。

实现拆解

  1. 后端标志定义:在 BaseAttentionBackend 添加 needs_cpu_seq_lens: bool = TrueTritonAttnBackendTritonMultiStepDraftBackend 重写为 False
  2. 决策函数:新增 decide_needs_cpu_seq_lensoverlap_utils.py),对所有后端标志取 OR,并在 TBO / piecewise CG 开启时强制返回 True(当前尚未适配无 CPU 镜像的路径)。
  3. FutureMap 条件跳过FutureMap.__init__ 接受 needs_cpu_seq_lens 参数;resolve_seq_lens_cpu 中当标志为 False 时保留 GPU 的 seq_lens 更新,但跳过 .cpu() D2H 和 sum 计算,将 seq_lens_cpu/seq_lens_sum 置为 None
  4. 下游适配eagle_info_v2.pyforward_batch_info.pycuda_graph_runner.pyschedule_batch.py 等多个文件增加 None 守卫,支持在缺少 CPU 镜像时走 GPU-only 路径;metrics_reporter.py 新增 _decode_total_seq_lens 函数,在无 CPU 镜像时退化为 sum(req.seqlen for req in batch.reqs)
文件 模块 状态 重要度
python/sglang/srt/managers/overlap_utils.py 调度同步 modified 7.14
python/sglang/srt/speculative/eagle_worker_v2.py 推测解码 modified 6.89
python/sglang/srt/managers/scheduler_components/metrics_reporter.py 指标收集 modified 6.69
python/sglang/srt/model_executor/forward_batch_info.py 前向批处理 modified 6.57
python/sglang/srt/managers/scheduler.py 调度器 modified 6.45
python/sglang/srt/layers/attention/triton_backend.py 注意力后端 modified 6.14
python/sglang/srt/managers/schedule_batch.py 批调度 modified 5.83
python/sglang/srt/speculative/eagle_info_v2.py 推测信息 modified 6.17

关键符号

decide_needs_cpu_seq_lens FutureMap.__init__ FutureMap.resolve_seq_lens_cpu spec_v2_attn_backends _decode_total_seq_lens ForwardBatchInfo.init_new

关键源码片段

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

核心决策与同步控制:新增 decide_needs_cpu_seq_lens 函数,修改 FutureMap 根据标志跳过 CPU D2H。

from typing import Sequence
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
from sglang.srt.server_args import ServerArgsdef decide_needs_cpu_seq_lens(
    server_args: "ServerArgs",
    attn_backends: Sequence["AttentionBackend"],
) -> bool:
    """
    聚合所有 attention 后端的 needs_cpu_seq_lens 标志,
    并强制 TBO / piecewise CG 路径返回 True(暂不支持无 CPU 镜像)。
    """
    if server_args.enable_two_batch_overlap:
        # FIXME: support TBO without seq lens cpu value
        return True
    if not server_args.disable_piecewise_cuda_graph:
        # FIXME: support PCG without seq lens cpu value
        return True
    return any(b.needs_cpu_seq_lens for b in attn_backends)
​
​
class FutureMap:
​
    def __init__(self, device, spec_algo, req_to_token_pool, needs_cpu_seq_lens=True):
        # ... 原有初始化 ...
        self.needs_cpu_seq_lens = needs_cpu_seq_lens
        # 根据标志决定是否分配 pinned buffer 和 d2h stream
        if _is_cuda and self.needs_cpu_seq_lens:
            self.new_seq_lens_cpu_pinned = torch.empty(...)
            self.fwd_prepare_d2h_stream = ...
​
    def resolve_seq_lens_cpu(self, batch: ScheduleBatch) -> None:
        # ... 等待 publish_ready,更新 GPU seq_lens ...
        if not self.needs_cpu_seq_lens:
            # 保留 GPU gather,跳过 D2H 和 sum,下游走 GPU-only 路径
            batch.seq_lens_cpu = None
            batch.seq_lens_sum = None
            return
        # 原有 D2H 路径:pinned stream 或 fallback .cpu()
python/sglang/srt/speculative/eagle_worker_v2.py core-logic

关键消费者:暴露 spec_v2_attn_backends 属性,在 draft 方法中处理 seq_lens_sum 为 None 的 fallback。

# eagle_worker_v2.py (partial)@property
def spec_v2_attn_backends(self) -> tuple:
    """
    本 worker 涉及的所有 attention 后端,供调度器决策是否保留 seq_lens_cpu。
    """
    return (
        self._target_worker.model_runner.attn_backend,
        self._draft_worker.draft_attn_backend,
        self._draft_worker.draft_extend_attn_backend,
    )# 在 draft 方法中处理 seq_lens_sum 可能为 None 的情况:
seq_lens_sum = batch.seq_lens_sum
if seq_lens_sum is None:
    if tree_mask_buf is None:
        # tree_mask 非预分配,使用最大静态长度作为上界
        max_context_len = self.target_worker.model_runner.attn_backend.max_context_len
        seq_lens_sum = batch.seq_lens.shape[0] * max_context_len
    else:
        # 预分配 buffer,kernel 会忽略 seq_lens_sum
        seq_lens_sum = 0

评论区精华

TBO / piecewise CG 是否应强制同步 设计

PR 描述指出 TBO 和 piecewise CG 场景仍需 seq_lens_cpu(读取 CPU 镜像),因此 decide_needs_cpu_seq_lens 对它们强制返回 True,并标记 FIXME。

结论:保留强制同步,待后续优化再移除。 · 已解决

风险与影响

若新增的后端未正确设置 needs_cpu_seq_lens,或下游调用未处理 seq_lens_cpuNone 的情况,可能导致 AttributeError 或静默错误。TBO/piecewise CG 路径当前强制同步,但未来若移除需大量适配。缺少针对性测试,回归依赖现有 CI。

使用纯 Triton 后端的 EAGLE3 用户受益于减少同步延迟;其他后端和配置无功能变化。对开发者:新增 attention 后端时需正确设置标志,否则可能引入不必要的同步或 bug。

缺少测试覆盖 核心调度同步变更 条件分支复杂化

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论