0%

Tutti:让基于 SSD 的 KV Cache 真正适用于长上下文 LLM Serving

笔记日期: 2026-05-10
笔记作者: Zhongzhu Zhou
阅读论文: Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving
论文作者: Shi Qiu, Yifan Hu, Xintao Wang, Wenhao Zhu, Jianqin Yan, Hao Chen, Kaiqiang Xu, Kai Chen, Yiming Zhang
arXiv: 2605.03375v1,2026-05-05
论文状态: arXiv preprint,方向:Operating Systems
本文使用的论文文件: src/related-documents/papers/2605.03375-Tutti.pdf


简短结论

这篇论文讨论的是长上下文 LLM serving 里一个很现实的问题:能不能把 KV cache 放到 SSD 上,同时让恢复速度快到足以比重新计算更划算?

现代推理系统已经有很多优化:vLLM / SGLang 这类系统使用 paged KV cache、continuous batching、prefix caching、分层内存管理等技术。它们的目标很明确:如果多个请求共享一段长 prompt,系统最好复用已经计算好的 key/value 状态,而不是每次都重新 prefill。问题是,长上下文、多轮对话、agent 工作流会产生巨大的 KV cache。GPU HBM 很快不够,CPU DRAM 也会在规模化场景下不够。NVMe SSD 容量大、单位成本低,看起来是自然的下一层缓存介质。

但论文指出,已有 SSD-backed KV cache 路径经常不够快。瓶颈并不主要是 SSD 原始带宽低,而是 paged KV cache 的细粒度碎片化访问CPU-centric I/O 控制路径 不匹配。即使使用 GPU Direct Storage(GDS)绕过 CPU bounce buffer,只要每个 I/O 仍然需要 CPU 发起,CPU 仍然会卡在关键路径上。

Tutti 的核心思路是:把 SSD-backed KV cache 的关键数据路径和 I/O 控制路径尽量搬到 GPU 侧,让 CPU 只做粗粒度准备工作。它由三个主要部分组成:

  1. GPU-native KV cache object store:用贴合 KV cache 的对象抽象组织 SSD 上的数据,而不是只暴露普通 block/file 接口;
  2. GPU io_uring:在 GPU HBM 中维护 submission/completion queue,让 GPU 侧 kernel 可以批量发起和完成 NVMe I/O;
  3. slack-aware I/O scheduler:通过离线 profiling 找到 GPU 执行里的空隙,把读写 I/O 放到不容易干扰模型计算的位置。

实验结果比较强。相对带 GDS 的 SSD-backed LMCache,Tutti 在严格 SLO 约束下把 TTFT 降低 78.3%,把可承载请求率提高到 ,并把 serving 成本降低约 27%。在原始带宽实验中,Tutti 的 retrieval bandwidth 最高达到 25.9 GB/s,相对 LMCache-GDS 最高有 2.08× 提升。延迟分解实验里,Tutti 把 compute/I/O crossover point 推到 98.3% cache hit rate,说明大多数测试区间里系统仍然接近 compute-bound,而不是被 SSD I/O 卡住。

我的总体理解是:Tutti 不只是“多加一层 SSD 缓存”。它其实是在围绕 LLM inference 的真实执行形态重做一部分存储栈。随着推理 kernel 越来越快,过去那种“CPU 负责调度每个 I/O”的方式会越来越不适合长上下文 KV cache。真正有效的系统需要把模型执行、KV cache 布局、GPU I/O、SSD 访问和 scheduler 一起设计。


1. 前置知识

1.1 KV cache 是什么,为什么会占很多内存

自回归 Transformer decoder 生成文本时,是一个 token 一个 token 生成的。生成新 token 时,模型需要对之前所有 token 做 attention。为了避免每一步都重新计算历史 token 的 key/value 向量,推理系统会把这些 key/value 向量缓存起来,这就是 KV cache

一个简化流程是:

1
2
3
4
输入 prompt -> prefill -> 计算并存下 prompt 的 K/V
生成 token 1 -> decode -> 追加 token 1 的 K/V
生成 token 2 -> decode -> 追加 token 2 的 K/V
...

KV cache 的好处是节省计算,代价是占用显存/内存。prompt 越长,初始 cache 越大;输出越长,decode 过程中 cache 保留得越久、增长得越多;并发请求越多,总 cache 占用越大。

所以长上下文推理并不是单纯的算力问题,也不是单纯的显存问题,而是一个算力、显存、主存、SSD、调度共同作用的问题。

1.2 Prefix caching 为什么重要

Prefix caching 指的是:如果多个请求共享相同前缀,系统可以复用这段前缀已经计算好的 KV cache。例如:

1
2
3
请求 1:[80K token 的代码仓库上下文] + 问题 A
请求 2:[80K token 的代码仓库上下文] + 问题 B
请求 3:[80K token 的代码仓库上下文] + 问题 C

