Cooperative groups (common subset)#

A shim for the widely-used cooperative_groups API so kernels written against it run on the simulator: group sync maps to the block/warp barriers and tiles map to warp-width shuffles. Covers thread_block, thread_block_tile<N> (N a power of two, 1..32), tiled_partition<N>, sync / thread_rank / size / num_threads / meta_group_*, tile shfl/shfl_up/down/xor + any/all/ballot, and cg::reduce with plus/less/greater. coalesced_group / coalesced_threads() / binary_partition / labeled_partition are modelled as per-thread singletons (see coalesced_group below). NOT modeled: grid / multi-grid groups (this_grid/grid.sync — cooperative launch), dynamic tiled_partition(g, n). A tile of N<32 syncs at warp granularity (correct when the whole block uses one tile size). Porting a real CG kernel: guard its own header include with #ifdef __CUDACC__ #include <cooperative_groups.h> // (+ …/reduce.h if it uses cg::reduce) #endif since the simulator supplies the namespace itself (see CLAUDE.md).

Defined in code/simulator.h.

This page documents 30 symbols: 3 classes, 1 constructors, 26 functions.

Classes#

Signature

Description

Availability

Location

Example

class thread_block : public thread_group

sim only

simulator.h:1182

class thread_block_tile : public thread_group

A static-size sub-group of N consecutive lanes.

sim only

simulator.h:1193

View

class thread_group

A group is either the whole block (tile_ == 0) or a runtime-sized tile of tile_ consecutive lanes.

sim only

simulator.h:1170

Constructors#

Signature

Description

Availability

Location

Example

thread_group() = default

sim only

simulator.h:1172

Functions#

Signature

Description

Availability

Location

Example

int all(int pred) const

sim only

simulator.h:1218

View

int any(int pred) const

sim only

simulator.h:1217

View

unsigned ballot(int pred) const

sim only

simulator.h:1219

View

dim3 group_dim() const

sim only

simulator.h:1186

uint3 group_index() const

sim only

simulator.h:1184

View

unsigned meta_group_rank() const

sim only

simulator.h:1210

View

unsigned meta_group_size() const

sim only

simulator.h:1211

View

unsigned num_threads() const

sim only

simulator.h:1177

unsigned num_threads() const

sim only

simulator.h:1209

T shfl(T v, unsigned src) const

sim only

simulator.h:1213

View

T shfl_down(T v, unsigned d) const

sim only

simulator.h:1215

View

T shfl_up(T v, unsigned d) const

sim only

simulator.h:1214

T shfl_xor(T v, unsigned m) const

sim only

simulator.h:1216

unsigned size() const

sim only

simulator.h:1176

View

unsigned size() const

sim only

simulator.h:1208

View

void sync() const

sim only

simulator.h:1174

View

inline void sync(const thread_group& g)

sim only

simulator.h:1190

View

void sync() const

sim only

simulator.h:1206

View

inline thread_block this_thread_block()

sim only

simulator.h:1189

View

explicit thread_group(unsigned tile) : tile_(tile)

sim only

simulator.h:1173

uint3 thread_index() const

sim only

simulator.h:1185

View

unsigned thread_rank() const

sim only

simulator.h:1175

View

unsigned thread_rank() const

sim only

simulator.h:1207

View

static unsigned tile_mask()

