CUDA Cheatsheet

Kernels

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

Kernel Declaration

A kernel is a __global__ function. It must return void. Every parameter is copied by value into constant memory at launch time (up to 32,764 bytes total on CC ≥ 7.0 with CUDA ≥ 12.1; 4,096 bytes on older toolkits/hardware).

__global__ void addVectors(const float* __restrict__ a,
                           const float* __restrict__ b,
                           float* __restrict__ c,
                           int n)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < n) c[i] = a[i] + b[i];
}

Kernel Launch Syntax

// Basic
addVectors<<<numBlocks, threadsPerBlock>>>(a, b, c, n);

// With dynamic shared memory and stream
addVectors<<<numBlocks, threadsPerBlock, sharedBytes, stream>>>(a, b, c, n);

Computing grid size from problem size:

int n = 1 << 20;
int blockSize = 256;
int gridSize  = (n + blockSize - 1) / blockSize;   // ceiling division
addVectors<<<gridSize, blockSize>>>(a, b, c, n);

Kernel Templates

template <int BLOCK_SIZE>
__global__ void tiledMatMul(const float* A, const float* B, float* C, int N) {
    __shared__ float tA[BLOCK_SIZE][BLOCK_SIZE];
    __shared__ float tB[BLOCK_SIZE][BLOCK_SIZE];
    // ...
}

// Instantiate at compile time
tiledMatMul<32><<<grid, dim3(32,32)>>>(dA, dB, dC, N);

Dynamic Parallelism (CDP2)

Kernels can launch child kernels from device code. Device-side cudaDeviceSynchronize() was deprecated in CUDA 11.6 and removed in CUDA 12 — a parent can no longer wait on its children. CDP2 orders child work with named device streams instead:

__global__ void parent(float* data, int n) {
    if (threadIdx.x == 0) {
        // Fire-and-forget: child starts immediately, no ordering with parent
        child<<<(n+31)/32, 32, 0, cudaStreamFireAndForget>>>(data, n);

        // Tail launch: child runs AFTER the entire parent grid completes
        finalize<<<1, 32, 0, cudaStreamTailLaunch>>>(data, n);
    }
}
  • Results a child produces are only guaranteed visible to grids launched after it (e.g. a tail launch) — chain dependent phases with cudaStreamTailLaunch.
  • Legacy CDP1 code that calls cudaDeviceSynchronize() in device code does not compile on CUDA 12+ toolkits.

Build flag required: nvcc -rdc=true -lcudadevrt

Cooperative Kernels (CC ≥ 6.0)

Allow grid-wide synchronization. All blocks must be resident simultaneously — use cudaOccupancyMaxActiveBlocksPerMultiprocessor to compute gridSize.

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

__global__ void coopKernel(int* data, int n) {
    auto grid = cg::this_grid();
    // ... phase 1 work ...
    grid.sync();   // grid-wide barrier
    // ... phase 2 work ...
}

// Launch via cudaLaunchCooperativeKernel
void* args[] = { &data, &n };
cudaLaunchCooperativeKernel((void*)coopKernel, gridSize, blockSize, args);

Variadic / Indirect Launch

// cudaLaunchKernel — type-erased launch (useful in libraries)
void* kernelArgs[] = { (void*)&d_in, (void*)&d_out, (void*)&n };
cudaLaunchKernel((const void*)myKernel,
                 gridDim, blockDim,
                 kernelArgs,
                 sharedMem, stream);

Kernel Attributes and Hints

// Query occupancy
int minGridSize, blockSize;
cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize,
                                   myKernel, 0, 0);

// Max active blocks per SM
int maxBlocks;
cudaOccupancyMaxActiveBlocksPerMultiprocessor(&maxBlocks,
    myKernel, blockSize, sharedBytesPerBlock);

// Set cache preference per kernel
cudaFuncSetCacheConfig(myKernel, cudaFuncCachePreferShared);
// Options: cudaFuncCachePreferNone | L1 | Shared | Equal

__launch_bounds__

Tells the compiler the max threads per block (and optionally min blocks per SM), enabling better register allocation.

__launch_bounds__(256, 2)          // maxThreadsPerBlock=256, minBlocksPerSM=2
__global__ void myKernel(...) { }

Predication and Warp Divergence

All threads in a warp execute the same instruction. Branches whose condition differs across a warp cause predication (serialized paths). Minimize divergence:

// Bad: each thread follows a different branch path
if (threadIdx.x % 2 == 0) doA();
else                        doB();

// Better: restructure to warp-aligned branches or use data to avoid divergence
// (threads 0-31 same branch, 32-63 same branch, etc.)

Printf in Kernels

#include <cstdio>

__global__ void debugKernel() {
    printf("Block %d Thread %d\n", blockIdx.x, threadIdx.x);
    // Buffer is flushed on cudaDeviceSynchronize / cudaStreamSynchronize
}

Output is buffered (default 1 MB). Increase with cudaDeviceSetLimit(cudaLimitPrintfFifoSize, 4<<20).

Assertion in Kernels

#include <cassert>

__global__ void safeKernel(float* data, int n) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    assert(i < n);   // triggers if condition false; compiled out in release
    data[i] *= 2.0f;
}

Compile with -DNDEBUG to strip all assert calls.

Kernel Execution Limits

LimitTypical value
Max threads per block1024
Max block dimension (x)1024
Max block dimension (y)1024
Max block dimension (z)64
Max grid dimension (x)2^31 − 1
Max grid dimension (y, z)65535
Max kernel parameter size32,764 bytes (CC ≥ 7.0, CUDA ≥ 12.1); 4,096 bytes otherwise
Warp size32
// Check limit at runtime
cudaDeviceProp p; cudaGetDeviceProperties(&p, 0);
printf("Max threads/block: %d\n", p.maxThreadsPerBlock);
printf("Max grid x:        %d\n", p.maxGridSize[0]);