CUDA Cheatsheet

Threads, Blocks, and Grids

Use this CUDA reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Thread Hierarchy Overview

Grid
└── Block [0,0]  Block [1,0]  Block [2,0]  …
    └── Thread [0,0]  Thread [1,0]  …

Three levels: grid → block → thread. Each level is up to 3-dimensional. All dimensions are available in device code as built-in variables.

Built-in Variables

VariableTypeValue
threadIdxuint3Thread index within its block (x, y, z)
blockIdxuint3Block index within the grid (x, y, z)
blockDimdim3Block dimensions in threads (x, y, z)
gridDimdim3Grid dimensions in blocks (x, y, z)
warpSizeintAlways 32 on all current hardware
__global__ void indexDemo() {
    // 1-D flat thread index
    int tid = blockIdx.x * blockDim.x + threadIdx.x;

    // 2-D: processing a matrix row/col
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    // 3-D: processing a volume
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    int z = blockIdx.z * blockDim.z + threadIdx.z;
}

dim3 Type

dim3 is a struct with .x, .y, .z; unspecified dimensions default to 1.

dim3 block(16, 16);       // 16×16×1 = 256 threads
dim3 grid(64, 64);        // 64×64×1 blocks

dim3 block3d(8, 8, 8);    // 512 threads/block (3-D)
dim3 grid3d(16, 16, 4);

myKernel<<<grid, block>>>(args);

1-D, 2-D, 3-D Index Patterns

1-D array

__global__ void process1D(float* data, int n) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < n) data[i] *= 2.0f;
}

2-D matrix (row-major, width W)

__global__ void process2D(float* mat, int H, int W) {
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    if (row < H && col < W)
        mat[row * W + col] += 1.0f;
}

// Launch
dim3 block(16, 16);
dim3 grid((W + 15) / 16, (H + 15) / 16);
process2D<<<grid, block>>>(dMat, H, W);

3-D volume

__global__ void process3D(float* vol, int D, int H, int W) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    int z = blockIdx.z * blockDim.z + threadIdx.z;
    if (x < W && y < H && z < D)
        vol[z*H*W + y*W + x] = 0.0f;
}

Warps

A warp is 32 consecutive threads within a block that execute in lockstep (SIMT). All current NVIDIA GPUs use warp size 32.

Block of 128 threads → 4 warps
  Warp 0: threads  031
  Warp 1: threads 3263
  Warp 2: threads 6495
  Warp 3: threads 96127

Warp index helpers

__device__ int laneId()  { return threadIdx.x & 31; }          // 0–31
__device__ int warpId()  { return threadIdx.x >> 5; }          // warp within block

Warp-level intrinsics (CC ≥ 7.0 — use __activemask())

__global__ void warpExample(int* data) {
    unsigned mask = __activemask();          // bitmask of active lanes

    int val = data[threadIdx.x];
    // Broadcast lane 0's value to all active lanes
    int broadcast = __shfl_sync(mask, val, 0);

    // Prefix sum within warp
    for (int offset = 1; offset < 32; offset <<= 1) {
        int n = __shfl_up_sync(mask, val, offset);
        if (laneId() >= offset) val += n;
    }
}

__shfl_sync family

IntrinsicDescription
__shfl_sync(mask, var, srcLane)Broadcast from srcLane
__shfl_up_sync(mask, var, delta)Shift down (higher lanes get lower lanes' data)
__shfl_down_sync(mask, var, delta)Shift up (lower lanes get higher lanes' data)
__shfl_xor_sync(mask, var, laneMask)Butterfly exchange

Supported types: int, unsigned, float, long long, unsigned long long, double.

Thread Blocks

All threads in a block: - Share shared memory (__shared__) - Can synchronize with __syncthreads() - Reside on the same SM (streaming multiprocessor) - Are limited to 1024 threads total

// Common block sizes (multiples of 32)
dim3 block1d(256);         // 1-D: 256 threads
dim3 block2d(16, 16);      // 2-D: 256 threads
dim3 block3d(8, 8, 4);     // 3-D: 256 threads

Gotcha: blockDim.x * blockDim.y * blockDim.z ≤ 1024. Exceeding this causes a launch error.

Grid-Stride Loop (best practice for large N)

Handles any N regardless of grid size; works well with persistent kernels.

__global__ void gridStride(float* data, int n) {
    int stride = gridDim.x * blockDim.x;
    for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += stride)
        data[i] = sqrtf(data[i]);
}

// Can launch with ANY grid size; correctness is guaranteed
gridStride<<<256, 256>>>(d_data, n);

Thread Block Clusters (CC 9.0 / Hopper)

A cluster groups multiple blocks that can share distributed shared memory and synchronize cluster-wide.

#include <cooperative_groups.h>
namespace cg = cooperative_groups;

__global__ void __cluster_dims__(2, 1, 1)   // 2-block cluster
clusterKernel(int* data) {
    auto cluster = cg::this_cluster();
    auto block   = cg::this_thread_block();

    cluster.sync();   // cluster-wide barrier (cheaper than grid sync)
}

// Launch via cudaLaunchKernelEx
cudaLaunchConfig_t cfg = {};
cfg.gridDim  = 8;
cfg.blockDim = 128;
cudaLaunchAttribute attr;
attr.id = cudaLaunchAttributeClusterDimension;
attr.val.clusterDim = {2, 1, 1};
cfg.attrs    = &attr;
cfg.numAttrs = 1;
cudaLaunchKernelEx(&cfg, clusterKernel, data);

Occupancy

Occupancy = active warps / max warps per SM. Higher occupancy hides latency.

// Automatic occupancy tuning
int blockSize, minGridSize;
cudaOccupancyMaxPotentialBlockSize(
    &minGridSize, &blockSize, myKernel,
    0,    // dynamic shared mem per block
    0);   // block size limit (0 = no limit)

int gridSize = (n + blockSize - 1) / blockSize;
myKernel<<<gridSize, blockSize>>>(args);

Factors limiting occupancy

ResourceEffect
Register count per threadLimits resident warps per SM
Shared memory per blockLimits resident blocks per SM
Block sizeToo small → not enough warps; too large → register pressure
__launch_bounds__Guides register allocator
// Query register / shared mem usage after compilation:
// nvcc --ptxas-options=-v myfile.cu