Prhub

#29925 ci: make multi-GPU jit test hangs attributable from the CI log

原始 PR 作者 alisonshao 合并时间 2026-07-08 10:56 文件变更 2 提交数 1 评论 2 代码增减 +12 / -1

执行摘要

为多 GPU 测试添加超时可归因诊断

CI nightly测试中 test_custom_all_reduce.py 在 nproc=2 时挂起,600秒后被杀,但日志中没有任何证据表明哪个测试挂起或在哪里挂起。pytest的块缓冲进度点混乱地刷入下一个测试的输出,被杀前也没有捕获堆栈。

该 PR 是基础设施层面的改进,值得相关测试维护者关注。建议后续跟进 review 中提到的 timeout=None 和整数除法问题,考虑采用更安全的条件判断。

讨论亮点

gemini-code-assist[bot] 指出:如果 timeout 显式设为 None(禁用超时),dump_after 仍会回退为 300 秒,导致即使不需要超时也会在 5 分钟后 dump 回溯。另外,整数除法(//)在很小的超时值(如 1)下会使 dump_after 为 0,从而禁用 faulthandler 超时。建议改用浮点数除法(/)并在 timeout is None 时跳过 faulthandler。注意:该评论在最终代码中未采纳,当前实现仍使用整数除法和 None 回退。

实现拆解

  1. mp.py 中设置子进程无缓冲输出:在 multigpu_launch 函数中,在环境变量设置阶段添加 os.environ.setdefault("PYTHONUNBUFFERED", "1"),确保 torchrun 子进程的 pytest 进度输出实时到达 CI 日志,避免超时被杀时输出丢失或乱序。
  2. utils.py 中启用 faulthandler_timeout:在 multigpu_pytest_maininner 函数中,在调用 pytest.main 时传递 -o faulthandler_timeout=<dump_after>,其中 dump_after = timeout // 2(如果 timeout 为 None 则默认 300 秒)。当单个测试超过该阈值时,pytest 的 faulthandler 会 dump 所有线程的堆栈(stderr 不会被重定向),而不杀死运行,从而在外部超时前显示挂起的测试名和完整堆栈。
文件 模块 状态 重要度
python/sglang/jit_kernel/mp.py JIT 启动 modified 4.9
python/sglang/jit_kernel/tests/utils.py 测试工具 modified 4.81

关键符号

multigpu_launch multigpu_pytest_main

关键源码片段

python/sglang/jit_kernel/mp.py core-logic

核心变更:在多 GPU 启动函数中设置 `PYTHONUNBUFFERED=1`,确保子进程标准输出无缓冲,避免日志乱序。

# python/sglang/jit_kernel/mp.py ( 片段 )
    os.environ[env_key] = "1"
    os.environ[pid_key] = str(os.getpid())
    os.environ.setdefault("OMP_NUM_THREADS", "1")
    os.environ.setdefault("GLOO_SOCKET_IFNAME", "lo") # single-machine setup
    # Unbuffered child stdout: when a worker is killed on timeout, pytest's
    # block-buffered progress output is otherwise lost or flushed out of
    # order into the CI log, making it impossible to tell which test hung.
    os.environ.setdefault("PYTHONUNBUFFERED", "1")
    signal.signal(signal.SIGINT, signal.default_int_handler)
python/sglang/jit_kernel/tests/utils.py test-coverage

测试框架入口:添加 pytest faulthandler_timeout 配置,在测试挂起时自动 dump 堆栈。

# python/sglang/jit_kernel/tests/utils.py ( 片段 )
    def inner() -> int:
        # CI's run_unittest_files invokes `python3 <file> -f` (legacy
        # unittest failfast). Translate to pytest's `-x` so it survives.
        pytest_args = ["-x" if a == "-f" else a for a in sys.argv[1:]]
        # Dump all thread stacks (every rank; stderr is not redirected) if a
        # single test exceeds half the harness budget, so a hung collective
        # is attributable from the CI log before the outer timeout kills the
        # process group. Non-fatal: the test keeps running after the dump.
        dump_after = (timeout // 2) if timeout else 300
        return pytest.main(
            [file, "-o", f"faulthandler_timeout={dump_after}"] + pytest_args
        )

评论区精华

timeout=None 时 faulthandler 仍被启用 正确性

gemini-code-assist[bot] 指出:如果 `timeout` 显式设为 `None`,`dump_after` 仍回退为 300 秒,导致即使不需要超时也会在 5 分钟后 dump 回溯。另外,整数除法在小超时值下会使 `dump_after` 为 0,禁用 faulthandler。

结论:未采纳。当前实现仍使用整数除法和 None 回退。 · unresolved

风险与影响

  • timeout=None 时,dump_after 默认 300 秒,可能在不期望超时的场景中意外触发 faulthandler dump,产生过多日志。
  • 整数除法在 timeout=1 时导致 dump_after=0,禁用 faulthandler,但此类场景极少。
  • PYTHONUNBUFFERED 可能带来少量性能开销,但在测试场景中可接受。
  • 该改动仅影响 JIT kernel 测试框架,不影响生产路径。

影响范围限于 sglang/jit_kernel 的多GPU测试。在 CI 中,超时测试将自动 dump 堆栈,极大降低排查挂起问题的难度。对用户无影响。

未解决 review 意见 : timeout=None 行为 整数除法边缘情况

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论