GPU 调度与弹性伸缩:K8s 上部署大模型推理服务的实战陷阱

一、GPU 利用率 15% 的尴尬:大模型推理上云的真实困境

将大模型推理服务部署到 Kubernetes 集群,听起来是标准的云原生操作。实际落地后,却发现 GPU 利用率长期徘徊在 15%-20%,单张 A10 卡只能服务 2-3 路并发,而推理延迟 P99 已经飙到 8 秒。更头疼的是,流量高峰时 HPA 触发扩容,新 Pod 因为 GPU 资源不足而 Pending,流量低谷时缩容又把正在处理请求的 Pod 杀掉,用户端直接 502。

这些问题的根源在于:K8s 原生的调度器和 HPA 是为 CPU 密集型无状态服务设计的,对 GPU 这种昂贵的、不可超卖的资源缺乏精细化调度能力。大模型推理服务既有 GPU 资源独占的需求,又有流量波动带来的弹性伸缩需求,两者之间存在根本性冲突。

本文从 GPU 资源模型、调度策略、弹性伸缩三个维度,拆解 K8s 上部署 AI 推理服务的工程实践。

二、K8s GPU 调度模型与推理服务生命周期

K8s 通过 Device Plugin 机制将 GPU 资源暴露为可调度的扩展资源(如 nvidia.com/gpu: 1)。调度器在 Pod 分配阶段检查节点上可用的 GPU 数量,但调度完成后,GPU 资源就被独占,直到 Pod 删除。

flowchart TD
    A[用户请求到达 Ingress] --> B[推理服务 Pod 接收请求]
    B --> C{GPU 显存是否充足?}
    C -->|是| D[加载模型到 GPU / 复用已加载模型]
    D --> E[执行推理计算]
    E --> F[返回结果]
    C -->|否| G[请求排队等待 / 返回 503]
    G --> H[HPA 检测到排队延迟上升]
    H --> I{集群有空闲 GPU 节点?}
    I -->|是| J[调度新 Pod 到 GPU 节点]
    I -->|否| K[Pod 处于 Pending 状态]
    K --> L[Cluster Autoscaler 扩容节点]
    L --> M[新节点就绪后 Pod 启动]
    M --> J
    J --> N[新 Pod 模型加载 冷启动 30-120s]
    N --> D

    style K fill:#ff6b6b,color:#fff
    style G fill:#ffa94d,color:#fff
    style N fill:#ffd43b,color:#333

上图揭示了三个关键瓶颈:

  1. GPU 独占与碎片化:一个 Pod 声明 nvidia.com/gpu: 1 就独占整张卡,即使只用了 30% 显存,剩余 70% 也无法被其他 Pod 使用。
  2. 冷启动延迟:新 Pod 启动后需要将模型权重从磁盘加载到 GPU 显存,大模型(7B/13B)的加载时间可达 30-120 秒,期间无法处理任何请求。
  3. 缩容破坏性:HPA 缩容时直接删除 Pod,不会等待推理请求完成,导致正在处理的请求被中断。

三、生产级 GPU 推理服务部署方案

3.1 多实例 GPU 分割与时间片复用

# 使用 NVIDIA MIG 将 A100 切分为多个实例,提升 GPU 利用率
# 适用于推理延迟敏感度较低、并发量较高的场景
apiVersion: v1
kind: ConfigMap
metadata:
  name: mig-partition-config
data:
  partition.yaml: |
    # A100 40GB 切分为 2 个 20GB 实例——每实例可独立运行一个 7B 模型
    # 设计意图:单卡双实例,将 GPU 利用率从 15% 提升到 50%+
    version: v1
    mig-configs:
      all-2g.20gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            2g.20gb: 2
---
# Pod 声明使用 MIG 实例而非整卡
apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-inference-mig
spec:
  replicas: 2
  selector:
    matchLabels:
      app: llm-inference
  template:
    metadata:
      labels:
        app: llm-inference
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.6.0
          resources:
            limits:
              # 声明 MIG 实例而非整卡——避免独占整张 GPU
              nvidia.com/mig-2g.20gb: 1
            requests:
              nvidia.com/mig-2g.20gb: 1
          env:
            # vLLM 的 PagedAttention 机制——减少显存碎片,提升并发能力
            - name: VLLM_GPU_MEMORY_UTILIZATION
              value: "0.85"

3.2 基于推理队列深度的 HPA 策略

