NVIDIA GPU Architectures Series — Presentation 04

Memory Hierarchy — Registers, Caches, HBM, GDDR, and Why Bandwidth Wins

Decode tok/s on a 70B model is bounded by memory bandwidth, not FLOPS. Walk through every level — registers, shared memory, L1/L2, HBM, GDDR, LPDDR — across NVIDIA generations, with numbers that tell you what your GPU can actually do.

RegistersSharedL1 L2HBM2eHBM3 HBM3eGDDR6XGDDR7 LPDDR5xTMA
Reg → Shmem → L1 → L2 → HBM → C2C → Host
00

Topics We'll Cover

Twelve slides on every level of the NVIDIA GPU memory hierarchy — how big each tier is, how fast, what it costs to miss it, and why the last level (HBM, GDDR, LPDDR) is the one that decides single-stream LLM tok/s.

01

The Hierarchy in One Picture

Five tiers, five orders of magnitude. The trade is the usual one: as you climb down the pyramid, capacity grows by 10× or more, but latency grows by 10× and bandwidth drops by 4–10×. Everything kernel authors do — tiling, double-buffering, async copies, tensor cores — is in service of keeping the working set as high in the pyramid as possible.

Memory pyramid — per-SM and GPU-wide tiers Registers 256 KB / SM ~20 TB/s aggregate · 0 cycle Shared Memory + L1 ~256 KB / SM ~20 TB/s · ~30 cycles L2 Cache (GPU-wide) 40–60 MB ~5 TB/s · ~200 cycles HBM / GDDR (VRAM) 40–192 GB 0.3–8 TB/s · ~400–700 cyc Host RAM — NVLink-C2C / PCIe 128 GB — 1 TB+ 0.05–0.6 TB/s · µs faster slower

The five tiers, summarised

TierScopeTypical sizeBandwidthLatency
Registersper thread (private)256 KB / SM file~20 TB/s aggregate0 cycles
Shared Memory + L1per thread block~256 KB / SM total~20 TB/s~30 cycles
L2 CacheGPU-wide40–60 MB (up to 96 on AD102)~5 TB/s~200 cycles
HBM / GDDR (VRAM)device global40–192 GB0.3–8 TB/s~400–700 cyc
Host RAM (C2C / PCIe)system-wide128 GB — 1 TB+0.05–0.6 TB/smicroseconds
The kernel author's mantra

Touch every byte of a tensor once from HBM, many times from L2, most from shared memory, and always from registers. A well-tiled GEMM hits HBM exactly (M·K + K·N + M·N) times even though the algorithm performs 2·M·N·K multiply-adds. The ratio between those two numbers is the arithmetic intensity, and it is what tensor cores need to keep fed.

02

Registers — The Real Bandwidth Story

Each SM has a 256 KB register file, split across four schedulers (partitions) of 64 KB each. That is — per chip — the largest, fastest, and most-bandwidth memory you have. An H100 with 132 SMs carries roughly 33 MB of register file on chip, two-thirds the size of its 50 MB L2.

Register pressure and occupancy

The register file is partitioned at kernel-launch time:

occupancy arithmetic
# Active threads per SM, given regs/thread
threads_per_SM = floor( 65536 regs / regs_per_thread )

# 65536 = 256 KB / 4 bytes per register, per partition × 4 partitions
# A warp = 32 threads. Active warps = threads_per_SM / 32.

# Examples
regs_per_thread = 32  → 2048 threads → 64 warps  # max occupancy on most arches
regs_per_thread = 64  → 1024 threads → 32 warps  # half-occupancy
regs_per_thread = 128 → 512  threads → 16 warps  # quarter
regs_per_thread = 255 → 256  threads → 8  warps  # tensor-core kernel

High-occupancy vs tensor-core kernels

Why this dwarfs everything else

Aggregate register bandwidth on a Hopper SM is roughly ~20 TB/s — the file is read every cycle by every active warp. Across 132 SMs, that is multi-petabyte/s of in-chip movement. HBM3, by contrast, is 3.35 TB/s. The register file is the only thing on the chip that can keep tensor cores busy; HBM only has to top up the tile.

