
🧠 记忆锚点:请求看到连续 KV,显存实际按页分配;按需扩容、及时回收,减少碎片并支撑动态批处理。
💡 答案要点
PagedAttention = 把 KV Cache 分页管理,像操作系统管理内存一样
解决的核心问题:KV Cache 碎片化和浪费
传统方案的问题:
传统:为每个请求预分配固定大小的 KV Cache
请求1:预估 2K tokens,实际用了 500
→ 浪费 1500 个 token 的空间
请求2:预估 1K tokens,实际用了 1200
→ 超出了,只能截断或重新分配
问题:
1. 预分配浪费空间(平均浪费 30-50%)
2. 长度不确定,要么截断要么OOM
3. 内存碎片化严重PagedAttention 方案:
┌─────────────────────────────────────────────────────────┐
│ PagedAttention │
└─────────────────────────────────────────────────────────┘
1. 将 KV Cache 分成固定大小的页(Page)
每页:64 或 128 tokens
2. 动态分配页
请求需要多少,就分配多少页
3. 页可以不连续
物理内存:[Page3, Page7, Page2, ...]
逻辑视图:连续的 KV Cache
4. 共享页(Prefix Sharing)
多个请求共享相同的 System Prompt示例(3个请求):
System Prompt: 500 tokens(所有请求共享)
请求1:"翻译:Hello" → 生成 50 tokens
页分配:[共享页1-4] + [独占页5]
请求2:"翻译:World" → 生成 80 tokens
页分配:[共享页1-4] + [独占页6-7]
请求3:"摘要:..." → 生成 200 tokens
页分配:[共享页1-4] + [独占页8-11]
节省:
传统:3 × 500 = 1500 tokens(System Prompt)
PagedAttention:1 × 500 = 500 tokens(共享)
节省:66%核心技术:
1. 分页存储:
python
class PagedKVCache:
def __init__(self, page_size=64):
self.page_size = page_size
self.pages = [] # 物理页池
self.page_table = {} # 逻辑地址 → 物理页
def allocate_page(self):
if self.free_pages:
return self.free_pages.pop()
else:
page = torch.empty([page_size, hidden_dim])
self.pages.append(page)
return len(self.pages) - 1
def get_kv(self, token_id):
page_id = token_id // self.page_size
offset = token_id % self.page_size
physical_page = self.page_table[page_id]
return self.pages[physical_page][offset]2. Copy-on-Write(写时复制):
python
# 共享页在修改时才复制
if page.ref_count > 1:
new_page = page.copy()
page.ref_count -= 1
page = new_page性能提升(vLLM,实测数据):
| 指标 | 传统方案 | PagedAttention | 提升 |
|---|---|---|---|
| 吞吐量 | 100 req/s | 240 req/s | 2.4x |
| 显存利用率 | 40% | 90% | 2.25x |
| 平均延迟 | 800ms | 600ms | 1.3x |
面试话术:
"PagedAttention 借鉴了操作系统的虚拟内存思想。分页管理避免了预分配的浪费,Copy-on-Write 实现了高效共享。vLLM 用它把吞吐量提升了 2.4 倍。"
📚 参考:vLLM:Efficient Memory Management for LLM Serving with PagedAttention(原论文)