vLLM 性能基石

vLLM 的核心性能优势来自三大技术:

技术解决的问题性能提升
PagedAttentionKV Cache 内存碎片2-4x 吞吐
连续批处理静态批处理浪费3-8x 吞吐
高效注意力标准 Attention 慢2x 推理速度

PagedAttention 原理

传统 KV Cache 为每个请求预分配连续内存,导致大量碎片:

传统方式:
[请求A: ████░░░░] [请求B: ██░░░░░░] [请求C: ██████░░]
  预分配8块, 用4块     预分配8块, 用2块      预分配8块, 用6块
  浪费4块              浪费6块               浪费2块
  总利用率: 12/24 = 50%

PagedAttention:
[块0:A1] [块1:A2] [块2:B1] [块3:C1] [块4:C2] [块5:A3] [块6:C3] [块7:空闲]
  按需分配, 几乎无浪费
  总利用率: 7/8 = 87.5%

KV Cache 被分成固定大小的 block(通常 16 token/block),通过页表映射,类似操作系统的虚拟内存。

from vllm import LLM, SamplingParams

# PagedAttention 默认启用, 通过参数调整
llm = LLM(
    model="meta-llama/Llama-3-8B",
    # PagedAttention 相关
    block_size=16,              # KV Cache block 大小 (token数)
    swap_space=4,               # CPU swap 空间 (GB)
    gpu_memory_utilization=0.9, # GPU 显存利用率上限
)

block_size 选择

block_size内存利用率查找开销推荐
8最高短序列
16默认推荐
32最低长序列

连续批处理

传统批处理等待所有请求完成才能释放,连续批处理在请求完成后立即加入新请求:

传统静态批处理:
时间 →
Req A: [生成中████████████]     ← 慢请求拖累全队
Req B: [生成████]                ← 完成但要等 A
Req C: [████████████████████]   ← 完成但要等 A
                              批次完成后才能接新请求

连续批处理 (Continuous Batching):
Req A: [生成中████████████]
Req B: [生成████]→完成→新Req D加入
Req C: [████████████████████]→完成→新Req E加入
                    ↑ 空位立即填充, 吞吐最大化
# vLLM 服务端连续批处理 (API 模式)
# 启动 API server
# vllm serve meta-llama/Llama-3-8B \
#   --host 0.0.0.0 --port 8000 \
#   --max-num-seqs 256 \        # 最大并发序列数
#   --max-num-batched-tokens 8192  # 单批次最大 token

from vllm import LLM, SamplingParams

# 离线批量推理也支持连续批处理
llm = LLM(
    model="meta-llama/Llama-3-8B",
    max_num_seqs=256,                # 最大并发序列
    max_num_batched_tokens=8192,     # 单批次最大 token 数
    enable_chunked_prefill=True,     # 分块 prefill, 减少 tail latency
)

prompts = [f"请写一首关于秋天的诗,第{i}首" for i in range(1000)]
sampling = SamplingParams(
    temperature=0.7,
    max_tokens=256,
)

# vLLM 自动用连续批处理
outputs = llm.generate(prompts, sampling)

关键参数调优

参数作用推荐值
max_num_seqs最大并发数128-256 (A100)
max_num_batched_tokens批次 token 上限4096-8192
enable_chunked_prefill分块 prefillTrue (推荐)

张量并行

大模型单卡放不下,需要将模型切分到多卡:

张量并行 (TP=2):
GPU 0: 负责 attention 的前半部分 + FFN 的前半部分
GPU 1: 负责 attention 的后半部分 + FFN 的后半部分
每层都需要 AllReduce 通信
# 方式1: Python API
llm = LLM(
    model="meta-llama/Llama-3-70B",
    tensor_parallel_size=4,       # 4卡张量并行
    pipeline_parallel_size=1,     # 流水线并行度
    dtype="bfloat16",
)

# 方式2: API Server
# vllm serve meta-llama/Llama-3-70B \
#   --tensor-parallel-size 4 \
#   --pipeline-parallel-size 1

TP vs PP 选择

方式通信量显存均衡适用场景
张量并行 (TP)高(每层AllReduce)单机多卡
流水线并行 (PP)低(仅层间传递)一般多机部署
TP + PP超大模型多机
# 8卡部署 70B 模型: TP=4, PP=2
llm = LLM(
    model="meta-llama/Llama-3-70B",
    tensor_parallel_size=4,
    pipeline_parallel_size=2,
    dtype="bfloat16",
)

流水线并行

PP=2 示例:
GPU 0: Layer 0-31    → 中间结果发送到 GPU 1
GPU 1: Layer 32-63   → 接收并完成推理
# vLLM 的流水线并行
llm = LLM(
    model="meta-llama/Llama-3-70B",
    pipeline_parallel_size=2,
    tensor_parallel_size=2,    # TP + PP 组合
    dtype="bfloat16",
)

