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 |
|---|---|---|---|---|
|
sim only |
simulator.h:1182 |
||
|
A static-size sub-group of N consecutive lanes. |
sim only |
simulator.h:1193 |
|
|
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 |
|---|---|---|---|---|
|
sim only |
simulator.h:1172 |
Functions#
Signature |
Description |
Availability |
Location |
Example |
|---|---|---|---|---|
|
sim only |
simulator.h:1218 |
||
|
sim only |
simulator.h:1217 |
||
|
sim only |
simulator.h:1219 |
||
|
sim only |
simulator.h:1186 |
||
|
sim only |
simulator.h:1184 |
||
|
sim only |
simulator.h:1210 |
||
|
sim only |
simulator.h:1211 |
||
|
sim only |
simulator.h:1177 |
||
|
sim only |
simulator.h:1209 |
||
|
sim only |
simulator.h:1213 |
||
|
sim only |
simulator.h:1215 |
||
|
sim only |
simulator.h:1214 |
||
|
sim only |
simulator.h:1216 |
||
|
sim only |
simulator.h:1176 |
||
|
sim only |
simulator.h:1208 |
||
|
sim only |
simulator.h:1174 |
||
|
sim only |
simulator.h:1190 |
||
|
sim only |
simulator.h:1206 |
||
|
sim only |
simulator.h:1189 |
||
|
sim only |
simulator.h:1173 |
||
|
sim only |
simulator.h:1185 |
||
|
sim only |
simulator.h:1175 |
||
|
sim only |
simulator.h:1207 |
||
|
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 |
|
|
sim only |
simulator.h:1222 |
||
|
Old-API dynamic partition: the width is a runtime value. |
sim only |
simulator.h:1225 |
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}