跳转至

笔记

vLLM KV Offloading and GDS

导言

我之前在 verl 里见过两种 vLLM offload 级别,后来又接触到 vLLM 的 KV offloading。再往 Mooncake 看,项目里似乎已经有 GDS。于是看到 vLLM Issue #48504 还要做 “GDS + KV offloading” 时,一个很自然的困惑出现了:这些机制是不是在重复做同一件事?

真正需要拆开的不是两个实现,而是三种不同的 offload 语义,以及两类不同的 GPU Direct。本文沿着实际数据路径,解释当前 vLLM 会自动做什么、Mooncake 怎样通过 Connector、Store 与 Transfer Engine 的解耦透明接入 GDS,以及 #48504 为什么仍然有独立价值。

Mooncake KV Metadata

导言

最初的问题很直接:Mooncake 管理 KV Cache,那么“KV Cache 的元数据”能否选择放在内存或 SSD?继续追问后才发现,元数据这个词把几种完全不同的东西揉在了一起:PagedAttention 的 Block Table、Master 保存的全局副本目录、SSD backend 保存的文件位置索引,以及为了故障恢复生成的 snapshot。

只有先回答“哪一种元数据真正负责定位 KV Cache 数据页”,才有可能讨论它应该放在哪里。本文基于 Mooncake 固定版本 0518784d,沿代码中的两级定位链路展开,再解释 Master snapshot 如何以较短在线停顿保存控制面状态,以及它为什么不能替代 KV payload 的持久化。

导言

一台服务器装有八张 400 Gbps NIC,不等于任意一张 GPU 都能拿到八张网卡的总带宽。现有 GPU 集群通常把 GPU 与“最近的”NIC 静态绑定;当 LLM 推理请求、MoE token 或推荐模型 embedding 产生不均衡流量时,热点 GPU 会堵在一张 NIC 上,旁边的 NIC 却可能空闲。

OSDI 2025 论文 FuseLink 的核心思想,是把 NVLink/NVSwitch 从“服务器内部的 GPU 互连”重新解释为“跨服务器网络的数据路径延伸”:流量先经 NVLink 到达更适合访问空闲 NIC 的中继 GPU,再通过 RDMA 发往远端。本文沿论文 Figure 1、2、3、4、5、8 还原这条设计链,并用其余实验结果说明收益、代价与边界。

NVIDIA SCADA

导言

GPUDirect Storage 已经能让 NVMe 和 GPU Memory 直接 DMA,为什么 NVIDIA 还要再做一套 SCADA?答案藏在“谁在运行时才知道下一块数据在哪里”这个问题里。

cuFile 适合由 CPU 或上层运行时提交已经成形的文件 I/O;图遍历、GNN 采样和磁盘型向量索引却可能让数十万个 GPU 线程各自发现下一个 offset。SCADA(SCaled Accelerated Data Access)试图把这些细粒度、数据依赖型请求留在 GPU 内核中:先经过 HBM 软件缓存与请求合并,再交给可信服务器访问本地或远端 NVMe。

一句话判断:SCADA 改变的首先是存储控制路径,其次才是数据路径。 它不是 GDS 的新名字,也还不是一个可直接下载部署的通用产品。公开论文已经证明 GPU-initiated storage 的基本机制,2024—2026 年的演讲和硬件演示展示了 SCADA 的扩展方向;但 API、部署要求和端到端生产数据仍处于有限公开阶段。

GPU-Centric Communication

导言

“GPU-aware” 很容易让人产生一个错觉:只要通信库能够接收 GPU buffer,数据就会完全绕开 CPU,通信也已经由 GPU 自主推进。现实并没有这么整齐。API 可能从 CPU 发起,数据却直接流向 NIC;GPU 也可能负责触发传输,但报文仍由 CPU 预先构造。

The Landscape of GPU-Centric Communication 给出的关键视角,是把通信拆成 API、报文构造、触发和数据路径,再观察每项责任究竟落在 CPU、GPU 还是 NIC。沿着这套坐标回看二十年演进,会发现主线不是“带宽越来越高”,而是先消除冗余拷贝,再缩短数据路径,最后把控制权逐步迁到 GPU