# 多机部署 (每台机器4卡, 2台共8卡)
# 节点0:
# vllm serve meta-llama/Llama-3-70B \
#   --tensor-parallel-size 4 \
#   --pipeline-parallel-size 2 \
#   --distributed-executor-backend ray
# 节点1: 同样命令 + Ray join

量化推理

# AWQ 量化 (推荐, 精度损失最小)
llm = LLM(
    model="TheBloke/Llama-3-70B-AWQ",
    quantization="awq",
    dtype="float16",
)

# GPTQ 量化
llm = LLM(
    model="TheBloke/Llama-3-70B-GPTQ",
    quantization="gptq",
    dtype="float16",
)

# FP8 量化 (H100 专属, 极致性能)
llm = LLM(
    model="meta-llama/Llama-3-70B",
    quantization="fp8",
    dtype="bfloat16",
)

量化方案对比

方案显存 (70B)精度损失推理速度硬件要求
BF16140GB0%1xA100 80G×2
FP870GB<1%1.5xH100
AWQ40GB~2%1.3x通用GPU
GPTQ40GB~3%1.2x通用GPU

LoRA 动态加载

vLLM 支持同时服务多个 LoRA adapter,动态切换:

from vllm import LLM, SamplingParams
from vllm.lora.request import LoRARequest

# 基座模型
llm = LLM(
    model="meta-llama/Llama-3-8B",
    enable_lora=True,
    max_loras=4,           # 同时加载的 LoRA 数
    max_lora_rank=64,      # 最大 LoRA rank
    max_cpu_loras=16,      # CPU 缓存的 LoRA 数
)

# 加载 LoRA adapters
# 语法: LoRARequest(lora_name, lora_id, lora_path)
lora_code = LoRARequest("code_assistant", 1, "./loras/code-v1")
lora_translate = LoRARequest("translator", 2, "./loras/translate-v1")
lora_chat = LoRARequest("chatbot", 3, "./loras/chat-v1")

# 不同请求用不同 LoRA
outputs = llm.generate(
    ["写一个 Python 排序函数", "Translate: Hello world", "你好"],
    SamplingParams(temperature=0.7, max_tokens=256),
    [
        lora_code,        # 第一个请求用 code LoRA
        lora_translate,   # 第二个用 translate LoRA
        lora_chat,        # 第三个用 chat LoRA
    ],
)

# API Server 模式
# vllm serve meta-llama/Llama-3-8B \
#   --enable-lora \
#   --lora-modules code=./loras/code-v1 translate=./loras/translate-v1 \
#   --max-loras 4 --max-lora-rank 64

# 请求时指定 LoRA
# curl http://localhost:8000/v1/completions \
#   -H "Content-Type: application/json" \
#   -d '{"model": "code", "prompt": "写排序函数", "max_tokens": 256}'

监控与调优

Prometheus 指标

vLLM 内置 Prometheus 指标导出:

# 启动时自动暴露 /metrics 端点
# vllm serve meta-llama/Llama-3-8B \
#   --host 0.0.0.0 --port 8000

关键监控指标:

指标含义告警阈值
vllm:num_requests_running正在处理的请求持续=max_num_seqs
vllm:num_requests_waiting排队请求>50
vllm:gpu_cache_usageKV Cache 使用率>90%
vllm:time_to_first_token首 token 延迟>500ms
vllm:time_per_output_token生成延迟>50ms/token
vllm:request_success_total成功请求数-

性能调优检查清单

# 推荐生产配置 (A100 80G × 4)
PROD_CONFIG = {
    # 模型
    "model": "meta-llama/Llama-3-70B",
    "dtype": "bfloat16",
    
    # 并行
    "tensor_parallel_size": 4,
    
    # 批处理
    "max_num_seqs": 128,
    "max_num_batched_tokens": 8192,
    "enable_chunked_prefill": True,
    
    # KV Cache
    "gpu_memory_utilization": 0.9,
    "swap_space": 4,
    "block_size": 16,
    
    # 量化 (可选, 省2x显存)
    # "quantization": "fp8",
    
    # LoRA (可选)
    # "enable_lora": True,
    # "max_loras": 4,
    
    # 超时
    "enforce_eager": False,  # False=用CUDA Graph, True=省编译时间
}

常见性能问题

问题原因解决方案
首 token 延迟高prefill 计算量大开启 chunked_prefill
吞吐量低批次太小增大 max_num_seqs
OOMKV Cache 不够降 gpu_memory_utilization 或用量化
排队过多并发超限加机器或增 max_num_seqs
长请求超时超过 max_tokens调大 max_model_len

实战建议

  1. 先量后调:用 vLLM 自带 benchmark 脚本测基线性能
  2. gpu_memory_utilization 不要拉满:留 5-10% 给系统和其他进程
  3. CUDA Graph 默认开:enforce_eager=False 有 20-30% 加速
  4. 量化优先用 AWQ:通用性最好,精度损失最小
  5. LoRA 多任务首选 vLLM:动态加载无额外延迟,比手动切换高效百倍
  6. 监控必有:没有监控的 vLLM 是黑盒,排障靠猜

加入讨论

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

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