Tutti:把 SSD 当 KV Cache 主力存储的生产级方案

  • 关联论文:2605.03375
  • 作者:flyP
  • 更新:2026-07-23

一句话结论:Tutti 把 CPU 从 HBM↔SSD 的关键 I/O 路径里彻底剔除,改成 GPU 直接驱动 NVMe(GPU io_uring + GPU 原生 KV cache 对象抽象 + slack-aware 调度),把 GDS 仍存在的 CPU 瓶颈变成「GPU 自己管对象」,结果相对 SSD-backed LMCache 把 SLO 严格约束下的 TTFT 降 78.3%,可达请求率翻倍,服务成本降 27%,性能几乎追平 DRAM-backed LMCache,但容量近乎无限。

一、解决的真问题

Prefix caching 是长上下文 LLM serving 的标配:相同前缀的请求可以复用 KV cache,省掉重算。但长上下文场景下 KV cache 体积迅速超过 GPU HBM 和 CPU DRAM,于是主流方案是把 KV cache 分层:HBM(热)→ DRAM(温)→ NVMe SSD(冷)

问题出在最后一跳:从 SSD 把 KV cache 搬回 GPU 时,I/O 性能极差,GPU 长时间空转等待。其根因有两层:

  1. 碎片化布局:KV cache 在 GPU 显存里是按 token / layer / head 细粒度切分的,落到 SSD 上的物理布局是大量小随机 I/O;
  2. CPU 是瓶颈:即便是 GPU Direct Storage(GDS)这种"绕过 CPU 拷贝数据"的方案,每条 I/O 命令仍然要 CPU 来发起——大量小 I/O 命令把 CPU 打满,CPU 成了新的瓶颈,依然是 CPU-centric。

Tutti 直接瞄准第二条根因:把 I/O 控制路径也搬到 GPU 上,让 CPU 只负责"为某一层异步加载一次 I/O kernel"这种一次性活儿,之后 GPU 自己用 io_uring 风格的异步原语拉数据。

二、核心方法

Tutti 的设计由三块组成,整体目标可以概括成一句话:GPU 原生对象存储 + GPU io_uring + 资源感知调度

2.1 GPU-Centric KV Cache 对象存储

传统做法把 KV cache 当成"显存里一坨张量"来管,到了 SSD 层就丢失语义,CPU 要负责"这块数据在哪里、怎么拼回去"。Tutti 给 KV cache 引入对象抽象

  • 把 KV cache 切成有语义的 KV Object(按 layer / window / head group 切),每个对象对应一段连续的 SSD 物理区域;
  • 对象元数据(位置、长度、校验)放在 GPU 可见的元数据表里,GPU 本身就能做对象查找与调度,不再回 CPU。

这一步把"CPU 当目录服务"的隐式假设打掉。

2.2 GPU io_uring:把 I/O 控制路径也搬上 GPU

Linux 的 io_uring 把同步系统调用变成"提交-收割"两步,让用户态可批量提交 I/O。Tutti 把这个思路搬到 GPU 端

  • GPU 上实现一个轻量 io_uring 风格的提交队列(SQ)/ 完成队列(CQ);
  • CPU 只在每个新 layer 第一次出现时异步加载一次 I/O kernel到 GPU,之后所有 I/O 命令都由 GPU 自己提交;
  • 配合 NVMe 的多队列(multi-queue),SSD 带宽被打满。

这里的关键不只是"绕开 CPU",而是"让发起 I/O 的频率匹配 NVMe 的吞吐"——CPU 单核每秒发起的命令数有上限(几十万级),而 NVMe 单盘 IOPS 在百万级;不搬上 GPU,IOPS 根本用不满。

2.3 Slack-Aware I/O 调度

把 I/O 搬到 GPU 上之后,新问题出现:GPU 自己的 SM / 拷贝引擎也是稀缺资源,如果 I/O 与计算抢资源,两边都会掉速。Tutti 引入 slack-aware 调度:

  • 跟踪每个请求的 SLO(Time-To-First-Token 之类)剩余时间;
  • 在 GPU 空闲 slack(剩余预算)足够时才发起大批量预取;slack 紧的请求优先排队;
  • 避免 I/O 与 attention 计算在 SM 上的争抢。

伪代码骨架:

for each request r in scheduler:
    slack = deadline(r) - now()
    if slack < threshold:
        issue_prefetch(r, urgent=True, max_concurrent=low)
    else:
        issue_prefetch(r, urgent=False, max_concurrent=high)
    submit_to_gpu_io_uring(SQ_entries)
    reap_completions_from(CQ_ring)  # GPU 内执行

