ryujin 2.1.1 revision ee5cbcbf2346c1299c942d0e1f13b46449973c18
Loading...
Searching...
No Matches
sparsity_pattern.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2020 - 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include "gpu.h"
9#include "simd.h"
10#include "sparsity_pattern.h"
11
12#include <deal.II/base/vectorization.h>
13#include <deal.II/lac/dynamic_sparsity_pattern.h>
14#include <deal.II/lac/sparsity_pattern.h>
15
16namespace ryujin
17{
18 template <int warp_size>
20 : n_internal_dofs_(0)
21 {
22 }
23
24
25 template <int warp_size>
27 const unsigned int n_internal_dofs,
28 const dealii::DynamicSparsityPattern &sparsity,
29 const std::shared_ptr<const dealii::Utilities::MPI::Partitioner>
30 &partitioner,
31 bool symmetrize_ghost_range,
32 const TransferPolicy transfer_policy)
33 {
34 reinit(n_internal_dofs,
35 sparsity,
36 partitioner,
37 symmetrize_ghost_range,
38 transfer_policy);
39 }
40
41
42 template <int warp_size>
44 const unsigned int n_internal_dofs,
45 const dealii::DynamicSparsityPattern &dsp,
46 const std::shared_ptr<const dealii::Utilities::MPI::Partitioner>
47 &partitioner,
48 const bool symmetrize_ghost_range,
49 const TransferPolicy transfer_policy)
50 {
51 /*
52 * Drop the transfer policy for the duration of the reinit so that a
53 * pinned memory space of a previous policy does not interfere:
54 */
55 this->set_transfer_policy(TransferPolicy::explicit_transfers);
56
57 this->n_internal_dofs_ = n_internal_dofs;
58 this->n_locally_owned_dofs_ = partitioner->locally_owned_size();
59 this->partitioner_ = partitioner;
60
61 const auto n_locally_relevant_dofs =
62 partitioner->locally_owned_size() + partitioner->n_ghost_indices();
63
64 /*
65 * First, create a static sparsity pattern in local indexing.
66 */
67
68 dealii::DynamicSparsityPattern dsp_local(n_locally_relevant_dofs,
69 n_locally_relevant_dofs);
70 for (unsigned int i = 0; i < n_locally_relevant_dofs; ++i) {
71 const auto global_row = partitioner->local_to_global(i);
72 for (auto it = dsp.begin(global_row); it != dsp.end(global_row); ++it) {
73 const auto global_column = it->column();
74 const auto j = partitioner->global_to_local(global_column);
75 dsp_local.add(i, j);
76
77 if (symmetrize_ghost_range && //
78 i < n_locally_owned_dofs_ && j >= n_locally_owned_dofs_)
79 dsp_local.add(j, i);
80 }
81 }
82
83 dealii::SparsityPattern sparsity;
84 sparsity.copy_from(dsp_local);
86 Assert(n_internal_dofs <= sparsity.n_rows(), dealii::ExcInternalError());
87 Assert(n_internal_dofs % warp_size == 0, dealii::ExcInternalError());
88 Assert(n_internal_dofs <= n_locally_owned_dofs_,
89 dealii::ExcInternalError());
90 Assert(n_locally_owned_dofs_ <= sparsity.n_rows(),
91 dealii::ExcInternalError());
92
93 AssertThrow(
94 sparsity.n_nonzero_elements() <
95 std::numeric_limits<unsigned int>::max(),
96 dealii::ExcMessage(
97 "Transposed indices only support up to 4 billion matrix entries "
98 "per MPI rank. Try to split into smaller problems with MPI"));
99
100 /* Allocate memory: */
101
102 using KokkosHost = dealii::MemorySpace::Host::kokkos_space;
103 using Aligned = Kokkos::MemoryTraits<Kokkos::Aligned>;
105 row_starts_host_ = Kokkos::View<unsigned int *, KokkosHost, Aligned>(
106 "sparsity_pattern_row_starts", sparsity.n_rows() + 1);
107
108 column_indices_host_ = Kokkos::View<unsigned int *, KokkosHost, Aligned>(
109 "sparsity_pattern_column_indices", sparsity.n_nonzero_elements());
110
111 indices_transposed_host_ =
112 Kokkos::View<unsigned int *, KokkosHost, Aligned>(
113 "sparsity_pattern_indices_transposed",
114 sparsity.n_nonzero_elements());
115
116 /* Vectorized part: */
117
118 row_starts_host_[0] = 0;
119
120 unsigned int *col_ptr = column_indices_host_.data();
121 unsigned int *transposed_ptr = indices_transposed_host_.data();
122
123 for (unsigned int i = 0; i < n_internal_dofs; i += warp_size) {
124 auto jts = generate_iterators<warp_size>(
125 [&](auto k) { return sparsity.begin(i + k); });
126
127 for (; jts[0] != sparsity.end(i); increment_iterators(jts))
128 for (unsigned int k = 0; k < warp_size; ++k) {
129 const unsigned int column = jts[k]->column();
130 *col_ptr++ = column;
131 const std::size_t position = sparsity(column, i + k);
132 if (column < n_internal_dofs) {
133 const unsigned int my_row_length = sparsity.row_length(column);
134 const std::size_t position_diag = sparsity(column, column);
135 const std::size_t pos_within_row = position - position_diag;
136 const unsigned int lane = column % warp_size;
137 *transposed_ptr++ = position - lane * my_row_length -
138 pos_within_row + lane +
139 pos_within_row * warp_size;
140 } else
141 *transposed_ptr++ = position;
142 }
143
144 row_starts_host_[i / warp_size + 1] =
145 col_ptr - column_indices_host_.data();
146 }
147
148 /* Rest: */
149
150 row_starts_host_[n_internal_dofs] =
151 row_starts_host_[n_internal_dofs / warp_size];
152
153 for (unsigned int i = n_internal_dofs; i < sparsity.n_rows(); ++i) {
154 for (auto j = sparsity.begin(i); j != sparsity.end(i); ++j) {
155 const unsigned int column = j->column();
156 *col_ptr++ = column;
157 const std::size_t position = sparsity(column, i);
158 if (column < n_internal_dofs) {
159 const unsigned int my_row_length = sparsity.row_length(column);
160 const std::size_t position_diag = sparsity(column, column);
161 const std::size_t pos_within_row = position - position_diag;
162 const unsigned int lane = column % warp_size;
163 *transposed_ptr++ = position - lane * my_row_length - pos_within_row +
164 lane + pos_within_row * warp_size;
165 } else
166 *transposed_ptr++ = position;
167 }
168 row_starts_host_[i + 1] = col_ptr - column_indices_host_.data();
169 }
170
171#ifdef DEBUG
172 const auto distance = std::distance(column_indices_host_.data(), col_ptr);
173 Assert(static_cast<std::size_t>(distance) == column_indices_host_.size(),
174 dealii::ExcInternalError());
175#endif
176
177 /*
178 * Compute the data exchange pattern:
179 */
180
181 receive_targets_.clear();
182 send_targets_.clear();
183
184 std::vector<ExchangeDescription> entries_to_be_sent;
185
186 if (sparsity.n_rows() > n_locally_owned_dofs_) {
187 const unsigned int mpi_tag =
188 dealii::Utilities::MPI::internal::Tags::partitioner_export_start + 0;
189
190 const auto &ghost_targets = partitioner->ghost_targets();
191 const auto &import_targets = partitioner->import_targets();
192 const auto &mpi_communicator = partitioner->get_mpi_communicator();
193
194 const unsigned int n_requests =
195 ghost_targets.size() + import_targets.size();
196 std::vector<MPI_Request> requests(n_requests);
197
198 /*
199 * Set up receive targets.
200 *
201 * We receive our local ghost rows from MPI ranks in the ghost range
202 * of the (scalar) partitioner. We receive our entire local ghost row
203 * from the owning MPI rank. We have to navigate one detail, though.
204 * Our local view of the ghost row is a subset of the full row of the
205 * owning rank. We thus have to communicate to the owning rank how
206 * many entries and what indices we are expecting.
207 *
208 * First, set up the receive_targets_ vector and send the cummulative
209 * row size to the owning MPI rank:
210 */
211
212 receive_targets_.resize(ghost_targets.size());
213 for (unsigned int p = 0; p < receive_targets_.size(); ++p) {
214 receive_targets_[p].first = ghost_targets[p].first;
215 }
216
217 {
218 /* Index into ghost targets: */
219 unsigned int ghost_targets_index = 0;
220 /* Current and previous index into ghost range of sparsity pattern: */
221 unsigned int index = 0;
222 unsigned int previous_index = 0;
223
224 unsigned int row_count = 0;
225 for (unsigned int i = n_locally_owned_dofs_; i < sparsity.n_rows();
226 ++i) {
227 index += sparsity.row_length(i);
228 ++row_count;
229 const auto ghost_target = ghost_targets[ghost_targets_index];
230 if (row_count == ghost_target.second) {
231 receive_targets_[ghost_targets_index].second = index;
232
233 unsigned int n_entries = index - previous_index;
234 const int ierr = MPI_Isend(
235 &n_entries,
236 1,
237 dealii::Utilities::MPI::mpi_type_id_for_type<unsigned int>,
238 ghost_target.first,
239 mpi_tag,
240 mpi_communicator,
241 &requests[ghost_targets_index]);
242 AssertThrowMPI(ierr);
243
244 /* Update indices: */
245 ++ghost_targets_index;
246 previous_index = index;
247 row_count = 0;
248 }
249 }
250
251 Assert(ghost_targets_index == partitioner->ghost_targets().size(),
252 dealii::ExcInternalError());
253 }
254
255
256 /*
257 * Set up send targets.
258 *
259 * First receive the number of entries that we will need to send.
260 */
261
262 std::vector<unsigned int> send_ranges(import_targets.size());
263 for (unsigned int p = 0; p < import_targets.size(); ++p) {
264 const int ierr = MPI_Irecv(
265 &send_ranges[p],
266 1,
267 dealii::Utilities::MPI::mpi_type_id_for_type<unsigned int>,
268 import_targets[p].first,
269 mpi_tag,
270 mpi_communicator,
271 &requests[ghost_targets.size() + p]);
272 AssertThrowMPI(ierr);
273 }
274
275 {
276 const int ierr =
277 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
278 AssertThrowMPI(ierr);
279 }
280
281 /*
282 * Now, that the owning rank knows the number of entries we request
283 * we can send the actual index pairs (i_global, j_global) that we
284 * require.
285 */
286
287 std::vector<dealii::types::global_dof_index> requested_entries;
288 {
289 /* Index into ghost targets: */
290 unsigned int ghost_targets_index = 0;
291
292 unsigned int row_count = 0;
293 for (unsigned int i = n_locally_owned_dofs_; i < sparsity.n_rows();
294 ++i) {
295 const auto i_global = partitioner_->local_to_global(i);
296 for (auto idx = sparsity.begin(i); idx != sparsity.end(i); ++idx) {
297 const unsigned int j = idx->column();
298 const auto j_global = partitioner_->local_to_global(j);
299 requested_entries.push_back(i_global);
300 requested_entries.push_back(j_global);
301 }
302
303 ++row_count;
304 if (row_count == ghost_targets[ghost_targets_index].second) {
305 /* Update indices: */
306 ++ghost_targets_index;
307 row_count = 0;
308 }
309 }
310
311 Assert(ghost_targets_index == partitioner->ghost_targets().size(),
312 dealii::ExcInternalError());
313
314#ifdef DEBUG
315 const auto ghost_offset = row_starts_host_(n_locally_owned_dofs_);
316 const auto n_nonzero_elements =
317 row_starts_host_(row_starts_host_.size() - 1);
318 Assert(requested_entries.size() ==
319 2 * (n_nonzero_elements - ghost_offset),
320 dealii::ExcInternalError());
321#endif
322
323 for (unsigned int p = 0; p < receive_targets_.size(); ++p) {
324 const auto request_offset =
325 p == 0 ? 0 : receive_targets_[p - 1].second;
326 const auto request_size = receive_targets_[p].second - request_offset;
327
328 const int ierr =
329 MPI_Isend(requested_entries.data() + 2 * request_offset,
330 2 * request_size,
331 dealii::Utilities::MPI::mpi_type_id_for_type<
332 dealii::types::global_dof_index>,
333 receive_targets_[p].first,
334 mpi_tag,
335 mpi_communicator,
336 &requests[p]);
337 AssertThrowMPI(ierr);
338 }
339 }
340
341 /*
342 * Accumulate all requests we received from other ranks:
343 */
344
345 send_targets_.resize(import_targets.size());
346
347 const unsigned int n_entries_to_be_sent =
348 std::accumulate(send_ranges.begin(), send_ranges.end(), 0);
349 std::vector<dealii::types::global_dof_index> entries_buffer(
350 2 * n_entries_to_be_sent );
351
352 {
353 /* Index into entries_to_be_sent: */
354 unsigned int index = 0;
355
356 for (unsigned int p = 0; p < send_targets_.size(); ++p) {
357 const auto n_entries = send_ranges[p];
358
359 const int ierr =
360 MPI_Irecv(entries_buffer.data() + 2 * index ,
361 2 * n_entries ,
362 dealii::Utilities::MPI::mpi_type_id_for_type<
363 dealii::types::global_dof_index>,
364 import_targets[p].first,
365 mpi_tag,
366 mpi_communicator,
367 &requests[ghost_targets.size() + p]);
368 AssertThrowMPI(ierr);
369
370 index += n_entries;
371 send_targets_[p].first = import_targets[p].first;
372 send_targets_[p].second = index;
373 }
374 }
375
376 {
377 const int ierr =
378 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
379 AssertThrowMPI(ierr);
380 }
381
382 entries_to_be_sent.reserve(n_entries_to_be_sent);
383 for (unsigned int e = 0; e < n_entries_to_be_sent; ++e) {
384 const auto i_global = entries_buffer[2 * e];
385 const auto j_global = entries_buffer[2 * e + 1];
386 const auto i = partitioner_->global_to_local(i_global);
387 const auto j = partitioner_->global_to_local(j_global);
388
389 const std::size_t position = sparsity(i, j);
390 Assert(
391 position != sparsity.invalid_entry,
392 dealii::ExcMessage("Inconsistent global view of sparsity pattern: "
393 "the requested column index is not present on "
394 "the row stored on the owning MPI rank."));
395
396 const std::size_t position_diag = sparsity(i, i);
397 const std::size_t position_within_row = position - position_diag;
398
399 entries_to_be_sent.emplace_back(ExchangeDescription{
400 i, static_cast<unsigned int>(position_within_row)});
401 }
402 }
403
404 /*
405 * FIXME: entries_to_be_sent_ should inherit the same "transfer_policy"
406 * at this point. Unfortunately, we do not support calling
407 * copy_to_memory_space<>(), or move_to_memory_space<>() of dependent
408 * data fields in an elegant way... Work around this issue by setting
409 * the transfer policy to "implicit_transfers".
410 */
411 entries_to_be_sent_.reinit(entries_to_be_sent.size(),
413 std::copy(entries_to_be_sent.begin(),
414 entries_to_be_sent.end(),
415 entries_to_be_sent_.view());
416
417 this->reset_residency(/*host*/ true,
418 /*default*/ !have_separate_memory_spaces);
419
420 /* The transfer policy is selected last, see MirroredStorage: */
421 this->set_transfer_policy(transfer_policy);
422 }
423} // namespace ryujin
void reinit(const unsigned int n_internal_dofs, const dealii::DynamicSparsityPattern &sparsity, const std::shared_ptr< const dealii::Utilities::MPI::Partitioner > &partitioner, bool symmetrize_ghost_range=true, const TransferPolicy transfer_policy=TransferPolicy::explicit_transfers)
constexpr unsigned int warp_size
Definition gpu.h:46
TransferPolicy
Definition gpu.h:88
constexpr bool have_separate_memory_spaces
Definition gpu.h:29
DEAL_II_ALWAYS_INLINE void increment_iterators(T &iterators)
Definition simd.h:131