用进程栈定位训练卡住或变慢
进程栈采集用于查看训练任务在某个时间点停在哪些函数、线程和 rank 上。它适合排查任务长时间无进展、疑似 Hang、首个 batch 很久不动、分布式训练只有部分 rank 卡住等问题。
进程栈更像一张现场截图,不会自动给出根因。排查时先看哪些 Worker / rank 状态相似,再结合日志、资源监控、checkpoint 时间和通信错误判断下一步。
何时采集进程栈
任务必须处于运行状态才能采集进程栈。遇到以下情况时,优先采集:
- 训练日志、step、loss 或 checkpoint 长时间没有更新。
- GPU 利用率明显下降,但任务没有退出。
- 多 Worker 任务中只有部分 Worker 或 rank 无进展。
- 任务被 Hang 检测判定为挂起。
- 分布式训练疑似停在数据读取、checkpoint 保存、barrier、allreduce 或 NCCL 通信附近。
如果任务已经失败退出,进程栈通常无法再采集。此时先看任务日志、事件和容错日志,详见故障排查。
采集进程栈
可以在 Hang 发生时自动采集,也可以在任务运行中手动采集。自动采集适合历史上出现过 Hang 的训练;手动采集适合您已经发现任务卡住或变慢,想立即看当前状态。
开启自动采集
在创建任务、改配任务或克隆任务时,按以下步骤开启自动采集:
- 在 容错 配置区域,开启 Hang 检测。
- 在 进程栈采集 配置区域,将开关切换为开启状态。
警告
进程栈自动采集依赖 Hang 检测。如果未开启 Hang 检测,平台不会在任务挂起时自动采集进程栈。
手动采集进程栈
无论是否开启自动采集,都可以在任务运行中手动触发采集:
- 进入 任务详情 页。
- 点击 进程栈采集 标签。
- 点击 手动采集。
- 等待采集完成。单次采集通常持续数秒到几十秒。
如果任务状态变化较快,可以间隔一段时间采集两次,对比同一 rank 是否一直停在同一类调用路径上。
查看采集结果
采集结果保存在任务详情页的 进程栈采集 标签中,保留 30 天。
该标签包含三个子标签:
- 报告:汇总整任务范围内各 Worker / rank 的函数调用统计。
- 进程栈:按线程展示原始调用栈。
- NCCL 状态:展示 NCCL RAS Server 返回的通信状态。NCCL RAS 需要镜像中的 NCCL 版本为 2.24 或更高;版本过低时,该页可能无法连接 RAS 服务。
解读分析报告
先看 报告。报告会把整任务的栈信息聚合成 Stack Trace Analysis Report,便于快速比较不同 Worker / rank 是否停在相似位置。
报告展示的是本次采集捕捉到的进程和线程,不一定就是正在推进训练 step 的主训练循环。训练仍在输出 epoch、loss 或 checkpoint 时,报告中的等待栈可能只是后台 helper 进程或线程处于空闲状态。
确认采集时间和 Print ID
Summary 中的 Job ID、Analysis Time 和 Print ID 用于确认这份报告来自哪次任务和哪次采集。联系平台支持时,优先提供 Job ID 和 Print ID。
查看函数调用统计
Function Call Statistics 是最值得先看的部分。阅读时重点看三件事:
Function Name:函数名和路径。路径在/mnt、项目目录或训练框架源码中时,通常更接近训练代码;路径在 PyTorch、CUDA、NCCL、Python runtime 或系统库中时,通常需要结合上层调用判断。Call Count:该函数在采集样本中出现的次数。次数高表示它频繁出现在本次栈快照里,不一定表示它就是根因。Sources:该函数出现在哪些 Worker / rank 上。多 rank 同时出现在同一类函数里,说明它们当时状态相似;只有个别 rank 出现时,优先对比这些 rank 的日志、数据读取和资源监控。
例如:
Stack Trace Analysis Report
Summary
Job ID: jo-***
Analysis Time: 2026-06-08 03:49:05 (UTC)
Print ID: 1780890495107774003
Function Call Statistics
main /opt/conda/lib/python3.11/site-packages/torch/_inductor/compile_worker/main.py:41
Call Count: 2
Sources: gpu31-rank1, gpu31-rank0
get /opt/conda/lib/python3.11/multiprocessing/queues.py:102
Call Count: 2
Sources: gpu31-rank1, gpu31-rank0
_multiprocessing_SemLock_acquire_impl .../Modules/_multiprocessing/semaphore.c:352
Call Count: 2
Sources: gpu31-rank1, gpu31-rank0这个例子里,gpu31-rank0 和 gpu31-rank1 都出现在 PyTorch Inductor 编译 worker、Python multiprocessing 队列和信号量等待路径中。它说明本次采集捕捉到两个 rank 的相关后台进程或线程都在等待类调用中;它不能单独证明训练主逻辑、NCCL 通信或 GPU 硬件已经出错。
如果您看到的是以下模式,可按对应方向继续查:
- 所有 rank 都停在同一段用户训练代码:看该阶段是否正在加载数据、评估、保存 checkpoint 或执行一次性编译。
- 一个 rank 在数据读取或预处理,其他 rank 在
barrier、all_reduce、wait等同步调用:优先检查慢 rank 的数据路径、CPU、存储 I/O 和日志。 - 多个 rank 同时出现 NCCL、CUDA stream 或 collective 相关调用:继续查看 NCCL 日志、RDMA 配置、网络规格和资源监控。
- 报告里主要是
sleep、Queue.get、SemLock、condition_variable等等待路径:继续看训练日志是否仍在推进。等待线程本身不一定异常,关键是主训练进程是否也没有进展。 - 报告里主要是
torch/_inductor/compile_worker路径,但训练日志仍在按 epoch 或 step 前进:通常说明采集到了 PyTorch Inductor 后台编译 worker 的空闲状态,不代表训练卡住。
查看进程栈详情
当报告只能说明“哪些函数出现得多”,但还不能说明“具体哪个线程在等什么”时,打开 进程栈。
按 Worker 和 GPU 对比
在 进程栈 标签中,使用筛选器选择特定 Worker 和 GPU。例如选择 worker-0-gpu-0,查看该 GPU 对应进程的栈跟踪。
排查分布式训练时,不要只看一个 rank。至少对比一个“疑似卡住的 rank”和一个“看起来正常的 rank”。如果多个 rank 的主线程栈完全不同,差异通常比单个栈更有价值。
查看线程和栈帧
原始数据通常以 JSON 数组展示。先看以下字段即可:
thread_name:线程名称,例如MainThread、QueueFeederThread。active:线程是否处于活跃状态。owns_gil:该线程是否持有 Python GIL。frames.name:函数名或原生符号名。frames.filename:源码文件、二进制文件或动态库路径。frames.line:源码行号。原生库或无法映射源码的栈帧可能显示为0。
简化示例:
[
{
"thread_name": "MainThread",
"active": false,
"owns_gil": false,
"frames": [
{
"name": "get",
"filename": "/opt/conda/lib/python3.11/multiprocessing/queues.py",
"line": 102
},
{
"name": "main",
"filename": "/opt/conda/lib/python3.11/site-packages/torch/_inductor/compile_worker/main.py",
"line": 41
}
]
}
]这类栈说明该线程当时在 multiprocessing 队列上等待。它是否异常,要看训练日志是否停在同一时间、其他 rank 是否也在等待、GPU 是否仍有计算活动。
信息
原始 JSON 字段可能随平台版本、镜像环境和采集方式变化。不要把当前字段当成稳定接口;自动化处理时请按实际输出做兼容。需要查看完整字段含义时,见附录:进程栈原始 JSON 字段。
查看 NCCL 状态
NCCL 状态 标签依赖 NCCL RAS Server,适合辅助判断分布式通信问题。NCCL RAS 是 NCCL 2.24 及以上版本提供的能力;如果镜像中的 NCCL 低于 2.24,平台无法从该任务中取得 NCCL RAS 状态。
这不影响 报告 和 进程栈 的采集,但会影响平台展示 NCCL 通信器状态的能力。也就是说,您仍然可以看到 rank 停在哪些函数或线程上,但可能看不到 NCCL RAS 提供的 communicator、rank 缺失、DEAD、INCOMPLETE 等通信状态。
先在 Worker 中确认 NCCL 版本:
env | grep -E 'NCCL|NV_LIBNCCL' || true
python3 - <<'PY'
import torch
print("torch:", torch.__version__)
print("cuda:", torch.version.cuda)
print("nccl:", torch.cuda.nccl.version() if torch.cuda.is_available() else None)
PY
python3 -m pip show nvidia-nccl-cu12 || true如果输出中显示 NCCL_VERSION=2.21.5-1、libnccl2=2.21.5-1+cuda12.4 等低于 2.24 的版本,NCCL 状态 不可用是预期结果。此时继续用训练日志、进程栈和资源监控判断训练是否真的发生通信问题。
信息
PyTorch 训练镜像中的 NCCL 版本通常跟随 PyTorch/CUDA 依赖组合。使用 pip 安装 PyTorch 时,NCCL 可能来自 nvidia-nccl-cu12;更换 PyTorch 版本或 CUDA wheel 版本,也可能改变训练进程实际加载的 NCCL。需要 NCCL RAS 时,优先选择包含 NCCL 2.24 或更高版本的 PyTorch/CUDA 组合,并在任务中确认 torch.cuda.nccl.version()。不建议在已有 PyTorch 环境中临时单独替换 NCCL 库;确实需要替换时,应在镜像构建阶段固定版本,并跑通最小分布式通信检查。
在容器内手动查询 NCCL RAS
如果您已经登录到开发机容器或任务 Worker,也可以在容器内尝试查询 NCCL RAS。该查询只在 NCCL 版本为 2.24 或更高、训练进程已经初始化 NCCL、RAS 未被关闭,并且查询发生在 NCCL 任务仍在运行时才有意义。
command -v ncclras || true
ncclras -h localhost -p 28028 || true
echo status | nc -w 5 localhost 28028 || true
echo "verbose status" | nc -w 5 localhost 28028 || true如果这些命令连接失败,先不要直接判断为 NCCL 通信失败。常见原因是镜像中的 NCCL 低于 2.24、训练还没有进入 NCCL 通信阶段、任务已经结束,或 RAS 被关闭。只有在 NCCL 版本满足要求、训练正在运行且日志中已经出现 NCCL 初始化之后,连接失败才需要继续排查 RAS 监听地址或平台采集链路。
查看可用的 NCCL 状态
NCCL 状态可用时,通常会看到版本、任务摘要、通信器状态和错误信息。例如:
NCCL version 2.26.3 compiled with CUDA 12.9
CUDA runtime version 12090, driver version 12080
Nodes Processes GPUs per process Processes (total) GPUs (total)
1 8 1 8 8
Group Comms Nodes Ranks Ranks Ranks Status Errors
# in group per comm per node per comm in group
0 1 1 7 8 7 RUNNING INCOMPLETE
DEAD
1 job process is considered dead (unreachable via the RAS network)重点看:
Nodes、Processes和GPUs是否符合本次任务配置。Status是否为RUNNING或INIT。Errors中是否出现DEAD、INCOMPLETE等异常。- 错误指向的 rank、进程或节点,是否与日志和资源监控中的异常对象一致。
NCCL 状态不可用时继续查日志
如果看到以下输出,先检查镜像中的 NCCL 版本是否低于 2.24:
Connecting to ::1:28028: Connection refused
Connecting to 127.0.0.1:28028: Connection refused
Failed to connect to the NCCL RAS service!
Please make sure that the NCCL job has the RAS service enabled and that
the RAS client was started on a node where the NCCL job is running.如果 NCCL 版本低于 2.24,这类输出通常说明镜像内 NCCL 不支持 RAS,平台无法连接到 RAS 服务。它不是 NCCL 通信失败的证据,也不是因为单机多卡训练天然无法使用 NCCL。
如果 NCCL 版本已经是 2.24 或更高,这类输出才需要继续排查 RAS 是否被关闭、任务是否尚未初始化 NCCL、采集时 NCCL communicator 是否已经销毁,或平台采集端是否无法访问 RAS 监听地址。
遇到这种情况,继续用以下信号判断:
- 任务日志中是否有 NCCL、RDMA、
all_reduce、barrier或 timeout 报错。 - 启动命令是否为分布式训练正确传入 rank、进程数和主节点地址。
- 资源监控中网络、GPU 利用率和显存是否符合预期。
- 进程栈中是否只有部分 rank 停在通信调用,另一些 rank 停在数据读取或 checkpoint 保存。
避免误判
进程栈能缩小范围,但单次采集很容易被误读。排查时注意以下边界:
- 看到等待函数,不代表训练一定卡死。数据加载线程、日志线程、编译 worker 和后台队列本来就可能等待。
- 看到 NCCL 状态页连接失败,不代表 NCCL 通信失败。先确认镜像中的 NCCL 是否为 2.24 或更高,再看训练日志和进程栈。
- 看到所有 rank 状态相似,不一定是分布式通信问题。也可能是模型编译、同步 checkpoint、评估阶段或共享数据准备。
- 看到某个 rank 独有的异常路径,比只看全局调用次数更重要。优先对比 rank 之间的差异。
- 单次采集只能说明一个时间点。卡住时间较长时,间隔一段时间再采集一次更可靠。
进程栈采集对训练任务的 GPU 算力占用通常很小。手动采集瞬间可能让任务进程短暂停顿;对延迟极度敏感的任务,请在确认需要时再触发。
联系支持前准备信息
进程栈数据保留 30 天。页面暂不提供直接下载,可使用 复制 将报告或关键原始栈保存到本地。
联系平台支持前,建议准备以下信息:
- 任务 ID、采集时间和
Print ID。 - 报告 的 Markdown 内容。
- 疑似异常 rank 和对照 rank 的 进程栈 片段。
- 同一时间段的训练日志、容错日志和资源监控截图。
- NCCL 状态 是否可用;如果不可用,提供连接失败输出。
附录:进程栈原始 JSON 字段
通常不需要逐个理解所有 JSON 字段。只有在对比多个 rank、编写内部分析脚本,或需要把原始数据发给平台支持时,再查看本节。
进程栈原始数据通常以 JSON 数组展示,每个元素对应一个线程的采集结果。以下示例用于说明字段形态,实际输出可能随平台版本、镜像环境和采集方式变化。
[
{
"pid": 4250,
"thread_id": 140200437851456,
"thread_name": "MainThread",
"os_thread_id": 4250,
"active": true,
"owns_gil": false,
"frames": [
{
"name": "cuStreamSynchronize",
"filename": "/usr/local/cuda/lib64/libcuda.so",
"module": "/usr/local/cuda/lib64/libcuda.so",
"short_filename": "libcuda.so",
"line": 0,
"locals": null,
"is_entry": true,
"is_shim_entry": true
},
{
"name": "train_step",
"filename": "/mnt/public/project/training.py",
"module": null,
"short_filename": "training.py",
"line": 1241,
"locals": null,
"is_entry": false,
"is_shim_entry": false
}
],
"process_info": null
}
]线程级字段:
pid:进程 ID。thread_id:线程 ID。thread_name:线程名称,例如MainThread、QueueFeederThread。部分线程名称可能为空。os_thread_id:操作系统线程 ID。active:线程是否处于活跃状态。owns_gil:线程是否持有 Python GIL。frames:该线程的调用栈帧数组。process_info:进程级附加信息。当前可能为空或为null。
栈帧字段:
name:函数名、原生符号名或原始地址。少数情况下可能为空。filename:源码文件、二进制文件或动态库路径。module:帧所属模块或动态库路径。部分 Python 源码帧可能为null。short_filename:简化后的文件名或路径片段。line:源码行号。系统库、CUDA、NCCL 或其他无法映射源码的原生帧可能显示为0。locals:局部变量信息。当前可能为空或为null。is_entry:该帧是否被识别为入口帧或边界帧。is_shim_entry:该帧是否属于桥接层、封装层或兼容层入口帧。
阅读原始 JSON 时,优先把字段放回训练上下文中判断。active: false、line: 0、locals: null 或系统库路径本身不代表异常;它们只说明采集当时该线程或栈帧的状态。