Kubernetes GPU 100% 利用率但显存 0、无进程:NVIDIA Xid 13/31/43 排查与恢复

这篇文章记录一次 Kubernetes GPU 节点的异常:容器内 nvidia-smi 显示一张卡利用率 100%,但显存为 0、进程表为空。继续回查宿主机后,发现这不是当前任务在正常计算,而是此前 Python/vLLM CUDA 任务触发 Xid 13、31、43,驱动没有完整清理 GPU context,导致 GPU 进入“无进程但保持高负载”的异常状态。

文章中的节点名、Pod 名、IP、GPU UUID 和业务名称均已替换为占位符。命令需要在你自己的 Kubernetes 工具机和目标节点上执行。

GPU Xid 13/31/43 故障链路

GPU Xid 13/31/43 排查过程

环境与问题表现

本次环境的关键版本如下:

项目 值
GPU NVIDIA GeForce RTX 5090 D
驱动 590.44.01
CUDA 13.1
节点系统 Ubuntu 22.04 系列
工作负载 Kubernetes 中的 Python/vLLM 服务

容器内第一次看到的输出类似这样:

GPU 0: NVIDIA GeForce RTX 5090 D
GPU 1: NVIDIA GeForce RTX 5090 D

GPU 0: 100%      0 MiB / 32607 MiB
GPU 1:   0%      0 MiB / 32607 MiB

Processes:
  No running processes found

这个组合不能直接解释成“容器里有进程把 GPU 跑满了”。至少要同时确认四件事:容器看到的 GPU UUID、宿主机对应的 GPU、compute-apps/pmon 是否能列出进程,以及宿主机内核是否记录了 Xid。

一、先确认容器内的现象是否稳定

非交互脚本不要依赖 ktt、kap 这类 shell 别名,直接使用明确的 kubeconfig:

K="kubectl --kubeconfig=/path/to/cluster-config"
NS=test
POD=<pod-name>

$K exec -n "$NS" "$POD" -- \
  nvidia-smi --query-gpu=timestamp,index,name,uuid,utilization.gpu,memory.used,power.draw \
  --format=csv -l 1

$K exec -n "$NS" "$POD" -- nvidia-smi pmon -c 5

如果多次采样都得到 100% + 0 MiB,并且 pmon 没有 PID,继续到宿主机核对。单次 nvidia-smi 读数可能只是瞬时采样,不能单独作为根因。

二、把容器 GPU 映射到宿主机 PCI 设备

先在容器内记录 GPU UUID:

$K exec -n "$NS" "$POD" -- \
  nvidia-smi --query-gpu=index,uuid,name --format=csv,noheader

然后在节点上查询完整 GPU 列表:

ssh root@<node> \
  'nvidia-smi --query-gpu=index,uuid,pci.bus_id,name,utilization.gpu,memory.used \
   --format=csv,noheader'

不要只按容器内的 GPU 0、GPU 1 判断。容器经过 NVIDIA device plugin 隔离后,索引可能已经重新编号;UUID 和 PCI Bus ID 才是可靠的关联键。

三、区分“真正的计算进程”和“仅持有设备句柄的进程”

在宿主机执行:

nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory \
  --format=csv,noheader

nvidia-smi pmon -c 5

fuser -v /dev/nvidia* 2>&1 || true

三类输出含义不同:

检查 能证明什么
compute-apps NVIDIA 当前登记的 CUDA 计算进程
pmon 采样窗口中能关联到 GPU engine 的进程
fuser /dev/nvidia* 进程打开过 NVIDIA 设备文件

fuser 中出现 Python、DCGM、gpud 或 device-plugin,并不等于这些进程正在提交 GPU kernel。只有 compute-apps 和 pmon 能把进程和计算活动直接关联起来。

本次异常中,GPU 1 的 compute-apps 为空,pmon 也为空;但利用率保持 100%、功耗约 110W、SM 和显存时钟保持高档。这说明当前没有可见的正常用户态计算进程。

四、用内核日志确认 Xid 事件

在宿主机查询最近的 NVIDIA 内核日志:

