已合并
feat(rdma): 支持多 AIV 共享 HNS1825 队列 #1727
feat(rdma): 支持多 AIV 共享 HNS1825 队列 #1727
已合并
xun创建于 27 天前
共 13 个文件变更+781-168
@@ -256,7 +256,7 @@ payload, the caller must ensure visibility according to the target platform and
256 not submit concurrently to one group. With `queue_num == N`, at most `kSdmaMaxChannelGroups / N` groups are valid.256 not submit concurrently to one group. With `queue_num == N`, at most `kSdmaMaxChannelGroups / N` groups are valid.
257- Concurrent `Set` producers should use separate remote signals. Use `AtomicAdd` when multiple AIVs share one signal257- Concurrent `Set` producers should use separate remote signals. Use `AtomicAdd` when multiple AIVs share one signal
258 as a completion counter. Payload destination ranges must not overlap in either mode.258 as a completion counter. Payload destination ranges must not overlap in either mode.
259-- For URMA and RDMA, submissions to the same peer/QP must be serialized even when callers constructed separate259+- For URMA, submissions to the same peer/QP must be serialized even when callers constructed separate
260 sessions over the same workspace. Different peers use independent queues.260 sessions over the same workspace. Different peers use independent queues.
261- Complete all earlier events before rebuilding a session or reusing its backend queue.261- Complete all earlier events before rebuilding a session or reusing its backend queue.
262- When using URMA, complete the last event for every active peer/QP and synchronize every host stream that uses the262- When using URMA, complete the last event for every active peer/QP and synchronize every host stream that uses the
@@ -245,7 +245,7 @@ Event,也会覆盖此前尚未完成的操作。URMA或RDMA访问不同peer时
245 Group。`queue_num` 为 `N` 时,合法Group数最多为 `kSdmaMaxChannelGroups / N`。245 Group。`queue_num` 为 `N` 时,合法Group数最多为 `kSdmaMaxChannelGroups / N`。
246- 多个AIV并发 `Set` 时,应为每个生产者使用独立signal;使用同一个signal做完成计数时应使用246- 多个AIV并发 `Set` 时,应为每个生产者使用独立signal;使用同一个signal做完成计数时应使用
247 `AtomicAdd`。无论哪种模式,各AIV的payload目的地址范围都不得重叠。247 `AtomicAdd`。无论哪种模式,各AIV的payload目的地址范围都不得重叠。
248-- 对URMA和RDMA,即使调用方基于同一workspace构建了不同Session,对同一peer/QP的提交也必须串行;248+- 对URMA,即使调用方基于同一workspace构建了不同Session,对同一peer/QP的提交也必须串行;
249 不同peer使用独立队列。249 不同peer使用独立队列。
250- 重新构建Session或复用后端队列前,必须先完成此前全部Event。250- 重新构建Session或复用后端队列前,必须先完成此前全部Event。
251- 使用URMA时,释放通信资源前必须完成每个活动peer/QP的最后一个Event,并同步所有使用该251- 使用URMA时,释放通信资源前必须完成每个活动peer/QP的最后一个Event,并同步所有使用该
@@ -11,11 +11,16 @@ See LICENSE in the root of the software repository for the full text of the Lice
11// HNS_1825 (Hi1825) RoCE RDMA device-side backend for A5.11// HNS_1825 (Hi1825) RoCE RDMA device-side backend for A5.
12//12//
13// Posts RDMA WRITE / READ from AIV using the RdmaInfo table supplied via RdmaExecContext.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.14+// WQE/CQE staging uses each AIV's own UB scratch. Local scalar atomics coordinate
15+// shared queues; remote RDMA atomic operations are not supported.
15 16 
16#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_BACKEND_HPP17#ifndef PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_BACKEND_HPP
17#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_BACKEND_HPP18#define PTO_COMM_ASYNC_RDMA_BACKENDS_HNS_1825_BACKEND_HPP
18 19 
20+#if defined(__NPU_ARCH__) && __NPU_ARCH__ != 0 && __NPU_ARCH__ != 3510
21+#error "The HNS1825 shared-queue backend requires Ascend950 (__NPU_ARCH__ == 3510)."
22+#endif
23+ 
19#include "pto/pto-inst.hpp"24#include "pto/pto-inst.hpp"
20#include "pto/comm/async_common/async_types.hpp"25#include "pto/comm/async_common/async_types.hpp"
21#include "pto/comm/async/rdma/rdma_device_common.hpp"26#include "pto/comm/async/rdma/rdma_device_common.hpp"
@@ -22,6 +22,19 @@ namespace comm {
22namespace rdma {22namespace rdma {
23namespace hns_1825 {23namespace hns_1825 {
24 24 
25+// One zero-initialized, 64-byte record per peer/QP. All device accesses
26+// to these fields use scalar atomics; session-local UB is never shared.
27+struct QueueState {
28+ uint64_t reserveHead;
29+ uint64_t readyHead;
30+ uint64_t postedHead;
31+ uint64_t completedHead;
32+ uint64_t postLock;
33+ uint64_t cqLock;
34+ uint64_t error;
35+ uint64_t reserved;
36+};
37+ 
25struct SqContext {38struct SqContext {
26 uint32_t wqn;39 uint32_t wqn;
27 uint64_t bufAddr;40 uint64_t bufAddr;
@@ -38,6 +51,7 @@ struct SqContext {
38 uint8_t mtuShift{4};51 uint8_t mtuShift{4};
39 uint8_t dbCos{0x7};52 uint8_t dbCos{0x7};
40 uint8_t reserved[6];53 uint8_t reserved[6];
54+ uint64_t stateAddr;
41};55};
42 56 
43struct CqContext {57struct CqContext {
@@ -141,6 +155,14 @@ constexpr uint32_t kSessionBuildError = 0x20000;
141constexpr uint32_t kInvalidArgumentError = 0x20001;155constexpr uint32_t kInvalidArgumentError = 0x20001;
142constexpr uint32_t kInvalidContextError = 0x20002;156constexpr uint32_t kInvalidContextError = 0x20002;
143constexpr uint32_t kCqeError = 0x20003;157constexpr uint32_t kCqeError = 0x20003;
158+// Handles retain their peer/u32-index encoding. Stop before the index can wrap
159+// into the zero/no-op handle or alias a live event from an earlier generation.
160+constexpr uint32_t kQueueIndexExhaustedError = 0x20004;
161+constexpr uint64_t kMaxQueueIndex = 0xffffffffULL;
162+constexpr uint32_t kCqProgressBatch = 8;
163+constexpr uint32_t kPostSendBatch = 32;
164+ 
165+static_assert(sizeof(QueueState) == 64, "unexpected HNS1825 queue-state layout");
144 166 
145#if defined(PTO_NPU_ARCH_A2A3) || defined(__DAV_C220_VEC__) || defined(__DAV_C220_CUBE__)167#if defined(PTO_NPU_ARCH_A2A3) || defined(__DAV_C220_VEC__) || defined(__DAV_C220_CUBE__)
146constexpr uint64_t kCycleToTimeBase = 50;168constexpr uint64_t kCycleToTimeBase = 50;
@@ -97,6 +97,7 @@ private:
97 RoceCqCtx* scq{nullptr};97 RoceCqCtx* scq{nullptr};
98 RoceCqCtx* rcq{nullptr};98 RoceCqCtx* rcq{nullptr};
99 RdmaMemInfo* memory{nullptr};99 RdmaMemInfo* memory{nullptr};
100+ uint64_t stateDeviceBase{0};
100 };101 };
101 102 
102 template <typename... Args>103 template <typename... Args>
@@ -150,6 +151,7 @@ private:
150 static bool ReadU32Device(uint64_t deviceAddress, uint32_t& value);151 static bool ReadU32Device(uint64_t deviceAddress, uint32_t& value);
151 static bool WriteU32Device(uint64_t deviceAddress, uint32_t value);152 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 InitializeQueueMirrors(const host::SqContext& sq, const host::CqContext& cq, uint32_t peer) const;
154+ bool InitializeSqOwners(const host::SqContext& sq, uint32_t peer) const;
153 bool PrepareRdmaInfoLayout(std::vector<uint8_t>& hostBuffer, RdmaInfoHostLayout& layout);155 bool PrepareRdmaInfoLayout(std::vector<uint8_t>& hostBuffer, RdmaInfoHostLayout& layout);
154 bool FillLocalMemoryInfo(RdmaMemInfo* memory);156 bool FillLocalMemoryInfo(RdmaMemInfo* memory);
155 bool FillPeerRdmaInfo(size_t channelIndex, RdmaInfoHostLayout& layout);157 bool FillPeerRdmaInfo(size_t channelIndex, RdmaInfoHostLayout& layout);
@@ -100,18 +100,39 @@ inline bool WorkspaceManager::InitializeQueueMirrors(
100 return true;100 return true;
101}101}
102 102 
103+inline bool WorkspaceManager::InitializeSqOwners(const host::SqContext& sq, uint32_t peer) const
104+{
105+ const auto context = DecodeRoceSqContext(sq);
106+ // These channels belong exclusively to this workspace and have not posted
107+ // any work. Every slot must stop NIC prefetch before its first publication.
108+ std::vector<uint8_t> ring(static_cast<size_t>(context.depth) * kWqebbSize, 0);
109+ for (size_t offset = 0; offset < ring.size(); offset += kWqebbSize) {
110+ ring[offset] = 0xc0; // fixed control bit + invalid owner for lap zero
111+ }
112+ if (aclrtMemcpy(
113+ reinterpret_cast<void*>(context.sqVa), ring.size(), ring.data(), ring.size(), ACL_MEMCPY_HOST_TO_DEVICE) !=
114+ ACL_SUCCESS) {
115+ std::cerr << "[RoCE] peer " << peer << " SQ owner initialization failed" << std::endl;
116+ return false;
117+ }
118+ return true;
119+}
120+ 
103inline bool WorkspaceManager::PrepareRdmaInfoLayout(std::vector<uint8_t>& hostBuffer, RdmaInfoHostLayout& layout)121inline bool WorkspaceManager::PrepareRdmaInfoLayout(std::vector<uint8_t>& hostBuffer, RdmaInfoHostLayout& layout)
104{122{
105 constexpr uint32_t kQpNum = 1;123 constexpr uint32_t kQpNum = 1;
106 const size_t sqBytes = sizeof(RoceSqCtx) * rankCount_ * kQpNum;124 const size_t sqBytes = sizeof(RoceSqCtx) * rankCount_ * kQpNum;
107 const size_t cqBytes = sizeof(RoceCqCtx) * rankCount_ * kQpNum;125 const size_t cqBytes = sizeof(RoceCqCtx) * rankCount_ * kQpNum;
108 const size_t memBytes = sizeof(RdmaMemInfo) * rankCount_;126 const size_t memBytes = sizeof(RdmaMemInfo) * rankCount_;
109- layout.totalSize = sizeof(RdmaInfo) + 2 * sqBytes + 2 * cqBytes + memBytes;127+ const size_t metadataBytes = sizeof(RdmaInfo) + 2 * sqBytes + 2 * cqBytes + memBytes;
128+ const size_t stateOffset = (metadataBytes + sizeof(QueueState) - 1) & ~(sizeof(QueueState) - 1);
129+ layout.totalSize = stateOffset + sizeof(QueueState) * rankCount_ * kQpNum;
110 if (aclrtMalloc(&rdmaInfoDevice_, layout.totalSize, ACL_MEM_MALLOC_HUGE_FIRST) != 0 || rdmaInfoDevice_ == nullptr) {130 if (aclrtMalloc(&rdmaInfoDevice_, layout.totalSize, ACL_MEM_MALLOC_HUGE_FIRST) != 0 || rdmaInfoDevice_ == nullptr) {
111 std::cerr << "[RoCE] aclrtMalloc(rdmaInfo) failed" << std::endl;131 std::cerr << "[RoCE] aclrtMalloc(rdmaInfo) failed" << std::endl;
112 return false;132 return false;
113 }133 }
114 hostBuffer.assign(layout.totalSize, 0);134 hostBuffer.assign(layout.totalSize, 0);
135+ layout.stateDeviceBase = reinterpret_cast<uint64_t>(rdmaInfoDevice_) + stateOffset;
115 layout.info = reinterpret_cast<RdmaInfo*>(hostBuffer.data());136 layout.info = reinterpret_cast<RdmaInfo*>(hostBuffer.data());
116 layout.info->magic = kRdmaWorkspaceMagic;137 layout.info->magic = kRdmaWorkspaceMagic;
117 layout.info->version = kRdmaWorkspaceVersion;138 layout.info->version = kRdmaWorkspaceVersion;
@@ -200,11 +221,12 @@ inline bool WorkspaceManager::FillPeerRdmaInfo(size_t channelIndex, RdmaInfoHost
200 return false;221 return false;
201 }222 }
202 CopyRoceSq(layout.sq[peer], sq);223 CopyRoceSq(layout.sq[peer], sq);
224+ layout.sq[peer].stateAddr = layout.stateDeviceBase + sizeof(QueueState) * peer;
203 layout.rq[peer] = layout.sq[peer];225 layout.rq[peer] = layout.sq[peer];
204 host::CqContext cq{};226 host::CqContext cq{};
205 if (entity.cqNum == 0 || entity.cqContextAddr == nullptr ||227 if (entity.cqNum == 0 || entity.cqContextAddr == nullptr ||
206 !ReadDeviceStruct(entity.cqContextAddr, &cq, sizeof(cq)) || !ValidateCqContext(cq, peer) ||228 !ReadDeviceStruct(entity.cqContextAddr, &cq, sizeof(cq)) || !ValidateCqContext(cq, peer) ||
207- !InitializeQueueMirrors(sq, cq, peer)) {229+ !InitializeQueueMirrors(sq, cq, peer) || !InitializeSqOwners(sq, peer)) {
208 return false;230 return false;
209 }231 }
210 CopyRoceCq(layout.scq[peer], cq);232 CopyRoceCq(layout.scq[peer], cq);
@@ -323,9 +345,9 @@ inline bool WorkspaceManager::ValidateCqContext(const host::CqContext& cq, uint3
323 const uint32_t cqeSize = context.cqeSize == 0 ? kHns1825DefaultCqeSize : context.cqeSize;345 const uint32_t cqeSize = context.cqeSize == 0 ? kHns1825DefaultCqeSize : context.cqeSize;
324 const uint64_t cqRing = static_cast<uint64_t>(context.cqDepth);346 const uint64_t cqRing = static_cast<uint64_t>(context.cqDepth);
325 const bool supportedCqeSize = cqeSize == sizeof(Hns1825Cqe) || cqeSize == kHns1825DefaultCqeSize;347 const bool supportedCqeSize = cqeSize == sizeof(Hns1825Cqe) || cqeSize == kHns1825DefaultCqeSize;
326- if (cq.type != host::CQ_CONTEXT_TYPE_ROCE || !IsPowerOfTwo(cqRing) || !supportedCqeSize || context.cqVa == 0 ||348+ if (cq.type != host::CQ_CONTEXT_TYPE_ROCE || !IsPowerOfTwo(cqRing) || cqRing < 4 || !supportedCqeSize ||
327- context.headAddr == 0 || context.tailAddr == 0 || context.dbSwVa == 0 || !IsCacheLineAligned(context.cqVa) ||349+ context.cqVa == 0 || context.headAddr == 0 || context.tailAddr == 0 || context.dbSwVa == 0 ||
328- !IsU32Aligned(context.tailAddr) || !IsU32Aligned(context.dbSwVa)) {350+ !IsCacheLineAligned(context.cqVa) || !IsU32Aligned(context.tailAddr) || !IsU32Aligned(context.dbSwVa)) {
329 std::cerr << "[RoCE] peer " << peer << " has an unusable RoCE CQ context: type=" << static_cast<int>(cq.type)351 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;352 << " depth=" << context.cqDepth << " cqeSize=" << context.cqeSize << std::endl;
331 return false;353 return false;
@@ -23,7 +23,7 @@ namespace comm {
23namespace rdma {23namespace rdma {
24 24 
25constexpr uint32_t kRdmaWorkspaceMagic = 0x52444d41U; // "RDMA"25constexpr uint32_t kRdmaWorkspaceMagic = 0x52444d41U; // "RDMA"
26-constexpr uint32_t kRdmaWorkspaceVersion = 1;26+constexpr uint32_t kRdmaWorkspaceVersion = 2;
27 27 
28enum class RdmaDbMode : int32_t {28enum class RdmaDbMode : int32_t {
29 INVALID_DB = -1,29 INVALID_DB = -1,
@@ -21,6 +21,9 @@ python3 tests/script/run_st.py -r npu -v a5 -t comm/tget_async_rdma -d -n 2
21python3 tests/script/run_st.py -r npu -v a5 -t comm/tput_async_notify_rdma -d -n 221python3 tests/script/run_st.py -r npu -v a5 -t comm/tput_async_notify_rdma -d -n 2
22```22```
23 23 
24+`TPutAsyncRdma.Vec_Int32_MultiAiv` checks 16 AIVs sharing one peer/QP, covering PUT/notify traffic, ring wraparound,
25+and data/event correctness. Add `-g TPutAsyncRdma.Vec_Int32_MultiAiv` to the `tput_async_rdma` command to run it alone.
26+ 
24For the first `TPUT_ASYNC_NOTIFY` validation, run only the focused two-rank case:27For the first `TPUT_ASYNC_NOTIFY` validation, run only the focused two-rank case:
25 28 
26```bash29```bash
@@ -20,6 +20,9 @@ python3 tests/script/run_st.py -r npu -v a5 -t comm/tget_async_rdma -d -n 2
20python3 tests/script/run_st.py -r npu -v a5 -t comm/tput_async_notify_rdma -d -n 220python3 tests/script/run_st.py -r npu -v a5 -t comm/tput_async_notify_rdma -d -n 2
21```21```
22 22 
23+`TPutAsyncRdma.Vec_Int32_MultiAiv` 验证16个AIV共享同一peer/QP时的PUT/notify、队列回绕及数据/Event正确性。
24+在 `tput_async_rdma` 命令中加 `-g TPutAsyncRdma.Vec_Int32_MultiAiv` 可单独运行。
25+ 
23首次验证`TPUT_ASYNC_NOTIFY`时,先只运行2个rank的定向用例:26首次验证`TPUT_ASYNC_NOTIFY`时,先只运行2个rank的定向用例:
24 27 
25```bash28```bash
@@ -13,6 +13,7 @@ See LICENSE in the root of the software repository for the full text of the Lice
13 13 
14#include <cstdint>14#include <cstdint>
15#include <cstdlib>15#include <cstdlib>
16+#include <acl/acl.h>
16 17 
17#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED18#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
18#include "hns_1825/hns_1825_bootstrap.hpp"19#include "hns_1825/hns_1825_bootstrap.hpp"
@@ -29,6 +30,38 @@ namespace test {
29#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED30#ifdef PTO_RDMA_BACKEND_HNS_1825_SUPPORTED
30using BackendBootstrap = hns_1825::bootstrap::BootstrapConfig;31using BackendBootstrap = hns_1825::bootstrap::BootstrapConfig;
31 32 
33+struct BackendQueueSnapshot {
34+ uint32_t sqDepth{0};
35+ uint32_t cqDepth{0};
36+ hns_1825::QueueState state{};
37+};
38+ 
39+// Read only while the test stream is idle; the driver owns both ring depths.
40+inline bool ReadBackendQueueSnapshot(const void* workspace, uint32_t peer, BackendQueueSnapshot& snapshot)
41+{
42+ RdmaInfo info{};
43+ if (aclrtMemcpy(&info, sizeof(info), workspace, sizeof(info), ACL_MEMCPY_DEVICE_TO_HOST) != ACL_SUCCESS ||
44+ peer >= info.rankCount || info.qpNum != 1 || info.sqPtr == 0 || info.scqPtr == 0) {
45+ return false;
46+ }
47+ hns_1825::SqContext sq{};
48+ hns_1825::CqContext cq{};
49+ if (aclrtMemcpy(
50+ &sq, sizeof(sq), reinterpret_cast<const void*>(info.sqPtr + peer * sizeof(sq)), sizeof(sq),
51+ ACL_MEMCPY_DEVICE_TO_HOST) != ACL_SUCCESS ||
52+ aclrtMemcpy(
53+ &cq, sizeof(cq), reinterpret_cast<const void*>(info.scqPtr + peer * sizeof(cq)), sizeof(cq),
54+ ACL_MEMCPY_DEVICE_TO_HOST) != ACL_SUCCESS ||
55+ sq.stateAddr == 0) {
56+ return false;
57+ }
58+ snapshot.sqDepth = sq.depth;
59+ snapshot.cqDepth = cq.depth;
60+ return aclrtMemcpy(
61+ &snapshot.state, sizeof(snapshot.state), reinterpret_cast<const void*>(sq.stateAddr),
62+ sizeof(snapshot.state), ACL_MEMCPY_DEVICE_TO_HOST) == ACL_SUCCESS;
63+}
64+ 
32inline bool BackendVerboseEnabled()65inline bool BackendVerboseEnabled()
33{66{
34 const char* verbose = std::getenv("PTO_ROCE_VERBOSE");67 const char* verbose = std::getenv("PTO_ROCE_VERBOSE");
@@ -39,13 +72,15 @@ inline const char* DescribeBackendCompletionStatus(uint32_t status)
39{72{
40 switch (status) {73 switch (status) {
41 case hns_1825::kHns1825PollCqTimeoutError:74 case hns_1825::kHns1825PollCqTimeoutError:
42- return "poll_cq_timeout";75+ return "queue_progress_timeout";
43 case hns_1825::kHns1825InvalidArgumentError:76 case hns_1825::kHns1825InvalidArgumentError:
44 return "invalid_transfer";77 return "invalid_transfer";
45 case hns_1825::kHns1825InvalidContextError:78 case hns_1825::kHns1825InvalidContextError:
46 return "invalid_context";79 return "invalid_context";
47 case hns_1825::kHns1825CqeError:80 case hns_1825::kHns1825CqeError:
48 return "cqe_error_without_syndrome";81 return "cqe_error_without_syndrome";
82+ case hns_1825::kQueueIndexExhaustedError:
83+ return "queue_index_exhausted";
49 default:84 default:
50 return nullptr;85 return nullptr;
51 }86 }
@@ -28,10 +28,10 @@ void ExpectPutResult(int nRanks = 2, int nDevices = 2)
28template <typename T, size_t count>28template <typename T, size_t count>
29void ExpectPutPlan(29void ExpectPutPlan(
30 int elemOffset, int elemCount, int operationCount, RdmaCompletionMode completionMode, int nRanks = 2,30 int elemOffset, int elemCount, int operationCount, RdmaCompletionMode completionMode, int nRanks = 2,
31- int nDevices = 2)31+ int nDevices = 2, int blockCount = 1)
32{32{
33 RdmaTestResult result = RunPutAsyncRdmaRootPutPlan<T, count>(33 RdmaTestResult result = RunPutAsyncRdmaRootPutPlan<T, count>(
34- nRanks, nDevices, 0, 0, elemOffset, elemCount, operationCount, completionMode);34+ nRanks, nDevices, 0, 0, elemOffset, elemCount, operationCount, completionMode, blockCount);
35 if (result == RdmaTestResult::SKIPPED) {35 if (result == RdmaTestResult::SKIPPED) {
36 GTEST_SKIP() << "RDMA runtime prerequisites are unavailable on at least one rank";36 GTEST_SKIP() << "RDMA runtime prerequisites are unavailable on at least one rank";
37 }37 }
@@ -86,6 +86,16 @@ TEST(TPutAsyncRdma, Vec_Int32_MultiWqe_WaitLast)
86 ExpectPutPlan<int32_t, 4096>(0, 256, 16, RdmaCompletionMode::STATUS_WAIT_LAST);86 ExpectPutPlan<int32_t, 4096>(0, 256, 16, RdmaCompletionMode::STATUS_WAIT_LAST);
87}87}
88 88 
89+TEST(TPutAsyncRdma, Vec_Int32_MultiAiv)
90+{
91+ SKIP_IF_RANKS_LT(2);
92+ const RdmaTestResult result = RunPutAsyncRdmaConcurrent(2, 2, 0, 0);
93+ if (result == RdmaTestResult::SKIPPED) {
94+ GTEST_SKIP() << "RDMA runtime prerequisites are unavailable on at least one rank";
95+ }
96+ ASSERT_EQ(result, RdmaTestResult::PASSED);
97+}
98+ 
89// Cover the public AsyncEvent dispatch, including Test before/after Wait.99// Cover the public AsyncEvent dispatch, including Test before/after Wait.
90TEST(TPutAsyncRdma, Vec_Float_PublicEventWaitTest)100TEST(TPutAsyncRdma, Vec_Float_PublicEventWaitTest)
91{101{
@@ -21,9 +21,10 @@ See LICENSE in the root of the software repository for the full text of the Lice
21#include <utility>21#include <utility>
22#include <vector>22#include <vector>
23 23 
24+#include <acl/acl.h>
24#include <pto/pto-inst.hpp>25#include <pto/pto-inst.hpp>
25 26 
26-#include "../common.hpp"27+#include "../comm_mpi.h"
27#include "tput_async_rdma_kernel.h"28#include "tput_async_rdma_kernel.h"
28#include "pto/common/pto_tile.hpp"29#include "pto/common/pto_tile.hpp"
29#ifdef PTO_RDMA_SUPPORTED30#ifdef PTO_RDMA_SUPPORTED
@@ -30,10 +30,12 @@ RdmaTestResult RunPutAsyncRdmaRootPut(int n_ranks, int n_devices, int first_rank
30 30 
31RdmaTestResult RunPutAsyncNotifyRdmaSet(int n_ranks, int n_devices, int first_rank_id, int first_device_id);31RdmaTestResult RunPutAsyncNotifyRdmaSet(int n_ranks, int n_devices, int first_rank_id, int first_device_id);
32 32 
33+RdmaTestResult RunPutAsyncRdmaConcurrent(int n_ranks, int n_devices, int first_rank_id, int first_device_id);
34+ 
33template <typename T, size_t count>35template <typename T, size_t count>
34RdmaTestResult RunPutAsyncRdmaRootPutPlan(36RdmaTestResult RunPutAsyncRdmaRootPutPlan(
35 int n_ranks, int n_devices, int first_rank_id, int first_device_id, int elem_offset, int elem_count,37 int n_ranks, int n_devices, int first_rank_id, int first_device_id, int elem_offset, int elem_count,
36- int operation_count, RdmaCompletionMode completion_mode);38+ int operation_count, RdmaCompletionMode completion_mode, int block_count = 1);
37 39 
38// Shared host entry point used by the RDMA GET test target.40// Shared host entry point used by the RDMA GET test target.
39template <typename T, size_t count>41template <typename T, size_t count>