Ray 分布式训练 Hang 问题自动排查

SkillProductivity

Automatically troubleshoot hang issues in Ray-scheduled distributed training jobs. Use when a training job is unresponsive, resource utilization is abnormal, or the job shows no progress for a long time. Automatically collects cluster state, task call stacks, and Actor states, analyzes blocking chai

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Ray 分布式训练 Hang 问题自动排查 skill

What this skill tells your AI

The instructions your AI receives, as published by redai-studio/relax in skills/debug-hang/SKILL.md and read by ahel’s review.

排查流程

Phase 1: 集群状态概览

目标: 确认集群健康状态和资源使用情况

ray status --address <address>
ray job list --address="<address>" | grep RUNNING

关注指标: 节点存活、CPU/GPU 使用率(异常低 → hang)、Pending resource demands、Object store 内存。

Phase 2: 定位阻塞 Tasks

ray list tasks --address="<address>" --filter "JOB_ID=<job_id>" --filter "state=RUNNING" --format yaml

关键字段: name(业务逻辑)、actor_idworker_pid(调用栈用)、node_id(py-spy 必须在正确节点执行)。

⚠️ 返回 No resource in the cluster / 空列表是常态,不是错误。Actor 内部 await / time.sleep / dist.barrier 等阻塞不会显示为 RUNNING task — actor 主线程一直停在 worker.main_loop,业务逻辑跑在后台线程或 asyncio coroutine 里。不要继续试 state=SUBMITTED_TO_WORKER / PENDING_NODE_ASSIGNMENT 等其他 state,直接跳到 Phase 4 拉 actor 列表 + Phase 3 对 actor PID 跑 py-spy。

Phase 3: 收集调用栈

重要: py-spy dump --pid <pid> 必须在目标进程所在的节点上执行。

# 列出所有节点
ray job submit --working-dir "./" --address="<address>" -- \
  python scripts/tools/run_on_each_ray_node.py --list

# 在指定节点执行 py-spy(推荐)
ray job submit --working-dir "./" --address="<address>" -- \
  python scripts/tools/run_on_each_ray_node.py -n <node_id_or_ip> "py-spy dump --pid <pid>"

# 在所有 GPU 节点执行(单节点集群适用)
ray job submit --working-dir "./" --address="<address>" -- \
  python scripts/tools/run_on_each_ray_node.py "py-spy dump --pid <pid>"

重点关注: 主线程阻塞点、后台线程状态、[Has the GIL] 标记。

⚠️ 反模式(实际踩过的坑)
反模式现象正确做法
本地 py-spy dump --pid <remote_pid>Error: No such file or directory (os error 2)PID 来自远端 actor 的 worker_pid,必须通过 ray job submit + run_on_each_ray_node.py -n <node_id> 在对应节点执行
RAY_ADDRESS=... python scripts/tools/run_on_each_ray_node.py --list启了新的本地 Ray 实例,看不到目标集群run_on_each_ray_node.py 内部 ray.init() 不带 address,env var 不生效;必须 ray job submit --address="<addr>" -- python scripts/tools/run_on_each_ray_node.py --list
ray job submit -- bash -c 'for pid in 1 2 3; do py-spy --pid $pid; done'py-spy 收到空的 --pid$pid 被 ray 的引号嵌套吃掉把循环写到 pyspy_dump.sh 文件,再 ray job submit --working-dir "./" -- bash pyspy_dump.sh(脚本随 working-dir 一起上传)
一个 PID 一个 ray job submit每次 ~30-60s 启动开销 × N 个 PID写一个脚本文件循环 dump 所有 PID,单次 ray job submit 跑完

Phase 4: Actor 依赖链分析

ray list actors --address="<address>" --filter "JOB_ID=<job_id>" --filter "STATE=ALIVE" --format yaml

分析维度: 数据流方向(生产者→消费者)、调用关系(parent_task_id→task_id)、资源竞争。

Phase 5: 阻塞模式匹配

模式调用栈特征排查方向
数据等待time.sleep 在迭代器/队列中上游数据生产者是否工作
分布式同步dist.broadcast, dist.all_reduce, dist.barrier所有 rank 是否到达同步点
条件等待while True: if condition: break; sleep条件是否有机会满足
资源竞争锁/信号量等待是否存在死锁
远程调用阻塞ray.get 等待被调用方是否响应
网络 I/Osocket read/write对端是否存活

Phase 6: 根因定位

  1. 追溯阻塞链: 从阻塞点向上游追溯(数据等待→生产者状态,分布式同步→其他 rank 状态,条件等待→条件设置逻辑)
  2. 状态机验证: 初始状态是否正确、状态转换条件是否可达、是否存在状态机死锁
  3. 配置一致性: 条件分支依赖的配置项、默认值与预期值的差异

自动化诊断脚本

#!/bin/bash
# scripts/tools/diagnose_ray_hang.sh
set -e

RAY_ADDRESS="${1:-$RAY_ADDRESS}"
OUTPUT_DIR="${2:-/tmp/ray_hang_diag_$(date +%Y%m%d_%H%M%S)}"
mkdir -p "$OUTPUT_DIR"

