3、vLLM核心概念:PagedAttention、Continuous Batching、KV Cache等原理

在生产环境中部署大语言模型(LLM)时,我们面对的不仅是模型本身的精度问题,更关键的是推理引擎的吞吐量、显存利用率和延迟表现。vLLM之所以能在众多推理框架中脱颖而出,核心在于它重新设计了LLM推理时的显存管理与调度机制。本章将深入剖析vLLM的三大支柱:KV Cache、PagedAttention与Continuous Batching,帮助你从原理层面理解vLLM为何“快”,以及在生产调优时应该关注哪些参数。

3.1 KV Cache:Transformer解码的“中间产物”

在自回归生成过程中,模型每生成一个token,都需要计算注意力分数。以“中国的首都是”为例,生成“北”时,模型需要让“北”与前面所有token(中、国、的、首、都、是)计算注意力。若每次生成都重新计算所有token的Key(键)和Value(值),计算量将随序列长度呈平方级增长。

KV Cache的核心思想是:将已生成token的Key矩阵和Value矩阵缓存下来,后续生成新token时,只需计算新token的Query(查询),并与缓存的Key、Value做注意力运算。这样,计算量从O(n²)降为O(n),大幅提升推理速度。

关键理解:KV Cache不是vLLM的发明,而是Transformer推理的标准优化手段。vLLM的贡献在于如何高效管理KV Cache的显存。

KV Cache的大小由以下公式决定:

KV Cache大小 = 2(K和V) × 层数 × 注意力头数 × 头维度 × 序列长度 × 精度字节数

以Llama-2-7B为例(32层,32头,头维度128,FP16),每token的KV Cache占用:

2 × 32 × 32 × 128 × 2字节 = 512KB/token

若并发处理100个请求,每个请求生成1024个token,仅KV Cache就需要:

512KB × 1024 × 100 = 50GB

这远超模型权重本身(约14GB)。因此,KV Cache的显存管理直接决定了系统能并发处理多少请求。

KV Cache 与 PagedAttention 原理对比 ❌ 无KV Cache:重复计算 Token 1 K1 V1 Token 2 K2 V2 Token 3 K3 V3 重复 计算 Q 每次生成新token,都要重新计算所有历史token的K、V ✅ 有KV Cache:缓存复用 KV Cache 缓存 K1,V1 | K2,V2 | K3,V3 已计算并存储 Token 4 Q4 读取 只需计算新token的Q,与缓存的K、V做注意力运算 PagedAttention:将KV Cache划分为固定大小的块(Block) 逻辑视图(连续) Block 0 Block 1 Block 2 Block 3 物理视图(非连续) Block 0 Block 2 Block 1 Block 3 通过块表(Block Table)维护逻辑块到物理块的映射,显存碎片率降低至接近0

3.2 PagedAttention:操作系统的“虚拟内存”思想

传统推理框架(如HuggingFace Transformers)在显存中为每个请求的KV Cache分配连续且固定大小的空间。这种方式存在两个严重问题:

  • 显存碎片化:不同请求的生成长度不同,预先分配的最大长度空间往往被浪费(内部碎片)。
  • 无法共享:即使多个请求共享相同的前缀(如系统提示词),也无法复用KV Cache。

PagedAttention借鉴操作系统虚拟内存的分页机制,将KV Cache划分为固定大小的块(Block)(默认16个token/块)。每个请求的逻辑KV Cache通过块表(Block Table)映射到物理显存中的任意位置,无需连续分配。

生产启示:PagedAttention使得显存利用率接近100%,在相同显存下可并发处理2~4倍的请求。这也是vLLM吞吐量远超传统框架的根本原因。

PagedAttention还支持块共享(Copy-on-Write)。当多个请求共享相同前缀时,它们可以指向同一物理块,仅在需要写入不同内容时才复制。这在并行采样(同一提示词生成多个候选)和beam search场景中效果显著。

3.3 Continuous Batching:动态调度请求

传统推理采用静态批处理(Static Batching):收集一批请求,等所有请求都生成完成后,再处理下一批。这导致两个问题:

  • 短请求需要等待长请求完成,GPU利用率波动大。
  • 新到达的请求必须等待当前批次全部结束才能进入。