Reading the ptxas verbose output

$ nvcc -Xptxas -v -arch=sm_90 my_kernel.cu
ptxas info    : 132 bytes gmem
ptxas info    : Compiling entry function '_Z6my_gemmPfS_S_' for 'sm_90'
ptxas info    : Function properties for _Z6my_gemmPfS_S_
                0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info    : Used 168 registers, 8704 bytes smem, 408 bytes cmem[0]
                                ^^^^                ^^^^
                          regs/thread        shared memory/block

168 regs/thread × 32 threads/warp = 5376 regs/warp. With four schedulers' worth of 16384 regs each (65536 total per SM), this kernel can host 65536 / 5376 ≈ 12 warps per SM — well below the 64-warp ceiling. That is fine for a tensor-core kernel where each warp is doing a 64×64×16 wgmma.async; bad if you intended a memory-bound kernel.

03

Shared Memory + L1 — Configurable Per Kernel

Pre-Volta, shared memory and L1 cache were separate SRAMs in the SM. Volta unified them into a single block of fast on-chip storage that the kernel can configure at launch time: how much for shared, how much for L1.

Sizes per generation

ArchTotal unified L1+Shmem / SMMax shared per blockConfigurable splits
Volta (V100)128 KB96 KB0/32/64/96 KB shared
Ampere (A100)192 KB164 KB0/8/16/32/64/100/132/164 KB
Ampere (GA10x)128 KB100 KBseveral
Ada (AD10x)128 KB100 KBseveral
Hopper (H100/H200)256 KB228 KB0/8/16/32/64/100/132/164/196/228 KB
Blackwell (B200)256 KB228 KBsame as Hopper

So a Hopper SM with the largest split is L1 = 28 KB, Shmem = 228 KB; tile-heavy kernels run that way. A graphics-style kernel that benefits from caching reads will pick a bigger L1 instead.

Bank conflicts — the one footgun

Shared memory is split into 32 banks of 4 bytes. A warp issues 32 lanes per cycle; if all 32 lanes hit different banks, the access completes in one cycle. If two lanes hit the same bank at different addresses, the access serialises — a 2-way conflict costs 2×, a 32-way conflict costs 32×. Same address on the same bank is fine: it is broadcast.

Conflict-free

  • 32 lanes × 4-byte stride 1 (consecutive)
  • Broadcasts (all lanes same address)
  • Padded tile widths (e.g. 33 instead of 32)

Conflict-prone

  • Column reads from a 32-wide tile
  • Power-of-two stride access (2, 4, 8, 16)
  • Naive transpose without padding

Common uses

04

L2 Cache — The GPU-Wide Backstop

L2 sits between the SMs and the memory controllers. Every HBM/GDDR transaction traverses L2; everything an SM evicts from its L1/shmem ends up there. Hits cost ~200 cycles instead of ~500–700 for an HBM round trip, and the bandwidth is roughly an order of magnitude higher.

L2 size by family

GPUArchL2 sizeNotes
P100Pascal4 MBThe 2016 baseline
V100Volta6 MBModest bump
A100Ampere40 MBHuge jump — first time L2 mattered for kernel design
GA10x (3090)Ampere consumer6 MBDatacenter and consumer dies are very different here
AD102 (4090, RTX 6000 Ada)Ada96 MBLargest of its generation — graphics-driven; shaders love it
H100Hopper50 MBPartitioned in two halves — one per HBM stack group
H200Hopper50 MBSame die as H100, just bigger HBM3e
B200Blackwell~126 MBTwo-die package; L2 partitioned per die

Why it matters — per workload

Graphics / shaders (Ada)

Working set per frame is a few tens of MB — G-buffers, shadow maps, textures. AD102's 96 MB L2 swallows almost the entire hot working set. The consumer 4090 looks "too good" in benchmarks partly because of this.

LLM weight streaming

A 70B FP16 model is 140 GB; even FP8 is 70 GB. Nothing caches a meaningful fraction of weights in L2. Decode reads every weight once per token from HBM/GDDR — L2 just buffers the next-tile prefetch.

HPC stencils / sparse