echo "=== Phase 1: Cluster Status ===" | tee "$OUTPUT_DIR/01_cluster.txt"
ray status --address "$RAY_ADDRESS" 2>&1 | tee -a "$OUTPUT_DIR/01_cluster.txt"

echo -e "\n=== Phase 2: Running Jobs ===" | tee "$OUTPUT_DIR/02_jobs.txt"
ray job list --address="$RAY_ADDRESS" 2>&1 | tee "$OUTPUT_DIR/02_jobs.txt"

JOB_ID=$(grep -oP "job_id='\K[^']+" "$OUTPUT_DIR/02_jobs.txt" | head -1)
[ -z "$JOB_ID" ] && echo "No running job found" && exit 1
echo "Target Job ID: $JOB_ID" | tee -a "$OUTPUT_DIR/02_jobs.txt"

echo -e "\n=== Phase 3: Running Tasks ===" | tee "$OUTPUT_DIR/03_tasks.txt"
ray list tasks --address="$RAY_ADDRESS" --filter "JOB_ID=$JOB_ID" --filter "state=RUNNING" --format yaml 2>&1 | tee "$OUTPUT_DIR/03_tasks.txt"

echo -e "\n=== Phase 4: Active Actors ===" | tee "$OUTPUT_DIR/04_actors.txt"
ray list actors --address="$RAY_ADDRESS" --filter "JOB_ID=$JOB_ID" --filter "STATE=ALIVE" --format yaml 2>&1 | tee "$OUTPUT_DIR/04_actors.txt"

echo -e "\n=== Phase 5: Stack Traces ===" | tee "$OUTPUT_DIR/05_stacks.txt"
awk '
  /node_id:/ { node=$2 }
  /worker_pid:/ { pid=$2; print pid, node }
' "$OUTPUT_DIR/03_tasks.txt" | while read PID NODE_ID; do
    echo -e "\n--- PID $PID (node: $NODE_ID) ---" | tee -a "$OUTPUT_DIR/05_stacks.txt"
    ray job submit --working-dir "./" --address="$RAY_ADDRESS" -- \
        python scripts/tools/run_on_each_ray_node.py -n "$NODE_ID" "py-spy dump --pid $PID" 2>&1 | tee -a "$OUTPUT_DIR/05_stacks.txt"
done

echo -e "\n=== Diagnosis Complete ==="
echo "Output saved to: $OUTPUT_DIR"

输出报告模板

## 集群状态摘要
- 活跃节点: X / Y
- GPU 使用率: Z%
- 异常信号: ...

## 阻塞 Task 分析
| Task Name | PID | 阻塞位置 | 模式分类 |
|-----------|-----|----------|----------|

## 阻塞链条
Actor A (阻塞于条件 X)
  ↑ 等待
Actor B (阻塞于数据 Y)
  ↑ 等待
Actor C (空闲,未生产数据 Y) ← 根因

## 根因诊断
- 主要原因 / 触发条件 / 影响范围

## 修复建议

关键排查原则

  1. 从现象到本质: GPU 低利用率 → 阻塞 Task → 阻塞代码行 → 阻塞原因
  2. 追溯数据流: 消费者阻塞 → 检查生产者 → 检查上游
  3. 验证假设: 提出假设 → 检查相关状态 → 确认或否定
  4. 最小化改动: 定位根因后,用最小改动修复
  5. Ray address 默认端口: 用户给出的 RAY_ADDRESS 若未显式指定端口,按 6379 处理(如 x.x.x.xx.x.x.x:6379

常见踩坑速查(实战教训)

下表汇总了实际 debug 中浪费时间的反模式,遇到对应现象立即跳到「正确做法」,不要重复试错。

阶段错误做法现象正确做法
Phase 2state=RUNNING 返回空 → 继续试 SUBMITTED_TO_WORKER / PENDING_NODE_ASSIGNMENT一直查不到 taskactor-internal hang(await/sleep/barrier)不显示为 RUNNING task,直接跳 Phase 4 拉 actors,对 actor PID 跑 py-spy
Phase 3本地 py-spy dump --pid <remote_pid>Error: No such file or directoryPID 是远端的,必须 ray job submit + run_on_each_ray_node.py -n <node_id>
Phase 3RAY_ADDRESS=... python scripts/tools/run_on_each_ray_node.py --list启了新本地 Ray,看不到目标集群该脚本内部 ray.init() 不带 address,env var 不生效;必须 ray job submit --address="<addr>" -- python ...
Phase 3ray job submit -- bash -c 'for pid in 1 2 3; do py-spy --pid $pid; done'py-spy 收到空 --pid,输出乱$pid 被 ray 的引号嵌套吞了;写脚本文件 pyspy_dump.sh 然后 ray job submit --working-dir "./" -- bash pyspy_dump.sh
Phase 3一个 PID 一次 ray job submit × N每次 30-60s 启动开销写循环到 .sh 脚本里,单次 submit 跑完所有 PID

参考案例

案例描述
case-rollout-eval-onload-hang.mdRollout eval 等待 onload 状态导致 hang(配置与逻辑不匹配)

Signals

GitHub stars
601
Forks
150
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
debug-hang
Source
github.com/redai-studio/relax