Architectures · Chapter 5 of 16 · GPUs

SIMT and GPUs

A GPU is a chip with thousands of small math units that all follow the same steps on different numbers. It was built to draw video game graphics and turned out to suit AI too.

GPUs group threads into warps that run in lockstep. Each core keeps many warps ready, so while one waits on memory another runs. Dedicated matrix units now do most of the AI math.

SIMT execution, warp scheduling and occupancy, the register file and shared-memory hierarchy, matrix-multiply instructions on tensor units, and the programmability that keeps GPUs general purpose.

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.

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. 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. In 2012 AlexNet, a landmark image-recognition network, was trained in five to six days on two consumer GPUs.

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. 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. 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. 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.

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.

ControlMathRegistersshared cache (L2)Cache
Processor

GPU: many simpler cores, small caches and big register files, kept busy by thousands of threads. Tap a part.

Where a CPU and a GPU spend their transistors. Schematic, not to scale; a real GPU has around a hundred or more cores.Share freely with credit: ‘Figure from chipfieldguide.com’

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. 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.

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.

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. 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.

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. 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.

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). 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. 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. Each block is split into warps in thread-ID order, so control flow that depends on threadIdx / warpSize never diverges. 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. Ampere and Hopper keep the four-scheduler layout; each scheduler issues one instruction per cycle from its statically assigned warps. 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).

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. 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. 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. It also broke implicit warp-synchronous code, which must now use explicit __syncwarp() or warp-level primitives.

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. 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.

data xlane 031x = data[i]if (x < 0) y = -xelse y = 2*xout[i] = yfilled = active lane · dashed = masked off
Input data
1 / 6

A warp of 32 threads, each with its own x; 16 are negative (pink). Step through the instructions.

One warp running a branch. Pick the data and step through: on a split, each side runs in turn with the other lanes masked off.Share freely with credit: ‘Figure from chipfieldguide.com’

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.

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.

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.

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. 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. AMD describes the same thing for its compute units: zero-overhead, single-cycle switching between resident wavefronts. 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. 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. The more registers each thread uses, the fewer warps fit:

Registers per threadThreads that fit (65,536 ÷ registers)WarpsOccupancy
322,04864100%
641,0243250%
1285121625%
255 (the maximum)about 256812.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.

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 LL cycles takes 4L4L instructions in flight per SM on devices that issue one instruction per scheduler per cycle across four schedulers. For 4-cycle dependent math that is 16 warps per SM, fewer if each warp has independent instructions to issue back to back. 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.

Measured V100 latencies give a sense of the spread a scheduler must cover:

AccessLatency (cycles)
Dependent FFMA (register operands)4
Shared memory, no bank conflict19
L1 hit28
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. The register limit has cliffs at block granularity. The CUDA guide’s example: 512-thread blocks at 64 registers fit exactly two blocks (2×512×64=65,5362 \times 512 \times 64 = 65{,}536, so 32 warps); at 65 registers only one block fits and the SM drops to 16 warps. The compiler balances register count against spills; programmers steer it with __launch_bounds__ or -maxrregcount. On AMD CDNA the same pressure applies to vector registers (256–512 KiB per CU) and to the 10 warp slots per SIMD.

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. 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. Under the hood works through the model and its failure modes.

Warp slots (64)32 resident · 50%Register file (65,536 × 32-bit)each stripe = one warp: 32 threads × 64 = 2,048 registers

64 registers per thread: 32 warps resident (50% occupancy), limited by the register file.

One SM’s register file (65,536 registers, 64 warp slots). More registers per thread means fewer resident warps. Allocation rounding ignored.Share freely with credit: ‘Figure from chipfieldguide.com’

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 min⁡(16, ⌊16,384/(32R)⌋)\min(16,\ \lfloor 16{,}384 / (32R) \rfloor), 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. 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.

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.

Loading simulation…

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. 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.

  1. 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.
  2. 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. AMD’s equivalent is the local data share (LDS) plus a separate L1.
  3. L2 cache. Shared by all cores, tens of megabytes: 40 MB on an A100.
  4. Global memory. outside the GPU die, today stacked (HBM) in the same package: 40 GB at 1,555 GB/s on the original A100.

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.

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. 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.

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. The levels, with representative published numbers:

LevelScopeNVIDIA (A100 / H100)AMD (MI300X)
RegistersThread256 KB per SM256–512 KiB VGPR per CU (CDNA family)
Shared memory / LDS + L1Block / CU192 KB / 256 KB unified per SM; up to 164 / 228 KB shared64 KB LDS + 32 KB L1 per CU
L2Chip (or die)40 MB / 50 MB4 MB per XCD (38 CUs)
Memory-side cachePackagenone256 MB Infinity Cache, 17.2 TB/s
HBMPackage40 GB, 1.56 TB/s / 80 GB, over 3 TB/s192 GB, 5.3 TB/s

H100 figures are NVIDIA’s preliminary announcement specs. Sources: register and shared capacities, caches and HBM.

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.
  • Bank conflicts. Shared memory has 32 banks of 4 bytes; nn threads hitting different addresses in one bank serialize into nn requests (a broadcast of the same word is free). 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.
  • Achievable bandwidth. Microbenchmarks reached 750 of a theoretical 900 GiB/s on V100 HBM2 (83%). 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. 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. 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. Both are moves toward the explicitly managed, tile-oriented memory of dataflow designs.

GPU diein packagemathregisters256 KB/coreshared + L1192 KB/coreL2 cache40 MBHBM40 GB← faster, smaller, less energybigger, slower, more energy →Energy per useDRAM each use640 pJreuse 1×645 pJ

One HBM read, then 1 on-chip read: 645 pJ per use, versus 640 pJ fetching from DRAM every time.

Sizes from the A100. Energy from a 45 nm estimate: about 5 pJ per on-chip SRAM read, 640 pJ per DRAM read. The reuse model is illustrative.Share freely with credit: ‘Figure from chipfieldguide.com’
warp: 32 threadsmemory, in 32-byte sectors (8 words each)4 sectors = 128 B moved · 128 B used · 100%
Address pattern

Consecutive 4-byte words: the warp’s 32 reads coalesce into 4 sectors of 32 bytes. Every fetched byte is used.

One warp’s 32 loads of 4-byte words, served in 32-byte sectors. Neighboring addresses coalesce; scattered ones waste most of each sector.Share freely with credit: ‘Figure from chipfieldguide.com’

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.

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. 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.

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 D=A×B+CD = A \times B + C 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.
  • Ampere (2020): four larger tensor cores per SM, 256 FP16 multiply-adds per clock each, plus formats such as BF16, TF32 and INT8.
  • 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.

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. An MI300X does 163.4 TFLOPS of FP32 vector math and 1,307 TFLOPS of FP16 matrix math, 8× more. 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%. 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%. 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. 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 AA, BB and CC fragments in registers and issues an MMA; on Volta each tensor core performs 64 FP16 FMAs per clock, 512 per SM. 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.
  • 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.
  • MFMA (AMD CDNA). Wavefront-wide matrix instructions executed by matrix cores that run independently of the VALU, so vector work can overlap. 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.

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.

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). The CDNA 3 whitepaper describes improved register-source caching so that each vector register read can feed more downstream vector or matrix operations.

D = A × B + C (4 × 4)0 / 64 multiply-addsEnergy for these 64 multiply-addsordinary lanes · 0 instr · 0 pJtensor core · 0 instr · 0 pJmathoverhead: fetch, decode, operands
Unit

A 4 × 4 × 4 tile: 64 fused multiply-adds (FMAs). On ordinary lanes each one is its own instruction.

One 4 × 4 × 4 tile of matrix math as 64 single instructions or one matrix instruction. Energies are 45 nm estimates from Dally (Hot Chips 2023).Share freely with credit: ‘Figure from chipfieldguide.com’

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.

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. Because blocks are independent, the same compiled program runs on a GPU with any number of cores.

Most AI code never touches that level. It runs in layers:

  1. Frameworks (such as PyTorch or JAX) describe the model.
  2. Vendor libraries (cuBLAS and cuDNN on NVIDIA, the ROCm libraries on AMD) provide tuned matrix multiplies and convolutions.
  3. 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.

Triton’s authors give the reason this matters: operations that vendor libraries don’t cover risk poor hardware utilization unless experts hand-write them. 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. Independent thread scheduling extended that to fine-grained locking within a warp.
  • 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.
  • 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. 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.

A sketch of the structure every fast GPU matrix multiply shares, simplified from CUTLASS’s hierarchy:

