跳到主内容
快讯直播
AI智模界
教程

GPU集群可用率体检:用DCGM指标揪出被浪费的27%

报错现象

先还原一个很典型的场景。月度容量评审会上,业务方说“卡不够用,队列里还有几十个任务在等”,可调度系统的报表显示 GPU 分配率已经接近 100%,一张空闲卡都挑不出来。运维把 DCGM 面板拉出来一看,全集群 DCGM_FI_PROF_SM_ACTIVE 的均值只有 0.73 左右——也就是说,在被占用的卡时里,大约有 27% 的时间连一个 warp 都没有驻留在 SM 上。卡是“分出去了”,但没在干活。

这类问题通常不是一条醒目的红色报错,而是由一组“看起来不疼不痒”的症状凑成的:

1. GPU 显存占满、算力空转。nvidia-smi 里 Utilization 长期在个位数,而 Memory-Usage 稳定在 80%~95%,功耗贴着空闲值。用户以为卡在跑,其实只是在“占着”。

2. nvidia-smi 说没有进程,但显存就是不释放。容器已经退出,显存要等宿主侧清理后才回来,中间这段时间调度器认为卡不可用。

3. torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate ...,但同一时刻 nvidia-smi 显示卡上还有大量空闲显存。这是典型的显存碎片,不是总量不足。

4. 多卡训练卡在启动阶段,日志里反复出现 Watchdog caught collective operation timeout: WorkNCCL(SeqNum=..., OpType=ALLREDUCE, Timeout(ms)=...) ran for ... milliseconds before timing out.,或者 NCCL WARN 开头的重试信息。任务既没失败也没进展,卡处于“等通信”的状态。

5. 同一份代码、同一个镜像,换一批节点吞吐差出一截。没有报错,只是慢,而慢的节点往往集中在某几个机柜或某几台交换机下面。

影响范围:小到一个容器、一个进程,中到一台 8 卡节点,大到整个 IB leaf 下的一个拓扑域。表现为“容量不够”和“任务莫名变慢”两种形态,且很容易被误判成“业务量涨了,该采购了”。

可能原因

按实际排查中出现的概率从高到低排:

1. 显存被占、SM 空转(占卡不干活)。Jupyter 内核残留、服务框架预分配显存、OOM 后未退出的僵尸进程,都会造成“卡被分配、算力闲置”。

2. 采集口径错误,27% 这个数字本身有问题。DCGM_FI_DEV_GPU_UTIL 的定义是“采样周期内是否有 kernel 在执行”,它是布尔式的,不反映 SM 用了多少。用它算可用率,会把“跑了一个小 kernel”算成满负荷,也会把“多进程分时共享”算得面目全非。

3. GPU 在等,不是在算。通信等待(NCCL all-reduce、参数同步)和数据加载等待(DataLoader、存储带宽、CPU 解码)都会让 SM 空转,但显存是满的。

4. 拓扑错配。多卡任务被分到了跨 NUMA、跨 PCIe switch、甚至跨 IB leaf 的卡上,all-reduce 带宽下降一个档位,任务整体变慢,卡时被白白消耗。

5. 调度粒度与碎片。整卡分配跑小任务、队列 bin-packing 策略不合理、任务之间有空窗期,都会让分配率虚高。

6. 硬件降频或错误。温度墙、功耗墙、ECC 错误累积、XID 报错,会让同一张卡的算力低于同型号其他卡。

逐条排查与解决

原因 1:占卡不干活

先看卡级快照,连续采样,别只看一眼:

```bash

nvidia-smi --query-gpu=index,uuid,utilization.gpu,memory.used,memory.total,power.draw,clocks.sm \

--format=csv -l 5

```

判断标准:utilization.gpu 长期低于 10%,同时 memory.used 超过总量的一半,power.draw 贴近该型号的空闲功耗。三者同时成立,基本可以确定是占卡不干活。

再看是谁占的:

```bash

nvidia-smi --query-compute-apps=pid,process_name,used_gpu_memory,gpu_uuid --format=csv

```

拿到 PID 后查生命周期:

```bash

ps -o pid,ppid,etime,time,rss,cmd -p <PID>

```

判断标准:etime(已运行时长)很长,但 time(累计 CPU 时间)很短,说明进程大部分时间在睡觉。再配合 cat /proc/<PID>/status | grep -i state 看是否处于 S(睡眠)状态。

常见根因与处置:

  • Jupyter / Notebook 内核残留:给容器加空闲内核回收,或设置 c.ServerApp.kernel_manager.cull_idle_timeout。
  • 服务框架显存预分配:vLLM 这类推理框架有 gpu_memory_utilization 之类的参数,TensorFlow 有 per_process_gpu_memory_fraction 或 set_memory_growth。把预分配比例调到贴合实际,别一上来就吃掉 90%。
  • 训练进程 OOM 后没有退出,父进程还在重试:检查容器的重启策略,加上失败即退出。

可以用一段最简脚本把“占卡不干活”的卡筛出来:

```bash

nvidia-smi --query-gpu=index,uuid,utilization.gpu,memory.used,memory.total \

--format=csv,noheader,nounits | \

awk -F', ' '{ if ($3 < 10 && $4/$5 > 0.5) print "疑似占卡空转 GPU" $1 " uuid=" $2 }'

```

原因 2:采集口径错误

先确认你看的是哪个指标。

DCGM_FI_DEV_GPU_UTIL:采样窗口内有没有 kernel 在跑,取值 0~100。一个每秒唤醒一次跑 1ms 小 kernel 的进程,在这个指标上可能接近 100。

DCGM_FI_PROF_SM_ACTIVE:SM 上至少有一个 warp 驻留的周期占比,取值 0~1。这个才更接近“算力用了多少”。

做法:把两个指标并排画出来,看差值。

```promql

avg by (instance) (DCGM_FI_DEV_GPU_UTIL) / 100

avg by (instance) (DCGM_FI_PROF_SM_ACTIVE)

```

如果前者 0.95、后者 0.4,说明卡上跑的是“高频唤醒的轻任务”,不是重计算。这时候用 DEV_GPU_UTIL 算可用率,结论会完全反过来。

注意两点:

  • PROF 系列指标(DCGM_FI_PROF_*)属于 profiling 采集,开启后有一定额外开销,建议只在体检期间或抽样节点上开启,长期采集要先评估影响。
  • 采集间隔对结论影响很大。用 dcgmi dmon 做现场排查时把间隔压到 1 秒:

```bash

dcgmi dmon -l # 先列出本机支持的字段 ID 与含义

dcgmi dmon -e <字段ID列表> -d 1000

```

字段 ID 在不同 DCGM 版本间可能调整,请以 dcgmi dmon -l 的实际输出和官方文档当前版本为准。

判定标准:如果 DEV_GPU_UTIL 与 PROF_SM_ACTIVE 长期背离超过 30 个百分点,就先修采集口径,再谈优化。否则后面的所有结论都是错的。

原因 3:GPU 在等通信或在等数据

先区分是“等网络”还是“等数据”。看两组指标的搭配:

```promql

avg by (instance) (DCGM_FI_PROF_SM_ACTIVE)

avg by (instance) (DCGM_FI_PROF_DRAM_ACTIVE)

avg by (instance) (DCGM_FI_PROF_NVLINK_TX_BYTES)

avg by (instance) (DCGM_FI_PROF_PCIE_TX_BYTES)

```

判定表:

SM 活跃DRAM 活跃NVLink/IB 流量结论
低低高通信等待,查 NCCL 与拓扑
低低低,但宿主机 CPU 高数据加载瓶颈,查 DataLoader 与存储
高高正常真的在算,问题不在这里

通信侧的证据可以直接从 NCCL 日志拿。用一个小规模的 all-reduce 测试跑一遍:

```bash

NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET \

<你的 all-reduce 测试程序>

```

日志里重点看两件事:GPU 之间是走 via P2P(NVLink / PCIe P2P)还是 via shared memory(退回主机内存),网卡侧是走 NET/IB 还是 NET/Socket。退回 shared memory 或 Socket,说明 P2P 或 RDMA 没启起来,带宽会差一个量级。

数据加载侧的判断更直接:在训练日志里看 step 时间的周期性。如果 GPU 利用率呈现规律的“锯齿”——几秒满载、几秒归零,基本就是 DataLoader 供给不上。处置方式是加 worker 数、换更快的存储、把解码放到 GPU、或者预先把小文件打包成大文件。

原因 4:拓扑错配

这是最容易被忽略、但对多卡任务伤害很大的一条。

```bash

nvidia-smi topo -m

```

输出是一张矩阵,交叉点上的标记含义要会读:

  • NV#:走 NVLink,# 是链路数,数字越大越好。
  • PIX:同一 PCIe switch 下。
  • PXB:跨 PCIe switch,但要经过 PCIe host bridge。
  • PHB:要经过 CPU 的 PCIe host bridge。
  • NODE:跨 NUMA 节点,同一个 socket 内。
  • SYS:跨 socket,走 UPI/QPI。

判断标准:一个 8 卡任务,如果关键的 all-reduce 组合落在 SYS 或 NODE 上,通信成本会明显高于落在 NV# 或 PIX 上。这时候不是卡坏了,是调度把不该放一起的卡放在一起了。

同时确认网卡位置:

```bash

lspci -tv | head -60

numactl -H

```

看 IB 网卡和 GPU 是否挂在同一个 PCIe switch / 同一个 NUMA 节点下。跨 NUMA 的 GPU 直连网卡,会让 RDMA 路径多绕一圈。

调度侧的动作:

  • 多卡任务优先整机分配,不要把一个 8 卡任务拆到两台 4 卡机上,除非你确认过通信量。
  • 启用拓扑感知调度(Kubernetes 侧有对应的 Topology Aware Scheduling 能力,具体可用性与配置方式以官方文档当前版本为准)。
  • 容器加 NUMA 亲和,配合 CUDA_VISIBLE_DEVICES 精确绑定到物理卡序号,避免容器内序号与物理序号错位。
  • 单机内可以用 NCCL_TOPO_DUMP_FILE 把拓扑导出,留档对比不同节点是否一致。

原因 5:调度粒度与碎片

先量化浪费。用一条 PromQL 粗略估算“已分配但 SM 不活跃”的卡时占比:

```promql

1 - avg by (instance) (DCGM_FI_PROF_SM_ACTIVE)

```

再按队列、按镜像、按用户维度拆开,就能看出浪费集中在谁身上。

显存碎片的量化:

```promql

avg by (instance) (DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL)

```

如果显存使用率长期在 90% 以上,但 SM 活跃度很低,且业务侧频繁报 OOM,就是碎片与大块预留并存。

处置手段:

  • 小任务用 MIG 切分,把一张卡切成多个实例,各自独立调度:

```bash

nvidia-smi mig -lgip # 列出本机支持的 GI profile

nvidia-smi mig -cgi <profile_id> -C

nvidia-smi -L # 确认 MIG 设备已生成

```

  • 短任务、交互式任务用 MPS 做时间片共享,减少上下文切换开销:

```bash

nvidia-cuda-mps-control -d

```

  • 队列策略上,把散布(spread)和装箱(bin-packing)按队列分开配:延迟敏感的小任务用 spread,大训练任务用 bin-packing 减少碎片。
  • 加 gang scheduling,避免“半个任务占着卡等另一半”的死锁型浪费。

原因 6:硬件降频与错误

```bash

nvidia-smi -q -d PERFORMANCE

nvidia-smi -q -d ECC

dmesg | grep -i xid | tail -50

```

