vLLM + K8s:大模型推理服务的弹性部署与GPU调度方案
一、概述
1.1 背景介绍
大模型推理服务在生产环境面临四个核心挑战:
GPU 显存管理:7B 模型 FP16 推理需要约 14GB 显存,70B 模型需要 140GB+,KV Cache 随并发数线性增长,显存碎片化导致实际利用率不足 60%
高并发低延迟:在线服务要求 P99 延迟可控,传统静态批处理在请求长度差异大时效率低下
弹性伸缩:GPU 资源昂贵(A100 单卡约 $2/h),流量波谷时需要快速缩容降本
多模型管理:生产环境通常同时运行多个模型版本,需要灰度发布和流量切分能力
vLLM 是 UC Berkeley 开源的高性能 LLM 推理引擎,通过三项核心技术解决上述问题:
PagedAttention:借鉴操作系统虚拟内存分页思想,将 KV Cache 拆分为固定大小的 Block,按需分配和回收,显存利用率从 ~50% 提升到 ~95%,同等显存下并发吞吐提升 2-4 倍
Continuous Batching:请求完成后立即释放资源并插入新请求,无需等待整个 Batch 完成,相比静态批处理延迟降低 30-50%
Tensor Parallelism:支持单节点多卡张量并行,大模型可跨多张 GPU 分片推理
1.2 推理框架对比
| 特性 | vLLM | TGI | TensorRT-LLM | Ollama | llama.cpp |
|---|---|---|---|---|---|
| 吞吐性能 | 高(PagedAttention) | 中高 | 最高(深度优化) | 低 | 低-中 |
| 首 Token 延迟 | 低 | 低 | 最低 | 中 | 中 |
| GPU 利用率 | 95%+ | 80-90% | 95%+ | 60-70% | 50-70% |
| 易用性 | 高(OpenAI 兼容) | 高 | 低(需编译引擎) | 最高 | 高 |
| K8s 集成 | 原生支持 | 原生支持 | 需 Triton 封装 | 弱 | 弱 |
| 量化支持 | AWQ/GPTQ/FP8 | GPTQ/AWQ | FP8/INT8/INT4 | GGUF | GGUF |
| 多卡并行 | Tensor Parallel | Tensor Parallel | Tensor+Pipeline | 不支持 | 部分支持 |
| 适用场景 | 在线推理服务 | 在线推理服务 | 极致性能场景 | 本地开发测试 | 边缘/CPU 推理 |
生产环境推荐 vLLM:性能与易用性平衡最好,OpenAI 兼容 API 降低业务接入成本,K8s 部署成熟。
1.3 GPU 调度基础
K8s 通过 NVIDIA Device Plugin 实现 GPU 资源管理:
资源声明:Pod 通过nvidia.com/gpu: 1请求 GPU,调度器自动分配
GPU Operator:自动管理 NVIDIA Driver、Container Toolkit、Device Plugin 的生命周期
MIG(Multi-Instance GPU):A100/H100 支持将单卡切分为最多 7 个独立实例,每个实例有独立的显存和计算单元
GPU 时间片共享:多个 Pod 分时复用同一张 GPU,适合开发测试环境
1.4 适用场景
在线推理服务:面向用户的实时对话、文本生成,要求低延迟高可用
批量推理:离线数据处理、文档摘要生成,追求高吞吐
多模型服务:同一集群部署多个模型,按业务需求路由
A/B 测试:模型版本灰度发布,基于流量比例切分
1.5 环境要求
| 组件 | 版本要求 | 说明 |
|---|---|---|
| vLLM | 0.6.x | 推理引擎,需与 CUDA 版本匹配 |
| CUDA | 12.1+ | 推荐 12.4,vLLM 0.6.x 官方测试版本 |
| NVIDIA Driver | 550+ | 需支持 CUDA 12.x |
| Kubernetes | 1.31+ | 需启用 DevicePlugin 特性门控 |
| GPU Operator | 24.6+ | 自动管理 GPU 软件栈 |
| 容器运行时 | containerd 1.7+ | 需配置 nvidia-container-runtime |
| GPU 硬件 | A100/H100/L40S/A10 | 推荐 Ampere 架构及以上 |
二、详细步骤
2.1 GPU 节点准备
2.1.1 NVIDIA Driver 安装验证
# 检查 GPU 硬件识别 lspci | grep -i nvidia # 检查驱动版本(需 550+) nvidia-smi # 检查 CUDA 版本 nvcc --version
2.1.2 NVIDIA GPU Operator 部署
GPU Operator 统一管理 Driver、Container Toolkit、Device Plugin,避免手动维护各组件版本兼容性。
# 添加 NVIDIA Helm 仓库 helm repo add nvidia https://helm.ngc.nvidia.com/nvidia helm repo update # 部署 GPU Operator(如果节点已安装驱动,设置 driver.enabled=false) helm install gpu-operator nvidia/gpu-operator --namespace gpu-operator --create-namespace --setdriver.enabled=false --settoolkit.enabled=true --setdevicePlugin.enabled=true --setmigManager.enabled=true --setdcgmExporter.enabled=true --version v24.6.2 # 等待所有组件就绪 kubectl -n gpu-operator get pods -w
2.1.3 GPU 资源验证
# 确认节点已注册 GPU 资源 kubectl describe node| grep -A 5"Capacity" # 预期输出:nvidia.com/gpu: 8(以 8 卡节点为例) # 运行 GPU 测试 Pod kubectl run gpu-test --rm -it --restart=Never --image=nvidia/cuda:12.4.0-base-ubuntu22.04 --limits=nvidia.com/gpu=1 -- nvidia-smi
2.1.4 MIG 配置(可选,A100/H100)
# 查看 MIG 支持状态 nvidia-smi mig -lgip # 启用 MIG 模式(需重启 GPU) sudo nvidia-smi -i 0 -mig 1 # 创建 MIG 实例(以 A100 80GB 为例,创建 7 个 10GB 实例) sudo nvidia-smi mig -i 0 -cgi 19,19,19,19,19,19,19 -C # 在 GPU Operator 中配置 MIG 策略 # 编辑 ConfigMap 选择 mixed 或 single 策略 kubectl -n gpu-operator edit configmap mig-parted-config
2.2 vLLM 单机部署
2.2.1 Docker 快速部署
# 拉取 vLLM 官方镜像 docker pull vllm/vllm-openai:v0.6.6 # 启动推理服务(以 Qwen2.5-7B 为例) docker run -d --name vllm-server --gpus'"device=0"' --shm-size=8g -p 8000:8000 -v /data/models:/models vllm/vllm-openai:v0.6.6 --model /models/Qwen2.5-7B-Instruct --served-model-name qwen2.5-7b --tensor-parallel-size 1 --gpu-memory-utilization 0.90 --max-model-len 8192 --max-num-seqs 64 --enable-prefix-caching --trust-remote-code
2.2.2 关键启动参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
| --gpu-memory-utilization | 0.9 | GPU 显存使用比例,预留 10% 防 OOM |
| --max-model-len | 模型最大值 | 最大上下文长度,直接影响 KV Cache 显存占用 |
| --max-num-seqs | 256 | 最大并发序列数,受显存限制 |
| --tensor-parallel-size | 1 | 张量并行卡数,需能整除模型 attention heads |
| --quantization | None | 量化方式:awq / gptq / fp8 |
| --enable-prefix-caching | False | 启用前缀缓存,相同 system prompt 复用 KV Cache |
| --enforce-eager | False | 禁用 CUDA Graph,调试时使用 |
2.2.3 量化方案对比
| 量化方式 | 显存占用(7B) | 吞吐影响 | 精度损失 | 适用场景 |
|---|---|---|---|---|
| FP16(无量化) | ~14 GB | 基准 | 无 | 显存充足时首选 |
| AWQ(INT4) | ~4.5 GB | -5~10% | 极小 | 显存受限,推荐 |
| GPTQ(INT4) | ~4.5 GB | -10~15% | 小 | 已有 GPTQ 模型时 |
| FP8 | ~7.5 GB | +5~10% | 极小 | H100/Ada 架构,推荐 |
2.2.4 OpenAI 兼容 API 验证
# 测试 Chat Completions 接口 curl -s http://localhost:8000/v1/chat/completions -H"Content-Type: application/json" -d'{ "model": "qwen2.5-7b", "messages": [{"role": "user", "content": "你好"}], "max_tokens": 100, "temperature": 0.7 }'| jq . # 查看已加载模型 curl -s http://localhost:8000/v1/models | jq .
2.3 Kubernetes 部署
2.3.1 Deployment 配置
# vllm-deployment.yaml apiVersion:apps/v1 kind:Deployment metadata: name:vllm-qwen25-7b namespace:llm-serving labels: app:vllm model:qwen25-7b spec: replicas:2 selector: matchLabels: app:vllm model:qwen25-7b template: metadata: labels: app:vllm model:qwen25-7b annotations: # Prometheus 指标采集 prometheus.io/scrape:"true" prometheus.io/port:"8000" prometheus.io/path:"/metrics" spec: # 调度到 GPU 节点 nodeSelector: nvidia.com/gpu.present:"true" # 同一模型的 Pod 尽量分散到不同节点 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: -weight:100 podAffinityTerm: labelSelector: matchLabels: model:qwen25-7b topologyKey:kubernetes.io/hostname containers: -name:vllm image:vllm/vllm-openai:v0.6.6 args: -"--model" -"/models/Qwen2.5-7B-Instruct" -"--served-model-name" -"qwen2.5-7b" -"--gpu-memory-utilization" -"0.90" -"--max-model-len" -"8192" -"--max-num-seqs" -"64" -"--enable-prefix-caching" -"--trust-remote-code" ports: -containerPort:8000 name:http protocol:TCP resources: limits: nvidia.com/gpu:"1" # 请求 1 张 GPU memory:"32Gi" requests: cpu:"4" memory:"16Gi" # 启动探针:模型加载可能需要数分钟 startupProbe: httpGet: path:/health port:8000 initialDelaySeconds:30 periodSeconds:10 failureThreshold:30 # 最多等待 5 分钟 # 就绪探针:确认服务可接收流量 readinessProbe: httpGet: path:/health port:8000 periodSeconds:10 failureThreshold:3 # 存活探针:检测服务是否卡死 livenessProbe: httpGet: path:/health port:8000 periodSeconds:30 failureThreshold:3 env: -name:VLLM_LOGGING_LEVEL value:"INFO" # 共享内存,PagedAttention 需要 -name:NCCL_SHM_DISABLE value:"0" volumeMounts: -name:model-storage mountPath:/models readOnly:true -name:shm mountPath:/dev/shm volumes: -name:model-storage persistentVolumeClaim: claimName:model-pvc -name:shm emptyDir: medium:Memory sizeLimit:8Gi # 优雅终止:等待正在处理的请求完成 terminationGracePeriodSeconds:120
2.3.2 Service + Ingress 暴露
# vllm-service.yaml
apiVersion:v1
kind:Service
metadata:
name:vllm-qwen25-7b
namespace:llm-serving
spec:
selector:
app:vllm
model:qwen25-7b
ports:
-name:http
port:8000
targetPort:8000
protocol:TCP
type:ClusterIP
---
# vllm-ingress.yaml
apiVersion:networking.k8s.io/v1
kind:Ingress
metadata:
name:vllm-ingress
namespace:llm-serving
annotations:
# 推理请求可能耗时较长,超时设为 5 分钟
nginx.ingress.kubernetes.io/proxy-read-timeout:"300"
nginx.ingress.kubernetes.io/proxy-send-timeout:"300"
# 支持 SSE 流式输出
nginx.ingress.kubernetes.io/proxy-buffering:"off"
spec:
ingressClassName:nginx
rules:
-host:llm-api.example.com
http:
paths:
-path:/v1
pathType:Prefix
backend:
service:
name:vllm-qwen25-7b
port:
number:8000
2.3.3 HPA 自动伸缩
# vllm-hpa.yaml apiVersion:autoscaling/v2 kind:HorizontalPodAutoscaler metadata: name:vllm-qwen25-7b-hpa namespace:llm-serving spec: scaleTargetRef: apiVersion:apps/v1 kind:Deployment name:vllm-qwen25-7b minReplicas:1 maxReplicas:8 metrics: # 基于 GPU 利用率伸缩(需 DCGM Exporter + Prometheus Adapter) -type:Pods pods: metric: name:DCGM_FI_DEV_GPU_UTIL target: type:AverageValue averageValue:"70" # GPU 利用率超过 70% 触发扩容 behavior: scaleUp: stabilizationWindowSeconds:60 policies: -type:Pods value:2 # 每次最多扩 2 个 Pod periodSeconds:120 scaleDown: stabilizationWindowSeconds:300# 缩容冷却 5 分钟,避免频繁波动 policies: -type:Pods value:1 periodSeconds:180
2.3.4 KEDA 事件驱动伸缩
HPA 基于资源指标伸缩存在滞后性,KEDA 支持基于 Prometheus 自定义指标实现更精准的伸缩策略。
# vllm-keda.yaml apiVersion:keda.sh/v1alpha1 kind:ScaledObject metadata: name:vllm-qwen25-7b-scaler namespace:llm-serving spec: scaleTargetRef: name:vllm-qwen25-7b minReplicaCount:1 maxReplicaCount:8 cooldownPeriod:300 # 缩容冷却期 5 分钟 pollingInterval:15 # 每 15 秒检查一次指标 triggers: # 基于等待队列长度伸缩 -type:prometheus metadata: serverAddress:http://prometheus.monitoring:9090 metricName:vllm_waiting_requests query:| avg(vllm:num_requests_waiting{model_name="qwen2.5-7b"}) threshold:"10" # 平均等待请求超过 10 个触发扩容 # 基于 GPU 显存利用率 -type:prometheus metadata: serverAddress:http://prometheus.monitoring:9090 metricName:gpu_memory_util query:| avg(DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE) by (pod) threshold:"0.85"
2.4 多模型服务与路由
2.4.1 多 Deployment 部署
生产环境通常需要同时运行多个模型,每个模型独立 Deployment 管理生命周期:
# 部署结构示意 # llm-serving namespace # ├── vllm-qwen25-7b (Deployment, 2 replicas, GPU: A10) # ├── vllm-qwen25-72b (Deployment, 1 replica, GPU: A100x4) # └── vllm-llama3-8b (Deployment, 2 replicas, GPU: L40S)
2.4.2 Gateway API 路由
# 按路径将请求分发到不同模型
apiVersion:gateway.networking.k8s.io/v1
kind:HTTPRoute
metadata:
name:llm-router
namespace:llm-serving
spec:
parentRefs:
-name:llm-gateway
rules:
# /v1/qwen-7b/* -> qwen25-7b 服务
-matches:
-headers:
-name:x-model-id
value:qwen-7b
backendRefs:
-name:vllm-qwen25-7b
port:8000
# /v1/qwen-72b/* -> qwen25-72b 服务
-matches:
-headers:
-name:x-model-id
value:qwen-72b
backendRefs:
-name:vllm-qwen25-72b
port:8000
# 金丝雀发布:90% 流量到稳定版,10% 到新版
-matches:
-headers:
-name:x-model-id
value:llama3-8b
backendRefs:
-name:vllm-llama3-8b-stable
port:8000
weight:90
-name:vllm-llama3-8b-canary
port:8000
weight:10
2.5 Tensor Parallelism 多卡推理
2.5.1 单节点多卡
70B 及以上模型单卡放不下,需要多卡张量并行:
# 72B 模型 4 卡并行部署片段 containers: -name:vllm image:vllm/vllm-openai:v0.6.6 args: -"--model" -"/models/Qwen2.5-72B-Instruct" -"--tensor-parallel-size" -"4" # 4 卡张量并行 -"--gpu-memory-utilization" -"0.92" -"--max-model-len" -"4096" resources: limits: nvidia.com/gpu:"4" # 请求 4 张 GPU memory:"128Gi"
2.5.2 GPU 拓扑感知调度
多卡推理时,GPU 间通信带宽直接影响推理延迟。NVLink 带宽(600 GB/s)远高于 PCIe(64 GB/s),调度时应优先选择 NVLink 互联的 GPU:
# 通过节点标签确保调度到 NVLink 节点 nodeSelector: nvidia.com/gpu.present:"true" gpu-topology:nvlink # 自定义标签,标识 NVLink 互联节点 # 配合 GPU Operator 的拓扑感知特性 # 在 ClusterPolicy 中启用: # topologyManager: # policy: single-numa-node # 确保 GPU 和 CPU 在同一 NUMA 节点
三、示例代码和配置
3.1 完整部署清单
将所有资源整合为一个可直接 apply 的清单:
# 文件路径:deploy/vllm-full-stack.yaml
# 包含:Namespace + PDB + ConfigMap + Deployment + Service + HPA
---
apiVersion:v1
kind:Namespace
metadata:
name:llm-serving
labels:
monitoring:enabled
---
# PodDisruptionBudget:确保滚动更新和节点维护时至少 1 个 Pod 可用
apiVersion:policy/v1
kind:PodDisruptionBudget
metadata:
name:vllm-qwen25-7b-pdb
namespace:llm-serving
spec:
minAvailable:1
selector:
matchLabels:
app:vllm
model:qwen25-7b
---
# ConfigMap:集中管理 vLLM 启动参数,修改无需重建镜像
apiVersion:v1
kind:ConfigMap
metadata:
name:vllm-config
namespace:llm-serving
data:
MODEL_PATH:"/models/Qwen2.5-7B-Instruct"
SERVED_MODEL_NAME:"qwen2.5-7b"
GPU_MEMORY_UTILIZATION:"0.90"
MAX_MODEL_LEN:"8192"
MAX_NUM_SEQS:"64"
TENSOR_PARALLEL_SIZE:"1"
---
apiVersion:apps/v1
kind:Deployment
metadata:
name:vllm-qwen25-7b
namespace:llm-serving
spec:
replicas:2
strategy:
type:RollingUpdate
rollingUpdate:
maxSurge:1 # 滚动更新时最多多 1 个 Pod
maxUnavailable:0 # 不允许不可用,确保零停机
selector:
matchLabels:
app:vllm
model:qwen25-7b
template:
metadata:
labels:
app:vllm
model:qwen25-7b
spec:
containers:
-name:vllm
image:vllm/vllm-openai:v0.6.6
command:["python3","-m","vllm.entrypoints.openai.api_server"]
args:
-"--model=$(MODEL_PATH)"
-"--served-model-name=$(SERVED_MODEL_NAME)"
-"--gpu-memory-utilization=$(GPU_MEMORY_UTILIZATION)"
-"--max-model-len=$(MAX_MODEL_LEN)"
-"--max-num-seqs=$(MAX_NUM_SEQS)"
-"--tensor-parallel-size=$(TENSOR_PARALLEL_SIZE)"
-"--enable-prefix-caching"
-"--trust-remote-code"
envFrom:
-configMapRef:
name:vllm-config
ports:
-containerPort:8000
resources:
limits:
nvidia.com/gpu:"1"
memory:"32Gi"
requests:
cpu:"4"
memory:"16Gi"
startupProbe:
httpGet:
path:/health
port:8000
initialDelaySeconds:30
periodSeconds:10
failureThreshold:30
readinessProbe:
httpGet:
path:/health
port:8000
periodSeconds:10
livenessProbe:
httpGet:
path:/health
port:8000
periodSeconds:30
volumeMounts:
-name:model-storage
mountPath:/models
readOnly:true
-name:shm
mountPath:/dev/shm
volumes:
-name:model-storage
persistentVolumeClaim:
claimName:model-pvc
-name:shm
emptyDir:
medium:Memory
sizeLimit:8Gi
terminationGracePeriodSeconds:120
---
apiVersion:v1
kind:Service
metadata:
name:vllm-qwen25-7b
namespace:llm-serving
spec:
selector:
app:vllm
model:qwen25-7b
ports:
-port:8000
targetPort:8000
---
apiVersion:autoscaling/v2
kind:HorizontalPodAutoscaler
metadata:
name:vllm-qwen25-7b-hpa
namespace:llm-serving
spec:
scaleTargetRef:
apiVersion:apps/v1
kind:Deployment
name:vllm-qwen25-7b
minReplicas:1
maxReplicas:8
metrics:
-type:Pods
pods:
metric:
name:DCGM_FI_DEV_GPU_UTIL
target:
type:AverageValue
averageValue:"70"
behavior:
scaleDown:
stabilizationWindowSeconds:300
3.2 GPU 弹性伸缩配置
基于 KEDA + Prometheus 实现精细化伸缩,结合 vLLM 内置指标:
# 文件路径:deploy/vllm-keda-scaler.yaml
# 前置条件:已部署 KEDA、Prometheus、DCGM Exporter
---
apiVersion:keda.sh/v1alpha1
kind:ScaledObject
metadata:
name:vllm-qwen25-7b-keda
namespace:llm-serving
spec:
scaleTargetRef:
name:vllm-qwen25-7b
minReplicaCount:1 # 最少保留 1 个副本
maxReplicaCount:8 # 最多扩到 8 个副本
cooldownPeriod:300 # 缩容冷却 5 分钟
pollingInterval:15 # 每 15 秒采集一次指标
advanced:
restoreToOriginalReplicaCount:false
horizontalPodAutoscalerConfig:
behavior:
scaleUp:
stabilizationWindowSeconds:30
policies:
-type:Pods
value:2
periodSeconds:60
scaleDown:
stabilizationWindowSeconds:300
policies:
-type:Pods
value:1
periodSeconds:180
triggers:
# 触发器 1:vLLM 等待队列长度
-type:prometheus
metadata:
serverAddress:http://prometheus.monitoring:9090
metricName:vllm_pending_requests
query:|
sum(vllm:num_requests_waiting{model_name="qwen2.5-7b"})
/
count(vllm:num_requests_waiting{model_name="qwen2.5-7b"})
threshold:"10" # 平均等待请求 > 10 触发扩容
activationThreshold:"3" # 等待请求 > 3 才开始评估
# 触发器 2:请求延迟 P95
-type:prometheus
metadata:
serverAddress:http://prometheus.monitoring:9090
metricName:vllm_request_latency_p95
query:|
histogram_quantile(0.95,
sum(rate(vllm:request_latency_seconds_bucket{model_name="qwen2.5-7b"}[2m])) by (le)
)
threshold:"5" # P95 延迟超过 5 秒触发扩容
---
# Prometheus 告警规则:GPU 资源异常告警
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:vllm-alerts
namespace:llm-serving
spec:
groups:
-name:vllm.rules
rules:
-alert:VLLMHighQueueDepth
expr:|
avg(vllm:num_requests_waiting{model_name="qwen2.5-7b"}) > 20
for:2m
labels:
severity:warning
annotations:
summary:"vLLM 请求队列积压"
description:"模型 qwen2.5-7b 平均等待请求数{{ $value }},持续 2 分钟"
-alert:VLLMGPUMemoryHigh
expr:|
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) > 0.95
for:5m
labels:
severity:critical
annotations:
summary:"GPU 显存使用率超过 95%"
description:"节点{{ $labels.node }}GPU{{ $labels.gpu }}显存即将耗尽"
3.3 推理服务压测脚本
#!/bin/bash
# 文件名:benchmark-vllm.sh
# 功能:vLLM 推理服务压测,统计吞吐量和延迟分布
set-euo pipefail
# ========== 配置区 ==========
API_URL="${1//localhost:8000/v1/chat/completions}"
MODEL_NAME="${2:-qwen2.5-7b}"
CONCURRENCY="${3:-16}" # 并发数
TOTAL_REQUESTS="${4:-200}" # 总请求数
MAX_TOKENS=256 # 每次生成的最大 token 数
RESULT_DIR="./benchmark_results"
mkdir -p"${RESULT_DIR}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RESULT_FILE="${RESULT_DIR}/bench_${TIMESTAMP}.csv"
SUMMARY_FILE="${RESULT_DIR}/bench_${TIMESTAMP}_summary.txt"
# 初始化结果文件
echo"request_id,status_code,latency_ms,first_token_ms,tokens_generated">"${RESULT_FILE}"
# ========== 请求函数 ==========
send_request() {
localreq_id=$1
localstart_time end_time latency_ms status_code body
# 构造请求体
body=$(cat </dev/null) ||true
end_time=$(date +%s%N)
# 解析响应
status_code=$(echo"${response}"| tail -1)
localresp_body
resp_body=$(echo"${response}"| sed'$d')
# 计算延迟(毫秒)
latency_ms=$(( (end_time - start_time) / 1000000 ))
# 提取生成的 token 数
localtokens
tokens=$(echo"${resp_body}"| jq -r'.usage.completion_tokens // 0'2>/dev/null ||echo"0")
echo"${req_id},${status_code},${latency_ms},0,${tokens}">>"${RESULT_FILE}"
}
export-f send_request
exportAPI_URL MODEL_NAME MAX_TOKENS RESULT_FILE
# ========== 执行压测 ==========
echo"=========================================="
echo" vLLM 推理服务压测"
echo"=========================================="
echo" 目标地址:${API_URL}"
echo" 模型名称:${MODEL_NAME}"
echo" 并发数: ${CONCURRENCY}"
echo" 总请求: ${TOTAL_REQUESTS}"
echo" 最大Token:${MAX_TOKENS}"
echo"=========================================="
echo""
echo"压测开始..."
BENCH_START=$(date +%s%N)
# 使用 xargs 控制并发
seq 1"${TOTAL_REQUESTS}"| xargs -P"${CONCURRENCY}"-I {} bash -c'send_request {}'
BENCH_END=$(date +%s%N)
TOTAL_TIME_MS=$(( (BENCH_END - BENCH_START) / 1000000 ))
# ========== 统计结果 ==========
{
echo"=========================================="
echo" 压测结果汇总"
echo"=========================================="
echo" 总耗时:${TOTAL_TIME_MS}ms"
echo" 总请求:${TOTAL_REQUESTS}"
# 成功率
localsuccess_count
success_count=$(awk -F',''NR>1 && $2==200 {count++} END {print count+0}'"${RESULT_FILE}")
echo" 成功数:${success_count}"
echo" 成功率:$(echo "scale=2; ${success_count} * 100 / ${TOTAL_REQUESTS}" | bc)%"
# 吞吐量
echo" QPS: $(echo "scale=2; ${TOTAL_REQUESTS} * 1000 / ${TOTAL_TIME_MS}" | bc)"
# 延迟统计(仅成功请求)
echo""
echo" 延迟分布 (ms):"
awk -F',''NR>1 && $2==200 {latencies[NR-1]=$3; sum+=$3; count++}
END {
if(count==0) {print " 无成功请求"; exit}
asort(latencies)
printf " 最小值: %d
", latencies[1]
printf " P50: %d
", latencies[int(count*0.5)]
printf " P90: %d
", latencies[int(count*0.9)]
printf " P95: %d
", latencies[int(count*0.95)]
printf " P99: %d
", latencies[int(count*0.99)]
printf " 最大值: %d
", latencies[count]
printf " 平均值: %d
", sum/count
}'"${RESULT_FILE}"
# Token 吞吐
localtotal_tokens
total_tokens=$(awk -F',''NR>1 && $2==200 {sum+=$5} END {print sum+0}'"${RESULT_FILE}")
echo""
echo" Token 吞吐:"
echo" 总生成 Token:${total_tokens}"
echo" Token/s: $(echo "scale=2; ${total_tokens} * 1000 / ${TOTAL_TIME_MS}" | bc)"
echo"=========================================="
} | tee"${SUMMARY_FILE}"
echo""
echo"详细结果:${RESULT_FILE}"
echo"汇总报告:${SUMMARY_FILE}"
使用方式:
# 默认参数压测 chmod +x benchmark-vllm.sh ./benchmark-vllm.sh # 自定义参数:目标地址、模型名、并发数 32、总请求 500 ./benchmark-vllm.sh http://llm-api.example.com/v1/chat/completions qwen2.5-7b 32 500
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 显存优化
显存是 LLM 推理的第一瓶颈,优化思路围绕三个方向:减少模型本身占用、控制 KV Cache 开销、选择合适的量化方案。
gpu-memory-utilization 参数调优:
# 默认值 0.9,表示 vLLM 预分配 90% 显存用于模型权重 + KV Cache # 单模型独占 GPU 时,可适当调高 --gpu-memory-utilization 0.92 # 多模型共享 GPU 或需要预留显存给监控进程时,适当调低 --gpu-memory-utilization 0.80 # 查看实际显存分配情况(启动日志会打印) # INFO: GPU memory: 79.15 GiB total, 71.24 GiB allocated for KV cache
实际调优建议:先用默认值 0.9 启动,观察nvidia-smi中显存占用是否稳定,如果出现 OOM 则降到 0.85,稳定后再逐步上调。
KV Cache 预分配策略:
vLLM 启动时会根据gpu-memory-utilization计算可用显存,扣除模型权重后全部预分配给 KV Cache。这意味着:
max-model-len越大,单个请求占用的 KV Cache 越多,可并发的请求数越少
如果业务场景中 90% 的请求上下文长度不超过 4K,没必要把max-model-len设为 32K
# 根据实际业务场景限制最大上下文长度 # 减少 max-model-len 可以显著增加并发容量 --max-model-len 8192 # 查看 KV Cache 可容纳的最大并发数 # 启动日志:Maximum number of running requests: 256
量化方案选择决策树:
模型参数量 → 可用显存 → 精度要求 → 选择方案 70B 模型 + 单卡 80G → 必须量化 ├─ 精度要求高 → AWQ 4bit(推荐,速度快且精度损失小) ├─ 追求极致压缩 → GPTQ 4bit(压缩率略高,推理稍慢) └─ H100/L40S → FP8(硬件原生支持,精度损失最小) 7B~14B 模型 + 单卡 24G~40G ├─ 显存充足 → FP16/BF16(无精度损失) └─ 显存紧张 → AWQ 4bit 7B 模型 + 80G 显卡 → FP16,不要量化(显存够用,量化反而增加延迟)
| 量化方案 | 显存节省 | 精度损失 | 推理速度 | 适用 GPU |
|---|---|---|---|---|
| FP16/BF16 | 基准 | 无 | 基准 | 所有 |
| AWQ 4bit | ~60% | 低 | 快(有专用 kernel) | 所有 |
| GPTQ 4bit | ~60% | 低~中 | 中等 | 所有 |
| FP8 | ~50% | 极低 | 快 | H100/L40S/Ada |
| SmoothQuant | ~50% | 低 | 快 | Ampere+ |
4.1.2 吞吐量优化
max-num-batched-tokens 调优:
这个参数决定了一个推理迭代中最多处理多少 token,直接影响批处理效率。
# 默认值等于 max-model-len,大多数场景不需要手动设置 # 如果请求长度差异大(既有 100 token 的短请求,也有 8K 的长请求),可以手动调整 --max-num-batched-tokens 16384 # 配合 max-num-seqs 控制最大并发请求数 # 短文本高并发场景:增大 max-num-seqs --max-num-seqs 512 # 长文本低并发场景:减小 max-num-seqs,增大 max-num-batched-tokens --max-num-seqs 32 --max-num-batched-tokens 32768
Continuous Batching 参数:
vLLM 默认启用 Continuous Batching,无需额外配置。但有几个参数影响批处理行为:
# 调度策略:默认 "auto",会根据显存情况自动选择 --scheduling-policy auto # 预取数量:控制 prefill 和 decode 的调度平衡 # 值越大,新请求的首 token 延迟越高,但整体吞吐越好 --num-scheduler-steps 1 # 默认值,低延迟优先 --num-scheduler-steps 10# 高吞吐优先,适合离线批处理
Prefix Caching(前缀缓存):
当多个请求共享相同的系统提示词(System Prompt)时,Prefix Caching 可以避免重复计算,显著提升吞吐。
# 启用自动前缀缓存 --enable-prefix-caching # 典型场景:所有请求使用相同的 system prompt(如 RAG 场景) # 第一个请求:计算完整 KV Cache(包括 system prompt 部分) # 后续请求:复用 system prompt 的 KV Cache,只计算用户输入部分 # 实测效果:相同 system prompt 下,TTFT 降低 30%~60%
适用条件:请求之间有大量重复前缀(系统提示词、Few-shot 示例等),如果每个请求的输入完全不同,开启后反而有轻微开销。
4.1.3 成本控制
GPU 利用率目标:
生产环境 GPU 利用率应维持在 70%~85% 之间。低于 70% 说明资源浪费,高于 85% 容易在流量突增时触发排队。
#!/bin/bash
set-euo pipefail
# gpu-utilization-check.sh - 检查 GPU 利用率并输出建议
THRESHOLD_LOW=70
THRESHOLD_HIGH=85
# 获取所有 GPU 的利用率
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total
--format=csv,noheader,nounits |whileIFS=', 'read-r idx util mem_used mem_total;do
mem_pct=$((mem_used * 100 / mem_total))
if["${util}"-lt"${THRESHOLD_LOW}"];then
echo"[GPU${idx}] 利用率${util}%(偏低),显存${mem_pct}% - 建议合并负载或缩容"
elif["${util}"-gt"${THRESHOLD_HIGH}"];then
echo"[GPU${idx}] 利用率${util}%(偏高),显存${mem_pct}% - 建议扩容或限流"
else
echo"[GPU${idx}] 利用率${util}%(正常),显存${mem_pct}%"
fi
done
Spot/Preemptible 实例使用:
离线批量推理任务(数据标注、批量摘要)适合使用 Spot 实例,成本降低 60%~70%
在线服务不建议使用 Spot,被回收时会导致请求中断
混合策略:On-Demand 实例承载基线流量,Spot 实例承载弹性流量
缩容策略(冷却期设计):
# HPA 缩容配置 - 避免频繁缩扩容导致的抖动 apiVersion:autoscaling/v2 kind:HorizontalPodAutoscaler spec: behavior: scaleDown: stabilizationWindowSeconds:600# 缩容冷却期 10 分钟 policies: -type:Pods value:1 # 每次最多缩减 1 个 Pod periodSeconds:120# 每 2 分钟最多执行一次缩容 scaleUp: stabilizationWindowSeconds:30 # 扩容冷却期 30 秒(快速响应) policies: -type:Pods value:2 # 每次最多扩 2 个 Pod periodSeconds:60
多模型共享 GPU:
| 方案 | 隔离性 | 配置复杂度 | 适用场景 |
|---|---|---|---|
| NVIDIA MIG | 硬件级隔离 | 高(需预先分区) | A100/H100,固定负载 |
| 时间片共享 | 无隔离 | 低 | 开发测试环境 |
| MPS | 进程级 | 中 | 多个小模型共享 |
| 多容器共享 | 无隔离 | 低 | 不推荐生产使用 |
# MIG 分区示例(A100 80G 分为 2 个 40G 实例) sudo nvidia-smi mig -cgi 9,9 -C # 查看 MIG 实例 nvidia-smi mig -lgi
4.1.4 高可用设计
多副本 + PodDisruptionBudget:
# PDB 配置 - 确保滚动更新和节点维护时至少有可用副本
apiVersion:policy/v1
kind:PodDisruptionBudget
metadata:
name:vllm-pdb
namespace:llm-serving
spec:
minAvailable:1 # 至少保持 1 个 Pod 可用
selector:
matchLabels:
app:vllm-inference
---
# 部署时使用反亲和性,将副本分散到不同节点
# 在 Deployment 的 spec.template.spec 中添加
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
-weight:100
podAffinityTerm:
labelSelector:
matchExpressions:
-key:app
operator:In
values:["vllm-inference"]
topologyKey:kubernetes.io/hostname
健康检查策略:
vLLM 内置/health端点,但默认的健康检查配置往往不够合理,模型加载阶段容易被误杀。
# 针对 LLM 推理服务的健康检查配置 containers: -name:vllm livenessProbe: httpGet: path:/health port:8000 initialDelaySeconds:300 # 大模型加载需要 3~8 分钟,给足启动时间 periodSeconds:30 timeoutSeconds:10 failureThreshold:3 # 连续 3 次失败才重启 readinessProbe: httpGet: path:/health port:8000 initialDelaySeconds:60 periodSeconds:10 timeoutSeconds:5 failureThreshold:3 startupProbe: # 启动探针:模型加载完成前不触发存活检查 httpGet: path:/health port:8000 initialDelaySeconds:30 periodSeconds:10 failureThreshold:60 # 最多等待 10 分钟(10s × 60)
优雅关闭(Drain 在途请求):
# Pod 关闭时给足时间处理在途请求
spec:
terminationGracePeriodSeconds:120# 给 2 分钟完成在途请求
containers:
-name:vllm
lifecycle:
preStop:
exec:
command:
-/bin/sh
--c
# 先从 Service 摘除(停止接收新请求),等待在途请求完成
-"sleep 5 && kill -SIGTERM 1"
配合 Service 端的配置,确保流量切换平滑:
# 验证优雅关闭是否生效 # 在滚动更新期间观察是否有 502/503 错误 kubectl rollout restart deployment/vllm-inference -n llm-serving # 同时监控错误率 curl -s http://llm-api.example.com/health
模型预加载(避免冷启动):
冷启动是 LLM 推理服务的痛点,70B 模型从磁盘加载到 GPU 需要 3~8 分钟。缓解方案:
使用emptyDir+initContainer预拉取模型到本地 SSD
保持最小副本数 >= 1,避免缩容到 0
使用 PVC 持久化模型文件,避免每次 Pod 重建都重新下载
考虑使用 Tensorizer 格式加速模型加载(加载速度提升 2~5 倍)
4.2 注意事项
4.2.1 配置注意事项
警告:OOM Killer 是 vLLM 服务最常见的致命问题
vLLM 启动时会预分配大量显存和内存,如果 K8s 资源限制设置不当,极易触发 OOM Killer。
共享内存不足:vLLM 使用 PyTorch,依赖/dev/shm进行进程间通信。Docker 默认/dev/shm只有 64MB,Tensor Parallel 场景下必须增大:
# 在 Pod spec 中挂载足够的共享内存 volumes: -name:shm emptyDir: medium:Memory sizeLimit:16Gi # 建议设为显存的 20% containers: -volumeMounts: -name:shm mountPath:/dev/shm
内存资源限制:resources.limits.memory必须大于模型权重大小 + KV Cache 的 CPU 部分。7B FP16 模型至少需要 20Gi 内存,70B 模型至少 80Gi
模型下载超时:从 HuggingFace 下载大模型容易超时,建议预先下载到 PV 或使用国内镜像源
# 使用 modelscope 镜像加速下载 exportVLLM_USE_MODELSCOPE=True # 或者手动下载后挂载 huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir /models/qwen2.5-7b
4.2.2 常见错误
| 错误现象 | 原因分析 | 解决方案 |
|---|---|---|
| CUDA out of memory | 显存不足以加载模型 + KV Cache | 降低gpu-memory-utilization(如 0.85);减小max-model-len;使用量化模型 |
| Model not found /OSError: Can't load tokenizer | 模型路径错误或文件不完整 | 检查挂载路径;用ls -la /models/确认文件完整;检查config.json是否存在 |
| tensor parallel size does not match | TP 数与可用 GPU 数不一致 | 确认--tensor-parallel-size等于nvidia.com/gpu的 limits 值 |
| Health check timeout → Pod 反复重启 | startupProbe 超时时间不够 | 增大failureThreshold × periodSeconds,70B 模型建议至少 600 秒 |
| torch.cuda.CudaError: invalid device ordinal | GPU 设备号不连续或 NVIDIA 驱动异常 | 检查nvidia-smi;重启nvidia-device-plugin;确认节点 GPU 状态正常 |
| KV cache is too small | max-model-len 过大导致 KV Cache 分配失败 | 减小max-model-len;增大gpu-memory-utilization;使用更大显存的 GPU |
| Connection refused on port 8000 | 模型仍在加载中,服务未就绪 | 等待 readinessProbe 通过;检查启动日志确认加载进度 |
4.2.3 兼容性问题
vLLM 版本与模型格式:
| vLLM 版本 | 支持的模型格式 | 注意事项 |
|---|---|---|
| v0.4.x | HF、AWQ、GPTQ | 不支持 FP8,Qwen2 需要 v0.4.3+ |
| v0.5.x | HF、AWQ、GPTQ、FP8、GGUF | 新增 FP8 支持,需要 H100/L40S |
| v0.6.x | 同上 + Marlin 格式 | 改进了 AWQ/GPTQ 推理速度 |
CUDA 版本匹配:
vLLM 官方镜像基于 CUDA 12.1 或 12.4 构建
宿主机驱动版本必须 >= 镜像中 CUDA 版本的最低驱动要求
CUDA 12.1 → 驱动 >= 530.30,CUDA 12.4 → 驱动 >= 550.54
# 检查驱动兼容性 nvidia-smi # 查看 Driver Version 和 CUDA Version # 如果驱动版本过低,vLLM 容器会报 CUDA initialization error
不同 GPU 架构支持:
| GPU 架构 | 代表型号 | vLLM 支持 | 特殊说明 |
|---|---|---|---|
| Ampere | A100, A10, A30 | 完整支持 | 推荐生产使用 |
| Hopper | H100, H200 | 完整支持 | 支持 FP8,性能最优 |
| Ada Lovelace | L40S, RTX 4090 | 完整支持 | 支持 FP8 |
| Turing | T4, RTX 2080 | 基础支持 | 不支持 BF16,需用 FP16 |
| Volta | V100 | 有限支持 | 不支持 BF16 和 FP8,部分模型不兼容 |
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
vLLM 日志级别控制:
# 启动时设置日志级别(默认 INFO) --log-level debug # 排查问题时使用,会输出详细的调度和推理信息 --log-level warning # 生产环境推荐,减少日志量 # 环境变量方式设置 VLLM_LOGGING_LEVEL=DEBUG
K8s Pod 日志查看:
# 查看当前日志(实时跟踪) kubectl logs -f deployment/vllm-inference -n llm-serving # 查看上一次崩溃的日志(Pod 重启后原日志会丢失) kubectl logs deployment/vllm-inference -n llm-serving --previous # 查看最近 500 行日志 kubectl logs deployment/vllm-inference -n llm-serving --tail=500 # 多副本场景,查看所有 Pod 日志 kubectl logs -l app=vllm-inference -n llm-serving --tail=100 # 查看 Pod 事件(排查调度和资源问题) kubectl describe pod -l app=vllm-inference -n llm-serving | grep -A 20"Events:"
5.1.2 常见问题排查
问题一:GPU OOM(显存溢出)
# 诊断:确认显存使用情况 nvidia-smi --query-gpu=index,memory.used,memory.total,memory.free --format=csv # 检查 vLLM 启动日志中的显存分配信息 kubectl logs deployment/vllm-inference -n llm-serving | grep -i"gpu memory|kv cache|OOM"
解决方案:
降低--gpu-memory-utilization到 0.85
减小--max-model-len(如从 32768 降到 8192)
减小--max-num-seqs(限制并发请求数)
换用量化模型(FP16 → AWQ 4bit)
问题二:推理延迟突增
# 诊断:检查 GPU 利用率是否打满 nvidia-smi dmon -s u -d 1 # 每秒采样一次 GPU 利用率 # 检查是否有大量排队请求 curl -s http://localhost:8000/metrics | grep"vllm:num_requests_waiting" # 检查是否触发了 swap(KV Cache 被换出到 CPU) kubectl logs deployment/vllm-inference -n llm-serving | grep -i"swap|preempt"
解决方案:
排队请求多 → 扩容副本数或增大--max-num-seqs
出现 swap/preempt → 减小--max-model-len或增加 GPU 显存
单个请求输入过长 → 业务侧限制输入长度
问题三:Pod 启动失败(模型加载阶段)
# 诊断:查看 Pod 状态和事件 kubectl get pods -n llm-serving -o wide kubectl describe pod-n llm-serving # 常见事件及含义 # FailedScheduling → GPU 资源不足,没有可用节点 # OOMKilled → 内存限制过低,模型加载时被杀 # CrashLoopBackOff → 启动失败后反复重启
解决方案:
FailedScheduling→ 检查集群 GPU 资源:kubectl describe nodes | grep -A 5 "nvidia.com/gpu"
OOMKilled→ 增大resources.limits.memory,7B 模型至少 20Gi,70B 至少 80Gi
模型文件不完整 → 进入 Pod 检查:kubectl exec -it
问题四:请求超时(504 Gateway Timeout)
# 诊断:确认超时发生在哪一层 # 1. vLLM 本身是否响应 kubectlexec-it-n llm-serving -- curl -s -o /dev/null -w"%{http_code} %{time_total}s" http://localhost:8000/health # 2. 检查 Ingress/Gateway 超时配置 kubectl get ingress -n llm-serving -o yaml | grep -i timeout # 3. 检查是否有慢请求阻塞 curl -s http://localhost:8000/metrics | grep"vllm:request_duration_seconds"
解决方案:
Ingress 超时 → 增大proxy-read-timeout(LLM 生成长文本需要 60s+)
vLLM 响应慢 → 检查 GPU 负载,考虑扩容
单个请求生成 token 过多 → 设置--max-tokens限制输出长度
5.1.3 nvidia-smi 与 GPU 诊断
#!/bin/bash set-euo pipefail # gpu-diagnose.sh - GPU 状态全面诊断脚本 echo"===== 驱动和 CUDA 版本 =====" nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -1 nvidia-smi --query-gpu=name,pci.bus_id,compute_mode --format=csv echo"" echo"===== 显存和利用率 =====" nvidia-smi --query-gpu=index,name,utilization.gpu,utilization.memory,memory.used,memory.total,temperature.gpu,power.draw --format=csv echo"" echo"===== GPU 进程占用 =====" nvidia-smi --query-compute-apps=pid,process_name,used_gpu_memory --format=csv echo"" echo"===== ECC 错误检查(硬件故障排查)=====" nvidia-smi --query-gpu=index,ecc.errors.corrected.volatile.total,ecc.errors.uncorrected.volatile.total --format=csv 2>/dev/null ||echo"ECC 不可用(消费级 GPU 不支持)" echo"" echo"===== NVLink 状态(多卡通信)=====" nvidia-smi nvlink -s 2>/dev/null ||echo"NVLink 不可用" echo"" echo"===== PCIe 带宽 =====" nvidia-smi --query-gpu=index,pcie.link.gen.current,pcie.link.width.current --format=csv
5.2 性能监控
5.2.1 关键指标
LLM 推理服务的监控指标与传统 Web 服务有本质区别,需要关注 GPU 维度和 token 维度的指标。
# 快速查看 vLLM 暴露的 Prometheus 指标 curl -s http://localhost:8000/metrics | grep"^vllm:"| sort # 关键指标一览 # vllm:num_requests_running - 正在处理的请求数 # vllm:num_requests_waiting - 排队等待的请求数 # vllm:gpu_cache_usage_perc - KV Cache 使用率 # vllm:avg_generation_throughput_toks_per_s - 生成吞吐量(tokens/s) # vllm:request_duration_seconds - 请求耗时分布
5.2.2 监控指标说明
| 指标名称 | 正常范围 | 告警阈值 | 说明 |
|---|---|---|---|
| GPU 利用率 | 40%~85% | > 90% 持续 5 分钟 | 过高说明需要扩容 |
| 显存使用率 | 70%~92% | > 95% | 接近上限会触发 OOM |
| KV Cache 使用率 | < 80% | > 90% 持续 3 分钟 | 过高会导致请求排队或被抢占 |
| 排队请求数 | 0~5 | > 20 持续 2 分钟 | 排队过多说明吞吐不足 |
| TTFT(首 token 延迟) | < 500ms(7B) | > 2s | 用户体感最直接的指标 |
| 生成速度 | 30~80 tokens/s(7B) | < 15 tokens/s | 低于阈值说明 GPU 负载过重 |
| P99 请求延迟 | < 30s | > 60s | 长尾延迟影响用户体验 |
| GPU 温度 | < 80°C | > 85°C | 过热会触发降频,影响性能 |
| 错误率 | < 0.1% | > 1% | 包括 OOM、超时等各类错误 |
5.2.3 Prometheus + DCGM Exporter + Grafana 监控体系
vLLM 自带 Prometheus 指标:
vLLM 默认在/metrics端点暴露 Prometheus 格式指标,无需额外配置。在 K8s 中通过 ServiceMonitor 采集:
apiVersion:monitoring.coreos.com/v1 kind:ServiceMonitor metadata: name:vllm-metrics namespace:llm-serving labels: release:prometheus # 匹配 Prometheus Operator 的 serviceMonitorSelector spec: selector: matchLabels: app:vllm-inference endpoints: -port:http path:/metrics interval:15s # 采集间隔
DCGM Exporter(GPU 硬件指标):
vLLM 自身指标不包含 GPU 温度、功耗、ECC 错误等硬件信息,需要 DCGM Exporter 补充:
# 通过 Helm 部署 DCGM Exporter helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts helm install dcgm-exporter gpu-helm-charts/dcgm-exporter --namespace monitoring --setserviceMonitor.enabled=true
Grafana 告警规则:
# PrometheusRule - vLLM 推理服务告警
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:vllm-alerts
namespace:llm-serving
spec:
groups:
-name:vllm.rules
rules:
# KV Cache 使用率过高
-alert:VLLMKVCacheHigh
expr:vllm:gpu_cache_usage_perc>0.9
for:3m
labels:
severity:warning
annotations:
summary:"vLLM KV Cache 使用率超过 90%"
description:"Pod{{ $labels.pod }}KV Cache 使用率{{ $value | humanizePercentage }},可能导致请求排队"
# 排队请求过多
-alert:VLLMRequestQueueHigh
expr:vllm:num_requests_waiting>20
for:2m
labels:
severity:critical
annotations:
summary:"vLLM 排队请求数过多"
description:"当前排队{{ $value }}个请求,建议扩容"
# GPU 温度过高(来自 DCGM Exporter)
-alert:GPUTemperatureHigh
expr:DCGM_FI_DEV_GPU_TEMP>85
for:5m
labels:
severity:warning
annotations:
summary:"GPU 温度过高"
description:"GPU{{ $labels.gpu }}温度{{ $value }}°C,可能触发降频"
5.3 备份与恢复
5.3.1 模型文件管理
模型文件是 LLM 推理服务的核心资产,丢失意味着需要重新下载(70B 模型 140GB+,耗时数小时)。
PV 持久化方案:
# 使用 PVC 持久化模型文件,Pod 重建时无需重新下载 apiVersion:v1 kind:PersistentVolumeClaim metadata: name:model-storage namespace:llm-serving spec: accessModes: -ReadOnlyMany # 多个 Pod 只读共享,避免写冲突 storageClassName:nfs # NFS 适合多节点共享;高性能场景用 Lustre 或本地 SSD resources: requests: storage:200Gi # 根据模型大小预留,70B FP16 约 140Gi
模型仓库镜像策略:
#!/bin/bash
set-euo pipefail
# sync-model.sh - 模型文件同步脚本(从源仓库同步到本地存储)
MODEL_NAME="${1:?用法: $0 <模型名> [源地址]}"
SOURCE="${2//modelscope.cn/models/${MODEL_NAME}}"
LOCAL_DIR="/data/models/${MODEL_NAME}"
BACKUP_DIR="/data/models-backup/${MODEL_NAME}"
echo"[$(date '+%Y-%m-%d %H:%M:%S')] 开始同步模型:${MODEL_NAME}"
# 下载到本地目录
mkdir -p"${LOCAL_DIR}"
ifcommand-v modelscope &>/dev/null;then
modelscope download --model"${MODEL_NAME}"--local_dir"${LOCAL_DIR}"
else
huggingface-cli download"${MODEL_NAME}"--local-dir"${LOCAL_DIR}"
fi
# 校验关键文件完整性
forfinconfig.json tokenizer.json;do
if[ ! -f"${LOCAL_DIR}/${f}"];then
echo"[错误] 缺少关键文件:${f}"
exit1
fi
done
# 创建备份(保留上一个版本)
if[ -d"${BACKUP_DIR}"];then
rm -rf"${BACKUP_DIR}.old"
mv"${BACKUP_DIR}""${BACKUP_DIR}.old"
fi
cp -al"${LOCAL_DIR}""${BACKUP_DIR}"# 硬链接拷贝,节省空间
echo"[$(date '+%Y-%m-%d %H:%M:%S')] 同步完成,模型路径:${LOCAL_DIR}"
echo"文件大小:$(du -sh "${LOCAL_DIR}" | cut -f1)"
5.3.2 服务恢复流程
当 vLLM 推理服务出现不可恢复故障时,按以下流程恢复:
#!/bin/bash
set-euo pipefail
# restore-vllm-service.sh - vLLM 服务恢复流程
NAMESPACE="llm-serving"
DEPLOYMENT="vllm-inference"
echo"===== 第一步:确认故障状态 ====="
kubectl get pods -n"${NAMESPACE}"-l app="${DEPLOYMENT}"-o wide
kubectl get events -n"${NAMESPACE}"--sort-by='.lastTimestamp'| tail -20
echo""
echo"===== 第二步:检查 GPU 节点状态 ====="
kubectl get nodes -l nvidia.com/gpu.present=true-o custom-columns=
NAME:.metadata.name,STATUS:.status.conditions[-1].type,GPU:.status.allocatable.nvidia\.com/gpu
echo""
echo"===== 第三步:检查模型文件完整性 ====="
# 在任意 GPU 节点上检查 PV 挂载
PV_NODE=$(kubectl get pv -o jsonpath='{.items[0].spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}'2>/dev/null ||echo"N/A")
echo"模型存储节点:${PV_NODE}"
echo""
echo"===== 第四步:重建服务 ====="
# 先删除异常 Pod,让 Deployment 控制器重建
kubectl delete pods -n"${NAMESPACE}"-l app="${DEPLOYMENT}"--grace-period=30
# 等待新 Pod 就绪
echo"等待 Pod 就绪..."
kubectl rollout status deployment/"${DEPLOYMENT}"-n"${NAMESPACE}"--timeout=600s
echo""
echo"===== 第五步:验证服务可用性 ====="
# 获取 Service ClusterIP
SVC_IP=$(kubectl get svc"${DEPLOYMENT}"-n"${NAMESPACE}"-o jsonpath='{.spec.clusterIP}')
kubectl run curl-test --rm -i --restart=Never --image=curlimages/curl --
curl -s -o /dev/null -w"HTTP %{http_code}, 耗时 %{time_total}s
"
"http://${SVC_IP}:8000/health"
echo""
echo"===== 恢复完成 ====="
kubectl get pods -n"${NAMESPACE}"-l app="${DEPLOYMENT}"
六、总结
6.1 技术要点回顾
引擎选型:vLLM 凭借 PagedAttention 和 Continuous Batching 机制,在吞吐量上显著优于原生 HuggingFace Transformers 推理,是当前生产环境 LLM 推理的首选引擎
部署架构:K8s + GPU Operator + vLLM 容器化部署是标准方案,通过 Deployment 管理副本、Service 暴露接口、HPA 实现弹性伸缩
显存管理:gpu-memory-utilization、max-model-len、max-num-seqs三个参数构成显存分配的核心调优三角,需要根据模型大小和业务场景综合调整
量化策略:AWQ 4bit 是通用性最好的量化方案,FP8 在 Hopper 架构上精度损失最小,选择量化方案前先评估显存是否真的不够用
高可用保障:PDB + 反亲和性 + 合理的健康检查配置 + 优雅关闭,四层防护确保服务稳定性
监控体系:vLLM 原生指标 + DCGM Exporter + Prometheus + Grafana 构成完整的可观测性方案,重点关注 KV Cache 使用率和排队请求数
6.2 进阶方向
Speculative Decoding(推测解码):使用小模型(Draft Model)预测多个 token,大模型一次性验证,在不损失精度的前提下提升生成速度 1.5~2 倍。vLLM 已支持--speculative-model参数
适用场景:对延迟敏感的在线服务
限制:需要额外显存加载 Draft Model
LoRA 动态加载:vLLM 支持在运行时动态加载/卸载 LoRA 适配器,无需为每个微调模型部署独立实例
启动参数:--enable-lora --max-loras 4 --max-lora-rank 64
请求时指定:"model": "base-model:lora-adapter-name"
适用场景:多租户场景,每个客户有独立的微调模型
多模态模型推理:vLLM 已支持 LLaVA、Qwen-VL 等视觉语言模型的推理,可处理图文混合输入
注意:图片预处理会占用额外 CPU 和内存,需要调整资源配置
Serverless GPU:基于 Knative + GPU 的按需推理方案,空闲时缩容到 0,请求到来时自动扩容
挑战:冷启动时间长(模型加载 3~8 分钟),需要配合模型缓存和预热策略
适用场景:低频调用的内部工具、开发测试环境
6.3 参考资料
vLLM 官方文档- 最权威的参数说明和部署指南
vLLM GitHub 仓库- 源码、Issue 和 Release Notes
NVIDIA GPU Operator 文档- K8s GPU 管理
DCGM Exporter- GPU 监控指标采集
PagedAttention 论文- vLLM 核心算法原理
HuggingFace Model Hub- 模型下载和文档
附录
A. 命令速查表
# ===== vLLM 服务管理 =====
# 启动 vLLM(Docker 方式)
docker run --gpus all -v /models:/models -p 8000:8000 vllm/vllm-openai:latest
--model /models/qwen2.5-7b --served-model-name qwen2.5-7b
# 健康检查
curl http://localhost:8000/health
# 查看已加载的模型
curl http://localhost:8000/v1/models
# 查看 Prometheus 指标
curl http://localhost:8000/metrics
# ===== K8s 运维 =====
# 查看 Pod 状态
kubectl get pods -n llm-serving -l app=vllm-inference -o wide
# 查看 Pod 日志
kubectl logs -f deployment/vllm-inference -n llm-serving
# 滚动重启
kubectl rollout restart deployment/vllm-inference -n llm-serving
# 查看 HPA 状态
kubectl get hpa -n llm-serving
# 手动扩缩容
kubectl scale deployment/vllm-inference -n llm-serving --replicas=3
# ===== GPU 诊断 =====
# 查看 GPU 状态
nvidia-smi
# 持续监控 GPU(每秒刷新)
nvidia-smi dmon -s u -d 1
# 查看 GPU 进程
nvidia-smi --query-compute-apps=pid,process_name,used_gpu_memory --format=csv
# 检查 GPU 拓扑(多卡 NVLink 连接)
nvidia-smi topo -m
# ===== 性能测试 =====
# 简单功能验证
curl http://localhost:8000/v1/chat/completions
-H"Content-Type: application/json"
-d'{"model":"qwen2.5-7b","messages":[{"role":"user","content":"hello"}],"max_tokens":50}'
# 并发压测(需安装 hey)
hey -n 100 -c 10 -m POST -H"Content-Type: application/json"
-d'{"model":"qwen2.5-7b","messages":[{"role":"user","content":"写一首五言绝句"}],"max_tokens":100}'
http://localhost:8000/v1/chat/completions
B. 配置参数详解
| 参数 | 默认值 | 说明 | 调优建议 |
|---|---|---|---|
| --model | 必填 | 模型路径或 HuggingFace 模型 ID | 生产环境使用本地路径 |
| --tensor-parallel-size | 1 | 张量并行数(多卡推理) | 等于使用的 GPU 数量 |
| --gpu-memory-utilization | 0.9 | GPU 显存使用比例 | 单模型独占可调到 0.92 |
| --max-model-len | 模型默认值 | 最大上下文长度 | 按业务实际需求设置,越小并发越高 |
| --max-num-seqs | 256 | 最大并发请求数 | 短文本场景可增大到 512 |
| --max-num-batched-tokens | max-model-len | 单次迭代最大 token 数 | 一般不需要手动设置 |
| --dtype | auto | 模型精度 | auto 会根据模型配置自动选择 |
| --quantization | None | 量化方式 | awq / gptq / fp8 / squeezellm |
| --enable-prefix-caching | False | 启用前缀缓存 | 相同 system prompt 场景建议开启 |
| --enable-lora | False | 启用 LoRA 支持 | 多租户微调场景使用 |
| --max-loras | 1 | 最大同时加载的 LoRA 数 | 根据显存和租户数设置 |
| --served-model-name | 模型路径 | API 中暴露的模型名称 | 建议设置简短易记的名称 |
| --host | 0.0.0.0 | 监听地址 | 容器环境保持默认 |
| --port | 8000 | 监听端口 | 容器环境保持默认 |
| --log-level | info | 日志级别 | 生产用 warning,排查用 debug |
| --disable-log-requests | False | 禁用请求日志 | 高并发生产环境建议开启以减少 IO |
| --num-scheduler-steps | 1 | 调度器步数 | 离线批处理可设为 10 提升吞吐 |
| --swap-space | 4 | CPU swap 空间(GiB) | 显存紧张时可增大,但会影响性能 |
| --seed | 0 | 随机种子 | 需要可复现结果时设置固定值 |
C. 术语表
| 术语 | 英文 | 解释 |
|---|---|---|
| 张量并行 | Tensor Parallelism (TP) | 将模型权重按张量维度切分到多张 GPU 上,每张卡持有部分权重,协同完成推理 |
| 流水线并行 | Pipeline Parallelism (PP) | 将模型按层切分到多张 GPU 上,数据在 GPU 之间流水线式传递 |
| KV Cache | Key-Value Cache | 推理时缓存已计算的 Key 和 Value 张量,避免重复计算,是自回归生成的核心优化 |
| PagedAttention | PagedAttention | vLLM 的核心算法,借鉴操作系统虚拟内存分页思想管理 KV Cache,消除显存碎片 |
| Continuous Batching | Continuous Batching | 连续批处理,请求完成后立即释放资源并插入新请求,而非等待整个 batch 完成 |
| Prefill | Prefill | 推理的第一阶段,处理完整输入序列并生成 KV Cache,计算密集型 |
| Decode | Decode | 推理的第二阶段,逐 token 生成输出,访存密集型 |
| TTFT | Time To First Token | 首 token 延迟,从发送请求到收到第一个生成 token 的时间 |
| TPOT | Time Per Output Token | 每个输出 token 的生成时间,决定了流式输出的速度 |
| 推测解码 | Speculative Decoding | 用小模型快速预测多个 token,大模型一次性验证,加速生成过程 |
| LoRA | Low-Rank Adaptation | 低秩适配,一种参数高效的模型微调方法,只训练少量额外参数 |
| 量化 | Quantization | 将模型权重从高精度(FP16)转换为低精度(INT4/INT8/FP8),减少显存占用 |
| AWQ | Activation-aware Weight Quantization | 基于激活感知的权重量化方法,在 4bit 量化中精度损失较小 |
| GPTQ | GPTQ | 基于近似二阶信息的训练后量化方法,压缩率高 |
| MIG | Multi-Instance GPU | NVIDIA 多实例 GPU 技术,将一张 GPU 硬件级分割为多个独立实例 |
| DCGM | Data Center GPU Manager | NVIDIA 数据中心 GPU 管理工具,提供监控、诊断和策略管理 |
-
gpu
+关注
关注
28文章
5259浏览量
136039 -
开源
+关注
关注
3文章
4325浏览量
46427 -
大模型
+关注
关注
2文章
3750浏览量
5268
原文标题:vLLM + K8s:大模型推理服务的弹性部署与GPU调度方案
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
无法在GPU上运行ONNX模型的Benchmark_app怎么解决?
【产品场景】弹性裸金属服务器服务于市场的技术概要分析
用tflite接口调用tensorflow模型进行推理
基于Docker的云资源弹性调度策略
大模型推理服务的弹性部署与GPU调度方案
评论