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)
其中:
addr和length定义地址范围;location告诉后端如何注册;remote_accessible决定是否公开给远端;update_metadata控制是否立即更新 Segment 描述。
批量注册接口可以在多个区域注册完成后统一更新元数据,避免每块 buffer 都触发一次远端写入。
四、注册调用链
经典实现的主链为:
TransferEngine::registerLocalMemory
-> TransferEngineImpl::registerLocalMemory
-> 检查地址重叠和 location
-> MultiTransport / 各已安装 Transport 注册
-> Transport::registerLocalMemory
-> 产生 BufferDesc 或后端注册信息
-> TransferMetadata 更新本地 Segment
-> metadata store 发布
不同后端的注册行为不同:
| 后端 | 注册可能执行的动作 |
|---|---|
| RDMA | ibv_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.hsrc/transfer_metadata.cppsrc/transfer_metadata_plugin.cppsrc/transfer_engine_impl.cppsrc/memory_location.cppsrc/multi_transport.cppsrc/transport/rdma_transport/endpoint_store.cpp