一、小白入门:先搞懂核心概念(无技术门槛)

1. 为什么需要PageAttention?

LLM(大语言模型)推理时,KV Cache 是核心(存储每一层的Key和Value矩阵,避免重复计算),但传统KV Cache有两个致命问题:

  • 内存碎片化:每个请求的KV Cache占连续内存,长请求/多请求时,显存里全是“小碎片”,利用率极低;
  • 内存浪费:即使请求只生成几个token,也要分配一整块大内存,空着的部分无法复用。

PageAttention的核心思路:借鉴操作系统的“内存分页”思想,把KV Cache切成固定大小的“小方块(Block)”,像书架上的固定尺寸书格——每个书格放固定数量的书(token),空书格可以给其他请求用,彻底解决碎片化和浪费问题。

2. 小白视角的Block(块)

  • 本质:GPU显存里的“固定大小内存单元”,是KV Cache的最小管理单位;
  • 存储内容(基础):每个Block存 N 个token的K和V数据(N叫block_size,vLLM默认64)。比如一个Block能存64个token的Key矩阵+64个token的Value矩阵;
  • 类比:把KV Cache看成一本厚书,Block就是这本书的“页码”——每页固定64行(对应64个token),每行存该token的K和V数据,空行可以留给后续内容。

二、基础进阶:PageAttention的核心实现逻辑(懂基础编程即可)

先明确两个关键术语(承上启下):

  • 物理Block:GPU显存中实际分配的内存块(真正存数据的地方);
  • 虚拟Block:每个请求的KV Cache被抽象成“虚拟Block列表”,映射到物理Block(类似电脑的“虚拟地址→物理地址”)。

1. PageAttention的核心工作流程(4步)

初始化Block池

处理请求:分配Block存KV

Attention计算:加载有效Block

请求结束:释放Block回池

步骤1:初始化Block池
  • 启动vLLM时,先在GPU显存里预分配一批物理Block(比如1000个),每个Block大小固定(block_size=64);
  • 每个Transformer层都有独立的Block池(因为每层的KV Cache是独立的);
  • 这些Block初始状态是“空闲”,形成一个“Block池”,供所有请求复用。
步骤2:处理请求(存储KV Cache)

假设你发送一个请求:“你好,今天天气怎么样?”(对应8个token):

  • 模型计算前8个token的K和V,从Block池里申请1个物理Block(8<64,够用);
  • 把8个token的K/V存在这个Block的前8个“位置(Slot)”里,剩下的56个Slot空着;
  • 记录“虚拟-物理映射表”:比如“请求A的第0个虚拟Block → 物理Block ID=10”;
  • 当请求继续生成token(比如生成第9个token),直接存在这个Block的第9个Slot里,直到64个Slot占满;
  • 占满后,再申请新的物理Block,更新映射表(比如“请求A的第1个虚拟Block → 物理Block ID=25”)。
步骤3:Attention计算(PageAttention的核心差异)

传统Attention需要加载“连续的整块KV Cache”,而PageAttention只加载“有效Block”:

  1. 先查当前请求的映射表,拿到所有用到的物理Block ID(比如请求A用到ID10、ID25);
  2. 只加载这些Block里的有效Slot(ID10用了64个,ID25用了12个),忽略空Slot;
  3. 用“稀疏索引”标记有效Slot,只对这些Slot做Attention计算(Q@K^T → softmax → 乘V),减少计算量和内存带宽。
步骤4:释放Block

请求生成完成后,它占用的物理Block会被标记为“空闲”,放回Block池,供其他请求复用(比如下一个请求可以直接用ID10的Block)。

2. 基础版Block结构(伪代码)

# 小白能看懂的Block类(简化版)
class KVBlock:
    def __init__(self, block_id, block_size=64, num_heads=32, head_dim=128):
        self.block_id = block_id          # Block唯一ID(比如10、25)
        self.block_size = block_size      # 每个Block能存的token数(默认64)
        # 核心存储:K和V矩阵(GPU显存上的张量,FP16格式节省内存)
        self.k = torch.zeros((block_size, num_heads, head_dim), device="cuda")  # Key
        self.v = torch.zeros((block_size, num_heads, head_dim), device="cuda")  # Value
        self.num_valid_tokens = 0         # 该Block已使用的token数(比如8、64)
        self.is_free = True               # 是否空闲(True=空闲,False=被占用)
        self.seq_id = None                # 所属请求ID(比如请求A的ID=1)

三、深度进阶:PageAttention的技术细节 + Block完整结构(源码级)

1. PageAttention的底层实现(CUDA核函数级)

vLLM的PageAttention是基于CUDA核函数(GPU并行编程)实现的,核心优化点:

  • 稀疏Block索引:用数组存储每个请求的物理Block ID列表,以及每个Block的有效token数,避免加载空数据;
  • 分块矩阵乘法(Blocked MatMul):将Q/K/V按Block大小分块,利用GPU的共享内存(Shared Memory)加速计算(共享内存比全局内存快100倍+);
  • 动态批处理:多个请求的Block可以混合计算,最大化GPU并行性(比如同时处理请求A的ID10和请求B的ID15)。
