core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
grid_halo.hpp
Go to the documentation of this file.
1// core — portable (Kokkos) GPU-resident ghost-layer exchange.
2//
3// Portable (Kokkos) GPU-resident grid halo: the field lives on the device as a
4// peclet::core::View<T>; pack (gather send cells), unpack (scatter into ghost cells) and the
5// periodic self-copy run as Kokkos::parallel_for on the default execution space (CUDA / HIP /
6// OpenMP), so the full field never crosses the bus — only the compact halo buffers are staged to
7// the host for MPI. The GPU-aware-MPI path (hand device pointers straight to MPI, dropping both
8// staging copies) is the DEFAULT on device builds when a loopback probe confirms MPI accepts
9// device pointers; env PECLET_CORE_GPU_AWARE_MPI=0 forces host staging (kill switch for stacks
10// that segfault instead of erroring), =1 forces the device path without probing (legacy
11// PECLET_CORE_CUDA_AWARE_MPI honoured). exchangeBegin/exchangeEnd expose the split so callers can
12// overlap interior compute with the messages in flight; exchange() is the blocking composition.
13// Topology comes from a host-built GridHaloTopology<Dim>::flatten(), and the result is bit-for-bit
14// identical to the CPU exchange.
15#ifndef PECLET_CORE_HALO_GRID_HALO_HPP
16#define PECLET_CORE_HALO_GRID_HALO_HPP
17
18#include <cstdio>
19#include <cstdlib>
20#include <string>
21#include <type_traits>
22#include <vector>
23
25#if __has_include(<mpi-ext.h>)
26#include <mpi-ext.h> // OpenMPI: MPIX_CUDA_AWARE_SUPPORT / MPIX_Query_cuda_support
27#endif
31
33
34namespace detail {
39inline bool mpiReportsCudaAware() {
40#if defined(MPIX_CUDA_AWARE_SUPPORT)
41#if MPIX_CUDA_AWARE_SUPPORT
42 return MPIX_Query_cuda_support() == 1;
43#else
44 return false;
45#endif
46#else
47 return false; // no query available: no signal
48#endif
49}
50
58inline bool probeGpuAwareMpi() {
59#ifdef KOKKOS_ENABLE_SERIAL
60 if (std::is_same_v<ExecSpace, Kokkos::Serial>)
61 return false; // host backend: device pointers are host pointers; staging copies alias anyway
62#endif
63#ifdef KOKKOS_ENABLE_OPENMP
64 if (std::is_same_v<ExecSpace, Kokkos::OpenMP>)
65 return false;
66#endif
67#ifdef KOKKOS_ENABLE_THREADS
68 if (std::is_same_v<ExecSpace, Kokkos::Threads>)
69 return false; // host backend, same as the two above — a wheel without a usable OpenMP gets
70 // this
71#endif
72 int inited = 0;
74 if (!inited)
75 return false;
78 return false;
80 constexpr int N = 64;
81 View<int> src(Kokkos::view_alloc("peclet::core::halo::probeSrc", Kokkos::WithoutInitializing), N);
82 View<int> dst("peclet::core::halo::probeDst", N);
83 Kokkos::parallel_for(
84 "peclet::core::halo::probeFill", Kokkos::RangePolicy<ExecSpace>(0, N),
85 KOKKOS_LAMBDA(const int i) { src(i) = 0x5EC0DE + i * 7919; });
86 Kokkos::fence();
87 const int rc = MPI_Sendrecv(src.data(), N, MPI_INT, 0, 77, dst.data(), N, MPI_INT, 0, 77, self,
90 if (rc != MPI_SUCCESS)
91 return false;
92 int bad = 1;
93 Kokkos::parallel_reduce(
94 "peclet::core::halo::probeCheck", Kokkos::RangePolicy<ExecSpace>(0, N),
95 KOKKOS_LAMBDA(const int i, int& b) {
96 if (dst(i) != 0x5EC0DE + i * 7919)
97 b += 1;
98 },
99 bad);
100 return bad == 0;
101}
102
111inline bool gpuAwareMpi() {
112 static const bool v = [] {
113 const char* e = std::getenv("PECLET_CORE_GPU_AWARE_MPI");
114 if (!e)
115 e = std::getenv("PECLET_CORE_CUDA_AWARE_MPI");
116 const bool aware = e ? (std::atoi(e) != 0) : (mpiReportsCudaAware() && probeGpuAwareMpi());
117 if (std::getenv("PECLET_CORE_HALO_VERBOSE"))
118 std::fprintf(stderr, "peclet.core halo: %s MPI buffers (%s)\n",
119 aware ? "DEVICE" : "host-staged", e ? "env-forced" : "auto");
120 return aware;
121 }();
122 return v;
123}
124
127inline double haloTimeout() {
128 static const double v = [] {
129 const char* e = std::getenv("PECLET_CORE_HALO_TIMEOUT");
130 return e ? std::atof(e) : 0.0;
131 }();
132 return v;
133}
134} // namespace detail
135
139template <class T>
140class GridHalo {
141 public:
142 GridHalo() = default;
143 GridHalo(const GridHalo&) = delete;
144 GridHalo& operator=(const GridHalo&) = delete;
145
146 template <int Dim>
147 void init(const GridHaloTopology<Dim>& halo) {
148 auto t = halo.flatten();
149 comm_ = t.comm;
150 sendRanks_ = t.sendRanks;
151 recvRanks_ = t.recvRanks;
152 sendCounts_ = t.sendCounts;
153 recvCounts_ = t.recvCounts;
154
155 sendOff_.assign(sendCounts_.size() + 1, 0);
156 for (std::size_t k = 0; k < sendCounts_.size(); ++k)
157 sendOff_[k + 1] = sendOff_[k] + sendCounts_[k];
158 recvOff_.assign(recvCounts_.size() + 1, 0);
159 for (std::size_t k = 0; k < recvCounts_.size(); ++k)
160 recvOff_[k + 1] = recvOff_[k] + recvCounts_[k];
161
162 nSend_ = static_cast<Index>(t.sendIdx.size());
163 nRecv_ = static_cast<Index>(t.recvIdx.size());
164 nSelf_ = static_cast<Index>(t.selfSrc.size());
165
166 d_sendIdx_ = toDevice(t.sendIdx, "peclet::core::halo::sendIdx");
167 d_recvIdx_ = toDevice(t.recvIdx, "peclet::core::halo::recvIdx");
168 d_selfSrc_ = toDevice(t.selfSrc, "peclet::core::halo::selfSrc");
169 d_selfDst_ = toDevice(t.selfDst, "peclet::core::halo::selfDst");
170 d_sendBuf_ =
171 View<T>(Kokkos::view_alloc("peclet::core::halo::sendBuf", Kokkos::WithoutInitializing),
172 static_cast<std::size_t>(nSend_));
173 d_recvBuf_ =
174 View<T>(Kokkos::view_alloc("peclet::core::halo::recvBuf", Kokkos::WithoutInitializing),
175 static_cast<std::size_t>(nRecv_));
176 h_sendBuf_ = Kokkos::create_mirror_view(d_sendBuf_);
177 h_recvBuf_ = Kokkos::create_mirror_view(d_recvBuf_);
178 }
179
182 void setLabel(std::string label) { label_ = std::move(label); }
183
185 void exchange(const View<T>& field, int tag = 0) {
188 }
189
194 void exchangeBegin(const View<T>& field, int tag = 0) {
195 const bool aware = detail::gpuAwareMpi();
196 // Periodic copy within our own block (read inner cell, write ghost cell).
197 if (nSelf_) {
198 View<T> f = field;
199 IndexView src = d_selfSrc_, dst = d_selfDst_;
200 Kokkos::parallel_for(
201 "peclet::core::halo::selfCopy", Kokkos::RangePolicy<ExecSpace>(0, nSelf_),
202 KOKKOS_LAMBDA(const Index i) { f(dst(i)) = f(src(i)); });
203 }
204 // Gather the cells we send into the contiguous send buffer.
205 if (nSend_) {
206 View<T> f = field;
207 IndexView idx = d_sendIdx_;
208 View<T> buf = d_sendBuf_;
209 Kokkos::parallel_for(
210 "peclet::core::halo::pack", Kokkos::RangePolicy<ExecSpace>(0, nSend_),
211 KOKKOS_LAMBDA(const Index i) { buf(i) = f(idx(i)); });
212 if (!aware)
213 Kokkos::deep_copy(h_sendBuf_, d_sendBuf_);
214 }
215 // The send buffer (host-staged, or device for the aware path) must be ready before MPI reads
216 // it.
217 Kokkos::fence();
218
219 T* sendBase = aware ? d_sendBuf_.data() : h_sendBuf_.data();
220 T* recvBase = aware ? d_recvBuf_.data() : h_recvBuf_.data();
221 reqs_.clear();
222 reqs_.reserve(recvRanks_.size() + sendRanks_.size());
223 for (std::size_t k = 0; k < recvRanks_.size(); ++k) {
224 reqs_.emplace_back();
225 MPI_Irecv(recvBase + recvOff_[k], recvCounts_[k] * static_cast<int>(sizeof(T)), MPI_BYTE,
226 recvRanks_[k], tag, comm_, &reqs_.back());
227 }
228 for (std::size_t k = 0; k < sendRanks_.size(); ++k) {
229 reqs_.emplace_back();
230 MPI_Isend(sendBase + sendOff_[k], sendCounts_[k] * static_cast<int>(sizeof(T)), MPI_BYTE,
231 sendRanks_[k], tag, comm_, &reqs_.back());
232 }
233 inFlightAware_ = aware;
234 }
235
238 void exchangeEnd(const View<T>& field) {
239 if (!reqs_.empty()) {
240 if (detail::haloTimeout() > 0)
241 waitAllDiagnosed(detail::haloTimeout());
242 else
243 MPI_Waitall(static_cast<int>(reqs_.size()), reqs_.data(), MPI_STATUSES_IGNORE);
244 }
245 reqs_.clear();
246 if (nRecv_) {
247 if (!inFlightAware_)
248 Kokkos::deep_copy(d_recvBuf_, h_recvBuf_);
249 View<T> f = field;
250 IndexView idx = d_recvIdx_;
251 View<T> buf = d_recvBuf_;
252 Kokkos::parallel_for(
253 "peclet::core::halo::unpack", Kokkos::RangePolicy<ExecSpace>(0, nRecv_),
254 KOKKOS_LAMBDA(const Index i) { f(idx(i)) = buf(i); });
255 }
256 Kokkos::fence();
257 }
258
259 private:
260 MPI_Comm comm_ = MPI_COMM_NULL;
261 std::vector<int> sendRanks_, recvRanks_, sendCounts_, recvCounts_;
262 std::vector<int> sendOff_, recvOff_;
263 std::vector<MPI_Request> reqs_; // in-flight requests between exchangeBegin and exchangeEnd
264 bool inFlightAware_ = false; // staging mode of the in-flight exchange
265 Index nSend_ = 0, nRecv_ = 0, nSelf_ = 0;
266 std::string label_;
267 IndexView d_sendIdx_, d_recvIdx_, d_selfSrc_, d_selfDst_;
268
273 void waitAllDiagnosed(double timeout) {
274 const double t0 = MPI_Wtime();
275 const int n = static_cast<int>(reqs_.size());
276 int done = 0;
277 while (!done) {
278 MPI_Testall(n, reqs_.data(), &done, MPI_STATUSES_IGNORE);
279 if (done || MPI_Wtime() - t0 < timeout)
280 continue;
281 int rank = -1, size = -1, wrank = -1, wsize = -1;
282 MPI_Comm_rank(comm_, &rank);
283 MPI_Comm_size(comm_, &size);
286 MPI_Group g, wg;
287 MPI_Comm_group(comm_, &g);
289 auto world = [&](int r) {
290 int w = -1;
291 MPI_Group_translate_ranks(g, 1, &r, wg, &w);
292 return w;
293 };
294 std::string msg = "peclet.core halo TIMEOUT after " + std::to_string(int(timeout)) + " s [" +
295 label_ + "] world " + std::to_string(wrank) + "/" + std::to_string(wsize) +
296 " comm " + std::to_string(rank) + "/" + std::to_string(size) + ": " +
297 std::to_string(recvRanks_.size()) + " recv + " +
298 std::to_string(sendRanks_.size()) + " send partners\n";
299 const std::size_t nr = recvRanks_.size();
300 for (std::size_t k = 0; k < reqs_.size(); ++k) {
301 int flag = 0;
303 if (flag)
304 continue;
305 const bool recv = k < nr;
306 const int q = recv ? recvRanks_[k] : sendRanks_[k - nr];
307 const int c = recv ? recvCounts_[k] : sendCounts_[k - nr];
308 msg += std::string(" pending ") + (recv ? "RECV from" : "SEND to ") + " comm " +
309 std::to_string(q) + " (world " + std::to_string(world(q)) + ") " +
310 std::to_string(c * static_cast<long>(sizeof(T))) + " bytes\n";
311 }
312 std::fputs(msg.c_str(), stderr);
313 std::fflush(stderr);
314 MPI_Group_free(&g);
317 }
318 }
319 View<T> d_sendBuf_, d_recvBuf_;
320 HostView<T> h_sendBuf_, h_recvBuf_;
321};
322
323} // namespace peclet::core::halo
324
325#endif // PECLET_CORE_HALO_GRID_HALO_HPP
GPU ghost-layer exchange for a contiguous device field peclet::core::View<T> (one element per extende...
void exchangeEnd(const View< T > &field)
Complete the exchange started by exchangeBegin: wait, then scatter the received halo cells into field...
GridHalo & operator=(const GridHalo &)=delete
void exchange(const View< T > &field, int tag=0)
Exchange ghost layers of the device field field. Blocking (== exchangeBegin+exchangeEnd).
GridHalo(const GridHalo &)=delete
void setLabel(std::string label)
Name printed by the PECLET_CORE_HALO_TIMEOUT diagnostic (e.g.
void init(const GridHaloTopology< Dim > &halo)
void exchangeBegin(const View< T > &field, int tag=0)
Start an exchange: periodic self-copy, device pack, post Isend/Irecv, return with the messages in fli...
#define MPI_SUCCESS
Definition mpi_stub.hpp:36
int MPI_Comm_size(MPI_Comm, int *s)
Definition mpi_stub.hpp:70
#define MPI_COMM_NULL
Definition mpi_stub.hpp:28
int MPI_Testall(int, MPI_Request *, int *flag, MPI_Status *)
Definition mpi_stub.hpp:153
int MPI_Comm
Definition mpi_stub.hpp:17
#define MPI_COMM_WORLD
Definition mpi_stub.hpp:27
int MPI_Waitall(int, MPI_Request *, MPI_Status *)
Definition mpi_stub.hpp:160
#define MPI_INT
Definition mpi_stub.hpp:39
int MPI_Comm_free(MPI_Comm *)
Definition mpi_stub.hpp:107
#define MPI_STATUS_IGNORE
Definition mpi_stub.hpp:31
#define MPI_BYTE
Definition mpi_stub.hpp:38
#define MPI_STATUSES_IGNORE
Definition mpi_stub.hpp:32
int MPI_Isend(const void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:132
int MPI_Abort(MPI_Comm, int code)
Definition mpi_stub.hpp:62
int MPI_Comm_rank(MPI_Comm, int *r)
Definition mpi_stub.hpp:66
int MPI_Initialized(int *f)
Definition mpi_stub.hpp:51
int MPI_Irecv(void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:135
bool mpiReportsCudaAware()
Does the MPI library itself claim CUDA awareness (OpenMPI's MPIX_Query_cuda_support)?...
Definition grid_halo.hpp:39
double haloTimeout()
PECLET_CORE_HALO_TIMEOUT=<seconds>: turn every exchangeEnd into a deadline wait that names the pendin...
bool gpuAwareMpi()
Whether to hand DEVICE pointers straight to MPI (GPU-aware MPI) instead of host-staging.
bool probeGpuAwareMpi()
Probe whether MPI actually accepts DEVICE pointers (GPU-aware MPI): checksummed loopback Sendrecv on ...
Definition grid_halo.hpp:58
View< T > toDevice(const std::vector< T > &h, const std::string &label)
Upload a host std::vector into a freshly-sized device View (empty vector => empty view).
Definition view.hpp:44
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
Kokkos::View< Index *, MemSpace > IndexView
Device array of grid/particle indices (the matched send/recv/self-copy lists).
Definition view.hpp:34
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15