已合并
feat(comm): add async RDMA support for HNS1825 #1374
feat(comm): add async RDMA support for HNS1825 #1374
已合并
xun创建于 7月24日
共 48 个文件变更+5420-20
@@ -83,6 +83,7 @@ DMA backend selection for `TPUT_ASYNC` and `TGET_ASYNC`:
83|-------|-------------|83|-------|-------------|
84| `DmaEngine::SDMA` | SDMA engine (supports 1D transfer) |84| `DmaEngine::SDMA` | SDMA engine (supports 1D transfer) |
85| `DmaEngine::URMA` | URMA engine (supports 1D transfer, Ascend950 / NPU_ARCH 3510 only; requires CANN >= 9.1.0) |85| `DmaEngine::URMA` | URMA engine (supports 1D transfer, Ascend950 / NPU_ARCH 3510 only; requires CANN >= 9.1.0) |
86+| `DmaEngine::RDMA` | RDMA engine (supports 1D transfer, Ascend950 / NPU_ARCH 3510 only; currently supports only the HNS1825 NIC platform) |
86 87 
87### AsyncEvent88### AsyncEvent
88 89 
@@ -83,6 +83,7 @@ comm::TTEST(signal, 1, comm::WaitCmp::GE);
83|-------|-------------|83|-------|-------------|
84| `DmaEngine::SDMA` | SDMA 引擎(支持一维传输,Ascend950 上仅支持TGET|84| `DmaEngine::SDMA` | SDMA 引擎(支持一维传输,Ascend950 上仅支持TGET|
85| `DmaEngine::URMA` | URMA 引擎(支持一维传输,仅 Ascend950 / NPU_ARCH 3510;要求 CANN >= 9.1.0)|85| `DmaEngine::URMA` | URMA 引擎(支持一维传输,仅 Ascend950 / NPU_ARCH 3510;要求 CANN >= 9.1.0)|
86+| `DmaEngine::RDMA` | RDMA 引擎(支持一维传输,仅 Ascend950 / NPU_ARCH 3510;当前网卡平台仅支持 HNS1825)|
86 87 
87### AsyncEvent88### AsyncEvent
88 89 
@@ -13,6 +13,7 @@ Data flow:
13- `engine`:13- `engine`:
14 - `DmaEngine::SDMA` (default)14 - `DmaEngine::SDMA` (default)
15 - `DmaEngine::URMA` (Ascend950, NPU_ARCH 3510 only)15 - `DmaEngine::URMA` (Ascend950, NPU_ARCH 3510 only)
16+ - `DmaEngine::RDMA` (Ascend950, NPU_ARCH 3510 only; currently supports only the HNS1825 NIC platform)
16 17 
17> **Important (SDMA path)**18> **Important (SDMA path)**
18> `TGET_ASYNC` with `DmaEngine::SDMA` currently supports **only flat contiguous logical 1D tensors**.19> `TGET_ASYNC` with `DmaEngine::SDMA` currently supports **only flat contiguous logical 1D tensors**.
@@ -82,6 +83,39 @@ PTO_INTERNAL bool BuildAsyncSession(__gm__ uint8_t *workspace,
82 83 
83URMA does not require `scratchTile` — polling uses `ld_dev`/`st_dev` hardware intrinsics directly.84URMA does not require `scratchTile` — polling uses `ld_dev`/`st_dev` hardware intrinsics directly.
84 85 
86+### RDMA Construction (NPU_ARCH 3510 only)
87+ 
88+RDMA is intended for standard cross-node networking and currently supports only the HNS1825 NIC platform.
89+ 
90+```cpp
91+#ifdef PTO_RDMA_SUPPORTED
92+template <DmaEngine engine, typename ScratchTile>
93+PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile,
94+ __gm__ uint8_t *workspace,
95+ uint32_t myPe,
96+ AsyncSession &session,
97+ uint32_t syncId = 0);
98+#endif
99+```
100+ 
101+| Parameter | Description |
102+|---|---|
103+| `scratchTile` | UB/Vec tile used for WQE/CQE control data; at least 64 bytes. |
104+| `workspace` | GM pointer returned by the host-side RDMA initialization flow. |
105+| `myPe` | Local rank id used to select the registered local memory region. |
106+| `session` | Output `AsyncSession` object. |
107+| `syncId` | MTE/scalar synchronization event id in the range 0-7. |
108+ 
109+Build one peer-independent session, then select the source rank with the explicit `peer` argument:
110+ 
111+```cpp
112+comm::AsyncSession session;
113+if (comm::BuildAsyncSession<comm::DmaEngine::RDMA>(scratchTile, rdmaWorkspace, myPe, session, syncId)) {
114+ auto event = comm::TGET_ASYNC<comm::DmaEngine::RDMA>(dstG, srcG, session, peer);
115+ (void)event.Wait(session);
116+}
117+```
118+ 
85## Constraints119## Constraints
86 120 
87- `GlobalSrcData::RawDType == GlobalDstData::RawDType`121- `GlobalSrcData::RawDType == GlobalDstData::RawDType`
@@ -93,6 +127,9 @@ URMA does not require `scratchTile` — polling uses `ld_dev`/`st_dev` hardware
93- URMA is only available on NPU_ARCH 3510 (Ascend950)127- URMA is only available on NPU_ARCH 3510 (Ascend950)
94- URMA requires CANN Toolkit **>= 9.1.0**128- URMA requires CANN Toolkit **>= 9.1.0**
95- The symmetric data buffer passed to `UrmaWorkspaceManager::Init()` must be backed by huge-page memory (allocate with `ACL_MEM_MALLOC_HUGE_ONLY`). The underlying MR registration requires huge-page backing; `ACL_MEM_MALLOC_HUGE_FIRST` may silently fall back to 4KB pages for small allocations, causing registration to fail129- The symmetric data buffer passed to `UrmaWorkspaceManager::Init()` must be backed by huge-page memory (allocate with `ACL_MEM_MALLOC_HUGE_ONLY`). The underlying MR registration requires huge-page backing; `ACL_MEM_MALLOC_HUGE_FIRST` may silently fall back to 4KB pages for small allocations, causing registration to fail
130+- RDMA is available only on Ascend950 / NPU_ARCH 3510 and currently supports only the HNS1825 NIC platform
131+- RDMA source and destination tensors must be flat contiguous logical 1D, and the complete local and remote ranges must lie within memory regions registered during host initialization
132+- One RDMA transfer must not exceed `0x7fffffff` bytes
96 133 
97If the 1D contiguous requirement is not met, current implementation returns an invalid async event (`handle == 0`).134If the 1D contiguous requirement is not met, current implementation returns an invalid async event (`handle == 0`).
98 135 
@@ -121,11 +158,14 @@ The completion mechanism differs by engine, but user-facing quiet semantics are
121 158 
122- **SDMA**: Each `TGET_ASYNC` submits data-transfer SQEs and flag SQEs that mark completion of that operation. `Wait` or `Test` on its returned event polls the corresponding flags to determine whether that `TGET_ASYNC` has completed; completion also guarantees that all earlier SDMA operations in the same session have completed.159- **SDMA**: Each `TGET_ASYNC` submits data-transfer SQEs and flag SQEs that mark completion of that operation. `Wait` or `Test` on its returned event polls the corresponding flags to determine whether that `TGET_ASYNC` has completed; completion also guarantees that all earlier SDMA operations in the same session have completed.
123- **URMA**: `TGET_ASYNC` submits an RDMA READ WQE and rings the doorbell immediately. `Wait` polls the Completion Queue (CQ) until all expected CQEs have been consumed.160- **URMA**: `TGET_ASYNC` submits an RDMA READ WQE and rings the doorbell immediately. `Wait` polls the Completion Queue (CQ) until all expected CQEs have been consumed.
161+- **RDMA**: `TGET_ASYNC` submits an RDMA READ WQE to the queue selected by `peer`. Completion is tracked independently for each peer/queue.
124 162 
125- `event.Wait(session)` — blocks until **all async operations issued since the last Wait** are complete163- `event.Wait(session)` — blocks until **all async operations issued since the last Wait** are complete
126 164 
127This means after multiple `TGET_ASYNC` calls, a single `Wait` on the last returned `AsyncEvent` drains all pending operations (similar to shmem's quiet semantics).165This means after multiple `TGET_ASYNC` calls, a single `Wait` on the last returned `AsyncEvent` drains all pending operations (similar to shmem's quiet semantics).
128 166 
167+For RDMA operations targeting different peers, wait for the last event of each peer separately.
168+ 
129Up to 64 operations may be outstanding in one session before submission can apply backpressure.169Up to 64 operations may be outstanding in one session before submission can apply backpressure.
130 170 
131After wait succeeds, all issued reads into `dstGlobalData` are complete.171After wait succeeds, all issued reads into `dstGlobalData` are complete.
@@ -13,6 +13,7 @@
13- `engine`:13- `engine`:
14 - `DmaEngine::SDMA`(默认)14 - `DmaEngine::SDMA`(默认)
15 - `DmaEngine::URMA`(Ascend 950PR/Ascend 950DT,仅NPU_ARCH 3510)15 - `DmaEngine::URMA`(Ascend 950PR/Ascend 950DT,仅NPU_ARCH 3510)
16+ - `DmaEngine::RDMA`(Ascend 950PR/Ascend 950DT,仅NPU_ARCH 3510;当前网卡平台仅支持 HNS1825)
16 17 
17> **注意(SDMA路径)**18> **注意(SDMA路径)**
18> `TGET_ASYNC` 配合 `DmaEngine::SDMA` 目前**仅支持扁平连续的逻辑一维tensor**。19> `TGET_ASYNC` 配合 `DmaEngine::SDMA` 目前**仅支持扁平连续的逻辑一维tensor**。
@@ -79,6 +80,39 @@ PTO_INTERNAL bool BuildAsyncSession(__gm__ uint8_t *workspace,
79 80 
80URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直接操作。81URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直接操作。
81 82 
83+### RDMA构建(仅NPU_ARCH 3510)
84+ 
85+RDMA面向经典跨机场景组网,当前网卡平台仅支持 HNS1825。
86+ 
87+```cpp
88+#ifdef PTO_RDMA_SUPPORTED
89+template <DmaEngine engine, typename ScratchTile>
xun
xunxun8月14日

考虑需要推荐用户使用新接口,在这里不说明老接口

likedislike
xun
xun
8月14日 评论:
xun
xun
8月14日 评论:
90+PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile,
91+ __gm__ uint8_t *workspace,
92+ uint32_t myPe,
93+ AsyncSession &session,
94+ uint32_t syncId = 0);
95+#endif
96+```
97+ 
98+| 参数 | 说明 |
99+|---|---|
100+| `scratchTile` | 用于 WQE/CQE 控制数据的 UB/Vec tile,至少 64 字节。|
101+| `workspace` | Host侧 RDMA 初始化流程返回的 GM 指针。|
102+| `myPe` | 本地 rank id,用于选择已注册的本地内存区域。|
103+| `session` | 输出的 `AsyncSession` 对象。|
104+| `syncId` | MTE/Scalar 同步事件 ID,取值范围为 0-7。|
105+ 
106+构建一次不绑定 peer 的Session,再通过显式 `peer` 参数选择源rank:
107+ 
108+```cpp
109+comm::AsyncSession session;
110+if (comm::BuildAsyncSession<comm::DmaEngine::RDMA>(scratchTile, rdmaWorkspace, myPe, session, syncId)) {
111+ auto event = comm::TGET_ASYNC<comm::DmaEngine::RDMA>(dstG, srcG, session, peer);
112+ (void)event.Wait(session);
113+}
114+```
115+ 
82## 约束116## 约束
83 117 
84- `GlobalSrcData::RawDType == GlobalDstData::RawDType`118- `GlobalSrcData::RawDType == GlobalDstData::RawDType`
@@ -90,6 +124,9 @@ URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直
90- URMA仅在NPU_ARCH 3510(Ascend 950PR/Ascend 950DT)上可用124- URMA仅在NPU_ARCH 3510(Ascend 950PR/Ascend 950DT)上可用
91- URMA要求CANN Toolkit **>= 9.1.0**125- URMA要求CANN Toolkit **>= 9.1.0**
92- 传给 `UrmaWorkspaceManager::Init()` 的对称数据缓冲区必须由大页内存支撑(使用 `ACL_MEM_MALLOC_HUGE_ONLY` 分配)。底层MR注册要求大页背景;`ACL_MEM_MALLOC_HUGE_FIRST` 在小尺寸分配时可能静默回退到4KB小页,导致注册失败126- 传给 `UrmaWorkspaceManager::Init()` 的对称数据缓冲区必须由大页内存支撑(使用 `ACL_MEM_MALLOC_HUGE_ONLY` 分配)。底层MR注册要求大页背景;`ACL_MEM_MALLOC_HUGE_FIRST` 在小尺寸分配时可能静默回退到4KB小页,导致注册失败
127+- RDMA仅在Ascend 950PR/Ascend 950DT(NPU_ARCH 3510)上可用,当前网卡平台仅支持 HNS1825
128+- RDMA源、目的tensor必须是扁平连续的逻辑一维,且本地和远端完整传输范围必须位于Host初始化阶段注册的内存区域内
129+- 单次RDMA传输不能超过 `0x7fffffff` 字节
93 130 
94若不满足一维连续要求,当前实现返回无效async event(`handle == 0`)。131若不满足一维连续要求,当前实现返回无效async event(`handle == 0`)。
95 132 
@@ -118,11 +155,14 @@ URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直
118 155 
119- **SDMA**:每次`TGET_ASYNC`都会提交数据传输SQE和用于标记本次操作完成的flag SQE。对其返回的Event调用`Wait`或`Test`时,通过轮询对应flag判断该次`TGET_ASYNC`是否完成;完成后也能保证同一Session中此前提交的所有SDMA操作均已完成。156- **SDMA**:每次`TGET_ASYNC`都会提交数据传输SQE和用于标记本次操作完成的flag SQE。对其返回的Event调用`Wait`或`Test`时,通过轮询对应flag判断该次`TGET_ASYNC`是否完成;完成后也能保证同一Session中此前提交的所有SDMA操作均已完成。
120- **URMA**:`TGET_ASYNC` 立即提交RDMA READ WQE并敲门铃。`Wait` 通过轮询Completion Queue(CQ)等待所有预期的CQE被消费。157- **URMA**:`TGET_ASYNC` 立即提交RDMA READ WQE并敲门铃。`Wait` 通过轮询Completion Queue(CQ)等待所有预期的CQE被消费。
158+- **RDMA**:`TGET_ASYNC` 向 `peer` 选择的队列提交RDMA READ WQE;不同peer/queue分别跟踪完成状态。
121 159 
122- `event.Wait(session)` —阻塞,直到**自上次Wait以来所有已发出的异步操作**全部完成160- `event.Wait(session)` —阻塞,直到**自上次Wait以来所有已发出的异步操作**全部完成
123 161 
124这意味着多次 `TGET_ASYNC` 调用后,只需对最后一个返回的 `AsyncEvent` 调用一次 `Wait`,即可等待所有pending操作完成(类似shmem的quiet语义)。162这意味着多次 `TGET_ASYNC` 调用后,只需对最后一个返回的 `AsyncEvent` 调用一次 `Wait`,即可等待所有pending操作完成(类似shmem的quiet语义)。
125 163 
164+RDMA操作涉及不同peer时,必须分别等待每个peer的最后一个Event。
165+ 
126同一Session最多可有64个未完成操作,超过后提交可能产生背压。166同一Session最多可有64个未完成操作,超过后提交可能产生背压。
127 167 
128wait成功后,所有已发出的 `dstGlobalData` 读入数据均已全部就绪。168wait成功后,所有已发出的 `dstGlobalData` 读入数据均已全部就绪。
@@ -14,6 +14,7 @@ Data flow:
14- `engine`:14- `engine`:
15 - `DmaEngine::SDMA` (default)15 - `DmaEngine::SDMA` (default)
16 - `DmaEngine::URMA` (Ascend950, NPU_ARCH 3510 only)16 - `DmaEngine::URMA` (Ascend950, NPU_ARCH 3510 only)
17+ - `DmaEngine::RDMA` (Ascend950, NPU_ARCH 3510 only; currently supports only the HNS1825 NIC platform)
17 18 
18> **Important (SDMA path)**19> **Important (SDMA path)**
19> `TPUT_ASYNC` with `DmaEngine::SDMA` currently supports **only flat contiguous logical 1D tensors**.20> `TPUT_ASYNC` with `DmaEngine::SDMA` currently supports **only flat contiguous logical 1D tensors**.
@@ -84,6 +85,39 @@ PTO_INTERNAL bool BuildAsyncSession(__gm__ uint8_t *workspace,
84 85 
85URMA does not require `scratchTile` — polling uses `ld_dev`/`st_dev` hardware intrinsics directly.86URMA does not require `scratchTile` — polling uses `ld_dev`/`st_dev` hardware intrinsics directly.
86 87 
88+### RDMA Construction (NPU_ARCH 3510 only)
89+ 
90+RDMA is intended for standard cross-node networking and currently supports only the HNS1825 NIC platform.
91+ 
92+```cpp
93+#ifdef PTO_RDMA_SUPPORTED
94+template <DmaEngine engine, typename ScratchTile>
95+PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile,
96+ __gm__ uint8_t *workspace,
97+ uint32_t myPe,
98+ AsyncSession &session,
99+ uint32_t syncId = 0);
100+#endif
101+```
102+ 
103+| Parameter | Description |
104+|---|---|
105+| `scratchTile` | UB/Vec tile used for WQE/CQE control data; at least 64 bytes. |
106+| `workspace` | GM pointer returned by the host-side RDMA initialization flow. |
107+| `myPe` | Local rank id used to select the registered local memory region. |
108+| `session` | Output `AsyncSession` object. |
109+| `syncId` | MTE/scalar synchronization event id in the range 0-7. |
110+ 
111+Build one peer-independent session, then select the destination rank with the explicit `peer` argument:
112+ 
113+```cpp
114+comm::AsyncSession session;
115+if (comm::BuildAsyncSession<comm::DmaEngine::RDMA>(scratchTile, rdmaWorkspace, myPe, session, syncId)) {
116+ auto event = comm::TPUT_ASYNC<comm::DmaEngine::RDMA>(dstG, srcG, session, peer);
117+ (void)event.Wait(session);
118+}
119+```
120+ 
87## Constraints121## Constraints
88 122 
89- `GlobalSrcData::RawDType == GlobalDstData::RawDType`123- `GlobalSrcData::RawDType == GlobalDstData::RawDType`
@@ -95,6 +129,9 @@ URMA does not require `scratchTile` — polling uses `ld_dev`/`st_dev` hardware
95- URMA is only available on NPU_ARCH 3510 (Ascend950)129- URMA is only available on NPU_ARCH 3510 (Ascend950)
96- URMA requires CANN Toolkit **>= 9.1.0**130- URMA requires CANN Toolkit **>= 9.1.0**
97- The symmetric data buffer passed to `UrmaWorkspaceManager::Init()` must be backed by huge-page memory (allocate with `ACL_MEM_MALLOC_HUGE_ONLY`). The underlying MR registration requires huge-page backing; `ACL_MEM_MALLOC_HUGE_FIRST` may silently fall back to 4KB pages for small allocations, causing registration to fail131- The symmetric data buffer passed to `UrmaWorkspaceManager::Init()` must be backed by huge-page memory (allocate with `ACL_MEM_MALLOC_HUGE_ONLY`). The underlying MR registration requires huge-page backing; `ACL_MEM_MALLOC_HUGE_FIRST` may silently fall back to 4KB pages for small allocations, causing registration to fail
132+- RDMA is available only on Ascend950 / NPU_ARCH 3510 and currently supports only the HNS1825 NIC platform
133+- RDMA source and destination tensors must be flat contiguous logical 1D, and the complete local and remote ranges must lie within memory regions registered during host initialization
134+- One RDMA transfer must not exceed `0x7fffffff` bytes
98 135 
99If the 1D contiguous requirement is not met, current implementation returns an invalid async event (`handle == 0`).136If the 1D contiguous requirement is not met, current implementation returns an invalid async event (`handle == 0`).
100 137 
@@ -123,11 +160,14 @@ The completion mechanism differs by engine, but user-facing quiet semantics are
123 160 
124- **SDMA**: Each `TPUT_ASYNC` submits data-transfer SQEs and flag SQEs that mark completion of that operation. `Wait` or `Test` on its returned event polls the corresponding flags to determine whether that `TPUT_ASYNC` has completed; completion also guarantees that all earlier SDMA operations in the same session have completed.161- **SDMA**: Each `TPUT_ASYNC` submits data-transfer SQEs and flag SQEs that mark completion of that operation. `Wait` or `Test` on its returned event polls the corresponding flags to determine whether that `TPUT_ASYNC` has completed; completion also guarantees that all earlier SDMA operations in the same session have completed.
125- **URMA**: `TPUT_ASYNC` submits an RDMA WRITE WQE and rings the doorbell immediately. `Wait` polls the Completion Queue (CQ) until all expected CQEs have been consumed.162- **URMA**: `TPUT_ASYNC` submits an RDMA WRITE WQE and rings the doorbell immediately. `Wait` polls the Completion Queue (CQ) until all expected CQEs have been consumed.
163+- **RDMA**: `TPUT_ASYNC` submits an RDMA WRITE WQE to the queue selected by `peer`. Completion is tracked independently for each peer/queue.
126 164 
127- `event.Wait(session)` — blocks until **all async operations issued since the last Wait** are complete165- `event.Wait(session)` — blocks until **all async operations issued since the last Wait** are complete
128 166 
129This means after multiple `TPUT_ASYNC` calls, a single `Wait` on the last returned `AsyncEvent` drains all pending operations (similar to shmem's quiet semantics).167This means after multiple `TPUT_ASYNC` calls, a single `Wait` on the last returned `AsyncEvent` drains all pending operations (similar to shmem's quiet semantics).
130 168 
169+For RDMA operations targeting different peers, wait for the last event of each peer separately.
170+ 
131Up to 64 operations may be outstanding in one session before submission can apply backpressure.171Up to 64 operations may be outstanding in one session before submission can apply backpressure.
132 172 
133After wait succeeds, all issued writes to `dstGlobalData` are complete.173After wait succeeds, all issued writes to `dstGlobalData` are complete.
@@ -13,6 +13,7 @@
13- `engine`:13- `engine`:
14 - `DmaEngine::SDMA`(默认)14 - `DmaEngine::SDMA`(默认)
15 - `DmaEngine::URMA`(Ascend 950PR/Ascend 950DT,仅NPU_ARCH 3510)15 - `DmaEngine::URMA`(Ascend 950PR/Ascend 950DT,仅NPU_ARCH 3510)
16+ - `DmaEngine::RDMA`(Ascend 950PR/Ascend 950DT,仅NPU_ARCH 3510;当前网卡平台仅支持 HNS1825)
16 17 
17> **注意(SDMA路径)**18> **注意(SDMA路径)**
18> `TPUT_ASYNC` 配合 `DmaEngine::SDMA` 目前**仅支持扁平连续的逻辑一维tensor**。19> `TPUT_ASYNC` 配合 `DmaEngine::SDMA` 目前**仅支持扁平连续的逻辑一维tensor**。
@@ -79,6 +80,39 @@ PTO_INTERNAL bool BuildAsyncSession(__gm__ uint8_t *workspace,
79 80 
80URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直接操作。81URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直接操作。
81 82 
83+### RDMA构建(仅NPU_ARCH 3510)
84+ 
85+RDMA面向经典跨机场景组网,当前网卡平台仅支持 HNS1825。
86+ 
87+```cpp
88+#ifdef PTO_RDMA_SUPPORTED
89+template <DmaEngine engine, typename ScratchTile>
90+PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile,
91+ __gm__ uint8_t *workspace,
92+ uint32_t myPe,
93+ AsyncSession &session,
94+ uint32_t syncId = 0);
95+#endif
96+```
97+ 
98+| 参数 | 说明 |
99+|---|---|
100+| `scratchTile` | 用于 WQE/CQE 控制数据的 UB/Vec tile,至少 64 字节。|
101+| `workspace` | Host侧 RDMA 初始化流程返回的 GM 指针。|
102+| `myPe` | 本地 rank id,用于选择已注册的本地内存区域。|
103+| `session` | 输出的 `AsyncSession` 对象。|
104+| `syncId` | MTE/Scalar 同步事件 ID,取值范围为 0-7。|
105+ 
106+构建一次不绑定 peer 的Session,再通过显式 `peer` 参数选择目标rank:
107+ 
108+```cpp
109+comm::AsyncSession session;
110+if (comm::BuildAsyncSession<comm::DmaEngine::RDMA>(scratchTile, rdmaWorkspace, myPe, session, syncId)) {
111+ auto event = comm::TPUT_ASYNC<comm::DmaEngine::RDMA>(dstG, srcG, session, peer);
112+ (void)event.Wait(session);
113+}
114+```
115+ 
82## 约束116## 约束
83 117 
84- `GlobalSrcData::RawDType == GlobalDstData::RawDType`118- `GlobalSrcData::RawDType == GlobalDstData::RawDType`
@@ -90,6 +124,9 @@ URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直
90- URMA仅在NPU_ARCH 3510(Ascend 950PR/Ascend 950DT)上可用124- URMA仅在NPU_ARCH 3510(Ascend 950PR/Ascend 950DT)上可用
91- URMA要求CANN Toolkit **>= 9.1.0**125- URMA要求CANN Toolkit **>= 9.1.0**
92- 传给 `UrmaWorkspaceManager::Init()` 的对称数据缓冲区必须由大页内存支撑(使用 `ACL_MEM_MALLOC_HUGE_ONLY` 分配)。底层MR注册要求大页背景;`ACL_MEM_MALLOC_HUGE_FIRST` 在小尺寸分配时可能静默回退到4KB小页,导致注册失败126- 传给 `UrmaWorkspaceManager::Init()` 的对称数据缓冲区必须由大页内存支撑(使用 `ACL_MEM_MALLOC_HUGE_ONLY` 分配)。底层MR注册要求大页背景;`ACL_MEM_MALLOC_HUGE_FIRST` 在小尺寸分配时可能静默回退到4KB小页,导致注册失败
127+- RDMA仅在Ascend 950PR/Ascend 950DT(NPU_ARCH 3510)上可用,当前网卡平台仅支持 HNS1825
128+- RDMA源、目的tensor必须是扁平连续的逻辑一维,且本地和远端完整传输范围必须位于Host初始化阶段注册的内存区域内
129+- 单次RDMA传输不能超过 `0x7fffffff` 字节
93 130 
94若不满足一维连续要求,当前实现返回无效async event(`handle == 0`)。131若不满足一维连续要求,当前实现返回无效async event(`handle == 0`)。
95 132 
@@ -118,11 +155,14 @@ URMA不需要 `scratchTile`——轮询通过 `ld_dev`/`st_dev` 硬件原语直
118 155 
119- **SDMA**:每次`TPUT_ASYNC`都会提交数据传输SQE和用于标记本次操作完成的flag SQE。对其返回的Event调用`Wait`或`Test`时,通过轮询对应flag判断该次`TPUT_ASYNC`是否完成;完成后也能保证同一Session中此前提交的所有SDMA操作均已完成。156- **SDMA**:每次`TPUT_ASYNC`都会提交数据传输SQE和用于标记本次操作完成的flag SQE。对其返回的Event调用`Wait`或`Test`时,通过轮询对应flag判断该次`TPUT_ASYNC`是否完成;完成后也能保证同一Session中此前提交的所有SDMA操作均已完成。
120- **URMA**:`TPUT_ASYNC` 立即提交RDMA WRITE WQE并敲门铃。`Wait` 通过轮询Completion Queue(CQ)等待所有预期的CQE被消费。157- **URMA**:`TPUT_ASYNC` 立即提交RDMA WRITE WQE并敲门铃。`Wait` 通过轮询Completion Queue(CQ)等待所有预期的CQE被消费。
158+- **RDMA**:`TPUT_ASYNC` 向 `peer` 选择的队列提交RDMA WRITE WQE;不同peer/queue分别跟踪完成状态。
121 159 
122- `event.Wait(session)` —阻塞,直到**自上次Wait以来所有已发出的异步操作**全部完成160- `event.Wait(session)` —阻塞,直到**自上次Wait以来所有已发出的异步操作**全部完成
123 161 
124这意味着多次 `TPUT_ASYNC` 调用后,只需对最后一个返回的 `AsyncEvent` 调用一次 `Wait`,即可等待所有pending操作完成(类似shmem的quiet语义)。162这意味着多次 `TPUT_ASYNC` 调用后,只需对最后一个返回的 `AsyncEvent` 调用一次 `Wait`,即可等待所有pending操作完成(类似shmem的quiet语义)。
125 163 
164+RDMA操作涉及不同peer时,必须分别等待每个peer的最后一个Event。
165+ 
126同一Session最多可有64个未完成操作,超过后提交可能产生背压。166同一Session最多可有64个未完成操作,超过后提交可能产生背压。
127 167 
128wait成功后,所有已发出的 `dstGlobalData` 写入均已全部完成。168wait成功后,所有已发出的 `dstGlobalData` 写入均已全部完成。
@@ -35,11 +35,11 @@ comm/
35├── a5/ # A5 (Ascend 950) architecture implementations35├── a5/ # A5 (Ascend 950) architecture implementations
36│ ├── T*.hpp # Sync instructions (include a2a3/ counterparts)36│ ├── T*.hpp # Sync instructions (include a2a3/ counterparts)
37│ └── async/37│ └── async/
38-│ ├── TPutAsync.hpp # TPUT_ASYNC_IMPL (SDMA with MTE fallback + URMA)38+│ ├── TPutAsync.hpp # TPUT_ASYNC_IMPL (SDMA with MTE fallback + URMA + RDMA)
39-│ └── TGetAsync.hpp # TGET_ASYNC_IMPL (SDMA + URMA)39+│ └── TGetAsync.hpp # TGET_ASYNC_IMPL (SDMA + URMA + RDMA)
40│40│
41└── async_common/ # Common async implementations (shared by a2a3/a5)41└── async_common/ # Common async implementations (shared by a2a3/a5)
42- ├── async_types.hpp # SDMA/URMA session and context types42+ ├── async_types.hpp # SDMA/URMA/RDMA session and context types
43 ├── async_event_impl.hpp # AsyncEvent::Wait/Test, BuildAsyncSession43 ├── async_event_impl.hpp # AsyncEvent::Wait/Test, BuildAsyncSession
44 ├── TPutAsyncCommonDetail.hpp # Common TPUT_ASYNC detail helpers + SDMA impl44 ├── TPutAsyncCommonDetail.hpp # Common TPUT_ASYNC detail helpers + SDMA impl
45 └── TGetAsyncCommonDetail.hpp # Common TGET_ASYNC detail helpers + SDMA impl45 └── TGetAsyncCommonDetail.hpp # Common TGET_ASYNC detail helpers + SDMA impl
@@ -66,7 +66,7 @@ comm/
66| Category | Instructions | Description |66| Category | Instructions | Description |
67|---|---|---|67|---|---|---|
68| Point-to-Point (sync) | `TPUT`, `TGET` | Remote write / read through UB staging tile. Supports single-buffer and ping-pong double-buffering modes. |68| Point-to-Point (sync) | `TPUT`, `TGET` | Remote write / read through UB staging tile. Supports single-buffer and ping-pong double-buffering modes. |
69-| Point-to-Point (async) | `TPUT_ASYNC`, `TGET_ASYNC` | GM-to-GM DMA via SDMA or URMA engine. Returns `AsyncEvent` for later Wait/Test. |69+| Point-to-Point (async) | `TPUT_ASYNC`, `TGET_ASYNC` | GM-to-GM DMA via SDMA, URMA, or RDMA engine. Returns `AsyncEvent` for later Wait/Test. |
70| Signal Synchronization | `TNOTIFY`, `TWAIT`, `TTEST` | Flag-based cross-NPU synchronization. Signals are `int32_t` scalars or 2D grids. |70| Signal Synchronization | `TNOTIFY`, `TWAIT`, `TTEST` | Flag-based cross-NPU synchronization. Signals are `int32_t` scalars or 2D grids. |
71| Collective | `TGATHER`, `TSCATTER`, `TBROADCAST`, `TREDUCE` | Multi-rank operations via `ParallelGroup`. Root-initiated; support chunked 2D sliding and ping-pong. |71| Collective | `TGATHER`, `TSCATTER`, `TBROADCAST`, `TREDUCE` | Multi-rank operations via `ParallelGroup`. Root-initiated; support chunked 2D sliding and ping-pong. |
72 72 
@@ -35,11 +35,11 @@ comm/
35├── a5/ # A5(Ascend 950)架构实现35├── a5/ # A5(Ascend 950)架构实现
36│ ├── T*.hpp # 同步指令(include a2a3/ 对应文件)36│ ├── T*.hpp # 同步指令(include a2a3/ 对应文件)
37│ └── async/37│ └── async/
38-│ ├── TPutAsync.hpp # TPUT_ASYNC_IMPL(SDMA + MTE 回退 + URMA)38+│ ├── TPutAsync.hpp # TPUT_ASYNC_IMPL(SDMA + MTE 回退 + URMA + RDMA)
39-│ └── TGetAsync.hpp # TGET_ASYNC_IMPL(SDMA + URMA)39+│ └── TGetAsync.hpp # TGET_ASYNC_IMPL(SDMA + URMA + RDMA)
40│40│
41└── async_common/ # 异步公共实现(a2a3/a5 共享)41└── async_common/ # 异步公共实现(a2a3/a5 共享)
42- ├── async_types.hpp # SDMA/URMA 会话与上下文类型42+ ├── async_types.hpp # SDMA/URMA/RDMA 会话与上下文类型
43 ├── async_event_impl.hpp # AsyncEvent::Wait/Test、BuildAsyncSession43 ├── async_event_impl.hpp # AsyncEvent::Wait/Test、BuildAsyncSession
44 ├── TPutAsyncCommonDetail.hpp # TPUT_ASYNC 公共辅助函数 + SDMA 实现44 ├── TPutAsyncCommonDetail.hpp # TPUT_ASYNC 公共辅助函数 + SDMA 实现
45 └── TGetAsyncCommonDetail.hpp # TGET_ASYNC 公共辅助函数 + SDMA 实现45 └── TGetAsyncCommonDetail.hpp # TGET_ASYNC 公共辅助函数 + SDMA 实现
@@ -66,7 +66,7 @@ comm/
66| 类别 | 指令 | 说明 |66| 类别 | 指令 | 说明 |
67|---|---|---|67|---|---|---|
68| 点对点(同步) | `TPUT`、`TGET` | 通过 UB 暂存 Tile 的远程写/读。支持单缓冲和 ping-pong 双缓冲模式。 |68| 点对点(同步) | `TPUT`、`TGET` | 通过 UB 暂存 Tile 的远程写/读。支持单缓冲和 ping-pong 双缓冲模式。 |
69-| 点对点(异步) | `TPUT_ASYNC`、`TGET_ASYNC` | 通过 SDMA 或 URMA 引擎进行 GM-to-GM DMA。返回 `AsyncEvent` 用于后续 Wait/Test。 |69+| 点对点(异步) | `TPUT_ASYNC`、`TGET_ASYNC` | 通过 SDMA、URMA 或 RDMA 引擎进行 GM-to-GM DMA。返回 `AsyncEvent` 用于后续 Wait/Test。 |
70| 信号同步 | `TNOTIFY`、`TWAIT`、`TTEST` | 基于标志的跨 NPU 同步。信号为 `int32_t` 标量或二维网格。 |70| 信号同步 | `TNOTIFY`、`TWAIT`、`TTEST` | 基于标志的跨 NPU 同步。信号为 `int32_t` 标量或二维网格。 |
71| 集合通信 | `TGATHER`、`TSCATTER`、`TBROADCAST`、`TREDUCE` | 基于 `ParallelGroup` 的多 rank 操作。由 root 发起,支持 2D 分块滑动和 ping-pong 双缓冲。 |71| 集合通信 | `TGATHER`、`TSCATTER`、`TBROADCAST`、`TREDUCE` | 基于 `ParallelGroup` 的多 rank 操作。由 root 发起,支持 2D 分块滑动和 ping-pong 双缓冲。 |
72 72 
@@ -15,6 +15,9 @@ See LICENSE in the root of the software repository for the full text of the Lice
15#ifdef PTO_URMA_SUPPORTED15#ifdef PTO_URMA_SUPPORTED
16#include "pto/comm/async/urma/urma_async_intrin.hpp"16#include "pto/comm/async/urma/urma_async_intrin.hpp"
17#endif17#endif
18+#ifdef PTO_RDMA_SUPPORTED
19+#include "pto/comm/async/rdma/rdma_async_intrin.hpp"
20+#endif
18 21 
19namespace pto {22namespace pto {
20namespace comm {23namespace comm {
@@ -60,11 +63,50 @@ TGET_ASYNC_URMA_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData,
60}63}
61#endif64#endif
62 65 
66+#ifdef PTO_RDMA_SUPPORTED
67+// RDMA GET: async remote read. dst is local, src is remote.
68+template <typename GlobalDstData, typename GlobalSrcData>
69+PTO_INTERNAL AsyncEvent TGET_ASYNC_RDMA_IMPL(
70+ GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session, uint32_t peer)
71+{
72+ (void)TGetAsyncCheckTensorCompatibility<GlobalDstData, GlobalSrcData>();
73+ 
74+ PTO_ASSERT(
75+ TGetAsyncIsFlatContiguous1D(srcGlobalData),
76+ "TGET_ASYNC RDMA: src tensor must be flat contiguous 1D (packed layout, single logical line).");
77+ PTO_ASSERT(
78+ TGetAsyncIsFlatContiguous1D(dstGlobalData),
79+ "TGET_ASYNC RDMA: dst tensor must be flat contiguous 1D (packed layout, single logical line).");
80+ 
81+ const uint32_t srcElems = TGetAsyncGetTotalElemCount(srcGlobalData);
82+ const uint32_t dstElems = TGetAsyncGetTotalElemCount(dstGlobalData);
83+ PTO_ASSERT(dstElems >= srcElems, "TGET_ASYNC RDMA: dst buffer too small for src data");
84+ 
85+ using T = typename GlobalSrcData::RawDType;
86+ const uint64_t transferSize = static_cast<uint64_t>(srcElems) * sizeof(T);
87+ if (transferSize == 0) {
88+ return AsyncEvent(0, DmaEngine::RDMA);
89+ }
90+ 
91+ const uint64_t eventHandle = rdma::Read(
92+ session, reinterpret_cast<__gm__ uint8_t*>(dstGlobalData.data()),
93+ reinterpret_cast<__gm__ uint8_t*>(srcGlobalData.data()), transferSize, peer);
94+ return AsyncEvent(eventHandle, DmaEngine::RDMA);
95+}
96+ 
97+template <typename GlobalDstData, typename GlobalSrcData>
98+PTO_INTERNAL AsyncEvent
99+TGET_ASYNC_RDMA_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session)
100+{
101+ return TGET_ASYNC_RDMA_IMPL(dstGlobalData, srcGlobalData, session, session.destRankId);
102+}
103+#endif
104+ 
63} // namespace detail105} // namespace detail
64 106 
65// ============================================================================107// ============================================================================
66// Main TGET_ASYNC_IMPL with DmaEngine template parameter108// Main TGET_ASYNC_IMPL with DmaEngine template parameter
67-// A5: SDMA and URMA engines are supported109+// A5: SDMA, URMA, and RDMA engines are supported
68// ============================================================================110// ============================================================================
69 111 
70template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>112template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>
@@ -79,6 +121,13 @@ TGET_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, cons
79#else121#else
80 static_assert(engine != DmaEngine::URMA, "TGET_ASYNC: URMA engine requires NPU_ARCH 3510");122 static_assert(engine != DmaEngine::URMA, "TGET_ASYNC: URMA engine requires NPU_ARCH 3510");
81 return AsyncEvent(0, engine);123 return AsyncEvent(0, engine);
124+#endif
125+ } else if constexpr (engine == DmaEngine::RDMA) {
126+#ifdef PTO_RDMA_SUPPORTED
127+ return detail::TGET_ASYNC_RDMA_IMPL(dstGlobalData, srcGlobalData, session);
128+#else
129+ static_assert(engine != DmaEngine::RDMA, "TGET_ASYNC: RDMA support is not enabled for this build");
130+ return AsyncEvent(0, engine);
82#endif131#endif
83 } else {132 } else {
84 PTO_ASSERT(false, "TGET_ASYNC: unsupported engine");133 PTO_ASSERT(false, "TGET_ASYNC: unsupported engine");
@@ -86,7 +135,7 @@ TGET_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, cons
86 }135 }
87}136}
88 137 
89-// peer overload: URMA uses peer for SQ/CQ/MemInfo; SDMA ignores peer.138+// peer overload: URMA and RDMA use peer for queue/memory selection; SDMA ignores peer.
90template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>139template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>
91PTO_INTERNAL AsyncEvent140PTO_INTERNAL AsyncEvent
92TGET_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session, uint32_t peer)141TGET_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session, uint32_t peer)
@@ -99,6 +148,13 @@ TGET_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, cons
99#else148#else
100 static_assert(engine != DmaEngine::URMA, "TGET_ASYNC: URMA engine requires NPU_ARCH 3510");149 static_assert(engine != DmaEngine::URMA, "TGET_ASYNC: URMA engine requires NPU_ARCH 3510");
101 return AsyncEvent(0, engine);150 return AsyncEvent(0, engine);
151+#endif
152+ } else if constexpr (engine == DmaEngine::RDMA) {
153+#ifdef PTO_RDMA_SUPPORTED
154+ return detail::TGET_ASYNC_RDMA_IMPL(dstGlobalData, srcGlobalData, session, peer);
155+#else
156+ static_assert(engine != DmaEngine::RDMA, "TGET_ASYNC: RDMA support is not enabled for this build");
157+ return AsyncEvent(0, engine);
102#endif158#endif
103 } else {159 } else {
104 PTO_ASSERT(false, "TGET_ASYNC: unsupported engine");160 PTO_ASSERT(false, "TGET_ASYNC: unsupported engine");
@@ -15,6 +15,9 @@ See LICENSE in the root of the software repository for the full text of the Lice
15#ifdef PTO_URMA_SUPPORTED15#ifdef PTO_URMA_SUPPORTED
16#include "pto/comm/async/urma/urma_async_intrin.hpp"16#include "pto/comm/async/urma/urma_async_intrin.hpp"
17#endif17#endif
18+#ifdef PTO_RDMA_SUPPORTED
19+#include "pto/comm/async/rdma/rdma_async_intrin.hpp"
20+#endif
18 21 
19namespace pto {22namespace pto {
20namespace comm {23namespace comm {
@@ -128,11 +131,50 @@ TPUT_ASYNC_URMA_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData,
128}131}
129#endif132#endif
130 133 
134+#ifdef PTO_RDMA_SUPPORTED
135+// RDMA PUT: async remote write. dst is remote, src is local.
136+template <typename GlobalDstData, typename GlobalSrcData>
137+PTO_INTERNAL AsyncEvent TPUT_ASYNC_RDMA_IMPL(
138+ GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session, uint32_t peer)
139+{
140+ (void)TPutAsyncCheckTensorCompatibility<GlobalDstData, GlobalSrcData>();
141+ 
142+ PTO_ASSERT(
143+ TPutAsyncIsFlatContiguous1D(srcGlobalData),
144+ "TPUT_ASYNC RDMA: src tensor must be flat contiguous 1D (packed layout, single logical line).");
145+ PTO_ASSERT(
146+ TPutAsyncIsFlatContiguous1D(dstGlobalData),
147+ "TPUT_ASYNC RDMA: dst tensor must be flat contiguous 1D (packed layout, single logical line).");
148+ 
149+ const uint32_t srcElems = TPutAsyncGetTotalElemCount(srcGlobalData);
150+ const uint32_t dstElems = TPutAsyncGetTotalElemCount(dstGlobalData);
151+ PTO_ASSERT(dstElems >= srcElems, "TPUT_ASYNC RDMA: dst buffer too small for src data");
152+ 
153+ using T = typename GlobalSrcData::RawDType;
154+ const uint64_t transferSize = static_cast<uint64_t>(srcElems) * sizeof(T);
155+ if (transferSize == 0) {
156+ return AsyncEvent(0, DmaEngine::RDMA);
157+ }
158+ 
159+ const uint64_t eventHandle = rdma::Write(
160+ session, reinterpret_cast<__gm__ uint8_t*>(dstGlobalData.data()),
161+ reinterpret_cast<__gm__ uint8_t*>(srcGlobalData.data()), transferSize, peer);
162+ return AsyncEvent(eventHandle, DmaEngine::RDMA);
163+}
164+ 
165+template <typename GlobalDstData, typename GlobalSrcData>
166+PTO_INTERNAL AsyncEvent
167+TPUT_ASYNC_RDMA_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session)
168+{
169+ return TPUT_ASYNC_RDMA_IMPL(dstGlobalData, srcGlobalData, session, session.destRankId);
170+}
171+#endif
172+ 
131} // namespace detail173} // namespace detail
132 174 
133// ============================================================================175// ============================================================================
134// Main TPUT_ASYNC_IMPL with DmaEngine template parameter176// Main TPUT_ASYNC_IMPL with DmaEngine template parameter
135-// A5: SDMA uses MTE fallback for PUT direction; URMA is also supported177+// A5: SDMA uses MTE fallback for PUT direction; URMA and RDMA are also supported
136// ============================================================================178// ============================================================================
137 179 
138template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>180template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>
@@ -147,6 +189,13 @@ TPUT_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, cons
147#else189#else
148 static_assert(engine != DmaEngine::URMA, "TPUT_ASYNC: URMA engine requires NPU_ARCH 3510");190 static_assert(engine != DmaEngine::URMA, "TPUT_ASYNC: URMA engine requires NPU_ARCH 3510");
149 return AsyncEvent(0, engine);191 return AsyncEvent(0, engine);
192+#endif
193+ } else if constexpr (engine == DmaEngine::RDMA) {
194+#ifdef PTO_RDMA_SUPPORTED
195+ return detail::TPUT_ASYNC_RDMA_IMPL(dstGlobalData, srcGlobalData, session);
196+#else
197+ static_assert(engine != DmaEngine::RDMA, "TPUT_ASYNC: RDMA support is not enabled for this build");
198+ return AsyncEvent(0, engine);
150#endif199#endif
151 } else {200 } else {
152 PTO_ASSERT(false, "TPUT_ASYNC: unsupported engine");201 PTO_ASSERT(false, "TPUT_ASYNC: unsupported engine");
@@ -154,7 +203,7 @@ TPUT_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, cons
154 }203 }
155}204}
156 205 
157-// peer overload: URMA uses peer for SQ/CQ/MemInfo; SDMA ignores peer.206+// peer overload: URMA and RDMA use peer for queue/memory selection; SDMA ignores peer.
158template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>207template <DmaEngine engine = DmaEngine::SDMA, typename GlobalDstData, typename GlobalSrcData>
159PTO_INTERNAL AsyncEvent208PTO_INTERNAL AsyncEvent
160TPUT_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session, uint32_t peer)209TPUT_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, const AsyncSession& session, uint32_t peer)
@@ -167,6 +216,13 @@ TPUT_ASYNC_IMPL(GlobalDstData& dstGlobalData, GlobalSrcData& srcGlobalData, cons
167#else216#else
168 static_assert(engine != DmaEngine::URMA, "TPUT_ASYNC: URMA engine requires NPU_ARCH 3510");217 static_assert(engine != DmaEngine::URMA, "TPUT_ASYNC: URMA engine requires NPU_ARCH 3510");
169 return AsyncEvent(0, engine);218 return AsyncEvent(0, engine);
219+#endif
220+ } else if constexpr (engine == DmaEngine::RDMA) {
221+#ifdef PTO_RDMA_SUPPORTED
222+ return detail::TPUT_ASYNC_RDMA_IMPL(dstGlobalData, srcGlobalData, session, peer);
223+#else
224+ static_assert(engine != DmaEngine::RDMA, "TPUT_ASYNC: RDMA support is not enabled for this build");
225+ return AsyncEvent(0, engine);
170#endif226#endif
171 } else {227 } else {
172 PTO_ASSERT(false, "TPUT_ASYNC: unsupported engine");228 PTO_ASSERT(false, "TPUT_ASYNC: unsupported engine");
@@ -0,0 +1,174 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+// RDMA host control plane. The backend selected at build time determines which
12+// manager creates and releases the underlying resources.
13+ 
14+#ifndef PTO_COMM_ASYNC_RDMA_WORKSPACE_MANAGER_HPP
15+#define PTO_COMM_ASYNC_RDMA_WORKSPACE_MANAGER_HPP
16+ 
17+#if defined(__CCE_KT_TEST__)
18+#error "rdma_workspace_manager.hpp is a host-only header and cannot be included in device code."
19+#endif
20+ 
21+#include <cstdint>
22+#include <iostream>
23+#include <string>
24+#include <vector>
25+ 
26+#include "pto/comm/rdma_backend.hpp"
27+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
28+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_arch.hpp"
29+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_workspace_manager.hpp"
30+#endif
31+ 
32+namespace pto {
33+namespace comm {
34+namespace rdma {
35+ 
36+enum class WorkspaceInitResult : uint8_t {
37+ DISABLED = 0,
38+ READY,
39+ ERROR,
40+};
41+ 
42+struct WorkspaceConfig {
43+ uint32_t rankId{0};
44+ uint32_t rankCount{0};
45+ uint32_t phyId{0};
46+ std::string localIp;
47+ uint16_t basePort{60032};
48+ std::vector<std::string> peerIps;
49+ std::vector<uint32_t> peerPhyIds;
50+ std::vector<uint64_t> peerSymAddrs;
51+ void* symmetricAddr{nullptr};
52+ uint64_t symmetricSize{0};
53+};
54+ 
55+class RdmaWorkspaceManager {
56+public:
57+ RdmaWorkspaceManager() = default;
58+ ~RdmaWorkspaceManager() = default;
59+ 
60+ RdmaWorkspaceManager(const RdmaWorkspaceManager&) = delete;
61+ RdmaWorkspaceManager& operator=(const RdmaWorkspaceManager&) = delete;
62+ 
63+ static RdmaBackend ConfiguredBackend()
64+ {
65+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
66+ return RdmaBackend::HNS_1825;
67+#else
68+ return RdmaBackend::NONE;
69+#endif
70+ }
71+ 
72+ static const char* ConfiguredBackendName()
73+ {
74+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
75+ return "HNS_1825";
76+#else
77+ return "NONE";
78+#endif
79+ }
80+ 
81+ static WorkspaceInitResult Preflight()
82+ {
83+ const RdmaBackend backend = ConfiguredBackend();
84+ if (backend == RdmaBackend::NONE) {
85+ return WorkspaceInitResult::DISABLED;
86+ }
87+ 
88+ switch (backend) {
89+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
90+ case RdmaBackend::HNS_1825: {
91+ const hns_1825::ArchitectureProbe arch = hns_1825::ProbeArchitecture();
92+ if (!arch.supported) {
93+ std::cerr << "[RDMA] the compiled HNS_1825 backend is only supported on A5; "
94+ "current architecture is "
95+ << hns_1825::DescribeArchitecture(arch) << std::endl;
96+ return WorkspaceInitResult::ERROR;
97+ }
98+ }
99+ return WorkspaceInitResult::READY;
100+#endif
101+ default:
102+ return WorkspaceInitResult::DISABLED;
103+ }
104+ }
105+ 
106+ WorkspaceInitResult Init(const WorkspaceConfig& config)
107+ {
108+ (void)config;
109+ const WorkspaceInitResult preflight = Preflight();
110+ if (preflight != WorkspaceInitResult::READY) {
111+ return preflight;
112+ }
113+ 
114+ switch (ConfiguredBackend()) {
115+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
116+ case RdmaBackend::HNS_1825: {
117+ hns1825Backend_.SetTraceId(traceId_);
118+ const bool initialized = hns1825Backend_.Init(
119+ config.rankId, config.rankCount, config.phyId, config.localIp, config.basePort, config.peerIps,
120+ config.peerPhyIds, config.peerSymAddrs, config.symmetricAddr, config.symmetricSize);
121+ activeBackend_ = initialized ? RdmaBackend::HNS_1825 : RdmaBackend::NONE;
122+ return initialized ? WorkspaceInitResult::READY : WorkspaceInitResult::ERROR;
123+ }
124+#endif
125+ default:
126+ return WorkspaceInitResult::DISABLED;
127+ }
128+ }
129+ 
130+ bool Finalize()
131+ {
132+ bool ok = true;
133+ switch (activeBackend_) {
134+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
135+ case RdmaBackend::HNS_1825:
136+ ok = hns1825Backend_.Finalize();
137+ break;
138+#endif
139+ default:
140+ break;
141+ }
142+ activeBackend_ = RdmaBackend::NONE;
143+ return ok;
144+ }
145+ 
146+ void SetTraceId(uint32_t traceId) { traceId_ = traceId; }
147+ 
148+ void* GetWorkspaceAddr() const
149+ {
150+ switch (activeBackend_) {
151+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
152+ case RdmaBackend::HNS_1825:
153+ return hns1825Backend_.GetWorkspaceAddr();
154+#endif
155+ default:
156+ return nullptr;
157+ }
158+ }
159+ 
160+ RdmaBackend ActiveBackend() const { return activeBackend_; }
161+ 
162+private:
163+ RdmaBackend activeBackend_{RdmaBackend::NONE};
164+ uint32_t traceId_{0};
165+#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
166+ hns_1825::WorkspaceManager hns1825Backend_;
167+#endif
168+};
169+ 
170+} // namespace rdma
171+} // namespace comm
172+} // namespace pto
173+ 
174+#endif // PTO_COMM_ASYNC_RDMA_WORKSPACE_MANAGER_HPP
@@ -17,6 +17,9 @@ See LICENSE in the root of the software repository for the full text of the Lice
17#ifdef PTO_URMA_SUPPORTED17#ifdef PTO_URMA_SUPPORTED
18#include "pto/comm/async/urma/urma_async_intrin.hpp"18#include "pto/comm/async/urma/urma_async_intrin.hpp"
19#endif19#endif
20+#ifdef PTO_RDMA_SUPPORTED
21+#include "pto/comm/async/rdma/rdma_async_intrin.hpp"
22+#endif
20 23 
21namespace pto {24namespace pto {
22namespace comm {25namespace comm {
@@ -34,7 +37,7 @@ PTO_INTERNAL bool BuildAsyncSession(
34 } else {37 } else {
35 static_assert(38 static_assert(
36 engine == DmaEngine::SDMA,39 engine == DmaEngine::SDMA,
37- "This overload is for SDMA; use the URMA-specific BuildAsyncSession for DmaEngine::URMA");40+ "This overload is for SDMA; use the engine-specific BuildAsyncSession overload for URMA or RDMA");
38 return false;41 return false;
39 }42 }
40}43}
@@ -65,6 +68,39 @@ PTO_INTERNAL bool BuildAsyncSession(__gm__ uint8_t* workspace, uint32_t destRank
65}68}
66#endif69#endif
67 70 
71+#ifdef PTO_RDMA_SUPPORTED
72+// Peer-independent RDMA session builder. The explicit-peer TPUT_ASYNC and
73+// TGET_ASYNC overloads can reuse this session for multiple remote ranks.
74+template <DmaEngine engine, typename ScratchTile>
75+PTO_INTERNAL bool BuildAsyncSession(
76+ ScratchTile& scratchTile, __gm__ uint8_t* workspace, uint32_t myPe, AsyncSession& session, uint32_t syncId = 0)
77+{
78+ static_assert(engine == DmaEngine::RDMA, "This overload is for RDMA only");
79+ rdma::RdmaTmpBuffer tmpBuf{};
80+ if (!rdma::detail::MakeTmpBufferFromTile(scratchTile, tmpBuf)) {
81+ session = AsyncSession{};
82+ return false;
83+ }
84+ return rdma::BuildSession(workspace, myPe, tmpBuf, syncId, session);
85+}
86+ 
87+// Peer-bound RDMA builder retained for callers that use the original async
88+// overload without an explicit peer.
89+template <DmaEngine engine, typename ScratchTile>
90+PTO_INTERNAL bool BuildAsyncSession(
91+ ScratchTile& scratchTile, __gm__ uint8_t* workspace, uint32_t destRankId, uint32_t myPe, AsyncSession& session,
92+ uint32_t syncId = 0)
93+{
94+ static_assert(engine == DmaEngine::RDMA, "This overload is for RDMA only");
95+ rdma::RdmaTmpBuffer tmpBuf{};
96+ if (!rdma::detail::MakeTmpBufferFromTile(scratchTile, tmpBuf)) {
97+ session = AsyncSession{};
98+ return false;
99+ }
100+ return rdma::BuildSession(workspace, destRankId, myPe, tmpBuf, syncId, session);
101+}
102+#endif
103+ 
68// ============================================================================104// ============================================================================
69// AsyncEvent::Wait / Test — AsyncSession overloads (primary user API)105// AsyncEvent::Wait / Test — AsyncSession overloads (primary user API)
70// ============================================================================106// ============================================================================
@@ -80,6 +116,10 @@ PTO_INTERNAL bool AsyncEvent::Wait(const AsyncSession& session) const
80#ifdef PTO_URMA_SUPPORTED116#ifdef PTO_URMA_SUPPORTED
81 case DmaEngine::URMA:117 case DmaEngine::URMA:
82 return urma::detail::UrmaWaitEvent(handle, session);118 return urma::detail::UrmaWaitEvent(handle, session);
119+#endif
120+#ifdef PTO_RDMA_SUPPORTED
121+ case DmaEngine::RDMA:
122+ return rdma::WaitEvent(handle, session);
83#endif123#endif
84 default:124 default:
85 return false;125 return false;
@@ -97,6 +137,10 @@ PTO_INTERNAL bool AsyncEvent::Test(const AsyncSession& session) const
97#ifdef PTO_URMA_SUPPORTED137#ifdef PTO_URMA_SUPPORTED
98 case DmaEngine::URMA:138 case DmaEngine::URMA:
99 return urma::detail::UrmaTestEvent(handle, session);139 return urma::detail::UrmaTestEvent(handle, session);
140+#endif
141+#ifdef PTO_RDMA_SUPPORTED
142+ case DmaEngine::RDMA:
143+ return rdma::TestEvent(handle, session);
100#endif144#endif
101 default:145 default:
102 return false;146 return false;
@@ -145,6 +145,9 @@ struct AsyncSession {
145 145 
146 uint32_t destRankId{0};146 uint32_t destRankId{0};
147 uint32_t qpIdx{0};147 uint32_t qpIdx{0};
148+ 
149+ RdmaBackend rdmaBackend{RdmaBackend::NONE};
150+ uint32_t myPe{0};
148};151};
149 152 
150} // namespace comm153} // namespace comm
@@ -17,6 +17,7 @@ See LICENSE in the root of the software repository for the full text of the Lice
17#include "pto/common/debug.h"17#include "pto/common/debug.h"
18#include "pto/common/type.hpp"18#include "pto/common/type.hpp"
19#include "pto/common/pto_tile.hpp"19#include "pto/common/pto_tile.hpp"
20+#include "pto/comm/rdma_backend.hpp"
20 21 
21namespace pto {22namespace pto {
22namespace comm {23namespace comm {
@@ -121,6 +122,7 @@ enum class ReduceOp : uint8_t {
121enum class DmaEngine : uint8_t {122enum class DmaEngine : uint8_t {
122 SDMA = 0, // Supports 2D transfer123 SDMA = 0, // Supports 2D transfer
123 URMA = 1, // Supports 1D transfer (HCCP V2 Jetty, NPU_ARCH 3510 only)124 URMA = 1, // Supports 1D transfer (HCCP V2 Jetty, NPU_ARCH 3510 only)
125+ RDMA = 2, // RDMA engine; RdmaBackend identifies the NIC implementation compiled into the binary
124};126};
125 127 
126// ============================================================================128// ============================================================================
@@ -352,7 +352,7 @@ PTO_INST AsyncEvent TPUT_ASYNC(
352/**352/**
353 * @brief Asynchronous remote write with explicit peer (A5 / CPU stub).353 * @brief Asynchronous remote write with explicit peer (A5 / CPU stub).
354 *354 *
355- * For URMA: @p peer selects SQ/CQ/MR in UrmaInfo (session need not bind peer).355+ * For URMA and RDMA: @p peer selects the per-peer queue and memory metadata (session need not bind peer).
356 * For SDMA: @p peer is ignored; addressing comes from the GlobalTensor VA.356 * For SDMA: @p peer is ignored; addressing comes from the GlobalTensor VA.
357 * For CPU: @p peer is ignored; use default implementation357 * For CPU: @p peer is ignored; use default implementation
358 */358 */
@@ -383,7 +383,7 @@ PTO_INST AsyncEvent TGET_ASYNC(
383/**383/**
384 * @brief Asynchronous remote read with explicit peer (A5 / CPU stub).384 * @brief Asynchronous remote read with explicit peer (A5 / CPU stub).
385 *385 *
386- * For URMA: @p peer selects SQ/CQ/MR in UrmaInfo (session need not bind peer).386+ * For URMA and RDMA: @p peer selects the per-peer queue and memory metadata (session need not bind peer).
387 * For SDMA: @p peer is ignored; addressing comes from the GlobalTensor VA.387 * For SDMA: @p peer is ignored; addressing comes from the GlobalTensor VA.
388 * For CPU: @p peer is ignored; use default implementation388 * For CPU: @p peer is ignored; use default implementation
389 */389 */
@@ -0,0 +1,29 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+#ifndef PTO_COMM_RDMA_BACKEND_HPP
12+#define PTO_COMM_RDMA_BACKEND_HPP
13+ 
14+#include <cstdint>
15+ 
16+namespace pto {
17+namespace comm {
18+ 
19+// A process may activate at most one RDMA backend. NONE means that no RDMA
20+// control plane has been initialized for the session.
21+enum class RdmaBackend : uint32_t {
22+ NONE = 0,
23+ HNS_1825 = 1,
24+};
25+ 
26+} // namespace comm
27+} // namespace pto
28+ 
29+#endif // PTO_COMM_RDMA_BACKEND_HPP
@@ -0,0 +1,85 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_ARCH_HPP
12+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_ARCH_HPP
13+ 
14+#if defined(__CCE_KT_TEST__)
15+#error "hns_1825_arch.hpp is a host-only header and cannot be included in device code."
16+#endif
17+ 
18+#include <dlfcn.h>
19+ 
20+#include <cstdint>
21+#include <string>
22+ 
23+namespace pto {
24+namespace comm {
25+namespace rdma {
26+namespace hns_1825 {
27+ 
28+struct ArchitectureProbe {
29+ uint32_t compiledNpuArch{0};
30+ std::string runtimeSocName;
31+ bool runtimeResolved{false};
32+ bool supported{false};
33+};
34+ 
35+// Accept HNS1825 only when every available signal agrees with 3510. Host-only
36+// translation units may not define __NPU_ARCH__, so the runtime SoC remains
37+// authoritative in that case.
38+inline ArchitectureProbe ProbeArchitecture()
39+{
40+ ArchitectureProbe result{};
41+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
42+ result.compiledNpuArch = 3510;
43+#elif defined(__NPU_ARCH__)
44+ result.compiledNpuArch = static_cast<uint32_t>(__NPU_ARCH__);
45+#endif
46+ 
47+ using GetSocNameFn = const char* (*)();
48+ auto getSocName = reinterpret_cast<GetSocNameFn>(dlsym(RTLD_DEFAULT, "aclrtGetSocName"));
49+ if (getSocName != nullptr) {
50+ const char* name = getSocName();
51+ if (name != nullptr && name[0] != '\0') {
52+ result.runtimeSocName = name;
53+ result.runtimeResolved = true;
54+ const bool runtimeIs3510 = result.runtimeSocName.find("Ascend950") != std::string::npos ||
55+ result.runtimeSocName.find("dav-3510") != std::string::npos ||
56+ result.runtimeSocName.find("3510") != std::string::npos;
57+ result.supported = runtimeIs3510 && (result.compiledNpuArch == 0 || result.compiledNpuArch == 3510);
58+ return result;
59+ }
60+ }
61+ 
62+ result.supported = result.compiledNpuArch == 3510;
63+ return result;
64+}
65+ 
66+inline std::string DescribeArchitecture(const ArchitectureProbe& probe)
67+{
68+ if (probe.runtimeResolved) {
69+ if (probe.compiledNpuArch != 0) {
70+ return probe.runtimeSocName + " (build NPU_ARCH " + std::to_string(probe.compiledNpuArch) + ')';
71+ }
72+ return probe.runtimeSocName + " (build NPU_ARCH unavailable)";
73+ }
74+ if (probe.compiledNpuArch != 0) {
75+ return std::string("NPU_ARCH ") + std::to_string(probe.compiledNpuArch) + " (runtime SoC unavailable)";
76+ }
77+ return "unknown (runtime SoC and NPU_ARCH unavailable)";
78+}
79+ 
80+} // namespace hns_1825
81+} // namespace rdma
82+} // namespace comm
83+} // namespace pto
84+ 
85+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_ARCH_HPP
@@ -0,0 +1,538 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+// HNS_1825 (Hi1825) RoCE RDMA device-side backend for A5.
12+//
13+// Posts RDMA WRITE / READ from AIV using the RdmaInfo table supplied via RdmaExecContext.
14+// WQE/CQE staging uses the RDMA session's UB scratch; atomics are not supported.
15+ 
16+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_BACKEND_HPP
17+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_BACKEND_HPP
18+ 
19+#include "pto/pto-inst.hpp"
20+#include "pto/comm/async_common/async_types.hpp"
21+#include "pto/comm/async/rdma/rdma_device_common.hpp"
22+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_types.hpp"
23+// AscendC::GetSystemCycle only — do NOT include full kernel_operator.h here.
24+// That pulls adv_api/hccl (`using namespace HcclApi`) and collides with ST
25+// common.hpp symbols (MAX_CC_TILING_NUM / GROUP_NAME_SIZE / ALG_CONFIG_SIZE).
26+#if !defined(__CPU_SIM) && !defined(__COSTMODEL)
27+#include "kernel_operator_sys_var_intf.h"
28+#endif
29+ 
30+namespace pto {
31+namespace comm {
32+namespace rdma {
33+namespace hns_1825 {
34+namespace detail {
35+ 
36+constexpr uint32_t kCqeReadSize = sizeof(Hns1825Cqe);
37+ 
38+struct PostSendResult {
39+ uint32_t curHead;
40+ uint32_t status;
41+};
42+ 
43+// NIC consumes big-endian; AscendC is little-endian. SQ/CQ head-tail mirrors stay host-order;
44+// only NIC-visible payloads (WQE dwords / software doorbell) are byte-swapped.
45+AICORE inline uint16_t Htobe16(uint16_t v) { return (uint16_t)(((v & 0x00ffU) << 8) | ((v & 0xff00U) >> 8)); }
46+ 
47+AICORE inline uint32_t Htobe32(uint32_t v)
48+{
49+ return ((v & 0x000000ffU) << 24) | ((v & 0x0000ff00U) << 8) | ((v & 0x00ff0000U) >> 8) | ((v & 0xff000000U) >> 24);
50+}
51+ 
52+AICORE inline uint64_t Htobe64(uint64_t v)
53+{
54+ return ((v & 0x00000000000000ffULL) << 56) | ((v & 0x000000000000ff00ULL) << 40) |
55+ ((v & 0x0000000000ff0000ULL) << 24) | ((v & 0x00000000ff000000ULL) << 8) |
56+ ((v & 0x000000ff00000000ULL) >> 8) | ((v & 0x0000ff0000000000ULL) >> 24) |
57+ ((v & 0x00ff000000000000ULL) >> 40) | ((v & 0xff00000000000000ULL) >> 56);
58+}
59+ 
60+// Host-order u32 mirror access (SQ/CQ head-tail): bypass the scalar data cache.
61+AICORE inline uint32_t ReadU32Gm(uint64_t addr) { return ld_dev((__gm__ uint32_t*)addr, 0); }
62+ 
63+AICORE inline void WriteU32Gm(uint64_t addr, uint32_t value) { st_dev(value, (__gm__ uint32_t*)addr, 0); }
64+ 
65+// Order scalar UB writes before MTE3 copies a NIC-visible record (WQE / software doorbell) to GM, then order the
66+// MTE3 store before the subsequent scalar dcci / st_dev. Post-copy sync MUST be MTE3->Scalar (not MTE3->MTE2):
67+// RoCE consumers are scalar NIC doorbell paths.
68+AICORE inline void WriteUbToGmWithSync(uint64_t gmAddr, __ubuf__ uint8_t* ub, uint32_t size, uint32_t syncId)
69+{
70+ set_flag(PIPE_S, PIPE_MTE3, syncId);
71+ wait_flag(PIPE_S, PIPE_MTE3, syncId);
72+ copy_ubuf_to_gm_align_v2(
73+ reinterpret_cast<__gm__ uint32_t*>(gmAddr), reinterpret_cast<__ubuf__ uint32_t*>(ub), 0, 1, size, 0, 0, 0);
74+ set_flag(PIPE_MTE3, PIPE_S, syncId);
75+ wait_flag(PIPE_MTE3, PIPE_S, syncId);
76+}
77+ 
78+// Copy `size` bytes from GM to the UB scratch, then make them scalar-readable.
79+AICORE inline void ReadGmToUb(__ubuf__ uint8_t* ub, uint64_t gmAddr, uint32_t size, uint32_t syncId)
80+{
81+ copy_gm_to_ubuf_align_v2(
82+ reinterpret_cast<__ubuf__ uint32_t*>(ub), reinterpret_cast<__gm__ uint32_t*>(gmAddr), 0, 1, size, 0, 0, 0, 0, 0,
83+ 0);
84+ set_flag(PIPE_MTE2, PIPE_S, syncId);
85+ wait_flag(PIPE_MTE2, PIPE_S, syncId);
86+}
87+ 
88+// SQ ring slot (one 64B WQEBB).
89+AICORE inline __gm__ uint8_t* GetSendWqe(__gm__ RoceSqCtx* sqCtx, uint32_t idx)
90+{
91+ return (__gm__ uint8_t*)sqCtx->bufAddr + (uint64_t)idx * kHns1825WqebbSize;
92+}
93+ 
94+// A CQE is ready when its owner bit matches the lap parity from the current consumer index.
95+AICORE inline bool CheckCqeOwner(__ubuf__ Hns1825Cqe* cqe, uint32_t curTail, uint32_t cqRing)
96+{
97+ constexpr uint32_t kOwnerShift = 31; // owner bit at dw1[31]
98+ uint32_t curOwner = ((cqe->owner_id_qpn & (1U << kOwnerShift)) != 0);
99+ uint32_t expectOwner = (uint32_t)(((curTail & cqRing) == 0));
100+ return (expectOwner ^ curOwner) != 0;
@@ -0,0 +1,277 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+// Host-only hcomm ABI definitions for the RoCE (HNS_1825) control plane.
12+//
13+// Public hcomm types come from <hcomm/hcomm_res.h>. ChannelEntity / SqContext /
14+// CqContext / RegedBufferEntity are not in the public include path; they are
15+// defined here ABI-compatible with hcomm_res_entity_defs.h. PTO supports the
16+// split-doorbell and vendor-specified RoCE context layouts used by
17+// HNS1825; legacy HCOMM V1 contexts are outside this backend's scope.
18+ 
19+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_HCOMM_DEFS_HPP
20+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_HCOMM_DEFS_HPP
21+ 
22+#if defined(__CCE_KT_TEST__)
23+#error "hns_1825_hcomm_defs.hpp is a host-only header and cannot be included in device code."
24+#endif
25+ 
26+#include <hcomm/hcomm_res.h>
27+ 
28+#include <cstddef>
29+#include <cstdint>
30+ 
31+#include "securec.h"
32+ 
33+namespace pto {
34+namespace comm {
35+namespace rdma {
36+namespace hns_1825 {
37+namespace host {
38+ 
39+enum ProtectionType {
40+ PROTECTION_TYPE_RESERVED = -1,
41+ PROTECTION_TYPE_ROCE = 0,
42+ PROTECTION_TYPE_UB = 1,
43+};
44+ 
45+enum RegedBufferType {
46+ REGED_BUFFER_TYPE_RESERVED = -1,
47+ REGED_BUFFER_TYPE_IPC = 0,
48+ REGED_BUFFER_TYPE_RMA = 1,
49+};
50+ 
51+enum RegedNotifyType {
52+ REGED_NOTIFY_TYPE_RESERVED = -1,
53+ REGED_NOTIFY_TYPE_IPC_RT = 0,
54+ REGED_NOTIFY_TYPE_IPC_MEM = 1,
55+ REGED_NOTIFY_TYPE_RMA_RT = 2,
56+ REGED_NOTIFY_TYPE_RMA_MEM = 3,
57+};
58+ 
59+enum SqContextType {
60+ SQ_CONTEXT_TYPE_RESERVED = -1,
61+ SQ_CONTEXT_TYPE_UB_JFS = 0,
62+ SQ_CONTEXT_TYPE_ROCE = 1,
63+};
64+ 
65+enum CqContextType {
66+ CQ_CONTEXT_TYPE_RESERVED = -1,
67+ CQ_CONTEXT_TYPE_UB_JFC = 0,
68+ CQ_CONTEXT_TYPE_ROCE = 1,
69+};
70+ 
71+struct ProtectionInfo {
72+ ProtectionType type;
73+ union {
74+ struct {
75+ uint32_t lkey;
76+ uint32_t rkey;
77+ } roce;
78+ struct {
79+ uint32_t tokenId;
80+ uint32_t tokenValue;
81+ } ub;
82+ uint8_t raws[24];
83+ } memInfo;
84+};
85+ 
86+struct RegedBufferEntity {
87+ RegedBufferType type;
88+ union {
89+ struct {
90+ uint64_t addr;
91+ uint64_t size;
92+ } ipc;
93+ struct {
94+ uint64_t addr;
95+ uint64_t size;
96+ ProtectionInfo protectionInfo;
97+ } rma;
98+ uint8_t raws[56];
99+ } bufferInfo;
100+};
101+ 
102+struct RegedNotifyEntity {
103+ RegedNotifyType type;
104+ union {
105+ struct {
106+ uint64_t addr;
107+ uint32_t size;
108+ int32_t notifyId;
109+ } ipcRt;
110+ struct {
111+ uint64_t addr;
112+ uint32_t size;
113+ } ipcMem;
114+ struct {
115+ uint64_t addr;
116+ uint32_t size;
117+ int32_t notifyId;
118+ ProtectionInfo protectionInfo;
119+ } rmaRt;
120+ struct {
121+ uint64_t addr;
122+ uint32_t size;
123+ ProtectionInfo protectionInfo;
124+ } rmaMem;
125+ uint8_t raws[56];
126+ } notifyInfo;
127+};
128+ 
129+// HCOMM has shipped both a split-doorbell layout and a vendor-specified QoS
130+// layout without adding an ABI tag to these private contexts. Keep explicit
131+// byte-layout views and decode from contextInfo.raws below.
132+#define PTO_HNS1825_ROCE_SQ_CONTEXT_COMMON_FIELDS \
133+ uint64_t sqVa; \
134+ uint64_t headAddr; \
135+ uint64_t tailAddr; \
136+ uint64_t dbHwVa; \
137+ uint64_t dbSwVa; \
138+ uint32_t qpn; \
139+ uint32_t wqeSize; \
140+ uint32_t depth; \
141+ uint8_t sl
142+ 
143+struct RoceSqContextSplitDb {
144+ PTO_HNS1825_ROCE_SQ_CONTEXT_COMMON_FIELDS;
145+ uint8_t mtuShift;
146+};
147+ 
148+struct RoceSqContextVendorSpecified {
149+ PTO_HNS1825_ROCE_SQ_CONTEXT_COMMON_FIELDS;
150+ uint64_t DbVendorSpecified;
151+};
152+ 
153+#undef PTO_HNS1825_ROCE_SQ_CONTEXT_COMMON_FIELDS
154+ 
155+#define PTO_HNS1825_ROCE_CQ_CONTEXT_COMMON_FIELDS \
156+ uint64_t cqVa; \
157+ uint64_t headAddr; \
158+ uint64_t tailAddr; \
159+ uint64_t dbHwVa; \
160+ uint64_t dbSwVa; \
161+ uint32_t cqn; \
162+ uint32_t cqeSize; \
163+ uint32_t cqDepth
164+ 
165+struct RoceCqContextSplitDb {
166+ PTO_HNS1825_ROCE_CQ_CONTEXT_COMMON_FIELDS;
167+};
168+ 
169+struct RoceCqContextVendorSpecified {
170+ PTO_HNS1825_ROCE_CQ_CONTEXT_COMMON_FIELDS;
171+ uint64_t DbVendorSpecified;
172+};
173+ 
174+#undef PTO_HNS1825_ROCE_CQ_CONTEXT_COMMON_FIELDS
175+ 
176+struct SqContext {
177+ SqContextType type;
178+ union {
179+ struct {
180+ uint64_t sqVa;
181+ uint64_t headAddr;
182+ uint64_t tailAddr;
183+ uint64_t dbVa;
184+ uint32_t jfsID;
185+ uint32_t wqeSize;
186+ uint32_t sqDepth;
187+ uint32_t tpID;
188+ uint8_t remoteEID[16];
189+ } ubJfs;
190+ RoceSqContextVendorSpecified roceSq;
191+ uint8_t raws[120];
192+ } contextInfo;
193+};
194+ 
195+struct CqContext {
196+ CqContextType type;
197+ union {
198+ struct {
199+ uint64_t scqVa;
200+ uint64_t headAddr;
201+ uint64_t tailAddr;
202+ uint64_t dbVa;
203+ uint32_t jfcID;
204+ uint32_t cqeSize;
205+ uint32_t cqDepth;
206+ } ubJfc;
207+ RoceCqContextVendorSpecified roceCq;
208+ uint8_t raws[120];
209+ } contextInfo;
210+};
211+ 
212+static_assert(offsetof(RoceSqContextSplitDb, mtuShift) == 53, "unexpected split-db SQ context layout");
213+static_assert(
214+ offsetof(RoceSqContextVendorSpecified, DbVendorSpecified) == 56, "unexpected vendor-specified SQ context layout");
215+static_assert(
216+ offsetof(RoceCqContextVendorSpecified, DbVendorSpecified) == 56, "unexpected vendor-specified CQ context layout");
217+ 
218+template <typename RoceContext, typename Context>
219+inline RoceContext ExtractRoceContext(const Context& context)
220+{
221+ RoceContext roce{};
222+ static_assert(sizeof(roce) <= sizeof(context.contextInfo.raws), "ROCE context view is too large");
223+ (void)memcpy_s(&roce, sizeof(roce), context.contextInfo.raws, sizeof(roce));
224+ return roce;
225+}
226+ 
227+inline RoceSqContextSplitDb ExtractRoceSqContextSplitDb(const SqContext& context)
228+{
229+ return ExtractRoceContext<RoceSqContextSplitDb>(context);
230+}
231+ 
232+inline RoceSqContextVendorSpecified ExtractRoceSqContextVendorSpecified(const SqContext& context)
233+{
234+ return ExtractRoceContext<RoceSqContextVendorSpecified>(context);
235+}
236+ 
237+inline RoceCqContextSplitDb ExtractRoceCqContextSplitDb(const CqContext& context)
238+{
239+ return ExtractRoceContext<RoceCqContextSplitDb>(context);
240+}
241+ 
242+struct ChannelEntity {
243+ CommAbiHeader abiHeader;
244+ CommEngine engine;
245+ CommProtocol protocol;
246+ uint32_t localNotifyNum;
247+ uint32_t remoteNotifyNum;
248+ uint32_t localBufferNum;
249+ uint32_t remoteBufferNum;
250+ uint32_t sqNum;
251+ uint32_t cqNum;
252+ RegedNotifyEntity* localNotifyAddr;
253+ RegedNotifyEntity* remoteNotifyAddr;
254+ RegedBufferEntity* localBufferAddr;
255+ RegedBufferEntity* remoteBufferAddr;
256+ SqContext* sqContextAddr;
257+ CqContext* cqContextAddr;
258+ uint8_t reserve[160];
259+};
260+ 
261+static_assert(sizeof(ProtectionInfo) == 28, "unexpected hcomm ProtectionInfo ABI");
262+static_assert(sizeof(RegedBufferEntity) == 64, "unexpected hcomm RegedBufferEntity ABI");
263+static_assert(sizeof(RegedNotifyEntity) == 64, "unexpected hcomm RegedNotifyEntity ABI");
264+static_assert(sizeof(SqContext) == 128, "unexpected hcomm SqContext ABI");
265+static_assert(sizeof(CqContext) == 128, "unexpected hcomm CqContext ABI");
266+static_assert(sizeof(ChannelEntity) == 256, "unexpected hcomm ChannelEntity ABI");
267+static_assert(offsetof(SqContext, contextInfo) == 8, "unexpected hcomm SqContext union offset");
268+static_assert(offsetof(CqContext, contextInfo) == 8, "unexpected hcomm CqContext union offset");
269+static_assert(offsetof(ChannelEntity, localNotifyAddr) == 48, "unexpected hcomm ChannelEntity pointer offset");
270+ 
271+} // namespace host
272+} // namespace hns_1825
273+} // namespace rdma
274+} // namespace comm
275+} // namespace pto
276+ 
277+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_HCOMM_DEFS_HPP
@@ -0,0 +1,187 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+// HNS_1825 NIC-visible SQ/CQ/WQE/CQE layouts and constants.
12+ 
13+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_TYPES_HPP
14+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_TYPES_HPP
15+ 
16+#include <cstdint>
17+ 
18+#include "pto/comm/async/rdma/rdma_workspace_types.hpp"
19+ 
20+namespace pto {
21+namespace comm {
22+namespace rdma {
23+namespace hns_1825 {
24+ 
25+struct SqContext {
26+ uint32_t wqn;
27+ uint64_t bufAddr;
28+ uint32_t wqeSize;
29+ uint32_t depth;
30+ uint64_t headAddr;
31+ uint64_t tailAddr;
32+ RdmaDbMode dbMode;
33+ uint64_t dbAddr;
34+ uint32_t sl;
35+ uint64_t amoAddr;
36+ uint32_t amoLkey;
37+ uint64_t dbSwAddr;
38+ uint8_t mtuShift{4};
39+ uint8_t dbCos{0x7};
40+ uint8_t reserved[6];
41+};
42+ 
43+struct CqContext {
44+ uint32_t cqn;
45+ uint64_t bufAddr;
46+ uint32_t cqeSize;
47+ uint32_t depth;
48+ uint64_t headAddr;
49+ uint64_t tailAddr;
50+ RdmaDbMode dbMode;
51+ uint64_t dbAddr;
52+ uint64_t dbSwAddr;
53+};
54+ 
55+struct WqeCtrlSeg {
56+ uint8_t owner_sl;
57+ uint8_t df_tsl;
58+ uint16_t wf_bdsl;
59+ uint32_t cl_pi;
60+ uint64_t db;
61+};
62+ 
63+union WqeTaskCommonSeg {
64+ struct {
65+ uint32_t xrc_srqn : 18;
66+ uint32_t ext : 1;
67+ uint32_t dif : 1;
68+ uint32_t rsvd : 3;
69+ uint32_t so : 1;
70+ uint32_t opcode : 5;
71+ uint32_t signal : 1;
72+ uint32_t fence : 1;
73+ uint32_t se : 1;
74+ } bs;
75+ uint32_t value;
76+};
77+ 
78+struct WqeRdmaTaskSeg {
79+ WqeTaskCommonSeg com_tsk;
80+ uint32_t data_len;
81+ uint32_t imm_data;
82+ union {
83+ struct {
84+ uint32_t last_ext_len : 8;
85+ uint32_t cmd_len : 8;
86+ uint32_t pi : 16;
87+ } bs;
88+ uint32_t value;
89+ } dw3;
90+ uint64_t va;
91+ uint32_t rkey;
92+ uint32_t ulp;
93+};
94+ 
95+struct WqeDataSeg {
96+ uint64_t buf_addr;
97+ uint32_t r_len;
98+ uint32_t le_key;
99+};
100+ 
101+struct Cqe {
102+ uint32_t owner_id_qpn;
103+ uint32_t op_sr_wqebb;
104+ uint32_t byte_cnt;
105+ uint32_t imm_data;
106+ uint32_t rsvd_dw5;
107+ uint32_t wqe_num;
108+ uint32_t vlan_queue_index;
109+ uint8_t syndrome;
110+ uint8_t rsvd;
111+ uint16_t wqe_counter;
112+};
113+ 
114+union SqDoorbell {
115+ struct {
116+ uint64_t qpn : 20;
117+ uint64_t cntx_size : 2;
118+ uint64_t rsvd0 : 1;
119+ uint64_t c : 1;
120+ uint64_t cos : 3;
121+ uint64_t type : 5;
122+ uint64_t pi : 8;
123+ uint64_t rsvd1 : 8;
124+ uint64_t xrc_vld : 1;
125+ uint64_t rsvd2 : 1;
126+ uint64_t mtu_shift : 3;
127+ uint64_t sgid_index : 7;
128+ uint64_t sub_type : 4;
129+ } bs;
130+ uint64_t value;
131+};
132+ 
133+constexpr uint32_t kWriteReadWqeSize = 64;
134+constexpr uint32_t kWqebbSize = 64;
135+constexpr uint32_t kDefaultCqeSize = 64;
136+constexpr uint32_t kPollCqThreshold = 10;
137+constexpr uint64_t kMaxTransferBytes = 0x7fffffffULL;
138+constexpr uint32_t kMaxSyncId = 7;
139+constexpr uint32_t kPollCqTimeoutError = 0x10000;
140+constexpr uint32_t kSessionBuildError = 0x20000;
141+constexpr uint32_t kInvalidArgumentError = 0x20001;
142+constexpr uint32_t kInvalidContextError = 0x20002;
143+constexpr uint32_t kCqeError = 0x20003;
144+ 
145+#if defined(PTO_NPU_ARCH_A2A3) || defined(__DAV_C220_VEC__) || defined(__DAV_C220_CUBE__)
146+constexpr uint64_t kCycleToTimeBase = 50;
147+#else
148+constexpr uint64_t kCycleToTimeBase = 1000;
149+#endif
150+constexpr uint64_t kPollCqTimeoutUs = 60ULL * 1000 * 1000;
151+constexpr uint64_t kPollCqTimeoutCycles = kPollCqTimeoutUs * kCycleToTimeBase;
152+ 
153+// Backend-local aliases preserve the vocabulary used by the verified HNS WQE
154+// implementation while keeping NIC layouts inside the HNS1825 backend.
155+using RoceSqCtx = SqContext;
156+using RoceCqCtx = CqContext;
157+using Hns1825WqeCtrlSeg = WqeCtrlSeg;
158+using Hns1825WqeRdmaTaskSeg = WqeRdmaTaskSeg;
159+using Hns1825WqeDataSeg = WqeDataSeg;
160+using Hns1825Cqe = Cqe;
161+using Hns1825SqDb = SqDoorbell;
162+constexpr uint32_t kHns1825WriteReadWqeSize = kWriteReadWqeSize;
163+constexpr uint32_t kHns1825WqebbSize = kWqebbSize;
164+constexpr uint32_t kHns1825DefaultCqeSize = kDefaultCqeSize;
165+constexpr uint32_t kHns1825PollCqThreshold = kPollCqThreshold;
166+constexpr uint64_t kHns1825MaxTransferBytes = kMaxTransferBytes;
167+constexpr uint32_t kHns1825MaxSyncId = kMaxSyncId;
168+constexpr uint32_t kHns1825PollCqTimeoutError = kPollCqTimeoutError;
169+constexpr uint32_t kHns1825SessionBuildError = kSessionBuildError;
170+constexpr uint32_t kHns1825InvalidArgumentError = kInvalidArgumentError;
171+constexpr uint32_t kHns1825InvalidContextError = kInvalidContextError;
172+constexpr uint32_t kHns1825CqeError = kCqeError;
173+constexpr uint64_t kHns1825CycleToTimeBase = kCycleToTimeBase;
174+constexpr uint64_t kHns1825PollCqTimeoutCycles = kPollCqTimeoutCycles;
175+ 
176+static_assert(sizeof(WqeCtrlSeg) == 16, "unexpected HNS1825 WQE control-segment layout");
177+static_assert(sizeof(WqeRdmaTaskSeg) == 32, "unexpected HNS1825 WQE task-segment layout");
178+static_assert(sizeof(WqeDataSeg) == 16, "unexpected HNS1825 WQE data-segment layout");
179+static_assert(sizeof(Cqe) == 32, "unexpected HNS1825 CQE layout");
180+static_assert(sizeof(SqDoorbell) == 8, "unexpected HNS1825 SQ doorbell layout");
181+ 
182+} // namespace hns_1825
183+} // namespace rdma
184+} // namespace comm
185+} // namespace pto
186+ 
187+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_TYPES_HPP
@@ -0,0 +1,216 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_HPP
12+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_HPP
13+ 
14+#if defined(__CCE_KT_TEST__)
15+#error "hns_1825_workspace_manager.hpp is a host-only header and cannot be included in device code."
16+#endif
17+ 
18+#include <arpa/inet.h>
19+ 
20+#include <algorithm>
21+#include <chrono>
22+#include <cstdint>
23+#include <cstdio>
24+#include <cstdlib>
25+#include <iostream>
26+#include <sstream>
27+#include <string>
28+#include <thread>
29+#include <utility>
30+#include <vector>
31+ 
32+#include "securec.h"
33+ 
34+#include "acl/acl.h"
35+ 
36+#include "pto/comm/async/rdma/rdma_workspace_types.hpp"
37+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_hcomm_defs.hpp"
38+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_types.hpp"
39+ 
40+namespace pto {
41+namespace comm {
42+namespace rdma {
43+namespace hns_1825 {
44+ 
45+// HCOMM-based RoCE control plane for the HNS_1825 backend.
46+//
47+// Flow:
48+// 1. Create the local RoCE endpoint.
49+// 2. Register the symmetric communication buffer.
50+// 3. Create one channel per peer and wait for every channel to become ready.
51+// 4. Read ChannelEntity SQ/CQ/MR metadata and copy RdmaInfo to device memory.
52+//
53+// Teardown releases resources in reverse ownership order: channels, registered
54+// memory, endpoint, then device metadata. The caller supplies peer network and
55+// symmetric-memory metadata; opt-in progress logs use PTO_ROCE_VERBOSE=1.
56+class WorkspaceManager {
57+public:
58+ WorkspaceManager() = default;
59+ ~WorkspaceManager();
60+ 
61+ WorkspaceManager(const WorkspaceManager&) = delete;
62+ WorkspaceManager& operator=(const WorkspaceManager&) = delete;
63+ 
64+ void SetTraceId(uint32_t traceId) { traceId_ = traceId; }
65+ 
66+ // rankId/rankCount — this PE and the total number of PEs.
67+ // phyId/localIp — this rank's physical device and RoCE IPv4.
68+ // basePort — base port for the HCOMM channel handshake.
69+ // peerIps/peerPhyIds — every rank's RoCE endpoint information.
70+ // peerSymAddrs — every rank's registered communication-buffer base.
71+ // symmetricAddr/Size — this rank's symmetric communication buffer.
72+ bool Init(
73+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
74+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
75+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize);
76+ 
77+ static bool Verbose();
78+ 
79+ // Best-effort teardown. Every owned resource is attempted once, errors are
80+ // accumulated in the return value, and PTO-side handles are always reset.
81+ bool Finalize();
82+ 
83+ // Device address of the RdmaInfo table passed to the RDMA kernel.
84+ void* GetWorkspaceAddr() const { return rdmaInfoDevice_; }
85+ 
86+private:
87+ struct DecodedRoceSqContext : host::RoceSqContextSplitDb {
88+ const char* abiName;
89+ uint8_t dbCos;
90+ };
91+ 
92+ struct RdmaInfoHostLayout {
93+ size_t totalSize{0};
94+ RdmaInfo* info{nullptr};
95+ RoceSqCtx* sq{nullptr};
96+ RoceSqCtx* rq{nullptr};
97+ RoceCqCtx* scq{nullptr};
98+ RoceCqCtx* rcq{nullptr};
99+ RdmaMemInfo* memory{nullptr};
100+ };
101+ 
102+ template <typename... Args>
103+ void Trace(Args&&... args) const;
104+ 
105+ static uint64_t ElapsedUs(const std::chrono::steady_clock::time_point& start);
106+ static std::string Hex(uint64_t value);
107+ 
108+ void AssignInitArguments(
109+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
110+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
111+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize);
112+ void LogInitConfiguration() const;
113+ bool RunInitStage(bool (WorkspaceManager::*stage)(), const char* stageName);
114+ 
115+ bool DestroyChannels();
116+ bool UnregisterMemory();
117+ bool DestroyEndpoint();
118+ bool FreeDeviceInfo();
119+ bool HasOwnedResources() const;
120+ static bool ValidateBasicInitArguments(
121+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
122+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
123+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize);
124+ static bool ValidatePeerPortMapping(
125+ uint32_t rankCount, const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
126+ const std::vector<uint64_t>& peerSymAddrs);
127+ static bool ValidateInitArguments(
128+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
129+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
130+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize);
131+ void ResetOwnedState();
132+ 
133+ bool CreateEndpoint();
134+ bool RegisterMemory();
135+ bool InitializeChannelDescriptions(std::vector<HcommChannelDesc>& descriptions) const;
136+ bool ConfigureChannelDescription(
137+ HcommChannelDesc& description, std::string& channelName, uint32_t channelIndex, uint32_t remoteRank, uint8_t tc,
138+ uint8_t sl) const;
139+ bool ConfigureChannelDescriptions(
140+ std::vector<HcommChannelDesc>& descriptions, std::vector<std::string>& channelNames);
141+ bool CreateAndValidateChannels(std::vector<HcommChannelDesc>& descriptions);
142+ bool BuildChannels();
143+ bool WaitChannelsReady();
144+ static uint8_t GetEnvU8(const char* name, uint8_t defaultValue, long maximumValue, bool requireMultipleOf4);
145+ static void SetEndpointDescDefaults(EndpointDesc& description);
146+ static void SetChannelDescDefaults(HcommChannelDesc& description);
147+ 
148+ static bool IsDbVendorSpecifiedValid(uint64_t value);
149+ static DecodedRoceSqContext DecodeRoceSqContext(const host::SqContext& context);
150+ static bool ReadU32Device(uint64_t deviceAddress, uint32_t& value);
151+ static bool WriteU32Device(uint64_t deviceAddress, uint32_t value);
152+ bool InitializeQueueMirrors(const host::SqContext& sq, const host::CqContext& cq, uint32_t peer) const;
153+ bool PrepareRdmaInfoLayout(std::vector<uint8_t>& hostBuffer, RdmaInfoHostLayout& layout);
154+ bool FillLocalMemoryInfo(RdmaMemInfo* memory);
155+ bool FillPeerRdmaInfo(size_t channelIndex, RdmaInfoHostLayout& layout);
156+ bool FillPeerRdmaInfoEntries(RdmaInfoHostLayout& layout);
157+ void LogRdmaInfo(const RdmaInfoHostLayout& layout) const;
158+ bool CopyRdmaInfoToDevice(const std::vector<uint8_t>& hostBuffer, size_t totalSize) const;
159+ bool FillRdmaInfo();
160+ static bool IsPowerOfTwo(uint64_t value);
161+ static bool IsU32Aligned(uint64_t address);
162+ static bool IsCacheLineAligned(uint64_t address);
163+ static bool ValidateChannelEntity(const host::ChannelEntity& entity, uint32_t peer);
164+ static bool ValidateRegisteredBuffer(
165+ const host::RegedBufferEntity& buffer, uint64_t expectedAddr, uint64_t expectedSize, const char* side,
166+ uint32_t rank);
167+ static bool ValidateSqContext(const host::SqContext& sq, uint32_t peer);
168+ static bool ValidateCqContext(const host::CqContext& cq, uint32_t peer);
169+ static void CopyRoceSq(RoceSqCtx& destination, const host::SqContext& source);
170+ static void CopyRoceCq(RoceCqCtx& destination, const host::CqContext& source);
171+ bool ReadChannelEntity(ChannelHandle handle, host::ChannelEntity& output);
172+ static bool ReadDeviceStruct(const void* devicePointer, void* host, size_t size);
173+ 
174+ static constexpr uint32_t kDbVendorFieldMask = 0x7;
175+ static constexpr uint32_t kDbCosShift = 24;
176+ static constexpr uint32_t kMtuShiftShift = 50;
177+ static constexpr uint8_t kDefaultMtuShift = 4;
178+ static constexpr uint8_t kDefaultDbCos = 0x7;
179+ static constexpr uint64_t kDbVendorSpecifiedMask = (static_cast<uint64_t>(kDbVendorFieldMask) << kDbCosShift) |
180+ (static_cast<uint64_t>(kDbVendorFieldMask) << kMtuShiftShift);
181+ static constexpr uint32_t kMaxRanksPerNic = 16;
182+ static constexpr uint32_t kDefaultRoceRetryCount = 7;
183+ static constexpr uint32_t kDefaultRoceRetryIntervalMs = 20;
184+ static constexpr uint32_t kChannelStatusPollIntervalPerChannelMs = 10;
185+ static constexpr uint32_t kChannelStatusPollTimeoutPerChannelMs = 60000;
186+ 
187+ uint32_t rankId_{0};
188+ uint32_t rankCount_{1};
189+ uint32_t phyId_{0};
190+ std::string localIp_;
191+ uint16_t basePort_{60032};
192+ std::vector<std::string> peerIps_;
193+ std::vector<uint32_t> peerPhyIds_;
194+ std::vector<uint64_t> peerSymAddrs_;
195+ void* symmetricAddr_{nullptr};
196+ uint64_t symmetricSize_{0};
197+ 
198+ EndpointHandle endpoint_{nullptr};
199+ HcommMemHandle memHandle_{nullptr};
200+ std::vector<ChannelHandle> channelHandles_;
201+ std::vector<uint32_t> channelPeer_;
202+ void* rdmaInfoDevice_{nullptr};
203+ bool initialized_{false};
204+ uint32_t traceId_{0};
205+};
206+ 
207+} // namespace hns_1825
208+} // namespace rdma
209+} // namespace comm
210+} // namespace pto
211+ 
212+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_workspace_manager_lifecycle.hpp"
213+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_workspace_manager_channel.hpp"
214+#include "pto/comm/async/rdma/backends/hns_1825/hns_1825_workspace_manager_info.hpp"
215+ 
216+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_HPP
@@ -0,0 +1,294 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_CHANNEL_HPP
12+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_CHANNEL_HPP
13+ 
14+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_HPP
15+#error "Include hns_1825_workspace_manager.hpp instead of this internal implementation header."
16+#endif
17+ 
18+namespace pto {
19+namespace comm {
20+namespace rdma {
21+namespace hns_1825 {
22+ 
23+inline bool WorkspaceManager::CreateEndpoint()
24+{
25+ EndpointDesc description{};
26+ HcommResult result = EndpointDescInit(&description, 1);
27+ if (result != 0) {
28+ std::cerr << "[RoCE] EndpointDescInit failed: " << result << std::endl;
29+ return false;
30+ }
31+ // HCOMM initializes unspecified descriptor bytes to 0xFF. Replace those
32+ // sentinels before setting the attributes used by PTO.
33+ SetEndpointDescDefaults(description);
34+ description.protocol = COMM_PROTOCOL_ROCE;
35+ description.commAddr.type = COMM_ADDR_TYPE_IP_V4;
36+ if (inet_pton(AF_INET, localIp_.c_str(), &description.commAddr.addr) != 1) {
37+ std::cerr << "[RoCE] invalid local ip: " << localIp_ << std::endl;
38+ return false;
39+ }
40+ description.loc.locType = ENDPOINT_LOC_TYPE_HOST;
41+ description.loc.device.devPhyId = phyId_;
42+ result = HcommEndpointCreate(&description, &endpoint_);
43+ if (result != 0 || endpoint_ == nullptr) {
44+ std::cerr << "[RoCE] HcommEndpointCreate failed: " << result << std::endl;
45+ return false;
46+ }
47+ Trace("CREATE endpoint handle=", endpoint_, " phyId=", phyId_, " ip=", localIp_);
48+ if (Verbose()) {
49+ std::cerr << "[RoCE][rank " << rankId_ << "] (1/4) endpoint created (ROCE, phyId=" << phyId_
50+ << ", ip=" << localIp_ << ")" << std::endl;
51+ }
52+ return true;
53+}
54+ 
55+inline bool WorkspaceManager::RegisterMemory()
56+{
57+ CommMem memory{};
58+ memory.type = COMM_MEM_TYPE_DEVICE;
59+ memory.addr = symmetricAddr_;
60+ memory.size = symmetricSize_;
61+ const HcommResult result = HcommMemReg(endpoint_, "PtoRoceBuffer", &memory, &memHandle_);
62+ if (result != 0 || memHandle_ == nullptr) {
63+ std::cerr << "[RoCE] HcommMemReg failed: " << result << std::endl;
64+ return false;
65+ }
66+ Trace("CREATE mem-reg handle=", memHandle_, " addr=", symmetricAddr_, " size=", symmetricSize_);
67+ if (Verbose()) {
68+ std::cerr << "[RoCE][rank " << rankId_ << "] (2/4) MR registered: addr=0x" << std::hex
69+ << reinterpret_cast<uint64_t>(symmetricAddr_) << std::dec << " size=" << symmetricSize_ << std::endl;
70+ }
71+ return true;
72+}
73+ 
74+inline bool WorkspaceManager::InitializeChannelDescriptions(std::vector<HcommChannelDesc>& descriptions) const
75+{
76+ const HcommResult result = HcommChannelDescInit(descriptions.data(), static_cast<uint32_t>(descriptions.size()));
77+ if (result != 0) {
78+ std::cerr << "[RoCE] HcommChannelDescInit failed: " << result << std::endl;
79+ return false;
80+ }
81+ for (HcommChannelDesc& description : descriptions) {
82+ SetChannelDescDefaults(description);
83+ }
84+ return true;
85+}
86+ 
87+inline bool WorkspaceManager::ConfigureChannelDescription(
88+ HcommChannelDesc& description, std::string& channelName, uint32_t channelIndex, uint32_t remoteRank, uint8_t tc,
89+ uint8_t sl) const
90+{
91+ description.remoteEndpoint.protocol = COMM_PROTOCOL_ROCE;
92+ description.remoteEndpoint.commAddr.type = COMM_ADDR_TYPE_IP_V4;
93+ if (inet_pton(AF_INET, peerIps_[remoteRank].c_str(), &description.remoteEndpoint.commAddr.addr) != 1) {
94+ std::cerr << "[RoCE] invalid peer ip[" << remoteRank << "]: " << peerIps_[remoteRank] << std::endl;
95+ return false;
96+ }
97+ description.remoteEndpoint.loc.locType = ENDPOINT_LOC_TYPE_HOST;
98+ description.remoteEndpoint.loc.device.devPhyId = peerPhyIds_[remoteRank];
99+ description.notifyNum = 3;
100+ description.exchangeAllMems = true;
101+ description.memHandles = nullptr;
102+ description.memHandleNum = 0;
103+ description.roceAttr.queueNum = 1;
104+ description.roceAttr.retryCnt = kDefaultRoceRetryCount;
105+ description.roceAttr.retryInterval = kDefaultRoceRetryIntervalMs;
106+ description.roceAttr.tc = tc;
107+ description.roceAttr.sl = sl;
108+ description.roceAttr.qpThreshold = 0;
109+ description.socket = nullptr;
110+ description.qos = 0;
111+ const bool isServer = rankId_ < remoteRank;
112+ description.role = isServer ? HCOMM_SOCKET_ROLE_SERVER : HCOMM_SOCKET_ROLE_CLIENT;
113+ const uint32_t serverRank = isServer ? rankId_ : remoteRank;
114+ const uint32_t clientRank = isServer ? remoteRank : rankId_;
115+ const uint32_t lowRank = std::min(rankId_, remoteRank);
116+ const uint32_t highRank = std::max(rankId_, remoteRank);
117+ channelName = "pto-rdma/" + std::to_string(lowRank) + "-" + std::to_string(highRank) + "/ch0";
118+ description.channelName = channelName.c_str();
119+ description.port = static_cast<uint16_t>(
120+ basePort_ + (serverRank % kMaxRanksPerNic) * kMaxRanksPerNic + (clientRank % kMaxRanksPerNic));
121+ if (Verbose()) {
122+ std::cerr << "[RoCE][rank " << rankId_ << "] channel[" << channelIndex << "] -> peer " << remoteRank
123+ << " ip=" << peerIps_[remoteRank] << " role=" << (isServer ? "SERVER" : "CLIENT")
124+ << " phyId=" << peerPhyIds_[remoteRank] << " port=" << description.port
125+ << " tc=" << static_cast<int>(tc) << " sl=" << static_cast<int>(sl)
126+ << " retryCnt=" << description.roceAttr.retryCnt
127+ << " retryIntervalMs=" << description.roceAttr.retryInterval << std::endl;
128+ }
129+ return true;
130+}
131+ 
132+inline bool WorkspaceManager::ConfigureChannelDescriptions(
133+ std::vector<HcommChannelDesc>& descriptions, std::vector<std::string>& channelNames)
134+{
135+ const uint8_t tc = GetEnvU8("HCCL_RDMA_TC", 132, 255, /*requireMultipleOf4*/ true);
136+ const uint8_t sl = GetEnvU8("HCCL_RDMA_SL", 4, 7, /*requireMultipleOf4*/ false);
137+ channelPeer_.clear();
138+ uint32_t channelIndex = 0;
139+ for (uint32_t remoteRank = 0; remoteRank < rankCount_; ++remoteRank) {
140+ if (remoteRank == rankId_) {
141+ continue;
142+ }
143+ if (!ConfigureChannelDescription(
144+ descriptions[channelIndex], channelNames[channelIndex], channelIndex, remoteRank, tc, sl)) {
145+ return false;
146+ }
147+ channelPeer_.push_back(remoteRank);
148+ ++channelIndex;
149+ }
150+ return true;
151+}
152+ 
153+inline bool WorkspaceManager::CreateAndValidateChannels(std::vector<HcommChannelDesc>& descriptions)
154+{
155+ const uint32_t channelNum = static_cast<uint32_t>(descriptions.size());
156+ channelHandles_.assign(channelNum, 0);
157+ if (Verbose()) {
158+ std::cerr << "[RoCE][rank " << rankId_ << "] (3/4) HcommChannelCreate: " << channelNum
159+ << " channel(s), waiting for QP handshake ..." << std::endl;
160+ }
161+ const HcommResult result = HcommChannelCreate(
162+ endpoint_, COMM_ENGINE_AIV, descriptions.data(), channelNum,
163+ reinterpret_cast<ChannelHandle*>(channelHandles_.data()));
164+ if (result != 0) {
165+ std::cerr << "[RoCE] HcommChannelCreate failed: " << result << std::endl;
166+ return false;
167+ }
168+ for (uint32_t index = 0; index < channelNum; ++index) {
169+ if (channelHandles_[index] == 0) {
170+ std::cerr << "[RoCE] HcommChannelCreate returned a null handle at index " << index << std::endl;
171+ return false;
172+ }
173+ Trace(
174+ "CREATE channel index=", index, " peer=", channelPeer_[index],
175+ " handle=", Hex(static_cast<uint64_t>(channelHandles_[index])));
176+ }
177+ if (!WaitChannelsReady()) {
178+ return false;
179+ }
180+ if (Verbose()) {
181+ std::cerr << "[RoCE][rank " << rankId_ << "] (3/4) all channels READY" << std::endl;
182+ }
183+ return true;
184+}
185+ 
186+inline bool WorkspaceManager::BuildChannels()
187+{
188+ if (rankCount_ <= 1) {
189+ return true;
190+ }
191+ const uint32_t channelNum = rankCount_ - 1;
192+ std::vector<HcommChannelDesc> descriptions(channelNum);
193+ std::vector<std::string> channelNames(channelNum);
194+ return InitializeChannelDescriptions(descriptions) && ConfigureChannelDescriptions(descriptions, channelNames) &&
195+ CreateAndValidateChannels(descriptions);
196+}
197+ 
198+inline bool WorkspaceManager::WaitChannelsReady()
199+{
200+ if (channelHandles_.empty()) {
201+ return true;
202+ }
203+ constexpr int32_t kChannelReady = 0;
204+ constexpr int32_t kChannelConnecting = 1;
205+ constexpr int32_t kChannelFailed = 2;
206+ constexpr int32_t kChannelTimeout = 3;
207+ const uint32_t channelNum = static_cast<uint32_t>(channelHandles_.size());
208+ const uint32_t pollIntervalMs = channelNum * kChannelStatusPollIntervalPerChannelMs;
209+ const uint32_t pollTimeoutMs = channelNum * kChannelStatusPollTimeoutPerChannelMs;
210+ std::vector<int32_t> statuses(channelNum, kChannelConnecting);
211+ uint32_t elapsedMs = 0;
212+ while (elapsedMs <= pollTimeoutMs) {
213+ const HcommResult result = HcommChannelGetStatus(channelHandles_.data(), channelNum, statuses.data());
214+ if (result != 0) {
215+ std::cerr << "[RoCE] HcommChannelGetStatus failed: " << result << std::endl;
216+ return false;
217+ }
218+ bool allReady = true;
219+ for (uint32_t index = 0; index < channelNum; ++index) {
220+ const int32_t status = statuses[index];
221+ if (status == kChannelReady) {
222+ continue;
223+ }
224+ allReady = false;
225+ if (status == kChannelConnecting) {
226+ continue;
227+ }
228+ const char* reason = status == kChannelFailed ? "failed" :
229+ status == kChannelTimeout ? "timed out" :
230+ "returned an unknown status";
231+ std::cerr << "[RoCE] channel " << index << " (peer " << channelPeer_[index] << ") " << reason << ": "
232+ << status << std::endl;
233+ return false;
234+ }
235+ if (allReady) {
236+ if (Verbose()) {
237+ std::cerr << "[RoCE][rank " << rankId_ << "] channels READY after " << elapsedMs << " ms" << std::endl;
238+ }
239+ return true;
240+ }
241+ std::this_thread::sleep_for(std::chrono::milliseconds(pollIntervalMs));
242+ elapsedMs += pollIntervalMs;
243+ }
244+ std::cerr << "[RoCE] wait channel READY timed out after " << elapsedMs << " ms" << std::endl;
245+ return false;
246+}
247+ 
248+inline uint8_t WorkspaceManager::GetEnvU8(
249+ const char* name, uint8_t defaultValue, long maximumValue, bool requireMultipleOf4)
250+{
251+ const char* value = std::getenv(name);
252+ if (value == nullptr) {
253+ return defaultValue;
254+ }
255+ char* end = nullptr;
256+ const long parsed = std::strtol(value, &end, 10);
257+ if (end == value || *end != '\0' || parsed < 0 || parsed > maximumValue) {
258+ std::cerr << "[RoCE] env " << name << "='" << value << "' invalid (expect 0.." << maximumValue
259+ << "); using default " << static_cast<int>(defaultValue) << std::endl;
260+ return defaultValue;
261+ }
262+ if (requireMultipleOf4 && parsed % 4 != 0) {
263+ std::cerr << "[RoCE] env " << name << "=" << parsed << " must be a multiple of 4; using default "
264+ << static_cast<int>(defaultValue) << std::endl;
265+ return defaultValue;
266+ }
267+ return static_cast<uint8_t>(parsed);
268+}
269+ 
270+inline void WorkspaceManager::SetEndpointDescDefaults(EndpointDesc& description)
271+{
272+ (void)memset_s(&description, sizeof(description), 0, sizeof(description));
273+ description.protocol = COMM_PROTOCOL_RESERVED;
274+ description.commAddr.type = COMM_ADDR_TYPE_RESERVED;
275+ description.loc.locType = ENDPOINT_LOC_TYPE_RESERVED;
276+}
277+ 
278+inline void WorkspaceManager::SetChannelDescDefaults(HcommChannelDesc& description)
279+{
280+ // Preserve the ABI header produced by HCOMM while deterministically
281+ // initializing every payload and padding byte owned by PTO.
282+ const CommAbiHeader header = description.header;
283+ (void)memset_s(&description, sizeof(description), 0, sizeof(description));
284+ description.header = header;
285+ SetEndpointDescDefaults(description.remoteEndpoint);
286+ description.role = HCOMM_SOCKET_ROLE_RESERVED;
287+}
288+ 
289+} // namespace hns_1825
290+} // namespace rdma
291+} // namespace comm
292+} // namespace pto
293+ 
294+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_CHANNEL_HPP
@@ -0,0 +1,390 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_INFO_HPP
12+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_INFO_HPP
13+ 
14+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_HPP
15+#error "Include hns_1825_workspace_manager.hpp instead of this internal implementation header."
16+#endif
17+ 
18+namespace pto {
19+namespace comm {
20+namespace rdma {
21+namespace hns_1825 {
22+ 
23+inline bool WorkspaceManager::IsDbVendorSpecifiedValid(uint64_t value)
24+{
25+ if (value == 0) {
26+ return false;
27+ }
28+ if ((value & ~0xffULL) == 0) {
29+ return value <= kDbVendorFieldMask;
30+ }
31+ return (value & ~kDbVendorSpecifiedMask) == 0;
32+}
33+ 
34+inline WorkspaceManager::DecodedRoceSqContext WorkspaceManager::DecodeRoceSqContext(const host::SqContext& context)
35+{
36+ const auto splitDb = host::ExtractRoceSqContextSplitDb(context);
37+ DecodedRoceSqContext decoded{};
38+ static_cast<host::RoceSqContextSplitDb&>(decoded) = splitDb;
39+ decoded.abiName = "split-db-mtushift";
40+ decoded.dbCos = kDefaultDbCos;
41+ const auto vendor = host::ExtractRoceSqContextVendorSpecified(context);
42+ if (IsDbVendorSpecifiedValid(vendor.DbVendorSpecified)) {
43+ decoded.abiName = "db-vendor-specified";
44+ if ((vendor.DbVendorSpecified & ~0xffULL) == 0) {
45+ decoded.mtuShift = kDefaultMtuShift;
46+ decoded.dbCos = static_cast<uint8_t>(vendor.DbVendorSpecified & kDbVendorFieldMask);
47+ } else {
48+ decoded.mtuShift = static_cast<uint8_t>((vendor.DbVendorSpecified >> kMtuShiftShift) & kDbVendorFieldMask);
49+ decoded.dbCos = static_cast<uint8_t>((vendor.DbVendorSpecified >> kDbCosShift) & kDbVendorFieldMask);
50+ }
51+ }
52+ return decoded;
53+}
54+ 
55+inline bool WorkspaceManager::ReadU32Device(uint64_t deviceAddress, uint32_t& value)
56+{
57+ return deviceAddress != 0 &&
58+ ReadDeviceStruct(
59+ reinterpret_cast<const void*>(static_cast<uintptr_t>(deviceAddress)), &value, sizeof(value));
60+}
61+ 
62+inline bool WorkspaceManager::WriteU32Device(uint64_t deviceAddress, uint32_t value)
63+{
64+ return deviceAddress != 0 && aclrtMemcpy(
65+ reinterpret_cast<void*>(static_cast<uintptr_t>(deviceAddress)), sizeof(value),
66+ &value, sizeof(value), ACL_MEMCPY_HOST_TO_DEVICE) == ACL_SUCCESS;
67+}
68+ 
69+inline bool WorkspaceManager::InitializeQueueMirrors(
70+ const host::SqContext& sq, const host::CqContext& cq, uint32_t peer) const
71+{
72+ const auto roceSq = host::ExtractRoceSqContextSplitDb(sq);
73+ const auto roceCq = host::ExtractRoceCqContextSplitDb(cq);
74+ uint32_t sqHead = UINT32_MAX;
75+ uint32_t sqTail = UINT32_MAX;
76+ uint32_t cqHead = UINT32_MAX;
77+ uint32_t cqTail = UINT32_MAX;
78+ const bool readOk = ReadU32Device(roceSq.headAddr, sqHead) && ReadU32Device(roceSq.tailAddr, sqTail) &&
79+ ReadU32Device(roceCq.headAddr, cqHead) && ReadU32Device(roceCq.tailAddr, cqTail);
80+ Trace(
81+ "QUEUE initial peer=", peer, " sq(head/tail)=", sqHead, "/", sqTail, " cq(head/tail)=", cqHead, "/", cqTail,
82+ " readOk=", readOk, " sqMirror=", Hex(roceSq.headAddr), "/", Hex(roceSq.tailAddr),
83+ " cqMirror=", Hex(roceCq.headAddr), "/", Hex(roceCq.tailAddr));
84+ if (!readOk) {
85+ std::cerr << "[RoCE] peer " << peer << " queue mirror D2H read failed" << std::endl;
86+ return false;
87+ }
88+ // HCOMM exposes freshly allocated PI/CI mirrors but does not guarantee the
89+ // allocation contents. A new QP starts at index zero, so initialize every
90+ // mirror explicitly before the first PTO WQE is posted.
91+ constexpr uint32_t kInitialQueueIndex = 0;
92+ const bool writeOk =
93+ WriteU32Device(roceSq.headAddr, kInitialQueueIndex) && WriteU32Device(roceSq.tailAddr, kInitialQueueIndex) &&
94+ WriteU32Device(roceCq.headAddr, kInitialQueueIndex) && WriteU32Device(roceCq.tailAddr, kInitialQueueIndex);
95+ if (!writeOk) {
96+ std::cerr << "[RoCE] peer " << peer << " queue mirror initialization failed" << std::endl;
97+ return false;
98+ }
99+ Trace("QUEUE reset peer=", peer, " sq(head/tail)=0/0 cq(head/tail)=0/0");
100+ return true;
101+}
102+ 
103+inline bool WorkspaceManager::PrepareRdmaInfoLayout(std::vector<uint8_t>& hostBuffer, RdmaInfoHostLayout& layout)
104+{
105+ constexpr uint32_t kQpNum = 1;
106+ const size_t sqBytes = sizeof(RoceSqCtx) * rankCount_ * kQpNum;
107+ const size_t cqBytes = sizeof(RoceCqCtx) * rankCount_ * kQpNum;
108+ const size_t memBytes = sizeof(RdmaMemInfo) * rankCount_;
109+ layout.totalSize = sizeof(RdmaInfo) + 2 * sqBytes + 2 * cqBytes + memBytes;
110+ if (aclrtMalloc(&rdmaInfoDevice_, layout.totalSize, ACL_MEM_MALLOC_HUGE_FIRST) != 0 || rdmaInfoDevice_ == nullptr) {
111+ std::cerr << "[RoCE] aclrtMalloc(rdmaInfo) failed" << std::endl;
112+ return false;
113+ }
114+ hostBuffer.assign(layout.totalSize, 0);
115+ layout.info = reinterpret_cast<RdmaInfo*>(hostBuffer.data());
116+ layout.info->magic = kRdmaWorkspaceMagic;
117+ layout.info->version = kRdmaWorkspaceVersion;
118+ layout.info->backend = RdmaBackend::HNS_1825;
119+ layout.info->qpNum = kQpNum;
120+ layout.info->rankCount = rankCount_;
121+ uint8_t* deviceBase = static_cast<uint8_t*>(rdmaInfoDevice_) + sizeof(RdmaInfo);
122+ layout.info->sqPtr = reinterpret_cast<uint64_t>(deviceBase);
123+ layout.info->rqPtr = reinterpret_cast<uint64_t>(deviceBase + sqBytes);
124+ layout.info->scqPtr = reinterpret_cast<uint64_t>(deviceBase + 2 * sqBytes);
125+ layout.info->rcqPtr = reinterpret_cast<uint64_t>(deviceBase + 2 * sqBytes + cqBytes);
126+ layout.info->memPtr = reinterpret_cast<uint64_t>(deviceBase + 2 * sqBytes + 2 * cqBytes);
127+ uint8_t* hostBase = hostBuffer.data() + sizeof(RdmaInfo);
128+ layout.sq = reinterpret_cast<RoceSqCtx*>(hostBase);
129+ layout.rq = reinterpret_cast<RoceSqCtx*>(hostBase + sqBytes);
130+ layout.scq = reinterpret_cast<RoceCqCtx*>(hostBase + 2 * sqBytes);
131+ layout.rcq = reinterpret_cast<RoceCqCtx*>(hostBase + 2 * sqBytes + cqBytes);
132+ layout.memory = reinterpret_cast<RdmaMemInfo*>(hostBase + 2 * sqBytes + 2 * cqBytes);
133+ return true;
134+}
135+ 
136+inline bool WorkspaceManager::FillLocalMemoryInfo(RdmaMemInfo* memory)
137+{
138+ bool localMrRead = rankCount_ == 1;
139+ if (rankCount_ == 1) {
140+ memory[rankId_].addr = reinterpret_cast<uint64_t>(symmetricAddr_);
141+ memory[rankId_].size = symmetricSize_;
142+ }
143+ for (size_t channel = 0; channel < channelHandles_.size() && !localMrRead; ++channel) {
144+ host::ChannelEntity entity{};
145+ if (!ReadChannelEntity(channelHandles_[channel], entity)) {
146+ std::cerr << "[RoCE] failed to read ChannelEntity while resolving the local MR" << std::endl;
147+ return false;
148+ }
149+ if (!ValidateChannelEntity(entity, channelPeer_[channel]) || entity.localBufferNum == 0 ||
150+ entity.localBufferAddr == nullptr) {
151+ std::cerr << "[RoCE] local registered buffer is absent for peer " << channelPeer_[channel] << std::endl;
152+ return false;
153+ }
154+ host::RegedBufferEntity localBuffer{};
155+ if (!ReadDeviceStruct(entity.localBufferAddr, &localBuffer, sizeof(localBuffer)) ||
156+ !ValidateRegisteredBuffer(
157+ localBuffer, reinterpret_cast<uint64_t>(symmetricAddr_), symmetricSize_, "local", rankId_)) {
158+ return false;
159+ }
160+ memory[rankId_].addr = localBuffer.bufferInfo.rma.addr;
161+ memory[rankId_].size = localBuffer.bufferInfo.rma.size;
162+ memory[rankId_].lkey = localBuffer.bufferInfo.rma.protectionInfo.memInfo.roce.lkey;
163+ memory[rankId_].rkey = localBuffer.bufferInfo.rma.protectionInfo.memInfo.roce.rkey;
164+ localMrRead = true;
165+ }
166+ if (!localMrRead) {
167+ std::cerr << "[RoCE] failed to read the local registered buffer from any channel" << std::endl;
168+ return false;
169+ }
170+ return true;
171+}
172+ 
173+inline bool WorkspaceManager::FillPeerRdmaInfo(size_t channelIndex, RdmaInfoHostLayout& layout)
174+{
175+ const uint32_t peer = channelPeer_[channelIndex];
176+ host::ChannelEntity entity{};
177+ if (!ReadChannelEntity(channelHandles_[channelIndex], entity)) {
178+ std::cerr << "[RoCE] read ChannelEntity failed for peer " << peer << std::endl;
179+ return false;
180+ }
181+ if (!ValidateChannelEntity(entity, peer)) {
182+ return false;
183+ }
184+ if (entity.remoteBufferNum == 0 || entity.remoteBufferAddr == nullptr) {
185+ std::cerr << "[RoCE] peer " << peer << " has no exchanged remote registered buffer" << std::endl;
186+ return false;
187+ }
188+ host::RegedBufferEntity remoteBuffer{};
189+ if (!ReadDeviceStruct(entity.remoteBufferAddr, &remoteBuffer, sizeof(remoteBuffer)) ||
190+ !ValidateRegisteredBuffer(remoteBuffer, peerSymAddrs_[peer], symmetricSize_, "remote", peer)) {
191+ return false;
192+ }
193+ layout.memory[peer].addr = remoteBuffer.bufferInfo.rma.addr;
194+ layout.memory[peer].size = remoteBuffer.bufferInfo.rma.size;
195+ layout.memory[peer].lkey = remoteBuffer.bufferInfo.rma.protectionInfo.memInfo.roce.lkey;
196+ layout.memory[peer].rkey = remoteBuffer.bufferInfo.rma.protectionInfo.memInfo.roce.rkey;
197+ host::SqContext sq{};
198+ if (entity.sqNum == 0 || entity.sqContextAddr == nullptr ||
199+ !ReadDeviceStruct(entity.sqContextAddr, &sq, sizeof(sq)) || !ValidateSqContext(sq, peer)) {
200+ return false;
201+ }
202+ CopyRoceSq(layout.sq[peer], sq);
203+ layout.rq[peer] = layout.sq[peer];
204+ host::CqContext cq{};
205+ if (entity.cqNum == 0 || entity.cqContextAddr == nullptr ||
206+ !ReadDeviceStruct(entity.cqContextAddr, &cq, sizeof(cq)) || !ValidateCqContext(cq, peer) ||
207+ !InitializeQueueMirrors(sq, cq, peer)) {
208+ return false;
209+ }
210+ CopyRoceCq(layout.scq[peer], cq);
211+ layout.rcq[peer] = layout.scq[peer];
212+ return true;
213+}
214+ 
215+inline bool WorkspaceManager::FillPeerRdmaInfoEntries(RdmaInfoHostLayout& layout)
216+{
217+ for (size_t channelIndex = 0; channelIndex < channelHandles_.size(); ++channelIndex) {
218+ if (!FillPeerRdmaInfo(channelIndex, layout)) {
219+ return false;
220+ }
221+ }
222+ return true;
223+}
224+ 
225+inline void WorkspaceManager::LogRdmaInfo(const RdmaInfoHostLayout& layout) const
226+{
227+ if (!Verbose()) {
228+ return;
229+ }
230+ std::cerr << "[RoCE][rank " << rankId_ << "] (4/4) RdmaInfo filled (qpNum=" << layout.info->qpNum
231+ << "):" << std::endl;
232+ std::cerr << "[RoCE][rank " << rankId_ << "] localMR[" << rankId_ << "] addr=0x" << std::hex
233+ << layout.memory[rankId_].addr << std::dec << " size=" << layout.memory[rankId_].size << " lkey=0x"
234+ << std::hex << layout.memory[rankId_].lkey << " rkey=0x" << layout.memory[rankId_].rkey << std::dec
235+ << std::endl;
236+ for (uint32_t peer : channelPeer_) {
237+ std::cerr << "[RoCE][rank " << rankId_ << "] peer[" << peer << "] MR addr=0x" << std::hex
238+ << layout.memory[peer].addr << " rkey=0x" << layout.memory[peer].rkey << std::dec
239+ << " | SQ wqn=" << layout.sq[peer].wqn << " depth=" << layout.sq[peer].depth << " sqVa=0x" << std::hex
240+ << layout.sq[peer].bufAddr << " dbHw=0x" << layout.sq[peer].dbAddr << " dbSw=0x"
241+ << layout.sq[peer].dbSwAddr << std::dec << " mtuShift=" << static_cast<int>(layout.sq[peer].mtuShift)
242+ << " dbCos=" << static_cast<int>(layout.sq[peer].dbCos) << " | CQ cqn=" << layout.scq[peer].cqn
243+ << " depth=" << layout.scq[peer].depth << " cqVa=0x" << std::hex << layout.scq[peer].bufAddr
244+ << " dbSw=0x" << layout.scq[peer].dbSwAddr << std::dec << std::endl;
245+ }
246+}
247+ 
248+inline bool WorkspaceManager::CopyRdmaInfoToDevice(const std::vector<uint8_t>& hostBuffer, size_t totalSize) const
249+{
250+ if (aclrtMemcpy(rdmaInfoDevice_, totalSize, hostBuffer.data(), totalSize, ACL_MEMCPY_HOST_TO_DEVICE) == 0) {
251+ return true;
252+ }
253+ std::cerr << "[RoCE] aclrtMemcpy(rdmaInfo H2D) failed" << std::endl;
254+ return false;
255+}
256+ 
257+inline bool WorkspaceManager::FillRdmaInfo()
258+{
259+ std::vector<uint8_t> hostBuffer;
260+ RdmaInfoHostLayout layout;
261+ if (!PrepareRdmaInfoLayout(hostBuffer, layout) || !FillLocalMemoryInfo(layout.memory) ||
262+ !FillPeerRdmaInfoEntries(layout)) {
263+ return false;
264+ }
265+ LogRdmaInfo(layout);
266+ return CopyRdmaInfoToDevice(hostBuffer, layout.totalSize);
267+}
268+ 
269+inline bool WorkspaceManager::IsPowerOfTwo(uint64_t value) { return value != 0 && (value & (value - 1)) == 0; }
270+ 
271+inline bool WorkspaceManager::IsU32Aligned(uint64_t address) { return address % alignof(uint32_t) == 0; }
272+ 
273+inline bool WorkspaceManager::IsCacheLineAligned(uint64_t address) { return address % kHns1825WqebbSize == 0; }
274+ 
275+inline bool WorkspaceManager::ValidateChannelEntity(const host::ChannelEntity& entity, uint32_t peer)
276+{
277+ if (entity.engine != COMM_ENGINE_AIV || entity.protocol != COMM_PROTOCOL_ROCE) {
278+ std::cerr << "[RoCE] peer " << peer
279+ << " ChannelEntity has unexpected engine/protocol: " << static_cast<int>(entity.engine) << "/"
280+ << static_cast<int>(entity.protocol) << std::endl;
281+ return false;
282+ }
283+ return true;
284+}
285+ 
286+inline bool WorkspaceManager::ValidateRegisteredBuffer(
287+ const host::RegedBufferEntity& buffer, uint64_t expectedAddr, uint64_t expectedSize, const char* side,
288+ uint32_t rank)
289+{
290+ const auto& rma = buffer.bufferInfo.rma;
291+ if (buffer.type != host::REGED_BUFFER_TYPE_RMA || rma.protectionInfo.type != host::PROTECTION_TYPE_ROCE ||
292+ rma.addr != expectedAddr || rma.size < expectedSize) {
293+ std::cerr << "[RoCE] " << side << " MR for rank " << rank
294+ << " has invalid type/protection/address/size: type=" << static_cast<int>(buffer.type)
295+ << " protection=" << static_cast<int>(rma.protectionInfo.type) << " addr=0x" << std::hex << rma.addr
296+ << " expected=0x" << expectedAddr << std::dec << " size=" << rma.size
297+ << " expected-at-least=" << expectedSize << std::endl;
298+ return false;
299+ }
300+ return true;
301+}
302+ 
303+inline bool WorkspaceManager::ValidateSqContext(const host::SqContext& sq, uint32_t peer)
304+{
305+ const auto context = DecodeRoceSqContext(sq);
306+ if (sq.type != host::SQ_CONTEXT_TYPE_ROCE || !IsPowerOfTwo(context.depth) ||
307+ context.depth <= kHns1825PollCqThreshold || context.sqVa == 0 || context.headAddr == 0 ||
308+ context.tailAddr == 0 || context.dbHwVa == 0 || context.dbSwVa == 0 || !IsCacheLineAligned(context.sqVa) ||
309+ !IsU32Aligned(context.headAddr) || !IsU32Aligned(context.tailAddr) || !IsU32Aligned(context.dbSwVa) ||
310+ context.mtuShift > kDbVendorFieldMask || context.dbCos > kDbVendorFieldMask) {
311+ std::cerr << "[RoCE] peer " << peer << " has an unusable RoCE SQ context: type=" << static_cast<int>(sq.type)
312+ << " depth=" << context.depth << " wqeSize=" << context.wqeSize << " abi=" << context.abiName
313+ << " mtuShift=" << static_cast<int>(context.mtuShift) << " dbCos=" << static_cast<int>(context.dbCos)
314+ << std::endl;
315+ return false;
316+ }
317+ return true;
318+}
319+ 
320+inline bool WorkspaceManager::ValidateCqContext(const host::CqContext& cq, uint32_t peer)
321+{
322+ const auto context = host::ExtractRoceCqContextSplitDb(cq);
323+ const uint32_t cqeSize = context.cqeSize == 0 ? kHns1825DefaultCqeSize : context.cqeSize;
324+ const uint64_t cqRing = static_cast<uint64_t>(context.cqDepth);
325+ const bool supportedCqeSize = cqeSize == sizeof(Hns1825Cqe) || cqeSize == kHns1825DefaultCqeSize;
326+ if (cq.type != host::CQ_CONTEXT_TYPE_ROCE || !IsPowerOfTwo(cqRing) || !supportedCqeSize || context.cqVa == 0 ||
327+ context.headAddr == 0 || context.tailAddr == 0 || context.dbSwVa == 0 || !IsCacheLineAligned(context.cqVa) ||
328+ !IsU32Aligned(context.tailAddr) || !IsU32Aligned(context.dbSwVa)) {
329+ std::cerr << "[RoCE] peer " << peer << " has an unusable RoCE CQ context: type=" << static_cast<int>(cq.type)
330+ << " depth=" << context.cqDepth << " cqeSize=" << context.cqeSize << std::endl;
331+ return false;
332+ }
333+ return true;
334+}
335+ 
336+inline void WorkspaceManager::CopyRoceSq(RoceSqCtx& destination, const host::SqContext& source)
337+{
338+ const auto context = DecodeRoceSqContext(source);
339+ destination.wqn = context.qpn;
340+ destination.bufAddr = context.sqVa;
341+ destination.wqeSize = context.wqeSize;
342+ destination.depth = context.depth;
343+ destination.headAddr = context.headAddr;
344+ destination.tailAddr = context.tailAddr;
345+ destination.dbMode = RdmaDbMode::SW_DB;
346+ destination.dbAddr = context.dbHwVa;
347+ destination.sl = context.sl;
348+ destination.amoAddr = 0;
349+ destination.amoLkey = 0;
350+ destination.dbSwAddr = context.dbSwVa;
351+ destination.mtuShift = context.mtuShift;
352+ destination.dbCos = context.dbCos;
353+}
354+ 
355+inline void WorkspaceManager::CopyRoceCq(RoceCqCtx& destination, const host::CqContext& source)
356+{
357+ const auto context = host::ExtractRoceCqContextSplitDb(source);
358+ destination.cqn = context.cqn;
359+ destination.bufAddr = context.cqVa;
360+ destination.cqeSize = context.cqeSize;
361+ destination.depth = context.cqDepth;
362+ destination.headAddr = context.headAddr;
363+ destination.tailAddr = context.tailAddr;
364+ destination.dbMode = RdmaDbMode::SW_DB;
365+ destination.dbAddr = context.dbHwVa;
366+ destination.dbSwAddr = context.dbSwVa;
367+}
368+ 
369+inline bool WorkspaceManager::ReadChannelEntity(ChannelHandle handle, host::ChannelEntity& output)
370+{
371+ if (handle == 0) {
372+ return false;
373+ }
374+ return ReadDeviceStruct(reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), &output, sizeof(output));
375+}
376+ 
377+inline bool WorkspaceManager::ReadDeviceStruct(const void* devicePointer, void* host, size_t size)
378+{
379+ if (devicePointer == nullptr) {
380+ return false;
381+ }
382+ return aclrtMemcpy(host, size, devicePointer, size, ACL_MEMCPY_DEVICE_TO_HOST) == 0;
383+}
384+ 
385+} // namespace hns_1825
386+} // namespace rdma
387+} // namespace comm
388+} // namespace pto
389+ 
390+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_INFO_HPP
@@ -0,0 +1,382 @@
1+/**
2+Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+CANN Open Software License Agreement Version 2.0 (the "License").
5+Please refer to the License for details. You may not use this file except in compliance with the License.
6+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+See LICENSE in the root of the software repository for the full text of the License.
9+*/
10+ 
11+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_LIFECYCLE_HPP
12+#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_LIFECYCLE_HPP
13+ 
14+#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_HPP
15+#error "Include hns_1825_workspace_manager.hpp instead of this internal implementation header."
16+#endif
17+ 
18+namespace pto {
19+namespace comm {
20+namespace rdma {
21+namespace hns_1825 {
22+ 
23+inline WorkspaceManager::~WorkspaceManager() { (void)Finalize(); }
24+ 
25+inline bool WorkspaceManager::Init(
26+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
27+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
28+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize)
29+{
30+ if (HasOwnedResources() && !Finalize()) {
31+ std::cerr << "[RoCE] previous session cleanup reported errors; continuing with a fresh HCOMM session"
32+ << std::endl;
33+ }
34+ if (!ValidateInitArguments(
35+ rankId, rankCount, phyId, localIp, basePort, peerIps, peerPhyIds, peerSymAddrs, symmetricAddr,
36+ symmetricSize)) {
37+ return false;
38+ }
39+ AssignInitArguments(
40+ rankId, rankCount, phyId, localIp, basePort, peerIps, peerPhyIds, peerSymAddrs, symmetricAddr, symmetricSize);
41+ const auto initStart = std::chrono::steady_clock::now();
42+ Trace(
43+ "INIT begin manager=", static_cast<const void*>(this), " endpoint=", endpoint_, " memHandle=", memHandle_,
44+ " channels=", channelHandles_.size(), " rdmaInfo=", rdmaInfoDevice_, " symAddr=", symmetricAddr_,
45+ " symSize=", symmetricSize_, " basePort=", basePort_);
46+ LogInitConfiguration();
47+ if (!RunInitStage(&WorkspaceManager::CreateEndpoint, "EndpointCreate") ||
48+ !RunInitStage(&WorkspaceManager::RegisterMemory, "MemReg") ||
49+ !RunInitStage(&WorkspaceManager::BuildChannels, "ChannelCreateReady") ||
50+ !RunInitStage(&WorkspaceManager::FillRdmaInfo, "FillRdmaInfo")) {
51+ (void)Finalize();
52+ return false;
53+ }
54+ if (Verbose()) {
55+ std::cerr << "[RoCE][rank " << rankId_ << "] connect DONE: workspace(RdmaInfo)=0x" << std::hex
56+ << reinterpret_cast<uint64_t>(rdmaInfoDevice_) << std::dec << std::endl;
57+ }
58+ initialized_ = true;
59+ Trace(
60+ "INIT success total_us=", ElapsedUs(initStart), " endpoint=", endpoint_, " memHandle=", memHandle_,
61+ " channels=", channelHandles_.size(), " rdmaInfo=", rdmaInfoDevice_);
62+ return true;
63+}
64+ 
65+inline bool WorkspaceManager::Verbose()
66+{
67+ const char* value = std::getenv("PTO_ROCE_VERBOSE");
68+ return value != nullptr && value[0] == '1';
69+}
70+ 
71+inline bool WorkspaceManager::Finalize()
72+{
73+ const bool hadResources = HasOwnedResources();
74+ const auto finalizeStart = std::chrono::steady_clock::now();
75+ if (hadResources) {
76+ Trace(
77+ "FINALIZE begin initialized=", initialized_, " endpoint=", endpoint_, " memHandle=", memHandle_,
78+ " channels=", channelHandles_.size(), " rdmaInfo=", rdmaInfoDevice_, " symAddr=", symmetricAddr_);
79+ }
80+ bool ok = true;
81+ auto phaseStart = std::chrono::steady_clock::now();
82+ const bool channelsOk = DestroyChannels();
83+ if (!channelsOk) {
84+ Trace("FINALIZE failed stage=ChannelDestroy elapsed_us=", ElapsedUs(phaseStart));
85+ ok = false;
86+ } else if (hadResources) {
87+ Trace("FINALIZE stage=ChannelDestroy ok elapsed_us=", ElapsedUs(phaseStart));
88+ }
89+ phaseStart = std::chrono::steady_clock::now();
90+ const bool memoryOk = UnregisterMemory();
91+ if (!memoryOk) {
92+ Trace("FINALIZE failed stage=MemUnreg elapsed_us=", ElapsedUs(phaseStart));
93+ ok = false;
94+ } else if (hadResources) {
95+ Trace("FINALIZE stage=MemUnreg ok elapsed_us=", ElapsedUs(phaseStart));
96+ }
97+ phaseStart = std::chrono::steady_clock::now();
98+ const bool endpointOk = DestroyEndpoint();
99+ if (!endpointOk) {
100+ Trace("FINALIZE failed stage=EndpointDestroy elapsed_us=", ElapsedUs(phaseStart));
101+ ok = false;
102+ } else if (hadResources) {
103+ Trace("FINALIZE stage=EndpointDestroy ok elapsed_us=", ElapsedUs(phaseStart));
104+ }
105+ phaseStart = std::chrono::steady_clock::now();
106+ const bool deviceInfoOk = FreeDeviceInfo();
107+ if (!deviceInfoOk) {
108+ Trace("FINALIZE failed stage=FreeRdmaInfo elapsed_us=", ElapsedUs(phaseStart));
109+ ok = false;
110+ } else if (hadResources) {
111+ Trace("FINALIZE stage=FreeRdmaInfo ok elapsed_us=", ElapsedUs(phaseStart));
112+ }
113+ ResetOwnedState();
114+ if (hadResources) {
115+ Trace("FINALIZE end ok=", ok, " total_us=", ElapsedUs(finalizeStart));
116+ }
117+ return ok;
118+}
119+ 
120+template <typename... Args>
121+inline void WorkspaceManager::Trace(Args&&... args) const
122+{
123+ if (!Verbose()) {
124+ return;
125+ }
126+ std::ostringstream stream;
127+ stream << "[RoCE][case " << traceId_ << "][rank " << rankId_ << "] ";
128+ (stream << ... << std::forward<Args>(args));
129+ stream << '\n';
130+ const std::string line = stream.str();
131+ (void)std::fwrite(line.data(), 1, line.size(), stderr);
132+ std::fflush(stderr);
133+}
134+ 
135+inline uint64_t WorkspaceManager::ElapsedUs(const std::chrono::steady_clock::time_point& start)
136+{
137+ return static_cast<uint64_t>(
138+ std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count());
139+}
140+ 
141+inline std::string WorkspaceManager::Hex(uint64_t value)
142+{
143+ std::ostringstream stream;
144+ stream << "0x" << std::hex << value;
145+ return stream.str();
146+}
147+ 
148+inline void WorkspaceManager::AssignInitArguments(
149+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
150+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
151+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize)
152+{
153+ rankId_ = rankId;
154+ rankCount_ = rankCount;
155+ phyId_ = phyId;
156+ localIp_ = localIp;
157+ basePort_ = basePort;
158+ peerIps_ = peerIps;
159+ peerPhyIds_ = peerPhyIds;
160+ peerSymAddrs_ = peerSymAddrs;
161+ symmetricAddr_ = symmetricAddr;
162+ symmetricSize_ = symmetricSize;
163+}
164+ 
165+inline void WorkspaceManager::LogInitConfiguration() const
166+{
167+ if (!Verbose()) {
168+ return;
169+ }
170+ std::cerr << "[RoCE][rank " << rankId_ << "/" << rankCount_ << "] connect start: phyId=" << phyId_
171+ << " localIp=" << localIp_ << " basePort=" << basePort_ << " symAddr=0x" << std::hex
172+ << reinterpret_cast<uint64_t>(symmetricAddr_) << std::dec << " symSize=" << symmetricSize_ << std::endl;
173+ for (uint32_t rank = 0; rank < rankCount_; ++rank) {
174+ std::cerr << "[RoCE][rank " << rankId_ << "] peer[" << rank << "] ip=" << peerIps_[rank]
175+ << " phyId=" << peerPhyIds_[rank] << " symAddr=0x" << std::hex << peerSymAddrs_[rank] << std::dec
176+ << std::endl;
177+ }
178+}
179+ 
180+inline bool WorkspaceManager::RunInitStage(bool (WorkspaceManager::*stage)(), const char* stageName)
181+{
182+ const auto phaseStart = std::chrono::steady_clock::now();
183+ if (!(this->*stage)()) {
184+ return false;
185+ }
186+ Trace("INIT stage=", stageName, " ok elapsed_us=", ElapsedUs(phaseStart));
187+ return true;
188+}
189+ 
190+inline bool WorkspaceManager::DestroyChannels()
191+{
192+ if (channelHandles_.empty()) {
193+ return true;
194+ }
195+ std::vector<ChannelHandle> valid;
196+ valid.reserve(channelHandles_.size());
197+ for (ChannelHandle handle : channelHandles_) {
198+ if (handle != 0) {
199+ valid.push_back(handle);
200+ }
201+ }
202+ if (valid.empty()) {
203+ channelHandles_.clear();
204+ channelPeer_.clear();
205+ return true;
206+ }
207+ if (Verbose()) {
208+ std::cerr << "[RoCE][rank " << rankId_ << "] HcommChannelDestroy n=" << valid.size() << std::endl;
209+ }
210+ for (size_t index = 0; index < channelHandles_.size(); ++index) {
211+ const ChannelHandle handle = channelHandles_[index];
212+ if (handle == 0) {
213+ continue;
214+ }
215+ const uint32_t peer = index < channelPeer_.size() ? channelPeer_[index] : UINT32_MAX;
216+ Trace("DESTROY channel index=", index, " peer=", peer, " handle=", Hex(static_cast<uint64_t>(handle)));
217+ }
218+ const HcommResult result = HcommChannelDestroy(valid.data(), static_cast<uint32_t>(valid.size()));
219+ channelHandles_.clear();
220+ channelPeer_.clear();
221+ if (result != 0) {
222+ std::cerr << "[RoCE] HcommChannelDestroy failed: " << result << std::endl;
223+ return false;
224+ }
225+ if (Verbose()) {
226+ std::cerr << "[RoCE][rank " << rankId_ << "] HcommChannelDestroy ok" << std::endl;
227+ }
228+ Trace("DESTROY channels API returned success");
229+ return true;
230+}
231+ 
232+inline bool WorkspaceManager::UnregisterMemory()
233+{
234+ if (memHandle_ == nullptr) {
235+ return true;
236+ }
237+ const HcommMemHandle memHandle = memHandle_;
238+ memHandle_ = nullptr;
239+ if (endpoint_ == nullptr) {
240+ std::cerr << "[RoCE] cannot unregister memory without its endpoint" << std::endl;
241+ return false;
242+ }
243+ const HcommResult result = HcommMemUnreg(endpoint_, memHandle);
244+ if (result != 0) {
245+ std::cerr << "[RoCE] HcommMemUnreg failed: " << result << std::endl;
246+ return false;
247+ }
248+ Trace("DESTROY mem-unreg API returned success handle=", memHandle);
249+ return true;
250+}
251+ 
252+inline bool WorkspaceManager::DestroyEndpoint()
253+{
254+ if (endpoint_ == nullptr) {
255+ return true;
256+ }
257+ const EndpointHandle endpoint = endpoint_;
258+ endpoint_ = nullptr;
259+ const HcommResult result = HcommEndpointDestroy(endpoint);
260+ if (result != 0) {
261+ std::cerr << "[RoCE] HcommEndpointDestroy failed: " << result << std::endl;
262+ return false;
263+ }
264+ Trace("DESTROY endpoint API returned success handle=", endpoint);
265+ return true;
266+}
267+ 
268+inline bool WorkspaceManager::FreeDeviceInfo()
269+{
270+ if (rdmaInfoDevice_ == nullptr) {
271+ return true;
272+ }
273+ void* rdmaInfo = rdmaInfoDevice_;
274+ rdmaInfoDevice_ = nullptr;
275+ const aclError result = aclrtFree(rdmaInfo);
276+ if (result != ACL_SUCCESS) {
277+ std::cerr << "[RoCE] aclrtFree(rdmaInfo) failed: " << static_cast<int>(result) << std::endl;
278+ return false;
279+ }
280+ Trace("DESTROY rdma-info free returned success addr=", rdmaInfo);
281+ return true;
282+}
283+ 
284+inline bool WorkspaceManager::HasOwnedResources() const
285+{
286+ return initialized_ || endpoint_ != nullptr || memHandle_ != nullptr || rdmaInfoDevice_ != nullptr ||
287+ !channelHandles_.empty();
288+}
289+ 
290+inline bool WorkspaceManager::ValidateBasicInitArguments(
291+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
292+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
293+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize)
294+{
295+ if (rankCount == 0 || rankId >= rankCount || peerIps.size() != rankCount || peerPhyIds.size() != rankCount ||
296+ peerSymAddrs.size() != rankCount || localIp.empty() || symmetricAddr == nullptr || symmetricSize == 0) {
297+ std::cerr << "[RoCE] invalid rank, peer, IP, or symmetric-memory arguments" << std::endl;
298+ return false;
299+ }
300+ if (peerIps[rankId] != localIp || peerPhyIds[rankId] != phyId ||
301+ peerSymAddrs[rankId] != reinterpret_cast<uint64_t>(symmetricAddr)) {
302+ std::cerr << "[RoCE] local IP/phyId/address does not match this rank's gathered peer entry" << std::endl;
303+ return false;
304+ }
305+ if (basePort > UINT16_MAX - (kMaxRanksPerNic * kMaxRanksPerNic - 1)) {
306+ std::cerr << "[RoCE] basePort leaves no room for the per-rank channel port range" << std::endl;
307+ return false;
308+ }
309+ return true;
310+}
311+ 
312+inline bool WorkspaceManager::ValidatePeerPortMapping(
313+ uint32_t rankCount, const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
314+ const std::vector<uint64_t>& peerSymAddrs)
315+{
316+ for (uint32_t rank = 0; rank < rankCount; ++rank) {
317+ if (peerIps[rank].empty() || peerPhyIds[rank] == UINT32_MAX || peerSymAddrs[rank] == 0) {
318+ std::cerr << "[RoCE] peer " << rank << " has an empty IP, invalid phyId, or null symmetric address"
319+ << std::endl;
320+ return false;
321+ }
322+ uint32_t ranksOnNic = 0;
323+ bool occupiedSlots[kMaxRanksPerNic] = {};
324+ for (uint32_t peer = 0; peer < rankCount; ++peer) {
325+ if (peerIps[peer] != peerIps[rank]) {
326+ continue;
327+ }
328+ ++ranksOnNic;
329+ const uint32_t slot = peer % kMaxRanksPerNic;
330+ if (occupiedSlots[slot]) {
331+ std::cerr << "[RoCE] ranks sharing NIC IP " << peerIps[rank]
332+ << " collide after modulo port mapping at slot " << slot << std::endl;
333+ return false;
334+ }
335+ occupiedSlots[slot] = true;
336+ }
337+ if (ranksOnNic > kMaxRanksPerNic) {
338+ std::cerr << "[RoCE] more than " << kMaxRanksPerNic << " ranks share NIC IP " << peerIps[rank]
339+ << "; the channel port mapping would collide" << std::endl;
340+ return false;
341+ }
342+ }
343+ return true;
344+}
345+ 
346+inline bool WorkspaceManager::ValidateInitArguments(
347+ uint32_t rankId, uint32_t rankCount, uint32_t phyId, const std::string& localIp, uint16_t basePort,
348+ const std::vector<std::string>& peerIps, const std::vector<uint32_t>& peerPhyIds,
349+ const std::vector<uint64_t>& peerSymAddrs, void* symmetricAddr, uint64_t symmetricSize)
350+{
351+ return ValidateBasicInitArguments(
352+ rankId, rankCount, phyId, localIp, basePort, peerIps, peerPhyIds, peerSymAddrs, symmetricAddr,
353+ symmetricSize) &&
354+ ValidatePeerPortMapping(rankCount, peerIps, peerPhyIds, peerSymAddrs);
355+}
356+ 
357+inline void WorkspaceManager::ResetOwnedState()
358+{
359+ channelHandles_.clear();
360+ channelPeer_.clear();
361+ peerIps_.clear();
362+ peerPhyIds_.clear();
363+ peerSymAddrs_.clear();
364+ memHandle_ = nullptr;
365+ endpoint_ = nullptr;
366+ rdmaInfoDevice_ = nullptr;
367+ symmetricAddr_ = nullptr;
368+ symmetricSize_ = 0;
369+ localIp_.clear();
370+ rankId_ = 0;
371+ rankCount_ = 1;
372+ phyId_ = 0;
373+ basePort_ = 60032;
374+ initialized_ = false;
375+}
376+ 
377+} // namespace hns_1825
378+} // namespace rdma
379+} // namespace comm
380+} // namespace pto
381+ 
382+#endif // PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_WORKSPACE_MANAGER_LIFECYCLE_HPP