vLLM 如何用分页减少显存浪费
文章借操作系统分页解释 PagedAttention:把 KV 缓存分成固定大小的块,通过映射表管理并按需分配。相较于为每个请求预留最大连续空间,这种方式减少了闲置容量与内存碎片。
AI 深度解读
LLM 服务的瓶颈,有时不在模型计算,而在显存被提前占住却没用上。原文介绍的 PagedAttention,把操作系统分页的思路搬到了 KV cache 管理上:按需分配小块显存,让同一张 GPU 容纳更多请求。
原文引用 vLLM 作者的数据:这种做法让吞吐量达到 FasterTransformer 和 Orca 的 2-4 倍。值得读的地方,是它解释了性能收益从哪里来,也提醒我们区分「服务能处理更多请求」和「单个回答生成得更快」。
为什么回答还没生成,显存就被占满了
LLM 生成文本时,会保存此前各个 token 在每层 attention 中的键和值,形成 KV cache。后续生成可以复用这些数据,减少重复计算,但缓存也会随序列增长。
难点在于:服务端事先不知道回答会有多长。
原文称,直到 2023 年前,多数服务系统仍会为每个请求预留一段连续 GPU 显存,容量按最大序列长度计算。一个只生成 200 个 token 就结束的请求,也可能一直占着足以容纳 2048 个 token 的空间。
浪费来自两处:预留容量没有填满,以及连续内存块之间留下难以利用的碎片。原文引用 vLLM 作者的测量,KV cache 所占显存中,只有 20-38% 存放着实际数据。
如果显存先被预留空间耗尽,GPU 就无法接纳更多并行请求。
这也是为什么优化缓存管理能提高吞吐量:它释放的是服务并发所需的容量。
1962 年的分页,怎样用到 attention 上
原文把这个问题追溯到约 60 年前。1962 年,曼彻斯特的 Atlas 计算机用分页(paging)管理内存:程序看到逻辑上连续的地址,实际数据可以散布在不同物理位置,中间由页表负责映射。
PagedAttention 对 KV cache 做了类似处理:
- 把缓存切成固定 token 数量的块。
- 为每条序列维护块表,将逻辑块映射到 GPU 中的物理块。
- 当前块填满后,才申请下一块。
这样,请求不必一次占住最大容量,也不要求整个缓存物理连续。按原文介绍,块内未用满的空间只留在每条序列的最后一块,浪费降到低于 4%。
省出的显存可以装进更大的批次(batch),原文所说相对 FasterTransformer 和 Orca 的 2-4 倍吞吐提升,正是沿着这条路径产生的。
块表还支持共享。同一个 prompt 生成多个并行回答时,不同序列可以指向同一份 prompt 缓存;需要分别修改时,再通过写时复制(copy-on-write)分开。原文用操作系统的 fork() 作类比。
不过,借鉴分页不等于直接调用操作系统分页功能。真正需要新增工程工作的,是 attention kernel:它必须按照块表,从不相邻的显存块中读取键和值,不能再假定缓存是一整段平铺的缓冲区。
我的判断
这篇解读最有价值的地方,是把「显存利用率」和「服务吞吐量」之间的因果关系讲清楚了。相比按最大长度预留连续空间,按需分块尤其适合回答长度不确定的场景。
但两个结论需要分开看。
第一,吞吐量提高 2-4 倍,不等于单个请求的生成延迟缩短到原来的四分之一。 原文解释的是同一 GPU 可以容纳更大的 batch,没有提供首 token 延迟或逐 token 延迟的对照数据。
第二,20-38% 的有效占用、低于 4% 的浪费,以及 2-4 倍吞吐,都是原文引用的研究结果,并非这位文章作者自行复测。原文没有交代 GPU、模型、块大小、请求长度分布等条件,因此不能直接当成一般团队的收益承诺。低于 4% 也应放在这里讨论的缓存空间浪费范围内理解,不能扩展为全部显存开销。
共享缓存的收益同样有前提:请求需要具有可共享的内容。原文明确举出的是一个 prompt 的多个并行回答,没有证明任意聊天请求都能获得同样收益。
能拿来做什么
- 如果你维护自托管模型服务,评估 vLLM 时可以同时观察 KV cache 占用、并发容量和吞吐量,并记录输入与输出长度,避免只比较一次回答的耗时。
- 如果你用 Node 接入模型服务,区分首 token 延迟、逐 token 延迟和整体吞吐量,再判断用户等待时间究竟卡在哪里。
- 如果日常 AI 编程流程会针对同一个 prompt 并行生成多个候选答案,可以检查服务端是否支持缓存共享与写时复制,并确认请求方式能否触发这条优化路径。