/home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/physics/integration/quadrature_integrator.hh Source File#

DiFfRG: /home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/physics/integration/quadrature_integrator.hh Source File
DiFfRG
Discretization Framework for functional Renormalization Group flows
quadrature_integrator.hh
Go to the documentation of this file.
1#pragma once
2
3// DiFfRG
14
15// std
16#include <cstdio>
17#include <string>
18
19namespace DiFfRG
20{
27 template <typename Coordinates>
28 inline constexpr bool has_cacheable_positions_v = requires(const Coordinates &c) {
29 c.to_string();
30 c.forward(c.from_linear_index(size_t(0)));
31 };
32
41 template <int dim, typename NT, typename KERNEL, typename ExecutionSpace>
42 requires(dim > 0)
44 {
45 public:
49 using ctype = typename get_type::ctype<NT>;
53 using execution_space = ExecutionSpace;
54
55 QuadratureIntegrator(QuadratureProvider &quadrature_provider, const std::array<size_t, dim> &_grid_size,
56 const std::array<ctype, dim> &grid_min, const std::array<ctype, dim> &grid_max,
57 const std::array<QuadratureType, dim> &quadrature_type)
58 : space(quadrature_provider.template next_execution_space<ExecutionSpace>()),
59 quadrature_provider(quadrature_provider)
60 {
61 for (size_t i = 0; i < dim; ++i) {
62 grid_size[i] = _grid_size[i];
63
64 nodes[i] = quadrature_provider.template nodes<ctype, typename ExecutionSpace::memory_space>(grid_size[i],
65 quadrature_type[i]);
66 weights[i] = quadrature_provider.template weights<ctype, typename ExecutionSpace::memory_space>(
67 grid_size[i], quadrature_type[i]);
68 }
69 set_grid_extents(grid_min, grid_max);
70 }
71
72 void set_grid_extents(const std::array<ctype, dim> &grid_min, const std::array<ctype, dim> &grid_max)
73 {
74 for (size_t i = 0; i < dim; ++i) {
75 grid_extents[0][i] = grid_min[i];
76 grid_extents[1][i] = grid_max[i];
77
78 grid_start[i] = grid_extents[0][i];
79 grid_scale[i] = (grid_extents[1][i] - grid_extents[0][i]);
80 }
81 }
82
83 template <typename... T>
84 requires is_valid_kernel<NT, KERNEL, ctype, dim, T...>
85 void get(NT &dest, const T &...t) const
86 {
87 // create an execution space
88 ExecutionSpace space;
89
90 if (!m_result_views_initialized) {
91 m_result_view = Kokkos::View<NT, typename ExecutionSpace::memory_space>("result");
92 m_result_host = Kokkos::create_mirror_view(m_result_view);
93 m_result_views_initialized = true;
94 }
95 get(space, m_result_view, t...);
96 Kokkos::deep_copy(space, m_result_host, m_result_view);
97 space.fence();
98 dest = m_result_host();
99 }
100
101 template <typename OT, typename... T>
102 requires(!std::is_same_v<OT, NT> && is_valid_kernel<NT, KERNEL, ctype, dim, T...>)
103 void get(OT &dest, const T &...t) const
104 {
105 ExecutionSpace space;
106 get(space, dest, t...);
107 }
108
109 template <typename OT, typename... T>
110 requires(!std::is_same_v<OT, NT> && is_valid_kernel<NT, KERNEL, ctype, dim, T...>)
111 void get(ExecutionSpace &space, OT &dest, const T &...t) const
112 {
113 const auto args = device::make_tuple(t...);
114
115 const auto &n = nodes;
116 const auto &w = weights;
117 const auto &start = grid_start;
118 const auto &scale = grid_scale;
119
120 auto functor = KOKKOS_LAMBDA(const device::array<size_t, dim> &idx, NT &update)
121 {
123 ctype weight = 1;
124 bool is_first = true;
125 for (size_t i = 0; i < dim; ++i) {
126 x[i] = Kokkos::fma(scale[i], n[i][idx[i]], start[i]);
127 weight *= w[i][idx[i]] * scale[i];
128 is_first &= idx[i] == 0;
129 }
130 device::apply([&](const auto &...iargs) { update += weight * KERNEL::kernel(iargs...); },
131 device::tuple_cat(x, args));
132 device::apply([&](const auto &...iargs) { update += is_first ? KERNEL::constant(iargs...) : NT(0); }, args);
133 };
134
135 Kokkos::parallel_reduce("QuadratureIntegral_" + std::to_string(dim) + "D", // name of the kernel
136 make_kokkos_nd_range<dim, ExecutionSpace>(space, {0}, grid_size),
138 }
139
140 template <typename view_type, typename Coordinates, typename... Args>
141 void map(ExecutionSpace &space, const view_type integral_view, const Coordinates &coordinates, const Args &...args)
142 {
144 extents[0] = integral_view.size();
145 for (int i = 0; i < dim; ++i)
146 extents[1 + i] = grid_size[i];
147
148 // Reuse cached view if large enough, otherwise reallocate (grow-only)
149 {
150 bool needs_realloc = false;
151 for (size_t i = 0; i < 1 + dim; ++i)
152 needs_realloc |= (extents[i] > m_cache_extents[i]);
153 if (needs_realloc) {
154 for (size_t i = 0; i < 1 + dim; ++i)
155 m_cache_extents[i] = std::max(m_cache_extents[i], extents[i]);
156 m_cache = make_kokkos_nd_view<1 + dim, NT, ExecutionSpace>("cache", m_cache_extents);
157 }
158 }
159 // Create a Restrict-tagged alias of the cache for no-alias optimization
160 const auto cache = KokkosNDViewRestrict<1 + dim, NT, ExecutionSpace>(m_cache);
161
162 const auto m_args = device::make_tuple(args...);
163
164 const auto &n = nodes;
165 const auto &w = weights;
166 const auto &start = grid_start;
167 const auto &scale = grid_scale;
168
169 // The external position is a function of idx[0] alone: at most integral_view.size() distinct
170 // values per launch, while the functor below runs size * prod(grid_size) threads. For the
171 // logarithmic coordinate classes forward() is a fp64 expm1/sinh+exp, so recomputing it per
172 // thread wastes one transcendental per thread. Precompute the positions once per coordinate
173 // system into a device view; the fill runs the very same forward() on the same device, so the
174 // cached values are bit-identical to what the per-thread computation produced.
175 //
176 // Coordinates without a to_string() identity keep the per-thread computation. SubCoordinates
177 // (the MPI path) is *not* one of them -- it has its own to_string(), and the key construction
178 // below was written to separate two windows into the same base grid. The only cost on that
179 // path is that a schedule which moves a slice boundary re-keys and re-runs the ~5 us fill.
180 constexpr size_t cdim = Coordinates::dim;
181 // Compile-time, so the un-taken branch in the device functors below is dead code and the
182 // per-thread forward() (a fp64 expm1/sinh+exp on the log grids) actually leaves the kernel.
184 // The key must identify the positions, not just the coordinate parameters. SubCoordinates
185 // does put its window into its own to_string(), but appending the exact (hex-formatted)
186 // first and last positions makes the separation independent of that.
187 std::string key = coordinates.to_string() + "|" + std::to_string(integral_view.size());
188 {
189 char buf[64];
190 const auto first = coordinates.forward(coordinates.from_linear_index(size_t(0)));
191 const auto last = coordinates.forward(coordinates.from_linear_index(integral_view.size() - 1));
192 for (size_t d = 0; d < cdim; ++d) {
193 std::snprintf(buf, sizeof(buf), "|%la|%la", double(first[d]), double(last[d]));
194 key += buf;
195 }
196 }
197 const size_t need = integral_view.size() * cdim;
198 if (m_positions_key != key || m_positions.extent(0) < need) {
199 if (m_positions.extent(0) < need)
200 m_positions = Kokkos::View<ctype *, typename ExecutionSpace::memory_space>(
201 Kokkos::view_alloc(space, Kokkos::WithoutInitializing, "QuadratureIntegrator_positions"), need);
202 const auto pos_fill = m_positions;
203 const auto coords = coordinates;
204 Kokkos::parallel_for(
205 "QuadratureIntegrator_fill_positions",
206 Kokkos::RangePolicy<ExecutionSpace>(space, 0, integral_view.size()), KOKKOS_LAMBDA(const size_t i) {
207 const auto p = coords.forward(coords.from_linear_index(i));
208 for (size_t d = 0; d < cdim; ++d)
209 pos_fill(i * cdim + d) = p[d];
210 });
211 m_positions_key = key;
212 }
213 }
214 const auto pos_view = m_positions;
215 using pos_ctype = typename Coordinates::ctype;
216 // Runtime copy for the team lambda below: its constant() evaluation runs once per team, so
217 // keeping the transform code there costs nothing, and a plain branch avoids nvcc's fragile
218 // handling of if-constexpr inside extended class lambdas.
219 const bool pos_cached_rt = has_cacheable_positions_v<Coordinates>;
220
221 // Two complete functors, selected by a HOST-level if constexpr. nvcc's extended-lambda
222 // transformation miscompiles `if constexpr` INSIDE the lambda body here (wrong or
223 // unregistered kernel stubs: cudaErrorInvalidResourceHandle, or a silently zero RHS), while
224 // a lambda DEFINED inside an if-constexpr block is fine (the position fill above). So the
225 // branch lives out here and each lambda body is branch-free.
226 //
227 // No explicit tile: Kokkos' own heuristic is used. An explicit tile matched to
228 // DIFFRG_LAUNCH_BOUNDS was measured and is INERT (numtracer/gpubench/FINDINGS.md) -- the
229 // 1.3-1.5x it appeared to give was GPU clock ramp, not tiling. Not worth the coupling: Kokkos
230 // hard-aborts when the tile product exceeds LaunchBounds, so an explicit tile turns a future
231 // launch-bounds change into a runtime abort.
233 auto functor = KOKKOS_LAMBDA(const device::array<size_t, 1 + dim> &idx)
234 {
235 // make subview
236 auto subview = device::apply([&](const auto &...i) { return Kokkos::subview(cache, i...); }, idx);
237
238 // get the (precomputed) position for the current index
240 for (size_t d = 0; d < cdim; ++d)
241 pos[d] = static_cast<pos_ctype>(pos_view(idx[0] * cdim + d));
242
244 ctype weight = 1;
245 for (int i = 0; i < dim; ++i) {
246 x[i] = Kokkos::fma(scale[i], n[i][idx[1 + i]], start[i]);
247 weight *= w[i][idx[1 + i]] * scale[i];
248 }
249
250 // Apply x/pos and the trailing arguments as two NESTED packs. Do not be tempted to
251 // tuple_cat them into one tuple and apply that once: tuple_cat builds a by-value object,
252 // and m_args holds every interpolator by value (device::make_tuple decays), so the
253 // concatenated tuple is a full per-thread copy of all of them in local memory. That cost
254 // 3576 B of stack frame on QCD_Nf2 (13 interpolators x 272 B) -- 89% of that binary's
255 // 4000 B frame -- and 2.3x of runtime, measured. Applying an *lvalue* tuple binds
256 // references instead, and KERNEL::kernel takes all of them by const&.
257 // See docs/NUMTRACER_PER_THREAD_FRAME.md in the numtracer repo.
258 device::apply(
259 [&](const auto &...xargs) {
260 device::apply([&](const auto &...iargs) { subview() = weight * KERNEL::kernel(xargs..., iargs...); },
261 m_args);
262 },
263 device::tuple_cat(x, pos));
264 };
265 Kokkos::parallel_for(make_kokkos_nd_range_divisible<1 + dim, ExecutionSpace>(space, {0}, extents),
267 } else {
268 auto functor = KOKKOS_LAMBDA(const device::array<size_t, 1 + dim> &idx)
269 {
270 // make subview
271 auto subview = device::apply([&](const auto &...i) { return Kokkos::subview(cache, i...); }, idx);
272
273 // get the position for the current index
274 const auto idx_v = coordinates.from_linear_index(idx[0]);
275 const auto pos = coordinates.forward(idx_v);
276
278 ctype weight = 1;
279 for (int i = 0; i < dim; ++i) {
280 x[i] = Kokkos::fma(scale[i], n[i][idx[1 + i]], start[i]);
281 weight *= w[i][idx[1 + i]] * scale[i];
282 }
283
284 // Apply x/pos and the trailing arguments as two NESTED packs. Do not be tempted to
285 // tuple_cat them into one tuple and apply that once: tuple_cat builds a by-value object,
286 // and m_args holds every interpolator by value (device::make_tuple decays), so the
287 // concatenated tuple is a full per-thread copy of all of them in local memory. That cost
288 // 3576 B of stack frame on QCD_Nf2 (13 interpolators x 272 B) -- 89% of that binary's
289 // 4000 B frame -- and 2.3x of runtime, measured. Applying an *lvalue* tuple binds
290 // references instead, and KERNEL::kernel takes all of them by const&.
291 // See docs/NUMTRACER_PER_THREAD_FRAME.md in the numtracer repo.
292 device::apply(
293 [&](const auto &...xargs) {
294 device::apply([&](const auto &...iargs) { subview() = weight * KERNEL::kernel(xargs..., iargs...); },
295 m_args);
296 },
297 device::tuple_cat(x, pos));
298 };
299 Kokkos::parallel_for(make_kokkos_nd_range_divisible<1 + dim, ExecutionSpace>(space, {0}, extents),
301 }
302
303 using TeamType = Kokkos::TeamPolicy<ExecutionSpace>::member_type;
304 // reduction with vector lanes for warp-level parallelism
305 constexpr int vector_width = 32;
306 Kokkos::parallel_for(
307 Kokkos::TeamPolicy(space, integral_view.size(), Kokkos::AUTO, vector_width),
308 KOKKOS_CLASS_LAMBDA(const TeamType &team) {
309 // get the current (continuous) index
310 const uint k = team.league_rank();
311
312 if (k >= integral_view.size()) return;
313
314 // no-ops to capture
315 (void)cache;
316 (void)grid_size;
317
318 // Flatten grid_size into total element count for thread+vector splitting
319 size_t total_elements = 1;
320 for (int d = 0; d < dim; ++d)
321 total_elements *= grid_size[d];
322
323 // Pre-compute stride array for index decomposition (avoids division/modulo in inner loop)
325 strides[dim - 1] = 1;
326 for (int d = dim - 2; d >= 0; --d)
327 strides[d] = strides[d + 1] * grid_size[d + 1];
328
329 NT res{};
330 Kokkos::parallel_reduce(
331 Kokkos::TeamThreadRange(team, (total_elements + vector_width - 1) / vector_width),
332 [&](const size_t outer, NT &team_update) {
333 NT vec_sum{};
334 Kokkos::parallel_reduce(
335 Kokkos::ThreadVectorRange(team, vector_width),
336 [&](const size_t inner, NT &vec_update) {
337 const size_t flat = outer * vector_width + inner;
338 if (flat < total_elements) {
339 // Convert flat index back to multi-dimensional using pre-computed strides
341 size_t remainder = flat;
342 for (int d = 0; d < dim; ++d) {
343 ridx[d] = remainder / strides[d];
344 remainder -= ridx[d] * strides[d];
345 }
346 device::apply([&](const auto &...iargs) { vec_update += cache(k, iargs...); }, ridx);
347 }
348 },
349 vec_sum);
350 team_update += vec_sum;
351 },
352 res);
353
354 // add the constant value (skip coordinate computation if kernel has no constant)
355 Kokkos::single(Kokkos::PerTeam(team), [&]() {
357 if (pos_cached_rt) {
358 for (size_t d = 0; d < cdim; ++d)
359 pos[d] = static_cast<pos_ctype>(pos_view(size_t(k) * cdim + d));
360 } else {
361 pos = coordinates.forward(coordinates.from_linear_index(k));
362 }
363 // Nested packs, not tuple_cat -- same reason as the phase-1 functor above. This
364 // kernel is only 0.3-4% of GPU time (docs/NUMTRACER_GPU_INVESTIGATION.md) but it
365 // carried the whole per-thread copy too: 3544 B of stack frame on a kernel that does
366 // almost no arithmetic.
367 integral_view(k) =
368 res + device::apply(
369 [&](const auto &...pargs) {
370 return device::apply(
371 [&](const auto &...iargs) { return KERNEL::constant(pargs..., iargs...); }, m_args);
372 },
373 pos);
374 });
375 });
376 }
377
379 size_t quadrature_volume() const
380 {
381 size_t volume = 1;
382 for (int i = 0; i < dim; ++i)
383 volume *= grid_size[i];
384 return volume;
385 }
386
387 template <typename Coordinates, typename... Args>
388 auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
389 {
390 auto &scheduler = MapScheduler::instance();
391
392 // One staging buffer per integrator, so a second map() from this integrator before a flush
393 // would clobber the first result. The *plan* is what decides, not the local pending list: a
394 // rank that owned no slice of the earlier map has nothing pending and would skip a flush the
395 // other ranks perform, leaving the collectives mismatched.
396 if (scheduler.active() && scheduler.plan_contains(integrator_id())) MapCompletion::flush();
397
398 const MapSlice slice = scheduler.schedule(integrator_id(), dest, sizeof(NT), coordinates.size(),
399 quadrature_volume(), /* splittable */ true,
401
402 if (slice.count == 0) {
403 // Not an owner: no kernels, but the plan entry is registered, so this rank still has to
404 // take part in the exchange.
406 return ExecutionSpace();
407 }
408 if (slice.owns_all(coordinates.size())) return map_dist(dest, coordinates, args...);
409
410 return map_dist(dest + slice.offset, SubCoordinates(coordinates, slice.offset, slice.count), args...);
411 }
412
413 template <typename Coordinates, typename... Args>
414 auto map_dist(NT *dest, const Coordinates &coordinates, const Args &...args)
415 {
416 const size_t n = coordinates.size();
417
418 // Reuse cached device view if large enough, otherwise reallocate (grow-only)
419 if (m_dest_device_size < n) {
420 m_dest_device =
421 Kokkos::View<NT *, ExecutionSpace>(Kokkos::view_alloc(space, "MapIntegrators_device_view"), n);
422 m_dest_device_size = n;
423 }
424 auto dest_device_view = Kokkos::View<NT *, ExecutionSpace>(m_dest_device, Kokkos::make_pair(size_t(0), n));
425
426 if constexpr (std::is_same_v<typename ExecutionSpace::memory_space, CPU_memory>) {
427 // Host backend: "device" memory is host memory, so there is nothing to stage. The work is
428 // synchronous though, so inside a deferral scope it is queued rather than run here -- see
429 // run_or_queue_host().
430 run_or_queue_host(dest, coordinates, args...);
431 // Nothing to land, but the MPI slices still have to be exchanged before the caller reads.
433 return space;
434 } else {
435 // One staging buffer per integrator, so a second map() before a flush would clobber the
436 // first result. Land the outstanding one first; in the normal call pattern (each flow
437 // mapped once per flush interval) this never triggers.
438 if (m_dest_pinned_size > 0 && MapCompletion::has_pending(m_dest_pinned.data())) MapCompletion::flush();
439
440 if (m_dest_pinned_size < n) {
441 m_dest_pinned = Kokkos::View<NT *, PinnedHost_memory>(
442 Kokkos::view_alloc(Kokkos::WithoutInitializing, "MapIntegrators_pinned_view"), n);
443 m_dest_pinned_size = n;
444 }
445 auto pinned_view =
446 Kokkos::View<NT *, PinnedHost_memory>(m_dest_pinned, Kokkos::make_pair(size_t(0), n));
447
448 map(space, dest_device_view, coordinates, args...);
449
450 // Genuinely asynchronous, because the destination is page-locked.
451 Kokkos::deep_copy(space, pinned_view, dest_device_view);
452
453 // Caller has promised not to read `dest` until its DeferredMaps scope closes, so leave the
454 // result in staging and keep the host running ahead of the device.
455 MapCompletion::record(dest, m_dest_pinned.data(), n * sizeof(NT));
456 // Original contract outside such a scope: `dest` is valid on return. flush() fences, lands
457 // the staged copy and -- under MPI -- exchanges this batch's slices, all of which must
458 // happen before the caller looks at `dest`.
460
461 return space;
462 }
463 }
464
465 private:
476 template <typename Coordinates, typename... Args>
477 void run_or_queue_host(NT *dest, const Coordinates &coordinates, const Args &...args)
478 {
479 if constexpr (internal::has_device_backend) {
481 // Everything the job needs is captured by value; `this` and `dest` are covered by the
482 // DeferredMaps contract, which already requires them to outlive the scope.
484 [this, dest, coordinates, args...]() { this->run_host(dest, coordinates, args...); });
485 return;
486 }
487 }
488 run_host(dest, coordinates, args...);
489 }
490
493 template <typename Coordinates, typename... Args>
494 void run_host(NT *dest, const Coordinates &coordinates, const Args &...args)
495 {
496 const size_t n = coordinates.size();
497 if (m_dest_device_size < n) {
498 m_dest_device =
499 Kokkos::View<NT *, ExecutionSpace>(Kokkos::view_alloc(space, "MapIntegrators_device_view"), n);
500 m_dest_device_size = n;
501 }
502 auto dest_device_view = Kokkos::View<NT *, ExecutionSpace>(m_dest_device, Kokkos::make_pair(size_t(0), n));
503 auto dest_view = Kokkos::View<NT *, CPU_memory, Kokkos::MemoryUnmanaged>(dest, n);
504 map(space, dest_device_view, coordinates, args...);
505 Kokkos::deep_copy(space, dest_view, dest_device_view);
506 }
507
508 protected:
510
511 ExecutionSpace space;
516
519
520 // Persistent view caches to avoid per-call GPU memory allocation
522 mutable device::array<size_t, 1 + dim> m_cache_extents{};
523 // Cached external positions for map(): one forward() per grid point instead of per thread.
524 // Keyed on the coordinates' to_string() identity; flat layout [grid_point * cdim + d].
525 mutable Kokkos::View<ctype *, typename ExecutionSpace::memory_space> m_positions;
526 mutable std::string m_positions_key;
527 mutable Kokkos::View<NT *, ExecutionSpace> m_dest_device;
528 mutable size_t m_dest_device_size = 0;
529 // Page-locked staging for the result copy; see map_dist() and MapCompletion.
530 mutable Kokkos::View<NT *, PinnedHost_memory> m_dest_pinned;
531 mutable size_t m_dest_pinned_size = 0;
532 mutable Kokkos::View<NT, typename ExecutionSpace::memory_space> m_result_view;
533 mutable typename Kokkos::View<NT, typename ExecutionSpace::memory_space>::host_mirror_type m_result_host;
534 mutable bool m_result_views_initialized = false;
535 };
536
537 template <int dim, typename NT, typename KERNEL>
538 class QuadratureIntegrator<dim, NT, KERNEL, TBB_exec> : public QuadratureIntegrator<dim, NT, KERNEL, KokkosHost_exec>
539 {
541
542 public:
547 using ctype = typename get_type::ctype<NT>;
549
550 QuadratureIntegrator(QuadratureProvider &quadrature_provider, const std::array<size_t, dim> _grid_size,
551 std::array<ctype, dim> grid_min, std::array<ctype, dim> grid_max,
552 const std::array<QuadratureType, dim> quadrature_type)
553 : Base(quadrature_provider, _grid_size, grid_min, grid_max, quadrature_type)
554 {
555 }
556
557 template <typename... T>
558 requires is_valid_kernel<NT, KERNEL, ctype, dim, T...>
559 void get(NT &dest, const T &...t) const
560 {
561 const auto args = device::tie(t...);
562
563 const auto &n = nodes;
564 const auto &w = weights;
565 const auto &start = grid_start;
566 const auto &scale = grid_scale;
567
568 auto functor = [&](const device::array<size_t, dim> &idx) {
570 ctype weight = 1;
571 bool is_first = true;
572 for (size_t i = 0; i < dim; ++i) {
573 x[i] = Kokkos::fma(scale[i], n[i][idx[i]], start[i]);
574 weight *= w[i][idx[i]] * scale[i];
575 is_first &= idx[i] == 0;
576 }
577 return device::apply([&](const auto &...iargs) { return weight * KERNEL::kernel(iargs...); },
578 device::tuple_cat(x, args));
579 };
580
581 dest = KERNEL::constant(t...) + TBBReduction<dim, NT, decltype(functor)>(grid_size, functor);
582 }
583
584 template <typename Coordinates, typename... Args>
585 void map(execution_space &, NT *dest, const Coordinates &coordinates, const Args &...args)
586 {
587 const auto m_args = device::tie(args...);
588
589 tbb::parallel_for(tbb::blocked_range<uint>(0, coordinates.size()), [&](const tbb::blocked_range<uint> &r) {
590 for (uint idx = r.begin(); idx != r.end(); ++idx) {
591 const auto dis_idx = coordinates.from_linear_index(idx);
592 const auto pos = coordinates.forward(dis_idx);
593 // make a tuple of all arguments
594 const auto full_args = device::tuple_cat(pos, m_args);
595 device::apply([&](const auto &...iargs) { get(dest[idx], iargs...); }, full_args);
596 }
597 });
598 }
599
600 template <typename Coordinates, typename... Args>
601 auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
602 {
603 auto space = execution_space();
604 auto &scheduler = MapScheduler::instance();
605
606 // A second map() from this integrator would overwrite `dest` before the first one's slices
607 // have been exchanged, so land the open batch first. Plan-driven, not local -- see the GPU
608 // overload for why that distinction is what keeps the collectives matched.
609 if (scheduler.active() && scheduler.plan_contains(this->integrator_id())) MapCompletion::flush();
610
611 const MapSlice slice = scheduler.schedule(this->integrator_id(), dest, sizeof(NT), coordinates.size(),
612 Base::quadrature_volume(), /* splittable */ true,
614
615 if (slice.count == 0) {
616 // Not an owner, but still part of the batch -- see the GPU overload.
618 return space;
619 }
620 if (slice.owns_all(coordinates.size()))
621 run_or_queue(dest, coordinates, args...);
622 else
623 run_or_queue(dest + slice.offset, SubCoordinates(coordinates, slice.offset, slice.count), args...);
624
625 // This backend writes straight into `dest`, so there is nothing to land -- but the slices
626 // still have to be exchanged before the caller reads them.
628 return space;
629 }
630
631 private:
635 template <typename Coordinates, typename... Args>
636 void run_or_queue(NT *dest, const Coordinates &coordinates, const Args &...args)
637 {
638 if constexpr (internal::has_device_backend) {
640 MapCompletion::record_work([this, dest, coordinates, args...]() {
641 auto sp = execution_space();
642 this->map(sp, dest, coordinates, args...);
643 });
644 return;
645 }
646 }
647 auto sp = execution_space();
648 map(sp, dest, coordinates, args...);
649 }
650
651 protected:
652 using Base::grid_extents;
653 using Base::grid_scale;
654 using Base::grid_size;
655 using Base::grid_start;
656 using Base::quadrature_provider;
657
658 using Base::nodes;
659 using Base::weights;
660 };
661} // namespace DiFfRG
Common base of every integrator, carrying the identity MapScheduler needs.
Definition abstract_integrator.hh:81
static bool deferral_enabled()
Whether the caller has opened a DeferredMaps scope.
Definition map_completion.hh:159
static void record(void *dst, const void *src, const size_t bytes)
Register a device->host result that still has to be copied from staging into dst.
Definition map_completion.hh:73
static bool has_pending(const void *src)
Whether src already has an unlanded result queued.
Definition map_completion.hh:149
static void flush()
Fence, land every pending map result, then exchange slices between MPI ranks.
Definition map_completion.hh:108
static void record_work(std::function< void()> job)
Queue a host-side map() to be run at flush time instead of now.
Definition map_completion.hh:96
static MapScheduler & instance()
auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:601
void get(NT &dest, const T &...t) const
Definition quadrature_integrator.hh:559
typename get_type::ctype< NT > ctype
Numerical type to be used for integration tasks e.g. the argument or possible jacobians.
Definition quadrature_integrator.hh:547
void run_or_queue(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:636
void map(execution_space &, NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:585
QuadratureIntegrator(QuadratureProvider &quadrature_provider, const std::array< size_t, dim > _grid_size, std::array< ctype, dim > grid_min, std::array< ctype, dim > grid_max, const std::array< QuadratureType, dim > quadrature_type)
Definition quadrature_integrator.hh:550
This class performs numerical integration over a d-dimensional hypercube using quadrature rules.
Definition quadrature_integrator.hh:44
Kokkos::View< NT, typename ExecutionSpace::memory_space > m_result_view
Definition quadrature_integrator.hh:532
ExecutionSpace execution_space
Execution space to be used for the integration, e.g. GPU_exec, TBB_exec.
Definition quadrature_integrator.hh:53
Kokkos::View< NT *, ExecutionSpace > m_dest_device
Definition quadrature_integrator.hh:527
void get(NT &dest, const T &...t) const
Definition quadrature_integrator.hh:85
void set_grid_extents(const std::array< ctype, dim > &grid_min, const std::array< ctype, dim > &grid_max)
Definition quadrature_integrator.hh:72
Kokkos::View< NT *, PinnedHost_memory > m_dest_pinned
Definition quadrature_integrator.hh:530
device::array< device::array< ctype, dim >, 2 > grid_extents
Definition quadrature_integrator.hh:513
QuadratureIntegrator(QuadratureProvider &quadrature_provider, const std::array< size_t, dim > &_grid_size, const std::array< ctype, dim > &grid_min, const std::array< ctype, dim > &grid_max, const std::array< QuadratureType, dim > &quadrature_type)
Definition quadrature_integrator.hh:55
Kokkos::View< NT, typenameExecutionSpace::memory_space >::host_mirror_type m_result_host
Definition quadrature_integrator.hh:533
ExecutionSpace space
Definition quadrature_integrator.hh:511
void map(ExecutionSpace &space, const view_type integral_view, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:141
typename get_type::ctype< NT > ctype
Numerical type to be used for integration tasks e.g. the argument or possible jacobians.
Definition quadrature_integrator.hh:49
KokkosNDView< 1+dim, NT, ExecutionSpace > m_cache
Definition quadrature_integrator.hh:521
device::array< Kokkos::View< const ctype *, typename ExecutionSpace::memory_space >, dim > nodes
Definition quadrature_integrator.hh:517
void run_host(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:494
device::array< Kokkos::View< const ctype *, typename ExecutionSpace::memory_space >, dim > weights
Definition quadrature_integrator.hh:518
device::array< ctype, dim > grid_scale
Definition quadrature_integrator.hh:515
QuadratureProvider & quadrature_provider
Definition quadrature_integrator.hh:512
device::array< ctype, dim > grid_start
Definition quadrature_integrator.hh:514
device::array< size_t, dim > grid_size
Definition quadrature_integrator.hh:509
std::string m_positions_key
Definition quadrature_integrator.hh:526
Kokkos::View< ctype *, typename ExecutionSpace::memory_space > m_positions
Definition quadrature_integrator.hh:525
auto map_dist(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:414
auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator.hh:388
size_t quadrature_volume() const
Points evaluated per external grid point. Half of the scheduler's cost score.
Definition quadrature_integrator.hh:379
void get(OT &dest, const T &...t) const
Definition quadrature_integrator.hh:103
void get(ExecutionSpace &space, OT &dest, const T &...t) const
Definition quadrature_integrator.hh:111
void run_or_queue_host(NT *dest, const Coordinates &coordinates, const Args &...args)
Run a host-backend map now, or queue it for flush time if a deferral scope is open.
Definition quadrature_integrator.hh:477
A class that provides quadrature points and weights, in host and device memory. The quadrature points...
Definition quadrature_provider.hh:239
A contiguous window into the linear index range of another coordinate system.
Definition coordinates.hh:207
Definition abstract_integrator.hh:61
std::array< T, N > array
Definition kokkos.hh:155
typename internal::_ctype< CT >::value ctype
Definition types.hh:76
constexpr bool has_device_backend
Whether the default execution space is a real device.
Definition map_completion.hh:26
Definition complex_math.hh:10
Kokkos::View< typename GetKokkosNDStarType< dim, T >::type, ExecutionSpace > KokkosNDView
Definition kokkos.hh:182
auto make_kokkos_nd_range(ExecutionSpace &space, const device::array< size_t, dim > start, const device::array< size_t, dim > end)
Definition kokkos.hh:390
auto make_kokkos_nd_range_divisible(ExecutionSpace &space, const device::array< size_t, dim > start, const device::array< size_t, dim > end)
Like make_kokkos_nd_range, but re-tiled so no lane is launched masked.
Definition kokkos.hh:437
constexpr bool has_cacheable_positions_v
Whether a coordinates type carries enough identity for QuadratureIntegrator::map() to cache its forwa...
Definition quadrature_integrator.hh:28
Kokkos::View< typename GetKokkosNDStarType< dim, T >::type, ExecutionSpace, Kokkos::MemoryTraits< Kokkos::Restrict > > KokkosNDViewRestrict
Definition kokkos.hh:187
constexpr auto & get(named_tuple< tuple_type, strSet > &ob)
get a reference to the element with the given name
Definition tuples.hh:125
MapTarget map_target()
The scheduling target of an execution space, selected at compile time.
Definition map_scheduler.hh:80
unsigned int uint
Definition utils.hh:24
NT TBBReduction(const device::array< size_t, dim > &grid_size, const FUN &functor)
Bitwise reproducible reduction of functor over a dim-dimensional index grid.
Definition tbb.hh:88
auto make_kokkos_nd_view(const std::string &label, const device::array< size_t, dim > &extents)
Definition kokkos.hh:203
ExecutionSpaces::TBB_exec_space TBB_exec
Definition kokkos.hh:74
This is a functor which wraps a lambda for reduction. Basically, this is necessary when one wants to ...
Definition kokkos.hh:512
This is a functor which wraps a lambda. Basically, this is necessary when one wants to call a variadi...
Definition kokkos.hh:486
This rank's window into the external grid of one QuadratureIntegrator::map() call.
Definition map_scheduler.hh:97
bool owns_all(const size_t grid_size) const
Definition map_scheduler.hh:103
size_t count
Number of grid points; 0 means this rank does not participate in this map().
Definition map_scheduler.hh:101
size_t offset
First external grid point this rank computes.
Definition map_scheduler.hh:99
The CPU execution space: TBB, the one host thread pool DiFfRG runs on.
Definition kokkos.hh:34