为什么 LLM 需要专门的 K8s 部署方案

LLM 推理服务与传统 Web 服务有本质区别:

  • 显存约束:7B 模型需要 ~14GB 显存,70B 模型需要 ~140GB 显存,且显存是最大的资源瓶颈
  • 冷启动慢:模型加载到 GPU 需要 30-120 秒
  • 请求特性:单次请求可能持续 30-300 秒(流式输出),与 HTTP 常规超时机制冲突
  • 异构硬件:不同模型需要不同 GPU 类型(推理用 T4/A10,训练用 A100/H100)

这些特性决定了标准 K8s 部署方式(HPA + 滚动更新)并不适用。

整体架构

┌─────────────────────────────────────────────────────┐
│                   Ingress / Gateway                 │
├─────────────────────────────────────────────────────┤
│              LLM Gateway (路由/限流)                │
│         (LiteLLM / APISIX / Higress)               │
├──────────────┬──────────────┬──────────────────────┤
│  GPU Pool 1  │  GPU Pool 2  │  CPU Pool (兜底)     │
│  (7B Models) │ (70B Models) │  (小模型/重写)       │
│  T4/A10×N    │  A100×N     │  gpt-4o-mini proxy  │
├──────────────┴──────────────┴──────────────────────┤
│            GPU Operator + NVIDIA Device Plugin       │
├─────────────────────────────────────────────────────┤
│              K8s Control Plane                      │
└─────────────────────────────────────────────────────┘

GPU 节点池配置

节点池规划

池名称GPU 型号显存用途节点数单节点副本数
gpu-smallT4 (16GB)16GB7B 以下模型32
gpu-mediumA10 (24GB)24GB7B-14B 模型21
gpu-largeA100 (80GB)80GB70B 模型21
cpu-fallback-预处理/重写510

GPU 节点 Label 与 Taint 配置

# GPU 节点打标签
apiVersion: v1
kind: Node
metadata:
  name: gpu-node-1
  labels:
    accelerator: nvidia-t4
    gpu.memory: "16Gi"
    node.kubernetes.io/instance-type: "g4dn.xlarge"
    pool: gpu-small
spec: {}

---

# 专用 GPU 节点设置 Taint(防止非 GPU Pod 调度上去)
apiVersion: v1
kind: Node
metadata:
  name: gpu-node-1
spec:
  taints:
    - key: nvidia.com/gpu
      value: "true"
      effect: NoSchedule
    - key: pool
      value: gpu-small
      effect: NoSchedule

NVIDIA GPU Operator 部署

# 安装 NVIDIA GPU Operator
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

helm install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator --create-namespace \
  --set driver.enabled=true \
  --set toolkit.enabled=true \
  --set devicePlugin.enabled=true \
  --set dcgmExporter.enabled=true \
  --set nodeStatusExporter.enabled=true

# 验证 GPU 可用
kubectl get nodes -o wide
kubectl describe node gpu-node-1 | grep nvidia.com/gpu

模型服务部署

vLLM 推理服务部署

vLLM 是目前性能最好的开源 LLM 推理框架,支持 PagedAttention、连续批处理和 Tensor Parallel。

# vllm-7b-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen-7b
  namespace: llm-prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm-qwen-7b
  template:
    metadata:
      labels:
        app: vllm-qwen-7b
    spec:
      # 调度到 T4 节点
      nodeSelector:
        pool: gpu-small
        accelerator: nvidia-t4
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model"
            - "Qwen/Qwen2.5-7B-Instruct"
            - "--tensor-parallel-size"
            - "1"
            - "--gpu-memory-utilization"
            - "0.90"
            - "--max-model-len"
            - "8192"
            - "--enable-prefix-caching"  # 启用前缀缓存(共享 prompt)
            - "--disable-log-requests"
          ports:
            - containerPort: 8000
              name: http
          env:
            - name: CUDA_VISIBLE_DEVICES
              value: "0"
            - name: NCCL_DEBUG
              value: "WARN"
          resources:
            requests:
              nvidia.com/gpu: "1"        # 请求 1 个 GPU
              memory: "32Gi"
              cpu: "8"
            limits:
              nvidia.com/gpu: "1"
              memory: "32Gi"
              cpu: "8"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120      # 模型加载需要时间
            periodSeconds: 30
            timeoutSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 30
            periodSeconds: 10
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 30"]  # 优雅退出
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-qwen-7b-svc
  namespace: llm-prod
