用nvidia-smi排查GPU问题,是我这几年在AI训练和推理环境里做的最多的一件事。很多朋友第一次接触它时,习惯直接敲一行nvidia-smi,盯着那张表看两秒,发现显存没满、温度不高,就觉得“没问题了”。但实际上,这张表里藏的信息远比你想象的多——GPU利用率、显存占用、功耗、温度、时钟频率、PCIe链路状态,每一个字段背后都对应一个真实的硬件状态。如果不会完整地读它,遇到性能掉点、驱动崩溃、多卡通信异常这些问题时,就只能靠猜。
这篇文章我会把nvidia-smi从输出字段到常用参数、从日常监控到报错排查、从单卡到多卡拓扑,完整过一遍。内容偏实操,适合正在做深度学习训练、推理服务部署、GPU运维的朋友。看完之后,你至少能回答自己几个问题:这块卡到底是不是满负荷在跑?瓶颈是算力、显存还是散热?驱动和CUDA到底哪里对不上?
1. nvidia-smi的输出结果,每一步都藏在细节里
1.1 头部信息:驱动版本、CUDA版本和“NVIDIA-SMI has failed”之间的微妙关系
先看一张典型输出里最容易被忽略的头部信息。第一次跑nvidia-smi,屏幕顶端会显示NVIDIA-SMI版本、Driver版本和CUDA Version。这三个数字的价值不只是给你看个版本号,它们直接决定了你这台机器能不能正常跑CUDA程序。
我经常遇到一种情况:用户装好驱动后,跑nvcc -V能看到CUDA 11.8,但nvidia-smi头部显示的CUDA Version是12.2,两边对不上。新手就会慌,以为是驱动坏了。其实这里有个关键区别:nvidia-smi头部显示的CUDA Version不是当前环境实际安装的CUDA Toolkit版本,而是当前驱动所能支持的最高CUDA运行时版本。你可以把驱动想象成一个翻译器,它能听懂的最高版本号是12.2,那么只要你的CUDA运行时版本不超过12.2,它都能正常翻译。实际跑程序用的是nvcc编译出来的运行时库,所以两个版本不一致不一定有问题,只要运行时版本“不高于”驱动支持的版本就行。
如果看到“NVIDIA-SMI has failed because it couldn’t communicate with the NVIDIA driver”这一行,那问题就严重了。这个报错的本质是nvidia-smi工具无法和内核态的NVIDIA驱动模块通信。常见原因有三个:驱动没装成功、驱动模块被禁用导致没加载、或者你刚更新了内核但没重新编译驱动模块。排查思路是先用lsmod | grep nvidia看模块有没有加载,再用dmesg | grep -i nvidia看内核日志里有没有报错,最后才考虑重装驱动。
1.2 GPU列表区:显存、温度、功耗、风扇转速怎么读
头部信息下面是每张GPU的详细状态,这才是日常监控的重点。先说最常用的一行:GPU-Util一栏,很多同学以为利用率高就代表卡在满负荷工作。实际上,GPU-Util反映的是在采样周期内,GPU上某个计算内核(kernel)处于活跃状态的时间占比。它不区分你运行的是轻量任务还是重型任务,也不反映指令流水线的真实吞吐。举例来说,一个不断启动小kernel的程序,可能能让利用率跑到99%,但实际算力消耗很小;反而是那种单个大kernel一直跑的程序,利用率反而不一定稳定在100%。所以判断性能瓶颈时,不能只看利用率,要结合功耗和显存占用一起看。
再看温度与功耗。散热正常的A100在满载时通常能跑到75-80摄氏度的稳定区间,如果开机待机就五六十度,散热膏或者风道多半有问题。留意Power一栏,通常显示类似250W / 400W这样的形式,前面的数字是当前实际功耗,后面的数字是卡的最大功耗限制。如果实际功耗远低于最大限制但利用率很低,那硬件没吃满;如果功耗已顶到最大但利用率只有80%,可能是时钟降频或峰值功耗被限制。
还有个容易被忽略的Volatile GPU-Util列,这个“Volatile”不是指不稳定的意思,而是表示这是一个随时间变化的数据,取值是最近一段采样间隔内的平均利用率。看GPU状态时最好多采样几次再下结论,短时间的一次输出往往不具代表性。
1.3 进程列表区:显存占用和“偷吃”GPU的元凶
GPU列表区下方是进程列表,它记录当前有哪些进程在占用GPU的显存和计算资源。这一块最典型的用途就是找“元凶”:你的卡显存明明有80G,某个模型却告诉我OOM(显存不足),那多半是上一个实验留下的进程没清理,死死占着几十G显存不放。
看到占用进程后,可以用kill -9 PID来结束。但我多说一句,在训练服务里直接kill进程要谨慎,如果这个进程负责保存模型 checkpoint,强杀可能导致模型权重文件损坏。更好的做法是先尝试优雅退出,比如用kill PID,等几秒再观察进程是否真的结束。有些场景下你会发现进程列表明明为空,但显存占用还是居高不下,这通常是CUDA context残留导致的,常见于某些推理框架,可能需要重启容器或重置GPU设备。
进程列表里还有个有意思的字段叫Type,它会标注进程类型是C(计算)还是G(图形)。正常训练任务都是C,如果你发现一个进程类型是G却占了几百M显存,那很可能是有图形界面或者远程桌面程序在占用GPU资源。
2. 高频参数使用指南,别再只会敲裸命令
2.1 最常用:-L、-i、-a
裸执行nvidia-smi可以满足大多数查看需求,但如果想快速获得结构化信息,就得靠参数。
nvidia-smi -L是列出系统里所有可用GPU,一行一张卡,输出内容包含GPU编号、产品名称和UUID。这个命令在写进自动化脚本时非常有用,比如要检测机器上是否插了8张卡,直接数输出行数就行。nvidia-smi -L的输出格式在不同驱动版本上略有变化,但核心字段始终是稳定的。
-i参数用来指定GPU编号,比如nvidia-smi -i 0只显示第0张卡。这个参数在排查多卡问题时非常关键,因为裸命令输出8张卡的信息会很长,用-i能让你聚焦到指定卡上。配合--query-gpu使用时,-i还能筛选指定卡的某几项指标,比如只看第3张卡的显存,命令是nvidia-smi -i 3 --query-gpu=memory.used,memory.total --format=csv。
还有个-a参数,它表示显示所有详细信息,输出内容非常长,包含GPU的时钟状态、最大时钟范围、电源状态、PState、ECC是否开启、PCIe链路速率等。初学者看到那一长串输出往往会犯怵,但其实-a是排查故障时最有力的工具之一。比如你要判断GPU是否因为温度过高而降频,-a输出里的Clocks部分会同时给出当前图形时钟、SM时钟、内存时钟和最大可运行频率,对比一下就知道到底有没有降频。
2.2 格式化输出:--query-gpu 搭配 --format=csv
nvidia-smi --query-gpu=... --format=csv是脚本监控GPU状态的核心命令。它允许你指定要查询的字段,并用CSV格式输出,方便直接把数据喂给监控系统或者Python脚本分析。
常用的字段包括index(GPU编号)、name(型号名称)、temperature.gpu(GPU温度)、utilization.gpu(利用率)、memory.used(已用显存)、memory.total(总显存)、power.draw(当前功耗)、power.limit(功耗上限)等。需要汇总多卡信息时,可以用逗号分隔多个字段,例如:
nvidia-smi --query-gpu=index,name,memory.used,memory.total,utilization.gpu,temperature.gpu --format=csv,noheader,nounits注意--format=csv可以加修饰词,noheader表示不输出表头,nounits表示不输出单位。这对后续数据处理非常友好,直接按逗号拆分即可。nvidia-smi在这个模式下还能查比较冷门的字段,比如pcie.link.gen.current(当前PCIe链路代数)、pcie.link.width.current(当前PCIe链路宽度)。如果你的程序在训练时数据加载比计算还慢,检查这两个字段就能判断是不是PCIe链路降速了。
值得留意的是,--query-gpu配合-l参数可以指定查询间隔,比如nvidia-smi --query-gpu=utilization.gpu --format=csv -l 1会每秒刷新一次。但在脚本里我不建议用-l,因为持续的交互式刷新会占用额外资源。定时任务或采集脚本更适合用-l 0的方式一次性输出,或者用watch命令来控制刷新频率(下一节细说)。
2.3 持久化模式 -pm 和功率限制 -pl
nvidia-smi -pm 1是让GPU进入持久化模式。默认情况下,当没有程序访问GPU时,NVIDIA驱动会释放一部分初始化状态,当新程序首次访问GPU时需要重新初始化,这会带来几百毫秒的延迟。持久化模式开启后,驱动会保持GPU状态就绪,避免反复初始化。对高频调用GPU的服务来说,这能明显优化响应延迟,但对单次长时间训练任务来说影响不大。
这个命令需要root权限,且会修改GPU运行状态。在容器里执行时,要注意容器是否有权限访问NVIDIA设备节点,如果在容器里无法设置,通常需要在宿主机上操作。有些部署手册会建议直接改/etc/rc.local或systemd服务在开机时自动设置,我更推荐用systemd,因为rc.local在新系统里往往默认不执行。
-pl参数用来限制GPU最大功耗,例如nvidia-smi -pl 200会把最大功耗限制在200W。这在多卡机箱散热受限时非常实用。比如一台机器塞了8张350W的卡,整机功耗可能超过电源额定值,此时通过-pl限制每张卡的功耗,能在不关服务的情况下保证系统稳定。但要注意,功耗限制设置过小会直接影响训练速度和GPU利用率,一般建议从默认功耗的70%-80%开始调,再根据实际性能表现做微调。
3. GPU监控与故障排查实战
3.1 watch 和定时任务,让监控自动化起来
裸敲nvidia-smi只能看瞬间状态,想要观察一段时间内的变化趋势,最省事的办法是配合watch命令。watch -n 1 nvidia-smi可以让输出每秒自动刷新一次,类似实时“仪表盘”。我在训练长任务时经常开一个独立的终端窗口跑watch -n 2 nvidia-smi,随时扫一眼就能判断训练是卡在数据加载还是真的在计算。
如果你需要把监控数据记录下来,watch就不合适了,因为它的输出是持续刷新到终端,不方便采集。更可靠的做法是写一个简单的shell脚本,用while循环定时执行nvidia-smi --query-gpu=... --format=csv,把输出追加到日志文件,例如:
while true; do echo "$(date +%Y-%m-%d\ %H:%M:%S)" >> gpu_usage.log nvidia-smi --query-gpu=index,utilization.gpu,memory.used,temperature.gpu,power.draw --format=csv,noheader,nounits >> gpu_usage.log sleep 5 done这段脚本会在每次采样前先记录当前时间戳,再把GPU各指标追加到日志文件。后期分析时可以直接用Python pandas处理CSV,或者用awk按GPU编号分组统计。需要注意的是,脚本里echo和nvidia-smi之间不要用&&连接成一行,那样时间戳会和GPU数据绑定得太紧,反而丢失了采样点信息。
另外提一下nvidia-smi dmon命令。它是nvidia-smi自带的实时设备监控工具,输出风格更像是一个流式表格,每一行都包含各GPU的利用率、显存、温度、功耗、风扇转速等数值。它的优势在于输出格式非常紧凑,适合直接灌入日志系统。缺点是字段缩写比较多,初次使用需要对照帮助文档才能认清每一列的含义。
3.2 常见报错与排查思路:command not found、No devices were found、device handle
命令行工具避免不了报错,nvidia-smi也不例外。我把这几年遇到最多、搜索引擎里高频出现的几类报错集中列一下。
第一种是nvidia-smi: command not found, but can be installed with:。这个报错有两个可能:要么是真的没装NVIDIA驱动,要么是驱动装了但可执行文件不在PATH环境变量里。在Ubuntu等系统上,nvidia-smi通常位于/usr/bin/nvidia-smi或/usr/lib/nvidia-xxx/bin/nvidia-smi,如果确认驱动已安装,可以尝试用绝对路径执行。如果绝对路径也找不到,那就要检查驱动安装过程是否有问题。
第二种是nvidia-smi: no devices were found。这个报错比较误导人,因为字面意思是“没有找到设备”,但实践中经常是驱动版本不兼容导致的。比如有人为了跑某个老框架,手动降级了驱动版本,结果驱动和GPU架构不匹配,于是报这个错。排查思路:先执行lspci | grep -i nvidia,确认系统PCI总线上能识别到GPU硬件;如果硬件在,再看驱动版本是否与该GPU的架构匹配;最后检查是不是有双显卡切换(比如笔记本的Optimus)导致独显没启用。
第三种是unable to determine the device handle for GPU0000:41:00.0: Unknown。这里0000:41:00.0是GPU的PCI总线地址。出现这个报错,通常意味着nvidia-smi在初始化设备句柄时失败。原因可能是GPU掉卡(物理接触不良或供电不稳)、驱动模块和硬件状态不一致、或者是多卡环境中某张卡进入了异常状态。排查时我会先跑nvidia-smi -L看系统还有多少张卡能被识别,再对比物理插槽与系统识别顺序是否一致,然后用dmesg | grep -i nvidia看是否有设备reset的日志。
第四种是高权限环境下常见的权限问题。在容器里执行nvidia-smi如果返回could not open device或insufficient permissions,多半是容器缺少--gpus参数或没有挂载NVIDIA设备节点。新版Docker配合NVIDIA Container Toolkit时,启动命令要带上--gpus all,或者用--runtime=nvidia,否则容器内无法访问GPU。
3.3 驱动模块问题:nvidia-smi 输出为空或报错时的处理
当nvidia-smi输出为空,既没有GPU信息也没有报错信息,这种“无输出”情况其实比报错更难排查。我碰到过几次,几乎都是驱动内核模块没加载成功导致的。先用lsmod | grep nvidia看模块是否存在。如果没有任何nvidia相关模块,需要手动加载:
modprobe nvidia modprobe nvidia_uvm modprobe nvidia_drm modprobe nvidia_modeset运行过程中如果提示模块不存在,说明驱动包没有完整安装或当前内核版本与驱动模块版本不匹配。modprobe nvidia这一步经常被忽略,但它是很多驱动问题的源头。特别是系统内核自动升级后,旧驱动模块可能已经失效,这时需要重新安装对应版本的NVIDIA驱动。
如果模块存在但nvidia-smi还是没输出,可以尝试卸载再重新装载内核模块:
rmmod nvidia_drm rmmod nvidia_modeset rmmod nvidia_uvm rmmod nvidia modprobe nvidia modprobe nvidia_uvm modprobe nvidia_drm modprobe nvidia_modeset注意rmmod时如果提示“Module is in use”说明驱动被进程占用,要先停掉GPU相关服务或进程。这套“先卸载再重载”的操作可以解决很多驱动状态异常问题,但它只影响驱动模块,不会伤害数据。
还有一种特殊报错是nvidia-smi因为X11服务占用GPU而拒绝显示。这种多见于带有图形界面的Linux系统,需要先关闭桌面服务或切换运行级别,再执行nvidia-smi。如果只是命令行远程登录,不涉及X11,可以忽略这个干扰项。
4. 多卡环境下的进阶玩法
4.1 GPU编号、总线ID和CUDA_VISIBLE_DEVICES三者的关系
在多卡服务器上,最容易踩的坑是GPU编号问题。物理上你插了8张卡,nvidia-smi给每张卡一个从0开始的编号,但这个编号基于PCI总线枚举顺序,不一定是物理插槽的安装顺序。更关键的是,在训练框架里,你通过CUDA_VISIBLE_DEVICES指定的是CUDA可见编号,这个编号和nvidia-smi的编号不一定一一对应。
举个例子:nvidia-smi显示GPU编号0、1、2对应三张物理卡,你设置CUDA_VISIBLE_DEVICES=2,在程序内部看到的设备编号其实是0,它对应的是nvidia-smi里的GPU 2。如果你在程序里同时用到了当前进程可见设备列表和宿主机上nvidia-smi的GPU编号,就很容易混淆。我在做多机多卡部署时,习惯先在每台机器上跑一次nvidia-smi -L记录UUID列表,然后在容器或程序内通过torch.cuda.get_device_name()或CUDA runtime API获取UUID来做交叉确认。NVIDIA官方建议用UUID来唯一标识GPU,因为它不随编号顺序变化。
有些系统里还能看到NV_AI_FABRIC_GPU这样的设备名,那是NVSwitch或者A100、H100等新型GPU在AI Fabric环境下的显示方式。遇到这种命名时,说明机器可能接了NVSwitch,拓扑更复杂,定位问题时要结合nvidia-smi topo -m来看。
4.2 查看卡间拓扑:nvidia-smi topo -m,知道你的NVLink通没通
多卡训练的性能瓶颈,很多时候不在单卡算力,而在卡间通信。如果两张卡之间的通信走的是PCIe总线,带宽大概只有几十GB/s;如果走NVLink,带宽可以达到几百GB/s。两者差了数倍,直接影响分布式训练效率。
nvidia-smi topo -m输出的是一张GPU间连接关系矩阵。里面用字母表示两个GPU之间的连接方式,比如NV#代表通过NVLink连接,PIX代表通过PCIe交换机同一端口连接,PXB代表通过PCIe桥接,SYS代表通过主机CPU和QPI/UPI连接。第一次看这张表时,你会发现与自己预期不一定一致——比如CPU0下面的两张卡可能是PIX连接,CPU1下面的两张卡是PIX连接,但CPU0和CPU1之间的卡走的是SYS,带宽低很多。
分布式训练时,如果数据并行通信量很大,就要尽量让通信频繁的GPU之间走NVLink或PIX链接。通俗点说,就像搬家时,东西要尽量放在有电梯直通的两个单元,而不是需要绕一圈从大门搬运。nvidia-smi topo -m就是帮你提前看清电梯路径是哪条。之前我帮一个朋友排查多机训练变慢的问题,折腾了半天最后发现是他机器上某两张卡之间根本没有NVLink覆盖,训练脚本又恰好把这两个卡分到了同一个通信组里,导致通信走PCIe,整个训练被拖慢。查看拓扑之后,把进程分配策略调整了一下,通信带宽立刻上去了。
4.3 MIG和虚拟化场景:nvidia-smi能做什么,不能做什么
MIG是A100、H100等高端GPU上的一项切片技术,可以把一张物理GPU切成多个独立的GPU实例,每个实例有自己的显存和计算单元。在这种场景下,nvidia-smi同样可以用来查看MIG实例,但需要额外配置。执行nvidia-smi mig -lgip可以列出一张卡上所有可用的MIG profile,nvidia-smi mig -cci可以用来查看当前已创建的实例。
MIG模式下的nvidia-smi输出和普通模式有明显区别:你会看到GPU编号下多出MIG 1g.5gb这样的设备标识,表示一张卡被分成了一个计算片上5GB显存的实例。对于容器化部署,这意味着不同容器可以各自绑定不同的MIG实例,实现更好的隔离。
不过在MIG场景下,nvidia-smi能做的事情也有限。它无法直接调整MIG实例的计算切片比例,也无法对MIG实例单独做功耗限制。如果需要细粒度的MIG管理,要去查mig-parted或者NVIDIA官方提供的管理工具。这算是一个容易误用的地方,很多人以为nvidia-smi是万能的GPU管理工具,但实际上它更偏“只读”视角,很多写操作(除了-pm、-pl这类少数命令)需要配合其他工具完成。
5. 分享几条实在的实战心得
5.1 监控数据务必落盘
有一次排查训练过程中GPU利用率周期性掉零的问题,我一开始完全没思路,后来调出了过去两天的GPU监控记录,才发现每两小时会规律性地出现一次利用率掉底,对照时间戳一查,发现是定时任务里有个数据备份脚本,每次执行都会抢占大量CPU资源,CPU调度跟不上,GPU就只能空转等数据。如果当时没有落盘的数据,这种周期性规律很难靠肉眼观察发现。
无论你用的是nvidia-smi加shell循环,还是接Prometheus、Grafana一类的监控体系,建议都要让GPU监控数据持续落盘保存。采集间隔可以放宽到5秒一条,但历史数据不能丢。这样排查问题时才能基于数据而不是凭感觉。
5.2 警惕“显存清零但利用率卡住”的假正常状态
训练过程中如果程序崩溃,但容器没退出,有时候会出现显存已经释放,GPU-Util却还在某个非零值的情况。这不是GPU坏了,大概率是残留的CUDA context或驱动状态异常。处理方式一般是重启容器,或者用前一篇文章里提到的rmmod/modprobe重载驱动模块。
如果重载驱动还不行,可以尝试nvidia-smi --gpu-reset,但这个命令在某些虚拟化环境下不可用,报错如Insufficient Permissions。该命令的作用是重置GPU状态,类似于电脑的“重启一下也许就好了”操作,但它破坏性相对大,执行前要确保没有重要训练任务在跑,否则正在执行的任务会直接被中断。
5.3 性能瓶颈排查时,先看温度和功耗再谈优化
很多人一遇到训练性能不好,就开始怀疑数据加载、怀疑算法模型,恨不得把代码翻个底朝天。但我的经验是,先用nvidia-smi把硬件状态全面扫一遍,往往能省下几个小时的排查时间。
如果看到利用率95%以上,但功耗只有额定功耗的60%,那说明GPU没有真正跑满,程序可能卡在内存拷贝或者kernel启动开销上。如果温度已经顶到88甚至90摄氏度,功耗也封顶了,那显卡可能触发过热保护在降频,这时候加风扇、清灰、改善机箱风道才是优先要做的事。先把硬件这层确认干净,再往下钻软件层的优化才会有效率。
我个人在实际使用中还有个习惯:跑训练长任务前,用nvidia-smi -q -d TEMPERATURE,CLOCK,POWER生成一份完整的初始状态备份,训练结束后再生成一份对比,两张状态表一对照,很多“隐性故障”就浮出水面了。某次训练损失函数反复震荡,我以为是学习率问题,结果对比功耗和时钟频率后,才发现卡在被动能限制的降频模式,功耗一恢复,训练立即稳定下来。把这一招分享给你,排查GPU相关疑难杂症的时候不妨先试一步,往往比急着改代码更有效。