Skip to content

Commit 0915a1f

Browse files
committed
Quartz sync: Jun 5, 2026, 5:16 PM
1 parent 7d6f92a commit 0915a1f

23 files changed

Lines changed: 2328 additions & 889 deletions

content/Big Feature/Inference optimization.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
- [x] flagcx connector实现,放在vllm-plugin-fl;Nccl engine->flagcx engine,和mooncake性能对齐; [[5. flagcx connector design&&dev&&test]] ✅ 2026-05-7
1616
- [x] Mooncake xfer engine源码学习 [[0. mooncake rdma transfer]] ✅ 2026-05-23
1717
- [x] vllm 如何管理 kv cache,nixl 和 mooncake 的 connector 如何使用 block 索引 kv cache 并指挥底层 rdma ✅ 2026-05-23
18-
- [ ] 在 flagcx 内设计一套多线程高性能的post wr+poll cq(定义general的数据结构处理上层的业务输入,然后flagcx_p2p内能初始化2worker)[[2. 多后端多线程的 ibrc p2p 方案设计]]
18+
- [x] 在 flagcx 内设计、开发、测试一套多线程高性能的post wr+poll cq(定义general的数据结构处理上层的业务输入,然后flagcx_p2p内能初始化2worker)[[2. 多后端多线程的 ibrc p2p 方案设计]] ✅ 2026-06-02
19+
- [x] flagcx p2p engine 增加 rpc 服务以及对外的 python wrapper [[3. flagcx ibrc p2p RPC 服务 + flagcx connector 改动]] ✅ 2026-06-02
20+
- [ ] 优化 flagcx connector 性能直到大于 mooncake 性能。
1921
- [ ] glm 模型用 flagos 跑通 [[3. vllm glm5 1P1D 推理]]
2022

2123
**核心工作 2:glm-5 使用 flag 系列完成 vllm pd 分离推理**

content/FlagCX-modify/Adapor/2. 多后端多线程的 ibrc p2p 方案设计.md

