One-sentence definition: CUDA is NVIDIA’s sophisticated parallel computing platform for leveraging GPU architectures to solve complex, large-scale computational problems.
Key use cases: High-performance computing (HPC) workloads, real-time ray tracing, and distributed deep learning.
Prerequisites: Proficiency in C/C++ and CUDA, understanding of GPU architecture (SMs, warps, memory hierarchy), and experience with profiling tools.
Basic principles: CUDA orchestrates thousands of threads across streaming multiprocessors (SMs), with performance hinging on memory coalescing, warp efficiency, and latency hiding.
Threads are grouped into warps (32 threads) that execute in lockstep.
Kernel execution is non-preemptive, requiring careful resource allocation.
Key components:
Memory Hierarchy: Global, shared, constant, texture, and registers, each with distinct latency and bandwidth profiles.
Streams: Asynchronous execution queues for overlapping computation and data transfer.
Unified Memory: Simplifies host-device data sharing (with caveats).
Common misconceptions:
"Unified Memory eliminates manual management" – It simplifies but often underperforms explicit transfers in production.
"Max threads = max performance" – Over-subscription can lead to register spilling or low occupancy.
graph TD
A[Host: CPU] -->|Streams| B[Device: GPU 1]
A -->|Streams| C[Device: GPU 2]
B --> D[Grid]
D --> E[Block 1]
D --> F[Block 2]
E --> G[Warps]
E --> H[Shared Memory]
B --> I[Global Memory]
B --> J[Constant Memory]
G -->|Access| H
G -->|Access| I
G -->|Access| J
B --> K[Stream 1]
B --> L[Stream 2]
K -->|Overlap| M[Kernel Exec]
L -->|Overlap| N[Data Transfer]
- System overview: Multiple GPUs process kernels in parallel, with streams enabling concurrency between computation and memory operations.
- Component relationships: Threads in warps share resources (e.g., shared memory), while streams coordinate independent tasks across the GPU.
// CUDA program for matrix multiplication with tiled shared memory and streams#include<cuda_runtime.h>#include<stdio.h>#define TILE_SIZE 32#define CHECK(err) if (err != cudaSuccess) { printf("Error: %s\n", cudaGetErrorString(err)); exit(1); }__global__voidmatrixMul(float*A,float*B,float*C,intN){__shared__floats_A[TILE_SIZE][TILE_SIZE];__shared__floats_B[TILE_SIZE][TILE_SIZE];intbx=blockIdx.x,by=blockIdx.y;inttx=threadIdx.x,ty=threadIdx.y;introw=by*TILE_SIZE+ty;intcol=bx*TILE_SIZE+tx;floatsum=0.0f;// Loop over tilesfor(intm=0;m<(N+TILE_SIZE-1)/TILE_SIZE;m++){if(row<N&&m*TILE_SIZE+tx<N)s_A[ty][tx]=A[row*N+m*TILE_SIZE+tx];elses_A[ty][tx]=0.0f;if(m*TILE_SIZE+ty<N&&col<N)s_B[ty][tx]=B[(m*TILE_SIZE+ty)*N+col];elses_B[ty][tx]=0.0f;__syncthreads();// Compute tile#pragma unrollfor(intk=0;k<TILE_SIZE;k++)sum+=s_A[ty][k]*s_B[k][tx];__syncthreads();}if(row<N&&col<N)C[row*N+col]=sum;}intmain(){intN=1024;// Matrix sizesize_tsize=N*N*sizeof(float);float*h_A,*h_B,*h_C,*d_A,*d_B,*d_C;// Host allocation and initializationh_A=(float*)malloc(size);h_B=(float*)malloc(size);h_C=(float*)malloc(size);for(inti=0;i<N*N;i++){h_A[i]=1.0f;h_B[i]=2.0f;}// Device allocationcudaMalloc(&d_A,size);cudaMalloc(&d_B,size);cudaMalloc(&d_C,size);// StreamscudaStream_tstream1,stream2;cudaStreamCreate(&stream1);cudaStreamCreate(&stream2);// Async memory transfers and kernel launchcudaMemcpyAsync(d_A,h_A,size,cudaMemcpyHostToDevice,stream1);cudaMemcpyAsync(d_B,h_B,size,cudaMemcpyHostToDevice,stream2);dim3block(TILE_SIZE,TILE_SIZE);dim3grid((N+TILE_SIZE-1)/TILE_SIZE,(N+TILE_SIZE-1)/TILE_SIZE);matrixMul<<<grid,block,0,stream1>>>(d_A,d_B,d_C,N);cudaMemcpyAsync(h_C,d_C,size,cudaMemcpyDeviceToHost,stream1);cudaStreamSynchronize(stream1);cudaStreamSynchronize(stream2);// CleanupcudaFree(d_A);cudaFree(d_B);cudaFree(d_C);cudaStreamDestroy(stream1);cudaStreamDestroy(stream2);free(h_A);free(h_B);free(h_C);return0;}
- System design:
- Tiling: Breaks matrix into smaller chunks fitting in shared memory, reducing global memory accesses.
- Streams: Overlaps data transfers with computation for better throughput.
- Optimization techniques:
- Coalesced memory access: Aligns thread accesses to contiguous memory.
- Loop unrolling: Reduces branch overhead with #pragma unroll.
- Warp divergence minimization: Ensures uniform execution paths within warps.
- Production considerations:
- Error handling with CHECK macro for robustness.
- Multi-GPU scaling (not shown but extendable via cudaSetDevice).
- Profile with Nsight to tune block sizes and occupancy.