Reuse pattern is medium — halos and ghost cells fit in L2, halos do not. A100's 40 MB and Hopper's 50 MB were specifically sized for stencil kernels.

Attention KV cache

Attention reads grow linearly with context length. At long context the KV cache no longer fits in L2; bandwidth becomes the limit. Hence Hopper's TMA + FlashAttention, designed to bypass L2 inefficiency.

Ada's curiously large L2

96 MB on AD102 was a graphics-driven decision, similar in spirit to AMD's Infinity Cache. It does not help LLM weight streaming much — models are too large — but it does help activation reuse and shader workloads. It is one of the reasons the 4090 punches above its memory-bandwidth weight on game and rendering benchmarks.

L2 residency control (Ampere+)

Ampere introduced explicit hints for L2: a kernel can mark a buffer as cudaAccessPropertyPersisting so the cache prefers to keep it resident across kernel launches. Useful for KV caches and constant tables that get touched by every kernel in a pipeline.

marking a buffer as persisting in L2
cudaStreamAttrValue attr = {};
attr.accessPolicyWindow.base_ptr  = kv_cache_ptr;
attr.accessPolicyWindow.num_bytes = 32 * 1024 * 1024;  // 32 MB window
attr.accessPolicyWindow.hitRatio  = 1.0f;
attr.accessPolicyWindow.hitProp   = cudaAccessPropertyPersisting;
attr.accessPolicyWindow.missProp  = cudaAccessPropertyStreaming;
cudaStreamSetAttribute(stream, cudaStreamAttributeAccessPolicyWindow, &attr);

The driver sets aside a fraction of L2 (configurable via cudaDeviceSetLimit(cudaLimitPersistingL2CacheSize, ...)) for the persisting set; the rest serves normal streaming traffic. Get this wrong and you starve the streaming path; get it right on a workload that actually has hot reuse and you can save ~10–20% bandwidth.

05

TMA — Tensor Memory Accelerator (Hopper+)

The single biggest memory-system change since unified shmem. Pre-Hopper, every thread that wanted to bring data from HBM into shared memory issued its own load instruction. A 128×128 BF16 tile is 32 KB, 16,384 elements — a warp of 32 threads needs 512 instructions just to issue the loads, plus address arithmetic, plus the bookkeeping to coalesce.

What TMA actually does

SM kernel: build tile descriptor (shape, stride, swizzle)
↓
cp.async.bulk.tensor.shared::cluster.global — one instruction
↓
TMA engine: walk HBM, swizzle, write to shmem
↓
mbarrier signals — SM consumes tile, issues next descriptor

Why it is a big deal for LLMs

FlashAttention-3 in one sentence

"Use TMA to async-load Q, K, V tiles into shmem; use the tensor cores to compute softmax-and-PV in a register accumulator; never write the attention matrix back to HBM." That single sentence is a 1.5–2× speedup over FA-2 on H100 for long contexts — entirely from memory-system features, not from new arithmetic.

06

GDDR — The Consumer Memory

GDDR is the descendant of DDR for graphics: faster signalling per pin, slightly looser latency, much wider point-to-point buses. It lives on the PCB next to the GPU die, soldered on a 256- or 384-bit bus.

Generation by generation

GenerationSignallingPer-pinCardsAggregate BW
GDDR6NRZ (DDR-style)14–16 GbpsL4, A6000 Ampere, 3060/30700.3–0.77 TB/s
GDDR6XPAM4 (4-level)19–23 Gbps3090, 3090 Ti, 4080, 4090~1.0 TB/s on 4090
GDDR7PAM3 (3-level)28–32 Gbps5090, RTX PRO 6000 Blackwell~1.79 TB/s

Signalling, briefly

Why GDDR is not "cheap HBM"

GDDR is cheap per gigabyte and moves fast on a per-pin basis, but its bus is narrow compared to HBM (a 384-bit GDDR card vs a 1024-bit-per-stack HBM × 5–8 stacks). It also has practical capacity ceilings: 24 GB on a 4090, 32 GB on a 5090, 96 GB on the RTX PRO 6000 Blackwell — and that 96 GB is achieved with clamshell (memory chips on both sides of the PCB), not stacking. There is no 3D-stacked GDDR.

