MFEM v4.10.0
Finite element discretization library
Loading...
Searching...
No Matches
scan.hpp
Go to the documentation of this file.
1// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#ifndef MFEM_SCAN_HPP
13#define MFEM_SCAN_HPP
14
15#include "backends.hpp"
16#include "forall.hpp"
17
18#if defined(MFEM_USE_CUDA_OR_HIP) && !defined(MFEM_USE_CUDA_OR_HIP_LANG)
19#error "This header requires compilation with CUDA/HIP language!"
20#else
21
22#ifdef MFEM_USE_CUDA
23#include <cub/device/device_scan.cuh>
24#include <cub/device/device_select.cuh>
25#define MFEM_CUB_NAMESPACE cub
26#elif defined(MFEM_USE_HIP)
27#include <hipcub/device/device_scan.hpp>
28#include <hipcub/device/device_select.hpp>
29#define MFEM_CUB_NAMESPACE hipcub
30#endif
31
32#include <algorithm>
33#include <functional>
34#include <numeric>
35#include <cstddef>
36
37namespace mfem
38{
39/// Equivalent to InclusiveScan(use_dev, d_in, d_out, num_items, workspace,
40/// std::plus<>{})
41template <class InputIt, class OutputIt>
42void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items)
43{
44 // forward to InclusiveSum for potentially faster kernels
45#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
47 {
48 static Array<std::byte> workspace;
49 size_t bytes = workspace.Size();
50 if (bytes)
51 {
52 auto error = MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
53 workspace.Write(), bytes, d_in, d_out, num_items);
54#if defined(MFEM_USE_CUDA)
55 if (error == cudaSuccess)
56 {
57 return;
58 }
59#elif defined(MFEM_USE_HIP)
60 if (error == hipSuccess)
61 {
62 return;
63 }
64#endif
65 }
66 // try allocating a larger buffer
67 bytes = 0;
68 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
69 nullptr, bytes, d_in, d_out, num_items));
70 workspace.SetSize(bytes);
71 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
72 workspace.Write(), bytes, d_in, d_out, num_items));
73 return;
74 }
75#endif
76#if 0
77 std::inclusive_scan(d_in, d_in + num_items, d_out);
78#else
79 // work-around to some compilers not fully supporting C++17
80 if (num_items)
81 {
82 *d_out = *d_in;
83 auto prev = d_out;
84 ++d_in;
85 ++d_out;
86 for (size_t i = 1; i < num_items; ++i)
87 {
88 *d_out = (*prev) + (*d_in);
89 prev = d_out;
90 ++d_in;
91 ++d_out;
92 }
93 }
94#endif
95}
96
97/// @brief Performs an inclusive scan of [d_in, d_in+num_items) -> [d_out,
98/// d_out+num_items). This call is potentially asynchronous on the device.
99///
100/// @a d_in input start.
101/// @a d_out output start. Can perform in-place scans with d_out = d_in
102/// @a scan_op binary scan functor. Must be associative. If only weakly
103/// associative (i.e. floating point addition) results are not deterministic. On
104/// device this must also be commutative.
105template <class InputIt, class OutputIt, class ScanOp>
106void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
107 ScanOp scan_op)
108{
109#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
111 {
112 static Array<std::byte> workspace;
113 size_t bytes = workspace.Size();
114 if (bytes)
115 {
116 auto error = MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
117 workspace.Write(), bytes, d_in, d_out, scan_op, num_items);
118#if defined(MFEM_USE_CUDA)
119 if (error == cudaSuccess)
120 {
121 return;
122 }
123#elif defined(MFEM_USE_HIP)
124 if (error == hipSuccess)
125 {
126 return;
127 }
128#endif
129 }
130 // try allocating a larger buffer
131 bytes = 0;
132 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
133 nullptr, bytes, d_in, d_out, scan_op, num_items));
134 workspace.SetSize(bytes);
135 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
136 workspace.Write(), bytes, d_in, d_out, scan_op, num_items));
137 return;
138 }
139#endif
140#if 0
141 std::inclusive_scan(d_in, d_in + num_items, d_out, scan_op);
142#else
143 // work-around to some compilers not fully supporting C++17
144 if (num_items)
145 {
146 *d_out = *d_in;
147 auto prev = d_out;
148 ++d_in;
149 ++d_out;
150 for (size_t i = 1; i < num_items; ++i)
151 {
152 *d_out = scan_op(*prev, *d_in);
153 prev = d_out;
154 ++d_in;
155 ++d_out;
156 }
157 }
158#endif
159}
160
161/// Performs an exclusive scan of [d_in, d_in+num_items) -> [d_out,
162/// d_out+num_items). This call is potentially asynchronous on the device.
163/// @a d_in input start.
164/// @a d_out output start. Can perform in-place scans with d_out = d_in
165/// @a scan_op binary scan functor. Must be associative. If only weakly
166/// associative (i.e. floating point addition) results are not deterministic. On
167/// device this must also be commutative.
168template <class InputIt, class OutputIt, class T, class ScanOp>
169void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
170 T init_value, ScanOp scan_op)
171{
172#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
174 {
175 static Array<std::byte> workspace;
176 size_t bytes = workspace.Size();
177 if (bytes)
178 {
179 auto error = MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
180 workspace.Write(), bytes, d_in, d_out, scan_op, init_value,
181 num_items);
182#if defined(MFEM_USE_CUDA)
183 if (error == cudaSuccess)
184 {
185 return;
186 }
187#elif defined(MFEM_USE_HIP)
188 if (error == hipSuccess)
189 {
190 return;
191 }
192#endif
193 }
194 // try allocating a larger buffer
195 bytes = 0;
196 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
197 nullptr, bytes, d_in, d_out, scan_op, init_value, num_items));
198 workspace.SetSize(bytes);
199 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
200 workspace.Write(), bytes, d_in, d_out, scan_op, init_value,
201 num_items));
202 return;
203 }
204#endif
205#if 0
206 std::exclusive_scan(d_in, d_in + num_items, d_out, init_value, scan_op);
207#else
208 // work-around to some compilers not fully supporting C++17
209 if (num_items)
210 {
211 for (size_t i = 0; i < num_items; ++i)
212 {
213 auto next = scan_op(init_value, *d_in);
214 *d_out = init_value;
215 init_value = next;
216 ++d_out;
217 ++d_in;
218 }
219 }
220#endif
221}
222
223/// Equivalent to ExclusiveScan(use_dev, d_in, d_out, num_items, init_value,
224/// workspace, std::plus<>{})
225template <class InputIt, class OutputIt, class T>
226void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
227 T init_value)
228{
229 ExclusiveScan(use_dev, d_in, d_out, num_items, init_value, std::plus<> {});
230}
231
232/// @brief Equivalent to *d_num_selected_out = std::copy_if(d_in,
233/// d_in+num_items, d_out, [=](auto iter){ return d_flags[iter-d_in]; }) -
234/// d_out;
235///
236/// None of the following ranges may overlap:
237/// - [d_in, d_in+num_items)
238/// - [d_flags, d_flags+num_items)
239/// - [d_out, d_out+*d_num_selected_out)
240/// - [d_num_selected_out, d_num_selected_out+1)
241template <class InputIt, class FlagIt, class OutputIt, class NumSelectedIt>
242void CopyFlagged(bool use_dev, InputIt d_in, FlagIt d_flags, OutputIt d_out,
243 NumSelectedIt d_num_selected_out, size_t num_items)
244{
245#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
246 if (use_dev &&
248 {
249 static Array<std::byte> workspace;
250 size_t bytes = workspace.Size();
251 if (bytes)
252 {
253 auto error = MFEM_CUB_NAMESPACE::DeviceSelect::Flagged(
254 workspace.Write(), bytes, d_in, d_flags, d_out, d_num_selected_out,
255 num_items);
256#if defined(MFEM_USE_CUDA)
257 if (error == cudaSuccess)
258 {
259 return;
260 }
261#elif defined(MFEM_USE_HIP)
262 if (error == hipSuccess)
263 {
264 return;
265 }
266#endif
267 }
268 // try allocating a larger buffer
269 bytes = 0;
270 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceSelect::Flagged(
271 nullptr, bytes, d_in, d_flags, d_out, d_num_selected_out, num_items));
272 workspace.SetSize(bytes);
273 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceSelect::Flagged(
274 workspace.Write(), bytes, d_in, d_flags, d_out, d_num_selected_out,
275 num_items));
276 return;
277 }
278#endif
279 *d_num_selected_out = 0;
280 for (size_t i = 0; i < num_items; ++i, ++d_in, ++d_flags)
281 {
282 if (*d_flags)
283 {
284 *d_out = *d_in;
285 ++d_out;
286 ++*d_num_selected_out;
287 }
288 }
289}
290
291/// @brief Equivalent to *d_num_selected_out = std::copy_if(d_in,
292/// d_in+num_items, d_out, select_op) - d_out;
293///
294/// None of the following ranges may overlap:
295/// - [d_in, d_in+num_items)
296/// - [d_out, d_out+*d_num_selected_out)
297/// - [d_num_selected_out, d_num_selected_out+1)
298template <class InputIt, class OutputIt, class NumSelectedIt, class SelectOp>
299void CopyIf(bool use_dev, InputIt d_in, OutputIt d_out,
300 NumSelectedIt d_num_selected_out, size_t num_items,
301 SelectOp select_op)
302{
303#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
304 if (use_dev &&
306 {
307#if defined(MFEM_USE_CUDA) && \
308 (__CUDACC_VER_MAJOR__ < 12 || \
309 (__CUDACC_VER_MAJOR__ == 12 && __CUDACC_VER_MINOR__ < 5))
310 // bug in cuda < 12.5, work-around: use Flagged instead
311 Array<bool> flags(num_items);
312 auto ptr = flags.Write();
313 forall(num_items,
314 [=] MFEM_HOST_DEVICE(int i) { ptr[i] = select_op(d_in[i]); });
315 CopyFlagged(use_dev, d_in, ptr, d_out, d_num_selected_out, num_items);
316#else
317 static Array<std::byte> workspace;
318 size_t bytes = workspace.Size();
319 if (bytes)
320 {
321 auto error = MFEM_CUB_NAMESPACE::DeviceSelect::If(
322 workspace.Write(), bytes, d_in, d_out, d_num_selected_out,
323 num_items, select_op);
324#if defined(MFEM_USE_CUDA)
325 if (error == cudaSuccess)
326 {
327 return;
328 }
329#elif defined(MFEM_USE_HIP)
330 if (error == hipSuccess)
331 {
332 return;
333 }
334#endif
335 }
336 // try allocating a larger buffer
337 bytes = 0;
338 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceSelect::If(
339 nullptr, bytes, d_in, d_out, d_num_selected_out, num_items,
340 select_op));
341 workspace.SetSize(bytes);
342 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceSelect::If(
343 workspace.Write(), bytes, d_in, d_out, d_num_selected_out, num_items,
344 select_op));
345#endif
346 return;
347 }
348#endif
349 *d_num_selected_out = 0;
350 for (size_t i = 0; i < num_items; ++i, ++d_in)
351 {
352 if (select_op(*d_in))
353 {
354 *d_out = *d_in;
355 ++d_out;
356 ++*d_num_selected_out;
357 }
358 }
359}
360
361/// @brief equivalent to *d_num_selected_out = std::unique_copy(d_in,
362/// d_in+num_items, d_out) - d_out;
363///
364/// None of the following ranges may overlap:
365/// - [d_in, d_in+num_items)
366/// - [d_out, d_out+*d_num_selected_out)
367/// - [d_num_selected_out, d_num_selected_out+1)
368template <class InputIt, class OutputIt, class NumSelectedIt>
369void CopyUnique(bool use_dev, InputIt d_in, OutputIt d_out,
370 NumSelectedIt d_num_selected_out, size_t num_items)
371{
372#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
373 if (use_dev &&
375 {
376 static Array<std::byte> workspace;
377 size_t bytes = workspace.Size();
378 if (bytes)
379 {
380 auto error = MFEM_CUB_NAMESPACE::DeviceSelect::Unique(
381 workspace.Write(), bytes, d_in, d_out, d_num_selected_out,
382 num_items);
383#if defined(MFEM_USE_CUDA)
384 if (error == cudaSuccess)
385 {
386 return;
387 }
388#elif defined(MFEM_USE_HIP)
389 if (error == hipSuccess)
390 {
391 return;
392 }
393#endif
394 }
395 // try allocating a larger buffer
396 bytes = 0;
397 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceSelect::Unique(
398 nullptr, bytes, d_in, d_out, d_num_selected_out, num_items));
399 workspace.SetSize(bytes);
400 MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceSelect::Unique(
401 workspace.Write(), bytes, d_in, d_out, d_num_selected_out,
402 num_items));
403 return;
404 }
405#endif
406 *d_num_selected_out =
407 std::unique_copy(d_in, d_in + num_items, d_out) - d_out;
408}
409} // namespace mfem
410
411#undef MFEM_CUB_NAMESPACE
412
413#endif // defined(MFEM_USE_CUDA_OR_HIP) && !defined(MFEM_USE_CUDA_OR_HIP_LANG)
414
415#endif // MFEM_SCAN_HPP
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:869
int Size() const
Return the logical size of the array.
Definition array.hpp:192
T * Write(bool on_dev=true)
Shortcut for mfem::Write(a.GetMemory(), a.Size(), on_dev).
Definition array.hpp:418
static bool Allows(unsigned long b_mask)
Return true if any of the backends in the backend mask, b_mask, are allowed.
Definition device.hpp:271
void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items, T init_value, ScanOp scan_op)
Definition scan.hpp:169
void CopyFlagged(bool use_dev, InputIt d_in, FlagIt d_flags, OutputIt d_out, NumSelectedIt d_num_selected_out, size_t num_items)
Equivalent to *d_num_selected_out = std::copy_if(d_in, d_in+num_items, d_out, [=](auto iter){ return ...
Definition scan.hpp:242
void CopyUnique(bool use_dev, InputIt d_in, OutputIt d_out, NumSelectedIt d_num_selected_out, size_t num_items)
equivalent to *d_num_selected_out = std::unique_copy(d_in, d_in+num_items, d_out) - d_out;
Definition scan.hpp:369
void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items)
Definition scan.hpp:42
void CopyIf(bool use_dev, InputIt d_in, OutputIt d_out, NumSelectedIt d_num_selected_out, size_t num_items, SelectOp select_op)
Equivalent to *d_num_selected_out = std::copy_if(d_in, d_in+num_items, d_out, select_op) - d_out;.
Definition scan.hpp:299
void forall(int N, lambda &&body)
Definition forall.hpp:1134
@ HIP_MASK
Biwise-OR of all HIP backends.
Definition device.hpp:98
@ CUDA_MASK
Biwise-OR of all CUDA backends.
Definition device.hpp:96