ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Prometheus GPU监控实战:从nvidia-smi到智能告警阈值设计

Prometheus GPU监控实战:从nvidia-smi到智能告警阈值设计 搞 GPU 监控这事我是被一个显卡偷偷罢工的案例逼上道的。当时线上有三台训练服务器跑深度学习模型白天还好好的一到后半夜利用率就莫名跌到个位数显存却还占着日志里看不出任何报错值班同学只能一遍遍手动敲nvidia-smi盯进程。后来我决定用 Prometheus 把 GPU 使用率、显存、温度这些指标全部纳入监控体系再配合一套相对阈值和动态基线结合的告警规则终于把半夜爬起来看显卡脸色变成了Alertmanager 钉钉告警精准定位。这就是我这次要分享的实战内容如何用 Prometheus 监控 GPU 使用率并设计一套真正能用的智能告警阈值而不是简单写个 95%就完事。这套方案适合谁如果你手里有单机多卡或者一个小的 GPU 服务器集群之前靠nvidia-smi肉眼观察或者只在 Grafana 上配了几个固定阈值面板那这篇文章可以让你少踩很多坑。我不会只给一堆 YAML 让你复制而是会把每个指标的含义、每条 PromQL 的意图、每个阈值背后的理由都拆开讲清楚确保你改完规则之后知道自己在干什么。1. 这套方案要解决什么问题以及为什么选 Prometheus1.1 用 nvidia-smi 盯 GPU 的三个硬伤先聊聊最原始的方案nvidia-smi。这个命令本身没有问题信息量大、输出直观但它有三个硬伤。第一无法形成历史曲线。你敲一次命令只能看到当前瞬间的状态训练跑 12 个小时中途某一个小时利用率从 98% 掉到 20%你没盯到就永远错过了。事后复盘只能靠猜。第二无法自动告警。你可以写个 while 循环配合脚本去轮询但脚本本身的可靠性、频率控制、告警通道都要自己操心本质上是在重复造轮子。第三多台机器没法统一看。我这边三台服务器每台可能有四张卡手动登录每一台去逐个 card 切换效率低到让人绝望。对比之下Prometheus 这套组合拳天然就是干这个的exporter负责采集指标Prometheus负责存储和计算Alertmanager负责把异常转成通知Grafana负责可视化。它不会漏掉历史数据告警规则的表达能力比脚本强得多而且一套规则可以同时覆盖所有节点。这不是赶时髦是解决真实问题的正确姿势。1.2 选型对比nvidia_gpu_exporter 还是 DCGM exporter现在 GPU 监控的 exporter 其实有两条主流路线我最初也被搞糊涂过这里直接说结论。第一条是utkuozdemir/nvidia_gpu_exporter它基于 Go 语言调用 NVIDIA Management LibraryNVML的接口把 GPU 状态转成 Prometheus 格式的指标默认监听9100端口。这个项目的优点是轻量、简单没有多余的依赖二进制扔到机器上就能跑适合单机或者节点数量不多、不想引入复杂组件的场景。我这边三台机器就是用它的资源占用几乎可以忽略不计。第二条是NVIDIA 官方的 DCGM exporternvidia/dcgm-exporter它基于 Data Center GPU Manager功能更强能拿到功率上限、时钟频率、PCIe 吞吐、显存 ECC 错误等更底层的指标。缺点是依赖 DCGM 服务如果你是裸机部署需要额外跑一个 dcgm 守护进程如果你是 K8s 环境通常以 DaemonSet 方式部署配合 kube-prometheus-stack 用得比较多。我的建议很直接裸机、规模小、只想关注利用率和温度选 nvidia_gpu_exporterK8s 集群、有运维平台、需要完整台账选 DCGM。两者的告警规则设计思路完全一样只是指标名不同下文我会以 nvidia_gpu_exporter 为主展开然后在关键位置点名 DCGM 的差异。1.3 这套方案的整体架构整套监控链路是这样的GPU 所在宿主机上运行 exporter它把 NVML 读到的指标暴露成一个 HTTP 接口Prometheus 每隔scrape_interval去抓取一次存进时序数据库在 Prometheus 里面配置 alerting rules当查询结果满足触发条件时生成 AlertAlertmanager 收到 Alert 之后根据路由规则发送到钉钉、企业微信或者邮件Grafana 直接对接 Prometheus 数据源把指标画成面板。在这个链条里智能告警阈值的核心其实不在 Alertmanager而在 Prometheus 的Recording rules 和 Alerting rules。我们可以在规则里用avg_over_time等函数计算历史基线用当前值跟基线做比较这样得到的告警就比固定阈值聪明得多。接下来我从采集端开始讲一步一步把这条链路搭起来。2. 采集端部署两条路线一次说清2.1 环境准备驱动、容器运行时、端口规划开始部署之前先把环境摸清楚。我这次用的节点是 Ubuntu 22.04NVIDIA 驱动版本 535显卡是 RTX 4060 Laptop GPU 加一块 Intel 核显。注意这个组合它埋了一个坑后面我会单独说。exporter 依赖 nvidia-smi 底层的 NVML所以NVIDIA 驱动必须装好先执行nvidia-smi确认能看到显卡列表和驱动版本再继续往下走。端口方面Prometheus 默认抓取端口是9090暴露自身 Web UIexporter 这边我让 nvidia_gpu_exporter 用9100DCGM 用9400。如果服务器上有防火墙我这边是 ufw记得放行sudo ufw allow 9100/tcp sudo ufw allow 9400/tcp如果不放行你在 Prometheus 服务器上curl目标端口会超时但本机 curl 又正常这种只有监控端连不上的故障最容易让人抓瞎。2.2 路线一裸机直接跑 nvidia_gpu_exporternvidia_gpu_exporter 提供了二进制和容器两种部署方式。我最推荐的是用 systemd 托管二进制原因很简单容器方案要求宿主机装了 nvidia-container-toolkit而二进制方案只要你驱动正常就行。先到项目 releases 页面下载对应架构的压缩包解压之后把二进制放到/usr/local/bin/nvidia_gpu_exporter然后创建一个 systemd 服务sudo tee /etc/systemd/system/nvidia_gpu_exporter.service /dev/null EOF [Unit] DescriptionNVIDIA GPU Exporter Afternetwork-online.target [Service] ExecStart/usr/local/bin/nvidia_gpu_exporter --web.listen-address:9100 Restartalways RestartSec5 [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now nvidia_gpu_exporter启动之后先在宿主机验证curl http://localhost:9100/metrics | grep nvidia_gpu_utilization如果输出里有类似nvidia_gpu_utilization{gpu0,...} 23这样的内容采集端就通了。这里有一个小细节exporter 默认会把gpu标签设成从 0 开始的序号后面我们写 PromQL 过滤单卡时就用这个标签。2.3 路线二Docker 跑 DCGM exporterK8s 同思路如果在 K8s 集群里直接用一个 DaemonSet 让每个 GPU 节点跑一个 DCGM exporter 是最省事的。裸机 Docker 部署也很简单但要特别注意必须加--gpus all否则容器内看不到 GPU 设备docker run -d --name dcgm-exporter --restart unless-stopped \ --gpus all \ --privileged \ -p 9400:9400 \ nvidia/dcgm-exporter:3.3.1-1.0.0验证方式跟上面类似curl http://localhost:9400/metrics | grep DCGM_FI_DEV_GPU_UTILDCGM 指标的默认名称保留了原始风格比如DCGM_FI_DEV_GPU_UTIL表示 GPU 利用率DCGM_FI_DEV_GPU_TEMP表示温度。我第一眼看到这种名字还愣了一下后来就习惯了。2.4 接入 Prometheusscrape 配置与验证采集端跑起来之后去 Prometheus 的配置文件中加一个 job。我的做法是把节点标签带上方便后面做多机聚合scrape_configs: - job_name: gpu_node static_configs: - targets: - 192.168.1.10:9100 - 192.168.1.11:9100 labels: cluster: training-env这里注意 Prometheus 默认的抓取超时是 10 秒一般 exporter 响应足够快不用动。改完配置之后别急着重启先用promtool做一次配置校验防止语法错误导致 Prometheus 直接挂掉promtool check config prometheus.yml然后kill -HUP重载或者重启服务到 Prometheus Web UI 的/targets页面确认两个 target 都是 UP 状态。这一步过了我们就有了源源不断的 GPU 指标数据流。3. 指标解读哪些是黄金指标哪些是噪音3.1 一张表先认识核心指标很多人拿到指标之后第一反应是全都画到面板上结果 Grafana 面板搞得跟仪表盘航母一样反而看不出问题。我这次整理了一张实战指标表标注了我认为的价值级别指标名含义单位价值级别nvidia_gpu_utilizationGPU 计算单元利用率0-100黄金指标nvidia_memory_used_bytesGPU 显存已使用量字节黄金指标nvidia_memory_total_bytesGPU 显存总量字节辅助指标nvidia_gpu_temperature_celsiusGPU 核心温度摄氏度黄金指标nvidia_gpu_power_usage_wattsGPU 实时功耗瓦辅助指标nvidia_gpu_core_clock_mhzGPU 核心频率MHz噪音指标nvidia_gpu_memory_clock_mhz显存频率MHz噪音指标对应的 DCGM 指标名就是DCGM_FI_DEV_GPU_UTIL、DCGM_FI_DEV_MEM_COPY_UTIL、DCGM_FI_DEV_GPU_TEMP、DCGM_FI_DEV_POWER_USAGE这几类语义一样只是命名风格不同。我的经验是核心频率、显存频率这类指标日常监控价值不大它们波动太快跟利用率强相关利用率能反映的问题它们反映不了反而容易在告警规则里引入噪音。3.2 分辨利用率和显存占用的差别这是新手最容易搞混的概念。nvidia_gpu_utilization说的是 GPU 的计算单元在一个采样周期内有多忙它不是 CPU 那种时间片占用率而是基于 NVML 的采样估算值训练模型跑矩阵乘法时它会很高而卡在 I/O 等待时它会掉得很厉害。nvidia_memory_used_bytes则代表显存里驻留了多少数据模型权重、中间激活、缓存都在里面。二者的关系很有意思显存占用高不代表 GPU 正在干活。一个训练进程如果因为数据加载太慢、同步锁等待等原因阻塞了完全可能显存占了 80%但利用率只有 5%。反过来图像推理服务并发高的时候利用率可以打满但显存可能只用了 40%。所以我的告警规则里面专门有一条就是同时看这两个指标专门抓这种假死状态。在下文第 4 节我会给出具体表达式。3.3 多卡环境下的标签陷阱回到我在开头提到的 Intel 核显 NVIDIA 独立显卡的组合。nvidia_gpu_exporter 是通过 NVML 枚举设备的它只认 NVIDIA GPU所以正常情况下只会看到 RTX 4060 Laptop GPU 这一张。但如果你用云厂商的裸金属实例或者机器上插着不同型号的多张卡gpu标签的序号和物理插槽位置不一定对应这点要注意。更微妙的问题出现在PromQL 按标签匹配时。比如你想看实例 A 上第一张卡的利用率直接写nvidia_gpu_utilization{instanceA, gpu0}一般没问题但如果你把多个实例混合在一起做计算比如算集群平均利用率时忘记按instance分组就会得到一张假平均曲线。多个节点之间有快有慢平均结果会把问题平滑掉告警也因此失去意义。我的习惯是凡是跨节点聚合的规则一律显式写出by (instance)或者按实际需要的维度分组。下面要讲的告警规则就大量用到这个原则。4. 智能告警阈值设计从固定阈值到自适应4.1 为什么固定阈值解决不了智能先看一个真实场景。某天我们的训练任务是数据密集型GPU 利用率本身就忽高忽低波动范围在 20% 到 99% 之间。如果只是简单写一条规则利用率低于 10% 就告警那么任务正常运行时极少数波谷会触发误报但真正的故障——比如 NCCL 通信卡死导致利用率跌破 1%——又被 for 参数卡着没法及时响警。反过来如果直接把阈值拉到 30%误报率会高到运维同学直接把告警静音整个监控体系就废了。这就是固定阈值的局限它不知道这台机器过去一小时什么样也不知道当前任务处在什么阶段。训练起步阶段利用率本来就要爬坡推理高峰期本来就该打满拿一个全局固定的数字去卡所有场景必然大量误报漏报。智能告警的思路不是放弃阈值而是让阈值跟随历史基线动态浮动或者设计组合条件把异常从正常波动里剥离出来。4.2 第一层组合条件告警先过滤掉无脑误报组合条件才是性价比最高的智能。我们先把容易误报的单一数值条件改成多个指标交叉验证。比如抓训练假死一条规则同时要求利用率极低和显存占用不低两者同时满足才报警groups: - name: gpu_rules rules: - record: gpu:memory_usage_ratio expr: nvidia_memory_used_bytes / nvidia_memory_total_bytes labels: cluster: training-env - alert: GPUIdleButMemoryOccupied expr: | nvidia_gpu_utilization 10 and on(instance, gpu) gpu:memory_usage_ratio 0.5 for: 10m labels: severity: critical annotations: summary: GPU {{ $labels.gpu }} 疑似训练假死 description: 实例 {{ $labels.instance }} 上 GPU {{ $labels.gpu }} 利用率低于 10%但显存占用超过 50%持续 10 分钟可能存在进程阻塞。这段 PromQL 的逻辑是先通过 recording rule 算出显存使用率存成新指标gpu:memory_usage_ratio告警里再用and on(instance, gpu)把它跟利用率做交集。on()在这里极其重要它显式指定按instance和gpu两个标签匹配避免两个向量因为标签不完全一致产生匹配错乱。这个规则实际上就解决了显存占着但 GPU 没在干活的假死问题——传统固定阈值规则完全做不到这种判断。4.3 第二层动态基线让阈值跟随历史浮动组合条件解决的是当前状态是否合理动态基线解决的是当前状态相比平时是否异常。我的做法是先跑一段记录规则计算每张卡在过去 1 小时的平均利用率然后告警规则里的阈值不再是绝对数字而是当前值相对于均值掉了多少- record: gpu:utilization:avg_1h expr: avg_over_time(nvidia_gpu_utilization[1h]) labels: cluster: training-env - alert: GPUDropFromBaseline expr: | (gpu:utilization:avg_1h - nvidia_gpu_utilization) 60 and nvidia_gpu_utilization 30 for: 5m labels: severity: warning annotations: summary: GPU {{ $labels.gpu }} 利用率较基线突降 description: 过去 1 小时平均利用率 {{ $value }}当前低于 30%推测训练进程可能异常退出或阻塞。这条规则的含义是此刻利用率掉得比过去一小时的平均值低 60 个百分点以上而且当前绝对值低于 30%持续 5 分钟才报警。加了for: 5m是为了扛过正常的短时波动比如数据加载导致的几十秒低峰。这里我故意不设平均值为 0 就跳过因为在训练正常期间基线通常不会低而如果基线本身很低说明任务阶段就是空的此时掉到更低也不值得告警。如果你觉得 60 个百分点太激进可以调整数值但建议保留相对差值 绝对上限双条件单条件永远不够稳。4.4 第三层温度与显存容量的分级告警除了利用率问题硬件健康类告警可以继续用固定阈值因为硬件规格就是固定的。温度超过 85 摄氏度和显存占用接近上限并不会因为训练任务不同而改变标准。我把告警分成 warning 与 critical 两级举个例子- alert: GPUTemperatureHigh expr: nvidia_gpu_temperature_celsius 85 for: 10m labels: severity: warning annotations: summary: GPU {{ $labels.gpu }} 温度过高 - alert: GPUTemperatureCritical expr: nvidia_gpu_temperature_celsius 90 for: 5m labels: severity: critical annotations: summary: GPU {{ $labels.gpu }} 温度异常需立即处理 - alert: GPUMemoryPressure expr: gpu:memory_usage_ratio 0.9 for: 15m labels: severity: warning annotations: summary: GPU {{ $labels.gpu }} 显存接近上限为什么温度规则要分两级因为 GPU 降到基础频率是自我保护机制短暂冲上 85 度可能是环境温度波动不一定需要立即打扰值班同学但 90 度以上持续 5 分钟说明散热或者风扇可能出了问题再拖可能真的烧硬件。显存超过 90% 不直接设成 critical也是因为训练任务本身可能合理占满显存关键看有没有报 OOM一旦 OOM 任务会自己退出告警自然变成进程消失类型。想让这些规则真正生效还要配合 Alertmanager 的抑制与分组下一节我单独讲配置。4.5 Alertmanager 降噪抑制、分组与冷却时间告警规则写得再好通知发得太烦也会被人为静音。Alertmanager 里有三个参数值得认真设。第一个是group_by把同一台机器上多个 GPU 的告警合并成一条通知。第二是group_wait / group_interval / repeat_interval分别控制首次通知的等待时间、同组告警再次检查的间隔、以及相同告警重复发送的最小间隔。我的配置长这样route: group_by: [instance, severity] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: severity: critical receiver: pager - match: severity: warning receiver: dingtalk第三个是inhibit_rules比如 GPU 温度 critical 告警时同一实例上所有 warning 级温度告警就不必再发一遍。这能有效避免告警风暴inhibit_rules: - source_matchers: - severitycritical - alertnameGPUTemperatureCritical target_matchers: - severitywarning - alertnameGPUTemperatureHigh equal: [instance, gpu]这套配置下来工作日高峰期的告警量大概能砍掉 60% 以上因为很多 warning 被抑制或者合并了。记住一个原则告警的目的是让人愿意去看不是让人烦到屏蔽。5. 常见问题与排错实录5.1 指标抓不到或者全是 0这类问题占到我运维排障里的七成。最常见的原因是 exporter 容器里看不到 GPU 设备。如果你用 Docker 跑 nvidia_gpu_exporter 但没加--gpus all容器日志会报.so加载失败或者 NVML 返回异常curl拿到的指标要么是空要么全是 0。在宿主机上先执行nvidia-smi确认驱动正常再用docker run --gpus all把设备映射进容器。第二个原因是端口没放行。Prometheus 部署在另外一台机器上scrape_configs里写的是内网 IP但安全组或防火墙只放行了 22 和 80 端口结果 Web UI 上 target 状态一会儿 UP 一会儿 down或者显示 connection refused。排查方式是在 Prometheus 服务器上手动 curl 一次curl -v http://192.168.1.10:9100/metrics能通就抓配置不能通则查网络和防火墙这一步能省下大量时间。5.2 PromQL 匹配错乱导致误报我在第 4 节特意强调了on()和by()实际排障中碰到最多的是类似这个报错many-to-many matching not allowed。场景通常是把两个不同来源的指标做运算两边标签集合不一致。比如nvidia_gpu_utilization有gpu标签而某个自定义导出指标没有gpu标签直接除就会出现匹配错乱。解决办法就是显式指定匹配维度要么on(instance)要么ignoring(gpu)不要让 Prometheus 自作主张地配对。另一个我踩过的坑是记录规则忘了加标签。如果gpu:utilization:avg_1h这个记录指标的标签集合跟原始指标不完全一致后续告警规则里on(instance, gpu)就匹配不上。所以定义 recording rule 时最好手动把关键标签写进labels块保持标签对齐。5.3 告警一直 Pending 不触发Prometheus 告警规则里带for时Alert 会先进入 Pending 状态持续满足条件达到for时长后才变成 Firing。很多人第一次配告警发现条件明明满足了但就是没通知原因多半是for: 10m之后短短几分钟内条件短暂消失计数器重置了。这其实是设计好的行为但如果你的场景对实时性要求高比如训练中断可以把for缩短到 2-3 分钟或者对同一个故障配两条规则一条短for高严重级一条长for低严重级。5.4 多机多卡场景下告警轰炸的终结技当你有三台机器、每台两张卡时同一时刻可能触发六条相似告警。这时候 group_by 的分组价值就体现了。但我还有一个小技巧在告警注释里直接暴露关键上下文。比如在 annotation 里写上请先在服务器上执行nvidia-smi --query-compute-appspid,used_gpu_memory --formatcsv查看占用进程能大幅缩短处理链路。这种细节表面上不增加告警信息量但值班同学收到一条可直接执行的命令体验是完全不同的。6. 写在最后的实战心得这套方案上线到现在跑了四个多月最大的体会是告警规则的智能不是一蹴而就的需要结合业务任务反复调优。第一周可以先把规则全部设成 warning 级别只记录不通知让 Alertmanager 把触发的历史全部留档第二周再根据触发记录调整阈值和for时长第三周才把 critical 级通知打开。我建议你先跑两周基线数据再逐步调整别一上来就抄我的参数因为你的任务负载和散热环境跟我这边不可能完全一样。另外监控面板上除了利用率曲线一定要把显存使用率和温度放在同一时间轴里看三者联动分析才是排查 GPU 假死的捷径。最后再分享一个小技巧每次训练任务启动时在任务脚本里往 Prometheus 打一条开始/结束的指标埋点告警规则里就能根据当前是否有活动任务来启用或禁用 GPU 利用率告警这样能直接过滤掉非训练时段的无效告警。拿这套思路去改你最终的告警准确率不会比我的差。
返回列表