ARTICLE DETAIL

资讯详情

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

nvidia-smi实时监控显存:从watch命令到OOM排查与显存预留实战

nvidia-smi实时监控显存:从watch命令到OOM排查与显存预留实战 跑模型的人应该都有类似经历程序抛了个 CUDA out of memory手忙脚乱打开终端敲下 nvidia-smi才看清显存到底被谁吃了。nvidia-smi 是 NVIDIA 驱动自带的管理工具很多人只用它看个温度和利用率但实际上它是排查显存问题最快、最直接的入口。这篇文章围绕 nvidia-smi 的实时刷新玩法做一次完整梳理包括 watch 命令的正确姿势、结构化输出、Python 可视化以及显存排查和预留显存的实际思路适合跑大模型、做深度学习训练、折腾 ComfyUI 这类图像生成工具的同学参考。提示本文所有命令都在 Linux 环境下验证过Windows 下大部分命令也能用只是个别参数和路径有差异。1. nvidia-smi 基础先知道你在看什么1.1 两条命令快速上手nvidia-smi 最基础的用法就是直接敲命令不带任何参数它会输出当前所有 NVIDIA GPU 的完整状态包括显卡型号、驱动版本、显存总量和已用数量、计算利用率、温度、功耗、风扇转速以及正在运行的进程列表。想要实时刷新最简单的是配合 watchwatch -n 1 nvidia-smi这条命令的意思是每隔 1 秒执行一次 nvidia-smi 并刷新终端界面。-n 后面的数字代表间隔秒数有人习惯写 0.5实际上 watch 的最小粒度到 0.1 秒但刷新太快意义不大因为 nvidia-smi 本身对 GPU 状态有固定的采样周期1 秒已经足够覆盖绝大多数场景。另一个常用参数是 -d即高亮显示两次刷新之间的差异。比如我只关心显存的变化可以这么写watch -n 1 -d nvidia-smi这样当前后两次输出里数值发生变化的地方会被高亮标出来。排查“哪个时刻显存突然涨上去”时这个高亮比肉眼扫全屏高效得多。如果机器上有多张卡默认会把所有 GPU 都打出来看着比较乱。想只看某一张卡用 -i 指定 GPU 编号nvidia-smi -i 0 watch -n 1 -d nvidia-smi -i 0GPU 编号就是物理插槽顺序从 0 开始。多卡机器上做单卡实验时这种写法能让输出干净很多。1.2 输出表里的字段到底在说什么默认输出中段是一张表最容易被误读的是两列GPU-Util 和 Memory-Usage。GPU-Util 表示计算单元利用率是采样时间内的平均活跃占比Memory-Usage 表示显存占用多少、总容量多少。这两者不是线性相关的。有的场景 GPU-Util 接近 100% 但显存占用不高比如纯计算密集的算子有的场景显存几乎打满但 GPU-Util 很低比如模型加载完正在等数据或者在做 CPU 预处理。看见“利用率低但显存高”不要急着下结论先看看进程有没有处于等待状态。显存和内存是两个不同的东西。显存是 GPU 板载的专用存储带宽高但容量有限系统内存是 CPU 用的通用内存。跑模型时权重、激活值、梯度、优化器状态、KV Cache 这些都优先放显存。只有当显存装不下时框架才会尝试搬到内存此时调度开销会急剧上升训练速度断崖式下跌。所以实时盯显存本质上是在盯“模型放不放得下”。表里还有两列容易忽略Temp 和 Pwr。温度超过 80 度时要注意散热功耗长时间顶满但利用率不高可能说明某个算子吃功耗但不吃算力。这两列配合显存一起看能快速判断 GPU 是否被完全利用。1.3 为什么实时刷新这么重要很多人平时不主动看显存等报错了才去查。问题在于显存分配是瞬时行为训练循环里同一个 batch 在不同阶段的显存峰值差别可以很大。举个例子PyTorch 默认在 forward 阶段把中间激活值全存在显存里backward 时逐步释放所以显存峰值通常出现在一个 step 的中间某个点。如果只是训练结束后去看 nvidia-smi数值早已回落根本定位不到是哪个环节爆的。实时刷新能看到瞬时波峰再配合打印日志或 hook大致能锁定哪一层、哪个张量把显存顶上去。另外实验室和公司环境往往是多人共用一台机器。nvidia-smi 默认会列出所有进程实时刷新能第一时间发现有人在你训练期间偷偷启动了占用大半显存的任务及时沟通总比半夜被 OOM 打断强。2. 实时刷新方案对比与选择2.1 watch 命令的正确姿势watch 是 Linux 下最通用的轮询工具它的逻辑很简单定时执行你给的命令把每次的结果输出到同一片终端区域。对 nvidia-smi 来说watch -n 1 -d nvidia-smi 已经能满足大部分实时监控需求。但有几个细节很多人不知道。第一watch 里的命令要用单引号包起来。命令里如果带管道符号比如要过滤出显存那几行watch -n 1 nvidia-smi | grep -A 1 MiB不加引号时管道符会被外层 shell 提前解析watch 里实际执行的是第一条命令结果完全不对。这个坑我见过不止一次。第二watch 刷新时间再短也受限于 nvidia-smi 本身的采样频率。NVIDIA 驱动对利用率、功耗等指标的更新周期大约在 100 毫秒到 1 秒之间整卡显存占用的更新也不像 CPU 进程那样实时。把间隔压到 0.1 秒除了眼睛看不过来没有任何额外价值1 秒就够省点终端资源。第三watch 默认自带标题栏显示刷新间隔和当前时间不影响使用。但如果你想让它更安静加 -t 可以去掉时间戳加 -x 可以直接执行命令不用经过 sh 解析在复杂命令组合时更可控。这三个参数组合起来就是我个人最常用的写法watch -n 1 -d -t nvidia-smi2.2 用循环脚本实现自定义刷新watch 适合直接看但它的输出格式固定不方便嵌入到自己已有的日志系统或者做条件触发。想更自由一点用 shell 循环while true; do clear; nvidia-smi; sleep 1; done这个写法胜在简单想加什么都可以往中间塞。比如只看显存不显示温度while true; do clear; nvidia-smi --query-gpumemory.used,memory.total --formatcsv; sleep 2; done再比如显存超过某个阈值时顺手抓个日志while true; do used$(nvidia-smi --query-gpumemory.used --formatcsv,noheader,nounits | head -n 1) if [ $used -gt 18000 ]; then echo $(date) 显存告警: ${used}MiB /tmp/mem_watch.log fi sleep 5 done这种做法的最大价值是可以把显存监控接入自己的流程而不是人一直盯着屏幕。条件告警、日志记录、调用别的脚本提醒都比 watch 做起来顺手得多。注意shell 循环里的 clear 会让终端闪动在远程 SSH 会话里偶尔会留下残影。不想用 clear 的话可以用 printf 输出 ANSI 控制码回到行首或者直接保留完整输出让终端自动向上滚动每分钟记录一次当前值就够了。2.3 刷新间隔的取舍间隔设多少没有绝对标准取决于你监控的目的。训练任务调试阶段1 秒刷新能看到明显的梯度波动长时间稳定性测试比如跑 24 小时5 秒刷新足矣还能少写一些日志排查 OOM 时建议把间隔压到 0.5 秒配合 -d 高亮同时打开训练日志两相对照才能确认 OOM 发生在哪个 step。另一个容易忽略的点是 nvidia-smi 本身有进程启动开销。虽然它很快但如果你把它放进一个高频率循环里老机器上可能占用 2% 到 5% 的 CPU。这听起来不多可当你在做基准测试时外部进程的 CPU 波动会干扰数据。基准测试期间建议尽量减少监控频率最好用 nvidia-smi 的查询模式而不是完整输出查询模式只抓数值开销小很多。3. 让 nvidia-smi 从“肉眼看”变成“程序读”3.1 结构化查询参数nvidia-smi 默认输出是给人看的不是给程序解析的。想接进自动化监控或者画图用 --query-gpu 和 --query-compute-apps 直接输出结构化数据nvidia-smi --query-gpuutilization.gpu,memory.used,memory.total --formatcsv,noheader,nounits输出类似35, 12345, 24564这是当前利用率百分比、已用显存 MiB、总显存 MiB。加 noheader 去掉表头、nounits 去掉单位脚本解析起来非常干净。完整字段列表可以通过官方文档查常用的有字段含义utilization.gpuGPU 计算利用率百分比memory.used已用显存MiBmemory.total显存总量MiBtemperature.gpu核心温度power.draw当前功耗瓦pstate性能状态P0 最高针对进程的查询更实用想知道哪些 PID 在占显存nvidia-smi --query-compute-appspid,process_name,used_memory --formatcsv输出的 PID 可以直接和ps aux | grep PID对上号快速找到是哪个训练任务、哪个推理服务在吃显存。这是排查显存问题最常用的一条命令。3.2 Python 实时可视化示例命令行看久了容易疲劳尤其是长时间训练时盯着满屏数字远不如一个图表直观。用 Python 调用 nvidia-smi 的查询接口再配一个简单的终端内图表是我跑实验时的标配。核心逻辑就两块调用外部命令拿到数据、绘图更新。下面给一个最小实现用 subprocess 调用 nvidia-smi然后用 matplotlib 做实时折线图import subprocess import matplotlib.pyplot as plt from collections import deque import time def get_gpu_memory(): output subprocess.check_output( [nvidia-smi, --query-gpumemory.used,memory.total, --formatcsv,noheader,nounits] ).decode() used, total output.strip().split(,) return int(used), int(total) history deque(maxlen200) total 0 plt.ion() fig, ax plt.subplots(figsize(10, 5)) try: while True: used, total get_gpu_memory() history.append(used) ax.clear() ax.plot(history, labelmemory used) ax.axhline(ytotal * 0.95, colorred, linestyle--, label95% threshold) ax.legend() ax.set_ylim(0, total * 1.1) ax.set_ylabel(MiB) ax.set_title(fGPU memory usage: {used}/{total} MiB) plt.pause(1) except KeyboardInterrupt: print(stopped)这段代码每 1 秒读一次显存保留最近 200 个点画出一条实时曲线同时在 95% 显存上限画一条红色虚线作为告警线。训练跑起来后只要余光扫一眼图就能判断显存是不是在稳步上涨、有没有逼近极限。不想用 matplotlib 的话也有更轻量的方案。用\r在同一行刷新文本import time import subprocess import sys while True: used, total get_gpu_memory() bar # * int(used / total * 40) sys.stdout.write(f\r显存: {used:6}MiB / {total:6}MiB [{bar:40}]) sys.stdout.flush() time.sleep(1)效果类似一个文本进度条不需要任何第三方库适合嵌到训练脚本里直接 print。3.3 用 pynvml 还是 subprocessPython 生态里除了调命令还有一个专用库 pynvml是 NVIDIA 官方 NVML 库的 Python 绑定。它比 subprocess 解析 nvidia-smi 更底层、更灵活能拿到温度、功耗、显存、利用率、PCIe 状态还能监听 GPU 事件。pynvml 的写法更 Pythonicimport pynvml pynvml.nvmlInit() handle pynvml.nvmlDeviceGetHandleByIndex(0) info pynvml.nvmlDeviceGetMemoryInfo(handle) print(fused: {info.used / 1024**2:.1f} MiB, total: {info.total / 1024**2:.1f} MiB)它的数据直接来自 NVML 库不经过命令行解析理论上解析错误更少数据结构也更干净。但利弊是明显的需要额外安装 pynvml对多卡、MIG、容器等特殊环境pynvml 的版本和底层驱动需要匹配否则会报版本不兼容。我的建议是只是做显存曲线或简单告警subprocess 就够了零依赖、稳定、容易理解要做更完整的 GPU 监控看板或者需要读取底层计数器pynvml 更合适。两者思路完全一致都是采样-存储-展示实时性的关键不在选哪个库而在采样间隔和数据缓存逻辑。4. 从监控到治理显存排查与预留实战4.1 一眼定位“谁吃掉了显存”实时刷新看到的只是“总量”真正要让显存降下来必须定位到具体进程。完整流程分三步第一步看进程层nvidia-smi --query-compute-appspid,process_name,used_memory --formatcsv第二步把 PID 对应到命令行ps -o pid,cmd -p $(pgrep -f python | head -n 5)第三步看是不是自己的任务决定是等待还是清理。注意nvidia-smi 里列出的进程只包括使用 GPU 的计算进程。如果是 CPU 进程把系统内存吃满了不会出现在这张表里这时该看的是free -h和top。4.2 显存不够时先分清问题类型显存不足翻来覆去就三种情况峰值需求超了总量、显存碎片化导致无法申请连续大块、进程异常没释放旧显存。实时监控能帮你分清楚是哪一类。峰值超量最典型表现为训练跑到某个 epoch 后 OOM日志里报的是 CUDA out of memory。解决办法是调小 batch size、用 gradient accumulation、开启激活值重计算这三者都直接降低中间显存峰值。显存碎片化更隐蔽。PyTorch 的缓存分配器默认会提前缓存一部分显存当某一大块显存需要连续地址而缓存被拆得七零八落时即便总量够也会 OOM。这种情况看 nvidia-smi 会发现显存占用恒定的 70%-80%却总是申请失败。缓解手段是设置环境变量PYTORCH_CUDA_ALLOC_CONFexpandable_segments:True让显存按可扩展段分配降低碎片概率。进程异常不释放则最直接程序已经退出但残留进程还占着显存。看 nvidia-smi 进程列表发现 PID 还在而 ps 查不到对应程序或者程序早已僵死直接 kill 掉。杀之前确认不是别人的任务命令行ps -o user,cmd -p PID看清楚团队共享机器尤其重要。注意PyTorch 的显存缓存机制会导致程序正常退出后显存不会立刻完全归零而是有个短暂释放过程。看见显存“卡住”先别急着 kill等几秒再看多半是驱动回收滞后。4.3 主动预留显存让模型跑得更稳实时监控跑通之后很多人会问能不能主动控制显存使用而不是等 OOM 再处理答案是可以而且有几种不同层级的做法。从轻到重排列第一用环境变量限制单卡可见性。CUDA_VISIBLE_DEVICES0可以让进程只看到编号为 0 的卡避免程序自动占用所有 GPU尤其在多卡机器上跑单卡任务时非常有用。第二用框架自带的内存分配限制。PyTorch 可以限制单进程最大使用显存比例import torch torch.cuda.set_per_process_memory_fraction(0.8)这个比例是相对整卡显存而言。把上限设在 0.8 意味着即便模型峰值超过剩余显存分配器也会在接近 80% 时先报错而不是让系统无限制抢占到翻车。它适合需要和其他进程共存一卡的场景牺牲一点峰值空间换稳定性。第三针对推理服务提前做显存预分配。服务启动时用一个小张量占住显存任务到来时直接复用避免不同请求之间的显存频繁抖动。这个做法适合 ComfyUI 这类图像生成工具启动后先用一个 dummy 流程把模型常驻内存加载好之后每次生成只增加临时开销。第四开启动态显存整理。PyTorch 的PYTORCH_CUDA_ALLOC_CONF支持多个选项常见组合是export PYTORCH_CUDA_ALLOC_CONFexpandable_segments:True,max_split_size_mb:512max_split_size_mb 控制缓存块分裂粒度过小的分裂是碎片化的主要来源。这两个选项配合使用能明显减少因为显存碎片导致的 OOM。图像生成类工具的显存问题更特殊。ComfyUI 这类应用在执行一次完整生成时文本编码器、UNet/DiT、VAE 解码器会依次加载到显存模型切换时旧权重如果释放不干净下一轮就会把显存顶爆。排查办法还是靠 nvidia-smi 实时刷新连续跑三次生成观察每次生成结束后的显存数值如果曲线一次比一次高说明有累积残留优先查节点里的模型加载逻辑或者手动插入显存清理节点在每次 batch 结束时强制释放中间缓存。4.4 低显存环境下跑模型的思路显存紧俏时光靠监控解决不了问题必须从算法层面压缩占用。低显存跑模型的核心思路可以归纳成四点用混合精度或量化压缩权重体积。FP16 比 FP32 少一半显存INT8 又比 FP16 少一半前提是模型对量化不敏感。换用梯度检查点activation checkpointing牺牲一点重计算时间换回大量中间显存。减小 batch size配合梯度累积保证训练效果。使用 KV Cache 量化和 PagedAttention 这类显存调度方案推理场景下收益尤其明显。实时刷新的价值在这里就体现得很充分调整完这些参数后用 nvidia-smi 观察显存峰值是否真正下降、有没有出现新的 OOM 点比想象中高效很多。我见过不少同学把 batch size 从 32 调到 16显存只降了一点一看 nvidia-smi 才发现中间激活值占了 80% 的显存这时真正该开的是 activation checkpointing 而不是 reduce batch。5. 常见问题与踩坑手记5.1 nvidia-smi 报驱动通信失败有段时间我的 nvidia-smi 经常报错nvidia-smi has failed because it couldnt communicate with the NVIDIA driver.这个错误的本质是用户态工具和内核态驱动之间断了连接。常见原因和排查方向刚装完驱动没重启。驱动模块光 insmod 不行很多版本要求重启才能完整初始化。先 reboot 再验证。内核更新导致驱动模块不匹配。Linux 系统升级内核后旧模块可能无法加载重新安装对应版本的驱动驱动包即可。在桌面环境下切换过图形驱动。一些笔记本的核显独显方案NVIDIA 驱动可能没被正确启用。检查lsmod | grep nvidia看模块是否在载。Docker 容器里缺 NVIDIA 工具链。容器内部 nvidia-smi 需要 nvidia-container-toolkit 正确挂载宿主机能跑不代表容器里能跑。最直接的排查顺序是先看lsmod | grep nvidia没有就modprobe nvidia手动加载还不行就查日志dmesg | tail -50看有没有驱动初始化报错。日志里通常写得比报错本身清楚得多。5.2 watch 刷新界面出现的几个小毛病watch 虽然稳定但也有让人抓狂的时候。最常见的是刷新内容过长超过终端高度导致界面上下倒腾看着眼睛都花。解决方法是缩小输出用查询模式只打印关键行watch -n 1 nvidia-smi --query-gpumemory.used,utilization.gpu --formatcsv另一个问题是远程 SSH 会话网络抖动时watch 刷新会卡住界面停在某一帧不再更新。这不是命令出错是 SSH 交互通道的问题。可以给 watch 加-x参数减少 shell 交互开销或者干脆改用循环脚本配合重定向把输出写到文件再用tail -f查看这样断线重连也不影响采集nohup bash -c while true; do nvidia-smi -i 0 --query-gpumemory.used --formatcsv,noheader gpu_mem.log; sleep 5; done 这个做法的好处是采样和查看分离断开 SSH 也不会中断监控回到终端后直接看日志文件就行。还有个小技巧当你既想实时刷又不想占一个终端窗口可以把 watch 放进 tmux 或 screen 的独立窗格主终端继续干别的活。多卡机器上甚至可以每个 GPU 开一个窗格互不干扰。5.3 容器环境与多卡场景的特殊情况Docker 容器里执行 nvidia-smi看到的显存是物理 GPU 的真实容量不是容器被限制的额度。如果你的容器是通过 --gpus all 启动的默认能访问所有 GPUnvidia-smi 显示的就是全部显存。但如果你用 Docker 的 NVIDIA_DEVICE_DRIVER 或者 CUDA 容器运行时做了资源限制nvidia-smi 显示的仍是物理总容量真实的可用显存反而要看别处的 quota 配置。这个反直觉的地方容易让人误判。多卡机器的显存监控还有一个常见误区nvidia-smi 只显示计算进程不显示 MIG 切分后的实例使用情况。如果用到了 MIG需要单独查看 MIG 上下文的占用默认输出会给不出完整视图。共享机器上我还踩过另一个坑nvidia-smi -l的循环模式在一些老版本里会持续占用 GPU 的 NVML 调用导致训练脚本的显存查询偶尔超时。解决办法就是少用 -l改成我们前面说的查询模式或外部循环。写在最后把 nvidia-smi 用到形成肌肉记忆之后我最大的感触是监控本身不解决显存不足但它能把“哪里出了问题”从猜变成看。很多 OOM 排查到最后其实只需要看清两件事峰值出现在哪个阶段、残留进程是谁。前者靠实时刷新曲线后者靠进程查询。这两件事做好显存治理的基本功就到位了。最后再分享一个个人习惯每次新建训练任务之前我都会先在后台挂一个轻量显存采样脚本把时间戳和显存数值写到文件。训练结束翻一遍这个文件任何一步显存异常都能找到对应时间点再配合训练日志定位精确原因。这个习惯帮我在“显存悄悄涨但又没超过阈值”的场景下省了很多排查时间建议你也试试。
返回列表