Skip to content

feat(data-plane): enable RDMA transport for TransferQueue - #256

Open
overloadedHenry wants to merge 31 commits into
redai-studio:mainfrom
overloadedHenry:feat/enable-tq-rdma
Open

feat(data-plane): enable RDMA transport for TransferQueue#256
overloadedHenry wants to merge 31 commits into
redai-studio:mainfrom
overloadedHenry:feat/enable-tq-rdma

Conversation

@overloadedHenry

@overloadedHenry overloadedHenry commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

【Task.026】Enable RDMA transport for the TransferQueue data plane

分支:feat/enable-tq-rdma(已 rebase 到 main 682d474
配套 PR:#278 —— pin 版本 TransferQueue 的运行时补丁,堆叠在本 PR 之上,按评审意见独立拆出

Closes #217

这个 PR 解决什么

Relax 的数据面(rollout → train 的样本传输)走 TransferQueue 的 SimpleStorage,即 ZMQ over TCP。在 RoCE 机器上,多模态 payload 全程用 TCP 搬运、RDMA 网卡闲置:Qwen3-VL 的 pixel_values 按默认 16384 token 预算单图就是 77 MB,32 图 batch 是 2.47 GB。

TransferQueue 本身已带 MooncakeStore 后端,protocol 可取 rdma,并支持 GDR staging、超大对象分块与重试。Relax 此前从未把它接出来:relax/core/controller.py 把后端固定为 SimpleStorage。本 PR 做的是配置接入、能力探测与一致回退,不是传输层实现——transfer_queue/ 零改动。

改了什么

一条默认关闭、不可用时安全回退的 RDMA 数据路径:

  • 四个只表达使用意图的 flag:--tq-storage-backend--tq-rdma-mode--tq-rdma-device--tq-use-gdrmooncake + off 表示 MooncakeStore/TCP(off 只关 RDMA 传输,不改后端)
  • driver 在第一次 tq.init 之前探测每个存活且有 GPU 的节点,AND 归约出 job 级唯一的 effective config,所有组件读同一份
  • Mooncake 生效前,driver 再向每个存活节点(不限 GPU——Serve replica 与 0-CPU actor 没有 placement 绑定)发起一次有界 attach 握手并立即 detach,失败汇总回 driver:auto 统一收敛 SimpleStorage,off/required 启动失败并列出节点
  • worker 侧 attach 有统一 deadline(默认 60 s):先有界等待 controller 提供配置,再在 watchdog 线程里跑 tq.init,超时该 worker 立刻失败而不是无限挂起
  • 分级降级:GDR → host RDMA → Mooncake/TCP → SimpleStorage;required 直接失败,不静默降级
  • hard_pin: true + 容量预检:按 token 预算推导最坏情况 payload(文本 seq_length × 32 B,多模态另加 seq_length × 784 像素/token × 12 B,8k 序列约 77 MiB/样本)对比 segment 大小
  • 每次 tq.init 前回收半初始化的 TransferQueueController(F10 防挂死);拆除时卸载 Mooncake segment;所有 worker teardown 钩子(Serve 组件 __del__RolloutManager.dispose、Megatron actor)立即 detach 本地客户端,不把死端点泄漏到 client_ttl 过期
  • 正确性:MC_STORE_MEMCPY 强制为 0 且 fail-closed(pin 的 mooncake 0.3.10 memcpy 路径已实证静默截断/SIGSEGV);启动前做只读能力校验(重试 API 存在性、put-before-notify 顺序)

默认参数(simple)与接入前逐行为一致:首次 tq.init 仍在 Controller 进程内执行、不创建 owner actor、拆除仍是裸 tq.close();唯一新增是 F10 reaper,且它只在遗留 controller 确证半初始化(会导致挂死)时才动作。

为什么这么设计

flag 只表达意图,不暴露 Mooncake 内部参数。 endpoint、buffer、segment、timeout、master 策略走内部默认与部署环境(见下方环境变量)。一对一暴露会让配置组合、文档和测试矩阵持续膨胀。

driver 决策一次,且必须在 tq.init 之前;生效前再用真实 attach 验证。 tq.init 会 attach 到已存在的 controller 并忽略传入的 conf,各 worker 各自决策会发散。/sys 探测覆盖不了实际调度位置,所以最终以每个存活节点的有界 attach 握手为准——验证的是真实 endpoint,不是能力启发式。

AND 归约,退化结果不丢弃。 探测任务超时或崩溃会转成退化结果让归约器降级,而不是被过滤掉、导致高报集群能力。

首期单任务独占集群。 不做多 job 并发、端口租约、master 共享。清理只动本作业拥有的资源:零 pkill;master 完全不碰(auto_init: false);健康的 controller 保持不动;attach 到他人 controller 的会话绝不替它做全局回退或拆除。

GDR 明确定性为实验性。 可用性无法在启动时探测(探测进程无 CUDA context),真实判定在各 worker 的 TQ 客户端内部,不满足则回退 host RDMA 并打 WARNING;--tq-rdma-mode=required 只覆盖传输层。

评审响应

评审意见 处理
memcpy 守卫可被 MC_STORE_MEMCPY=1 绕过(tq_correctness.py:179) fail-closed:显式设 1 启动即拒绝,强制 0;测试改为断言 fail-fast
loopback master 默认值导致多节点误降级(Codex P1) MC_MASTER_ADDRESS 必填,无 localhost 回退
多 HCA 机器首设备失败即整机降级(Codex P1) 扫描全部设备 × 端口 × GID,端口 ACTIVE 且 GID 可用联合通过才选定
Controller 构造失败泄漏 TQ owner(controller.py:216,Codex P1) 构造失败路径关闭 owner 并回收其拥有的 controller
worker 销毁不 detach Mooncake 客户端(Codex P1) detach_tq_client() 挂到全部 teardown 钩子;强杀场景退回 master TTL
默认路径仍建 owner、off 强制 SimpleStorage(controller.py:241 + Copilot) simple 恢复 upstream 等价路径(见上);mooncake+off = Mooncake/TCP
探测覆盖面 ≠ 实际 endpoint、attach 无超时(rdma_probe.py:338、tq_lifecycle.py:278) 有界 attach(RELAX_TQ_ATTACH_TIMEOUT_SECONDS)+ 全节点握手 + driver 汇总统一回退
tq.init 超时后 daemon thread 仍可能在复用的 Ray worker 中运行 handshake task 设置 max_calls=1max_retries=0,并补测试锁定一次性 worker isolation
segment 容量固定 8 MiB/样本严重低估(tq_config.py:155,Codex P1) 按 token 预算推导上界;RELAX_TQ_GLOBAL_SEGMENT_SIZE_GB 可调,容量校验与客户端配置读同一值
monkey patch 应拆独立 PR 并限定版本(tq_correctness.py:131) 拆到 #278(精确版本门控 + 删除条件);本 PR 只保留只读能力校验

环境变量

变量 语义
MC_MASTER_ADDRESS 必填,外部管理的 mooncake master host:port,每个节点都要设置
MC_STORE_MEMCPY 强制为 0;显式设 1 会被启动拒绝(pin 版本缺陷,升 pin 后按版本重新放开)
MC_TCP_ENABLE_CONNECTION_POOL Mooncake/TCP 长会话的部署与验收契约;C1 模式下 driver 和所有 worker 均设置为 1,复用 TCP connection,避免长矩阵运行耗尽临时端口;不影响 RDMA path
RELAX_TQ_ATTACH_TIMEOUT_SECONDS worker attach 统一 deadline,默认 60
RELAX_TQ_GLOBAL_SEGMENT_SIZE_GB 每客户端 segment 大小,默认 4 GiB

改动清单(分组)

区域 文件 内容
参数 relax/utils/arguments.py 四个意图 flag
编排 relax/core/controller.py _resolve_tq_backend / 默认路径短路 / attach 握手汇总 / 失败清理
探测 relax/utils/rdma_probe.py 节点能力检查、多 HCA 选择、集群探测与 AND 归约
配置 relax/utils/tq_config.py backend dict 构建、master 解析、token 预算容量预检
正确性 relax/utils/tq_correctness.py memcpy fail-closed + 只读能力校验(运行时补丁在 #278
生命周期 relax/utils/tq_lifecycle.py owner 事务、F10 reaper、有界 attach、全节点握手、detach
组件接入 relax/components/*relax/distributed/ray/rollout.pyrelax/backends/megatron/actor.py attach/detach 钩子
测试 tests/utils/test_rdma_probe.pytest_tq_failure_paths.pytest_tq_dataplane_behavior.py CPU 可跑的参数矩阵、失败路径、行为用例
文档/工具 docs/draft/transfer_queue_rdma.mdscripts/benchmarks/tq_cross_node_bench.py 使用指南与跨节点基准

测试

  • 当前 feat(data-plane): enable RDMA transport for TransferQueue #256 head:Relax cfe6153014a554e28c5e40a9002d054f44fee7b3;Lint、Pre-commit 和 Python 3.10/3.11/3.12 tests 全部通过
  • C0/C1/C2 双节点正式矩阵:每种模式 60/60 正式回合均通过 byte-exact 与 wire-proof;C1 的 driver 和所有 worker 均设置 MC_TCP_ENABLE_CONNECTION_POOL=1
  • 16 GPU training validation:实际运行口径为 --fully-asyncfully_async=Truehybrid=False
  • 运行时依赖:TransferQueue commit 58054a33834aadbcf76aacd6b1e32e25c030f2c9(package version 0.1.10.dev0);Mooncake package version 0.3.10.post2
  • 真机项(MooncakeStore 字节一致性、容量故障注入)需要多节点 GPU 与独立可丢弃 master,按仓库规则显式 skip 并在文档中给出运行方法

Copilot AI lite review requested due to automatic review settings August 11, 2026 05:15
@overloadedHenry

Copy link
Copy Markdown
Contributor Author

@gongshaotian 此处按计划做了第一轮实现。

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR wires up an RDMA-capable TransferQueue data plane in Relax by introducing intent-only CLI flags, probing RDMA/Mooncake capability across GPU nodes before tq.init, AND-reducing results into a job-unique effective config with graded fallback, and adding lifecycle helpers + tests/benchmarks/docs to validate failure paths and byte-exactness.

Changes:

  • Add TransferQueue RDMA intent flags and a driver-side pre-tq.init probe/reduction flow to select MooncakeStore (RDMA/TCP) or fall back to SimpleStorage.
  • Introduce reusable helpers for TransferQueue controller reaping and Mooncake segment unmounting to prevent hangs/leaks.
  • Add unit/integration tests, benchmarks, and operational docs covering degradation, retries, teardown ordering, and byte-exact round-trips.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
relax/utils/arguments.py Adds CLI flags for backend selection and RDMA/GDR intent.
relax/core/controller.py Adds backend resolution logic (probe → reduce → effective config) and uses lifecycle helpers on teardown.
relax/utils/rdma_probe.py Implements node probe + cluster fan-out + AND-reduction + config validation.
relax/utils/tq_config.py Centralizes TQ backend config building and segment-capacity precheck.
relax/utils/tq_lifecycle.py Adds controller reaping and Mooncake segment unmount teardown helpers.
tests/utils/test_rdma_probe.py CPU-only unit tests for validation, probing, reduction, and config building.
tests/utils/test_tq_failure_paths.py Failure-path tests for controller reaping, retry/raise behavior, degradation, and Mooncake byte-exactness (skipped when unavailable).
tests/utils/test_tq_dataplane_behavior.py Integration tests for SimpleStorage dataplane behavior contracts and byte-exactness.
scripts/benchmarks/tq_rdma_bench.py Single-node benchmark comparing SimpleStorage vs Mooncake/TCP vs Mooncake/RDMA.
scripts/benchmarks/tq_cross_node_bench.py Cross-node benchmark mirroring Relax’s persistent-actor usage and on-wire transport verification.
scripts/benchmarks/cross_node_rdma_bench.py Raw MooncakeDistributedStore benchmark to isolate transport-layer TCP vs RDMA.
docs/draft/transfer_queue_rdma.md Usage/ops guide including downgrade ladder, logs, and troubleshooting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread relax/core/controller.py Outdated
Comment on lines +209 to +216
# 2. SimpleStorage short-circuit (default, zero behavior change).
if backend == "simple" or mode == "off":
from relax.utils.tq_config import build_simple_storage_config

return build_simple_storage_config(
total_storage_size=total_storage_size,
num_data_storage_units=self.config.num_data_storage_units,
)
Comment thread tests/utils/test_tq_failure_paths.py Outdated
Comment on lines +151 to +155
manager = MagicMock()
if store_client is None:
del manager.storage_client # SimpleStorage manager has no storage_client
else:
manager.storage_client = store_client
Comment thread relax/utils/tq_config.py Outdated
Comment on lines +42 to +44
def build_simple_storage_config(total_storage_size: int, num_data_storage_units: int) -> dict[str, Any]:
"""Build the ``backend`` dict for SimpleStorage (current default
behavior)."""
Copilot AI review requested due to automatic review settings August 13, 2026 09:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (3)

relax/core/controller.py:247

  • --tq-storage-backend=mooncake --tq-rdma-mode=off currently short-circuits to SimpleStorage (because of if backend == "simple" or mode == "off"). This contradicts the CLI help text where off means "no RDMA" (i.e., MooncakeStore over TCP), and it also bypasses validate_mooncake_runtime_contract() for that configuration.
        # 2. SimpleStorage short-circuit (default, zero behavior change).
        if backend == "simple" or mode == "off":
            from relax.utils.tq_config import build_simple_storage_config

            return build_simple_storage_config(

relax/utils/tq_config.py:76

  • build_simple_storage_config is typed to require total_storage_size: int, but scripts/benchmarks/tq_cross_node_bench.py calls it with None to request unlimited capacity. The signature should reflect the actual accepted value to avoid type-checking drift.
def build_simple_storage_config(total_storage_size: int, num_data_storage_units: int) -> dict[str, Any]:

scripts/benchmarks/tq_rdma_bench.py:306

  • run_one() calls close_tq_and_wait() before any tq.init(), and close_tq_and_wait() calls ray.get_actor(...). Without a prior ray.init(), this benchmark will fail immediately with Ray not initialized (it only catches ValueError, not the runtime init error).
def main():
    """Run the benchmark across all requested payload/field/config
    combinations."""
    args = parse_args()

Copilot AI review requested due to automatic review settings August 13, 2026 12:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 13, 2026 22:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 14, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@overloadedHenry

overloadedHenry commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

TransferQueue RDMA 性能补充:双节点真实多模态载荷

结论摘要

  • syntheticmultimodalreal-multimodal 三类载荷、256 MiB/1 GiB/2 GiB/4 GiB 四个档位上,C0/C1/C2 共 36 个测量点全部逐字节 SHA-256 校验通过。
  • 所有测量点均满足 RDMA 相对 TCP 的性能门槛:C2/C1 最低为 2.1×,最高为 8.4×
  • RDMA 档位的网络计数器与载荷量匹配,且 IB 计数器增长、bond0 基本不增长;TCP 档则表现相反,完成了实际走线证明。
  • 测试过程中发现并修复了 mooncake 0.3.10 TCP memcpy 路径的静默截断问题。下表中的 C1 数据均来自 MC_STORE_MEMCPY=0 守卫开启后的正确性基线,旧的未守卫数据不应与本结果直接比较。

测试环境

项目 配置
拓扑 2 节点,节点 A 运行 driver/head/master,节点 B 运行 consumer;每节点 8 张 H800
GPU/RDMA 两端均具备 mlx5_2~mlx5_9mlx5_bond_0
系统限制 两端 memlock unlimited
Python 3.12.3
PyTorch 2.11.0+cu129
Ray 2.56.0
tensordict 0.10.0
transfer_queue 0.1.10.dev0
mooncake 0.3.10.post2
真实多模态模型 本地 Qwen3.5-4B

两端软件版本完全一致。测试通过 Ray runtime_env 临时分发本次 PR 的 relax 代码,对端仓库未做 checkout、rsync、stash 或其他读写操作。

测试对象与配置

配置

  • C0 — SimpleStorage:TransferQueue 的存储基线。
  • C1 — Mooncake/TCP:Mooncake TCP 数据面。
  • C2 — Mooncake/RDMA:Mooncake RDMA 数据面。

三种配置均通过 TransferQueue 完成 put/get,而不是绕过 TQ 直接调用底层存储。C2 使用 IB 计数器、C1 使用 bond0/TCP 计数器做线连证明。

载荷 profile

  • synthetic:原有合成稠密张量 profile,用于与 PR 既有结果对照。
  • multimodal:形状兼容的合成多模态 profile,覆盖 NonTensorStack/msgpack 非张量路径,但不依赖本地模型和数据集。
  • real-multimodal:从真实数据和模型处理链生成的生产代表性载荷。其结构是每个样本一个 dict,经过 dict_to_tensordict 后形成 NonTensorStack 列,实际覆盖多模态非张量慢路径。

真实 profile 的 fixture 由以下生产链路生成,不在测试中重新实现等价逻辑:

parquet prompt/image
  → build_messages
  → tokenizer.apply_chat_template
  → process_vision_info
  → sglang rollout image processor
  → multimodal_train_inputs
  → dict_to_tensordict
  → TransferQueue put/get

fixture 共 12 个样本、约 210 MiB,pixel_values 为真实 processor 生成的 fp32 数据,形状为 [2176~3840, 1536],prompt 长度为 623~1017 token。processor 双跑结果逐叶子字节一致。

测试参数

  • 每个测量点:1 次预热 + 5 次正式测量。
  • 档位:256 MiB、1 GiB、2 GiB、4 GiB。
  • 吞吐单位:GB/s。
  • get 阶段逐样本、逐叶子进行 SHA-256 校验。
  • 启用 --require-wire-proof,没有通过正确性或实际走线校验的结果不计入性能统计。
  • 每个协议在独立进程中运行,避免 mooncake 0.3.10 在同一进程反复创建不同协议 client 时的会话级不稳定。

正式实测结果

下表为 get 吞吐均值。C1 为 MC_STORE_MEMCPY=0 守卫开启后的正确性基线;带 ¹ 的数据是在重启 master 后补测。

Profile 载荷档位 C0 SimpleStorage C1 Mooncake/TCP C2 Mooncake/RDMA C2/C1
synthetic 256 MiB 2.26 0.93 2.12 2.3×
synthetic 1 GiB 1.25 0.93 2.25 2.4×
synthetic 2 GiB 1.32 0.91 2.36 2.6×
synthetic 4 GiB 1.33 0.93 4.61 5.0×
multimodal 256 MiB 1.73 0.78 1.61 2.1×
multimodal 1 GiB 1.40 0.82 2.26 2.8×
multimodal 2 GiB 1.49 0.86¹ 2.51 2.9×
multimodal 4 GiB 1.35 0.90¹ 2.60 2.9×
real-multimodal 256 MiB 1.93 1.10 2.83 2.6×
real-multimodal 1 GiB 2.57 1.10 3.11 2.8×
real-multimodal 2 GiB 3.21 1.03 2.91 2.8×
real-multimodal 4 GiB 1.98 1.00 8.36 8.4×

¹ multimodal 的 2 GiB 和 4 GiB TCP 数据在 master 重启后的新会话中补测;两档均逐字节校验通过。

结果解读

  • RDMA 读侧收益稳定:真实多模态 profile 下 C2/C1 为 2.6×、2.8×、2.8×、8.4×,全部超过 PR 要求的 1.2×。
  • 大档位更能体现 RDMA 优势:4 GiB 时,real-multimodal 的 C2 达到 8.36 GB/s,synthetic 达到 4.61 GB/s。更大的批量可以摊薄每个 key 的 MR 注册开销。
  • 真实多模态路径确实更受碎片化影响:每个样本包含多个字典叶子和非张量列,实际吞吐特征与合成稠密张量不同;因此 real-multimodal 结果是对生产负载更有代表性的补充,而不是稠密快路径结果的重复。
  • 写侧也有收益:各配置的 put 均值范围为 C2 4.112.9 GB/s、C1 1.52.6 GB/s、C0 1.4~2.5 GB/s。
  • 绝大多数档位的轮间标准差不超过 0.2 GB/s;C0 的 real-multimodal 小档偶发较大波动,最高标准差为 1.01 GB/s。

正确性与线连证明

本次验收不是只采集吞吐,而是将正确性作为性能数据的准入条件:

  1. 每个 get 结果按样本 ID 对齐。
  2. 对每个样本的每个叶子检查 dtype、形状和原始字节的 SHA-256。
  3. C2 检查对端 IB 计数器增长量与载荷匹配,且 bond0 计数器基本不增长。
  4. C1/C0 检查 TCP/bond0 计数器增长,确认流量没有错误地走 RDMA 或本地回环。

最终结果为 36/36 测量点 byte-exact PASS,wire-proof 全部通过

测试中发现并修复的 TCP 数据面问题

双节点正式矩阵首次运行时,TCP 会话出现了返回成功但内容被截断的情况:

  • 256 行、每行 1 MiB 的探针中有 138 行损坏。
  • 每个坏行从 64 KiB 对齐偏移开始尾部全零,前段字节正确,属于静默截断而非位翻转、乱序或串行化错误。
  • 全新 TCP 会话约有一半复现;RDMA 会话连续 60+ 回合未复现。
  • mooncake 日志显示 TCP-only 环境自动启用了 memcpy 快路径。
  • 默认配置约 6/13 个 TCP 会话出现损坏;设置 MC_STORE_MEMCPY=0 后 12/12 个会话全部干净。

修复在 ensure_mooncake_correctness_guards() 中使用:

os.environ.setdefault("MC_STORE_MEMCPY", "0")

该守卫在每个进程创建或附着 Mooncake client 前执行,默认关闭有问题的 memcpy 路径,同时尊重运维显式设置的环境变量。守卫对 RDMA 会话无影响,并增加了“默认置 0”和“显式值不覆盖”两条契约测试。

因此,守卫后的 C1 吞吐(0.781.10 GB/s)才是可用于比较的正确性基线。此前 1.21.7 GB/s 的部分 TCP 读数混入了未真正完成数据传输的损坏路径,不具备性能参考价值。

稳定性与复现注意事项

  • 长时间复用同一个 Mooncake master 后,曾在 multimodal 2 GiB 档遇到 batch_upsert_from 全批返回 -800;重启 master 后同档位恢复正常,2 GiB/4 GiB 分别以 0.86/0.90 GB/s 完成 byte-exact 验收。
  • 因此,正式长跑前建议启动全新的 master,并保留原始进程返回码和未过滤日志。
  • 测试结果和 CSV 留档于运行节点的 /tmp/tq_bench_run/results/;首轮重复结果在 /tmp/tq_bench_run/results_r3/
  • real-multimodal fixture 约 210 MiB,不纳入 git;通过 scripts/benchmarks/make_multimodal_fixture.py 生成,并由 manifest 中的叶子哈希校验来源和内容。

复现命令

在具备真实模型和数据集的环境中,先按 scripts/benchmarks/make_multimodal_fixture.py 生成本地 fixture,再从中立工作目录启动双节点测试:

PYTHONPATH=. python -u scripts/benchmarks/tq_cross_node_bench.py \
  --master <master-host>:50051 \
  --nodeb-ip <node-b-ip> \
  --device <rdma-device> \
  --payload-profiles synthetic multimodal real-multimodal \
  --payload-mib 256 1024 2048 4096 \
  --repeats 5 \
  --require-wire-proof

建议 TCP、RDMA 和 SimpleStorage 分别在独立进程中运行,并在结果中标注 fixture 来源([real][synthetic])。

实验结论

在真实双节点环境下,TransferQueue 的 RDMA 数据面已经通过真实多模态载荷验收:三种配置、三类 profile、四个容量档位共 36 个测量点全部逐字节一致,RDMA 相对 TCP 的 get 吞吐提升为 2.1×~8.4×,并有 IB/TCP 计数器提供实际走线证明。测试同时暴露并推动修复了 mooncake 0.3.10 TCP memcpy 路径的静默截断问题;因此本次性能数据同时具备可比性和正确性保障。

@RexFlux

RexFlux commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

@codex review following the repository AGENTS.md and skills/code-review/SKILL.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1df2b8d8ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread relax/utils/tq_config.py Outdated

def resolve_mooncake_master_address() -> str:
"""Return the externally managed Mooncake master endpoint."""
return os.environ.get("MC_MASTER_ADDRESS", "localhost:50051")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require an explicit Mooncake master endpoint

When MC_MASTER_ADDRESS is absent in a multi-node Mooncake run, every node interprets this fallback as its own loopback endpoint, so auto abandons Mooncake and off/required abort even if a shared master exists elsewhere. Reject the missing deployment configuration instead of embedding an endpoint; hardcoded endpoints are also explicitly prohibited by the repository rules.

AGENTS.md reference: AGENTS.md:L55-L57

Useful? React with 👍 / 👎.

Comment thread relax/utils/rdma_probe.py Outdated
Comment on lines +149 to +150
for dev in sorted(os.listdir(base)):
return _check_port_active(dev, port)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Probe every usable HCA before degrading RDMA

On a multi-HCA host where the lexicographically first device has an inactive port or no GID but a later device is usable, this loop returns the first failure immediately; the analogous GID loop does the same. Consequently auto unnecessarily degrades the whole job to TCP and required rejects a valid cluster. Select one device whose active port and usable GID both pass rather than assuming the first HCA and fixed port/GID configuration.

AGENTS.md reference: AGENTS.md:L59-L59

Useful? React with 👍 / 👎.

Comment thread relax/utils/tq_config.py Outdated
Comment on lines +152 to +155
# Conservative: 8 MiB per sample when multimodal is enabled (real range
# 7.4 MiB for a 400-token image to hundreds of MiB at max token budget).
per_sample_mb = 8 if getattr(args, "multimodal_keys", None) is not None else 0
return rollout_batch * n_samples * per_sample_mb * 1024 * 1024

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Size the segment from worst-case multimodal payloads

For multimodal jobs near the configured token/image limits, the fixed 8 MiB estimate can be orders of magnitude below the real payload even though the comment acknowledges sizes of hundreds of MiB. For example, 32 samples at 77 MiB with max_staleness=1 pass this check as 512 MiB but require about 4.9 GiB, exceeding the fixed 4 GiB hard-pinned segment and failing puts after training has started. Derive a defensible upper bound from the processor limits or make the segment size configurable instead of treating this lower bound as capacity validation.

Useful? React with 👍 / 👎.

Comment on lines +803 to +807
self.data_system_client = attach_tq_client(
self.args.tq_config,
requested_gdr=getattr(self.args, "tq_use_gdr", False),
role="rollout_worker",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detach worker Mooncake clients during teardown

When a normal shutdown, global restart, or in-place service restart destroys this worker, its newly attached Mooncake client is never detached: RolloutManager.dispose() only stops monitors and engines, and the analogous component/Megatron lifecycles also omit client cleanup. Since this change itself documents that an unclosed storage client leaves its segment registered until the master TTL, an immediate restart can encounter stale endpoints and Failed to open segment errors. Expose an attach-only detach operation and invoke it from every worker teardown hook.

Useful? React with 👍 / 👎.

Comment thread relax/core/controller.py
Comment on lines +211 to +216
init_result = initialize_tq_with_fallback(
tq_config,
mode=getattr(self.config, "tq_rdma_mode", "off"),
fallback_conf=fallback_config,
)
self._tq_owner = init_result.owner

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clean up the TQ owner if controller construction fails

If DCS creation or any later service registration raises after this owner is assigned, Controller() never returns, so train.main() never installs _ctrl, signal handlers, or the atexit cleanup. The owner actor can disappear while its healthy named TransferQueueController survives; the next launch then deliberately attaches with owner=None, making subsequent shutdown a no-op and leaving the global TQ state orphaned. Wrap the post-initialization sequence in exception cleanup that closes the newly created owner before re-raising.

Useful? React with 👍 / 👎.

@RexFlux RexFlux left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢补充真实 multimodal_train_inputs/list[dict] 路径和双节点验收数据,最新版本相比初版在真实性、逐字节校验和 benchmark 覆盖上完善了很多。

结合 Codex review 和补充检查,目前仍建议 Request changes,优先处理以下问题:

  1. Codex 已指出的 master 配置、多 HCA、容量估算、worker detach 和 Controller 构造失败清理问题;
  2. 当前已知会静默损坏数据的 MC_STORE_MEMCPY=1 仍可绕过 correctness guard,应在受影响版本上 fail closed;
  3. capability probe 只覆盖 GPU 节点,但实际 TQ owner 和 Serve endpoint 没有固定在这些节点上,探测结论不一定覆盖真实数据面;
  4. 普通 worker 的 tq.init attach 没有 timeout,也没有纳入 job-level auto fallback;
  5. 默认 SimpleStorage 仍会创建新的 owner Actor,与“zero behavior change/no extra resource”的描述不一致。

此外,PR 当前仍无法直接合入 main,存在以下 merge conflicts:

  • relax/components/sft.py
  • relax/core/controller.py
  • relax/distributed/ray/rollout.py

PR 描述中的测试数量和部分 benchmark 说明仍是旧版本,也建议在 rebase 后一起更新。真实双节点结果已经在 Conversation 中补充得比较完整,但原始 CSV 目前只保存在运行节点的 /tmp 路径,外部 reviewer 无法访
问;建议上传或附加一份脱敏后的 CSV/日志作为验收附件。

考虑到当前 PR 已达到 25 个文件、约 5.7k 行新增,也建议重新确认首期范围:RDMA 接入主流程保留在本 PR,上游 TransferQueue correctness patch、运行时 monkey patch 和额外诊断工具尽量拆成独立 PR,降低
review 和后续维护成本。

Comment thread relax/utils/tq_correctness.py Outdated
memcpy anyway, so this default is a no-op there; ``setdefault`` keeps an
explicit operator override (e.g. ``MC_STORE_MEMCPY=1``) possible.
"""
os.environ.setdefault("MC_STORE_MEMCPY", "0")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里已经确认 mooncake 0.3.10 的 memcpy 路径会静默截断数据甚至触发 SIGSEGV,但 setdefault() 仍允许外部通过 MC_STORE_MEMCPY=1 绕过 correctness guard。对于当前明确 pin 且已确认存在数据损坏的版本,这里应
该 fail closed,而不是保留未经验证的运维覆盖能力。

建议当前版本强制设置为 0;如果检测到 MC_STORE_MEMCPY=1,则直接拒绝启动并给出明确错误。未来升级到已修复版本后,再根据版本判断是否允许开启。对应的 test_contract_respects_explicit_memcpy_override 也应该
改为断言 fail-fast。

Comment thread relax/utils/rdma_probe.py Outdated
# ---------------------------------------------------------------------------


def _select_dataplane_node_ids(nodes: list[dict]) -> list[str]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里假设 TransferQueue data plane 只运行在 advertise GPU 的节点上,但当前 TQ client 是在 Actor/Rollout 等 Ray Serve replica 进程中初始化的,Serve deployment 本身并没有绑定到相应的 GPU placement group
或具体节点;新增的 _TransferQueueOwner 也是未设置 node affinity 的 0-CPU Actor。

因此可能出现 GPU 节点探测全部通过、driver 选择 RDMA,但实际 owner/producer/consumer 被调度到未探测的 CPU 节点,随后 tq.init/attach 失败。Codex 提到的多 HCA
问题只解决“单节点选错设备”,没有解决“探测的不是实际 endpoint”这一层。

建议先固定 owner 和必要 TQ client 的 placement,再探测这些实际节点;或者在所有必要 endpoint 完成有界 attach handshake 后,再确认 job-level effective config。

Supporting code:

Comment thread relax/utils/tq_lifecycle.py Outdated
return status


def attach_tq_client(conf: Any, *, requested_gdr: bool, role: str) -> Any:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前只有首次 _TransferQueueOwner 初始化通过 ray.get(..., timeout=...) 做了超时保护,但 Actor/Rollout/Critic/Megatron worker 都会从这里直接调用 tq.init(),没有 timeout,也没有被纳入
initialize_tq_with_fallback() 的事务。

如果某个实际 endpoint 上 Mooncake setup 卡住、controller 处于半初始化状态,或者该节点的 RDMA/master 条件与启动探测不同,这里仍可能无限等待或导致 Serve replica 启动失败;此前 owner 初始化成功后,auto
模式也不会再统一回退到 SimpleStorage。

建议给 attach 增加有界超时,并让必要 endpoint 的 attach 结果汇总到 driver;auto 模式下任一必要 endpoint 失败时,应统一清理 Mooncake 状态并收敛到同一个 fallback backend。

Comment thread relax/core/controller.py Outdated

# 2. SimpleStorage short-circuit (default, zero behavior change).
# ``mooncake + off`` is MooncakeStore/TCP, not SimpleStorage.
if backend == "simple":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的 short-circuit 只跳过了 RDMA probe,并没有保留原来的 SimpleStorage 初始化路径:_initialize_data_system() 后面仍然无条件调用 initialize_tq_with_fallback(),最终会创建额外的 _TransferQueueOwner
Actor,并把首次 tq.init 从 Controller 进程移动到该 Actor。
Mooncake 路径;如果确实希望同时改造默认路径,需要修正文档并补完整的默认路径回归测试。

Comment thread relax/utils/tq_correctness.py Outdated
logger.debug(f"Failed to close TransferQueue notification socket: {error}")


def _install_store_guards(client_cls: type) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里已经超出了普通 integration guard:Relax 在运行时替换 TransferQueue 的 init、_notify_and_wait 等私有实现,并直接依赖上游内部 ZMQ 协议。后续 TransferQueue 升级时,即使公开 API 没变,也可能因为
私有实现变化出现难以定位的问题。

更建议把这些 correctness fix 合入 TransferQueue 上游,然后 Relax 只升级 pin 并保留版本/能力校验。如果首期必须临时保留 monkey patch,建议拆成独立 PR,限制精确适用版本,并写清楚删除条件。

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

结合 Codex review 和补充检查,目前仍建议 Request changes,优先处理以下问题:
感谢您的细致审核,我们立刻修改。

Add a default-off, safely-degrading RDMA path for the rollout->train
sample transfer, reusing TransferQueue's existing MooncakeStore backend.
No transfer_queue/ or payload-shape changes; default flags (simple+off)
short-circuit to the original SimpleStorage path, so existing jobs are
unaffected.

Code
- 4 intent-only flags (--tq-storage-backend / --tq-rdma-mode /
  --tq-rdma-device / --tq-use-gdr); Mooncake internals (endpoint, buffer,
  segment, timeout, master) stay internal
- driver probes every alive GPU node before tq.init and AND-reduces a
  single job-level effective config; graded fallback
  GDR -> host RDMA -> Mooncake/TCP -> SimpleStorage; required mode fails
  fast on probe failure and capacity shortfall
- hard_pin=True + segment-capacity precheck so produced-but-unconsumed
  data is never silently evicted
- reap half-initialised TransferQueueController before tq.init (F10
  anti-hang, incl. get_config timeout) and unmount the Mooncake segment
  on teardown so dead endpoints don't leak past client_ttl
- GDR marked EXPERIMENTAL: not probed (probe runs without a CUDA
  context); decided per worker at runtime with a fallback WARNING

Tests (52 passed)
- test_rdma_probe.py (26): config validation, AND-reduction, multi-node
  fan-out, capacity, storage_backend key selects the manager
- test_tq_failure_paths.py (19): reaper/timeout, teardown order, retry,
  disconnect, auto-degradation, MooncakeStore byte-exact
- test_tq_dataplane_behavior.py (7): real SimpleStorage connection,
  backpressure, empty-get, repeat-put, cleanup
- CI-safe: tests needing real transfer_queue/mooncake skip on the CPU CI
  single-file stub via real-submodule detection

Benchmark + docs
- scripts/benchmarks/tq_cross_node_bench.py: C0/C1/C2 same-topology
  cross-node (256M-4.5G, 5-run mean, per-run wire verification +
  async-tail diagnostic)
- docs/draft/transfer_queue_rdma.md: master lifecycle, resource
  ownership, log reading, troubleshooting, known limits

Measured (2-node cluster, 5-run mean): cross-node get C2/C1 =
+28%..+126% across 256M..4.5G; put +45%..+146%.
- Run first initialization in a dedicated Ray owner actor with a bounded timeout
- Clean partial controllers with owner tokens and restrict global close to the owner
- Fall back once in auto mode and fail fast in required mode

- Probe the external master across all data-plane nodes before initialization
- Require retry-and-raise storage operations and storage-before-ready notification
- Report requested GDR intent separately from per-worker runtime status

---

- Cover master failures, owner cleanup, capacity errors, and notification ordering
- Add multimodal byte-exact validation and tiered cross-node benchmark output
- Separate mock coverage from opt-in real-environment acceptance checks

---

- Document external master prerequisites, fallback behavior, and ownership rules
- Record capacity guarantees, upstream correctness requirements, and validation tiers
Preserve Mooncake TCP semantics when RDMA is off and reject unsafe controller attachments.

Fail closed on incomplete Mooncake batch results, removal failures, and unsuccessful production-status notifications.

Make RDMA benchmarks and correctness tests safe for CPU-only CI environments.
# ✅ Tests

## Cover the production multimodal container (list[dict] slow path)

- relax/utils/payload_digest.py: canonical leaf-level SHA-256 fingerprints
  (contiguous-CPU-normalized storage bytes; NaN-safe, stricter than
  torch.equal; NestedTensor rows == list rows; NonTensorData/Stack unwrap)
- tests/utils/mm_payload_fixtures.py: payload source shared by tests -- real
  fixture (auto-verified against its manifest) with production-structured
  synthetic fallback for CI; tier reported in every assertion
- test_tq_dataplane_behavior.py: TestRealMultimodalFullLink -- full
  tq.init/put/get with multimodal_train_inputs as NonTensorStack via the
  production dict_to_tensordict, per-sample leaf digests aligned by sample_id
- test_tq_failure_paths.py: TestMooncakeByteExact gains the msgpack
  non-tensor slow-path roundtrip (tcp/rdma), one spawn child per protocol to
  isolate the mooncake 0.3.10 in-session protocol-switch instability

---

# ⭐ Feature

## Real-payload fixture generator + bench profile

- scripts/benchmarks/make_multimodal_fixture.py: replays the exact rollout
  preprocessing chain (build_messages -> apply_chat_template ->
  process_vision_info -> HF processor -> remap_mm_train_inputs) on real
  dataset rows; double-run determinism check validates the F4 group-sharing
  assumption; emits leaf manifest + committable provenance JSON
- tq_cross_node_bench.py: real-multimodal profile (fixture tiled to each
  payload tier, NonTensorStack column) with order-insensitive row-multiset
  digests; dtype+bytes row contract absorbs the scalar-row () vs [1]
  representation difference between SimpleStorage and MooncakeStore

---

# 📝 Documentation

## Acceptance layering for real payloads

- docs/draft/transfer_queue_rdma.md: fixture workflow, real vs synthetic
  tier reporting rules, real-multimodal bench command; troubleshooting row
  for the mooncake 0.3.10 TCP loopback SIGSEGV found by this tier
- .gitignore: tests/fixtures/ (machine-local, hundreds of MB)
# 🐛 Bug Fix

## Silent TCP truncation traced to mooncake's memcpy fast path

- relax/utils/tq_correctness.py: correctness guards now default
  MC_STORE_MEMCPY=0 (setdefault, operator can override).  mooncake 0.3.10
  auto-enables the memcpy fast path in TCP-only environments and that path
  silently truncates cross-node gets: two-node forensic probes captured
  rows zero-filled from 64 KiB-aligned offsets onward while every batch
  code reported success (~50% of fresh-session first transfers; not
  limited to the first transfer -- a canary transfer does not fully
  prevent it; 12/12 sessions clean with memcpy off).  The same path is
  the single-node loopback SIGSEGV documented earlier; both symptoms are
  gone with the guard (loopback multimodal re-run passes byte-exact).
  RDMA sessions auto-disable memcpy, so the default is a no-op there.

---

# ✅ Tests

## Contract coverage for the new guard

- tests/utils/test_rdma_probe.py: validate_mooncake_runtime_contract now
  must default MC_STORE_MEMCPY to "0" when unset and must respect an
  explicit operator override ("1"); both skip on the CPU-CI transfer_queue
  stub like the existing contract test

---

# 📝 Documentation

## Two-node acceptance record + updated troubleshooting

- docs/draft/transfer_queue_rdma.md: full 3x3x4-tier acceptance table
  (36/36 byte-exact PASS, wire-proof PASS; C2/C1 get gain 2.1x-8.4x with
  guarded-TCP as the honest C1 baseline); troubleshooting rows for the
  memcpy silent truncation (fixed by guard), the loopback SIGSEGV (same
  root cause, verified fixed), and master-aging batch_upsert -800 (fresh
  master clears it); known-limitations note that C1 is a correctness
  fallback, not a performance option
# 📝 docs

- 将「双节点实测记录」日期化实验小节收敛为「参考吞吐区间」:只保留
  get/put 量级区间与结论(供容量规划参考),逐档明细、逐轮分布与
  原始 CSV 归入交付验收材料,不再在文档内维护
- 排障表三行(TCP 静默截断、回环 SIGSEGV、master 状态劣化)压缩为
  「现象/原因/处理」一行式,剥离取证过程叙事(会话统计、探针细节)
- `MC_STORE_MEMCPY=0` 守卫的行为说明移入「容量不足与正确性依赖」,
  排障表引用之;补充 RDMA 会话不受影响与显式覆盖方式
- 「已知限制」与验收措辞去除开发过程口吻("此前读数"、"本次开发
  环境"),与 docs/draft 下其他使用指南的无时间性语态对齐
# 🐛 Bug Fix

## Fail closed on unsafe MC_STORE_MEMCPY (review: tq_correctness.py:179)

- Reject startup when MC_STORE_MEMCPY=1 is set: the pinned mooncake
  0.3.10 memcpy fast path silently truncates TCP transfers and can
  SIGSEGV; force the variable to 0 otherwise
- Re-gate on the mooncake version once the pin moves past the fix

## Require an explicit Mooncake master endpoint (Codex P1)

- resolve_mooncake_master_address() rejects a missing MC_MASTER_ADDRESS
  instead of assuming localhost:50051, which made every node of a
  multi-node job treat itself as the master

## Probe every usable HCA before degrading RDMA (Codex P1)

- _select_usable_rdma_device() scans all devices, all ports, and the
  GID table of the first ACTIVE port; a node degrades only when no
  device passes both checks together
- probe_node reports the jointly validated device instead of the
  lexicographically first one

---

# ✅ Tests

## Cover the fail-closed and multi-HCA behaviours

- test_contract_rejects_explicit_memcpy_enable asserts fail-fast
- master-address tests for the required env contract
- multi-HCA selection tests (down first device, all-down degradation)
# 🐛 Bug Fix

## Close the TQ owner if Controller construction fails (review)

- Wrap the post-_initialize_data_system() construction sequence in
  exception cleanup: close the newly created TQ owner before re-raising
  so a failed Controller() cannot orphan a healthy named
  TransferQueueController whose next launch attaches with owner=None

## Detach worker Mooncake clients during teardown (Codex P1)

- Expose detach_tq_client(), the attach-only inverse of
  attach_tq_client(); it deregisters the worker segment immediately
  instead of waiting for the master client_ttl
- Base.__del__ detaches on Ray Serve replica shutdown (covers Actor,
  ActorFwd, Advantages, Critic, Rollout, SFT)
- RolloutManager.dispose() and MegatronTrainRayActor.__del__ detach on
  worker teardown; force-kills still fall back to the master TTL

---

# ✅ Tests

## Worker detach coverage

- detach_tq_client delegates to the process-local close helper
- Base.__del__ detaches only when a TQ client was attached
@overloadedHenry

Copy link
Copy Markdown
Contributor Author

代码评审已经通过,这里不影响当前 Approve。为了高级任务最终验收和打分归档,辛苦再补充确认几项测试口径:

实际 Relax commit SHA

Benchmark 和 fully-async smoke 使用的是同一组 PR 功能代码,具体由以下两个 PR 合并而来:

两个节点在运行前均核对为:

  • TransferQueue 0.1.10.dev0,source commit 58054a33834aadbcf76aacd6b1e32e25c030f2c9
  • Mooncake 0.3.10.post2

资源监控口径

本轮没有采集连续的平均 GPU utilization、peak VRAM 或 sender/receiver CPU utilization,因此不提供这些数值,也不从零散日志反推。

日志ERROR解释:

  • Metric health: 发生在 route ready 前,随后可正常写入。
  • Mooncake resource: 发生在 segment mount 前;随后所有节点 attach 成功。
  • Actor killed 发生在训练成功后,最终 Job exit code 0。

@RexFlux

RexFlux commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

hi,考虑到目前PR的changes比较大,从维护性角度看,需要完成下代码瘦身才可合入。建议至少做到

1. 归类存放tq新增utils

relax/utils下新增的大量tq专属文件,需要开一个submodule统一归类存放

2. 删除首期不必要的 GDR 支持

GDR 不是题目要求,目前也没有完成实际生效验证。建议首期删除:

  • --tq-use-gdr;
  • GDR runtime status 探测;
  • 相关日志和测试。

首期只交付 host-RDMA,后续有真实需求再单独实现 GDR。

3. 收窄配置面

建议首期只保留:

--tq-rdma-mode {off,auto,required}
--tq-rdma-device

语义可以收敛为:

  • off:原有 SimpleStorage;
  • auto:RDMA 可用则启用,否则回退 SimpleStorage;
  • required:RDMA 不可用则失败。

Mooncake/TCP 继续作为 benchmark 的 C1 对照,不一定需要成为公开生产配置。这样可以删除 backend/mode 组合矩阵、TCP connection-pool 运维契约以及两级降级状态机。是否保留 Mooncake/TCP 中间回退,需
要会上明确。

4. 用真实 attach 代替过度细化的 /sys 探测

当前 rdma_probe.py 有 652 行,分别检查 HCA、port、GID、memlock、master 等,最后仍然要执行真实 attach handshake。

可以考虑:

  • 代码只做最小 import、device 和配置检查;
  • 以一次性 Ray worker 的真实 Mooncake/RDMA attach 作为最终判断;
  • auto attach 失败就回退;
  • 详细 HCA/GID/memlock 排障命令放到文档。

真实 attach 已经是最终事实,重复维护一套启发式探测容易和底层实现漂移。

5. 按“单任务独占集群”简化生命周期

当前 tq_lifecycle.py 有 734 行,其中包含:

  • 多 initializer 竞争;
  • configuration/sampler signature;
  • owner token;
  • client generation lease;
  • 对健康外部 Controller 的安全 attach。

但 RFC 已明确首期只支持单任务独占 Ray 集群。可以考虑收敛为:

  • 半初始化 Controller:安全清理;
  • worker:bounded attach + detach;
  • owner-only close。

要保留超时、半初始化清理和 Mooncake segment unmount,但可以删除多 job/并发 initializer 兼容逻辑。

6. 删除一次性验收工具

建议最终只留一个精简版跨节点 benchmark:

  • 删除单节点 tq_rdma_bench.py;
  • 删除 raw Mooncake cross_node_rdma_bench.py;
  • fixture 生成、payload digest 等可以放验收附件或独立 benchmark PR;
  • tq_cross_node_bench.py 从 770 行收敛到核心的 C0/C1/C2、byte-exact 和 wire-proof。

测试也不应该简单删除覆盖,而是用参数化合并重复 case。建议从约 2,800 行收敛到 800~1,200 行,保留题目要求的连接、背压、超时、断连、重试、清理和逐字节一致性。

优先考虑以下删减:

  1. GDR 延后到独立 PR;
  2. Mooncake/TCP 仅保留为 benchmark 对照,是否作为公开运行模式请重新评估;
  3. 用真实 attach 作为最终能力判断,减少重复的 HCA/GID/memlock 启发式探测;
  4. 按单任务独占集群的已冻结边界,删除多 job、并发 initializer、复杂 signature/generation 兼容逻辑;
  5. 删除重复的单节点/raw benchmark 和一次性 fixture 工具,只保留一个可复现的跨节点 benchmark;
  6. 合并参数化重复测试,但必须保留连接、背压、超时、断连、重试、清理和 byte-exact 覆盖;

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

hi,考虑到目前PR的changes比较大,从维护性角度看,需要完成下代码瘦身才可合入。建议至少做到

了解,我会尽快完成修改。

@RexFlux

RexFlux commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

还有一些不必要的代码封装可以调整下:

relax/utils/tq_lifecycle.py:165

这里的 uses_mooncake() 只是对 _uses_mooncake() 的纯转发,没有增加新的语义。建议只保留一个公开的 uses_mooncake(),并让本模块内部也直接复用它,减少一层无收益封装。

relax/utils/rdma_probe.py:412

_alive_gpu_nodes() 目前只是对 ray.nodes() 和 _select_dataplane_node_ids() 的一行包装,主要作用只是方便测试 mock。建议直接在 probe_cluster_nodes() 中调用节点筛选逻辑,测试侧 mock ray.nodes(),
避免为了单个调用点保留额外的函数层级。

relax/utils/rdma_probe.py:478

_probe_on_node() 只重新 import 并转发到 probe_node(),没有增加实际逻辑。这里可以直接使用 ray.remote(num_cpus=0.001)(probe_node) 创建远程函数,避免嵌套一个纯转发 wrapper。

这次代码瘦身建议同时检查类似的一次性 helper 和纯转发 wrapper:如果函数只服务一个调用点、没有形成独立语义或隔离必要的 Ray/线程边界,可以优先内联。需要保留的例外包括线程 watchdog、完整的远程
attach handshake 以及资源清理边界,这些封装具有明确作用。

_attempt()、_handshake() 和 _bounded_tq_init() 中的 _run() 这种承载 fallback 复用、Ray 进程边界和超时异常传递的可以保留。

# ♻️ Refactor

## Limit the initial transport to host RDMA

- Remove public GDR configuration and runtime status plumbing
- Pin Mooncake client configuration to host memory transfers
- Preserve compatibility checks for legacy GDR controllers

---

# ✅ Tests

## Cover the host-RDMA contract

- Remove deferred GDR behavior tests
- Reject attaching to a controller configured for GDR

---

# 📝 Documentation

## Clarify the initial transport scope

- Document host RDMA as the supported first-phase path
# ♻️ Refactor

## Reduce public data-plane choices

- Replace the backend and transport matrix with a three-state host RDMA mode
- Route automatic capability failures directly to SimpleStorage
- Keep Mooncake TCP confined to benchmark baselines

---

# ✅ Tests

## Cover production backend decisions

- Verify off, auto, and required mode behavior
- Ensure the production resolver never selects Mooncake TCP
- Validate the narrowed command-line interface on CPU-only environments

---

# 📝 Documentation

## Describe the two-path production contract

- Document direct fallback from host RDMA to SimpleStorage
- Clarify master configuration behavior and benchmark-only TCP usage
- remove static RDMA probing and simplify backend configuration
- enforce exclusive controller ownership and fail-closed cleanup
- isolate handshake and train actor initialization failures
- preserve byte-exact coverage with hardened payload checks
Remove redundant single-node and raw transport benchmarks along with the one-off fixture generator. Keep one C0/C1/C2 acceptance tool with selected-device wire proof, byte-exact NonTensorStack coverage, fail-closed teardown, and safe external fixture loading.
Parameterize straightforward backend and lifecycle cases while keeping complex timeout and cleanup paths explicit. Remove duplicate endpoint and fixture harnesses, retain byte-exact and process-isolation coverage, and align the acceptance documentation.
Copilot AI review requested due to automatic review settings August 23, 2026 20:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 23, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@overloadedHenry

overloadedHenry commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

目前根据评审意见进行了深度修改,后面会立刻在 Relax 维护的 TransferQueue 中提 PR,并汇报多机通信情况。
Update:目前正在进行全局 Review,以减少 reviewer 的工作量。

Correct multimodal segment sizing, clean partial actor initialization, retain failed owner handles, and unmount storage on teardown.

Enforce the Mooncake memcpy safety contract, sanitize topology logs, and align tests and deployment docs.
Copilot AI review requested due to automatic review settings August 24, 2026 06:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

# 🐛 Bug Fix

## Make cross-node acceptance fail closed

- Require exact counter selection and account for idle traffic
- Persist byte-exact and wire-proof failures before aborting
- Preserve validation failures when partition cleanup also fails
- Record exact source and dependency provenance in result rows

---

# ♻️ Refactor

## Consolidate correctness and lifecycle coverage

- Reuse raw-byte payload helpers across acceptance and dataplane checks
- Parameterize repeated configuration, lifecycle, and cleanup scenarios
- Retain process-isolation, retry, backpressure, and multimodal coverage

---

# 📝 Documentation

## Clarify acceptance requirements

- Document counter scope, idle adjustment, TCP baseline, and result provenance
Copilot AI review requested due to automatic review settings August 24, 2026 16:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

重测的时候会出现上游 TQ 的 Tensor shape 的问题。由于不属于本 PR 的范围,于是只在本地解决。解决后均可正常通过。

相关 issue:
#286

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

Another Question:上游 TQ 是否要传 Capability marker?然后本 PR 内的代码进行校验?

@RexFlux

RexFlux commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Another Question:上游 TQ 是否要传 Capability marker?然后本 PR 内的代码进行校验?

看了下最新的调整,目前PR瘦身是有效的,代码整体approve

关于nested tensor问题,这属于独立的 #286 数据消费兼容问题,不需要再并入 #256。你本地叠加修复完成重测是可以的,只需要在测试记录中注明实际使用了#256 head 和 #286 的本地修复,后续让 #286 独立推进即可。

关于 Capability marker,建议在 TransferQueue PR redai-studio/TransferQueue#5 中增加一个很小的 correctness contract version,用来表示 retry 结果校验、删除失败传播和 ACK fail-closed 已经具备。#256 中用这个 marker 替换当前的方法名和源码检查即可,不需要再扩展成 capability 矩阵,也不要重新引入之前删掉的探测或 patch 逻辑

Capability marker 可以尽量简单,例如TQ PR 5 在 transfer_queue/init.py 中增加:

MOONCAKE_CORRECTNESS_CONTRACT_VERSION = 1

这里的 version=1 表示已经具备本次 PR #5 的三项正确性契约:

  1. 每次 batch/retry 返回数量校验;
  2. batch_remove 失败向调用方传播;
  3. production-status ACK fail-closed。

Relax #256 中只需要读取并比较一次:

import transfer_queue as tq

_REQUIRED_TQ_MOONCAKE_CONTRACT_VERSION = 1

actual = getattr(tq, "MOONCAKE_CORRECTNESS_CONTRACT_VERSION", 0)
if actual < _REQUIRED_TQ_MOONCAKE_CONTRACT_VERSION:
raise RuntimeError(
"Installed TransferQueue does not satisfy the required Mooncake correctness contract"
)

建议用这个检查替换当前 retry method existence 和 inspect.getsource 检查,而不是在现有逻辑上继续叠加。这样 TQ 侧只增加一个常量,Relax 侧也是几行版本比较,不会抵消 这轮代码瘦身。

最后比较推荐的合入顺序是:

  1. 先合入 TransferQueue PR Agent RL的多轮rollout过程中是否支持手动对历史对话进行压缩,比如删除历史对话中的图像 #5,并拿到稳定 commit;
  2. feat(data-plane): enable RDMA transport for TransferQueue #256 中把 CUDA/NPU Dockerfile 和 arguments.py 的 TQ commit 更新到该 commit;
  3. feat(data-plane): enable RDMA transport for TransferQueue #256 中补充最小 marker 校验并跑一次最终 smoke;
  4. 然后合入 feat(data-plane): enable RDMA transport for TransferQueue #256

Copilot AI review requested due to automatic review settings August 26, 2026 18:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【Task.026】TransferQueue RDMA - RFC

3 participants