journalctl -k --since "48 hours ago" --no-pager \
  | grep -Ei 'NVRM|Xid|nvidia|GPU' \
  | tail -200

节点如果重启过,旧日志可能在轮转文件中:

zgrep -iE 'NVRM|Xid' /var/log/kern.log* 2>/dev/null | tail -200

本次节点的关键记录是:

Xid 13: Graphics SM Warp Exception ... Out Of Range Address
Xid 31: MMU Fault ... ACCESS_TYPE_VIRT_WRITE
Xid 43: channel exception
GPU1 refcntRequestReference_IMPL: Failed to enter state 1

这几类日志把“100% 利用率”与此前的 CUDA fault 连接起来了:

Python/vLLM CUDA kernel 非法地址访问
  -> Xid 13 / Xid 31
  -> channel 异常 Xid 43
  -> 驱动引用计数/恢复失败
  -> 原进程消失,但 GPU engine/context 没有被清理
  -> nvidia-smi 继续显示 SM 100%,却找不到 compute process

这里的“驱动没有清理干净”是基于 Xid 43、refcntRequestReference_IMPL 和持续采样结果的诊断结论;它不是根据单条利用率指标猜出来的。

五、gpud 和 DCGM 应该怎样看

gpud 可以帮助确认当前硬件健康状态,但不能把所有应用类 Xid 都直接判成硬件故障。建议同时读取 gpud 的状态接口和日志:

kubectl -n gpud get pod -o wide
kubectl -n gpud logs <gpud-pod> --since=2h --all-containers \
  | grep -Ei 'xid|ecc|slowdown|unhealthy|error|fail' | tail -100

kubectl -n gpud exec <gpud-pod> -c gpud -- \
  sh -c 'curl -sk --max-time 5 https://127.0.0.1:15132/v1/states'

本次 gpud 仍报告 ECC、SXID 和硬件降速 Healthy。这并不推翻 Xid 证据:应用类 CUDA fault 可能不会立即表现为 ECC 或硬件降速故障,但仍会把某张卡留在不可用状态。

六、官方资料和相似案例

NVIDIA 官方 Xid 文档:

公开的 Kubernetes 案例:

该 issue 记录了 Xid 43 之后多个 Triton/PyTorch 推理 Pod 持续失败、同一 GPU 后续任务继续失败,最后通过 drain 并删除节点恢复。它还讨论了为什么 Xid 13、31、43 不能简单全部纳入默认自动修复:有些是单个输入或应用 kernel 触发的应用类错误,但如果 GPU 已经 wedged,就需要节点级处置。

为什么不能把 Xid 13、31、43 全部设成“看到就自动修复”

自动修复的目标是恢复节点健康,但 GPU Xid 的语义并不等于“整张卡已经永久损坏”。把这几个码无条件绑定到 drain、重启或替换节点,会有三类风险:

  1. 单个任务可能是错误源,而不是整张卡。 Xid 13、31 常见于 kernel 越界访问、非法地址或页表 fault。一个坏输入、一个自定义 CUDA 扩展或一次版本兼容问题,都可能只打坏当前进程。此时重启整台节点会影响同节点上其他正常 GPU 工作负载。
  2. 同一错误码的恢复结果可能不同。 有的任务退出后 GPU 能继续服务,有的任务会留下无法清理的 channel/context。只看 Xid 数字,无法区分“进程级失败”和“GPU 已经 wedged”。
  3. 共享节点会放大误伤。 一台节点可能承载多张 GPU 和多个租户。一次应用级 Xid 如果直接触发节点替换,会让原本健康的 Pod 一起迁移,造成不必要的容量抖动。

因此更稳妥的默认策略是:

单次 Xid 13/31
  -> 记录事件,保留 PID、GPU UUID、Pod UID 和 traceback
  -> 优先让业务进程自行退出或重建

短窗口内重复 Xid,或多个不同进程命中同一 GPU
  -> 提升为 GPU 级异常
  -> 暂停向该 GPU 调度新任务

Xid 43 + compute-apps 为空 + SM 持续 100% + 后续任务无法运行
  -> 视为 GPU context/驱动状态卡住
  -> 进入节点维护、GPU reset 或节点替换流程

