LMCache Pin/Persistence 实战:通过控制器 API 持久化请求的 KV Cache
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
导读
本文讲解 LMCache 的Pin/Persistence(固定/持久化)能力:如何在 LMCacheEngine 外部,通过控制器(Controller)提供的 HTTP API,将一个请求的 KV Cache 按 token 序列“钉住”,使其在存储后端中保留而不被驱逐。你将学会完整的端到端操作流程——从启动 vLLM + LMCache 实例、启动控制器,到发送推理请求、获取 token 序列,再到调用/pin接口完成持久化并解读返回结果。文末还会结合仓库源码,深入剖析 Pin 操作从 HTTP 入口到存储后端的完整调用链,以及它与缓存驱逐机制的底层关系。
本文以仓库中的 pin 示例目录 及配套配置文件(example.yaml)为操作骨架,相关接口实现与文档可进一步参考 LMCache 控制器 API 文档 与 控制器内部 API 服务。
一、Pin 是什么:从“缓存”到“持久化”
LMCache 的核心职责是为 LLM 推理提供高性能的 KV Cache 层:推理过程中的 KV cache 会被分块(chunk)存储到 GPU、本地 CPU 内存或远端存储后端中,并在后续请求命中时复用,从而省去重复预填充(prefill)的计算开销。
但 KV cache 本质上是“缓存”,默认受容量上限与驱逐(eviction)策略约束。当缓存空间不足时,LRU 等策略会回收旧的 chunk,这可能导致你希望长期保留的热点数据被换出。
Pin 接口解决了这一问题:它允许外部调用者(比如一个调度系统、代理或运维脚本)显式指定一段 token 序列,要求 LMCache 将其对应的 KV cache 固定下来,使其不会被驱逐。文档中将其描述为:
在 LMCacheEngine 外部演示如何 pin/persist(固定/持久化)一个请求的 KV cache。
从源码接口看,Pin 的核心语义是:
pin(instance_id: str, location: str, tokens: List[int]) -> (event_id: str, num_tokens: int)instance_id:目标 LMCache 实例标识;location:KV cache 所在的存储位置(后端);tokens:要固定的 token id 序列;- 返回
event_id(操作事件 ID)与num_tokens(被固定下来的 token 数)。
这与“移动(move)”“压缩(compress)”“清理(clear)”等操作一起,构成了 LMCache 控制器对外提供的 KV cache 管理能力集合,相关接口在 控制器 KV 控制器实现 中均有对应实现。
二、环境准备与端口规划
开始之前,请确认以下前提:
- 至少 1 张 GPU(示例使用
CUDA_VISIBLE_DEVICES=0指定); - 端口规划如下:
| 端口 | 服务 | 说明 |
|---|---|---|
| 8000 | vLLM 推理服务 | 接收推理请求与 tokenize 请求 |
| 8001 | LMCache worker | 在example.yaml中通过lmcache_worker_ports配置 |
| 9000 | LMCache 控制器 | 提供/pin等管理 API |
| 9001 | 控制器 monitor(pull 端口) | 控制器拉取 worker 心跳/信息 |
示例中的模型为meta-llama/Llama-3.1-8B-Instruct,需确保已能通过 vLLM 正常加载。
三、第一步:编写 LMCache 实例配置example.yaml
在 pin 示例目录 中提供了完整的配置文件,内容如下:
chunk_size: 256 local_cpu: True max_local_cpu_size: 5 # cache controller configurations enable_controller: True lmcache_instance_id: "lmcache_default_instance" controller_pull_url: "localhost:9001" lmcache_worker_ports: 8001 # Peer identifiers p2p_host: "localhost" p2p_init_ports: 8200各参数含义:
chunk_size: 256:KV cache 分块大小(token 数),LMCache 以 chunk 为粒度存储与检索 KV cache;local_cpu: True与max_local_cpu_size: 5:启用本地 CPU 内存作为 L2 存储,容量上限为 5GB,Pin 操作的目标位置LocalCPUBackend即指这一层;enable_controller: True:开启缓存控制器,使其能够接收外部的/pin等管理指令;lmcache_instance_id: "lmcache_default_instance":实例 ID,后续调用/pin时instance_id字段必须与此一致;controller_pull_url: "localhost:9001":worker 上报给控制器的地址,对应控制器的 monitor 端口 9001;lmcache_worker_ports: 8001:LMCache worker 监听端口;p2p_host: "localhost"、p2p_init_ports: 8200:节点间 P2P 通信(如跨实例共享)的标识信息。
四、第二步:启动 vLLM 引擎与 LMCache 控制器
4.1 启动 vLLM 引擎(端口 8000)
CUDA_VISIBLE_DEVICES=0 LMCACHE_CONFIG_FILE=example.yaml vllm serve meta-llama/Llama-3.1-8B-Instruct --gpu-memory-utilization 0.8 --port 8000 --kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'要点说明:
LMCACHE_CONFIG_FILE=example.yaml指定 LMCache 配置文件,vLLM 集成层会读取它来初始化 LMCache 实例;--kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'指定 KV 传输配置:LMCacheConnectorV1是 LMCache 为 vLLM 提供的 KV 连接器,kv_both表示该实例同时承担 KV cache 的保存(store)与加载(load)角色,使推理过程中的 KV cache 能够被 LMCache 捕获并存入配置的存储层。
4.2 启动 LMCache 控制器(端口 9000,monitor 端口 9001)
lmcache_controller --host localhost --port 9000 --monitor-port 9001从 控制器入口源码 可以看到,--host默认0.0.0.0、--port默认 9000、--monitor-port默认 9001(兼容旧接口,新接口为--monitor-ports,可传入'{"pull": 8300, "reply": 8400}'形式的 JSON)。控制器启动后,会持续监听 worker 的心跳与上报信息,并将管理 API(如/pin、/lookup、/clear、/move、/compress等)暴露在host:port上。
五、第三步:发送推理请求并获取 token 序列
5.1 发送一次 completion 请求
curl -X POST http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Llama-3.1-8B-Instruct", "prompt": "Explain the significance of KV cache in language models.", "max_tokens": 10 }'该请求会让 vLLM 对 prompt 做预填充,LMCache 随即把这段 prompt 对应的 KV cache 分块写入本地 CPU 存储层(LocalCPUBackend)。
5.2 通过 tokenize 接口拿到 token id 序列
curl -X POST http://localhost:8000/tokenize \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Llama-3.1-8B-Instruct", "prompt": "Explain the significance of KV cache in language models." }'返回结果类似:
{"count":12,"max_model_len":4096,"tokens":[128000,849,21435,279,26431,315,85748,6636,304,4221,4211,13],"token_strs":null}其中count为 12,说明该 prompt 共 12 个 token(包含起始符128000);tokens数组即完整的 token id 序列,下一步 Pin 操作直接使用这组 id。
六、第四步:调用/pin固定该请求的 KV cache
curl -X POST http://localhost:9000/pin \ -H "Content-Type: application/json" \ -d '{ "tokens": [128000, 849, 21435, 279, 26431, 315, 85748, 6636, 304, 4221, 4211, 13], "instance_id": "lmcache_default_instance", "location": "LocalCPUBackend" }'三个字段分别对应:
tokens:从/tokenize获得的 12 个 token id;instance_id:必须与example.yaml中的lmcache_instance_id(lmcache_default_instance)一致;location:KV cache 所在后端,示例为LocalCPUBackend(本地 CPU 存储层)。
成功后会返回:
{"event_id": "xxx", "num_tokens": 12}num_tokens:表示有多少个 token 的 KV cache 被成功固定(此处为 12,与 tokenize 得到的 count 一致);event_id:本次操作的唯一事件 ID,可用于后续查询操作状态(如通过check_finish接口确认异步操作是否完成,参见 控制器 API 服务实现)。
七、源码视角:一次 Pin 操作的完整调用链
Pin 功能并非黑盒,仓库源码清晰地展示了从 HTTP 请求到存储后端的一整条链路:
7.1 HTTP 入口:/pin端点
在 控制器 FastAPI 服务 中,PinRequest定义了请求体(instance_id、location、tokens: list[int]),PinResponse定义返回体(event_id、num_tokens)。处理器会把请求封装为PinMsg并交给LMCacheControllerManager进行编排:
msg = PinMsg(event_id=event_id, instance_id=req.instance_id, location=req.location, tokens=req.tokens) ret_msg = await lmcache_controller_manager.handle_orchestration_message(msg)PinMsg/PinRetMsg的定义见 消息定义 与 返回消息定义:PinRetMsg包含event_id与num_tokens,对应接口返回的两个字段。
7.2 控制平面:KV Controller 与 Executor
控制器收到PinMsg后,由 KVController.pin 转发给集群执行器(cluster executor):
async def pin(self, msg: PinMsg) -> PinRetMsg: assert self.cluster_executor is not None return await self.cluster_executor.execute("pin", msg)在 Executor.pin 中,控制器会根据instance_id找到该实例下注册的全部 worker(reg_controller.get_workers),为每个 worker 构造一个PinWorkerMsg,并通过 socket 并行下发,最后汇总各 worker 返回的num_tokens:
worker_event_id = f"Worker{worker_id}{msg.event_id}" serialized_msg = msgspec.msgpack.encode( PinWorkerMsg(worker_event_id=worker_event_id, tokens=tokens, location=location) )如果某个 worker 未注册,控制器会返回ErrorMsg,提示Worker {worker_id} not registered for instance {instance_id}——这也是实践中常见的报错来源(例如instance_id拼写与配置不一致时)。
7.3 数据平面:存储后端的 pin 语义
Pin 最终作用于存储后端中的“内存对象”(MemoryObj)。在 抽象后端接口 中定义了:
@abc.abstractmethod def pin(self, key: CacheEngineKey) -> bool: """Pin a memory object so it will not be evicted."""对应地,内存对象基类 提供pin()/unpin()抽象方法,其语义注释明确写道:
Pin the memory obj so that it will not be evicted. / Unpin the memory obj so that it can be evicted.
这说明 Pin 的本质是在存储后端与内存管理层共同维护一个“固定标记”:被 pin 的对象在驱逐(eviction)流程中会被跳过,只有 unpin 之后才重新进入可回收集合。unpin/remove等反向操作同样在 抽象后端接口 中有对应定义,可供需要解除固定的场景使用。
7.4 在 MP 模式中的对应能力
若使用 LMCache MP(多进程)模式,Pin 能力由协调器(coordinator)提供,对应 HTTP 接口为 POST/DELETE/GET/cache/pins,相关请求/响应 schema 定义在 coordinator schemas,其中pin_count表示某个 key 上的活跃 pin 数,每次DELETE /cache/pins都会减少一个计数,force参数则允许绕过锁与 pin 过滤器强制删除。官方文档建议新项目优先使用 MP 模式以获得更完善的功能支持与性能(见 LMCache MP 文档)。
八、常见问题与排查建议
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
返回ErrorMsg: Worker not registered | instance_id与配置不一致,或 worker 尚未上报 | 核对example.yaml中的lmcache_instance_id;确认controller_pull_url(9001)可达 |
num_tokens为 0 | 该 token 序列的 KV cache 尚未写入指定location | 先发送一次 completion 请求再执行 Pin;确认local_cpu: True且容量充足 |
| 控制器端口连不上 | 控制器未启动或端口被占用 | 确认lmcache_controller正常运行,--port/--monitor-port与配置对应 |
| 无法通过 9001 上报 | controller_pull_url与控制器 monitor 端口不一致 | 两者必须都是localhost:9001 |
此外还需注意:tokens必须是 vLLM 使用的真实 token id 序列(建议直接复用/tokenize的输出,避免手工分词产生偏差);location名称应与实际存储后端一致,不同后端(如LocalCPUBackend、远端存储等)的命名以当前仓库配置与实现为准。
九、总结
通过本示例你可以看到,LMCache 的 Pin/Persistence 能力让 KV cache 的管理不再局限于推理引擎内部:外部系统可以通过控制器暴露的/pinHTTP 接口,精确到 token 粒度地指定哪些 KV cache 需要长期保留。这在热 prompt 预加载、多请求共享前缀缓存、以及缓存预热等场景中非常实用。
整套流程的核心在于“实例 ID + 位置 + token 序列”三者对齐:instance_id决定命中哪个实例,location决定作用于哪个存储层,tokens决定固定哪些 chunk。配合 example.yaml 与本文的调用链分析,你可以直接在自己的环境中复现这一流程,并将 Pin 集成到业务侧的缓存管理逻辑中。若想进一步了解控制器暴露的其他管理接口(lookup、clear、move、compress 等),可继续阅读 控制器内部 API 服务 与 KV cache 管理文档。
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考