Mooncake Transfer Engine 的核心抽象不是“远端指针”,而是 Segment + BufferDesc + Metadata。应用注册本地内存后,TE 将地址范围、设备位置和传输协议发布为 Segment 描述,其他节点才能定位并建立连接。

阅读版本:

Mooncake commit: 777cc7782417b6e554cf7c2d53210d0d8f89f5cc

一、为什么不能直接发送指针

一个进程中的地址:

0x7f12...

在另一个进程中通常没有意义。即使两台机器都使用相同数值,它们也不指向同一块物理内存。

高性能传输还需要额外信息:

  • 这是 CPU DRAM 还是 GPU VRAM;
  • 对应哪张 GPU;
  • 是否注册成 RDMA MR;
  • rkey 和网卡 endpoint 是什么;
  • 是否能用 NVLink、GPU IPC 或 CXL;
  • 节点当前是否存活;
  • 地址区间是否已经注销。

因此 TE 把地址空间包装成 Segment,并通过元数据服务交换描述。

二、SegmentDesc

TransferMetadata::SegmentDesc 是远端发现的中心结构,主要承载:

Segment ID
Segment 名称
协议或协议集合
本机 RPC / endpoint 信息
BufferDesc 列表
设备与拓扑信息
后端扩展元数据

可以把一个 Segment 理解成某个 TE 实例公开的可传输地址空间:

Segment: decode-node-7
├── Buffer A: CPU metadata pool, protocol=rdma
├── Buffer B: GPU KV pool, protocol=rdma
└── Buffer C: GPU KV pool, protocol=hip

同一块 GPU buffer 可以被多个后端注册,从而同时支持:

同机:HIP / CUDA IPC 快路径
跨机:RDMA 路径

当前 MultiTransport::selectTransport() 正是根据 Buffer 覆盖范围和可达性进行选择。

三、BufferDesc

BufferDesc 描述一段已注册区域,关键语义包括:

  • 起始地址或后端 offset;
  • 长度;
  • memory location;
  • protocol;
  • transport-specific key 或 handle;
  • 可能的设备标识。

内存位置由 memory_location 模块解析。典型值表示 CPU、CUDA GPU、ROCm、Ascend 等设备。

注册接口为:

registerLocalMemory(addr, length, location, remote_accessible, update_metadata)

其中:

  • addrlength 定义地址范围;
  • location 告诉后端如何注册;
  • remote_accessible 决定是否公开给远端;
  • update_metadata 控制是否立即更新 Segment 描述。

批量注册接口可以在多个区域注册完成后统一更新元数据,避免每块 buffer 都触发一次远端写入。

四、注册调用链

经典实现的主链为:

TransferEngine::registerLocalMemory
  -> TransferEngineImpl::registerLocalMemory
  -> 检查地址重叠和 location
  -> MultiTransport / 各已安装 Transport 注册
  -> Transport::registerLocalMemory
  -> 产生 BufferDesc 或后端注册信息
  -> TransferMetadata 更新本地 Segment
  -> metadata store 发布

不同后端的注册行为不同:

后端注册可能执行的动作
RDMAibv_reg_mr 或 GPU peer memory MR,记录 lkey/rkey
TCP登记可访问区间,主要用于统一校验和元数据
NVLink / HIP建立或导出 GPU IPC handle
NVMe-oF建立文件、offset 或 cuFile 相关描述
CXL记录共享地址窗口和 base offset

因此 registerLocalMemory 不是简单把指针塞进 map。它是为所有适用 Transport 准备数据面访问能力。

五、地址重叠检查

TransferEngine::checkOverlap() 和内部注册表用于阻止不一致的重叠区域。

如果:

Buffer A: [0x1000, 0x3000)
Buffer B: [0x2000, 0x4000)

它们在不同协议、不同设备位置或不同生命周期下可能产生歧义:给定一个 offset 时究竟选择哪个注册项?注销其中一项是否会破坏另一项?

当前 multi-protocol 设计允许同一区域按不同协议注册,但需要在描述和选择逻辑中明确归属,而不是无约束地重复注册。

六、元数据插件

