Skip to content

Commit 7be8f40

Browse files
committed
refactor(ipc): add GPU-side verification and improve IPC benchmark
1 parent db38225 commit 7be8f40

4 files changed

Lines changed: 84 additions & 81 deletions

File tree

experiments/ipc/main.cu

Lines changed: 23 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,90 +21,53 @@
2121
*/
2222
#include <affinity/affinity.h>
2323
#include <bench/arguments.h>
24-
#include <io/progress.h>
2524

2625
#include <bench/modules/ipc.cuh>
2726
#include <bench/mpi/fabric.cuh>
2827

2928
/**
30-
* @brief IPC benchmark with configurable parallelism
31-
* @tparam BufType Buffer type (SymmetricDMAMemory)
29+
* @brief IPC benchmark test with configurable grid/block dimensions
3230
* @tparam NumBlocks Number of thread blocks (grid dimension)
3331
* @tparam NumThreads Number of threads per block
3432
*/
35-
template <typename BufType, unsigned int NumBlocks, unsigned int NumThreads>
36-
struct TestConfig {
37-
static BenchResult Run(size_t size, const Options& opts, FabricBench& peer, std::string_view name = "IPCWrite") {
33+
template <unsigned int NumBlocks, unsigned int NumThreads>
34+
struct Test {
35+
static BenchResult Run(size_t size, const Options& opts, FabricBench& peer, std::string_view name) {
3836
int rank = peer.mpi.GetWorldRank();
39-
int world = peer.mpi.GetWorldSize();
4037
int local_size = peer.mpi.GetLocalSize();
41-
int local_rank = peer.mpi.GetLocalRank();
4238

43-
int next = (rank + 1) % world;
44-
auto mem = std::make_unique<BufType>(peer.channels[next], size, world, peer.device);
45-
auto local_world_ranks = peer.Handshake(mem);
46-
47-
auto ctx = mem->GetContext();
48-
size_t len = size / sizeof(int);
39+
// Allocate IPC buffer and exchange handles with local ranks
40+
auto bufs = peer.AllocIPC<SymmetricDMAMemory>(size);
41+
auto local_world_ranks = peer.Handshake(bufs, std::true_type{});
4942

5043
auto& affinity = GPUloc::Get().GetGPUAffinity()[peer.device];
51-
size_t ipc_bw_bps = affinity.mem_support.nvlink_bw * 8;
52-
53-
cudaLaunchConfig_t cfg{.gridDim = {NumBlocks, 1, 1}, .blockDim = {NumThreads, 1, 1}, .stream = peer.stream};
44+
size_t ipc_bw = affinity.mem_support.nvlink_bw * 8;
5445

46+
// Benchmark rank 0 writing to each local peer via IPC
5547
double sum_bw = 0, sum_time = 0;
5648
for (int t = 1; t < local_size; ++t) {
57-
int target_rank = local_world_ranks[t];
58-
Progress progress(opts.repeat, ipc_bw_bps, name);
59-
MPI_Barrier(MPI_COMM_WORLD);
60-
auto start = std::chrono::high_resolution_clock::now();
61-
62-
if (rank == 0) {
63-
for (int i = 0; i < opts.repeat; ++i) {
64-
LAUNCH_KERNEL(&cfg, IPCWriteKernel, ctx.ipc_ptrs, target_rank, len, 1);
65-
if ((i + 1) % Progress::kPrintFreq == 0) {
66-
CUDA_CHECK(cudaStreamSynchronize(peer.stream));
67-
progress.Print(std::chrono::high_resolution_clock::now(), size, i + 1);
68-
}
69-
}
70-
CUDA_CHECK(cudaStreamSynchronize(peer.stream));
71-
}
72-
73-
MPI_Barrier(MPI_COMM_WORLD);
74-
auto end = std::chrono::high_resolution_clock::now();
75-
76-
// GPU-side verification: target rank checks received data
77-
if (rank == target_rank) {
78-
int* result;
79-
CUDA_CHECK(cudaMallocManaged(&result, sizeof(int)));
80-
*result = 1;
81-
LAUNCH_KERNEL(&cfg, IPCVerifyKernel, static_cast<int*>(mem->Data()), target_rank, len, result);
82-
CUDA_CHECK(cudaStreamSynchronize(peer.stream));
83-
bool ok = (*result == 1);
84-
CUDA_CHECK(cudaFree(result));
85-
if (!ok) throw std::runtime_error("IPC verification failed");
86-
}
87-
88-
double elapsed_us = std::chrono::duration<double, std::micro>(end - start).count();
89-
double avg_us = elapsed_us / opts.repeat;
90-
double bw_gbps = (size * 8.0) / (avg_us * 1000.0);
91-
sum_bw += bw_gbps;
92-
sum_time += avg_us;
49+
int target = local_world_ranks[t];
50+
using Write = IPCWrite<FabricBench, NumBlocks, NumThreads>;
51+
using Verify = IPCVerify<FabricBench, NumBlocks, NumThreads>;
52+
53+
peer.Warmup(bufs, bufs, Write{target}, Verify{target}, opts.warmup);
54+
auto r = peer.Bench(name, bufs, bufs, Write{target}, Verify{target}, opts.repeat, 0, ipc_bw);
55+
sum_bw += r.bw_gbps;
56+
sum_time += r.time_us;
9357
}
9458

9559
int npairs = local_size - 1;
9660
double avg_bw = sum_bw / npairs;
97-
double link_bw = ipc_bw_bps / 1e9;
98-
double bus_bw = (link_bw > 0) ? (avg_bw / link_bw) * 100.0 : 0;
99-
return {size, sum_time / npairs, avg_bw, bus_bw};
61+
double link_bw = ipc_bw / 1e9;
62+
return {size, sum_time / npairs, avg_bw, (link_bw > 0) ? (avg_bw / link_bw) * 100.0 : 0};
10063
}
10164
};
10265

10366
// Test configurations demonstrating parallelism impact on NVLink utilization
104-
using Test1x256 = TestConfig<SymmetricDMAMemory, 1, 256>; // Low parallelism baseline
105-
using Test1x1024 = TestConfig<SymmetricDMAMemory, 1, 1024>; // Single block, max threads
106-
using Test16x256 = TestConfig<SymmetricDMAMemory, 16, 256>; // Multi-block, moderate
107-
using Test128x256 = TestConfig<SymmetricDMAMemory, 128, 256>; // High parallelism
67+
using Test1x256 = Test<1, 256>; // Low parallelism baseline
68+
using Test1x1024 = Test<1, 1024>; // Single block, max threads
69+
using Test16x256 = Test<16, 256>; // Multi-block, moderate
70+
using Test128x256 = Test<128, 256>;
10871

10972
int main(int argc, char* argv[]) {
11073
auto opts = parse_args(argc, argv);

src/include/bench/modules/ipc.cuh

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,22 @@ __global__ void IPCReadKernel(void* const* __restrict__ ipc_ptrs, int source, si
4545
}
4646

4747
/**
48-
* @brief IPC Write benchmark functor
48+
* @brief IPC Write benchmark functor with configurable parallelism
4949
*/
50-
template <typename Peer>
50+
template <typename Peer, unsigned int NumBlocks = 1, unsigned int NumThreads = 256>
5151
struct IPCWrite {
52-
int iters, target;
52+
int target;
5353

5454
template <typename T>
55-
void operator()(Peer& peer, typename Peer::template Buffers<T>& a, typename Peer::template Buffers<T>& b) {
55+
void operator()(Peer& peer, typename Peer::template Buffers<T>& a, typename Peer::template Buffers<T>&) {
5656
int rank = peer.mpi.GetWorldRank();
5757
if (rank != 0) return;
5858

59-
auto& mem = *a[rank];
60-
auto ctx = mem.GetContext();
61-
size_t len = mem.Size() / sizeof(int);
59+
auto ctx = a[rank]->GetContext();
60+
size_t len = a[rank]->Size() / sizeof(int);
6261

63-
cudaLaunchConfig_t cfg{.gridDim = {1, 1, 1}, .blockDim = {256, 1, 1}, .stream = peer.stream};
64-
LAUNCH_KERNEL(&cfg, IPCWriteKernel, ctx.ipc_ptrs, target, len, iters);
62+
cudaLaunchConfig_t cfg{.gridDim = {NumBlocks, 1, 1}, .blockDim = {NumThreads, 1, 1}, .stream = peer.stream};
63+
LAUNCH_KERNEL(&cfg, IPCWriteKernel, ctx.ipc_ptrs, target, len, 1);
6564
CUDA_CHECK(cudaStreamSynchronize(peer.stream));
6665
}
6766
};
@@ -89,20 +88,28 @@ struct IPCRead {
8988
};
9089

9190
/**
92-
* @brief IPC Verify functor - verifies data on receiver side
91+
* @brief IPC Verify functor with configurable parallelism (GPU-side verification)
9392
*/
93+
template <typename Peer, unsigned int NumBlocks = 1, unsigned int NumThreads = 256>
9494
struct IPCVerify {
9595
int target;
9696

97-
template <typename P, typename Buffers>
98-
void operator()(P& peer, Buffers& b) const {
97+
template <typename T>
98+
void operator()(Peer& peer, typename Peer::template Buffers<T>& bufs) {
9999
int rank = peer.mpi.GetWorldRank();
100100
if (rank != target) return;
101101

102-
auto& mem = *b[rank];
103-
size_t num_ints = mem.Size() / sizeof(int);
104-
std::vector<int> host_buf(num_ints);
105-
CUDA_CHECK(cudaMemcpy(host_buf.data(), mem.Data(), mem.Size(), cudaMemcpyDeviceToHost));
106-
if (VerifyBufferData(host_buf, rank, rank, 0) > 0) throw std::runtime_error("IPC verification failed");
102+
auto* data = static_cast<int*>(bufs[rank]->Data());
103+
size_t len = bufs[rank]->Size() / sizeof(int);
104+
105+
int* result;
106+
CUDA_CHECK(cudaMallocManaged(&result, sizeof(int)));
107+
*result = 1;
108+
cudaLaunchConfig_t cfg{.gridDim = {NumBlocks, 1, 1}, .blockDim = {NumThreads, 1, 1}, .stream = peer.stream};
109+
LAUNCH_KERNEL(&cfg, IPCVerifyKernel, data, target, len, result);
110+
CUDA_CHECK(cudaStreamSynchronize(peer.stream));
111+
bool ok = (*result == 1);
112+
CUDA_CHECK(cudaFree(result));
113+
if (!ok) throw std::runtime_error("IPC verification failed");
107114
}
108115
};

src/include/bench/mpi/fabric.cuh

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,30 @@ class FabricBench : public Peer {
221221
return buffers;
222222
}
223223

224+
/**
225+
* @brief Allocate buffer for IPC (intra-node) communication
226+
*
227+
* Unlike Alloc() which creates buffers for each peer, this allocates a single
228+
* buffer at bufs[rank] that will be shared via CUDA IPC with local ranks.
229+
* Use with Handshake(bufs, std::true_type{}) to exchange IPC handles.
230+
*
231+
* @tparam T Buffer type (typically SymmetricDMAMemory)
232+
* @param size Buffer size in bytes
233+
* @param init_value Initial value for buffer (-1 uses rank as value)
234+
* @return Vector with buffer only at bufs[rank], others are nullptr
235+
*/
236+
template <typename T>
237+
Buffers<T> AllocIPC(size_t size, int init_value = 0) {
238+
const auto world_size = mpi.GetWorldSize();
239+
const auto rank = mpi.GetWorldRank();
240+
Buffers<T> buffers(world_size);
241+
buffers[rank] = MakeBuffer<T>(channels[rank], device, size, world_size);
242+
const size_t num_ints = size / sizeof(int);
243+
int value = (init_value == -1) ? rank : init_value;
244+
InitBuffer(buffers[rank].get(), num_ints, value, stream);
245+
return buffers;
246+
}
247+
224248
/**
225249
* @brief Allocate send/recv buffer pair
226250
* @param size Buffer size in bytes
@@ -262,7 +286,11 @@ class FabricBench : public Peer {
262286
BenchResult
263287
Bench(std::string_view name, Buffers<T>& a, Buffers<T>& b, F&& func, V&& verify, int iters, size_t progress_bytes = 0, size_t progress_bw = 0) {
264288
const auto rank = mpi.GetWorldRank();
265-
const size_t buf_size = a[rank == 0 ? 1 : 0]->Size();
289+
const auto world_size = mpi.GetWorldSize();
290+
size_t buf_size = 0;
291+
for (int i = 0; i < world_size && buf_size == 0; ++i) {
292+
if (a[i]) buf_size = a[i]->Size();
293+
}
266294
const size_t bytes = progress_bytes > 0 ? progress_bytes : buf_size;
267295
const size_t bw = progress_bw > 0 ? progress_bw : GetBandwidth(0);
268296
Progress progress(iters, bw, name);

src/include/bootstrap/mpi/fabric.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,21 @@ class Peer : private NoCopy {
9898
*
9999
* @tparam T SymmetricMemory type (must be DeviceDMABuffer-based)
100100
* @param bufs Buffer set to exchange IPC handles for
101+
* @return Vector mapping local rank index to world rank
101102
*/
102103
template <typename T>
103-
void Handshake(Buffers<T>& bufs, std::true_type /* ipc */) {
104+
std::vector<int> Handshake(Buffers<T>& bufs, std::true_type /* ipc */) {
104105
const int local_size = mpi.GetLocalSize();
105106
const int local_rank = mpi.GetLocalRank();
106107
const int world_rank = mpi.GetWorldRank();
107108

108-
if (local_size <= 1) return;
109+
std::vector<int> local_world_ranks(local_size);
110+
if (local_size <= 1) {
111+
local_world_ranks[0] = world_rank;
112+
return local_world_ranks;
113+
}
109114

110115
std::vector<cudaIpcMemHandle_t> all_handles(local_size);
111-
std::vector<int> local_world_ranks(local_size);
112116

113117
cudaIpcMemHandle_t local_handle;
114118
CUDA_CHECK(cudaIpcGetMemHandle(&local_handle, bufs[world_rank]->Data()));
@@ -119,6 +123,7 @@ class Peer : private NoCopy {
119123
MPI_Allgather(&local_handle, hsz, MPI_BYTE, all_handles.data(), hsz, MPI_BYTE, local);
120124

121125
bufs[world_rank]->OpenIPCHandles(all_handles, local_world_ranks, local_rank);
126+
return local_world_ranks;
122127
}
123128

124129
/**

0 commit comments

Comments
 (0)