什么是Paged Attention?

模型部署好之后,权重占的显存基本固定。为什么并发用户一多,显存还是先告急? 因为真正随用户数量线性膨胀的,是每个请求都要独占一份的 KV Cache。
本文收录于微信公众号:愤怒的代码 AngryCode

0x00、KV Cache:算一次,存起来,反复用

LLM 生成文字是一个 token 一个 token 生成的。token 是文本的最小单位,可能是一个词、半个词,也可能是一个字。

模型每预测一个新 token,都要「看」一眼前面所有 token 的信息。在注意力机制里,这「看一眼」具体表现为:每个 token 都有自己的 Key(用来被查询"关键字")和 Value(用来被读取的内容)。预测下一个 token 时,当前 token 的 Query 要和前面所有 token 的 Key 算相似度,再按权重去取它们的 Value。

如果不做任何缓存,每生成一个新 token,都要把前面所有 token 重新过一遍网络,把它们的 Key 和 Value 重新算一遍。这一步是纯重复劳动。

KV Cache 的做法:每个 token 的 Key 和 Value 算好之后就存起来,后面直接复用。生成速度因此大幅提升。

代价是显存。存下来的 Key 和 Value 都待在显存里,而且每个请求独占一份。

KV Cache 相关内容可以参阅 《读懂大模型的"记忆":KV Cache 到底是什么》、《KV Cache 里为什么没有 Q》


0x01、显存浪费从哪来:系统不知道回复有多长

系统在收到请求的那一刻,并不知道这个请求会生成多长的回复。

问一句「深圳今天天气怎么样」,可能回 20 个 token;问一句「讲讲注意力机制」,可能回 2000 个 token。系统事先无从判断。

传统做法是:给每个请求一次性预留一整块连续的显存空间,按最大可能长度来。比如按 2048 个 token 预留,结果实际只生成了 50 个 token。

这跟传统 KV Cache 的处境一模一样。浪费分两种:

  • 内部浪费:预留了但没用上。预留 2048,只生成 50 个 token,剩下 1998 个 token 的空间全废。
  • 外部浪费:因为必须一整块连续分配,显存里那些零散的空闲小缝隙就用不上,哪怕这些缝隙加起来够服务一个新请求。

显存有限,浪费越多,能同时服务的用户越少。Paged Attention 就是来解决这个的。


0x02、Paged Attention:把显存切成固定小块

Paged Attention(分页注意力) 的思路来自操作系统:操作系统给程序分配内存时,也不是每个程序给一大块连续内存,而是把内存切成固定大小的「页(page)」,程序需要几页给几页,这些页可以散落在内存的任意位置。

Paged Attention 把这套思路搬到 KV Cache 上:把 KV Cache 的显存切成固定大小的块(block),每个块能存固定数量 token 的 Key 和 Value,块和块之间不需要连续。

假设块大小是 4 个 token,用户让模型生成「我喜欢在公园里跑步」。这句话 9 个字,假设按一个字一个 token 算,就是 9 个 token。

传统做法假设预先先分配 16 个槽位:

css 复制代码
[我 喜 欢 在 公 园 里 跑 步 _ _ _ _ _ _ _]
 用了 9 个,浪费 7 个槽位

分页做法按需申请,用满一块才要下一块:

css 复制代码
块 1:[我 喜 欢 在]
块 2:[公 园 里 跑]
块 3:[步 -  -  -]   ← 只写了 1 个,浪费 3 个槽位

浪费从 7 个槽位缩到了 3 个,而且开头没有再预留一整块用不上的空间。

块大小是 4,最后一块最多浪费 3 个槽位。

块大小的真实取值,vLLM 里默认是 16 个 token 一个block,这里用 4个token一个 block 只是为了简化让图看得清楚。


0x03、块表:散落的小块怎么找回来

块既然散落在显存各处,模型生成时怎么找到上一个 token 的 Key 和 Value?

答案是块表(block table)。系统为每个请求维护一张表,记录「这个请求的第几个块,实际放在显存哪个位置」,跟操作系统的页表(page table)一个道理:

复制代码
请求 1 的块表:
  块 0 → 显存位置 5
  块 1 → 显存位置 12
  块 2 → 显存位置 3

块按顺序编号(0、1、2),每个编号指向它在显存里的真实位置。模型要读某个历史 token 时,先查块表,再跳过去取。物理上散落,逻辑上依然是一条连续的序列。


