Thread blocks and grids
The Two-Level Hierarchy
GPUs execute millions of threads simultaneously, but hardware constraints force a structure:
Thread Block (Cooperative Thread Array):
- A group of threads (typically 128–1024) that execute the same kernel code
- All threads in a block run on the same Streaming Multiprocessor (SM)
- Share fast on-chip shared memory (user-managed cache)
- Can synchronize using
__syncthreads()barrier - Maximum size: hardware-limited (e.g., 1024 threads/block on modern GPUs)
Grid:
- A collection of thread blocks launched together
- Blocks can be 1D, 2D, or 3D arrays (matching problem geometry)
- Blocks execute independently and may run in any order
- Spans all SMs on the GPU
- No cross-block synchronization within a kernel (blocks are autonomous units)
Why This Design?
Why blocks exist:
- Scalability: Same kernel runs on GPUs with 10 SMs or 100 SMs—runtime just schedules blocks differently
- Data locality: Threads processing nearby data elements stay physically close, sharing fast memory
- Synchronization: Only threads that need to coordinate (within a block) can synchronize—avoids global stalls
Why grids exist:
- Problem decomposition: Natural mapping to data structures (2D grid for images, 3D for volumes)
- Paralelism exposure: Programmer expresses maximum parallelism; hardware exploits what it can
- Load balancing: Runtime distributes blocks to SMs dynamically, hiding idle time

Deriving Global Thread IDs
For a 1D problem (e.g., vector addition of length N):
Setup:
- Grid has
Gblocks, each withBthreads - Total threads:
- Each thread processes one element
Why this indexing?
Thread 0 in block 0 should map to element 0, thread 0 in block 1 should map to element B, etc.
Derivation:
Block 0: threads [0, B-1] → elements [0, B-1]
Block 1: threads [0, B-1] → elements [B, 2B-1]
Block k: threads [0, B-1] → elements [kB, (k+1)B-1]
For thread t in block k:
Why this step? The block index tells us how many complete blocks came before (each contributing B elements), and threadIdx gives our position within the current block.
Block Scheduling
Warp-based execution within blocks:
- Threads in are grouped into warps (32 threads)
- All threads in a warp execute the same instruction simultaneously (SIMT model)
- Block size should be a multiple of 32 for efficiency
Example: 256-thread block = 8 warps
- If threads diverge (if-else), some warp threads are masked—still consume cycles
- Synchronization via
__syncthreads()ensures all warps in a block reach the barrier before any proceeds
Resource limits per SM:
- Maximum threads (e.g., 2048 on Ampere)
- Registers per thread
- Shared memory per block
- Maximum blocks (typically 16-32)
Why block size matters:
- Too small (e.g., 32): underutilizes SM, can't hide memory latency
- Too large (e.g., 1024): fewer blocks fit on SM, reduces occupancy if one block stalls
- Sweet spot: 128–512 threads, chosen empirically or via occupancy calculator
Recall Explain to a 12-Year-Old
Imagine you're organizing a giant school art project: 10,000 students painting a massive mural. You can't just let everyone swarm the wall—chaos! Instead:
- Split students into teams of 30 (thread blocks). Each team gets a section of the wall and shares paint buckets (shared memory) at their table.
- Teams can talk to each other ("Hey, is everyone done with the sky?") but only within their own table—shouting across the gym is too slow.
- The gym has 10 workstations (SMs), so 10 teams work simultaneously. As teams finish, the teacher assigns new sections to those workstations.
- The grid is the entire mural divided into sections, one per team.
GPUs work the same way: millions of tiny workers (threads) grouped into cooperative teams (blocks) that paint different parts of the canvas (grid) without neding to coordinate with distant teams. This is how your GPU renders a game frame with billions of pixels in milliseconds!
Connections
- CUDA Programming Model – syntax and API for launching grids/blocks
- Streaming Multiprocessors – hardware that executes thread blocks
- Warp Execution – how threads within a block run in SIMT groups
- Shared Memory – fast on-chip storage for intra-block communication
- Thread Synchronization –
__syncthreads()and cooperative groups - GPU Occupancy – maximizing active threads per SM
- Memory Coalescing – how thread indexing affects memory bandwidth
- Parallel Algorithm Design – decomposing problems into grids of blocks
#flashcards/hardware
What are the two main levels in the GPU execution hierarchy? :: Thread blocks and grids. Threads are grouped into blocks that share resources, and blocks are organized into a grid covering the problem space.
Why can't threads in different blocks synchronize within a kernel?
What is the formula for a thread's global ID in1D?
globalID = blockIdx.x * blockDim.x + threadIdx.x. This accounts for all threads in previous blocks plus position in current block.Why do we use ceiling division when calculating grid size?
(N + B - 1) / B rounds up, so the last block handles remaining elements even if N isn't divisible by block size.What are the built-in CUDA variables for thread identification?
threadIdx (position in block), blockIdx (block position in grid), blockDim (block size), gridDim (grid size). Each has x, y, z components.Why is a 32-multiple block size recommended?
What limits how many blocks can run on an SM simultaneously?
Why can't you assume block 0 executes before block 1?
What happens to extra threads when N isn't divisible by block size? :: The last block has excess threads. Use an if-guard if (i < N) to prevent out-of-bounds memory access.
Why use 2D blocks for matrix operations?
What is GPU occupancy?
Why are thread blocks called "cooperative thread arrays"?
__syncthreads()), unlike threads in different blocks.Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
GPU programming mein ek fundamental concept hai thread blocks aur grids. Socho ki tumhe ek bahut bada kaam karna hai—jaise 1 million numbers ko add karna.Ek single thread se yeh kaam bahut slow hoga, toh GPU hume lakhs threads ek sath chalane ki power deta hai.
Lekin itne threads ko manage karna mushkil hai, toh GPU ek two-level hierarchy use karta hai. Pehle level pe hum threads ko blocks mein group karte hain (typically 128-256 threads). Ek block ke andar ke threads ek dusre se fast shared memory ke through baat kar sakte hain aur __syncthreads() se synchronize ho sakte hain—matlab sab ek sath checkpoint pe pohochte hain. Yeh threads physically bhi pas hote hain, same Streaming Multiprocessor (SM) pe run karte hain.
Dusre level pe hum in blocks ko grid mein arrange karte hain. Grid1D, 2D, ya 3D ho sakta hai—tumhare problem ki geometry ke hisaab se. Jaise image processing ke liye 2D grid natural lagta hai. Har block independently run hota hai aur GPU scheduler inhe dynamically different SMs pe assign karta hai. Yeh design scalable hai—same code10 SMs wale laptop GPU pe bhi chalega aur 100 SMs wale datacenter GPU pe bhi.
Thread indexing ka formula simple hai: globalID = blockIdx.x * blockDim.x + threadIdx.x. Matlab pehle dekho kitne complete blocks pehle aye (har block mein blockDim threads), phir current block ke andar apna position add karo. Yeh unique ID har thread ko apna kaam assign karne mein help karti hai—like array ke kaunse element pe kaam karna hai. Common mistake yeh hai ki log blocks ko sequential samajhte hain (block 0 pehle, phir block 1), lekin GPU inhe any order mein run kar sakta hai. Isliye blocks ke bech coordination nahi ho sakta ek kernel andar—cross-block synchronization ke liye separate kernel launch chahiye.