
本文档深入剖析 LMCache 的多级存储架构及其代码实现机制。重点阐述核心组件 StorageManager 如何协同管理内存 (L1)、弹性互联 (L2)、本地磁盘 (L3) 及远程共享存储 (L4),并通过智能调度策略实现 KV Cache 的高效流转与生命周期管理。

相关文章:LMCache 架构概览,持续更新,请参考:https://github.com/ForceInjection/AI-fundermentals/blob/main/inference/lmcache/lmcache_overview.md
说明:为了更清晰地阐述多级存储架构,本文引入了 L1 (内存)、L2 (弹性互联)、L3 (磁盘)、L4 (远程) 的分层概念。该模型旨在反映各存储介质的访问延迟与层级关系,并非 LMCache 代码中的官方命名。
LMCache 的存储系统采用模块化设计,主要由调度管理层和分级存储后端两部分构成。
这一层定义了系统的控制逻辑与交互规范,确保数据在复杂的多级存储结构中能够高效、准确地流转。
put, get, exists 等标准操作,所有存储后端均需实现此接口,确保了系统的可扩展性。LMCache 将存储介质划分为四个层级(部分层级可选),依据访问速度与容量成本的权衡,构建了从本地内存到远程服务的全方位存储体系。
L1: 极速内存层 (Memory Tier):
LocalCPUBackend 充当 Allocator,基于 GPU 显存,负责接收来自 Prefiller 的数据流。L2: 弹性互联层 (P2P Tier):
L3: 本地持久层 (Disk Tier):
O_DIRECT 和异步 I/O 技术作为后备缓存,适合存储被逐出的冷数据。L4: 远程共享层 (Remote/Shared Tier):
StorageManager 是整个多级存储的大脑,位于 lmcache/v1/storage_backend/storage_manager.py。它负责初始化存储后端、分发存储请求以及协调数据检索。
在 StorageManager.__init__ 中,它调用 CreateStorageBackends 来创建并初始化所有启用的存储后端。这些后端被存储在一个 OrderedDict 中,这个顺序至关重要,因为它定义了数据检索的优先级(层级)。
# lmcache/v1/storage_backend/storage_manager.py
class StorageManager:
def __init__(self, config, metadata, ...):
# ...
self.storage_backends: OrderedDict[str, StorageBackendInterface] = (
CreateStorageBackends(
config,
metadata,
self.loop,
dst_device,
lmcache_worker,
)
)StorageManager 的存储策略倾向于 "Write-All" (全写模式)。当调用 batched_put 时,它会将数据分发给所有活跃的存储后端。
为了保证主线程(通常是 GPU 推理线程)不被阻塞,batched_put 采用了以下优化:
backend.batched_submit_put_task 是非阻塞的,它通常会将任务放入后台队列(如 LocalDiskBackend 的线程池)。StorageManager 使用独立的 CUDA Stream (internal_copy_stream) 进行异步拷贝,避免阻塞计算流。# lmcache/v1/storage_backend/storage_manager.py
def batched_put(self, keys, memory_objs, ...):
# ...
for backend_name, backend in self.storage_backends.items():
# ...
# 如果需要,使用 CUDA Stream 异步拷贝数据到 CPU
if cname not in obj_dict:
new_keys, new_objs = allocate_and_copy_objects(
allocator_backend, keys, memory_objs, self.internal_copy_stream
)
obj_dict[cname] = (new_keys, new_objs)
# 提交非阻塞任务
backend.batched_submit_put_task(ks, objs, transfer_spec=transfer_spec)这种机制确保了数据在各个层级(如 L1 内存、L3 磁盘、L4 远程)都有副本。
StorageManager 提供了同步和异步两种检索方式。它们都遵循 "层级遍历" (Waterfall) 的原则:按照 storage_backends 中定义的顺序,依次查询数据。
更重要的是,StorageManager 在 get 方法中实现了 "自动提升" (Promotion / Write-Back) 机制:当数据在低层级(如 L3 Disk 或 L4 Remote)被找到时,它会被自动写回高层级(L1 CPU),以加速后续的访问。
# lmcache/v1/storage_backend/storage_manager.py
def get(self, key, location=None):
# 遍历所有活跃的后端(按顺序:CPU -> P2P -> Disk -> Remote)
for backend_name, backend in self.get_active_storage_backends(location):
memory_obj = backend.get_blocking(key)
if memory_obj:
# 如果不是从 L1 获取的,则提升到 L1
if (
backend_name not in ["LocalCPUBackend", "PDBackend"]
and "LocalCPUBackend" in self.storage_backends
):
local_cpu_backend = self.storage_backends["LocalCPUBackend"]
local_cpu_backend.submit_put_task(key, memory_obj)
return memory_obj
return None这种机制确保了 LMCache 总是优先从速度最快的层级(如 CPU)获取数据,如果未命中,再尝试下一层级(如 Disk)。
由于 L1 (LocalCPUBackend) 和 L3 (LocalDiskBackend) 的容量有限,LMCache 必须通过高效的策略管理数据生命周期。
注意:
PDBackend作为特殊的 L1 实现,不参与自动缓存逐出。当内存不足时,它会阻塞等待直到有可用内存(通常依赖上层应用释放)。只有LocalCPUBackend支持以下逐出策略。
本章详细解析 LMCache 中关键存储后端的实现细节,包括内存分配器 (PDBackend / LocalCPUBackend)、本地磁盘 (LocalDiskBackend) 和远程存储 (RemoteBackend)。
存储后端的创建顺序定义在 lmcache/v1/storage_backend/__init__.py 中的 CreateStorageBackends 函数里。
lmcache/v1/storage_backend/__init__.py
目前的层级顺序是固定的(Hardcoded):
enable_pd=True),它是第一个被创建的,并替代 LocalCPUBackend 成为核心 Allocator。local_cpu=True,它也会被创建,但在 PDBackend 之后。local_disk 路径,则创建。storage_plugin_launcher 加载的自定义后端(如果有配置)。重要说明:内存分配器 (Allocator) 的必要性 系统必须启用
LocalCPUBackend或PDBackend其中之一。这是因为它们不仅作为存储后端,还实现了AllocatorBackendInterface,承担了内存分配器 (Allocator) 的角色。 即使数据最终只存储在远程 (RemoteBackend),系统也需要先通过 Allocator 分配本地 CPU 内存 或 GPU 显存 作为中转 Buffer,才能将数据发送到远程。因此,Allocator 后端是系统运行的基石。
LocalCPUBackend 位于 lmcache/v1/storage_backend/local_cpu_backend.py,利用系统内存存储 KV Cache。它是 LMCache 最基础的存储后端,也是默认的内存分配器 (Allocator)。
LocalCPUBackend 负责管理有限的内存资源。当调用 allocate 分配内存时,如果当前内存不足,它会触发逐出机制。逐出策略是可插拔的,通过 CachePolicy 接口实现。
cache_policy.get_evict_candidates 获取最近最少使用的 Key。hot_cache 中移除。由于 LMCache 采用 Write-All 策略,这些数据通常已存在于磁盘或远程后端,因此直接丢弃是安全的。# lmcache/v1/storage_backend/local_cpu_backend.py
def allocate(self, ...):
# ...
while True:
# 尝试逐出
if self.use_hot:
evict_keys = self.cache_policy.get_evict_candidates(self.hot_cache, ...)
if evict_keys:
self.batched_remove(evict_keys, force=False)
# ...PDBackend (位于 lmcache/v1/storage_backend/pd_backend.py) 是为了支持 Prefill-Decode 分离 (Disaggregation) 架构而设计的特殊后端。在此架构中,计算负载被拆分为 Prefill(预填充)和 Decode(解码)两个阶段,通常部署在不同的实例甚至机器上。
PDBackend 根据配置 (pd_role) 扮演两种角色,并替代 LocalCPUBackend 成为系统的内存分配器 (Allocator):
put 操作时,不进行本地存储,而是直接通过网络将数据推送到 Receiver。在 StorageManager 初始化时:
enable_pd=True,系统会初始化 PDBackend。PDBackend 成为主要的 Allocator (_get_allocator_backend 返回 PDBackend)。LocalCPUBackend 可能仍会被初始化(如果 local_cpu=True),但主要作为本地缓存使用,而非核心分配器。# lmcache/v1/storage_backend/storage_manager.py
def _get_allocator_backend(self, config):
if self.enable_pd:
return self.storage_backends["PDBackend"]
else:
return self.storage_backends["LocalCPUBackend"]这一设计允许 LMCache 在保持统一存储接口的同时,支持高性能的跨实例 KV Cache 传输流。
P2PBackend 位于 lmcache/v1/storage_backend/p2p_backend.py,它在存储层级中拥有比本地磁盘更高的优先级。这意味着当本地 CPU 未命中时,系统会优先尝试从其他节点的 CPU 内存中拉取数据,而不是读取本地磁盘。
P2PBackend 采用分层查找策略来定位数据,以最小化延迟:
Cache Controller 发送批量查找请求 (BatchedP2PLookupMsg)。Controller 维护了全局的 Key 分布视图(RegistryTree),返回持有数据的 Peer 节点地址。CreateTransferChannel 创建抽象传输通道,底层可支持 RDMA、TCP 等多种协议。LocalCPUBackend 的内存池,尽量实现数据在网络与 GPU/CPU 之间的零拷贝传输。LocalDiskBackend 位于 lmcache/v1/storage_backend/local_disk_backend.py。
值得注意的是,LocalDiskBackend 在初始化时需要传入 local_cpu_backend。这是因为磁盘 I/O 通常需要经过 CPU 内存作为中转(Buffer),或者在读取后将数据“提升”回 CPU 缓存以加速后续访问。
为了不阻塞主线程,LocalDiskBackend 使用了一个专门的 Worker 类 LocalDiskWorker,内部维护了一个 AsyncPQThreadPoolExecutor(基于优先级的线程池执行器)来处理磁盘读写任务。
任务优先级(Priority)设计如下(数值越小优先级越高):
# lmcache/v1/storage_backend/local_disk_backend.py
class LocalDiskWorker:
def __init__(self, loop):
# 使用线程池进行磁盘 I/O
self.executor = AsyncPQThreadPoolExecutor(loop, max_workers=4)
async def submit_task(self, task_type, ...):
if task_type == "prefetch":
priority = 0
elif task_type == "delete":
priority = 1
elif task_type == "put":
priority = 2
# .../ 替换为 -,后缀 .pt)。O_DIRECT 标志打开文件,绕过操作系统页缓存(Page Cache),直接在用户空间缓冲区和磁盘之间传输数据,减少内存拷贝并降低 CPU 开销。# lmcache/v1/storage_backend/local_disk_backend.py
def write_file(self, buffer, path):
# ...
if size % self.os_disk_bs != 0 or not self.use_odirect:
with open(path, "wb") as f:
f.write(buffer)
else:
# 使用 O_DIRECT 进行高性能写入
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_DIRECT, 0o644)
os.write(fd, buffer)
os.close(fd)RemoteBackend 位于 lmcache/v1/storage_backend/remote_backend.py,作为四级缓存,负责与远程存储服务交互。
为了支持多种远程存储后端(如 Redis, S3, Memcached 等),RemoteBackend 使用了 RemoteConnector 抽象层。
put, get, exists)。RemoteBackend 使用 CreateSerde 工厂创建序列化器(如 CacheGen 编码器),以优化传输效率。RemoteBackend 同样支持异步操作,并通过 RemoteMonitor 动态监控连接状态。如果连接断开,它会尝试自动重连,并在断连期间降级服务(例如返回 False),避免阻塞本地推理。
# lmcache/v1/storage_backend/remote_backend.py
class RemoteBackend(StorageBackendInterface):
def __init__(self, ...):
# ...
# 初始化序列化器
self.serializer, self.deserializer = CreateSerde(...)
# 启动连接监控
self.remote_monitor = RemoteMonitor(self)
self.remote_monitor.start()LMCache 根据应用场景的不同,通过配置组合出不同的存储后端拓扑。
本模式是 LMCache 最基础的用法,旨在通过利用本地资源(CPU 内存和磁盘)来加速单机环境下的推理任务,特别适合开发、测试以及对延迟敏感的单实例服务。
local_cpu=Truelocal_disk=Path (可选)LocalCPUBackendLocalDiskBackend (可选)LocalCPUBackend)。LocalDiskBackend,后台线程将数据写入磁盘文件。LocalCPUBackend,若命中则直接使用。LocalDiskBackend。LocalCPUBackend 以加速后续访问。本模式通过引入中心化的存储服务,实现了多实例间的 KV Cache 共享,适用于中小型集群或对一致性要求较高的场景。
remote_url="lm://<host>:<port>"LocalCPUBackendRemoteBackendLocalCPUBackend。StorageManager 通过 RemoteBackend 异步将数据序列化。LMCache Server 进行持久化存储。LocalCPUBackend (未命中)。RemoteBackend,向 Server 发送 Lookup 请求。LocalCPUBackend。本模式专为大规模高并发集群设计,去除了中心化存储瓶颈,通过点对点直接传输实现高性能的数据共享。
Cache Controller 仅负责元数据管理和对等点发现。enable_p2p=TrueLocalCPUBackendP2PBackendLocalCPUBackend。P2PBackend 异步向 Cache Controller 发送 ADMIT 消息,注册 KV Cache 的位置信息。LocalCPUBackend (未命中)。P2PBackend 向 Cache Controller 发起查询,获取持有数据的节点列表 (如 Instance A)。LocalCPUBackend。特别说明:
lmcache/v1/storage_backend/p2p_backend.py 中的 _handle_batched_lookup_and_get 方法仅调用了 local_cpu_backend。# lmcache/v1/storage_backend/p2p_backend.py
async def _handle_batched_lookup_and_get(
self, msg: BatchedLookupAndGetMsg
) -> P2PMsgBase:
# ...
# 仅查询本地 CPU 后端 (Memory)
num_hit_chunks = await self.local_cpu_backend.batched_async_contains(
lookup_id=lookup_id,
keys=keys,
pin=True,
)
mem_objs = await self.local_cpu_backend.batched_get_non_blocking(
lookup_id=lookup_id,
keys=keys[:num_hit_chunks],
)
# ...设计思考 (Design Rationale): 为什么 P2P 不穿透磁盘?
EVICT 信号,从路由表中移除该节点。这种设计简化了状态管理,避免了复杂的跨层级一致性维护。本模式确保了 LMCache 能够无缝集成到基于张量并行 (TP) 或流水线并行 (PP) 的大模型推理架构中,维持各并行 Rank 的数据一致性。
本模式针对极致性能优化的 Prefill-Decode 分离架构,通过内存直传机制,实现了跨节点的毫秒级 KV Cache 传输。
PDBackend 直接将 KV Cache 推送给 Decoder 节点。enable_pd=TruePDBackendLocalCPUBackend (可选)StorageManager 调用 PDBackend。PDBackend 作为 Allocator 分配传输 Buffer。PDBackend 在后台监听并接收数据,存入本地内存字典 (self.data)。StorageManager 查询 PDBackend。PDBackend 的本地接收缓冲区获取已就绪的 KV Cache,并加载到 GPU 进行 Decode。本章详细介绍 LMCache 中缓存逐出策略的实现机制。该模块位于 lmcache/v1/storage_backend/cache_policy/,主要被 LocalCPUBackend 和 LocalDiskBackend 调用以维护容量限制。
正如 2.4 节 所述,逐出策略的核心目标是最大化高层级存储的命中率。
BaseCachePolicy。LMCache 目前支持多种标准的缓存淘汰算法,用户可以通过配置 cache_policy 参数进行选择(默认为 LRU):
LRU 策略通过维护一个双向链表(在 Python 中通常使用 OrderedDict)来跟踪 Key 的访问历史。
move_to_end),标记为“最近活跃”。# lmcache/v1/storage_backend/cache_policy/lru.py
class LRUCachePolicy(BaseCachePolicy):
def update_on_hit(self, key, cache_dict):
# 命中时将 Key 移动到末尾,表示最近被使用
cache_dict.move_to_end(key)
def get_evict_candidates(self, cache_dict, num_candidates=1):
# 从字典头部(最久未使用)开始选择需要逐出的 Key
evict_keys = []
for key, cache in cache_dict.items():
if not cache.can_evict: continue
evict_keys.append(key)
if len(evict_keys) == num_candidates: break
return evict_keys当 KV Cache 被从内存 (LocalCPUBackend) 或磁盘 (LocalDiskBackend) 中逐出时,LMCache 会主动通知 Cache Controller 更新全局元数据。这是保证 P2P 路由准确性的关键。
remove 操作(通常由缓存策略触发)时。batched_msg_sender.add_kv_op(OpType.EVICT, key)。LMCacheWorker 通过长连接异步将 EVICT 消息批量发送给 Cache Controller。lmcache/v1/storage_backend/local_cpu_backend.py: remove 方法中发送消息。lmcache/v1/storage_backend/local_disk_backend.py: remove 方法中发送消息。LMCache 通过精妙的分层存储架构和灵活的调度策略,有效地解决了大模型推理中的 KV Cache 管理难题。
StorageManager 统一管理 L1 (Local CPU/PDBackend)、L2 (P2P)、L3 (Local Disk) 和 L4 (Remote) 多级后端,实现了从极速内存到大容量磁盘再到远程共享存储的无缝衔接。O_DIRECT 以及智能的缓存逐出策略 (LRU),LMCache 在显著扩展 KV Cache 容量(支持无限上下文)的同时,最大限度地降低了首字延迟 (TTFT) 并提升了推理吞吐量。欢迎关注 亨利笔记, 👍 点赞 | ⭐ 收藏 | ↗️ 转发。