Skip to content

Commit 98ec768

Browse files
committed
tests(core): add a set of unit tests for Communicators
For both MPI and NCCL specializations, covers: - Factory functions (`from_raw`, `duplicate`, `split`); - Accessors (`comm`, `size`, `rank`); - Move semantics.
1 parent d6461fb commit 98ec768

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

unit_tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ kc_add_unit_test(test.core.reduce CORE NUM_PES 2 FILES test_main.cpp test_reduce
5353
kc_add_unit_test(test.core.all-reduce CORE NUM_PES 2 FILES test_main.cpp test_allreduce.cpp)
5454
kc_add_unit_test(test.core.all-to-all CORE NUM_PES 2 FILES test_main.cpp test_alltoall.cpp)
5555

56+
kc_add_unit_test(test.core.communicator CORE NUM_PES 2 FILES test_main.cpp test_communicator.cpp)
5657
kc_add_unit_test(test.core.datatype-conv CORE NUM_PES 1 FILES test_main.cpp test_datatype_conversion.cpp)
5758
kc_add_unit_test(test.core.red-op-conv CORE NUM_PES 1 FILES test_main.cpp test_red_op_conversion.cpp)
5859

unit_tests/test_communicator.cpp

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2+
// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project
3+
4+
#include <gtest/gtest.h>
5+
#include <KokkosComm/KokkosComm.hpp>
6+
7+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
8+
#include "nccl/utils.hpp"
9+
#endif
10+
11+
namespace {
12+
13+
using Ex = Kokkos::DefaultExecutionSpace;
14+
using Co = KokkosComm::DefaultCommunicationSpace;
15+
16+
// ============================================================================
17+
// from_raw
18+
// ============================================================================
19+
20+
TEST(Communicator, from_raw_null_returns_nullopt) {
21+
// from_raw must return std::nullopt when passed a null communicator handle
22+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
23+
auto result = KokkosComm::Communicator<Co, Ex>::from_raw(nullptr, Ex{});
24+
#else
25+
auto result = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_NULL, Ex{});
26+
#endif
27+
ASSERT_FALSE(result.has_value());
28+
}
29+
30+
TEST(Communicator, from_raw_valid_returns_communicator) {
31+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
32+
auto nccl_ctx = test_utils::nccl::Ctx::init();
33+
auto result = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{});
34+
#else
35+
auto result = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{});
36+
#endif
37+
ASSERT_TRUE(result.has_value());
38+
}
39+
40+
TEST(Communicator, from_raw_size_and_rank_are_consistent) {
41+
// size()/rank() must match the created-from raw communicator size/rank
42+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
43+
auto nccl_ctx = test_utils::nccl::Ctx::init();
44+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
45+
const int expected_size = []() {
46+
int s;
47+
ncclCommCount(nccl_ctx.comm(), &s);
48+
return s;
49+
}();
50+
const int expected_rank = []() {
51+
int r;
52+
ncclCommUserRank(nccl_ctx.comm(), &r);
53+
return r;
54+
}();
55+
#else
56+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
57+
const int expected_size = []() {
58+
int s;
59+
MPI_Comm_size(MPI_COMM_WORLD, &s);
60+
return s;
61+
}();
62+
const int expected_rank = []() {
63+
int r;
64+
MPI_Comm_rank(MPI_COMM_WORLD, &r);
65+
return r;
66+
}();
67+
#endif
68+
ASSERT_EQ(comm.size(), expected_size);
69+
ASSERT_EQ(comm.rank(), expected_rank);
70+
}
71+
72+
// ============================================================================
73+
// duplicate
74+
// ============================================================================
75+
76+
TEST(Communicator, duplicate_from_raw_returns_communicator) {
77+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
78+
auto nccl_ctx = test_utils::nccl::Ctx::init();
79+
auto result = KokkosComm::Communicator<Co, Ex>::duplicate(nccl_ctx.comm(), Ex{});
80+
#else
81+
auto result = KokkosComm::Communicator<Co, Ex>::duplicate(MPI_COMM_WORLD, Ex{});
82+
#endif
83+
ASSERT_TRUE(result.has_value());
84+
}
85+
86+
TEST(Communicator, duplicate_preserves_size_and_rank) {
87+
// A duplicated communicator must have the same size and rank as the source
88+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
89+
auto nccl_ctx = test_utils::nccl::Ctx::init();
90+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
91+
#else
92+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
93+
#endif
94+
auto dup = original.duplicate().value();
95+
96+
ASSERT_EQ(dup.size(), original.size());
97+
ASSERT_EQ(dup.rank(), original.rank());
98+
}
99+
100+
TEST(Communicator, duplicate_produces_independent_communicator) {
101+
// The duplicated communicator must hold a distinct handle from the original.
102+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
103+
auto nccl_ctx = test_utils::nccl::Ctx::init();
104+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
105+
#else
106+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
107+
#endif
108+
auto dup = original.duplicate().value();
109+
110+
ASSERT_NE(dup.comm(), original.comm());
111+
}
112+
113+
// ============================================================================
114+
// split
115+
// ============================================================================
116+
117+
TEST(Communicator, split_undefined_color_returns_nullopt) {
118+
// A rank that passes MPI_UNDEFINED/NCCL_SPLIT_NOCOLOR as color must be excluded from the new communicator and receive
119+
// std::nullopt
120+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
121+
auto nccl_ctx = test_utils::nccl::Ctx::init();
122+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
123+
auto result = original.split(NCCL_SPLIT_NOCOLOR, 0);
124+
#else
125+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
126+
auto result = original.split(MPI_UNDEFINED, 0);
127+
#endif
128+
ASSERT_FALSE(result.has_value());
129+
}
130+
131+
TEST(Communicator, split_same_color_groups_all_ranks) {
132+
// When all ranks use the same color, the resulting communicator must have the same size as the original
133+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
134+
auto nccl_ctx = test_utils::nccl::Ctx::init();
135+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
136+
#else
137+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
138+
#endif
139+
auto result = comm.split(0, comm.rank());
140+
141+
ASSERT_TRUE(result.has_value());
142+
ASSERT_EQ(result->size(), comm.size());
143+
}
144+
145+
TEST(Communicator, split_two_colors_produces_half_sized_communicators) {
146+
// With two colors assigned by parity, each sub-communicator must contain roughly half the ranks.
147+
// Requires an even number of ranks >= 2.
148+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
149+
auto nccl_ctx = test_utils::nccl::Ctx::init();
150+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
151+
#else
152+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
153+
#endif
154+
if (comm.size() < 2 or comm.size() % 2 != 0) {
155+
GTEST_SKIP() << "Requires an even number of ranks >= 2 (" << comm.size() << " provided)";
156+
}
157+
158+
const int color = comm.rank() % 2;
159+
auto result = comm.split(color, comm.rank());
160+
161+
ASSERT_TRUE(result.has_value());
162+
ASSERT_EQ(result->size(), comm.size() / 2);
163+
}
164+
165+
TEST(Communicator, split_key_controls_rank_ordering) {
166+
// When ranks split into a single group with reversed keys, the rank order inside the new communicator must be the
167+
// mirror of the original
168+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
169+
auto nccl_ctx = test_utils::nccl::Ctx::init();
170+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
171+
#else
172+
auto comm = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
173+
#endif
174+
if (comm.size() < 2) {
175+
GTEST_SKIP() << "Requires >= 2 ranks (" << comm.size() << " provided)";
176+
}
177+
178+
// Reverse key: rank 0 gets the highest key, rank N-1 gets 0
179+
const int reversed_key = comm.size() - 1 - comm.rank();
180+
auto result = comm.split(0, reversed_key);
181+
182+
ASSERT_TRUE(result.has_value());
183+
// After reversal, each rank's new rank must equal its reversed_key position
184+
ASSERT_EQ(result->rank(), reversed_key);
185+
}
186+
187+
// ============================================================================
188+
// Move semantics
189+
// ============================================================================
190+
191+
TEST(Communicator, move_constructed_communicator_is_valid) {
192+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
193+
auto nccl_ctx = test_utils::nccl::Ctx::init();
194+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
195+
#else
196+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
197+
#endif
198+
const int expected_size = original.size();
199+
const int expected_rank = original.rank();
200+
201+
auto moved = std::move(original);
202+
203+
ASSERT_EQ(moved.size(), expected_size);
204+
ASSERT_EQ(moved.rank(), expected_rank);
205+
}
206+
207+
TEST(Communicator, move_assigned_communicator_is_valid) {
208+
#if defined(KOKKOSCOMM_ENABLE_NCCL)
209+
auto nccl_ctx = test_utils::nccl::Ctx::init();
210+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(nccl_ctx.comm(), Ex{}).value();
211+
auto target = KokkosComm::Communicator<Co, Ex>::duplicate(nccl_ctx.comm(), Ex{}).value();
212+
#else
213+
auto original = KokkosComm::Communicator<Co, Ex>::from_raw(MPI_COMM_WORLD, Ex{}).value();
214+
auto target = KokkosComm::Communicator<Co, Ex>::duplicate(MPI_COMM_WORLD, Ex{}).value();
215+
#endif
216+
const int expected_size = original.size();
217+
const int expected_rank = original.rank();
218+
219+
target = std::move(original);
220+
221+
ASSERT_EQ(target.size(), expected_size);
222+
ASSERT_EQ(target.rank(), expected_rank);
223+
}
224+
225+
} // namespace

0 commit comments

Comments
 (0)