nvidia-smi 实战:排查 GPU 利用率与显存占用异常
GPU 利用率突然 100%、显存被占满、进程列表为空但设备持续繁忙、某张卡温度和功耗异常,这些现象不能只靠一张 nvidia-smi 截图下结论。nvidia-smi 展示的是驱动和 NVML 在采样时刻提供的状态:利用率是时间窗口内引擎忙碌比例,显存是设备内存分配,进程表则受权限、容器命名空间、MIG、驱动状态和采样时刻影响。三者不一致并不自动等于“幽灵进程”。
本文适用于 Linux 与 NVIDIA 数据中心 GPU。<GPU编号>、<进程PID>、<容器ID>、<命名空间>、<Pod名> 是占位符,需要替换为真实对象。修改持久化模式、功率限制、MIG 配置、结束进程或 GPU reset 都会影响业务,必须在只读证据完整后再执行。
一、先记录驱动、GPU 和工具版本
排障报告首先要说明驱动版本、CUDA Driver API 版本、GPU 型号、UUID 和固件状态。CUDA Version 字段表示驱动最高支持的 CUDA 版本,不等于当前 Python 环境实际安装的 CUDA Toolkit。
#!/usr/bin/env bash
set -euo pipefail
# 总览:驱动版本、GPU 型号、显存与当前进程
nvidia-smi
# 按固定字段输出 CSV,便于留存与对比(优先使用 UUID/PCI Bus ID,避免依赖易变的 index)
nvidia-smi --query-gpu=index,uuid,name,driver_version,pci.bus_id,memory.total --format=csv,noheader
# 记录内核版本,驱动问题常与内核头、内核模块版本相关
uname -a
GPU index 可能在重启、设备筛选或 MIG 变化后改变。长期关联应优先使用 GPU UUID 或 PCI Bus ID,而不是只写“GPU 0”。
列出设备与 MIG 实例,确认操作对象。
# 列出所有 GPU 与 MIG 实例,UUID 是长期关联的稳定标识
nvidia-smi -L
# 确认持久化模式与是否接显示输出
nvidia-smi --query-gpu=index,uuid,pci.bus_id,display_active,persistence_mode --format=csv
二、理解利用率和显存不是同一指标
GPU utilization 通常表示一个采样窗口内是否有 kernel 在 GPU 上执行;memory utilization 是显存读写引擎活跃度,不是显存占用百分比。FB memory used 则表示已分配设备内存。模型加载后可能显存很高但 GPU 利用率接近 0,这是等待请求的正常状态。
按固定字段采集一份当前快照。
# 单次快照:同时采集利用率、显存、温度、功耗与 P-State,字段随驱动版本变化
nvidia-smi --query-gpu=index,uuid,name,utilization.gpu,utilization.memory,memory.used,memory.free,memory.total,temperature.gpu,power.draw,pstate --format=csv
单次快照容易错过瞬时峰值。dmon 适合按秒观察功耗、利用率、时钟、显存、ECC 和吞吐;支持的字段组以当前版本 nvidia-smi dmon --help 为准。
# 每秒采样:功耗(p)、利用率(u)、时钟(c)、显存(v)、ECC(m)、编码(e)、温/引擎吞吐(t),-o DT 附带时间戳
nvidia-smi dmon -s pucvmet -d 1 -o DT
观察至少覆盖一个真实业务请求或一个异常周期。持续 100% 利用率只有与吞吐下降、时延升高、温度/功率限制、Xid 或队列增长同时出现时才是故障线索。
三、定位占用显存的计算进程
先通过 NVML 查询计算进程。used_memory 在部分 MIG 或驱动模式下可能显示 N/A,不能强行解释为 0。
# 列出当前占用显存的计算进程;used_memory 为 N/A 时不能当作 0
nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory --format=csv
再将 PID 对应到系统用户、父进程、启动时间和完整命令。命令行可能包含令牌或模型路径,输出应按敏感信息处理。
# 替换为 nvidia-smi 进程表里的宿主 PID
PID="<进程PID>"
# 进程用户、父进程、启动时间、状态与完整命令行(命令行可能含敏感信息,注意脱敏)
ps -o user,pid,ppid,lstart,etime,stat,%cpu,%mem,args -p "$PID"
# 查看进程树与祖先,确认是谁拉起的
pstree -aps "$PID"
# cgroup 路径,用于后续反查容器归属
cat "/proc/$PID/cgroup"
pmon 按采样显示 GPU 上活动进程的 SM、显存引擎、编码与解码使用情况,适合判断“占显存但是否真正计算”。
# 每秒采样该卡上进程的 SM 利用率(u)与显存引擎(m),判断“占显存是否真在算”
nvidia-smi pmon -i <GPU编号> -s um -d 1
短 kernel 可能在两次采样间完成,因此 pmon 没有进程不代表从未执行。应同时对照应用日志、调度器和 dmon 时间线。
四、容器和 Kubernetes 中映射进程归属
宿主机 nvidia-smi 显示的是宿主 PID。Docker 环境可从 cgroup 和容器进程列表反查归属。
PID="<进程PID>"
# cgroup 路径中包含容器 ID 前缀,可与 docker ps 结果对照
cat "/proc/$PID/cgroup"
# --no-trunc 显示完整容器 ID,便于与 cgroup 匹配
docker ps --no-trunc --format '{{.ID}} {{.Names}} {{.Status}}'
# 查看容器内进程树,核对宿主 PID 与容器内 PID 的对应关系
docker top <容器ID> -eo pid,ppid,user,etime,args
不要仅根据进程名 python 就结束进程;同一节点可能同时运行训练、推理、监控和平台守护程序。
Kubernetes 中先找出节点上的 GPU Pod,再核对容器 ID。所有 kubectl 命令显式指定 namespace。
# 列出该节点上的全部 Pod,先圈定 GPU 业务范围
kubectl get pod -n <命名空间> -o wide \
--field-selector spec.nodeName=<节点名>
# 提取容器 ID(去掉 docker:// 前缀后与进程 cgroup 对照)
kubectl get pod <Pod名> -n <命名空间> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.containerID}{"\n"}{end}'
如果不知道业务 namespace,应通过平台资产或调度记录查找,不要执行无边界的集群导出。设备插件与 GPU Operator 组件通常位于平台 namespace,需要单独按其实际 namespace 检查。
查看 Pod 声明的 GPU 资源和状态事件。
# 查看 Pod 声明的 GPU 资源请求/限制与当前状态
kubectl get pod <Pod名> -n <命名空间> -o json \
| jq '{node:.spec.nodeName,containers:[.spec.containers[]|{name,resources}],status:.status.phase}'
# 按时间排序查看该 Pod 的调度、驱逐等事件
kubectl get events -n <命名空间> \
--field-selector involvedObject.name=<Pod名> --sort-by=.lastTimestamp
五、进程列表为空但显存或利用率异常
先检查设备文件是否仍被某个进程打开。fuser 与 lsof 需要足够权限,且可能在高进程数主机上耗时。
# 进程表为空时,先看设备文件是否仍被进程打开(需要 root 权限)
sudo fuser -v /dev/nvidia* 2>/dev/null || true
# 交叉验证占用设备文件的进程与 PID
sudo lsof /dev/nvidia0 /dev/nvidiactl /dev/nvidia-uvm 2>/dev/null || true
如果 nvidia-smi 显示利用率 100% 但显存 0 MiB、进程表为空,应连续采样 dmon/pmon,并检查 Xid、驱动日志、容器退出残留和虚拟化层。一次快照可能正好处于上下文销毁或遥测延迟窗口。
# 连续采样 20 秒,排除单次快照恰好落在上下文销毁/遥测延迟窗口的干扰
for i in $(seq 1 20); do
date -Is # 时间戳便于与业务日志对齐
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,pstate,power.draw --format=csv,noheader
sleep 1
done
若连续采样仍异常,检查内核和驱动日志。Xid 是诊断方向,不同错误码含义不同,不能看到 Xid 就直接宣布硬件损坏。
# 内核日志中的驱动错误与 Xid 错误码(是诊断方向,不等于硬件损坏)
sudo journalctl -k --since '-30 min' --no-pager | grep -Ei 'NVRM|Xid|nvidia|nvlink|pcie' || true
# -T 显示人类可读时间,只看最近 100 条驱动相关日志
dmesg -T | grep -Ei 'NVRM|Xid' | tail -n 100 || true
六、检查温度、功耗、时钟和限频原因
性能下降时要看 GPU 是否处于低 P-State、是否触发功率或热限制、SM/显存时钟是否符合负载。查询字段随驱动版本变化,先列出帮助中的可查询字段。
# 查询字段随驱动版本变化,先确认当前版本支持哪些字段
nvidia-smi --help-query-gpu | less
# 查看温度、功耗、时钟与限频原因(clocks throttle reasons)
nvidia-smi -q -d TEMPERATURE,POWER,CLOCK,PERFORMANCE -i <GPU编号>
温度高不一定已经降频,功耗接近上限也不一定异常。只有时钟下降、限制原因激活、吞吐下降与温度/功率同时变化,才能建立证据链。
连续记录关键字段便于与业务吞吐对齐。
# -l 1 每秒追加一行,连续记录便于与业务吞吐、温度功率变化对齐证据链
nvidia-smi --query-gpu=timestamp,index,pstate,temperature.gpu,power.draw,power.limit,clocks.sm,clocks.mem,utilization.gpu --format=csv -l 1
七、检查 ECC、PCIe 与 NVLink
数据中心 GPU 的 ECC、PCIe replay 和 NVLink 错误可能导致性能或稳定性问题。具体支持字段取决于 GPU 型号和驱动。
# ECC 错误计数与退役页面(page retirement),支持字段因型号/驱动而异
nvidia-smi -q -d ECC,PAGE_RETIREMENT -i <GPU编号>
# volatile 是自驱动加载以来的累计值,需结合增长时间与 Xid 判断是否当前故障
nvidia-smi --query-gpu=index,ecc.errors.corrected.volatile.total,ecc.errors.uncorrected.volatile.total --format=csv
累计 ECC 非零不等于当前故障,应观察 volatile/aggregate、增长时间和 Xid。不可纠正错误需要按 NVIDIA 和硬件厂商流程处理。
查看 GPU 拓扑,判断多卡任务是否跨 NUMA、PCIe Switch 或低带宽路径。
# 拓扑矩阵:判断多卡是否跨 NUMA、PCIe Switch 或低带宽路径
nvidia-smi topo -m
# 点对点(P2P)能力:决定多卡通信走 NVLink/PCIe 直连还是经 CPU 转发
nvidia-smi topo -p2p r
# 主机 NUMA 布局,配合拓扑判断 GPU 与 CPU/内存的亲缘关系
numactl --hardware
NVLink 状态命令只在支持 NVLink 的设备上有效;不支持时返回错误不能当作链路故障。
# NVLink 链路状态(不支持 NVLink 的设备会报错,不能当作链路故障)
nvidia-smi nvlink --status -i <GPU编号>
# NVLink 带宽能力
nvidia-smi nvlink --capabilities -i <GPU编号>
八、检查 MIG 与设备切分
MIG 开启后,一个物理 GPU 被切为多个 GPU Instance 和 Compute Instance。显存、利用率和进程需要按 MIG 设备解释,容器可见的 UUID 也可能是 MIG UUID。
# MIG 模式与切分配置;开启后需按 MIG 设备(而非物理卡)解释显存与利用率
nvidia-smi -q -d MIG
# 列出 GPU Instance 与 Compute Instance,确认容器可见的 MIG UUID
nvidia-smi mig -lgi
nvidia-smi mig -lci
创建、销毁或禁用 MIG 会影响该物理 GPU 上所有实例,通常需要先排空工作负载。普通排障只读查看,不应为了“刷新状态”修改 MIG 布局。
九、检查驱动模块和设备健康
确认 NVIDIA 模块版本、设备节点和持久化守护进程。宿主机驱动与容器 CUDA 用户态不兼容时,应用日志通常出现 CUDA 初始化或符号错误。
# 驱动模块版本与路径(需与容器内 CUDA 用户态兼容)
modinfo nvidia | grep -E '^(version|filename):'
# 确认驱动模块已加载
lsmod | grep '^nvidia'
# 设备节点是否齐全(nvidiactl、nvidia-uvm 缺失会影响功能)
ls -l /dev/nvidia*
# 持久化守护进程状态;未运行时设备在无客户端后可能进入省电状态
systemctl status nvidia-persistenced --no-pager 2>/dev/null || true
容器内验证驱动可见性,应使用已批准且与环境兼容的 CUDA 基础镜像。
# 用与业务同系 CUDA 版本的镜像验证容器内驱动可见性(镜像需先经过安全审批)
docker run --rm --gpus all <受信任CUDA镜像> nvidia-smi --query-gpu=index,uuid,name,driver_version --format=csv
宿主机成功而容器失败时,优先检查 NVIDIA Container Toolkit、Docker device request、CDI 配置和容器权限,不要重装驱动碰运气。
十、判断 OOM 与显存碎片
CUDA OOM 不一定意味着 nvidia-smi 显存达到 100%。框架缓存、预留内存、显存碎片、上下文、通信缓冲和瞬时峰值都会造成分配失败。先保存应用完整错误栈,再采集进程和 GPU 状态。
PID="<进程PID>"
# 记录 OOM 时点的显存用量与余量
nvidia-smi --query-gpu=index,memory.used,memory.free,memory.total --format=csv
# 进程内存与线程数,区分“设备显存 OOM”与“主机内存压力”
grep -E 'VmRSS|VmSwap|Threads' "/proc/$PID/status"
# 系统 OOM Killer 记录,与 CUDA OOM 是不同层次
journalctl -k --since '-15 min' --no-pager | grep -Ei 'oom|killed process' || true
系统 OOM Killer 与 CUDA OOM 是不同层次。前者通常有内核日志并杀死主机进程;后者由 CUDA/框架返回设备内存分配错误。根因结论必须引用对应日志。
十一、谨慎结束异常任务
确认 PID、用户、容器、作业 ID、checkpoint 和业务影响后,优先让调度器或应用正常停止。SIGTERM 允许程序处理退出;超时后才考虑 SIGKILL。
# 结束前最后核对一次进程身份与命令行
PID="<进程PID>"
ps -fp "$PID"
# SIGTERM 让程序有机会保存 checkpoint 并优雅退出
sudo kill -TERM "$PID"
# 最多等待 30 秒,进程退出即成功返回
for i in $(seq 1 30); do
kill -0 "$PID" 2>/dev/null || exit 0
sleep 1
done
echo "process did not exit after SIGTERM" >&2
exit 1
SIGKILL 会立即终止进程,可能损坏输出、丢失 checkpoint 或让分布式其他 rank 卡死。只有在确认无法优雅退出且有恢复方案时才使用。
# 仅在前一步超时且确认无法优雅退出时使用;SIGKILL 可能损坏输出或丢失 checkpoint
sudo kill -KILL <进程PID>
# 确认进程已从计算进程表消失
nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory --format=csv
十二、持久化模式和 GPU reset 的边界
持久化模式可减少无客户端时驱动状态初始化开销,但不会修复正在卡死的 kernel。修改它会改变设备运行策略,应记录原值并可回滚。
# 变更前先记录当前持久化模式,便于回滚
nvidia-smi --query-gpu=index,persistence_mode --format=csv
# 开启持久化模式(减少无客户端时驱动状态初始化开销)
sudo nvidia-smi -pm 1 -i <GPU编号>
# 变更后复核
nvidia-smi --query-gpu=index,persistence_mode --format=csv
若变更不符合预期,恢复原状态。
# 不符合预期时恢复原状态
sudo nvidia-smi -pm 0 -i <GPU编号>
# 复核已回滚
nvidia-smi --query-gpu=index,persistence_mode --format=csv
GPU reset 会终止或破坏该 GPU 上全部上下文,某些 NVLink 拓扑、MIG、显示设备或虚拟化环境不支持单卡 reset。执行前必须排空任务、确认无进程占用、保存数据并准备节点重启方案。
# reset 前确认没有进程占用该卡,否则会破坏该卡上的全部上下文
sudo fuser -v /dev/nvidia<GPU编号> 2>/dev/null || true
# 单卡 reset;部分 NVLink 拓扑/MIG/虚拟化环境不支持,失败不要循环重试
sudo nvidia-smi --gpu-reset -i <GPU编号>
# reset 后检查设备状态
nvidia-smi -i <GPU编号>
reset 失败时不要循环执行,也不要卸载正在使用的驱动模块。应保留 Xid、拓扑、进程和驱动日志,按平台流程隔离节点或维护重启。
十三、自动采集故障快照
下面脚本创建时间目录,保存 GPU 总览、查询字段、拓扑、进程、内核日志和系统信息。它只读,不会结束进程或重置 GPU。
#!/usr/bin/env bash
set -euo pipefail
# 按时间创建诊断目录,0700 权限防止敏感信息泄露
OUT_DIR="<诊断目录>/gpu-$(date +%Y%m%d-%H%M%S)"
install -d -m 0700 "$OUT_DIR"
# GPU 总览与完整查询输出
nvidia-smi > "$OUT_DIR/nvidia-smi.txt"
nvidia-smi -q > "$OUT_DIR/nvidia-smi-q.txt"
# 拓扑与进程占用
nvidia-smi topo -m > "$OUT_DIR/topology.txt"
nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory --format=csv > "$OUT_DIR/processes.csv"
# 内核日志与系统信息
journalctl -k --since '-60 min' --no-pager > "$OUT_DIR/kernel.log"
uname -a > "$OUT_DIR/uname.txt"
诊断目录可能包含用户名、命令、容器和内部拓扑,应限制权限并按故障数据策略交接。
十四、监控而不是依赖人工截图
生产 GPU 监控通常使用 DCGM Exporter 或 NVML 采集器。Prometheus 指标名称以实际 exporter 暴露的指标为准,应同时观察 GPU 利用率、显存、温度、功耗、时钟、Xid、ECC、PCIe/NVLink 和业务吞吐。
低频巡检脚本可输出机器可解析 CSV,并在 GPU 数量与预期不符时返回非零。
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_GPUS="<预期GPU数量>"
# 统计当前可见 GPU 数量,与预期不一致时立即告警(常见于掉卡/驱动异常)
ACTUAL_GPUS="$(nvidia-smi --query-gpu=index --format=csv,noheader | wc -l)"
# 输出机器可解析的 CSV,供监控系统采集
nvidia-smi --query-gpu=timestamp,index,uuid,utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw,pstate --format=csv
# GPU 数量不符时返回非零,便于接入巡检/告警
if [[ "$ACTUAL_GPUS" -ne "$EXPECTED_GPUS" ]]; then
echo "GPU count mismatch: expected=$EXPECTED_GPUS actual=$ACTUAL_GPUS" >&2
exit 2
fi
最终判断应把 GPU 指标与应用阶段对应起来:模型加载时显存增长、推理时 SM 活跃、通信阶段 NVLink/PCIe 活跃、等待数据时 GPU 空闲都可能正常。只有状态与预期工作阶段不一致,并有进程、日志、Xid、温度或性能证据支持,才称得上异常。
十五、不同工作负载下的正常形态
训练任务通常表现为显存长期稳定、高 SM 利用率、周期性通信和 checkpoint 阶段的 I/O 波动;推理服务则可能在模型加载后长期占用大部分显存,却只有请求到来时才提高利用率。视频编解码任务主要使用 Encoder/Decoder 引擎,GPU-Util 不一定能完整体现负载;多进程共享服务还可能在不同进程间快速切换。排障前应先明确应用当前处于加载、预热、计算、通信、等待数据、保存还是空闲阶段。
某张卡利用率明显低于同组其他卡时,先看它是否是流水线边界、是否等待最慢 rank、输入 batch 是否不均、数据加载是否卡在 CPU,或者分布式任务是否处于同步点。仅凭一张卡 0% 不能认定设备掉线;如果同卡显存、进程和 NVLink 都存在,应用日志可能更能解释它在等待什么。反过来,利用率持续 100% 但业务吞吐为零,则要检查 kernel 卡死、通信重试、错误恢复循环和驱动 Xid。
十六、识别显存持续增长
显存增长可能是正常的缓存预热、KV Cache 扩展、CUDA Graph 捕获,也可能是请求未释放、张量引用泄漏或异常任务残留。应以固定请求集重复测试,记录每轮结束后的 used memory、框架缓存统计和活动请求数。若业务回到空闲后显存没有回落,也不能立即称为泄漏,因为 PyTorch 等框架会保留缓存池供后续复用;真正的判断要看可复用缓存、分配失败、请求数和多轮趋势。
当显存增长最终触发 OOM 时,保留首次错误栈尤其重要。后续 OOM 可能只是级联结果,例如一个 rank 先失败,其他 rank 在通信处报错。不要在日志中只截最后一行 CUDA out of memory;应保存最先出现异常的 rank、分配大小、当时的 free memory、输入长度和并发数。
十七、驱动升级与节点隔离
驱动升级会影响所有 GPU 任务、内核模块和容器兼容性,不能作为普通故障的第一反应。只有在错误码、兼容矩阵、已知缺陷或厂商建议支持时,才应进入升级流程。升级前排空节点、保存作业 checkpoint、记录当前驱动与固件、验证新驱动对应的 CUDA 和容器镜像,并准备回退包与维护重启。
如果同一 GPU 重复出现不可纠正 ECC、Xid、PCIe/NVLink 错误或 reset 后再次异常,应优先隔离节点并阻止新任务调度,而不是让调度器持续重试。隔离结论必须引用 GPU UUID、时间、错误码、内核日志和受影响作业;设备恢复后还应完成压力验证,不能只看 nvidia-smi 能重新打开。
十八、告警阈值必须结合业务基线
GPU 利用率高不适合单独告警,因为训练和高吞吐推理本来就希望设备持续繁忙;更有效的告警是业务吞吐下降或队列增长,同时 GPU 利用率、时钟、温度、功耗或错误状态偏离基线。显存使用率同样不能只按固定百分比处理:模型服务常常预分配 KV Cache,长期高显存可以是设计行为,而显存突然下降则可能意味着 worker 退出或模型被卸载。
温度、功率和 ECC 阈值应依据具体 GPU 数据手册、机房环境和硬件团队策略确定。Prometheus 指标名称以实际 exporter 暴露的指标为准,不同 DCGM Exporter、NVML 采集器和 GPU 型号的字段与标签可能不同。上线告警前应在监控系统直接查询真实时间序列,验证 GPU UUID、节点、Pod 和容器标签能够正确关联,避免因标签缺失把多张卡聚合成一个错误结论。
故障恢复也应设置判断窗口。进程退出后显存释放、驱动遥测和调度器状态同步可能需要时间;如果告警在单个采样点立即恢复,容易掩盖抖动。建议把 GPU 指标、应用请求、作业状态、节点事件和 Xid 放在同一时间线上,并在恢复记录中说明采取了停止任务、reset、节点重启还是仅等待负载结束。