@@ -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
331370TEST_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
369434template <typename T, typename Op, MemoryType MemType>
0 commit comments