The participating lanes are exactly this tile’s N lanes, so the tile rendezvous releases as soon as they arrive - other tiles of the same warp may be somewhere else entirely (independent thread …

sim only

simulator.h:1200

inline thread_block_tile<N> tiled_partition(const thread_group&)

sim only

simulator.h:1222

View

inline thread_group tiled_partition(const thread_group&, unsigned n)

Old-API dynamic partition: the width is a runtime value.

sim only

simulator.h:1225

View

Examples#

Code from the repository’s example corpus (code\*.cu); the highlighted line is the call site. These blocks are what the View links above open.

thread_block_tile (test_web_bf_warp_cg.cu:133)
123    {
124        if (tid < offset)
125        {
126            s_y[tid] += s_y[tid + offset];
127        }
128        __syncthreads();
129    }
130
131    real y = s_y[tid];
132
133    thread_block_tile<32> g = tiled_partition<32>(this_thread_block());
134    for (int i = g.size() >> 1; i > 0; i >>= 1)
135    {
136        y += g.shfl_down(y, i);
137    }
138
139    if (tid == 0)
140    {
141        atomicAdd(d_y, y);
142    }
143}
all (test_web_warp_primitives.cu:124)
114        if (v_down[t]      != e_down)    ok = false;
115        if (v_xor[t]       != e_xor)     ok = false;
116    }
117
118    std::printf("threadIdx: ");   for (int t=0;t<n;++t) std::printf("%2d ", t);           std::printf("\n");
119    std::printf("lane_id:   ");   for (int t=0;t<n;++t) std::printf("%2d ", lane[t]);     std::printf("\n");
120    std::printf("shfl(l2):  ");   for (int t=0;t<n;++t) std::printf("%2d ", v_shfl[t]);   std::printf("\n");
121    std::printf("shfl_up:   ");   for (int t=0;t<n;++t) std::printf("%2d ", v_up[t]);     std::printf("\n");
122    std::printf("shfl_down: ");   for (int t=0;t<n;++t) std::printf("%2d ", v_down[t]);   std::printf("\n");
123    std::printf("shfl_xor:  ");   for (int t=0;t<n;++t) std::printf("%2d ", v_xor[t]);    std::printf("\n");
124    std::printf("ballot(tid>0)=%x  ballot(tid==0)=%x  all(FULL)=%d  any(FULL)=%d\n",
125                ballot_pos[0], ballot_zero[0], all_full[0], any_full[0]);
126    std::printf("warp primitives (%u lanes, width %u): %s\n",
127                BLOCK_SIZE, WIDTH, ok ? "PASSED" : "FAILED");
128
129    CHECK(cudaFree(d_lane)); CHECK(cudaFree(d_all)); CHECK(cudaFree(d_any));
130    CHECK(cudaFree(d_shfl)); CHECK(cudaFree(d_up)); CHECK(cudaFree(d_down));
131    CHECK(cudaFree(d_xor)); CHECK(cudaFree(d_bpos)); CHECK(cudaFree(d_bzero));
132    return ok ? 0 : 1;
133}
any (test_web_cs_reduction_full.cu:111)
101    }
102
103    // write result for this block to global mem
104    if (cta.thread_rank() == 0)
105        g_odata[blockIdx.x] = mySum;
106}
107
108/*
109    This version is completely unrolled, unless warp shuffle is available, then
110    shuffle is used within a loop. It uses a template parameter to achieve
111    optimal code for any (power of 2) number of threads. (verbatim)
112*/
113template <class T, unsigned int blockSize> __global__ void reduce5(T *g_idata, T *g_odata, unsigned int n) {
114    // Handle to thread block group
115    cg::thread_block cta   = cg::this_thread_block();
116    T               *sdata = SharedMemory<T>();
117
118    // perform first level of reduction,
119    // reading from global memory, writing to shared memory
120    unsigned int tid = threadIdx.x;
121    unsigned int i   = blockIdx.x * (blockSize * 2) + threadIdx.x;
ballot (test_web_warp_primitives.cu:124)
114        if (v_down[t]      != e_down)    ok = false;
115        if (v_xor[t]       != e_xor)     ok = false;
116    }
117
118    std::printf("threadIdx: ");   for (int t=0;t<n;++t) std::printf("%2d ", t);           std::printf("\n");
119    std::printf("lane_id:   ");   for (int t=0;t<n;++t) std::printf("%2d ", lane[t]);     std::printf("\n");
120    std::printf("shfl(l2):  ");   for (int t=0;t<n;++t) std::printf("%2d ", v_shfl[t]);   std::printf("\n");
121    std::printf("shfl_up:   ");   for (int t=0;t<n;++t) std::printf("%2d ", v_up[t]);     std::printf("\n");
122    std::printf("shfl_down: ");   for (int t=0;t<n;++t) std::printf("%2d ", v_down[t]);   std::printf("\n");
123    std::printf("shfl_xor:  ");   for (int t=0;t<n;++t) std::printf("%2d ", v_xor[t]);    std::printf("\n");
124    std::printf("ballot(tid>0)=%x  ballot(tid==0)=%x  all(FULL)=%d  any(FULL)=%d\n",
125                ballot_pos[0], ballot_zero[0], all_full[0], any_full[0]);
126    std::printf("warp primitives (%u lanes, width %u): %s\n",
127                BLOCK_SIZE, WIDTH, ok ? "PASSED" : "FAILED");
128
129    CHECK(cudaFree(d_lane)); CHECK(cudaFree(d_all)); CHECK(cudaFree(d_any));
130    CHECK(cudaFree(d_shfl)); CHECK(cudaFree(d_up)); CHECK(cudaFree(d_down));
131    CHECK(cudaFree(d_xor)); CHECK(cudaFree(d_bpos)); CHECK(cudaFree(d_bzero));
132    return ok ? 0 : 1;
133}
group_index (test_web_olcf_hw9_task1.cu:46)
36        g.sync();
37        if (lane < i) val += x[lane + i];
38        g.sync();
39    }
40    return val;
41}
42
43__global__ void my_reduce_kernel(int* data, int* out) {
44    __shared__ int sdata[nTPB];
45    auto g1 = this_thread_block();
46    size_t gindex = g1.group_index().x * nTPB + g1.thread_index().x;
47
48    auto g2 = tiled_partition(g1, 32);      // 32-wide tile of the block
49    auto g3 = tiled_partition(g2, 16);      // 16-wide tile of that tile
50    auto g  = g3;
51
52    int sdata_offset = (int)(g1.thread_index().x / g.size()) * (int)g.size();
53    int val = reduce(g, sdata + sdata_offset, data[gindex]);
54
55    // [SIM] publish each group's partial sum so the host can verify it.
56    if (g.thread_rank() == 0) out[g1.thread_index().x / g.size()] = val;
57}
meta_group_rank (test_coop_groups.cu:81)
71    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
72    auto tile = cg::tiled_partition<32>(cg::this_thread_block());
73    out[gid] = cg::reduce(tile, in[gid], cg::plus<int>());
74}
75
76// tile.thread_rank() and meta_group_rank() identify a lane and its warp.
77__global__ void kTileInfo(int* rank, int* warp) {
78    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
79    auto tile = cg::tiled_partition<32>(cg::this_thread_block());
80    rank[gid] = static_cast<int>(tile.thread_rank());
81    warp[gid] = static_cast<int>(tile.meta_group_rank());
82}
83
84static int* upload(const std::vector<int>& h) {
85    int* d = nullptr;
86    CUDA_CHECK(cudaMalloc(&d, h.size() * sizeof(int)));
87    CUDA_CHECK(cudaMemcpy(d, h.data(), h.size() * sizeof(int), cudaMemcpyHostToDevice));
88    return d;
89}
90static bool report(const char* name, bool ok, unsigned seed) {
91    std::printf("  %-26s seed=%-5u %s\n", name, seed, ok ? "PASSED" : "FAILED");
92    return ok;
meta_group_size (test_web_cs_awbarrier.cu:78)
68    if (tile32.thread_rank() == 0) {
69        tmp[tile32.meta_group_rank()] = threadSum;
70    }
71
72    auto token = barrier.arrive();
73
74    barrier.wait(std::move(token));
75
76    // The warp 0 will perform last round of reduction
77    if (tile32.meta_group_rank() == 0) {
78        double beta = tile32.thread_rank() < tile32.meta_group_size()
79                          ? tmp[tile32.thread_rank()]
80                          : 0.0;
81
82#pragma unroll
83        for (int offset = tile32.size() / 2; offset > 0; offset /= 2) {
84            beta += tile32.shfl_down(beta, offset);
85        }
86
87        if (tile32.thread_rank() == 0) {
88            if (writeSquareRoot)
89                *result = sqrt(beta);
shfl (test_web_cs_warpaggr.cu:63)
53{
54    cg::coalesced_group active = cg::coalesced_threads();
55
56    // leader does the update
57    int res = 0;
58    if (active.thread_rank() == 0) {
59        res = atomicAdd(counter, active.size());
60    }
61
62    // broadcast result
63    res = active.shfl(res, 0);
64
65    // each thread computes its own value
66    return res + active.thread_rank();
67}
68
69// verbatim device code — filter positives, packing them via atomicAggInc
70__global__ void filter_arr(int *dst, int *nres, const int *src, int n)
71{
72    int id = threadIdx.x + blockIdx.x * blockDim.x;
73
74    for (int i = id; i < n; i += gridDim.x * blockDim.x) {
shfl_down (test_coop_groups.cu:65)
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
57}
58
59// Warp-tile reduction via tile.shfl_down; rank 0 of each tile writes the sum.
60__global__ void kTileShfl(const int* in, int* outWarp) {
61    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
62    auto block = cg::this_thread_block();
63    auto tile  = cg::tiled_partition<32>(block);
64    int v = in[gid];
65    for (unsigned d = tile.size() / 2; d > 0; d >>= 1) v += tile.shfl_down(v, d);
66    if (tile.thread_rank() == 0) outWarp[gid / 32u] = v;
67}
68
69// cg::reduce broadcasts the warp sum to every lane.
70__global__ void kCgReduce(const int* in, int* out) {
71    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
72    auto tile = cg::tiled_partition<32>(cg::this_thread_block());
73    out[gid] = cg::reduce(tile, in[gid], cg::plus<int>());
74}
75
76// tile.thread_rank() and meta_group_rank() identify a lane and its warp.
size (test_coop_groups.cu:43)
33    } while (0)
34
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
size (test_coop_groups.cu:43)
33    } while (0)
34
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
sync (test_coop_groups.cu:45)
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
sync (test_coop_groups.cu:45)
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
sync (test_coop_groups.cu:45)
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
this_thread_block (test_coop_groups.cu:55)
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
57}
58
59// Warp-tile reduction via tile.shfl_down; rank 0 of each tile writes the sum.
60__global__ void kTileShfl(const int* in, int* outWarp) {
61    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
62    auto block = cg::this_thread_block();
63    auto tile  = cg::tiled_partition<32>(block);
64    int v = in[gid];
65    for (unsigned d = tile.size() / 2; d > 0; d >>= 1) v += tile.shfl_down(v, d);
66    if (tile.thread_rank() == 0) outWarp[gid / 32u] = v;
thread_index (test_web_olcf_hw9_task1.cu:46)
36        g.sync();
37        if (lane < i) val += x[lane + i];
38        g.sync();
39    }
40    return val;
41}
42
43__global__ void my_reduce_kernel(int* data, int* out) {
44    __shared__ int sdata[nTPB];
45    auto g1 = this_thread_block();
46    size_t gindex = g1.group_index().x * nTPB + g1.thread_index().x;
47
48    auto g2 = tiled_partition(g1, 32);      // 32-wide tile of the block
49    auto g3 = tiled_partition(g2, 16);      // 16-wide tile of that tile
50    auto g  = g3;
51
52    int sdata_offset = (int)(g1.thread_index().x / g.size()) * (int)g.size();
53    int val = reduce(g, sdata + sdata_offset, data[gindex]);
54
55    // [SIM] publish each group's partial sum so the host can verify it.
56    if (g.thread_rank() == 0) out[g1.thread_index().x / g.size()] = val;
57}
thread_rank (test_coop_groups.cu:42)
32        }                                                                    \
33    } while (0)
34
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
thread_rank (test_coop_groups.cu:42)
32        }                                                                    \
33    } while (0)
34
35constexpr int N     = 256;   // 8 warps
36constexpr int BLOCK = 128;   // 4 warps/block
37constexpr int WARPS = N / 32;
38
39// The CoffeeBeforeArch reduce_sum: reduce a thread_group to a single value in
40// thread 0, using shared scratch and group syncs.
41__device__ int reduce_sum(cg::thread_group g, int* temp, int val) {
42    int lane = static_cast<int>(g.thread_rank());
43    for (int i = static_cast<int>(g.size()) / 2; i > 0; i /= 2) {
44        temp[lane] = val;
45        g.sync();
46        if (lane < i) val += temp[lane + i];
47        g.sync();
48    }
49    return val;  // full sum in rank 0
50}
51
52__global__ void kBlockReduce(const int* in, int* outBlk) {
53    __shared__ int temp[BLOCK];
tiled_partition (test_coop_groups.cu:63)
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
57}
58
59// Warp-tile reduction via tile.shfl_down; rank 0 of each tile writes the sum.
60__global__ void kTileShfl(const int* in, int* outWarp) {
61    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
62    auto block = cg::this_thread_block();
63    auto tile  = cg::tiled_partition<32>(block);
64    int v = in[gid];
65    for (unsigned d = tile.size() / 2; d > 0; d >>= 1) v += tile.shfl_down(v, d);
66    if (tile.thread_rank() == 0) outWarp[gid / 32u] = v;
67}
68
69// cg::reduce broadcasts the warp sum to every lane.
70__global__ void kCgReduce(const int* in, int* out) {
71    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
72    auto tile = cg::tiled_partition<32>(cg::this_thread_block());
73    out[gid] = cg::reduce(tile, in[gid], cg::plus<int>());
74}
tiled_partition (test_coop_groups.cu:63)
53    __shared__ int temp[BLOCK];
54    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
55    int bsum = reduce_sum(cg::this_thread_block(), temp, in[gid]);
56    if (threadIdx.x == 0) outBlk[blockIdx.x] = bsum;
57}
58
59// Warp-tile reduction via tile.shfl_down; rank 0 of each tile writes the sum.
60__global__ void kTileShfl(const int* in, int* outWarp) {
61    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
62    auto block = cg::this_thread_block();
63    auto tile  = cg::tiled_partition<32>(block);
64    int v = in[gid];
65    for (unsigned d = tile.size() / 2; d > 0; d >>= 1) v += tile.shfl_down(v, d);
66    if (tile.thread_rank() == 0) outWarp[gid / 32u] = v;
67}
68
69// cg::reduce broadcasts the warp sum to every lane.
70__global__ void kCgReduce(const int* in, int* out) {
71    const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
72    auto tile = cg::tiled_partition<32>(cg::this_thread_block());
73    out[gid] = cg::reduce(tile, in[gid], cg::plus<int>());
74}