Cheat sheet
The things you look up while writing a kernel, on one page. For why they work that way, go back to the matching lesson.
Builtins and qualifiers
| Form | Meaning |
|---|---|
threadIdx.x/y/z | Thread coordinates in the block |
blockIdx.x/y/z | Block coordinates in the grid |
blockDim.x/y/z | Threads per block |
gridDim.x/y/z | Number of blocks in the grid |
warpSize | Always 32 |
__global__ | Kernel entry: GPU executes, host launches, returns void |
__device__ | GPU executes, GPU calls |
__host__ __device__ | Compiled once for each side |
__shared__ | On-chip memory shared within the block |
__constant__ | Read-only constant memory, 64 KB, broadcast-optimized |
__restrict__ | No-alias guarantee; with const it can use the read-only cache |
__launch_bounds__(T, B) | Constrains the compiler's register budget |
Index arithmetic
// 1Dint i = blockIdx.x * blockDim.x + threadIdx.x;if (i < n) { ... } // 2D (x is the column, the contiguous memory direction)int col = blockIdx.x * blockDim.x + threadIdx.x;int row = blockIdx.y * blockDim.y + threadIdx.y;if (row < rows && col < cols) m[row * cols + col] = ...; // grid-stride loop: grid size is decoupled from data sizefor (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += gridDim.x * blockDim.x) { ... } // Linear tid in the block (warps slice this into groups of 32)int tid = threadIdx.x + threadIdx.y * blockDim.x + threadIdx.z * blockDim.x * blockDim.y; // Ceiling division for the grid sizeint blocks = (n + threads - 1) / threads;Runtime API
cudaMalloc(&d_p, bytes); // note the &d_pcudaMallocManaged(&p, bytes); // unified memorycudaHostAlloc(&h_p, bytes, cudaHostAllocDefault); // pinned; required for async copiescudaMemcpy(dst, src, bytes, cudaMemcpyHostToDevice);cudaMemcpyAsync(dst, src, bytes, kind, stream);cudaMemset(d_p, 0, bytes);cudaFree(d_p); cudaFreeHost(h_p); cudaStreamCreate(&s);cudaStreamSynchronize(s); // wait on this stream onlycudaStreamWaitEvent(s2, evt, 0); // cross-stream dependency; CPU does not blockcudaDeviceSynchronize(); // wait for everything; debug and shutdown only cudaEventCreate(&e); cudaEventRecord(e, s);cudaEventSynchronize(e);cudaEventElapsedTime(&ms, start, stop); // the only correct way to time a kernel cudaGetLastError(); // synchronous error from the launchcudaGetErrorString(err);cudaOccupancyMaxPotentialBlockSize(&minGrid, &block, kernel, 0, 0);cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, 96*1024);Synchronization and warp primitives
| Primitive | Scope | Notes |
|---|---|---|
__syncthreads() | block | Barrier. Never place it in a divergent branch |
__syncwarp(mask) | warp | After Volta, exchanging data inside a warp requires an explicit sync |
__threadfence() | device | Memory ordering, not a barrier |
__shfl_sync(m, v, src) | warp | Broadcast |
__shfl_down_sync(m, v, d) | warp | Reduction; the result lands in lane 0 |
__shfl_xor_sync(m, v, k) | warp | Butterfly; every lane receives the result |
__ballot_sync(m, pred) | warp | Vote bitmap; pair with __popc for stream compaction |
__activemask() | warp | Build a mask on a divergent path |
CUDA C++
__inline__ __device__ float warpReduceSum(float v) { for (int off = 16; off > 0; off >>= 1) v += __shfl_down_sync(0xffffffff, v, off); return v;}Compiler flags
| Flag | Effect |
|---|---|
-arch=sm_80 | Specifies both the virtual and the real architecture |
-gencode arch=compute_80,code=sm_80 | Precise control of a single target |
-gencode arch=compute_90,code=compute_90 | Keeps PTX so newer cards can JIT. Always ship this in a release |
-Xptxas -v | Prints register usage and spills |
-lineinfo | Adds line numbers for profiler and sanitizer; does not affect optimization |
-G | Device-side debug. Disables all optimization. Use with care |
--use_fast_math | Fast math: trades precision for speed |
-maxrregcount=N | Global register cap; too coarse. Prefer __launch_bounds__ |
--default-stream per-thread | Stops the default stream from implicitly synchronizing with other streams |
| Architecture | Code name | Typical cards |
|---|---|---|
sm_70 | Volta | V100 |
sm_75 | Turing | T4, RTX 20 series |
sm_80 | Ampere | A100 |
sm_86 | Ampere | RTX 30 series, A10 |
sm_89 | Ada | RTX 40 series, L4 |
sm_90 | Hopper | H100 |
Profiling commands
# Step 1: inspect the overall timeline and find the real bottleneck kernelnsys profile -o report --stats=true ./app # Step 2: drill into a single kernelncu --set full -o profile ./appncu --kernel-name myKernel --launch-count 3 ./app # Key metrics only (much faster)ncu --metrics \ sm__throughput.avg.pct_of_peak_sustained_elapsed,\ gpu__dram_throughput.avg.pct_of_peak_sustained_elapsed,\ sm__warps_active.avg.pct_of_peak_sustained_active \ ./app # Check coalescing: sectors/requests ideal is 4; near 32 means no coalescing at allncu --metrics \ l1tex__t_sectors_pipe_lsu_mem_global_op_ld.sum,\ l1tex__t_requests_pipe_lsu_mem_global_op_ld.sum ./app # Check bank conflictncu --metrics l1tex__data_bank_conflicts_pipe_lsu_mem_shared.sum ./app # Correctnesscompute-sanitizer --tool memcheck ./appcompute-sanitizer --tool racecheck ./app # Inspect the compiler outputcuobjdump -sass ./app | lessnvcc -ptx -arch=sm_80 -o k.ptx k.cuNumbers worth remembering
These magnitudes help you decide whether an idea is worth trying
| Item | Magnitude |
|---|---|
| Register latency | ~1 cycle |
| Shared memory latency | ~20-30 cycles |
| L2 latency | ~200 cycles |
| Global memory latency | ~400-800 cycles |
| Sector granularity | 32 bytes |
| Shared memory banks | 32 banks, 4 bytes wide each |
| A100 peak bandwidth | 1.55 TB/s |
| A100 FP32 throughput | 19.5 TFLOPS |
| A100 BF16 Tensor Core | 312 TFLOPS (about 16x) |
| A100 arithmetic intensity knee | ≈ 12.5 FLOP/Byte |
| Kernel launch overhead | 3-10 microseconds |
| Max threads per block | 1024 |
Symptom to cause
| Symptom | Most likely cause |
|---|---|
printf in a kernel prints nothing | Missing a sync point; the process already exited |
| Results are all zeros | Forgot to cudaMemcpy back, or the launch failed and errors were not checked |
| Results are occasionally wrong | Missing __syncthreads(), or a PyTorch extension used the default stream |
invalid configuration argument | blockDim exceeds 1024, or shared memory is over the limit |
no kernel image is available | Compiled -arch does not match the GPU, and no PTX was kept |
| Copy and compute do not overlap | Host memory is not pinned, or the default stream was used |
| A one-line change tanks performance | Register count crossed a cliff and occupancy dropped; check -Xptxas -v |
| Performance far below the bandwidth ceiling | Accesses are not coalesced; check the sectors/requests ratio |
| Shared-memory kernel is slow | Bank conflict; try row width +1 |
Not started yet? Begin with your first kernel , or jump to the occupancy calculator and see what is capping your kernel.