Prefix Caching 技术详解:从原理到 vLLM/LMCache 实践

1. 引言:为什么需要 Prefix Caching?

Prefix Caching(前缀缓存) 是一种通过缓存共享前缀的 KV Cache 来避免重复计算的推理优化技术。在大语言模型(LLM)的实际应用中,许多请求往往共享相同的前缀(Prefix),例如:

  • System Prompt:同一应用的所有请求使用相同的系统指令
  • Few-shot Examples:相同的示例在多个请求中复用
  • RAG 场景:检索到的文档片段被多个查询复用
  • 多轮对话:历史对话上下文在后续轮次中反复出现

如果每次请求都从零开始计算这些重复前缀的 KV Cache,将造成巨大的计算浪费。以一个 8K token 的 System Prompt 为例:

  • 按照 KV Cache 原理简介 中的分析,Prefill 阶段 Attention 部分的计算复杂度为 $O(N^2)$
  • 如果每秒有 100 个请求,每个请求都重复计算这 8K token 的 KV Cache,将消耗大量 GPU 算力
  • 这些重复计算本质上是完全冗余的——相同的输入总是产生相同的 KV 输出

Prefix Caching 正是为解决这一问题而设计的优化技术。其核心思想非常直观:

相同的 token 序列总是产生相同的 KV Cache,因此只需计算一次,后续请求直接复用即可。

通过 Prefix Caching,我们可以跳过前缀部分的 QKV 投影和自注意力计算,将 Prefill 计算量大幅降低。例如对于一个 8K 前缀 + 256 token 用户输入的典型场景,理论加速比可达约 $16.5\times$(详见第 5.1 节分析),从而显著降低 TTFT (Time-To-First-Token) 并提升系统吞吐量。


2. Prefix Caching 核心原理

本章将介绍 Prefix Caching 的核心原理,包括基本概念、前缀匹配的约束条件以及哈希 Key 的设计方案。

2.1 基本概念

Prefix Caching 的核心机制可以分为三个步骤:

  1. 切分(Chunking):将 token 序列按固定大小切分为多个 Chunk(在通用概念中称为 Chunk,在 vLLM 的 PagedAttention 实现中特指 Block)
  2. 哈希(Hashing):为每个 Chunk 计算唯一的哈希值作为缓存 Key
  3. 查找与复用(Lookup & Reuse):新请求到来时,逐 Chunk 查找缓存,命中则复用,未命中则计算并存储
输入 Token 序列: [T0,  T1,  T2,  T3,  T4,  T5,  T6,  T7,  T8,  T9,  T10,  T11]
                |----- Chunk 0 -----|---- Chunk 1 -----|----- Chunk 2 -----|
                    Hash: H0              Hash: H1            Hash: H2

缓存查找:
  - H0 命中 → 复用 Chunk 0 的 KV Cache
  - H1 命中 → 复用 Chunk 1 的 KV Cache
  - H2 未命中 → 计算 Chunk 2 的 KV Cache 并存储

2.2 前缀匹配的约束

由于 Transformer 的因果注意力(Causal Attention)机制,后续 token 的 KV Cache 依赖于之前所有 token 的信息。这意味着:

  • Prefix Caching 必须从序列开头开始逐 Chunk 匹配
  • 一旦某个 Chunk 未命中,后续所有 Chunk 都必须重新计算
  • 即使后续 Chunk 的 token 内容与缓存中某条记录相同,由于上下文不同,其 KV Cache 也不相同
请求 A: [System Prompt] + [Query A]  → 缓存 System Prompt 的 KV
请求 B: [System Prompt] + [Query B]  → 复用 System Prompt 的 KV ✓
请求 C: [Query C] + [System Prompt]  → 无法复用(前缀不匹配)✗

:对于打破前缀约束的非前缀复用场景(如 RAG 中的乱序文档),可以使用 CacheBlend 等高级技术通过选择性重算实现近似复用。

2.3 哈希 Key 的设计

Prefix Caching 的哈希 Key 设计直接影响缓存的命中率和正确性。常见的设计有两种:

