大语言模型推理系统的工程化落地,长期以来面临着显存利用率与吞吐量之间的核心矛盾。传统的推理引擎在处理长上下文、多并发请求时,往往因为 KV 缓存的静态分配策略导致大量显存碎片,或是因缺乏有效的缓存复用机制而重复计算。vLLM 作为开源推理引擎的代表,通过引入 Paged Attention 机制和精细化的调度策略,从根本上重新定义了高效推理的行业标准。本文将从调度器设计、KV 缓存调度策略、内存优化机制以及块淘汰压缩策略四个维度,深入剖析 vLLM 风格推理引擎的核心架构。
调度器设计:请求生命周期与资源分配
vLLM 的调度器是整个推理引擎的中枢神经,其核心职责是协调有限 GPU 显存资源与无限涌入的用户请求之间的平衡。在 vLLM V1 架构中,调度器采用了基于分块预填充的调度策略,这与传统的连续批处理有着本质区别。传统的连续批处理在处理长提示词时会阻塞整个批处理,导致短请求的延迟显著增加。而 vLLM 的调度器将预填充阶段拆解为多个可调度单元,在每个调度周期内优先处理解码请求,同时将预填充请求切分为不超过 max_num_batched_tokens 限制的块进行处理。
这种调度策略的设计哲学源于对推理工作负载特性的深刻洞察。预填充阶段是计算密集型操作,涉及对整个提示词的并行处理;而解码阶段是内存密集型操作,每次仅生成一个 token,却需要加载完整的 KV 缓存。当这两类请求混合在同一个批处理中时,如果不做任何干预,计算密集型操作会长时间占用 GPU 显存,导致解码请求被迫等待,表现为用户感知的首 token 时间过长。vLLM 通过 Chunked Prefill 机制,确保解码请求能够被持续调度,从而优化了交互式场景下的用户体验。
调度器的另一个关键职责是处理显存不足的情况。当并发请求数超过 GPU 显存所能容纳的上限时,调度器会触发预占机制。vLLM V1 的默认预占模式是 RECOMPUTE,这意味着被驱逐的请求在重新获得调度机会时,需要重新计算其 KV 缓存。相较于 V0 时代的 SWAP 模式将缓存交换到主机内存,RECOMPUTE 模式在 V1 架构下的开销反而更低。这是因为 V1 的执行引擎针对重新计算场景进行了优化,避免了复杂的内存传输开销。当然,如果预占频繁发生,通常意味着需要调整 gpu_memory_utilization 参数以分配更多显存,或减小 max_num_seqs 以降低并发请求数量。
KV 缓存调度:从静态分配到动态流转
KV 缓存的调度策略直接决定了推理系统的内存效率。vLLM 的核心创新在于将操作系统中成熟的虚拟内存和分页机制引入到 GPU 显存管理中。在传统的推理引擎中,KV 缓存按照最大序列长度进行预分配,这导致了严重的显存浪费 —— 短序列无法利用为其预留的空闲空间,而长序列又面临着显存不足的风险。vLLM 通过 Paged Attention 机制,将 KV 缓存分割为固定大小的块进行离散存储,每个序列的 KV 缓存可以非连续地分布在 GPU 显存中。
vLLM 默认的块大小为 16 个 token,这意味着每个序列的 KV 缓存由若干个块组成,块与块之间通过指针进行逻辑连接。当序列生成新 token 时,系统只需分配一个新块并追加到链表尾部,而无需重新分配整段连续显存。这种设计带来了两个关键优势:其一,显存碎片被显著减少,因为所有块都来自统一的块池;其二,序列之间的缓存共享成为可能,当多个请求共享相同的系统提示词时,它们可以引用相同的物理块。
在 vLLM 0.11.0 版本中引入的 KV 缓存卸载特性,进一步扩展了缓存调度的边界。当 GPU 显存不足以容纳所有活跃请求的 KV 缓存时,系统可以选择将部分缓存卸载到 CPU 内存或更底层的存储介质。这一特性在处理长上下文或高并发场景时尤为重要。卸载过程通过异步 API 执行,确保卸载操作不会阻塞调度器的正常运行。vLLM 0.12.0 版本对这一特性进行了重大优化,通过改进 KV 缓存的物理布局,使得 CPU 卸载的吞吐量提升了数个数量级。
具体而言,早期版本的 KV 缓存物理布局存在严重的碎片化问题。对于一个典型的 32 层模型,每个逻辑块在物理上被分散存储为 32 个独立的子块(每层一个),甚至可能进一步分裂为 Key 和 Value 两个子块。这种碎片化布局导致 CPU 卸载时的传输单元过小,无法充分利用 DMA 传输的带宽潜力。vLLM 0.12.0 通过将同一逻辑块在所有层和头类型上的数据合并为单个物理块,使得传输单元从原来的数 KB 级别扩展到数 MB 级别。
内存优化策略:分层存储与压缩技术
vLLM 的内存优化策略构建了一个多层次的存储体系,旨在平衡访问延迟与存储容量。最顶层是 GPU HBM 显存,其带宽最高但容量有限,承载最活跃的请求和热点数据。中间层是 CPU DRAM,其容量通常是 GPU 显存的数倍乃至数十倍,但访问延迟也相应更高。底层是持久化存储,如 NVMe SSD,容量最大但访问延迟也最高。vLLM 的 KV 卸载机制使得数据能够在这个层次结构中动态流转,确保系统在资源受限条件下仍能维持服务。
量化技术是另一个重要的内存优化手段。vLLM 支持对 KV 缓存进行 FP8 量化,将每个元素的存储空间从 16 位压缩到 8 位,从而将缓存容量翻倍。量化过程在缓存写入时自动执行,在读取时自动解量化,对上层调度逻辑透明。需要注意的是,量化会带来一定的精度损失,因此在生产环境中通常需要进行基准测试以验证量化对模型输出质量的影响。
前缀缓存机制是 vLLM 在多请求场景下的另一项优化。当多个请求共享相同的前缀(如系统提示词或常用指令模板)时,系统只需为该前缀计算一次 KV 缓存,后续请求可以直接复用。vLLM 通过对前缀内容进行哈希索引,实现高效的缓存查找。这一机制在聊天机器人等高频重复前缀的场景下效果显著,能够大幅降低预填充阶段的计算开销。
块淘汰策略: evicted 决策与缓存回收
当活跃请求所需的 KV 缓存超出 GPU 显存容量时,vLLM 必须做出淘汰决策。在 vLLM 的块管理体系中,每个物理块都有一个引用计数,记录当前有多少个逻辑序列正在使用该块。当引用计数归零时,说明该块已不再被任何活跃请求需要,可以被回收用于新请求的分配。
vLLM 采用的淘汰策略结合了最近最少使用原则和优先级机制。当需要回收块空间时,系统首先选择引用计数为零的块进行回收。如果所有块都有非零引用计数,则需要进一步决策:对于共享前缀对应的块,系统会保留这些块更长时间,因为它们被复用的概率更高;对于仅被单个长序列占用的块,在内存压力下可能被选择驱逐,此时被驱逐序列在后续调度时将需要重新计算其 KV 缓存。
vLLM 0.11.0 引入的卸载机制为淘汰策略提供了额外的缓冲空间。当内存压力出现时,系统可以选择将低优先级的块卸载到 CPU 内存而非直接驱逐。这种策略在处理长上下文请求时尤为有用 —— 即使某个长序列暂时不被调度,其上下文信息仍可保留在 CPU 端,待该序列重新获得调度机会时再快速加载回 GPU,无需完全重新计算。
监控预占行为的频率是调优 vLLM 配置的重要指标。通过 Prometheus 指标或日志统计中的 total_cumulative_preemption_cnt 字段,生产运维人员可以判断当前的显存配置是否满足工作负载需求。如果预占频繁发生,说明需要增加 gpu_memory_utilization 参数值,或者考虑使用 tensor parallelism 将模型权重分散到多张 GPU 上,从而释放更多单卡显存用于 KV 缓存。
工程实践中的关键配置参数
在生产环境中部署 vLLM 时,若干关键参数直接影响系统的性能表现。gpu_memory_utilization 参数控制 vLLM 预分配 GPU 缓存的比例,默认值通常为 0.9。在显存充裕的场景下,适当提高该值(如 0.95)可以增加 KV 缓存容量,减少预占发生;在显存紧张或需要为其他进程预留空间的场景下,降低该值(如 0.8)可以避免 OOM 风险。
max_num_batched_tokens 是控制 Chunked Prefill 行为的核心参数。较小的值(如 2048)能够改善交互延迟,因为每次调度的预填充量较小,解码请求不会被长时间阻塞;较大的值(如 8192 或更大)则有利于提升吞吐量,允许系统在单次调度中处理更多预填充 token。对于追求高并发吞吐量的离线推理场景,建议将该值设得更大;对于追求低延迟的在线服务场景,则应将其设得较小。
max_num_seqs 控制单批处理中的最大序列数,增加该值可以提升吞吐量,但也会增加显存压力。在实际部署中,该参数通常与 max_num_batched_tokens 配合调整,找到当前硬件配置下的最优平衡点。
vLLM 的架构设计代表了推理系统工程化实践的一个重要里程碑。从 Paged Attention 带来的显存利用革命,到 Chunked Prefill 重塑的调度范式,再到 KV 卸载开启的分层存储时代,vLLM 持续推动着高效推理的边界。理解这些核心机制的设计原理与工程权衡,对于构建生产级的大语言模型服务系统至关重要。
参考资料
- vLLM 官方文档:https://docs.vllm.ai/
- vLLM Blog: "Inside vLLM's New KV Offloading Connector" (2026-01-08)