一、概述

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

降低 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"

解决方案:

  1. 降低 --gpu-memory-utilization 到 0.85

  2. 减小 --max-model-len(如从 32768 降到 8192)

  3. 减小 --max-num-seqs(限制并发请求数)

  4. 换用量化模型(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"

解决方案:

  1. 排队请求多 → 扩容副本数或增大 --max-num-seqs

  2. 出现 swap/preempt → 减小 --max-model-len 或增加 GPU 显存

  3. 单个请求输入过长 → 业务侧限制输入长度

问题三:Pod 启动失败(模型加载阶段)

# 诊断:查看 Pod 状态和事件
kubectl get pods -n llm-serving -o wide
kubectl describe pod <pod-name> -n llm-serving

# 常见事件及含义
# FailedScheduling → GPU 资源不足,没有可用节点
# OOMKilled → 内存限制过低,模型加载时被杀
# CrashLoopBackOff → 启动失败后反复重启

解决方案:

  1. FailedScheduling → 检查集群 GPU 资源:kubectl describe nodes | grep -A 5 "nvidia.com/gpu"

  2. OOMKilled → 增大 resources.limits.memory,7B 模型至少 20Gi,70B 至少 80Gi

  3. 模型文件不完整 → 进入 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"

解决方案:

  1. Ingress 超时 → 增大 proxy-read-timeout(LLM 生成长文本需要 60s+)

  2. vLLM 响应慢 → 检查 GPU 负载,考虑扩容

  3. 单个请求生成 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-utilizationmax-model-lenmax-num-seqs 三个参数构成显存分配的核心调优三角,需要根据模型大小和业务场景综合调整

  • ✅ 量化策略:AWQ 4bit 是通用性最好的量化方案,FP8 在 Hopper 架构上精度损失最小,选择量化方案前先评估显存是否真的不够用

  • ✅ 高可用保障:PDB + 反亲和性 + 合理的健康检查配置 + 优雅关闭,四层防护确保服务稳定性

  • ✅ 监控体系:vLLM 原生指标 + DCGM Exporter + Prometheus + Grafana 构成完整的可观测性方案,重点关注 KV Cache 使用率和排队请求数

6.2 进阶方向

  1. Speculative Decoding(推测解码):使用小模型(Draft Model)预测多个 token,大模型一次性验证,在不损失精度的前提下提升生成速度 1.5~2 倍。vLLM 已支持 --speculative-model 参数

    • 适用场景:对延迟敏感的在线服务

    • 限制:需要额外显存加载 Draft Model

  2. LoRA 动态加载:vLLM 支持在运行时动态加载/卸载 LoRA 适配器,无需为每个微调模型部署独立实例

    • 启动参数:--enable-lora --max-loras 4 --max-lora-rank 64

    • 请求时指定:"model": "base-model:lora-adapter-name"

    • 适用场景:多租户场景,每个客户有独立的微调模型

  3. 多模态模型推理:vLLM 已支持 LLaVA、Qwen-VL 等视觉语言模型的推理,可处理图文混合输入

    • 注意:图片预处理会占用额外 CPU 和内存,需要调整资源配置

  4. 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 管理工具,提供监控、诊断和策略管理

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