KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
alltoall.hpp
1// This file is part of KaMPIng.
2//
3// Copyright 2022-2024 The KaMPIng Authors
4//
5// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
7// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9// for more details.
10//
11// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see
12// <https://www.gnu.org/licenses/>.
13
14#pragma once
15
16#include <cstddef>
17#include <numeric>
18#include <tuple>
19#include <type_traits>
20
21#include <kassert/kassert.hpp>
22#include <mpi.h>
23
24#include "kamping/assertion_levels.hpp"
26#include "kamping/collectives/collectives_helpers.hpp"
27#include "kamping/comm_helper/is_same_on_all_ranks.hpp"
28#include "kamping/communicator.hpp"
32#include "kamping/result.hpp"
33
34/// @addtogroup kamping_collectives
35/// @{
36
37/// @brief Wrapper for \c MPI_Alltoall.
38///
39/// This wrapper for \c MPI_Alltoall sends the same amount of data from each rank to each rank. The following
40/// buffers are required:
41/// - \ref kamping::send_buf() containing the data that is sent to each rank. This buffer has to be the same size at
42/// each rank and divisible by the size of the communicator unless a send_count or a send_type is explicitly given
43/// as parameter. Each rank receives the same number of elements from this buffer.
44///
45/// The following parameters are optional:
46/// - \ref kamping::send_count() specifying how many elements are sent. If
47/// omitted, the size of send buffer divided by communicator size is used.
48/// This parameter is mandatory if \ref kamping::send_type() is given.
49///
50/// - \ref kamping::recv_count() specifying how many elements are received. If
51/// omitted, the value of send_counts will be used.
52/// This parameter is mandatory if \ref kamping::recv_type() is given.
53///
54/// - \ref kamping::recv_buf() specifying a buffer for the output. A buffer of at least
55/// `recv_count * communicator size` is required.
56///
57/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype
58/// is derived automatically based on send_buf's underlying \c value_type.
59///
60/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype
61/// is derived automatically based on recv_buf's underlying \c value_type.
62///
63/// Inplace alltoall is supported by passing send_recv_buf as parameter. This changes the requirements for the other
64/// parameters, see \ref Communicator::alltoall_inplace.
65///
66/// @tparam Args Automatically deduced template parameters.
67/// @param args All required and any number of the optional buffers described above.
68/// @return Result type wrapping the output parameters to be returned by value.
69///
70/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
71/// <hr>
72/// \include{doc} docs/resize_policy.dox
73template <
74 template <typename...>
75 typename DefaultContainerType,
76 template <typename, template <typename...> typename>
77 typename... Plugins>
78template <typename... Args>
80 using namespace internal;
82 if constexpr (inplace) {
83 return this->alltoall_inplace(std::forward<Args>(args)...);
84 } else {
86 Args,
89 );
90
91 // Get the buffers
92 auto const send_buf =
93 internal::select_parameter_type<internal::ParameterType::send_buf>(args...).construct_buffer_or_rebind();
94 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
95 using default_recv_value_type = std::remove_const_t<send_value_type>;
96
99 auto recv_buf =
100 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
101 std::tuple(),
102 args...
103 )
105 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
106
107 static_assert(!std::is_const_v<recv_value_type>, "The receive buffer must not have a const value_type.");
108
109 auto [send_type, recv_type] =
110 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
111 [[maybe_unused]] constexpr bool recv_type_has_to_be_deduced = has_to_be_computed<decltype(recv_type)>;
112
113 // Get the send counts
115 auto send_count =
116 internal::select_parameter_type_or_default<internal::ParameterType::send_count, default_send_count_type>(
117 std::tuple(),
118 args...
119 )
120 .construct_buffer_or_rebind();
122 if constexpr (do_compute_send_count) {
123 send_count.underlying() = asserting_cast<int>(send_buf.size() / size());
124 }
125 // Get the recv counts
127 auto recv_count =
128 internal::select_parameter_type_or_default<internal::ParameterType::recv_count, default_recv_count_type>(
129 std::tuple(),
130 args...
131 )
132 .construct_buffer_or_rebind();
133
135 if constexpr (do_compute_recv_count) {
136 recv_count.underlying() = send_count.get_single_element();
137 }
138
139 KASSERT(
140 (!do_compute_send_count || send_buf.size() % size() == 0lu),
141 "There are no send counts given and the number of elements in send_buf is not divisible by the number of "
142 "ranks "
143 "in the communicator.",
145 );
146
147 auto compute_required_recv_buf_size = [&]() {
148 return asserting_cast<size_t>(recv_count.get_single_element()) * size();
149 };
150 recv_buf.resize_if_requested(compute_required_recv_buf_size);
151 KASSERT(
152 // if the recv type is user provided, kamping cannot make any assumptions about the required size of the
153 // recv buffer
155 "Recv buffer is not large enough to hold all received elements.",
157 );
158
159 // These KASSERTs are required to avoid a false warning from g++ in release mode
160 KASSERT(send_buf.data() != nullptr, assert::light);
161 KASSERT(recv_buf.data() != nullptr, assert::light);
162
163 [[maybe_unused]] int err = MPI_Alltoall(
164 send_buf.data(), // send_buf
165 send_count.get_single_element(), // send_count
166 send_type.get_single_element(), // send_type
167 recv_buf.data(), // recv_buf
168 recv_count.get_single_element(), // recv_count
169 recv_type.get_single_element(), // recv_type
170 mpi_communicator() // comm
171 );
172
173 this->mpi_error_hook(err, "MPI_Alltoall");
174 return make_mpi_result<std::tuple<Args...>>(
175 std::move(recv_buf), // recv_buf
176 std::move(send_count), // send_count
177 std::move(recv_count), // recv_count
178 std::move(send_type), // send_type
179 std::move(recv_type) // recv_type
180 );
181 }
182}
183
184/// @brief Wrapper for the in-place version of \ref Communicator::alltoall.
185///
186/// This variant must be called collectively by all ranks in the communicator. It sends the same amount of data from
187/// each rank to each rank, using the same buffer for sending and receiving data.
188///
189/// The following parameteres are required:
190///
191/// - \ref kamping::send_recv_buf() containing the data that is sent to each rank and received from each rank. The size
192/// of this buffer has to be the same at each rank and divisible by the size of the communicator unless a
193/// send_recv_count or a send_recv_type is explicitly given as parameter. Each rank receives the same number of elements
194/// from this buffer.
195///
196/// The following parameters are optional:
197///
198/// - \ref kamping::send_recv_count() specifying how many elements are sent and received. If
199/// omitted, the size of send_recv_buf divided by communicator size is used.
200/// This parameter is mandatory if \ref kamping::send_recv_type() is given.
201/// -
202/// \ref kamping::send_recv_type() specifying the \c MPI datatype to use as send and recv type. If omitted, the \c MPI
203/// datatype is derived automatically based on send_recv_buf's underlying \c value_type.
204///
205/// @tparam Args Automatically deduced template parameters.
206/// @param args All required and any number of the optional buffers described above.
207/// @return Result type wrapping the output parameters to be returned by value.
208///
209/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
210/// <hr>
211/// \include{doc} docs/resize_policy.dox
212template <
213 template <typename...>
214 typename DefaultContainerType,
215 template <typename, template <typename...> typename>
216 typename... Plugins>
217template <typename... Args>
219 using namespace internal;
221 Args,
224 );
225
226 auto send_recv_buf =
227 internal::select_parameter_type<internal::ParameterType::send_recv_buf>(args...).construct_buffer_or_rebind();
228 using send_recv_value_type = typename std::remove_reference_t<decltype(send_recv_buf)>::value_type;
229 auto send_recv_type =
230 internal::determine_mpi_send_recv_datatype<send_recv_value_type, decltype(send_recv_buf)>(args...);
231 [[maybe_unused]] constexpr bool send_recv_type_has_to_be_deduced = has_to_be_computed<decltype(send_recv_type)>;
232
233 // Get the optional recv_count parameter. If the parameter is not given, allocate a new container.
235 auto count_param = internal::select_parameter_type_or_default<ParameterType::send_recv_count, default_count_type>(
236 std::tuple(),
237 args...
238 )
239 .construct_buffer_or_rebind();
240 constexpr bool count_has_to_be_computed = has_to_be_computed<decltype(count_param)>;
241
242 KASSERT(
243 (!count_has_to_be_computed || send_recv_buf.size() % size() == 0lu),
244 "There is no send_recv_count given and the number of elements in send_recv_buf is not divisible by the number "
245 "of "
246 "ranks "
247 "in the communicator.",
249 );
250
251 if constexpr (count_has_to_be_computed) {
252 count_param.underlying() = asserting_cast<int>(send_recv_buf.size() / size());
253 }
254 auto compute_required_recv_buf_size = [&]() {
255 return asserting_cast<size_t>(count_param.get_single_element()) * size();
256 };
258 KASSERT(
259 // if the type is user provided, kamping cannot make any assumptions about the required size of the
260 // buffer
262 "send_recv_buf is not large enough to hold all received elements.",
264 );
265 int err = MPI_Alltoall(
266 MPI_IN_PLACE, // send_buf
267 0, // send_count (ignored)
268 MPI_DATATYPE_NULL, // send_type (ignored)
269 send_recv_buf.data(), // recv_buf
270 count_param.get_single_element(), // recv_count
271 send_recv_type.get_single_element(), // recv_type
272 mpi_communicator() // comm
273 );
274 this->mpi_error_hook(err, "MPI_Alltoall");
275
276 return make_mpi_result<std::tuple<Args...>>(
277 std::move(send_recv_buf),
278 std::move(count_param),
279 std::move(send_recv_type)
280 );
281}
282
283/// @brief Wrapper for \c MPI_Alltoallv.
284///
285/// This wrapper for \c MPI_Alltoallv sends the different amounts of data from each rank to each rank. The following
286/// buffers are required:
287/// - \ref kamping::send_buf() containing the data that is sent to each rank. The size of this buffer has to be at least
288/// the sum of the send_counts argument.
289///
290/// - \ref kamping::send_counts() containing the number of elements to send to each rank.
291///
292/// The following parameters are optional but result in communication overhead if omitted:
293/// - \ref kamping::recv_counts() containing the number of elements to receive from each rank.
294/// This parameter is mandatory if \ref kamping::recv_type() is given.
295///
296/// The following buffers are optional:
297/// - \ref kamping::recv_buf() specifying a buffer for the output. Afterwards, this buffer will contain
298/// the data received as specified for send_buf. A buffer size of at least `max(recv_counts[i] +
299/// recv_displs[i])` for \c i in `[0, communicator size)` elements is required.
300///
301/// - \ref kamping::send_displs() containing the offsets of the messages in send_buf. The `send_counts[i]` elements
302/// starting at `send_buf[send_displs[i]]` will be sent to rank `i`. If omitted, this is calculated as the exclusive
303/// prefix-sum of `send_counts`.
304///
305/// - \ref kamping::recv_displs() containing the offsets of the messages in recv_buf. The `recv_counts[i]` elements
306/// starting at `recv_buf[recv_displs[i]]` will be received from rank `i`. If omitted, this is calculated as the
307/// exclusive prefix-sum of `recv_counts`.
308///
309/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype is
310/// derived automatically based on send_buf's underlying \c value_type.
311///
312/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype is
313/// derived automatically based on recv_buf's underlying \c value_type.
314///
315/// @tparam Args Automatically deduced template parameters.
316/// @param args All required and any number of the optional buffers described above.
317/// @return Result object wrapping the output parameters to be returned by value.
318///
319/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
320/// <hr>
321/// \include{doc} docs/resize_policy.dox
322template <
323 template <typename...>
324 typename DefaultContainerType,
325 template <typename, template <typename...> typename>
326 typename... Plugins>
327template <typename... Args>
329 // Get all parameter objects
331 Args,
334 );
335
336 // Get send_buf
337 auto const& send_buf =
338 internal::select_parameter_type<internal::ParameterType::send_buf>(args...).construct_buffer_or_rebind();
339 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
340 using default_recv_value_type = std::remove_const_t<send_value_type>;
341
342 // Get recv_buf
344 auto recv_buf =
345 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
346 std::tuple(),
347 args...
348 )
350 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
351
352 // Get send/recv types
353 auto [send_type, recv_type] =
354 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
357
358 // Get send_counts
359 auto const& send_counts = internal::select_parameter_type<internal::ParameterType::send_counts>(args...)
361 using send_counts_type = typename std::remove_reference_t<decltype(send_counts)>::value_type;
362 static_assert(std::is_same_v<std::remove_const_t<send_counts_type>, int>, "Send counts must be of type int");
363 static_assert(
365 "Send counts must be given as an input parameter"
366 );
367 KASSERT(send_counts.size() >= this->size(), "Send counts buffer is not large enough.", assert::light);
368
369 // Get recv_counts
371 auto recv_counts =
372 internal::select_parameter_type_or_default<internal::ParameterType::recv_counts, default_recv_counts_type>(
373 std::tuple(),
374 args...
375 )
377 using recv_counts_type = typename std::remove_reference_t<decltype(recv_counts)>::value_type;
378 static_assert(std::is_same_v<std::remove_const_t<recv_counts_type>, int>, "Recv counts must be of type int");
379
380 // Get send_displs
382 auto send_displs =
383 internal::select_parameter_type_or_default<internal::ParameterType::send_displs, default_send_displs_type>(
384 std::tuple(),
385 args...
386 )
388 using send_displs_type = typename std::remove_reference_t<decltype(send_displs)>::value_type;
389 static_assert(std::is_same_v<std::remove_const_t<send_displs_type>, int>, "Send displs must be of type int");
390
391 // Get recv_displs
393 auto recv_displs =
394 internal::select_parameter_type_or_default<internal::ParameterType::recv_displs, default_recv_displs_type>(
395 std::tuple(),
396 args...
397 )
399 using recv_displs_type = typename std::remove_reference_t<decltype(recv_displs)>::value_type;
400 static_assert(std::is_same_v<std::remove_const_t<recv_displs_type>, int>, "Recv displs must be of type int");
401
402 static_assert(!std::is_const_v<recv_value_type>, "The receive buffer must not have a const value_type.");
403
404 // Calculate recv_counts if necessary
406 KASSERT(
407 is_same_on_all_ranks(do_calculate_recv_counts),
408 "Receive counts are given on some ranks and have to be computed on others",
410 );
411 if constexpr (do_calculate_recv_counts) {
412 /// @todo make it possible to test whether this additional communication is skipped
413 recv_counts.resize_if_requested([&]() { return this->size(); });
414 KASSERT(recv_counts.size() >= this->size(), "Recv counts buffer is not large enough.", assert::light);
415 this->alltoall(kamping::send_buf(send_counts.get()), kamping::recv_buf(recv_counts.get()));
416 } else {
417 KASSERT(recv_counts.size() >= this->size(), "Recv counts buffer is not large enough.", assert::light);
418 }
419
420 // Calculate send_displs if necessary
422 KASSERT(
423 is_same_on_all_ranks(do_calculate_send_displs),
424 "Send displacements are given on some ranks and have to be computed on others",
426 );
427
428 if constexpr (do_calculate_send_displs) {
429 send_displs.resize_if_requested([&]() { return this->size(); });
430 KASSERT(send_displs.size() >= this->size(), "Send displs buffer is not large enough.", assert::light);
431 std::exclusive_scan(send_counts.data(), send_counts.data() + this->size(), send_displs.data(), 0);
432 } else {
433 KASSERT(send_displs.size() >= this->size(), "Send displs buffer is not large enough.", assert::light);
434 }
435
436 // Check that send displs and send counts are large enough
437 KASSERT(
438 // if the send type is user provided, kamping cannot make any assumptions about the size of the send
439 // buffer
441 || *(send_counts.data() + this->size() - 1) + // Last element of send_counts
442 *(send_displs.data() + this->size() - 1) // Last element of send_displs
443 <= asserting_cast<int>(send_buf.size()),
445 );
446
447 // Calculate recv_displs if necessary
449 KASSERT(
450 is_same_on_all_ranks(do_calculate_recv_displs),
451 "Receive displacements are given on some ranks and have to be computed on others",
453 );
454 if constexpr (do_calculate_recv_displs) {
455 recv_displs.resize_if_requested([&]() { return this->size(); });
456 KASSERT(recv_displs.size() >= this->size(), "Recv displs buffer is not large enough.", assert::light);
457 std::exclusive_scan(recv_counts.data(), recv_counts.data() + this->size(), recv_displs.data(), 0);
458 } else {
459 KASSERT(recv_displs.size() >= this->size(), "Recv displs buffer is not large enough.", assert::light);
460 }
461
462 auto compute_required_recv_buf_size = [&]() {
463 return compute_required_recv_buf_size_in_vectorized_communication(recv_counts, recv_displs, this->size());
464 };
465
466 recv_buf.resize_if_requested(compute_required_recv_buf_size);
467 KASSERT(
468 // if the recv type is user provided, kamping cannot make any assumptions about the required size of the recv
469 // buffer
471 "Recv buffer is not large enough to hold all received elements.",
473 );
474
475 // Do the actual alltoallv
477 send_buf.data(), // send_buf
478 send_counts.data(), // send_counts
479 send_displs.data(), // send_displs
480 send_type.get_single_element(), // send_type
481 recv_buf.data(), // send_counts
482 recv_counts.data(), // recv_counts
483 recv_displs.data(), // recv_displs
484 recv_type.get_single_element(), // recv_type
485 mpi_communicator() // comm
486 );
487
488 this->mpi_error_hook(err, "MPI_Alltoallv");
489
490 return internal::make_mpi_result<std::tuple<Args...>>(
491 std::move(recv_buf), // recv_buf
492 std::move(recv_counts), // recv_counts
493 std::move(recv_displs), // recv_displs
494 std::move(send_displs), // send_displs
495 std::move(send_type), // send_type
496 std::move(recv_type) // recv_type
497 );
498}
499/// @}
Helper functions that make casts safer.
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
T value_type
The value type.
Definition allocator.hpp:53
constexpr int light
Assertion level for lightweight assertions.
Definition assertion_levels.hpp:13
constexpr int light_communication
Assertions that perform lightweight communication.
Definition assertion_levels.hpp:25
auto alltoall(Args... args) const
Wrapper for MPI_Alltoall.
Definition alltoall.hpp:79
auto alltoall_inplace(Args... args) const
Wrapper for the in-place version of Communicator::alltoall.
Definition alltoall.hpp:218
auto alltoallv(Args... args) const
Wrapper for MPI_Alltoallv.
Definition alltoall.hpp:328
static constexpr auto alloc_new
Convenience wrapper for creating library allocated containers. See AllocNewT for details.
Definition data_buffer.hpp:194
auto recv_displs_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:804
auto recv_displs(Container &&container)
Passes a container as receive displacements to the underlying call, i.e. the container's storage must...
Definition named_parameters.hpp:699
auto send_displs(Container &&container)
Passes a container as send displacements to the underlying call, i.e. the container's storage must co...
Definition named_parameters.hpp:576
auto send_counts(Container &&container)
Passes a container as send counts to the underlying call, i.e. the container's storage must contain t...
Definition named_parameters.hpp:205
auto send_recv_buf(Data &&data)
Passes a container/single value as a send or receive buffer to the underlying MPI call.
Definition named_parameters.hpp:139
auto send_count(int count)
Passes count as send count to the underlying call.
Definition named_parameters.hpp:323
auto send_displs_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:681
auto send_recv_count_out()
Indicates to deduce the send/recv count and return it to the caller as part of the underlying call's ...
Definition named_parameters.hpp:557
auto recv_count(int count)
Passes count as recv count to the underlying call.
Definition named_parameters.hpp:492
auto send_recv_type(MPI_Datatype send_recv_type)
Passes send_recv_type as send/recv type to the underlying call. (This parameter is in MPI routines su...
Definition named_parameters.hpp:1346
auto recv_buf(Container &&container)
Passes a container, into which the received elements will be written, to the underlying call....
Definition named_parameters.hpp:861
auto recv_counts_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:480
auto recv_type(MPI_Datatype recv_type)
Passes recv_type as recv type to the underlying call.
Definition named_parameters.hpp:1302
auto recv_counts(Container &&container)
Passes a container as recv counts to the underlying call, i.e. the container's storage must contain t...
Definition named_parameters.hpp:368
auto send_type(MPI_Datatype send_type)
Passes send_type as send type to the underlying call.
Definition named_parameters.hpp:1259
auto send_count_out()
Indicates to deduce the send count and return it to the caller as part of the underlying call's resul...
Definition named_parameters.hpp:349
auto send_recv_count(int count)
Passes count as send/recv count to the underlying call.
Definition named_parameters.hpp:531
auto recv_count_out()
Indicates to deduce the recv count and return it to the caller as part of the underlying call's resul...
Definition named_parameters.hpp:518
auto send_buf(internal::ignore_t< Data > ignore)
Generates a dummy send buf that wraps a nullptr.
Definition named_parameters.hpp:53
constexpr bool has_parameter_type()
Checks if parameter with requested parameter type exists.
Definition named_parameter_selection.hpp:186
Template magic to check named parameters passed to wrappers at compile time.
#define KAMPING_REQUIRED_PARAMETERS(...)
Wrapper to pass (possibly empty) list of parameter type names as required parameters to KAMPING_CHECK...
Definition named_parameter_check.hpp:52
#define KAMPING_OPTIONAL_PARAMETERS(...)
Wrapper to pass (possibly empty) list of parameter type names as optional parameters to KAMPING_CHECK...
Definition named_parameter_check.hpp:58
#define KAMPING_CHECK_PARAMETERS(args, required, optional)
Assertion macro that checks if passed parameters are correct, i.e., all parameter types are unique,...
Definition named_parameter_check.hpp:80
Template magic to implement named parameters in cpp.
Factory methods for buffer wrappers.
auto make_mpi_result(Buffers &&... buffers)
Construct result object for a wrapped MPI call. Four different cases are handled: a) The recv_buffer ...
Definition result.hpp:1017
static constexpr bool has_to_be_computed
Checks if the buffer has to be computed by kamping, i.e. if it is an output parameter or the buffer h...
Definition named_parameter_check.hpp:398
Some functions and types simplifying/enabling the development of wrapped MPI calls in KaMPIng.