spec:
  selector:
    app: vllm-qwen-7b
  ports:
    - port: 8000
      targetPort: 8000
      name: http
  type: ClusterIP

多 GPU 推理(Tensor Parallel)

对于 70B 级别的模型,单张 GPU 显存不足,需要多 GPU 并行推理:

# vllm-70b-deployment.yaml - 多 GPU 部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen-72b
  namespace: llm-prod
spec:
  replicas: 1  # 单副本占用 4 张 GPU
  selector:
    matchLabels:
      app: vllm-qwen-72b
  template:
    spec:
      nodeSelector:
        pool: gpu-large
        accelerator: nvidia-a100
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model"
            - "Qwen/Qwen2.5-72B-Instruct"
            - "--tensor-parallel-size"
            - "4"                    # 使用 4 张 GPU
            - "--pipeline-parallel-size"
            - "1"
            - "--gpu-memory-utilization"
            - "0.95"
            - "--max-model-len"
            - "32768"
            - "--enable-chunked-prefill"  # 分块预填充(降低首 token 延迟)
          env:
            - name: CUDA_VISIBLE_DEVICES
              value: "0,1,2,3"     # 使用 4 张 GPU
          resources:
            requests:
              nvidia.com/gpu: "4"   # 请求 4 个 GPU
              memory: "256Gi"
              cpu: "32"
            limits:
              nvidia.com/gpu: "4"
              memory: "256Gi"
              cpu: "32"

自动扩缩容方案

问题:HPA 不适用于 LLM

标准 HPA 基于 CPU/内存扩缩容,但 LLM 服务的特点是:

  • GPU 利用率低但显存已满(无法再调度新 Pod)
  • 请求排队中但 CPU 利用率低
  • 冷启动耗时 30-120 秒,无法快速响应流量

解决方案:自定义 Metrics + 队列感知 HPA

# k8s-prometheus-adapter 配置:暴露自定义指标
apiVersion: v1
kind: ConfigMap
metadata:
  name: adapter-config
  namespace: monitoring
data:
  config.yaml: |
    rules:
      - seriesQuery: 'vllm:num_requests_running{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: "namespace"}
            pod: {resource: "pod"}
        name:
          matches: "vllm:num_requests_running"
          as: "llm_requests_running"
        metricsQuery: "avg_over_time(vllm:num_requests_running{<<.LabelMatchers>>}[2m])"
      
      - seriesQuery: 'vllm:gpu_cache_usage{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: "namespace"}
            pod: {resource: "pod"}
        name:
          matches: "vllm:gpu_cache_usage"
          as: "llm_gpu_cache_usage"
        metricsQuery: "avg_over_time(vllm:gpu_cache_usage{<<.LabelMatchers>>}[2m])"
---
# 自定义 HPA:基于排队请求数扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vllm-qwen-7b-hpa
  namespace: llm-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-qwen-7b
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - type: Pods
      pods:
        metric:
          name: llm_requests_running
        target:
          type: AverageValue
          averageValue: "8"           # 每 Pod 平均 8 个并发请求
    - type: Pods
      pods:
        metric:
          name: llm_gpu_cache_usage
        target:
          type: AverageValue
          averageValue: "0.85"       # GPU KV Cache 使用率不超过 85%
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 100                  # 扩容时直接翻倍
          periodSeconds: 30
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容前稳定 5 分钟
      policies:
        - type: Percent
          value: 25                   # 缓慢缩容
          periodSeconds: 60

预热(Warm Up)机制

