I used to think of the GPU as a black box: data goes in, tensor math happens, results come out. That mental model works until you need to debug why your kernel is hitting 40% of theoretical throughput instead of 80%. Then you need to understand the actual hardware. Here is the architecture from the bottom up, with a focus on why each piece matters for LLM inference.
Streaming Multiprocessors (SMs)
The SM is the fundamental compute unit of an NVIDIA GPU. Think of it as a small processor with its own register file, shared memory, and execution units. A GPU is made of many SMs running in parallel:
- H100 SXM: 132 SMs
- A100: 108 SMs
- B200: 160 SMs (estimated, full Blackwell die)
Each SM contains:
- CUDA cores: scalar floating-point and integer units. On Hopper, each SM has 128 FP32 CUDA cores.
- Tensor Cores: specialized matrix-multiply-accumulate units. These are the workhorses of LLM inference. Each Hopper SM has 4 fourth-generation Tensor Cores that can do 4x4 matrix multiplies in a single cycle.
- Register file: 256 KB per SM on Hopper. This is the fastest memory (zero-latency access), but it is private to each thread.
- Shared memory / L1 cache: 228 KB per SM on Hopper, configurable between shared memory and L1. This is programmer-visible fast memory shared across threads in a thread block. FlashAttention uses this extensively to tile attention computation and avoid HBM round-trips.
- Warp schedulers: each SM has 4 warp schedulers. A warp is 32 threads executing in lockstep. The scheduler can switch between warps to hide memory latency, issuing computation for one warp while another waits for data.
More SMs means more parallel compute capacity. But for memory-bound decode, the SM count barely matters. What matters is how fast data gets to the SMs. The compute units are fast enough; they are starved for data.
Tensor Cores: the matrix engines
Tensor Cores are the reason modern GPUs are so fast at neural network workloads. They perform fused matrix-multiply-accumulate (MMA) operations: D = A * B + C where A, B, C, D are small matrices (typically 16x16 or 16x8 tiles).
Each generation adds lower-precision support:
- Volta (V100): FP16 Tensor Cores. First generation.
- Ampere (A100): Added BF16, TF32, INT8, INT4 support.
- Hopper (H100): Added FP8 (E4M3, E5M2) support. FP8 delivers 2x the FLOPS of FP16.
- Blackwell (B200): Added FP4 (NVFP4) support. FP4 delivers 2x the FLOPS of FP8.
For LLM inference, Tensor Cores are fully utilized during prefill (large matrix multiplies) but underutilized during decode (small vector-matrix multiplies where the batch dimension is 1). This is the hardware manifestation of the compute-vs-bandwidth bottleneck from the roofline post.
HBM: High Bandwidth Memory
HBM is the main memory of the GPU. It is where model weights, KV cache, and activations live. HBM is built as stacked DRAM dies connected to the GPU through silicon interposers, providing much higher bandwidth than traditional GDDR memory:
- A100: 80 GB HBM2e, 2.0 TB/s bandwidth
- H100 SXM: 80 GB HBM3, 3.35 TB/s bandwidth
- H200: 141 GB HBM3e, 4.8 TB/s bandwidth
- B200: 192 GB HBM3e, 8.0 TB/s bandwidth
Two numbers define HBM's role in inference:
- Capacity determines how large a model you can serve. A 70B BF16 model needs 140 GB. On an H100 (80 GB), you need at least TP=2. On an H200 (141 GB), it fits on one GPU with a tiny KV cache budget.
- Bandwidth determines decode throughput. During decode, you load the full model weights for every token. At 3.35 TB/s on the H100, a 70B BF16 model (140 GB) takes ~42 ms per token on a single GPU. That is about 24 tokens per second, just from dividing bytes by bandwidth.
# Back-of-envelope decode speed
model_bytes = 70e9 * 2 # 70B params, 2 bytes each (BF16)
bandwidth = 3.35e12 # H100 HBM3 bandwidth in bytes/sec
time_per_token = model_bytes / bandwidth # ~0.042 seconds
tokens_per_second = 1 / time_per_token # ~24 tok/s (single sequence)
This is why the H200's jump to 4.8 TB/s and Blackwell's 8.0 TB/s matter so much. For memory-bound decode, bandwidth is throughput.
The cache hierarchy
Between HBM and the compute units sits a cache hierarchy that can dramatically affect performance:
- L2 cache: shared across all SMs. 50 MB on the H100. This caches HBM accesses. If your working set fits in L2, you avoid the HBM round-trip entirely. For small models or small batch sizes, L2 hits can significantly boost throughput.
- L1 cache / Shared memory: per-SM, 228 KB on Hopper. Used for tiling in kernels like FlashAttention. The programmer controls what goes here.
- Register file: per-SM, 256 KB. Fastest, smallest. Thread-private.
The bandwidth at each level is dramatically different:
- Register: effectively infinite (same-cycle access)
- Shared memory / L1: ~19 TB/s aggregate across all SMs on H100
- L2: ~12 TB/s on H100
- HBM: 3.35 TB/s on H100
The ratio between L2 bandwidth and HBM bandwidth (roughly 3.5x on H100) is why L2-resident KV caches are such a big deal. If your per-request KV cache fits in L2, attention computation is 3.5x faster than if it spills to HBM. This is one reason why GQA (grouped-query attention, which shrinks the KV cache) had such an outsized impact on inference performance.
NVLink and NVSwitch
For multi-GPU serving, the interconnect between GPUs matters as much as the GPUs themselves. NVLink is NVIDIA's proprietary high-bandwidth interconnect:
- H100 NVSwitch: 900 GB/s bidirectional per GPU (all-to-all within a node of 8 GPUs)
- B200 NVSwitch: 1.8 TB/s bidirectional per GPU
Compare this to PCIe Gen5 at 64 GB/s or InfiniBand at 50-100 GB/s. Tensor parallelism requires all-reduce operations after every layer. Over NVLink, an all-reduce on a 16 MB tensor takes microseconds. Over PCIe, it takes milliseconds. That difference, multiplied by 80 layers, is the difference between a fast model and a slow one.
The GPU is not a monolithic accelerator. It is a hierarchy of memories connected by buses of decreasing bandwidth, with compute units at the leaves. Understanding this hierarchy is understanding why certain optimizations work and others do not.
Tomorrow: how this architecture has evolved from Hopper through Blackwell to Rubin, and what each generation changed for inference.