0x04、额外收益:两个请求共享同一份显存

分页还带来了另一个收益:显存共享。

两个用户问了同一个问题,输入部分(问题本身)的 token 是完全一样的。既然 Key 和 Value 只取决于 token 内容,那这两个请求的输入 token 的 Key、Value 就是同一份。分页方案下,两个请求可以在各自的块表里,指向同一批块,不用各存一份拷贝。

这在两类场景里特别省:

  • 并行采样(parallel sampling):同一个问题让模型生成多个不同答案,再挑最好的。输入部分完全一样,可以共享。
  • 束搜索(beam search):模型同时探索多条可能的生成路径。共享的输入前缀存一份,多条路径复用。

SGLang 把这个「共享前缀」的想法又往前推了一步,用基数树(radix tree)来组织共享文本,把前缀匹配做得更细。


0x05、两个误区:Paged Attention 不压缩缓存,也不等于 vLLM

第一,Paged Attention 不会让 KV Cache 本身变小。

它只负责消灭「缓存周围被浪费的显存」。真正把缓存体积压下来,是 KV Cache 压缩技术(KV Cache Compression)的活,两条路解决的是两个不同的问题。

关于KV Cache压缩可以参阅《KV Cache 压缩技术:把大模型的"记忆"塞进一个小盒子》

第二,Paged Attention 不等于 vLLM。

Paged Attention 是一项技术,vLLM 是建立在这项技术之上的高吞吐推理引擎,Paged Attention 是它的核心。同样的分块思想也出现在 NVIDIA 的 TensorRT-LLM 里,名字叫 paged KV cache。


0x06、总结

  1. KV Cache 让每个 token 的 Key、Value 只算一次、反复复用,代价是显存------每个请求独占一份。
  2. 传统做法按最大长度一次性预留整块连续显存,产生内部浪费(预留没用上)和外部浪费(零散缝隙用不上)。
  3. Paged Attention 把缓存切成固定大小的块,按需分配、不必连续,用块表记录位置------减少显存浪费。
  4. 块可以跨请求共享,同一份输入 token 的 Key、Value 只存一份,并行采样、束搜索场景尤其省。
  5. Paged Attention 不压缩缓存本身,那是 KV Cache 压缩的活;它也不等于 vLLM,vLLM 是建立在这套技术上的推理引擎。

模型部署好之后,权重占多少显存基本固定,用户一多却撑不住,问题不在权重,而在每个请求的 KV Cache 各自预占的那一大块空间。Paged Attention 做的事很朴素:把「一次预留一大块空间」换成「用一点给一点」,再配一张块表把散落的碎片重新串起来。显存还是那块显存,能装下的用户却多了不少。

这是理解现代 LLM 推理引擎(vLLM、TensorRT-LLM)的一块入场券。


本文收录于微信公众号:愤怒的代码 AngryCode

相关推荐
智码看视界1 小时前
字节 Seed-2-OSS 开源,内部豆包能力的开源释放
agent·moe·开源大模型·字节跳动·长上下文·apache-2.0·seed-2-oss
樱木花道長1 小时前
AMD Ross 深度系列 7:六组对照实验看Ross如何赋能AI完成Vivado 硬核IP配置
人工智能·fpga开发
OpenVINO 中文社区1 小时前
英特尔 × ADG:从 Agent 落地到 3DGS 重建,GDPS 2026 空间计算实战工作坊技术复盘
人工智能·3d·空间计算
W***25921 小时前
视频怎么单独提取声音:全设备通用操作指南与后续创作全流程
人工智能
龙孚信息1 小时前
一座塔,与三十年的文件夹
人工智能
LV技术派1 小时前
【成为 AI Native Builder · 01】开篇:为什么 AI 总在返工?
人工智能·程序员·ai编程
QuZhengRong1 小时前
【AI】大模型推理中 KV Cache 原理解析
人工智能·大模型·agent·提示词·推理
论文复现现场1 小时前
2026 低成本 AI Agent 模型怎么选?DeepSeek V4.1 Flash、Qwen3.8 Flash、GLM-5.3 Flash 对比
数据库·人工智能·qwen·ai agent·glm·deepseek
SEO_juper1 小时前
Go 站点地图生成器:从首页爬全站,一键生成 sitemap.xml
xml·开发语言·人工智能·golang·node.js·seo·外贸独立站