vLLM + K8s:大模型推理服务的弹性部署与GPU调度方案
GPU 显存管理:7B 模型 FP16 推理需要约 14GB 显存,70B 模型需要 140GB+,KV Cache 随并发数线性增长,显存碎片化导致实际利用率不足 60%高并发低延迟:在线服务要求 P99 延迟可控,传统静态批处理在请求长度差异大时效率低下弹性伸缩:GPU 资源昂贵(A100 单卡约 $2/h),流量波谷时需要快速缩容降本多模型管理:生产环境通常同时运行多个模型版本,需要灰度发布
一、概述
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 \
--set driver.enabled=false \
--set toolkit.enabled=true \
--set devicePlugin.enabled=true \
--set migManager.enabled=true \
--set dcgmExporter.enabled=true \
--version v24.6.2
# 等待所有组件就绪
kubectl -n gpu-operator get pods -w
2.1.3 GPU 资源验证
# 确认节点已注册 GPU 资源
kubectl describe node <gpu-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:-http://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() {
local req_id=$1
local start_time end_time latency_ms status_code body
# 构造请求体
body=$(cat <<REQEOF
{
"model": "${MODEL_NAME}",
"messages": [
{"role": "system", "content": "你是一个技术助手。"},
{"role": "user", "content": "请用 200 字简要介绍 Kubernetes 的核心架构设计理念。请求编号:${req_id}"}
],
"max_tokens": ${MAX_TOKENS},
"temperature": 0.7
}
REQEOF
)
start_time=$(date +%s%N)
# 发送请求并捕获响应
local response
response=$(curl -s -w "\n%{http_code}" \
-X POST "${API_URL}" \
-H "Content-Type: application/json" \
-d "${body}" \
--max-time 120 2>/dev/null) || true
end_time=$(date +%s%N)
# 解析响应
status_code=$(echo"${response}" | tail -1)
local resp_body
resp_body=$(echo"${response}" | sed '$d')
# 计算延迟(毫秒)
latency_ms=$(( (end_time - start_time) / 1000000 ))
# 提取生成的 token 数
local tokens
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
export API_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}"
# 成功率
local success_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\n", latencies[1]
printf " P50: %d\n", latencies[int(count*0.5)]
printf " P90: %d\n", latencies[int(count*0.9)]
printf " P95: %d\n", latencies[int(count*0.95)]
printf " P99: %d\n", latencies[int(count*0.99)]
printf " 最大值: %d\n", latencies[count]
printf " 平均值: %d\n", sum/count
}'"${RESULT_FILE}"
# Token 吞吐
local total_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 | while IFS=', '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 镜像加速下载 export VLLM_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 |
降低 |
Model not found
/ |
模型路径错误或文件不完整 |
检查挂载路径;用 |
tensor parallel size does not match |
TP 数与可用 GPU 数不一致 |
确认 |
Health check timeout
→ Pod 反复重启 |
startupProbe
超时时间不够 |
增大 |
torch.cuda.CudaError: invalid device ordinal |
GPU 设备号不连续或 NVIDIA 驱动异常 |
检查 |
KV cache is too small |
max-model-len
过大导致 KV Cache 分配失败 |
减小 |
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 <pod-name> -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 <pod> -- ls -la /models/
问题四:请求超时(504 Gateway Timeout)
# 诊断:确认超时发生在哪一层
# 1. vLLM 本身是否响应
kubectl exec -it <pod> -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 \
--set serviceMonitor.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:-https://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
# 校验关键文件完整性
for f in config.json tokenizer.json; do
if [ ! -f "${LOCAL_DIR}/${f}" ]; then
echo"[错误] 缺少关键文件: ${f}"
exit 1
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\n" \
"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 管理工具,提供监控、诊断和策略管理 |
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐

所有评论(0)