在 LLM 推理服务的工程实践中,Serving 层的性能瓶颈已从早期的计算密集型逐步转向内存受限型。当模型参数规模突破数十亿级别,每一次前向传播都需要将海量的 Key-Value 矩阵加载至 GPU 显存,而 KV Cache 的高效管理直接决定了系统的吞吐量上限和请求排队时长。本文聚焦 KV Cache 的生命周期管理和动态批处理调度,从底层机制出发,推导出可落地到生产环境的参数配置清单与监控指标体系。

一、KV Cache 的本质与内存占用模型

理解 KV Cache 的管理策略,首先需要明确它的物理本质。在 Transformer 架构的每一层注意力机制中,Query、Key、Value 三个矩阵参与运算。对于自回归生成任务,当模型已经生成了若干 token 之后,这些 token 对应的 Key 和 Value 矩阵在后续的解码步骤中会被反复引用 —— 每一层、每一个注意力头都会保存一份独立的副本。因此,KV Cache 本质上是一组按层级和注意力头索引的键值缓存,其容量直接与模型规模、序列长度和 batch size 正相关。

以 LLaMA-7B 为例,其默认配置下单请求的 KV Cache 占用约为 4 至 8 MB,这个数字随着上下文长度的增长线性膨胀。当 GPU 显存被多个并发请求填满时,新的请求只能在队列中等待,直到已有请求释放缓存资源。这种内存竞争关系构成了推理 Serving 系统的核心约束,也是所有调度策略试图优化的目标。

值得注意的是,KV Cache 的空间复杂度为 O (batch_size × num_layers × sequence_length × hidden_dim),在长上下文场景下,单个请求就可能占用数十 GB 显存。因此,缓存的精细化管理不是可选项,而是生产级系统的必备能力。

二、PagedAttention:非连续内存分配的实现哲学

传统推理框架在管理 KV Cache 时,通常采用连续内存分配策略 —— 为一个请求的完整序列分配一段连续的 GPU 显存区域。这种方式实现简单,但带来两个严重的工程问题:内存碎片化和预分配浪费。当请求长度动态增长时,连续内存很难灵活扩展;而当请求提前结束时,已分配的内存又无法被其他请求有效复用。

vLLM 提出的 PagedAttention 彻底改变了这一局面。其核心思想借鉴了操作系统的虚拟内存分页机制:将 KV Cache 按固定大小的 block 进行切分,每个 block 存储固定数量的 token(例如 16 个 token)的 Key-Value 数据。这些 block 不需要物理连续,GPU 可以通过一个块表(block table)将逻辑上的序列映射到物理分散的显存位置。这种设计带来了几个关键优势:首先,内存碎片率大幅降低,研究数据表明相同显存下可支持的并发请求数提升 1.6 至 2.0 倍;其次,block 粒度的内存分配使得缓存回收更加灵活,当一个请求生成完毕,其占用的 block 可以立即被调度器回收并分配给新请求,而无需等待整个批处理周期结束。

在生产环境中部署 vLLM 时,有几个关键参数需要重点关注。block_size 决定了每个物理块容纳的 token 数量,默认值 16 在大多数场景下表现均衡 —— 过大的 block 会导致内部碎片加剧,过小的 block 则会增加块表管理的元开销。gpu_memory_utilization 控制用于 KV Cache 的显存比例,0.9 是较为稳健的起始值,留出约 10% 的显存用于计算中间结果和突发缓冲。max_model_len 设定了 KV Cache 能够容纳的最大 token 总数,这个值需要根据实际业务中最常见的输入输出长度分布来调优 —— 如果业务中大量出现超长文档摘要,则应将此值调高;反之则可以降低以释放更多空间给并发请求。

三、Continuous Batching:从静态批处理到动态调度

批处理(batching)是提升 GPU 利用率的基本手段,但在 LLM 推理场景中,传统的 Static Batching 面临一个根本性的效率瓶颈:同一个批次内的所有请求必须同时完成前向传播。当某个请求的输出长度远超其他请求时,短请求的 GPU 计算资源会被长请求 “绑架”,导致整体吞吐量下降。更糟糕的是,在自回归解码过程中,每个请求每生成一个 token 都需要一次前向传播,这意味着即使 batch 内只有一个长请求,GPU 也必须为其逐个 token 地运行计算,其他请求只能旁观等待。

Continuous Batching(又称 Dynamic Batching 或 Orion-style Batching)正是为解决这一问题而设计。它的核心策略是允许一个批次内的请求在不同时间完成解码 —— 当某个请求生成结束符(EOS)并释放其 KV Cache 占据的显存时,调度器立即从等待队列中选取新的请求加入批次,继续参与后续的 token 生成。这种 “流水线式” 的调度方式使得 GPU 的计算资源几乎始终处于饱和状态,实际测试中相比 Static Batching 可获得 2 至 3 倍的吞吐量提升。

实现 Continuous Batching 需要调度器具备两个核心能力。其一是灵活的内存管理能力 —— 当新请求加入批次时,调度器必须实时计算其 KV Cache 所需的显存增量,并在显存充足时执行加入操作,否则将请求保留在等待队列中。其二是细粒度的调度决策能力 —— 在每一个解码 step,调度器需要决定哪些请求继续参与计算、哪些请求可以移除、是否可以插入新的 prefill 请求。这个决策过程直接影响 GPU 利用率和请求延迟。

在 vLLM 中,max_num_seqs 控制单批次允许的最大并发请求数,默认 256 是一个经验性的保守值,对于显存较小的 GPU(如 24GB)可以适当降低以减少调度开销。此外,prefill_chunk_size 决定了 prefill 阶段每次处理的 token 数量,较大的值可以加速 prefill 但会增加单次计算的显存峰值,512 是一个平衡点。

