3、vLLM核心机制:PagedAttention原理、显存碎片化问题、KV Cache管理

3.1 从传统显存管理到PagedAttention

在大语言模型推理过程中,KV Cache(键值缓存)是决定吞吐量的核心瓶颈。传统推理框架(如HuggingFace Transformers)采用静态连续显存分配策略:在请求开始前,根据最大序列长度一次性预留完整的KV Cache空间。这种方式存在两个严重问题:

  • 内部碎片:实际生成长度远小于最大长度时,预留空间被浪费;
  • 外部碎片:不同请求的KV Cache大小不同,频繁分配释放导致显存空洞。

vLLM借鉴操作系统虚拟内存的分页思想,提出了PagedAttention机制。它将KV Cache划分为固定大小的块(Block),每个块可存储固定数量token的键值张量。逻辑上连续的KV Cache在物理显存中可以不连续存放,通过块表(Block Table)维护映射关系。这种设计使显存利用率接近100%,因为只有实际生成的token才会占用物理块。

核心对比:传统方式按最大长度预留 → 显存浪费率可达60%~80%;PagedAttention按需分配块 → 浪费率降至4%以下(仅块内最后一个块可能部分空闲)。
传统静态分配 请求A:预留 max_len=2048 实际生成 512 token → 浪费 1536 请求B:预留 max_len=2048 实际生成 1024 token → 浪费 1024 请求C:预留 max_len=2048 实际生成 300 token → 浪费 1748 外部碎片:释放后无法合并 显存利用率 ≈ 30%~40% PagedAttention 分块管理 逻辑视图(连续) 0 1 2 3 块表 物理显存(非连续) 块7 块3 块9 多请求按需分配 请求A:3块 已用 2.5 块 请求B:5块 已用 4.8 块 请求C:2块 已用 1.9 块 Prefix共享(如System Prompt) 共享块A 共享块B 请求1独有 请求2独有 显存利用率 ≈ 96%+

3.2 KV Cache管理核心机制

PagedAttention的块管理涉及三个关键数据结构:Block Table(块表)、Free Block List(空闲块列表)和Reference Count(引用计数)。每个请求维护一张块表,记录其逻辑块到物理块的映射。调度器在每次解码迭代中执行以下流程:

  1. 检查:当前请求的最后一个逻辑块是否还有剩余slot;
  2. 分配:若无剩余slot,从空闲列表中取出一个物理块,追加到块表末尾;
  3. 写入:将新token的Key和Value张量写入对应slot;
  4. 释放:请求完成时,遍历其块表,将每个物理块的引用计数减1;若引用计数归零,则归还空闲列表。

vLLM还实现了Copy-on-Write(写时复制)机制用于支持并行采样(beam search)。多个采样序列共享同一前缀的物理块,只有当某个序列需要写入不同内容时才复制该块。这极大减少了多序列生成时的显存开销。

3.3 显存碎片化问题与解决路径

尽管PagedAttention解决了大部分碎片问题,但仍有块内碎片存在:每个物理块固定存储16个token(默认block_size=16),最后一个块可能只使用了部分slot。当请求长度不是16的倍数时,平均浪费约半个块。此外,KV Cache与激活值(Activation)之间的显存竞争也可能导致OOM。

碎片类型 产生原因 PagedAttention的解决方式 残余影响
内部碎片(预留浪费) 按最大长度预留 按需分配物理块 几乎消除
外部碎片(地址空洞) 变长分配/释放 固定大小块+块表映射 完全消除
块内碎片(尾部空闲) block_size固定 无法完全避免 平均浪费 < block_size/2
KV与激活竞争 显存总量固定 通过gpu_memory_utilization预留比例 需合理配置
调优建议:对于短请求占比高的场景,可尝试将--block-size从默认16调小至8,减少块内碎片;但块越小,块表开销越大,需权衡。

3.4 实际案例:显存分配对比

假设GPU显存为40GB,gpu_memory_utilization=0.9,即可用于KV Cache的显存约36GB。模型为7B参数(权重约14GB),剩余22GB用于KV Cache。以block_size=16、每个token的KV Cache占用约0.5MB(以GQA-8B为例)计算:

可用物理块数 = 22GB / (16 × 0.5MB) ≈ 22 × 1024 / 8 = 2816 块

传统方式:每个请求预留2048 token → 每请求占用 2048×0.5MB = 1GB
          最多并发 22 个请求(即使实际生成很短)

PagedAttention:假设平均实际生成512 token → 每请求占用 512×0.5MB = 256MB
          最多并发 88 个请求(按平均长度估算)
          实际吞吐提升约 3~4 倍

在sharegpt数据集上的实测中,vLLM相比HF Transformers的吞吐量提升达8~12倍,其中显存利用率提升贡献了约60%的增益,其余来自连续批处理(Continuous Batching)的调度优化。

3.5 常见误区与复盘

误区一:认为PagedAttention完全消除了显存浪费。
实际上块内碎片仍然存在。当请求长度分布集中在某个非16倍数附近时,浪费可能达到7%~10%。对于极端短请求(如长度1~5),浪费率甚至超过50%。此时应调小block_size。
误区二:盲目调大gpu_memory_utilization。
该参数控制KV Cache可用的显存比例。设置过高会导致激活值(Activation)显存不足,引发OOM或频繁显存交换。建议从0.85开始,逐步上调至刚好不OOM的临界值。
误区三:忽略Prefix Caching的显存收益。
当多个请求共享相同的系统提示词(System Prompt)或Few-shot示例时,vLLM的--enable-prefix-caching可让这些请求共享前缀物理块。在ChatBot场景中,这能额外节省30%~50%的KV Cache显存。

复盘要点:在生产环境中,应结合请求长度分布、并发模式、模型参数量等因素,综合调整block_size、gpu_memory_utilization和max_num_seqs三个核心参数。通过监控vllm:num_requests_running和vllm:gpu_cache_usage_perc指标,可以实时判断显存分配是否健康。当cache使用率持续高于95%且请求排队增加时,说明KV Cache成为瓶颈,应考虑增加gpu_memory_utilization或减少max_num_seqs以降低块竞争。