Block-Structured AMR Software Framework
Loading...
Searching...
No Matches
AMReX_CSR.H
Go to the documentation of this file.
1#ifndef AMREX_CSR_H_
2#define AMREX_CSR_H_
3#include <AMReX_Config.H>
4
5#include <AMReX_Gpu.H>
6#include <AMReX_INT.H>
7#include <AMReX_OpenMP.H>
8
9#if defined(AMREX_USE_CUDA)
10#include <cub/cub.cuh> // for Clang
11#endif
12
13#ifdef AMREX_USE_SYCL
14# include <AMReX_Reduce.H>
15# include <mkl_version.h>
16# include <oneapi/mkl/spblas.hpp>
17#endif
18
19#include <algorithm>
20#include <concepts>
21#include <climits>
22#include <type_traits>
23
24namespace amrex {
25
32
40template <typename T, typename Index = Long>
41struct CsrView {
42 using index_type = Index;
43 using CI = std::conditional_t<std::is_const_v<T>, Index const, Index>;
44 T* AMREX_RESTRICT mat = nullptr;
47 Long nnz = 0;
49};
50
58template <typename T, template <typename> class V, typename Index = Long>
59struct CSR {
60 using index_type = Index;
61 V<T> mat;
62 V<Index> col_index;
63 V<Index> row_offset;
64 Long nnz = 0;
65
67 [[nodiscard]] Long nrows () const {
68 return row_offset.empty() ? Long(0) : Long(row_offset.size())-1;
69 }
70
80 void resize (Long num_rows, Long num_non_zeros) {
81 mat.resize(num_non_zeros);
82 col_index.resize(num_non_zeros);
83 row_offset.resize(num_rows+1);
84 nnz = num_non_zeros;
85 }
86
89 return CsrView<T,Index>{mat.data(), col_index.data(), row_offset.data(),
90 nnz, Long(row_offset.size())-1};
91 }
92
94 [[nodiscard]] CsrView<T const,Index> view () const {
95 return CsrView<T const,Index>{.mat = mat.data(),
96 .col_index = col_index.data(),
97 .row_offset = row_offset.data(),
98 .nnz = nnz,
99 .nrows = Long(row_offset.size())-1};
100 }
101
103 [[nodiscard]] CsrView<T const,Index> const_view () const {
104 return CsrView<T const,Index>{mat.data(), col_index.data(), row_offset.data(),
105 nnz, Long(row_offset.size())-1};
106 }
107
111 void sort ();
112
117};
118
119template <typename C, typename T, template<typename> class AD, template<typename> class AS,
120 typename Index>
121requires (std::same_as<C,Gpu::HostToDevice> ||
122 std::same_as<C,Gpu::DeviceToHost> ||
123 std::same_as<C,Gpu::DeviceToDevice>)
132{
133 dst.mat.resize(src.mat.size());
134 dst.col_index.resize(src.col_index.size());
135 dst.row_offset.resize(src.row_offset.size());
137 src.mat.begin(),
138 src.mat.end(),
139 dst.mat.begin());
141 src.col_index.begin(),
142 src.col_index.end(),
143 dst.col_index.begin());
145 src.row_offset.begin(),
146 src.row_offset.end(),
147 dst.row_offset.begin());
148 dst.nnz = src.nnz;
149}
150
151template <typename T, template <typename> class V, typename Index>
153{
154 if (nnz <= 0) { return; }
155
156#ifdef AMREX_USE_GPU
157
158 // The function is synchronous. If that is no longer the case, we might
159 // need to update SpMatrix::define.
160
161#if defined(AMREX_USE_CUDA) || defined(AMREX_USE_HIP)
162
163 constexpr int nthreads = 256;
164 constexpr int nwarps_per_block = nthreads / Gpu::Device::warp_size;
165
166 AMREX_ALWAYS_ASSERT((nrows()+nwarps_per_block-1) < Long(std::numeric_limits<int>::max()));
167
168 auto nr = int(nrows());
169 int nblocks = (nr + nwarps_per_block-1) / nwarps_per_block;
170 auto const& stream = Gpu::gpuStream();
171
172#else
173
174 AMREX_ALWAYS_ASSERT(nrows() < Long(std::numeric_limits<int>::max()));
175
176 auto nr = int(nrows());
177
178#endif
179
180 auto* pmat = mat.data();
181 auto* pcol = col_index.data();
182 auto* prow = row_offset.data();
183
184 // Short rows (the common case on fine AMG levels): one thread per row,
185 // insertion sort in registers. Longer rows are left to the backend-specific
186 // code below. All kernels skip rows that are already sorted.
187 // The cap keeps the per-thread arrays at ~256 bytes for wide value types.
188 constexpr int SHORT_ROW = std::clamp(int(256/(sizeof(Index)+sizeof(T))), 4, 16);
189 ParallelFor(nr, [=] AMREX_GPU_DEVICE (int r) noexcept
190 {
191 Long const b = prow[r];
192 int const len = int(prow[r+1] - b);
193 if (len <= 1 || len > SHORT_ROW) { return; }
194 Index keys[SHORT_ROW];
195 bool sorted = true;
196 for (int i = 0; i < len; ++i) {
197 keys[i] = pcol[b+i];
198 if (i > 0) { sorted = sorted && (keys[i-1] <= keys[i]); }
199 }
200 if (sorted) { return; }
201 T vals[SHORT_ROW];
202 for (int i = 0; i < len; ++i) {
203 vals[i] = pmat[b+i];
204 }
205 for (int i = 1; i < len; ++i) {
206 Index const k = keys[i];
207 T const v = vals[i];
208 int j = i - 1;
209 while (j >= 0 && keys[j] > k) {
210 keys[j+1] = keys[j];
211 vals[j+1] = vals[j];
212 --j;
213 }
214 keys[j+1] = k;
215 vals[j+1] = v;
216 }
217 for (int i = 0; i < len; ++i) {
218 pcol[b+i] = keys[i];
219 pmat[b+i] = vals[i];
220 }
221 });
222
223#if defined(AMREX_USE_CUDA) || defined(AMREX_USE_HIP)
224
225 Gpu::Buffer<int> needs_fallback({0});
226 auto* d_needs_fallback = needs_fallback.data();
227
228 amrex::launch_global<nthreads><<<nblocks, nthreads, 0, stream>>>
229 ([=] AMREX_GPU_DEVICE () noexcept
230 {
231 int wid = int(threadIdx.x)/Gpu::Device::warp_size;
232 int r = int(blockIdx.x)*nwarps_per_block + wid;
233 if (r >= nr) return;
234
235 Long const b = prow[r];
236 Long const e = prow[r+1];
237 auto const len = int(e - b);
238
239 if (len <= 1) return;
240
241 int lane = int(threadIdx.x) - wid * Gpu::Device::warp_size;
242
243 bool sorted = true;
244 for (Long i = lane + 1; i < len; i += Gpu::Device::warp_size) {
245 sorted = sorted && (pcol[b+i-1] <= pcol[b+i]);
246 }
247#if defined(AMREX_USE_CUDA)
248 if (__all_sync(0xffffffff, sorted)) { return; }
249#else
250 if (__all(sorted)) { return; }
251#endif
252
253 constexpr int ITEMS_PER_THREAD = AMREX_HIP_OR_CUDA(2,4);
254 constexpr int ITEMS_PER_WARP = Gpu::Device::warp_size * ITEMS_PER_THREAD;
255
256 if (len <= ITEMS_PER_WARP)
257 {
258#if defined(AMREX_USE_CUDA)
259 using WarpSort = cub::WarpMergeSort<Index, ITEMS_PER_THREAD, Gpu::Device::warp_size, T>;
260 __shared__ typename WarpSort::TempStorage temp_storage[nwarps_per_block];
261#elif defined(AMREX_USE_HIP)
262 using WarpSort = rocprim::warp_sort<Index, Gpu::Device::warp_size, T>;
263 __shared__ typename WarpSort::storage_type temp_storage[nwarps_per_block];
264#endif
265
266 Index keys[ITEMS_PER_THREAD];
267 T values[ITEMS_PER_THREAD];
268
269 #pragma unroll
270 for (int i = 0; i < ITEMS_PER_THREAD; ++i) {
271 int idx = lane * ITEMS_PER_THREAD + i;
272 if (idx < len) {
273 keys[i] = pcol[b + idx];
274 values[i] = pmat[b + idx];
275 } else {
276 keys[i] = std::numeric_limits<Index>::max();
277 values[i] = T{};
278 }
279 }
280
282 WarpSort{}.sort(keys, values, temp_storage[wid]),
283 WarpSort(temp_storage[wid]).Sort(
284 keys, values, [](Index x, Index y) {return x < y;}));
285
286 #pragma unroll
287 for (int i = 0; i < ITEMS_PER_THREAD; ++i) {
288 int idx = lane * ITEMS_PER_THREAD + i;
289 if (idx < len) {
290 pcol[b + idx] = keys[i];
291 pmat[b + idx] = values[i];
292 }
293 }
294 } else {
295 if (lane == 0) {
296 Gpu::Atomic::AddNoRet(d_needs_fallback, 1);
297 }
298 }
299 });
300
301 auto* h_needs_fallback = needs_fallback.copyToHost();
302
303 if (*h_needs_fallback)
304 {
305 // The item count of the segmented sort below is 32-bit.
306 AMREX_ALWAYS_ASSERT(nnz < Long(std::numeric_limits<int>::max()));
307
308 V<Index> col_index_out(col_index.size());
309 V<T> mat_out(mat.size());
310 auto* d_col_out = col_index_out.data();
311 auto* d_val_out = mat_out.data();
312
313 std::size_t temp_bytes = 0;
314
316 rocprim::segmented_radix_sort_pairs,
317 cub::DeviceSegmentedRadixSort::SortPairs)
318 (nullptr, temp_bytes, pcol, d_col_out, pmat, d_val_out,
319 nnz, nr, prow, prow+1, 0, int(sizeof(Index)*CHAR_BIT),
320 stream));
321
322 auto* d_temp = (void*) The_Arena()->alloc(temp_bytes);
323
325 rocprim::segmented_radix_sort_pairs,
326 cub::DeviceSegmentedRadixSort::SortPairs)
327 (d_temp, temp_bytes, pcol, d_col_out, pmat, d_val_out,
328 nnz, nr, prow, prow+1, 0, int(sizeof(Index)*CHAR_BIT),
329 stream));
330
331 std::swap(col_index, col_index_out);
332 std::swap(mat, mat_out);
333
335 The_Arena()->free(d_temp);
336 }
337
338 // let's test both by print matrix out to see if it's sorted.
339
341
342#elif defined(AMREX_USE_SYCL)
343
344 Gpu::Buffer<int> needs_fallback({0});
345 auto* d_needs_fallback = needs_fallback.data();
346
347 ParallelFor(nr, [=] AMREX_GPU_DEVICE (int r) noexcept
348 {
349 Long const b = prow[r];
350 Long const e = prow[r+1];
351 if (e - b <= SHORT_ROW) { return; }
352 for (Long i = b+1; i < e; ++i) {
353 if (pcol[i-1] > pcol[i]) {
354 Gpu::Atomic::AddNoRet(d_needs_fallback, 1);
355 return;
356 }
357 }
358 });
359
360 auto* h_needs_fallback = needs_fallback.copyToHost();
361
362 if (*h_needs_fallback)
363 {
364 // Longer unsorted rows: let oneMKL sort the whole matrix on the device.
365 Long const ncols = 1 + Long(Reduce::Max<Index>(nnz, pcol));
366 auto& q = Gpu::Device::streamQueue();
367 oneapi::mkl::sparse::matrix_handle_t handle{};
368 oneapi::mkl::sparse::init_matrix_handle(&handle);
369#if defined(INTEL_MKL_VERSION) && (INTEL_MKL_VERSION < 20250300)
370 oneapi::mkl::sparse::set_csr_data(q, handle, Index(nr), Index(ncols),
371 oneapi::mkl::index_base::zero, prow, pcol, pmat);
372#else
373 oneapi::mkl::sparse::set_csr_data(q, handle, Index(nr), Index(ncols), Index(nnz),
374 oneapi::mkl::index_base::zero, prow, pcol, pmat);
375#endif
376 oneapi::mkl::sparse::sort_matrix(q, handle, {}).wait();
377 oneapi::mkl::sparse::release_matrix_handle(q, &handle).wait();
378 }
379
381
382#endif
383
384#else
385
386 sort_on_host();
387
388#endif
389}
390
391template <typename T, template <typename> class V, typename Index>
393{
394 if (nnz <= 0) { return; }
395
396 constexpr int SMALL = 128;
397
398 Long nr = nrows();
399
400#ifdef AMREX_USE_OMP
401#pragma omp parallel
402#endif
403 {
404 V<Index> lcols;
405 V<T> lvals;
406 V<int> perm;
407
408 Index scols[SMALL];
409 T svals[SMALL];
410
411#ifdef AMREX_USE_OMP
412#pragma omp for
413#endif
414 for (Long r = 0; r < nr; ++r) {
415 Long const b = row_offset[r ];
416 Long const e = row_offset[r+1];
417 auto const len = int(e - b);
418
419 if (len <= 1) { continue; }
420
421 bool sorted = true;
422 for (int i = 1; i < len; ++i) {
423 if (col_index[b+i-1] > col_index[b+i]) {
424 sorted = false;
425 break;
426 }
427 }
428 if (sorted) { continue; }
429
430 if (len <= SMALL) {
431 // Insertion sort using arrays on stack
432 for (int i = 0; i < len; ++i) {
433 scols[i] = col_index[b+i];
434 svals[i] = mat [b+i];
435 }
436 for (int i = 1; i < len; ++i) {
437 auto c = scols[i];
438 auto v = svals[i];
439 auto j = i;
440 while (j > 0 && scols[j-1] > c) {
441 scols[j] = scols[j-1];
442 svals[j] = svals[j-1];
443 --j;
444 }
445 scols[j] = c;
446 svals[j] = v;
447 }
448 for (int i = 0; i < len; ++i) {
449 col_index[b+i] = scols[i];
450 mat [b+i] = svals[i];
451 }
452 } else {
453 lcols.resize(len);
454 lvals.resize(len);
455 perm.resize(len);
456
457 for (int i = 0; i < len; ++i) {
458 lcols[i] = col_index[b+i];
459 lvals[i] = mat [b+i];
460 perm [i] = i;
461 }
462
463 std::ranges::sort(perm,
464 [&] (int i0, int i1) {
465 return lcols[i0] < lcols[i1];
466 });
467
468 for (int out = 0; out < len; ++out) {
469 auto const in = perm[out];
470 col_index[b+out] = lcols[in];
471 mat [b+out] = lvals[in];
472 }
473 }
474 }
475 }
476}
477
478// CsrIndex is a helper class for cuSPARSE, because many cuSparse functions
479// such as cusparseSpGEMM do not support 64-bit integers.
480template <typename Index, template <typename> class V>
482{
483 V<Index> col_index;
484 V<Index> row_offset;
485
486 template <typename T, typename J>
487 void copyFrom (CsrView<T,J> const& csr)
488 {
489 AMREX_ALWAYS_ASSERT(csr.nnz < Long(std::numeric_limits<Index>::max()) &&
490 csr.nrows < Long(std::numeric_limits<Index>::max()));
491
492 col_index.resize(csr.nnz);
493 row_offset.resize(csr.nrows+1);
494
495 auto N = static_cast<Index>(std::max(col_index.size(), row_offset.size()));
496 auto lnnz = static_cast<Index>(col_index.size());
497 auto lnro = static_cast<Index>(row_offset.size());
498 auto* col_dst = col_index.data();
499 auto* row_dst = row_offset.data();
500 auto const* col_src = csr.col_index;
501 auto const* row_src = csr.row_offset;
502 ParallelFor(N, [=] AMREX_GPU_DEVICE (Index i)
503 {
504 if (i < lnnz) { col_dst[i] = static_cast<Index>(col_src[i]); }
505 if (i < lnro) { row_dst[i] = static_cast<Index>(row_src[i]); }
506 });
507 }
508
509 template <typename T, typename J>
510 void copyTo (CsrView<T,J> const& csr) const
511 {
512 static_assert(!std::is_const_v<T>);
513 auto N = static_cast<Index>(std::max(col_index.size(), row_offset.size()));
514 auto lnnz = static_cast<Index>(col_index.size());
515 auto lnro = static_cast<Index>(row_offset.size());
516 auto const* col_src = col_index.data();
517 auto const* row_src = row_offset.data();
518 auto* col_dst = csr.col_index;
519 auto* row_dst = csr.row_offset;
520 ParallelFor(N, [=] AMREX_GPU_DEVICE (Index i)
521 {
522 if (i < lnnz) { col_dst[i] = static_cast<J>(col_src[i]); }
523 if (i < lnro) { row_dst[i] = static_cast<J>(row_src[i]); }
524 });
525 }
526
527 template <typename T, template <typename> class U, typename J>
528 void copyTo (CSR<T,U,J>& csr) const
529 {
530 copyTo(csr.view());
531 }
532};
533
534}
535
536#endif
#define AMREX_ALWAYS_ASSERT(EX)
Definition AMReX_BLassert.H:50
#define AMREX_RESTRICT
Definition AMReX_Extension.H:37
#define AMREX_HIP_OR_CUDA(a, b)
Definition AMReX_GpuControl.H:17
#define AMREX_GPU_SAFE_CALL(call)
Definition AMReX_GpuError.H:63
#define AMREX_GPU_ERROR_CHECK()
Definition AMReX_GpuError.H:151
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
Convenience header for the core AMReX GPU facilities.
virtual void free(void *pt)=0
Free a previously allocated block pointed to by pt.
virtual void * alloc(std::size_t sz)=0
Allocate sz bytes from this arena.
Definition AMReX_GpuBuffer.H:24
T const * data() const noexcept
Definition AMReX_GpuBuffer.H:51
static constexpr int warp_size
Definition AMReX_GpuDevice.H:239
amrex_long Long
Definition AMReX_INT.H:30
T Max(N n, T const *v, T init_val=std::numeric_limits< T >::lowest())
Compute the maximum of an array of values.
Definition AMReX_Reduce.H:1125
Arena * The_Arena()
Definition AMReX_Arena.cpp:839
__host__ __device__ AMREX_FORCE_INLINE void AddNoRet(T *sum, T value) noexcept
Definition AMReX_GpuAtomic.H:283
void copyAsync(HostToDevice, InIter begin, InIter end, OutIter result) noexcept
A host-to-device copy routine. Note this is just a wrapper around memcpy, so it assumes contiguous st...
Definition AMReX_GpuContainers.H:228
void streamSynchronize() noexcept
Definition AMReX_GpuDevice.H:329
gpuStream_t gpuStream() noexcept
Definition AMReX_GpuDevice.H:310
Definition AMReX_Amr.cpp:50
@ C
Definition AMReX_Order.H:7
void ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition AMReX_CTOParallelForImpl.H:202
@ y
Definition AMReX_Orientation.H:14
@ x
Definition AMReX_Orientation.H:14
void duplicateCSR(C c, CSR< T, AD, Index > &dst, CSR< T, AS, Index > const &src)
Definition AMReX_CSR.H:131
const int[]
Definition AMReX_BLProfiler.cpp:1665
__global__ void launch_global(L f0, Lambdas... fs)
Definition AMReX_GpuLaunch.H:68
Owning CSR container backed by AMReX resizable vectors.
Definition AMReX_CSR.H:59
CsrView< T, Index > view()
Mutable view of the underlying buffers.
Definition AMReX_CSR.H:88
void resize(Long num_rows, Long num_non_zeros)
Resize the storage to accommodate num_rows and num_non_zeros entries.
Definition AMReX_CSR.H:80
Index index_type
Definition AMReX_CSR.H:60
CsrView< T const, Index > view() const
Const view of the underlying buffers.
Definition AMReX_CSR.H:94
Long nrows() const
Number of logical rows represented by the CSR offset array.
Definition AMReX_CSR.H:67
void sort()
Sort each row by column index. Uses GPU acceleration when possible.
Definition AMReX_CSR.H:152
void sort_on_host()
Host-only fallback that sorts column indices row by row.
Definition AMReX_CSR.H:392
CsrView< T const, Index > const_view() const
Convenience alias for view() const.
Definition AMReX_CSR.H:103
Definition AMReX_CSR.H:482
V< Index > row_offset
Definition AMReX_CSR.H:484
void copyFrom(CsrView< T, J > const &csr)
Definition AMReX_CSR.H:487
void copyTo(CSR< T, U, J > &csr) const
Definition AMReX_CSR.H:528
void copyTo(CsrView< T, J > const &csr) const
Definition AMReX_CSR.H:510
V< Index > col_index
Definition AMReX_CSR.H:483
Lightweight non-owning CSR view that can point to host or device buffers.
Definition AMReX_CSR.H:41
Index index_type
Definition AMReX_CSR.H:42
Long nrows
Definition AMReX_CSR.H:48
Long nnz
Definition AMReX_CSR.H:47
CI *__restrict__ col_index
Definition AMReX_CSR.H:45
T *__restrict__ mat
Definition AMReX_CSR.H:44
CI *__restrict__ row_offset
Definition AMReX_CSR.H:46
std::conditional_t< std::is_const_v< T >, Index const, Index > CI
Definition AMReX_CSR.H:43