# 自定义 HPA 指标——基于推理队列深度而非 CPU 利用率
# 原因:GPU 推理服务 CPU 利用率极低,无法反映真实负载
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-inference-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llm-inference
  minReplicas: 2
  maxReplicas: 8
  metrics:
    # 核心指标:推理请求队列深度
    # 队列深度 > 5 说明当前 Pod 已无法及时处理请求,需要扩容
    - type: Pods
      pods:
        metric:
          name: vllm_num_requests_waiting
        target:
          type: AverageValue
          averageValue: "5"
  behavior:
    scaleDown:
      # 缩容稳定窗口设为 5 分钟——避免流量波动导致频繁缩容
      stabilizationWindowSeconds: 300
      # 每次最多缩 1 个 Pod——减少缩容对在线请求的冲击
      policies:
        - type: Pods
          value: 1
          periodSeconds: 60
    scaleUp:
      # 扩容可以更激进——推理请求堆积时需要快速增加处理能力
      policies:
        - type: Pods
          value: 2
          periodSeconds: 30

3.3 优雅缩容:保护推理中的请求

"""
推理服务优雅退出处理器——确保缩容时不中断正在处理的请求
设计意图:K8s 发送 SIGTERM 后,给服务一个宽限期完成推理
"""
import signal
import sys
import time
from threading import Event

# 全局标志:收到终止信号后,拒绝新请求但继续处理已有请求
shutting_down = Event()

def graceful_shutdown(signum, frame):
    """收到 SIGTERM 后的优雅退出逻辑"""
    shutting_down.set()
    # 等待所有进行中的推理请求完成
    # 超时后强制退出,避免 Pod 被 SIGKILL
    timeout = 120  # 大模型推理可能需要较长时间
    start = time.monotonic()
    while active_requests_count() > 0:
        if time.monotonic() - start > timeout:
            print(f"[WARN] 优雅退出超时,仍有 {active_requests_count()} 个请求未完成")
            break
        time.sleep(1)
    sys.exit(0)

def active_requests_count():
    """返回当前正在处理的推理请求数量"""
    # 实际实现中从推理引擎获取
    return _inference_engine.pending_count

signal.signal(signal.SIGTERM, graceful_shutdown)

四、GPU 推理上云的架构权衡

4.1 MIG 分割 vs. 时间片复用 vs. 整卡独占

方案 GPU 利用率 推理延迟 适用场景
整卡独占 低(15%-30%) 最低 延迟敏感的在线推理
MIG 硬分割 中(40%-60%) 多模型同卡部署
MPS 时间片 高(60%-80%) 不稳定 离线批处理、非实时推理
vLLM Continuous Batching 高(70%-90%) 高并发在线推理(推荐)

4.2 HPA 扩容的冷启动陷阱

扩容新 Pod 后,模型加载需要 30-120 秒。在此期间,新 Pod 无法处理请求,流量全部压在已有 Pod 上。如果已有 Pod 也已满载,用户请求会持续超时。

缓解方案:

  • 预热池:维持 1-2 个已加载模型的备用 Pod,HPA 扩容时直接从预热池取 Pod,跳过冷启动。
  • 预测性扩容:基于历史流量模式提前扩容,而非等队列堆积后再响应。
  • 模型权重预加载:使用 initContainer 将模型权重从对象存储下载到本地 SSD,减少 Pod 启动后的加载时间。

4.3 禁用场景

  • 超大规模模型(70B+):单卡显存不足,需要张量并行,MIG 和时间片方案均不适用。
  • 严格延迟要求(P99 < 200ms):任何形式的资源复用都会引入延迟抖动,必须整卡独占。
  • GPU 节点池不足:如果集群只有 2-3 张 GPU 卡,弹性伸缩没有意义,固定副本数更稳定。

五、总结

大模型推理服务上 K8s,核心矛盾是 GPU 资源的独占性与流量的弹性需求之间的冲突。解决思路:

  1. 提升单卡利用率:优先使用 vLLM 的 Continuous Batching 机制,配合 PagedAttention 减少显存碎片,单卡并发能力可提升 3-5 倍。
  2. 精细化 HPA 策略:基于推理队列深度而非 CPU 利用率触发扩缩容,缩容时设置稳定窗口和优雅退出,避免中断在线请求。
  3. 冷启动治理:通过预热池或预测性扩容,将扩容响应时间从分钟级压缩到秒级。
  4. GPU 资源模型选择:根据延迟要求和并发量,在 MIG 分割、时间片复用、整卡独占之间做取舍,不要一刀切。

落地路线:先用 vLLM + Continuous Batching 验证单卡并发能力;再配置基于队列深度的 HPA;最后引入预热池解决冷启动问题。每一步都用压测数据验证效果,避免过度设计。

Logo

免费领 200 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