Prhub

#34017 [Fix] Judge the phase-checker device-assert test by its FAIL line, not the exit code

原始 PR 作者 hnyls2002 合并时间 2026-08-08 07:19 文件变更 3 提交数 5 评论 2 代码增减 +14 / -12

执行摘要

修复 phase-checker 设备断言测试的 flaky 判定逻辑

PR body 明确指出:CI 运行在 SGLANG_CUDA_COREDUMP=1 下,coredump handler 可能 abort 进程;同时 torch.cuda.synchronize() 会抛出 cudaErrorAssert("device-side assert triggered")或泛化的 cudaErrorLaunchFailure("unspecified launch failure"),子进程只匹配了第一种措辞并 exit 0,第二种走 exit 2,被断言拒绝。这直接导致 base-b-test-1-gpu-small (3) 在两个不同 runner 上失败,而同一验证内容在另一个 shard 通过,属于典型的 flaky 测试。

值得快速浏览,了解 CUDA 设备端 assert 在 CI 环境下的非确定性退出行为。核心设计决策是“以证据行而非退出码作为断言标准”,对类似需要跨进程判断内核错误的测试有借鉴意义。无需精读,但可作为 CI 稳定性改进的参考范例。

讨论亮点

本 PR 没有 review 评论。作者在 PR body 中给出了完整的根因解释:同测试在不同 runner 上通过/失败的原因在于 coredump 生成是否发生,而 stdout 中的 FAIL 行始终存在,因此应以 FAIL 行为准而非退出码。commit 记录中提交了“trim comment”和清理死代码的独立提交,说明作者在合并 main 后主动清理了相关测试文件。

实现拆解

1. 根因分析

  • test/registered/utils/test_phase_checker.pytest_update_mismatch_after_enable_raises_in_subprocess 中,原逻辑要求子进程退出码为 (0, -6)。但 CI 的 coredump 机制会引入额外退出路径,且 torch.cuda.synchronize() 的错误措辞不唯一,导致断言不稳定。

2. 修改核心断言

  • 保留 stdout 中 SimplePhaseChecker FAIL 行作为“内核侧 assert 已触发”的唯一证据。
  • assertIn(result.returncode, (0, -6)) 改为 assertNotEqual(result.returncode, 1),因为退出码 1 表示子进程明确报告“没有抛 RuntimeError”,即 assert 从未触发。
  • 失败信息中加入 returncode,便于后续诊断。

3. 清理相关测试文件

  • test/registered/vlm/test_vision_openai_server_a.py:将通配符导入 from sglang.test.vlm_utils import * 替换为显式导入 DEFAULT_TIMEOUT_FOR_SERVER_LAUNCHDEFAULT_URL_FOR_TESTpopen_launch_serverIMAGE_MAN_IRONING_URL,并删除未使用的 OmniOpenAITestMixin 导入及删除列表中的对应项,避免通配符泄漏和隐式依赖。
  • test/registered/models_e2e/test_qwen3_next_models.py:删除两个测试类中未使用的 gsm8k_accuracy_thres = 0.93 配置,保留 kl_div_thres 作为有效阈值。

4. 验证

  • 通过 /rerun-test test/registered/utils/test_phase_checker.py1-gpu-5090 runner 上重跑,结果 ✅。
  • 无产品代码、配置或部署配套改动,全部变更集中在测试目录。
文件 模块 状态 重要度
test/registered/utils/test_phase_checker.py 阶段检查 modified 5.2
test/registered/vlm/test_vision_openai_server_a.py 视觉服务 modified 4.24
test/registered/models_e2e/test_qwen3_next_models.py 模型端到端 modified 3.63

关键符号

test_update_mismatch_after_enable_raises_in_subprocess

关键源码片段

test/registered/utils/test_phase_checker.py test-stability

核心修改:将设备端 assert 触发的判据从子进程退出码改为 stdout 中的 FAIL 行,并保留退出码不为 1 的最低约束,修复 CI 上的 flaky 失败。

def test_update_mismatch_after_enable_raises_in_subprocess(self) -> None:
    """在子进程中运行,因为设备端 assert 会污染 CUDA 上下文。"""
    script = textwrap.dedent("""
        import sys
        import torch
        from sglang.srt.utils.phase_checker import SimplePhaseChecker        device = torch.device("cuda:0")
        checker = SimplePhaseChecker(initial_phase=0, device=device)
        checker.enable_assert()
        # phase=0 但期望 phase=99 —— 内核必须触发 device_assert。
        checker.update(expect_phase=99, next_phase=1, caller_name="bad")
        try:
            torch.cuda.synchronize()
        except RuntimeError as e:
            msg = str(e).lower()
            if "device-side assert" in msg or "phase mismatch" in msg:
                sys.exit(0)
            print(f"Unexpected RuntimeError: {e}", file=sys.stderr)
            sys.exit(2)
        print("expected RuntimeError but none was raised", file=sys.stderr)
        sys.exit(1)
        """)
    result = subprocess.run(
        [sys.executable, "-c", script],
        capture_output=True,
        text=True,
        timeout=180,
    )
    # FAIL 行是内核侧检查已触发的证据;进程如何退出则不是:
    # CI 开启 SGLANG_CUDA_COREDUMP=1 时 coredump handler 可能 abort 进程,
    # 而同步时可能抛出 cudaErrorAssert 或泛化的 cudaErrorLaunchFailure。
    # 只有 exit code 为 1 才意味着 assert 从未触发。
    self.assertIn(
        "SimplePhaseChecker FAIL",
        result.stdout,
        f"returncode {result.returncode}; "
        f"stdout: {result.stdout}\nstderr: {result.stderr}",
    )
    self.assertNotEqual(
        result.returncode,
        1,
        "the mismatch did not fire device_assert; "
        f"stdout: {result.stdout}\nstderr: {result.stderr}",
    )

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

主要风险是断言放宽可能引入漏报:如果 coredump handler 在打印 SimplePhaseChecker FAIL 行之前就 abort,stdout 将缺失该行,测试会误判为失败(false negative),但这种情况原本也会失败,因此可接受。另一个隐患是测试依赖 CI 环境变量 SGLANG_CUDA_COREDUMP 的行为,本地或非 CI 环境可能无法复现原始 flaky,但这不影响测试通过性。此外,若未来 stdout 输出策略变化(如缓冲导致 FAIL 行未 flush),同样可能导致误判,但目前没有迹象。

影响范围限于 CI 测试稳定性:修复了 base-b-test-1-gpu-small (3) 等在 phase-checker 上的间歇性失败,降低开发者在 main 分支看见红色 CI 的概率。对用户无感知,团队 CI 可靠性得到提升。清理导入和死代码也减少了测试文件对隐式通配符导入的依赖,提升可维护性。

违反退出码断言放宽 额外依赖 FAIL 行 stdout 输出 CI 环境变量假设 清理导入可能影响其他测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论