100 Days of CUDA

pmpp chapter 4 · Compute Architecture and Scheduling
PMPP · Chapter 04 · Compute Architecture and Scheduling

Dear reader: These notes were created with the help of AI, with me cherry-picking the parts of the book I found most relevant. I also reviewed the content to make sure no AI hallucinations slipped through. I hope you find them useful. Happy reading! :)

Programming Massively Parallel Processors: Chapter 4, Compute Architecture and Scheduling

  1. Modern GPU Architecture
  2. Thread Block Scheduling
  3. Synchronization and Transparent Scalability
  4. Warps and SIMD Hardware
  5. Control Divergence
  6. Warp Scheduling and Latency Tolerance
  7. Resource Partitioning and Occupancy
  8. Querying Device Properties

1. Modern GPU Architecture

The key idea of this chapter is that a GPU is a collection of Streaming Multiprocessors (SMs) that execute thousands of threads in parallel, and understanding how the hardware schedules those threads is the foundation of CUDA performance.

Key Components

Memory System

The Hardware Hierarchy

GPU
└── GPC
    └── SM
        └── Streaming Processors (CUDA Cores)

Summary: A GPU is built from clusters of SMs, each SM containing many CUDA cores plus fast on-chip memory, all backed by large but slower off-chip DRAM (GDDR or HBM).


2. Thread Block Scheduling

When a kernel is launched, CUDA creates a grid of thread blocks, and the hardware assigns whole blocks to SMs.

Because all threads in a block live on the same SM, they can:

Threads in different blocks cannot directly synchronize or share shared memory.

Note: Thread Block Clusters (Hopper and newer) are an optional grouping of thread blocks that enables closer cooperation between blocks.

Summary: The unit of scheduling is the thread block: a block always lands on a single SM, which is what makes barrier synchronization and shared memory possible within a block and impossible across blocks.


3. Synchronization and Transparent Scalability

Barrier Synchronization

Analogy: Four friends go shopping at different stores. Each friend shops independently (parallel execution), but everyone must return to the car before leaving. Friends who finish early wait for the last one, and once everyone has arrived, they leave together. That is exactly a barrier: no thread proceeds until every thread reaches the synchronization point.

Synchronization Scopes

The scope of a barrier is the set of threads that participate in it:

Scope Mechanism
Block-wide __syncthreads()
Cluster-wide Cooperative Groups API (Thread Block Clusters)
Grid-wide Cooperative Groups API, with extra restrictions

Barrier Synchronization Rules

Transparent Scalability

Waves

Example: a grid with 660 blocks on a GPU that can execute 264 blocks simultaneously runs in 2.5 waves:

Summary: Barriers only work inside a block, and that restriction is a feature: independent blocks can run in any order, which gives CUDA transparent scalability across GPUs of any size, with grids executing in waves when they exceed the hardware's capacity.


4. Warps and SIMD Hardware

Threads within a block should not be assumed to execute in any specific order; thread scheduling is hardware dependent and may vary between GPU architectures. Use __syncthreads() whenever threads must complete one phase before starting the next.

Warp Partitioning

Linearizing 2D and 3D Blocks

For 2D or 3D thread blocks, CUDA first linearizes the threads in row-major order, then partitions them into warps:

SIMD Execution and the Von Neumann Model

Flynn's Taxonomy

Class Meaning Example
SISD Single Instruction, Single Data Classic CPU core
SIMD Single Instruction, Multiple Data GPU warps
MISD Multiple Instruction, Single Data Rare
MIMD Multiple Instruction, Multiple Data Multicore CPUs

Summary: Blocks are carved into warps of 32 consecutive threads (after row-major linearization for 2D/3D blocks), and each warp executes one instruction at a time on SIMD hardware. SIMT lets you write scalar code while the hardware handles the lockstep execution.


5. Control Divergence

How Divergence Happens

Independent Thread Scheduling (Volta+)

Common Causes of Divergence

Boundary Divergence

Summary: Divergence forces a warp to execute branch paths in multiple passes, so keep threads in a warp on the same path when you can. Boundary-check divergence is usually acceptable because it only touches the last warp, and on Volta and newer you must use __syncwarp() rather than assuming reconvergence.


6. Warp Scheduling and Latency Tolerance

Latency Hiding

Analogy: Think of a post office. A customer filling out a form is like a warp waiting for memory. Instead of waiting, the clerk serves the next ready customer, and the first customer resumes when they are done with the form. The GPU hides latency the same way: it switches to ready warps instead of idling.

Zero-Overhead Scheduling

Example (H100): an SM has 128 streaming processors but can keep 2048 threads (64 warps) resident. That oversubscription is what makes latency hiding work.

Summary: SMs deliberately hold far more warps than they can run at once. Because every warp's state lives permanently in registers, switching costs nothing, and stalls on memory are hidden by simply running whichever warps are ready.


7. Resource Partitioning and Occupancy

What Limits Occupancy

Occupancy = active warps on an SM / maximum supported warps.

SM resources are dynamically partitioned among thread blocks, and occupancy is limited by whichever resource runs out first:

Smaller blocks allow more blocks per SM, while larger blocks allow fewer blocks per SM. High occupancy generally improves latency hiding, but 100% occupancy is not always achievable due to resource limits. Occupancy can also drop when the block size does not divide evenly into the available thread slots.

Block Slots vs. Thread Slots

Occupancy may be limited by a resource other than thread slots:

Registers and the Performance Cliff

Exam tip (H100): 65,536 registers/SM ÷ 2048 max threads/SM = 32 registers/thread to maintain full occupancy. Exceeding this limit can reduce occupancy.

NVIDIA provides tools to estimate occupancy:

Summary: Occupancy is the fraction of an SM's warp capacity you actually use, and it is bounded by registers, shared memory, thread slots, and block slots. Watch for performance cliffs: a couple of extra registers per thread can knock out an entire block's worth of occupancy.


8. Querying Device Properties

The Query API

CUDA applications can query GPU hardware properties at runtime, which lets the same code adapt to different GPUs.

Important Device Properties

Property Meaning
maxThreadsPerBlock Maximum threads allowed per block
multiProcessorCount Number of SMs in the GPU
clockRate GPU clock frequency; with SM count, estimates compute throughput
maxThreadsDim[3] Maximum block dimensions (x, y, z)
maxGridSize[3] Maximum grid dimensions (x, y, z)
regsPerBlock Maximum registers available to a thread block
warpSize Warp size (typically 32)

Why Query Device Properties?

Summary: Query device properties at runtime with cudaGetDeviceCount() and cudaGetDeviceProperties() so your launch configurations and occupancy assumptions match the actual hardware instead of being hard-coded for one GPU.

‹ Ch03: Multidimensional Grids and DataCh05: Memory Architecture and Data Locality ›