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#include <algorithm>
14#include <concepts>
15#include <climits>
16#include <type_traits>
17
18namespace amrex {
19
34template <typename T, typename I = Long>
35struct CsrView {
36 using index_type = I;
37 using CI = std::conditional_t<std::is_const_v<T>, I const, I>;
38 T* AMREX_RESTRICT mat = nullptr;
41 Long nnz = 0;
43};
44
52template <typename T, template <typename> class V, typename I = Long>
53struct CSR {
54 using index_type = I;
55 V<T> mat;
58 Long nnz = 0;
59
61 [[nodiscard]] Long nrows () const {
62 return row_offset.empty() ? Long(0) : Long(row_offset.size())-1;
63 }
64
74 void resize (Long num_rows, Long num_non_zeros) {
75 mat.resize(num_non_zeros);
76 col_index.resize(num_non_zeros);
77 row_offset.resize(num_rows+1);
78 nnz = num_non_zeros;
79 }
80
83 return CsrView<T,I>{mat.data(), col_index.data(), row_offset.data(),
84 nnz, Long(row_offset.size())-1};
85 }
86
88 [[nodiscard]] CsrView<T const,I> view () const {
89 return CsrView<T const,I>{.mat = mat.data(),
90 .col_index = col_index.data(),
91 .row_offset = row_offset.data(),
92 .nnz = nnz,
93 .nrows = Long(row_offset.size())-1};
94 }
95
97 [[nodiscard]] CsrView<T const,I> const_view () const {
98 return CsrView<T const,I>{mat.data(), col_index.data(), row_offset.data(),
99 nnz, Long(row_offset.size())-1};
100 }
101
105 void sort ();
106
111};
112
113template <typename C, typename T, template<typename> class AD, template<typename> class AS,
114 typename I>
115requires (std::same_as<C,Gpu::HostToDevice> ||
116 std::same_as<C,Gpu::DeviceToHost> ||
117 std::same_as<C,Gpu::DeviceToDevice>)
125void duplicateCSR (C c, CSR<T,AD,I>& dst, CSR<T,AS,I> const& src)
126{
127 dst.mat.resize(src.mat.size());
128 dst.col_index.resize(src.col_index.size());
129 dst.row_offset.resize(src.row_offset.size());
131 src.mat.begin(),
132 src.mat.end(),
133 dst.mat.begin());
135 src.col_index.begin(),
136 src.col_index.end(),
137 dst.col_index.begin());
139 src.row_offset.begin(),
140 src.row_offset.end(),
141 dst.row_offset.begin());
142 dst.nnz = src.nnz;
143}
144
145template <typename T, template <typename> class V, typename I>
147{
148 if (nnz <= 0) { return; }
149
150#ifdef AMREX_USE_GPU
151
152#if defined(AMREX_USE_CUDA) || defined(AMREX_USE_HIP)
153
154 // The function is synchronous. If that is no longer the case, we might
155 // need to update SpMatrix::define.
156
157 constexpr int nthreads = 256;
158 constexpr int nwarps_per_block = nthreads / Gpu::Device::warp_size;
159
160 AMREX_ALWAYS_ASSERT((nrows()+nwarps_per_block-1) < Long(std::numeric_limits<int>::max()));
161
162 auto nr = int(nrows());
163 int nblocks = (nr + nwarps_per_block-1) / nwarps_per_block;
164 auto const& stream = Gpu::gpuStream();
165
166 auto* pmat = mat.data();
167 auto* pcol = col_index.data();
168 auto* prow = row_offset.data();
169
170 // Short rows (the common case on fine AMG levels): one thread per row,
171 // insertion sort in registers. Longer rows are left to the warp kernel
172 // below. Both kernels skip rows that are already sorted.
173 // The cap keeps the per-thread arrays at ~256 bytes for wide value types.
174 constexpr int SHORT_ROW = std::clamp(int(256/(sizeof(I)+sizeof(T))), 4, 16);
175 ParallelFor(nr, [=] AMREX_GPU_DEVICE (int r) noexcept
176 {
177 Long const b = prow[r];
178 int const len = int(prow[r+1] - b);
179 if (len <= 1 || len > SHORT_ROW) { return; }
180 I keys[SHORT_ROW];
181 bool sorted = true;
182 for (int i = 0; i < len; ++i) {
183 keys[i] = pcol[b+i];
184 if (i > 0) { sorted = sorted && (keys[i-1] <= keys[i]); }
185 }
186 if (sorted) { return; }
187 T vals[SHORT_ROW];
188 for (int i = 0; i < len; ++i) {
189 vals[i] = pmat[b+i];
190 }
191 for (int i = 1; i < len; ++i) {
192 I const k = keys[i];
193 T const v = vals[i];
194 int j = i - 1;
195 while (j >= 0 && keys[j] > k) {
196 keys[j+1] = keys[j];
197 vals[j+1] = vals[j];
198 --j;
199 }
200 keys[j+1] = k;
201 vals[j+1] = v;
202 }
203 for (int i = 0; i < len; ++i) {
204 pcol[b+i] = keys[i];
205 pmat[b+i] = vals[i];
206 }
207 });
208
209 Gpu::Buffer<int> needs_fallback({0});
210 auto* d_needs_fallback = needs_fallback.data();
211
212 amrex::launch_global<nthreads><<<nblocks, nthreads, 0, stream>>>
213 ([=] AMREX_GPU_DEVICE () noexcept
214 {
215 int wid = int(threadIdx.x)/Gpu::Device::warp_size;
216 int r = int(blockIdx.x)*nwarps_per_block + wid;
217 if (r >= nr) return;
218
219 Long const b = prow[r];
220 Long const e = prow[r+1];
221 auto const len = int(e - b);
222
223 if (len <= 1) return;
224
225 int lane = int(threadIdx.x) - wid * Gpu::Device::warp_size;
226
227 bool sorted = true;
228 for (Long i = lane + 1; i < len; i += Gpu::Device::warp_size) {
229 sorted = sorted && (pcol[b+i-1] <= pcol[b+i]);
230 }
231#if defined(AMREX_USE_CUDA)
232 if (__all_sync(0xffffffff, sorted)) { return; }
233#else
234 if (__all(sorted)) { return; }
235#endif
236
237 constexpr int ITEMS_PER_THREAD = AMREX_HIP_OR_CUDA(2,4);
238 constexpr int ITEMS_PER_WARP = Gpu::Device::warp_size * ITEMS_PER_THREAD;
239
240 if (len <= ITEMS_PER_WARP)
241 {
242#if defined(AMREX_USE_CUDA)
243 using WarpSort = cub::WarpMergeSort<I, ITEMS_PER_THREAD, Gpu::Device::warp_size, T>;
244 __shared__ typename WarpSort::TempStorage temp_storage[nwarps_per_block];
245#elif defined(AMREX_USE_HIP)
246 using WarpSort = rocprim::warp_sort<I, Gpu::Device::warp_size, T>;
247 __shared__ typename WarpSort::storage_type temp_storage[nwarps_per_block];
248#endif
249
250 I keys[ITEMS_PER_THREAD];
251 T values[ITEMS_PER_THREAD];
252
253 #pragma unroll
254 for (int i = 0; i < ITEMS_PER_THREAD; ++i) {
255 int idx = lane * ITEMS_PER_THREAD + i;
256 if (idx < len) {
257 keys[i] = pcol[b + idx];
258 values[i] = pmat[b + idx];
259 } else {
260 keys[i] = std::numeric_limits<I>::max();
261 values[i] = T{};
262 }
263 }
264
266 WarpSort{}.sort(keys, values, temp_storage[wid]),
267 WarpSort(temp_storage[wid]).Sort(
268 keys, values, [](I x, I y) {return x < y;}));
269
270 #pragma unroll
271 for (int i = 0; i < ITEMS_PER_THREAD; ++i) {
272 int idx = lane * ITEMS_PER_THREAD + i;
273 if (idx < len) {
274 pcol[b + idx] = keys[i];
275 pmat[b + idx] = values[i];
276 }
277 }
278 } else {
279 if (lane == 0) {
280 Gpu::Atomic::AddNoRet(d_needs_fallback, 1);
281 }
282 }
283 });
284
285 auto* h_needs_fallback = needs_fallback.copyToHost();
286
287 if (*h_needs_fallback)
288 {
289 // The item count of the segmented sort below is 32-bit.
290 AMREX_ALWAYS_ASSERT(nnz < Long(std::numeric_limits<int>::max()));
291
292 V<I> col_index_out(col_index.size());
293 V<T> mat_out(mat.size());
294 auto* d_col_out = col_index_out.data();
295 auto* d_val_out = mat_out.data();
296
297 std::size_t temp_bytes = 0;
298
300 rocprim::segmented_radix_sort_pairs,
301 cub::DeviceSegmentedRadixSort::SortPairs)
302 (nullptr, temp_bytes, pcol, d_col_out, pmat, d_val_out,
303 nnz, nr, prow, prow+1, 0, int(sizeof(I)*CHAR_BIT),
304 stream));
305
306 auto* d_temp = (void*) The_Arena()->alloc(temp_bytes);
307
309 rocprim::segmented_radix_sort_pairs,
310 cub::DeviceSegmentedRadixSort::SortPairs)
311 (d_temp, temp_bytes, pcol, d_col_out, pmat, d_val_out,
312 nnz, nr, prow, prow+1, 0, int(sizeof(I)*CHAR_BIT),
313 stream));
314
315 std::swap(col_index, col_index_out);
316 std::swap(mat, mat_out);
317
319 The_Arena()->free(d_temp);
320 }
321
322 // let's test both by print matrix out to see if it's sorted.
323
325
326#elif defined(AMREX_USE_SYCL)
327
328 // xxxxx TODO SYCL: Let's not worry about performance for now.
330 duplicateCSR(Gpu::deviceToHost, h_csr, *this);
332 h_csr.sort_on_host();
333 duplicateCSR(Gpu::hostToDevice, *this, h_csr);
335
336#endif
337
338#else
339
340 sort_on_host();
341
342#endif
343}
344
345template <typename T, template <typename> class V, typename I>
347{
348 if (nnz <= 0) { return; }
349
350 constexpr int SMALL = 128;
351
352 Long nr = nrows();
353
354#ifdef AMREX_USE_OMP
355#pragma omp parallel
356#endif
357 {
358 V<I> lcols;
359 V<T> lvals;
360 V<int> perm;
361
362 I scols[SMALL];
363 T svals[SMALL];
364
365#ifdef AMREX_USE_OMP
366#pragma omp for
367#endif
368 for (Long r = 0; r < nr; ++r) {
369 Long const b = row_offset[r ];
370 Long const e = row_offset[r+1];
371 auto const len = int(e - b);
372
373 if (len <= 1) { continue; }
374
375 bool sorted = true;
376 for (int i = 1; i < len; ++i) {
377 if (col_index[b+i-1] > col_index[b+i]) {
378 sorted = false;
379 break;
380 }
381 }
382 if (sorted) { continue; }
383
384 if (len <= SMALL) {
385 // Insertion sort using arrays on stack
386 for (int i = 0; i < len; ++i) {
387 scols[i] = col_index[b+i];
388 svals[i] = mat [b+i];
389 }
390 for (int i = 1; i < len; ++i) {
391 auto c = scols[i];
392 auto v = svals[i];
393 auto j = i;
394 while (j > 0 && scols[j-1] > c) {
395 scols[j] = scols[j-1];
396 svals[j] = svals[j-1];
397 --j;
398 }
399 scols[j] = c;
400 svals[j] = v;
401 }
402 for (int i = 0; i < len; ++i) {
403 col_index[b+i] = scols[i];
404 mat [b+i] = svals[i];
405 }
406 } else {
407 lcols.resize(len);
408 lvals.resize(len);
409 perm.resize(len);
410
411 for (int i = 0; i < len; ++i) {
412 lcols[i] = col_index[b+i];
413 lvals[i] = mat [b+i];
414 perm [i] = i;
415 }
416
417 std::ranges::sort(perm,
418 [&] (int i0, int i1) {
419 return lcols[i0] < lcols[i1];
420 });
421
422 for (int out = 0; out < len; ++out) {
423 auto const in = perm[out];
424 col_index[b+out] = lcols[in];
425 mat [b+out] = lvals[in];
426 }
427 }
428 }
429 }
430}
431
432// CsrIndex is a helper class for cuSPARSE, because many cuSparse functions
433// such as cusparseSpGEMM do not support 64-bit integers.
434template <typename I, template <typename> class V>
436{
439
440 template <typename T, typename J>
441 void copyFrom (CsrView<T,J> const& csr)
442 {
443 AMREX_ALWAYS_ASSERT(csr.nnz < Long(std::numeric_limits<I>::max()) &&
444 csr.nrows < Long(std::numeric_limits<I>::max()));
445
446 col_index.resize(csr.nnz);
447 row_offset.resize(csr.nrows+1);
448
449 auto N = static_cast<I>(std::max(col_index.size(), row_offset.size()));
450 auto lnnz = static_cast<I>(col_index.size());
451 auto lnro = static_cast<I>(row_offset.size());
452 auto* col_dst = col_index.data();
453 auto* row_dst = row_offset.data();
454 auto const* col_src = csr.col_index;
455 auto const* row_src = csr.row_offset;
456 ParallelFor(N, [=] AMREX_GPU_DEVICE (I i)
457 {
458 if (i < lnnz) { col_dst[i] = static_cast<I>(col_src[i]); }
459 if (i < lnro) { row_dst[i] = static_cast<I>(row_src[i]); }
460 });
461 }
462
463 template <typename T, typename J>
464 void copyTo (CsrView<T,J> const& csr) const
465 {
466 static_assert(!std::is_const_v<T>);
467 auto N = static_cast<I>(std::max(col_index.size(), row_offset.size()));
468 auto lnnz = static_cast<I>(col_index.size());
469 auto lnro = static_cast<I>(row_offset.size());
470 auto const* col_src = col_index.data();
471 auto const* row_src = row_offset.data();
472 auto* col_dst = csr.col_index;
473 auto* row_dst = csr.row_offset;
474 ParallelFor(N, [=] AMREX_GPU_DEVICE (I i)
475 {
476 if (i < lnnz) { col_dst[i] = static_cast<J>(col_src[i]); }
477 if (i < lnro) { row_dst[i] = static_cast<J>(row_src[i]); }
478 });
479 }
480
481 template <typename T, template <typename> class U, typename J>
482 void copyTo (CSR<T,U,J>& csr) const
483 {
484 copyTo(csr.view());
485 }
486};
487
488}
489
490#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:236
amrex_long Long
Definition AMReX_INT.H:30
Arena * The_Arena()
Definition AMReX_Arena.cpp:829
__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
static constexpr DeviceToHost deviceToHost
Definition AMReX_GpuContainers.H:106
static constexpr HostToDevice hostToDevice
Definition AMReX_GpuContainers.H:105
void streamSynchronize() noexcept
Definition AMReX_GpuDevice.H:310
gpuStream_t gpuStream() noexcept
Definition AMReX_GpuDevice.H:291
Definition AMReX_Amr.cpp:50
void duplicateCSR(C c, CSR< T, AD, I > &dst, CSR< T, AS, I > const &src)
Definition AMReX_CSR.H:125
void ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition AMReX_CTOParallelForImpl.H:202
const int[]
Definition AMReX_BLProfiler.cpp:1665
Owning CSR container backed by AMReX resizable vectors.
Definition AMReX_CSR.H:53
CsrView< T const, I > const_view() const
Convenience alias for view() const.
Definition AMReX_CSR.H:97
V< I > row_offset
Definition AMReX_CSR.H:57
void sort_on_host()
Host-only fallback that sorts column indices row by row.
Definition AMReX_CSR.H:346
Long nrows() const
Number of logical rows represented by the CSR offset array.
Definition AMReX_CSR.H:61
I index_type
Definition AMReX_CSR.H:54
CsrView< T, I > view()
Mutable view of the underlying buffers.
Definition AMReX_CSR.H:82
Long nnz
Definition AMReX_CSR.H:58
V< I > col_index
Definition AMReX_CSR.H:56
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:74
void sort()
Sort each row by column index. Uses GPU acceleration when possible.
Definition AMReX_CSR.H:146
CsrView< T const, I > view() const
Const view of the underlying buffers.
Definition AMReX_CSR.H:88
V< T > mat
Definition AMReX_CSR.H:55
Definition AMReX_CSR.H:436
void copyTo(CsrView< T, J > const &csr) const
Definition AMReX_CSR.H:464
void copyTo(CSR< T, U, J > &csr) const
Definition AMReX_CSR.H:482
void copyFrom(CsrView< T, J > const &csr)
Definition AMReX_CSR.H:441
V< I > row_offset
Definition AMReX_CSR.H:438
V< I > col_index
Definition AMReX_CSR.H:437
Lightweight non-owning CSR view that can point to host or device buffers.
Definition AMReX_CSR.H:35
I index_type
Definition AMReX_CSR.H:36
std::conditional_t< std::is_const_v< T >, I const, I > CI
Definition AMReX_CSR.H:37
CI *__restrict__ col_index
Definition AMReX_CSR.H:39
T *__restrict__ mat
Definition AMReX_CSR.H:38
CI *__restrict__ row_offset
Definition AMReX_CSR.H:40
Long nnz
Definition AMReX_CSR.H:41
Long nrows
Definition AMReX_CSR.H:42