四、缓存淘汰策略与调度优先级

当 GPU 显存接近饱和而新的请求持续到达时,调度器必须决定淘汰哪些已有请求的 KV Cache 以释放空间。这个决策过程涉及两个层面的策略:淘汰算法和调度优先级。

在淘汰算法层面,LRU(最近最少使用)是最直观的策略 —— 优先淘汰最久未被访问的缓存块,因为它在未来被再次使用的概率最低。但纯 LRU 在 LLM 场景中存在缺陷:一个刚启动的长请求在开始阶段会被频繁访问,但随着生成过程的推进,其被访问的频率会显著下降,此时保留它的缓存可能不如保留一个已经部分生成的中等长度请求更有价值。因此,更好的策略是采用 LRU 与 TTL(Time-To-Live)相结合的混合淘汰机制 —— 为每个缓存块维护一个未被访问的时间窗口,超过窗口后即使未被淘汰也会被降权。

vLLM 目前默认采用基于可用块数量的简单淘汰策略,在实际生产中可以结合业务特点引入自定义淘汰逻辑。例如,如果业务中大量存在短查询(如问答对),可以调高 max_model_len 的倒数来优先保留短请求的缓存;如果长文本生成是核心场景,则应确保每个长请求在开始生成后不会被中途 “饿死”。

调度优先级方面,大多数系统采用 FCFS(先到先服务)作为基础策略以保证公平性。但在高并发场景下,FCFS 可能导致长请求阻塞大量短请求,形成所谓的 “队头阻塞”。一个工程上常见的改进是引入分层队列 —— 将请求按预估的计算量(可以通过输入 token 数近似估算)划分到不同优先级队列,调度器优先处理高优先级队列中的请求,但同时设置每个队列的最大并发比例以防止低优先级请求被饿死。

对于更复杂的场景,还可以引入 preemption(抢占)机制 —— 当高优先级请求到达但显存不足时,系统可以选择牺牲某个低优先级请求的 KV Cache,将其状态写入 CPU 内存或直接丢弃,待高优先级请求处理完毕后再恢复。这种机制在多租户场景下尤为重要,但实现复杂度较高,需要考虑状态恢复和请求重试的逻辑。

五、生产环境的监控指标体系

KV Cache 管理和批处理调度的效果最终需要通过可量化的指标来评估。以下是一套生产级推理服务应重点监控的指标体系:

KV Cache 利用率与命中率是最核心的指标。前者反映显存的使用效率,后者反映缓存复用带来的计算节省。如果 KV Cache 命中率持续偏低,说明业务请求之间的上下文相似度不足,缓存复用的价值有限,此时应将重点放在优化并发调度策略而非单纯增加缓存容量。

Batch Size 分布揭示了调度器的实际行为。理想的分布应集中在较高数值(如 32 至 128 之间),如果大量批次的 size 仅为 1 或 2,说明系统处于严重的资源饥饿状态,GPU 利用率低下。此时需要检查是否是 max_model_len 设置过小导致每个请求占用了过多独立显存,或者是请求到达率本身不足。

Pending Queue 长度和等待时长直接关联用户体验。当 pending 队列开始增长时,意味着请求到达率超过了系统的处理能力,此时需要考虑水平扩展或调整 gpu_memory_utilization 以在单节点内容纳更多并发。

Preemption 频率反映了调度策略与业务负载的匹配程度。如果 preemption 发生过于频繁,说明系统频繁被迫中断请求以释放资源,这会显著增加请求的尾延迟。可以通过适当增大 max_model_len 或调整请求优先级策略来缓解。

内存碎片率是 PagedAttention 效率的直观体现。虽然 vLLM 的分页机制已经大幅降低了碎片,但在长时间运行后仍可能出现碎片累积。定期监控并在碎片率超过阈值时触发缓存清理或进程重启是必要的运维操作。

六、工程实践清单

综合以上分析,将关键配置和决策点总结为以下可操作清单:

在 vLLM 启动参数方面,--gpu-memory-utilization 0.9 保留 10% 显存作为计算缓冲是稳健的起始配置;--max-model_LEN 8192 适用于大多数对话和短文本生成场景,若业务中存在超长上下文则需相应调高;--block-size 16 是大多数模型的推荐默认值;--max-num-seqs 256 根据 GPU 显存大小调整,24GB 显存建议降低至 128 以减少调度开销;--prefill-chunk-size 512 在 prefill 速度和显存峰值之间取得平衡。

在调度策略选型方面,如果业务负载相对稳定、请求长度差异不大,Continuous Batching 配合 FCFS 调度即可满足需求;如果存在显著的长尾请求,应引入分层优先级队列或基于预估计算量的调度策略;多租户环境下建议实现 preemption 机制并配合 SLA 保障。

在监控告警方面,建议对 KV Cache 利用率设置 >85% 的告警阈值,对 pending queue 长度设置持续 >10 的告警,对 preemption 频率设置 >5% 的告警。

结语

KV Cache 的生命周期管理和动态批处理调度构成了 LLM 推理 Serving 层的两大基石。PagedAttention 通过分页机制解决了内存碎片化难题,使得高并发推理在有限显存下成为可能;Continuous Batching 通过解耦请求的生命周期,最大限度地榨取了 GPU 的计算吞吐量。这两项技术与上一层的「多后端动态路由」策略形成正交关系 —— 路由层决定 “哪个请求去哪个后端”,而缓存与调度层决定 “如何在单一后端内高效执行”。二者协同配合,才能构建出真正具备生产级能力的 LLM 推理服务。

参考资料:vLLM 官方文档与 PagedAttention 论文(Kwon et al., 2023)