这三块合起来,效果是:NVMe 带宽饱和 + GPU 停顿接近 0

三、关键实验与数据

论文把 Tutti 接到 vLLM 上做端到端评测,对照是当时最强基线 GDS-enabled、SSD-backed LMCache

| 指标 | 相对 SSD-backed LMCache | |---| | 严格 SLO 下 TTFT | 降低 78.3% | | 可达请求率(request rate) | 提升 2× | | 服务成本 | 降低 27% | | 与 DRAM-backed LMCache 的性能差距 | 几乎一致("nearly the same")| | 容量 | 几乎无限(受 SSD 容量限制)|

⚠️ 数据核查说明:78.3% TTFT 降低 / 2× 请求率 / 27% 成本下降均引自论文摘要;但论文摘要未给出 GPU 型号(NVIDIA A100/H100/H200?)、SSD 型号与数量、batch size、prefix 长度、具体 SLO 阈值等关键硬件配置。缺少硬件配置使数字无法在其他集群复现,引用时建议补全原文表格(§4/§5)。

论文已集成 vLLM 代码库,⚠️ 具体 GitHub 仓库 URL paper card 与摘要均未明确给出,需从 arXiv PDF 或作者主页独立核查。

四、亮点与局限

亮点

  1. 彻底去 CPU 化:不只是数据路径绕过 CPU,控制路径也绕过,这是 GDS 路线没做到的事;
  2. GPU io_uring 是真新意:把内核的异步 I/O 范式移植到 GPU 端,对 NVMe 吞吐的释放是关键;
  3. slack-aware 调度 把推理系统里的实时性约束接到存储调度里,避免了"为换 SSD 把 GPU 算力赔进去"的常见陷阱;
  4. 直接接入 vLLM:工程落地路径短,paper card 标注其与 disaggregated 推理(prefill/decode 分离)路线契合度高。

局限

  1. 硬件绑定较深:依赖 GPU 对 NVMe 的 peer-to-peer DMA(GDS 或类似机制),在不支持 GDS 的 GPU 上要回退或重写;
  2. CPU 仍要做事:每个新 layer 第一次出现的 I/O kernel 加载仍由 CPU 触发,频繁切换 layer 时 CPU 仍会回到关键路径上;
  3. 元数据表管理:KV 对象元数据本身在 GPU 上占显存,长上下文下元数据规模也需要仔细设计;
  4. 多 GPU 扩展未深入:paper card 与摘要未明确给出跨节点 KV cache 共享场景下的方案;
  5. 可重复性:硬件组合(GPU + NVMe + kernel 版本)敏感,论文未明确给出全部基准环境。

五、对工程落地的启发

  • 长上下文服务(RAG、代码助手、多文档摘要)可以放心把 KV cache 主存下沉到 NVMe,无需再为"塞满 HBM/DRAM"焦虑;
  • disaggregated 推理架构(prefill 与 decode 分引擎)能直接受益 Tutti 的双路径思路:prefill 引擎 IO 密集,decode 引擎算力密集,两边都可用 slack-aware 调度分别调优;
  • 国内 NPU 推理云(含 Tenstor、寒武纪等)若要把 SSD 后备 KV 做成生产级,需要复刻 Tutti 的"控制路径从 CPU 撤出"思路,不能只复刻 GDS;
  • 容量规划简化:之前必须精细化设计"哪些 KV 留 HBM、哪些 evict",有了 SSD 主力存储,可以显著降低 evict 策略复杂度;
  • 成本结构:成本下降 27% 这个数字很值得给业务方看——把"长上下文"做便宜了。

六、与同方向工作的关系

路线 代表 与 Tutti 的关系
Prefix caching vLLM、SGLang、RadixAttention Tutti 是它们的"存储后端下沉"版本
分层 KV cache LMCache(DRAM / SSD)、Mooncake Tutti 用 GPU io_uring 替代 GDS,性能与可扩展性更高
GPU Direct Storage NVIDIA GDS Tutti 把 GDS 仍依赖的 CPU I/O 发起路径去掉,更进一步
Disaggregated 推理 DistServe、Splitwise Tutti 与 prefill/decode 分离天然契合
KV 压缩 / 量化 KIVI、KVQuant 正交路线;Tutti 在不压缩的前提下走大容量

可以理解为:Tutti 是「KV cache 出 HBM」这条路线上的"GPU-centric 终态",把 CPU-centric 的最后一道卡口打掉。