Lines changed: 595 additions & 41 deletions
Large diffs are not rendered by default.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
## 0. 调研
2+
3+
## Mooncake内:
4+
> Mooncake 的 RPC 与 get_rpc_port 做了啥?
5+
6+
Mooncake 把通信拆成**控制面****数据面**两条独立通路,这是整个设计的核心。
7+
8+
`TransferEngine.initialize(hostname, "P2PHANDSHAKE", "rdma", "")` 内部做两件事:建立 RDMA 资源(PD/CQ/设备),以及在一个自动选取的 TCP 端口上拉起一个握手守护线程(`startDaemon`,端口由 findAvailableTcpPort 分配)。`get_rpc_port()` 返回的就是这个守护线程监听的端口,上层用 `hostname:rpc_port` 作为对端的唯一会话标识(session)。
9+
10+
控制面就是这个守护线程:它收 JSON over TCP 的请求(Connection / Metadata / Notify 三类),在对端发起连接时交换 RDMA 连接参数(QP num、GID、LID、MTU)并解析出对方内存段的 rkey。数据面则完全是裸 RDMA verbs:`batch_register_memory(ptrs, lens)` 一次性把所有 KV 内存注册成 MR 并发布段描述符(地址区间 + rkey),`batch_transfer_sync_write(remote_session, src_ptrs, dst_ptrs, lengths)`**绝对虚拟地址**寻址——引擎按需对 `remote_session` 做一次握手(若连接已存在则复用),从对端发布的段元数据里查出 `dst_ptrs` 落在哪个 MR、拿到 rkey,然后 post RDMA write,阻塞到完成。
11+
12+
关键结论:**rpc_port 只服务于数据面建链时的元数据握手,与请求级控制完全无关**。vLLM 的 `mooncake_connector.py` 另外维护了一条 ZMQ side channel(端口 `VLLM_MOONCAKE_BOOTSTRAP_PORT + dp_rank*tp_size`),Decode 用它把"我要拉哪些请求的哪些 block、我的 base 地址、我的 rpc_port"打包成 `MooncakeAgentMetadata` 发给 Prefill;Prefill 收到后才用其中的 `remote_hostname:remote_port(=rpc_port)` 去驱动引擎做实际写入。两条通路职责清晰:ZMQ 管"传什么",rpc_port 管"怎么连"。
13+
14+
### FLAGCX内:
15+
16+
`flagcx/core/flagcx_p2p.cc` 的引擎 API(`flagcxP2pEngine*`,定义在 `flagcx_p2p.h`)
17+
18+
- `FlagcxP2pRdmaDesc`(64 字节,`addr + size + rkey`)= Mooncake 的段描述符,且已有序列化函数 `flagcxP2pSerializeRdmaDesc`
19+
- `gMemRegInfo`(全局 `unordered_map<baseAddr, FlagcxP2pMemRegEntry>`)= Mooncake 的内存段表,每项已缓存预计算好的 64B desc(`descBuf`)。
20+
- `flagcxP2pEngineReg / PrepareDesc / WriteVector / ReadVector / XferStatus` = `batch_register_memory` + `batch_transfer_sync_write` 的全部能力,WriteVector 已支持按 `(mrId, 本地源VA, size, 远端desc)` 向量化提交。
21+
- `flagcxP2pEngineConnect / Accept` 已经在 `flagcxSocket` 上交换了 `FlagcxP2pCtrlMeta`(gpuIdx、notifPort、flags)并在 adaptor 内完成 QP 参数协商和 QP 状态机迁移;`flagcxP2pEngineGetMetadata` 已能吐出 `ip:rdma_port?gpu?notif_port`
22+
23+
flagcx 内没有中间 nixl 的话还需要提供一下功能:
24+
(1) 握手时**需要交换 MR/desc 表**——目前 desc 交换被甩给了应用层(现 connector 用 ZMQ 传 base 地址 + MR 索引);
25+
(2) `Accept`**常驻守护**,不是阻塞单次调用;
26+
(3) `getRpcPort` 按 session 复用连接的缓存
27+
(4) flagcx engine API **导出到 `flagcx_wrapper.py`**(Python 侧现在只能调旧的 comm API)。
28+
29+
## 1. flagcx 侧代码改动
30+
31+
**现有 connect/accept 的 socket 握手扩展成携带 MR desc 表的控制面, 再把 accept 包装成常驻守护线程,并补一个 session 连接缓存**。类似 Mooncake `SocketHandShakePlugin` ,只是复用 FlagCX 已有的 `flagcxSocket` + `gMemRegInfo` + `FlagcxP2pRdmaDesc`
32+
33+
**rpc_port 不新开。** `getRpcPort` 直接返回现有 RDMA listener 的端口(即 `flagcxP2pEngineGetMetadata` 里那个 `rdma_port`,从 `listeners[dev].handle` 解析)。新增一个薄接口:
34+
35+
```cpp
36+
int flagcxP2pEngineGetRpcPort(FlagcxP2pEngine *engine); // 返回 listener 端口
37+
```
38+
39+
**握手捎带 desc 表。** 在 `flagcxP2pEngineConnect` 和 `flagcxP2pEngineAccept` 交换完 `FlagcxP2pCtrlMeta` 之后,追加一轮 `flagcxSocketSendRecv`:把本端 `gMemRegInfo` 里所有条目序列化为 `{baseAddr, size, rkey}` 数组发出去,同时收下对端的同样数组,存到 `FlagcxP2pConn` 上新增的字段 `std::vector<RemoteRegion> remoteRegions`。这一步用的就是 `descBuf` 里现成的 rkey,不需要新逻辑。
40+
41+
**accept 守护化。** 仿照已有的 `notifWorkerLoop` 线程模式,新增 `flagcxP2pEngineStartRpcServer(engine)`,内部起一个线程循环调用现有的 `flagcxP2pEngineAccept`,每接入一个对端就完成上面的握手并把 conn 挂进引擎的 session 表。Decode 侧(写入的接收方)在注册完内存后拉起它即可。
42+
43+
**session 缓存 + desc 查找。** 引擎内维护 `unordered_map<string("host:port"), FlagcxP2pConn*>`。新增:
44+
45+
```cpp
46+
// 按 session 取连接,不存在则发起 connect(内部完成 desc 表交换),存在则复用
47+
FlagcxP2pConn *flagcxP2pEngineGetConn(FlagcxP2pEngine *engine, const char *session);
48+
49+
// 给定远端绝对 VA + size,在 conn->remoteRegions 里定位落点,填出 desc(addr=VA, rkey=该段rkey)
50+
int flagcxP2pEngineMakeDesc(FlagcxP2pConn *conn, uint64_t remoteVa, uint32_t size,
51+
FlagcxP2pRdmaDesc *desc);
52+
```
53+
54+
`MakeDesc` 就是 Mooncake "按 dst_ptr 查 rkey" 那一步的本地化实现。有了它,上层只需给绝对地址,无需再关心 MR 索引。
55+
56+
**同步写。** WriteVector 已是非阻塞 + `flagcxP2pEngineXferStatus` 轮询。再包一个阻塞版对齐 `batch_transfer_sync_write` 即可,完成判定复用 `FlagcxTransferTask::isAllDone`:
57+
58+
```cpp
59+
int flagcxP2pEngineWriteVectorSync(FlagcxP2pConn *conn,
60+
std::vector<FlagcxP2pMr> mrIds,
61+
std::vector<void*> srcVec,
62+
std::vector<size_t> sizeVec,
63+
std::vector<FlagcxP2pRdmaDesc> descs);
64+
```
65+
66+
最后把以上接口(Create/Destroy、Reg、GetRpcPort、StartRpcServer、GetConn、MakeDesc、WriteVectorSync)按 `Function(...)` 形式补进 `flagcx_wrapper.py`,并加一个薄 Python 包装类。整套改动不碰 ibrc_p2p adaptor 的 QP/WorkerPool 数据面,只在 `flagcx_p2p.cc` 控制面上做加法。
67+
68+
![[3. flagcx ibrc p2p RPC 服务 2026-06-01 19.13.06.excalidraw.svg]]
69+
%%[[3. flagcx ibrc p2p RPC 服务 2026-06-01 19.13.06.excalidraw.md|🖋 Edit in Excalidraw]]%%
70+
## 2. vllm-plugin-fl侧改动
71+
72+
只需"建链 + 注册 + 传输"这三处内核换掉。
73+
74+
注册阶段(`register_kv_caches`)不再只收集 `(base, size)`,而是为每个 KV tensor 调 `flagcxP2pEngineReg` 拿到 mrId 列表,并记录 `kv_caches_base_addr`;同时 `self.rpc_port = engine.get_rpc_port()`,Decode 侧拉起 `StartRpcServer`。`FlagCXAgentMetadata.remote_port` 从原来的 side_channel 端口改为 `rpc_port`(side channel 仍保留,只用于请求级信号)。
75+
76+
删除旧逻辑:`_create_pair_comm`、`_decode_listener_thread`、uid 握手、`flagcxCommInitRank`、`_register_kv_for_comm`、`flagcxOneSideRegister/SignalRegister`、以及 `flagcxBatchPut + flagcxWaitCounter` 那条计数器同步链路。`PairCommInfo` 退化为按 session 缓存的连接句柄(直接交给引擎的 session 表管理,connector 侧甚至可以不再自己存)。
77+
78+
传输阶段(`_send_blocks`)由 Prefill 发起。原来按 `(src_off, dst_off, mr_idx)` 拼 batch 的代码改为:先 `conn = engine.get_conn(f"{remote_host}:{remote_rpc_port}")`(首次触发握手 + desc 表交换,之后复用);沿用现有的 `_group_contiguous` 把连续 block 合并;对每个分组,本地源 = `本地KV base + 偏移`,远端 desc 由 `engine.make_desc(conn, 远端KV base + 偏移, size)` 算出;最后 `engine.write_vector_sync(conn, mrIds, srcVec, sizeVec, descs)`。无需 signal buffer、无需 counter。Prefill 完成后照旧通过 ZMQ 回 `TRANS_DONE`,Decode 的 `_receive_kv` 收到即标记请求完成。
79+
80+
**Decode 是写入接收方,对外公布自己的 `host:rpc_port` 和 KV base 地址;Prefill 连过去、取回 Decode 的 desc 表、用绝对地址 RDMA write 把 KV 推过去。**
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
3+
excalidraw-plugin: parsed
4+
tags: [excalidraw]
5+
6+
---
7+
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
8+
9+
# Excalidraw Data
10+
11+
## Text Elements
12+
step1. router.py
13+
8998 ^KkFVgy26
14+
15+
prefill ^p2zXz5WG
16+
17+
decode ^MJBmjTSA
18+
19+
ip1:201 ^hsLVGJbt
20+
21+
ip2:202 ^uCepUuyQ
22+
23+
step2. port8998 ^x9kxKHZZ
24+
25+
ZMQ ^Q5bY0qpu
26+
27+
%%
28+
## Drawing
29+
```compressed-json
30+
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
73+
```
74+
%%

0 commit comments

Comments
 (0)