Bandwidth maths

Aggregate bandwidth = bus_width × per-pin_rate / 8. The 4090's GDDR6X runs at 21 Gbps on a 384-bit bus → 384 × 21e9 / 8 = ~1008 GB/s. The 5090's GDDR7 runs at 28 Gbps on a 512-bit bus → 512 × 28e9 / 8 = ~1792 GB/s. Two levers, neither of them changes lightly: bus width is set by the die's memory-PHY count, per-pin rate is set by the signalling tech.

What ECC costs you on consumer GDDR

Datacenter cards (A100, H100, L40S, RTX 6000 Ada) have ECC; the consumer 4090/5090 do not. On HBM, ECC is "side-band" — extra dies in the stack store the ECC bits without consuming user bandwidth. On GDDR, ECC is "in-line" — ECC consumes ~6.25% of the user bandwidth and capacity, which is why Quadro / RTX 6000 GDDR cards advertise slightly less peak BW than their consumer twins running at the same per-pin rate.

07

HBM — The Datacenter Memory

HBM (High-Bandwidth Memory) is 3D-stacked DRAM — 8 to 12 dies stacked vertically, connected through the stack with through-silicon vias (TSVs), and bonded to a base logic die. The whole stack sits on a silicon interposer right next to the GPU die, usually under the same heatspreader.

PCB
↓
Silicon interposer (passive Si bridge with thousands of fine traces)
↓
[GPU die]   [HBM stack 1]   [HBM stack 2]   ...   [HBM stack 8]
↓
Each stack: 8–12 DRAM dies + 1 logic die, connected through TSVs — 1024+ bits wide

The trade compared to GDDR

HBM in the wild

CardHBM genCapacityBWStacks
P100HBM216 GB0.73 TB/s4
V100HBM216/32 GB0.9 TB/s4
A100 40HBM240 GB1.55 TB/s5
A100 80HBM2e80 GB2.04 TB/s5
H100 SXM5HBM380 GB3.35 TB/s5
H200HBM3e141 GB4.8 TB/s6
B100 / B200HBM3e192 GB8.0 TB/s8
Why HBM is a supply-chain story

Only three vendors make HBM at scale (SK hynix, Samsung, Micron) and only two assembly houses (TSMC CoWoS and a smaller fraction at Intel) can integrate it onto a GPU. HBM and CoWoS capacity, not GPU dies, are the throttle on H100/H200/B200 supply. This is also why GDDR is not going away — consumer and prosumer cards cannot afford to compete with datacenter for the HBM line.

08

LPDDR5x + Unified Memory (Grace Systems)

The third memory tier in the modern lineup is the one most people miss. NVIDIA's Grace CPU pairs with Hopper or Blackwell GPUs over NVLink-C2C, a 900 GB/s coherent chip-to-chip link, and lets the GPU treat the CPU's LPDDR5x as EGM — Extended GPU Memory.

Grace CPU
↔
NVLink-C2C 900 GB/s coherent
↔
Hopper / Blackwell GPU
LPDDR5x ~480 GB/s · 480–960 GB
 
HBM3 / HBM3e 3.4–8 TB/s · 80–192 GB

What this gives you

Where it appears

SystemCPU LPDDR5xGPU HBMTotal addressable
DGX Spark (GB10)128 GB unified(unified, not separate)128 GB
GH200 480480 GB96 GB HBM3576 GB
GH200 624480 GB144 GB HBM3e624 GB
GB200 (per superchip)480 GB (1× Grace)384 GB HBM3e (2× B200)864 GB
DGX Spark is the odd one out

Spark uses the GB10 SoC: a single SoC where Grace CPU and Blackwell GPU share the same 128 GB LPDDR5x pool — there is no HBM; inside the package the CPU and GPU dies are joined by NVLink-C2C. Bandwidth is modest (~273 GB/s), which makes 70B q4 decode land around 5–7 tok/s (ceiling 273 ÷ ~40 GB ≈ 7) — slower than a 4090, but with 128 GB of capacity you can run things that physically do not fit on a 4090. Different point on the curve, same architecture lineage.

