GPU 与加速器可观测性:DCGM 指标、显存归因与 XID 故障定位

系统讲解 GPU 与加速器可观测性工程:DCGM 与 NVML 指标来源、SM 利用率与显存占用的正确读法、XID 错误码与硬件故障定位、ECC 与 NVLink 健康度、训练与推理任务的 GPU 归因(MIG、Device Plugin、Pod 标签)、功耗温度监控与告警阈值,以及常见误读与最佳实践清单。

一台 8 卡 A100 的训练节点,nvidia-smi 显示显存占满 99%,但训练吞吐只有理论值的 30%;另一台机器上训练任务频繁重启,日志里只有一句 “CUDA error: unspecified launch failure”,没有任何业务异常。这两类问题,传统的 CPU 内存、网络 IO 指标一个都答不上来——加速器的故障模式、指标语义与归因维度都和 CPU 完全不同。

本文讲清楚三件事:GPU 指标从哪来、怎么读(DCGM 与 NVML 的指标语义)、坏了怎么定位(XID 错误码与 ECC/NVLink 健康度)、以及一个多卡多任务节点上「这块卡到底是谁在用、为什么慢」的归因方法。同时给出可直接落地的采集架构与告警规则。

1. GPU 为什么需要专门的可观测性

1.1 CPU 指标套不到 GPU 上

把 GPU 当成「一块更快的 CPU」来监控,几乎必然踩坑。差异体现在三个层面:

语义差异:
  CPU 利用率 = 非空闲时间占比,接近线性可解释
  GPU "利用率" 至少有三个口径:SM 活跃、显存带宽、Tensor Core 占用
  它们可以同时很高或同时很低,单看一个数字会得出相反结论

故障差异:
  CPU 故障多数表现为进程崩溃或 ECC 内存报错
  GPU 故障表现为 XID 错误、NVLink 降速、显存不可纠正 ECC、
  甚至"静默降频"——任务不报错但变慢

归因差异:
  一个进程独占一个 CPU 核心是常态
  一张 GPU 上可能同时跑多个进程、多个容器、多个 MIG 实例
  不解决归因,"这块卡很忙"就无法回答"该找谁"

1.2 三层指标来源

GPU 可观测性的数据来自三个层次,覆盖范围和开销各不相同:

层次接口能拿到什么特点
驱动层NVML(C 库)利用率、显存、温度、功耗、ECC、XID最全,需进程直连驱动
导出层dcgm-exporter把 DCGM 字段转成 Prometheus 指标事实标准,带 GPU 标签
应用层CUDA/NVML Profilingkernel 耗时、SM occupancy、显存分配栈精细但开销大,按需开启

生产环境的默认组合是 DCGM(Data Center GPU Manager)+ dcgm-exporter + Prometheus:DCGM 负责与驱动和硬件对话(含健康检查与诊断),exporter 负责暴露成时序指标。应用层 profiling 只在排查具体 kernel 问题时临时开启。

1.3 与通用方法论的衔接

GPU 属于典型的「资源」,可以直接套用 RED/USE 方法论 中的 USE 三件套:Utilization(利用率)、Saturation(饱和度)、Errors(错误)。GPU 的饱和度不看队列长度,而看「是否有任务在等卡」(NVIDIA 侧的 nvidia-smi 排队、Kubernetes 侧的 Pending Pod、调度器的等待时长);错误则看 XID 与 ECC。

2. DCGM 指标体系

2.1 核心指标字段

DCGM 用一套 Field ID 命名空间组织指标,dcgm-exporter 会转成 DCGM_FI_* 形式的 Prometheus 指标。最常用的几类:

Prometheus 指标含义单位常见误读
DCGM_FI_DEV_GPU_UTILSM 活跃时间占比%高不等于算得满
DCGM_FI_DEV_MEM_COPY_UTIL显存控制器忙占比%与 GPU_UTIL 独立
DCGM_FI_DEV_FB_USED已用帧缓冲MiB包含缓存,不等于有效数据
DCGM_FI_DEV_FB_FREE空闲帧缓冲MiB碎片化时"有空间却分配失败"
DCGM_FI_DEV_POWER_USAGE板卡功耗W撞到 TDP 会触发降频
DCGM_FI_DEV_GPU_TEMP核心温度℃超过阈值触发硬件降频
DCGM_FI_DEV_SM_CLOCKSM 实际频率MHz与 Max Clock 对比看降频
DCGM_FI_DEV_ECC_DBE_VOL_TOTAL不可纠正 ECC 累计次非 0 即需介入
DCGM_FI_DEV_XID_ERRORS最近一次 XID 码码值是"最近值"不是计数器

2.2 利用率与显存的正确读法

