Skip to content

Commit 016094f

Browse files
committed
port old code
Signed-off-by: niranda perera <niranda.perera@gmail.com>
1 parent e5214b4 commit 016094f

10 files changed

Lines changed: 383 additions & 71 deletions

File tree

cpp/include/rapidsmpf/statistics.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,22 @@ class Statistics : public std::enable_shared_from_this<Statistics> {
537537
MemoryType mem_type, std::size_t nbytes, StreamOrderedTiming&& timing
538538
);
539539

540+
/**
541+
* @brief Record byte count for a send operation.
542+
*
543+
* @param src Source memory type.
544+
* @param nbytes Number of bytes sent.
545+
*/
546+
void record_send(MemoryType src, std::size_t nbytes);
547+
548+
/**
549+
* @brief Record byte count for a receive operation.
550+
*
551+
* @param dst Destination memory type.
552+
* @param nbytes Number of bytes received.
553+
*/
554+
void record_recv(MemoryType dst, std::size_t nbytes);
555+
540556
/**
541557
* @brief Get the names of all statistics.
542558
*

cpp/src/communicator/mpi.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ std::unique_ptr<Communicator::Future> MPI::send(
127127
RAPIDSMPF_MPI(MPI_Isend(
128128
msg->data(), safe_cast<int>(msg->size()), MPI_UINT8_T, rank, tag, comm_, &req
129129
));
130+
statistics_->record_send(MemoryType::HOST, msg->size());
130131
return std::make_unique<Future>(req, std::move(msg));
131132
}
132133

@@ -139,6 +140,7 @@ std::unique_ptr<Communicator::Future> MPI::send(
139140
RAPIDSMPF_MPI(MPI_Isend(
140141
msg->data(), safe_cast<int>(msg->size), MPI_UINT8_T, rank, tag, comm_, &req
141142
));
143+
statistics_->record_send(msg->mem_type(), msg->size);
142144
return std::make_unique<Future>(req, std::move(msg));
143145
}
144146

@@ -167,6 +169,7 @@ std::unique_ptr<Communicator::Future> MPI::recv(
167169
mpi_recv_impl(
168170
rank, tag, recv_buffer->exclusive_data_access(), recv_buffer->size, comm_, &req
169171
);
172+
statistics_->record_recv(recv_buffer->mem_type(), recv_buffer->size);
170173
return std::make_unique<Future>(req, std::move(recv_buffer));
171174
}
172175

@@ -180,6 +183,7 @@ std::unique_ptr<Communicator::Future> MPI::recv_sync_host_data(
180183
);
181184
MPI_Request req;
182185
mpi_recv_impl(rank, tag, synced_buffer->data(), synced_buffer->size(), comm_, &req);
186+
statistics_->record_recv(MemoryType::HOST, synced_buffer->size());
183187
return std::make_unique<Future>(req, std::move(synced_buffer));
184188
}
185189

@@ -211,6 +215,7 @@ std::pair<std::unique_ptr<std::vector<std::uint8_t>>, Rank> MPI::recv_any(Tag ta
211215
safe_cast<std::size_t>(size) == msg->size(),
212216
"incorrect size of the MPI_Recv message"
213217
);
218+
statistics_->record_recv(MemoryType::HOST, msg->size());
214219
return {std::move(msg), probe_status.MPI_SOURCE};
215220
}
216221

@@ -240,6 +245,7 @@ std::unique_ptr<std::vector<std::uint8_t>> MPI::recv_from(Rank src, Tag tag) {
240245
safe_cast<std::size_t>(size) == msg->size(),
241246
"incorrect size of the MPI_Recv message"
242247
);
248+
statistics_->record_recv(MemoryType::HOST, msg->size());
243249
return msg;
244250
}
245251

cpp/src/communicator/ucxx.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,7 @@ std::unique_ptr<Communicator::Future> UCXX::send(
12211221
msg->size(),
12221222
tag_with_rank(shared_resources_->rank(), static_cast<int>(tag))
12231223
);
1224+
statistics_->record_send(MemoryType::HOST, msg->size());
12241225
return std::make_unique<Future>(req, std::move(msg));
12251226
}
12261227

@@ -1232,6 +1233,7 @@ std::unique_ptr<Communicator::Future> UCXX::send(
12321233
auto req = get_endpoint(rank)->tagSend(
12331234
msg->data(), msg->size, tag_with_rank(shared_resources_->rank(), tag)
12341235
);
1236+
statistics_->record_send(msg->mem_type(), msg->size);
12351237
return std::make_unique<Future>(req, std::move(msg));
12361238
}
12371239

@@ -1250,6 +1252,7 @@ std::unique_ptr<Communicator::Future> UCXX::recv(
12501252
tag_with_rank(rank, tag),
12511253
::ucxx::TagMaskFull
12521254
);
1255+
statistics_->record_recv(recv_buffer->mem_type(), recv_buffer->size);
12531256
return std::make_unique<Future>(req, std::move(recv_buffer));
12541257
}
12551258

@@ -1265,6 +1268,7 @@ std::unique_ptr<Communicator::Future> UCXX::recv_sync_host_data(
12651268
tag_with_rank(rank, tag),
12661269
::ucxx::TagMaskFull
12671270
);
1271+
statistics_->record_recv(MemoryType::HOST, synced_buffer->size());
12681272
return std::make_unique<Future>(req, std::move(synced_buffer));
12691273
}
12701274

@@ -1290,6 +1294,7 @@ std::pair<std::unique_ptr<std::vector<std::uint8_t>>, Rank> UCXX::recv_any(Tag t
12901294
}
12911295
req->checkError();
12921296

1297+
statistics_->record_recv(MemoryType::HOST, msg->size());
12931298
return {std::move(msg), sender_rank};
12941299
}
12951300

@@ -1314,6 +1319,7 @@ std::unique_ptr<std::vector<std::uint8_t>> UCXX::recv_from(Rank src, Tag tag) {
13141319
}
13151320
req->checkError();
13161321

1322+
statistics_->record_recv(MemoryType::HOST, msg->size());
13171323
return msg;
13181324
}
13191325

cpp/src/statistics.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,4 +742,26 @@ void Statistics::record_alloc(
742742
);
743743
}
744744

745+
void Statistics::record_send(MemoryType src, std::size_t nbytes) {
746+
static auto const names = [] {
747+
std::array<std::string, MEMORY_TYPE_NAMES.size()> ret;
748+
std::ranges::transform(MEMORY_TYPE_NAMES, ret.begin(), [](char const* n) {
749+
return std::format("send-from-{}", n);
750+
});
751+
return ret;
752+
}();
753+
add_stat(names[static_cast<std::size_t>(src)], static_cast<double>(nbytes));
754+
}
755+
756+
void Statistics::record_recv(MemoryType dst, std::size_t nbytes) {
757+
static auto const names = [] {
758+
std::array<std::string, MEMORY_TYPE_NAMES.size()> ret;
759+
std::ranges::transform(MEMORY_TYPE_NAMES, ret.begin(), [](char const* n) {
760+
return std::format("recv-to-{}", n);
761+
});
762+
return ret;
763+
}();
764+
add_stat(names[static_cast<std::size_t>(dst)], static_cast<double>(nbytes));
765+
}
766+
745767
} // namespace rapidsmpf

cpp/tests/main/mpi.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ void Environment::SetUp() {
2727

2828
options_ = rapidsmpf::config::Options(rapidsmpf::config::get_environment_variables());
2929

30+
// enable statistics globally in the communicator
3031
comm_ = std::make_shared<rapidsmpf::MPI>(
3132
mpi_comm_,
3233
options_,
3334
std::make_shared<rapidsmpf::ProgressThread>(
34-
rapidsmpf::Statistics::from_options(options_)
35+
rapidsmpf::Statistics::create(rapidsmpf::Statistics::Mode::Enabled)
3536
)
3637
);
3738
}

cpp/tests/main/ucxx.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ void Environment::SetUp() {
3636
);
3737

3838
options_ = rapidsmpf::config::Options(rapidsmpf::config::get_environment_variables());
39+
// enable statistics globally in the communicator
3940
comm_ = rapidsmpf::ucxx::init_using_mpi(
4041
MPI_COMM_WORLD,
4142
options_,
4243
std::make_shared<rapidsmpf::ProgressThread>(
43-
rapidsmpf::Statistics::from_options(options_)
44+
rapidsmpf::Statistics::create(rapidsmpf::Statistics::Mode::Enabled)
4445
)
4546
);
4647
}

cpp/tests/test_allgather.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,67 @@ TEST_P(AllGatherOrderedTest, non_uniform_inserts) {
271271
}
272272
}
273273

274+
// Test that send/recv statistics record correct byte counts when allgather is run with an
275+
// enabled Statistics object.
276+
//
277+
// In the ring allgather each data chunk travels (nranks-1) hops. Every forwarding rank
278+
// records one DEVICE send and the receiving rank records one DEVICE recv, so the
279+
// aggregate per-rank totals are both (nranks-1) * data_bytes.
280+
TEST_F(BaseAllGatherTest, stats_egress_ingress) {
281+
auto const& comm = GlobalEnvironment->comm_;
282+
if (comm->nranks() == 1) {
283+
GTEST_SKIP()
284+
<< "Stats test requires multiple ranks (no network traffic on 1 rank)";
285+
}
286+
287+
constexpr int n_elements = 10;
288+
constexpr std::size_t data_bytes = n_elements * sizeof(int);
289+
290+
// Note: there is split-brain scenario here because communicator and br have their own
291+
// statistics objects
292+
auto stats = comm->statistics();
293+
EXPECT_TRUE(stats->enabled());
294+
295+
stats->clear(); // clear any previous stats since communicator stats are global
296+
297+
AllGather allgather{comm, 0, br.get()};
298+
299+
allgather.insert(
300+
0, generate_packed_data(n_elements, comm->rank() * n_elements, stream, *br)
301+
);
302+
allgather.insert_finished();
303+
304+
std::vector<rapidsmpf::PackedData> results;
305+
EXPECT_NO_THROW(
306+
results =
307+
allgather.wait_and_extract(AllGather::Ordered::NO, std::chrono::seconds{30})
308+
);
309+
310+
auto const nranks = comm->nranks();
311+
ASSERT_EQ(results.size(), static_cast<std::size_t>(nranks));
312+
for (auto const& result : results) {
313+
EXPECT_EQ(result.data->size, data_bytes);
314+
}
315+
auto const metadata_bytes = results[0].metadata->size();
316+
317+
// DEVICE: exact — each chunk does (nranks-1) hops
318+
auto const expected_device_bytes = static_cast<double>((nranks - 1) * data_bytes);
319+
EXPECT_DOUBLE_EQ(stats->get_stat("send-from-DEVICE").value(), expected_device_bytes);
320+
EXPECT_DOUBLE_EQ(stats->get_stat("recv-to-DEVICE").value(), expected_device_bytes);
321+
322+
// HOST: lower bound — at minimum (nranks-1) data-chunk sends/recvs, each carrying
323+
// at least metadata_bytes of user content (ChunkID and data_size fields add more).
324+
auto const host_lower_bound = static_cast<double>((nranks - 1) * metadata_bytes);
325+
auto const sent_host_bytes = stats->get_stat("send-from-HOST").value();
326+
auto const recv_host_bytes = stats->get_stat("recv-to-HOST").value();
327+
// metadata sends and recieves are symmetric
328+
EXPECT_GT(sent_host_bytes, host_lower_bound);
329+
EXPECT_GT(recv_host_bytes, host_lower_bound);
330+
EXPECT_DOUBLE_EQ(sent_host_bytes, recv_host_bytes);
331+
332+
stats->clear();
333+
}
334+
274335
// Test that reusing an OpID after a completed allgather doesn't cause cross-matching of
275336
// messages between the old and new collective.
276337
//

cpp/tests/test_allreduce.cu

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,45 @@ class AllReduceIntSumTest
305305
mem_type = std::get<1>(GetParam());
306306
}
307307

308+
void run_basic_allreduce_sum_int_test() {
309+
auto this_rank = comm->rank();
310+
auto nranks = comm->nranks();
311+
312+
// Choose operator based on reduction type
313+
ReduceOperator kernel =
314+
mem_type == MemoryType::DEVICE
315+
? rapidsmpf::coll::detail::make_device_reduce_operator<int>(SumOp<int>{})
316+
: rapidsmpf::coll::detail::make_host_reduce_operator<int>(SumOp<int>{});
317+
318+
std::vector<int> data(std::max(0, n_elements));
319+
for (int j = 0; j < n_elements; j++) {
320+
data[j] = this_rank;
321+
}
322+
323+
auto in_buffer = make_buffer<int>(br.get(), data.data(), data.size(), mem_type);
324+
auto reservation = br->reserve_or_fail(in_buffer->size, in_buffer->mem_type());
325+
auto out_buffer =
326+
br->make_buffer(in_buffer->size, in_buffer->stream(), reservation);
327+
328+
AllReduce allreduce(
329+
GlobalEnvironment->comm_,
330+
std::move(in_buffer),
331+
std::move(out_buffer),
332+
OpID{0},
333+
std::move(kernel)
334+
);
335+
336+
auto [in_result, out_result] = allreduce.wait_and_extract();
337+
338+
auto reduced = unpack_to_host<int>(*out_result);
339+
ASSERT_EQ(static_cast<std::size_t>(n_elements), reduced.size());
340+
// Expected value is sum of all ranks (0 + 1 + 2 + ... + nranks-1)
341+
int const expected_value = (nranks * (nranks - 1)) / 2;
342+
EXPECT_THAT(reduced, ::testing::Each(expected_value));
343+
344+
EXPECT_TRUE(allreduce.finished());
345+
}
346+
308347
int n_elements{};
309348
MemoryType mem_type{};
310349
};
@@ -329,41 +368,67 @@ INSTANTIATE_TEST_SUITE_P(
329368
);
330369

331370
TEST_P(AllReduceIntSumTest, basic_allreduce_sum_int) {
332-
auto this_rank = comm->rank();
333-
auto nranks = comm->nranks();
334-
335-
// Choose operator based on reduction type
336-
ReduceOperator kernel =
337-
mem_type == MemoryType::DEVICE
338-
? rapidsmpf::coll::detail::make_device_reduce_operator<int>(SumOp<int>{})
339-
: rapidsmpf::coll::detail::make_host_reduce_operator<int>(SumOp<int>{});
371+
EXPECT_NO_THROW(run_basic_allreduce_sum_int_test());
372+
}
340373

341-
std::vector<int> data(std::max(0, n_elements));
342-
for (int j = 0; j < n_elements; j++) {
343-
data[j] = this_rank;
374+
TEST_P(AllReduceIntSumTest, stats_egress_ingress) {
375+
if (comm->nranks() == 1) {
376+
GTEST_SKIP()
377+
<< "Stats test requires multiple ranks (no network traffic on 1 rank)";
344378
}
345379

346-
auto in_buffer = make_buffer<int>(br.get(), data.data(), data.size(), mem_type);
347-
auto reservation = br->reserve_or_fail(in_buffer->size, in_buffer->mem_type());
348-
auto out_buffer = br->make_buffer(in_buffer->size, in_buffer->stream(), reservation);
380+
auto stats = comm->statistics();
381+
stats->clear(); // clear any previous stats since communicator stats are global
349382

350-
AllReduce allreduce(
351-
GlobalEnvironment->comm_,
352-
std::move(in_buffer),
353-
std::move(out_buffer),
354-
OpID{0},
355-
std::move(kernel)
356-
);
383+
EXPECT_NO_THROW(run_basic_allreduce_sum_int_test());
357384

358-
auto [in_result, out_result] = allreduce.wait_and_extract();
385+
auto const rank = comm->rank();
386+
auto const nranks = comm->nranks();
387+
auto const data_bytes = static_cast<double>(n_elements * sizeof(int));
388+
389+
// Compute the butterfly round count and pre-remainder size.
390+
// nearest_pow2 = largest power of 2 <= nranks
391+
// remainder = nranks - nearest_pow2
392+
// butterfly_rounds = log2(nearest_pow2)
393+
auto const nearest_pow2 =
394+
static_cast<int>(std::bit_floor(static_cast<unsigned>(nranks)));
395+
auto const remainder = nranks - nearest_pow2;
396+
// bit_width of a power-of-2 n is log2(n)+1, so subtract 1 to get log2.
397+
auto const butterfly_rounds =
398+
static_cast<int>(std::bit_width(static_cast<unsigned>(nearest_pow2))) - 1;
399+
400+
// Expected traffic per rank:
401+
// Even pre-remainder (rank < 2*r, even): 1 pre-send + 1 post-recv → 1×D each
402+
// Odd pre-remainder (rank < 2*r, odd): 1 pre-recv + B butterfly
403+
// + 1 post-send → (1+B)×D each
404+
// Non-remainder (rank >= 2*r): B butterfly rounds → B×D each
405+
double expected_egress{};
406+
double expected_ingress{};
407+
if (rank < 2 * remainder) {
408+
if (rank % 2 == 0) {
409+
expected_egress = data_bytes;
410+
expected_ingress = data_bytes;
411+
} else {
412+
expected_egress = (1 + butterfly_rounds) * data_bytes;
413+
expected_ingress = (1 + butterfly_rounds) * data_bytes;
414+
}
415+
} else {
416+
expected_egress = butterfly_rounds * data_bytes;
417+
expected_ingress = butterfly_rounds * data_bytes;
418+
}
359419

360-
auto reduced = unpack_to_host<int>(*out_result);
361-
ASSERT_EQ(static_cast<std::size_t>(n_elements), reduced.size());
362-
// Expected value is sum of all ranks (0 + 1 + 2 + ... + nranks-1)
363-
int const expected_value = (nranks * (nranks - 1)) / 2;
364-
EXPECT_THAT(reduced, ::testing::Each(expected_value));
420+
EXPECT_DOUBLE_EQ(
421+
stats->get_stat(std::format("send-from-{}", rapidsmpf::to_string(mem_type)))
422+
.value(),
423+
expected_egress
424+
);
425+
EXPECT_DOUBLE_EQ(
426+
stats->get_stat(std::format("recv-to-{}", rapidsmpf::to_string(mem_type)))
427+
.value(),
428+
expected_ingress
429+
);
365430

366-
EXPECT_TRUE(allreduce.finished());
431+
stats->clear();
367432
}
368433

369434
template <typename T, typename Op, MemoryType MemType>

0 commit comments

Comments
 (0)