2.3.1 增量哈希

增量哈希(Incremental Hash)方案中,每个 Chunk 的哈希值依赖于之前所有 Chunk 的内容,形成一条哈希链:

\[H_0 = \text{Hash}(\text{Chunk}_0)\] \[H_i = \text{Hash}(H_{i-1}, \text{Chunk}_i), \quad i > 0\]

优点

  • 天然编码了前缀依赖关系
  • 相同的 token 内容 + 相同的前缀 → 相同的哈希值
  • 不同的前缀 + 相同的 token 内容 → 不同的哈希值

缺点

  • 需要按序计算,无法并行

2.3.2 内容哈希 + 位置编码

直接对 Chunk 内容及其位置信息计算哈希:

\[H_i = \text{Hash}(\text{Chunk}_i, \text{Position}_i)\]

优点

  • 可以并行计算所有 Chunk 的哈希
  • 适合预计算场景

缺点

  • 需要额外机制确保前缀一致性

vLLM 和 LMCache 均采用增量哈希方案,以确保缓存复用的正确性。


3. vLLM 的 Automatic Prefix Caching (APC)

vLLM 从 v0.4.0 开始引入了 Automatic Prefix Caching (APC) 功能 [1],实现了 GPU 显存内的高效前缀缓存。

背景知识:vLLM 的 APC 设计深受 SGLang 提出的 RadixAttention [3] 启发。不同之处在于,SGLang 使用 Radix Tree(基数树)来维护 token 序列与 KV Cache 的映射关系,而 vLLM 选择了更易于与其 Block Manager 集成的 Hash Table(哈希表) 方案。尽管数据结构不同,两者在缓存淘汰策略(如 LRU + 引用计数)上是高度一致的。Hash Table 方案的优势在于查找时间复杂度为 $O(1)$ 且易于与现有的 PagedAttention 块管理集成,但在处理大量极短前缀时可能会面临一定的内存碎片化挑战。

3.1 启用方式

vLLM 支持通过命令行参数和 Python API 两种方式启用 APC。

# 启动 vLLM 服务时开启 APC
vllm serve meta-llama/Llama-2-7b-hf --enable-prefix-caching
# 使用 Python API 时开启 APC
from vllm import LLM
llm = LLM(model="meta-llama/Llama-2-7b-hf", enable_prefix_caching=True)

3.2 核心实现:Block Hash

vLLM 的 APC 基于 PagedAttention(一种将 KV Cache 分块管理的内存优化技术)的 Block 粒度实现缓存。核心数据结构是 BlockHashToBlockMap

# vLLM Block Hash 计算逻辑(简化)
def hash_block_tokens(
    parent_block_hash: Optional[int],  # 父 Block 的哈希值
    curr_block_token_ids: Tuple[int, ...],  # 当前 Block 的 Token IDs
) -> int:
    """计算 Block 的哈希值,形成哈希链"""
    return hash((parent_block_hash, curr_block_token_ids))

关键设计点:

  1. Block 粒度:以 Block(通常 16-32 个 token)为缓存单位,而非单个 token
  2. 哈希链:每个 Block 的哈希值依赖于父 Block,确保前缀一致性
  3. 引用计数:使用引用计数管理 Block 生命周期,支持多请求共享

3.3 缓存查找与分配流程

当新请求到来时,vLLM 的 Block Manager 执行以下流程:

1. 将请求的 Token 序列切分为 Block 大小的 Chunk
2. 从第一个 Block 开始,逐个计算哈希并查找缓存:
   - 命中:复用已有 Block,增加引用计数
   - 未命中:分配新 Block,计算 KV Cache,存入缓存
3. 标记第一个未命中的位置为 Prefill 起点
4. 仅对未命中部分执行 Prefill 计算
graph TD
    Req["请求 Token 序列"] --> B0["Block 0: [T0...T15]"]
    Req --> B1["Block 1: [T16...T31]"]
    Req --> B2["Block 2: [T32...T47]"]

    B0 --> H0["Hash: H0"]
    B1 --> H1["Hash: H1"]
    B2 --> H2["Hash: H2"]

    H0 --> M0{"命中?"}
    H1 --> M1{"命中?"}
    H2 --> M2{"命中?"}

    M0 -- 是 --> R0["复用 KV Cache"]
    M1 -- 是 --> R1["复用 KV Cache"]
    M2 -- 否 --> C2["计算 KV Cache"]

    C2 --> S2["存入缓存"]

