跑模型的时候GPU利用率一直上不去,或者显存莫名其妙被占满,这种情况每个做深度学习或者科学计算的人应该都遇到过。想在Linux下把显卡状态摸清楚,最常用的路子其实就是那么几个命令,但很多人只会敲一个nvidia-smi,看到个表格就觉得完事了,其实远远不够。这篇文章我从最基础的查询命令讲起,到实时监控、工具选型,再到脚本化采集和问题排查,把我在实际环境中用过、踩过坑的经验都整理出来,希望对你有帮助。
1. 基础命令:nvidia-smi的完整使用思路
1.1 初识nvidia-smi:最核心的显卡状态查询
nvidia-smi是所有NVIDIA显卡监控的基石工具,只要装了NVIDIA官方驱动,这个命令就天然可用,不需要额外安装任何东西。
直接敲nvidia-smi,你会看到一块信息面板,里面包含了驱动版本、CUDA版本、GPU名称、显存总量、当前显存占用、GPU利用率、温度、功耗等核心信息。这个命令本质上是NVIDIA驱动对外暴露的系统管理接口,它的数据来源是驱动实时采集的硬件状态,所以它的准确度是可信的。
我见过不少人只看最上面几行的显存使用情况,却忽略了下面的进程列表。实际上nvidia-smi下方展示的进程信息,才是定位"谁占了我的卡"的关键。这个点后面我会展开讲。
用nvidia-smi有个误区:很多人以为必须要用sudo才能执行。其实普通用户只要对NVIDIA设备节点有读权限,这个命令就能正常使用,而绝大多数Linux发行版默认都开放了这些权限。如果你在执行命令时遇到"Unable to determine the device handle"这类报错,那通常不是权限问题,而是驱动与硬件之间出现了异常,比如驱动崩溃后没能自动恢复。
1.2 常用参数详解:从静态查询到定制化输出
nvidia-smi本身支持非常丰富的参数,只敲一个裸命令属于最低效的用法。我把日常用得最多的几个参数整理一下:
nvidia-smi -L:列出当前机器上所有的GPU设备列表,每张卡会显示编号、型号和UUID。这个参数适合在写脚本时用来确认当前环境有几张卡。nvidia-smi --query-gpu=... --format=csv:这是最灵活的用法,你可以指定要查询的字段,输出成csv格式,方便程序解析。nvidia-smi -l 2:每隔2秒刷新一次,这是最简单的实时监控方式。但注意,这个参数在较新版本的驱动里已经改名为--loop,功能是一样的。nvidia-smi -i 0:指定查询编号为0的那张卡,适合多卡环境下单独查看某一张卡。nvidia-smi -p:显示GPU的ECC错误信息和PCIe链路信息,这个参数对排查硬件稳定性问题很有用,但大部分做算法的人不知道。
举个例子,如果我想用脚本采集每张卡的利用率和显存占用,通常我会这样写:
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits输出结果会像下面这样:
0, 85, 10240, 24576 1, 12, 4096, 24576noheader表示不输出表头,nounits表示去掉百分号和MB这类单位后缀,方便后续用awk或者Python做数值处理。这种输出格式在写监控脚本时是刚需,比直接解析nvidia-smi的默认面板要容易得多。
2. 实时监控:从静态查询到动态刷新
2.1 watch命令:最简单的实时刷新方案
nvidia-smi本身虽然有循环刷新功能,但实际生产环境中我更推荐用watch命令配合nvidia-smi使用。
watch -n 2 nvidia-smi这个命令的作用是每2秒执行一次nvidia-smi并全屏刷新显示结果。为什么要用watch而不是直接用nvidia-smi -l 2?因为watch有一个非常好用的特性:它可以高亮显示两次输出之间发生变化的内容。当显存占用从10G跳到11G时,watch会自动把变化的部分用白底高亮标出来,这对快速捕获资源波动非常有效。
watch的另一个好处是退出干净。按Ctrl+C即可退出,不会遗留下后台进程。相比之下,如果你用了nvidia-smi -l然后直接关闭终端,偶尔会出现进程未清理的情况(虽然概率很低)。
注意,watch默认刷新间隔是2秒,如果你的GPU利用率变化非常快,可以缩短到1秒,但再短就没有意义了,因为nvidia-smi本身的数据采集和渲染也需要几十毫秒,刷新频率过高反而造成无谓的系统开销。
2.2 gpustat:更美观的监控工具
nvidia-smi什么都好,就是输出面板太"朴素"了。如果你习惯用终端工作,而且想在监控GPU的同时兼顾美观和可读性,gpustat是绝佳的补充工具。
gpustat是一个基于Python的第三方工具,安装方式很简单:
pip install gpustat装好之后直接运行:
gpustat -i 2它会用彩色表格展示每张卡的利用率、显存占用、温度、功耗,还会列出每个占用GPU的进程以及对应的用户。最舒服的一点是,它会自动把显存占用按当前用户/进程做一个排序展示,一眼就能看清是谁在占用资源。
gpustat的底层数据来源同样是nvidia-smi,所以它的准确性完全不用担心。它只是把nvidia-smi的原生输出处理成了更适合人眼阅读的形式。在多人共用的服务器上,我一般会常驻一个gpustat窗口,比默认面板清晰太多。
有小伙伴问过gpustat是否会引入额外性能开销,毕竟它引入了Python层。实测下来,它的查询频率远低于nvidia-smi本身的硬件查询上限,CPU开销几乎可以忽略不计。
2.3 其他实用工具:nvtop 与 GPU 性能分析器
如果你需要的是更接近"任务管理器"那样的体验——带有柱状图、历史曲线,并且想同时监控CPU和GPU的整体系统负载,那么nvtop很合适。安装方式在Ubuntu/Debian系可以这样:
sudo apt install nvtopnvtop启动后的界面很直观:上半部分是GPU的整体指标折线图,下半部分是每张卡的详细数据,包括SM利用率、显存带宽、PCIe吞吐量等。这些指标比nvidia-smi默认展示的信息深入得多,对分析"GPU利用率为什么低"这类性能问题帮助很大。
不过要给个建议:nvtop更适合做深度的性能剖析,日常快速查看还是用nvidia-smi或gpustat效率更高。因为nvtop的界面信息密度很高,新手容易迷失在大量数字里。先跑起来再说,等真需要深入定位瓶颈时,再切换到nvtop查看细节。
3. 读懂输出内容:看一眼数据就知道哪里出了问题
3.1 核心指标解读:利用率、显存、温度、功耗
很多人在第一眼看到nvidia-smi的面板时,最大的困惑是"这么多数字,到底哪些才是关键的?"我按重要程度排个序:
- GPU利用率(utilization.gpu):表示GPU计算单元(SM)的忙碌程度。注意,这里的利用率计算的是"在采样周期内是否有计算任务在执行"的比例,而不是"计算单元被占用了多少容量"。所以你会看到利用率高达95%以上,但模型实际速度并没有提升,因为这里的95%可能只是表示"一直有任务在跑",并不代表计算资源被充分用满。
- 显存占用(memory.used):深度学习场景下最直观的指标之一,直接决定你这张卡还能不能塞下下一个任务。
- 温度(temperature.gpu):长期在85度以上运行的GPU,性能会明显下降,甚至触发降频保护。
- 功耗(power.draw):结合显卡的
Max Power参数,可以判断当前是否处于高负载状态。功耗接近上限说明GPU在满负荷运行,如果利用率很高但功耗很低,说明可能出现了降频或供电限制。
这几项数据之间是关联的,不能割裂来看。举个例子:一张卡写着utilization.gpu 100%、memory.used 2G、power.draw 60W,这说明计算单元虽然满载,但实际任务量并不大,可能是因为模型太小或者瓶颈在数据读取上。GPU利用率高不代表显卡在高效工作,这个认知非常重要。
3.2 判断GPU是否真正被有效利用
我见过不少团队拿着一张跑满100%利用率的卡,以为已经压榨出了全部性能,结果换一种数据读取方式之后,训练耗时直接下降了一半。这就是典型的"虚假繁忙"。
要判断GPU是否在高效工作,除了看利用率,还要结合这几项:
- 计算规模是否匹配显存带宽。显存带宽是固定的硬件上限,当模型参数和激活值很大时,带宽会成为瓶颈,此时SM利用率反而不高。
- 是否存在频繁的数据拷贝。如果训练代码中频繁在CPU与GPU之间传输数据,GPU的利用率会像过山车一样忽高忽低。
- 是否有其他进程抢占资源。用
nvidia-smi查看进程列表,看看除了当前任务之外,有没有别的进程也在占用显存或SM。
用nvtop可以查看更直观的SM/内存带宽占用曲线。如果SM利用率持续在90%以上,而且显存带宽也接近理论峰值,那这张卡几乎就是满负荷在工作,没有多少优化的空间了。
4. 进阶玩法:脚本化监控与自动化告警
4.1 用脚本采集GPU数据:写一个轻量监控器
监控GPU不只是为了"看",更重要的是把数据记录下来,用于后续分析。很多同学部署训练任务时,跑完一个模型就完事了,真正遇到"训练速度为什么变慢"这类问题时,手里完全没有历史数据可以查。这就非常被动了。
我习惯的做法是写一个简单的bash脚本,用一个循环定期向日志文件写GPU状态:
#!/bin/bash while true; do echo "$(date '+%Y-%m-%d %H:%M:%S')" >> gpu_monitor.log nvidia-smi --query-gpu=index,utilization.gpu,memory.used,temperature.gpu,power.draw \ --format=csv,noheader,nounits >> gpu_monitor.log sleep 5 done用nohup在后台执行这个脚本,训练任务跑多久它就记录多久。事后想复盘时,直接看日志文件即可。如果配合Python的matplotlib,把数据和帧率画成曲线,训练过程中的资源变化一目了然。
import subprocess import time import csv LOG_FILE = "gpu_metrics.csv" INTERVAL = 5 # 采集间隔(秒) with open(LOG_FILE, mode="w", newline="") as f: writer = csv.writer(f) writer.writerow(["timestamp", "gpu_index", "utilization", "memory_used", "temperature", "power"]) while True: output = subprocess.check_output([ "nvidia-smi", "--query-gpu=index,utilization.gpu,memory.used,temperature.gpu,power.draw", "--format=csv,noheader,nounits" ]).decode("utf-8").strip().split("\n") timestamp = time.strftime("%Y-%m-%d %H:%M:%S") for line in output: parts = [x.strip() for x in line.split(", ")] writer.writerow([timestamp] + parts) f.flush() time.sleep(INTERVAL)把数据落到CSV文件后,可以用pandas做数据切片,比如只看某一段训练时间内的平均利用率、温度峰值等,定位性能拐点。
4.2 多GPU均衡调度与占用检测
在多卡服务器上,除了查看单卡状态,更重要的是感知"哪张卡有空闲"。当多个人共用一台服务器时,经常出现有人把任务都堆在了0号卡上,而其他卡空着。用nvidia-smi配合一条简短的shell命令,可以快速挑出空闲卡:
for i in $(nvidia-smi --query-gpu=index --format=csv,noheader); do mem=$(nvidia-smi -i $i --query-gpu=memory.used --format=csv,noheader,nounits | tr -d ' ') echo "GPU $i used mem: ${mem} MiB" done顺着这个思路,在Python里可以写成更优雅的自动选卡工具:
def get_free_gpus(max_memory_threshold_mb=1024): """返回显存占用低于阈值的GPU索引列表""" import subprocess output = subprocess.check_output([ "nvidia-smi", "--query-gpu=index,memory.used", "--format=csv,noheader,nounits" ]).decode("utf-8").strip().split("\n") free_gpus = [] for line in output: idx, mem_used = [x.strip() for x in line.split(", ")] if int(mem_used) < max_memory_threshold_mb: free_gpus.append(int(idx)) return free_gpus这样在启动训练脚本时,可以自动选择最空闲的卡,而不是每次手工指定CUDA_VISIBLE_DEVICES。在多人共享的服务器上,这个小工具能省下非常多的沟通成本。
4.3 自动告警:当GPU异常时第一时间通知自己
长训练任务跑在后台,人不可能24小时盯着监控面板。我的习惯是设置一个简单的告警脚本——比如当GPU温度超过阈值,或者利用率长时间低于某个值(说明可能死锁或挂起了),就触发通知。
#!/bin/bash THRESHOLD_TEMP=85 while true; do temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits | sort -n | tail -1) if [ "$temp" -gt "$THRESHOLD_TEMP" ]; then echo "GPU temperature exceeded ${THRESHOLD_TEMP}°C !" | mail -s "GPU Temp Alert" your_email@example.com fi sleep 60 done如果没有配置邮件服务,也可以用curl结合第三方通知渠道发送告警到手机端。这个思路适用于所有需要无人值守的训练场景,虽然逻辑简单,但能避免因温度过高导致训练中断后无人知晓的尴尬情况。
5. 常见问题与排查经验:这些坑我都替你踩过
5.1 为什么nvidia-smi显示显存占用很高,但看不到进程?
这个问题出现的频率极高。用nvidia-smi看到显存被占了几十G,但下方进程列表却是空的,或者只显示几个<defunct>进程。这个现象通常是以下原因之一:
- 进程已经崩溃,但由于显存回收机制尚未完全执行,资源还没有被完全释放。这种情况一般几十秒内会自动恢复。
- 程序使用了CUDA的某种上下文缓存机制,即使主进程退出,子进程或僵尸进程仍持有显存和上下文。排查时可以执行
ps aux | grep python看看是否有残留进程。 - 使用了MIG(多实例GPU)模式,但当前用户没有权限查看其他实例的进程详情。
遇到这种情况,先检查所有用户的进程:
ps aux | grep -i python找到残留进程后,如果能确认是废弃任务,直接kill -9终止即可。如果无法确认,也可以等一段时间再观察,因为某些框架在进程退出后需要一些时间异步释放显存。
注意:直接
kill -9会有一定风险。如果那个进程正在写重要的模型checkpoint,强杀可能导致文件损坏。稳妥的做法是先发送SIGTERM(即kill不加参数),让程序有机会做清理工作,等几秒再考虑强杀。
5.2 为什么GPU利用率总是上不去?
GPU利用率低往往不是显卡本身的问题,而是数据供给跟不上。常见的根源有三个:
- CPU预处理成为瓶颈。当数据加载、增强操作过于复杂,CPU处理速度跟不上GPU消费速度时,GPU只能空转等待。解决思路是增加
num_workers、使用pin_memory,或者用TFRecord/LMDB这类高性能数据格式。 - 频繁的
cpu与cuda数据搬运。比如在训练循环里写了tensor.cpu()然后再次tensor.cuda(),这类操作非常昂贵,会中断GPU的计算流水线。 - 模型结构本身并行度低。某些模型存在大量串行计算逻辑,即使GPU算力充足,也无法充分利用多核SM。
定位这类问题,一个简单的方法是逐步减小数据加载的负载,比如把数据集换成纯随机数,如果GPU利用率立刻大幅提升,那瓶颈基本就锁定在数据加载环节。
5.3 watch模式下刷新卡顿怎么办?
在多人共用、负载很高的服务器上,watch -n 1 nvidia-smi可能会出现刷新不及时或者画面撕裂感。这是因为watch需要在每次刷新时重新执行命令并渲染整个终端界面,而在高负载环境下nvidia-smi的响应也会变慢。
这种情况下建议把刷新间隔放宽到2秒以上,或者改用gpustat -i 5这类轻量级轮询。如果真的需要秒级甚至毫秒级的精确监控,建议使用NVIDIA官方提供的NVML绑定库(Python版为pynvml),它可以直接调用驱动层的接口,几乎不受终端渲染影响。
import pynvml pynvml.nvmlInit() handle = pynvml.nvmlDeviceGetHandleByIndex(0) util = pynvml.nvmlDeviceGetUtilizationRates(handle) print(f"GPU util: {util.gpu}%, Memory util: {util.memory}%")pynvml就相当于Python版的nvidia-smi底层接口,相比于每次调用子进程执行nvidia-smi,它的开销低很多,适合用来做高频监控或集成进入训练框架。
5.4 结尾小技巧:让nvidia-smi真正成为你的专属工具
最后分享一个我的个人使用习惯:在服务器的~/.bashrc里加一个别名,把常用的监控参数固化下来,这样每次登录服务器只需要敲一个简短别名就能获得定制化的GPU信息。
alias gpu='nvidia-smi --query-gpu=index,name,utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw --format=csv' alias gpuwatch='watch -n 2 nvidia-smi' alias gpustat='gpustat -i 2'做算法也好,做系统运维也好,GPU监控这事本身不难,难的是把它用成一种习惯。不要等模型跑挂了才想到去看卡的状态,而是从任务启动的第一秒开始,就让监控数据滚动起来。一套顺手的小命令,能帮你省下大量排查问题的时间。希望这些经验能对你的实际工作有帮助。