news 2026/11/3 21:20:16

nvidia-smi 实战:GPU 显存、利用率与监控排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
nvidia-smi 实战:GPU 显存、利用率与监控排查指南

凌晨两点多,值班手机震了一下,某台八卡机器的显存水位告警。我迷迷糊糊登上跳板机,第一件事不是翻日志,也不是去看训练脚本,而是敲下nvidia-smi——看哪张卡顶着、哪个进程在吃显存、温度和功耗正不正常。这条命令大概是所有跟 GPU 打交道的人敲得最多的一条,从刚跑通第一个训练脚本的学生,到维护几百张卡集群的平台工程师,日常都绕不开它。但真要把每一列讲清楚、把参数用在合适的场景、在输出对不上号的时候知道往哪查,很多人其实只有一个模糊印象。

nvidia-smi是随 GPU 驱动一起装进系统的管理工具,全称 System Management Interface。它能干的事远比"看一眼显卡"多:查看每张卡的型号、温度、功耗、风扇转速、显存占用、计算利用率、性能状态、ECC 错误计数、PCIe 链路状态、多卡之间的互联拓扑;能把指定字段按 CSV 吐出来直接喂给监控系统;还能改持久模式、设功耗上限、重置卡。它解决的核心问题是——当你怀疑"是不是卡出问题了"的时候,给你一个不依赖任何框架、不依赖 Python 环境、直接在系统层面取证的手段。这篇内容适合三类人:刚接触 GPU、想搞懂输出里那些数字含义的新手;需要在服务器上排查训练卡顿、显存泄漏、掉卡问题的算法和运维同学;以及想把 GPU 指标接进自有监控体系的平台开发。

1. 先搞清楚这条命令在跟谁对话

1.1 它读的不是显卡本体,是驱动暴露的接口

不少人以为nvidia-smi是直接去问显卡"你几度",其实不是。它是驱动安装包里的一个用户态可执行文件,通过设备节点和内核态驱动模块通信,把驱动维护的统计数据读出来格式化打印。这条链路决定了好几个很实际的结论,理解它们能省下大量瞎折腾的时间。

第一,命令存在不代表驱动正常。系统里留着旧版二进制、但内核模块没加载时,执行会直接报错,提示无法与驱动通信。这句话跟显卡坏没坏关系不大,多数情况是模块没加载、被卸载了,或者内核升级后模块没跟着重新编译。先跑lsmod | grep nvidia看模块在不在,比反复重装驱动有效得多。

第二,首行那个 CUDA Version 最容易误导人。它表示当前驱动能够支持的最高 CUDA 运行时版本,不是机器上实际安装的工具包版本。经常有人看到这里写着 12.x 就以为环境配好了,结果编译时报找不到nvcc。想看实际安装的版本,用nvcc --version,或者去看/usr/local/cuda这个软链接指向哪里。

第三,容器里能不能用,取决于驱动有没有被映射进去。容器共享宿主机内核,所以不需要在容器内再装一套驱动,但运行时必须把设备节点和驱动库挂进来。容器里执行报 "No devices were found",先别怀疑镜像,回宿主机确认卡在不在,再看容器启动参数有没有正确声明 GPU 资源。

1.2 一份默认输出里的六个信息区块

不带任何参数执行,输出是固定版式的表格。很多人盯着它看半天,其实从上到下就是六块内容,拆开来看比一行行猜要快得多。

区块位置内容用途
表头第一行工具版本、驱动版本、支持的最高 CUDA 版本判断环境基线
每卡第 1 行卡序号、型号、持久模式、PCIe 总线地址、是否用于显示、ECC 状态定位物理卡
每卡第 2 行风扇转速、温度、性能状态、功耗/功耗上限、显存已用/总量、GPU 利用率、计算模式看运行状态
每卡第 3 行MIG 模式判断是否切分了实例
进程区表头GPU、GI ID、CI ID、PID、类型、进程名、显存占用找占用来源
进程区内容每个使用 GPU 的进程一行定位到具体进程