如果每次都重新 prefill 这 80K token,成本会非常高。如果系统可以复用前缀 KV cache,就能显著降低 TTFT 和单位 token 成本。论文也提到,prefix caching 在现代服务中可以显著改善 SLO,并把 per-token cost 降低到原来的一个数量级以内。

问题是,prefix caching 越有效,就越需要保留大量历史 KV cache。HBM 很快不够;DRAM 容量更大,但论文指出,即使约 2 TB DRAM,在大规模场景下也可能只能保留大约五分钟的 KV cache。SSD 容量可以到几十 TB、上百 TB,单位成本也低很多,因此很适合作为下一层 KV cache 容量池。

1.3 HBM、DRAM、SSD 的差别不只是速度

可以先用一个简单层次理解:

1
2
3
HBM  -> 最快、容量最小、最贵
DRAM -> 较慢、容量更大、规模化时仍然很贵
SSD -> 更慢、容量很大、单位 GB 成本低得多

但对 KV cache 来说,这个理解还不够。HBM 直接被 GPU kernel 使用。DRAM 虽然慢一些,但随机访问能力较好,也比较容易通过 GPU-assisted copy 和 layer-wise pipeline 隐藏一部分传输开销。SSD 原始顺序带宽可以很高,但它非常不喜欢大量小而随机的 I/O。

现代 LLM serving 里的 KV cache 刚好很容易形成大量小随机访问。原因是 paged KV layout 会把逻辑上连续的 KV cache 切成很多 block,分散在不同物理位置。把这些 block 换出到 SSD 后,再恢复时就会产生很多零散读取。

论文的重点就是:SSD 不是不能用,而是不能用传统 CPU-centric 的方式粗暴接上去。

1.4 Paged KV cache:为什么会产生碎片化 I/O

vLLM 的 PagedAttention 代表了一类现代 KV cache 管理方式。它不要求一个请求的 KV cache 在 HBM 中连续存放,而是把 KV cache 切成 block,按需分配。

这样做的好处是显存利用率高,适合不同长度、动态增长的请求:

1
2
3
4
5
一个请求逻辑上的 KV cache:
[token 0 ... token 127]

HBM 中真实 block:
block 17, block 901, block 42, block 603, ...

但当这些 block 被换出到 SSD,再被读回来时,逻辑上连续的长前缀就变成了物理上分散的大量对象。论文给了一个很直观的例子:对 64 层 Qwen3-32B、block size 64 的模型,恢复一个 128K-token KV cache 可能需要读取大约 256K 个分散的 80 KB 对象

这个数量级非常关键。即使每个对象不大,只要每次 I/O 都要 CPU 参与发起、同步、管理,那么 CPU 软件开销就会吞掉 SSD 的硬件带宽优势。

1.5 TTFT、ITL 和 GPU bubble

论文主要关注两个 serving 指标。

TTFT(Time to First Token) 是用户请求到达后,到第一个 token 输出之间的时间。它强烈受 prefill 和 prefix cache retrieval 影响。

ITL(Inter-Token Latency) 是流式生成中 token 与 token 之间的时间间隔。它影响输出是否顺滑。

如果 GPU 在等待 SSD 把 KV cache 读回来,这段时间 GPU 没有做有用计算。论文把这种等待称作 GPU bubble。可以用下面的图理解:

1
2
3
理想情况: compute compute compute compute compute
差的 SSD: compute wait wait compute wait wait compute
Tutti: compute + 被隐藏的 I/O,尽量少 visible wait

Tutti 的目标不是简单地“能从 SSD 读 KV cache”,而是让 SSD 读取尽量不暴露在用户可见延迟里。

1.6 GDS 不等于真正 GPU-centric

GPU Direct Storage(GDS)可以让数据在存储设备和 GPU memory 之间直接移动,避免 CPU bounce buffer。这个优化很重要,但它不等于 CPU 完全退出关键路径。

论文强调:GDS-enabled LMCache 仍然是 CPU-centric,因为每个 I/O 仍然需要 CPU 发起。对大量碎片化 KV 对象来说,CPU 仍然要处理很多 I/O submission/completion 控制工作。也就是说,GDS 解决的是数据拷贝路径的一部分问题,没有解决 I/O 控制路径的问题。

Tutti 想做得更彻底:CPU 负责准备粗粒度 metadata,真正大量并行 I/O 的发起和完成由 GPU 侧完成。


2. 这篇论文解决了什么问题

Tutti 面向的是长上下文 LLM serving 中的 SSD-backed prefix KV cache。论文的基本判断是:如果服务想保留足够多的历史 KV cache,SSD 几乎不可避免;但如果 SSD 路径还是 CPU-centric,那么高 cache hit rate 也不一定带来低延迟。

可以把已有路径简化成:

1
2
3
4
CPU-centric 路径

inference engine -> CPU 准备大量 I/O -> GDS 或普通异步 I/O -> SSD
^ 对很多碎片化 KV block 重复发生

Tutti 想变成:

1
2
3
4
5
GPU-centric 路径

CPU 一次性准备 layer-level metadata
GPU-side queue 批量发起 KV-object I/O
scheduler 把 I/O 放进 profiled slack window