3.4 Prefix Caching 与混合注意力模型

对于采用混合注意力机制的模型(如 Gemma-3 的 Sliding Window + Full Attention),vLLM 的 Hybrid KV Cache Manager 提供了分层的 Prefix Caching 支持:

  • Full Attention 层:标准的从左到右前缀匹配
  • Sliding Window 层:从右到左匹配,只关心最近 sliding_window_size 个 token
  • 混合模型:取 Full Attention 和 Sliding Window 匹配长度中的较小值作为有效匹配长度

3.5 局限性

vLLM 原生 APC 的主要局限在于:

  1. 仅限 GPU 显存:缓存存储在 GPU 显存中,受容量限制。此外,当大量短前缀缓存占据显存时,可能导致 Block Table 碎片化,影响大 batch 的分配效率。
  2. 单实例隔离:不同 vLLM 实例之间无法共享缓存
  3. 重启丢失:进程重启后缓存完全丢失

这些局限正是 LMCache 等外部 KV Cache 管理系统要解决的问题。下一章将介绍 LMCache 如何通过多级存储架构突破这些限制。


4. LMCache 的多级 Prefix Caching

LMCache 在 vLLM APC 的基础上,提供了跨介质、跨实例的 Prefix Caching 能力。

4.1 架构概览

LMCache 在 vLLM 的 GPU 显存缓存(由 APC 管理)之外,提供了额外的多级存储扩展:

层级 存储介质 典型延迟 应用场景
L1 CPU 内存 (LocalCPUBackend) ~100 μs 本地扩展缓存
L2 P2P 网络 (P2PBackend) ~1 ms 跨实例共享
L3 本地磁盘 (LocalDiskBackend/GDS) ~10 ms 持久化缓存
L4 远程存储 (Redis/S3/Mooncake) ~100 ms 集群级共享

:GPU 显存中的 KV Cache 由 vLLM 的 PagedAttention 和 APC 机制管理,LMCache 负责 GPU 显存之外的存储层级。此表层级编号为 LMCache 扩展存储的编号,与经典缓存层级中的 L1/L2 含义略有不同。

4.2 Token Hash 机制

LMCache 使用 Token Content Hash 作为缓存 Key,与 vLLM 的 Block Hash 设计类似但更加灵活:

# LMCache Token Hash 计算(简化)
# 注意:实际实现通常使用 XXHash 等高性能确定性哈希算法,
# 以确保跨进程/跨实例的一致性。此处使用 hash() 仅为简化示意。
class TokenDatabase:
    def get_cache_key(
        self,
        token_ids: List[int],
        chunk_size: int,
    ) -> List[CacheKey]:
        """将 Token 序列切分并计算增量哈希"""
        keys = []
        parent_hash = None
        for i in range(0, len(token_ids), chunk_size):
            chunk = tuple(token_ids[i:i + chunk_size])
            chunk_hash = hash((parent_hash, chunk))
            keys.append(CacheKey(hash=chunk_hash, start=i, end=i+chunk_size))
            parent_hash = chunk_hash
        return keys

关键设计:

  1. 增量确定性哈希:每个 Chunk 的哈希值由当前 Chunk 的 token 内容与父 Chunk 的哈希值共同决定,相同的前缀序列始终产生相同的 Key 链
  2. 跨实例一致性:基于确定性哈希算法,同一模型的不同实例对相同 token 序列产生相同的 Key,天然支持跨实例缓存共享
  3. 可序列化:Key 可以在网络中传输,支持分布式查找

4.3 多级缓存查找

LMCache 的 StorageManager 实现了 Waterfall(瀑布式) 检索策略:

