Prhub

#28074 [AMD] Fix no-op dtype cast in _topk_ids_logical_to_physical_dynamic on HIP

原始 PR 作者 rbrugaro-amd 合并时间 2026-06-21 02:07 文件变更 2 提交数 3 评论 4 代码增减 +157 / -1

执行摘要

修复 HIP 动态调度 dtype 转换无操作 bug

修复动态专家调度路径下 dtype 保留失效导致的专家路由损坏。PR body 指出:"Without this fix, int64 expert indices flow into MORI's C++ dispatch kernel, which reads via raw data_ptr() assuming TOPK_IDX_DTYPE = torch.int32. The int64 data is misinterpreted: every other int32 element reads as 0 ... completely corrupting expert routing."

该 PR 值得所有 AMD HIP 用户关注。设计亮点:作者通过对比静态路径的正确实现,发现动态路径中因变量覆盖顺序导致的常见 bug 模式(no-op cast)。新增的测试设计清晰,覆盖边界情况,可作为类似 bugfix 测试的参考。

讨论亮点

本 PR 无人工 review 评论,只有作者与 CI bot 的交互。HaiShaw 在 CI 失败后发起 /tag-and-rerun-ci,amd-bot 回复的 CI 总结确认:"No executed failure is caused by this PR — all reds are pre-existing infra/network/flake on unrelated backends". 最终 HaiShaw 批准合并。

实现拆解

  1. 保存原始 dtype:在 _topk_ids_logical_to_physical_dynamic 函数中,在 topk_idspartial_logical_to_all_physical_map 覆盖之前,增加 original_dtype = topk_ids.dtype 语句,保留输入 tensor 的数据类型。
  2. 修正 cast 目标:将条件判断 if _is_hip: 内的 cast 语句从 topk_ids.to(topk_ids.dtype) 改为 topk_ids.to(original_dtype),确保结果与输入 dtype 一致。这是唯一的逻辑变更。
  3. 新增测试覆盖:创建 test_dispatch_dtype_preservation.py,包含 TestStaticDispatchDtypeTestDynamicDispatchDtype 两个测试类,共 9 个 CPU 单元测试,验证 dtype 保留、值正确性和形状保持。通过 register_cpu_ci 注册到 CI 套件 base-a-test-cpu
  4. CI 配置:测试文件通过 register_cpu_ci 自动注册,无需手动修改 CI 流程。
文件 模块 状态 重要度
python/sglang/srt/eplb/expert_location_dispatch.py 专家调度 modified 5.96
test/registered/unit/eplb/test_dispatch_dtype_preservation.py 单元测试 added 7.6

关键符号

_topk_ids_logical_to_physical_dynamic _topk_ids_logical_to_physical_static

关键源码片段

python/sglang/srt/eplb/expert_location_dispatch.py core-logic

核心修复文件,修改了动态调度函数的 dtype 保留逻辑,仅 2 行变更。

def _topk_ids_logical_to_physical_dynamic(
    topk_ids: torch.Tensor, info: Optional[ExpertLocationDispatchInfo]
) -> torch.Tensor:
    topk_ids_original_shape = topk_ids.shape
    # 保存原始 dtype,因为下一行会覆盖 topk_ids 为 int64 映射表结果
    original_dtype = topk_ids.dtype
    device = topk_ids.device
    topk_ids = topk_ids.flatten()
​
    chosen_dispatch_index = (
        torch.randint(0, 65536, topk_ids.shape, dtype=torch.int32, device=device)
        % info.partial_logical_to_all_physical_map_num_valid[topk_ids]
    )
    # 此时 topk_ids 变为 int64(映射表 dtype)
    topk_ids = info.partial_logical_to_all_physical_map[
        topk_ids, chosen_dispatch_index
    ]
    if _is_hip:
        # 转换为原始 dtype,而不是 topk_ids.dtype(已为 int64)
        topk_ids = topk_ids.to(original_dtype)
​
    topk_ids = topk_ids.view(topk_ids_original_shape)
    return topk_ids
test/registered/unit/eplb/test_dispatch_dtype_preservation.py test-coverage

新增的测试文件,提供全面的 dtype 保留测试,确保修复正确且无回归。

class TestDynamicDispatchDtype(CustomTestCase):
    """Tests for _topk_ids_logical_to_physical_dynamic dtype preservation."""
​
    def test_preserves_int32_dtype_on_hip(self):
        """int32 输入在 HIP 下必须产生 int32 输出。"""
        info = _make_permuted_info() # int64 映射表
        info.ep_dispatch_algorithm = "dynamic"
        topk_ids = torch.tensor([5, 103, 206], dtype=torch.int32)
        with patch("sglang.srt.eplb.expert_location_dispatch._is_hip", True):
            result = _topk_ids_logical_to_physical_dynamic(topk_ids, info)
        self.assertEqual(result.dtype, torch.int32)
        # 验证值正确:映射表取出后 int32 保留
        expected = info.partial_logical_to_all_physical_map[
            topk_ids.long(), 0
        ].to(torch.int32)
        self.assertTrue(torch.equal(result, expected))

评论区精华

CI 状态确认 test

amd-bot 评论确认 CI 失败非本 PR 导致,所有失败均与预存在的基础设施 / 网络 / 不稳定有关;本 PR 变更被 base-a-test-cpu 执行并验证通过。

结论:PR 无 CI 回归问题,可以合并。 · 已解决

风险与影响

变更极其聚焦(2 行源码 + 测试),核心风险是 original_dtype 是否始终正确。若 topk_ids 输入为 int64 且映射表也为 int64,转换到 int64 是多余的但无害。测试覆盖了 int64 输入和 int32 输入情况。仅影响 HIP 路径(_is_hip=True),CUDA 或其他后端不走此分支。无性能影响。回归风险较低。

修复影响使用 AMD GPU、MORI 专家并行(EP)和动态(或 fake)EPLB 算法的用户。此前动态 EPLB 导致专家路由完全错误(路由到错误专家),影响推理质量。修复后动态 EPLB 的正确性与静态路径一致,高并发解码均衡度从 ~0.49 恢复到 ~0.87。对静态路径用户无影响。

AMD 专用路径 专家路由逻辑

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论