ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

深度学习GPU监控实战:从nvidia-smi到Prometheus+Grafana告警体系

深度学习GPU监控实战:从nvidia-smi到Prometheus+Grafana告警体系

跑深度学习训练的人,几乎每天都要跟 NVIDIA GPU 打交道。训练崩了、显存爆了、利用率上不去,说来说去都是同一个问题:一开始没人把 GPU 监控当回事。我自己早几年刚接触服务器的时候,只会敲 nvidia-smi 看一眼显存还剩多少,用完就完事。后来开始调多卡训练、排查 OOM、搭可视化监控,才发现 GPU 的状态远比表面看到的要复杂。这篇文章会把我在实际环境里验证过的监控方法完整梳理一遍,从最基础的 nvidia-smi 命令,到长期日志、可视化告警,再到训练场景下容易踩的几个坑。刚入门的同学建议从第 1 节开始看,有一定基础的同学可以直接跳到第 4、5 节,那里有不少我踩过之后才总结出来的经验。

1. 先从最基础的 nvidia-smi 说起

1.1 一条命令看懂 GPU 的“体检报告”

输入 nvidia-smi,你会在终端里看到一张大表。很多人习惯只看右上角的显存占用,但这一屏信息其实非常密集。顶部写着驱动版本和 CUDA 版本,每次环境排查我都会先确认这两个对得上号;下面每张卡单独一行,从左到右依次是 GPU 型号、当前显存用量、计算利用率(Util%)、温度、风扇转速、功耗、性能状态(Pstate),以及底部列出的正在运行的进程。

这里面的门道在于,利用率并不能完整反映卡在忙什么。一类常见场景是 GPU Util 显示接近 100%,但功耗只有几十瓦,这说明计算单元一直在发起大量小 kernel,计算效率不高;反过来,功耗拉满但利用率上不去,多半是显存带宽或数据传输卡住了。所以我建议练成一个习惯:看状态时把利用率、功耗、温度三列同时参看,而不是只盯着一个数字。单独看利用率很容易产生“跑得还不错”的错觉,真正排查性能问题时,这三个字段缺一不可。

1.2 把输出变成脚本能解析的格式

nvidia-smi 支持用 --query 指定字段输出,这比人工读表靠谱得多。我最常用的一条是:

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

这里有几个容易忽略的参数:csv 表示输出逗号分隔;noheader 表示不带表头,方便拼接时间戳;nounits 会去掉百分号、瓦特这类单位,得到的全是纯数字,用 awk 或者 Python 处理都不用清洗。如果想把输出接到告警脚本里,可以直接用 awk 按列提取,比如取第三列温度做判断,写成awk -F', ' '$2>85 {print}'就是一个最简单的温度告警雏形。想查看有哪些字段可以传入,运行 nvidia-smi --help-query-gpu,列表非常长,像风扇转速、显存总数、PCIe 吞吐、电源功耗上限都包含在内。

查看正在占用 GPU 的进程则用:

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

配合 pid 再去看这个进程到底在做什么,基本就能定位是谁在吃卡。这里补充一个我踩过的坑:第一次调用 nvidia-smi 偶尔会有半秒延迟,这是驱动初始化导致的,写在高频采样脚本里时别把间隔设得太小,10 秒一次足够,否则脚本自身也会占用不少 CPU 和 IO。

1.3 容易被忽略的 Pstate、持续模式和 MIG

Pstate 是显卡当前工作状态的编号,范围从 0 到 12,数字越大越接近闲置。看到 P8、P12 说明显卡基本在“待机”,不用担心功耗;如果长期在 P0 附近,则说明负载一直很重。这个字段平时容易被忽视,但在排查“显卡功耗高但利用率低”时非常有用,两个数字一对比,就能看出是不是在做大量小指令调度。

还有两类情况,新手第一次看到常常会慌。一类是打开了 MIG 模式的卡,nvidia-smi 显示的显存总容量会明显变小,因为整张卡被切片成多个实例,这时所有查询要加上 --id 指定具体实例;另一类是把驱动程序的“持久化模式”关闭了,每次调用 nvidia-smi 都得重新初始化,响应明显变慢。前者主要是多用户共享资源时的配置,后者建议在服务器上通过nvidia-smi --persistence-mode=1打开,尤其是有频繁监控需求的环境,这样连续查询的延迟会稳定很多。

2. 实时刷新与后台日志:把监控变成长期任务

2.1 watch 命令:调试时最快的刷新方式

需要盯住当前状态变化时,我最常用的是:

watch -n 1 nvidia-smi