论文 Figure 1 清楚对比了 CPU-centric LMCache 和 GPU-centric Tutti。LMCache 即使使用 GDS,CPU 仍然在 I/O 控制关键路径上;Tutti 则把关键路径尽量移到 GPU 侧。

从设计原则上看,Tutti 要同时满足三点:

  1. 抽象要贴合 KV cache。 存储层应该理解 layer-wise KV object,而不是只暴露普通 block/file。
  2. 控制路径要支持大规模并行。 I/O submission/completion 不能让 CPU 对每个小对象逐个介入。
  3. I/O 必须理解计算调度。 即使 I/O 能在 GPU 上发起,如果它抢占 SM 或造成读写带宽冲突,也会拖慢 inference。

所以 Tutti 是一个跨内存管理、GPU runtime、存储 I/O、serving scheduler 的共同设计。这也是它和“简单加一层 SSD cache”最大的区别。


3. 方法细节

3.1 从 Figure 2 和 Figure 3 看问题来源

Figure 2 比较了 vLLM + LMCache 在 HBM、DRAM、SSD tier 上的表现,模型是 Llama3-8B,sequence length 是 64K,cache hit rate 是 75%。这个图展示了几个关键信号:

  • DRAM-backed cache 相对 HBM 只带来较小开销;
  • SSD-backed cache 造成很大的 GPU bubble;
  • GDS 能减少一部分数据搬运开销,但 GPU bubble 仍然很大;
  • 当 vLLM 从 v0.12.0 进化到 v0.17.0,计算更快后,SSD/GDS 路径的相对瓶颈更明显。

最后一点特别重要。推理 kernel 越快,能够隐藏 I/O 的计算时间越少。如果存储路径不一起变快,那么整个系统会越来越像“GPU 在等 SSD”。

Figure 3 比较 CPU 和 GPU 上做 hash table 的性能。论文报告,在不同 sequence length 下,GPU hash insert 比 CPU 慢 9.0× 到 24.2×,lookup 比 CPU 慢 25.6× 到 50.0×。这个结果说明,把所有 KV cache metadata 管理都搬到 GPU 并不现实。hash lookup 有顺序依赖,不是 GPU 最擅长的模式。

因此 Tutti 采用一种折中:CPU-prepared, GPU-executed。复杂 metadata 和 mapping 仍然由 CPU 管理,但每个 I/O 不再由 CPU 逐个发起。

3.2 GPU-native KV cache object store

Tutti 的第一部分是 GPU-centric KV cache object store。它不是简单把 KV block 当普通文件写到 SSD,而是设计了贴合 KV cache 的 object abstraction。

论文 Figure 4 展示了整体布局,主要包括:

  • GPU file pool:对 inference engine 可见;
  • NVMe file pool:由类似 GeminiFS 的机制管理 SSD 上的物理 extent;
  • P2P memory mapping table:把 GPU-visible object 映射到真实存储位置。

Tutti 使用 tensor-stripe layout,把 GPU file 映射到多个 NVMe file,同时尽量保持和原始 KV tensor 粒度一致。这样做的目标是让存储 I/O 和 KV cache 的 layer-wise 移动对齐。

对 inference engine 来说,Tutti 暴露的是 retrieve_layerstore_layer 这样的接口。也就是说,系统可以按 layer 批量恢复或写回 KV cache,而不是临时构造几万、几十万个细碎 I/O。

论文给出的复杂度变化很直观:

1
2
传统 CPU 准备路径:O(layer × blocks)
Tutti 准备路径: O(layer)

当 block 数随上下文长度快速增长时,这个变化非常有价值。

PRP 与 SGL:一个低层细节为什么影响很大

论文中特别值得注意的是 PRP 和 SGL 的对比。

PRP(Physical Region Page)使用固定 4 KB page 描述 GPU HBM 地址。对 KV cache 这种几十 KB、上百 KB 的可变长度对象来说,PRP 会引入大量指针 page 和地址翻译开销。论文举例:在 80 GB HBM 上描述 60 GB KV cache,如果使用 PRP,需要 15,728,640 个 page;如果 PRP list page 以 64 KB 粒度分配,实际 HBM 元数据占用可以达到约 3.75 GB

SGL(Scatter Gather List)更适合这个场景。一个 16 字节 entry 可以描述较大连续 chunk,包括物理地址、长度、identifier。同样例子下,元数据内存可以降到约 15 MB

这个细节说明:GPU direct I/O 不是打开一个开关就结束了。descriptor 格式、地址翻译、metadata 内存占用都会直接影响最终性能。

3.3 GPU io_uring

Tutti 的第二部分是 GPU io_uring,论文中写作 gio_uring。它借鉴 CPU-side io_uring 的思想,但把 submission queue 和 completion queue 放到 GPU HBM 中,让 GPU 侧可以异步发起和完成 I/O。