有个细节值得记一下:卡序号(0、1、2……)是驱动枚举出来的逻辑序号,不一定等于机箱里的物理位置,也不一定等于 CUDA 里device 0对应的那张卡。真正稳定的是 UUID 和 PCIe 总线地址。多卡机器加卡、换槽之后,序号可能整体错位,写脚本做映射时用 UUID 才靠谱。我自己就遇到过一次,脚本里硬编码了-i 0,换卡之后每次采到的都是另一张卡的指标,排查了小半天才发现是序号漂移。

1.3 为什么别人的输出和你的长得不一样

新手最容易困惑的一点:网上搜到的输出里有 ECC 那一列、有 NVLink 那一栏,自己机器上却是 N/A 或者压根没有。这不是版本旧,是硬件能力不同。消费级显卡通常不支持 ECC 显存、不支持 MIG 切分、也没有 NVLink 桥,这些列自然显示 N/A 或者干脆不出现。数据中心卡才有完整的一套。

驱动版本高低也会影响字段多少,新驱动会加一些监控项,老驱动没有。开了 MIG 之后,一张卡被切成若干实例,顶部的卡级信息还在,但进程区会多出 GI/CI 两列来标识实例归属。虚拟机里做直通的场景,某些字段同样可能拿不到。所以看到 N/A 先别慌,对照型号确认这个能力本身存不存在,比盲目升级驱动有效。

2. 输出字段逐项拆解

2.1 显存三个数字,以及"为什么释放不掉"

显存那一栏格式是"已用 / 总量",单位 MiB。这是驱动统计的、被各个进程上下文占用的显存总和。围绕这三个数字,实际工作中被问得最多的一句话是:"训练脚本都退出了,为什么显存还没释放?"

先分清两种占用。一种是框架的缓存分配器,比如深度学习框架里的 caching allocator,它会向驱动一次性申请大块显存,然后自己在内部切分复用。进程退出前,这些块即使空闲也不会还给驱动,所以nvidia-smi看到的往往接近峰值而不是实时需求。这种情况下显存"看起来"很高,但程序内部其实还有余量。另一种是真正的进程残留:进程死了但父进程没回收,或者卡在内核态等待,驱动还没来得及清理上下文。

判断办法很朴素:进程区里还有没有对应条目。如果进程列表是空的、显存却仍然被占,就往上查父进程和容器,用fuser -v /dev/nvidia*看谁还开着设备节点,或者lsof过滤一下。还有一种容易忽略的情况——每个 CUDA 进程都要建上下文,这部分开销按驱动版本不同在几百 MB 量级,八卡机器上如果每张卡都挂着几个进程,光是上下文就能吃掉不少显存,进程显存之和对不上总量也就不奇怪了。

提示:显存总量本身也不等于"可用量"。开启 ECC 会保留一部分,MIG 切分后各实例的可用量是重新划分的,看memory.total时要把这些因素算进去。

2.2 GPU-Util 的真实含义

利用率那一列几乎是误解最多的地方。它的定义是:在过去一个采样周期内,至少有一个 kernel 在 GPU 上执行的时间占比。注意,是"时间占比",不是"算力占用百分比"。所以 100% 只能说明每个采样窗口里都有任务在跑,完全不代表计算单元被打满了;反过来 0% 也不代表这张卡没被占着资源。

实际场景里这两种情况都很常见。一是利用率 100% 但训练速度上不去,这时候瓶颈可能在显存带宽或者核函数本身写得低效,光看这一个数字得不出结论。二是利用率长期在 0% 附近徘徊、显存却占得很高,八成是进程卡在数据加载、进程间通信或者锁等待上,GPU 根本没活干。我见过一次典型的:训练脚本在读一个网络存储上的小文件,DataLoader 的 worker 数量又设得很少,结果每张卡的利用率在 0 和 30 之间来回跳,看着像"卡在摸鱼",实际是 CPU 侧喂不饱。

和它配合看的是utilization.memory这个字段,含义是显存控制器读写时间占比,对访存密集型的任务更有参考价值。默认输出里没有它,需要用--query-gpu单独取。排查性能问题时,把这两个指标加上功耗、SM 时钟放在一起看,比单看一个利用率靠谱得多。

2.3 温度、功耗、时钟与性能状态的联动

