Confirms snapshot_link works for cuda device pointers, not just host memory. Sender on cuda:0 pushes to receiver on cuda:1 via RDMA over mlx5_60. All 5 sizes (16K, 1M, 16M, 64M, 256M) pass SHA verification. 16 KB 8.3 ms 0.016 Gbps (cold openSegment) 1 MB 0.10 ms 87.6 Gbps 16 MB 0.84 ms 159 Gbps 64 MB 2.52 ms 213 Gbps 256 MB 8.54 ms 251 Gbps (~60% NDR400 line rate) For Inferact-scale sessions (~50K tokens × ~80 KB layer-per-token = ~4 GB), this projects D→P transfer time at ~130 ms — within the "reseed-savings" envelope sketched in design doc §3.2. Files: scripts/snapshot_link_receiver_gpu.py scripts/smoke_snapshot_link_gpu.py Next: SGLang scheduler integration for D-side dump + P-side ingest.
7.5 KiB
D→P Phase 1:底层 RDMA 链路(已验收)
日期:2026-05-13
状态:底层链路通过 smoke test 验收
前置:docs/D_TO_P_SYNC_DESIGN_ZH.md
对应 commit:feat(snapshot): D→P snapshot link over mooncake RDMA
0. 一句话
实现一个独立于 SGLang MooncakeKVManager 的最小 RDMA 字节传输模块(src/agentic_pd_hybrid/snapshot_link.py),双进程 smoke test 跑通 1 KB → 64 MB 一共 5 个 size,全部 SHA 校验通过,64 MB 单次 RDMA write 实测 315 Gbps(mlx5_60 NDR 400 Gb 的约 80%)。
1. 设计动机
docs/D_TO_P_SYNC_DESIGN_ZH.md 选定 Option C(D→P snapshot push + P SessionSlot + prefill bypass)。这个方案的最底层依赖是"D 进程能把字节通过 RDMA 推到 P 进程的预注册缓冲区"。
直接复用 SGLang 的 MooncakeKVManager 不可行:
add_transfer_request在conn.py:1563硬 assertdisaggregation_mode == PREFILL- PD pipeline 的发送 / 接收 thread / queue / staging 紧耦合 PD 角色
- 改 PD 路径风险大(影响现有 E1/E2/E3 配置)
因此把 D→P link 单独写成一个轻量模块,直接调 mooncake.engine.TransferEngine 的 transfer_sync_write / batch_transfer_sync_write,不经过 PD pipeline。
2. 实现
2.1 snapshot_link.SnapshotPeer
peer = SnapshotPeer(host, port, ib_device, receive_capacity_bytes)
endpoint = peer.endpoint # SnapshotEndpoint(session_id, base_ptr, capacity_bytes)
peer.register_send_buffer(ptr, length)
peer.push(target_endpoint, local_ptr, local_off, length, remote_off=0)
peer.batch_push(target, local_addrs, remote_addrs, lengths)
peer.read_bytes(offset, length) -> bytes
peer.close()
- 每个
SnapshotPeer拥有自己的TransferEngine,绑定host:port receive_capacity_bytes > 0时分配一段 ctypesc_ubyte数组 +register_memorypush直接走engine.transfer_sync_write(peer_session_id, local_ptr, remote_ptr, length)- 角色完全对称——任何
SnapshotPeer既可以发送也可以接收,由 caller 决定
2.2 Smoke test 双进程结构
父进程 (sender) 子进程 (receiver, subprocess.Popen)
│ │
│ spawn → ──────────────────────────────►│
│ │ SnapshotPeer(recv_capacity=64MB)
│ │ write endpoint.json
│ read endpoint.json ◄───────────────────│
│ │
│ SnapshotPeer(no recv buf) │
│ register_send_buffer(64MB) │
│ │
│ for size in [1K, 16K, 1M, 16M, 64M]: │
│ fill_pattern(send_buf, seed) │
│ peer.push(endpoint, 0, size) ─RDMA──►│
│ │ wait signal
│ write endpoint.do{size} ────────────►│ read signal seed
│ │ compute expected SHA
│ │ recv_bytes = peer.read_bytes
│ wait endpoint.ack{size} │ compare SHA → emit JSON event
│ │ write endpoint.ack{size}
│ ... │
│ │
│ drain child stdout, parse JSON │ exit
│ verify each event has ok=true │
2.3 性能(首次 smoke run)
| Size | Push duration | Throughput |
|---|---|---|
| 1 KB | 9.0 ms | 0.001 Gbps |
| 16 KB | 0.037 ms | 3.5 Gbps |
| 1 MB | 0.102 ms | 82 Gbps |
| 16 MB | 0.577 ms | 232 Gbps |
| 64 MB | 1.70 ms | 316 Gbps |
- 1 KB 第一次有 ~9 ms 的 mooncake p2p handshake/openSegment overhead(一次性)
- 16 KB 之后是稳态,吞吐随 size 增长接近线速
- mlx5_60 是 mlx5 ConnectX-7 NDR 400 Gb(4× 100Gb lanes);64 MB 测到 316 Gbps 是 79% 的链路利用率,对单次 RDMA write 来说正常(剩余空间留给 verb dispatch / completion handling overhead)
3. 验收
- ✅ 5/5 size SHA 校验全部通过
- ✅ 64 MB 一次 RDMA 1.7 ms
- ✅ 双进程独立,不耦合 SGLang PD pipeline
- ✅ Smoke test 脚本
scripts/smoke_snapshot_link.py可重跑
4. 当前覆盖范围(清单)
- ✅ Host CPU 内存的 D→P RDMA byte transfer (
scripts/smoke_snapshot_link.py) - ✅ GPU 内存 cuda:0 → cuda:1 的 D→P RDMA(
scripts/smoke_snapshot_link_gpu.py,5/5 size 全 SHA 校验通过,256 MB 8.5 ms / 251 Gbps) - ✅ 单 IB device (mlx5_60)
- ✅ 同节点 loopback(127.0.0.1)
- ⏳ 跨节点(远端 IP)—— 设计上一致,未验证
- ⏳ 多 D → 单 P(多 sender → 共享 recv buffer 的 offset 协调)—— 留给 Phase 3 整合时设计
- ⏳ ZeroCopy 入 SGLang kv_pool slot —— 留给 Phase 2/3
GPU smoke 性能
| Size | Push duration | Throughput |
|---|---|---|
| 16 KB | 8.27 ms (cold) | 0.016 Gbps |
| 1 MB | 0.096 ms | 87.6 Gbps |
| 16 MB | 0.844 ms | 159 Gbps |
| 64 MB | 2.52 ms | 213 Gbps |
| 256 MB | 8.54 ms | 251 Gbps |
GPU↔GPU 比 host↔host 慢一些(251 vs 316 Gbps for 64MB),但仍接近 mlx5_60 NDR 400Gb 的 60% 线率。对 KVC 单 session ~50K tokens × ~80 KB/token ≈ 4 GB 量级的 transfer,对应 D→P 时间约 130 ms。
5. 下一步(Phase 2 / Phase 3)
详见 docs/D_TO_P_SYNC_DESIGN_ZH.md §5。本 phase 1 解锁后,整个 D→P 同步可以正式开始整合到 SGLang scheduler:
| Phase | 描述 | 风险 |
|---|---|---|
| 2 | D-side commit hook:cache_finished_req 完成后 enqueue snapshot push |
中。需要在 scheduler 后台线程跑 push,不能阻塞 schedule loop |
| 3 | P-side snapshot store + prefill bypass:P scheduler 收到 use-snapshot 请求时跳过 model.forward(),直接用 snapshot KV 触发 P→D' transfer |
最高。需要深入 SGLang prefill 流程 |
| 4 | agentic-pd-hybrid hook:_invoke_kvcache_seeded_router 先 probe P → 决定走 bypass 还是 fallback |
低 |
| 5 | CLI flag + structural log | 低 |
| 6 | 端到端 smoke + E4 sweep | 中 |
6. 知识沉淀
易踩坑
| 坑 | 原因 | 修法 |
|---|---|---|
多进程 multiprocessing.Process 子进程崩溃信息丢失 |
spawn context 下 child 没有继承 parent 的 stderr | 改用 subprocess.Popen + stderr 重定向到文件 |
bytes(ctypes.c_byte * N) 失败 ValueError: bytes must be in range(0, 256) |
c_byte 是 signed,>= 128 的 byte 在 Python 看就是负数 |
用 c_ubyte 或 ctypes.string_at(addr, length) 做内存复制 |
| 第一次 push 有 ~9ms openSegment overhead | mooncake p2p handshake lazy 建链 | 稳态忽略;如需 warm-up,提前发 1 KB pre-flight |
mooncake API 速查
engine = TransferEngine()
engine.initialize(f"{host}:{port}", "P2PHANDSHAKE", "rdma", ib_device)
engine.register_memory(ptr, length) # mr 注册
engine.transfer_sync_write(peer_session_id, local_ptr, remote_ptr, length) # RDMA write
engine.batch_transfer_sync_write(peer_session_id, [local_ptrs], [remote_ptrs], [lengths])
engine.unregister_memory(ptr)
peer_session_id 是 "host:rpc_port",其中 rpc_port = peer_engine.get_rpc_port()。
核心句:D→P 底层 RDMA 链路独立模块跑通,64 MB 1.7 ms / 316 Gbps,与 SGLang PD pipeline 完全解耦。Phase 2/3 可以放心在这上面叠加。