Figure 5 展示了它的结构。主要组成包括:

  • GPU HBM 中的 SQ / CQ;
  • IOCB(I/O control block),每个 IOCB 里包含多个 IOCTX;
  • 通过 non-cached mmap 映射给 CPU 的 zero-copy ring buffer;
  • 用 CUDA event 维护 out-of-order stream 下的依赖顺序;
  • GPU I/O kernel 负责生成 NVMe command、提交、轮询完成并写入 CQ。

运行流程可以简化为:

1
2
3
4
5
6
7
8
9
10
11
1. init_queue(depth)
创建 GPU-resident SQ/CQ 和 IOCB

2. get_iocb(nums, event)
预留 IOCB,填入 CPU 准备好的 metadata

3. issue_io(IOCB_ids, SMs)
以指定 SM 预算启动 GPU I/O kernel

4. wait_cqe(IOCB_ids)
在 GPU-side completion queue 等待完成

这个设计的关键不是“异步”两个字。CPU-side async I/O 也可以异步。关键是大量 I/O 的 submission/completion 不再要求 CPU 每次都进入关键路径。

SM partitioning:I/O kernel 不能随便抢计算资源

把 I/O 搬到 GPU 之后,还有一个新问题:I/O kernel 也会占 GPU SM。如果一个长 I/O kernel 抢占了 SM,模型计算 kernel 可能被延迟,反而伤害 TTFT/ITL。

Tutti 使用 NVIDIA green context,把 GPU 资源分成 compute domain 和 I/O control domain。I/O kernel 运行在专门分配的 SM 上,不容易被模型计算波动影响,也不容易反过来造成计算 kernel 的长尾延迟。

这一步很重要。它说明 Tutti 不是只追求高 I/O bandwidth,而是在追求可预测的 compute-I/O coexistence。

3.4 Slack-aware I/O scheduler

Tutti 的第三部分是 slack-aware I/O scheduler。问题是:即使 GPU 能发 I/O,什么时候发才不会影响模型计算?

Figure 6 先展示了一个负面现象:如果 read/write 并发执行,PCIe 带宽会严重下降。论文报告,在复现实验中 concurrent read/write 会造成 60.1% bandwidth drop。原因不是简单的读写平分带宽,而是 NVMe 内部资源(例如 SSD cache)竞争导致整体带宽塌陷。

Figure 7 展示了 scheduler。Tutti 离线 profile 每层执行中的 slack window。所谓 slack window,可以理解为:这段时间里 GPU 有可用 SM 资源,同时发起 I/O 不会造成有害读写带宽冲突。lookup table 的索引包括 input length 和 prefix length,因为 attention 计算量会随上下文长度变化。

调度策略大致如下:

  • prefill 阶段优先调度 read,因为 KV retrieval 在 prefix reuse 的关键路径上;
  • write 可以延迟,避免干扰关键 read 和 TTFT;
  • 如果当前 layer 前有合适 slack window,就尽量启动能放进去的 IOCB;
  • 如果没有合适 slack window,说明 retrieval 已经成为瓶颈,就立即启动必要 read,避免后面计算停等;
  • 剩余 write 可以在 decode 阶段 best-effort flush。

可以用下面的对比理解:

1
2
3
4
5
差的 pipeline:
只要有机会就读写 -> 读写互相打架 + I/O kernel 抢 SM

Tutti pipeline:
profile compute slack -> critical read 优先 -> write 延后 -> 尽量隐藏 I/O

我觉得这里最值得学习的是工程取舍:Tutti 没有做复杂在线最优调度,而是用离线 profile 换运行时简单查表。这种方式更适合低延迟 serving,因为 runtime decision 必须便宜且稳定。

3.5 vLLM 集成、多 GPU 和分布式路径

论文报告 Tutti 用约 8,000 行 C++ 实现,并用约 1,500 行 Python 集成到 vLLM 的 KVConnector。它向 vLLM 暴露 retrieve_layerstore_layer,注册预分配 KV memory block pool,识别可复用 prefix,并把 logical KV block 映射到 GPU file。

多 GPU 场景下,Tutti 延续 vLLM 的 one-process-per-GPU 模式。每个 GPU process 有自己的 Tutti instance,只管理该 GPU 上的 KV cache。local daemon 负责分配 GPU memory,并为每个 GPU 初始化专门的 NVMe submission/completion queue pair。每个 GPU 有独立 queue pair,因此多个 GPU 可以并行访问本地 NVMe,不需要争抢同一个 queue。

更大规模分布式场景下,论文描述了和 Mooncake 的配合:Tutti 保持本机 GPU-to-local-NVMe 的快速路径,Mooncake 负责集群范围内的 space allocation、replica metadata、location lookup。不过当前 remote path 还没有完全 GPU-driven:如果 KV 在远端,prototype 会通过 CPU-side interface 读到 host memory,再通过 RDMA 发到本地。这部分作者也明确留作未来工作。

所以这篇论文证据最强的部分是 local SSD-backed KV reuse。跨节点路径有方向,但成熟度还需要后续系统工作验证。