DCGM_FI_DEV_GPU_UTIL 的定义是「在过去采样周期内,至少有一个 kernel 在执行的时间占比」。这意味着两件事:一是它是时间维度的占比,不反映并行度;二是采样周期越短,抖动越大。

典型误判一:GPU_UTIL 98% 但吞吐低
  原因:kernel 之间存在大量串行同步与空洞,或者 kernel 本身访存受限
  佐证:同时看 MEM_COPY_UTIL 与 SM_CLOCK,若带宽打满则是访存瓶颈

典型误判二:GPU_UTIL 30% 但任务"很慢"
  原因:数据加载(DataLoader)在 CPU 侧成为瓶颈,GPU 在等数据
  佐证:看进程的 CPU 使用率与磁盘/网络 IO

典型误判三:显存 OOM 但 FB_FREE 不为 0
  原因:PyTorch 缓存分配器碎片化,或 MIG 实例限额
  佐证:看 FB_USED 的锯齿曲线与 MIG 配置

显存指标要区分「进程视角」与「设备视角」。DCGM_FI_DEV_FB_USED 是整卡视角(含所有进程与 CUDA context 开销);要归因到具体进程,需要 nvidia-smi --query-compute-apps 或 DCGM 的进程级字段。

2.3 dcgm-exporter 部署要点

在 Kubernetes 上以 DaemonSet 方式部署,关键是让每个 Pod 的指标带上 gpu、GPU_I_ID(MIG 实例)、Hostname、pod、namespace 标签:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: dcgm-exporter
  namespace: gpu-observability
spec:
  selector:
    matchLabels:
      app: dcgm-exporter
  template:
    metadata:
      labels:
        app: dcgm-exporter
    spec:
      nodeSelector:
        nvidia.com/gpu.present: "true"
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      containers:
      - name: dcgm-exporter
        image: nvcr.io/nvidia/k8s/dcgm-exporter:3.3.5-3.4.0-ubuntu22.04
        args:
        - -f
        - /etc/dcgm-exporter/counters.csv
        - --kubernetes=true
        - --collect-interval=15000
        ports:
        - name: metrics
          containerPort: 9400
        securityContext:
          privileged: false
          capabilities:
            add: ["SYS_ADMIN"]
        volumeMounts:
        - name: dev
          mountPath: /dev
      volumes:
      - name: dev
        hostPath:
          path: /dev

几个容易忽略的参数:

--collect-interval      采集周期,默认 30000ms;训练场景建议 10~15s
--kubernetes=true       附加 Pod/Namespace 标签,需 RBAC 读 Pods
--mig-format            多实例 GPU 的标签格式,决定能否按 MIG 归因
-f counters.csv         白名单式选字段,避免暴露全部 200+ 字段导致基数膨胀

⚠️ 注意:dcgm-exporter 默认暴露全部字段时,单节点指标量可达数千条。生产环境务必用 counters.csv 只保留真正要告警和绘图的字段,并关闭无用的 DCGM_FI_PROF_*(Profiling 字段需要额外开启且开销较高)。

3. XID 错误与硬件故障定位

3.1 XID 码表

XID(NVIDIA Xid)是驱动在检测到 GPU 异常时写入内核日志的错误码。它不是「累计计数」,而是「最近一次事件的码值」,需要配合 dmesg 或 journald 才能拿到完整历史。

XID含义严重程度处置
13Graphics Engine Exception中多为应用代码问题,可重试
31GPU memory page fault中越界访问,查应用
43GPU stopped processing高该进程上下文已损坏,需重启进程
48Double Bit ECC Error极高显存不可纠正错误,换卡
63ECC page retirement 记录中有页被退休,关注累计趋势
74NVLink 错误高检查 NVSwitch/线缆,可能降速
79GPU has fallen off the bus极高掉卡,需重启节点/换卡
94Contained ECC error高单卡隔离,容器被终止
119GSP RPC timeout高固件/驱动异常,升级驱动
120GSP error高同上

3.2 采集与告警

dcgm-exporter 暴露的 DCGM_FI_DEV_XID_ERRORS 只反映最近一次 XID,更可靠的来源是内核日志。推荐双路采集:

# 方式一:从 dmesg / journald 抓 NVRM 行,转成日志指标
journalctl -k -f | grep -E 'NVRM: Xid' 
# 输出样例:
# NVRM: Xid (PCI:0000:3b:00): 79, pid=12345, GPU has fallen off the bus.

# 方式二:nvidia-smi 查询 XID 与健康状态
nvidia-smi --query-gpu=index,pci.bus_id,xid.errors,retired_pages.pending \
  --format=csv

# 方式三:DCGM 主动诊断(可离线跑,用于送修前取证)
dcgmi diag -r 3 -g 0