gemm_structure.txt (illustrative)text
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)
  1. 1L2Accumulators take at least half a thread’s registers, which caps occupancy.
  2. 2L3Double buffering: the next tile loads while the current one is multiplied.
  3. 3L7Shared memory → registers: reuse within the block cuts global traffic.
  4. 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.

FrameworkPyTorch, JAXVendor librariescuBLAS, cuDNN, ROCmCompilersTriton, CUTLASSHand-written kernelCUDA or HIPGPU12 blocks1 round
Operation
Cores on the GPU

Standard operations go to vendor libraries (cuBLAS and cuDNN on NVIDIA, the ROCm libraries on AMD), already tuned for the chip.

How AI code reaches the GPU: frameworks on top, then libraries, compilers or hand-written kernels. The 12-block job and core counts are illustrative.Share freely with credit: ‘Figure from chipfieldguide.com’
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.
  • 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.
  • 19.5 versus 312: on one popular AI GPU, the tensor cores are 16 times faster than the ordinary math units.
  • 2000% versus 22%: for one small multiply, the paperwork costs twenty times the math. For a grid multiply, it adds only about a fifth.

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 ≈1,000 T/3 T≈330\approx 1{,}000\,\mathrm{T} / 3\,\mathrm{T} \approx 330; MI300X ≈1,307/5.3≈250\approx 1{,}307 / 5.3 \approx 250. 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. Earlier anchors from the same documents: V100 had 80 SMs, 6 MB L2 and 900 GB/s; A100 108 SMs, 40 MB and 1,555 GB/s. 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.

Programs full of “if this, do that” make teams take turns, which wastes time.

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.

You getYou give up
Latency hiding through many resident warpsHuge 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 coresEnergy spent on instruction fetch, decode and scheduling rather than math
Matrix units with 8–16× the throughputPeak only when work is shaped as large, low-precision matrix multiplies
Fast on-chip shared memorySmall 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.
  • Register pressure. One extra register can halve occupancy; capping registers can cause spills to slow memory.
  • Divergent branches inside hot loops.
  • Ignoring the matrix units, which leaves most of a modern AI GPU’s peak unused.
  • 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). 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.
  • 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.
  • Scheduling policy. Fair round-robin makes warps hit long-latency loads together; greedy policies stagger them but can starve warps and disturb cache locality.
  • 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.
  • Power density. 700–750 W per package drives liquid cooling and rack power design; see Power and cooling.
HBM1.56 TB/sGPU dietensor cores312 TFLOPS FP16the limit (good)FP32 lanesCeiling, share of peak≤ 312 TFLOPS · 100%0peak: 312 TFLOPS
Workload

Large low-precision matrix multiplies keep the tensor cores busy: the only work that can approach the 312 TFLOPS peak.

Upper bounds on an A100 from the chapter’s numbers (312 and 19.5 TFLOPS, 1,555 GB/s). Not measurements: real programs land below them.Share freely with credit: ‘Figure from chipfieldguide.com’

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 LL followed by CC dependent math instructions of latency AA. A warp that is never kept waiting completes a loop in

P=L+AC cycles,I=1+C instructions,\begin{aligned} P &= L + AC \ \text{cycles}, \\ I &= 1 + C \ \text{instructions}, \end{aligned}

so one warp sustains I/PI/P instructions per cycle. With NN warps and no contention, issue-slot utilization is

U=min⁡ ⁣(1, NIP),N≥⌈PI⌉for full utilization.\begin{aligned} U &= \min\!\left(1,\ \frac{N I}{P}\right), \\ N &\ge \left\lceil \frac{P}{I} \right\rceil \quad \text{for full utilization.} \end{aligned}

That is Little’s law with concurrency counted in warps: throughput (1 per cycle) × latency per instruction (P/IP/I) = warps needed. Worked example with V100-like numbers (L≈375L \approx 375, A=4A = 4): with C=8C = 8, P=407P = 407 and I=9I = 9, so about 46 warps per scheduler, nearly three times the 16 slots a quarter-SM has. With C=32C = 32, P=503P = 503 and I=33I = 33, so 16 warps suffice. This is the CUDA guide’s qualitative statement made quantitative: low arithmetic intensity needs more warps.

