概述#
LMCache multiprocess (MP) mode runs LMCache as a standalone service that vLLM instances reach through a configurable ZMQ or gRPC request transport. One LMCache server per node can serve multiple vLLM pods, providing process isolation, shared caching, and independent resource scaling.
主要优势#
进程隔离 -- LMCache 和 vLLM 在独立的进程(或容器)中运行,因此与缓存相关的问题不会导致推理引擎崩溃。
推理路径上没有 GIL 争用或 Python 开销 -- 通过在单独的进程中运行 LMCache,它的 Python GIL 和 CPU 工作(哈希、内存管理、L2 I/O)不会与 vLLM 的推理线程竞争。
跨 Pod 共享缓存 -- 同一节点上的多个 vLLM 实例共享一个 L1 缓存,最大化 KV 重用。
独立资源扩展 -- 为缓存分配 CPU 内存,与用于推理的 GPU 显存独立。
多层存储 (L1 + L2) -- L1 缓存(在 CPU DRAM 中,或通过 GPUDirect Storage 的 NVMe 块),由通过 NIXL(GDS、POSIX、HF3FS 等)的持久化 L2 存储提供支撑。
内置可观察性 -- 开箱即用的 Prometheus 指标和遥测事件系统。
前提条件#
vLLM 建议使用最新版本以获得最佳兼容性
LMCache 最新开发分支
服务器变体#
LMCache 提供两个服务器入口点:
入口点 |
描述 |
|---|---|
|
Recommended. Configurable request transport (ZMQ by default, or gRPC) + FastAPI HTTP frontend — see HTTP API. |
|
(Legacy) Request server with no HTTP endpoints; same
|
以下部分介绍 LMCache MP 模式的内部实现,适合希望了解、调试或扩展该系统的读者。
高层架构#
Engine Worker(s)
|
| RequestClient (URL scheme selects ZMQ or gRPC)
v
RequestServer factory (transport/server_factory.py)
| |
| ZMQ | gRPC
v v
MessageQueueServer GrpcMultiprocessServer
(transport/zmq_impl/mq.py) (transport/grpc_impl/server.py)
| |
+------------------+-------------------+
| dispatch by operation name
v
EngineModule handlers owned by MPCacheServer (server.py)
|
|--- TokenHasher / SessionManager
|
v
StorageManager (distributed/storage_manager.py)
|
|--- L1Manager (l1_manager.py)
| |--- L1MemoryManager (CPU DRAM),
| | DevDaxL1MemoryManager (Device-DAX slab), or
| | GDSL1MemoryManager (NVMe slab via cuFile / hipFile)
| |--- TTLLock per object (read/write)
|
|--- StoreController -----> L2 Adapter(s) (async L1->L2 push)
|--- PrefetchController ---> L2 Adapter(s) (async L2->L1 load)
|--- EvictionController ----> L1Manager (watermark-triggered eviction)
|
v
EventBus + OTel providers (observability)
引擎和模块#
所有服务器入口点共享相同的 MPCacheServer 和 StorageManager 核心。MPCacheServer 现在是一个轻量级合成器:它持有一个 MPCacheServerContext 和一个由 _build_modules()(在 server.py 中)基于 --engine-type 和 --supported-transfer-mode 组装的 EngineModule 实例列表。
``server.py`` -- The transport-neutral server compositor. Creates an
MPCacheServer, assembles the engine modules
(LookupModule + ManagementModule + LMCacheDrivenTransferModule
and/or EngineDrivenTransferModule depending on
--supported-transfer-mode — lmcache_driven (default) or
engine_driven loads just one,
auto loads both — plus the blend module when
--engine-type blend is set). It calls create_request_server() to build
the ZMQ or gRPC request server selected by --transport, discovers the
annotated operations exposed by the loaded modules, and blocks in a keep-alive
loop.
``modules/blend.py`` -- Defines BlendModule, the paged-aware
blend pipeline that enables non-prefix KV cache reuse (e.g. across
document paragraphs) on the sparse-prefetch path. KV-cache registration
rides the standard REGISTER_KV_CACHE; the module adds only the CB RPCs
(CB_REGISTER_ROPE, CB_UNREGISTER_ROPE,
CB_RETRIEVE_PRE_COMPUTED, CB_UNIFIED_LOOKUP) and wraps
STORE to register chunk fingerprints, reusing the existing
LMCacheDrivenTransferModule and LookupModule. Selected by passing
--engine-type blend to lmcache server; requires
--supported-transfer-mode to be lmcache_driven or auto and
refuses to load when it is engine_driven.
``http_server.py`` -- Wraps run_cache_server() (from server.py)
inside a FastAPI application. Endpoints are contributed by modules under
http_apis/ and auto-registered via HTTPAPIRegistry: GET / (basic
liveness), GET /healthcheck for Kubernetes probes, POST /cache/clear
for clearing all KV cache data in L1 (CPU) memory, and GET /status
for inspecting detailed internal state. The selected request server runs as
part of the same process, and any configured runtime plugins are spawned by
MPRuntimePluginLauncher during FastAPI startup.
Request Operations#
Workers call the same typed operations through either ZMQ (DEALER/ROUTER) or gRPC. The handler scheduling contract is transport-neutral.
ZMQ encodes requests as msgspec multipart messages over DEALER/ROUTER sockets; gRPC encodes the same operations with protobuf. Both dispatch by operation name; see Request Transport for endpoint selection and wire details.
RPC operations (declared by typed methods on RequestClient):
请求类型 |
处理程序类型 |
描述 |
|---|---|---|
|
同步 |
为 vLLM 实例注册 GPU KV Cache 张量。 |
|
同步 |
注销 KV Cache 张量。 |
|
同步 |
注册一个引擎驱动的 KV Cache 上下文(使用 PREPARE/COMMIT 传输路径的 CPU/加速器工作线程)。仅在 |
|
同步 |
注销引擎驱动的 KV Cache 上下文。 |
|
阻塞 |
将 KV Cache 块从 GPU 存储到 L1 (CPU)。基于 LMCache 的传输路径 (CUDA IPC);仅在 |
|
阻塞 |
将 KV Cache 块从 L1 (CPU) 复制回 GPU。由 LMCache 驱动的传输路径 (CUDA IPC);仅在 |
|
阻塞 |
(引擎驱动路径)工作线程请求服务器为一个键准备存储端传输状态。当 |
|
阻塞 |
(引擎驱动路径)工作线程提交块的序列化字节(pickle 路径),或释放已准备好的共享内存槽(SHM 路径),以便服务器将数据持久化到 L1 存储。 |
|
阻塞 |
(引擎驱动路径)工作线程请求服务器为指定键准备检索负载。pickle 路径直接内联返回字节;SHM 路径返回槽位信息,供工作线程从共享内存中读取。 |
|
阻塞 |
(引擎驱动路径)工作线程确认检索完成,服务器随即释放底层读取锁并回收相关传输状态。 |
|
阻塞 |
提交前缀查找;预取任务由 request_id 在服务器端跟踪。 |
|
阻塞 |
通过 request_id 查询预取作业。当完成时返回已加载的块数,预取仍在进行中时返回 |
|
阻塞 |
(仅限 SGLang)阻塞直到预取作业完成,然后返回其加载的块数,超时则返回 |
|
阻塞 |
在预取完成之前,通过 request_id 查询查找阶段的命中块计数。当查找仍在运行时返回 |
|
阻塞 |
从取消的查找中释放读取锁,而无需执行完整的检索。 |
|
阻塞 |
移除已完成请求的会话状态。 |
|
阻塞 |
Clear cached data. The optional |
|
同步 |
返回服务器的块大小。 |
|
阻塞 |
存活 ping;处理程序始终返回 |
|
阻塞 |
vLLM 调度器的即发即忘通道,用于向可观察性子系统上报 GPU 块分配事件。 |
|
同步 |
调试心跳 -- 返回确认字符串。 |
|
同步 |
(Blend) Share the RoPE cos/sin cache onto a context already
registered via |
|
同步 |
(Blend) Drop the RoPE state (paged KV cache lives on; use
|
|
阻塞 |
(Blend) Scatter all matched chunks (prefix- and non-prefix-hit) into paged KV by per-token block ID; re-RoPE only the shifted subset. |
|
阻塞 |
(Blend) Sole lookup path: one RPC runs prefix + non-prefix
match, reconciles, issues one sparse-coalesced prefetch, and
classifies per-TP-rank. Returns |
|
阻塞 |
(P2P)查找给定的键并对本地缓存的前缀进行读锁。返回一个任务 ID,调用者将其传递给 |
|
阻塞 |
(P2P) 查询查找任务的传输地址。查找完成后返回一个 |
|
阻塞 |
(P2P) 释放之前由 |
处理程序类型:
SYNC -- Serialized by the request transport (the ZMQ main loop or the gRPC sync-handler lock) for fast, non-blocking work.
BLOCKING -- Dispatched to a normal or client-affinity thread pool (may involve GPU copies or I/O).
配置系统#
每个配置模块都暴露一个可组合的三元组:
(DataclassConfig, add_*_args(parser), parse_args_to_*_config(args))
server.py:parse_args() 组合它们:
parser = argparse.ArgumentParser(...)
add_mp_server_args(parser) # from multiprocess/config.py
# includes runtime-plugin args
# (--runtime-plugin-locations,
# --runtime-plugin-config)
add_storage_manager_args(parser) # from distributed/config.py
# which internally calls add_l2_adapters_args(parser)
add_observability_args(parser) # from mp_observability/config.py
http_server.py reuses this pattern, adding
add_http_frontend_args() and add_coordinator_args() for the
lmcache server CLI. CacheBlend is no longer a separate entry point —
it is opted into at runtime by passing --engine-type to
server.py (or lmcache server): --engine-type blend appends
BlendModule.
分布式存储#
StorageManager#
lmcache/v1/distributed/storage_manager.py
将 L1、L2 和所有控制器连接在一起的顶级管理器。关键方法:
reserve_write()/finish_write()-- 对 L1 的两阶段写入。submit_prefetch_task()/query_prefetch_status()-- 异步查找 + L2 预取。query_prefetch_status()是非阻塞的,并在 L2 预取仍在进行时返回None。wait_prefetch_status()-- 轮询query_prefetch_status()的阻塞替代方案:在预取结果发布(或超时到期)之前,等待控制器条件变量。仅 L1 的预取会立即返回。由WAIT_PREFETCH_STATUSRPC 使用,以避免在加载路径上进行忙等待。read_prefetched_results()/finish_read_prefetched()-- 从 L1 读取预取的数据,并自动管理锁。
L1Manager#
lmcache/v1/distributed/l1_manager.py
在 CPU 内存中使用状态机管理对象:
None --> write_locked --> ready --> read_locked
(reserve_write) (finish_write) (reserve_read)
| |
v v
evictable finish_read -> ready
每个对象都有两个 TTLLock 实例(读和写),并具有可配置的超时,以防止因崩溃的客户端导致的死锁。
The underlying memory allocation is handled by one of three interchangeable
tiers selected at startup (all satisfy L1ManagerProtocol):
L1MemoryManager(默认)-- 固定 CPU DRAM,按需惰性增长至--l1-size-gb。DevDaxL1MemoryManager-- a Device-DAX-backed L1 slab when--l1-devdax-pathis set. A pure Device-DAX configuration maps the DAX device as the full L1 arena; a hybrid configuration uses DRAM first and spills overflow allocations into Device-DAX. See the L1 Memory Manager section of 配置参考 for the accepted knobs.GDSL1MemoryManager-- an NVMe slab file when--gds-l1-pathis set. The bytes live on disk; reads/writes DMA directly between the GPU staging buffer and the slab, driven by the process-globalGDSContext(gpu_connector/gds_context.py) and dispatched fromgpu_ops. The DMA backend is selected by platform viagpu_connector/_gds_backends.py-- cuFile (libcufile.so) on NVIDIA and hipFile (libhipfile.so) on AMD ROCm; see the GDS L1 Tier section of 配置参考 for the vendor-specific requirements. The CPU tier is disabled in this mode.
L2 适配器#
lmcache/v1/distributed/l2_adapters/
L2AdapterInterface(在 base.py 中)定义了三个异步任务方法:
submit_store_task(key, data)-- 将数据推送到 L2。submit_lookup_and_lock_task(keys)-- 检查键是否存在于 L2 中。submit_load_task(keys, layout_desc)-- 从 L2 加载数据到 L1。
工厂函数 create_l2_adapter()(位于 __init__.py)通过对配置类型执行 isinstance() 检查,实例化对应的适配器。
新适配器类型通过 register_l2_adapter_type() 在 config.py 中注册。
控制器#
StoreController (storage_controllers/store_controller.py):事件驱动的后台线程,使用 select.poll() 监听 listener eventfd 和各适配器的存储 eventfd。当 L1 中出现新对象(通过 StoreListener 发信号)时,根据 StorePolicy 向每个 L2 适配器提交异步存储任务。
EvictionController (storage_controllers/eviction_controller.py):
Periodically checks L1 memory usage against the watermark threshold. When
triggered, evicts objects using the configured policy (LRU, ARC,
IsolatedLRU, or noop) until usage drops below the target.
IsolatedLRU evicts per cache_salt against limits registered through
the /quota HTTP endpoints; see 配额管理.
PrefetchController (storage_controllers/prefetch_controller.py):处理 StorageManager 在 LOOKUP RPC 期间提交的 L2 查找和加载请求。当键不在 L1 中时,查询 L2 适配器并将命中的数据加载回 L1。
请求流#
查找流程#
vLLM MPCacheServer StorageManager L1Manager L2 (PrefetchController)
| | | | |
|---LOOKUP(key)-------->| | | |
| |--submit_prefetch------>| | |
| | |--reserve_read----->| |
| | |<--hit_count--------| |
| | |--submit_prefetch_request--------------->|
| | | (remaining keys) |
| |--query_prefetch------->| | |
| | |--query_prefetch_result----------------->|
| |<--found_count----------| | |
|<--found_count---------| | | |
存储流程#
vLLM MPCacheServer StorageManager L1Manager
| | | |
|---STORE(key,blocks)-->| | |
| |--reserve_write-------->| |
| | |--reserve_write---->|
| | |<--memory_objs------|
| | (GPU->CPU copy) | |
| |--finish_write--------->| |
| | |--finish_write----->|
| | | |
| | | [StoreController detects new objects]
| | | [async L1->L2 push via adapters]
|<--event_handle--------| | |
获取流程#
vLLM MPCacheServer StorageManager L1Manager
| | | |
|---RETRIEVE(key)------>| | |
| |--read_prefetched------>| |
| | |--unsafe_read------>|
| | |<--memory_objs------|
| | (CPU->GPU copy) | |
| |--finish_read_prefetch->| |
| | |--finish_read------>|
|<--event_handle--------| | |
可观察性内部实现#
EventBus (lmcache/v1/mp_observability/event_bus.py) 是一个全局单例,在服务器启动时由 init_observability() 初始化。生产者(L1Manager、StorageManager、MPCacheServer)将 Event 对象发布到有界队列(--event-bus-queue-size,默认 10000,溢出时尾部丢弃)。一个后台排空线程(drain thread)将每个事件分发给所有已注册的订阅者。
订阅者 位于 lmcache/v1/mp_observability/subscribers/ 下,按关注点分组:metrics/(OTel 计数器和生命周期直方图)、logging/(Python 日志处理器、查找哈希 JSONL)以及 tracing/(由 START/END 事件对构建的 OTel span)。init_observability() 根据 CLI 标志(--disable-metrics、--disable-logging、--enable-tracing)注册相应的订阅者集合。
OTel 提供者在构造订阅者之前通过 otel_init.py 完成初始化,确保模块级的 get_meter() / get_tracer() 调用绑定到真实的提供者。指标同时导出到进程内的 Prometheus /metrics 端点(--prometheus-port,默认 9090),并在设置了 --otlp-endpoint 时推送到 OTel 收集器。
如何扩展#
添加新的 L2 适配器#
在 lmcache/v1/distributed/l2_adapters/ 下创建一个新的 *_l2_adapter.py 模块 — __init__.py 会通过 pkgutil 自动发现匹配该后缀的模块,并在首次使用时惰性导入,无需修改其他文件。
创建一个配置类,继承自
L2AdapterConfigBase,并实现from_dict()和help()方法。创建一个实现
L2AdapterInterface的适配器类,以及一个小型工厂函数(config, l1_memory_desc) -> L2AdapterInterface。在模块级别,自注册配置和工厂:
register_l2_adapter_type("my_adapter", MyAdapterConfig) register_l2_adapter_factory("my_adapter", _create_my_adapter)
请参阅 mock_l2_adapter.py 或 s3_l2_adapter.py 以获取参考实现。
添加可观察性订阅者#
创建一个继承自
EventSubscriber的订阅者类(定义在lmcache/v1/mp_observability/event_bus.py中):实现get_subscriptions()以返回{EventType: callback}映射;可选地重写shutdown()以进行清理。将类放置在对应的关注组(
subscribers/metrics/、subscribers/logging/或subscribers/tracing/)下,并从该包的__init__.py中导出。在
init_observability()(lmcache/v1/mp_observability/config.py)中,通过bus.register_subscriber(...)在与其关注点(metrics / logging / tracing)匹配的分支内注册订阅者,如有需要可受相应 CLI 标志控制。
Adding a new RPC#
Add a typed
@rpc_methodtoRequestClient.Add the gRPC protobuf request, response, and service method.
Add a same-named
@request_handleron the appropriateEngineModule.
Existing ZMQ operations retain their frozen numeric wire IDs. New operations use their string name and do not extend the legacy compatibility table.
关键源文件#
文件 |
目的 |
|---|---|
|
MPCacheServer composition and transport-neutral request-server entry point |
|
MPServerConfig, HTTPFrontendConfig |
|
MPCacheServerContext(传递给每个 EngineModule 的共享状态) |
|
Transport-neutral |
|
RPC discovery and typed operation specifications |
|
Typed |
|
Transport-neutral request-server construction boundary |
|
ZMQ |
|
Protobuf wire contracts for the gRPC request transport |
|
Build-time protobuf generator and generated Python package |
|
gRPC service binding, handler scheduling, and request-server construction |
|
Engine module implementations: |
|
带健康检查和许多其他有用 API 的 FastAPI 包装器 |
|
|
|
可扩展的 HTTP 端点 ( |
|
|
|
存储管理器(顶级管理器) |
|
StorageManagerConfig 层次结构 |
|
L1Manager(对象状态机) |
|
L2 适配器配置注册表 |
|
L2AdapterInterface |
|
StoreController(事件驱动 L1->L2) |
|
逐出控制器(基于水印触发) |
|
PrefetchController (L2->L1 未命中时) |
|
可观察性配置 + |
|
事件总线单例和 |
|
|
|
OTel 指标 / 跟踪提供程序设置 |
|
指标、日志和追踪订阅者 |
|
追踪记录 ( |