1. 从一次训练任务卡死说起:智算集群为什么需要“听诊器”
凌晨两点,训练任务突然卡在某个 step 不动了。日志没有报错,GPU 利用率从 95% 掉到 3%,nvidia-smi 显示显存还占着,但计算流已经停了。这种场景,搞过大模型训练的人大概率都遇到过。你第一反应可能是代码有 bug,但排查半天发现代码没问题;接着怀疑是数据加载慢,换了数据管道还是卡;最后才想到——可能是某张卡、某条链路、某个交换机端口出了问题。
这就是智算集群和传统 IT 集群最大的区别:算力不是单点资源,而是一张 tightly-coupled 的网。一张 GPU 卡降频,可能拖慢整个 all-reduce;一条 IB 链路误码,可能让 NCCL 通信超时;一个交换机 buffer 溢出,可能让整个 job 挂死。传统监控看 CPU、内存、磁盘 IO 那套,在智算集群里基本不够用。
所以这一讲要聊的“听诊器”,其实是三件套:Benchmark 做基线、监控做实时感知、故障诊断做根因定位。Benchmark 告诉你“正常应该跑多快”,监控告诉你“现在跑多快、哪里不对劲”,故障诊断告诉你“为什么不对劲、怎么修”。三者缺一不可。我见过不少团队只上 Prometheus + Grafana 看 GPU 利用率,结果遇到 NCCL 超时只能靠重启碰运气,这就是典型的“有体温计没听诊器”。
这篇文章面向的是已经或即将运维 AI 集群的工程师、算法团队的基础设施负责人,以及想搞清楚“集群性能到底怎么看”的开发者。我会从 Benchmark 的选型和实操讲起,再到监控指标体系的搭建,最后重点拆解 NCCL 通信故障的诊断链路。全程按真实运维场景来,不堆概念,直接给能抄的配置和排查命令。
2. Benchmark 不是跑个分就完事:基线、压测与瓶颈定位
2.1 为什么单卡跑分在集群里毫无意义
很多人拿到新集群第一件事是跑个单卡 benchmark,看 TFLOPS 达不达标。这个动作本身没错,但单卡分数高不代表集群能用。我见过单卡 FP16 跑到 300+ TFLOPS 的机器,8 卡 all-reduce 带宽只有理论值的 30%。原因可能是 NVLink 拓扑没配对、PCIe 交换机瓶颈、或者 NCCL 版本和驱动不匹配。
智算集群的 benchmark 必须分层做:
| 层级 | 测试目标 | 常用工具 | 关键指标 |
|---|---|---|---|
| 单卡 | 算力峰值、显存带宽 | 官方 benchmark、自定义 GEMM | TFLOPS、GB/s |
| 单机多卡 | NVLink/PCIe 通信 | NCCL Tests、nccl-tests | bus bandwidth |
| 多机多卡 | 跨节点 RDMA 通信 | NCCL Tests、IB 工具 | 带宽、延迟、误码 |
| 端到端 | 真实模型训练吞吐 | 实际训练脚本 | samples/s、step time |
单卡测试用官方工具就行,比如 NVIDIA 的bandwidthTest和gemm测试。但真正决定集群可用性的是后三层。我一般建议新集群上线前至少跑完这三步:单机 8 卡 NCCL all-reduce、双机 16 卡 all-reduce、以及一个真实的小模型训练(比如 ResNet-50 或 GPT-2 small)做端到端验证。
2.2 NCCL Tests 的正确打开方式
NCCL Tests 是诊断集合通信的标配工具,但很多人只跑默认参数,结果数据参考价值有限。默认的all_reduce_perf只测固定消息大小,实际训练里消息大小是变化的。我通常这样跑:
# 单机 8 卡 all-reduce,扫描消息大小 ./build/all_reduce_perf -b 8 -e 128M -f 2 -g 8 # 多机,指定 hostfile mpirun -np 16 --hostfile hosts -x NCCL_DEBUG=INFO \ ./build/all_reduce_perf -b 8 -e 128M -f 2 -g 8参数含义:-b起始消息大小(字节),-e结束大小,-f倍增因子,-g每进程 GPU 数。跑完之后重点看bus bandwidth这一列,它才是真实通信效率。理论值怎么算?以 NVLink 为例,A100 的 NVLink 单向带宽约 300 GB/s,8 卡 all-reduce 的理论 bus bandwidth 大约在 250-280 GB/s 之间。如果你跑出来只有 100 GB/s,那肯定有问题。
注意:NCCL_DEBUG=INFO 会输出大量日志,生产环境排查时建议只在单次测试中开启,避免日志刷爆磁盘。另外 NCCL_DEBUG_SUBSYS 可以只开特定子系统,比如
NCCL_DEBUG_SUBSYS=NET只看网络层。
2.3 压测时最容易忽略的“环境一致性”
Benchmark 数据不可信,十有八九是环境不一致导致的。我踩过的坑包括:测试节点和训练节点 CUDA 版本不同、NCCL 版本不同、IB 驱动版本不同、甚至 CPU governor 设置不同导致频率抖动。所以跑 benchmark 前,先做一轮环境对齐检查:
# 检查关键组件版本 nvidia-smi --query-gpu=driver_version --format=csv nvcc --version dpkg -l | grep nccl ibstat | grep -E "Firmware|Rate" # 检查 CPU 频率策略 cpupower frequency-info | grep governor如果 governor 是 powersave,建议临时切到 performance,否则 CPU 降频可能影响数据加载和 MPI 通信。这个细节在文档里很少提,但实测对多机 benchmark 影响能到 5%-10%。
2.4 用真实模型做端到端基线
NCCL Tests 测的是通信极限,但真实训练里计算和通信是交错的。所以最后一定要跑一个真实模型做端到端基线。我一般用两个模型:一个 CNN(ResNet-50)看数据管道和单卡计算,一个 Transformer(GPT-2 small 或 BERT-base)看通信密集场景。
跑的时候记录step time 的 P50 和 P99,而不是只看平均值。P99 高说明有长尾延迟,可能是某张卡偶尔降频、某条链路偶发误码。这个指标比平均 step time 更能反映集群健康度。基线建好之后,后续任何性能波动都可以拿这个基线做对比。
3. 监控体系怎么搭:从 GPU 指标到 NCCL 通信健康度
3.1 监控分层:别把 GPU 利用率和业务指标混在一起
智算集群监控我习惯分四层:
- 硬件层:GPU 温度、功耗、时钟、显存、ECC 错误、NVLink 状态、IB 端口误码
- 系统层:CPU、内存、磁盘 IO、网络吞吐、进程状态
- 通信层:NCCL 通信耗时、all-reduce 带宽、RDMA 重传率
- 业务层:训练 step time、吞吐、loss 曲线、checkpoint 耗时
很多团队只做前两层,结果通信层出问题完全看不见。我的建议是:硬件层和系统层用 Prometheus + Grafana 做常规监控,通信层和业务层用自定义埋点 + 日志分析。
硬件层采集用 DCGM Exporter 最省事,它直接暴露 GPU 的详细指标:
# dcgm-exporter 的 Prometheus 配置片段 - job_name: 'dcgm' static_configs: - targets: ['dcgm-exporter:9400']关键指标包括DCGM_FI_DEV_GPU_UTIL、DCGM_FI_DEV_MEM_COPY_UTIL、DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL、DCGM_FI_DEV_XID_ERRORS。其中XID errors特别重要,它对应 GPU 硬件错误码,比如 XID 48 是 ECC 双比特错误,XID 79 是 GPU 掉卡。看到 XID 错误不要犹豫,直接隔离节点。
3.2 IB 网络监控:别等 NCCL 超时了才看
InfiniBand 是智算集群的血管,但很多人只监控端口 up/down,不看误码和重传。IB 的误码率上升往往是链路老化的前兆,等它发展到 NCCL 超时,训练已经挂了好几次了。
用perfquery可以看端口统计:
# 查看所有 IB 端口的误码和重传 perfquery -x -a # 关键字段: # PortRcvErrors: 接收错误 # PortXmitDiscards: 发送丢弃 # PortRcvRemotePhysicalErrors: 远端物理错误 # LinkDowned: 链路 down 次数如果PortRcvErrors或PortXmitDiscards持续增长,说明链路有问题。我一般设个阈值:单端口误码率超过 1e-12 就告警,超过 1e-9 直接隔离端口。这个阈值不是拍脑袋,是结合 IB 规范和历史故障数据定的。
Prometheus 采集 IB 指标可以用infiniband_exporter,或者自己写个脚本定期跑perfquery转成 metrics。Grafana 看板建议把误码率、重传率、端口利用率放在同一张图里,方便关联分析。
3.3 NCCL 通信监控:在代码里埋点比事后猜靠谱
NCCL 本身不暴露实时指标,但你可以通过环境变量让它输出通信日志,再用工具解析。更主动的做法是在训练代码里埋点,记录每次 all-reduce 的耗时。
PyTorch 里可以用torch.distributed的 hook 机制:
import torch.distributed as dist import time def timed_all_reduce(tensor): start = time.time() dist.all_reduce(tensor) elapsed = time.time() - start # 上报到监控系统 report_metric("nccl_all_reduce_seconds", elapsed) return tensor更细粒度的话,可以用 NCCL 的 profiling 接口,或者直接开NCCL_DEBUG=INFO然后解析日志里的Avg bus bandwidth。但生产环境不建议长期开 INFO,日志量太大。折中方案是定期(比如每小时)跑一次小规模 NCCL 测试,把结果上报监控。
提示:NCCL 有个
NCCL_DEBUG_TIMESTAMP_LEVELS环境变量,可以控制日志时间戳精度,排查时序问题时很有用。但同样,只在排查时开。
3.4 业务层监控:step time 的 P99 比平均值重要
业务层监控最核心的指标是step time 分布。平均值会掩盖长尾问题,P99 才能反映真实体验。我一般要求训练框架每 100 步上报一次 step time 的 P50、P95、P99。
如果 P99 突然升高但 P50 正常,说明少数 step 被拖慢了,可能是某张卡偶尔降频、某条链路偶发误码、或者数据加载偶发 IO 抖动。如果 P50 整体升高,说明系统性问题,可能是通信瓶颈、计算资源不足、或者全局降频。
Grafana 看板上建议把 step time 和 GPU 利用率、NCCL 带宽放在同一时间轴上,方便关联。我见过一个案例:step time P99 周期性升高,最后发现是某台机器的 CPU 在跑定时任务,抢占了数据加载进程的资源。这种问题只看 GPU 指标永远找不到。
4. NCCL 故障诊断实战:从超时日志到根因定位
4.1 NCCL 超时的典型日志长什么样
NCCL 超时是最常见的训练故障,典型日志是这样的:
NCCL INFO Call to connect returned Connection refused, retrying NCCL INFO Bootstrap : Using eth0:10.0.0.1<0> NCCL WARN Connect to 10.0.0.2<45123> failed : Connection timed out NCCL WARN Timeout after 1800 seconds看到Timeout after基本可以确定是通信问题。但具体是网络断了、对端进程挂了、还是链路拥塞,需要进一步排查。我一般按这个顺序查:
- 确认对端进程是否存活:
pdsh -w node[02-04] "ps aux | grep python" - 确认网络连通性:
pdsh -w node[02-04] "ibstat | grep State" - 确认 NCCL 版本一致:
pdsh -w node[02-04] "dpkg -l | grep nccl" - 看 IB 端口误码:
pdsh -w node[02-04] "perfquery -x"
4.2 一个真实的 NCCL 超时排查链路
去年我遇到一次训练卡死,NCCL 超时日志指向 node-07。按常规查了进程、网络、版本都没问题。最后用NCCL_DEBUG=INFO重跑,发现日志里有大量NET/IB : Got completion with error 12。error 12 对应 IB 的IBV_WC_RETRY_EXC_ERR,意思是重传次数超限。
继续查perfquery,发现 node-07 的某个 IB 端口PortXmitDiscards在持续增长。换到交换机侧看,对应端口的 CRC 错误计数也在涨。最终定位是光模块老化,换模块后恢复。
这个案例的教训是:NCCL 超时只是表象,根因可能在物理层。排查链路要覆盖应用层 → 通信库 → 驱动 → 硬件 → 物理链路,不能只看 NCCL 日志。
4.3 用 NCCL Tests 做二分定位
如果超时是偶发的,不好复现,可以用 NCCL Tests 做二分定位。比如 8 机训练偶发超时,先跑 4 机测试,如果没问题再跑 6 机,逐步缩小范围。NCCL Tests 的好处是它可以单独测通信,排除计算和数据加载的干扰。
# 二分定位示例:先测 4 机 mpirun -np 32 --hostfile hosts_4machines \ ./build/all_reduce_perf -b 1G -e 1G -f 1 -g 8 -n 100 # 如果 4 机没问题,加到 6 机 mpirun -np 48 --hostfile hosts_6machines \ ./build/all_reduce_perf -b 1G -e 1G -f 1 -g 8 -n 100重点看-n 100跑 100 次里有没有失败或带宽骤降。如果有,再缩小到具体节点对。这个方法比反复重启训练任务高效得多。
4.4 常见 NCCL 故障速查表
| 现象 | 可能原因 | 排查命令 | 处理方式 |
|---|---|---|---|
| Connection refused | 对端进程未启动 | ps aux | grep python | 检查启动脚本 |
| Connection timed out | 网络不通/防火墙 | ibstat、ping | 检查 IB 状态和防火墙 |
| Timeout after 1800s | 通信卡死/链路拥塞 | NCCL_DEBUG=INFO重跑 | 二分定位 |
| Got completion with error 12 | IB 重传超限 | perfquery -x | 检查光模块和交换机 |
| Unhandled system error | 驱动/硬件故障 | dmesg、nvidia-smi -q | 隔离节点 |
| Bus bandwidth 远低于理论值 | 拓扑/版本/配置问题 | nvidia-smi topo -m | 检查 NVLink 拓扑和 NCCL 配置 |
这张表是我自己运维时总结的,覆盖了 80% 以上的常见故障。建议打印出来贴在工位上,出问题时按表排查,比临时翻文档快得多。
5. 把“听诊器”用起来:日常巡检与故障预防
5.1 每日巡检清单:10 分钟排除 90% 隐患
监控和诊断工具搭好之后,关键是日常用起来。我一般要求团队每天做一次 10 分钟巡检,清单如下:
- GPU:
nvidia-smi -q看 ECC 错误、温度、功耗是否正常 - IB:
perfquery -x看误码和重传是否增长 - 存储:
df -h和iostat看容量和 IO 延迟 - 进程:
pdsh -w all "ps aux | grep -c python"看是否有僵尸进程 - 日志:
grep -i "error\|warn" /var/log/syslog看系统日志
这个清单看起来简单,但坚持做能提前发现大部分硬件老化问题。我见过太多团队等到训练挂了才查,其实误码率一周前就开始涨了。
5.2 建立故障知识库:别让同一个坑踩两次
每次故障排查完,我都会要求写一份简短的 RCA(根因分析),记录现象、排查过程、根因、修复方式。积累半年之后,这就是团队最宝贵的知识库。下次遇到类似现象,直接搜关键词就能找到历史案例。
知识库的格式不用复杂,一个 Markdown 文件就行:
## 故障:NCCL 超时,日志显示 error 12 - 日期:2024-XX-XX - 现象:训练卡死,NCCL 超时 - 排查:perfquery 发现 PortXmitDiscards 增长 - 根因:光模块老化 - 修复:更换光模块 - 预防:每日巡检增加误码率检查5.3 自动化诊断脚本:把重复劳动交给机器
日常巡检和常见故障排查可以脚本化。我写过一个简单的诊断脚本,输入节点列表,自动收集关键信息:
#!/bin/bash # nccl_diag.sh - 快速收集 NCCL 诊断信息 NODES=$1 pdsh -w $NODES " echo '=== GPU ===' nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,ecc.errors.uncorrected.volatile.total --format=csv echo '=== IB ===' ibstat | grep -E 'State|Rate' perfquery -x | grep -E 'PortRcvErrors|PortXmitDiscards' echo '=== NCCL ===' dpkg -l | grep nccl echo '=== CPU ===' cpupower frequency-info | grep governor "这个脚本跑一遍,基本能定位大部分环境问题。配合前面的速查表,效率能提升不少。
5.4 从“救火”到“防火”:监控告警的阈值怎么定
监控告警最怕两种:一是阈值太松,出问题了不告警;二是阈值太紧,天天误报,最后没人看。我的经验是分层设阈值:
- 硬件层:ECC 错误 > 0 就告警(硬件错误零容忍)
- 网络层:误码率 > 1e-12 告警,> 1e-9 严重告警
- 业务层:step time P99 超过基线 20% 告警,超过 50% 严重告警
阈值不是一次定死的,要根据实际运行数据调整。我一般每季度回顾一次告警记录,看看哪些是误报、哪些是漏报,然后调整阈值。这个过程持续做,监控系统才会越来越准。
6. 一些踩坑之后的个人体会
智算集群的运维,说到底是个“基线 + 监控 + 诊断”的闭环。没有基线,你不知道什么是正常;没有监控,你不知道什么时候不正常;没有诊断能力,你知道了不正常也修不了。这三件事里,最容易忽视的是基线,最容易做偏的是监控,最考验功力的是诊断。
我自己的习惯是:新集群上线前一定跑完分层 benchmark,把数据存档;日常监控重点看 P99 和误码率,不被平均值迷惑;遇到故障先按速查表排查,再深入根因,最后写 RCA。这套流程跑了几年,大部分故障都能在 30 分钟内定位。
另外分享一个小技巧:NCCL 的日志级别可以用NCCL_DEBUG_FILE输出到指定文件,排查时不会刷屏。还有NCCL_DEBUG_SUBSYS=INIT,NET可以只看初始化和网络子系统,日志量少很多。这些环境变量在官方文档里都有,但很多人不知道,排查时开全量日志反而被淹没。
最后说一句,工具是死的,人是活的。再好的监控也替代不了对系统的理解。多跑 benchmark、多看日志、多总结,时间长了自然就有“听诊”的感觉了。