架构与开发者指南#
本页描述了 LMCache 多进程模式的内部架构。旨在帮助希望理解、调试或扩展系统的开发人员。
高层架构#
vLLM Instance(s)
|
| ZMQ (tcp)
v
MessageQueueServer (mq.py)
|
| dispatch by RequestType
v
MPCacheEngine (server.py)
|
|--- TokenHasher / SessionManager
|
v
StorageManager (distributed/storage_manager.py)
|
|--- L1Manager (l1_manager.py)
| |--- L1MemoryManager (memory allocator)
| |--- 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)
服务器变体#
所有三个服务器入口点共享相同的 MPCacheEngine 和 StorageManager 核心。
``server.py`` -- 默认的仅 ZMQ 服务器。创建一个 MPCacheEngine 和一个 MessageQueueServer,为所有核心 RequestType 值注册处理程序,并在保持活动循环中阻塞。
``blend_server_v2.py`` -- 扩展了 MPCacheEngine,增加了 BlendEngineV2,该引擎添加了 CacheBlend 操作 (CB_REGISTER_KV_CACHE, CB_LOOKUP_PRE_COMPUTED, CB_STORE_PRE_COMPUTED, CB_RETRIEVE_PRE_COMPUTED, CB_STORE_FINAL)。 使得在文档段落之间能够重用非前缀 KV Cache。
``http_server.py`` -- 在 FastAPI 应用程序中封装 run_cache_server() (来自 server.py)。 端点由 http_apis/ 下的模块贡献,并通过 HTTPAPIRegistry 自动注册:GET / (基本存活检查)、GET /healthcheck 用于 Kubernetes 探针、POST /clear-cache 用于清除 L1 (CPU) 内存中的所有 KV 缓存数据,以及 GET /status 用于检查详细的内部状态。 ZMQ 服务器作为同一进程的一部分运行,任何配置的运行时插件在 FastAPI 启动期间由 MPRuntimePluginLauncher 生成。
ZMQ 协议#
vLLM 和 LMCache 之间的通信使用 ZMQ(DEALER/ROUTER 模式)。
**RequestType 枚举**(定义在 protocols/base.py):
请求类型 |
处理程序类型 |
描述 |
|---|---|---|
|
同步 |
为 vLLM 实例注册 GPU KV Cache 张量。 |
|
同步 |
注销 KV Cache 张量。 |
|
阻塞 |
将 KV Cache 块从 GPU 存储到 L1 (CPU)。 |
|
阻塞 |
将 KV Cache 块从 L1 (CPU) 复制回 GPU。 |
|
阻塞 |
提交前缀查找;预取作业由 request_id 在服务器端进行跟踪。 |
|
阻塞 |
通过 request_id 轮询预取作业。完成时返回加载的块数,预取仍在进行时返回 |
|
阻塞 |
在预取完成之前,通过 request_id 查询查找阶段的命中块计数。当查找仍在运行时返回 |
|
阻塞 |
从取消的查找中释放读取锁,而无需执行完整的 RETRIEVE。 |
|
阻塞 |
移除已完成请求的会话状态。 |
|
阻塞 |
清除所有缓存数据。 |
|
同步 |
返回服务器的块大小。 |
|
阻塞 |
存活探测;处理程序始终返回 |
|
阻塞 |
vLLM 调度器的火忘通道,用于向可观察性子系统报告 GPU 块分配事件。 |
|
同步 |
调试心跳 -- 返回确认字符串。 |
|
同步 |
(Blend) 注册 CacheBlend KV 缓冲区。 |
|
同步 |
(Blend) 取消注册 CacheBlend KV 缓冲区。 |
|
阻塞 |
(Blend) 存储预计算的段落块。 |
|
阻塞 |
(Blend) 查找预计算的段落块。 |
|
阻塞 |
(Blend) 将预计算的段落块检索到 GPU。 |
|
阻塞 |
(Blend) 存储最终混合块。 |
|
阻塞 |
(Blend V2)查找预计算的块;返回 |
|
阻塞 |
(Blend V2)使用 |
处理程序类型:
同步 -- 直接在 ZMQ 主循环中运行(快速,非阻塞)。
阻塞 -- 分配到线程池(可能涉及 GPU 复制或 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
blend_server_v2.py 和 http_server.py 都重用了这个模式,为 HTTP 变体添加了 add_http_frontend_args()。
分布式存储#
StorageManager#
lmcache/v1/distributed/storage_manager.py
将 L1、L2 和所有控制器连接在一起的顶级管理器。关键方法:
reserve_write()/finish_write()-- L1 的两阶段写入。submit_prefetch_task()/query_prefetch_status()-- 异步查找 + L2 预取。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 实例(读和写),并具有可配置的超时,以防止因客户端崩溃而导致的死锁。
L1MemoryManager 处理底层内存分配(懒惰增长至 --l1-size-gb)。
L2 适配器#
lmcache/v1/distributed/l2_adapters/
L2AdapterInterface``(在 ``base.py 中)定义了三个异步任务方法:
submit_store_task(key, data)-- 将数据推送到 L2.submit_lookup_and_lock_task(keys)-- 检查 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() 监听事件文件描述符和适配器存储事件文件描述符。当 L1 中出现新对象时(通过 StoreListener 发出信号),它根据 StorePolicy 向每个 L2 适配器提交异步存储任务。
逐出控制器 (storage_controllers/eviction_controller.py):定期检查 L1 内存使用情况与水位线阈值的关系。当触发时,使用配置的策略(LRU、IsolatedLRU 或 noop)逐出对象,直到使用量降到目标以下。IsolatedLRU 根据通过 /quota HTTP 端点注册的限制,针对 cache_salt 进行逐出;请参见 /quota — 每个``cache_salt``的配额管理。
预取控制器 (storage_controllers/prefetch_controller.py): 处理 StorageManager 在 LOOKUP RPC 中提交的 L2 查找和加载请求。当键不在 L1 中时,它会查询 L2 适配器并将找到的数据加载回 L1。
请求流程#
查找流程#
vLLM MPCacheEngine 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 MPCacheEngine 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 MPCacheEngine 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、MPCacheEngine)将 Event 对象发布到一个有界队列中 (--event-bus-queue-size, 默认 10000,溢出时尾部丢弃)。一个后台排空线程将每个事件分发给所有注册的订阅者。
订阅者 位于 lmcache/v1/mp_observability/subscribers/ 目录下,按关注点分组:metrics/``(OTel 计数器和生命周期直方图)、``logging/``(Python 日志处理程序、查找哈希 JSONL)和 ``tracing/``(由 START/END 事件对构建的 OTel 跨度)。``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 标志的限制。
添加新的请求类型#
在
protocols/base.py中向RequestType添加一个新成员。在适当的
protocols/*.py文件中创建一个ProtocolDefinition``(``engine、controller、observability、debug、blend或blend_v2),并将请求名称添加到该模块的REQUEST_NAMES中。在
MPCacheEngine(或BlendEngineV2)上实现处理程序方法。在
run_cache_server()中通过add_handler_helper()注册处理程序。
关键源文件#
文件 |
目的 |
|---|---|
|
MPCacheEngine + ZMQ 服务器入口点 |
|
MPServerConfig, HTTPFrontendConfig |
|
BlendEngineV2 (extends MPCacheEngine) |
|
带健康检查和许多其他有用 API 的 FastAPI 包装器 |
|
|
|
可扩展的 HTTP 端点 ( |
|
|
|
请求类型、处理程序类型、协议定义 |
|
存储管理器(顶层管理器) |
|
StorageManagerConfig 层次结构 |
|
L1Manager(对象状态机) |
|
L2 适配器配置注册表 |
|
L2AdapterInterface |
|
StoreController(事件驱动 L1->L2) |
|
逐出控制器(基于水印触发) |
|
预取控制器 (未命中时从 L2->L1) |
|
可观察性配置 + |
|
事件总线单例和 |
|
|
|
OTel 指标 / 跟踪提供程序设置 |
|
指标、日志和追踪订阅者 |
|
跟踪记录 ( |