TransferMetadata 并不把 etcd 写死在核心代码里。元数据实现通过插件和连接字符串选择,仓库包含或支持:

etcd
redis
http
p2p handshake

元数据层提供的核心能力包括:

  • 添加、更新和删除本地 Segment;
  • 按名称或 ID 查询远端 Segment;
  • 更新 Buffer 列表;
  • 发布拓扑、RPC 和 endpoint 数据;
  • 缓存 SegmentDesc;
  • 失效或同步缓存。

这是一条控制面路径。真正的大块 KV Cache 不会写入 etcd 或 Redis,元数据服务只保存描述和定位信息。

七、openSegment 与缓存

openSegment(name) 大致执行:

查本地 segment cache
  -> 未命中则访问 metadata store
  -> 解析 SegmentDesc
  -> 验证状态
  -> 建立 SegmentHandle / 引用
  -> 后端按需创建 endpoint

远端描述通常会缓存,减少每个 I/O 都访问元数据服务。syncSegmentCache() 用于显式同步,连接异常或远端重启后也需要使旧描述失效。

缓存带来典型分布式问题:

  • 远端注销内存后,本地仍持有旧 rkey;
  • 远端进程重启,Segment 名称不变但 endpoint 已变化;
  • 本地正在提交请求时描述被刷新;
  • 多线程同时 open 同一 Segment。

因此 RDMA endpoint 代码还实现了重新建立连接和错误恢复,不能把 metadata cache 当作永久真相。

八、注销流程

unregisterLocalMemory(addr) 的正确顺序不能只是删除元数据:

阻止新请求命中该区域
  -> 等待或拒绝仍在使用的请求
  -> 从各 Transport 注销 MR / IPC handle
  -> 更新 SegmentDesc
  -> 发布新元数据
  -> 释放本地注册项

如果先释放 MR,再让远端继续使用旧 rkey,可能触发远端访问错误;如果只删除元数据但旧 endpoint 仍缓存,问题同样存在。

经典实现依赖上层在生命周期边界正确协调。TENT 则进一步引入 SegmentManager、SegmentRegistry 和跟踪机制,把资源生命周期集中管理。

九、RPC communicator 与 notify

元数据服务适合保存相对稳定的描述,但不适合承担所有实时控制消息。TE 还启动 RPC communicator,用于:

  • 节点间握手;
  • notify;
  • 活性探测;
  • 某些 endpoint 协商;
  • P2P metadata 模式。

这形成两条控制路径:

Metadata Store:持久或可缓存的 Segment 描述
RPC:实时点对点控制消息

数据本身则走 RDMA、TCP、NVLink 等 Transport。

十、locality 与协议选择

TE 通过节点名称、地址和拓扑判断目标是否同机。对 multi-protocol Segment,选择逻辑会检查请求 offset 落在哪个 Buffer,并按优先级选择。

当前实现中可见类似优先关系:

GPU IPC 类后端 > CXL > RDMA > TCP

但 GPU IPC 只能用于同主机可达 GPU。跨主机时即使 Buffer 同时标记 HIP 和 RDMA,也必须跳过 HIP。

这种选择不是通用最优调度器,而是一套明确、可预测的规则。更复杂的链路质量、拥塞、故障和 QoS 决策,是 TENT 重构的重要目标。

十一、Segment 设计的价值

Segment 把三类信息绑定在一起:

身份:我是谁,怎样被发现
地址:我公开哪些 Buffer
能力:这些 Buffer 能通过哪些 Transport 访问

它让上层只需要表达:

向 segment X 的 offset Y 写入 N 字节

而不必直接管理:

远端网卡、QP、rkey、GPU IPC handle、TCP session、文件描述符……

下一篇将选择 RDMA 和 TCP 两个后端深入调用链:请求怎样被切片到不同 NIC,如何进入 worker queue,完成事件怎样回写 Batch 状态,以及出错后 endpoint 如何恢复。

参考源码

  • include/transfer_metadata.h
  • src/transfer_metadata.cpp
  • src/transfer_metadata_plugin.cpp
  • src/transfer_engine_impl.cpp
  • src/memory_location.cpp
  • src/multi_transport.cpp
  • src/transport/rdma_transport/endpoint_store.cpp