PagedAttention 与连续批处理:LLM Serving 的底层架构拆解
从内存管理视角,拆解 vLLM 如何通过 PagedAttention 和连续批处理,把 GPU 显存利用率从残缺棋盘变成无缝拼图。
LLM Serving 的瓶颈从来不是算力,是内存。
这句话听起来有点反直觉——GPU 不是算力强吗?但当我们用自回归方式生成文本时,每一步都要把完整的 Key-Value 缓存(KV Cache)塞进显存。一个 7B 模型处理 2048 token 的序列,光 KV Cache 就要占大约 2GB 显存。如果同时处理 50 个请求,那就是 100GB。没有哪张消费级显卡扛得住。
本质问题是:传统深度学习推理的批处理(static batching)假设所有请求同时到达、同时结束。但 LLM 推理的自回归特性决定了——每个请求的长度不同、生成步数不同、完成时间不同。一个请求生成完了,它的 slot 空着,但你不能插新请求进去,因为整个 batch 的显存布局在推理开始时就定死了。
这就是 vLLM 要解决的问题,它用两个核心设计把显存利用率拉到了极致。
PagedAttention:把显存做成虚拟内存
PagedAttention 的核心洞察非常朴素:KV Cache 太大,但它的访问模式是有规律的——每个 token 生成时只需要关注它的前缀 tokens 的 KV,而这个"关注"在 attention 计算中是固定模式的。
vLLM 借鉴了操作系统的虚拟内存思想。在传统实现中,一个序列的 KV Cache 是一块连续的显存区域,大小在开始时就要预分配好(通常是最大长度)。这就像你给每个进程预分配一整块物理内存——大量浪费。
PagedAttention 把 KV Cache 切成固定大小的"页"(page),每页可以独立分配和释放。一个序列的 KV Cache 由多个不连续的页组成,通过页表(page table)来索引。Attention 计算时,通过页表找到每一页,再在页内做 attention。
这个设计的好处是多重的:
- 零内部碎片:不再为每个序列预留最大长度的空间,按需分配即可
- 按需换入换出:当显存不够时,可以把不活跃序列的页换到 CPU 内存,需要时再换回来
- 共享前缀:如果多个请求有共同的 prompt 前缀(比如系统提示词),它们的 KV Cache 页可以共享,只需一份显存
最后这一点对 agent 场景特别重要——很多 agent 框架会在每个请求中带上几十行系统提示词,PagedAttention 自动让这些重复内容只存一份。
连续批处理:让 GPU 永远有活干
PagedAttention 解决了"空间"问题,连续批处理(Continuous Batching)解决的是"时间"问题。
continuous batching 的思路是:在每次迭代(iteration)中,动态决定当前 batch 包含哪些序列。当一个序列生成完了(遇到 EOS 或达到 max_tokens),就从 batch 中移除,并在下一次迭代中加入一个新请求。
这个过程在每个 token 生成步骤后都会发生,而不是在一个完整的请求结束后。所以叫做"连续"批处理。
具体实现上,vLLM 维护了一个调度器(scheduler),它管理三个队列:
1. 等待队列:刚到达的请求,还没开始生成
2. 运行队列:正在生成的请求,每一步都在这里
3. 完成队列:已经生成完的请求,等待返回结果
每次迭代开始前,调度器会检查:
- 当前运行队列中有没有请求完成了?如果有,移除它,释放它占用的页
- 显存够不够加新请求?如果够,从等待队列拉一个进来
- 如果显存快不够了,是否要对某些请求做 swapping?
这里有一个微妙的权衡:加入新请求会改变 batch 的 shape,但 GPU 的矩阵运算对固定 shape 最友好。vLLM 的做法是维持一个"预算"机制——每次最多加入多少个新 token,保持 batch size 在一定范围内波动。
组合起来的优雅
PagedAttention 和连续批处理不是两个独立的功能,它们是深度耦合的。
连续批处理需要动态的显存管理——你永远不知道下一秒哪个请求会完成、哪个新请求会加入。而 PagedAttention 的页式管理正好提供了这种灵活性:释放某序列的页就像释放物理内存页一样自然。
反过来,PagedAttention 的共享前缀机制在连续批处理中价值更大——因为很多请求同时运行,它们共享系统 prompt 的概率更高,共享带来的显存节省直接转化为更大的 batch size,从而提升吞吐。
这种组合带来的量化提升很惊人。vLLM 论文报告的数据是:相比 HuggingFace Transformers 的 naive 实现,吞吐提升了 8-24 倍;相比 FasterTransformer,提升了 1.7-2.7 倍。这不仅仅是工程优化,而是对 LLM 推理的计算模型做了重新设计。
对 agent 开发者的启示
如果你在做 agent 系统,理解 PagedAttention 和连续批处理有两个实际价值:
第一,prefill 和 decode 的调度差异。Prefill 阶段(处理 prompt)是 compute-bound,decode 阶段(逐 token 生成)是 memory-bound。在 batch 调度中,如果同时有 prefill 和 decode 请求,需要不同的调度策略。vLLM 会把 prefill 请求尽量错开,避免扎堆抢占计算资源。
第二,系统提示词的设计影响性能。虽然 PagedAttention 自动共享前缀,但共享只在"页边界对齐"时才生效(通常 16 tokens 一页)。如果你的系统提示词长度不是页大小的整数倍,末尾未对齐的部分无法共享。设计系统提示词时,如果可以控制在页对齐的长度,会有意外的性能收益。
评论
发表评论