Real kernels close the gap with memory-level parallelism. If each warp issues kk independent loads before using any of them, the load latency is paid once per kk loads: P≈L+AkCP \approx L + A k C, I=k(1+C)I = k(1 + C), and the warps needed fall roughly kk-fold. Volkov’s central point is that occupancy and instruction-level parallelism are interchangeable sources of the same concurrency. The model also ignores bandwidth: once N⋅(bytes per load)/PN \cdot (\text{bytes per load}) / P exceeds the memory system’s throughput, latency rises with load (queueing), and no number of warps helps. That is the roofline’s memory roof.

One warp’s loopP = 407 cycles · I = 9 instructions1 load, then wait L8 math, 4 cycles apartWarps needed ⌈P/I⌉46 > 16 slots16 slots640Issue utilization with 16 warpsU = 35%

P = 375 + 4·1·8 = 407 cycles, I = 9: ⌈P/I⌉ = 46 warps. With 16 slots, U = 35%.

Little’s law for one scheduler issuing one instruction per cycle, with A = 4-cycle math and 16 warp slots. k independent loads per warp model memory-level parallelism. Bandwidth and queueing are ignored.Share freely with credit: ‘Figure from chipfieldguide.com’

2. Computing occupancy

For an NVIDIA SM with Wmax⁡W_{\max} warp slots, register file RSMR_{\mathrm{SM}}, and shared memory SSMS_{\mathrm{SM}}:

  • warps per block w=⌈T/32⌉w = \lceil T / 32 \rceil for TT threads per block
  • blocks by registers ⌊RSM/(Tr)⌋\lfloor R_{\mathrm{SM}} / (T r) \rfloor for rr registers per thread (allocation granularity ignored)
  • blocks by shared memory ⌊SSM/s⌋\lfloor S_{\mathrm{SM}} / s \rfloor for ss bytes per block
  • blocks by slots ⌊Wmax⁡/w⌋\lfloor W_{\max} / w \rfloor, and the architecture’s block limit

Resident blocks are the minimum of these; occupancy is resident warps over Wmax⁡W_{\max}. With RSM=65,536R_{\mathrm{SM}} = 65{,}536, T=512T = 512 and r=64r = 64, two blocks fit; at r=65r = 65 one fits. The sim applies the register rule per scheduler: 16,384 registers (the 64 KB per-partition file of GV100) and 16 slots, so Nmax⁡=min⁡(16, ⌊512/r⌋)N_{\max} = \min(16,\ \lfloor 512 / r \rfloor).

3. Divergence and lane efficiency

For a branch where a fraction pp of a warp’s threads take path XX (nXn_X instructions) and the rest take YY (nYn_Y), a mixed warp issues nX+nYn_X + n_Y instructions while each thread needs only one path. Lane efficiency over the region is