flowchart TD
    A["新请求到来"] --> B{"L1: GPU 显存\n命中?"}
    B -- 是 --> B1["直接使用 ✓"]
    B -- 否 --> C{"L1: CPU 内存\n命中?"}
    C -- 是 --> C1["加载到 GPU ✓"]
    C -- 否 --> D{"L2: P2P 网络\n命中?"}
    D -- 是 --> D1["拉取并加载 ✓"]
    D -- 否 --> E{"L3: 本地磁盘\n命中?"}
    E -- 是 --> E1["读取并加载 ✓"]
    E -- 否 --> F{"L4: 远程存储\n命中?"}
    F -- 是 --> F1["下载并加载 ✓"]
    F -- 否 --> G["Prefill 计算\n→ 存入多级缓存"]

4.4 跨实例共享场景

LMCache 支持两种跨实例共享模式:

4.4.1 集中式共享

所有实例通过一个中心化的缓存服务(如 Redis 或 S3)进行 KV Cache 的读写,即 Hub-and-Spoke 模式,适合部署简单、实例数量适中的场景。

         ┌──────────────┐
         │LMCache Server│
         │ (Redis/S3)   │
         └──────┬───────┘
                │
       ┌────────┴────────┐
       │                 │
┌──────┴──────┐    ┌─────┴───────┐
│ vLLM 实例 A  │    │ vLLM 实例 B │
│ + LMCache   │    │ + LMCache   │
└─────────────┘    └─────────────┘

流程:
1. 实例 A 计算 System Prompt 的 KV Cache
2. 实例 A 将 KV Cache 写入 LMCache Server
3. 实例 B 收到包含相同 System Prompt 的请求
4. 实例 B 从 LMCache Server 读取 KV Cache,跳过 Prefill

4.4.2 点对点共享

实例之间通过 P2P 网络直接传输 KV Cache 数据,即 Mesh 模式,由一个轻量的 Cache Controller 管理元数据,适合对延迟敏感、实例间网络带宽充足的场景。

┌─────────────┐      P2P 传输      ┌─────────────┐
│ vLLM 实例 A │◄──────────────────►│ vLLM 实例 B │
│ + LMCache   │                   │ + LMCache   │
└──────┬──────┘                   └──────┬──────┘
       │                                 │
       └─────────────┬───────────────────┘
                     │
            ┌────────┴─────────┐
            │ Cache Controller │
            │  (元数据管理)      │
            └──────────────────┘

流程:
1. 实例 A 计算 KV Cache,向 Controller 注册元数据
2. 实例 B 需要相同前缀,查询 Controller 获取位置
3. 实例 B 直接从实例 A 拉取数据(支持 RDMA/TCP)

:在底层传输协议选择上,RDMA 的典型延迟约为 1-2 μs,而传统 TCP 约为 10-50 μs。借助 RDMA 极低的通信开销,可以最大化 P2P 模式在延迟敏感场景下的核心价值。

4.5 配置示例

以下示例展示了如何通过配置文件和命令行启动带有多级 Prefix Caching 的 vLLM 服务。

# LMCache 配置示例:启用多级 Prefix Caching
chunk_size: 256 # 每个 Chunk 的 Token 数量
local_cpu: true # 启用 CPU 内存缓存
local_disk: /mnt/nvme/lmcache # 启用本地磁盘缓存
remote_url: redis://cache-server:6379 # 启用远程缓存
p2p_enabled: true # 启用 P2P 共享
# 启动带 LMCache 的 vLLM
# --enable-prefix-caching: 启用 vLLM 内置的 Prefix Caching (显存内)
# --kv-transfer-config: 启用 LMCache 连接器以支持多级缓存和跨实例共享
LMCACHE_CONFIG_FILE=lmcache_config.yaml vllm serve meta-llama/Llama-2-7b-hf \
    --enable-prefix-caching \
    --kv-transfer-config '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_both"}'

5. 性能收益分析

本章从理论和实测两个维度分析 Prefix Caching 带来的性能收益,并给出最佳实践建议。

5.1 理论分析

为了量化 Prefix Caching 的加速效果,建立如下简化模型。

