3、vLLM核心概念:PagedAttention、Continuous Batching、KV Cache、吞吐量与延迟优化原理

在大模型推理服务化落地过程中,vLLM 之所以能成为生产环境的首选框架之一,核心在于它对 Transformer 解码阶段的内存管理与调度机制做了系统性重构。本章将深入拆解 vLLM 的四大支柱:KV Cache 的内存本质、PagedAttention 的分页管理、Continuous Batching 的动态调度,以及吞吐量与延迟之间的权衡艺术。理解这些原理,是后续章节中配置引擎参数、诊断性能瓶颈、设计服务容量的基础。

vLLM 推理引擎核心机制全景 KV Cache 内存管理 逻辑连续 [K1|V1] [K2|V2] [K3|V3] ... 物理分块 Block 7 → Block 3 Block 9 → Block 1 块表 PagedAttention 核心 按需分配 16KB Block 消除内部碎片与显存浪费 Continuous Batching 调度 Step 1: [Req A] [Req B] [Req C] Step 2: [Req B] [Req C] [Req D] 对比传统 Static Batching 必须等待最慢请求完成 新请求只能排队等待 性能指标与优化目标 吞吐量 Tokens/秒 TTFT 越低越好 延迟 TPOT 越低越好 首Token延迟 优化杠杆 增大 Batch → 提高吞吐 限制 Batch → 降低延迟 核心公式:吞吐量 = 并发请求数 × 平均生成长度 / 总耗时 PagedAttention 解决“显存放不下”,Continuous Batching 解决“GPU 闲不住” 显存利用率对比 传统预留式:浪费 60%~80% vLLM 分页式:利用率 > 90% 支持超额订阅(Overcommit) Block 级 LRU 淘汰 调度粒度对比 传统:请求级调度(粗粒度) vLLM:迭代级调度(细粒度) 每步解码后重新决策 抢占与恢复机制 生产环境关键参数 --max-num-seqs: 最大并发 --gpu-memory-utilization: 显存占比 --max-model-len: 最大序列长度 --block-size: Block 大小(16/32)

在开始深入之前,请先建立一个整体认知:vLLM 的优化并非单一技术突破,而是“内存管理(PagedAttention)+ 调度策略(Continuous Batching)+ 硬件利用(吞吐/延迟权衡)”三位一体的系统工程。上图展示了这三者如何协同工作——左侧解决显存碎片化问题,中间解决 GPU 空闲问题,右侧则定义了优化的量化指标。

3.1 KV Cache 的本质与显存瓶颈

核心原理:Transformer 解码器在生成每个 token 时,都需要计算注意力分数,而注意力计算依赖当前 token 的 Query 与所有历史 token 的 Key 和 Value。为了避免重复计算,推理框架会将历史 token 的 K 和 V 矩阵缓存在显存中,这就是 KV Cache。以 Llama-2-7B 为例,每个 token 的 KV Cache 大小为:2(K和V)× 32(层数)× 4096(隐藏维度)× 2(字节,FP16)= 1MB。当生成长度为 2048 的序列时,仅 KV Cache 就需要 2GB 显存。

显存碎片化问题:传统推理框架(如 HuggingFace Transformers 原生实现)会为每个请求预先分配最大长度的 KV Cache 空间。假设设置最大生成长度为 2048,但实际请求平均只生成 200 token,那么 90% 的显存被浪费。更严重的是,不同请求的生成长度差异巨大,导致显存出现大量碎片,无法被其他请求利用。

对比维度传统预留式分配vLLM 分页式分配
分配单位整个请求的连续显存固定大小的 Block(默认 16KB)
内部碎片严重(预留长度远大于实际)仅最后一块可能不满
外部碎片无法利用其他请求的空闲区通过块表映射,物理上可不连续
显存利用率通常 20%~40%可达 90% 以上
内存共享不支持支持(如并行采样时共享前缀)