η=p nX+(1−p) nYnX+nY,\eta = \frac{p\, n_X + (1 - p)\, n_Y}{n_X + n_Y},

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 (2C2C 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. 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.

4. Warp scheduling policies

Two baselines from the GPU-architecture literature:

  • 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. In the sim, 16 warps with L=200L = 200 and C=16C = 16 should hide latency (⌈264/17⌉=16\lceil 264/17 \rceil = 16), 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%. 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.

5. GEMM at low occupancy

A tiled GEMM with an Mt×NtM_t \times N_t block tile and KK-step ktk_t loads (Mt+Nt) kt(M_t + N_t)\, k_t elements per step and does MtNtktM_t N_t k_t MACs, an intensity of MtNt/(Mt+Nt)M_t N_t / (M_t + N_t) MACs per element loaded: 64 for a 128×128128 \times 128 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. Latency is hidden instead by double buffering in shared memory and in registers: the next KK-step’s loads are in flight while the current one is multiplied, providing the memory-level parallelism of section 1 without extra warps. FlashAttention applies the same idea to attention, choosing tile sizes from the SRAM capacity to minimize HBM accesses.

Novice · 0 of 4 correct
  1. 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?

  2. Q2A kernel uses more registers per thread. What usually happens to occupancy?

  3. Q3Why do threads in a warp ideally read neighboring addresses in memory?

  4. 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
  1. Lecture 7: GPU Architecture and CUDA Programming (CS149: Parallel Computing)Kayvon Fatahalian and Kunle Olukotun · Stanford University · 2025GPUs were built to accelerate 3D games; programmable shaders and ‘GPGPU’ around 2001–2003; Brook (2004); CUDA introduced in 2007 with NVIDIA’s Tesla architecture.
  2. ImageNet Classification with Deep Convolutional Neural NetworksAlex Krizhevsky, Ilya Sutskever, Geoffrey E. Hinton · Advances in Neural Information Processing Systems 25 (NeurIPS) · 2012A very efficient GPU implementation of convolutional nets; training took five to six days on two GTX 580 3 GB GPUs.
  3. CUDA C++ Programming Guide, v12.4 (sections 4, 5.2–5.3 and 16)NVIDIA · NVIDIA CUDA Toolkit documentation archive · 2024SIMT 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.
  4. NVIDIA Tesla V100 GPU Architecture (whitepaper WP-08608-001_v1.1)NVIDIA · NVIDIA · 2017SM 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).
  5. NVIDIA A100 Tensor Core GPU Architecture (whitepaper)NVIDIA · NVIDIA · 2020108 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.
  6. NVIDIA Hopper Architecture In-Depth (NVIDIA Technical Blog)Michael Andersch, Greg Palmer, Ronny Krashinsky, Nick Stam, Vishal Mehta, Gonzalo Brito, Sridhar Ramaswamy · NVIDIA Developer Technical Blog · 2022H100 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.
  7. Introducing AMD CDNA 3 Architecture (whitepaper)AMD · AMD · 2023MI300X: 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.
  8. Introducing AMD CDNA 4 Architecture (whitepaper)AMD · AMD · 2025The LDS grows from 64 KB (CDNA 3) to 160 KB per CU, with doubled read bandwidth, to feed matrix multiplication.
  9. Hardware implementation (HIP documentation)AMD ROCm documentation · AMD ROCm (open-source project docs)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.
  10. Introduction to the HIP programming model (HIP documentation)AMD ROCm documentation · AMD ROCm (open-source project docs)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.
  11. Understanding Latency Hiding on GPUs (PhD thesis, UCB/EECS-2016-143)Vasily Volkov · University of California, Berkeley · 2016Little’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.
  12. Dissecting the NVIDIA Volta GPU Architecture via MicrobenchmarkingZhe Jia, Marco Maggioni, Benjamin Staiger, Daniele P. Scarpazza · arXiv:1804.06826 · 2018Measured 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.
  13. Dynamic Warp Formation and Scheduling for Efficient GPU Control FlowWilson W. L. Fung, Ivan Sham, George Yuan, Tor M. Aamodt · MICRO-40 (author copy, University of British Columbia) · 2007The stack-based reconvergence mechanism at the immediate post-dominator; regrouping threads after divergence improves performance by 20.7% on average.
  14. Improving GPU Performance via Large Warps and Two-Level Warp SchedulingVeynu Narasiman, Michael Shebanow, Chang Joo Lee, Rustam Miftakhutdinov, Onur Mutlu, Yale N. Patt · MICRO-44 (author copy, Carnegie Mellon University) · 2011Round-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%.
  15. Cache-Conscious Wavefront SchedulingTimothy G. Rogers, Mike O’Connor, Tor M. Aamodt · MICRO-45 (author copy, University of British Columbia) · 2012Defines loose round-robin (LRR) and greedy-then-oldest (GTO) warp schedulers and compares them in GPGPU-Sim.
  16. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-AwarenessTri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré · arXiv:2205.14135 (NeurIPS 2022) · 2022A100: 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×.
  17. Efficient GEMM in CUDA (CUTLASS documentation)NVIDIA CUTLASS project · GitHub (open-source project docs)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.
  18. NVIDIA Tensor Core Programmability, Performance & PrecisionStefano Markidis, Steven Wei Der Chien, Erwin Laure, Ivy Bo Peng, Jeffrey S. Vetter · arXiv:1803.04014 · 2018Three ways to program tensor cores (WMMA, CUTLASS, cuBLAS); up to 83 TFLOPS mixed precision on V100; precision loss from half-precision inputs.
  19. Triton: An Intermediate Language and Compiler for Tiled Neural Network ComputationsPhilippe Tillet, H. T. Kung, David Cox · MAPL ’19 (author copy, Harvard University) · 2019Operations outside vendor libraries risk poor utilization unless experts write them; a tile-based language and compiler reaching parity with cuBLAS and cuDNN.
  20. Hardware for Deep Learning (Hot Chips 2023 keynote)Bill Dally · Hot Chips 35 · 2023Specialized 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.