LMCache 专项:系统设计题与源码阅读
白话导读:三道共享 KV 层系统设计题的完整推演,加上按问题找源码的导航。
- 设计题一给出完整模板:需求、容量、带宽、组件流程、降级和验收。
- 设计题二、三分别考 PD 分离与复用的组合、RAG 片段顺序变化对复用的影响。
- 源码导航按“要回答的问题”组织,而不是按目录。
先独立做一遍设计题一,再对照参考推演。
1. 设计题一:长文档问答的共享 KV 层
1.1 需求与假设
题目:一个 RAG 服务用的是普通 GQA 模型(32 层、8 个 KV head、head 维度 128、BF16)。每次输入 8K token,其中常见的文档前缀约 6K;峰值 20 请求/秒;热门文档前缀约 1,000 个。目标是降低 TTFT,同时不明显拖慢 Decode 的尾延迟。这些数字是练习用的,不是 LMCache 或 Mooncake 的公开测试结果。
先向面试官问清楚:
- 这 6K 是不是在同一个 system prompt、同一个模型版本、同一个位置下的完全相同的前缀?热门文档的分词结果真的一样吗?
- 输出多长?突发流量多大?SLO 看 P95 还是 P99?
- 允许跨租户共享吗?网络带宽多少?可用性要求多少?
如果 system prompt 各不相同,普通前缀缓存能共享的部分会大幅减少,后面的预算都要重算。
1.2 容量预算
每 token 128 KiB:
- 每条 6K 前缀 0.75 GiB;1,000 条共 750 GiB。
- 存两份副本:1,500 GiB。
- 如果实际存放的数据只能占物理内存池的 70%,至少需要约 2,143 GiB。剩下的 30% 要明确留给 staging、元数据、碎片、系统和突发,不是白送的空间。
- chunk = 256 时,每条前缀 24 个 chunk,共 24,000 个逻辑 chunk。TP = 4、每个 rank 存一个对象、只有一个组时,共 96,000 个对象;两副本对应 192,000 条副本记录。
一个对象可以有多个副本,对象数和副本数不要混。如果有多个组或换了 chunk 策略,对象数还会变。在这个均匀切分的例子里,每个 rank 的每个 chunk 是 8 MiB。可以用容量规划工具验证。
1.3 带宽与写入预算
如果 20 个请求每秒全部从远端读完整的 6K 前缀:
这只是数据本身的总读取量,不含协议开销、本机 H2D、控制面和流量波动。一张 200G 网卡的线速是 25 GB/s,约 23.28 GiB/s;按 70% 有效利用率算,约 16.30 GiB/s,已经非常接近 15 GiB/s 的持续读负载。这点余量撑不住突发、故障和复制。
写入:如果 10% 的请求产生新的完整 8K 前缀(每条 1 GiB),两副本的写流量是 20 × 10% × 1 × 2 = 4 GiB/s。读和写是不是挤同一个方向、同一张网卡、同一条 PCIe、同一个存储池,要按实际拓扑拆开看;不能把全双工两个方向简单相加就说超了线速,但内存带宽、CPU 和交换网络仍可能是共同瓶颈。
元数据:每秒约要查 20 × 24 × 4 = 1,920 个对象。批处理能减少 RPC 次数,但目录的工作量还在;还要算上保存、续租、淘汰和重试的流量。
在途内存:如果每份数据平均在 staging 停留 100 ms,15 GiB/s 意味着平均约 1.5 GiB 在途;还要给 P99 和突发留余量。
1.4 组件与请求流程
查看图源
flowchart TB
API["Gateway / 鉴权 / 限流"] --> ROUTE["缓存感知 Router"]
ROUTE --> ENGINE["兼容的 vLLM 实例池"]
ENGINE <--> LOCAL["每节点 LMCache MP / host L1"]
LOCAL <--> POOL["Mooncake Store / 远端内存池"]
POOL -.-> MASTER["Master / HA 控制面"]
INDEX["可选缓存目录 / 事件索引"] -.-> ROUTE
LOCAL -.-> INDEX
OBS["Metrics / Traces / SLO"] -.-> ENGINE
OBS -.-> LOCAL
OBS -.-> POOL- 网关做认证,确定这个请求能用哪个租户/模型的命名空间。
- 路由同时看队列长度、前缀在哪、加载要多少代价,选出候选实例。
- 引擎先看本地 APC 命中多少;LMCache 再查还能额外复用多少。
- 在保护和内存预算的约束下,从 L1/L2 加载,并检查所有需要的 rank 和组是否齐全。
- 把命中的部分放进 GPU,计算剩下的 Prefill 和 Decode。
- 值得复用的数据异步保存;事件索引用来辅助路由,但真正读取时仍要验证对象状态。
1.5 降级与一致性
- 对象没命中,或者读取失败但能安全恢复:走重算。
- 布局不兼容、数据损坏、完成状态未知:绝不能当成命中。
- Store 节点故障:先试有效副本,不行就降低对缓存的依赖,并限制重算的速率。
- Master 高可用、存储副本、引擎重启要分别测试,它们不能互相替代。
- 模型版本或 LoRA 变了:用新的命名空间,旧对象按计划过期清理。
- 同样的内容跨租户共享,必须先明确授权和侧信道边界;默认按租户隔离更容易说清楚。
- 元数据过时可以导致没命中,但绝不能导致加载错数据。
1.6 验收标准
用同一份请求 trace,比较“只有 APC”“APC + L1”“APC + 远端缓存”三种配置的:TTFT/ITL 的 P50/P95/P99、goodput、GPU 利用率和抢占、实际传输量、命中 token、主机内存与 staging 占用、失败率和输出正确性。再测一次节点退出时的尾延迟和恢复时间——如果平均收益很高,但一出故障就把 GPU 压垮,方案还没到生产标准。
2. 设计题二:PD 分离 + 跨请求复用
先别急着选传输库,先分清两种数据需求:
- 本次请求:Prefill 算出的 KV 要交给 Decode。交接失败时,可能需要明确报错,而不是悄悄重算。
- 后续请求:已经算好的前缀可以存进共享层,让任何兼容的 Prefill worker 复用。
学习版本的 LMCache MP 做法是在 MultiConnector 里同时用 NixlConnector 和 LMCacheMPConnector,两边的引擎各配一个 LMCache server。版本前提和 real-blocks 修复要求见 PD 分离指南。别的系统也可以用 Mooncake Transfer Engine 做交接,但不能照搬 NIXL 的配置。
容量要分开算:请求级交接的速率、跨请求读写的速率、Decode 端在途序列的 KV、TP/PP 布局是否兼容、交接期间的双份 buffer、副本保存多久。Prefill 和 Decode 的并行度不同时,必须有验证过的布局转换(重新切分)支持——“字节传成功了”不代表布局会自动转换。
面试官会追问:首 token 由哪个阶段产生?路由怎样转发流式输出?Prefill 什么时候可以释放原来的 GPU KV?Decode 超时后,谁来清理哪些对象?跨请求缓存写失败会不会影响当前请求?这些答案要来自具体协议,而不是画几个方框。
3. 设计题三:RAG 片段顺序变化
同一个文档,放在不同的 system prompt 后面,或者排在其他文档的前后,普通前缀命中都会减少。有三类做法:
- 统一模板和文档顺序,提高真正相同的前缀比例;代价是可能要改业务的提示词设计。
- 只缓存稳定的共同前缀,其余部分重算;简单,也好验证。
- 用 CacheBlend 这类非前缀复用加选择性重算;需要在模型和任务上验证质量,还需要额外的运行时支持。
比较方案时要同时报告质量和时延,不能只统计“文档块在存储里有没有”。也不能把旧的 K 做一下位置旋转,就说所有层都和新上下文一致了。
4. 源码导航:按问题找文件
下面都是 LMCache 仓库里的相对路径。读法是先看设计文档,再看实现,最后看测试。这些只是入口,不要求一个月内把整个目录精读完。
| 想弄清楚 | 设计文档 | 实现与测试 |
|---|---|---|
| vLLM 查命中怎么推进? | docs/design/integration/vllm/mp_lookup_state.md |
lmcache/integration/vllm/mp_lookup_state.py;
tests/v1/test_mp_lookup_state.py |
| key 与多 group 如何表示? | docs/design/v1/distributed/storage_manager.md |
lmcache/v1/distributed/api.py;
lmcache/v1/multiprocess/token_hasher.py |
| 谁保护正在写的数据? | docs/design/v1/distributed/l1_manager.md |
lmcache/v1/distributed/l1_manager.py;
tests/v1/distributed/test_l1_manager.py |
| 跨层预取如何规划与限流? | docs/design/v1/distributed/storage_controllers/prefetch_controller.md |
lmcache/v1/distributed/storage_controllers/prefetch_controller.py |
| L2 adapter 要遵守什么? | docs/design/v1/distributed/l2_adapters/overall.md |
lmcache/v1/distributed/l2_adapters/base.py |
| 如何接 Mooncake? | Mooncake adapter 指南 | lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py;
csrc/storage_backends/mooncake/ |
| GPU KV layout 从哪里识别? | docs/design/v1/gpu_connector/layout-invariant.md |
lmcache/v1/gpu_connector/utils.py;
lmcache/v1/gpu_connector/kv_format/ |
| copy 为什么有不同路径? | docs/design/v1/multiprocess/lmcache_driven_transfer_copy_paths.md |
lmcache/v1/multiprocess/object_group_transfer.py |
| 抢占恢复如何验证? | docs/design/integration/vllm/mp_preemption_correctness.md |
lmcache/integration/vllm/vllm_multi_process_adapter.py |
| SGLang 集成哪里不同? | docs/design/integration/sglang/lmcache-unified-radix-cache.md |
lmcache/integration/sglang/ |
| 旧版 LMCacheEngine 在哪里? | 进程内模式资料 | lmcache/inprocess/v1/cache_engine.py;
lmcache/inprocess/v1/token_database.py |
LMCache 上游的测试可能依赖 GPU、native 扩展和测试夹具;做了离线练习不等于跑过这些测试。测试环境和命令以上游的 AGENTS.md 和贡献指南为准。
Mooncake 对照入口(Mooncake 仓库相对路径):
docs/source/design/architecture.md:组件边界,控制和数据怎样分开。docs/source/design/store/mooncake-store.md:Put/Get、对象和租约。docs/source/design/transfer-engine/index.md:segment 和传输接口。docs/source/design/hicache-design.md:SGLang 分层缓存、预取和布局。docs/source/api-reference/python/mooncake-store.md:Python 接口真实的返回值和前提。mooncake-store/、mooncake-transfer-engine/:深入 C++ 实现。
5. 进一步阅读
5.1 先跑起来
- Quickstart、兼容性:先确定版本和运行方式。
- MP 总览、MP 配置:部署边界和真实的默认值。
- Observability:把架构里的各个阶段变成能测量的事件。
- Mooncake adapter 指南:把两套系统组合起来学,而不是孤立地学 API。
5.2 带着问题读论文
论文里的实验结果只适用于它报告的配置,替代不了自己复现;也不要把论文里的设计都说成现在的默认实现。每篇带一个问题去读:
- Efficient Memory Management for Large Language Model Serving with PagedAttention:物理页怎样共享?什么时候写时复制?没有外部存储时容量瓶颈在哪?
- Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving:为什么把 KV 放在资源调度的中心?存储、传输、调度各解决什么?
- SGLang: Efficient Execution of Structured Language Model Programs:radix 共享怎样和程序结构、请求复用结合?
- CacheGen: KV Cache Compression and Streaming for Fast Large Language Model Serving:压缩和流式传输分别省掉了什么?什么时候编码反而更贵?
- CacheBlend: Fast Large Language Model Serving with Cached Knowledge Fusion:非前缀复用为什么要选择性重算?质量怎么评价?
- FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness:为什么优化 attention kernel 的 I/O,并不能解决跨请求的 KV 存储?
5.3 选一个方向深挖
- KV 存储工程师:adapter、异步 I/O、slab/分配器、配额、租约、高可用与故障恢复。
- 推理运行时工程师:Connector、调度器、APC、TP/组、设备事件和抢占。
- 传输工程师:RDMA 注册、QP/CQ、NUMA、批量、拓扑和 buffer 生命周期。
- 性能工程师:可复现的压测、trace 关键路径、goodput、成本模型和质量约束。
一个月比较现实的目标是:吃透一条完整链路,再深挖一个点,而不是背完所有框架的所有参数。面试时用自己真正做过的实验和证据回答,分清哪些是实测、哪些是读来的结论、哪些是设计假设。