跑模型的时候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... --formatcsv这是最灵活的用法你可以指定要查询的字段输出成csv格式方便程序解析。nvidia-smi -l 2每隔2秒刷新一次这是最简单的实时监控方式。但注意这个参数在较新版本的驱动里已经改名为--loop功能是一样的。nvidia-smi -i 0指定查询编号为0的那张卡适合多卡环境下单独查看某一张卡。nvidia-smi -p显示GPU的ECC错误信息和PCIe链路信息这个参数对排查硬件稳定性问题很有用但大部分做算法的人不知道。举个例子如果我想用脚本采集每张卡的利用率和显存占用通常我会这样写nvidia-smi --query-gpuindex,utilization.gpu,memory.used,memory.total --formatcsv,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的另一个好处是退出干净。按CtrlC即可退出不会遗留下后台进程。相比之下如果你用了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-gpuindex,utilization.gpu,memory.used,temperature.gpu,power.draw \ --formatcsv,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, modew, 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-gpuindex,utilization.gpu,memory.used,temperature.gpu,power.draw, --formatcsv,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-gpuindex --formatcsv,noheader); do mem$(nvidia-smi -i $i --query-gpumemory.used --formatcsv,noheader,nounits | tr -d ) echo GPU $i used mem: ${mem} MiB done顺着这个思路在Python里可以写成更优雅的自动选卡工具def get_free_gpus(max_memory_threshold_mb1024): 返回显存占用低于阈值的GPU索引列表 import subprocess output subprocess.check_output([ nvidia-smi, --query-gpuindex,memory.used, --formatcsv,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_TEMP85 while true; do temp$(nvidia-smi --query-gputemperature.gpu --formatcsv,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_emailexample.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(fGPU util: {util.gpu}%, Memory util: {util.memory}%)pynvml就相当于Python版的nvidia-smi底层接口相比于每次调用子进程执行nvidia-smi它的开销低很多适合用来做高频监控或集成进入训练框架。5.4 结尾小技巧让nvidia-smi真正成为你的专属工具最后分享一个我的个人使用习惯在服务器的~/.bashrc里加一个别名把常用的监控参数固化下来这样每次登录服务器只需要敲一个简短别名就能获得定制化的GPU信息。alias gpunvidia-smi --query-gpuindex,name,utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw --formatcsv alias gpuwatchwatch -n 2 nvidia-smi alias gpustatgpustat -i 2做算法也好做系统运维也好GPU监控这事本身不难难的是把它用成一种习惯。不要等模型跑挂了才想到去看卡的状态而是从任务启动的第一秒开始就让监控数据滚动起来。一套顺手的小命令能帮你省下大量排查问题的时间。希望这些经验能对你的实际工作有帮助。