CUDA¶
Learning Objectives
Explain the host/device memory model and why explicit data transfers exist
Write a CUDA kernel and configure its grid and block dimensions
Allocate GPU memory with
cudaMallocand transfer data withcudaMemcpyCheck CUDA API error codes and kernel launch errors
Measure kernel execution time separately from data transfer time
Compute effective memory bandwidth and compare to GPU specs
Note
If you’re just beginning, it’s highly recommended that you use Kokkos instead. It’s programmed largely the same as cuda but acts as a portability layer that lets your code run on GPUs other than nvidia.
GPU Execution Model¶
A GPU consists of thousands of simple cores organized into Streaming Multiprocessors (SMs). Unlike a CPU, which has a small number of powerful, latency-optimized cores, a GPU is designed to hide memory latency by switching between thousands of in-flight threads.
When you launch a CUDA kernel, you specify:
A grid — the number of thread blocks to launch
A thread block — the number of threads within each block
Every thread executes the same kernel function but with a unique index (SIMT: Single Instruction, Multiple Thread).
Thread Indexing (1D case)¶
global_thread_index = blockIdx.x * blockDim.x + threadIdx.x
Variable |
Meaning |
|---|---|
|
Thread index within its block (0 to |
|
Block index within the grid |
|
Number of threads per block |
|
Number of blocks in the grid |
Block size must be a multiple of 32 (the warp size). Common choices: 256 or 512 threads per block.
Warning
When n is not evenly divisible by blocksize, the last block contains threads whose computed index exceeds the array bounds. Always guard with if (i >= n) return; at the start of the kernel.
Host and Device Memory¶
CPUs and GPUs have separate memory spaces. The GPU has its own DRAM (device memory) connected via a high-bandwidth bus (900–3000 GB/s for HBM3). CPU-to-GPU transfers go over PCIe (~16 GB/s) — a significant bottleneck.
The standard workflow:
Allocate host (CPU) arrays with
mallocAllocate device (GPU) arrays with
cudaMallocCopy input data host → device with
cudaMemcpy(..., cudaMemcpyHostToDevice)Launch kernel(s)
cudaDeviceSynchronize()— wait for the GPU to finishCopy results device → host with
cudaMemcpy(..., cudaMemcpyDeviceToHost)cudaFreedevice arrays;freehost arrays
Tip
A common convention is to suffix device pointers with _d (e.g., a_d, b_d) to distinguish them from host pointers.
CUDA Stream Triad Kernel¶
The Stream Triad computes:
__global__ void StreamTriad(const int n, const double scalar,
const double *a, const double *b, double *c) {
const int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n) return;
c[i] = a[i] + scalar * b[i];
}
int main() {
const int n = 80000000;
double *a = (double *)malloc(n * sizeof(double));
double *b = (double *)malloc(n * sizeof(double));
double *c = (double *)malloc(n * sizeof(double));
// Initialize host arrays
for (int i = 0; i < n; i++) { a[i] = 1.0; b[i] = 2.0; }
double *a_d, *b_d, *c_d;
cudaMalloc(&a_d, n * sizeof(double));
cudaMalloc(&b_d, n * sizeof(double));
cudaMalloc(&c_d, n * sizeof(double));
const int blocksize = 512;
const int gridsize = (n + blocksize - 1) / blocksize;
// Copy to device
cudaMemcpy(a_d, a, n * sizeof(double), cudaMemcpyHostToDevice);
cudaMemcpy(b_d, b, n * sizeof(double), cudaMemcpyHostToDevice);
// Launch kernel
StreamTriad<<<gridsize, blocksize>>>(n, 3.0, a_d, b_d, c_d);
cudaDeviceSynchronize();
// Copy back
cudaMemcpy(c, c_d, n * sizeof(double), cudaMemcpyDeviceToHost);
cudaFree(a_d); cudaFree(b_d); cudaFree(c_d);
free(a); free(b); free(c);
}
Building and Running¶
On Workstation¶
nvcc -O3 stream_triad.cu -o stream_triad
./stream_triad
On ARC Clusters with GPU hardware¶
Load CUDA module:
module load cudaRequest GPU in SLURM:
#SBATCH --gres=gpu:1 #SBATCH --partition=<valid_gpu_partition>
Submit:
sbatch run.slurm
See GPU Usage for cluster-specific details.
Performance Measurement¶
The effective bandwidth for the Stream Triad is:
where \(N\) is array size and \(t\) is runtime. Compare to GPU specs:
GPU |
Memory Bandwidth |
|---|---|
A100 80GB |
~2000 GB/s |
V100 32GB |
~900 GB/s |
L40S |
~864 GB/s |