假设

  • System Prompt 长度: $P$ tokens
  • 用户 Query 长度: $Q$ tokens
  • Prefill 计算复杂度: $O(N^2)$(其中 $N = P + Q$)

无 Prefix Caching

每个请求都需要对完整序列执行 Prefill。由于因果注意力机制(Causal Attention)的掩码特性,Attention 矩阵中约有一半的元素需要计算:

\[\text{Cost}_{\text{total}} \approx \frac{1}{2}(P + Q)^2 \approx \frac{1}{2}P^2 \quad (\text{当 } P \gg Q)\]

有 Prefix Caching(命中)

前缀部分的 KV Cache 直接从缓存加载,跳过其 QKV 投影和自注意力计算。但后缀 $Q$ 个 token 在 Attention 时仍需访问前缀的全部 KV 数据(即每个 query token 都要 attend 到 $P$ 个前缀 token 和之前的 query token):

\[\text{Cost}_{\text{cached}} \approx Q \cdot P + \frac{1}{2}Q^2 \approx Q(P + 0.5Q) \quad (\text{当 } P \gg Q \text{ 时,}Q^2 \text{ 项可忽略})\]

此外还需加上缓存读取的 I/O 开销(Cache Load)。

加速比(假设 $P = 8192$, $Q = 256$):

精确的浮点运算比为:

\[\frac{\frac{1}{2}(P+Q)^2}{Q \cdot P + \frac{1}{2}Q^2}\]

当 $P \gg Q$ 时,分母可近似为 $Q(P+Q)$,得到一个简洁且保守的加速比估计:

\[\text{Speedup} \approx \frac{\frac{1}{2}(P + Q)^2}{Q(P + Q)} = \frac{P + Q}{2Q} = \frac{8448}{512} = 16.5\times\]

直观理解:加速比 ≈ 总长度 / (2 × 用户输入长度)。也就是说,8K 前缀 + 256 用户输入 → 理论加速约 16.5 倍。

可以看出,加速比近似等于前缀与后缀长度比的一半 $\frac{P}{2Q}$,随前缀长度线性增长。这意味着前缀越长、用户 Query 越短,Prefix Caching 的收益越显著。

5.2 实测数据

根据 LMCache 官方文档 [2] 的基准测试数据,在典型场景下的性能提升如下(测试基于 Llama-2-7B 模型,单 A100 GPU 环境):

场景 前缀长度 TTFT 降低 吞吐量提升
多轮对话 2K tokens 60-80% 2-3x
RAG 应用 4K tokens 70-85% 3-5x
长文档处理 8K+ tokens 80-95% 5-10x

5.3 最佳实践

以下是在生产环境中使用 Prefix Caching 时的关键优化建议。

  1. Chunk Size 选择
    • 太小:哈希计算开销大,缓存管理复杂
    • 太大:命中粒度粗,内存利用率低
    • 推荐:256-512 tokens(为 vLLM Block Size 的整数倍,便于缓存对齐)。实验表明,256 tokens 的 chunk 能够很好地平衡哈希计算开销(约占总计算量的 0.1% 以下)与内存对齐效率。
  2. System Prompt 设计
    • 将稳定的指令放在前面
    • 动态内容(如用户名、时间戳)放在后面
    • 避免在中间插入变化内容(会破坏后续前缀)
  3. 缓存预热
    • 服务启动时预先计算常用前缀的 KV Cache
    • 使用 LMCache Server 持久化,避免冷启动

5.4 缓存失效与一致性

在实际生产环境中,除了考虑缓存命中,还需要妥善处理缓存失效(Invalidation)问题。当模型权重更新(如 LoRA 热切换或模型整体升级)时,旧版本的 KV Cache 将不再有效。为保证一致性,系统通常采用以下机制:

  • Model Version Tag:在缓存 Key 中引入模型版本或哈希标识,隔离不同版本模型的缓存空间。
  • TTL(Time-To-Live)淘汰:为长期未访问的缓存设置过期时间,结合 LRU 策略自动清理失效数据,防止脏数据污染。

