core
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. A GPU-aware-MPI path (hand device pointers straight to MPI) is opt-in via the
8// env var PECLET_CORE_GPU_AWARE_MPI (the legacy PECLET_CORE_CUDA_AWARE_MPI is still honoured);
9// host-staging is the portable default. Topology comes from a host-built
10// GridHaloTopology<Dim>::flatten(), exactly like the CUDA version, and the result is bit-for-bit
11// identical to the CPU exchange.
12#ifndef PECLET_CORE_HALO_GRID_HALO_HPP
13#define PECLET_CORE_HALO_GRID_HALO_HPP
14
15#include <cstdlib>
16#include <vector>
17
22
24
25namespace detail {
29inline bool gpuAwareMpi() {
30 static const bool v = [] {
31 const char* e = std::getenv("PECLET_CORE_GPU_AWARE_MPI");
32 if (!e)
33 e = std::getenv("PECLET_CORE_CUDA_AWARE_MPI");
34 return e && std::atoi(e) != 0;
35 }();
36 return v;
37}
38} // namespace detail
39
43template <class T>
44class GridHalo {
45 public:
46 GridHalo() = default;
47 GridHalo(const GridHalo&) = delete;
48 GridHalo& operator=(const GridHalo&) = delete;
49
50 template <int Dim>
51 void init(const GridHaloTopology<Dim>& halo) {
52 auto t = halo.flatten();
53 comm_ = t.comm;
54 sendRanks_ = t.sendRanks;
55 recvRanks_ = t.recvRanks;
56 sendCounts_ = t.sendCounts;
57 recvCounts_ = t.recvCounts;
58
59 sendOff_.assign(sendCounts_.size() + 1, 0);
60 for (std::size_t k = 0; k < sendCounts_.size(); ++k)
61 sendOff_[k + 1] = sendOff_[k] + sendCounts_[k];
62 recvOff_.assign(recvCounts_.size() + 1, 0);
63 for (std::size_t k = 0; k < recvCounts_.size(); ++k)
64 recvOff_[k + 1] = recvOff_[k] + recvCounts_[k];
65
66 nSend_ = static_cast<Index>(t.sendIdx.size());
67 nRecv_ = static_cast<Index>(t.recvIdx.size());
68 nSelf_ = static_cast<Index>(t.selfSrc.size());
69
70 d_sendIdx_ = toDevice(t.sendIdx, "peclet::core::halo::sendIdx");
71 d_recvIdx_ = toDevice(t.recvIdx, "peclet::core::halo::recvIdx");
72 d_selfSrc_ = toDevice(t.selfSrc, "peclet::core::halo::selfSrc");
73 d_selfDst_ = toDevice(t.selfDst, "peclet::core::halo::selfDst");
74 d_sendBuf_ =
75 View<T>(Kokkos::view_alloc("peclet::core::halo::sendBuf", Kokkos::WithoutInitializing),
76 static_cast<std::size_t>(nSend_));
77 d_recvBuf_ =
78 View<T>(Kokkos::view_alloc("peclet::core::halo::recvBuf", Kokkos::WithoutInitializing),
79 static_cast<std::size_t>(nRecv_));
80 h_sendBuf_ = Kokkos::create_mirror_view(d_sendBuf_);
81 h_recvBuf_ = Kokkos::create_mirror_view(d_recvBuf_);
82 }
83
85 void exchange(const View<T>& field, int tag = 0) {
86 const bool aware = detail::gpuAwareMpi();
87
88 // Periodic copy within our own block (read inner cell, write ghost cell).
89 if (nSelf_) {
90 View<T> f = field;
91 IndexView src = d_selfSrc_, dst = d_selfDst_;
92 Kokkos::parallel_for(
93 "peclet::core::halo::selfCopy", Kokkos::RangePolicy<ExecSpace>(0, nSelf_),
94 KOKKOS_LAMBDA(const Index i) { f(dst(i)) = f(src(i)); });
95 }
96 // Gather the cells we send into the contiguous send buffer.
97 if (nSend_) {
98 View<T> f = field;
99 IndexView idx = d_sendIdx_;
100 View<T> buf = d_sendBuf_;
101 Kokkos::parallel_for(
102 "peclet::core::halo::pack", Kokkos::RangePolicy<ExecSpace>(0, nSend_),
103 KOKKOS_LAMBDA(const Index i) { buf(i) = f(idx(i)); });
104 if (!aware)
105 Kokkos::deep_copy(h_sendBuf_, d_sendBuf_);
106 }
107 // The send buffer (host-staged, or device for the aware path) must be ready before MPI reads
108 // it.
109 Kokkos::fence();
110
111 T* sendBase = aware ? d_sendBuf_.data() : h_sendBuf_.data();
112 T* recvBase = aware ? d_recvBuf_.data() : h_recvBuf_.data();
113 std::vector<MPI_Request> reqs;
114 reqs.reserve(recvRanks_.size() + sendRanks_.size());
115 for (std::size_t k = 0; k < recvRanks_.size(); ++k) {
116 reqs.emplace_back();
117 MPI_Irecv(recvBase + recvOff_[k], recvCounts_[k] * static_cast<int>(sizeof(T)), MPI_BYTE,
118 recvRanks_[k], tag, comm_, &reqs.back());
119 }
120 for (std::size_t k = 0; k < sendRanks_.size(); ++k) {
121 reqs.emplace_back();
122 MPI_Isend(sendBase + sendOff_[k], sendCounts_[k] * static_cast<int>(sizeof(T)), MPI_BYTE,
123 sendRanks_[k], tag, comm_, &reqs.back());
124 }
125 if (!reqs.empty())
126 MPI_Waitall(static_cast<int>(reqs.size()), reqs.data(), MPI_STATUSES_IGNORE);
127
128 // Scatter received halo cells back into the field's ghost region.
129 if (nRecv_) {
130 if (!aware)
131 Kokkos::deep_copy(d_recvBuf_, h_recvBuf_);
132 View<T> f = field;
133 IndexView idx = d_recvIdx_;
134 View<T> buf = d_recvBuf_;
135 Kokkos::parallel_for(
136 "peclet::core::halo::unpack", Kokkos::RangePolicy<ExecSpace>(0, nRecv_),
137 KOKKOS_LAMBDA(const Index i) { f(idx(i)) = buf(i); });
138 }
139 Kokkos::fence();
140 }
141
142 private:
143 MPI_Comm comm_ = MPI_COMM_NULL;
144 std::vector<int> sendRanks_, recvRanks_, sendCounts_, recvCounts_;
145 std::vector<int> sendOff_, recvOff_;
146 Index nSend_ = 0, nRecv_ = 0, nSelf_ = 0;
147 IndexView d_sendIdx_, d_recvIdx_, d_selfSrc_, d_selfDst_;
148 View<T> d_sendBuf_, d_recvBuf_;
149 HostView<T> h_sendBuf_, h_recvBuf_;
150};
151
152} // namespace peclet::core::halo
153
154#endif // PECLET_CORE_HALO_GRID_HALO_HPP
GPU ghost-layer exchange for a contiguous device field peclet::core::View<T> (one element per extende...
Definition grid_halo.hpp:44
GridHalo & operator=(const GridHalo &)=delete
void exchange(const View< T > &field, int tag=0)
Exchange ghost layers of the device field field. Blocking.
Definition grid_halo.hpp:85
GridHalo(const GridHalo &)=delete
void init(const GridHaloTopology< Dim > &halo)
Definition grid_halo.hpp:51
#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_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_Irecv(void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
Definition mpi_stub.hpp:101
bool gpuAwareMpi()
Whether to hand DEVICE pointers straight to MPI (GPU-aware MPI) instead of host-staging.
Definition grid_halo.hpp:29
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