文章

LLM GPU 故障自愈生产实战:用健康探针、节点隔离与请求排空降低推理中断

LLM 推理服务如何应对 GPU 异常?本文讲解 DCGM 健康检查、Kubernetes 四层探针设计、节点隔离、请求排空、CUDA OOM 分级恢复与灰度验证,帮助团队构建 GPU 故障自愈闭环,降低推理中断与重试风暴。

背景:LLM 推理服务最怕”半死不活”的 GPU

在普通 Web 服务中,进程挂了重启通常就能恢复。但 LLM 推理服务完全不同——一次请求可能占用大量显存,推理进程还持有模型权重、KV Cache、CUDA Context、NCCL 通信状态和运行时调度队列。

GPU 出问题时,服务未必立刻崩溃,常见表现反而更隐蔽:

  • 请求突然变慢、首 token 延迟拉高
  • 流式输出中断
  • 某张卡显存无法释放
  • 容器还活着但 CUDA 调用失败
  • 监控指标间歇性缺失

这类问题如果只靠”进程是否存活”来判断,极易误判——Pod 还在 Running/healthz 还能返回 200,但新请求已无法稳定执行。更糟的是,某次 CUDA OOM 被简单重启后,节点反复加入服务、反复失败,进一步放大排队和重试风暴。

生产级 GPU 自愈的目标不是”所有错误都自动重启”,而是建立清晰的闭环:

发现异常 → 停止接收新请求 → 排空进行中请求 → 隔离可疑节点 → 重建推理进程 → 验证恢复结果 → 保留故障证据

这条链路缺一环,故障就会从单卡问题扩散为集群级抖动。


核心原理:把健康状态拆成四层

第一层:应用健康

应用健康回答的是”推理服务进程是否还能正常接收请求”。典型信号包括:

  • HTTP health endpoint 是否可达
  • 模型是否完成加载
  • tokenizer、scheduler、worker 是否初始化完成
  • 队列是否已进入过载保护
  • 最近一段时间的请求错误率、超时率、取消率是否异常

这一层适合接入 Kubernetes readiness probe。readiness probe 的作用不是重启容器,而是决定 Pod 是否应继续接收 Service 流量。对 LLM Serving 而言,readiness 的语义应更严格:只要模型未完成加载、GPU worker 不可用、队列进入保护水位、节点正在排空,就应返回不 ready。

第二层:GPU 设备健康

GPU 设备健康回答的是”这张卡是否还能可信地执行计算”。不能只看利用率和显存占用,NVIDIA DCGM 的健康检查覆盖以下维度:

维度检查内容
PCIe带宽、重放计数、链路状态
MemoryECC 错误、pending page retirement、faulty memory
Thermal温度、降频事件
Power功耗异常、供电不足
NVLink链路错误、带宽下降
Driver驱动版本、设备可见性

DCGM 文档将以下情况列为故障条件:GPU fallen off bus、严重 XID、需要 reset、需要 reboot、pending page retirement、faulty memory。应用层在消费这些信号时应分级处理:

  • warning → 降权、观察、触发迁移
  • error → 排空、隔离
  • reset / reboot 类信号 → 不仅重启容器,还要把节点从调度池中摘出

第三层:Kubernetes 调度健康

调度健康回答的是”这个节点是否还应接收新的 GPU Pod”。NVIDIA Kubernetes device plugin 可以暴露 GPU 数量、跟踪健康状态,但官方文档明确指出:device plugin 本身不应被理解为完整的 GPU 故障自愈系统。

这意味着生产环境通常需要额外的 GPU node controller,它读取 DCGM、device plugin、Prometheus、Kubernetes event 和推理服务指标,在必要时对节点执行:

  • cordon —— 标记节点不可调度
  • taint —— 增加驱逐性污点
  • 摘除 endpoints
  • 触发 Pod 重建或通知人工介入

第四层:请求生命周期健康

请求生命周期健康回答的是”故障发生时,正在执行的请求如何收尾”。LLM 请求与普通 REST 调用不同,可能正在流式输出、已消耗大量 token、或挂在工具调用与长上下文 decode 阶段。

自愈逻辑必须区分三类请求:

  1. 已完成的请求:正常返回,记录节点处于可疑状态即可
  2. 正在输出的请求:优先完成或明确中断,不让连接长期悬挂
  3. 尚未开始执行的排队请求:尽快迁移或返回可重试错误

因此自愈流程必须包含 drain mode。实例进入 drain mode 后不再接收新请求,只处理已有请求,超过最大排空时间后强制关闭。


工程落地:一条可执行的 GPU 自愈链路

