LESSON READING // 00
PROGRAMMABLE PARALLELISM
Rewrite a serial loop as many threads, then see Grid, Block, and Thread as a language for describing parallel work—not as a product specification.
Write down answers as you go; the estimate includes code tracing and practice.
PLAY THE INTERACTIVE LESSON→- 01ORIENT + OBJECTIVES05 MIN
- 02MENTAL MODEL08 MIN
- 03DEEP DIVES15 MIN
- 04CODE TRACE12 MIN
- 05HISTORY + ECOSYSTEM08 MIN
- 06PRACTICE + REVIEW12 MIN
Build hands-on accelerated-computing fundamentals: identify parallel work, write kernels, configure the hierarchy, and validate correctness and performance.
The game builds a visual Grid/Block/Thread model; the optional lab transfers that exact mapping into a real vector-add program.
- Basic C/C++ or Python
- Arrays, loops, and functions
- Ability to run command-line examples
- CUDA Toolkit / nvcc
- CUDA Samples
- Nsight Systems or CUDA Events
ORIENT + OBJECTIVES
REMEMBER THIS FIRST
Find independent work first; only then decide how many threads should express it.
BY THE END, YOU SHOULD BE ABLE TO
- 01
Decide whether loop iterations are independent enough to map onto GPU threads.
- 02
Compute a grid from data size and threads per block, including a safe boundary guard.
- 03
Separate the programming semantics of grid, block, warp, and thread from physical scheduling.
- 04
Name at least three reasons why more threads do not imply proportional speedup.
BUILD THE MENTAL MODEL
- 01WORK
Every array element performs the same addition, with no dependency on another element.
- 02MAP
One thread owns one element; blocks organize groups of threads, and a grid organizes all blocks.
- 03BOUND
Launch sizes usually round up, so the kernel must guard its global index against the data boundary.
CONCEPT DEEP DIVES
GLOBAL THREAD INDEX
blockIdx.x × blockDim.x + threadIdx.x turns a block-local thread number into a unique index across the grid.
THE HIERARCHY HAS SEMANTICS
Threads in one block can synchronize and share on-chip memory. Blocks should normally be safe to schedule independently.
PARALLELISM IS NOT SPEEDUP
Thread count describes available work. Memory access, branches, resource use, and problem size still control observed performance.
A LOGICAL HIERARCHY IS NOT AN SM FLOOR PLAN
Grid and block describe a kernel's logical work decomposition. A grid can contain far more blocks than the physical SM count; the runtime schedules blocks in waves as resources become available. Correct code cannot assume block 0 completes before block 1 or use an implicit cross-block barrier.
Threads inside a block execute in warps, commonly 32 threads in today's CUDA model. Warp behavior matters for branches, memory, and performance, while block scope defines shared memory and synchronization. Blocks are therefore more than arbitrary number ranges.
PAUSE AND REASONCan a grid with 10,000 blocks run correctly on a GPU with only dozens of SMs?+
REFERENCE ANSWERYes. Blocks execute in waves. Physical concurrency changes elapsed time, not correctness, provided blocks do not depend on an undefined order.
AN INDEX IS A DATA-OWNERSHIP RULE
The global index defines which thread owns which data. One-dimensional arrays often use blockIdx.x × blockDim.x + threadIdx.x. A 2D image computes x and y separately and often flattens them with y × width + x. A bad mapping causes duplicate writes, gaps, or out-of-range access.
Rounding a launch upward is normal because data sizes rarely divide evenly by block size. A boundary branch affects only a few threads in the final block and lets one kernel safely handle arbitrary n. Establish unique ownership, complete coverage, and safety before tuning.
PAUSE AND REASONWhat breaks if a 2D image kernel checks x < width but not y < height?+
REFERENCE ANSWERThreads beyond the last valid row can still compute linear addresses and access out of bounds. Both dimensions need guards.
BLOCK SIZE TRADES MULTIPLE RESOURCES
Threads per block determines warp count and combines with registers per thread and shared memory per block to limit how many blocks can reside on an SM. A larger block can reduce resident block count; a tiny block may expose too few warps to hide latency.
Occupancy is a diagnostic, not the only objective. Memory-bound, register-heavy, and compute-bound kernels may prefer different configurations. The game's 64-thread block satisfies one mapping exercise; it is not a universal optimum.
PAUSE AND REASONWhy might changing a block from 128 to 1,024 threads make a kernel slower?+
REFERENCE ANSWERThe larger block may consume enough registers or shared memory to reduce resident blocks and warps, while increasing tail or branch waste.
PUT IT BACK INTO CODE
01__global__ void add(float* a, float* b, float* c, int n) {02 int i = blockIdx.x * blockDim.x + threadIdx.x;03 if (i < n) c[i] = a[i] + b[i];04}05int blocks = (n + threads - 1) / threads;06add<<<blocks, threads>>>(a, b, c, n);TRACE THE CODE
- 01KERNEL__global__ DEFINES WHERE CODE RUNS
The host launches the function and the device executes it; every thread runs the same kernel program.
- 02INDEXBUILD ONE UNIQUE i
Block index, block width, and thread index combine into global ownership.
- 03GUARDONLY VALID THREADS WRITE
Rounded-up launches create extra threads. The guard safely excludes them.
- 04LAUNCHCONFIGURATION FOLLOWS PROBLEM SIZE
ceil(n / threads) covers all elements; choose block size by resource analysis and measurement after correctness.
KNOWLEDGE ATLAS
OPTIONAL REFERENCE · REVISIT AS NEEDED · OUTSIDE THE CORE 60 MINUTES
CORE VOCABULARY
- Kernel
- A host-launched function executed on the device by many threads; each runs the same program with a different index.
- Grid
- All blocks created by one launch—the logical problem space, not a diagram of physical GPU units.
- Block
- A schedulable group whose threads can share memory and use block-scoped synchronization.
- Warp
- The basic thread execution group on an SM, typically 32 threads; branches and addresses shape its efficiency.
- Occupancy
- Active warps relative to the maximum possible on an SM; useful for latency reasoning, but not a performance score.
WORKED EXAMPLE
- 01COUNT BLOCKS
ceil(1,000 ÷ 256) = 4, so the grid needs four blocks.
- 02COUNT THREADS
4 × 256 = 1,024 threads launch; the first 1,000 own real data.
- 03GUARD THE TAIL
The final 24 threads own no element, so if (i < n) must stop them.
DIAGNOSTIC PLAYBOOK
Intermittent wrong results or illegal address
- INSPECT FIRST
- Index ownership, rounded-up tail threads, array lengths, and error checks
- EVIDENCE
- Compute Sanitizer, the smallest failing size, and a CPU-reference diff
GPU is slower than CPU
- INSPECT FIRST
- Problem size, cold start, movement, and launch share
- EVIDENCE
- Segmented timing and a warmed-up median—not one run
A larger block is slower
- INSPECT FIRST
- Registers/thread, shared memory/block, active warps, and tail blocks
- EVIDENCE
- Compiler resource report, occupancy data, and a controlled block-size sweep
HARDWARE → ECOSYSTEM
- 01FIXED GRAPHICS PIPELINE
Programs organized work around graphics stages.
General computation had to masquerade as graphics. - 02G80 + CUDA
Unified shader hardware met the kernel/thread model.
Developers could express data-parallel work directly. - 03MODERN CUDA ECOSYSTEM
Libraries, compilers, profilers, and frameworks share the execution model.
Thread mapping became a common entry point to HPC and AI.
HARDWARE + ECOSYSTEM COORDINATE
A unified shader architecture made many processing units available to more general programs; CUDA then exposed kernels, the thread hierarchy, memory, and synchronization. This was not the birth of parallel computing—it was a major shift in GPU programmability.
PRACTICE + REVIEW
With 1,000 elements and 256 threads per block, why does the kernel still need if (i < n)?
- A. To make threads execute faster
- B. Because 1,024 threads launch and the last 24 indices are out of range
- C. Because a warp contains 16 threads
REVEAL ANSWER+
The block count rounds up to four, launching 1,024 threads. The guard lets extra threads exit safely.
n = 10,000 and threads per block = 256. How many blocks and extra threads launch?
HINT+
Round 10,000 / 256 upward.
REFERENCE ANSWER+
40 blocks launch 10,240 threads, so 240 extra threads exit through the boundary guard.
The kernel writes c[i] with no if (i < n). When is the bug most likely to appear?
HINT+
Use an n that is not divisible by block size.
REFERENCE ANSWER+
The final block contains invalid indices and may read or write out of bounds. Guard every affected array access.
For a 1920×1080 image and 16×16 blocks, what is the grid?
HINT+
Round each dimension independently.
REFERENCE ANSWER+
120×68. The y dimension covers 1,088 rows, so the kernel must check both x < 1920 and y < 1080.
Why should block 1 not spin on a normal global variable until block 0 writes it?
HINT+
Consider scheduling order and residency.
REFERENCE ANSWER+
Blocks have no guaranteed order, so the scheme can race or deadlock. Split kernels, use supported cooperative launch, or redesign blocks to be independent.
OPTIONAL HANDS-ON LAB
OUTSIDE THE CORE 60 MINUTES · REQUIRES A SUITABLE CUDA / GPU ENVIRONMENT
LAB GOAL
MOVE YOUR FIRST VECTOR ADD FROM CPU TO GPU
Match a CPU reference and explain launch configuration, the boundary guard, and host/device movement.PROCEDURE
- 01
Create deterministic 10,000-element input and a CPU correctness oracle.
- 02
Write one-thread-per-element code with 256 threads per block and a rounded-up grid.
- 03
Test n = 10,000, 10,001, and a sub-block input.
- 04
Time allocation, H2D, kernel, and D2H separately from end-to-end time.
Submit the kernel, three correctness comparisons, and a table separating kernel and end-to-end time; do not treat one cold run as a conclusion.
Map a 2D image with 16×16 blocks and explain both x and y guards.