核心伪代码(接近vLLM源码逻辑)
import torch
import math

# Block池管理
class BlockPool:
    def __init__(self, num_blocks=1000, block_size=64, num_heads=32, head_dim=128):
        self.block_size = block_size
        self.blocks = [KVBlock(i, block_size, num_heads, head_dim) for i in range(num_blocks)]
        self.free_block_ids = set(range(num_blocks))  # 空闲Block ID集合

    def allocate_block(self):
        """分配一个空闲Block"""
        if not self.free_block_ids:
            raise RuntimeError("GPU显存不足:无空闲Block")
        block_id = self.free_block_ids.pop()
        self.blocks[block_id].is_free = False
        return self.blocks[block_id]

    def free_block(self, block_id):
        """释放Block(标记为空闲,重置有效token数)"""
        block = self.blocks[block_id]
        block.is_free = True
        block.num_valid_tokens = 0
        block.seq_id = None
        self.free_block_ids.add(block_id)

# PageAttention核心计算函数(CUDA加速的核心逻辑)
def page_attention(
    q: torch.Tensor,  # 当前token的Query:[1, num_heads, head_dim]
    request_block_map: dict,  # 请求的Block映射表:{虚拟BlockID: 物理Block对象}
    block_size: int = 64
) -> torch.Tensor:
    """
    核心逻辑:只加载有效Block的有效Slot,计算Attention
    """
    all_scores = []  # 存储所有Block的Attention分数
    all_v = []       # 存储所有Block的Value

    # 遍历该请求的所有物理Block
    for virtual_block_id, physical_block in request_block_map.items():
        # 1. 加载该Block的有效K/V(只取有数据的部分)
        valid_num = physical_block.num_valid_tokens
        valid_k = physical_block.k[:valid_num]  # [valid_num, num_heads, head_dim]
        valid_v = physical_block.v[:valid_num]  # [valid_num, num_heads, head_dim]

        # 2. 计算Q@K^T(Attention分数)
        # 转置K的最后两个维度:[valid_num, num_heads, head_dim] → [valid_num, head_dim, num_heads]
        score = torch.matmul(q, valid_k.transpose(-1, -2))  # [1, num_heads, valid_num]
        all_scores.append(score)
        all_v.append(valid_v)

    # 3. 拼接所有Block的分数和Value
    all_scores = torch.cat(all_scores, dim=-1)  # [1, num_heads, total_valid_tokens]
    all_v = torch.cat(all_v, dim=0)            # [total_valid_tokens, num_heads, head_dim]

    # 4. Softmax + 乘V(标准Attention最后两步)
    attn_weights = torch.softmax(all_scores / math.sqrt(q.shape[-1]), dim=-1)
    output = torch.matmul(attn_weights, all_v)  # [1, num_heads, head_dim]

    return output

2. vLLM源码中Block的完整结构(KVBlock)

在vLLM的vllm/core/kv_cache.py中,Block(KVBlock)存储的信息远不止基础版的K/V,核心字段如下:

字段名 数据类型 核心作用
k_data GPU张量(torch.Tensor) 存储Block内所有token的Key,维度[block_size, num_heads, head_dim],默认FP16/FP8格式
v_data GPU张量(torch.Tensor) 存储Block内所有token的Value,维度同上
block_id int 物理Block的唯一标识(全局唯一)
state Enum(Free/Used/Pinned) Block状态:空闲/被占用/固定(前缀缓存用)
ref_count int 引用计数(前缀缓存时,多个请求共享一个Block,计数>0时不释放)
seq_id int/None 所属请求ID(None=空闲)
slot_mapping List[int]/None 记录Slot→token的映射(前缀缓存时,标记哪些Slot属于共享前缀)
device torch.device Block所在设备(GPU/CPU,异构内存管理用)

3. 进阶优化:前缀缓存(Prefix Caching)

PageAttention的延伸优化,进一步提升内存利用率:

  • 多个请求如果有相同的前缀(比如都以“今天天气”开头),可以共享同一个Block;
  • 此时Block的ref_count会记录有多少个请求引用它(比如2个请求共享→ref_count=2);
  • 只有当ref_count=0时,Block才会被释放,避免重复存储相同前缀的KV Cache。

总结

核心关键点回顾

  1. 小白核心:PageAttention是把KV Cache切成固定大小的Block(像分页内存),解决传统KV Cache的碎片化和浪费问题;每个Block存固定数量token的K/V。
  2. 进阶核心:PageAttention通过“虚拟-物理Block映射”管理内存,计算时只加载有效Block/有效Slot,用CUDA核函数加速;Block不仅存K/V,还包含状态、引用计数等管理信息。
  3. 关键优化:Block池复用、稀疏计算、前缀缓存是PageAttention高效的核心,也是vLLM比传统推理框架快的关键原因。

Block核心信息总结

  • 基础存储:固定数量token的Key和Value矩阵(核心);
  • 管理信息:Block ID、使用状态、有效token数、所属请求ID;
  • 进阶信息:引用计数、Slot映射、设备信息(异构内存管理)。
Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