You have seen
The contract is called SIMT — Single Instruction, Multiple Threads — and its genius is a sleight of hand: the programmer writes one ordinary scalar program, and the silicon runs vector operations. Understanding exactly where the illusion sits is the whole lesson.
The unit of GPU work is a kernel: a scalar function describing what one thread does. You launch it over a grid of thread blocks, each block holding up to ~1024 threads. Every thread runs the same code and discovers who it is from its coordinates — here in CUDA-style pseudocode:
The index arithmetic is the idiom you will type ten thousand times:
if (i < n) keeps the spares from scribbling out of bounds.
Now the sleight of hand. The hardware does not run those threads independently. It bundles them, 32 at a time, into warps that execute in lock-step: one instruction issues, and all 32 lanes perform it on their own registers. Each thread keeps the illusion of a private program counter and private registers — but per warp there is really one instruction stream driving a 32-wide vector unit.
Compare the two honest alternatives you already know. With
Step through the decomposition. Two of these levels (grid, block) belong to the programming model; the warp level belongs to the microarchitecture — keep that boundary in sight, because the "Watch out!" below turns on it:
The best way to internalise the model is to build it. Here is a toy SIMT machine: it takes a scalar
kernel and runs it over a grid warp by warp, exactly as an SM's scheduler would (warp width
4 instead of 32, so the printout fits). Watch the index arithmetic produce each lane's global
i, and watch the final ragged warp run with its out-of-range lanes masked:
The inner lane loop is the part that is hardware on a real GPU — all four (or
32) calls happen in the same cycle, driven by one instruction. Only the outer loops are genuinely
sequential decisions of the scheduler, and even those interleave freely across SMs.
Why does the model bother with blocks at all, rather than one flat sea of threads? Because the contract needs a boundary between "threads that may cooperate" and "threads that may not":
__syncthreads()-style: everyone waits until all arrive).That second, deliberately empty promise is the scalability contract, and it is why the GPU ecosystem works at all: a kernel that launches 10,000 independent blocks runs correctly on a laptop GPU with 8 SMs (blocks queue up, ~1250 each) and on a datacentre GPU with 140 SMs (blocks spread wide) — the same binary, no recompilation, near-linear scaling. The hardware designer gets to choose the SM count per market segment precisely because the software was never allowed to care.
The name comes from weaving: on a loom, the warp is the set of parallel threads held under tension while the shuttle passes through — dozens of threads, advancing together, one row at a time. For a machine that advances 32 "threads" together one instruction at a time, some early NVIDIA architect couldn't resist, and the textile pun stuck (Star Trek had nothing to do with it). As for 32: it is a compromise, not a law of nature. Wider warps amortise the instruction-issue machinery over more lanes (cheaper control per FLOP) but waste more work when threads diverge or the data runs ragged; narrower warps waste control. NVIDIA landed on 32 in 2006 and has never moved; AMD ran 64-wide "wavefronts" for a decade, then added a 32-wide mode in RDNA (2019) — two vendors' independent gradient descent converging on the same neighbourhood.
Nothing in the kernel language mentions warps — and that is a specification, not an oversight. The classic trap: threads in a warp run in lock-step today, so old code often skipped block barriers for threads it knew shared a warp ("implicit warp synchronisation") — and it worked, for years. Then Volta (2017) gave threads independent program counters so diverged threads could interleave, and that entire genre of code silently broke. The rule that survives hardware generations: correctness may rely only on the model (block barriers, explicit warp-sync primitives); warp awareness is for performance (coalescing, divergence — you will meet both in the next module). Code that is only correct because of a microarchitectural habit is a time bomb with a driver-update fuse.