PERFORMANCE 段里会列出 Clocks Throttle Reasons,看是 SW Power Cap、HW Slowdown、HW Thermal Slowdown 还是 SW Thermal Slowdown 被置位。如果长期挂着温度或功耗降频,同型号卡的算力就不是一个水平线。

XID 错误在 dmesg 里以 NVRM: Xid 开头。偶发一次可以观察,同一张卡反复出现就要下线。DCGM 侧对应 DCGM_FI_DEV_XID_ERRORS 指标,可以直接做告警。

硬件层面的完整体检用:

```bash

dcgmi diag -r 2

```

-r 的等级越高,跑得越久、覆盖越全,高等级会独占 GPU,务必先把节点从调度池里摘出来。

都不管用时的兜底方案

如果上面六条都排查过,指标还是对不上,按顺序做这几件事:

1. 先隔离,再深挖。Kubernetes 上 kubectl cordon <node> 加 kubectl taint nodes <node> dedicated=gpu-check:NoSchedule;Slurm 上 scontrol update NodeName=<node> State=DRAIN Reason="gpu-check"。别让可疑节点继续接活。

2. 做 A/B 对照。同一个镜像、同一份数据、同一个 benchmark,在“好节点”和“坏节点”上各跑一遍,记录吞吐、SM 活跃度、NVLink 带宽。差异稳定复现,才说明是节点问题而不是任务问题。

3. 跑完整诊断。节点下线后执行 dcgmi diag -r 3,同时保留 nvidia-bug-report.sh 的输出。这两份材料是找厂商支持时最有用的东西。

4. 换路径兜住业务。把任务临时迁到备用卡型或备用机房;对延迟不敏感的离线任务,可以先转 CPU 池跑着,别让队列堵死。

5. 回滚最近变更。驱动、CUDA、DCGM、容器运行时、调度器版本,任何一项在问题出现前动过,都先回退验证。这类问题的根因里,变更引入的比例不低。

如何预防再次发生

体检做完,最重要的是把一次性排查变成常态化的几个指标和几条规则。

一、定义四个“有效可用率”指标,而不是一个

  • 分配率:卡被分配出去的时长占比。
  • SM 活跃率:DCGM_FI_PROF_SM_ACTIVE 的均值。
  • 显存有效率:DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL 的分布。
  • 拓扑命中率:多卡任务的卡组合落在 NV#/PIX 上的比例。

只看分配率,就会得到“卡很紧张”的错觉;四个一起看,才能看出那 27% 在哪里。

二、把采集口径固定下来

统一 exporter 的采集间隔和字段集,PROF 指标按需开启并记录开启窗口;所有看板以 GPU UUID 而不是序号作为主键,避免容器内序号错位;面板上把 DEV_GPU_UTIL 和 PROF_SM_ACTIVE 并排放,从源头防止误读。

三、把调度规则写进配置

  • 多卡任务整机分配,卡数超过单机一半时禁止跨机拆分。
  • 拓扑感知调度开启,绑定 NVLink 域。
  • NUMA 亲和与 CUDA_VISIBLE_DEVICES 绑定由平台统一注入,不交给用户手填。
  • 小任务默认走 MIG 或 MPS 池,不占整卡。
  • 空闲超时回收策略覆盖所有交互式容器。

四、加两条告警

  • SM 活跃率低于阈值且显存占用高于阈值,持续一段时间 → 疑似占卡空转。
  • DCGM_FI_DEV_XID_ERRORS 出现非零增量 → 立即定位到卡,纳入观察名单。

五、定期巡检与准入

每周对空闲窗口的节点跑一次低等级 dcgmi diag,每月对全集群做一次拓扑一致性比对;新节点上线前必须过一遍诊断和基准跑分,把结果和机型、固件、驱动一起入库。这样下次再出现“卡不够用”的声音时,你能在十分钟内回答:是真不够,还是那 27% 又藏起来了。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。