Continuous Batching(连续批处理)将调度粒度从“请求级别”细化到“token级别”。vLLM的调度器在每个解码步骤后,动态决定:

  1. 哪些请求继续生成下一个token;
  2. 哪些请求已完成,释放其KV Cache块;
  3. 哪些新请求可以加入当前批次。

调度器的工作流程如下:

每个解码步骤:
1. 遍历当前批次中的请求
2. 若请求已生成结束标记(EOS)或达到max_tokens → 标记完成,释放KV Cache块
3. 若请求仍在生成中 → 保留在批次中,继续前向传播
4. 检查等待队列,若显存中有空闲块 → 将新请求加入批次
5. 执行一次前向传播,生成所有活跃请求的下一个token

这种机制让GPU始终处理“最需要计算”的请求,避免了等待空闲。在混合负载(短查询+长生成)场景下,Continuous Batching可将吞吐量提升10~20倍。

3.4 三者协同:vLLM的推理引擎

三大机制并非孤立工作,而是通过vLLM的调度器(Scheduler)紧密配合:

组件 职责 生产影响
KV Cache 缓存历史token的K、V矩阵 决定单请求的显存占用
PagedAttention 分块管理KV Cache,支持非连续存储与共享 决定显存利用率和并发上限
Continuous Batching 动态调度请求,按token粒度决定去留 决定GPU利用率和响应延迟

调度器维护一个等待队列(Waiting Queue)和一个运行批次(Running Batch)。每个解码步骤,调度器根据显存剩余块数(由PagedAttention的块表计算)决定是否接纳新请求。当请求完成时,其占用的物理块被释放,立即供新请求使用。

3.5 生产环境中的关键参数与调优

理解原理后,在生产部署时你需要关注以下vLLM参数:

--max-num-seqs:最大并发序列数(受显存限制)
--max-model-len:模型最大上下文长度(决定KV Cache预留上限)
--gpu-memory-utilization:GPU显存利用率上限(默认0.9)
--block-size:PagedAttention块大小(默认16,可调为32)
--enable-prefix-caching:启用前缀缓存(共享前缀场景收益大)

常见误区:盲目调大--max-num-seqs会导致KV Cache溢出,触发显存不足(OOM)。正确做法是:先根据gpu-memory-utilization计算可用显存,再除以单请求KV Cache峰值,得到安全并发上限。

例如,A100 80GB部署Llama-2-7B(权重约14GB),设置gpu-memory-utilization=0.9,则KV Cache可用显存约为:

80GB × 0.9 - 14GB = 58GB
单请求最大KV Cache(max_model_len=4096):
512KB × 4096 = 2GB
安全并发上限 ≈ 58GB / 2GB = 29个请求

实际生产环境中,由于请求长度分布不均,可适当调高并发数,但需监控显存水位,避免OOM导致服务崩溃。

3.6 案例复盘:从“OOM频发”到“吞吐翻倍”

某金融客服场景,使用vLLM部署7B模型,最初配置如下:

--max-num-seqs 64
--max-model-len 2048
--gpu-memory-utilization 0.85

运行后发现:GPU利用率仅40%,且频繁出现OOM。排查过程:

  1. 查看vLLM日志,发现KV Cache blocks: 12000 used / 15000 total,说明显存已满。
  2. 分析请求长度分布,发现平均生成长度仅300 token,但max_model_len=2048导致每个请求预留了2048 token的KV Cache空间。
  3. 将max-model-len调整为1024(业务最长请求约800 token),并发数提升至128。
  4. 开启enable-prefix-caching,系统提示词(约200 token)被所有请求共享,KV Cache占用进一步降低30%。

优化后结果:吞吐量从120 req/min提升至350 req/min,OOM完全消失。这个案例说明:理解KV Cache的显存计算方式,是生产调优的基础。

核心复盘:vLLM的性能优势并非“免费午餐”。PagedAttention解决了显存碎片问题,但max-model-len和gpu-memory-utilization的配置仍需根据实际业务请求长度分布精细调整。建议上线前统计生产环境的请求长度分布,据此设置参数。