nvidia-smi 默认只打印一次,加 watch 后每秒钟刷新一次,温度、利用率、显存变化都能实时看到。加-d参数还能高亮两次刷新之间有变化的部分,观察显存抖动时特别有用。这个命令适合调试程序、观察 OOM 快要发生时的显存走势。它的局限也很明显:关掉终端就没有任何历史记录,而且只能看到当前状态,前面发生过的情况全部被冲掉。所以它定位是“临时工具”,不能作为长期监控方案。真要把监控留痕,还得往日志方向走。

2.2 一个简单的采样脚本:把状态写进日志

想要留痕,最直接的方式是写一个后台脚本,定时把 GPU 状态追加到 CSV 文件里。我最初用的版本是这样:

#!/bin/bash while true; do ts=$(date +"%Y-%m-%d %H:%M:%S") status=$(nvidia-smi --query-gpu=index,utilization.gpu,memory.used,temperature.gpu --format=csv,noheader,nounits | paste -sd '|') echo "$ts,$status" >> /var/log/gpu_monitor.csv sleep 30 done

用 nohup 拉起来之后,它就会每 30 秒记录一次。这里有几个细节:采样间隔不建议低于 10 秒,否则监控脚本自己会占用额外 IO 和 CPU;日志文件要配合 logrotate 定期归档,避免跑几个月后把磁盘塞满;如果要写更复杂的判断逻辑,比如温度超过阈值就写一条 WARN,建议改成 Python 脚本,流程上更容易维护。启动方式也很常规:

nohup /usr/local/bin/gpu_status.sh >> /dev/null 2>&1 &

或者直接放入 crontab,每分钟执行一次,再把输出追加到同一个 CSV。这个方案没有告警能力,但作为“事后回溯”的数据来源已经够用。真想做告警和面板展示,光靠 CSV 不够,后面那套可视化方案才是完整的答案。

2.3 nvtop:SSH 进去一眼看清全盘状态

长时间在终端里看 GPU,我强烈推荐 nvtop,它对标的体验完全是 htop。安装好之后直接运行 nvtop,界面会展示每张卡的利用率、显存、温度、功耗的实时曲线,底部还能列出当前占用 GPU 的进程,按 CPU、内存排序也都能操作。整个界面会覆盖终端当前屏幕,退出按 q 就行。

用它的经验是:在排查“为什么这轮训练 GPU 利用率掉到 0”时,开着 nvtop 同时再开另一个终端跑数据加载测试,能够很快定位是不是 CPU 预处理把数据喂慢了。类似工具还有 gpustat,但在交互体验和信息密度上,nvtop 更接近 htop,我个人更建议先装 nvtop。这个工具虽然看起来只是把 nvidia-smi 的数据可视化,但信息密度高很多,用完基本回不去了。

3. 真正可以告警的可视化监控:Prometheus + Grafana

3.1 这套组合是怎么分工的

如果你想做到“温度超过 85 度就告警、显存不足自动通知、训练跑到一半能回看历史曲线”,那就必须上可视化监控了。我选的是行业内验证过很多次的组合:一个采集器进程负责把 GPU 状态转成 HTTP 接口;Prometheus 定时去拉取这些指标并存储成时序数据;Grafana 负责从 Prometheus 读取数据,画成面板并执行告警规则。

为什么选这套而不是自己写脚本?我自己以前也觉得写个 shell 脚本够了,但真正遇到多节点、多卡、需要历史回溯的场景,脚本就力不从心了:一是历史查询没有现成索引,二是告警阈值改起来要改代码,三是和 CPU、内存、网络监控没法统一在一张图里。Prometheus 这套体系天然就是为长期运行、多维查询、规则告警设计的,之前只用来监控过 CPU 的话,这次加一个 GPU exporter 就能把空白补上。社区里有大量现成的 exporter 实现,选择基于 NVIDIA 官方工具暴露指标的那种即可,避免去维护一套自己解析 nvidia-smi 输出的私有协议。

3.2 从零部署一次:采集、存储、展示

先说采集器。如果熟悉容器操作,最省事的方式是跑一个 exporter 容器:

docker run -d --name gpu-exporter \ --restart unless-stopped \ --gpus all \ -p 9835:9835 \ 社区或官方维护的 GPU exporter 镜像

这里有个容易踩的坑:容器必须通过--gpus all显式把 GPU 设备传进去,否则 exporter 读不到任何显卡指标,日志里全是 devices not found。启动后可以用curl localhost:9835/metrics验证,能看到大量以 gpu_ 开头的指标就说明采集端正常。

Prometheus 端只需要在配置文件里加一个 job:

scrape_configs: - job_name: 'gpu' scrape_interval: 15s static_configs: - targets: ['localhost:9835']

