随着提示变长, 大语言模型推理 的成本会不断上升。
RAG 系统、编码代理、多代理工作流以及长对话往往会反复将相同的文档、系统提示、工具定义或对话历史发送给模型。
每次在 预填充(prefill) 阶段处理这些共享上下文时,模型会生成用于生成阶段的 键值(KV) 状态。
对同一上下文反复计算这些状态会浪费 GPU 计算并增加延迟。
LMCache 通过在推理栈中加入专用的 KV 缓存层来解决这一问题。
它可以存储先前计算的 KV 状态,并在请求与推理工作进程之间复用;同时支持 CPU、本地存储、远程后端以及解耦式服务架构。
在本教程中,我将展示 LMCache 的工作原理、它与 vLLM 原生 KV 缓存的区别,以及多层存储、共享缓存、CacheBlend、预填充/解码解耦等特性在现代 LLM 服务中的位置。
随后,您将把 LMCache 连接到 vLLM,服务 IBM Granite 4.2 3B,并运行对照实验来测量复用大型共享前缀对推理延迟的影响。
什么是 LMCache?
LMCache 是一个开源的 LLM 推理 KV 缓存管理层 。
它允许将预填充阶段创建的键值状态存储起来,并在后续请求共享相同提示前缀时复用。
来源: LMCache – Building the foundation of AI memory tensor with KV Cache Infrastructure
这与传统的响应缓存不同。
响应缓存会存储最终生成的答案,并在完全相同的请求再次出现时返回它:
cache = {
"What is Python?" :
"Python is a general-purpose programming language..."
} 技术支持
LMCache 则在推理流程的更早阶段工作。它不是缓存最终响应,而是存储模型处理提示时产生的注意力 KV 状态。
例如,没有可复用 KV 缓存时,每次新增问题时都必须再次对很长的共享提示执行预填充。
借助 LMCache,首个请求会执行完整预填充并存储生成的 KV 状态。后续具有同一前缀的请求可以加载这些状态,仅处理新增的 token。
图 1:没有可复用的 KV 缓存时,每个请求都要重新计算整个提示;借助 LMCache,复用已缓存的 KV 状态,仅处理新增 token。
随着上下文窗口变大,这一点的价值愈发明显。
重新处理几百个 token 也许不贵,但反复处理数万共享 token 会显著增加延迟和 GPU 占用。
LMCache 的工作原理
理解 LMCache 的最简单方式,是跟踪两个拥有相同前缀的请求。
对于第一个请求,LMCache 会检查是否已有可复用的 KV 状态。由于该提示首次出现,查找结果为缓存未命中。
随后 vLLM 在 GPU 上执行预填充,创建 KV 状态,并将其交给 LMCache 存储。
对于第二个请求,共享前缀保持不变,但问题不同。
LMCache 再次执行查找。这一次,前缀已存在,因此缓存命中。缓存状态被复用,GPU 只需处理未缓存的后缀。
图 2:首个带新前缀的请求未命中缓存,vLLM 计算预填充并将 KV 状态存入 LMCache;相同前缀的重复请求命中缓存,仅计算新的后缀。
关键在于 LMCache 并不要求整个请求完全一致,重要的是可复用的前缀。
因此它非常适合背景上下文保持不变、用户查询变化的应用。
vLLM 前缀缓存 vs LMCache
vLLM 已支持自动前缀缓存,因此一个合理的问题是:为何还需要 LMCache?
两者都致力于避免对模型已处理过的提示前缀重复计算 KV 状态。
主要区别在于缓存所在的位置,以及它能被多广泛地复用。
特性
vLLM 原生 KV 缓存
LMCache
前缀 KV 复用
是,通过自动前缀缓存
是
非前缀 KV 复用
否
是,通过 CacheBlend
GPU KV 缓存
是
与 vLLM 的 GPU KV 缓存协同工作
CPU KV 卸载
是
是
SSD / NVMe 存储
通过次级存储层支持
是,包含本地磁盘、NVMe 和 GDS
远程存储
支持如 S3 兼容对象存储等选项
支持多种后端,包括 Redis/Valkey、S3、Mooncake、InfiniStore、NIXL 等
跨进程缓存共享
在部分配置中支持,包括 P2P 和共享存储
核心特性,多个推理工作进程可复用共享的 KV 状态
独立缓存服务
否,缓存作为 vLLM 服务栈的一部分进行管理
是,LMCache MP 模式可独立于 vLLM 运行
独立扩展缓存
缓存资源通常随推理部署一起配置
缓存容量可独立于 vLLM 工作进程扩展
预填充/解码解耦
通过 KV 连接器支持
专为在预填充与解码工作进程之间传输 KV 而构建
最适用场景
在 vLLM 部署内高效复用前缀并进行 KV 卸载
大规模、共享、多层或分布式 KV 缓存工作负载
当重复请求由同一推理服务器处理时,vLLM 内置的前缀缓存是更简单的选择。
如果若干请求以相同 token 开始,vLLM 可以复用现有 KV 块,而不是再次执行整个预填充。
LMCache 则将这一思路扩展到推理引擎之外。
LMCache 不仅将可复用的 KV 状态保存在 vLLM 自身缓存中,还引入了一个独立的缓存层,将 KV 状态在更多存储层之间保存与迁移,并将其提供给更广的服务架构使用。
可以用一个简单的方式来理解两者差异:
图 3:vLLM 前缀缓存 vs LMCache。LMCache 额外添加了一个缓存层,将 KV 状态分层至 CPU 内存、本地 SSD/NVMe 与远程存储,而非仅限于 GPU 内存。
因此,LMCache 并不是替代 vLLM 中 KV 缓存的概念,而是将 KV 缓存管理扩展到推理引擎的常规边界之外。
LMCache 的关键特性
LMCache 不只是简单的内存缓存。它旨在跨不同存储层与推理工作进程存储、迁移与复用 KV 状态。
1. KV 缓存复用
LMCache 的主要特性是能够复用先前计算的 KV 状态,而无需再次让模型处理相同的 token。
当请求反复包含相同或相似的上下文时,这尤其有用,例如:
检索到的文档
大型系统提示
代码仓库上下文
对话历史
工具定义
代理之间的共享指令
通过复用已缓存的 KV 状态,LMCache 可以减少重复的预填充计算并缩短首 token 时间。
2. 多层存储
GPU 内存有限且昂贵,尤其在处理长上下文时。
LMCache 可以将 KV 状态迁移出 GPU 内存,跨多个层级存储,包括 CPU 内存、本地 SSD 或 NVMe 存储,以及远程或分布式存储。
这使得可保留的可复用上下文远超 GPU 内存容量;同时在需要时仍可将常用 KV 状态迁回 GPU。
3. 跨工作进程共享 KV 缓存
LMCache 可让多个推理工作进程访问缓存的 KV 状态,而不是将其绑定到单一模型进程。
例如,当一个 vLLM 工作进程处理了长文档,另一工作进程即可复用缓存的 KV 状态,而无需重复计算相同上下文。
这对分布式服务、多 GPU 部署,以及请求被路由到多个推理副本的应用尤为有用。
4. 与 vLLM 集成
LMCache 通过 vLLM 的 KV 连接器接口进行集成。
应用可以继续通过 OpenAI 兼容的 vLLM 端点通信,而 LMCache 在推理层之下运行。
这意味着现有应用无需改变发送提示或接收响应的方式。LMCache 在 vLLM 与配置的缓存后端之间处理 KV 的存取。
5. 多进程架构
LMCache 可作为与 vLLM 分离的独立进程运行。
与其在每个推理工作进程内进行缓存管理,不如使用专用的 LMCache 服务器独立管理 KV 状态,并服务多个 vLLM 进程。
这将缓存管理与模型服务解耦,使多个工作进程共享同一缓存,并便于将缓存容量独立于 GPU 推理资源进行扩展。
6. CacheBlend
传统前缀缓存最适用于请求共享提示的开头部分。
对于可复用内容可能出现在不同位置的工作负载,LMCache 还提供了 CacheBlend。
例如,RAG 应用可能会针对不同问题以不同顺序检索到相同文档。
CacheBlend 可复用先前计算的部分 KV 状态,同时选择性地重算依赖于新上下文的 token。
这让 KV 复用更适用于 RAG、多文档应用以及经常重排提示的代理式工作流。
7. 预填充与解码解耦
LMCache 还能帮助在独立的预填充与解码工作进程之间传输 KV 状态。
在这种架构中,一组 GPU 负责计算密集的提示处理阶段,另一组负责 token 生成。
LMCache 提供了在两者之间移动 KV 状态的中间层。
这使预填充与解码资源可独立扩展,并可在更大的推理部署中提升 GPU 利用率。
开始使用 LMCache
既然我们已经理解了 LMCache 的工作机制,接下来将其连接到 vLLM——一款为高效服务与内存管理而设计的高性能 LLM 推理引擎。
在本指南中,我将使用 IBM Granite 4.2 3B ,这是一款在体量上保持轻量同时具备强劲性能的紧凑模型,便于进行实际测试。
我们将以多进程模式与 vLLM 并行运行 LMCache,并验证 KV 状态可在请求间复用。
1. 环境配置
本教程将使用 Python 3.12、uv、vLLM、LMCache、OpenAI Python 客户端、pandas 和 matplotlib。
开始前,请确保您具备:
一块已安装合适驱动的 NVIDIA GPU
系统已安装并可用的 CUDA 13 工具包
已安装 uv
您可以通过以下命令安装 uv:
curl -LsSf https://astral.sh/uv/install.sh | sh 技术支持
然后创建项目:
mkdir lmcache-tutorial
cd lmcache-tutorial技术支持
创建并激活 Python 3.12 虚拟环境:
uv venv --python 3.12
source .venv/bin/activate技术支持
安装所需依赖:
uv pip install -U \
"lmcache==0.5.4" \
"vllm==0.28.0" \
openai \
pandas \
matplotlib \
--torch-backend= cu130技术支持
此处使用 cu130,因为我们的环境基于 CUDA 13 。
如果您使用不同版本的 CUDA,请将 --torch-backend 的值更改为与您环境兼容的 PyTorch 后端。
Granite 4.2 使用自定义的推理解析器,请从 Hugging Face 下载:
hf download ibm-granite/granite-4.2-3b \
granite_thinking_parser.py \
--local-dir . 技术支持
最后,验证您的 GPU 与 CUDA 安装是否被检测到:
nvidia-smi技术支持
本教程使用的是 16 GB 显存的 NVIDIA GeForce RTX 5070 Ti。
系统运行 NVIDIA 驱动 580.126.09,支持 CUDA 13.0。
2. 启动 vLLM 与 LMCache
我们需要两个运行中的进程:一个 LMCache 服务器,另一个 vLLM。
在第一个终端中启动 LMCache:
lmcache server \
--host localhost \
--port 6555 \
--http-port 8081 \
--l1-size-gb 20 \
--eviction-policy LRU技术支持
这里我们使用:
端口 6555 用于 vLLM 与 LMCache 之间通信
端口 8081 用于 LMCache 的 HTTP 接口
20 GB 作为 L1 缓存大小
LRU 作为淘汰策略
保持此终端运行。
打开第二个终端,激活同一虚拟环境,如果启用了可扩展 CUDA 分配器配置,请取消设置:
cd lmcache-tutorial
source .venv/bin/activate
unset PYTORCH_CUDA_ALLOC_CONF技术支持
现在使用 LMCache 连接器启动 vLLM:
vllm serve ibm-granite/granite-4.2-3b \
--served-model-name granite-4.2-3b \
--dtype bfloat16 \
--max-model-len 65536 \
--kv-cache-dtype fp8 \
--gpu-memory-utilization 0.90 \
--enforce-eager \
--max-num-seqs 1 \
--max-num-batched-tokens 2048 \
--no-enable-prefix-caching \
--reasoning-parser granite_thinking_parser \
--reasoning-parser-plugin "$( pwd ) /granite_thinking_parser.py" \
--tool-call-parser qwen3_coder \
--enable-auto-tool-choice \
--port 8000 \
--kv-transfer-config \
'{
"kv_connector":"LMCacheMPConnector",
"kv_connector_module_path":"lmcache.integration.vllm.lmcache_mp_connector",
"kv_role":"kv_both",
"kv_connector_extra_config":{
"lmcache.mp.host":"tcp://localhost",
"lmcache.mp.port":6555,
"lmcache.mp.mp_transfer_mode":"lmcache_driven"
}
}' 技术支持
我们通过以下方式禁用 vLLM 的内置前缀缓存:
--no-enable-prefix-caching技术支持
这使接下来的实验能将重点放在通过 LMCache 进行 KV 复用。
vLLM 加载完成后,可通过其 OpenAI 兼容端点访问模型:
http://localhost:8000/v1技术支持
3. 通过可控命中验证 LMCache
我们现在将进行一个简单的对照实验,以确认 LMCache 正常工作。
我们将创建一个 大型共享前缀 ,清空缓存,先发送一次前缀以填充 LMCache,然后在相同前缀下更换问题再发送一次请求。
首先,创建 OpenAI 客户端:
from openai import OpenAI
client = OpenAI(
base_url= "http://localhost:8000/v1" ,
api_key= "local" ,
) 技术支持
接着,创建一个很长的共享文档:
base_text = """
Large language model inference consists of two main stages:
prefill and decoding.
During prefill, the model processes the input sequence and calculates
the key and value tensors used by the attention mechanism.
During autoregressive decoding, these tensors are reused so the model
does not need to recompute attention states for every previous token.
KV cache management is therefore an important part of modern LLM
serving infrastructure, particularly for long-context applications.
"""
document = base_text * 500
SYSTEM_PROMPT = f"""
You are a technical assistant.
Use only the following document to answer the user's question.
DOCUMENT:
{ document}
""" 技术支持
将文档重复 500 次可构造足够庞大的共享前缀,从而让 KV 复用的效果更明显。
在发送首个请求前,清空 LMCache,以空缓存开始:
import subprocess
import time
subprocess. run(
[
"lmcache" ,
"kvcache" ,
"clear" ,
"--url" ,
"http://localhost:8081" ,
] ,
check= True ,
)
time. sleep( 0.5 ) 技术支持
这样可确保第一个请求是 冷缓存请求 ,不会复用之前测试遗留的 KV 状态。
现在发送第一个请求:
prompt_1 = SYSTEM_PROMPT + """
Question:
Explain the purpose of the KV cache.
"""
start = time. perf_counter( )
client. completions. create(
model= "granite-4.2-3b" ,
prompt= prompt_1,
max_tokens= 100 ,
temperature= 0 ,
)
latency_1 = time. perf_counter( ) - start技术支持
这是 预热请求 。vLLM 需在预填充阶段处理很长的共享前缀,同时 LMCache 存储生成的 KV 状态。
接着,保持共享前缀完全一致,仅更换问题:
prompt_2 = SYSTEM_PROMPT + """
Question:
Explain what happens during prefill.
"""
start = time. perf_counter( )
client. completions. create(
model= "granite-4.2-3b" ,
prompt= prompt_2,
max_tokens= 100 ,
temperature= 0 ,
)
latency_2 = time. perf_counter( ) - start技术支持
由于大型前缀未变,LMCache 可以直接复用第一次请求生成的 KV 状态,而无需重新计算完整前缀。
最后,比较两次请求的耗时:
print ( f"Cold request: { latency_1: .3f } s" )
print ( f"Warm request: { latency_2: .3f } s" )
print ( f"Speedup: { latency_1 / latency_2: .2f } x" )
print (
f"Latency saved: "
f" { ( ( latency_1 - latency_2) / latency_1) * 100 : .1f } %"
) 技术支持
我的运行结果如下:
Cold request: 26.276s
Warm request: 4.402s
Speedup: 5.97x
Latency saved: 83.2%技术支持
热请求几乎 快了 6 倍 ,端到端延迟降低了 83.2% 。
由于问题不同,LMCache 并未缓存最终响应。而是复用了 针对共享前缀生成的 KV 状态 。
第一次请求必须在预填充阶段处理整个长前缀并从零生成这些 KV 状态。
第二次请求中,LMCache 检索到匹配的缓存状态,使 vLLM 能跳过大部分重复的预填充工作,只关注新的后缀。
这证实了 LMCache 成功复用了共享前缀。
LMCache 何时有用?
当应用反复发送大量相同的上下文时,LMCache 尤其有用。
一些常见示例包括:
RAG 应用
用户可能围绕同一检索文档提出多个问题。
无需为每个问题从头处理整份文档,其 KV 状态可被复用。
编码代理
编码助手在每次模型调用中经常附带仓库文件、项目说明和其他大型上下文。
其中很多上下文在任务变化时保持不变。
AI 代理
代理 常常在多步过程中复用大型系统提示、工具定义、记忆、指令与环境状态。
长对话
一段长时间的对话可能会在每条新消息中反复发送相当一部分历史。
多代理系统
多个代理可能共享相同的背景文档或系统上下文,同时 处理不同任务 。
在上述所有情况中,模型反复被展示它在早先请求中已经处理过的信息。
LMCache 提供了一种在 KV 缓存层面复用这些工作的方式。
结语
LMCache 通过让预填充阶段创建的 KV 状态可复用,解决了长上下文推理中的一大浪费来源。
模型无需一遍遍处理相同的共享上下文,而是复用已完成的工作。
本教程未包含基准测试代码或详细结果,但我们使用相同配置进行了单独测试。
结果非常显著。复用大型共享前缀将总请求延迟从 26.276 秒降至 4.386 秒 。
平均首 token 时间从 22.149 秒降至 0.726 秒 ,实现了 30.54 倍 的提升。随着上下文规模增大,收益也随之提升:从 约 12,000 个共享字符时的 3.55 倍 ,提升到约 245,000 个字符时的 30.22 倍 。
简单来说,可复用的上下文越多,LMCache 能避免的重复工作就越多。
DeepSeek 是一个很好的真实案例,说明这为何重要。
其缓存系统可以复用匹配的提示前缀,这对反复发送相同系统提示、工具、对话历史与代码上下文的编码代理尤其有价值。
更高的缓存命中率意味着更少的重复计算,以及对服务方与用户而言更低的成本。
本指南的核心结论是,更快的 LLM 推理不仅取决于更强的 GPU 或更快的 token 生成,也在于 不要做不必要的重复工作 。
对于 RAG 系统 、编码助手、代理与其他长上下文应用而言,可复用的 KV 缓存可以成为改善延迟、降低 GPU 使用与推理成本的重要一环。
随着提示变长,大语言模型推理的成本会不断上升。
RAG 系统、编码代理、多代理工作流以及长对话往往会反复将相同的文档、系统提示、工具定义或对话历史发送给模型。
每次在预填充(prefill)阶段处理这些共享上下文时,模型会生成用于生成阶段的键值(KV)状态。
对同一上下文反复计算这些状态会浪费 GPU 计算并增加延迟。
LMCache 通过在推理栈中加入专用的 KV 缓存层来解决这一问题。
它可以存储先前计算的 KV 状态,并在请求与推理工作进程之间复用;同时支持 CPU、本地存储、远程后端以及解耦式服务架构。
在本教程中,我将展示 LMCache 的工作原理、它与 vLLM 原生 KV 缓存的区别,以及多层存储、共享缓存、CacheBlend、预填充/解码解耦等特性在现代 LLM 服务中的位置。
随后,您将把 LMCache 连接到 vLLM,服务 IBM Granite 4.2 3B,并运行对照实验来测量复用大型共享前缀对推理延迟的影响。
什么是 LMCache?
LMCache 是一个开源的LLM 推理 KV 缓存管理层。
它允许将预填充阶段创建的键值状态存储起来,并在后续请求共享相同提示前缀时复用。
来源:LMCache – Building the foundation of AI memory tensor with KV Cache Infrastructure
这与传统的响应缓存不同。
响应缓存会存储最终生成的答案,并在完全相同的请求再次出现时返回它:
LMCache 则在推理流程的更早阶段工作。它不是缓存最终响应,而是存储模型处理提示时产生的注意力 KV 状态。
例如,没有可复用 KV 缓存时,每次新增问题时都必须再次对很长的共享提示执行预填充。
借助 LMCache,首个请求会执行完整预填充并存储生成的 KV 状态。后续具有同一前缀的请求可以加载这些状态,仅处理新增的 token。
图 1:没有可复用的 KV 缓存时,每个请求都要重新计算整个提示;借助 LMCache,复用已缓存的 KV 状态,仅处理新增 token。
随着上下文窗口变大,这一点的价值愈发明显。
重新处理几百个 token 也许不贵,但反复处理数万共享 token 会显著增加延迟和 GPU 占用。
LMCache 的工作原理
理解 LMCache 的最简单方式,是跟踪两个拥有相同前缀的请求。
对于第一个请求,LMCache 会检查是否已有可复用的 KV 状态。由于该提示首次出现,查找结果为缓存未命中。
随后 vLLM 在 GPU 上执行预填充,创建 KV 状态,并将其交给 LMCache 存储。
对于第二个请求,共享前缀保持不变,但问题不同。
LMCache 再次执行查找。这一次,前缀已存在,因此缓存命中。缓存状态被复用,GPU 只需处理未缓存的后缀。
图 2:首个带新前缀的请求未命中缓存,vLLM 计算预填充并将 KV 状态存入 LMCache;相同前缀的重复请求命中缓存,仅计算新的后缀。
关键在于 LMCache 并不要求整个请求完全一致,重要的是可复用的前缀。
因此它非常适合背景上下文保持不变、用户查询变化的应用。
vLLM 前缀缓存 vs LMCache
vLLM 已支持自动前缀缓存,因此一个合理的问题是:为何还需要 LMCache?
两者都致力于避免对模型已处理过的提示前缀重复计算 KV 状态。
主要区别在于缓存所在的位置,以及它能被多广泛地复用。
特性
vLLM 原生 KV 缓存
LMCache
前缀 KV 复用
是,通过自动前缀缓存
是
非前缀 KV 复用
否
是,通过 CacheBlend
GPU KV 缓存
是
与 vLLM 的 GPU KV 缓存协同工作
CPU KV 卸载
是
是
SSD / NVMe 存储
通过次级存储层支持
是,包含本地磁盘、NVMe 和 GDS
远程存储
支持如 S3 兼容对象存储等选项
支持多种后端,包括 Redis/Valkey、S3、Mooncake、InfiniStore、NIXL 等
跨进程缓存共享
在部分配置中支持,包括 P2P 和共享存储
核心特性,多个推理工作进程可复用共享的 KV 状态
独立缓存服务
否,缓存作为 vLLM 服务栈的一部分进行管理
是,LMCache MP 模式可独立于 vLLM 运行
独立扩展缓存
缓存资源通常随推理部署一起配置
缓存容量可独立于 vLLM 工作进程扩展
预填充/解码解耦
通过 KV 连接器支持
专为在预填充与解码工作进程之间传输 KV 而构建
最适用场景
在 vLLM 部署内高效复用前缀并进行 KV 卸载
大规模、共享、多层或分布式 KV 缓存工作负载
当重复请求由同一推理服务器处理时,vLLM 内置的前缀缓存是更简单的选择。
如果若干请求以相同 token 开始,vLLM 可以复用现有 KV 块,而不是再次执行整个预填充。
LMCache 则将这一思路扩展到推理引擎之外。
LMCache 不仅将可复用的 KV 状态保存在 vLLM 自身缓存中,还引入了一个独立的缓存层,将 KV 状态在更多存储层之间保存与迁移,并将其提供给更广的服务架构使用。
可以用一个简单的方式来理解两者差异:
图 3:vLLM 前缀缓存 vs LMCache。LMCache 额外添加了一个缓存层,将 KV 状态分层至 CPU 内存、本地 SSD/NVMe 与远程存储,而非仅限于 GPU 内存。
因此,LMCache 并不是替代 vLLM 中 KV 缓存的概念,而是将 KV 缓存管理扩展到推理引擎的常规边界之外。
LMCache 的关键特性
LMCache 不只是简单的内存缓存。它旨在跨不同存储层与推理工作进程存储、迁移与复用 KV 状态。
1. KV 缓存复用
LMCache 的主要特性是能够复用先前计算的 KV 状态,而无需再次让模型处理相同的 token。
当请求反复包含相同或相似的上下文时,这尤其有用,例如:
通过复用已缓存的 KV 状态,LMCache 可以减少重复的预填充计算并缩短首 token 时间。
2. 多层存储
GPU 内存有限且昂贵,尤其在处理长上下文时。
LMCache 可以将 KV 状态迁移出 GPU 内存,跨多个层级存储,包括 CPU 内存、本地 SSD 或 NVMe 存储,以及远程或分布式存储。
这使得可保留的可复用上下文远超 GPU 内存容量;同时在需要时仍可将常用 KV 状态迁回 GPU。
3. 跨工作进程共享 KV 缓存
LMCache 可让多个推理工作进程访问缓存的 KV 状态,而不是将其绑定到单一模型进程。
例如,当一个 vLLM 工作进程处理了长文档,另一工作进程即可复用缓存的 KV 状态,而无需重复计算相同上下文。
这对分布式服务、多 GPU 部署,以及请求被路由到多个推理副本的应用尤为有用。
4. 与 vLLM 集成
LMCache 通过 vLLM 的 KV 连接器接口进行集成。
应用可以继续通过 OpenAI 兼容的 vLLM 端点通信,而 LMCache 在推理层之下运行。
这意味着现有应用无需改变发送提示或接收响应的方式。LMCache 在 vLLM 与配置的缓存后端之间处理 KV 的存取。
5. 多进程架构
LMCache 可作为与 vLLM 分离的独立进程运行。
与其在每个推理工作进程内进行缓存管理,不如使用专用的 LMCache 服务器独立管理 KV 状态,并服务多个 vLLM 进程。
这将缓存管理与模型服务解耦,使多个工作进程共享同一缓存,并便于将缓存容量独立于 GPU 推理资源进行扩展。
6. CacheBlend
传统前缀缓存最适用于请求共享提示的开头部分。
对于可复用内容可能出现在不同位置的工作负载,LMCache 还提供了 CacheBlend。
例如,RAG 应用可能会针对不同问题以不同顺序检索到相同文档。
CacheBlend 可复用先前计算的部分 KV 状态,同时选择性地重算依赖于新上下文的 token。
这让 KV 复用更适用于 RAG、多文档应用以及经常重排提示的代理式工作流。
7. 预填充与解码解耦
LMCache 还能帮助在独立的预填充与解码工作进程之间传输 KV 状态。
在这种架构中,一组 GPU 负责计算密集的提示处理阶段,另一组负责 token 生成。
LMCache 提供了在两者之间移动 KV 状态的中间层。
这使预填充与解码资源可独立扩展,并可在更大的推理部署中提升 GPU 利用率。
开始使用 LMCache
既然我们已经理解了 LMCache 的工作机制,接下来将其连接到 vLLM——一款为高效服务与内存管理而设计的高性能 LLM 推理引擎。
在本指南中,我将使用 IBM Granite 4.2 3B,这是一款在体量上保持轻量同时具备强劲性能的紧凑模型,便于进行实际测试。
我们将以多进程模式与 vLLM 并行运行 LMCache,并验证 KV 状态可在请求间复用。
1. 环境配置
本教程将使用 Python 3.12、uv、vLLM、LMCache、OpenAI Python 客户端、pandas 和 matplotlib。
开始前,请确保您具备:
您可以通过以下命令安装
uv:然后创建项目:
创建并激活 Python 3.12 虚拟环境:
安装所需依赖:
此处使用
cu130,因为我们的环境基于CUDA 13。如果您使用不同版本的 CUDA,请将
--torch-backend的值更改为与您环境兼容的 PyTorch 后端。Granite 4.2 使用自定义的推理解析器,请从 Hugging Face 下载:
最后,验证您的 GPU 与 CUDA 安装是否被检测到:
本教程使用的是 16 GB 显存的 NVIDIA GeForce RTX 5070 Ti。
系统运行 NVIDIA 驱动 580.126.09,支持 CUDA 13.0。
2. 启动 vLLM 与 LMCache
我们需要两个运行中的进程:一个 LMCache 服务器,另一个 vLLM。
在第一个终端中启动 LMCache:
这里我们使用:
6555用于 vLLM 与 LMCache 之间通信8081用于 LMCache 的 HTTP 接口20 GB作为 L1 缓存大小LRU作为淘汰策略保持此终端运行。
打开第二个终端,激活同一虚拟环境,如果启用了可扩展 CUDA 分配器配置,请取消设置:
现在使用 LMCache 连接器启动 vLLM:
我们通过以下方式禁用 vLLM 的内置前缀缓存:
这使接下来的实验能将重点放在通过 LMCache 进行 KV 复用。
vLLM 加载完成后,可通过其 OpenAI 兼容端点访问模型:
3. 通过可控命中验证 LMCache
我们现在将进行一个简单的对照实验,以确认 LMCache 正常工作。
我们将创建一个大型共享前缀,清空缓存,先发送一次前缀以填充 LMCache,然后在相同前缀下更换问题再发送一次请求。
首先,创建 OpenAI 客户端:
接着,创建一个很长的共享文档:
将文档重复 500 次可构造足够庞大的共享前缀,从而让 KV 复用的效果更明显。
在发送首个请求前,清空 LMCache,以空缓存开始:
这样可确保第一个请求是冷缓存请求,不会复用之前测试遗留的 KV 状态。
现在发送第一个请求:
这是预热请求。vLLM 需在预填充阶段处理很长的共享前缀,同时 LMCache 存储生成的 KV 状态。
接着,保持共享前缀完全一致,仅更换问题:
由于大型前缀未变,LMCache 可以直接复用第一次请求生成的 KV 状态,而无需重新计算完整前缀。
最后,比较两次请求的耗时:
我的运行结果如下:
热请求几乎快了 6 倍,端到端延迟降低了83.2%。
由于问题不同,LMCache 并未缓存最终响应。而是复用了针对共享前缀生成的 KV 状态。
第一次请求必须在预填充阶段处理整个长前缀并从零生成这些 KV 状态。
第二次请求中,LMCache 检索到匹配的缓存状态,使 vLLM 能跳过大部分重复的预填充工作,只关注新的后缀。
这证实了 LMCache 成功复用了共享前缀。
LMCache 何时有用?
当应用反复发送大量相同的上下文时,LMCache 尤其有用。
一些常见示例包括:
RAG 应用
用户可能围绕同一检索文档提出多个问题。
无需为每个问题从头处理整份文档,其 KV 状态可被复用。
编码代理
编码助手在每次模型调用中经常附带仓库文件、项目说明和其他大型上下文。
其中很多上下文在任务变化时保持不变。
AI 代理
代理常常在多步过程中复用大型系统提示、工具定义、记忆、指令与环境状态。
长对话
一段长时间的对话可能会在每条新消息中反复发送相当一部分历史。
多代理系统
多个代理可能共享相同的背景文档或系统上下文,同时处理不同任务。
在上述所有情况中,模型反复被展示它在早先请求中已经处理过的信息。
LMCache 提供了一种在 KV 缓存层面复用这些工作的方式。
结语
LMCache 通过让预填充阶段创建的 KV 状态可复用,解决了长上下文推理中的一大浪费来源。
模型无需一遍遍处理相同的共享上下文,而是复用已完成的工作。
本教程未包含基准测试代码或详细结果,但我们使用相同配置进行了单独测试。
结果非常显著。复用大型共享前缀将总请求延迟从26.276 秒降至 4.386 秒。
平均首 token 时间从22.149 秒降至 0.726 秒,实现了30.54 倍的提升。随着上下文规模增大,收益也随之提升:从约 12,000 个共享字符时的3.55 倍,提升到约245,000 个字符时的30.22 倍。
简单来说,可复用的上下文越多,LMCache 能避免的重复工作就越多。
DeepSeek 是一个很好的真实案例,说明这为何重要。
其缓存系统可以复用匹配的提示前缀,这对反复发送相同系统提示、工具、对话历史与代码上下文的编码代理尤其有价值。
更高的缓存命中率意味着更少的重复计算,以及对服务方与用户而言更低的成本。
本指南的核心结论是,更快的 LLM 推理不仅取决于更强的 GPU 或更快的 token 生成,也在于不要做不必要的重复工作。
对于RAG 系统、编码助手、代理与其他长上下文应用而言,可复用的 KV 缓存可以成为改善延迟、降低 GPU 使用与推理成本的重要一环。