3. vLLM核心机制:PagedAttention、Continuous Batching、调度器工作原理

要理解vLLM为何能实现高吞吐,必须深入其三大核心机制:PagedAttention(分页注意力)、Continuous Batching(连续批处理)和调度器(Scheduler)。这三者分别解决了显存碎片化、批处理空转和请求动态调度的问题,共同构成了vLLM性能优势的技术底座。

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

传统推理框架在生成前,会为每个请求预先分配一块连续的KV Cache(键值缓存)显存,大小按最大序列长度预留。这导致两个严重问题:内部碎片(预留空间用不满)和外部碎片(不同请求的缓存块之间无法复用)。PagedAttention借鉴操作系统虚拟内存的分页思想,将KV Cache划分为固定大小的块(Block),每个块通常存储16或32个token的KV向量。这些块在物理显存中不必连续,通过一张块表(Block Table)映射逻辑位置到物理块地址。

核心数据结构:

  • 逻辑块:请求视角下的连续KV序列,按token顺序编号。
  • 物理块:显存中实际分配的块,由GPU全局管理。
  • 块表:每个请求维护一张映射表,记录其逻辑块对应的物理块ID。

当生成新token时,只需在块表末尾追加一个物理块映射;当请求结束时,其占用的物理块立即释放,可供其他请求使用。这种机制将显存利用率从传统方案的40%~60%提升至90%以上,且支持跨请求共享(如并行采样时多个输出共享同一前缀的KV块),进一步节省显存。

PagedAttention 块表映射示意 请求逻辑块 逻辑块 0 (token 0-15) 逻辑块 1 (token 16-31) 逻辑块 2 (token 32-47) 逻辑块 3 (token 48-63) 逻辑块 4 (未分配) 块表 (Block Table) 逻辑0 → 物理7 逻辑1 → 物理3 逻辑2 → 物理9 逻辑3 → 物理1 GPU 物理显存块池 块0 块1 块2 块3 块4 块5 块6 块7 块8 块9 块10 块11 块12 块13 块14 块15 本请求占用 其他请求占用 空闲块

上图中,请求的逻辑块0~3分别映射到物理块7、3、9、1,物理块无需连续。当逻辑块4需要分配时,调度器从空闲块池中任选一块(如块5)映射即可。这种设计彻底消除了显存碎片,并允许前缀共享——多个请求共享相同的系统提示词时,只需保存一份物理块。

3.2 Continuous Batching:消除批处理空转

传统静态批处理(Static Batching)中,一个批次必须等待所有请求都完成生成后才整体退出,导致早完成的请求被迫等待,GPU算力白白浪费。Continuous Batching(连续批处理)将“批”的粒度从请求级细化为token级:调度器在每个解码步骤后,立即将已完成的请求移出批次,同时将等待中的新请求加入批次。

这种机制的核心优势在于:

  • 无等待空转:GPU始终在处理有效token,不会因个别慢请求阻塞整个批次。
  • 动态吞吐:新请求无需等待当前批次完全结束,平均排队延迟大幅降低。
  • 资源利用率高:每个解码步的批次大小接近GPU算力上限,而非受限于最慢请求。

关键区别:静态批处理是“同进同出”,连续批处理是“随走随进”。在LLM服务中,由于每个请求的生成长度差异巨大(从几十到上千token),连续批处理可将GPU利用率从静态批处理的50%~70%提升至95%以上。

3.3 调度器:vLLM的“交通指挥官”

调度器(Scheduler)负责决定每个解码步执行哪些请求,以及如何分配显存块。vLLM采用先来先服务(FCFS)与显存水位控制相结合的调度策略。其核心流程如下:

  1. 请求入队:新请求进入等待队列,调度器为其预分配初始逻辑块。
  2. 显存检查:判断当前空闲物理块是否满足请求的最小需求(至少1个块)。
  3. 批次构建:从队首开始,依次将请求加入当前批次,直到显存不足或达到最大批大小。
  4. 迭代执行:对批次内所有请求执行一次前向传播,生成下一个token。
  5. 状态更新:完成生成的请求释放所有物理块并移出批次;未完成的请求继续保留。
  6. 补充新请求:在下一轮迭代前,将等待队列中的新请求加入批次(若显存允许)。

调度器还支持抢占(Preemption)机制:当显存不足时,它会选择将某些请求的KV Cache换出到CPU内存(Swap),或将最早请求的块暂时释放,优先保证新请求的公平性。vLLM通过watermark(水位线)参数控制显存使用阈值,默认在显存使用达到90%时停止接收新请求,预留10%作为安全余量。

3.4 三者协同:一次完整请求的生命周期

假设一个推理服务同时收到3个请求:A(短文本生成)、B(长文本生成)、C(流式输出)。三者协同工作流程如下:

阶段 调度器动作 PagedAttention动作 Continuous Batching效果
初始 将A、B、C加入批次 为每个请求分配逻辑块0 批次大小=3,开始并行生成
第10步 A生成完毕,移出批次 释放A的所有物理块 批次变为{B,C},GPU不空闲
第15步 新请求D入队,加入批次 为D分配刚释放的物理块 批次变为{B,C,D},吞吐不降
第50步 B显存不足,触发抢占 将B的部分块换出到CPU 批次保持{C,D},B稍后恢复

这种协同设计使得vLLM在同等硬件条件下,相比传统框架(如FasterTransformer的静态批处理)可实现2~4倍的吞吐提升,且请求越长、长度差异越大,优势越明显。

3.5 常见误区与调优复盘

误区1:认为PagedAttention只是“节省显存”。实际上它更关键的价值在于消除碎片和支持共享。即使显存总量充足,碎片化也会导致OOM(显存溢出)。

误区2:盲目调大max_num_batched_tokens。虽然增大批大小可提升吞吐,但会显著增加延迟。需根据业务SLA(服务等级协议)平衡吞吐与延迟。

误区3:忽略调度器的抢占开销。频繁抢占会导致KV Cache在CPU与GPU间反复拷贝,反而降低吞吐。应通过合理设置gpu_memory_utilization(默认0.9)预留足够显存,减少抢占发生。

调优建议:生产环境中,建议将gpu_memory_utilization设为0.85~0.92,并监控scheduler_num_preemptions指标。若该值持续增长,说明显存配置过紧,应降低水位线或增加交换空间。同时,对于长度差异极大的混合负载,可考虑启用enable_prefix_caching(前缀缓存),让共享系统提示词的请求复用KV块,进一步降低首token延迟。