Prometheus 侧的告警规则可以这样写:

groups:
- name: gpu-health
  rules:
  - alert: GPUUncorrectableECC
    expr: increase(DCGM_FI_DEV_ECC_DBE_VOL_TOTAL[1h]) > 0
    labels:
      severity: critical
    annotations:
      summary: "GPU {{ $labels.gpu }} 出现不可纠正 ECC 错误"
      runbook: "确认 XID=48/94,隔离该卡并申请更换"

  - alert: GPUXidError
    expr: DCGM_FI_DEV_XID_ERRORS != 0
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "GPU {{ $labels.gpu }} XID={{ $value }},检查 dmesg NVRM 行"

  - alert: GPUThrottling
    expr: DCGM_FI_DEV_SM_CLOCK < DCGM_FI_DEV_SM_CLOCK_MAX * 0.7
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "GPU {{ $labels.gpu }} 降频超过 30%,检查温度与功耗"

3.3 从 XID 到根因的判断路径

XID 48/94(不可纠正 ECC)
  → 立即隔离该卡:kubectl cordon 节点,或把该 GPU 从资源池摘除
  → dcgmi diag -r 3 留证
  → 更换硬件,不要"再观察一下"

XID 79(掉卡)
  → 通常是 PCIe 链路或供电问题
  → 检查 dmesg 是否有 link down / AER 报错
  → 重启节点后若复现,换卡或换槽位

XID 74(NVLink)
  → nvidia-smi nvlink -s 查看各链路状态与错误计数
  → 多机训练场景下,NVLink 降速会直接拖垮 AllReduce

4. 训练与推理任务的 GPU 归因

4.1 归因难在哪

一张 A100 上可能同时有:系统侧的 CUDA context、多个训练容器的进程、监控/推理小任务。FB_USED 是整卡汇总值,直接看它无法回答「谁占的」。归因需要在指标上挂业务标签,路径有两条:容器标签(Pod/Namespace)和 MIG 实例标签。

4.2 Kubernetes 场景

dcgm-exporter 通过 kubelet 的 Pod 资源与 Device Plugin 的分配信息,把 pod、namespace、container 标签附加到指标上。前提是 RBAC 允许读 Pods,且 GPU 由 nvidia-device-plugin 分配:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dcgm-exporter-reader
rules:
- apiGroups: [""]
  resources: ["pods", "nodes", "services", "endpoints"]
  verbs: ["get", "list", "watch"]

归因查询示例——按 Pod 看显存占用与利用率:

# 每个训练 Pod 的显存占用(MiB)
sort_desc(
  sum by (namespace, pod) (DCGM_FI_DEV_FB_USED)
)

# 找出"占着卡但没干活"的 Pod:显存高、SM 利用率低
(
  sum by (pod) (DCGM_FI_DEV_FB_USED) > 20000
)
and on (pod)
(
  avg by (pod) (DCGM_FI_DEV_GPU_UTIL) < 5
)

第二条查询是排障中最实用的模式:显存占用高但利用率长期接近 0,通常是进程卡死、死锁,或者模型加载后进入了长时间的数据预处理而没启动训练。

4.3 MIG 场景

MIG(Multi-Instance GPU)把一张卡切成多个独立实例,每个实例有独立的 SM 与显存。此时:

指标区分:
  DCGM_FI_DEV_GPU_UTIL 仍是整卡视角,会把多个实例混在一起
  必须按 GPU_I_ID(实例 ID)与 GPU_I_PROFILE(如 3g.40gb)分组
  显存指标在 MIG 下按实例上报,可精确归因

注意:
  MIG 实例的 SM 数固定,利用率上限受实例规格限制
  混部不同规格实例时,跨实例比较利用率没有意义

4.4 与业务指标关联

GPU 指标要和业务侧打通才有诊断价值。推理服务的吞吐、TTFT、排队时长属于 LLM 服务可观测性 的范畴,把它的指标与 GPU 指标按 pod 对齐后,可以回答「吞吐下降是因为模型变慢,还是因为卡被抢占」:

关联维度:pod / node / gpu
典型问题:vLLM 的 prefill 延迟上升
  若 GPU_UTIL 同时下降 → 上游请求变少或数据侧阻塞
  若 GPU_UTIL 上升但吞吐下降 → 可能是显存不足触发重算,或降频
  若 SM_CLOCK 下降 → 先查温度与功耗,再看业务

在 Kubernetes 上调度 GPU 工作负载时,节点亲和、拓扑(NVLink 域)与 MIG 策略本身也是可观测性的一部分,可参考 Kubernetes AI/ML GPU 专题 中的调度与资源治理章节。

5. 采集架构与开销控制

5.1 采集方式对比