4. 实验设置

4.1 硬件环境

实验服务器配置为:

  • 64-core Intel Xeon 6530;
  • 512 GB memory;
  • 2 张 NVIDIA H100 GPU,每张 80 GB HBM;
  • 4 块 Solidigm D7-PS1010 7.68 TB enterprise NVMe SSD。

分层存储配置中,作者分配 256 GB host DRAM 作为 pinned memory,并为每张 GPU 配置 14 TB SSD volume。相关 SSD 实验使用 two-disk RAID-0。

这个硬件环境很高端,因此结果应该理解为数据中心 serving 场景,而不是普通开发机环境。

4.2 模型

主要单 GPU 实验使用 Llama3-8B。多 GPU scalability 实验使用 GLM-4-9B-Chat-1M,这个模型支持 1M token context window,并通过 tensor parallelism 部署在两张 GPU 上。

这样的选择比较合理:Llama3-8B 适合在单 H100 上观察 serving/system 行为;GLM-4-9B-Chat-1M 则更能体现长上下文和多 GPU 下的 KV cache 压力。

4.3 Workloads

论文使用两个 long-context benchmark:

  • LEval:包含 20 个子任务,覆盖 law、finance、technology、academic papers、code 等领域,输入长度从约 3K 到 200K tokens;
  • LooGLE:包含 4 个 ultra-long-context 任务,很多样本超过 100K tokens,关注 long dependency QA 和 single-turn summarization。

因为这些数据集没有真实请求到达时间戳,作者使用 Poisson distribution 模拟请求到达,并用 round-robin 方式从不同子任务中抽取请求。这是 serving 论文里常见的实验折中,但仍然意味着流量模式是合成的。

4.4 Baselines

论文对比了以下 baseline:

  1. HBM:标准 vLLM,只使用 HBM;
  2. LMCache-DRAM-LW:使用 host memory 扩展容量,并用 layer-wise compute/I/O pipelining;
  3. LMCache-SSD:用 NVMe SSD offload KV data,采用 memcopy 和标准异步 I/O;
  4. LMCache-GDS:使用 GDS 优化 SSD access,绕过 CPU bounce buffer;
  5. ablation 里还展示了不使用 layer-wise transfer 的 LMCache-DRAM

作者还比较了两个 vLLM 世代:v0.12.0v0.17.0。这点很有价值,因为推理框架迭代很快。如果一个 SSD 路径只在旧版慢 runtime 下有效,随着 compute kernel 变快,它可能很快暴露瓶颈。

4.5 指标

论文主要报告:

  • TTFT:首 token 延迟;
  • ITL:生成 token 间隔;
  • cache hit rate:不同 tier 能保留多少可复用 KV;
  • retrieve/store bandwidth:原始读写带宽;
  • GPU bubble time:I/O 是否暴露为 GPU 等待;
  • cost per 1M tokens:归一化 serving 成本。

这组指标比较完整,因为它把低层 I/O 性能和用户可感知 serving 延迟联系了起来。


5. 结果与分析

5.1 Table 1:SSD 容量为什么重要

Table 1 给出不同存储 tier 的 cache hit rate:

Storage medium LEval hit rate LooGLE hit rate
HBM 8% 4%
DRAM 53% 24%
SSD 84% 86%

这个表非常关键。HBM-only 在长上下文场景里 hit rate 很低,说明大量可复用 KV 很快被挤掉。DRAM 明显改善 LEval,但对 LooGLE 这类更长上下文仍然不足。SSD 因为容量大,可以保留绝大多数可复用 KV 状态。

这也解释了为什么 Tutti 要做 SSD-backed KV cache:

1
2
3
HBM-only:hit 很快,但 hit 很少
DRAM tier:hit 更多,但容量仍有限
SSD tier:hit 很多,但必须把 retrieval 做快

如果 SSD retrieval 很慢,84%/86% 的 hit rate 反而可能只是“高命中但高延迟”。Tutti 的价值就是让这些 hit 真正转化成低 TTFT 和低成本。

5.2 Figure 8:端到端 TTFT 和 ITL

Figure 8 比较 Llama3-8B 在 LEval 和 LooGLE 上的 TTFT/ITL,覆盖 vLLM v0.12.0 和 v0.17.0。

论文报告了几个关键结果:

  • 在旧版 vLLM 的 LEval 高负载点,Tutti 相对 GDS 把 TTFT 降低 71.8%
  • 在新版 vLLM 中,高负载下 Tutti 相对 DRAM 降低 TTFT 69.1%,相对 GDS 降低 78.3%
  • 1s TTFT SLO 下,Tutti 相对 DRAM 把 effective request rate 提高 50%,相对 GDS 提高 100%
  • 在新版 vLLM 的 LooGLE 0.6 RPS 下,GDS 的 TTFT 仍然约为 Tutti 的 2.63×
  • 同一负载点,Tutti 相对 DRAM 降低 TTFT 93.2%,相对 GDS 降低 62.0%