温度、功耗、性能状态这三列在同一条线上,不是随便排的,它们之间存在明确的因果链。性能状态(P-State)从 P0 到 P12,P0 是最高性能档,空闲时通常掉到 P8 左右,跑任务时回到 P0。功耗那一栏是"当前功耗 / 功耗上限",当当前功耗贴着上限跑时,卡会主动降频来维持在预算内,这就是所谓的撞功耗墙。

撞墙的证据在哪?不在默认输出里,得用详细查询看降频原因字段。那里会列出空闲、应用设置时钟、软件功耗限制、硬件降速、硬件温度降速、软件温度降速等若干项,哪一项是 Active,就说明卡正被哪个因素压着。温度墙一般出现在散热条件差的机器上,比如风道被线缆挡住、风扇策略保守、机房进风温度偏高。判断标准可以看详细温度信息里的降速阈值和关机阈值,GPU 温度逼近降速阈值时,时钟就会开始往下掉。

功耗墙则更常见于高密度机型。同样一颗芯片,功耗上限设成 300W 和 450W,满载时的持续频率差别不小,而很多机器的默认上限是按整机供电能力配的,不一定适合你的负载。查看详细功耗信息能看到默认上限、当前生效上限和可调范围,需要时可以在有权限的前提下调整。这个操作有风险,改之前务必确认供电和散热余量,别为了跑分把机器搞不稳定。

2.4 进程区里的类型、GI/CI 与对不上的显存

进程区的类型列有三个取值:C 表示计算类(CUDA、OpenCL 之类),G 表示图形渲染,C+G 表示两者兼有。服务器上基本清一色是 C,工作站上跑图形任务时会看到 G。MIG 开启后,GI ID 和 CI ID 用来标识这个进程落在哪个 GPU 实例、哪个计算实例上,做多租户隔离时这两列很关键。

另一个高频疑问是:进程显存加起来为什么和总量对不上?除了前面说的上下文开销,还有几个来源:驱动自身保留、ECC 占用、MIG 实例的固定开销、以及采样瞬间进程正在申请或释放导致的时间差。所以做容量规划时不要拿"进程显存之和"当精确值,留出 10% 到 20% 的余量更稳妥。

3. 高频参数与组合用法

3.1 持续观察:循环刷新与采样间隔

单次输出只是一个瞬间。要看趋势,用循环刷新参数,后面跟秒数,比如每 2 秒刷一次。它会在原地不断重绘,适合盯着看负载爬升的过程。如果要做毫秒级的短时观察,还有毫秒版的循环参数,用来抓那种一闪而过的尖峰。

不过要注意,循环刷新是给人看的,不适合重定向到文件做长期记录——它带大量控制字符,落盘之后基本没法解析。长时间记录应该用查询模式配合自己的循环,这一点在第 4 节会给出完整脚本。另外采样间隔别设得太小,驱动查询本身有开销,几十毫秒一次的高频轮询在多卡机器上会明显增加系统负载。

3.2 结构化查询:把输出变成可解析的数据

这是我认为最值得花时间掌握的一组参数。查询模式允许你只取需要的字段,输出格式支持 CSV,还能去掉表头和单位,直接对接脚本和监控系统。

# 只取关键指标,带表头 nvidia-smi --query-gpu=index,uuid,name,utilization.gpu,memory.used,memory.total,power.draw,temperature.gpu \ --format=csv # 去掉表头和单位,方便程序解析 nvidia-smi --query-gpu=index,uuid,utilization.gpu,memory.used \ --format=csv,noheader,nounits # 查询进程维度的信息 nvidia-smi --query-compute-apps=pid,process_name,used_memory \ --format=csv,noheader,nounits

可用字段很多,常用的包括序号、UUID、型号、温度、风扇、利用率、显存、功耗、SM 时钟、显存时钟、性能状态、ECC 错误计数、PCIe 链路代次和宽度、计算模式、MIG 模式、持久模式等。字段名都是点分结构,写脚本时建议先跑一次--help-query-gpu把当前驱动支持的字段列出来,不同版本之间存在差异。

注意:型号字段里偶尔可能包含逗号,解析时别用简单的字符串切分,走标准 CSV 解析器更稳。

3.3 多卡定位与互联拓扑

