大模型推理服务的吞吐,很多时候不是被算力卡住的,而是被显存卡住的。而显存之所以不够用,罪魁往往不是模型权重,是 KV 缓存。PagedAttention 借用了操作系统里一个几十年的老主意——分页——把这块显存管得干净利落。
KV 缓存是怎么把显存吃光的
自回归解码时,每生成一个新 token,都要用到前面所有 token 的 Key 和 Value。为了不重复计算,系统会把它们缓存下来,这就是 KV 缓存。它随序列长度线性增长,而且每条并发请求各有一份。
传统服务系统为了让注意力核函数能连续读取,会给每条请求预留一整段连续显存,大小按「最大可能长度」算。于是问题来了:
- 内部碎片:一条请求实际只生成了几十个 token,却按上千 token 的上限预留了空间,中间那一大段全是「留着但没用上」的浪费。
- 外部碎片:请求有长有短、来了又走,显存被切得七零八落,剩下的空洞单个都太小,新请求哪个也塞不进去。
浪费的显存本可以用来容纳更多并发请求。请求并发数上不去,GPU 的吞吐自然也就上不去。
分页:逻辑连续,物理离散
vLLM 的 PagedAttention 把思路整个换掉。它不再要求一条请求的 KV 缓存在物理上连续,而是切成固定大小的 KV 块(比如每块放 16 个 token)。这些块在显存里可以散落各处,一张块表负责记录「这条请求的第几个逻辑块,对应物理显存里的哪一块」。
块按需分配:请求生成到哪,就分配到哪。于是浪费只剩下最后一个没填满的块——内部碎片被压到一个块以内,外部碎片直接消失。注意力核函数则改成通过块表去「按图索骥」,从离散的物理块里把 KV 收集起来参与计算。
它其实就是虚拟内存
这套机制熟悉操作系统的人会一眼认出来,它几乎是虚拟内存分页的逐项对应:
| 操作系统 | PagedAttention |
|---|---|
| 进程 | 一条推理请求 |
| 虚拟页 | 逻辑 KV 块 |
| 页表 | 块表 |
| 物理内存 | GPU 显存 |
| 按需调页 | 按需分配物理块 |
好处也跟着一起继承过来了。最漂亮的是共享:多条请求如果有相同前缀(同一段系统提示、并行采样的多个候选、束搜索的多条分支),它们的逻辑块可以指向同一个物理块,用引用计数管理;一旦某条分支要写入、内容开始分叉,才复制出一份——也就是写时复制(copy-on-write)。这在批量生成、Agent 大量复用同一段提示词的场景下,能再省下可观的显存。
本文只讲机制。PagedAttention 带来的吞吐提升取决于模型、序列长度分布、块大小和硬件,各家基准测法不同,这里不引用具体倍数——真要评估,请在你自己的负载上测。
该记住的一点
比起「又发明了一个新算法」,PagedAttention 更像是一次恰当的类比迁移:显存紧张、需要在离散资源上做动态分配——这正是操作系统几十年前就解决过的问题。识别出「这题我在别处见过」,往往比从零硬想更值钱。
讨论
评论
还没有评论,来留下第一条讨论吧。