ITL 方面也有明显改善:

  • 旧版 vLLM 的 LEval 1.5 RPS 下,Tutti 相对 DRAM 降低 ITL 60.4%,相对 GDS 降低 24.9%
  • 新版 vLLM 的 LEval 1.5 RPS 下,Tutti 相对 DRAM 仍降低 ITL 22.0%,相对 GDS 降低 24.4%
  • LooGLE 上 ITL 改善幅度变小,因为更长输入让每 token compute 变重,但 Tutti 仍然保持最好或最稳定的曲线。

我的理解是:Tutti 最适合那些“有足够多 prefix reuse,同时还有足够 compute 可以隐藏 I/O”的场景。如果 workload 极端 compute-heavy,存储优化对 ITL 的相对收益会变小;如果 workload 极端 retrieval-heavy,DRAM 的低延迟优势可能重新出现。但在论文测试的长上下文 serving 区间里,Tutti 的优势很明显。

5.3 Figure 9:retrieve/store bandwidth

Figure 9 直接测试 1K 到 128K context length 下的 retrieve/store bandwidth。

retrieval 方面:

  • Tutti 曲线平滑,长上下文下最高达到 25.9 GB/s
  • LMCache-GDS 即使用两块 SSD,也大约卡在 11.9 GB/s
  • 因此 Tutti retrieval bandwidth 最高达到 LMCache-GDS 的 2.08×
  • LMCache-DRAM 有明显不稳定,例如在 16K tokens 时因为 memory fragmentation 降到 8.5 GB/s

store 方面:

  • LMCache-DRAM 最高可到 18.4 GB/s,但它没有 SSD 持久容量,仍受 DRAM 限制;
  • Tutti 在持久存储后端中更稳定,约 10 GB/s,128K tokens 时为 9.8 GB/s
  • LMCache-GDS 在同样双 SSD 配置下约 7 GB/s

这里要注意 retrieval 比 store 更关键,因为 prefix cache hit 的读取通常在 TTFT 关键路径上。write 可以通过 scheduler 延后,在 decode 或后续 slack window 里 best-effort flush。

5.4 Figure 10:SGL 为什么比 PRP 重要

Figure 10 是一个单 GPU thread microbenchmark,每次读写 500 MB 数据。结果如下:

Descriptor path Read bandwidth Write bandwidth
PRP 0.287 GB/s 0.032 GB/s
SGL 8.891 GB/s 2.922 GB/s

从 PRP 切到 SGL 后,read 提升 31.0×,write 提升 91.3×

这个结果很有启发。很多时候我们讨论 LLM serving 只看 batch size、attention kernel、KV cache policy,但底层 NVMe descriptor 的选择也可能决定系统是否能跑起来。Tutti 的贡献之一就是把这些底层细节和上层 KV cache 对象形态对齐。

5.5 Figure 11:不同 prefix reuse 下的 TTFT

Figure 11 固定 total input length 为 128K tokens,把 cached prefix 从 16K 增加到 128K。这个实验测试随着 prefix reuse 增加,各系统如何变化。

论文报告:

  • cached prefix 为 112K 时,LMCache-SSD 的 TTFT 达到 7.84 s
  • Tutti 同一位置为 3.43 s,比 LMCache-SSD 快 2.28×
  • 相对 LMCache-GDS,Tutti 在所有 prefix length 下都有改善,从 32K 的 5.8% 到 128K 的 61.4%
  • 在 16K 到 96K 的中等 reuse 区间,Tutti 甚至可以匹配或超过 DRAM,最高提升 13.4%
  • 当 reuse 极高(超过 96K)时,任务几乎变成纯 retrieval-bound,DRAM 会重新领先,Tutti 最多落后 20.6%

这个结果很细腻。它没有说 Tutti 在任何情况下都比 DRAM 快。更准确的说法是:当 compute-I/O overlap 和 SSD 容量优势能发挥作用时,Tutti 可以超过 DRAM;当剩下的工作几乎全是读取,DRAM 的低延迟优势仍然存在。

5.6 Figure 12:多 GPU 和超长上下文

Figure 12 使用 GLM-4-9B-Chat-1M,在两张 GPU 和四块 SSD 上测试 prefix length 变化。论文报告,128K prefix 时 Tutti 的 TTFT 为 155.743 s,相比 LMCache-GDS 的 207.12 s 降低约 25%

更重要的是长 prefix 下的稳定性:LMCache-GDS 在 512K 和 640K prefix length 失败,出现 OOM。论文认为原因是 GDS 通过 cufile 做 direct transfer 时需要额外 GPU memory 作为 staging buffer;长上下文下这些 buffer 会触发显存不足。Tutti 通过和 inference engine 深度集成,直接管理 GPU memory,避免了这类额外 staging buffer,因此能完成更长 prefix 的测试。

我更愿意把这个结果看成架构鲁棒性的证据。长上下文系统的失败往往不是平均带宽不够,而是某些额外 memory buffer、metadata、同步路径在极端上下文下突然放大。Tutti 的价值之一就是减少这些隐藏额外开销。