案例拆解:假设我们有一张 24GB 显存的 A10G 显卡,部署 Llama-2-7B(权重占 14GB)。传统方式下,剩余 10GB 显存按最大长度 2048 预留,每个请求需要 2GB,最多只能并发 5 个请求。而实际平均生成长度只有 300 token(0.3GB/请求),理论上可以支持 33 个并发,但传统方式只能利用 5 个并发,GPU 利用率极低。vLLM 通过按需分配 Block,可以将并发数提升至 20~30 个,吞吐量提升 4~6 倍。

经验提醒:在配置 --max-model-len 时,不要盲目设置过大。该参数直接影响 KV Cache 的预留策略。建议根据业务实际分布设置,例如 P99 生成长度 + 20% 余量。过大的 max-model-len 会导致显存预留过多,降低有效并发数。

3.2 PagedAttention:操作系统的内存管理思想

核心原理:PagedAttention 借鉴了操作系统虚拟内存的分页机制。它将 KV Cache 划分为固定大小的 Block(默认 16KB,可容纳 16 个 token 的 K/V),每个请求维护一张“块表”(Block Table),记录逻辑 Block 到物理 Block 的映射。当请求需要新的 KV 空间时,vLLM 从全局 Block 池中分配一个空闲物理 Block,并在块表中添加映射项。

关键数据结构:

# 逻辑视图(请求视角)
Request A 的 KV Cache: [Block 0] → [Block 1] → [Block 2] → ...
# 物理视图(显存视角)
Block Table: {0: 物理块7, 1: 物理块3, 2: 物理块9, ...}
# 全局 Block 池
free_blocks = [1, 2, 4, 5, 6, 8, ...]  # 空闲块列表

核心优势:一是按需分配,请求生成多少 token 就分配多少 Block,彻底消除内部碎片;二是物理不连续,通过块表映射,显存碎片可以被任意请求利用;三是支持内存共享,当多个请求共享相同前缀(如系统提示词)时,可以共享相同的物理 Block,通过引用计数管理生命周期。

操作流程:当请求完成解码后,vLLM 会释放其占用的物理 Block 并归还全局池。如果显存不足,vLLM 会触发抢占(Preemption):将某些请求的 Block 交换到 CPU 内存,腾出 GPU 显存给新请求。被抢占的请求在后续调度时,需要从 CPU 加载回 GPU,这会增加延迟,因此生产环境应尽量避免频繁抢占。

风险提示:PagedAttention 的块表查询会引入少量计算开销(约 2%~5%)。在短序列(如 <100 token)场景下,这种开销可能抵消部分收益。对于超长文本生成(如 32K 上下文),PagedAttention 的优势则非常明显,因为传统方式根本无法分配连续显存。

3.3 Continuous Batching:迭代级动态调度

核心原理:传统推理框架采用“静态批处理”(Static Batching):一批请求同时开始,必须等最慢的请求完成后,整批才能释放,新请求只能排队等待。这导致 GPU 在批处理尾部出现大量空闲。vLLM 采用 Continuous Batching(又称 Iteration-level Scheduling),在每个解码步骤(Step)结束后,立即将已完成的请求移出批次,并加入新的请求。

调度流程:

  1. 初始化:从等待队列中选取请求,填充至 GPU 可容纳的最大批次。
  2. 迭代解码:每个 Step 对所有活跃请求执行一次前向传播,生成下一个 token。
  3. 完成检测:检查是否有请求生成 EOS token 或达到最大长度,将其移出批次并释放 KV Cache。
  4. 补充新请求:从等待队列中选取新请求,加入下一轮迭代。

对比分析:

特性Static BatchingContinuous Batching
调度粒度请求级(整个生命周期)迭代级(每个解码步骤)
GPU 空闲率批次尾部明显空闲几乎为零
新请求响应需等待当前批次完成下一个 Step 即可加入
实现复杂度简单需要管理动态批次和显存分配
吞吐量提升基线2~3 倍(混合长短请求场景)

实战场景:某客服机器人服务,请求长度分布极不均匀:约 30% 的请求只生成 50 token(简单问答),50% 生成 200~500 token(多轮对话),20% 生成 1000+ token(长文本总结)。使用静态批处理时,短请求必须等待长请求完成,平均响应时间被拉长 3 倍。切换到 vLLM 后,短请求在 2~3 个 Step 内即可完成并释放显存,新请求立即补入,整体吞吐量提升 2.8 倍,P95 延迟降低 60%。