七、适合谁读

  • 做 LLM serving 系统、推理引擎优化的工程师:直接可借鉴的工程设计;
  • 做存储/操作系统结合 AI workload 工作的同学:GPU io_uring 是一个值得推广的范式;
  • 长上下文 SaaS(代码助手、文档问答、企业 RAG)的架构师:容量规划与成本模型需要重写;
  • 不适合只关心算法层、不关心硬件栈的人:Tutti 的价值在系统而非算法;
  • 不适合"我只有 CPU/GPU 没有 NVMe"的场景——这条路线前提就是 NVMe 作为主力。

工程落地与核查(Jay)

1. 事实核查结果

核查项 结论 风险
论文标题与作者 ✅ 实为 arXiv 2605.03375,Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving,作者含 Shi Qiu 等
78.3% TTFT 降低 ✅ 引自摘要;⚠️ 原文未给出 GPU 型号(A100/H100/H200 未明确)、SSD 型号、batch size、SLO 阈值 :数字无法独立复现
2× 请求率提升 ✅ 引自摘要,⚠️ 同上缺少硬件配置
27% 服务成本下降 ✅ 引自摘要;⚠️ 未定义"成本"口径(GPU 机时费/美元/每 token?)
与 vLLM 集成 ✅ 论文正文称已集成;⚠️ GitHub URL 未在摘要/paper card 给出,需自行核查 低-中
GPU io_uring 技术细节 ⚠️ 摘要仅一句话;完整实现细节需读原文 §3 待读原文

2. 生产落地关键坑

坑 1:GDS 硬件依赖——A100 以下基本不能用 Tutti 依赖 GPU ↔ NVMe peer-to-peer DMA,当前只有 Ampere(RTX 3090/A6000 不支持)和 Hopper(H100/H200)系列确认可用。国内常见的 4090 / A10 / T4 等消费/入门级 GPU 均不支持 GDS。选型前必须确认 NVMe 直连 GPU 的硬件拓扑。

坑 2:io_uring kernel 版本门槛 GPU io_uring 依赖 Linux kernel 5.1+ 的 io_uring 接口,以及支持 GPU 端提交队列的 driver(通常需要 NVIDIA driver 535+)。企业内网低内核版本(< 5.1)机器直接不可用,升级 kernel 有运维风险。

坑 3:元数据表显存开销被低估 KV Object 元数据表放在 GPU 显存里,论文未给出具体开销估算。实测时需要监控:nvidia-smi 观察 GPU memory usage,nvtop 观察 GPU ←→ NVMe 的实际吞吐。若元数据表膨胀,轻则挤占 attention 计算显存,重则 OOM。

坑 4:slack-aware 调度的 SLO 阈值需要 workload profiling Slack threshold 是 Tutti 最关键的调参项——设太高导致预取过于保守(GPU 仍会饿),设太低导致 I/O 与计算竞争(GPU 计算降速)。论文未给出推荐的 threshold 数值,生产环境需要用实际请求 trace 做 profiling,找到 latency SLO 与吞吐的帕累托最优点。

坑 5:多 GPU 跨节点 KV cache 共享未覆盖 如果 serving 集群有多 GPU 跨节点场景(如张量并行),Tutti 的 KV Object 只管理单机 NVMe,跨节点 SSD cache 一致性(哪个节点缓存哪个 KV Object)需要额外设计。这个场景论文未讨论,生产 TP 场景直接用需要自己补。

坑 6:SSD 写入寿命 KV cache 是"写多读多"的工作负载(prefix 不断生成、evict、reload),高 QPS 场景下 SSD 写入量很大。生产部署必须监控 SSD DWPD(Device Writes Per Day)指标,选择企业级 NVMe(如 Samsung PM1733、Intel D7-P5510),消费级 NVMe 寿命扛不住。

3. 可复现性自查清单

□ kernel ≥ 5.1,确认:uname -r
□ NVIDIA driver ≥ 535,确认:nvidia-smi | grep Driver
□ GPU 支持 GDS(仅 Ampere+/Hopper+):lspci | grep -i nvidia
□ NVMe SSD 支持 PCI-e 4.0 x4 及以上
□ vLLM 版本 ≥ 0.4(确认 GPU offload 路径已打通)
□ 做一次 workload trace 摸清 slack 阈值
□ 确认 SSD DWPD 指标满足 KV cache 写入量
□ 多 GPU 场景:自行设计 KV Object 跨节点缓存一致性方案

4. 评分理由

3 分(机制扎实,工程路径明确,但核心数字缺硬件配置、生产级坑未覆盖)。亮点是 GPU io_uring 思路清晰,slack-aware 调度有实战价值;但 78.3% / 2× / 27% 三个核心数字均未绑定硬件配置,生产环境无法直接复用。补上硬件配置与坑位分析后接近 4 分。