3. vLLM基础

3.1 为什么传统推理服务难以支撑高并发

在深入 vLLM 之前,我们先理解传统 LLM 推理服务的瓶颈。大多数推理框架采用“静态批处理”(Static Batching)方式:请求到达后,要么等待当前批次全部生成完毕,要么单独占用 GPU 资源。这种方式存在两个致命问题:显存碎片化和计算等待。每个请求的 KV Cache(键值缓存)在显存中按最大可能长度预分配,导致大量显存被闲置;同时,批次内所有序列必须同步完成生成,短请求被迫等待长请求。

vLLM 的核心目标就是解决这两个问题,其实现依赖两项关键技术:PagedAttention 和 Continuous Batching。

3.2 PagedAttention:显存管理的“虚拟内存”方案

PagedAttention 借鉴操作系统中的虚拟内存分页思想。传统方案为每个序列分配连续的显存块,而 PagedAttention 将 KV Cache 划分为固定大小的“块”(Block,通常为 16 或 32 个 的 KV 数据)。每个序列的 KV Cache 可以存储在多个不连续的物理块中,通过一张块表(Block Table)记录逻辑块到物理块的映射。

核心优势:
  • 显存利用率接近 100%:仅按实际使用量分配块,消除内部碎片和预留浪费。
  • 共享前缀:多个序列共享相同系统提示词或上下文时,可共享同一物理块,大幅降低显存占用。
  • 灵活增长:生成新 时动态分配新块,无需预先为最大长度预留空间。

下图展示了 PagedAttention 的块表映射机制,以及两个序列共享前缀块的情形:

序列 A 逻辑块 块 0 块 1 块 2 块 3 序列 B 逻辑块 块 0 块 1 块表(逻辑→物理映射) A: 0→P0, 1→P2 A: 2→P5, 3→P7 B: 0→P0, 1→P3 物理显存块 P0 P1 P2 P3 P4 P5 P6 P7 P0(A/B 共享前缀) 序列A独占 序列B独占 共享块

图中可见,序列 A 的逻辑块 0 和序列 B 的逻辑块 0 都映射到物理块 P0,实现了前缀共享;而逻辑块 1 分别映射到 P2 和 P3,互不干扰。这种机制使得 vLLM 在长上下文场景下显存占用比传统方案降低 50%~80%。

3.3 Continuous Batching:动态调度与请求级抢占

Continuous Batching(连续批处理)打破了“批次同步完成”的限制。调度器以迭代级别(Iteration-level)进行调度:每完成一个 的生成,就检查是否有新请求可以加入、是否有请求已完成。GPU 不再等待整个批次结束,而是持续处理不同进度的序列。

具体流程如下:

  1. 初始批次包含 3 个请求,各自处于不同生成阶段。
  2. 每步迭代后,请求 A 提前生成完毕(遇到 EOS 或达到 max_tokens),立即释放其占用的 KV Cache 块。
  3. 调度器将等待队列中的请求 D 插入当前批次,复用刚释放的显存块。
  4. 批次内剩余请求继续生成,新请求从 prefill 阶段开始。
关键区别:传统静态批处理中,一个慢请求会拖慢整个批次;Continuous Batching 中,慢请求不会阻塞其他请求的完成和退出,GPU 吞吐量可提升 2~10 倍(取决于请求长度分布)。

3.4 vLLM 基本用法:从启动到调用

vLLM 提供两种主要使用方式:离线批处理(Python API)和在线服务( 兼容服务器)。

3.4.1 离线批处理

from vllm import LLM, SamplingParams

# 初始化模型,自动启用 PagedAttention 和 Continuous Batching
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct", gpu_memory_utilization=0.85)

# 定义采样参数
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
    stop=["</s>"]
)

# 批量推理
prompts = ["解释什么是量子纠缠", "写一首关于秋天的诗", "总结这篇文章的要点"]
outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    print(output.prompt, "→", output.outputs[0].text)

关键参数说明:

参数作用推荐值
gpu_memory_utilization控制用于 KV Cache 的显存比例0.80~0.90
max_num_seqs单批次最大序列数256~1024
max_model_len模型最大上下文长度与模型配置一致
enable_prefix_caching启用前缀缓存,加速重复前缀请求True

3.4.2 在线服务

# 启动  兼容服务器
python -m vllm.entrypoints..api_server \
    --model Qwen/Qwen2.5-7B-Instruct \
    --port 8000 \
    --gpu-memory-utilization 0.85 \
    --max-num-seqs 512

# 客户端调用(兼容  SDK)
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [{"role": "user", "content": "你好"}],
    "max_tokens": 128
  }'

3.5 案例:多模型混合部署的批处理效果

假设我们在一台 8×A100(80GB)服务器上部署 3 个模型:Qwen2.5-7B、Llama-3-8B、Mistral-7B。使用 vLLM 分别启动 3 个服务实例,每个实例分配 2 张 GPU。通过 Nginx 做负载均衡,将不同业务请求路由到对应模型。

实测数据(请求长度 200~2000 混合):

  • 传统静态批处理:GPU 利用率约 35%,平均吞吐 1200 /s。
  • vLLM Continuous Batching:GPU 利用率提升至 78%,平均吞吐 4200 /s。
  • 启用前缀缓存后,系统提示词相同的请求延迟降低 60%。

3.6 常见误区与复盘

误区 1:认为 vLLM 只适合长文本场景。
实际上,即使短文本(如 50 ),PagedAttention 的块分配机制也能减少显存浪费,只是收益不如长文本明显。但 Continuous Batching 对短请求的吞吐提升依然显著。
误区 2:盲目调大 max_num_seqs。
增大批次大小会提高吞吐,但也会增加显存压力。当 KV Cache 不足时,vLLM 会触发“抢占”(Preemption),将部分序列的 KV Cache 换出到 CPU,反而增加延迟。建议根据实际显存余量逐步调整。
误区 3:忽略前缀缓存的作用。
在多轮对话或 RAG 场景中,大量请求共享相同的系统提示词或检索上下文。不开启 enable_prefix_caching 会浪费显存和计算资源。开启后,共享前缀的 KV Cache 只需计算一次。
复盘建议:在生产环境上线前,使用 vLLM 自带的 benchmark 脚本(benchmarks/benchmark_serving.py)压测不同并发下的吞吐和延迟曲线,找到最优的 max_num_seqs 和 gpu_memory_utilization 组合。同时监控 GPU 显存碎片率,若碎片率超过 15%,考虑调整块大小或升级 vLLM 版本。