1. 定义健康状态机

不要只用 healthy=true/false。推荐将实例状态拆为以下几个阶段:

STARTING → WARMING → READY → DEGRADED → DRAINING → QUARANTINED → RECOVERING → READY
                                                                    ↘ FAILED
状态含义动作
STARTING容器启动中,模型未加载startup probe 等待
WARMING模型已加载,正在 warmup探针推理,不接生产流量
READY可接收生产流量readiness=true,正常调度
DEGRADED出现 warning 信号降权、限制新长请求、告警观察
DRAINING停止接收新请求,排空中readiness=false,负载均衡摘流量
QUARANTINED节点或实例被隔离cordon/taint,不参与调度
RECOVERING重启 worker 或重建 Pod 后验证探针推理 + 灰度恢复
FAILED自动恢复失败人工介入,保留故障证据

该状态机应同时驱动 readiness probe、负载均衡权重、告警级别和运维动作。

2. 把 readiness 与 liveness 分开

很多团队把 readiness 和 liveness 都指向同一个 /healthz,这是常见错误。LLM 推理服务应至少提供三个端点:

端点语义失败效果
/livez进程是否还活着重启容器
/readyz是否可以接收新请求摘流量
/gpuzGPU worker、CUDA context、DCGM 信号、显存状态是否可信触发 DRAINING

Kubernetes 配置示例:

readinessProbe:
  httpGet:
    path: /readyz
    port: 8080
  periodSeconds: 5
  failureThreshold: 2

livenessProbe:
  httpGet:
    path: /livez
    port: 8080
  initialDelaySeconds: 120
  periodSeconds: 10
  failureThreshold: 3

startupProbe:
  httpGet:
    path: /readyz
    port: 8080
  periodSeconds: 10
  failureThreshold: 60

关键点:模型加载和 warmup 可能很慢,需要 startupProbe 避免 kubelet 在模型未加载完成时误杀容器。readiness 用于流量摘除,不能等到进程崩溃才触发。

3. 用 DCGM 信号触发节点隔离

DCGM 的健康结果分三类处理:

groups:
  - name: llm-gpu-health
    rules:
      - alert: LlmGpuMetricsMissing
        expr: absent_over_time(DCGM_FI_DEV_GPU_UTIL[3m])
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "GPU metrics disappeared for a serving node"

      - alert: LlmGpuXidError
        expr: increase(DCGM_FI_DEV_XID_ERRORS[5m]) > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "GPU reported XID errors; drain and quarantine the node"

重点不是照抄规则,而是把握两个思路:一是监控明确硬件错误(XID、ECC、NVLink),二是监控结构性异常——例如指标突然消失、scrape 延迟、设备数量变化。

4. 让请求排空成为一等能力

GPU 故障自愈最容易做错的就是直接重启,这会带来三类副作用:

  • 已经开始输出的流式请求被突然断开
  • 客户端重试将流量打到其他实例,造成二次峰值
  • 故障现场被清理,后续无法定位根因

更稳妥的排空流程:

Controller 标记实例 DRAINING
    → readiness probe 返回失败,负载均衡摘除新流量
    → Gateway 停止分配新请求到该实例
    → 实例继续处理已进入 decode 的请求
    → 排队但未执行的请求迁移或返回可重试错误
    → 超过 max_drain_seconds 后强制终止
    → 保存最后错误、GPU 指标窗口、请求样本和容器事件
    → 重建 worker 或隔离节点

Pod 配置方向:

terminationGracePeriodSeconds: 180
lifecycle:
  preStop:
    exec:
      command:
        - /bin/sh
        - -c
        - "curl -X POST localhost:8080/admin/drain && sleep 30"

terminationGracePeriodSeconds 必须大于最大可接受排空时间,否则 kubelet 会在请求未收尾时强杀进程。生产环境还需让 Gateway 和 Controller 感知实例进入 drain mode,而非仅在容器内 sleep。

5. 区分 CUDA OOM 的根因

CUDA OOM 不等于 GPU 坏了,至少要分四种情况:

OOM 类型典型触发推荐动作
单请求超限prompt 或 max_tokens 过大拒绝请求或降低 max_tokens
批量过载scheduler 同时放入太多 token降低并发、降低 batch token 上限
显存碎片化长时间运行后可用显存不连续drain 后重建 worker
驱动/设备异常OOM 伴随 XID、CUDA context 错误隔离节点并做 GPU 健康检查

如果把所有 OOM 都交给 liveness probe 重启,会掩盖真正问题。正确做法是记录每次 OOM 的上下文——输入 token、输出 token、batch size、KV cache 占用、GPU ID、模型版本、adapter 版本和最近 DCGM 信号——再做分类处理。

