compute-acceleration
GEMM and sum reduction across serial, OpenMP and CUDA
Loading...
Searching...
No Matches
sum_reduction.cuh
1// Copyright (c) 2025 yanghuafang
2// SPDX-License-Identifier: MIT
3
4#ifndef ACCEL_CUDA_SUM_REDUCTION_CUH_
5#define ACCEL_CUDA_SUM_REDUCTION_CUH_
6
7#include <cstddef>
8
9#include <cuda_runtime.h>
10
11namespace accel {
12
13// Must stay a power of two: the halving loop in BlockSumReduceKernel drops the
14// odd element at every other level, silently under-counting.
15inline constexpr int kReductionBlockSize = 512;
16
17// Just how many partial sums the host is asked to finish -- the grid-stride
18// load decouples this from the input length.
19inline constexpr int kReductionGridSize = 8;
20
21// Reduces `input` to one partial sum per block: a grid-stride accumulation
22// folding an arbitrary length into one value per thread, then a shared-memory
23// tree. The grid-stride loop is what frees grid size from input size; the
24// obvious alternative, blocks * threads == count, reads out of bounds for
25// every other pairing.
26//
27// The host finishes the handful of partials. A second launch would cost more
28// than the host add, and keeping the last step on the CPU makes the numerics
29// inspectable.
30//
31// Addition is not associative, so the result depends on the launch geometry;
32// compare against a host sum by relative tolerance, never equality.
33//
34// Requires blockDim.x a power of two and blockDim.x * sizeof(float) bytes of
35// dynamic shared memory. `partials` holds at least gridDim.x floats; `count`
36// may be zero.
37__global__ void BlockSumReduceKernel(const float* __restrict__ input,
38 float* __restrict__ partials,
39 std::size_t count);
40
41// Validates geometry and enqueues BlockSumReduceKernel. Asynchronous; the
42// caller synchronises. Shared-memory size is derived from block_size here so
43// call sites cannot desynchronise the two.
44//
45// block_size must be a power of two in (0, 1024]. Throws
46// std::invalid_argument on bad geometry, CudaError if the launch is rejected.
47void LaunchBlockSumReduce(const float* input, float* partials,
48 std::size_t count, int grid_size = kReductionGridSize,
49 int block_size = kReductionBlockSize,
50 cudaStream_t stream = nullptr);
51
52// Allocates, uploads, reduces, downloads and finishes on the host. For tests;
53// the benchmark driver keeps the stages separate so it can time the kernel
54// alone. Accumulates the partials in double so the tail does not lose what the
55// kernel preserved.
56//
57// Throws std::invalid_argument on bad geometry, CudaError on runtime failure.
58// Device allocations are released either way.
59double SumOnDevice(const float* host_input, std::size_t count,
60 int grid_size = kReductionGridSize,
61 int block_size = kReductionBlockSize);
62
63} // namespace accel
64
65#endif // ACCEL_CUDA_SUM_REDUCTION_CUH_