状态:草稿(2026-10-13),部分为实测数据。 实测数据引用自同网站“托斯 ML/AI Infra 准备”第 8 篇,在单张 8GB 级 GPU、Qwen2.5-1.5B-Instruct、vLLM 0.29.0、LMCache 0.5.5(进程内模式)环境下测得。论文与官方文档已于 2026-10-13 直接打开并核实。LMCache 当前推荐的方式(MP 模式)和大模型的数据尚未实测(需要重新下载镜像和模型,正在等待审批)。 2026-10-27 补充。 获得批准后,我们使用 vLLM 0.32.0 官方镜像(已内置 LMCache 0.5.6)和 Qwen3-0.6B 对 MP 模式进行了测试。结果在第 9 节,前文 2026-10-13 的数值保持不变。大模型和多台服务器的场景仍未实测。 编写规则:以实测数据为准,未能测出的标为“未实测”,无法确认的标为“未确认”。
LMCache:将 KV 缓存移出 GPU
目录
- 1. GPU 内存中缓存的两大局限
- 2. LMCache 的架构
- 3. 为什么 TTFT 和吞吐量会有所改善
- 4. 实测数据:从 576ms 到 48.6ms
- 5. 收支平衡点:仅在加载比计算更划算时
- 6. 复用非前沿片段:CacheBlend、CacheGen
- 7. 预填充与解码分离场景下的 KV 传输
- 8. 什么时候没有收益
- 9. 2026-10-27 MP 模式重新测试
- 参考
1. GPU 内存中缓存的两大局限
第 2 篇中的前缀缓存虽然强大,但它仅在 GPU 内存中工作。因此存在两个遗憾之处。
- 容量不足时会被逐出。 当被逐出的前缀再次出现时,系统会从头开始计算。
- 实例之间相互隔离。 如果在多张 GPU 上运行相同的模型,旁边的实例无法使用刚刚计算好的 KV。
我们曾测量过第一点带来的痛点有多严重。让系统按顺序读取一组超出 GPU KV 容量(21,520 Token)的文档(10 个文档,每个约 5,000 Token,总计约 5 万 Token),然后 按相同的顺序再次 读取。如果仅使用 vLLM,第二次读取的首 Token 延迟为 576ms,与首次(575ms)相同,vLLM 的强力前缀缓存命中率为 0。因为在采用最近最少使用(LRU)策略且循环访问的缓存中,只要空间稍微不够,接下来要用的文档总是刚被逐出。
2. LMCache 的架构
LMCache 是一个挂载在推理引擎旁边的 KV 缓存层。根据技术报告(arXiv 2510.09665,2025-12 修订版)和官方文档(2026-10-13 查阅),其架构如下。
- 与引擎的交汇点是 KV 连接器(KV connector)。 vLLM 提供了一个标准接口(KV connector),用于将 KV 导出到引擎外部并导入,而 LMCache 正是其中一种实现。调度器侧会询问“此请求前端有多少 Token 是在外部找到的”,而模型执行侧则会启动并等待各层的加载与保存。
- 基本单位是块(Chunk)。 默认将 256 个 Token 作为一个整体处理。之所以比 vLLM 的块(16 个 Token)更大,是因为当存储和网络频繁传输大量小碎片时速度会变慢。报告引用了其他测量数据,作为必须使用较大的单次传输单位来榨干带宽的依据。
- 存储层级为:GPU → CPU 内存 → 本地磁盘 → 远程存储。报告和文档中提到的后端包括 Redis/Valkey、S3、Mooncake、NIXL、InfiniStore、共享文件系统以及 GPUDirect Storage 等。
- 避免阻塞的机制。 保存操作在后台异步进行。加载操作则与各层的计算重叠(在计算当前层的同时获取下一层的 KV)。当请求在队列中等待时,系统甚至会提前将慢速层中的 KV 提升到快速层。
- **目前推荐的部署方式是每个节点运行一个独立的服务器(MP 模式)**。多个 vLLM 实例共同使用一个
lmcache server。最初的“在引擎进程内运行的方式”在文档中被标记为 Legacy(旧版)·deprecated(已弃用)。最新正式版为 v0.5.5(2026-09-12),目前已有 v0.5.6 预发布版。
由于将相同的前端用作键(Key),因此 实例 1 保存的 KV 可以被实例 2 查找并使用 。如果在同一节点上,则使用 MP 服务器的 CPU 内存;如果在不同节点上,则采用通过共享存储或 RDMA 读取邻近节点内存的 P2P 方式。
3. 为什么 TTFT 和吞吐量会有所改善
核心一句话: 将重复的预填充替换为从存储中加载。
- TTFT。 首 Token 延迟绝大部分消耗在预填充阶段(第 1 篇)。如果加载前 4,864 个 Token,就只需要计算剩下的约 150 个 Token。
- 吞吐量。 预填充是消耗 GPU 计算资源最多的阶段。如果跳过这一阶段,相同的 GPU 在相同时间内就能接收更多请求。因此,“在遵守相同 TTFT 的前提下能够支撑的请求速率”随之提升。
- Token 间延迟(ITL)。 正如第 2 篇中所见,长预填充会暂停同一 GPU 的解码。当预填充缩短时,插队的现象也会减少,从而使 Token 间延迟更加均匀。
报告中的数据(8×H100 服务器,Llama-3.1-8B·70B、Qwen2.5-72B 等六个模型,CPU 卸载上限 500GB):
| 实验 | 条件 | 结果(相比最强基线) |
|---|---|---|
| 多轮文档问答,CPU 卸载(8.2 节) | 查询约 1 万 Token(约 12 页文档 + 问题),最大输出 100 Token,基线为 vLLM 0.10.2(GPU 前缀缓存)、vLLM CPU 卸载及两个商业产品 | 在 1 请求/秒下,TTFT 缩短 1.9~8.1 倍,ITL 减少 7%~92%, 在相同 TTFT 下吞吐量提升 2.3~14 倍 |
| 实际业务记录回放(8.3 节) | 两家公司的输入输出分布,CPU 500GB,基线为 vLLM | TTFT 缩短 3.7~6.8 倍,ITL 减少 19%~58% |
| 中央存储服务器(8.4 节) | 远程服务器与 15Gbps 链路 | 在相同 TTFT 下吞吐量提升 1.3~3 倍 |
摘要中虽然写着吞吐量“最高 15 倍”,但在正文各节中确认的最大值为 8.2 节中的 14 倍。远程存储侧的收益(1.3~3 倍)小于 CPU 内存,是因为网络链路较慢,这与下一节的收支平衡是同一个道理。
4. 实测数据:从 576ms 到 48.6ms
我们在第 1 篇的实验中开启了 LMCache(CPU 内存上限 1.5GB,块大小 256)。环境为 8GB 笔记本 GPU、Qwen2.5-1.5B-Instruct(fp16)、vLLM 0.29.0、LMCache 0.5.5,请求单条发送。
| 设置 · 轮次 | 首 Token p50 | p95 | 整轮耗时 |
|---|---|---|---|
| 仅 vLLM · 首次 | 575.5ms | 637.3ms | 6.2 秒 |
| 仅 vLLM · 再次 | 576.2ms | 577.9ms | 6.2 秒 |
| LMCache · 首次 | 597.8ms | 676.2ms | 6.5 秒 |
| LMCache · 再次 | 48.6ms | 59.6ms | 0.9 秒 |
“再次”指按相同顺序重新读取的第二轮。
- 重新读取的首 Token 延迟缩短了 约 12 倍 。LMCache 日志显示,在约 5,000 个 Token 中,有 4,864 个 Token(256 × 19)从 CPU 加载,而引擎重新计算的 Token 数量为 0。由于采用块大小限制,最后不足的碎片无法加载,只能重新计算。
- 加载的 KV 为每个文档 0.13GB。根据第 1 篇的公式,Qwen2.5-1.5B(28 层,KV 头数 2,头维度 128,2 字节)每个 Token 为 28,672 字节,4,864 个 Token 即为 0.1299 GiB,与日志吻合。
- 首次读取时慢了约 4%(575.5 → 597.8ms)。这是在读取的同时将 KV 写入 CPU 的开销。
- 两种设置下的 GPU 内存占用相同。LMCache 并未消耗额外的 GPU 空间。
改变长度后发现,随着上下文长度增加,收益也随之增大(CPU 2GB,重新请求在 GPU 中被挤出的文档)。
| 输入 Token | 重新计算 | 已加载 | 倍数 |
|---|---|---|---|
| 554 | 66ms | 21ms | 3.1 倍 |
| 1,077 | 119ms | 24ms | 5.0 倍 |
| 2,144 | 231ms | 29ms | 8.0 倍 |
| 4,273 | 481ms | 45ms | 10.7 倍 |
| 6,414 | 768ms | 41ms | 约 19 倍 |
重新计算的时间几乎与长度成正比,但加载时间增长缓慢,仅比在 GPU 中直接命中(20~32ms)多出几毫秒。
容量呈断崖式表现。 我们仅修改 CPU 上限对同样的重新读取进行了测试(工作集的 KV 计算量约为 1.49GB)。
| CPU 上限 | 重新读取首 Token 延迟 | 外部命中 |
|---|---|---|
| 无(仅 vLLM) | 600ms | — |
| 0.5GB | 620ms | 0% |
| 1.0GB | 620ms | 0% |
| 1.5GB | 39ms | 98% |
| 2.0GB | 39ms | 98% |
只要比工作集稍小一点,收益就为 0,只剩下存储开销。这就是与第 1 篇相同的 LRU 循环访问陷阱。在符合实际的某些文档被频繁读取的分布(齐普夫分布,60 个文档)下,较小的缓存也能发挥作用,平均首 Token 延迟从仅 vLLM 的 278ms 分别降至 CPU 1GB 时的 174ms 和 2GB 时的 63ms。
本实验的局限性:并发数为 1,使用单个 1.5B 模型,采用随机单词构成的合成文档,且为单次测量值;测试时使用的是进程内模式,而非当前文档推荐的 MP 模式。
5. 盈亏平衡:仅当读取成本低于重新计算时
若要让读取获利, 传输 KV 的耗时必须短于重新计算该 KV 的耗时 。针对 n 个 Token 的计算公式如下:
- 重新计算:n ×(每个 Token 的 Prefill 耗时)
- 读取:n ×(每个 Token 的 KV 字节数)÷ 带宽 + 固定延迟
由于两边都有 n,减去固定延迟后得出: 盈亏平衡带宽 = 每个 Token 的 KV 字节数 ÷ 每个 Token 的 Prefill 耗时代入上述实际测量值(6,414 个 Token 耗时 768ms,每个 Token 约 0.12ms),得:28,672 字节 ÷ 0.12ms ≈ 0.24GB/s (计算结果)。
- CPU 内存(该 GPU 的 PCIe 实测带宽为 13.4GB/s)比平衡点快了 50 倍以上,因此收益非常确定。
- 1GbE 级别的链路(其他实验中测得的有效带宽为 104MB/s)慢于平衡点。传输 6,414 个 Token 的 KV(约 184MB)本身就需要约 1.8 秒,比重新计算(0.77 秒)还要慢(计算结果)。 从慢速网络存储中读取属于得不偿失。
- 报告得出的结论也是如此。在 B200 GPU 上,32Gbps 链路只有在输入超过 256K Token 时,读取速度才快于 Prefill;而 64Gbps 和 128Gbps 在测试的所有长度下都更快(第 8.7 节。本实验的模型名称在正文和图 15 的说明中均未提及 —— 于 2026-10-15 重新打开 v2 确认。第 8.1 节的模型列表基于 H100 实验)。因此,作者建议根据具体情况选择是从存储读取还是重新计算。
模型和 GPU 决定了平衡点。 Prefill 的计算量大致为每个 Token 「2 × 参数量」次 FLOP,KV 字节数则遵循第 1 篇中的公式。若用公开配置计算二者的比值(即节省 1 字节 KV 所省下的计算量,此处忽略 Attention 计算进行估算),结果如下:
| 模型 | 每个 Token 的 Prefill 计算量 | 每个 Token 的 KV | 每 1 字节 KV 对应的计算量 |
|---|---|---|---|
| Qwen2.5-1.5B | 约 3.1 GFLOP | 28KiB | 约 11 万 FLOP |
| Llama 3.1 8B | 约 16 GFLOP | 128KiB | 约 12 万 FLOP |
| Qwen2.5-7B | 约 15 GFLOP | 56KiB | 约 27 万 FLOP |
| Llama 3.1 70B | 约 140 GFLOP | 320KiB | 约 43 万 FLOP |
每 1 字节 KV 对应的计算量越大,读取就越具优势。通过 GQA 减少了 KV 的大模型越有「读取的价值」。反之, GPU 越快 ,重新计算的速度就越快,从而拉高了平衡带宽。即使是相同的存储,在慢速 GPU 上可能是划算的,但在高速 GPU 上则可能会亏本。
命中率也存在平衡点。 命中时可节省 527.6ms(576.2 − 48.6),未命中时则需付出 22.3ms 的存储开销(597.8 − 575.5)。令期望收益为零的命中概率 p 大约为 22.3 ÷ (22.3 + 527.6) ≈ 4%。这是本实验测得的数值,并非通用常数。代入其他公开资料的数据,该数值最高可上升至 20% 左右。
6. 重写非头部片段:CacheBlend、CacheGen
前缀缓存 仅在从头开始完全一致时 才会生效。如果像 RAG 那样每次以不同顺序拼接多个文档片段,除了第一个片段外,其余均无法命中。如果直接将各片段单独生成的 KV 拼接起来,会缺少片段之间的 Attention(后一片段对前一片段的关注),从而导致回答错误。
CacheBlend(Yao 等人,EuroSys 2025)在拼接后 挑选出 KV 偏移最大的那些 Token 进行重新计算。在各层中,此类 Token 约占 10%~15%,重新计算 10%~20% 就足够了。它将重新计算与下一层的读取重叠以掩盖延迟。结果显示,与完全重新计算相比,TTFT 减少了 2.2~3.3 倍,吞吐量提升了 2.8~5 倍,质量(F1·Rouge-L)下降幅度在 0.01~0.03 以下(Mistral-7B、Yi-34B、Llama-70B,双 A40,NVMe 实测 4.8GB/s)。LMCache 的 MP 服务器通过 --engine-type blend 来启用此方案。
CacheGen(Liu 等人,SIGCOMM 2024)从根本上降低了传输 KV 的开销。它利用相邻 Token 的 KV 相似这一特点,仅对差值进行编码,并对靠前的层采用密集量化、靠后的层采用稀疏量化。相比常规量化,KV 体积缩小了 3.5~4.3 倍;在 3Gbps 链路下,TTFT 比量化基准低 3.2~3.7 倍,准确率下降幅度在 2% 以内(Mistral-7B、Llama-34B、70B,4 张 A40)。当带宽下降时,它会降低每个区块的压缩级别,或者 切换为发送文本进行重新计算 。这相当于对第 5 节的盈亏平衡点进行实时逐块计算。
7. Prefill 与 Decode 分离架构中的 KV 传输
传输 KV 的能力也可用于其他场景。在第 4 篇中将看到的 分离式服务需要将 Prefill GPU 生成的 KV 发送给 Decode GPU。LMCache 文档中的分离配置将 Prefill 到 Decode 的传输交给 NIXL(vLLM 的 NixlConnector),并结合 vLLM 的 MultiConnector 将 LMCache 连接器打包在一起,以进一步提升请求之间的复用率。报告显示,在 8K 输入、200 输出以及 NVLink 条件下,相比 vLLM 自带的分离配置,其平均 TTFT 降低了 1.53~1.84 倍,平均 ITL 降低了 1.12~1.66 倍(第 8.5 节,本节未确认模型名称)。
8. 什么时候没有收益
| 条件 | 原因 | 依据 |
|---|---|---|
| 提示词很短 | 重新计算已经足够便宜,可节省的空间很小。固定延迟占比更大 | 实测:554 个 Token 时为 3.1 倍,6,414 个 Token 时为 19 倍 |
| 相同的前缀不会再次出现 | 如果没有命中,就只会平添存储成本 | 实测:首次读取 +4% |
| 缓存容量小于工作集(循环访问) | LRU 总是会删掉接下来要用的内容 | 实测:在 0.5·1.0GB 下命中率为 0% |
| 存储或网络很慢 | 传输耗时超过重新计算耗时 | 计算:1GbE 低于该模型的平衡带宽 0.24GB/s。报告第 8.7 节 |
| 小模型、快 GPU | 重新计算速度极快,推高了平衡带宽 | 计算(第 5 节表格) |
| GPU 前缀缓存已经足够 | 如果工作集能完全塞进 GPU,仅靠 vLLM 即可命中 | 实测:在区块大小测试中,数值与仅使用 vLLM 时相同 |
| 频繁读取的文档残留在 GPU 中(齐普夫分布) | 只会徒增查询和存储开销 | 实测(2026-10-27,第 9-4 节):平均首个 Token 从 17.9 增至约 20ms |
| 文档比区块(256 个 Token)更短 | 不足一个区块的尾部无法被读取 | 实测(2026-10-27,第 9-3 节):510 个 Token 时为 1.1 倍 |
9. 2026-10-27 MP 模式重新测量
第 4 节使用的是当前文档标记为 “Legacy” 的进程内模式。我们在同类 8GB 级 GPU 上重新测试了官方推荐的 MP 模式(独立进程的 lmcache server 和 LMCacheMPConnector)。由于模型和版本不同,请勿将本节数字与第 4 节直接作比较,而应在同一天、同一条件下对比 “vLLM 独占 / 进程内 / MP” 这三行数据。
9-1. 环境与方法
- 单张 8GB 级笔记本 GPU(Ada 架构),vLLM 0.32.0 官方镜像,LMCache 0.5.6(2026-10-14 正式版,已包含在该镜像中,无需单独安装),容器内存限制 4.5GiB。
- 模型:Qwen3-0.6B(bf16)。共 28 层,8 个 KV 头,头维度 128,因此每个 Token 的 KV 大小为 114,688 字节,是第 4 节中 Qwen2.5-1.5B(28,672 字节)的四倍。相反,每个 Token 的 Prefill 计算量约为 1.2 GFLOP,不到前者的的一半。若换算为第 5 节表格中的“每 1 字节 KV 对应的计算量”,约为 1 万 FLOP,仅为 1.5B(约 11 万)的十分之一。这是对读取最不利的模型。
- MP 模式的启动方式类似于 NVIDIA Dynamo 1.6.0 示例(
agg_lmcache_mp.sh)。
lmcache server --l1-size-gb 0.75 --eviction-policy LRU --port 5555 --http-port 8080
vllm serve Qwen/Qwen3-0.6B --disable-hybrid-kv-cache-manager \
--kv-transfer-config '{"kv_connector":"LMCacheMPConnector","kv_role":"kv_both","kv_connector_extra_config":{"lmcache.mp.port":5555}}'
- 进程内模式通过
LMCacheConnectorV1以及环境变量LMCACHE_LOCAL_CPU=True,LMCACHE_MAX_LOCAL_CPU_SIZE=0.75,LMCACHE_CHUNK_SIZE=256开启。 - 内存预算: LMCache 服务器在甚至还没使用 L1 之前就占用了约 0.93GiB 的匿名内存,vLLM 服务器则占用了约 2.7GiB。为了将内存控制在 4.5GiB 的限制内,L1 被设为 0.75GB 以下。在第一次尝试中,刚关闭进程内模式服务器就启动 MP 服务器导致容器因内存不足而崩溃;在修改为确认前一个服务器完全退出后再启动后,重新进行了测试。
- 请求:在作为随机种子生成的单词文档后面附加简短提问,通过
max_tokens 2进行提问,并使用流式传输测量首个 Token 的耗时。GPU 的 KV 存储空间通过--kv-cache-memory-bytes故意限制得较小。同一条件运行三到四次并取中位数,第一次运行(包含存储开销)单独记录。
9-2. 基础效果:重新读取无法完全塞入 GPU 的文档合集
将 GPU KV 限制为 2,336 个 Token,按相同顺序连续四次读取 4 个大小约为 1,020 个 Token 的文档(总计约 4,080 个 Token)。
| 配置 | 首次运行(含存储开销) | 重新读取(第 2~4 次运行的中位数) | 每个文档读取的 Token 数 |
|---|---|---|---|
| vLLM 独占 | 55.7ms | 46.5ms | 0 |
| 进程内(LMCacheConnectorV1,CPU 0.75GB) | 71.3ms(+28%) | 24.2ms(1.9 倍) | 768 |
| MP(LMCacheMPConnector,L1 0.75GB) | 60.7ms(+9%) | 32.9ms(1.4 倍) | 768 |
- vLLM 独占陷入了与第 4 节相同的陷阱。由于是循环访问,GPU 命中率为 0。
- 可以观察到以区块为单位的损失。 在 1,020 个 Token 中,只有三个 256 Token 的区块(768 个 Token)被读取,其余约 250 个 Token 则被重新计算。文档越短,这部分占比就越大。
- MP 模式的重新读取速度比进程内模式慢约 9ms,而首次运行时的存储开销较小。 这可能是因为 KV 需要在引擎之外的其他进程之间穿梭。在 MP 模式下,即使是在 GPU 中直接命中的请求,也比 vLLM 独占慢 3~4ms(1,000 个 Token 时为 15.7ms 对比 12.0ms)。这是因为每次请求都附带了向服务器查询的往返开销。
9-3. 按长度对比:重新计算与读取
将 GPU KV 设为 0.75GiB(7,020 个 Token),在读取并存储一次文档后,利用 vLLM 的 /reset_prefix_cache(开发模式)清空 GPU 缓存并再次读取。LMCache 侧则通过针对每个文档的管理 API 清空 L1。针对每个文档长度,去掉四次测试中的第一次,取其余的中位数。
| 输入 Token 数 | vLLM 独占重新计算 | MP 读取 | 读取的 Token 数 | 倍率 | GPU 命中(参考) |
|---|---|---|---|---|---|
| 约 510 | 32.2ms | 29.8ms | 256 | 1.1 倍 | 11.2ms |
| 约 1,005 | 45.9ms | 37.0ms | 768 | 1.2 倍 | 12.0ms |
| 约 2,010 | 98.3ms | 46.3ms | 1,792 | 2.1 倍 | 14.5ms |
| 约 4,020 | 226.2ms | 68.2ms | 3,840 | 3.3倍 | 19.2ms |
| 约 6,030 | 390.9ms | 91.3ms | 5,888 | 4.3倍 | 23.8ms |
放大倍数比第4节中 1.5B 模型的实测结果(6,414个 Token 约为 19 倍)要小。这是因为该模型重新计算的速度很快而 KV 很大,在相同长度下节省的时间较少,但需要迁移的字节较多。不过,长度越长放大倍数越大的趋势是一样的。
从第一次尝试中吸取的教训。 在没有清空 L1 的情况下运行按长度测试时,2,000个 Token 以上的大部分都没有命中。LMCache 日志中累积了「Failed to batched allocate … no enough memory」警告,并且指标 lmcache_mp_l1_allocation_failure_chunks_total{during="l1_store"} 有所增加。由于驱逐机制是在使用率超过阈值(默认 0.8)时以异步方式每次清空 20%,因此当大文档同时涌入时,它没有等待腾出空间,而是 丢弃了存储。 由于不会产生错误、只会导致未命中,因此必须为 L1 预留比单次涌入的最大文档更加充足的空间,并密切监控这一指标。
9-4. 容量:非断崖式下跌而是部分命中,以及齐普夫分布的负面效果
循环访问读取了与 9-2 相同的 4 个文档(工作集约 0.46GB),齐普夫分布读取了约 480 个 Token 的 20 个文档,共读取 120 次(指数 1.1)。GPU KV 为 2,336个 Token。
| 配置 | 循环:重新读取首个 Token | 循环:外部命中 Token | 齐普夫:平均首个 Token |
|---|---|---|---|
| vLLM 独占 | 46.5ms | 0 | 17.9ms |
| MP, L1 0.25GB | 41.2ms | 1,791 / 4,034(44%) | 20.3ms |
| MP, L1 0.5GB | 32.6ms | 3,328 | 20.0ms |
| MP, L1 0.75GB | 33.0ms | 3,328 | 19.9ms |
- 这一次并没有出现断崖式下跌。 即使在小于工作集的 0.25GB 下也加载了 44%。而第4节的进程内模式下,只要比工作集小一点点,命中率就会归零。由于 MP 服务器的驱逐采用的是在阈值处仅清空一部分的方式,因此即使在循环访问中也有一部分得以保留。
- 在齐普夫分布下,MP 模式比单独运行 vLLM 更慢(17.9 → 约 20ms)。 经常读取的文档留在了 GPU 的前缀缓存中,因此三种配置下的 GPU 命中率相同(28,208个 Token),而 LMCache 加载的文档大多为 480 个 Token,因此只获取了一个块(256个 Token)。每次请求附带的查询和存储开销比节省下来的开销更大。 对于 GPU 前缀缓存已经能够很好匹配的负载,最好不要叠加 LMCache。
9-5. MP 模式独有的优势
- 即使重新启动 vLLM,缓存依然保留。 在读取了 4 个文档后,仅关闭并重新启动了 vLLM(服务器保持不变,重启约 30 秒)。尽管 GPU 缓存被清空,但所有 4 个文档都是从服务器加载了 768 个 Token,首个 Token 的中位数为 37.5ms,而在同一个新的 vLLM 中首次看到的文档为 56.7ms。进程内模式由于内存在引擎进程关闭时会一起消失,因此无法做到这一点。它能够在部署、重启、休眠模式之后,以预热的状态接收首个请求。
- 两个 vLLM 共享一个服务器的情况未进行实测。 由于一个 vLLM 大约占用 2.7GiB,服务器大约占用 0.93GiB,因此无法在设定为节点剩余内存一半的容器限制内同时运行两个 vLLM。9-5 第一项的重启测试从侧面展示了「其他引擎进程读取前一个进程所存储的 KV」这一相同的原理。
9-6. L2 本地磁盘层与收支平衡点
将 L1 设置得较小(0.25GB),并挂载了文件系统 L2(--l2-adapter '{"type":"fs","base_path":"…","use_odirect":true}',绕过页面缓存的 O_DIRECT,容器的临时磁盘)。
| 项目 | 值 |
|---|---|
| 循环重新读取首个 Token(L1 0.25GB + L2) | 41.6ms(无 L2 时为 41.9ms) |
| 将从 L1 驱逐的 1,000个 Token 文档重新读入 L2 | 54.6ms, 55.4ms(加载了 768个 Token) |
| 由 vLLM 独占重新计算相同文档 | 45.9ms |
L2 读取速度(LMCache 指标平均值) / dd O_DIRECT 读取 |
每秒 3.8GB / 每秒 4.8GB |
| L1→GPU 加载速度(LMCache 指标,20次平均) | 每秒 11.8GB |
| GPU→L1 存储速度(同上指标,32次平均) | 每秒 12.6GB |
- 从磁盘加载比重新计算更慢(55ms 对比 46ms)。 尽管指标上显示存在 L2 命中(
lookup_hit_l2_tokens_total1,024),但经过了磁盘 → L1 → GPU 两个阶段,并且每次请求都伴随着等待预取固定的延迟。 - 用第5节的公式来匹配,结果如下。 该模型的每个 Token 预填充时间在 1,000个 Token 时约为 0.046ms,在 6,000个 Token 时约为 0.065ms。收支平衡带宽通过将 114,688 字节除以该时间得出,在每秒约 1.8GB(6,000个 Token)到 2.5GB(1,000个 Token)之间(计算结果)。L1(每秒 11.8GB)比平衡点快了 5 倍以上,因此像 9-3 一样获胜了;而磁盘(每秒 3.8GB)只比平衡点快了区区 1.5 倍左右,无法克服固定延迟。如果是第 5 节中的 1.5B 模型,平衡点将是每秒 0.24GB,因此使用相同的磁盘也能获胜。 模型越小、GPU 越快,较慢存储层的价值就越低这一第5节的结论得到了印证。
- 由于内存限制,未测得从磁盘加载长文档(6,000个 Token)的情况(未实测)。固定延迟的占比可能会减少,从而带来收益。
9-7. 指标
MP 服务器的 Prometheus 指标可以在此版本的管理 HTTP 端口的 /metrics 中找到(通过--prometheus-port 传递的 9090 端口无响应)。 lmcache_mp_ 以 开头的指标共有 48 种,在运维中最先需要查看的是以下内容:
| 想要询问的问题 | 指标 | 本次观测到的值 |
|---|---|---|
| 命中率(以块为单位) | lookup_hit_tokens_total ÷ lookup_requested_tokens_total |
按长度测试 83,456 ÷ 150,528 |
| 来自哪个层 | lookup_hit_l1_tokens_total, lookup_hit_l2_tokens_total |
L2 测试 5,120 / 1,024 |
| L1 填充了多少 | l1_usage_ratio, l1_memory_usage_bytes |
0.77(L1 0.25GB) |
| 存储是否被丢弃 | l1_allocation_failure_chunks_total{during="l1_store"} |
L2 测试 6 个块 |
| 传输速度 | l0_l1_load_throughput_GB_per_second, l0_l1_store_…, l2_load_…(直方图) |
9-6 表格 |
| 驱逐 | l1_evicted_chunks_total, l1_eviction_loop_triggered_total |
|
| 复用间隔 | real_reuse_gap_seconds, l1_chunk_reuse_gap_seconds |
在 vLLM 端, vllm:prompt_tokens_by_source_total{source="external_kv_transfer"} 是从 LMCache 接收的 Token 数量(本节的「外部命中 Token」)。帮助文档中写明管理 HTTP API 没有认证且默认仅在 127.0.0.1 上开放。路由列表中包含 /cache/clear, /reconfigure/…, /run_script ,如果将该端口暴露到 Pod 外部,任何人都可以清除缓存或更改设置(未确认/run_script 的默认激活状态)。
9-8. 总结
| 问题 | 2026-10-27 MP 模式实测的答案 |
|---|---|
| 是否能快速重新读取被驱逐的长文档 | 是。6,000个 Token 从 391 缩短至 91ms(4.3倍) |
| 短文档如何 | 收益较小。不足一个块(256个 Token)的尾部会重新计算,在 1,000个 Token 时为 1.2 倍 |
| 与进程内模式相比 | 重新读取慢约 9ms,存储开销较小 |
| 重新启动引擎后缓存是否仍会保留 | 是(仅 MP 支持) |
| 热文档停留在 GPU 上的负载 | 反而变慢了(17.9 → 20ms) |
| 磁盘层 | 对于这个小模型而言,比重新计算更慢 |
TeeTee 的一句话。 为了重新观看比赛视频,没必要每次都重新打一场比赛。播放录像就行了。不过,如果录像存放在很远的仓库而且路况堵塞,有时候直接再打一局反而更快。必须先衡量一下距离有多远、需要重复观看多少次。
参考
- Liu 等人, LMCache: An Efficient KV Cache Layer for Enterprise-Scale LLM Inference: https://arxiv.org/abs/2510.09665 (v2, 2025-12-05. 第 5、6、8.2~8.7、9 节)
- LMCache 文档: https://docs.lmcache.ai/ (
mp/index.html,mp/l2_storage/supported_storages.html,mp/disaggregated_prefill.html,mp/p2p.html,kv_cache_optimizations/cacheblend.html, 2026-10-13 查阅), 发布: https://github.com/LMCache/LMCache/releases - Yao 等人, CacheBlend (EuroSys 2025): https://arxiv.org/abs/2405.16444 (v3, 摘要、第 4、5、7 节)
- Liu 等人, CacheGen (SIGCOMM 2024): https://arxiv.org/abs/2310.07240 (v6, 第 5、7 节)
- vLLM 分离式预填充与 KV 连接器: https://docs.vllm.ai/en/latest/features/disagg_prefill/, LMCache 示例: https://docs.vllm.ai/en/latest/examples/disaggregated/lmcache/
- 实测引用:同网站「Toss ML/AI Infra 准备」第 8 篇(KV 卸载实测)、第 24 篇(1GbE 有效吞吐量)
- 2026-10-27 MP 模式实测原始数据:仓库
docs/experiments/2026-10-27-lmcache-mp/ - NVIDIA Dynamo 1.6.0 示例
examples/backends/vllm/launch/agg_lmcache_mp.sh: https://github.com/ai-dynamo/dynamo/blob/v1.6.0/examples/backends/vllm/launch/agg_lmcache_mp.sh