3、核心概念:LLM推理原理、KV Cache、PagedAttention、连续批处理(Continuous Batching)机制

要真正驾驭 vLLM 的离线批处理与在线服务双模式,必须先穿透其底层引擎的设计逻辑。vLLM 之所以能在吞吐量和显存效率上显著优于传统推理框架,核心在于它对 Transformer 解码过程的深刻理解与三项关键技术的工程化落地:KV Cache 的动态管理、PagedAttention 的非连续显存分配,以及 Continuous Batching 的微观调度。本节将逐一拆解这些概念,并揭示它们如何协同工作,为后续的实战操作打下坚实的认知基础。

vLLM 核心机制协同关系图 输入序列 [token1, token2, ...] Prompt Tokens Transformer 解码层 Self-Attention + FFN 逐 Token 生成 KV Cache 存储历史 Token 的 Key 和 Value 矩阵 显存占用随序列增长 读取历史 KV 计算 Attention PagedAttention 显存管理 逻辑 KV 块 Block 0, 1, 2... 物理显存块 非连续地址空间 块表映射 逻辑→物理地址 显存碎片消除 按需分配 Continuous Batching 迭代级调度 迭代 1 Req A, B, C 全部生成 迭代 2 A 完成, 加入 D B, C, D 生成 迭代 3 B 完成, 加入 E C, D, E 生成 传统静态 Batching 等待最慢请求 GPU 利用率低 三者协同:KV Cache 减少重复计算 → PagedAttention 优化显存 → Continuous Batching 提升吞吐

上图清晰地展示了 vLLM 引擎内部三大核心机制的协同关系。下面我们将逐一深入剖析每个环节的原理与工程实现。

3.1 LLM 推理原理:从概率分布到 Token 生成

核心原理:自回归解码

当前主流的大语言模型(如 GPT、LLaMA、Qwen 等)均基于 Transformer 的解码器(Decoder)架构,其推理过程本质上是自回归(Autoregressive)的。这意味着模型在生成第 N 个 Token 时,会将前 N-1 个 Token(即 Prompt 和已生成的 Token)全部作为输入,通过模型计算得到下一个 Token 的概率分布,然后根据采样策略(如贪心搜索、温度采样、Top-p 采样)选出最终的 Token。这个过程会一直循环,直到生成结束符(EOS Token)或达到最大长度限制。

以一个简单的例子说明:假设 Prompt 为“中国的首都是”,模型第一步会计算整个序列的概率分布,选出“北”字;第二步会将“中国的首都是北”作为输入,再选出“京”字;第三步输入“中国的首都是北京”,选出“。”。每一步都依赖完整的输入序列。

关键洞察:自回归解码的每一步都包含一次完整的前向传播(Forward Pass)。如果不做任何优化,生成 100 个 Token 就需要执行 100 次完整的前向计算,其中包含大量对历史 Token 的重复计算,计算复杂度为 O(N²),N 为序列长度。这正是引入 KV Cache 的根本原因。

判断方法:如何识别推理阶段的性能瓶颈

在实际部署中,我们可以通过以下指标判断推理是否高效:

  • 首 Token 延迟(TTFT, Time To First Token):从请求发出到收到第一个 Token 的时间。该指标主要受 Prompt 长度和模型计算速度影响,与 KV Cache 的预填充(Prefill)阶段直接相关。
  • 单 Token 生成延迟(TPOT, Time Per Output Token):生成后续每个 Token 的平均耗时。该指标主要受显存带宽和 KV Cache 读取速度影响。
  • 吞吐量(Throughput):单位时间内模型能处理的 Token 总数或请求总数。这是衡量系统整体效率的核心指标。

如果 TPOT 过高,往往意味着 KV Cache 的读取成为了瓶颈,或者显存带宽不足;如果 TTFT 过高,则可能是 Prompt 过长导致 Prefill 计算量过大。

案例拆解:一个简单的生成过程

假设我们使用 vLLM 离线批处理接口,输入两个请求:

请求1: "给我讲一个关于AI的笑话"
请求2: "用一句话解释什么是量子计算"