6. 设计节点隔离和恢复策略

节点隔离不应只依赖删除 Pod,建议分三层:

层级范围适用场景
实例隔离单个推理进程进入 DRAINING单请求异常、队列过载
Pod 隔离Pod 重建,节点仍可调度其他 GPU Pod显存碎片化、进程泄漏、worker 死锁
节点隔离cordon + taint,禁止新 GPU 工作负载fallen off bus、严重 XID、NVLink/驱动级故障

恢复时不要立即全量放回。应采用灰度恢复:先做探针推理 → 接收少量短请求 → 观察错误率、TTFT、TPOT、显存释放和 DCGM 信号 → 满足稳定窗口后恢复完整权重。


适用场景

这套方法适合以下场景:

  • 自建 vLLM、TensorRT-LLM、TGI、SGLang 或 Ray Serve LLM 推理集群
  • Kubernetes 上运行多副本 GPU 推理服务
  • 存在长输出、流式输出、多租户请求或高并发队列
  • GPU 节点数量较少,单节点故障会明显影响可用性
  • 需要对推理中断、超时、重试风暴和节点雪崩做工程治理

如果是开发测试环境或单机离线推理,可简化为:健康检查、OOM 日志、手动重启和基础告警。但只要服务面向用户,就应至少实现 readiness、drain 和故障证据保留。


常见误区

误区一:只看 GPU 利用率

GPU 利用率正常不代表节点健康。驱动错误、设备丢失、指标缺口、PCIe/NVLink 错误、显存不可释放,都可能先于或独立于利用率变化出现。生产系统需要同时监控数值指标、结构性指标和请求结果。

误区二:所有错误都自动重启

自动重启适合进程级故障,不适合所有 GPU 故障。需要 reset 或 reboot 的 GPU 问题,容器重启通常不够;如果继续调度新 Pod 到该节点,只会反复失败。

误区三:排空只靠 Kubernetes 删除 Pod

Kubernetes 的 graceful termination 提供基础终止流程,但并不知道 LLM 请求是否仍在 decode、是否正在流式输出、是否可以迁移。排空必须由 Gateway、Serving Runtime 和 Controller 协同完成。

误区四:故障恢复后立即满流量

GPU 故障恢复后应先做 canary。尤其是驱动重置、节点重启、模型重新加载后,应先验证探针推理、短请求、长请求和并发请求,而不是直接恢复所有流量。


上线检查清单

健康探针

  • /livez/readyz/gpuz 是否语义分离
  • startupProbe 是否覆盖模型加载和 warmup 时间
  • readinessProbe 是否会在 drain、过载、GPU worker 异常时失败
  • livenessProbe 是否避免误杀长时间 warmup 的实例

GPU 监控

  • 是否接入 DCGM 或等价 GPU 健康指标
  • 是否监控 XID、fallen off bus、reset required、pending page retirement
  • 是否监控指标缺失、scrape 延迟、GPU 设备数量变化
  • 是否把 warning、error、fatal 映射为不同动作

请求排空

  • Gateway 是否支持实例级摘流量
  • 排队请求是否可迁移或快速失败
  • 流式请求是否能明确结束或返回可解释错误
  • terminationGracePeriodSeconds 是否大于最大排空窗口

节点隔离

  • 是否支持 cordon/taint 可疑 GPU 节点
  • 是否区分 Pod 重建、GPU reset、节点 reboot、人工维修
  • 是否有恢复前探针推理和恢复后稳定窗口
  • 是否保留最后一段指标、日志、Kubernetes event 和请求样本

参考资料

常见问题

GPU 故障自愈是不是只要配置 liveness probe 就够了?
不够。liveness probe 只能触发容器重启,无法判断 GPU 是否需要排空、隔离、重置或人工维修。生产系统必须把应用探针、GPU 健康信号、节点调度状态与请求排空串成完整闭环。
CUDA OOM 是否应该一律重启推理服务?
不应该。单请求超长上下文导致的 OOM、批量调度过载导致的 OOM、显存碎片化导致的 OOM、驱动异常导致的 OOM,处理方式完全不同。应先分级,再决定拒绝请求、降并发、重建 worker 还是隔离节点。
为什么 GPU 节点还在上报利用率,却仍然可能不可用?
GPU 故障不一定表现为单个指标越限。设备消失、指标缺口、scrape 延迟、驱动异常或请求超时都可能在利用率正常时发生。因此必须同时监控数值指标和结构性信号。