☰
nvidia-smi详解:从GPU利用率到工程级日志监控与故障排查
2026/9/26 16:24:35 网站建设 项目流程

训练跑着跑着,终端里敲下nvidia-smi,看到 GPU-Util 在 30% 到 40% 之间来回跳,这种事情我相信很多人都不陌生。网上一搜,有人说数据加载太慢,有人说是 kernel 没写好,也有人怀疑自己是不是买到了有问题的卡,但很少有人把话说透:同样是监控 GPU 使用率,不同的人看的东西完全不一样。大部分人只瞄一眼利用率百分比,做底层优化和训练平台运维的人却会把显存、功耗、温度、SM 时钟、正在跑的进程一起拉出来,甚至把这些指标落到带时间戳的日志文件里,变成可以复盘、报警、做容量规划的数据资产。

这篇文章想做的,就是把从“nvidia-smi 一次性查看”到“工程级日志实践”的整个链路顺一遍。适合刚接触 GPU 开发的算法工程师、自己搭深度学习工作站的研究生,以及需要维护多卡服务器或训练平台的同学参考。我会先讲清楚几个容易误读的指标,再给出一批我实测下来高效的查询命令,然后分享一套可以直接套用的日志采集脚本,最后单独把couldn't communicate with the nvidia driver这个高频报错的排查路径完整梳理出来。

1. 为什么只看“利用率”不够

1.1 利用率只是“忙不忙”,不是“算得快不快”

nvidia-smi里的 GPU-Util 字段,准确含义是在采样周期内 GPU 上有 kernel 执行的时间占 SM(流处理器簇)可调度时间的比例。你可以把它理解成一条高速公路的“是否有车在跑”的比率,而不是“车速”或者“单位时间通过车辆数”。这里面藏着两个非常常见的误判场景。

第一个场景:GPU 利用率只有 30%,但代码写得并不差。我遇到过不少同学拿着一张 4090 跑 PyTorch,发现利用率上不去,第一反应就是“代码优化不到位”,结果查了半天发现是 DataLoader 的num_workers设成了 0,每个 step 都在等 CPU 喂数据,GPU 大部分时间是在空转等待。这时候利用率低,恰恰说明硬件本身没问题,瓶颈在数据管线,而不是计算密度不够。

第二个场景更反直觉:GPU 利用率一直在 90% 以上,但训练速度反而比某台利用率 60% 的机器还慢。原因也很简单,如果代码里大量调用特别小的 kernel,每个 kernel 的启动和调度开销远大于实际计算时间,SM 确实一直处于“有活干”的状态,但干的活很多是在执行内存搬运、线程块调度这类辅助操作。这种“虚假繁忙”在自研算子或者过度细粒度切分任务时特别常见。

所以,不要单独拿利用率来评判 GPU 工作是否正常。它只是一个“忙闲指示器”,真正要判断“算得快不快”,你得把功耗、时钟频率、温度放到一起交叉验证。

1.2 显存、温度、功耗、时钟是“交叉验证四件套”

我平时看nvidia-smi,很少只看那一栏。完整的信息组合是这样的:

指标看什么健康信号异常信号
利用率SM 是否有 kernel 在跑有大任务时较高波动剧烈且伴随吞吐下降
显存占用张量/模型/缓存占了多少有任务时稳定持续缓慢上升,可能是泄漏
功耗当前实际功率 / 功耗上限接近 Cap 且利用率高利用率高但功耗很低
温度GPU 核心温度65~85 摄氏度为常见区间超过 92 摄氏度需要考虑降频
SM 时钟当前 SM 频率接近 Boost 频率明显掉频,撞功耗墙或温度墙

这四类指标一旦互相矛盾,基本就说明有问题。比如利用率显示 100%,功耗却只有 30W 左右,SM 时钟也从 1700MHz 掉到 400MHz,这种情况多半是撞了温度墙或者功耗墙,驱动主动降频来保护硬件。反过来,显存占用接近上限、利用率却只有个位数,可能是代码里显存泄漏,也可能是加载了一大批数据但计算还没有开始。把几个指标放到一起看,比只看一个数字可靠得多。