09

Bandwidth Ladder Visualised

Memory bandwidth from low to high, across every memory technology covered. The shape of this chart is the shape of single-stream tok/s on a fixed model size.

Memory bandwidth per GPU — GB/s, higher is better LPDDR5x (Spark) 273 GB/s GDDR6 (L4) 300 GB/s GDDR6 (A6000) 768 GB/s GDDR6X (4090) 1008 GB/s GDDR7 (5090) 1792 GB/s HBM2e (A100 80) 2039 GB/s HBM3 (H100 SXM) 3350 GB/s HBM3e (H200) 4800 GB/s HBM3e (B200) 8000 GB/s 0 2000 4000 6000 8000
Reading this chart

The ratio between top and bottom is ~30×. That is roughly the ratio of single-stream decode tok/s between a B200 and a Spark on the same 70B FP8 model. There is no software in the world that closes that gap — bandwidth is a hardware fact.

Same shape, different axis

If you replace the x-axis with "single-stream 70B FP8 decode tok/s" you get almost the same shape, divided by 70 (since FP8 is 1 byte/param and 70B is 70 GB). That is the entire content of the next slide.

10

What Bandwidth Buys You — The Decode-Bound Insight

Autoregressive decoding generates one token at a time. To produce that token, the model reads every weight, exactly once, multiplies by the previous activation, accumulates, and writes the next token. The arithmetic intensity of decode is ~1 (one multiply-add per byte read). That is firmly memory-bound on every modern GPU.

the only formula you need for single-stream decode
tok/s ≈ min( bandwidth / weight_bytes,  compute_limit )

# For a decode-bound model (compute_limit very high), the right term drops:
tok/s ≈ bandwidth / weight_bytes

# weight_bytes = num_params × bytes_per_param
#   FP16/BF16 → 2.0 bytes/param
#   FP8       → 1.0 bytes/param
#   INT4 / q4 → 0.5 bytes/param
#   MX-FP4    → 0.5 bytes/param + tiny scale overhead

Worked examples — 70B model

PrecisionWeight bytesGPUBWDecode tok/s (est.)
FP16140 GBH2004800 GB/s~34 tok/s
FP870 GBH2004800 GB/s~68 tok/s
FP870 GBB2008000 GB/s~114 tok/s
q4 (INT4)40 GB2× RTX 4090 (layer split; 40 GB won't fit one)1008 GB/s each~25 tok/s
FP870 GBH1003350 GB/s~48 tok/s
q440 GBRTX PRO 6000 (96 GB; won't fit a 32 GB 5090)1792 GB/s~45 tok/s

Where compute starts to matter

The single sentence to take away

For single-stream decode, bandwidth wins. For prefill and big-batch serving, FLOPS win. Real serving systems are an interleave of both, which is why Hopper-class HBM3/HBM3e cards dominate datacenter LLM serving — they are good at both.

The KV cache, the other big bandwidth consumer

So far we have only counted weight reads. There is a second per-token cost: reading the KV cache — the past keys and values for every token in the context window. Per layer, per head, this is 2 × context_len × head_dim × bytes_per_elem. For a 70B model with 64 layers, 8 KV heads, 128 head_dim, BF16, 4 K context: 2 × 4096 × 1024 × 2 × 64 = ~1 GB per stream. At 32 K context that becomes 8 GB.

11

Interactive: Decode-Speed Estimator

Pick a model size, a precision, and a GPU. The estimator computes the weight bytes, checks they fit in VRAM, and applies tok/s ≈ bandwidth / weight_bytes.

Weight bytes
—
Fits in VRAM
—
GPU bandwidth
—
Decode tok/s (est.)
—

Prefill is FLOPS-bound and behaves very differently — a 4 K-token prompt on a 70B FP8 model exercises the tensor cores, not the memory bus, and a B200 will out-prefill an H200 by roughly the FLOPS ratio (~2×) regardless of the bandwidth gap. Big-batch serving is similar: at batch 32+, weight reads are amortised across streams and the workload re-enters compute-bound territory. The estimator below is for the single-stream decode case where bandwidth dominates.