Memory Semantics vs RDMA
结论
内存语义相对 MPI 与 RDMA 的五个收益,可以先压缩成以下判断:
- 相对普通 MPI Send/Recv 更简单:应用不再逐次匹配
rank + tag + send buffer + recv buffer,而是发布一个可寻址窗口,再对地址执行 Put/Get/Load/Store。需要注意,MPI RMA 本身也已经提供单边 Put/Get,因此不能把“所有 MPI”都等同于双边消息。 - 减少应用可见的通信缓冲:生产者和消费者可直接操作注册窗口或对称内存,不必先把张量打包进独立 Send/Recv staging buffer。注册池、tile buffer、协议状态和必要的内部队列仍然存在,准确说法是减少 bounce 与显式 staging,不是“系统里没有 buffer”。
- 完成路径更接近计算流水:传统 Verbs 以 WQE 提交、CQE 或 event 完成为主;MTE、Load/Store Engine 等专用搬运流水可以用 stream event、数据依赖或 tile-ready 信号驱动计算。RDMA 本身同样有硬件卸载,差别在硬件放置位置与完成语义,不是“有硬件对无硬件”。
- Scale-Up 域内可缩短外围链路:外围 RNIC 路径通常包含 WQE、doorbell、QP 状态、PCIe/RNIC DMA 与 CQE;片上或片间控制器可让计算侧更直接地调度存储侧。GPUDirect RDMA 已能绕过 host bounce,因此这里比较的是进一步减少 RNIC/QP/CQE/PCIe handoff,不是声称所有 RDMA 都经过 CPU 拷贝。
- 更自然地支持细粒度边传边算:消费者可以等待
tile i ready,而非把一个完整 tensor 对应的元操作完成作为唯一边界。RDMA 也能提交小 WQE、使用 unsignaled completion 并并发多个请求;内存语义的优势是把这种流水写成地址范围和计算依赖,而不是由应用维护更多队列事务。
MPI:匹配消息
MPI Point-to-Point 的核心对象是消息 envelope。发送方提供 source buffer、count、datatype、destination 和 tag,接收方提供 receive buffer、source 和 tag;运行时先完成匹配,再推进数据与请求状态。MPI-4.1 Point-to-Point
一个典型双边过程可以规范化为:
1 | def exchange_with_mpi(rank, peer, tensor, tensor_shape, dtype, communicator): |
这里不意味着 MPI 必然执行两次真实内存拷贝。若 tensor 已经连续、实现支持设备内存或协议能直接使用用户 buffer,pack/unpack 可以被省略或融合。问题在于应用仍需围绕双方 buffer、消息匹配和 request 生命周期组织控制流。
其延迟可以用一个非精确但有助于定位成本的分解表示:
$$
T_{\text{MPI}}
\approx
T_{\text{pack}}
+T_{\text{match}}
+T_{\text{transport}}
+T_{\text{unpack}}
+T_{\text{wait}}.
$$
MPI RMA 已经提供 Window、Put、Get、Accumulate 和同步 epoch,使 origin 可以单边访问 target window。MPI-4.1 RMA Communication Calls MPI-4.1 RMA Synchronization 因此,“内存语义比 MPI 简单”应限定为:相对普通 Point-to-Point 消息匹配更直接,同时把单边寻址进一步做成跨 CPU、NPU 和存储介质的统一数据面。
RDMA:提交远端事务
RDMA 将远端 CPU 从逐次收包和拷贝中移出数据面。应用注册 Memory Region,交换 remote_addr + rkey,构造包含 SGE、opcode 和远端地址的 Work Request,再提交到 Queue Pair。完成结果由 Completion Queue 返回。ibv_post_send(3) ibv_poll_cq(3)
1 | def write_with_rdma(context, queue_pair, completion_queue, local_tensor, remote_ref): |
RDMA 的事务化接口带来成熟的可靠传输、隔离、路由和生态,也引入一组应用或运行时要维护的对象:MR、QP、SGE、WQE、CQE、doorbell、重试与错误状态。一个简化延迟模型是:
$$
T_{\text{RDMA}}
\approx
T_{\text{reg,amortized}}
+T_{\text{post}}
+T_{\text{queue}}
+T_{\text{fabric}}
+T_{\text{remote-memory}}
+T_{\text{CQE}}.
$$
内存语义:用地址表达位置
内存语义把控制面与数据面拆开:
- 控制面发布
GVA/对称地址 + size + dtype + generation + 权限; - 数据面按地址范围执行 Load、Store、Get、Put 或 xcopy;
- runtime 根据源、目的和拓扑选择 MTE、URMA、RDMA 或 host copy;
- 完成通过 event、signal、fence 或 completion handle 发布;
- 地址引用在所有消费者和 in-flight tile 完成前保持有效。
UnifiedBus 的公开资料将内存编程模型作为 SuperPoD 互联的重要组成;MemFabric 则公开介绍了以 Global Virtual Address 和 xcopy 类接口统一多种搬运引擎的设计。UnifiedBus Huawei UnifiedBus launch MemFabric
一个支持 tile pipeline 的上层接口可以是:
1 | def consume_remote_tensor(memory_runtime, stream, remote_ref, tile_bytes): |
这个接口并没有消灭底层 RDMA。跨更远的节点时,runtime 仍可能把 get_async 编译成 WQE;在 HCCS 连接的设备间,它也可能选择 MTE。收益是上层控制流围绕地址、tile 与依赖不变,物理 route 由 runtime 适配。
简化后的成本模型是:
$$
T_{\text{MEM}}
\approx
T_{\text{map,amortized}}
+T_{\text{issue}}
+T_{\text{fabric}}
+T_{\text{remote-memory}}
+T_{\text{order}}.
$$
相对 RDMA,主要减少机会位于 post/queue/completion 一侧;映射、权限、顺序和故障处理并不会消失。
MTE:搬运进入流水
华为 Ascend 文档将 MTE 定义为 AI Core 的 Memory Transfer Engine / Load-Store Unit。常见流水中,MTE1 负责 L1 与 L0 之间的数据移动,MTE2 负责 Global Memory 到 Local Memory,MTE3 负责 Local Memory 到 Global Memory;不同搬运流水可与 Vector/Cube 计算流水并行,并通过 event 保证依赖。Ascend MTE glossary Ascend C pipeline
Ascend SHMEM 的 stream API 在固定版本代码中会根据拓扑选择路径:HCCS 可用时走 MTE,其他支持场景走 RDMA;其 device API 还提供 remote GM 与 local UB 之间的非阻塞 MTE Put/Get,包括非连续搬运参数。这说明 MTE 可以参与统一内存语义的数据面,但 MTE 本身不是这个语义层。Ascend SHMEM repository
双缓冲流水可以写成:
1 | def mte_double_buffer_pipeline(source, destination, tile_count, tile_shape): |
五个收益的准确说法
| 用户直觉 | 准确的工程表述 | 仍然存在的成本 |
|---|---|---|
| 相对 MPI 更简单 | 相对普通 Send/Recv,不再逐次匹配 rank/tag 和双方 buffer;对地址窗口执行单边访问 | MPI RMA 已有相似抽象;窗口同步、权限和 epoch 仍要管理 |
| 无通信 buffer | 无应用可见的独立 Send/Recv staging 或 host bounce;可直接访问注册/对称窗口 | 注册池、UB tile、协议状态、网络 credit 和必要队列仍存在 |
| 不同于 CQE,有专用硬件 | 搬运引擎更靠近计算与内存流水,用 event/依赖驱动 tile 可见性 | RDMA 也有 RNIC 硬件卸载;错误与最终完成仍需状态对象 |
| 绕过 NIC-GM-cache 长链路 | 在适用 Scale-Up 拓扑中,可减少外围 RNIC、QP/CQ、PCIe 和多次域切换 | GPUDirect 已绕 host bounce;远距离仍需 NIC/transport,cache coherence 也非自动获得 |
| 不等整个元操作结束 | 以 cache line、burst 或 tile 发布 ready,消费者可对已到达部分开算 | RDMA 也可小包、并发和 unsignaled;过细粒度会被固定开销、乱序和事件流量反噬 |
这五点背后的共同变化是:
1 | MPI: identity(rank, tag) + two-sided buffers |
细粒度为何能边传边算
假设一个 tensor 被切成 $N$ 个 tile。整体搬完再计算的时间近似为:
$$
T_{\text{bulk}}
\approx
\sum_{i=0}^{N-1} T_{\text{move},i}
+
\sum_{i=0}^{N-1} T_{\text{compute},i}.
$$
双缓冲流水稳定后,则近似为:
$$
T_{\text{pipeline}}
\approx
T_{\text{fill}}
+
\sum_{i=0}^{N-1}
\max\left(T_{\text{move},i},T_{\text{compute},i}\right)
+
T_{\text{drain}}.
$$
真正关键的不是传输 API 名字,而是完成边界是否与消费者所需的最小数据单元对齐:
- 生产者发布
tile i的地址范围和 generation; - 搬运引擎只拉取或写入这个 tile;
- 写入完成后先执行必要的 memory ordering;
- 再发布
ready[i]; - 消费者只等待
ready[i],开始计算; tile i+1同时在搬运;- 最后一个消费者完成后,slot 才能被复用。
若仍以整个 tensor 的 CQE 或全局 barrier 作为可见性边界,即使链路带宽很高,也会形成头部阻塞。反过来,tile 太小会让寻址、event、packet header、credit 和乱序管理占比升高,因此必须通过实测找到 crossover,而不是默认越细越好。
路径证据
OpenURMA 给出了一个可以检查机制的公开实现:它把 UB Controller 放在 on-chip bus,与外围 PCIe RNIC 路径对照,并将 Load/Store 短路径与 URMA Work Request 路径分开。OpenURMA arXiv
其 Figure 19 在作者控制的 matched model 中设置单向链路延迟 100 ns、并发 1、payload 64 B(barrier 为 8 B)。pointer chase 的 UB Load/Store 路径为 500 ns,RoCE DMA-WQE 路径为 2186 ns;bulk write 和 FAA barrier 也展示了相同实现内不同路径的固定成本差异。
适用边界
更有优势的场景
- Scale-Up 域内短操作:控制器与计算、内存处在更近的硬件域,外围队列与 PCIe handoff 占比较高。
- 多消费者复用:控制面传播引用,消费者按需读取字段或 tile,避免重复序列化整对象。
- 异步生产消费:数据字段在不同时刻 ready,消费者只等待自己的地址范围。
- 多模态与 RL 数据面:图像、视频、token、mask、logprob、value 和 reward 的大小与到达时间不同,适合 field/tile 级可见性。
- 通信计算融合:搬运与 Vector/Cube 流水有显式事件,可以稳定执行
move i+1 // compute i // store i-1。
RDMA 更稳妥的场景
- Scale-Out 与跨厂商互通:网络距离长、故障面大,需要成熟的可靠传输、拥塞控制、隔离与运维工具。
- 大块顺序搬运:payload 足够大时,队列与完成固定成本被摊薄,带宽而非提交延迟占主导。
- 现有系统已围绕 Verbs 优化:使用 registered pool、BlueFlame、inline data、unsignaled completion、batch posting 和 GPUDirect 后,迁移收益可能有限。
- 需要成熟错误语义:地址语义若没有 generation、lease、权限、完成与恢复协议,容易把显式队列错误变成更隐蔽的 stale pointer 与提前复用。
对 RL 基础设施而言,合理架构不是删除 RDMA,而是:
1 | TensorRef / FieldRef API |
这一结论也直接约束 VeRL TransferQueue 的后端设计:TransferQueue 继续管理 key、field、ready、consumed 和 retry;内存 runtime 管理地址、搬运、完成、lease 与 generation;MTE/RDMA 只是可替换的执行引擎。
总结
内存语义的核心价值不是发明另一种“更快的 memcpy”,而是把通信的第一性对象从消息或队列事务改成可寻址数据与依赖关系。
- 对 MPI Point-to-Point,它减少双方匹配和显式 staging 的编排;
- 对 RDMA Verbs,它把 QP/WQE/CQE 隐藏在 topology-aware runtime 之下;
- 对 MTE,它让搬运成为计算侧流水的一部分;
- 对异步 RL 和多模态,它允许 field/tile 先到先算、按需复用;
- 对整个系统,它同时要求更严格的地址代际、可见性、租约、权限和故障恢复。
最终是否更快,取决于 payload、拓扑、固定提交成本、内存位置、复用次数和计算重叠。最值得验证的不是单次峰值带宽,而是同一业务 workload 下的 controller RSS、bounce bytes、首 tile 延迟、pipeline overlap、尾延迟与故障恢复。