# 使用 Init Container 预热模型
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen-7b
spec:
  template:
    spec:
      initContainers:
        - name: warmup
          image: vllm/vllm-openai:latest
          command:
            - /bin/sh
            - -c
            - |
              python -c "
              import requests, time, json
              # 发送预热请求
              for prompt in ['你好', '介绍一下自己', '今天天气怎么样']:
                  try:
                      resp = requests.post('http://localhost:8000/v1/chat/completions', 
                          json={'model': 'Qwen/Qwen2.5-7B-Instruct', 'messages': [{'role': 'user', 'content': prompt}], 'max_tokens': 10})
                      print(f'Warmup: {resp.status_code}')
                  except: pass
                  time.sleep(1)
              print('Warmup complete')
              "
          env:
            - name: VLLM_HOST
              value: "0.0.0.0"
      containers:
        - name: vllm
          # 主容器配置...

推理优化

vLLM 关键参数调优

参数说明推荐值影响
--gpu-memory-utilizationGPU 显存使用率0.85-0.95高 → 更多并发请求
--max-model-len最大上下文长度8192高 → 显存占用增加
--tensor-parallel-size张量并行度1/2/4/8多 GPU 推理必需
--enable-prefix-caching前缀缓存True共享 prompt 命中率提升
--enable-chunked-prefill分块预填充True降低首 token 延迟
--max-num-seqs最大并发序列数256高 → 吞吐提升但延迟增加
--max-num-batched-tokens批处理最大 Token 数8192控制批处理大小

连续批处理(Continuous Batching)

# vLLM 自动支持连续批处理,无需额外配置
# 以下是效果对比:

"""
静态批处理(传统方式):
  Batch 1: [Req1, Req2, Req3, Req4] — 等待所有请求完成
  → Req1 先完成,但 GPU 空闲等待 Req2/3/4

连续批处理(vLLM):
  Step 1: [Req1, Req2, Req3, Req4] — 推理
  Step 2: [Req2, Req3, Req4, Req5] — Req1 完成,Req5 加入
  Step 3: [Req2, Req3, Req5, Req6] — Req4 完成,Req6 加入
  → GPU 利用率始终接近 100%
"""

KV Cache 量化

# 启用 FP8/INT8 KV Cache 量化(节省显存)
args:
  - "--model"
  - "Qwen/Qwen2.5-7B-Instruct"
  - "--kv-cache-dtype"
  - "fp8"                    # FP8 量化 KV Cache
  - "--quantization"
  - "awq"                    # 权重 INT4 量化(AWQ)
  - "--max-model-len"
  - "16384"                  # 量化后支持更长上下文

流量路由与负载均衡

LiteLLM 网关部署

apiVersion: apps/v1
kind: Deployment
metadata:
  name: litellm-gateway
  namespace: llm-prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: litellm-gateway
  template:
    spec:
      containers:
        - name: litellm
          image: ghcr.io/berriai/litellm:main-stable
          ports:
            - containerPort: 8000
          env:
            - name: LITELLM_MASTER_KEY
              valueFrom:
                secretKeyRef:
                  name: litellm-secrets
                  key: master-key
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: litellm-secrets
                  key: db-url
          volumeMounts:
            - name: config
              mountPath: /app/config.yaml
              subPath: config.yaml
      volumes:
        - name: config
          configMap:
            name: litellm-config
---
# LiteLLM 配置:路由到不同后端
apiVersion: v1
kind: ConfigMap
metadata:
  name: litellm-config
  namespace: llm-prod
data:
  config.yaml: |
    model_list:
      - model_name: qwen-7b
        litellm_params:
          model: openai/Qwen/Qwen2.5-7B-Instruct
          api_base: http://vllm-qwen-7b-svc:8000/v1
      
      - model_name: qwen-72b
        litellm_params:
          model: openai/Qwen/Qwen2.5-72B-Instruct
          api_base: http://vllm-qwen-72b-svc:8000/v1
      
      - model_name: gpt-4o-mini
        litellm_params:
          model: openai/gpt-4o-mini
          api_key: os.environ/OPENAI_API_KEY
    
    router_settings:
      routing_strategy: least-busy      # 最少忙碌路由
      num_retries: 2
      timeout: 300
      fallbacks:                        # 兜底策略
        - qwen-72b -> gpt-4o-mini      # 72B 失败时降级到 gpt-4o-mini