方式粒度开销适用
nvidia-smi 轮询整卡低但进程开销明显临时排查
NVML 直连整卡 + 进程低自研 agent
dcgm-exporter整卡 + 进程 + MIG低(默认字段)生产标准
DCGM Profiling 字段kernel 级中高性能调优
nsys / ncukernel 级高单次深挖

ℹ️ 核心:nvidia-smi 每次调用都会与驱动通信,在高频轮询(如每秒)下本身就会成为可观的开销。生产采集一律走常驻的 exporter,不要用脚本循环调 nvidia-smi。

5.2 频率与基数

采集频率:
  15s  —— 告警与容量规划(推荐默认)
  1s   —— 短期性能分析,仅临时开启
  60s  —— 长期趋势,可降低远端存储压力

基数控制:
  每卡约 20 个字段 × 8 卡 × 数百节点 = 数万时序
  加上 pod/namespace 标签后基数会放大
  策略:counters.csv 白名单 + 关闭 DCGM_FI_PROF_*
  长期存储用远端写,参考 Prometheus 长期存储方案

6. 告警、容量与成本

6.1 该告的与不该告的

必告:
  ECC 不可纠正错误 > 0
  XID 出现在高危码表(48/74/79/94/119/120)
  温度 > 85℃ 持续 5 分钟
  降频(SM_CLOCK / MAX_CLOCK < 0.7)持续 10 分钟
  掉卡(GPU 数量少于期望值)

不该告:
  GPU_UTIL 高 —— 卡忙是正常状态,不是故障
  FB_USED 高 —— 显存占满在设计内
  单次 XID 13 —— 多为应用问题,交业务侧处理

6.2 容量与成本

GPU 是数据中心里最贵的资源,利用率与成本的关联非常直接。闲置的 GPU 与低效的 GPU 是两种不同的浪费,前者靠调度解决,后者靠监控发现:

# GPU 利用率长期低于 20% 的卡(闲置浪费)
avg_over_time(DCGM_FI_DEV_GPU_UTIL[7d]) < 20

# 显存分配率高但利用率低(低效使用)
avg_over_time(DCGM_FI_DEV_FB_USED[7d])
  / avg_over_time(DCGM_FI_DEV_FB_TOTAL[7d]) > 0.8
and
avg_over_time(DCGM_FI_DEV_GPU_UTIL[7d]) < 30

把 GPU 时数折算成成本,与业务收益对齐,属于可观测性向财务侧延伸的部分,可参考 可观测性成本智能 的思路:指标采集的粒度要与决策粒度匹配——如果决策是「这个团队该不该再申请 8 张卡」,那么按团队聚合的周级利用率就够,不需要每卡每秒的原始数据。

7. 常见误读与最佳实践

误读真实情况正确做法
GPU_UTIL 高就是好高利用率也可能在空转同步结合吞吐与 MEM_COPY_UTIL
显存没满就没问题碎片化可导致分配失败看分配失败计数与锯齿曲线
没有 XID 就是健康静默降频不产生 XID对比实际频率与最大频率
单卡指标能代表整机多卡故障具有局部性按 gpu 标签逐卡告警
MIG 下看整卡利用率多实例混在一起无法解释按 GPU_I_ID 分组
□ 用 dcgm-exporter DaemonSet 采集,白名单字段而非全量
□ 指标必须带 gpu / pod / namespace / GPU_I_ID 标签
□ ECC 不可纠正错误与高危 XID 一律 critical 告警
□ 单独监控降频(实际频率 / 最大频率)
□ 显存与利用率交叉查询,识别"占卡不干活"
□ XID 历史从 journald 采集,不只依赖 exporter 的最近值
□ 采集频率 15s 起步,不要用脚本高频调 nvidia-smi
□ GPU 指标与业务指标按 pod 对齐,形成归因闭环

小结

GPU 可观测性的核心矛盾是「整卡视角的指标」与「多租户、多进程、多实例的归因需求」之间的错位。解决路径有三步:一是选对指标来源(DCGM + dcgm-exporter,白名单控制基数);二是读对语义(利用率是时间占比而非并行度,显存是整卡视角而非进程视角);三是挂对标签(Pod/Namespace/MIG 实例),让每一条指标都能回答「是谁」。故障侧则以 XID 与 ECC 为纲:不可纠正 ECC 与高危 XID 必须立即隔离硬件,不要"再观察"。最后,GPU 是最贵的资源,把利用率与成本、业务吞吐对齐,监控才从"看健康"升级为"看效益"。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「infra」更多文章

  1. 仪表盘设计与可读性工程:信息层级、图表选型与避免误读
  2. 日志采样、去重与降噪:从每天 TB 级日志里捞出信号
  3. OpenMetrics 与指标规范治理:暴露格式、命名单位与兼容性