1. 为什么我们需要PagedAttention?
大语言模型推理过程中的显存管理一直是个棘手问题。传统KV缓存机制存在两大痛点:一是显存碎片化严重,不同请求的KV缓存大小动态变化,导致显存利用率低下;二是无法跨请求共享显存,比如相同前缀的prompt会重复存储。这就好比在传统操作系统中直接分配物理内存给每个进程,既浪费又低效。
PagedAttention的灵感源自操作系统的虚拟内存分页机制。它将KV缓存划分为固定大小的"块"(block),每个块存储固定数量的token的key和value。这种设计带来三个关键优势:
- 显存利用率接近100%,彻底消除碎片化问题
- 支持不同请求间的显存块共享
- 允许非连续存储,像OS管理虚拟内存一样灵活管理显存
2. PagedAttention核心原理拆解
2.1 基本数据结构设计
PagedAttention引入了两种关键数据结构:
class Block: def __init__(self, block_size=16): self.keys = torch.zeros(block_size, head_size) self.values = torch.zeros(block_size, head_size) self.ref_count = 0 # 引用计数 class BlockTable: def __init__(self): self.blocks = [] # 存储block指针 self.block_indices = {} # 逻辑块到物理块的映射每个Block默认存储16个token的KV对,相当于操作系统的"内存页"。BlockTable则维护了逻辑块到物理块的映射关系,类似页表。
2.2 分块注意力计算过程
传统注意力计算:
Attention(Q,K,V) = softmax(QK^T/√d)VPagedAttention的计算需要处理分块存储的K和V:
def paged_attention(query, block_table): output = torch.zeros_like(query) for block in block_table: # 计算当前块的注意力分数 scores = torch.matmul(query, block.keys.transpose()) / sqrt(d) attn = torch.softmax(scores, dim=-1) # 累加各块的注意力结果 output += torch.matmul(attn, block.values) return output这种分块计算方式虽然增加了循环开销,但通过CUDA内核优化可以保持高效。
2.3 显存共享机制
PagedAttention支持两种显存共享:
- 请求内共享:在beam search中,不同beam可能共享前缀序列的KV缓存
- 请求间共享:相同prompt前缀的不同请求可以共享KV块
共享通过引用计数实现:
def share_block(src_table, dst_table, block_idx): block = src_table.get_block(block_idx) block.ref_count += 1 dst_table.add_block(block_idx, block)3. vLLM中的工程实现
3.1 内存管理架构
vLLM采用中心化的BlockManager管理显存:
┌─────────────┐ ┌─────────────┐ │ BlockManager │──────▶│ GPU Memory │ └─────────────┘ └─────────────┘ ▲ ▲ │ │ ┌─────┴─────┐ ┌───────┴───────┐ │ Request 1 │ │ Request 2 │ │ BlockTable │ │ BlockTable │ └───────────┘ └───────────────┘3.2 关键性能优化
- 异步内存拷贝:使用CUDA流实现host-device内存传输与计算重叠
- 块预分配:启动时预分配显存池,避免运行时动态分配开销
- 内存压缩:对低活跃度的块进行FP8量化存储
实测表明,这些优化使vLLM比FasterTransformer快2-4倍,尤其在长序列场景下优势更明显。
4. 实践中的常见问题
4.1 显存不足排查
当出现OOM错误时,建议检查:
--block-size参数是否设置合理(默认16)- 是否启用了
--enable-chunked-prefix共享前缀 - 使用
nvidia-smi -l 1监控显存波动
4.2 性能调优技巧
- 对于70B以上模型,建议设置
--block-size=8减少浪费 - 多用户场景下,增加
--max-num-blocks预留更多显存 - 使用
--warmup参数预加载模型可以减少首次请求延迟
5. 进阶应用场景
5.1 长文本处理
通过分块机制,vLLM可以处理远超显存容量的长文本。我们测试中成功在24GB显存上运行32K tokens的输入。
5.2 多模态扩展
PagedAttention思想可扩展到视觉tokens管理。实验性分支已支持将图像patch分块存储。
我在部署Qwen-72B模型时发现,结合PagedAttention和FP8量化,可以将单卡支持的并发数从3提升到9。实际部署时要注意,不同解码策略(如beam search和sampling)对块共享的影响差异很大。建议针对具体场景进行压力测试,找到最优的块大小和预分配策略。