CUDA Cooperative Groups Guide
December 25, 2025
12 min
By RightNow AI Team
Introduction
Cooperative Groups provide flexible, composable thread synchronization beyond traditional __syncthreads. They enable grid-wide synchronization, dynamic group partitioning, and cleaner modular code. Essential for advanced algorithms requiring global barriers.
This guide covers the cooperative groups API, common patterns, and performance considerations.
Common Performance Issues
- Grid sync requires cooperative launch API
- Not all GPUs support grid sync
- Overhead of dynamic group creation
Optimization Techniques
1. Thread Block Tiles
Create fixed-size tile groups for modular sync.
2. Coalesced Groups
Group only active threads after branch.
3. Grid-wide Sync
Synchronize entire grid for multi-phase algorithms.
Implementation Comparison
Before (Naive Implementation)
Traditional sync with __syncthreads.
// Traditional block synchronization
__global__ void kernel_traditional(float* data, int n) {
__shared__ float sdata[256];
int tid = threadIdx.x;
// Phase 1
sdata[tid] = data[blockIdx.x * blockDim.x + tid];
__syncthreads(); // Traditional sync
// Phase 2
if (tid < 128) sdata[tid] += sdata[tid + 128];
__syncthreads();
// More phases...
}
After (Optimized Implementation)
Cooperative groups enable flexible synchronization and grid-wide barriers.
#include <cooperative_groups.h>
namespace cg = cooperative_groups;
__global__ void kernel_coop_groups(float* data, int n) {
// Get thread block group
cg::thread_block block = cg::this_thread_block();
// Create warp-sized tile
cg::thread_block_tile<32> warp = cg::tiled_partition<32>(block);
// Warp shuffle using cooperative groups API
int lane = warp.thread_rank();
float val = data[blockIdx.x * blockDim.x + threadIdx.x];
// Warp-level reduction
for (int offset = 16; offset > 0; offset /= 2) {
val += warp.shfl_down(val, offset);
}
// Block sync
block.sync();
// Coalesced group for active threads only
if (threadIdx.x < n) {
cg::coalesced_group active = cg::coalesced_threads();
// Only active threads participate
float sum = cg::reduce(active, val, cg::plus<float>());
}
}
// Grid-wide synchronization
__global__ void grid_sync_kernel(float* data, int n) {
cg::grid_group grid = cg::this_grid();
// Phase 1: All blocks process their portion
int idx = grid.thread_rank();
if (idx < n) data[idx] *= 2.0f;
// Grid-wide barrier - ALL blocks wait here
grid.sync();
// Phase 2: Now all data is updated
// Can safely read neighbors from other blocks
}
// Launch with cooperative kernel API
void launch_grid_sync() {
int numBlocksPerSm;
cudaOccupancyMaxActiveBlocksPerMultiprocessor(&numBlocksPerSm,
grid_sync_kernel, 256, 0);
void* args[] = { &d_data, &n };
cudaLaunchCooperativeKernel((void*)grid_sync_kernel,
numBlocksPerSm * num_sms, 256, args);
}
Performance Results
| Metric | Naive | Optimized | Improvement |
|---|---|---|---|
| Grid sync overhead | N/A | ~10μs | Enables new algorithms |
Frequently Asked Questions
When to use grid sync?
Grid sync is useful for iterative algorithms (Jacobi, conjugate gradient) where you need to complete a phase before the next. It avoids kernel launch overhead for multi-phase algorithms.