多卡机器上排查问题,绕不开"这两张卡之间是怎么连的"。拓扑查询会打印一个矩阵,行和列都是卡,交叉点是连接方式。常见取值里,带 NV 前缀表示通过高速互联桥直连,后面的数字是链路数;SYS 表示要跨处理器间互联;PHB 和 PXB 表示经过 PCIe 主机桥或交换芯片;PIX 表示在同一颗交换芯片下;NODE 表示同 NUMA 节点。这个矩阵对分布式训练的性能分析非常有用——如果两张卡本该走高速互联却显示成了跨节点的 PCIe 路径,通信瓶颈几乎必然出现。

配套的还有链路状态查询,能看到每条互联链路的速率和状态。部分机型还支持链路计数器。拓扑信息建议在机器交付验收时就存一份档,后面出现性能异常可以拿来对比,比凭记忆判断可靠。

3.4 平时少用、关键时刻救命的参数

参数作用典型场景风险提示
-i指定卡号或 UUID只看某张卡,脚本里避免序号漂移无
-q输出详细全量信息排查 ECC、功耗、时钟细节输出很长,建议配合-d限定域
-q -d MEMORY只看显存域详情分析显存构成无
-q -d PERFORMANCE看降频原因性能不达预期无
-q -d ECC看 ECC 错误计数排查静默数据错误无
-dmon设备级持续采样终端里长时间看趋势输出量大
topo -m打印互联拓扑矩阵多卡通信调优无
-pm 1开启持久模式减少驱动初始化延迟待机功耗略增
-r重置指定 GPU掉卡、异常状态恢复会中断所有在用进程
-pl设置功耗上限控制整机功耗与散热需权限,改错影响稳定性

重置这个操作要单独说一句。它能救回处于异常状态的卡,但会直接干掉卡上所有进程,生产环境里必须确认没有任务在跑,而且要在维护窗口内做。重启之后如果同样的错误反复出现,那基本可以判定是硬件或者散热问题,别再指望软件层解决。

4. 实操:搭一套轻量 GPU 指标采集

4.1 第一版:十行 shell 采样

需求很简单——每 10 秒采一次所有卡的关键指标,追加到 CSV,跑在后台。这个脚本我用了很久,足够应付大多数临时排查。

#!/usr/bin/env bash set -euo pipefail INTERVAL="${1:-10}" OUT="${2:-gpu_metrics.csv}" FIELDS="index,uuid,utilization.gpu,memory.used,memory.total,power.draw,temperature.gpu" [ -f "$OUT" ] || echo "ts,$FIELDS" > "$OUT" while true; do ts=$(date +%s) nvidia-smi --query-gpu="$FIELDS" --format=csv,noheader,nounits \ | sed "s/^/${ts},/" >> "$OUT" sleep "$INTERVAL" done

几个设计取舍说明一下。用date +%s而不是格式化时间,是为了后续做时间序列分析时省掉解析成本。用sed给每行加时间戳而不是逐行循环处理,少一次进程创建开销,多卡机器上差异明显。noheader和nounits必须加,否则数值里带着 "MiB" 之类的后缀,入库时还要再做一次清洗。

4.2 第二版:Python 解析并做阈值判断

shell 版本适合快速上手,但要做阈值告警和字段扩展,Python 更顺手。下面这段是核心逻辑,省略了告警发送部分。

import csv import subprocess import time from datetime import datetime FIELDS = [ "index", "uuid", "name", "utilization.gpu", "utilization.memory", "memory.used", "memory.total", "power.draw", "power.limit", "temperature.gpu", "clocks.current.sm", "pstate", ] def collect(): cmd = [ "nvidia-smi", f"--query-gpu={','.join(FIELDS)}", "--format=csv,noheader,nounits", ] raw = subprocess.check_output(cmd, text=True, timeout=5) rows = [] for rec in csv.reader(raw.strip().splitlines()): item = dict(zip(FIELDS, [v.strip() for v in rec])) rows.append(item) return rows def check(rows, mem_threshold=0.9, temp_threshold=80): alerts = [] for r in rows: try: used = float(r["memory.used"]) total = float(r["memory.total"]) temp = float(r["temperature.gpu"]) except ValueError: continue if total and used / total >= mem_threshold: alerts.append(f"GPU{r['index']} 显存水位 {used/total:.0%}") if temp >= temp_threshold: alerts.append(f"GPU{r['index']} 温度 {temp}C") return alerts if __name__ == "__main__": while True: rows = collect() ts = datetime.now().isoformat(timespec="seconds") for r in rows: print(ts, r["index"], r["utilization.gpu"], r["memory.used"]) for msg in check(rows): print("[ALERT]", msg) time.sleep(10)