GPU-Initiated I/O

导言

把 SSD 数据送进 GPU,最直观的优化似乎是“绕过 CPU”。但这句话混合了两个不同问题:数据是否经过 CPU 内存,以及 I/O 请求究竟由 CPU 还是 GPU 发起。NVIDIA GDS 解决前者,BaM 进一步改变后者;代价是原本由 CPU 承担的队列管理、并发和缓存压力转移到了 GPU。

本文整理 DaMoN 2025 论文 Path to GPU-Initiated I/O for Data-Intensive Systems:先沿 Figure 1 比较五条 GPU-centric Storage 路径,再还原 BaM、GDS 与 SPDK 的实验边界。核心结论不是“GPU 发起一定更快”,而是系统应根据 CPU、GPU、PCIe、SSD 与数据复用状态,选择由谁支付 I/O 控制成本。

Mooncake File vs Block Device

导言

我最开始想确认的是一个很具体的问题:Mooncake Classic TE 和 TENT 打开的究竟都是文件系统普通文件,还是也能把 /dev/nvme0n1 这样的 SSD 裸块设备交给 GDS?源码里两边都会调用 open(path, O_DIRECT),NVIDIA cuFile 又确实接受 device fd,看起来答案应该是“可以”。继续向上追到 Segment 层后,结论却发生了分叉。

这篇文章把这条调用链从头串起来:先解释普通文件、块设备、S_ISREGS_ISBLK,再看 GDS 提供了什么 API、Mooncake 实际用了什么,最后落到 cuFileBatchIOSubmit 的同一组参数为什么在两个场景里具有不同含义。核心判断是:GDS 统一了 I/O 接口,却没有替 Mooncake 补上块设备的容量、对齐、越界和独占语义。

Mooncake Classic vs TENT Engine

导言

看到“应用必须持有 Transport*”时,我真正卡住的是:持有到底是什么意思?为什么直接保存一个后端指针就能工作,统一的 TransferEngine 反而走不通?

把这个词拆开后,Classic 与 TENT 的差异就不再只是两套接口。Classic 的公共 Batch 先于后端选择而创建,后端私有状态缺少稳定的安放位置;TENT 则在选定后端后,为每个 transport 创建独立 SubBatch,再由统一运行时协调提交、状态与回收。

因此,判断一个 Engine 是否容易扩展多个后端,不能只数有多少个 Transport 子类。真正要问的是:后端的私有状态放在哪里,由谁创建,又由谁保证它完成整个异步生命周期。

Mooncake Classic NVMeoF Transport

导言

在分析 Mooncake 的 NDS 接入位置时,经典 NVMeoFTransport 很容易被误认为 TENT GdsTransport 的前一版:二者都注册 buffer 和文件 handle,都使用 cuFile Batch API,也都维护异步完成事件。

但这条旧路径真正特殊的地方不在 cuFile,而在 Batch 所有权。可运行的调用必须直接持有 Transport*,并始终走 xport->allocateBatchID → xport->submitTransfer → xport->getTransferStatus → xport->freeBatchID。一旦换成 engine->allocateBatchID → engine->submitTransfer,批次便由 MultiTransport 创建,NVMeoFTransport 无法附加私有 descriptor,最终返回 NotImplemented

本文把这条旧路径单独展开:先给出从 NVMe-oF 挂载到专用测试的 SOP,再画出可运行路径与断路分支,最后按执行顺序逐句解释 Batch 分配、请求切片、cuFile 提交、完成事件聚合与资源回收。

Mooncake Codebase Architecture

导言

上一篇文章从 FAST'25 论文出发,解释了 P/D 解耦、分布式 KV Cache 与调度机制。论文读懂以后,打开代码仓却很容易再次迷路:Connector、Mooncake Store、Transfer Engine、TENT 看起来像四个并列组件,实际却跨越 vLLM 与 Mooncake 两个仓库,并分别承担框架适配、对象管理、字节搬运和新传输内核

本文固定在 Mooncake 6a00c353 与 vLLM 5bbc58c0,从仓库结构、请求流、时序和关键类关系重新组织这些概念,最后给出一套可重复的源码走读与开发 SOP。