1.3 先搞清楚“谁”在用 GPU

在公用服务器上,nvidia-smi默认只告诉你 GPU 整体状态,但往往你更想知道的是“到底是哪个进程占的显存”。默认输出最下面的 Processes 列表会列出 pid、进程名、显存占用,但如果多个用户同时在跑任务,这张表格会非常长。

这时候我更推荐用:

nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv,noheader

这条命令会输出类似下面的内容:

12345, python3, 6124 MiB 12789, python3, 8921 MiB

拿到 PID 之后可以再用ps -fp <pid>确认用户和工作目录。遇到“GPU 显存占着但利用率是 0”的情况,多半是进程已经退出但没释放显存,或者是一个常驻服务卡死在等待 I/O 上。这一步能帮你快速判断要不要直接 kill 掉残留进程。

2. 日常查询的正确姿势

2.1 先读懂一条默认输出

直接在终端敲nvidia-smi,返回的信息分三块:顶部是驱动和 CUDA 版本,中间是每张 GPU 的运行时状态,底部是进程列表。很多人忽略的是 Persistence-M 这一列,它表示是否开启了持久化模式。默认情况下驱动在最后一个进程退出后会把 GPU 状态清理掉,下次再调用时要重新初始化,这会导致每次程序启动都有一段额外的驱动加载时间,也会让nvidia-smi首次查询反应变慢。

我建议在深度学习服务器上把持久化模式直接打开:

nvidia-smi -pm 1

开了之后驱动的初始化开销被摊平,频繁启停训练任务时稳定性明显更好。这个操作不会改动业务数据,重启后某些系统会复位,需要在启动脚本里再执行一次。

2.2 定制化查询:--query-gpu 才是效率神器

默认输出会有大量无关信息,比如风扇转速、MIG 模式之类的。在脚本里写监控、或者只想快速看关键指标时,直接筛字段:

nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total,power.draw,temperature.gpu --format=csv,noheader,nounits

--format=csv,noheader去掉表头,nounits去掉 MiB、W、°C 这些单位后缀,方便后续用 awk 或者 Python 解析。比较实用的字段我整理了一下:

字段含义用途
timestamp驱动时间戳和外部日志做时间对齐
utilization.gpuSM 利用率(%)快速判断忙闲
utilization.memory显存控制器利用率(%)判断是否访存密集
memory.used已用显存(MiB)排查泄漏、OOM
power.draw当前功耗(W)判断是否满负载
power.limit功耗上限(W)判断是否有功耗墙
temperature.gpuGPU 核心温度(°C)散热检查
clocks.smSM 当前频率(MHz)判断是否掉频
clocks.mem显存当前频率(MHz)判断显存是否降频

在脚本里我通常还会把clocks.sm和clocks.mem也带上,降频问题没有预计到的话很多时候要靠这两个字段才能看出来。

2.3 让 nvidia-smi 滚动起来

单次查看适合排查问题,实时观察则用 watch:

watch -n 1 nvidia-smi

-n 1表示每秒刷新一次,-d可以在每次刷新时高亮发生变化的部分:

watch -n 2 -d nvidia-smi

如果机器上插了多张卡,默认输出会把整个屏幕占满。我习惯只盯着关键指标看,比如只关注利用率和显存:

watch -n 2 "nvidia-smi --query-gpu=index,utilization.gpu,memory.used,temperature.gpu --format=csv,noheader"

这样一张卡一行,四张卡也就四行,不会乱。注意有些精简版系统的watch不支持-d和彩色输出,通过-c参数开颜色前最好先在机器上确认一下。

3. 把 nvidia-smi 变成工程级日志采集

3.1 为什么要“留痕”

一次性命令再方便,也存在一个致命短板:没法回答“昨天凌晨三点 GPU 到底发生了什么”。我接手过一台 V100 机器,同事一直抱怨显存好像不太够,训练偶尔会 OOM。等他找我的时候,机器已经重启过好几次了。我翻当周的日志发现,显存占用从周一早上的 6000MiB 一路爬到周四晚上的 10200MiB,利用率却始终不高,最后定位到是某个常驻推理服务每次请求都会把中间张量累积在后端模型里,相当于温水煮青蛙式泄漏。没有历史日志,这种问题几乎不可能查。