在未使用 KV Cache 的朴素实现中,生成“笑话”这个词时,模型需要重新计算“给我讲一个关于AI的”这 8 个 Token 的 Key 和 Value 矩阵。而使用 KV Cache 后,这 8 个 Token 的 KV 值在第一次计算后就被缓存,后续生成“笑话”时只需计算新 Token 的 KV 值,并与缓存的历史 KV 值做 Attention 计算即可。这极大地减少了重复计算,将计算复杂度从 O(N²) 降为 O(N)。

常见误区与总结

误区:认为 KV Cache 只是简单的“缓存”,不占用显存。实际上,KV Cache 的显存占用与模型层数、注意力头数、头维度、序列长度成正比。对于一个 7B 参数的模型,在 FP16 精度下,每个 Token 的 KV Cache 大小约为 0.1MB。当并发请求数达到数百时,KV Cache 的显存占用会轻松超过模型权重本身。

总结:LLM 推理的核心是自回归解码,KV Cache 是消除重复计算、降低单 Token 生成延迟的必备手段,但它的引入也带来了新的显存管理挑战。

3.2 KV Cache 与 PagedAttention:显存管理的革命

核心原理:为什么需要 PagedAttention

传统推理框架(如 HuggingFace Transformers)在管理 KV Cache 时,会为每个请求预先分配一块连续且固定大小的显存。这种方式的弊端非常明显:

  1. 预分配浪费:我们无法预知请求最终会生成多少 Token,因此只能按最大序列长度(如 2048)来分配。但实际请求可能只生成了 50 个 Token 就结束了,导致大量显存被闲置浪费。
  2. 内部碎片化:即使我们动态扩容,由于显存分配是连续的,当多个请求的 KV Cache 大小不一且频繁申请/释放时,会产生大量无法利用的显存碎片。
  3. 无法共享:在并行采样(如 Beam Search)或前缀共享场景下,传统方式无法让多个序列共享相同的 KV Cache 块。

vLLM 借鉴了操作系统中虚拟内存分页(Paging)的思想,提出了 PagedAttention。它将 KV Cache 划分为固定大小的“块”(Block,通常大小为 16 或 32 个 Token)。每个请求的逻辑 KV Cache 由一个或多个块组成,但这些块在物理显存中可以是非连续的。vLLM 维护一张“块表”(Block Table),记录逻辑块到物理块的映射关系。

对比维度 传统连续显存管理 PagedAttention 非连续管理
分配方式 一次性预分配最大长度 按需分配,动态增长
显存利用率 低(存在大量内部碎片) 高(接近物理极限)
序列长度限制 受限于预分配大小 仅受物理显存总量限制
并行采样支持 不支持 KV 共享 支持块级共享,节省显存
调度灵活性 低(无法应对突发请求) 高(支持 Continuous Batching)

操作流程:PagedAttention 如何工作

在 vLLM 的 Prefill(预填充)阶段,系统会计算 Prompt 中所有 Token 的 KV 值,并将它们写入分配的物理块中。在 Decode(解码)阶段,每生成一个新 Token,系统会计算其 KV 值,并写入当前块的末尾。如果当前块已满,则通过块表分配一个新的物理块。Attention 计算时,GPU Kernel 会通过块表间接寻址,读取非连续的 KV 数据。

经验提醒:PagedAttention 不仅解决了显存碎片问题,还使得批处理大小(Batch Size)不再受限于单个请求的最大序列长度,而是受限于物理显存中可用的 KV Cache 块总数。这为高并发场景提供了可能。

案例拆解:显存利用率对比

假设一块 80GB 的 A100 显卡,模型权重占用 20GB,剩余 60GB 用于 KV Cache。传统方式下,若每个请求预分配 2048 Token 的 KV Cache(约 0.2GB),则最多支持 300 个并发请求。但实际上,这些请求的平均生成长度可能只有 200 Token,实际 KV Cache 占用仅为 0.02GB。这意味着 90% 以上的显存被浪费。使用 PagedAttention 后,系统可以按需分配,实际支持的并发请求数可能提升 5-10 倍,达到 1500-3000 个。