重启 Prometheus 后,在 Targets 页面如果看到 UP 状态,采集链路就通了。Grafana 这边推荐的做法:先把 Prometheus 填入数据源,然后导入社区现成的 GPU 监控仪表盘模板,导入后一般就能看到温度、利用率、显存、功耗这些面板。如果想自己画图,最简单的查询是avg(gpu 利用率字段)并按时间范围展示,配置好单位和图例即可。我自己第一次搭这套时,最耗时间的就是找对指标字段名,所以建议先导入模板,再从模板里逆向学习指标关系,会比从零建面板快很多。

告警配置在 Grafana 的 Alerting 栏里通过规则统一管理,也可以直接写在 Prometheus 的 rules 文件中。下面是一个示例,实际部署时把指标名替换成你采集到的字段:

groups: - name: gpu_alerts rules: - alert: GPUHighTemperature expr: 你的温度指标 > 85 for: 5m labels: severity: warning annotations: summary: "GPU {{ $labels.gpu }} 温度过高"

3.3 一开始就设定好的三个告警阈值

结合我实际运营服务器的经验,建议至少先做三类告警。

告警项建议阈值触发说明
GPU 温度85℃,持续 5 分钟温度长时间过高容易引起降频和硬件老化
显存剩余低于 10% 或不足某阈值防止生产任务 OOM,尤其要注意推理节点
利用率异常10 分钟内持续为 0 且有训练进程训练可能已卡死或数据线程异常

第一是温度告警,消费级卡长期超过 85 度会影响寿命,建议阈值设在 85,持续 5 分钟以上再触发,避免瞬时波动误报。第二是显存告警,手动设置“剩余显存低于 10%”或“可用显存不足某数值”,这个在生产环境的推理任务里尤其重要,不然出现 OOM 的时候都是事后才知道。第三是“利用率长期为 0 但进程还在”的异常告警,这类情况多半意味着训练卡死或数据加载线程挂掉了,没有这个规则,你可能第二天早上才发现训练停了半夜。还可以考虑功耗和风扇转速,但前三条覆盖了绝大多数日常问题。初期不要设置太多规则,否则告警疲劳反而会让人忽略真正的问题。

4. 深度学习训练场景下的监控心得

4.1 最隐蔽的陷阱:显存碎片导致的 OOM

在 PyTorch 这类框架里跑训练,监控最常遇到的现象是:训练开了一两个小时后中途突然报 CUDA out of memory,重启同一份代码又能继续跑。这种不是单纯因为显存不够,更多是显存碎片造成的——框架默认的显存缓存分配器会在反复分配和释放中留下大量不连续的小块,最终导致即使总剩余显存足够,也分配不出连续的大块。

建议先用 nvidia-smi 看两层数据:一层是整卡显存容量和使用量,另一层是进程自身统计的显存占用,两者的差值往往就是缓存碎片。框架层面常见的缓解办法是开启扩展内存段支持,在启动训练前设置:

export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True

结合监控去验证时,你会发现开启后显存曲线比之前稳定不少。TensorFlow 场景也有类似限制显存增长、保留最小碎片空间的选项。这个坑最值得记住的是:遇到“偶尔 OOM”先别急着加显存,优先怀疑碎片。

4.2 多卡利用率一并打到 100% 并没有那么简单

训练时打开 nvidia-smi,常见画面是四块卡里利用率在 0 到 100 之间来回抖动,或者某些卡长期落后。这种不均匀,最常见的原因不在 GPU 而在数据供给链路:DataLoader 的 num_workers 不够、CPU 预处理太慢、磁盘 IO 跟不上,都会让 GPU 在等待中空转。想验证的话,我会同时开一个终端看 CPU 使用率和 nvtop,如果 CPU 在那一刻已经打满,大概率就是喂数据喂不上的问题。

更细的定位可以用nvidia-smi dmon,它按秒刷新,能看到 Utilization 列的瞬时波动;再用工具查看网络吞吐,能区分是在等数据还是在等梯度同步。多机训练里梯度同步的等待经常被误认为 GPU 故障,其实看 Power 和 Utilization 就会明白:训练并没有停止,只是每轮之间有一段同步空窗。这种问题监控面板看平均值往往看不出来,必须看秒级曲线,所以我一直强调日志采样间隔不能太大。

4.3 在训练日志里埋点:GPU 指标和 loss 放一起看

如果要长期优化训练,建议把 GPU 监控和训练本身的指标挂到同一个观测面板上。最简单的方式是写一小段采集代码,在验证循环里读取当前显存、温度和利用率:

import pynvml pynvml.nvmlInit() handle = pynvml.nvmlDeviceGetHandleByIndex(0) def read_gpu_stats(): mem = pynvml.nvmlDeviceGetMemoryInfo(handle) util = pynvml.nvmlDeviceGetUtilizationRates(handle) temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU) return { "gpu_mem_used": mem.used // 1024 ** 2, "gpu_mem_total": mem.total // 1024 ** 2, "gpu_util": util.gpu, "gpu_temp": temp, }

把这个函数的返回值配合每个迭代的 loss 一起写入 TensorBoard 或 CSV,后面复盘时就能看到“loss 开始震荡的那一刻,GPU 温度是不是已经接近 90 度”这种问题。比单独看监控面板更直观的一点是,它可以把 GPU 状态和训练进度对齐到同一条时间线上,而不用手动去对两张图的时间戳。训练结束前记得调用 pynvml.nvmlShutdown() 释放资源,不然下次初始化可能会报警告。再有条件的话,把每轮的 batch 耗时也记录下来,和 GPU 利用率一起观察,就能快速判断瓶颈在前后向计算还是数据加载。

5. 常见问题速查与避坑

5.1 GPU 利用率很低,显存却占了很高

这种情况我遇到最多的是两类原因。一类是数据加载瓶颈,GPU 在等 CPU 把样本喂进来,此时显存里已经缓存了部分 batch,但计算单元闲得慌;另一类是模型结构里存在大量串行小算子,导致计算单元没有被充分利用。前者把 DataLoader 的 num_workers 调大、用 prefetch 机制预取数据,观察是否有改善;后者需要用 profiler 工具分析热点算子,看能不能把串行操作改成 batch 化。判断到底是哪一种,最快的方法是看 CPU 利用率:CPU 被打满多半是数据问题,CPU 不高但 GPU 利用率愣是上不去,就要往算子执行效率方向查。

5.2 温度过高导致显卡降频

Pstate 会直观反映降频。如果发现温度上升后 Util 反而下降,看 Pstate 是不是从 P0 退到了更高的数字,功耗上限也被压低,那基本可以确认触发了温度保护。除了清灰、改善机箱风道之外,临时手段可以用nvidia-smi -pl手动限制功耗上限,把温度压在一个稳定区间。要注意这个设置会改变整卡的性能边界,只建议在确实散热不足的机器上使用。还有一个容易被忽略的细节是显卡与显卡之间的间距,有些服务器为了节省空间把卡插得太密,中间几块卡的温度往往比两端高出不少,看监控的时候如果只有中间卡报警,优先检查物理散热。

5.3 nvidia-smi 输出异常或显示 “N/A”

驱动问题、MIG 模式、容器权限都可能导致输出不对。常见的是更新驱动后内核模块没有加载成功,先用lsmod | grep nvidia确认模块状态,再加载对应的 nvidia 相关模块。容器里出现权限问题时,往往需要在启动时传入设备访问权限或者直接使用 NVIDIA 容器工具包,让容器正确看到 GPU 设备。另外,MIG 开启后某些字段在整卡维度上是“N/A”的,需要按照实例 ID 查询,别急着怀疑驱动坏了。这类问题排查时先分清楚是“物理卡看不到”还是“部分字段读不到”,前者考虑驱动和设备直通,后者基本就是查询维度选错了。

5.4 显存泄漏:跑多久显存就涨多久

长时间运行后显存慢慢涨满,通常是缓存没释放。排查思路是分阶段盯曲线:先跑固定的若干轮,看显存是不是每轮都稳定上涨;然后在代码里先注释掉验证、再注释掉数据加载,逐步二分定位到底哪一段在涨。PyTorch 下还要小心 DataLoader 的 worker 数量设置,以及在循环里创建的临时变量没有及时清除,该加 del 加 del,该清缓存清缓存。这里尤其推荐把第 2 节的后台采样脚本先跑起来,因为显存泄漏一定是缓慢的、跨几个小时的,没有长期日志帮忙拉曲线,靠肉眼很难判断什么时候开始异常。我处理过的一个案例,就是靠对比日志发现某个验证函数在每次调用后多保留了整个 batch 的梯度,定位后一行代码就解决了。

我自己现在服务器上保留的最终方案其实很固定:日常排查用 nvtop,长期稳定性交给 Prometheus 加 Grafana,训练日志里埋一段 pynvml 采样。这套组合每个环节都用在自己最合适的位置,不算复杂但非常实用。如果你现在还停留在“出问题时才敲一下 nvidia-smi”的阶段,我建议先把第 1 节的查询参数练熟,再加个后台采样脚本,养成随时有数据可查的习惯。GPU 监控这件事最难的不是工具,而是先有“它值得被持续记录”的意识,一旦这个习惯建立起来,很多诡异的问题都能在真正出事之前被监控曲线提前拦住。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进