6. 进阶话题

本章讨论 Prefix Caching 与其他推理优化技术的协同使用方式,以及在特殊场景下的扩展应用。

6.1 与 Chunked Prefill 的协同

Chunked Prefill 将长 Prefill 拆分为多个小批次执行,与 Decode 请求交替调度。Prefix Caching 可以与 Chunked Prefill 协同工作:

  • 缓存命中的 Chunk 完全跳过
  • 仅对未命中的 Chunk 执行 Chunked Prefill
  • 进一步降低长前缀场景的调度延迟

6.2 与 PD 分离架构的结合

Prefill-Decode 分离架构 中,Prefix Caching 可以显著优化 Prefill 节点的负载:

  • Prefill 节点维护热门前缀的缓存
  • 相同前缀的请求路由到同一 Prefill 节点
  • 通过 PDBackend 将 KV Cache 推送到 Decode 节点

7. 非前缀复用与 CacheBlend

Prefix Caching 主要解决的是严格前缀匹配的问题。对于 RAG 场景中检索文档顺序不固定的情况,传统的前缀缓存会因为顺序打乱而失效。CacheBlend 提供了一种创新的解决方案:

  • 允许复用非前缀位置的 KV Cache
  • 通过选择性重算修正 Attention 偏差
  • 在复用率和计算开销之间动态平衡

8. 总结

Prefix Caching 是 LLM 推理优化中的关键技术,通过缓存和复用重复前缀的 KV Cache,可以显著降低 Prefill 计算开销(实测 TTFT 降低 60%-95%,取决于前缀长度和应用场景)。

核心要点回顾

  1. 原理:相同 token 序列 → 相同 KV Cache,利用哈希索引实现高效查找
  2. vLLM APC:GPU 显存内的自动前缀缓存,以 Block 为粒度,使用哈希链确保正确性
  3. LMCache:多级存储 + 跨实例共享,将 Prefix Caching 扩展到 CPU/磁盘/远程存储
  4. 应用场景:多轮对话、RAG、长文档处理、共享 System Prompt

技术选型建议

场景 推荐方案
单实例、显存充足 vLLM APC(内置)
单实例、显存不足 vLLM + LMCache(CPU/磁盘扩展)
多实例、需要共享 vLLM + LMCache(P2P 或 Server 模式)
生产级高可用 vLLM + LMCache(Redis/Mooncake 后端)

9. 参考资料

9.1 延伸阅读

  • KV Cache 原理简介 - KV Cache 基础概念
  • LMCache 架构概览 - LMCache 系统设计
  • LMCacheConnector 源码分析 - vLLM 集成实现
  • CacheBlend 技术详解 - RAG 场景的非前缀复用
  • Hybrid KV Cache Manager - 混合注意力模型的 Prefix Caching

9.2 项目文档

  • [1] vLLM Team, “Automatic Prefix Caching,” vLLM Documentation, 2026. [Online]. Available: https://docs.vllm.ai/en/latest/features/automatic_prefix_caching.html
  • [2] LMCache Team, “LMCache Documentation,” LMCache, 2026. [Online]. Available: https://docs.lmcache.ai

9.3 学术论文

Prefix Caching 的核心思想与实现机制在多篇学术论文中均有探讨,其中最具代表性的是 SGLang 提出的 RadixAttention 和 Prompt Cache:

  • [3] L. Zheng et al., “SGLang: Efficient Execution of Structured Language Model Programs,” arXiv preprint arXiv:2312.07104, 2023.
    • 贡献:提出了 RadixAttention 技术,通过维护 Radix Tree(基数树)来自动管理和复用 KV Cache。vLLM 的 APC 借鉴了其 LRU 淘汰策略,但采用了 Hash Table(哈希表)作为底层数据结构。
  • [4] I. Gim et al., “Prompt Cache: Modular Attention Reuse for Low-Latency Inference,” arXiv preprint arXiv:2311.04934, 2023.
    • 贡献:系统阐述了通过复用 Attention 状态来加速 LLM 推理的模块化方法,验证了其在长文本场景下的显著收益。