5.7 Figure 13:bubble time 和 crossover point

Figure 13 把 latency 分解成 compute time 和 bubble time,并随 cache hit rate 变化。核心直觉是:

1
2
3
4
如果 T_compute > T_transfer:
I/O 可以被计算隐藏
否则:
storage bubble 会暴露出来

LMCache-SSD 的 bubble 很大,无法被有效隐藏。Tutti 则在大多数测试区间里把 bubble 压得很小。论文报告,Tutti 的 bubble time 平均约 25 ms,在 93.75% hit rate 时低到 6 ms,并把 crossover point 推到 98.3% hit rate

我认为这是论文里最能体现 scheduler 价值的结果。Figure 9 说明 Tutti I/O 路径更快;Figure 13 进一步说明,这条更快的路径确实被放到了合适时间点,所以没有显著打断模型计算。

5.8 Figure 14:成本

Figure 14 计算每 1M tokens 的 serving 成本。公式可以理解为:

1
每 1M tokens 成本 = (GPU 成本 + DRAM/SSD 成本) / token throughput × 1,000,000

论文使用的价格假设包括:

  • H100:每小时 $5;
  • DRAM:每 GB 每小时 $0.0088;
  • NVMe SSD:每 GB 每小时 $0.000082。

在 LooGLE 0.5 QPS 下,Tutti 相对 LMCache-SSD 降低成本 66.2%,相对 LMCache-GDS 降低约 27%

这个成本优势来自两方面:SSD 容量很便宜,同时 Tutti 又能让 GPU 不因为等待 SSD 而空转。便宜存储本身不够,如果 GPU 利用率低,最终每 token 成本仍然会高。Tutti 的目标就是同时利用 SSD 容量和 GPU 计算能力。


6. 局限性与边界条件

6.1 设计强依赖硬件和系统栈

Tutti 依赖高端 GPU、企业级 NVMe SSD、GPU direct access、CUDA runtime、SM partitioning、vLLM integration 等能力。论文使用的是 H100 和 Solidigm D7-PS1010 这类数据中心硬件。

因此,这不是一个可以直接搬到普通开发机或任意云环境的方案。某些云平台可能不允许足够低层的 NVMe / GDS / GPU P2P 控制;某些 PCIe topology 也可能不适合。

6.2 Offline profiling 会带来运维成本

slack-aware scheduler 依赖离线 profile。模型、GPU、CUDA version、attention kernel、vLLM version、tensor parallel layout、SSD 数量、batch policy 变化后,profile 都可能需要重新生成。

这在稳定线上集群里是可以接受的,但它确实增加了系统维护成本。如果 workload 跑到 profile 覆盖范围外,scheduler 的效果也可能下降。

6.3 分布式 remote path 还不是最成熟部分

论文描述了通过 Mooncake 做分布式 metadata 和 replica management,但当前 remote retrieval path 仍然会通过 CPU-side interface 读到 host memory,再用 RDMA 发到其他节点。更直接的 GPU-driven RDMA path 被留作未来工作。

所以本文最扎实的证据集中在本机 GPU-to-local-NVMe 路径。真正大规模多节点生产系统还需要处理远端 KV 放置、跨节点复制、失效恢复、网络拥塞、远端读取延迟等问题。

6.4 Baseline 会随系统演化变化

论文主要对比 LMCache-SSD 和 LMCache-GDS。这个对比是合理的,但 serving/storage 系统迭代很快。未来如果 LMCache 或其他系统也引入 GPU-side submission、更强 scheduler、更贴合 KV object 的布局,百分比差距可能变化。

所以我更看重论文的架构判断,而不只看某个版本上的 78.3%。最重要的信息是:CPU-centric I/O 不适合长上下文碎片化 KV retrieval。

6.5 Cache hit 假设决定收益

Tutti 对 prefix reuse 高的 workload 很有价值。如果 workload 几乎没有 prefix reuse,或者隐私/租户隔离策略不允许跨请求复用 KV cache,那么 SSD 容量再大也未必有用。

此外,系统还需要决定哪些 KV 值得保留、何时 evict、如何避免低价值 prefix 占满 SSD、如何保证多租户隔离。论文重点解决的是“SSD-backed KV retrieval 怎么足够快”,并没有完全展开 cache admission / eviction / privacy policy。

6.6 复现信息还不够完整

论文摘要说 Tutti 是 open-source SSD-backed KV caching solution,但正文 PDF 中没有给出清晰仓库地址。要完整复现实验,读者需要代码、构建步骤、兼容 vLLM 分支、驱动版本、SSD 配置、GeminiFS 或等价组件、profiling 脚本、workload 生成脚本等。

仅凭论文内容,理解系统设计没有问题,但要从零搭起同等系统会比较困难。


7. 可复现性与实用笔记

7.1 如果我要复现实验,需要哪些信息

