Prhub

#24550 [R3] Avoid implicit CUDA sync in routed experts DP slicing

原始 PR 作者 zyzshishui 合并时间 2026-05-07 09:37 文件变更 2 提交数 5 评论 6 代码增减 +22 / -5

执行摘要

避免 routed experts DP 切片中隐式 CUDA 同步

Issue #24514 指出,在 routed experts overlap 路径中,get_dp_local_info 返回 GPU 张量,当被用作 Python 切片索引时,PyTorch 内部调用 aten::_local_scalar_dense 触发 cudaStreamSynchronize,导致 CPU 线程阻塞,破坏了 overlap 调度的目的。此 PR 旨在消除这一隐式同步。

推荐精读。该 PR 是高性能推理中消除隐式同步的典型案例,展示了如何通过 CPU 侧整数计算避免 GPU 同步。同时 review 中关于数据可用性的讨论值得关注,未来可能的改进方向可以增加防御性检查。

讨论亮点

Review 中主要有两个焦点:

  1. CPU 数据可用性风险:Chatgpt-codex 指出 global_num_tokens_cpu 在 CUDA graph capture 阶段可能为 None,新函数无条件索引可能导致错误。该问题在合并时未显式处理,但实际运行中 _get_local_slice 仅在 overlap 运行时被调用,capture 阶段可能不会进入该分支。
  2. 函数命名与放置:ByronHsu 建议将辅助函数移到 dp_attention.py 并命名为 get_dp_local_info_cpu(),以保持模块内聚性。最终采纳并命名为 get_dp_local_slice_cpu

实现拆解

  1. 新增 CPU 切片函数:在 python/sglang/srt/layers/dp_attention.py 中新增 get_dp_local_slice_cpu(forward_batch, can_run_graph, cuda_graph_batch) -> Tuple[int, int]。该函数从 forward_batch.global_num_tokens_cpu(CPU 列表)获取每 DP rank 的 token 数,根据 can_run_graph 标志计算本地起始位置:若为 CUDA graph 模式,则用 dp_rank * cuda_graph_batch;否则用前缀和 sum(global_num_tokens[:dp_rank])。返回 Python int 类型的 local_start_poslocal_num_tokens
  2. 修改导入与调用:在 python/sglang/srt/state_capturer/routed_experts.py 中,将导入的符号从 get_dp_local_infoget_attention_dp_rank 替换为 get_dp_local_slice_cpu
  3. 重构 _get_local_slice 方法:直接调用新函数获取本地切片范围,移除原有的条件分支中手动计算 local_start_pos 的逻辑。非 DP attention 或 DeepEP 路径保持不变。
  4. 配套调整:移除不再需要的 get_attention_dp_rank 导入,保持代码整洁。
文件 模块 状态 重要度
python/sglang/srt/layers/dp_attention.py DP 注意力 modified 6.77
python/sglang/srt/state_capturer/routed_experts.py 专家路由 modified 5.91

关键符号

get_dp_local_slice_cpu _get_local_slice

关键源码片段

python/sglang/srt/layers/dp_attention.py core-logic

新增核心函数 `get_dp_local_slice_cpu`,是消除隐式同步的关键。

def get_dp_local_slice_cpu(
    forward_batch: ForwardBatch,
    can_run_graph: bool,
    cuda_graph_batch: Optional[int],
) -> Tuple[int, int]:
    # 从 CPU 侧的全局 token 计数计算本地 DP rank 的切片范围。
    # 返回 Python int,避免 GPU→CPU 同步。
    global_num_tokens = forward_batch.global_num_tokens_cpu
    dp_rank = get_attention_dp_rank()
    local_num_tokens = global_num_tokens[dp_rank]
    if can_run_graph:
        # CUDA graph 模式下,每个 rank 占据固定大小的 batch
        local_start_pos = dp_rank * cuda_graph_batch
    else:
        # 非 graph 模式,通过前缀和计算起始位置
        local_start_pos = sum(global_num_tokens[:dp_rank])
    return local_start_pos, local_num_tokens
python/sglang/srt/state_capturer/routed_experts.py entrypoint

调用方,修改导入并调用新函数,整合了原有的 CUDA graph 分支逻辑。

def _get_local_slice(
    self,
    forward_batch: ForwardBatch,
    can_run_graph: bool,
    cuda_graph_batch: Optional[int],
) -> torch.Tensor:
    # 非 DeepEP + DP attention 时,使用 CPU 切片避免同步
    if is_dp_attention_enabled() and not get_moe_a2a_backend().is_deepep():
        local_start_pos, local_num_tokens = get_dp_local_slice_cpu(
            forward_batch, can_run_graph, cuda_graph_batch
        )
        local_end_pos = local_start_pos + local_num_tokens
    else:
        # DeepEP 或非 DP attention 时,使用完整 buffer
        local_start_pos, local_end_pos = 0, forward_batch.out_cache_loc.shape[0]
    return self.device_cache.buffer[
        local_start_pos:local_end_pos, :, : self.topk_size
    ]

评论区精华

global_num_tokens_cpu 在 CUDA graph capture 中可能为 None 正确性

Chatgpt-codex 指出 `global_num_tokens_cpu` 在 CUDA graph 构造时可能为 None,只有 GPU 字段被填充。新函数无条件索引可能导致错误。

结论:PR 最终未处理此问题,但可能在实际路径中不会触发(因为 `_get_local_slice` 仅在 overlap 运行时被调用,而 capture 阶段可能不会进入该分支)。需要进一步验证。 · unresolved

函数命名与位置 设计

ByronHsu 建议将辅助函数移到 `dp_attention.py` 并命名为 `get_dp_local_info_cpu()`,以保持模块内聚性和命名一致性。

结论:PR 采纳了建议,将函数命名为 `get_dp_local_slice_cpu` 并放入 `dp_attention.py`。 · 已解决

风险与影响

  1. CPU 数据为 None 风险:如果 forward_batch.global_num_tokens_cpu 在部分路径(如 CUDA graph capture)中未被填充,新函数会引发 TypeError。虽然当前 overlap 场景下该字段总是可用的,但未来代码演进可能导致意外。
  2. 缺少测试覆盖:PR 未增加针对非 DeepEP DP 切片路径的单元测试,回归风险完全依赖现有 CI(如 test_return_routed_experts.py)。
  3. 数值一致性假设:假设 global_num_tokens_cpu 与 GPU 侧计数严格一致,任何同步延迟可能导致切片错误。

对用户:修复了启用 DP attention + overlap + 非 DeepEP MoE 后端的隐式同步性能问题,预期减少一次 cudaStreamSynchronize 延迟,提升吞吐。影响范围限制在 MoE 模型配置了 --enable-dp-attention 且未使用 DeepEP 的场景。对系统:改动量小,仅涉及两个文件的少量修改,无回归风险。对团队:解决了捕获器中的性能痛点,为后续 overlap 优化扫清障碍。

global_num_tokens_cpu 可能为 None 缺少针对非 DeepEP DP 切片的单元测试

关联 Issue

#24514 [R3] Non-DeepEP DP attention codepath causes implicit CUDA sync in routed experts overlap

完整报告

参与讨论