LMCache:知识地图、架构与时序图
白话导读:用图把 LMCache 的架构、冷请求、热请求、对象状态和与 Mooncake 的组合画出来。
- 冷请求:先算再存;热请求:先查、再预取到 L1、最后回填 GPU。
- LMCache 的 L1/L2 是它自己的管理层级(常见为主机内存/外部存储),不是 CPU 缓存,也不是 GPU。
- 图省略了错误分支和平台差异,失败路径看设计图册 D10。
图看不懂时,对照架构与源码主线中同名小节。
怎么看这些图:实线是主要的调用或数据路径,虚线是管理、元数据或可选的联系。架构图省略了平台差异和错误分支,不能据此认为任意两个节点之间都能直接传输。失败路径(超时、租约过期、主节点切换)见设计图册。图太宽时可以在图上横向滚动。
1. 全景知识地图
学 LMCache 要同时懂四块:推理语义(KV 为什么能复用)、LMCache 运行时(怎样查、取、存)、存储与传输(数据放哪、怎么搬)、性能与生产(值不值、出错怎么办)。
查看图源
flowchart LR
A["KV Cache / 推理 infra"] --> B["推理语义"]
A --> C["LMCache 运行时"]
A --> D["存储与传输"]
A --> E["性能与生产"]
B --> B1["Prefill / Decode / Attention"]
B --> B2["GQA / MLA / Hybrid / TP"]
B --> B3["前缀依赖 / 位置 / key / layout"]
C --> C1["Connector / Scheduler / Worker"]
C --> C2["Lookup / Prefetch / Retrieve / Store"]
C --> C3["L1 staging / 锁 / 事件 / 会话"]
D --> D1["DRAM / SSD / S3 / 远端内存"]
D --> D2["Mooncake Store / Transfer Engine"]
D --> D3["RDMA / PCIe / NUMA / GDS"]
E --> E1["TTFT / ITL / Goodput / 尾延迟"]
E --> E2["准入 / 淘汰 / 背压 / 路由"]
E --> E3["隔离 / 故障 / 可观测 / 正确性"]
C2 --> F["高级复用"]
F --> F1["CacheBlend / 压缩 / Layerwise"]
F --> F2["P/D 分离 / 跨实例共享"]对应阅读:推理语义看KV 原理;运行时看架构与源码主线;存储看后端契约;生产问题看优化与可观测。
2. MP 架构:谁决定、谁执行、谁存储
一句话:引擎决定算什么,LMCache 服务负责查、搬、存,后端负责保存字节。引擎里的调度器通过 adapter 发 LOOKUP、查状态;worker 通过 adapter 发 STORE、RETRIEVE。LMCache 服务内部由 StorageManager 统一管理 L1 和各个 controller,再通过 L2 adapter 连接外部存储。
查看图源
flowchart TB
U["请求 / tokenizer"] --> S["vLLM Scheduler"]
subgraph ENGINE["推理引擎进程"]
S --> SA["LMCacheMPSchedulerAdapter"]
W["vLLM Worker"] --> WA["LMCacheMPWorkerAdapter"]
W <--> G["GPU paged KV"]
end
subgraph MP["独立 LMCache 服务"]
R["RPC / Session / Modules"]
L["LookupModule"]
T["Transfer modules"]
SM["StorageManager"]
L1["L1Manager / allocator"]
P["PrefetchController"]
ST["StoreController"]
AD["L2 adapters"]
R --> L --> SM
R --> T --> SM
SM <--> L1
SM --> P
L1 --> ST
P <--> AD
ST --> AD
end
SA -->|"LOOKUP / 状态"| R
WA -->|"STORE / RETRIEVE / 上下文"| R
G <-->|"设备传输;不等于普通 RPC payload"| T
AD <--> FS["文件 / 对象存储 / 远端 Store"]
CO["可选 Coordinator"] -.-> R
OBS["HTTP / Metrics / Traces"] -.-> SM三点说明:
- 常见配置中 L1 是主机内存,L2 是外部后端。L1/L2 是 LMCache 自己的管理层级,不是 CPU 的硬件缓存层级;L1 也可以用其他内存来实现。
- GPU 与 LMCache 之间的数据走设备传输,不是塞进普通 RPC 消息里。
- MP 模式也支持由引擎发起传输,图中的跨进程传输不一定由 server 发起。
3. 进程内模式对照
早期教程多用进程内模式:LMCache 作为库跑在引擎进程里。了解它是为了看懂旧资料。
查看图源
flowchart LR
V["vLLM / LMCacheConnectorV1"] --> E["inprocess LMCacheEngine"]
E --> H["TokenDatabase"]
E --> GC["GPU connector"]
E --> BS["inprocess StorageManager"]
BS --> B["CPU / Disk / Remote backend"]它和 MP 模式共用一部分底层能力,但 ObjectKey、L2 adapter 和 MP server 的参数不能直接拿到进程内配置里用。
4. 冷请求:先算再存
缓存里没有可用数据时:先查一遍确认没命中,引擎照常做 Prefill,然后把新算出的 KV 存进 L1,再按配置异步写到 L2。
查看图源
sequenceDiagram
participant S as Scheduler
participant M as LMCache server
participant W as Worker/GPU
participant L as L1 manager
participant B as L2 backend
S->>M: LOOKUP
M-->>S: ack
S->>M: QUERY_PREFETCH_STATUS
M-->>S: 0 个可复用 chunk
S->>W: 调度 prefill
W->>W: 计算 KV / 记录设备完成事件
W->>M: STORE 元数据与传输依赖
M->>L: reserve_write,创建 staging
M->>W: 等待生产者事件并发起 GPU 到 L1 传输
Note over M,L: 实际 copy 完成后才发布
M->>L: finish_write,admit resident
L->>B: StoreController 异步写 L2(若配置)
B-->>L: 完成后释放所持读锁
W-->>S: 继续生成;请求完成与存储完成分开处理注意:
- 这是依赖关系图,不代表每次 STORE 都会让调度器同步等待。请求完成和存储完成是两件事。
- 拷贝真正完成后才发布到 L1,之前对读者不可见。
- 只能保存引擎声明已经算完的有效 token 范围。尾部不满一个 chunk 的部分、Decode 阶段新增 KV 什么时候写回,取决于具体路径和策略。
5. 热请求:查找、预取、回填 GPU
缓存里有数据时:查找触发预取,数据从 L2 先搬进 L1,再在 RETRIEVE 时拷贝到 GPU,最后引擎只计算没覆盖的部分。
查看图源
sequenceDiagram
participant S as Scheduler
participant M as LookupModule
participant P as PrefetchController
participant L as L1
participant B as L2 adapters
participant W as Worker/GPU
S->>M: LOOKUP
M->>P: 提交 grouped keys
M-->>S: ack(并非预取完成)
P->>L: reserve_read
P->>B: lookup_and_lock
B-->>P: 命中 bitmap
P->>P: 汇总层级结果,规划不重复的数据来源
P->>L: 为需要的 L2 数据 reserve_write
P->>B: load 到 L1 staging
B-->>P: 完成 bitmap
P->>L: admission 并保留读锁
S->>M: QUERY_PREFETCH_STATUS
M-->>S: 完成后的可复用前缀(未完成时为 None)
S->>W: 分配物理块并传递加载计划
W->>M: RETRIEVE
M->>W: L1 到 GPU;发出完成事件
W->>W: 等待数据依赖后执行剩余计算
M->>L: 读完成后释放;清理未使用的尾锁要分清三个时刻:lookup 回执(只是登记)、预取完成(数据已在 L1 且被保护)、GPU 可读(拷贝事件完成)。有多个 server 时,学习版本取大家共同可用的前缀,而不是“最快那个 server 的最长结果”。
6. L1 对象的状态变化
一个 L1 对象从无到有、被读、被回收的过程:
查看图源
stateDiagram-v2
[*] --> Absent
Absent --> Staging: reserve_write(key, tag)
Staging --> Resident: finish_write / admission
Staging --> Discarded: 另一 writer 已发布同 key
Staging --> Reclaimable: 写锁过期
Reclaimable --> Absent: 回收
Resident --> Reading: reserve_read
Reading --> Resident: finish_read(仍有副本)
Reading --> Absent: 最后读者结束且对象为 temporary
Resident --> Absent: 无锁时淘汰或删除
Discarded --> Absent: 释放此 staging,其他 resident 不受影响读图时注意三点:
- staging 是某个写者私有的,resident 是同一个 key 对所有人公开的那一份。同一个 key 可以同时有多个写者的 staging,这张简图不是“整个 key 只有一个状态”的互斥状态机。
- “Discarded → Absent”只是被丢弃的那份 staging 被释放,已经发布的 resident 不受影响。
- 写锁过期不等于 GPU 或 DMA 操作自动停止;设备操作结束和内存回收要另外协调。
7. LMCache 与 Mooncake 组合
LMCache 通过 native L2 adapter 内嵌一个 Mooncake Client:控制信息找 Master(分配、副本位置、租约),数据通过 Transfer Engine 直接和远端 segment 传输。
查看图源
flowchart LR
G["推理 GPU"] <-->|"D2H / H2D"| L["LMCache L1 buffer"]
L <--> A["Mooncake native L2 adapter"]
A --> C["嵌入式 Mooncake Client"]
C -.->|"分配 / 副本位置 / 租约"| M["Mooncake Master"]
C --> T["本地 Transfer Engine"]
T <-->|"数据面:TCP / RDMA"| RT["远端 Transfer Engine"]
RT <--> R["已注册的远端 segment"]
T -.-> MS["segment / endpoint metadata"]
RT -.-> MS
M -.-> R两类元数据别混:Master 管“对象在哪、能不能读、何时回收”;Transfer Engine 的元数据管“segment、buffer 和网络端点怎么访问”。这张图画的是常见的主机 L1 路径,并不表示这个 adapter 会自动做 GPU 直连远端 GPU 的 GPUDirect RDMA,控制 RPC 也不传大块数据。完整的保存与恢复步骤见联合链路。
8. PD 分离与跨请求复用
这是两件不同的事:PD 分离让 Prefill 和 Decode 分开扩缩容、互不干扰;跨请求复用让后来的请求少算重复的 Prefill。两者可以组合。
查看图源
flowchart TB
R["Router"] --> P["Prefill engine"]
R --> D["Decode engine"]
P -->|"本次请求 KV 交接:例如 NIXL"| D
P <--> LP["Prefill 的 LMCache server"]
D <--> LD["Decode 的 LMCache server"]
LP <--> SH["可选共享存储 / P2P"]
LD <--> SH学习版本仓库里的 MP PD 示例用 MultiConnector 同时接 NIXL(负责本次请求的 KV 交接)和 LMCache(负责跨请求复用),并且两个引擎各配一个 LMCache server。它不是“打开 Mooncake Store 开关就自动有了 PD 分离”。
9. 性能分析因果链
命中更多,一方面少做了 Prefill,另一方面多了查询、网络和 H2D,还可能带来排队、内存和锁的竞争。最终 TTFT、goodput 和 Decode 的 ITL 是变好还是变差,取决于这两方面谁更大。
查看图源
flowchart LR
H["更多缓存命中"] --> S["少做重复 prefill"]
H --> IO["更多查询 / 网络 / H2D"]
S --> T["TTFT 与 goodput"]
IO --> T
IO --> Q["队列 / 内存 / 锁竞争"]
Q --> T
Q --> D["Decode ITL"]
POL["准入 / 路由 / chunk / 背压"] --> H
POL --> Q所以命中率只说明“有多少复用机会”,不能单独证明性能变好。可以用互动盈亏计算器体会这个取舍。