有个坑必须提醒:字段值可能是[N/A]这种带方括号的形式,float()会直接抛异常,所以要么用nounits让驱动输出纯数值,要么在转换前做清洗。我第一次写的时候没注意,某台机器没有风扇传感器,跑到那台就整段崩掉,日志里全是堆栈。

4.3 容器与多用户环境下的注意点

容器化部署现在很普遍,但这里有几个反直觉的地方。第一,环境变量里指定可见设备,只影响 CUDA 应用枚举到哪些卡,不影响nvidia-smi的输出。也就是说容器里限制只给两张卡,敲nvidia-smi仍然会看到全部卡,很多人因此误判"隔离没生效"。第二,进程列表里显示的 PID 是当前命名空间视角的,容器内看到的 PID 和宿主机上不一致,排查占用时最好回宿主机执行一次,对照着找。

还有多用户共用一台机器的情况。不同用户跑的任务混在一张卡上,显存互相挤占,靠人工协调不现实,但至少要做到出问题时能定位。建议在采集脚本里同时记录进程维度的数据,把 PID、进程名、显存占用一起落盘,事后回溯时有据可查。

5. 常见问题与排查实录

5.1 显存占着,进程却找不到

这是被问得最多的一个问题,处理顺序建议固定下来。先看进程区是不是真的空,如果空但显存高,检查是否有容器在跑——容器里的进程在宿主机上是可见的,但如果你在容器内看,可能因为命名空间差异看不到别的容器。再用fuser -v /dev/nvidia*确认设备节点被哪些进程打开,这个手段比nvidia-smi更底层,能抓到一些没被驱动正确登记的残留。

还有一种情况是进程已经变成僵尸,父进程没回收,显存自然释放不了。找到父进程处理掉即可。如果确认没有进程、显存依然不降,那就要考虑驱动状态异常,这时候重置卡是最后手段,重置前把dmesg里最近的驱动相关日志抓下来,那是判断硬件问题的关键证据。

5.2 命令卡住、执行超时、掉卡

nvidia-smi突然不返回、卡在那儿几十秒,通常说明驱动和硬件之间的通信出了问题。这时候别反复敲,先看内核日志里有没有驱动报错记录,重点找带 Xid 字样的行,那个编号是判断故障类型的核心线索。同时确认卡是否还在 PCIe 总线上,用系统自带的总线枚举工具看一眼。

如果是偶发的一两次,重置后恢复正常,可以先观察。如果同一张卡反复出现,基本可以排除软件因素,往供电、散热、插槽接触、卡本身硬件方向查。我处理过一次,某张卡每隔几天就"消失"一次,重置能恢复,最后发现是散热风道被一组电源线挡住,局部温度过高导致降频保护触发。

注意:掉卡之后不要急着重启整机,先抓日志。重启会把现场清掉,等于放弃了唯一的诊断线索。

5.3 利用率与预期对不上

训练慢但利用率高,优先查两处:数据加载是否成为瓶颈,以及核函数效率。判断数据瓶颈的办法是看利用率的时间序列,如果是规律的锯齿状,基本可以确定是在等数据。训练慢且利用率低,先看是不是撞了功耗墙或温度墙,再看进程间通信是否在等。

还有一个隐蔽的场景:多进程共享一张卡,每个进程 batch 很小,kernel 启动开销占比过高,表现出来就是利用率看起来不低但吞吐很差。这时候把 batch 调大或者减少并发进程,效果比优化模型明显。

5.4 一份速查表

