版本信息最后核对:2026-10-25
分布式训练卡死:先定位最后一个完成 step 和缺失 rank,再判断初始化、通信、慢节点还是数据问题。
训练启动后进程一直不退出,日志停在 init_process_group、某个 step 的集合通信、checkpoint 保存或数据加载处;GPU 利用率可能是零,也可能有个别卡仍在空转。Kubernetes 里 Pod 长期 Running 却不完成,Job 不重启也不失败;也可能某个 rank 已因 OOM、CUDA 错误或节点问题退出,其余仍在等待。
框架最终往往会抛出超时或通信错误:
Watchdog caught collective operation timeout
NCCL error: unhandled system error
Remote process exited or there was a network error
ProcessGroupNCCL aborted
还有一种误判:任务没死,只是某个 rank 特别慢,整体吞吐接近停滞。因此第一步不是改超时或加 NCCL 环境变量,而是建立时间线:最后完成的 step、缺失的 rank、卡住的调用。
先收集所有 rank 的日志,而不是只看 rank 0:
# Kubernetes:按 Pod 和容器收集,保留时间戳
kubectl get pods -o wide
kubectl logs <pod> -c <container> --timestamps --tail=200
kubectl describe pod <pod>
# 裸机或容器:抓取卡住进程的 Python 栈
py-spy dump --pid <PID>
# GPU、拓扑、网络和共享内存
nvidia-smi
nvidia-smi topo -m
ibstat
df -h /dev/shm
把各 rank 的最后一条 step 日志按时间排序,得到“最后一个完成 step”和“缺失 rank 集合”。再回答四个问题:
MASTER_ADDR、端口、主机名解析、防火墙和 rendezvous;训练中卡住,查网络、慢节点、GPU 故障和 rank 提前退出。| 线索 | 更可能的方向 |
|---|---|
卡在 init_process_group |
会合地址、端口、DNS、防火墙、rank 数 |
| 所有 rank 停在同一个 collective | 通信链路、NCCL、交换机、超时 |
| 缺少一个 rank,其余 rank 等待 | 该进程 OOM、崩溃、被驱逐或未启动 |
| 个别 rank 每步都慢 | 数据加载、CPU、NUMA、坏卡、降频 |
| step 时间周期性尖峰 | checkpoint、数据分片、共享存储 |
| 只在容器或 Kubernetes 复现 | 设备插件、CNI、共享内存、资源限制 |
MASTER_ADDR 指向其他节点不可达的地址、端口被占用、主机名在不同节点解析结果不同、防火墙阻断会合端口、node_rank 重复或缺失,都会让进程组建立卡住。弹性训练的 rendezvous 配置错误或旧成员未清理,也会让新成员一直等待;容器网络命名空间或代理变量错误同样会阻断会合。
某个 rank 因为条件分支、异常捕获或提前退出,没有进入同一个 collective,其他 rank 就会永远等待。变长序列导致各 rank 的 collective 次数不同、最后一批数据被跳过、某个 rank 在 NaN 检查后跳过 backward、模型并行分支不一致,都会造成调用序列错位。find_unused_parameters 配置不当或动态图路径变化,也会让 DDP 的通信次数不一致。
OOM Killer、CUDA 错误、GPU Xid、节点驱逐、容器被杀、磁盘写满、数据加载阻塞,都会让一个 rank 消失,其余 rank 停在集合通信。慢 rank 则可能来自数据分片不均、CPU 争抢、NUMA 放置不当、网卡与 GPU 跨 PCIe 分支、降频、ECC 错误或共享存储抖动。只看 GPU 利用率很容易把“等待数据”误判为“GPU 空闲故障”。
各 rank 的样本数或 token 数差异过大、最后一个 batch 处理不一致、DistributedSampler 的 padding 与 drop_last 配置不匹配、某个 shard 位于慢速存储,都会让快的 rank 在 collective 处等待。num_workers 太少会饿死 GPU,太多则可能耗尽 CPU、内存或文件句柄;容器内 /dev/shm 过小会让多进程 DataLoader 卡住或崩溃。
IB/RoCE 链路 down、光模块或交换机端口异常、MTU 不一致、RoCE 的 GID 或拥塞配置错误、NCCL_SOCKET_IFNAME 选到管理网,都会让通信挂起或回退到极慢的路径。超时过短会把首次通信、编译或保存的慢操作误判为故障;过长则让真正的死锁长期占用 GPU。
进程组超时、看门狗、异步错误处理配置不一致,DDP、FSDP、DeepSpeed 与张量并行的启动参数不匹配,都会改变失败暴露的方式。Kubernetes 的 CPU limit 节流、内存 limit 触发 OOM、GPU 共享或 MIG 配置变化、节点被 drain 或抢占,也会表现为训练突然停住。
收集所有 rank 的日志,统一时区排序,记录各 rank 最后完成的 step、最后一条日志和进程状态。重点回答:最后一个全局完成 step 是多少?哪些 rank 没到?缺失发生在保存、评估还是训练之后?没有时间线,后续判断都只是猜测。
对仍在运行的进程执行 py-spy dump,区分它停在集合通信、数据加载、文件 I/O、锁还是 CUDA 同步。多 rank 同时抓栈,能看出是全部停在同一个 collective,还是各自停在不同位置。nsys 可看通信与计算时间线,判断“等待通信”还是“计算真的慢”。抓栈通常只读,但避免在高负载时反复执行。
初始化卡住时,先用最小规模的 torchrun --standalone 或 nccl-tests 验证单机;再检查多机的地址、端口、DNS 和防火墙。能在单机跑、多机卡在会合,优先查网络和启动参数。已经进入训练后卡住,则把重点转到最后一个 collective、缺失 rank 和慢节点。
对比实际存活的 rank 集合与预期 world size,检查容器退出码、OOMKilled、节点事件和内核日志:
kubectl describe pod <pod>
kubectl get events --sort-by=.lastTimestamp
dmesg -T | grep -i -E 'oom|nvrm|xid'
退出码 137 常见于被 SIGKILL 或 OOM,139 常见于段错误;CUDA 或 NCCL 错误要看完整 traceback。节点被驱逐、抢占或 drain 也会让 rank 消失。找到第一个退出的 rank,比盯着仍在等待的 rank 更有效。
对比各 rank 的 step 耗时、数据加载、GPU 利用率、CPU 和 I/O 等待。个别 rank 持续慢,查该节点的数据、CPU、NUMA、网卡和 GPU;所有 rank 同时停在同一位置,查通信链路和共同依赖。调大超时只能观察“慢还是死”,不能当修复;仍不推进就是死锁或进程缺失。
确认 /dev/shm 容量、worker 数、各 rank 样本数和最后一个 batch 的处理方式。容器共享内存往往很小,多 worker 场景容易卡住。共享存储抖动会让所有 rank 变慢;本地缓存、预取和分片读取通常比增加 worker 更有效。
在复现命令上临时打开 NCCL_DEBUG=INFO,确认选中的网卡、HCA、拓扑和连接建立过程;用 ibstat、ibv_devinfo 和端到端 MTU 测试检查链路。单机 nccl-tests 正常、多机异常时,优先查交换机、线缆、GID、MTU 和 NCCL_SOCKET_IFNAME。日志量很大且影响性能,只用于复现。
每次只改一个变量:先减少 rank 数,再换节点,再禁用 RDMA 走 TCP,最后替换线缆或交换机端口。记录每一步结果,避免同时改变网络、代码和调度配置。测试环境中的禁用 RDMA 只用于定位,不能作为生产修复。
Kubernetes 中查看 Pod 事件、节点条件、抢占记录、device plugin 和 CNI。节点被 cordon、drain 或标记不健康时,Pod 可能被驱逐而不留明显训练日志。确认 GPU 仍可分配、MIG 配置未变、Pod 没有因 limit 被节流。
按根因处理:
MASTER_ADDR、端口、node_rank、DNS 和防火墙,统一各节点启动参数,清理失效的 rendezvous 状态。验证至少覆盖:
nccl-tests 多次运行稳定,带宽与基线一致。nccl-tests 覆盖实际 rank 数和网卡,连续运行不挂起。验证期间保留现场日志;对节点执行 cordon、drain 或重启前,先确认训练有可用 checkpoint。涉及驱逐、强制删除和驱动变更的操作应先在测试环境演练,并准备回滚方案。
num_workers、batch 和 sampler 语义。nccl-tests 和 IB 健康检查,把单机、多机结果写入基线。合上页面,先回答,再展开对照。
py-spy 栈、DataLoader 耗时、CPU/内存限制和进程状态判断。
小程序版
微信扫码,随时查命令