监控与可观测性

Prometheus 指标配置

# vLLM 内置 Prometheus 指标,通过 /metrics 暴露
apiVersion: v1
kind: ServiceMonitor
metadata:
  name: vllm-metrics
  namespace: llm-prod
  labels:
    app: vllm-qwen-7b
spec:
  selector:
    matchLabels:
      app: vllm-qwen-7b
  endpoints:
    - port: http
      path: /metrics
      interval: 15s
      scrapeTimeout: 10s

关键监控指标

指标PromQL 查询告警阈值说明
请求成功率rate(vllm:num_requests_finished{status="success"}[5m])<0.95成功率低于 95%
首 Token 延迟 P95histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m]))>2s首 Token 延迟
Token 生成速率rate(vllm:num_tokens_generated_total[1m])-吞吐量
GPU 利用率DCGM_FI_DEV_GPU_UTIL<50%GPU 利用率过低
GPU 显存使用率DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL>0.95显存不足
排队请求数vllm:num_requests_waiting>10排队过长
KV Cache 命中率vllm:gpu_cache_usage-前缀缓存效果

Grafana 告警规则

apiVersion: v1
kind: ConfigMap
metadata:
  name: llm-alerts
  namespace: monitoring
data:
  llm-alerts.yaml: |
    groups:
      - name: llm_alerts
        rules:
          - alert: LLMHighLatency
            expr: histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m])) > 2
            for: 5m
            labels:
              severity: warning
            annotations:
              summary: "LLM 首 Token 延迟过高 (P95 > 2s)"
              description: "Pod {{ $labels.pod }} 的 P95 首 Token 延迟为 {{ $value }}s"
          
          - alert: LLMQueueBacklog
            expr: vllm:num_requests_waiting > 20
            for: 2m
            labels:
              severity: critical
            annotations:
              summary: "LLM 请求排队过多"
              description: "等待队列中有 {{ $value }} 个请求"
          
          - alert: GPUOutOfMemory
            expr: DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL > 0.98
            for: 1m
            labels:
              severity: critical
            annotations:
              summary: "GPU 显存即将耗尽"

成本优化:Spot 实例 + 断点续训

# 使用 Spot 实例运行非关键推理负载
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen-7b-spot
  namespace: llm-prod
spec:
  replicas: 4
  template:
    spec:
      # Spot 实例容忍
      tolerations:
        - key: "spot"
          operator: "Equal"
          value: "true"
          effect: "NoSchedule"
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: cloud.google.com/machine-family
                    operator: In
                    values:
                      - "g4dn"          # 优先调度到 GPU Spot 节点
      containers:
        - name: vllm
          # 配置...

部署检查清单

□ GPU Operator 正常运行
□ 节点 GPU 资源可被调度 (kubectl describe node | grep nvidia.com/gpu)
□ 模型服务 Pod 成功启动 (kubectl get pods -n llm-prod)
□ 健康检查通过 (kubectl describe pod)
□ 自定义指标暴露 (curl POD_IP:8000/metrics | grep vllm)
□ HPA 正常工作 (kubectl get hpa)
□ 负载测试验证 (wrk / hey / vegetable)
□ 监控面板正常显示
□ 告警规则验证
□ 优雅退出测试 (kubectl delete pod 观察 30s  grace period)

结语

LLM 在 K8s 上的部署是一个系统工程,涉及硬件调度、推理优化、流量管理和监控告警多个层面。核心要点:

  1. GPU 调度:使用 Node Label + Taint 隔离不同 GPU 池
  2. 推理框架:vLLM 是目前最优选择,支持连续批处理和 PagedAttention
  3. 扩缩容:基于自定义指标(排队请求数、KV Cache 使用率)而非 CPU
  4. 优化:前缀缓存、分块预填充、量化是三大核心优化手段
  5. 监控:GPU 指标 + LLM 特有指标缺一不可

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。