Mooncake Transfer Engine(简称 TE)是 Mooncake 数据平面的基础组件。它不负责决定 KV Cache 应该放在哪里,而是提供统一接口,把一段本地内存搬到远端 Segment,或者从远端 Segment 读取到本地内存。
本文基于 Mooncake 官方仓库:
commit: 777cc7782417b6e554cf7c2d53210d0d8f89f5cc
commit date: 2026-08-21
当前仓库同时保留经典 Transfer Engine 和下一代 TENT。前三篇先阅读经典实现,第四篇再分析 TENT 如何重构控制面、调度和传输后端。
一、源码布局
核心目录为 mooncake-transfer-engine/:
| 路径 | 作用 |
|---|---|
include/transfer_engine.h | 对外 C++ API |
include/transfer_engine_impl.h | 经典实现内部接口 |
include/transport/transport.h | Transport 抽象、请求和状态 |
include/transfer_metadata.h | Segment、Buffer 与元数据接口 |
src/transfer_engine.cpp | 公共 API 转发层,同时兼容经典 TE 与 TENT |
src/transfer_engine_impl.cpp | 初始化、内存注册、Segment 与批次管理 |
src/multi_transport.cpp | 安装和选择具体 Transport |
src/transport/ | RDMA、TCP、NVLink、NVMe-oF、EFA 等后端 |
tent/ | Transfer Engine Next 实现 |
TE 的经典架构可以概括为:
应用 / Mooncake Store
│
▼
TransferEngine
│
▼
TransferEngineImpl
┌────┼──────────────┐
▼ ▼ ▼
Metadata MultiTransport Batch 生命周期
│
┌──────┼───────────────┐
▼ ▼ ▼ ▼
RDMA TCP NVLink NVMe-oF ...
二、公共 API 是一层门面
TransferEngine 类本身很薄,大多数函数转发给 TransferEngineImpl:
init
openSegment / closeSegment
registerLocalMemory / unregisterLocalMemory
allocateBatchID / freeBatchID
submitTransfer
getTransferStatus
installTransport
这层门面还有一个重要职责:根据 use_tent_ 把同一套兼容 API 转发到经典实现或 TENT。
例如 init():
经典模式 -> impl_->init(...)
TENT 模式 -> 构造 tent::Config -> tent::TransferEngine
因此从应用视角看,迁移到 TENT 不一定要一次性改写全部调用代码,但部分经典接口在 TENT 下会成为空操作或采用不同语义,例如手动 installTransport() 不再是主要配置方式。
三、初始化过程
经典 TransferEngineImpl::init() 的主要工作为:
解析 metadata connection string
-> 创建 TransferMetadata
-> 确定本地 segment/server name
-> 探测本机拓扑、GPU 和 HCA
-> 启动 RPC communicator
-> 创建 MultiTransport
-> 注册本地 Segment 描述
-> 按配置自动安装可用 Transport
metadata connection string 支持显式指定后端类型,例如:
etcd
redis
http
也支持点对点 handshake 模式。连接字符串在 parseConnectionStringInternal() 中拆为协议和地址,再由元数据插件创建具体实现。
本地 server name 不是简单日志标签。它同时是 Segment 名称和远端发现键,其他进程通过它查询:
- Segment ID;
- RPC 地址;
- 支持协议;
- 已注册 Buffer;
- 设备拓扑与网络端点。
四、核心传输请求
对外请求类型来自 Transport::TransferRequest,其核心字段表达:
操作类型:READ / WRITE
本地地址:source 或 destination
目标 Segment ID
目标 offset
长度
这里的 offset 值得注意:经典 TE 的一些内存 Segment 使用远端虚拟地址语义,而 CXL、NVMe-oF 等后端可能使用不同地址解释。MultiTransport::selectTransport() 会结合 SegmentDesc 和 BufferDesc 判断请求落在哪个注册区间。
典型写请求可表示为:
local_ptr + length
--WRITE-->
target_segment_id + target_offset
读请求则反向搬运:
target_segment_id + target_offset
--READ-->
local_ptr + length
TE 只搬运字节,不理解 KV Cache 的 token、layer 或 tensor 语义。上层必须把逻辑对象映射为地址范围。
五、BatchID 不是网络请求 ID
应用先调用:
BatchID id = engine.allocateBatchID(requests.size());
engine.submitTransfer(id, requests);
BatchID 对应一组传输任务的本地状态容器。每个 task 有独立状态,批次也可以汇总状态。
请求状态通常经历:
WAITING
-> PENDING
-> COMPLETED
或 FAILED / CANCELED
不同 Transport 内部的队列和状态实现不同,但公共 API 通过 getTransferStatus() 和 getBatchTransferStatus() 统一暴露。
批次设计的价值包括:
- 一次提交多个 KV Cache slice;
- 分摊 API 和锁开销;
- 允许后端按 NIC、QP 或 lane 并行调度;
- 统一跟踪完成与错误;
- 支持完成后发送 notify。
六、SegmentHandle 与 SegmentID
应用通常通过名称打开远端 Segment:
SegmentHandle handle = engine.openSegment("decode-node-7");
openSegment() 查询元数据,将名称解析为 SegmentDesc,并维护本地缓存或引用。请求真正携带的是 Segment ID。
区分两者很重要:
- Segment 名称面向部署和服务发现;
- Segment ID 是运行期标识;
- SegmentHandle 是调用侧管理对象;
- BufferDesc 才描述可访问地址区间。
closeSegment() 释放本地引用或缓存,不等于删除远端 Segment。删除本地 Segment 使用单独的 removeLocalSegment()。
七、MultiTransport 的职责
MultiTransport 不是一种传输协议,而是后端工厂和路由器。
installTransport(proto) 根据构建宏创建具体对象,例如:
rdma
tcp
nvlink
nvmeof
cxl
efa
cxi
hip
musa
maca
随后调用:
transport->install(local_server_name, metadata, topology);
具体后端在 install 阶段建立设备上下文、worker、listener、endpoint cache 或连接资源。
selectTransport() 根据目标 Segment 的协议选择后端。当前源码还支持 multi-protocol Segment:同一个 Segment 中不同 Buffer 可以归属于不同协议,例如 GPU KV pool 同时注册到 HIP 和 RDMA。
路由器会结合:
- 目标 offset 落在哪个 Buffer;
- Buffer 的 protocol;
- 是否同主机、GPU IPC 是否可达;
- 固定协议优先级;
- 禁用某种后端的环境配置。
例如同节点优先 GPU IPC,跨节点自动跳过 HIP,回退 RDMA。
八、一次调用的完整骨架
应用创建 TransferEngine
-> init(metadata, local_name, address)
-> registerLocalMemory(local_buffer)
-> openSegment(remote_name)
-> allocateBatchID(N)
-> 构造 N 个 TransferRequest
-> submitTransfer(batch_id, requests)
-> TransferEngineImpl
-> MultiTransport::selectTransport
-> Transport::submitTransfer
-> 后端队列 / 网络操作
-> getTransferStatus
-> freeBatchID
-> closeSegment
-> unregisterLocalMemory
传输前必须注册本地内存,因为高性能后端需要预先建立 MR、GPU IPC handle、DMA-BUF 或其他设备映射。TCP 虽不一定需要 RDMA MR,也遵循统一注册接口以保持元数据一致。
九、通知机制
submitTransferWithNotify() 在传输完成后向远端发送通知。TE 还提供:
sendNotifyByID
sendNotifyByName
getNotifies
数据搬运和“数据已经可消费”的控制消息是两件事。上层不能只根据本地提交成功就假设远端业务已经开始使用;notify 为生产者—消费者协议提供轻量控制面。
十、这层抽象的边界
Transfer Engine 负责:
- 注册可传输内存;
- 发布和发现 Segment;
- 选择传输后端;
- 提交异步读写;
- 跟踪状态与通知。
它不负责:
- KV Cache 的 key、淘汰和副本策略;
- 推理请求调度;
- tensor layout 设计;
- 自动保证上层对象一致性;
- 在所有环境都实现 GPU 零拷贝。
下一篇将深入 TransferMetadata、SegmentDesc 和 BufferDesc,解释一块内存如何从本地指针变成集群中可发现、可路由的远端地址空间。
参考源码
include/transfer_engine.hinclude/transfer_engine_impl.hinclude/transport/transport.hsrc/transfer_engine.cppsrc/transfer_engine_impl.cppsrc/multi_transport.cpp