
在这篇文章里,我们会学习 Paged Attention。这是一种解决 KV Cache 内存浪费问题的技术,可以让 LLM 同时服务多得多的用户。
我们会先快速回顾 KV Cache,理解它带来的内存问题;再通过一个例子看看传统内存分配方式如何浪费空间;最后一步步看 Paged Attention 如何借用计算机管理内存的思路来解决这个问题。
我是 Amit Shekhar,Outcome School 创始人。我教过并辅导过很多开发者,他们靠自己的努力拿到了高薪技术岗位;我也帮助过许多科技公司解决各自独特的问题,并创建过很多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。
我在 Outcome School 教 AI and Machine Learning。
我们开始吧。
快速回顾:KV Cache
当 LLM 生成文本时,它一次预测一个 token。token 是一小段文本。它可以是一个词、词的一部分,甚至可以是单个字符。
在每一步,模型都会为新的 token 计算一个 Key 和一个 Value,并且需要所有之前 token 的 Key 和 Value。
如果没有 KV Cache,模型每一步都要为所有之前的 token 重新计算 Key 和 Value,这会浪费大量计算。KV Cache 的做法是:每个 token 的 Key 和 Value 计算出来以后就保存起来,后续步骤可以直接复用。
KV Cache 让文本生成快了很多。但它也有一个取舍:必须用内存来保存所有这些 Key 和 Value。
接下来,我们看看这会带来什么内存问题。
问题:KV Cache 中的内存浪费
当用户向 LLM 发送请求时,系统需要为这个请求的 KV Cache 预留内存。但挑战在于:系统事先不知道回复会有多长。
用户可能问一个简单问题,最后只生成 20 个 token;也可能问一个复杂问题,最后生成 2,000 个 token。系统无法提前知道结果。
在传统做法里,系统会为每个请求预留一大块连续内存,里面所有位置都挨在一起,并且足够容纳最大可能的回复长度。也就是说,即使实际回复最后只有 50 个 token,系统也可能按 2,048 个 token 预留内存。
我们用一个类比来理解。
酒店类比
想象一家酒店,客人入住时并不知道自己会住几晚。有些人只住 1 晚,有些人会住 30 晚。
传统做法: 客人入住时,酒店会在同一层连续预留 30 间房,以防这个客人真的住 30 晚。即使客人最后只住 2 晚,这 30 间房也会全部保持预留状态,不能分给其他人。
现在想象有 10 位客人入住。酒店会连续预留 30 × 10 = 300 间房。但如果大多数客人只住几晚,几百间房就会空着,白白浪费。
这正是传统做法中 KV Cache 内存会发生的情况。
浪费体现在两个方面:
- 内部浪费: 内存被预留了,但从未真正使用。如果一个请求只生成 50 个 token,却按 2,048 个 token 预留了内存,那么剩下 1,998 个 token 的空间就被浪费了。
- 外部浪费: 因为内存必须以一大块连续空间来分配,散落在各处的小块空闲内存就用不上;哪怕这些小块加起来已经足够服务一个新请求,也无法使用。
这种内存浪费是一个严重问题。大量预留内存实际上从未被用到。
因为内存有限,这种浪费意味着系统同一时间能服务的用户更少。如果系统能更高效地使用内存,就可以同时处理多得多的请求。
这就是 Paged Attention 要解决的问题。
什么是 Paged Attention?
Paged Attention 是一种更高效管理 KV Cache 内存的技术。它会把内存拆成小的固定大小块,这些块叫作页(page)。这些页在内存中不需要挨在一起。
“Paged”这个词来自操作系统里的一个概念。操作系统要为计算机上运行的所有程序管理内存。
操作系统不会给每个程序一整块连续内存,而是把内存拆成固定大小的小块,叫作页(page)。每个程序按需获得若干页,这些页可以分散在内存的任何位置。操作系统会用**页表(page table)**记录每个页在哪里;页表就是一张简单列表,把程序的每个页映射到它在内存中的实际位置。
Paged Attention 把同样的思路用到了 KV Cache 上。
我们回到酒店类比,看看它是怎么工作的。
酒店类比:分页做法
分页做法: 酒店不再为每位客人连续预留 30 间房,而是按需一间一间给房。客人入住时,先得到 1 间房。需要再住一晚时,再拿到另一间有空的房。它不必挨着第一间房。酒店会维护一张列表,记录哪些房间属于哪位客人。
这样,如果客人只住 2 晚,就只会用 2 间房。剩下的房间可以给其他客人使用。没有浪费。
如果 10 位客人入住,每个人都只拿到自己真正需要的房间。酒店就能用同样数量的房间服务多得多的客人。
这就是 Paged Attention 处理 KV Cache 的方式。
给你一个小提示
无论你在哪个技术领域工作,都应该熟悉这些主题:
- LLM
- RAG
- MCP
- Agent
- Fine-tuning
- Quantization
我们把它们整理进了一个视频:
AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization
不用现在停下来去看——先收藏,等有时间再看。未来的你会感谢现在的你。
现在,我们回到主题。
Paged Attention 如何工作
在 Paged Attention 中,KV Cache 内存会被拆成固定大小的小块。每个块可以保存固定数量 token 的 Key 和 Value。比如,如果块大小是 4,每个块就能保存 4 个 token 的 Key 和 Value。
我们来看一个例子。
假设用户发送了一个请求,模型正在生成回复:“I love teaching AI and Machine Learning at Outcome School”(我喜欢在 Outcome School 教 AI 和机器学习)。
没有 Paged Attention(传统做法)
系统会预先为这个请求预留一整块内存,比如 16 个 token 的空间:
Memory: [__ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __]
Reserved for this request (16 slots)
随着模型生成 token,它会一个个填入这些位置:
Memory: [I love teaching AI and Machine Learning at Outcome School __ __ __ __ __ __]
Used: 10 tokens Wasted: 6 slots
剩下 6 个位置已经被预留,但永远不会使用,也就是浪费的内存。
使用 Paged Attention
系统只在需要时,按每块 4 个 token 的小块来分配内存:
第 1 步: 模型开始生成。系统从内存中任意可用的位置分配 Block 1:
Block 1: [I love teaching AI]
第 2 步: Block 1 已经填满。系统分配 Block 2。它不需要挨着 Block 1:
Block 1: [I love teaching AI]
Block 2: [and Machine Learning at]
第 3 步: Block 2 已经填满。系统分配 Block 3:
Block 1: [I love teaching AI]
Block 2: [and Machine Learning at]
Block 3: [Outcome School __ __]
Block 3 里只浪费了 2 个位置,而传统做法会浪费 6 个位置。而且,系统没有提前预留最后用不上的内存。
Block Table
但如果这些块分散在内存各处,模型怎么找到它们?
系统会用一张 块表(block table) 跟踪哪些块属于哪个请求,就像操作系统使用页表(page table)一样。
Block Table for Request 1:
Block 0 → Location 5 in memory
Block 1 → Location 12 in memory
Block 2 → Location 3 in memory
这些块按顺序编号(0、1、2),每个编号都会指向它在内存中的实际位置。当模型需要读取之前某个 token 的 Key 和 Value 时,就通过块表找到正确的块。
这就是为什么即使块分散在内存各处,模型仍然可以访问任何之前 token 的 Key 和 Value。
如果想端到端学习 Paged Attention、KV Cache、vLLM 和推理基础设施,可以看看 Outcome School 的 AI and Machine Learning Program。
为什么 Paged Attention 如此有效
Paged Attention 解决了前面讨论过的两类内存浪费:
- 内部浪费几乎被消除。 内存只会按需逐块分配。唯一的浪费出现在最后一个块里,因为最后一个块不一定总能完全填满。在我们的例子里,最后一个块只浪费了 4 个位置里的 2 个,而传统做法会浪费 16 个位置里的 6 个。
- 外部浪费被消除。 因为这些块不需要彼此相邻,所以内存中任何位置的空闲块都可以使用。不会再有用不上的碎片空间。
结果是:Paged Attention 可以更高效地使用内存。这意味着在同样的内存容量下,系统可以同时服务明显更多的请求。
请求之间的内存共享
Paged Attention 还带来了一个重要优化:内存共享。
假设两个用户问了同一个问题。两个请求的输入 token(也就是问题)是相同的。使用 Paged Attention 时,这两个请求可以共享输入 token 对应的同一批块,因为这些 token 的 Key 和 Value 完全相同。
系统不用存两份副本,只需要存一份,然后让两个请求在各自的 block table 里指向同一批块。这样能进一步节省内存。
这在下面这些场景里尤其有用:
- 并行采样(Parallel sampling): 有时我们希望模型针对同一个问题生成多个不同回复,再从中挑选最好的一个。由于问题对所有回复都是一样的,输入块可以在所有回复之间共享。
- 束搜索(Beam search): 有时模型会同时探索多个可能的补全,寻找最好的那个。同样,共享输入可以只保存一次,并在所有并行序列之间复用。
在这些场景中,内存共享会显著降低所需的总内存。
SGLang 把共享相同前缀的思路进一步推进:它用基数树(radix tree)组织共享文本,从而更精确地匹配共享前缀。
Paged Attention 是流行的高吞吐服务引擎 vLLM 背后的核心思路。我们有一篇详细介绍 vLLM 的文章,解释了它如何工作。
现在,我们已经理解了 Paged Attention:它借用了操作系统里的分页思想,高效管理 KV Cache 内存,消除浪费,并让系统同时服务更多用户。
在下一篇文章里,我们会学习 Continuous Batching。它会和 Paged Attention 配合,在任何旧请求完成的瞬间把新请求加入 batch,让 GPU 保持忙碌。
准备 AI 工程面试可以看这里:AI Engineering Interview Questions
这次就到这里。
谢谢
Amit Shekhar Outcome School 创始人