生产告警可以把“事件告警”和“自动处置条件”分开:所有 Xid 都记录,但只有满足重复次数、时间窗口、多个 PID、当前 GPU 健康状态和业务失败等组合条件时,才触发自动隔离或节点修复。这样既不会漏掉本次这种 GPU 已经卡住的情况,也不会因为一次用户代码错误重启整台共享节点。

七、正确的恢复顺序

不要直接对持有 /dev/nvidia1 的 Python 进程执行 kill -9。正确的处理顺序是:

1. 先确认影响范围

kubectl get pods -A --field-selector=spec.nodeName=<node> -o wide
kubectl describe node <node> | grep -A 12 -B 3 'nvidia.com/gpu\|Allocated resources'

2. 停止继续调度并迁移 GPU 业务

在确认节点上的 GPU Pod 清单、PDB、替代节点容量和业务影响后,再执行集群标准的 cordon/drain 流程。生产环境必须先做变更确认和回滚准备。

3. 尝试单卡 reset

确认该 GPU 已没有业务进程且节点维护窗口成立后,才尝试:

nvidia-smi --gpu-reset -i <gpu-index>

不同 GPU 型号、驱动和进程占用条件下,reset 可能被拒绝。不要绕过 busy 检查强行卸载 NVIDIA 内核模块。

4. reset 失败时重启或替换节点

如果 GPU reset 失败、nvidia-smi 仍然卡住,或者 Xid 持续出现,节点重启/替换通常比反复杀进程更可靠。重启后重新检查:

nvidia-smi
nvidia-smi pmon -c 5
journalctl -k --since boot --no-pager | grep -Ei 'NVRM|Xid' || true

5. 恢复业务前做小流量验证

确认 GPU 利用率、显存、功耗和进程列表恢复正常,再逐步恢复工作负载。不要在刚恢复的节点上直接一次性放回全部 GPU 服务。

八、如何避免再次发生

恢复节点只解决当前状态,不能替代根因治理。建议保留以下证据:

  1. 记录镜像中的 Python、PyTorch、CUDA、vLLM 版本和 GPU 驱动版本。
  2. 对出现 Xid 13/31 的服务保留完整 CUDA traceback 和请求样本摘要。
  3. 测试环境可使用 CUDA_LAUNCH_BLOCKING=1 缩短异步 kernel 错误与业务调用之间的距离。
  4. 排查自定义 CUDA 扩展、Tensor Parallel、NCCL 和非法显存访问。
  5. 对连续出现 Xid 13/31/43 的节点设置“节点级异常”告警,不要只看 Pod 是否 Ready。
  6. 把 Xid 事件、GPU UUID、PCI Bus ID、Pod UID 和节点维护动作放在同一条时间线上。

需要区分两层问题:

  • 本次节点的直接原因:历史 CUDA fault 触发 Xid 13/31/43,GPU1 的驱动状态未恢复。
  • 最初非法访问的代码原因:仅靠 Xid 日志不能判定是 vLLM、PyTorch、CUDA 扩展还是驱动兼容性问题,需要结合对应工作负载版本和可复现 traceback 继续定位。

快速参考

看到 100% + 0 MiB + 无进程 时

nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory --format=csv
nvidia-smi pmon -c 5
nvidia-smi dmon -s pucm -c 5
journalctl -k --since "48 hours ago" --no-pager | grep -Ei 'NVRM|Xid'

结论口径

  • compute-apps 有进程:先按业务任务排查。
  • compute-apps 为空、pmon 为空、Xid 13/31/43 存在:优先按 GPU context/驱动状态异常处置。
  • 只有 fuser 有 Python:只能说明打开设备文件,不能证明它正在计算。
  • reset 前先迁移业务;reset 失败再重启或替换节点。

这类故障最容易误判的地方,是把“GPU 利用率 100%”直接等同于“现在有一个进程在正常跑满 GPU”。真正可靠的结论必须同时对齐 GPU UUID、compute process、pmon、内核 Xid 和恢复结果。

posted @ 2026-09-28 11:09  Hello_worlds  阅读(13)  评论(0)    收藏  举报