你有没有遇到过这样的场景:深夜调试一个基于大语言模型的在线服务,明明模型推理速度很快,但服务一上并发,响应时间就直线飙升,GPU 内存占用像坐火箭一样往上窜,最后直接 OOM(Out of Memory)崩溃?你检查了代码,优化了模型,甚至尝试了各种推理框架,但那个根本性的瓶颈——大模型服务中 KV 缓存(Key-Value Cache)的爆炸式内存占用——就像一道无形的墙,让你无法将模型高效、稳定地服务给更多用户。
这正是SOSP 2023 最佳论文《Efficient Memory Management for Large Language Model Serving with PagedAttention》及其开源实现vLLM要解决的核心问题。它不是一个简单的性能优化补丁,而是一次从操作系统经典思想中汲取灵感,对 Transformer 推理内存管理范式的重构。很多人初次接触 vLLM,只看到它“吞吐量提升数十倍”的惊人数字,却容易忽略其背后PagedAttention机制的精妙设计,以及它真正改变的是什么:将大模型服务从“静态、僵化”的内存分配模式,转变为“动态、灵活”的虚拟内存管理模式。
这篇文章,我们不只复述论文结论,而是深入拆解 PagedAttention 的设计哲学,剖析 vLLM 如何将其工程化,并重点回答一个实践问题:当你准备在生产环境部署 vLLM 时,除了启动命令,真正需要关注和理解的底层逻辑与配置边界是什么?
1. 问题的根源:为什么传统服务方式在 LLM 上“失灵”了?
要理解 PagedAttention 的价值,必须先看清它要解决什么问题。传统的大模型服务(例如使用 Hugging Face Transformers 的pipeline或早期推理服务器)在内存管理上,通常采用一种简单直接的策略:为每一个输入的序列(sequence)预先分配一块固定大小的、连续的内存空间,用于存储生成过程中不断增长的 KV 缓存。
1.1 KV 缓存:Transformer 推理的内存“黑洞”
在 Transformer 的自回归解码(如文本生成)过程中,为了计算下一个 token,模型需要用到之前所有已生成 token 的 Key 和 Value 向量。这些向量被缓存起来,就是 KV 缓存。其内存占用公式大致为:
总内存 ≈ 批处理大小 (batch_size) × 序列长度 (seq_len) × 层数 (num_layers) × 注意力头数 (num_heads) × 向量维度 (head_dim) × 2 (K和V) × 数据类型大小 (e.g., 2 for fp16)
对于一个 70B 参数的模型,即使序列长度只有 1024,服务一个用户请求的 KV 缓存就可能占用数 GB 的 GPU 内存。当多个请求并发时,内存需求线性叠加,迅速耗尽显存。
1.2 传统内存管理的三大痛点
在这种预分配连续内存的模式下,三个核心痛点无法避免:
- 内部碎片化严重:为了应对可变长度的生成结果,系统必须为每个序列分配其最大可能长度的内存。如果用户只生成了几十个 token,但系统预留了 2048 个 token 的空间,那么剩余的大量内存就被浪费了,这就是内部碎片。
- 外部碎片化导致无法服务新请求:即使总剩余显存看起来足够容纳一个新请求的 KV 缓存,但由于这些空闲内存是由之前已结束请求释放的、大小不一的“碎片”组成的,系统可能找不到一块足够大的连续空间来分配给新请求,从而导致服务被阻塞或拒绝。这与操作系统早期面临的内存碎片问题如出一辙。
- 内存利用率与吞吐量的根本矛盾:为了提高 GPU 计算利用率(吞吐量),我们希望增大批处理大小(batch size)。但更大的 batch size 意味着需要为更多序列预分配 KV 缓存内存,这直接加剧了上述碎片化问题,甚至可能因 OOM 而根本无法增大 batch size。服务陷入了“要吞吐就不能要并发,要并发就得牺牲吞吐”的两难境地。
注意:这里的“碎片化”不是指磁盘碎片,而是 GPU 显存中由于分配和释放模式导致的空间无法被有效利用的状态。它直接限制了服务的并发能力和资源效率。
2. PagedAttention:向操作系统虚拟内存“借”来的灵感
面对碎片化这一经典难题,计算机系统领域早有成熟的解决方案:虚拟内存和分页。PagedAttention 的核心思想,正是将操作系统中虚拟内存管理的理念,创造性地引入到 Transformer 的注意力机制中。
2.1 核心类比:从“连续公寓”到“分散酒店房间”
让我们用一个类比来理解这个转变:
- 传统方式:就像为每个旅行团(请求序列)预订一整栋连续的公寓楼。即使旅行团只有10人,也得包下整栋50人的楼,空置40个房间(内部碎片)。当多个旅行团离开后,空出的公寓楼大小不一,一个新来的50人团可能找不到一栋完整的空楼入住(外部碎片)。
- PagedAttention 方式:不再预订整栋楼,而是将酒店的所有房间标准化为固定大小的“页”(例如,每个页存储固定数量token的KV向量,比如16个token)。每个旅行团(序列)的住宿需求被记录在一张“页表”中,页表里记录了该团分散在酒店各处的房间号。10人团就只占用10人对应的房间,新人团可以入住任何分散的空房间,由页表来维护逻辑上的连续性。
2.2 技术实现拆解
PagedAttention 具体是如何工作的?
- 将 KV 缓存分页:系统将 KV 缓存在物理内存(GPU显存)中划分为固定大小的块,称为“块”(Block),每个块可以存储固定数量token(例如16个)的Key和Value向量。这类似于操作系统的内存页。
- 引入逻辑“块表”:对于每一个输入的序列,系统维护一个逻辑上的“块表”(Block Table)。这个表不关心块在物理内存中的实际位置,只按顺序记录该序列的KV缓存分别存储在哪些物理块中。
- 注意力计算时动态寻址:当进行注意力计算时,对于序列中某个token,需要访问其之前所有token的KV缓存。系统通过查询该序列的块表,动态地找到这些KV向量可能分布在不同物理块中的位置,然后高效地读取并参与计算。这个过程由高度优化的GPU内核完成,对上层模型透明。
2.3 带来的根本性改变
这一设计范式转移,直接解决了传统方式的痛点:
- 消除内部碎片:序列需要多少token的缓存,就分配多少个块,按需分配,没有预留浪费。
- 消除外部碎片:所有块大小相同,任何被释放的块都可以立即分配给新的序列使用,物理内存池变得完全可互换和高效复用。
- 实现内存共享:这是 PagedAttention 一个更强大的衍生优势。在诸如并行采样(beam search)或前缀共享(多个请求有相同提示词)的场景中,不同序列间可以共享只读的KV缓存块(例如共享提示词对应的块),进一步大幅节省内存。
本质上,PagedAttention 将KV缓存从一种紧耦合的、序列私有的数据结构,解耦为一种池化的、全局管理的存储资源。服务系统现在可以像操作系统管理进程内存一样,灵活、高效地管理最宝贵的大模型推理资源——KV缓存。
3. vLLM:将论文思想转化为生产级引擎
PagedAttention 提供了理论框架和核心算法,而vLLM则是将其工程化、系统化,打造出的一个高性能、易用的大模型推理与服务引擎。理解 vLLM,不能只看成一个“用了 PagedAttention 的推理库”,而应视为一个围绕高效内存管理重构的端到端服务系统。
3.1 核心架构组件
vLLM 的架构紧密围绕 PagedAttention 构建:
- 块管理器(Block Manager):这是系统的“内存管理单元”。它维护着一个全局的物理块池,负责块的分配、释放和共享。它跟踪哪些块是空闲的,哪些块被哪些序列引用(用于共享和垃圾回收)。
- 调度器(Scheduler):决定哪些请求的序列在何时执行模型的前向传播。vLLM 采用了迭代级调度(Iteration-level Scheduling),也称为连续批处理(Continuous Batching)。它与块管理器协同工作:当一个序列需要新的块来存储新生成的token的KV缓存时,调度器会暂停该序列的执行,直到块管理器为其分配好新的物理块。
- 模型执行器与定制化内核:vLLM 深度优化了模型执行流水线,特别是集成了实现 PagedAttention 算子的高效CUDA内核。这些内核能够根据块表,高效地从分散的物理块中收集(Gather)KV向量进行计算,并将结果分散(Scatter)回正确的块中。
3.2 关键工作流程:一次请求的旅程
让我们跟随一个用户请求,看看 vLLM 内部如何运作:
- 请求接收:用户发送一个提示词(prompt)到 vLLM 服务器。
- 块分配与预处理:调度器接收请求。块管理器为这个新序列分配物理块来存储提示词 tokens 的 KV 缓存。如果提示词与正在运行的其他序列相同,可能直接共享已有的块。
- 迭代解码:序列进入运行队列。调度器将多个序列的当前 tokens 组成一个批处理(batch),送入模型执行。
- 注意力计算:在执行注意力层时,PagedAttention 内核被调用。对于序列中的每个 token,内核查询其块表,从可能位于多个不同物理块中读取其历史 KV 缓存,完成注意力计算。
- 生成与块扩展:模型输出下一个 token。如果序列尚未完成,且当前使用的最后一个块已满,序列会向块管理器申请一个新的空闲块,并将其加入自己的块表末尾。然后该序列可能进入等待状态,让其他序列执行。
- 完成与释放:当序列生成结束(达到最大长度或生成停止符),调度器标记该序列完成。块管理器回收该序列独占的所有物理块(共享块会等待所有引用者都完成后才回收),这些块立即可供新序列使用。
这个过程实现了极高的资源利用率:GPU计算单元(忙于迭代解码)和内存资源(块的循环利用)都被持续、充分地使用。
4. 实践指南:部署 vLLM 时,你需要关注什么?
了解了原理,最终要落地。使用vLLM的命令行vllm serve启动服务可能很简单,但要使其在生产环境中稳定、高效运行,你需要理解以下几个关键配置和概念。
4.1 核心配置参数解析
以下参数直接影响内存、性能和调度行为:
| 参数 | 含义与影响 | 调优建议 |
|---|---|---|
--block-size | 一个物理块存储的 token 数量。 | 这是最重要的参数之一。较小的块(如8)减少内部碎片,但增加块表管理和内核调度的开销。较大的块(如32)管理开销小,但可能增加碎片。通常建议使用默认值(16),这是一个经过权衡的折中点。仅在特定负载下有深入理解后再调整。 |
--gpu-memory-utilization | 目标 GPU 显存利用率(0到1之间)。vLLM 会尝试将 KV 缓存等内存占用控制在此比例内。 | 默认值(0.9)已较激进。如果你的模型权重本身很大,或需要为其他操作(如上下文中的图像编码)留出空间,可适当降低(如0.8)。监控实际显存使用,避免因OOM崩溃。 |
--max-num-batched-tokens | 调度器一次前向传播允许处理的最大 token 总数(包括输入和生成的缓存)。 | 这限制了批处理的计算规模。值越大,吞吐潜力越高,但延迟可能增加,且需要更多显存存放“正在处理”的KV缓存。可根据GPU算力和显存调整,通常先从默认值开始。 |
--max-num-seqs | 调度器同时处理的最大序列数。 | 限制并发请求数。防止系统因过多并发序列导致调度开销过大或内存超限。需要根据block-size和可用内存估算。 |
--tensor-parallel-size/--pipeline-parallel-size | 张量并行和流水线并行大小,用于超大模型的多卡分布式推理。 | 对于单卡可忽略。对于多卡,需与模型本身的并行策略匹配。这是部署超大模型的必备知识。 |
--dtype/--quantization | 模型权重加载的数据类型和量化方法(如 awq, gptq)。 | 对内存影响巨大。auto会自动选择(如 fp16)。使用--quantization awq等可以显著减少模型权重内存,从而为 KV 缓存留出更多空间,提升并发。是提升性价比的关键手段。 |
4.2 性能监控与问题排查
部署后,如何判断 vLLM 是否工作在最佳状态?
监控指标:
- 吞吐量(Tokens/s):核心性能指标。使用 vLLM 内置的指标接口或 Prometheus 导出。
- 延迟(Time to First Token, TTFT & Inter-token Latency):分别衡量首字响应时间和后续 token 的生成速度。
- GPU 利用率与显存使用:使用
nvidia-smi或gpustat持续观察。理想情况是计算利用率高且显存使用稳定在gpu-memory-utilization目标附近。 - vLLM 详细日志:通过
--verbose或调整日志级别,查看块分配、调度决策等信息。
常见问题排查链路:
- 现象:吞吐量低于预期。
- 检查 GPU 计算利用率是否低。如果低,可能是
--max-num-batched-tokens设置过小,未能充分利用 GPU;或者是输入序列非常短,计算本身不饱和。 - 检查是否频繁进行块分配/释放(看日志)。过于频繁可能意味着
--block-size太小或请求长度极短且多变。
- 检查 GPU 计算利用率是否低。如果低,可能是
- 现象:请求延迟高,特别是长序列。
- 检查是否开启了 Swap(将 KV 缓存换出到 CPU 内存)。虽然 vLLM 支持,但会极大增加延迟。仅当必须服务超长上下文且能接受高延迟时使用。
- 检查并发请求数是否过多,导致调度等待。
- 现象:出现 OOM(内存不足)。
- 首先确认模型权重加载后的基础显存占用。
- 降低
--gpu-memory-utilization。 - 降低
--max-num-seqs或--max-num-batched-tokens。 - 考虑使用量化(
--quantization)来减小模型权重大小。
- 现象:吞吐量低于预期。
4.3 适用边界与长期考量
vLLM 并非银弹,理解其边界至关重要:
- 优势场景:多请求、变长、自回归文本生成服务。这是其设计的主战场,性能提升最显著。
- 局限场景:
- 非自回归任务:如图像生成、嵌入计算等不需要 KV 缓存的任务,vLLM 的优势无法体现。
- 极短固定长度请求:如果所有请求都是固定的、极短的分类或标注任务,传统批处理可能更简单高效。
- 严格实时性要求:迭代级调度和块管理引入了一定开销,对于超低延迟(毫秒级)的极致要求,需要精细测试。
- 工程化集成:vLLM 提供了 OpenAI 兼容的 API,易于集成。但在生产环境,还需考虑:
- 高可用与负载均衡:部署多个 vLLM 实例,前端通过负载均衡器分发。
- 监控告警:对上述吞吐、延迟、内存指标建立监控和告警。
- 模型热更新:vLLM 支持一定程度的模型重新加载,但大规模生产环境需要更严谨的蓝绿部署策略。
PagedAttention 和 vLLM 的成功,其深远意义在于它揭示了一条路径:通过将系统软件(操作系统、数据库)中久经考验的设计思想(虚拟内存、缓冲池),引入AI系统领域,可以解决由AI模型特性(如Transformer的KV缓存)引发的新兴系统瓶颈。这不仅仅是优化了一个库,更是为后续的大模型推理系统设计树立了范式。当你下次面对大模型服务的性能瓶颈时,或许可以跳出模型和算法的层面,从系统和资源管理的角度去寻找答案。而 vLLM,就是你手中那把已经锻造好的、开启高效服务之门的钥匙。