为什么 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-small | T4 (16GB) | 16GB | 7B 以下模型 | 3 | 2 |
| gpu-medium | A10 (24GB) | 24GB | 7B-14B 模型 | 2 | 1 |
| gpu-large | A100 (80GB) | 80GB | 70B 模型 | 2 | 1 |
| cpu-fallback | 无 | - | 预处理/重写 | 5 | 10 |
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-utilization | GPU 显存使用率 | 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 延迟 P95 | histogram_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 上的部署是一个系统工程,涉及硬件调度、推理优化、流量管理和监控告警多个层面。核心要点:
- GPU 调度:使用 Node Label + Taint 隔离不同 GPU 池
- 推理框架:vLLM 是目前最优选择,支持连续批处理和 PagedAttention
- 扩缩容:基于自定义指标(排队请求数、KV Cache 使用率)而非 CPU
- 优化:前缀缓存、分块预填充、量化是三大核心优化手段
- 监控:GPU 指标 + LLM 特有指标缺一不可
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
