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.
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.
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.
| Tier | Scope | Typical size | Bandwidth | Latency |
|---|---|---|---|---|
| Registers | per thread (private) | 256 KB / SM file | ~20 TB/s aggregate | 0 cycles |
| Shared Memory + L1 | per thread block | ~256 KB / SM total | ~20 TB/s | ~30 cycles |
| L2 Cache | GPU-wide | 40–60 MB (up to 96 on AD102) | ~5 TB/s | ~200 cycles |
| HBM / GDDR (VRAM) | device global | 40–192 GB | 0.3–8 TB/s | ~400–700 cyc |
| Host RAM (C2C / PCIe) | system-wide | 128 GB — 1 TB+ | 0.05–0.6 TB/s | microseconds |
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.
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.
The register file is partitioned at kernel-launch time:
# 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
nvcc -Xptxas -v tells you how many regs/thread and how many spill bytes.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.
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.
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.
| Arch | Total unified L1+Shmem / SM | Max shared per block | Configurable splits |
|---|---|---|---|
| Volta (V100) | 128 KB | 96 KB | 0/32/64/96 KB shared |
| Ampere (A100) | 192 KB | 164 KB | 0/8/16/32/64/100/132/164 KB |
| Ampere (GA10x) | 128 KB | 100 KB | several |
| Ada (AD10x) | 128 KB | 100 KB | several |
| Hopper (H100/H200) | 256 KB | 228 KB | 0/8/16/32/64/100/132/164/196/228 KB |
| Blackwell (B200) | 256 KB | 228 KB | same 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.
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.
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.
| GPU | Arch | L2 size | Notes |
|---|---|---|---|
| P100 | Pascal | 4 MB | The 2016 baseline |
| V100 | Volta | 6 MB | Modest bump |
| A100 | Ampere | 40 MB | Huge jump — first time L2 mattered for kernel design |
| GA10x (3090) | Ampere consumer | 6 MB | Datacenter and consumer dies are very different here |
| AD102 (4090, RTX 6000 Ada) | Ada | 96 MB | Largest of its generation — graphics-driven; shaders love it |
| H100 | Hopper | 50 MB | Partitioned in two halves — one per HBM stack group |
| H200 | Hopper | 50 MB | Same die as H100, just bigger HBM3e |
| B200 | Blackwell | ~126 MB | Two-die package; L2 partitioned per die |
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.
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.
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 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.
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.
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.
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.
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.
cp.async.bulk.tensor instruction with the descriptor and a destination shared-memory address.mbarrier) signals when the new tile lands."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.
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 | Signalling | Per-pin | Cards | Aggregate BW |
|---|---|---|---|---|
| GDDR6 | NRZ (DDR-style) | 14–16 Gbps | L4, A6000 Ampere, 3060/3070 | 0.3–0.77 TB/s |
| GDDR6X | PAM4 (4-level) | 19–23 Gbps | 3090, 3090 Ti, 4080, 4090 | ~1.0 TB/s on 4090 |
| GDDR7 | PAM3 (3-level) | 28–32 Gbps | 5090, RTX PRO 6000 Blackwell | ~1.79 TB/s |
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.
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.
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.
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.
| Card | HBM gen | Capacity | BW | Stacks |
|---|---|---|---|---|
| P100 | HBM2 | 16 GB | 0.73 TB/s | 4 |
| V100 | HBM2 | 16/32 GB | 0.9 TB/s | 4 |
| A100 40 | HBM2 | 40 GB | 1.55 TB/s | 5 |
| A100 80 | HBM2e | 80 GB | 2.04 TB/s | 5 |
| H100 SXM5 | HBM3 | 80 GB | 3.35 TB/s | 5 |
| H200 | HBM3e | 141 GB | 4.8 TB/s | 6 |
| B100 / B200 | HBM3e | 192 GB | 8.0 TB/s | 8 |
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.
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.
| System | CPU LPDDR5x | GPU HBM | Total addressable |
|---|---|---|---|
| DGX Spark (GB10) | 128 GB unified | (unified, not separate) | 128 GB |
| GH200 480 | 480 GB | 96 GB HBM3 | 576 GB |
| GH200 624 | 480 GB | 144 GB HBM3e | 624 GB |
| GB200 (per superchip) | 480 GB (1× Grace) | 384 GB HBM3e (2× B200) | 864 GB |
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.
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.
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.
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.
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.
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
| Precision | Weight bytes | GPU | BW | Decode tok/s (est.) |
|---|---|---|---|---|
| FP16 | 140 GB | H200 | 4800 GB/s | ~34 tok/s |
| FP8 | 70 GB | H200 | 4800 GB/s | ~68 tok/s |
| FP8 | 70 GB | B200 | 8000 GB/s | ~114 tok/s |
| q4 (INT4) | 40 GB | 2× RTX 4090 (layer split; 40 GB won't fit one) | 1008 GB/s each | ~25 tok/s |
| FP8 | 70 GB | H100 | 3350 GB/s | ~48 tok/s |
| q4 | 40 GB | RTX PRO 6000 (96 GB; won't fit a 32 GB 5090) | 1792 GB/s | ~45 tok/s |
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.
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.
weight_bytes + kv_bytes_per_token × context_len. At long context, KV reads can equal or exceed weight reads.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.
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.