Cholla 3.0.1-dev
Cholla - Massively parallel hydro on GPUs
Loading...
Searching...
No Matches
reduction_utilities.h
Go to the documentation of this file.
1
8#pragma once
9
10// STL Includes
11#include <climits>
12#include <cstdint>
13
14// External Includes
15
16// Local Includes
17#include "../global/global.h"
18#include "../global/global_cuda.h"
19#include "../utils/gpu.hpp"
20
21namespace reduction_utilities::backport
22{
31template <class To, class From>
32__device__ constexpr To bit_cast(const From& from) noexcept
33{
34 // TODO: replace with `std::bitcast` once we adopt C++20 or libcu++ adds it
35 To to{};
36 static_assert(sizeof(To) == sizeof(From));
37 memcpy(&to, &from, sizeof(To));
38 return to;
39}
40
48inline long long __device__ atomicMin(long long* address, long long val)
49{
50 // this uses the pattern recommended by CUDA docs for implementing atomics in terms of CAS
51 unsigned long long* address_as_ull = (unsigned long long*)address;
52 unsigned long long old = *address_as_ull;
53 unsigned long long assumed;
54
55 do {
56 assumed = old;
57 long long assumed_LL = bit_cast<long long>(assumed);
58 long long newval_LL = (assumed_LL < val) ? assumed_LL : val;
59 old = atomicCAS(address_as_ull, assumed, bit_cast<long long>(newval_LL));
60
61 } while (assumed != old);
62
63 return bit_cast<long long>(old);
64}
65
66} // namespace reduction_utilities::backport
67
68#if defined(O_HIP) && (HIP_VERSION < 50700000)
69// HIP versions before 5.7 did not implement atomicMin (or atomicMax) for `long long`, so we
70// backport the function
71
72// expose atomicMin as part of the global namespace
73using ::reduction_utilities::backport::atomicMin;
74
75#endif // defined(O_HIP) && (HIP_VERSION < 50700000)
76
83namespace reduction_utilities
84{
85// =====================================================================
94__inline__ __device__ Real warpReduceMax(Real val)
95{
96 for (int offset = warpSize / 2; offset > 0; offset /= 2) {
97 val = max(val, __shfl_down(val, offset));
98 }
99 return val;
100}
101// =====================================================================
102
103// =====================================================================
112__inline__ __device__ Real blockReduceMax(Real val)
113{
114 // Shared memory for storing the results of each warp-wise partial
115 // reduction
116 __shared__ Real shared[::maxWarpsPerBlock];
117
118 int lane = threadIdx.x % warpSize; // thread ID within the warp,
119 int warpId = threadIdx.x / warpSize; // ID of the warp itself
120
121 val = warpReduceMax(val); // Each warp performs partial reduction
122
123 if (lane == 0) {
124 shared[warpId] = val;
125 } // Write reduced value to shared memory
126
127 __syncthreads(); // Wait for all partial reductions
128
129 // read from shared memory only if that warp existed
130 val = (threadIdx.x < blockDim.x / warpSize) ? shared[lane] : 0;
131
132 if (warpId == 0) {
133 val = warpReduceMax(val);
134 } // Final reduce within first warp
135
136 return val;
137}
138// =====================================================================
139
140#ifndef O_HIP
141// =====================================================================
142// This section handles the atomics. It is complicated because CUDA
143// doesn't currently support atomics with non-integral types.
144// This code is taken from
145// https://github.com/rapidsai/cuml/blob/dc14361ba11c41f7a4e1e6a3625bbadd0f52daf7/cpp/src_prims/stats/minmax.cuh
146// with slight tweaks for our use case.
147// =====================================================================
148
155inline __device__ int encode(float val)
156{
157 int i = backport::bit_cast<int>(val);
158 return i >= 0 ? i : (1 << 31) | ~i; // NOLINT(hicpp-signed-bitwise)
159}
160
167inline __device__ long long encode(double val)
168{
169 auto i = backport::bit_cast<std::int64_t>(val);
170 return i >= 0 ? i : (1ULL << 63) | ~i; // NOLINT(hicpp-signed-bitwise)
171}
172
179inline __device__ float decode(int val)
180{
181 if (val < 0) {
182 val = (1 << 31) | ~val; // NOLINT(hicpp-signed-bitwise)
183 }
184 return backport::bit_cast<float>(val);
185}
186
193inline __device__ double decode(long long val)
194{
195 if (val < 0) {
196 val = (1ULL << 63) | ~val; // NOLINT(hicpp-signed-bitwise)
197 }
198 return backport::bit_cast<double>(val);
199}
200#endif // O_HIP
210inline __device__ float atomicMaxBits(float* address, float val)
211{
212#ifdef O_HIP
213 return atomicMax(address, val);
214#else // O_HIP
215 int old = atomicMax((int*)address, encode(val));
216 return decode(old);
217#endif // O_HIP
218}
219
229inline __device__ double atomicMaxBits(double* address, double val)
230{
231#ifdef O_HIP
232 return atomicMax(address, val);
233#else // O_HIP
234 long long old = atomicMax((long long*)address, encode(val));
235 return decode(old);
236#endif // O_HIP
237}
238
248inline __device__ float atomicMinBits(float* address, float val)
249{
250#ifdef O_HIP
251 return atomicMin(address, val);
252#else // O_HIP
253 int old = atomicMin((int*)address, encode(val));
254 return decode(old);
255#endif // O_HIP
256}
257
267inline __device__ double atomicMinBits(double* address, double val)
268{
269#ifdef O_HIP
270 return atomicMin(address, val);
271#else // O_HIP
272 long long old = atomicMin((long long*)address, encode(val));
273 return decode(old);
274#endif // O_HIP
275}
276// =====================================================================
277
278// =====================================================================
314__inline__ __device__ void gridReduceMax(Real val, Real* out)
315{
316 // Reduce the entire block in parallel
317 val = blockReduceMax(val);
318
319 // Write block level reduced value to the output scalar atomically
320 if (threadIdx.x == 0) {
321 atomicMaxBits(out, val);
322 }
323}
324// =====================================================================
325
326// =====================================================================
344__global__ void kernelReduceMax(Real* in, Real* out, size_t N);
345// =====================================================================
346
347// =====================================================================
348/* Performs N sum-reductions on an input shared memory array within the block and adds the results to an
349 * N-element array.
350 *
351 * \tparam N the length of the output array
352 * \tparam blocksize the number of threads per block
353 *
354 * \param[out] dest destination memory address containing N entries. This is NOT ALLOWED to overlap with
355 * src_shared
356 * \param[in] src_shared Source memory address. This MUST refer to a __shared__ memory region containing
357 * N times blocksize elements. The numbers that are added together are offset by blocksize elements.
358 *
359 * \note
360 * If passing src to a function causes problems (since we are potentially obfuscating the __shared__ descriptor),
361 * we can always convert this into a macro.
362 */
363template <std::size_t N, std::size_t Blocksize>
364__device__ void blockAccumulateIntoNReals(Real* __restrict__ dest, Real* __restrict__ src_shared)
365{
366 // reduce the info from all the threads within the block (since src_shared was declared as __shared__,
367 // each block has its own copy of src_shared)
368 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
369 if (threadIdx.x < s) {
370 for (unsigned int cur_ind = 0; cur_ind < N; cur_ind++) {
371 src_shared[N * threadIdx.x + cur_ind] += src_shared[N * (threadIdx.x + s) + cur_ind];
372 }
373 }
374 __syncthreads();
375 }
376
377 // atomicAdd reduces across all blocks
378 if (threadIdx.x == 0) {
379 for (unsigned int cur_ind = 0; cur_ind < N; cur_ind++) {
380 atomicAdd(dest + cur_ind, // <- pointer arithmetic
381 src_shared[cur_ind]);
382 }
383 }
384}
385} // namespace reduction_utilities
Namespace to contain device resident reduction functions. Includes functions and kernels for array re...
Definition reduction_utilities.cu:17
__device__ float atomicMinBits(float *address, float val)
Perform an atomic reduction to find the minimum value of val
Definition reduction_utilities.h:248
__device__ float decode(int val)
Decodes an int as a float.
Definition reduction_utilities.h:179
__device__ void gridReduceMax(Real val, Real *out)
Perform a reduction within the grid to find the maximum value of val. Note that the value of out shou...
Definition reduction_utilities.h:314
__device__ Real warpReduceMax(Real val)
Perform a reduction within the warp/wavefront to find the maximum value of val
Definition reduction_utilities.h:94
__device__ float atomicMaxBits(float *address, float val)
Perform an atomic reduction to find the maximum value of val
Definition reduction_utilities.h:210
__device__ int encode(float val)
Encode a float as an int.
Definition reduction_utilities.h:155
__global__ void kernelReduceMax(Real *in, Real *out, size_t N)
Find the maximum value in the array. Make sure to initialize out correctly before using this kernel; ...
Definition reduction_utilities.cu:19
__device__ Real blockReduceMax(Real val)
Perform a reduction within the block to find the maximum value of val
Definition reduction_utilities.h:112
long long __device__ atomicMin(long long *address, long long val)
Perform an atomic reduction to find the minimum value of val
Definition reduction_utilities.h:48
__device__ constexpr To bit_cast(const From &from) noexcept
Do a device side bit cast.
Definition reduction_utilities.h:32