日志的价值不只是排障。多卡调度的时候,想知道两张卡是不是负载不均衡;扩容的时候,想知道当前机器的利用率到底是 20% 还是 80%;做训练回放时,想知道显存到底是什么时候涨上去的——这些都依赖“带时间戳的历史状态”,而不是当下那一刻的快照。

3.2 一个可以直接套用的 Bash 采集脚本

下面这个脚本我在多台服务器上用过,不依赖第三方库,只靠bash、date、nvidia-smi。

#!/bin/bash # gpu_monitor.sh # 用法:nohup ./gpu_monitor.sh my_task > /dev/null 2>&1 & # 环境变量 INTERVAL 控制采样间隔,默认 10 秒 INTERVAL="${INTERVAL:-10}" TASK_ID="${1:-default}" LOG_DIR="/var/log/gpu_monitor" mkdir -p "$LOG_DIR" while true; do DAY=$(date '+%Y%m%d') TS=$(date '+%Y-%m-%d %H:%M:%S') CSV="${LOG_DIR}/${TASK_ID}_${DAY}.csv" if [ ! -s "$CSV" ]; then echo "time,task_id,gpu_id,gpu_name,utilization_percent,memory_used_mib,memory_total_mib,power_draw_w,temperature_c,sm_clock_mhz,mem_clock_mhz" > "$CSV" fi nvidia-smi --query-gpu=index,name,utilization.gpu,memory.used,memory.total,power.draw,temperature.gpu,clocks.sm,clocks.mem \ --format=csv,noheader,nounits | \ while IFS=',' read -r idx name util mem_used mem_total pwr temp sm_clk mem_clk; do name=$(echo "$name" | tr -d ' ') echo "$TS,$TASK_ID,$idx,$name,$util,$mem_used,$mem_total,$pwr,$temp,$sm_clk,$mem_clk" >> "$CSV" done sleep "$INTERVAL" done

几个细节解释一下。TASK_ID设计成了参数,这样做的好处是同一台机器上跑不同任务时可以分开记录,后面对比“任务 A 的显存特征”和“任务 B 的显存特征”会非常方便。日志按天拆文件,避免单个文件无限膨胀。脚本本身没有做进程去重,所以同一台机器上只建议起一个采集进程,否则多个进程同时写同一个 CSV 会出现行交错。

用 nohup 丢后台即可:nohup ./gpu_monitor.sh training_run1 > /dev/null 2>&1 &。如果希望开机自启,可以放到 systemd service 或者 rc.local 里,脚本本身不需要交互输入。

3.3 用 Python 的 pynvml 做更细的采集

如果觉得 Bash 解析 CSV 不够灵活,或者想直接在训练脚本内部同步采样,我推荐pynvml,它是 NVIDIA 官方 NVML 库的 Python 绑定:

pip install nvidia-ml-py

一个最小示例:

from datetime import datetime import csv import pynvml pynvml.nvmlInit() device_count = pynvml.nvmlDeviceGetCount() fieldnames = [ "time", "gpu_index", "util_percent", "mem_used_mib", "mem_total_mib", "power_w", "temp_c", "sm_clock_mhz", "mem_clock_mhz", ] with open("gpu_detail.csv", "a", newline="") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) for i in range(device_count): handle = pynvml.nvmlDeviceGetHandleByIndex(i) util = pynvml.nvmlDeviceGetUtilizationRates(handle) memory = pynvml.nvmlDeviceGetMemoryInfo(handle) power = pynvml.nvmlDeviceGetPowerUsage(handle) temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU) sm_clk = pynvml.nvmlDeviceGetClockInfo(handle, pynvml.NVML_CLOCK_SM) mem_clk = pynvml.nvmlDeviceGetClockInfo(handle, pynvml.NVML_CLOCK_MEM) writer.writerow({ "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "gpu_index": i, "util_percent": util.gpu, "mem_used_mib": memory.used // (1024 * 1024), "mem_total_mib": memory.total // (1024 * 1024), "power_w": power / 1000.0, "temp_c": temp, "sm_clock_mhz": sm_clk, "mem_clock_mhz": mem_clk, }) pynvml.nvmlShutdown()

注意两个单位坑:nvmlDeviceGetMemoryInfo返回的是字节,所以要除1024 * 1024才是 MiB;nvmlDeviceGetPowerUsage返回的是毫瓦,要除 1000 才是瓦。这两个单位我一开始没核对,日志里出现过好几条天文数字。

pynvml 的优势在于可以精确选择采样点。比如你可以在每个训练 epoch 结束的时候采一次样本,把 GPU 数据和 loss 变化对齐。基于命令行定时抓取做不到这种“和训练流程同步”的效果。

3.4 日志轮转与目录规划

日志文件一旦开始长期运行,存储早晚是个问题。按天拆文件只是第一步,还要考虑保留周期。我的建议是目录结构按“任务/日期”分层:

/var/log/gpu_monitor/ ├── training_run1_20250601.csv ├── training_run1_20250602.csv ├── inference_svc_20250601.csv └── ...

如果底层是 Linux 系统,直接用 logrotate 做轮转最省事。配置文件可以这样写:

/var/log/gpu_monitor/*.csv { daily rotate 14 compress delaycompress missingok notifempty copytruncate }

copytruncate很关键。因为采集脚本持有 CSV 文件描述符,普通 rename 轮转会导致脚本继续往旧 inode 写数据,而copytruncate是先拷贝一份再清空原文件,这样脚本不需要重启就能正常切换。虽然理论上存在极小概率的写入丢失,但对于秒级采集的 GPU 日志来说可以接受。

3.5 采样间隔与时间戳

采样间隔推荐 5 秒到 30 秒之间。有人为了“看得更精细”把采集间隔压到 0.5 秒,实际测下来有两个问题:一是日志文件膨胀特别快,7 天就能堆出几个 GB;二是nvidia-smi本身有调用开销,频繁调用会在驱动层产生不必要的负载,尤其在高并发训练时会影响性能统计的准确度。训练任务用 10 秒,推理任务用 5 秒,长周期容量评估用 30 秒,基本覆盖了我遇到的绝大多数场景。

时间戳必须用本地时间,而且要在多台机器之间统一时区。不然你把两台机器的日志拉到一起对比,发现 GPU 利用率一个在 8 点掉下去、一个在 3 点掉下去,结果只是因为两台机器一个用了 UTC 一个用了 CST,这种问题排查起来非常闹心。采集脚本里最好直接写明,比如date '+%Y-%m-%d %H:%M:%S %z',把时区偏移也放进去。

4. 那条经典报错:“couldn't communicate with the nvidia driver”排查全集

4.1 报错长什么样、什么时候容易出现

完整报错是这样一行:

NVIDIA-SMI has failed because it couldn't communicate with the nvidia driver. Make sure that the latest NVIDIA driver is installed and running.

这句话字面意思是nvidia-smi找不到驱动通信入口。最常发生的场景包括:新装完 Linux 系统还没装驱动就急着跑nvidia-smi;内核升级后没有重新安装对应驱动;系统重启后驱动模块没有自动加载;笔记本双显卡切换没切好;容器环境没有把 GPU 设备挂载进去。

有一点必须说在前面:绝大多数情况下这个报错不是显卡坏了。我在公司处理过几十次类似工单,真正硬件损坏的只有两次,而且那两次机器都是插在扩展坞上被频繁热插拔折腾过。所以看到这个报错不用慌,按顺序一套流程走下来基本都能定位。

4.2 完整排查链路

第一步是确认系统到底认不认这张卡。在终端执行:

lspci | grep -i nvidia

如果这里能看到类似NVIDIA Corporation GA102 [GeForce RTX 3080]的输出,说明 PCIe 层面的硬件枚举是正常的,问题方向可以确定为驱动或权限。如果这一行都没有,那么优先怀疑物理插槽接触不良、主板 BIOS 设置里把 PCIe 槽禁用了,或者机器是云主机但实际没有分配 GPU 资源(有些“GPU 云服务器”只是开了带虚拟 GPU 的实例,物理驱动路径不一样)。

第二步检查内核模块有没有加载:

lsmod | grep nvidia

正常会看到nvidia、nvidia_uvm、nvidia_drm、nvidia_modeset这几个模块。如果完全没有输出,先手动加载一下:

modprobe nvidia

加载成功后再执行nvidia-smi。如果modprobe报错或者没有任何反应,就进一步看内核日志:

dmesg | grep -i nvidia

常见错误包括版本不匹配、符号缺失、NVRM: failed to initialize等等。内核从 5.x 升到 6.x 之后,旧版驱动模块经常出现这种问题,解决办法一般不是抠配置文件,而是直接下载对应新内核版本的驱动重新安装。

第三步确认设备节点存在。驱动加载成功后,系统会在/dev下创建nvidia0、nvidia1、nvidiactl、nvidia-uvm等设备文件:

ls -l /dev/nvidia*

如果设备文件缺失或者权限不对,nvidia-smi同样会报通信失败。可以尝试nvidia-modprobe重新生成设备节点,或者手动授权:

chmod 0666 /dev/nvidia*

第四步排查内核版本与驱动版本匹配关系。执行uname -r看当前内核版本,再到 NVIDIA 驱动说明文档确认该版本支持的 LTS 内核范围。这里有一条铁律:升级内核之后,不重新安装驱动,NVIDIA 模块几乎必然失效。我自己习惯是保留一条Ubuntu 的 HWE 内核回滚入口,万一新内核和驱动不兼容,能快速回到旧内核继续干活。

第五步检查 Nouveau 驱动是否抢占。有些发行版默认加载开源的 Nouveau,它和 NVIDIA 闭源驱动冲突,导致后者初始化失败。要么彻底 disable Nouveau:

echo "blacklist nouveau" >> /etc/modprobe.d/blacklist-nvidia-nouveau.conf echo "options nouveau modeset=0" >> /etc/modprobe.d/blacklist-nvidia-nouveau.conf update-initramfs -u

要么在安装 NVIDIA 驱动时选择覆盖掉它。装完之后重启再看nvidia-smi是否正常。

4.3 双显卡笔记本的特殊情况

热词里有个“Intel UHD Graphics + NVIDIA GeForce RTX 4060 Laptop GPU”的组合,这类笔记本在 Linux 下非常容易触发上面的报错。核心原因是笔记本默认会把独显切到电源门控状态,驱动在省电模式下没有做初始化,nvidia-smi访问时找不到活跃设备。

可以先尝试强制唤醒独显:

nvidia-smi -L

如果仍然报错,检查是不是处于 PRIME 切换状态。使用prime-select把模式切到nvidia:

sudo prime-select nvidia sudo reboot

重启之后如果nvidia-smi已经有输出,再按照上一节的“内核模块 + 设备节点 + 权限”顺序确认持久化。注意这种机器在反复切换 Windows/Linux 双系统时,有些 BIOS 的 Fast Boot 会导致硬件初始化状态异常,进入 BIOS 关闭 Fast Boot 再试一次,属于性价比极高的排查动作。

4.4 容器环境下的特殊处理

很多同学不是在宿主机上直接跑,而是通过 Docker 起容器来跑深度学习。宿主机上nvidia-smi一切正常,容器内部一执行就报 communication failed,这种情况几乎都是因为设备没有挂载进来。

用 Docker 运行 GPU 容器,现在已经不需要手动挂/dev/nvidia*了,关键是安装并配置nvidia-container-toolkit。典型流程:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | sudo tee /etc/apt/sources.list.d/libnvidia-container.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker

然后容器启动时:

docker run --gpus all -it nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi

--gpus all会自动帮容器挂载 GPU 设备和驱动库。如果用的是旧版 Docker 或者旧版 toolkit,这部分还是有可能失败,可以手动用--device=/dev/nvidia0:/dev/nvidia0 --device=/dev/nvidiactl:/dev/nvidiactl挂载设备文件,再把/usr/lib/x86_64-linux-gnu/libnvidia-ml.so这类运行库拷进容器,但复杂度会高很多,能升级 toolkit 就不要手动挂载。

4.5 如何让监控脚本不至于一崩到底

排查完报错之后,还应该想一下怎么预防下一次中断,尤其是长期跑监控日志的场景。我做了三个防御动作:

第一个动作,开启持久化模式,nvidia-smi -pm 1。它能减少驱动懒加载窗口,这个窗口期间nvidia-smi的响应会出现短时超时。

第二个动作,在采集脚本里检查退出码。如果某一次nvidia-smi失败,脚本不能继续往日志里写空数据或者重复写最后一行,应该记录一条告警再跳过本次采样。用 Bash 的话可以在nvidia-smi命令后面加上|| echo "$TS,$TASK_ID,ERROR,nvidia-smi failed" >> "$CSV",方便日志语义保持完整。

第三个动作,给监控脚本加一个 systemd service,让它在崩溃后自动重启:

[Unit] Description=GPU monitor service [Service] ExecStart=/usr/local/bin/gpu_monitor.sh default Restart=always RestartSec=10 [Install] WantedBy=multi-user.target

需要说明的是,这层防护只解决“监控进程没挂”的问题。如果驱动本身已经和内核不匹配了,不管脚本怎么重启,底层的nvidia-smi该失败还是失败,最终还是要回到 4.2 的流程重新装驱动。

5. 日志只是第一步:向上对接监控平台与趋势分析

5.1 轻量对接 Prometheus 的 textfile 模式

单机日志做得再完善,也还是偏“离线”。如果公司已经有一套基于 Prometheus 的监控体系,最省事的接入方式是用 node_exporter 的 textfile collector。原理很简单:node_exporter 会定时读取指定目录下以.prom结尾的文件,把文件里的指标直接暴露给 Prometheus。

先建目录:

mkdir -p /var/lib/node_exporter/textfile

然后写一个采集脚本,把nvidia-smi的输出转换成 Prometheus 指标格式:

#!/bin/bash # gpu_to_prom.sh # 放到 cron 里每 15 秒执行一次也可以 OUT_DIR="/var/lib/node_exporter/textfile" OUT_FILE="${OUT_DIR}/gpu.prom" TMP_FILE="${OUT_DIR}/gpu.prom.$$" nvidia-smi --query-gpu=index,name,utilization.gpu,memory.used,memory.total,power.draw,temperature.gpu \ --format=csv,noheader,nounits | \ while IFS=',' read -r index name util mem_used mem_total power temp; do cat > "$TMP_FILE" <<EOF # HELP gpu_utilization_percent GPU utilization in percent. # TYPE gpu_utilization_percent gauge gpu_utilization_percent{gpu_index="$index",gpu_name="$name"} $util # HELP gpu_memory_used_bytes Memory used on GPU in bytes. # TYPE gpu_memory_used_bytes gauge gpu_memory_used_bytes{gpu_index="$index",gpu_name="$name"} $((mem_used * 1024 * 1024)) # HELP gpu_memory_total_bytes Total memory on GPU in bytes. # TYPE gpu_memory_total_bytes gauge gpu_memory_total_bytes{gpu_index="$index",gpu_name="$name"} $((mem_total * 1024 * 1024)) # HELP gpu_power_watts Current GPU power draw in watts. # TYPE gpu_power_watts gauge gpu_power_watts{gpu_index="$index",gpu_name="$name"} $power # HELP gpu_temperature_celsius Current GPU temperature in Celsius. # TYPE gpu_temperature_celsius gauge gpu_temperature_celsius{gpu_index="$index",gpu_name="$name"} $temp EOF done mv "$TMP_FILE" "$OUT_FILE"

关键点在于最后用mv移动临时文件,而不是直接 append 到目标文件,这样能避免 Prometheus 读到半行残留。启动 node_exporter 时加上--collector.textfile.directory=/var/lib/node_exporter/textfile就能自动采集这些指标。

如果是数据中心级显卡,NVIDIA 官方还有 DCGM 方案,能提供更细粒度的 SM 占用、NVLink 带宽、PCIe 吞吐等指标,但它的部署成本更高。对个人工作站和普通训练服务器来说,nvidia-smi的 textfile 模式已经够用了。

5.2 在训练代码里做“指标对齐”

对接了 Prometheus 之后,你看到的是 GPU 侧的独立指标流。另一个非常实用的做法是把 GPU 采样直接埋进训练代码里,让 GPU 指标和 loss、step、吞吐量落在同一条时间线上。pynvml 在 3.3 已经提过了,这里给一个训练循环内的典型用法:

for step, batch in enumerate(dataloader): loss = train_step(batch) if step % 50 == 0: handle = pynvml.nvmlDeviceGetHandleByIndex(0) util = pynvml.nvmlDeviceGetUtilizationRates(handle) memory = pynvml.nvmlDeviceGetMemoryInfo(handle) log.append({ "step": step, "loss": float(loss), "gpu_util": util.gpu, "mem_used_mib": memory.used // (1024 * 1024), })

这种做法的价值在定位 OOM 时尤其明显。显存并不是一直线性增长的,某个环节会突然飙升。把显存采样点和数据 pipeline 的日志对齐后,你可以精确定位到“OOM 前 20 个 step 里到底发生了什么”,而不是靠猜。

5.3 告警阈值别拍脑袋

接入监控平台之后,接下来就是配告警。最容易踩的坑是拿 GPU 利用率做一刀切告警。我见过有人给 GPU 利用率设了一个“低于 30% 就报警”的规则,结果马上被推理服务误伤——低负载推理服务本来利用率就是 0,因为它大部分时间在等待请求,并不代表服务异常。

更适合做告警的指标有三个:GPU 温度超过阈值(比如 92°C),显存剩余低于阈值(比如剩余 2GiB 以下且持续 5 分钟),以及功率异常归零(可能伴随掉卡或者驱动异常)。这三个指标背后都有明确的硬件事件,误报率低。

如果确实想用利用率告警,建议加一个前提条件:当前确实在跑训练任务,且外部吞吐量低于预期。单纯只看 GPU 利用率本身,信号是高度冗余的。

5.4 趋势分析才是最终目的

日志和监控平台都跑起来之后,回头再去看数据,你会发现趋势比瞬时值有用得多。瞬时利用率 50% 说明不了任何问题,但连续一周的利用率曲线能告诉你这台 GPU 到底该配给哪个团队。容量规划也一样:你把所有训练任务各自的显存峰值画出来,就会发现“16G 显存的机器已经跑不下三个并发任务了”,这时候换 24G 或者 48G 的机器就不是拍脑袋,而是有数据支撑的决策。

我自己比较喜欢做的一个分析,是按小时汇总平均利用率:

SELECT date_trunc('hour', time) AS hour, AVG(utilization_percent) AS avg_util, MAX(memory_used_mib) AS max_used_mib FROM gpu_metrics WHERE task_id = 'training_run1' GROUP BY hour ORDER BY hour;

有了这种时间粒度的一条曲线,再叠加上训练代码的版本记录,几乎每个性能劣化都能对上号。注意,做这些分析的前提是日志采集从一开始就规范地加了时间戳、任务 ID、机器 ID,否则后面一切拉取汇总都是无源之水。

我在实际落地这套监控日志之后,最大的感受不是“多了一张趋势图”,而是当训练出问题时,我能把时间轴拉到分钟级,直接回看事发前后几分钟的 GPU 状态。有一次深夜大模型训练速度突然掉了一半,我对比 GPU 监控日志和训练日志,发现正好是数据缓存目录被清掉,所有 worker 都在疯狂重新读取数据,GPU 在等数据。这种问题如果还停留在手动敲一遍nvidia-smi的阶段,根本来不及发现,更别提定位了。

最后再分享一个我常用的土办法,帮你快速判断训练慢是不是卡在数据读取:把采样间隔压到 0.5 秒,连续盯十几秒的 GPU 利用率曲线。如果它像心跳一样“跳一下、停一下、再跳一下”,大概率是 CPU 每拿到一个 batch 的间隙里 GPU 在空转,瓶颈在数据管线;如果利用率一直拉满但吞吐依然上不去,那就要去看 kernel 规模和计算密度了。这个判断不一定绝对,但作为第一轮排查方向,真的能省下大把时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询