常见误区与总结

误区:认为 PagedAttention 会降低计算性能。实际上,虽然引入了间接寻址,但 vLLM 高度优化的 CUDA Kernel 使得这种开销几乎可以忽略不计。相比显存浪费导致的无法并发或 OOM,PagedAttention 带来的性能收益是巨大的。

总结:PagedAttention 是 vLLM 的核心创新之一,它通过非连续显存管理,将 KV Cache 的利用率提升至接近物理极限,为高并发推理扫清了显存障碍。

3.3 连续批处理(Continuous Batching)机制

核心原理:从静态批处理到迭代级调度

传统的静态批处理(Static Batching)方式下,推理框架会等待一批请求全部到达后,再统一进行前向传播。这一批请求必须全部生成完毕后,才能释放显存并接收下一批请求。这种方式存在严重的“木桶效应”:整个批次的生成速度受限于最慢的那个请求。如果某个请求生成了很长的序列,其他已经完成的请求所占据的 GPU 资源也无法被释放,导致 GPU 利用率大幅波动。

Continuous Batching(连续批处理),也称为迭代级调度(Iteration-level Scheduling),打破了这种“批”的界限。它在每一个解码步骤(Iteration)结束后,都会检查当前批次中是否有请求已经完成。如果有,则立即将其从批次中移除,并释放其占用的 KV Cache 物理块;同时,如果有新的请求等待,则立即将其加入当前批次,参与下一轮的解码计算。这样,GPU 始终在处理“活跃”的请求,不会因为等待慢请求而空闲。

判断方法:如何评估 Batching 策略的优劣

我们可以通过观察 GPU 利用率曲线来判断。静态批处理的利用率曲线是锯齿状的,波峰和波谷差距很大;而 Continuous Batching 的利用率曲线则相对平稳,长期保持在高位。此外,平均请求延迟(Average Latency)和P99 延迟也是关键指标。Continuous Batching 能显著降低 P99 延迟,因为它避免了慢请求阻塞快请求的情况。

案例拆解:一个动态调度的完整闭环

假设我们有一个在线服务,当前批次中有 3 个请求(A、B、C)。

  • 初始状态:A 需要生成 100 Token,B 需要生成 10 Token,C 需要生成 50 Token。
  • 静态批处理:系统必须等待 A 生成完 100 Token 后,才能释放 B 和 C 的资源。B 和 C 虽然早已生成完毕,但只能空等,导致 GPU 算力浪费。
  • Continuous Batching:
    1. 第 10 步迭代后,B 生成完毕。系统立即将 B 移出批次,释放其 KV Cache 块,并加入新请求 D。
    2. 第 50 步迭代后,C 生成完毕。系统将 C 移出,加入新请求 E。
    3. 第 100 步迭代后,A 生成完毕。此时 D 和 E 可能仍在生成中,系统继续调度。

通过这种方式,GPU 在整个过程中始终在处理 3 个请求(A+B+C → A+C+D → A+D+E → D+E...),计算资源被充分利用。整体吞吐量相比静态批处理可提升 2-3 倍。

实战场景:在离线批处理任务中,如果输入数据集的 Token 长度差异巨大(例如有的文章 100 字,有的文章 10000 字),使用 vLLM 的 Continuous Batching 能自动平衡负载,避免长文本任务拖垮整个批次的效率。你无需手动对数据进行长度排序,vLLM 的调度器会自动优化。

常见误区与总结

误区:认为 Continuous Batching 会增加单请求的延迟。实际上,对于单个请求而言,其计算量并未增加。相反,由于 GPU 利用率提升,整体排队时间减少,单个请求的响应速度通常会更快。唯一的代价是实现复杂度较高,但这由 vLLM 框架内部承担,对使用者透明。

总结:Continuous Batching 是 vLLM 实现高吞吐的关键调度策略。它与 PagedAttention 紧密配合——PagedAttention 提供了快速分配/释放 KV Cache 的能力,而 Continuous Batching 则利用这一能力实现了请求的即时插拔。理解这三者的关系,是掌握 vLLM 性能调优的前提。