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.hTransport 抽象、请求和状态
include/transfer_metadata.hSegment、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 零拷贝。

下一篇将深入 TransferMetadataSegmentDescBufferDesc,解释一块内存如何从本地指针变成集群中可发现、可路由的远端地址空间。

参考源码

  • include/transfer_engine.h
  • include/transfer_engine_impl.h
  • include/transport/transport.h
  • src/transfer_engine.cpp
  • src/transfer_engine_impl.cpp
  • src/multi_transport.cpp