preCICE
Loading...
Searching...
No Matches
KokkosPUMKernels_Impl.hpp
Go to the documentation of this file.
1#include <KokkosBatched_LU_Decl.hpp>
2#include <KokkosBatched_Util.hpp>
3
4#include <KokkosBatched_ApplyPivot_Decl.hpp>
5#include <KokkosBatched_ApplyQ_Decl.hpp>
6#include <KokkosBatched_QR_WithColumnPivoting_Decl.hpp>
7#include <KokkosBatched_Trsv_Decl.hpp>
8#include <KokkosBlas2_gemv.hpp>
9
11#include "math/math.hpp"
12
13#include <functional>
14
17
19
20namespace impl {
21
22// Enum to use when computing the team size
28
29// Helper to compute the power of 2^x for the given n and the above,
30// i.e., either the flooring, ceiling, or the closest one
31// used only below to determine a reasonable team size
32inline int powerOfTwo(int n, const Pow2Mode mode)
33{
34 if (n < 1)
35 return 1;
36 // find highest power-of-two <= n
37 int pow2 = 1;
38 while (pow2 <= n) {
39 pow2 = pow2 * 2;
40 }
41 // now that we have the value larger, we look for the pow2 below n
42 int lower = pow2 / 2;
43
44 if (mode == Pow2Mode::Larger) {
45 return pow2;
46 } else if (mode == Pow2Mode::Smaller) {
47 return lower;
48 } else {
50 // decide whether n is closer to lower or upper (pow2)
51 if (n - lower < pow2 - n) {
52 return lower;
53 } else {
54 return pow2;
55 }
56 }
57}
58
59// Try to find a good team size based on the cluster size
60// avgWork is in our case simply the average cluster size, as it determines
61// the local matrix sizes
62template <typename ExecSpace, typename FunctorType, typename Policy>
63auto findTeamSize(int avgWork, const Pow2Mode mode, const FunctorType &functor, const Policy &policy)
64{
65 // If using OpenMP, Kokkos::AUTO works best
66 if constexpr (std::is_same_v<ExecSpace, Kokkos::HostSpace::execution_space>) {
67 return Kokkos::AUTO;
68 } else {
69 // Compute the power of two according to the configuration
70 int teamSize = powerOfTwo(avgWork, mode);
71 // Ensure minimum of one warp for the GPU to avoid partial-warp inefficiency
72 int warpSize = Kokkos::TeamPolicy<ExecSpace>::vector_length_max();
73 if (teamSize < warpSize) {
74 teamSize = warpSize;
75 }
76 // Query Kokkos for the max recommended team size for this functor
77 // (takes also memory constraints into account)
78 int maxRecommended = policy.team_size_recommended(functor, Kokkos::ParallelForTag());
79 if (teamSize > maxRecommended) {
80 teamSize = maxRecommended;
81 }
82 return teamSize;
83 }
84}
85
86// small helper function to make the compiler handle variables in lambdas which are only conditionally used
87template <typename... Args>
88KOKKOS_INLINE_FUNCTION constexpr void capture_conditional_variables(const Args &...) {}
89} // namespace impl
90
91// For within the kernels
92// The batch matrix has a default layout, which is consistent across kernels
93template <typename MemorySpace = ExecutionSpace>
94using BatchMatrix = Kokkos::View<double **, MemorySpace, UnmanagedMemory>;
95template <typename T = double *, typename MemorySpace = ExecutionSpace>
96using BatchVector = Kokkos::View<T, MemorySpace, UnmanagedMemory>;
97
98template <typename MemorySpace>
99bool compute_weights(const int nCenters,
100 const int avgOutClusterSize,
101 const offset_1d_type nWeights,
102 const int nMeshVertices,
103 const int dim,
105 MeshView<MemorySpace> centers,
108 const CompactPolynomialC2 &w,
109 VectorView<MemorySpace> normalizedWeights)
110{
111 using TeamPolicy = Kokkos::TeamPolicy<MemorySpace>;
112 using TeamMember = typename TeamPolicy::member_type;
113
114 VectorView<MemorySpace> weightSum("weightSum", nMeshVertices);
115 Kokkos::deep_copy(weightSum, 0.0);
116 Kokkos::fence();
117
118 const auto rbf_params = w.getFunctionParameters();
119
120 // We launch one team per local system
121 auto kernel = KOKKOS_LAMBDA(const TeamMember &team)
122 {
123 const int batch = team.league_rank();
124 const auto begin = offsets(batch);
125 const auto end = offsets(batch + 1);
126
127 Kokkos::parallel_for(
128 Kokkos::TeamThreadRange(team, begin, end),
129 [&](offset_1d_type i) {
130 auto globalID = globalIDs(i);
131 double dist = 0.0;
132 for (int d = 0; d < dim; ++d) {
133 double diff = mesh(globalID, d) - centers(batch, d);
134 dist += diff * diff;
135 }
136 dist = Kokkos::sqrt(dist);
137 double val = w(dist, rbf_params);
138 // is NUMERICAL_ZERO_DIFFERENCE_DEVICE
139 double res = Kokkos::fmax(val, 1.0e-14);
140 normalizedWeights(i) = res;
141
142 Kokkos::atomic_add(&weightSum(globalID), res);
143 }); // TeamThreadRange
144 };
145
146 // auto teamSize = impl::findTeamSize<typename MemorySpace::execution_space>(avgOutClusterSize, impl::Pow2Mode::Larger, kernel, TeamPolicy(nCenters, Kokkos::AUTO));
147 Kokkos::parallel_for("compute_weights", TeamPolicy(nCenters, Kokkos::AUTO), kernel);
148
149 // Check for output mesh vertices which are unassigned
150 // This check is a pure sanity check
151 bool hasZero = false;
152 Kokkos::parallel_reduce(
153 "check_zero",
154 nMeshVertices,
155 KOKKOS_LAMBDA(int i, bool &local) {
156 if (weightSum(i) == 0.0)
157 local = true;
158 },
159 Kokkos::LOr<bool>(hasZero));
160
161 if (hasZero) {
162 return false;
163 }
164
165 // Now scale back the sum
166 Kokkos::parallel_for(
167 "scale_weights",
168 Kokkos::RangePolicy<MemorySpace>(0, nWeights),
169 KOKKOS_LAMBDA(const int i) {
170 const int id = globalIDs(i);
171 normalizedWeights(i) /= weightSum(id);
172 });
173
174 return true;
175}
176
177template <typename MemorySpace>
178void do_batched_qr(int nCluster,
179 int dim,
180 int avgClusterSize,
181 int maxClusterSize,
188{
189 using TeamPolicy = Kokkos::TeamPolicy<MemorySpace>;
190 using MemberType = typename TeamPolicy::member_type;
191 using ScratchView = Kokkos::View<double *, typename MemorySpace::scratch_memory_space, UnmanagedMemory>;
192
193 // First, we fill the entire matrix with ones such that we don't need to fill the 1 separately later
194 Kokkos::deep_copy(qrMatrix, 1.0);
195 Kokkos::fence();
196
197 auto kernel = KOKKOS_LAMBDA(const MemberType &team)
198 {
199 // Step 1: define some pointers we need
200 const int batch = team.league_rank();
201
202 // For the batch
203 const auto begin = offsets(batch);
204 const int verticesPerCluster = offsets(batch + 1) - begin; // the local cluster size
205 const int matrixCols = dim + 1; // our polyParams
206 const offset_1d_type matrixBegin = begin * matrixCols;
207
208 // Step 2: fill the polynomial matrix
209 BatchMatrix<MemorySpace> qr(&qrMatrix(matrixBegin), verticesPerCluster, matrixCols);
210
211 Kokkos::parallel_for(
212 Kokkos::TeamThreadRange(team, verticesPerCluster),
213 [&](int i) {
214 auto globalID = globalIDs(i + begin);
215 // the 1 is already set in the last column
216 for (int d = 0; d < dim; ++d) {
217 qr(i, d) = mesh(globalID, d);
218 }
219 });
220
221 // Step 3: Compute the QR decomposition
222 const offset_1d_type tauBegin = batch * matrixCols;
223 // the 1 here is for the local rank and has nothing to do with the permutation itself
224 const offset_1d_type PBegin = batch * (matrixCols + 1);
225
226 BatchVector<double *, MemorySpace> tau(&qrTau(tauBegin), matrixCols);
227 BatchVector<int *, MemorySpace> P(&qrP(PBegin), matrixCols);
228
229 // Essentially P.end() - 1 = matrixCols + 1 - 1
230 int &rank = qrP(PBegin + matrixCols);
231
232 // The scratch memory (shared memory for the device)
233 ScratchView work(team.team_scratch(0), 2 * verticesPerCluster);
234
235 // auto b = Kokkos::subview(work, std::pair<int, int>(0, n));
236 // auto res = Kokkos::subview(work, std::pair<int, int>(n, n + m));
237 // A Serial QR_WithColumnPivoting would probably be better suited here, but this is as of now not available
238 // The workload to put a Team on the whole matrix is most likely not a good balance
239 KokkosBatched::TeamVectorQR_WithColumnPivoting<MemberType,
240 KokkosBatched::Algo::QR::Unblocked>::invoke(team, qr, tau,
241 P, work,
242 rank);
243 // We have to define our own criterion for the rank, as the one provided is not stable enough
244 // A pivot will be considered nonzero if its absolute value is strictly greater than |pivot|⩽threshold×|maxpivot| where maxpivot is the biggest pivot.
245 // We use 1e-6 as threshold, which is much stricter than the Kokkos internal criterion
246 // The kokkos internal criterion can be found in KokkosBatched_QR_WithColumnPivoting_TeamVector_Internal.hpp
247 // if diagonal value is smaller than threshold(10 * max_diag * ats::epsilon())
248 // note that the pivoted algorithm aborts once the rank deficiency is detected, thus, values larger than rank just contain rubbish
249 double threshold = 1e-5;
250 if (team.team_rank() == 0) {
251 const double maxp = Kokkos::abs(qr(0, 0)); // largest pivot
252 int r = 0;
253 for (int i = 0; i < rank; ++i) {
254 r += static_cast<int>(Kokkos::abs(qr(i, i)) > (threshold * maxp));
255 }
256 rank = r;
257 }
258 // parallel_for
259 };
260
261 // Required as workspace for the pivoted QR, see code comment
262 // workspace (norm and householder application, 2 * max(m,n) is needed)
263
264 auto scratchSize = ScratchView::shmem_size(2 * maxClusterSize);
265 auto teamSize = impl::findTeamSize<typename MemorySpace::execution_space>(avgClusterSize, impl::Pow2Mode::Smaller, kernel, TeamPolicy(nCluster, Kokkos::AUTO).set_scratch_size(0, Kokkos::PerTeam(scratchSize)));
266 // const int VL = TeamPolicy::vector_length_max();
267 // The inner loop uses vector parallelism, so we should try to configure accordingly
268 std::unique_ptr<TeamPolicy> policy;
269 if constexpr (std::is_same_v<decltype(teamSize), Kokkos::AUTO_t>) {
270 policy = std::make_unique<TeamPolicy>(nCluster, Kokkos::AUTO);
271 } else {
272 policy = std::make_unique<TeamPolicy>(nCluster, 4, teamSize / 4);
273 }
274 policy->set_scratch_size(/* level = */ 0, Kokkos::PerTeam(scratchSize));
275 Kokkos::parallel_for("do_batched_qr", *policy, kernel);
276}
277
278template <typename MemorySpace>
280 MatrixOffsetView<MemorySpace> dst, int nCluster)
281{
282 PRECICE_ASSERT(src1.extent(0) == src2.extent(0));
283 PRECICE_ASSERT(src2.extent(0) == dst.extent(0));
284 Kokkos::parallel_scan("compute_offsets", nCluster, KOKKOS_LAMBDA(const int i, offset_2d_type &update, const bool final) {
285 // Number of rows for local system i
286 int nrows = src1(i + 1) - src1(i);
287 // Number of columns for local system i
288 int ncols = src2(i + 1) - src2(i);
289
290 // Number of entries in the i-th local matrix
291 int localSize = nrows * ncols;
292
293 // Add to running sum
294 update += static_cast<offset_2d_type>(localSize);
295
296 // 'final == true' indicates we should write to matrixOffsets
297 if (final) {
298 // matrixOffsets(i+1) = partial sum up to i
299 dst(i + 1) = update;
300 }
301 // end parallel_for
302 });
303}
304
305template <typename EvalFunctionType, typename MemorySpace>
307 int nCluster, // Number of local systems
308 int dim, // Dimension of points
309 int avgClusterSize,
310 int maxInClusterSize,
311 EvalFunctionType f,
312 const VectorOffsetView<MemorySpace> &inOffsets, // vertex offsets (length N+1)
313 const GlobalIDView<MemorySpace> &globalInIDs,
314 const MeshView<MemorySpace> &inCoords, // meshes
315 const MatrixOffsetView<MemorySpace> &matrixOffsets,
316 VectorView<MemorySpace> matrices) // 1D view of batched matrices
317{
318 using ExecSpace = typename MemorySpace::execution_space;
319 using TeamPolicy = Kokkos::TeamPolicy<ExecSpace>;
320 using MemberType = typename TeamPolicy::member_type;
321
322 using ScratchSpace = typename MemorySpace::scratch_memory_space;
323 using ScratchView1d = Kokkos::View<double *, ScratchSpace, UnmanagedMemory>;
324 using ScratchMatrix = Kokkos::View<double **, Kokkos::LayoutRight, ScratchSpace, UnmanagedMemory>;
325
326 const auto rbf_params = f.getFunctionParameters();
327 auto kernel = KOKKOS_LAMBDA(const MemberType &team)
328 {
329 const int batch = team.league_rank();
330 // Ranges
331 const auto inBegin = inOffsets(batch);
332 const auto inEnd = inOffsets(batch + 1);
333 const int n = inEnd - inBegin;
334
335 ScratchMatrix mesh(team.team_scratch(0), n, dim);
336
337 Kokkos::parallel_for(
338 Kokkos::TeamThreadRange(team, n), [&](int i) {
339 auto globalID = globalInIDs(i + inBegin);
340 for (int d = 0; d < dim; ++d) {
341 mesh(i, d) = inCoords(globalID, d);
342 }
343 });
344
345 // The matrix offset
346 const auto matrixBegin = matrixOffsets(batch);
347
348 team.team_barrier();
349
350 // Create an unmanaged 2D subview pointing into matrices
351 // This constructor: View(pointer, layout)
352 BatchMatrix<MemorySpace> localMatrix(&matrices(matrixBegin), n, n);
353
354 // Now fill localMatrix(r,c). We'll do a standard 2D nested parallel loop
355 Kokkos::parallel_for(
356 Kokkos::TeamThreadMDRange(team, n, n),
357 [=](int r, int c) {
358 // global indices in the original support/target arrays
359 // 1) Compute Euclidean distance
360 double dist = 0;
361 for (int d = 0; d < dim; ++d) {
362 double diff = mesh(r, d) - mesh(c, d);
363 dist += diff * diff;
364 }
365 dist = Kokkos::sqrt(dist);
366
367 // 2) Evaluate the RBF
368 double val = f(dist, rbf_params);
369
370 // 3) Store into localMatrix (2D)
371 localMatrix(c, r) = val;
372 }); // TeamThreadRange
373 };
374
375 // We put the solution and the in data values into shared memory
376 auto inBytes = ScratchView1d::shmem_size(maxInClusterSize);
377
378 auto teamSize = impl::findTeamSize<ExecSpace>(avgClusterSize, impl::Pow2Mode::Larger, kernel, TeamPolicy(nCluster, Kokkos::AUTO).set_scratch_size(/* level = */ 0, Kokkos::PerTeam(dim * inBytes)));
379 Kokkos::parallel_for("do_input_assembly", TeamPolicy(nCluster, teamSize).set_scratch_size(/* level = */ 0, Kokkos::PerTeam(dim * inBytes)), kernel);
380}
381
382// TODO: Using Kokkos::LayoutRight for the Coords performs a bit better for the assembly,
383// but it might deteriorate performance related to the polynomials. Especially for the gemv
384// for the polynomial contributions etc
385// For the full GPU porting, the Layout can only be LayoutRight, as we don't access the coordinates
386// coalesced, but rather first pick what we need
387template <typename EvalFunctionType, typename MemorySpace>
389 int nCluster, // Number of local systems
390 int dim, // Dimension of points
391 int avgClusterSize,
392 EvalFunctionType f,
393 const VectorOffsetView<MemorySpace> &inOffsets, // vertex offsets (length N+1)
394 const GlobalIDView<MemorySpace> &globalInIDs,
395 const MeshView<MemorySpace> &inCoords, // meshes
396 const VectorOffsetView<MemorySpace> &targetOffsets,
397 const GlobalIDView<MemorySpace> &globalTargetIDs,
398 const MeshView<MemorySpace> &targetCoords,
399 const MatrixOffsetView<MemorySpace> &matrixOffsets,
400 VectorView<MemorySpace> matrices) // 1D view of batched matrices
401{
402 using ExecSpace = typename MemorySpace::execution_space;
403 using TeamPolicy = Kokkos::TeamPolicy<ExecSpace>;
404 using MemberType = typename TeamPolicy::member_type;
405
406 const auto rbf_params = f.getFunctionParameters();
407 auto kernel = KOKKOS_LAMBDA(const MemberType &team)
408 {
409 const int batch = team.league_rank();
410 // Ranges
411 const auto inBegin = inOffsets(batch);
412 const auto inEnd = inOffsets(batch + 1);
413 const auto targetBegin = targetOffsets(batch);
414 const auto targetEnd = targetOffsets(batch + 1);
415
416 // For our batched matrix, this results in
417 const int nrows = targetEnd - targetBegin;
418 const int ncols = inEnd - inBegin;
419
420 // The matrix offset
421 const auto matrixBegin = matrixOffsets(batch);
422 // const size_t matrixEnd = matrixOffsets(batch + 1);
423
424 // Create an unmanaged 2D subview pointing into matrices
425 // This constructor: View(pointer, layout)
426 BatchMatrix<MemorySpace> localMatrix(&matrices(matrixBegin), nrows, ncols);
427
428 // Now fill localMatrix(r,c). We'll do a standard 2D nested parallel loop
429 Kokkos::parallel_for(
430 Kokkos::TeamThreadMDRange(team, nrows, ncols),
431 [=](int r, int c) {
432 // global indices in the original support/target arrays
433 offset_1d_type targetIdx = targetBegin + r;
434 offset_1d_type inIdx = inBegin + c;
435
436 auto globalIn = globalInIDs(inIdx);
437 auto globalTarget = globalTargetIDs(targetIdx);
438 // 1) Compute Euclidean distance
439 double dist = 0;
440 for (int d = 0; d < dim; ++d) {
441 double diff = inCoords(globalIn, d) - targetCoords(globalTarget, d);
442 dist += diff * diff;
443 }
444 dist = Kokkos::sqrt(dist);
445
446 // 2) Evaluate the RBF
447 double val = f(dist, rbf_params);
448
449 // 3) Store into localMatrix (2D)
450 localMatrix(r, c) = val;
451 }); // ThreadVectorRange
452 };
453
454 auto teamSize = impl::findTeamSize<ExecSpace>(avgClusterSize, impl::Pow2Mode::Larger, kernel, TeamPolicy(nCluster, Kokkos::AUTO));
455 Kokkos::parallel_for("do_batched_assembly", TeamPolicy(nCluster, teamSize), kernel);
456}
457
458template <typename MemorySpace>
460 int nCluster,
461 int avgClusterSize,
462 const MatrixOffsetView<MemorySpace> &matrixOffsets,
464{
465 using ExecSpace = typename MemorySpace::execution_space;
466 using TeamPolicy = Kokkos::TeamPolicy<ExecSpace>;
467 using MemberType = typename TeamPolicy::member_type;
468
469 auto kernel = KOKKOS_LAMBDA(const MemberType &team)
470 {
471 const int i = team.league_rank();
472 auto start = matrixOffsets(i);
473 auto end = matrixOffsets(i + 1);
474 int n = static_cast<int>(Kokkos::sqrt(end - start));
475
476 BatchMatrix<MemorySpace> A(&matrices(start), n, n);
477
478 KokkosBatched::TeamLU<MemberType, KokkosBatched::Algo::LU::Blocked>::invoke(team, A);
479 // Parallel end
480 };
481
482 // Using Kokkos::AUTO resulted in the best performance for all cases
483 auto teamSize = impl::findTeamSize<ExecSpace>(avgClusterSize, impl::Pow2Mode::Larger, kernel, TeamPolicy(nCluster, Kokkos::AUTO));
484 Kokkos::parallel_for("do_batched_lu", TeamPolicy(nCluster, teamSize), kernel);
485}
486
491template <bool polynomial, bool evaluation_op_available, typename EvalFunctionType, typename MemorySpace>
493 int nCluster,
494 int dim,
495 int avgInClusterSize,
496 int maxInClusterSize,
497 int maxOutClusterSize,
498 EvalFunctionType f,
499 const VectorOffsetView<MemorySpace> &rhsOffsets,
500 const GlobalIDView<MemorySpace> &globalRhsIDs,
502 const MatrixOffsetView<MemorySpace> &matrixOffsets,
503 const VectorView<MemorySpace> &matrices,
504 const VectorView<MemorySpace> &normalizedWeights,
505 const MatrixOffsetView<MemorySpace> &evalOffsets,
506 const VectorView<MemorySpace> &evalMat,
507 const VectorOffsetView<MemorySpace> &outOffsets,
508 const GlobalIDView<MemorySpace> &globalOutIDs,
510 // For the polynomial required in addition
511 const MeshView<MemorySpace> &inMesh,
512 const MeshView<MemorySpace> &outMesh,
513 const VectorView<MemorySpace> &qrMatrix,
514 const VectorView<MemorySpace> &qrTau,
515 const PivotView<MemorySpace> &qrP)
516{
517 using ExecSpace = typename MemorySpace::execution_space;
518 using TeamPolicy = Kokkos::TeamPolicy<ExecSpace>;
519 using MemberType = typename TeamPolicy::member_type;
520
521 using ScratchSpace = typename MemorySpace::scratch_memory_space;
522 // Layout is important for how we use these matrices: we need to ensure that cols are contiguous in memory
523 // We use the scratch memory for the input data and potentially for workspace required for the
524 // polynomial or for the input mesh in case we have to compute the evaluation on the fly
525 using ScratchView1d = Kokkos::View<double *[1], Kokkos::LayoutLeft, ScratchSpace, UnmanagedMemory>;
526 using ScratchView4d = Kokkos::View<double *[4], Kokkos::LayoutLeft, ScratchSpace, UnmanagedMemory>;
527 using ScratchVector = Kokkos::View<double *, ScratchSpace, UnmanagedMemory>;
528 using ScratchMatrix = std::conditional_t<!evaluation_op_available || polynomial, ScratchView4d, ScratchView1d>;
529 using ScratchMesh = Kokkos::View<double **, Kokkos::LayoutRight, ScratchSpace, UnmanagedMemory>;
530
531 const auto rbf_params = f.getFunctionParameters();
532 // We define the lambda here such that we can query the recommended team size from Kokkos
533 // Launch policy is then handled below
534 auto kernel = KOKKOS_LAMBDA(const MemberType &team)
535 {
536 // Required for correct capturing (mostly by device compilers), as these variables are only conditionally used further down
537 impl::capture_conditional_variables(dim, qrMatrix, qrTau, qrP, inMesh, outMesh, evalOffsets, evalMat, globalOutIDs, f, rbf_params, normalizedWeights, out);
538
539 // Step 1: Define some pointers
540 // TODO: We could potentially remove the rhsOffsets here and use a sqrt instead
541 const int batch = team.league_rank();
542 const auto inBegin = rhsOffsets(batch);
543 const int inSize = rhsOffsets(batch + 1) - inBegin;
544 const auto outBegin = outOffsets(batch);
545 const int outSize = outOffsets(batch + 1) - outBegin;
546
547 // Step 2: Allocate shared memory for the team and fill it with the inData of this cluster
548 // The scratch memory (shared memory for the device)
549 ScratchMatrix work(team.team_scratch(0), Kokkos::max(4, inSize));
550 auto in = Kokkos::subview(work, Kokkos::pair<int, int>(0, inSize), 0);
551
552 Kokkos::parallel_for(
553 Kokkos::TeamThreadRange(team, inSize), [&](int i) {
554 auto globalID = globalRhsIDs(i + inBegin);
555 in(i) = rhs(globalID);
556 });
557 team.team_barrier();
558
559 Kokkos::Array<double, 4> qrCoeffs = {0., 0., 0., 0.};
560
561 // Step 3: Solve the polynomial QR system, if we have one
562 if constexpr (polynomial) {
563
564 // Step 3a: Backup the current in data, since we solve the QR in place
565 // In principle, we need a vector here (just as in), but the ApplyQ routine expects a Rank2 matrix,
566 // so we have to stick to this particular syntax (keeping it rank 2 with one column)
567 auto in_cp = Kokkos::subview(work, Kokkos::pair<int, int>(0, inSize), Kokkos::pair<int, int>(1, 2));
568 Kokkos::parallel_for(
569 Kokkos::TeamThreadRange(team, inSize), [&](int i) { in_cp(i, 0) = in(i); });
570 team.team_barrier();
571
572 // Step 3b: Define pointers and matrices
573 const int matrixCols = dim + 1;
574 const offset_1d_type qrBegin = inBegin * matrixCols;
575 const offset_1d_type tauBegin = batch * matrixCols;
576 const offset_1d_type PBegin = batch * (matrixCols + 1);
577 const int rank = qrP(PBegin + matrixCols);
578
579 BatchMatrix<MemorySpace> qr(&qrMatrix(qrBegin), inSize, matrixCols);
580 BatchVector<double *, MemorySpace> tau(&qrTau(tauBegin), matrixCols);
581 BatchVector<int *, MemorySpace> P(&qrP(PBegin), matrixCols);
582
583 // Step 3c: Apply Q on the left of in, i.e., y = Q^T * in
584 if (team.team_rank() == 0) {
585
586 // tmp size might be insufficient: there was no size requirement specified for the workspace
587 // however, it needs to be contiguous
588 auto tmp = Kokkos::subview(work, Kokkos::ALL, 2);
589 KokkosBatched::ApplyQ<MemberType,
590 KokkosBatched::Side::Left,
591 KokkosBatched::Trans::Transpose,
592 KokkosBatched::Mode::Serial,
593 KokkosBatched::Algo::ApplyQ::Unblocked>::invoke(team, qr, tau, in_cp, tmp);
594
595 // Step 3d: Solve triangular solve R z = y
596 auto in_r = Kokkos::subview(in_cp, Kokkos::pair<int, int>(0, rank), 0);
597 auto R = Kokkos::subview(qr, Kokkos::pair<int, int>(0, rank), Kokkos::pair<int, int>(0, rank));
598
599 KokkosBatched::Trsv<
600 MemberType,
601 KokkosBatched::Uplo::Upper,
602 KokkosBatched::Trans::NoTranspose,
603 KokkosBatched::Diag::NonUnit,
604 KokkosBatched::Mode::Serial,
605 KokkosBatched::Algo::Trsv::Unblocked>::invoke(team, 1.0, R, in_r);
606 }
607
608 team.team_barrier();
609
610 // Step 3e: Copy the result over into memory for each thread
611 for (int r = 0; r < rank; ++r) {
612 qrCoeffs[r] = in_cp(r, 0);
613 }
614
615 // Step 3f: Apply pivoting x = P z
616 // There is also convenience routines for the pivoting, but we let every thread just
617 // apply the pivoting on its own, as it is more compact and the routine doesn't allow
618 // using an Array The below is the equivalent of the following (but qrCoeffs would need to be a view):
619 // KokkosBatched::TeamVectorApplyPivot<MemberType, Side::Left, Direct::Backward>::invoke(team, P, qrCoeffs);
620 for (int i = (matrixCols - 1); i >= 0; --i) {
621 Kokkos::kokkos_swap(qrCoeffs[i], qrCoeffs[i + P(i)]);
622 }
623 }
624
625 // Step 3g: Subtract polynomial portion from the input data: in -= Q * p
626 // In case we have to compute the evaluation operator further down, we
627 // also pull the in mesh into local shared memory
628 // threading over inSize
629 // This vector uses memory we have in principle managed by "work" (for the QR solve as tmp),
630 // but its layout is different (LayoutRight to have the coordinates aligned in memory).
631 // We have to declare it here outsie the "if" to be able to use it further down then.
632 // The memory here might point to null (or rather the end of "work"), in case polynomial = false
633 // and the evaluation_op is available but then it also remains unused. Using a std::optional
634 // is not portable, so the view here is unintitalized and only assigned in the "if"
635 // branch below
636 ScratchMesh localInMesh;
637
638 if constexpr (!evaluation_op_available || polynomial) {
639 localInMesh = ScratchMesh(&work(0, 1), inSize, dim);
640
641 Kokkos::parallel_for(
642 Kokkos::TeamThreadRange(team, inSize),
643 [&](int i) {
644 auto globalID = globalRhsIDs(i + inBegin);
645 // The "1"/constant term is the last value in the result
646 // dim is here matrixCols - 1
647 double sum = qrCoeffs[dim];
648 // ... and the linear polynomial
649 for (int d = 0; d < dim; ++d) {
650 sum += inMesh(globalID, d) * qrCoeffs[d];
651 // Put it in shared memory as we later need it in the output evaluation
652 localInMesh(i, d) = inMesh(globalID, d);
653 }
654 in(i) -= sum;
655 });
656 team.team_barrier();
657 }
658
659 // Step 4: Solve the LU decomposition
660 // The lu inplace lu decomposition computed with KokkosBatched
661 // There is also a convenience routine for the LU solve, but it
662 // uses Trsm under the hood, which is quite a bit slower
663 auto matStart = matrixOffsets(batch);
664 BatchMatrix<MemorySpace> A(&matrices(matStart), inSize, inSize);
665
666 // Forward substitution: solve L * y = b and
667 KokkosBatched::Trsv<
668 MemberType,
669 KokkosBatched::Uplo::Lower,
670 KokkosBatched::Trans::NoTranspose,
671 KokkosBatched::Diag::Unit,
672 KokkosBatched::Mode::Team,
673 KokkosBatched::Algo::Trsv::Blocked>::invoke(team, 1.0, A, in);
674
675 team.team_barrier();
676
677 // Backward substitution: solve U * x = y
678 KokkosBatched::Trsv<
679 MemberType,
680 KokkosBatched::Uplo::Upper,
681 KokkosBatched::Trans::NoTranspose,
682 KokkosBatched::Diag::NonUnit,
683 KokkosBatched::Mode::Team,
684 KokkosBatched::Algo::Trsv::Blocked>::invoke(team, 1.0, A, in);
685 team.team_barrier();
686
687 // Step 5: Apply the output operator
688 // If we have the evaluation operator, we use it (might be slower though)
689 if constexpr (evaluation_op_available) {
690 // Step 5a: Allocate and zero out a local result vector (more of a safety feature)
691 ScratchVector res(team.team_scratch(1), outSize);
692 Kokkos::parallel_for(
693 Kokkos::TeamThreadRange(team, outSize),
694 [&](int i) { res(i) = 0; });
695 team.team_barrier();
696
697 // Step 5b: Multiply by the evaluation operator
698 // the evaluation matrix
699 auto startEval = evalOffsets(batch);
700 BatchMatrix<MemorySpace> eval(&evalMat(startEval), outSize, inSize);
701 // res := 1.0 * eval * b + 0.0 * res
702 KokkosBlas::Experimental::Gemv<
703 KokkosBlas::Mode::Team,
704 KokkosBlas::Algo::Gemv::Blocked>::invoke(team, 'N', 1.0, eval, in, 0.0, res);
705
706 team.team_barrier();
707
708 // Step 5c: write the weightes result back to the global vector
709 // potentially applying the polynomial term alongside
710 Kokkos::parallel_for(
711 Kokkos::TeamThreadRange(team, outSize),
712 [&](int i) {
713 auto globalID = globalOutIDs(i + outBegin);
714 double sum = res(i);
715 // Add polynomial portion to the output data: out += V * p
716 if constexpr (polynomial) {
717 // The "1"/constant term is the last value in the result
718 // dim is here matrixCols - 1
719 sum += qrCoeffs[dim];
720 // ... and the linear polynomial
721 for (int d = 0; d < dim; ++d) {
722 sum += outMesh(globalID, d) * qrCoeffs[d];
723 }
724 }
725 auto w = normalizedWeights(i + outBegin);
726 Kokkos::atomic_add(&out(globalID), sum * w);
727 }); // TeamThreadRange
728 } else {
729 // Alternative approach: do all in one go:
730 // Step 5a: each thread takes care of one output vertex
731 Kokkos::parallel_for(
732 Kokkos::TeamThreadRange(team, outSize), [&](int r) {
733 auto globalID = globalOutIDs(r + outBegin);
734
735 // we first extract the vertex coordinates
736 Kokkos::Array<double, 3> outVertex = {0., 0., 0.};
737 for (int d = 0; d < dim; ++d) {
738 outVertex[d] = outMesh(globalID, d);
739 }
740
741 // Step 5b: The matrix vector multiplication res = A * in
742 // Accumulate partial dot product in a thread-parallel manner
743 double sum = 0.0;
744 Kokkos::parallel_reduce(
745 Kokkos::ThreadVectorRange(team, inSize),
746 [&](int c, double &localSum) {
747 // compute the local output coefficients
748 double dist = 0;
749 for (int d = 0; d < dim; ++d) {
750 double diff = outVertex[d] - localInMesh(c, d);
751 dist += diff * diff;
752 }
753 dist = Kokkos::sqrt(dist);
754 // Evaluate the RBF
755 double val = f(dist, rbf_params);
756 localSum += val * in(c);
757 },
758 sum); // ThreadVectorRange
759
760 // Step 5c: if we have the polynomial as well, we have to apply it here
761 if constexpr (polynomial) {
762 // The "1"/constant term is the last value in the result
763 // dim is here matrixCols - 1
764 sum += qrCoeffs[dim];
765 // ... and the linear polynomial
766 for (int d = 0; d < dim; ++d) {
767 sum += outVertex[d] * qrCoeffs[d];
768 }
769 }
770 // Step 5d: Store final (weighted result) in the global out vector
771 auto w = normalizedWeights(r + outBegin);
772 Kokkos::atomic_add(&out(globalID), sum * w);
773 }); // End TeamThreadRange
774 } // end if-merged-evaluation
775 // End Team parallel loop
776 };
777
778 // We allocate one vector for indata and once for outdata
779 // We need at least four entries for the polynomial, seems unlikely for maxInClusterSize to be lower
780 // but we should be certain
781 auto inBytes = ScratchVector::shmem_size(std::max(4, maxInClusterSize));
782 auto outBytes = ScratchVector::shmem_size(maxOutClusterSize);
783 if (!evaluation_op_available || polynomial) {
784 // and additional storage if we have the polynomial
785 inBytes = 4 * inBytes;
786 }
787 // We use the outBytes only for the per-cluster output vector, which
788 // we don't need if we evaluate everything on the fly
789 if (!evaluation_op_available) {
790 outBytes = 0;
791 }
792
793 // We put the solution and the in data values into shared memory
794 // TODO: Avoid the duplicate memory definitions here, currently needed as we
795 // cannot change the Kokkos::AUTO to the actual recommendataion later
796 auto tmpPol = TeamPolicy(nCluster, Kokkos::AUTO)
797 .set_scratch_size(
798 /* level = */ 0, Kokkos::PerTeam(inBytes))
799 .set_scratch_size(
800 /* level = */ 1, Kokkos::PerTeam(outBytes));
801
802 auto teamSize = impl::findTeamSize<ExecSpace>(avgInClusterSize, impl::Pow2Mode::Smaller, kernel, tmpPol);
803
804 auto policy = TeamPolicy(nCluster, teamSize)
805 .set_scratch_size(
806 /* level = */ 0, Kokkos::PerTeam(inBytes))
807 .set_scratch_size(
808 /* level = */ 1, Kokkos::PerTeam(outBytes));
809
810 Kokkos::parallel_for("do_batched_solve", policy, kernel);
811}
812
817// note: since input and output meshes are here swapped, the in data vector belongs to all output data structures
818template <bool polynomial, bool evaluation_op_available, typename EvalFunctionType, typename MemorySpace>
820 int nCluster,
821 int dim,
822 int avgInClusterSize,
823 int maxInClusterSize,
824 int maxOutClusterSize,
825 EvalFunctionType f,
826 const VectorOffsetView<MemorySpace> &rhsOffsets,
827 const GlobalIDView<MemorySpace> &globalRhsIDs,
828 VectorView<MemorySpace> rhsdst, // output data, but belongs to input mesh/structures
829 const MatrixOffsetView<MemorySpace> &matrixOffsets,
830 const VectorView<MemorySpace> &matrices,
831 const VectorView<MemorySpace> &normalizedWeights,
832 const MatrixOffsetView<MemorySpace> &evalOffsets,
833 const VectorView<MemorySpace> &evalMat,
834 const VectorOffsetView<MemorySpace> &outOffsets,
835 const GlobalIDView<MemorySpace> &globalOutIDs,
836 VectorView<MemorySpace> src, // input data, but belongs to output mesh/structures
837 // For the polynomial required in addition
838 const MeshView<MemorySpace> &inMesh,
839 const MeshView<MemorySpace> &outMesh,
840 const VectorView<MemorySpace> &qrMatrix,
841 const VectorView<MemorySpace> &qrTau,
842 const PivotView<MemorySpace> &qrP)
843{
844 using ExecSpace = typename MemorySpace::execution_space;
845 using TeamPolicy = Kokkos::TeamPolicy<ExecSpace>;
846 using MemberType = typename TeamPolicy::member_type;
847
848 using ScratchSpace = typename MemorySpace::scratch_memory_space;
849 // Layout is important for how we use these matrices: we need to ensure that cols are contiguous in memory
850 // We use the scratch memory for the input data and potentially for workspace required for the
851 // polynomial or for the input mesh in case we have to compute the evaluation on the fly
852 using ScratchView1d = Kokkos::View<double *[1], Kokkos::LayoutLeft, ScratchSpace, UnmanagedMemory>;
853 using ScratchView4d = Kokkos::View<double *[4], Kokkos::LayoutLeft, ScratchSpace, UnmanagedMemory>;
854 using ScratchView3d = Kokkos::View<double *[3], Kokkos::LayoutLeft, ScratchSpace, UnmanagedMemory>;
855 using ScratchVector = Kokkos::View<double *, ScratchSpace, UnmanagedMemory>;
856 using ScratchMatrix4 = std::conditional_t<!evaluation_op_available || polynomial, ScratchView4d, ScratchView1d>;
857 using ScratchMatrix3 = std::conditional_t<!evaluation_op_available || polynomial, ScratchView3d, ScratchView1d>;
858 using ScratchMesh = Kokkos::View<double **, Kokkos::LayoutRight, ScratchSpace, UnmanagedMemory>;
859
860 const auto rbf_params = f.getFunctionParameters();
861 // We define the lambda here such that we can query the recommended team size from Kokkos
862 // Launch policy is then handled below
863 auto kernel = KOKKOS_LAMBDA(const MemberType &team)
864 {
865 // Required for correct capturing (mostly by device compilers), as these variables are only conditionally used further down
866 impl::capture_conditional_variables(dim, qrMatrix, qrTau, qrP, inMesh, outMesh, evalOffsets, evalMat, globalOutIDs, f, rbf_params, normalizedWeights, src, globalRhsIDs);
867
868 // Step 1: Define some pointers
869 const int batch = team.league_rank();
870
871 const auto inBegin = rhsOffsets(batch);
872 const int inSize = rhsOffsets(batch + 1) - inBegin;
873
874 const auto outBegin = outOffsets(batch);
875 const int outSize = outOffsets(batch + 1) - outBegin;
876
877 // Step 2: Define the data structures to work with
878 // The scratch memory (shared memory for the device)
879 ScratchMatrix3 work(team.team_scratch(0), Kokkos::max(4, inSize));
880 auto Au = Kokkos::subview(work, std::pair<int, int>(0, inSize), 0);
881
882 // Step 3: Extract the input data using the PU weights and
883 // compute the matrix vector product A^T * inputData
884
885 ScratchMatrix4 localIn(team.team_scratch(1), outSize);
886 auto in = Kokkos::subview(localIn, std::pair<int, int>(0, outSize), 0);
887 ScratchMesh localMesh;
888
889 if constexpr (!evaluation_op_available || polynomial)
890 localMesh = ScratchMesh(&localIn(0, 1), outSize, dim);
891
892 // Step 3a: Extract input data
893 Kokkos::parallel_for(
894 Kokkos::TeamThreadRange(team, outSize), [&](int r) {
895 auto globalID = globalOutIDs(r + outBegin);
896 auto w = normalizedWeights(r + outBegin);
897 in(r) = src(globalID) * w;
898 // Cache the mesh, if needed
899 if constexpr (!evaluation_op_available || polynomial) {
900 for (int d = 0; d < dim; ++d)
901 localMesh(r, d) = outMesh(globalID, d);
902 }
903 });
904 team.team_barrier();
905
906 // ... minimal compute variant
907 if constexpr (evaluation_op_available) {
908 auto startEval = evalOffsets(batch);
909 BatchMatrix<MemorySpace> eval(&evalMat(startEval), outSize, inSize);
910 // Step 3b: Au := 1.0 * eval^T * in + 0.0 * Au
911 KokkosBlas::Experimental::Gemv<
912 KokkosBlas::Mode::Team,
913 KokkosBlas::Algo::Gemv::Blocked>::invoke(team, 'T', 1.0, eval, in, 0.0, Au);
914
915 } else {
916 // ...minimal memory variant
917
918 // Variant 1: outer parallel_for over inSize and inner reduction over outSize
919 // -> each thread is responsible for one inMesh vertex
920 // -> suboptimal in terms of data access
921
922 // The inner loop runs cleanly die to the cached mesh
923 // Alternative would be to have the mesh access in the inner loop
924 // Step 3b: compute eval^T * in on-the-fly
925 Kokkos::parallel_for(
926 Kokkos::TeamThreadRange(team, inSize), [&](int r) {
927 auto globalRhsID = globalRhsIDs(r + inBegin);
928 Kokkos::Array<double, 3> vertex = {0., 0., 0.};
929 for (int d = 0; d < dim; ++d) {
930 vertex[d] = inMesh(globalRhsID, d);
931 }
932
933 double sum = 0.0;
934 Kokkos::parallel_reduce(
935 Kokkos::ThreadVectorRange(team, outSize),
936 [&](int c, double &localSum) {
937 // compute the local output coefficients
938 double dist = 0;
939 for (int d = 0; d < dim; ++d) {
940 double diff = vertex[d] - localMesh(c, d);
941 dist += diff * diff;
942 }
943 dist = Kokkos::sqrt(dist);
944 // Evaluate the RBF
945 double val = f(dist, rbf_params);
946 localSum += val * in(c);
947 },
948 sum); // ThreadVectorRange
949 Au(r) = sum;
950 });
951
952 // Variant 2 (not followed here): outer parallel reduction over outsize and inner parallel_for over inSize
953 // (not even sure if Kokkos would allow such a dynamic reduction)
954 // -> better data access pattern, because we can cache the inMesh in shared memory,
955 // but the the parallelization creates a dependency across the inner loops
956 }
957 team.team_barrier();
958
959 // Step 4: Solve for the RBF coefficients
960 // The inplace lu decomposition computed with KokkosBatched
961 // There is also a convenience routine for the LU solve, but it
962 // uses Trsm under the hood, which is quite a bit slower
963 auto matStart = matrixOffsets(batch);
964 BatchMatrix<MemorySpace> A(&matrices(matStart), inSize, inSize);
965
966 // Forward substitution: solve L * y = b and
967 KokkosBatched::Trsv<
968 MemberType,
969 KokkosBatched::Uplo::Lower,
970 KokkosBatched::Trans::NoTranspose,
971 KokkosBatched::Diag::Unit,
972 KokkosBatched::Mode::Team,
973 KokkosBatched::Algo::Trsv::Blocked>::invoke(team, 1.0, A, Au);
974
975 team.team_barrier();
976
977 // Backward substitution: solve U * x = y
978 KokkosBatched::Trsv<
979 MemberType,
980 KokkosBatched::Uplo::Upper,
981 KokkosBatched::Trans::NoTranspose,
982 KokkosBatched::Diag::NonUnit,
983 KokkosBatched::Mode::Team,
984 KokkosBatched::Algo::Trsv::Blocked>::invoke(team, 1.0, A, Au);
985 team.team_barrier();
986
987 // Step 5: take care of the polynomial part, the solution is stored in qrSolution
988 ScratchView1d qrSolution;
989 if constexpr (polynomial) {
990 qrSolution = Kokkos::subview(work, std::pair<int, int>(0, inSize), std::pair<int, int>(1, 2));
991 // Step 5a: compute polynomial contribution: V^T * in
992 if constexpr (polynomial) {
993 Kokkos::parallel_reduce(
994 Kokkos::TeamThreadRange(team, outSize),
995 [&](const int c, double &s0, double &s1, double &s2, double &s3) {
996 const double tmp = in(c);
997
998 // Making the reductors more elegant is not easily possible, as they need static sizes
999 s0 += localMesh(c, 0) * tmp;
1000 s1 += localMesh(c, 1) * tmp;
1001 if (dim == 3)
1002 s2 += localMesh(c, 2) * tmp;
1003 s3 += tmp;
1004 },
1005 qrSolution(0, 0), qrSolution(1, 0), qrSolution(2, 0), qrSolution(3, 0));
1006 }
1007 team.team_barrier();
1008
1009 // Step 5b: compute the matrix vector product epsilon = Q^T * Au (in Eigen Au = out)
1010 // Eigen::MatrixXd epsilon = _matrixV.transpose() * inputData;
1011 auto tmp = Kokkos::subview(work, Kokkos::ALL(), 2);
1012
1013 Kokkos::parallel_reduce(
1014 Kokkos::TeamThreadRange(team, inSize),
1015 [&](const int k, double &s0, double &s1, double &s2, double &s3) {
1016 const double val = Au(k);
1017 auto globalID = globalRhsIDs(k + inBegin);
1018
1019 s0 += inMesh(globalID, 0) * val;
1020 s1 += inMesh(globalID, 1) * val;
1021 if (dim == 3)
1022 s2 += inMesh(globalID, 2) * val;
1023 s3 += val;
1024 },
1025 tmp(0), tmp(1), tmp(2), tmp(3));
1026
1027 // Step 5c: Subtract the result
1028 Kokkos::single(Kokkos::PerTeam(team), [&] {
1029 for (int d = 0; d < dim; ++d) {
1030 tmp(d) -= qrSolution(d, 0);
1031 }
1032 tmp(dim) = tmp(3) - qrSolution(3, 0);
1033 });
1034
1035 team.team_barrier();
1036
1037 // Step 5d: Solve the QR system to compute the polynomial coefficients
1038 const int matrixCols = dim + 1;
1039 const offset_1d_type qrBegin = inBegin * matrixCols;
1040 const offset_1d_type tauBegin = batch * matrixCols;
1041 const offset_1d_type PBegin = batch * (matrixCols + 1);
1042 const int rank = qrP(PBegin + matrixCols);
1043
1044 BatchMatrix<MemorySpace> qr(&qrMatrix(qrBegin), inSize, matrixCols);
1045 BatchVector<double *, MemorySpace> tau(&qrTau(tauBegin), matrixCols);
1046 BatchVector<int *, MemorySpace> P(&qrP(PBegin), matrixCols);
1047
1048 // Needed here
1049 Kokkos::parallel_for(
1050 Kokkos::TeamThreadRange(team, inSize), [&](int i) { qrSolution(i, 0) = 0; });
1051 team.team_barrier();
1052
1053 if (team.team_rank() == 0) {
1054
1055 // _qrMatrixQ.transpose().solve(
1056 auto R = Kokkos::subview(qr, std::pair<int, int>(0, rank), std::pair<int, int>(0, rank));
1057 auto rhs_re = Kokkos::subview(qrSolution, std::pair<int, int>(0, matrixCols), 0);
1058
1059 for (int i = 0; i < matrixCols; ++i) {
1060 rhs_re(i) = tmp(i);
1061 }
1062 KokkosBatched::TeamVectorApplyPivot<MemberType,
1063 KokkosBatched::Side::Left,
1064 KokkosBatched::Direct::Forward>::invoke(team, P, rhs_re);
1065
1066 auto rhs_r = Kokkos::subview(qrSolution, std::pair<int, int>(0, rank), 0);
1067
1068 // Solve (R^T) * rhs_r = rhs_r in-place
1069 KokkosBatched::Trsv<
1070 MemberType,
1071 KokkosBatched::Uplo::Upper,
1072 KokkosBatched::Trans::Transpose,
1073 KokkosBatched::Diag::NonUnit,
1074 KokkosBatched::Mode::Serial,
1075 KokkosBatched::Algo::Trsv::Unblocked>::invoke(team, 1.0, R, rhs_r);
1076
1077 // Use ApplyQ with NoTranspose to apply Q
1078 KokkosBatched::ApplyQ<MemberType,
1079 KokkosBatched::Side::Left,
1080 KokkosBatched::Trans::NoTranspose,
1081 KokkosBatched::Mode::Serial,
1082 KokkosBatched::Algo::ApplyQ::Unblocked>::invoke(team, qr, tau, qrSolution, tmp);
1083 }
1084
1085 team.team_barrier();
1086 } // end polynomial part
1087
1088 // Step 6 (final): Accumulate the result into the output vector, without weights in this case
1089 Kokkos::parallel_for(
1090 Kokkos::TeamThreadRange(team, inSize), [&](int i) {
1091 auto globalID = globalRhsIDs(i + inBegin);
1092 double val = Au(i);
1093 if constexpr (polynomial) {
1094 // qrSolution carries the solution of the polynomial contribution, it has only a single column and inSize rows
1095 val -= qrSolution(i, 0);
1096 }
1097 Kokkos::atomic_add(&rhsdst(globalID), val);
1098 });
1099 };
1100
1101 // We allocate one vector for indata and once for outdata
1102 // We need at least four entries for the polynomial, seems unlikely for maxInClusterSize to be lower
1103 // but we should be certain
1104 auto inBytes = ScratchVector::shmem_size(std::max(4, maxInClusterSize));
1105 auto outBytes = ScratchVector::shmem_size(maxOutClusterSize);
1106 if (polynomial) {
1107 // We use one column for the solution, and two for the QR decomposition (intermediate results and final solution)
1108 inBytes = 3 * inBytes;
1109 }
1110
1111 // In the conservative case, we cache the outMesh and the outvector, if we have to compute the evaluation on-the-fly
1112 // or in case we have a polynomial
1113 if (!evaluation_op_available || polynomial) {
1114 outBytes = 4 * outBytes;
1115 }
1116
1117 // We put the solution and the in data values into shared memory
1118 // TODO: Avoid the duplicate memory definitions here, currently needed as we
1119 // cannot change the Kokkos::AUTO to the actual recommendation later
1120 auto tmpPol = TeamPolicy(nCluster, Kokkos::AUTO)
1121 .set_scratch_size(
1122 /* level = */ 0, Kokkos::PerTeam(inBytes))
1123 .set_scratch_size(
1124 /* level = */ 1, Kokkos::PerTeam(outBytes));
1125
1126 auto teamSize = impl::findTeamSize<ExecSpace>(avgInClusterSize, impl::Pow2Mode::Smaller, kernel, tmpPol);
1127
1128 auto policy = TeamPolicy(nCluster, teamSize)
1129 .set_scratch_size(
1130 /* level = */ 0, Kokkos::PerTeam(inBytes))
1131 .set_scratch_size(
1132 /* level = */ 1, Kokkos::PerTeam(outBytes));
1133
1134 Kokkos::parallel_for("do_batched_conservative_solve", policy, kernel);
1135}
1136
1137// Currently not used at all, solves the QR decomposition used for the polynomial
1138// but cannot be applied standalone, as we subtract the polynomial part on a per-cluster
1139// basis from the inData. Still useful for development purposes
1140template <typename MemorySpace>
1141void do_qr_solve(int nCluster,
1142 int dim,
1143 int maxInClusterSize,
1145 GlobalIDView<MemorySpace> globalInIDs,
1147 MeshView<MemorySpace> inMesh,
1148 VectorView<MemorySpace> qrMatrix,
1151 const VectorView<MemorySpace> weights,
1153 GlobalIDView<MemorySpace> globalOutIDs,
1155 MeshView<MemorySpace> outMesh)
1156{
1157 using TeamPolicy = Kokkos::TeamPolicy<MemorySpace>;
1158 using MemberType = typename TeamPolicy::member_type;
1159 using ScratchView = Kokkos::View<double *[2], typename MemorySpace::scratch_memory_space, UnmanagedMemory>;
1160
1161 // Used for the inData solution vector and the QR solving
1162 auto scratchSize = ScratchView::shmem_size(2 * maxInClusterSize);
1163 Kokkos::parallel_for("do_qr_solve", TeamPolicy(nCluster, Kokkos::AUTO).set_scratch_size(
1164 /* level = */ 0, Kokkos::PerTeam(scratchSize)),
1165 KOKKOS_LAMBDA(const MemberType &team) {
1166 // Step 1: Some pointers we need
1167 const int batch = team.league_rank();
1168 // Ranges
1169 const auto inBegin = inOffsets(batch);
1170 const auto inEnd = inOffsets(batch + 1);
1171 const int inSize = inEnd - inBegin;
1172 const auto outBegin = outOffsets(batch);
1173 const auto outEnd = outOffsets(batch + 1);
1174 const int outSize = outEnd - outBegin;
1175
1176 // Step 2: Collect the inData
1177 ScratchView tmp(team.team_scratch(0), inSize, 2);
1178 auto in = Kokkos::subview(tmp, Kokkos::ALL, 0);
1179 auto work = Kokkos::subview(tmp, Kokkos::ALL, 1); // work size is just a guess at the moment
1180
1181 Kokkos::parallel_for(
1182 Kokkos::TeamThreadRange(team, inSize),
1183 [&](int i) {
1184 auto globalID = globalInIDs(i + inBegin);
1185 in(i) = inData(globalID);
1186 });
1187
1188 team.team_barrier();
1189
1190 // Step 3: Gather the QR data structures
1191 const int matrixCols = dim + 1;
1192 const offset_1d_type matrixBegin = inBegin * matrixCols;
1193 const offset_1d_type tauBegin = batch * matrixCols;
1194 const offset_1d_type PBegin = batch * (matrixCols + 1);
1195 const int rank = qrP(PBegin + matrixCols);
1196
1197 BatchMatrix<MemorySpace> qr(&qrMatrix(matrixBegin), inSize, matrixCols);
1198 BatchVector<double *, MemorySpace> tau(&qrTau(tauBegin), matrixCols);
1199 BatchVector<int *, MemorySpace> P(&qrP(PBegin), matrixCols);
1200
1201 // Step 4: Solve the linear least-square system A x = b, in our case Q R P^T x = in
1202
1203 // Step 4a: Apply Q on the left of in, i.e., y = Q^T * in
1204 KokkosBatched::TeamVectorApplyQ<MemberType,
1205 KokkosBatched::Side::Left,
1206 KokkosBatched::Trans::Transpose,
1207 KokkosBatched::Algo::ApplyQ::Unblocked>::invoke(team, qr, tau, in, work);
1208 team.team_barrier();
1209
1210 auto in_r = Kokkos::subview(in, Kokkos::pair<int, int>(0, rank));
1211 auto R = Kokkos::subview(qr, Kokkos::pair<int, int>(0, rank), Kokkos::pair<int, int>(0, rank));
1212
1213 // Step 4b: Solve triangular solve R z = y
1214 KokkosBatched::Trsv<
1215 MemberType,
1216 KokkosBatched::Uplo::Upper,
1217 KokkosBatched::Trans::NoTranspose,
1218 KokkosBatched::Diag::NonUnit,
1219 KokkosBatched::Mode::Team,
1220 KokkosBatched::Algo::Trsv::Blocked>::invoke(team, 1.0, R, in_r);
1221 team.team_barrier();
1222
1223 auto res = Kokkos::subview(in, Kokkos::pair<int, int>(0, matrixCols));
1224
1225 // Steo 4c: zero out the entries which are not within the rank region
1226 Kokkos::parallel_for(
1227 Kokkos::TeamThreadRange(team, rank, matrixCols), [&](int i) { res(i) = 0; });
1228
1229 team.team_barrier();
1230
1231 // Step 4d: Apply pivoting x = P z
1232 KokkosBatched::TeamVectorApplyPivot<MemberType,
1233 KokkosBatched::Side::Left,
1234 KokkosBatched::Direct::Backward>::invoke(team, P, res);
1235 team.team_barrier();
1236
1237 // Step 5: Subtract polynomial portion from the input data: in -= Q * p
1238 // threading over inSize
1239 Kokkos::parallel_for(
1240 Kokkos::TeamThreadRange(team, inBegin, inEnd),
1241 [&](int i) {
1242 auto globalID = globalInIDs(i);
1243
1244 // The "1"/constant term is the last value in the result
1245 double tmp = res(matrixCols - 1);
1246 // ... and the linear polynomial
1247 for (int d = 0; d < dim; ++d)
1248 tmp += inMesh(globalID, d) * res(d);
1249
1250 Kokkos::atomic_sub(&inData(globalID), tmp);
1251 });
1252 // no barrier needed here
1253
1254 // Step 6: Add polynomial portion to the output data: out += V * p
1255 // threading over outSize
1256 Kokkos::parallel_for(
1257 Kokkos::TeamThreadRange(team, outBegin, outEnd),
1258 [&](offset_1d_type i) {
1259 auto globalID = globalOutIDs(i);
1260
1261 // The "1"/constant term is the last value in the result
1262 double tmp = res(matrixCols - 1);
1263 // ... and the linear polynomial
1264 for (int d = 0; d < dim; ++d)
1265 tmp += outMesh(globalID, d) * res(d);
1266
1267 // and the weight as usual
1268 double w = weights(i);
1269 Kokkos::atomic_add(&outData(globalID), tmp * w);
1270 });
1271 // end parallel_for
1272 });
1273}
1274} // namespace precice::mapping::kernel
#define PRECICE_ASSERT(...)
Definition assertion.hpp:85
Wendland radial basis function with compact support.
RadialBasisParameters getFunctionParameters() const
auto findTeamSize(int avgWork, const Pow2Mode mode, const FunctorType &functor, const Policy &policy)
int powerOfTwo(int n, const Pow2Mode mode)
KOKKOS_INLINE_FUNCTION constexpr void capture_conditional_variables(const Args &...)
void do_batched_qr(int nCluster, int dim, int avgClusterSize, int maxClusterSize, VectorOffsetView< MemorySpace > inOffsets, GlobalIDView< MemorySpace > globalInIDs, MeshView< MemorySpace > inMesh, VectorView< MemorySpace > qrMatrix, VectorView< MemorySpace > qrTau, PivotView< MemorySpace > qrP)
void do_batched_assembly(int nCluster, int dim, int avgClusterSize, EvalFunctionType f, const VectorOffsetView< MemorySpace > &inOffsets, const GlobalIDView< MemorySpace > &globalInIDs, const MeshView< MemorySpace > &inCoords, const VectorOffsetView< MemorySpace > &targetOffsets, const GlobalIDView< MemorySpace > &globalTargetIDs, const MeshView< MemorySpace > &targetCoords, const MatrixOffsetView< MemorySpace > &matrixOffsets, VectorView< MemorySpace > matrices)
void do_batched_conservative_solve(int nCluster, int dim, int avgInClusterSize, int maxInClusterSize, int maxOutClusterSize, EvalFunctionType f, const VectorOffsetView< MemorySpace > &rhsOffsets, const GlobalIDView< MemorySpace > &globalRhsIDs, VectorView< MemorySpace > rhs, const MatrixOffsetView< MemorySpace > &matrixOffsets, const VectorView< MemorySpace > &matrices, const VectorView< MemorySpace > &normalizedWeights, const MatrixOffsetView< MemorySpace > &evalOffsets, const VectorView< MemorySpace > &evalMat, const VectorOffsetView< MemorySpace > &outOffsets, const GlobalIDView< MemorySpace > &globalOutIDs, VectorView< MemorySpace > out, const MeshView< MemorySpace > &inMesh, const MeshView< MemorySpace > &outMesh, const VectorView< MemorySpace > &qrMatrix, const VectorView< MemorySpace > &qrTau, const PivotView< MemorySpace > &qrP)
bool compute_weights(const int nCluster, const int avgOutClusterSize, const offset_1d_type nWeights, const int nMeshVertices, const int dim, VectorOffsetView< MemorySpace > offsets, MeshView< MemorySpace > centers, GlobalIDView< MemorySpace > globalIDs, MeshView< MemorySpace > mesh, const CompactPolynomialC2 &w, VectorView< MemorySpace > normalizedWeights)
void do_input_assembly(int nCluster, int dim, int avgClusterSize, int maxInClusterSize, EvalFunctionType f, const VectorOffsetView< MemorySpace > &inOffsets, const GlobalIDView< MemorySpace > &globalInIDs, const MeshView< MemorySpace > &inCoords, const MatrixOffsetView< MemorySpace > &matrixOffsets, VectorView< MemorySpace > matrices)
void do_qr_solve(int nCluster, int dim, int maxInClusterSize, VectorOffsetView< MemorySpace > inOffsets, GlobalIDView< MemorySpace > globalInIDs, VectorView< MemorySpace > inData, MeshView< MemorySpace > inMesh, VectorView< MemorySpace > qrMatrix, VectorView< MemorySpace > qrTau, PivotView< MemorySpace > qrP, const VectorView< MemorySpace > weights, VectorOffsetView< MemorySpace > outOffsets, GlobalIDView< MemorySpace > globalOutIDs, VectorView< MemorySpace > outData, MeshView< MemorySpace > outMesh)
void do_batched_solve(int nCluster, int dim, int avgInClusterSize, int maxInClusterSize, int maxOutClusterSize, EvalFunctionType f, const VectorOffsetView< MemorySpace > &rhsOffsets, const GlobalIDView< MemorySpace > &globalRhsIDs, VectorView< MemorySpace > rhs, const MatrixOffsetView< MemorySpace > &matrixOffsets, const VectorView< MemorySpace > &matrices, const VectorView< MemorySpace > &normalizedWeights, const MatrixOffsetView< MemorySpace > &evalOffsets, const VectorView< MemorySpace > &evalMat, const VectorOffsetView< MemorySpace > &outOffsets, const GlobalIDView< MemorySpace > &globalOutIDs, VectorView< MemorySpace > out, const MeshView< MemorySpace > &inMesh, const MeshView< MemorySpace > &outMesh, const VectorView< MemorySpace > &qrMatrix, const VectorView< MemorySpace > &qrTau, const PivotView< MemorySpace > &qrP)
void compute_offsets(const VectorOffsetView< MemorySpace > src1, const VectorOffsetView< MemorySpace > src2, MatrixOffsetView< MemorySpace > dst, int nCluster)
void do_batched_lu(int nCluster, int avgClusterSize, const MatrixOffsetView< MemorySpace > &matrixOffsets, VectorView< MemorySpace > matrices)
Kokkos::View< T, MemorySpace, UnmanagedMemory > BatchVector
Kokkos::View< double **, MemorySpace, UnmanagedMemory > BatchMatrix
ExecutionSpace::size_type offset_2d_type
Kokkos::View< int *, MemorySpace > PivotView
Kokkos::View< offset_2d_type *, MemorySpace > MatrixOffsetView
Kokkos::View< offset_1d_type *, MemorySpace > VectorOffsetView
Kokkos::View< VertexID *, MemorySpace > GlobalIDView
ExecutionSpace::size_type offset_1d_type
Kokkos::View< double *, MemorySpace > VectorView
Kokkos::View< double **, Kokkos::LayoutRight, MemorySpace > MeshView
constexpr T pow_int(const T base)
Computes the power of a given number by an integral exponent given at compile time,...
Definition math.hpp:22
provides Mesh, Data and primitives.
Wrapper struct that is used to transfer RBF-specific parameters to the GPU.