core 0.5.0
Shared MPI block decomposition + asynchronous ghost-layer exchange (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 <type_traits>
21#include <vector>
22
24#if __has_include(<mpi-ext.h>)
25#include <mpi-ext.h> // OpenMPI: MPIX_CUDA_AWARE_SUPPORT / MPIX_Query_cuda_support
26#endif
30
32
33namespace detail {
38inline bool mpiReportsCudaAware() {
39#if defined(MPIX_CUDA_AWARE_SUPPORT)
40#if MPIX_CUDA_AWARE_SUPPORT
41 return MPIX_Query_cuda_support() == 1;
42#else
43 return false;
44#endif
45#else
46 return false; // no query available: no signal
47#endif
48}
49
57inline bool probeGpuAwareMpi() {
58#ifdef KOKKOS_ENABLE_SERIAL
59 if (std::is_same_v<ExecSpace, Kokkos::Serial>)
60 return false; // host backend: device pointers are host pointers; staging copies alias anyway
61#endif
62#ifdef KOKKOS_ENABLE_OPENMP
63 if (std::is_same_v<ExecSpace, Kokkos::OpenMP>)
64 return false;
65#endif
66 int inited = 0;
67 MPI_Initialized(&inited);
68 if (!inited)
69 return false;
70 MPI_Comm self;
71 if (MPI_Comm_dup(MPI_COMM_SELF, &self) != MPI_SUCCESS)
72 return false;
73 MPI_Comm_set_errhandler(self, MPI_ERRORS_RETURN);
74 constexpr int N = 64;
75 View<int> src(Kokkos::view_alloc("peclet::core::halo::probeSrc", Kokkos::WithoutInitializing),
76 N);
77 View<int> dst("peclet::core::halo::probeDst", N);
78 Kokkos::parallel_for(
79 "peclet::core::halo::probeFill", Kokkos::RangePolicy<ExecSpace>(0, N),
80 KOKKOS_LAMBDA(const int i) { src(i) = 0x5EC0DE + i * 7919; });
81 Kokkos::fence();
82 const int rc = MPI_Sendrecv(src.data(), N, MPI_INT, 0, 77, dst.data(), N, MPI_INT, 0, 77, self,
84 MPI_Comm_free(&self);
85 if (rc != MPI_SUCCESS)
86 return false;
87 int bad = 1;
88 Kokkos::parallel_reduce(
89 "peclet::core::halo::probeCheck", Kokkos::RangePolicy<ExecSpace>(0, N),
90 KOKKOS_LAMBDA(const int i, int& b) {
91 if (dst(i) != 0x5EC0DE + i * 7919)
92 b += 1;
93 },
94 bad);
95 return bad == 0;
96}
97
106inline bool gpuAwareMpi() {
107 static const bool v = [] {
108 const char* e = std::getenv("PECLET_CORE_GPU_AWARE_MPI");
109 if (!e)
110 e = std::getenv("PECLET_CORE_CUDA_AWARE_MPI");
111 const bool aware = e ? (std::atoi(e) != 0) : (mpiReportsCudaAware() && probeGpuAwareMpi());
112 if (std::getenv("PECLET_CORE_HALO_VERBOSE"))
113 std::fprintf(stderr, "peclet.core halo: %s MPI buffers (%s)\n",
114 aware ? "DEVICE" : "host-staged", e ? "env-forced" : "auto");
115 return aware;
116 }();
117 return v;
118}
119} // namespace detail
120
124template <class T>
125class GridHalo {
126 public:
127 GridHalo() = default;
128 GridHalo(const GridHalo&) = delete;
129 GridHalo& operator=(const GridHalo&) = delete;
130
131 template <int Dim>
132 void init(const GridHaloTopology<Dim>& halo) {
133 auto t = halo.flatten();
134 comm_ = t.comm;
135 sendRanks_ = t.sendRanks;
136 recvRanks_ = t.recvRanks;
137 sendCounts_ = t.sendCounts;
138 recvCounts_ = t.recvCounts;
139
140 sendOff_.assign(sendCounts_.size() + 1, 0);
141 for (std::size_t k = 0; k < sendCounts_.size(); ++k)
142 sendOff_[k + 1] = sendOff_[k] + sendCounts_[k];
143 recvOff_.assign(recvCounts_.size() + 1, 0);
144 for (std::size_t k = 0; k < recvCounts_.size(); ++k)
145 recvOff_[k + 1] = recvOff_[k] + recvCounts_[k];
146
147 nSend_ = static_cast<Index>(t.sendIdx.size());
148 nRecv_ = static_cast<Index>(t.recvIdx.size());
149 nSelf_ = static_cast<Index>(t.selfSrc.size());
150
151 d_sendIdx_ = toDevice(t.sendIdx, "peclet::core::halo::sendIdx");
152 d_recvIdx_ = toDevice(t.recvIdx, "peclet::core::halo::recvIdx");
153 d_selfSrc_ = toDevice(t.selfSrc, "peclet::core::halo::selfSrc");
154 d_selfDst_ = toDevice(t.selfDst, "peclet::core::halo::selfDst");
155 d_sendBuf_ =
156 View<T>(Kokkos::view_alloc("peclet::core::halo::sendBuf", Kokkos::WithoutInitializing),
157 static_cast<std::size_t>(nSend_));
158 d_recvBuf_ =
159 View<T>(Kokkos::view_alloc("peclet::core::halo::recvBuf", Kokkos::WithoutInitializing),
160 static_cast<std::size_t>(nRecv_));
161 h_sendBuf_ = Kokkos::create_mirror_view(d_sendBuf_);
162 h_recvBuf_ = Kokkos::create_mirror_view(d_recvBuf_);
163 }
164
166 void exchange(const View<T>& field, int tag = 0) {
167 exchangeBegin(field, tag);
168 exchangeEnd(field);
169 }
170
175 void exchangeBegin(const View<T>& field, int tag = 0) {
176 const bool aware = detail::gpuAwareMpi();
177 // Periodic copy within our own block (read inner cell, write ghost cell).
178 if (nSelf_) {
179 View<T> f = field;
180 IndexView src = d_selfSrc_, dst = d_selfDst_;
181 Kokkos::parallel_for(
182 "peclet::core::halo::selfCopy", Kokkos::RangePolicy<ExecSpace>(0, nSelf_),
183 KOKKOS_LAMBDA(const Index i) { f(dst(i)) = f(src(i)); });
184 }
185 // Gather the cells we send into the contiguous send buffer.
186 if (nSend_) {
187 View<T> f = field;
188 IndexView idx = d_sendIdx_;
189 View<T> buf = d_sendBuf_;
190 Kokkos::parallel_for(
191 "peclet::core::halo::pack", Kokkos::RangePolicy<ExecSpace>(0, nSend_),
192 KOKKOS_LAMBDA(const Index i) { buf(i) = f(idx(i)); });
193 if (!aware)
194 Kokkos::deep_copy(h_sendBuf_, d_sendBuf_);
195 }
196 // The send buffer (host-staged, or device for the aware path) must be ready before MPI reads
197 // it.
198 Kokkos::fence();
199
200 T* sendBase = aware ? d_sendBuf_.data() : h_sendBuf_.data();
201 T* recvBase = aware ? d_recvBuf_.data() : h_recvBuf_.data();
202 reqs_.clear();
203 reqs_.reserve(recvRanks_.size() + sendRanks_.size());
204 for (std::size_t k = 0; k < recvRanks_.size(); ++k) {
205 reqs_.emplace_back();
206 MPI_Irecv(recvBase + recvOff_[k], recvCounts_[k] * static_cast<int>(sizeof(T)), MPI_BYTE,
207 recvRanks_[k], tag, comm_, &reqs_.back());
208 }
209 for (std::size_t k = 0; k < sendRanks_.size(); ++k) {
210 reqs_.emplace_back();
211 MPI_Isend(sendBase + sendOff_[k], sendCounts_[k] * static_cast<int>(sizeof(T)), MPI_BYTE,
212 sendRanks_[k], tag, comm_, &reqs_.back());
213 }
214 inFlightAware_ = aware;
215 }
216
219 void exchangeEnd(const View<T>& field) {
220 if (!reqs_.empty())
221 MPI_Waitall(static_cast<int>(reqs_.size()), reqs_.data(), MPI_STATUSES_IGNORE);
222 reqs_.clear();
223 if (nRecv_) {
224 if (!inFlightAware_)
225 Kokkos::deep_copy(d_recvBuf_, h_recvBuf_);
226 View<T> f = field;
227 IndexView idx = d_recvIdx_;
228 View<T> buf = d_recvBuf_;
229 Kokkos::parallel_for(
230 "peclet::core::halo::unpack", Kokkos::RangePolicy<ExecSpace>(0, nRecv_),
231 KOKKOS_LAMBDA(const Index i) { f(idx(i)) = buf(i); });
232 }
233 Kokkos::fence();
234 }
235
236 private:
237 MPI_Comm comm_ = MPI_COMM_NULL;
238 std::vector<int> sendRanks_, recvRanks_, sendCounts_, recvCounts_;
239 std::vector<int> sendOff_, recvOff_;
240 std::vector<MPI_Request> reqs_; // in-flight requests between exchangeBegin and exchangeEnd
241 bool inFlightAware_ = false; // staging mode of the in-flight exchange
242 Index nSend_ = 0, nRecv_ = 0, nSelf_ = 0;
243 IndexView d_sendIdx_, d_recvIdx_, d_selfSrc_, d_selfDst_;
244 View<T> d_sendBuf_, d_recvBuf_;
245 HostView<T> h_sendBuf_, h_recvBuf_;
246};
247
248} // namespace peclet::core::halo
249
250#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 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:35
#define MPI_COMM_NULL
Definition mpi_stub.hpp:27
int MPI_Comm
Definition mpi_stub.hpp:16
int MPI_Waitall(int, MPI_Request *, MPI_Status *)
Definition mpi_stub.hpp:126
#define MPI_INT
Definition mpi_stub.hpp:38
int MPI_Comm_free(MPI_Comm *)
Definition mpi_stub.hpp:73
#define MPI_STATUS_IGNORE
Definition mpi_stub.hpp:30
#define MPI_BYTE
Definition mpi_stub.hpp:37
#define MPI_STATUSES_IGNORE
Definition mpi_stub.hpp:31
int MPI_Isend(const void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:98
int MPI_Initialized(int *f)
Definition mpi_stub.hpp:50
int MPI_Irecv(void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:101
bool mpiReportsCudaAware()
Does the MPI library itself claim CUDA awareness (OpenMPI's MPIX_Query_cuda_support)?...
Definition grid_halo.hpp:38
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:57
decltype(Kokkos::create_mirror_view(std::declval< View< T > >())) HostView
Host-accessible mirror of View<T> (identical to View<T> on host backends).
Definition view.hpp:31
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