关键参数:--max-num-seqs 控制最大并发批次大小。增大该值可提升吞吐量,但会增加显存压力和单请求延迟。建议从 256 开始,根据显存余量和延迟要求逐步调整。同时,--max-paddings 参数控制批次中允许的 padding token 数量,过大的 padding 会浪费计算资源。

3.4 吞吐量与延迟的权衡艺术

核心指标定义:

  • 吞吐量(Throughput):单位时间内生成的 token 总数,通常以 tokens/s 衡量。计算公式:吞吐量 = 并发请求数 × 平均生成长度 / 平均完成时间。
  • TTFT(Time To First Token):从请求发出到收到第一个 token 的时间。受 prefill 阶段影响,与输入长度和批次大小相关。
  • TPOT(Time Per Output Token):生成每个输出 token 的平均时间。受 decode 阶段计算量和批次大小影响。
  • 端到端延迟:TTFT + TPOT × 输出长度。

优化杠杆分析:增大批次大小可以摊薄权重加载和计算开销,提升吞吐量,但会线性增加每个请求的 TPOT(因为 GPU 计算资源被更多请求共享)。反之,减小批次可降低延迟,但 GPU 利用率下降。vLLM 提供了 --max-num-seqs 和 --gpu-memory-utilization 两个核心旋钮来调节这一平衡。

案例拆解:某 AI 写作助手服务,目标要求:P95 TTFT < 1.5s,P95 TPOT < 80ms,吞吐量最大化。初始配置 --max-num-seqs 128,实测吞吐量 850 tokens/s,但 P95 TPOT 达到 120ms,超出目标。逐步调低至 96,TPOT 降至 85ms,吞吐量降至 780 tokens/s。继续调低至 64,TPOT 降至 65ms,但吞吐量仅 620 tokens/s。最终选择 96 作为平衡点,并配合 --gpu-memory-utilization 0.9 确保显存充足。

常见误区:不要只关注吞吐量而忽略延迟分布。在混合负载场景下,长请求会持续占用 GPU 资源,导致短请求的 TTFT 飙升。建议使用 vLLM 的 --priority 参数(如果版本支持)或通过应用层做请求分类,将短请求优先调度。另外,--max-model-len 设置过大会导致 KV Cache 预留过多,降低有效并发数,反而降低吞吐量。

3.5 生产环境调优实践与复盘

调优流程:

  1. 基准测试:使用 vllm benchmark 工具或自定义脚本,测量不同并发下的吞吐量和延迟分布。
  2. 显存监控:通过 nvidia-smi 或 vLLM 的 --verbose 日志观察 KV Cache 使用率,目标利用率在 85%~95% 之间。
  3. 参数扫描:对 --max-num-seqs、--gpu-memory-utilization、--block-size 做网格搜索,记录每组参数下的吞吐量和延迟指标。
  4. 长尾优化:分析 P99 延迟,定位是否因抢占(Preemption)导致。如果抢占频繁,考虑降低 --max-num-seqs 或增加 CPU offload 缓冲。

复盘总结:在一次实际部署中,我们发现某模型在 --block-size 16 下吞吐量比 --block-size 32 高 12%,但显存碎片率也更高。原因是该模型的平均生成长度较短(约 200 token),16 的 block-size 能更精细地分配显存。对于长文本场景(平均 2000+ token),32 的 block-size 反而更优,因为减少了块表查询次数。这提醒我们:没有绝对最优的参数,只有针对特定负载的最优配置。

经验总结:vLLM 的优化核心是“让 GPU 永远在计算,让显存永远在利用”。PagedAttention 解决了“放不下”的问题,Continuous Batching 解决了“闲不住”的问题,而吞吐量与延迟的权衡则是业务需求与硬件资源的博弈。在生产环境中,建议建立持续的性能监控体系,定期根据业务流量变化调整参数。