A (graphics processing unit) was invented to draw video games. Drawing one frame means working out the color of millions of pixels. Every pixel follows the same recipe, just with different numbers.
So GPUs were built with thousands of small, simple math units that all follow the same steps at once.1
AI turns out to need exactly that: the same multiply-and-add, repeated billions of times on different numbers. In 2012, a famous picture-recognizing program was trained in under a week on two gaming GPUs.2 AI has run mostly on GPUs ever since.
The hard part isn’t the math. It’s waiting for numbers to arrive from memory.
A is a processor built for throughput rather than for finishing any single task quickly. GPUs began as hardware for 3D games; by 2001–2003 their programmable shading stages were fast enough that researchers started using them for general math, and in 2007 NVIDIA introduced CUDA, a way to program them without pretending the work was graphics.1 In 2012 AlexNet, a landmark image-recognition network, was trained in five to six days on two consumer GPUs.2
A CPU spends its transistors on making one thread fast: a few powerful cores, large caches, complex branch prediction, and at most a couple of hardware threads per core. A GPU spends them on many simpler cores, small caches and very large register files, and it relies on having thousands of threads in flight.10 This chapter covers the five ideas that make that work:
- SIMT: threads grouped into warps that execute one instruction at a time together.
- Latency hiding: each core keeps many warps resident and switches between them every cycle.
- The memory hierarchy: registers, shared memory and caches on chip, high-bandwidth DRAM beside it.
- Matrix units: tensor cores that now do most of the AI math.
- Programmability: why GPUs stayed general purpose while other AI chips specialized.
It is the first of four ways to run AI covered in this guide; the others are systolic arrays, spatial dataflow meshes and wafer-scale designs.
You know a neural network is mostly matrix multiplication. A GPU runs it as a throughput machine: tens of thousands of scalar threads, grouped in hardware into of 32 (NVIDIA) or 64 (AMD CDNA) that share one instruction stream, spread over one to a few hundred multithreaded cores.39 Three design decisions follow from that and set the rest of the chapter:
- Latency is tolerated, not avoided. Instead of large caches and out-of-order execution, each core holds dozens of warp contexts on chip and issues from whichever is ready, with no branch prediction or speculation.3 The needed concurrency follows Little’s law, and registers per thread trade directly against it.
- Bandwidth is managed explicitly. A software-managed scratchpad (shared memory or LDS) and coalesced access patterns are part of the programming model, because DRAM bandwidth per FLOP keeps shrinking.
- Dense math moved into matrix instructions. Since Volta, tensor or matrix cores deliver roughly 8–16× the throughput of the vector lanes at AI precisions, mostly by amortizing per-instruction overhead.57
We use NVIDIA’s and AMD’s published architecture documents side by side; terms differ (SM vs CU, warp vs wavefront, shared memory vs LDS, tensor core vs matrix core) but the ideas map one to one. Arithmetic intensity and the roofline come from What the workload needs; precision formats from Number formats.
GPU: many simpler cores, small caches and big register files, kept busy by thousands of threads. Tap a part.
A GPU program is written for one worker: “take your number, multiply it, add it to this one.” The GPU then runs that same little program for thousands of workers at once, each on its own number.
The workers are grouped into teams, usually of 32. A team is called a , a word borrowed from weaving, where a loom moves many threads together.3 The whole team gets one instruction at a time, and every member does it at once. Giving one order to 32 workers is much cheaper than giving 32 separate orders. A CPU uses a smaller version of the same trick in its vector units.
The catch comes when team members disagree at an “if” in the program. The team can’t split up, so it does one side while the rest stand idle, then swaps. That’s called , and it slows the team down.3
A big GPU has around a hundred cores, and each core juggles dozens of teams.
The programmer writes a : a function for a single thread. Launching it creates a grid of thousands or millions of threads, organized into of up to 1,024 threads. All threads of a block run on one GPU core, called a (SM) by NVIDIA and a compute unit (CU) by AMD.3
Inside the core, the block is cut into of consecutive threads: 32 on NVIDIA GPUs, 64 on AMD’s data-center (CDNA) GPUs, where a warp is called a wavefront.3910 A warp executes one common instruction at a time. This is , single instruction, multiple threads: the hardware fetches and decodes each instruction once and runs it on 32 lanes, each with its own registers and data.3
When threads disagree
If threads in one warp take different sides of a branch, the warp runs each side in turn, with the threads not on that side disabled. Different warps never slow each other down this way; divergence only happens within a warp.3 A 50/50 split means each instruction on either side uses half the lanes, so the branchy part of the code runs at half efficiency. On AMD hardware the same thing happens through an execution mask: the lanes still occupy the ALUs, but their results are thrown away.10
Inside one core
A recent NVIDIA SM is split into four quarters, each with its own warp scheduler, a slice of the register file, 16 lanes of FP32 math, and two tensor cores (in the V100 generation).4 An AMD CDNA compute unit similarly has four SIMD units of 16 lanes; a 64-thread wavefront passes through a 16-lane SIMD in four cycles.9 The core is designed to keep thousands of threads resident at once, which is the subject of the next section.
The execution hierarchy is grid → (workgroup) → warp → thread. Blocks are distributed to SMs with free capacity, all threads of a block run on that one SM, and they share its shared memory and can barrier-synchronize.3 Each block is split into warps in thread-ID order, so control flow that depends on threadIdx / warpSize never diverges.3 For the CPU counterpart, fixed-width and length-agnostic SIMD with explicit masks, see Multicore and vector units.
Physically, an SM is four scheduler partitions. In GV100 each partition has one warp scheduler, one dispatch unit, a 64 KB register file (16,384 32-bit registers), 16 FP32 lanes, 8 FP64 lanes, 16 INT32 lanes and two tensor cores, so a 32-thread FP32 instruction occupies its partition’s FP32 pipe for two cycles.4 Ampere and Hopper keep the four-scheduler layout; each scheduler issues one instruction per cycle from its statically assigned warps.3 AMD’s CDNA CU has four 16-lane SIMDs (a wave64 instruction takes four cycles on one SIMD), four warp pools of up to ten slots each (40 per CU; eight per pool on CDNA 2), and an issue arbiter that can send up to five instructions per cycle to different unit types (vector, vector memory, scalar, LDS, branch).9
Divergence mechanics
Before Volta, each warp had one program counter and an active mask. On a divergent branch the warp saves its mask, runs one path and then the other with the inactive threads masked off, and restores the full mask where the paths rejoin.4 The standard reconvergence point is the branch’s immediate post-dominator, the first instruction every path must pass through, implemented with a per-warp stack.13 Lane efficiency for a region is the average fraction of active lanes; nested divergence compounds multiplicatively.
Volta added independent thread scheduling: per-thread program counters and call stacks, with a schedule optimizer that regroups active threads of a warp into SIMT units. Divergent paths can now interleave, which makes intra-warp locks safe, but at any cycle the warp still issues one instruction for the active lanes of one path.4 It also broke implicit warp-synchronous code, which must now use explicit __syncwarp() or warp-level primitives.3
AMD handles divergence by predication with a per-wavefront execution mask, while a separate scalar unit runs warp-uniform control flow and values without touching the vector lanes.9 A wider wavefront amortizes each issued instruction over more threads but wastes more lanes when threads disagree; AMD’s documentation notes that the 32-thread mode of its graphics (RDNA) parts reduces divergence penalties and register pressure.9
A warp of 32 threads, each with its own x; 16 are negative (pink). Step through the instructions.
Here’s the GPU’s big trick. A chip works in steps, timed by a clock that ticks billions of times a second. When a team asks for numbers from the main memory, they take hundreds of ticks to arrive. The team can’t do anything useful while it waits.3
So each core keeps lots of teams ready at once. Every tick, it picks a team that has work it can do right now. Switching teams costs nothing, because every team’s notes stay inside the core the whole time.3
There’s a limit. Each team needs its own scratch space in the core, and space runs out. With too few teams, the core sits idle again.3
Every instruction has a latency, the number of clock cycles before its result can be used. On a V100, simple math takes 4 cycles, while a load that misses every cache and goes to DRAM takes about 375.12 A single warp spends nearly all its time waiting.
The GPU’s answer is hardware multithreading. Each warp’s state (its program counter and registers) stays on chip for the warp’s whole life, so switching between warps costs nothing. Every cycle, each warp scheduler picks a warp whose next instruction is ready and issues it.3 AMD describes the same thing for its compute units: zero-overhead, single-cycle switching between resident wavefronts.9 If enough warps are resident, some warp is always ready, and the latency is .
How many warps are enough?
NVIDIA’s guide works an example: with 4-cycle math and 4 schedulers per SM, 16 warps are needed just to hide arithmetic latency. Loads from off-chip memory take hundreds of cycles, so they need far more, and the more math each warp does per load (its ), the fewer warps it takes.3 The sim below lets you find that balance yourself.
Occupancy and the register budget
is the number of warps resident on a core divided by the maximum it can hold (up to 64 on recent NVIDIA SMs). What usually limits it is the , the core’s fast scratch storage. An NVIDIA SM has 65,536 32-bit registers shared by every resident thread.4 The more registers each thread uses, the fewer warps fit:
| Registers per thread | Threads that fit (65,536 ÷ registers) | Warps | Occupancy |
|---|---|---|---|
| 32 | 2,048 | 64 | 100% |
| 64 | 1,024 | 32 | 50% |
| 128 | 512 | 16 | 25% |
| 255 (the maximum) | about 256 | 8 | 12.5% |
(Real allocation rounds up in chunks, so actual numbers can be a little lower.) Shared memory per block and a cap on blocks per SM are the other limits. Give a thread too few registers, though, and the compiler values to slow off-chip memory instead.3
The CUDA guide states the condition directly: full utilization requires every scheduler to have some instruction to issue at every cycle of the latency period. With maximum-throughput instructions, hiding a latency of cycles takes instructions in flight per SM on devices that issue one instruction per scheduler per cycle across four schedulers.3 For 4-cycle dependent math that is 16 warps per SM, fewer if each warp has independent instructions to issue back to back.3 Volkov frames it as Little’s law, concurrency = latency × throughput: on Maxwell, 6-cycle math at 4 instructions per cycle per SM needs 24 instructions in flight, and 368-cycle memory at the sustainable bandwidth needs about 30 memory instructions in flight per SM.11
Measured V100 latencies give a sense of the spread a scheduler must cover12:
| Access | Latency (cycles) |
|---|---|
| Dependent FFMA (register operands) | 4 |
| Shared memory, no bank conflict | 19 |
| L1 hit | 28 |
| L2 hit | ≈ 193 |
| DRAM (HBM2), TLB hit | ≈ 375 |
| DRAM with a TLB miss | ≈ 1,029 |
Occupancy limits
Resident warps per SM = min(warp slots, register limit, shared-memory limit, block limit × warps per block). From Kepler GK180 (compute capability 3.5) through Volta GV100, NVIDIA’s data-center SMs held 64 warps, 65,536 registers and at most 255 registers per thread.4 The register limit has cliffs at block granularity. The CUDA guide’s example: 512-thread blocks at 64 registers fit exactly two blocks (, so 32 warps); at 65 registers only one block fits and the SM drops to 16 warps.3 The compiler balances register count against spills; programmers steer it with __launch_bounds__ or -maxrregcount.3 On AMD CDNA the same pressure applies to vector registers (256–512 KiB per CU) and to the 10 warp slots per SIMD.9
Two refinements matter in practice. First, synchronization also stalls warps: a block’s warps waiting at a barrier can idle the SM, which is why having several resident blocks helps.3 Second, occupancy is a means, not a goal. Because Little’s law counts instructions in flight, not warps, independent loads and math within a warp can replace warps.11 Under the hood works through the model and its failure modes.
64 registers per thread: 32 warps resident (50% occupancy), limited by the register file.
This is one GPU core, seen over a short slice of time. Each row is a team of workers. Orange marks are trips to memory, followed by a long wait. Blue marks are math. The red strip at the top shows when the core has nothing to do.
Start with one team and watch the core sit idle. Then add teams until the red gaps disappear. Then try making memory slower, or giving each team more math per fetch.
The timeline shows one warp scheduler (a quarter of an SM) issuing at most one instruction per cycle. Each warp loops: one load from memory, then a chain of dependent math instructions 4 cycles apart. The red strip marks empty issue slots. Things to try:
- With 1 warp, how much of the time is the scheduler busy? Why?
- With 20 math instructions per load and 200 cycles of latency, how many warps does it take to fill every slot? Compare with the estimate.
- Raise the math instructions per load. Why does that cut the number of warps you need?
The Expert view adds a registers-per-thread slider that caps resident warps at , one scheduler’s share of a 65,536-register SM; a divergence toggle that runs the math block twice at half the lanes; and a scheduler toggle between greedy-then-oldest (the default) and loose round-robin, both as defined in the GPU-architecture literature.15 Try:
- Set 16 warps, latency 200 and math 16 (about 90% busy). Now raise registers per thread from 32 to 128. At what register count does the cap start to cost utilization, and by how much?
- Turn divergence on. Issue-slot utilization goes up, while useful lane work goes down. Why?
- With 16 warps, switch to round-robin. The warps fall into step and stall together, the effect Narasiman et al. describe.14
The model is deliberately simple: fixed latencies, no bandwidth limit, no cache, one load per loop and no independent instructions within a warp. It isolates thread-level latency hiding.
A GPU has several kinds of memory, from tiny and lightning-fast to huge and slow. The two ends matter most.
Inside each core is a small, fast notepad that a team of workers can share. It sits right next to the math units. Beside the GPU sit big memory chips that hold the whole AI model. They hold far more, but every trip there is slow.
A trip to the big memory also costs far more energy: by one estimate, about a hundred times more per number.20 So fast GPU programs copy a block of numbers into the notepad once. Then they use it as many times as they can before fetching the next block.
The GPU’s has four main levels. Each is larger and slower than the one before.3
- Registers. Private to each thread, fastest, and on a GPU unusually large: 256 KB per NVIDIA SM, as much as an H100 core’s combined L1 cache and shared memory.56
- Shared memory and L1 cache. A block of fast on-chip in each core, split between a programmer-controlled and an automatic L1 . On an A100 it is 192 KB per SM, up to 164 KB of which can be shared memory.3 AMD’s equivalent is the local data share (LDS) plus a separate L1.7
- L2 cache. Shared by all cores, tens of megabytes: 40 MB on an A100.5
- Global memory. outside the GPU die, today stacked (HBM) in the same package: 40 GB at 1,555 GB/s on the original A100.5
FlashAttention’s authors put the gap plainly for an A100: HBM delivers 1.5–2.0 TB/s, while the on-chip SRAM across 108 SMs delivers an estimated 19 TB/s, about ten times faster but thousands of times smaller. Rewriting attention to work on tiles that fit in SRAM cut HBM traffic by up to 9× and made it several times faster.16
Two rules for using memory well
- Coalesce. When the 32 threads of a warp read neighboring addresses, the hardware merges them into a few transactions. If each thread’s 4-byte read lands in its own 32-byte transaction instead, throughput drops by a factor of eight.3 This is called .
- Reuse on chip. Load a tile of data into shared memory once, then let every thread in the block read it many times. This is how GPU matrix multiplication reaches high arithmetic intensity.17
Why the off-chip memory is the bottleneck for AI, and what HBM is, is the subject of The memory wall.
Each level trades capacity for latency, bandwidth and energy. A Horowitz 45 nm estimate puts a 32-bit read from an 8 KB SRAM at about 5 pJ and from DRAM at about 640 pJ, more than 100× apart.20 The levels, with representative published numbers:
| Level | Scope | NVIDIA (A100 / H100) | AMD (MI300X) |
|---|---|---|---|
| Registers | Thread | 256 KB per SM | 256–512 KiB VGPR per CU (CDNA family) |
| Shared memory / LDS + L1 | Block / CU | 192 KB / 256 KB unified per SM; up to 164 / 228 KB shared | 64 KB LDS + 32 KB L1 per CU |
| L2 | Chip (or die) | 40 MB / 50 MB | 4 MB per XCD (38 CUs) |
| Memory-side cache | Package | none | 256 MB Infinity Cache, 17.2 TB/s |
| HBM | Package | 40 GB, 1.56 TB/s / 80 GB, over 3 TB/s | 192 GB, 5.3 TB/s |
H100 figures are NVIDIA’s preliminary announcement specs. Sources: register and shared capacities639, caches and HBM567.
Details that bite
- Coalescing granularity. Global accesses are served in 32-byte transactions; the number of transactions per warp instruction sets the bandwidth efficiency. Fully coalesced also requires block and array widths to be multiples of the warp size for 2D access.3
- Bank conflicts. Shared memory has 32 banks of 4 bytes; threads hitting different addresses in one bank serialize into requests (a broadcast of the same word is free).3 GEMM and attention kernels swizzle their tile layouts to avoid this.
- Spills. Spilled registers go to local memory, which lives in device memory and has global-memory latency.3
- Achievable bandwidth. Microbenchmarks reached 750 of a theoretical 900 GiB/s on V100 HBM2 (83%).12 Budget against measured, not pin, bandwidth.
Both vendors grew the scratchpad and made it asynchronous
Ampere added an asynchronous copy from global to shared memory that bypasses the register file, so loads for the next tile overlap math on the current one.5 Hopper added a Tensor Memory Accelerator that moves whole multi-dimensional tiles by descriptor, thread block clusters, and distributed shared memory that lets a block read a neighboring SM’s shared memory about 7× faster than exchanging through global memory.6 AMD’s CDNA 4 grew the LDS from 64 KB to 160 KB per CU with doubled read bandwidth and lets it load directly from L1, explicitly to feed matrix multiplication.8 Both are moves toward the explicitly managed, tile-oriented memory of dataflow designs.
One HBM read, then 1 on-chip read: 645 pJ per use, versus 640 pJ fetching from DRAM every time.
Consecutive 4-byte words: the warp’s 32 reads coalesce into 4 sectors of 32 bytes. Every fetched byte is used.
Every instruction a chip runs comes with paperwork. The chip has to fetch it, work out what it means, and hand it its numbers. For a single multiply-and-add, that paperwork costs about twenty times more energy than the math itself.20
Since 2017, GPUs have had (AMD calls them matrix cores). These special units multiply a whole small grid of numbers, like a 4-by-4 grid times another, in one instruction.4 The paperwork is paid once for dozens of multiplications. And most AI math is exactly this kind of grid multiply.
So on a modern AI GPU, the tensor cores do almost all the work. The ordinary math units are still there for everything else, but they give only a small slice of the chip’s top speed.5
Neural-network layers boil down to matrix multiplication, the operation counted in What the workload needs. On ordinary GPU lanes, each multiply-accumulate () is one instruction per thread. A instead computes on small matrix tiles in one operation.
- Volta (2017): each tensor core multiplies 4 × 4 FP16 matrices and accumulates in FP16 or FP32, 64 fused multiply-adds per clock; eight per SM give 512 per clock.4
- Ampere (2020): four larger tensor cores per SM, 256 FP16 multiply-adds per clock each, plus formats such as BF16, TF32 and INT8.5
- AMD CDNA: matrix fused multiply-add (MFMA) units, described by AMD as mini systolic arrays, executing instructions like
v_mfma_f32_16x16x4f16, a 16 × 16 × 4 FP16 multiply with FP32 accumulation.9
The payoff shows in the peak numbers. An A100 does 19.5 TFLOPS (trillion floating-point operations per second) of ordinary FP32 math but 312 TFLOPS of FP16 on tensor cores, 16× more.5 An MI300X does 163.4 TFLOPS of FP32 vector math and 1,307 TFLOPS of FP16 matrix math, 8× more.7 So AI software works hard to cast everything it can as matrix multiplies in low precision; formats such as BF16, FP8 and FP4 are covered in Number formats.
Why are matrix instructions so much cheaper? In a 45 nm estimate, fetching, decoding and reading operands for an instruction costs tens of picojoules, about 30 pJ for a simple one. For a single FP16 multiply-add (1.5 pJ of math) that overhead is 2,000%; for a matrix instruction doing 110 pJ of math, it’s only 22%.20 A tensor core is, in effect, a small systolic array embedded in a programmable core.
A matrix instruction amortizes the per-instruction overhead (fetch, decode, operand fetch, about 30 pJ in a 45 nm estimate) over many MACs: HFMA carries 2,000% overhead, HDP4A 500%, HMMA 22%, IMMA 16%.20 Dally attributes NVIDIA’s ~1000× single-chip inference gain from 2012 to 2023 to number formats (~16×), complex instructions (~12.5×), process (~2.5×) and sparsity (~2×), with model efficiency on top.20 By that accounting, complex instructions contributed about five times as much as a decade of process scaling.
How the instructions are organized
- Warp-level MMA (Volta–Ampere). The warp collectively holds , and fragments in registers and issues an MMA; on Volta each tensor core performs 64 FP16 FMAs per clock, 512 per SM.4 Ampere doubled per-SM dense throughput to 1,024 FP16 FMAs per clock with four larger tensor cores, and added 2:4 structured sparsity for another 2× on eligible weights.5
- Warp-group, asynchronous MMA (Hopper). MMA instructions span a warp group, execute asynchronously, can read operands directly from shared memory, and can reassign register capacity among warp groups. NVIDIA recommends reaching them through CUTLASS or cuBLAS rather than hand PTX.3
- MFMA (AMD CDNA). Wavefront-wide matrix instructions executed by matrix cores that run independently of the VALU, so vector work can overlap.9 CDNA 3 does 2,048 FP16 FLOPs per clock per CU in matrix form versus 256 in vector form; CDNA 4 doubles the matrix rate and adds MXFP6/MXFP4.78
Precision is part of the contract: inputs in FP16, BF16, FP8 or smaller, accumulation usually in FP32. Markidis et al. measured up to 83 TFLOPS on V100 tensor cores and showed that the error from half-precision inputs can be reduced with extra computation when an application needs it.18
The feeding problem
Faster math raises the bar for everything feeding it. Matrix throughput grew much faster than register and memory bandwidth, so each generation added data-movement machinery: asynchronous copies (Ampere), TMA and shared-memory operands (Hopper), larger LDS with direct loads (CDNA 4).568 The CDNA 3 whitepaper describes improved register-source caching so that each vector register read can feed more downstream vector or matrix operations.7
A 4 × 4 × 4 tile: 64 fused multiply-adds (FMAs). On ordinary lanes each one is its own instruction.
Many AI chips are built only for AI. GPUs are different. They can still run almost any program that splits into lots of similar pieces, from weather forecasts to video editing.
That has been a big advantage. When researchers invent a new kind of AI, they can write it for a GPU the same week, without waiting for a new chip. Over the years, a huge pile of ready-made GPU software has built up.19
The price is that part of the chip goes to being flexible instead of pure math. And writing truly fast GPU code is hard.
Programmers reach GPUs through on NVIDIA hardware and HIP on AMD hardware, two closely matching C++ extensions: write a kernel for one thread, launch it on a grid, use shared memory and barriers inside a block.110 Because blocks are independent, the same compiled program runs on a GPU with any number of cores.3
Most AI code never touches that level. It runs in layers:
- Frameworks (such as PyTorch or JAX) describe the model.
- Vendor libraries (cuBLAS and cuDNN on NVIDIA, the ROCm libraries on AMD) provide tuned matrix multiplies and convolutions.
- Templates and compilers such as CUTLASS (C++ templates for matrix multiplies) and Triton (a language and compiler built around tiles of data) let engineers write new kernels without hand-scheduling every instruction.1719
Triton’s authors give the reason this matters: operations that vendor libraries don’t cover risk poor hardware utilization unless experts hand-write them.19 General programmability lets the field try new ideas quickly; how close to peak those ideas run depends on software.
The programmability argument has three parts.
- A scalar-thread ISA. SIMT lets ordinary per-thread code, branches included, run on the device; divergence costs performance, not correctness.3 Independent thread scheduling extended that to fine-grained locking within a warp.4
- Scalable decomposition. Independent blocks let one binary scale across SM counts; Hopper’s clusters relax that slightly: a cluster’s blocks are guaranteed to be scheduled concurrently on a group of SMs, which is what makes distributed shared memory possible.6
- Libraries and compilers as the real interface. Peak tensor throughput increasingly requires architecture-specific features (asynchronous warp-group MMA, TMA descriptors) that NVIDIA recommends using through CUTLASS and the CUDA-X libraries.3 Tile-level compilers like Triton target the same hardware from a higher level and reach parity with cuBLAS and cuDNN on the kernels they evaluated.19
A sketch of the structure every fast GPU matrix multiply shares, simplified from CUTLASS’s hierarchy:
for each output tile (one thread block, e.g. 128x128):
acc[warp tile] = 0 # accumulators live in registers
prefetch A[k=0], B[k=0] -> shared buf 0 # async copy / TMA
for k in K-steps:
prefetch A[k+1], B[k+1] -> shared buf (k+1)%2
for each warp (e.g. 64x64 warp tile):
load fragments from shared buf k%2 -> registers
acc += MMA(fragA, fragB) # tensor / matrix cores
barrier
write acc -> global memory (fused epilogue: bias, activation)- 1L2Accumulators take at least half a thread’s registers, which caps occupancy.
- 2L3Double buffering: the next tile loads while the current one is multiplied.
- 3L7Shared memory → registers: reuse within the block cuts global traffic.
- 4L8The only line doing useful FLOPs; everything else exists to feed it.
Each level of the hierarchy is a reuse level: thread-block tiles in shared memory, warp tiles in registers, MMA fragments in the tensor core.17
Standard operations go to vendor libraries (cuBLAS and cuDNN on NVIDIA, the ROCm libraries on AMD), already tuned for the chip.
- Threads per warp: NVIDIA / AMD CDNA
- 32 / 64
- V100 latency: dependent math vs DRAM load
- 4 vs ≈ 375 cycles
- A100 peak: FP32 vs FP16 tensor (dense)
- 19.5 vs 312 TFLOPS
- Instruction overhead: FP16 FMA vs matrix op
- 2000% vs 22%
What these numbers mean:
- 32 / 64 is the size of a team that always does the same step together. NVIDIA uses 32, and AMD’s AI chips use 64.39
- 4 versus 375: a simple sum is ready in 4 clock ticks, but a trip to the big memory takes about 375. That’s why each core juggles many teams.12
- 19.5 versus 312: on one popular AI GPU, the tensor cores are 16 times faster than the ordinary math units.5
- 2000% versus 22%: for one small multiply, the paperwork costs twenty times the math. For a grid multiply, it adds only about a fifth.20
Two quick readings of the table. First, both chips have roughly 8–17× more matrix throughput than ordinary FP32 throughput, so software that can’t use the matrix units leaves most of the chip idle. Second, divide peak FP16 math by memory bandwidth: both chips can do roughly 250–330 operations for every byte they read from HBM. A computation that does fewer operations per byte than that is limited by memory, not math, the idea from What the workload needs. Comparing chips properly takes measured workloads, which is the subject of Comparing chips.
Ridge points from the table (dense FP16 FLOPs per HBM byte): H100 ; MI300X . Both are well above the intensity of batch-1 decode (each FP16 weight is read once per token for one multiply and one add: about 1 FLOP per byte), which is why decode is memory-bound on any GPU. The designs reach their numbers differently: NVIDIA with fewer, larger SMs and a large L2 on one die; AMD with more, smaller CUs across eight compute chiplets, a small L2 per die and a large memory-side cache on the I/O dies.67 Earlier anchors from the same documents: V100 had 80 SMs, 6 MB L2 and 900 GB/s4; A100 108 SMs, 40 MB and 1,555 GB/s.5 Measured rather than peak figures belong to Comparing chips, and how GPUs are joined into larger systems to Scale-up fabrics.
A GPU can run almost anything, but some of its energy goes to handling instructions and switching teams, not math. Chips built only for AI can squeeze out more.20
Programs full of “if this, do that” make teams take turns, which wastes time.3
The biggest limit is memory. The math units are so fast that keeping them fed is the hard part. A chatbot writing one word at a time spends most of its time waiting on memory.
And GPUs run hot. A top data-center GPU uses about 700 watts, as much as a microwave oven.6
| You get | You give up |
|---|---|
| Latency hiding through many resident warps | Huge register files and fewer registers per thread; too few warps and the core idles |
| Simple per-thread programming (SIMT) | Lost throughput whenever threads in a warp diverge |
| General-purpose cores | Energy spent on instruction fetch, decode and scheduling rather than math |
| Matrix units with 8–16× the throughput | Peak only when work is shaped as large, low-precision matrix multiplies |
| Fast on-chip shared memory | Small capacity that the programmer (or compiler) must manage explicitly |
Common ways GPU code goes wrong
- Uncoalesced access. Scattered reads waste most of every memory transaction.3
- Register pressure. One extra register can halve occupancy; capping registers can cause spills to slow memory.3
- Divergent branches inside hot loops.3
- Ignoring the matrix units, which leaves most of a modern AI GPU’s peak unused.5
- Generality tax. Work that can’t be expressed as matrix instructions pays the full per-instruction overhead: about 2,000% of the math energy for an FP16 FMA versus 22% for a matrix instruction (45 nm estimates).20 Systolic and dataflow designs remove more of that overhead and more of the flexibility.
- Occupancy versus per-thread state. Registers buy reuse (bigger GEMM tiles, higher arithmetic intensity) and cost warps. CUTLASS notes that accumulators alone take at least half the register budget, so GEMMs run at relatively low occupancy and must hide latency by pipelining instead.17
- Warp width. Wider warps (64) amortize issue and scheduling over more lanes but waste more on divergence and partial tails; AMD splits the difference by using wave64 on CDNA and wave32 on RDNA.9
- Scheduling policy. Fair round-robin makes warps hit long-latency loads together; greedy policies stagger them but can starve warps and disturb cache locality.1415
- Memory capacity per device. HBM capacity (80–192 GB in the table above) bounds model and KV-cache size per GPU, which pushes large models across many GPUs and onto the interconnect; see The memory wall and Why the network looks this way.
- Software coupling. Peak throughput depends on architecture-specific instructions reached through vendor libraries and templates; portability across vendors leans on compilers like Triton and on HIP’s close match to CUDA.31910
- Power density. 700–750 W per package drives liquid cooling and rack power design; see Power and cooling.67
Large low-precision matrix multiplies keep the tensor cores busy: the only work that can approach the 312 TFLOPS peak.
This part goes deeper, into the math, models and algorithms behind the chapter. It’s written for the Expert level.
1. A Little’s-law model of one scheduler
Model one scheduler issuing at most one instruction per cycle. Each warp loops over one load of latency followed by dependent math instructions of latency . A warp that is never kept waiting completes a loop in
so one warp sustains instructions per cycle. With warps and no contention, issue-slot utilization is
That is Little’s law with concurrency counted in warps: throughput (1 per cycle) × latency per instruction () = warps needed.11 Worked example with V100-like numbers (, )12: with , and , so about 46 warps per scheduler, nearly three times the 16 slots a quarter-SM has. With , and , so 16 warps suffice. This is the CUDA guide’s qualitative statement made quantitative: low arithmetic intensity needs more warps.3
Real kernels close the gap with memory-level parallelism. If each warp issues independent loads before using any of them, the load latency is paid once per loads: , , and the warps needed fall roughly -fold. Volkov’s central point is that occupancy and instruction-level parallelism are interchangeable sources of the same concurrency.11 The model also ignores bandwidth: once exceeds the memory system’s throughput, latency rises with load (queueing), and no number of warps helps. That is the roofline’s memory roof.11
P = 375 + 4·1·8 = 407 cycles, I = 9: ⌈P/I⌉ = 46 warps. With 16 slots, U = 35%.
2. Computing occupancy
For an NVIDIA SM with warp slots, register file , and shared memory :
- warps per block for threads per block3
- blocks by registers for registers per thread (allocation granularity ignored)
- blocks by shared memory for bytes per block
- blocks by slots , and the architecture’s block limit
Resident blocks are the minimum of these; occupancy is resident warps over . With , and , two blocks fit; at one fits.3 The sim applies the register rule per scheduler: 16,384 registers (the 64 KB per-partition file of GV1004) and 16 slots, so .
3. Divergence and lane efficiency
For a branch where a fraction of a warp’s threads take path ( instructions) and the rest take (), a mixed warp issues instructions while each thread needs only one path. Lane efficiency over the region is
which is 50% whenever the two paths have equal length, whatever the split. The sim’s divergence toggle is that case: issue slots fill faster ( math per load) but useful lane work halves, so utilization alone overstates performance. The reconvergence stack implements this: on divergence it pushes entries holding the reconvergence PC (the immediate post-dominator) and each path’s mask, executes the top entry until its PC reaches the reconvergence point, then pops.13 Fung et al.’s dynamic warp formation regrouped threads from different warps that took the same path, recovering 20.7% on average in their benchmarks for an estimated 4.7% area.13
4. Warp scheduling policies
Two baselines from the GPU-architecture literature15:
- Loose round-robin (LRR): warps take turns; if a warp cannot issue on its turn, the next ready one goes.
- Greedy-then-oldest (GTO): keep issuing from one warp until it stalls, then pick the oldest ready warp (lowest thread IDs among warps that arrived together).
LRR’s fairness has a cost: warps progress in lockstep, reach their long-latency loads together and leave no one to issue while they wait.14 In the sim, 16 warps with and should hide latency (), and GTO comes close; LRR leaves roughly 40% of slots empty because all sixteen warps stall in the same window. Narasiman et al.’s two-level scheduler splits warps into fetch groups, round-robin within a group, so groups reach their stalls at different times; combined with large warps it gained 19.1%.14 Greedy policies also preserve each warp’s cache locality, which Rogers et al. push further by reducing the number of warps allowed to issue when they would thrash the L1.15
5. GEMM at low occupancy
A tiled GEMM with an block tile and -step loads elements per step and does MACs, an intensity of MACs per element loaded: 64 for a tile. Larger tiles mean more accumulator registers per thread, which is why the accumulators take at least half of each thread’s register budget and why GEMMs run at relatively low occupancy.17 Latency is hidden instead by double buffering in shared memory and in registers: the next -step’s loads are in flight while the current one is multiplied, providing the memory-level parallelism of section 1 without extra warps.17 FlashAttention applies the same idea to attention, choosing tile sizes from the SRAM capacity to minimize HBM accesses.16
Q1On a recent NVIDIA GPU, simple math has about 4 cycles of latency and each SM has 4 warp schedulers. Roughly how many warps does the SM need to keep its schedulers busy with dependent math?
Q2A kernel uses more registers per thread. What usually happens to occupancy?
Q3Why do threads in a warp ideally read neighboring addresses in memory?
Q4On an A100, peak FP32 math (without tensor cores) is 19.5 TFLOPS and dense FP16 tensor-core math is 312 TFLOPS. What does that tell you about running AI on it?
Sources
Show Hide 20 sources
- Lecture 7: GPU Architecture and CUDA Programming (CS149: Parallel Computing)GPUs were built to accelerate 3D games; programmable shaders and ‘GPGPU’ around 2001–2003; Brook (2004); CUDA introduced in 2007 with NVIDIA’s Tesla architecture.
- ImageNet Classification with Deep Convolutional Neural NetworksA very efficient GPU implementation of convolutional nets; training took five to six days on two GTX 580 3 GB GPUs.
- CUDA C++ Programming Guide, v12.4 (sections 4, 5.2–5.3 and 16)SIMT architecture and warps of 32; divergence; hardware multithreading with on-chip contexts; latency hiding at the multiprocessor level (4-cycle arithmetic, 16 warps, hundreds of cycles off-chip); the 64-register example; coalescing; shared-memory banks; per-architecture SM resources.
- NVIDIA Tesla V100 GPU Architecture (whitepaper WP-08608-001_v1.1)SM split into four processing blocks, each with one warp scheduler and a 64 KB register file; tensor cores doing 64 FMAs per clock on 4×4 matrices; independent thread scheduling; compute-capability limits (64 warps and 65,536 registers per SM, 255 per thread).
- NVIDIA A100 Tensor Core GPU Architecture (whitepaper)108 SMs; 40 MB L2; 1,555 GB/s HBM2; 192 KB L1/shared per SM; tensor cores doing 256 FP16 FMAs per clock; 19.5 TFLOPS FP32 versus 312 TFLOPS FP16 tensor (dense); asynchronous copy into shared memory.
- NVIDIA Hopper Architecture In-Depth (NVIDIA Technical Blog)H100 SXM5 at announcement: 132 SMs, 50 MB L2, 80 GB HBM3 at over 3 TB/s, 256 KB register file and 256 KB of combined L1/shared memory per SM (up to 228 KB shared); Tensor Memory Accelerator with copy descriptors, thread block clusters guaranteed to be co-scheduled, distributed shared memory about 7× faster than exchanging through global memory. Peak rates, memory bandwidth and the 700 W TDP are marked preliminary.
- Introducing AMD CDNA 3 Architecture (whitepaper)MI300X: 8 XCDs, 304 CUs, 4 MB L2 per XCD, 256 MB Infinity Cache at 17.2 TB/s, 192 GB HBM3 at 5.3 TB/s, 64 KB LDS and 32 KB L1 per CU; 163.4 TFLOPS FP32 vector versus 1,307.4 TFLOPS FP16 matrix; 750 W.
- Introducing AMD CDNA 4 Architecture (whitepaper)The LDS grows from 64 KB (CDNA 3) to 160 KB per CU, with doubled read bandwidth, to feed matrix multiplication.
- Hardware implementation (HIP documentation)CDNA warps of 64 threads (32 on RDNA); four warp pools of up to ten warps (40 per CU); zero-overhead switching; four 16-lane SIMDs, so one 64-thread instruction takes four cycles; 256–512 KiB of VGPRs per CU; register pressure limits occupancy; MFMA matrix units as mini systolic arrays.
- Introduction to the HIP programming model (HIP documentation)CPU versus GPU design; a warp (HIP) is a wavefront in AMD ISA terms; divergent lanes are masked out and waste ALU cycles; each cycle one instruction from any resident wavefront can issue.
- Understanding Latency Hiding on GPUs (PhD thesis, UCB/EECS-2016-143)Little’s law for GPUs: concurrency = latency × throughput; Maxwell needs 24 arithmetic and about 30 memory instructions in flight per SM; concurrency counts instructions, not warps, so instruction-level parallelism substitutes for occupancy.
- Dissecting the NVIDIA Volta GPU Architecture via MicrobenchmarkingMeasured V100 latencies: 4-cycle FFMA, 19-cycle shared memory, 28-cycle L1 hit, ~193-cycle L2 hit, ~375-cycle DRAM (1,029 with a TLB miss); HBM2 750 of 900 GiB/s achieved.
- Dynamic Warp Formation and Scheduling for Efficient GPU Control FlowThe stack-based reconvergence mechanism at the immediate post-dominator; regrouping threads after divergence improves performance by 20.7% on average.
- Improving GPU Performance via Large Warps and Two-Level Warp SchedulingRound-robin warp scheduling tends to bring all warps to the same long-latency operation at the same time; two-level scheduling plus large warps improve performance by 19.1%.
- Cache-Conscious Wavefront SchedulingDefines loose round-robin (LRR) and greedy-then-oldest (GTO) warp schedulers and compares them in GPGPU-Sim.
- FlashAttention: Fast and Memory-Efficient Exact Attention with IO-AwarenessA100: 40–80 GB of HBM at 1.5–2.0 TB/s versus 192 KB of on-chip SRAM per SM at an estimated 19 TB/s; tiling attention to cut HBM traffic by up to 9×.
- Efficient GEMM in CUDA (CUTLASS documentation)Hierarchical tiling: thread-block tiles in shared memory, warp tiles in registers; double buffering to overlap loads and math; accumulators take at least half a thread’s registers, so GEMM runs at relatively low occupancy.
- NVIDIA Tensor Core Programmability, Performance & PrecisionThree ways to program tensor cores (WMMA, CUTLASS, cuBLAS); up to 83 TFLOPS mixed precision on V100; precision loss from half-precision inputs.
- Triton: An Intermediate Language and Compiler for Tiled Neural Network ComputationsOperations outside vendor libraries risk poor utilization unless experts write them; a tile-based language and compiler reaching parity with cuBLAS and cuDNN.
- Hardware for Deep Learning (Hot Chips 2023 keynote)Specialized instructions amortize a 30 pJ fetch/decode/operand overhead (2000% for an FP16 FMA, 22% for a matrix instruction); a 1000× decade of inference gains split into number formats (~16×), complex instructions (~12.5×), process (~2.5×) and sparsity (~2×); 5 pJ SRAM versus 640 pJ DRAM reads.