CUDA Cheatsheet
Compiling with nvcc
Use this CUDA reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
nvcc Basics
nvcc is NVIDIA's CUDA compiler driver. It separates host and device code, compiles device code to PTX or cubin, and passes host code to the system C++ compiler (GCC, Clang, MSVC).
nvcc myfile.cu -o myapp # basic compile + link nvcc myfile.cu -o myapp -O2 # with optimization nvcc myfile.cu -o myapp -arch=sm_89 # target Ada Lovelace (RTX 4090)
Compute Capability Targets
| Flag form | Meaning |
|---|---|
-arch=sm_XY | Compile for SM X.Y; PTX + cubin for that SM only |
-code=sm_XY | Generate cubin for SM X.Y (used with -gencode) |
-code=compute_XY | Embed PTX for SM X.Y (JIT-compiled at runtime) |
-gencode arch=compute_XY,code=sm_XY | Full "real" compilation for one SM |
-gencode arch=compute_XY,code=compute_XY | Embed PTX only (forward compat) |
Multi-arch fat binary (recommended for distribution)
nvcc myfile.cu -o myapp \ -gencode arch=compute_80,code=sm_80 \ # Ampere -gencode arch=compute_89,code=sm_89 \ # Ada -gencode arch=compute_90,code=sm_90 \ # Hopper -gencode arch=compute_100,code=sm_100 \ # Blackwell data center (CUDA ≥ 12.8) -gencode arch=compute_120,code=sm_120 \ # Blackwell RTX 50 (CUDA ≥ 12.8) -gencode arch=compute_120,code=compute_120 # PTX (JIT for future GPUs)
Shorthand -arch
-arch=sm_80 is a shortcut for -gencode arch=compute_80,code=sm_80 plus embedding PTX for forward compatibility. Equivalent long form:
-gencode arch=compute_80,code=sm_80 -gencode arch=compute_80,code=compute_80
Common Compiler Flags
| Flag | Effect |
|---|---|
-O0 / -O1 / -O2 / -O3 | Host optimization level |
-g | Host debug info |
-G | Device debug info (disables optimizations) |
-lineinfo | Device line info without disabling opts (for profilers) |
-use_fast_math | Enable approximate math (__sinf, FMA, etc.) |
-ftz=true | Flush denormals to zero |
-prec-div=false | Fast (less precise) division |
-prec-sqrt=false | Fast (less precise) sqrt |
--maxrregcount=N | Cap registers per thread to N |
-Xptxas -v | Show register/shared mem usage per kernel |
-Xptxas -O3 | PTX assembler optimization level |
-rdc=true | Relocatable device code (for dynamic parallelism, separate compilation) |
-keep | Keep intermediate .ptx, .cubin, .cudafe files |
-dryrun | Print subcommands without executing |
-v | Verbose — show all subcommands |
--ptx | Output PTX only (no binary) |
--cubin | Output cubin only |
-fatbin | Output fat binary |
--expt-relaxed-constexpr | Allow constexpr in device code |
--expt-extended-lambda | Allow __device__ lambdas |
-std=c++17 | C++ standard for host code |
-Xcompiler -fopenmp | Pass flag to host compiler |
-Xlinker -rpath,/usr/local/cuda/lib64 | Pass flag to linker |
Separate Compilation and Linking
For large projects with multiple .cu files:
# Compile each file to a relocatable device object nvcc -rdc=true -c fileA.cu -o fileA.o nvcc -rdc=true -c fileB.cu -o fileB.o # Link (device link step + host link) nvcc -rdc=true fileA.o fileB.o -o myapp # Or explicitly device-link then host-link: nvcc -rdc=true -dlink fileA.o fileB.o -o device_link.o g++ fileA.o fileB.o device_link.o -lcudart -o myapp
CMake Integration
cmake_minimum_required(VERSION 3.18) project(MyCUDAProject LANGUAGES CXX CUDA) set(CMAKE_CUDA_STANDARD 17) set(CMAKE_CUDA_ARCHITECTURES "80;89;90;120") # or "native" for local GPU add_executable(myapp main.cu kernels.cu) target_compile_options(myapp PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:--use_fast_math -lineinfo> ) # Separate compilation (for dynamic parallelism) set_target_properties(myapp PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
PTX (Parallel Thread Execution)
PTX is NVIDIA's virtual ISA — an intermediate representation that the driver JIT-compiles to machine code.
nvcc -ptx myfile.cu -o myfile.ptx # emit PTX nvcc --ptxas-options=-v myfile.cu # show register/smem usage
Inline PTX
__global__ void inlinePtxDemo(unsigned* out) { unsigned tid; asm("mov.u32 %0, %%tid.x;" : "=r"(tid)); // read threadIdx.x via PTX *out = tid; // Atomic add with relaxed ordering asm volatile("atom.relaxed.global.add.u32 [%0], 1;" :: "l"(out) : "memory"); }
Linking CUDA Libraries
| Library | Link flag | Header |
|---|---|---|
| CUDA Runtime | -lcudart | <cuda_runtime.h> |
| CUDA Driver | -lcuda | <cuda.h> |
| cuBLAS | -lcublas | <cublas_v2.h> |
| cuSPARSE | -lcusparse | <cusparse.h> |
| cuFFT | -lcufft | <cufft.h> |
| cuRAND | -lcurand | <curand.h> |
| cuDNN | -lcudnn | <cudnn.h> |
| cuSOLVER | -lcusolver | <cusolverDn.h> |
| NCCL | -lnccl | <nccl.h> |
| Thrust | (header-only) | <thrust/...> |
| CUB | (header-only) | <cub/cub.cuh> |
| NVTX | -lnvToolsExt | <nvtx3/nvToolsExt.h> |
| NVRTC (runtime compile) | -lnvrtc | <nvrtc.h> |
| Device runtime | -lcudadevrt | needed for -rdc=true |
nvcc myfile.cu -o myapp -lcublas -lcufft -lcurand
Environment Variables
| Variable | Effect |
|---|---|
CUDA_VISIBLE_DEVICES | Comma-list of device IDs visible to process (or NoDevFiles) |
CUDA_DEVICE_ORDER | FASTEST_FIRST (default) or PCI_BUS_ID |
CUDA_LAUNCH_BLOCKING | 1 → make all kernel launches synchronous (debug) |
CUDA_CACHE_DISABLE | 1 → disable JIT cubin cache |
CUDA_CACHE_PATH | Override JIT cache directory |
CUDA_FORCE_PTX_JIT | 1 → force JIT from PTX even if cubin present |
NVCC_PREPEND_FLAGS | Prepend these flags to all nvcc invocations |
NVCC_APPEND_FLAGS | Append these flags |
CUDA_VISIBLE_DEVICES=0,1 ./myapp # restrict to GPUs 0 and 1 CUDA_LAUNCH_BLOCKING=1 ./myapp # synchronize after every kernel (debugging)
Runtime Compilation (NVRTC)
Compile CUDA source code at runtime (useful for JIT kernels in libraries).
#include <nvrtc.h> #include <cuda.h> const char* src = R"( extern "C" __global__ void myKernel(float* data, int n) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) data[i] *= 2.0f; } )"; nvrtcProgram prog; nvrtcCreateProgram(&prog, src, "myKernel.cu", 0, nullptr, nullptr); const char* opts[] = {"--gpu-architecture=compute_89"}; nvrtcCompileProgram(prog, 1, opts); size_t ptxSize; nvrtcGetPTXSize(prog, &ptxSize); char* ptx = new char[ptxSize]; nvrtcGetPTX(prog, ptx); // Load and launch via Driver API CUmodule module; CUfunction kernel; cuModuleLoadDataEx(&module, ptx, 0, nullptr, nullptr); cuModuleGetFunction(&kernel, module, "myKernel"); void* args[] = { &d_data, &n }; cuLaunchKernel(kernel, gridDim, 1, 1, blockDim, 1, 1, 0, 0, args, nullptr); nvrtcDestroyProgram(&prog); delete[] ptx; cuModuleUnload(module);
Useful nvcc Output and Inspection
# Show PTX for a single function nvcc -ptx -arch=compute_89 myfile.cu && grep -A 50 "myKernel" myfile.ptx # Show SASS (native assembly) cuobjdump -sass myapp | grep -A 30 "myKernel" # Show embedded cubins / PTX in a binary cuobjdump -all myapp cuobjdump -ptx myapp # extract PTX sections cuobjdump -elf myapp # extract ELF sections # Register / shared mem usage per kernel nvcc --ptxas-options=-v myfile.cu 2>&1 | grep "Used" # Output: ptxas info: Used 32 registers, 4096 bytes smem, 352 bytes cmem[0]