我会希望作者提供:

  • Tutti C++ 和 Python 代码;
  • 对应 vLLM v0.12.0 / v0.17.0 的集成分支;
  • LMCache 版本,尤其是图中出现的 LMCache 0.3.9 / 0.4.1;
  • CUDA、driver、GDS、NVMe driver 版本;
  • GeminiFS 或等价 GPU file system 的搭建方式;
  • RAID-0、SSD queue、P2P mapping 配置脚本;
  • slack-window profiling 脚本;
  • LEval / LooGLE workload 预处理脚本;
  • Poisson arrival traffic generator;
  • SLO 规则和 Figure 8 中缺失点的判定逻辑。

论文提供了理解设计和结果所需的主要信息,但要精确复现,还需要工程细节。

7.2 如果在生产里评估类似系统,我会先检查什么

Workload 是否适合

  • prefix reuse 是否足够高?
  • 长上下文请求是常态还是极少数尾部?
  • 多租户和隐私策略是否允许 KV cache 复用?
  • 流量分布是否接近 LEval/LooGLE,还是完全不同?

硬件是否适合

  • GPU 和 NVMe SSD 是否在合适的 PCIe topology 下?
  • P2P DMA 是否稳定可用?
  • SSD 是否能承受持续 mixed read/write?
  • 预留 SM 给 I/O 是否会伤害模型吞吐?

Runtime 是否适合

  • 当前使用哪个 vLLM 版本?
  • 是否有 KVConnector 类似接口?
  • 团队是否能维护 CUDA/C++ storage kernel?
  • profile 多久需要重新生成一次?

运维是否可控

  • SSD 故障时如何 fallback?
  • KV object 如何 evict、replicate、invalidate?
  • 如何监控 GPU bubble、SSD queue depth、cache hit quality?
  • SSD tier 不稳定时,能否回退到 DRAM 或 recomputation?

7.3 这篇论文给我的系统启发

这篇论文背后的趋势是:LLM serving 正在变成更完整的 full-stack systems 问题。过去很多推理系统可以把 storage 当成外围组件:加载权重、写日志、偶尔 offload 一些数据。但长上下文改变了这一点。KV cache 本身可能比模型权重大得多,KV cache 移动也可能直接决定用户看到的延迟。

Tutti 的设计风格可以概括成:

1
2
3
4
5
模型结构      -> layer-wise KV object
GPU runtime -> io_uring-like async queue
存储设备 -> SGL-friendly high-bandwidth transfer
scheduler -> profile compute slack,避免 I/O 干扰
serving 指标 -> TTFT / ITL / cost under load

它最有价值的地方不是某一个技巧,而是说明单点优化不够:

  • 只有 SSD 容量,不解决延迟;
  • 只有 GDS,不解决 CPU 控制路径;
  • 只有异步 I/O,不解决 SM 干扰;
  • 只有带宽,不解决 read/write 竞争;
  • 只有 cache hit,不保证用户体验。

最后的性能来自这些组件围绕 KV cache 数据流的共同设计。


8. 我的主要收获

  1. SSD-backed KV cache 必要但不自动有效。 Table 1 说明 SSD 能显著提高 hit rate,但 Figure 2 也说明低效 SSD retrieval 可能比 recomputation 更糟。

  2. CPU control path 是长上下文碎片化 I/O 的核心瓶颈之一。 GDS 去掉了 CPU bounce buffer,但如果每个 I/O 还要 CPU 发起,仍然无法适应大量小对象。

  3. 抽象必须贴合 KV cache。 Tutti 的 object store 有价值,是因为它把存储操作和 layer-wise KV movement 对齐。

  4. 调度和带宽同样重要。 Figure 13 显示 Tutti 不只是提高 GB/s,还把 I/O 放到合适时间,使 bubble time 很小。

  5. 系统复杂度不低。 Tutti 需要 CUDA/C++ kernel、vLLM integration、NVMe/GPU memory 细节和离线 profiling。它不是一个简单 Python cache。

  6. 未来 LLM serving 可能会越来越 GPU-centric。 当 KV cache 成为主导状态时,GPU-controlled data movement、model-aware storage layout、compute-I/O joint scheduling 会越来越重要。


参考与延伸阅读

  1. Qiu et al., Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving, arXiv:2605.03375, 2026.
  2. Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023.
  3. Liu et al., CacheGen: KV Cache Compression and Streaming for Fast Large Language Model Serving, SIGCOMM 2024.
  4. Qin et al., Mooncake: Kimi's KVCache-centric Architecture for LLM Serving, arXiv:2407.00079, 2024.
  5. Qiu et al., GeminiFS: A Companion File System for GPUs, FAST 2025.
  6. Qureshi et al., GPU-Initiated On-Demand High-Throughput Storage Access in the BaM System Architecture, ASPLOS 2023.
  7. Chen et al., IMPRESS: An Importance-Informed Multi-Tier Prefix KV Storage System for Large Language Model Inference, FAST 2025.
  8. Gao et al., Fast State Restoration in LLM Serving with HCache, EuroSys 2025.

笔记写于 2026-05-10。