现象优先检查常见原因处理方向
显存高但无进程fuser、容器列表残留进程、父进程未回收清理进程或重置卡
利用率 0 显存高进程状态、通信日志数据等待、死锁查 CPU 侧和网络
利用率高但速度慢显存带宽、batch 大小访存瓶颈、kernel 开销调 batch、优化算子
温度高、频率下降降频原因字段散热不足、风道受阻清理风道、调风扇策略
功耗贴上限功耗详情、降频原因功耗墙评估供电后调整上限
命令无响应内核日志、总线枚举驱动异常、掉卡抓日志后重置
容器内看不到卡宿主机设备、启动参数驱动未映射检查容器运行时配置

6. 把它当基线,别当全部

用了这么多年,我对这条命令的定位一直很明确:它是排查的起点,不是终点。它的采样粒度比较粗,默认刷新间隔在一秒量级,抓不住毫秒级的波动;它给的是卡级和进程级的聚合数据,看不到 SM 占用、显存带宽利用率、拷贝引擎负载这些更细的指标;在 MIG 或者多进程共享的场景下,进程归属也不总是精确。

所以真正做集群监控时,我会把它当成一条保底链路——任何机器上都能跑,不依赖额外组件,出问题时第一时间有数据可看。要做长期趋势和精细分析,再叠加专门的指标采集组件或者更丰富的终端工具。两层配合,既保证覆盖面,又保证深度。

最后分享一个我养成的小习惯:每次新机器上架,先跑一遍详细查询,把型号、UUID、拓扑、功耗上限、温度阈值这些基线信息存成一份文件,跟机器资产绑定。后面任何一次异常,都可以拿当前状态和基线对比,很多"感觉不对劲"的模糊判断,一对比就变成了明确的结论。这个动作花不了五分钟,但省下来的排查时间,往往是以小时计的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 21:20:10

从零实现buzz:多平台热搜追踪与热度评分系统实战

做内容的人都有一个共同的痛苦:选题。每天打开微博、知乎、百度、B站,来回刷一遍热搜榜,其实只是想搞清楚一个问题——今天大家到底在聊什么。后来我干脆写了一个叫buzz的小工具,每天定时把各平台的热搜榜抓下来,做归一…

作者头像 李华
网站建设 2026/11/3 21:19:44

paperclip 开源 AI agent 框架:Node.js + React 全栈开发与调试实战

1. 从“paperclip”这个名字说起:它到底想解决什么问题第一次看到paperclip这个项目名,我脑子里蹦出来的不是回形针,而是那个经典的“回形针最大化”思想实验——一个足够聪明的智能体,如果目标设定稍有偏差,就会把整个…

作者头像 李华
网站建设 2026/11/3 21:18:46

mobile-mcp 实战:用 MCP 协议让 AI 直接操作 iOS 和 Android 设备

移动端自动化这个领域,每隔几年就会冒出一套新的玩法。早些年大家用 Appium 写脚本,后来 Playwright 把 Web 自动化体验拉高了一个档次,再后来各种云真机平台把设备管理这件事做成了服务。但真正让我觉得"工作方式被改变了"的&…

作者头像 李华
网站建设 2026/11/3 21:17:58

ROS开发环境搭建:Ubuntu虚拟机安装与避坑指南

简介:这份PDF教程面向准备进入ROS机器人开发领域的初学者与自学者,聚焦机器人学习中最基础也最容易卡壳的一环——Ubuntu系统的安装与环境搭建,帮助读者在正式接触ROS框架前扫清系统层面的障碍。资源包共1个PDF文件,大小约13.36MB…

作者头像 李华
网站建设 2026/11/3 21:17:40

HeidiSQL多表页签:状态保持与多表数据核对实战

1. 一次被"表太多"逼出来的操作习惯做数据库相关工作的朋友大概都经历过这样一个场景:接到一个遗留系统的排查任务,打开客户端连上库,左侧对象树里密密麻麻几十上百张表,业务方在旁边说"你帮我看看订单表、订单明细…

作者头像 李华
网站建设 2026/11/3 21:15:21

YRC1000报警代码解读:重故障、轻故障与子代码排查

简介:这份PDF手册面向安川MOTOMAN工业机器人的调试、运维与教学人员,用于YRC1000控制柜出现报警时快速定位与处置。内容按重故障报警篇与轻故障报警篇编排,报警列表以编号区间组织,覆盖0000~0999、1000~199…

作者头像 李华