← LLM Deployment from First PrinciplesModule 3 of 9

GPU Architecture.

You already reason fluently about CPUs, caches and NUMA — this module rebuilds that intuition for the GPU. The throughline from carries the whole way through: an LLM decode step is , not compute-bound. So almost every architectural choice NVIDIA makes — , , , FP8 — exists to feed the math units fast enough. We trace the execution model, what lives inside a , the memory hierarchy from registers to to host, the interconnects that gate multi-GPU serving, and finish with a 4090 / H100 / B200 / GB200 comparison read through an inference engineer's eyes.

HardwareCUDAHBM

32

threads / warp

the SIMT scheduling unit

~3.35 TB/s

H100 HBM3

sets the decode ceiling

~10×

bandwidth drop / level

registers → HBM → PCIe

~900 GB/s

NVLink 4

vs ~64 GB/s PCIe 5

The CUDA execution model

A CPU is latency-optimized: a few fat cores, deep pipelines, big out-of-order windows and huge caches, all to make one thread finish as fast as possible. A GPU is the opposite trade — throughput-optimized: thousands of small, in-order lanes that hide latency by keeping enormous numbers of threads in flight and switching between them for free. Everything your instinct learned tuning a JVM inverts here.

exposes a nested hierarchy. You write a kernel — a function run by many threads in parallel — and launch it over a grid of thread blocks. A thread has private registers and an index; a warp is exactly 32 threads that execute in lock-step (the true hardware scheduling unit — treat 32 as a hard constant); a block is up to 1024 threads guaranteed to run on a single , able to cooperate via shared memory and barriers; a grid is all the blocks of one launch, independent and free to run in any order on any SM.

SIMT (Single Instruction, Multiple Threads) is the model: the 32 threads of a warp share instruction issue and each runs the same instruction on different data. If threads in a warp take different branches — if (threadIdx.x % 2) — the warp diverges: the hardware runs each path serially with the other lanes masked off, throttling you to a fraction of peak. It is the GPU analog of a mispredicted branch, except the cost is structural, not statistical.

One systems fact to internalize: kernel launches are asynchronous (like submitting to an executor and getting a future) and cost ~5–20 µs each — and a real transformer forward pass is thousands of launches. That overhead is why CUDA Graphs (capture the whole sequence once, replay it) matter for , where each step is tiny.

// CUDA kernel: one thread per output element, C = A + B
__global__ void vadd(const float* A, const float* B, float* C, int n) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;  // global thread index
    if (i < n)                                       // guard the tail
        C[i] = A[i] + B[i];
}

// Host launch: grid of ceil(n/256) blocks, 256 threads each
int threads = 256;
int blocks  = (n + threads - 1) / threads;
vadd<<<blocks, threads>>>(A, B, C, n);   // <<<grid, block>>> = launch config
cudaDeviceSynchronize();                 // host waits for the grid to finish

Inside the Streaming Multiprocessor

The is the GPU's core — the rough analog of a CPU core, except one SM runs dozens of warps concurrently. An H100 (GH100) has 132 active SMs; an RTX 4090 (AD102) has 128. Every block of your grid is distributed across them by a hardware block scheduler (the 'GigaThread engine').

A modern SM is partitioned into 4 sub-partitions, each with its own warp scheduler, a slice of the register file, CUDA cores (scalar ALUs for the elementwise work — activations, normalization, residual adds, softmax) and one Tensor Core. Each cycle a scheduler picks a warp whose operands are ready and issues an instruction; when one warp stalls on a memory load it simply issues from another. That warp-swapping is latency hiding — the entire reason a GPU tolerates ~500 ns HBM latency without dying.

Two on-chip resources shape performance. The register file (~256 KB / SM) is per-thread private state, partitioned across all resident threads — more registers per thread means fewer resident warps. Shared memory / L1 (up to ~228 KB / SM on Hopper) is a software-managed scratchpad that makes tiled matmul and FlashAttention fast. Hopper adds the TMA (Tensor Memory Accelerator) to asynchronously copy large tiles into shared memory, keeping the Tensor Cores fed.

Occupancy = resident warps ÷ the SM's maximum — a proxy for how much latency-hiding parallelism you gave the scheduler. It is limited by whichever resource runs out first (registers/thread, shared mem/block, threads/block). The nuance every optimizer learns: higher occupancy is not always faster. A tiled GEMM or FlashAttention kernel deliberately burns registers and shared memory for data reuse, running at modest occupancy but near-peak throughput. You want enough warps to hide latency, not the maximum.

Inside one SM — click a component

A Hopper-class SM: 4 sub-partitions (one shown) plus shared/L1 and the TMA.

sub-partition × 4Warp Scheduler + DispatchRegister File (~16K × 32-bit / sub-part)32× FP32CUDA cores1× Tensor Core(4th gen)SFU / LD-ST unitsTensor MemoryAccelerator (TMA)L1 Data Cache / Shared Memory (unified, up to ~228 KB usable / SM)

1× Tensor Core (4th gen)

The matrix engine: one per sub-partition, four per SM. It executes the GEMMs (QKV, attention scores, output projection, FFN) that dominate LLM compute — matrix-multiply-accumulate on tiles, ~10–30× the CUDA-core throughput.

An H100 has 132 such SMs; an RTX 4090 has 128. Blocks of your grid are distributed across all of them.

Tensor Cores

If you remember one thing about why LLMs run on GPUs specifically, it is . A Tensor Core performs a matrix-multiply-accumulate (MMA) on small tiles in a single hardware operation — — versus a CUDA core doing one scalar fused multiply-add at a time.

A transformer is, at the FLOP level, a stack of large matrix multiplications: the QKV projections, the attention-score matmul, the output projection and the big FFN/MLP GEMMs. Tensor Cores execute these an order of magnitude faster per watt because MMA amortizes instruction overhead and data movement across a whole tile — on a modern datacenter GPU, ~10–30× the FP32 CUDA-core throughput. This is the reason LLM training and are feasible.

Tensor Cores accept progressively narrower inputs, and narrower = more throughput and less memory traffic: TF32 → FP16/BF16 → FP8 → FP4, roughly doubling throughput at each step. BF16 keeps FP32's 8-bit exponent (wide dynamic range, fewer overflow headaches) at the cost of mantissa bits, which is why it became the default LLM dtype. The accumulation is done in higher precision (typically FP32) internally, so you get narrow-input speed with acceptable error — the core trick, and the hardware side of .

Each generation added a format: Volta (V100) introduced Tensor Cores (FP16); Turing added INT8/INT4; Ampere (A100) added TF32, BF16 and sparsity; Hopper (H100) added FP8 and the Transformer Engine; Blackwell (B200/GB200) added FP4. The Transformer Engine dynamically picks per-layer precision and manages FP8 scaling factors automatically — a big part of why H100 crushed A100 on transformer workloads beyond raw spec deltas.

The memory hierarchy

This is the section that matters most for inference. is : each generated token requires reading the entire model's weights (and the growing ) from while doing comparatively little math. Your token/sec ceiling is set by HBM bandwidth, not FLOPs — the argument from Module 1.

Read the bandwidth column of the hierarchy top to bottom and you fall off a cliff at every level: on-chip SRAM (registers/shared) is ~10× the bandwidth of HBM, and HBM is ~50× the bandwidth of PCIe to host. The whole game of a fast kernel is keep data as high up this pyramid as possible and touch HBM as few times as you can — which is literally what FlashAttention does, tiling attention so the score matrix never round-trips to HBM.

(High Bandwidth Memory) is DRAM dies stacked vertically and wired to the GPU through a silicon interposer with an extremely wide bus (thousands of bits) rather than the narrow-fast bus of GDDR. Wide-and-moderate-clock beats narrow-and-fast for aggregate bandwidth and energy per bit. Generations climb steeply: HBM2e (~2.0 TB/s, A100) → HBM3 (~3.35 TB/s, H100 SXM) → HBM3e (~4.8 TB/s H200, ~8 TB/s B200).

Why this dominates decode: emitting one token from a 70B FP16 model streams ~140 GB of weights through the math units, so . Double the bandwidth (H100 → B200) and single-stream decode roughly doubles — without any FLOP increase mattering. The RTX 4090's GDDR6X (24 GB at ~1.0 TB/s) is excellent value for a 7B–13B model but ~3× less bandwidth than H100. The roofline below makes the memory-bound reality visual.

The memory hierarchy — click a level

Bandwidth falls off a cliff at every step down. Bar width is illustrative (log-ish).

Registersper-thread (per-SM file)~tens of TB/sShared / L1per-block (per SM)~10s of TB/s / SML2 cachewhole GPU (shared by SMs)~several TB/s aggr.HBM (device DRAM)whole GPU — the 'VRAM'~3.35 TB/sHost RAM (over PCIe)CPU DRAM, system memoryPCIe 5: ~64 GB/s↑ faster / smaller / on-chip  ·  slower / larger / off-chip ↓

HBM (device DRAM)

size

80 GB (H100 SXM)

bandwidth

~3.35 TB/s

latency

~400–600 ns

The stacked DRAM that holds model weights and the KV cache. Because decode must stream every weight from here once per token, HBM bandwidth — not FLOPs — sets your single-stream token/sec ceiling.

Fast kernels keep data as high up this pyramid as possible — that is exactly what FlashAttention does, tiling so the score matrix never round-trips to HBM.

Hopper H100-class figures, approximate. HBM bandwidth (~3.35 TB/s) is what sets the single-stream decode token/sec ceiling.
Registersper-thread (per-SM file)~256 KB / SM~tens of TB/s~1 cycle (≈ free)
Shared / L1per-block (per SM)up to ~228 KB / SM~10s of TB/s / SM~20–30 cycles
L2 cachewhole GPU (shared by SMs)~50 MB (Hopper)~several TB/s aggr.~200–400 cycles
HBM (device DRAM)whole GPU — the 'VRAM'80 GB (H100 SXM)~3.35 TB/s~400–600 ns
Host RAM (over PCIe)CPU DRAM, system memory100s GB – TBsPCIe 5: ~64 GB/s~microseconds+
Hopper H100-class figures, approximate. Bandwidth drops ~10× at each step down; HBM bandwidth sets the decode ceiling.

The roofline — why decode wastes the GPU

Attainable throughput vs. arithmetic intensity. Left of the ridge you are memory-bound.

GPU

0.11101001000arithmetic intensity (FLOP / byte, log scale)attainable TFLOP/smemory-bounddecode (~1.5)prefill / big batch
Ridge point ≈ 296 FLOP/byte for H100 SXM. Single-stream decode sits at ~1–2 FLOP/byte — far left, using a tiny fraction of peak. Prefill and large batches push right toward the compute roof. Switch GPUs: a faster GPU raises the roof but decode stays memory-bound.

Interconnects: PCIe, NVLink, NVSwitch

A single GPU has finite HBM (80 GB on H100). Big models don't fit, and even when they do you want multiple GPUs for . How fast GPUs talk to each other and to the host sets the ceiling on the parallelism strategies of Module 5.

is the baseline bus every GPU sits on: PCIe Gen4 x16 gives ~32 GB/s per direction, Gen5 x16 ~64 GB/s. That is fine for loading weights from host once, but far too slow for the per-layer all-reduce traffic of tensor parallelism — which is exactly why NVLink exists.

is NVIDIA's proprietary point-to-point GPU interconnect, roughly an order of magnitude faster: ~600 GB/s (A100, 3rd gen), ~900 GB/s (H100, 4th gen), ~1.8 TB/s (B200, 5th gen). So H100↔H100 over NVLink is ~900 GB/s vs ~128 GB/s bidirectional over PCIe 5 — the difference between tensor parallelism being viable or being throttled by communication. NVSwitch then adds a switch chip connecting all GPUs in a node (typically 8) in a non-blocking all-to-all fabric, so every GPU talks to every other at full NVLink bandwidth at once.

That is what makes an 8-GPU HGX/DGX box behave like one big accelerator for collective ops. Cross-node you drop to InfiniBand/Ethernet (~400 Gb/s-class per NIC), a large step down — hence the rule 'keep a model within one NVLink domain if you can.' The parallelism strategies that ride on this are Module 5.

MIG and GPU scheduling

(Multi-Instance GPU, on A100/H100 and newer) hardware-partitions one GPU into up to 7 isolated instances, each with a dedicated slice of SMs, L2 and HBM — with real fault and performance isolation, not just time-slicing. Each instance looks like a smaller standalone GPU to CUDA.

Reach for it for small models and multi-tenancy — serving several 1–7B models or many low-QPS endpoints where a whole H100 per model wastes silicon — and for predictable latency SLAs, since there is no noisy-neighbor interference between instances. It is the GPU analog of K8s resource requests/limits with real isolation. Skip it for any single model that needs more memory or compute than one slice provides; partitions are static-ish (reconfiguring requires draining), so MIG suits steady multi-tenant serving, not spiky single-tenant bursts. Contrast plain time-slicing (software context-switch, no isolation) and MPS (concurrent processes sharing SMs, no hard isolation) — MIG is the only one with hardware-enforced boundaries.

On the concurrency side: by default kernels on the default stream run serially in issue order. A CUDA stream is an ordered queue — operations within a stream run in order, operations in different streams may overlap. The classic win is overlapping a host→device copy on one stream with a compute kernel on another, so PCIe transfer hides behind computation; small kernels can also be co-scheduled on different SMs when neither fills the GPU.

Scheduling happens at three layers: the driver places stream operations onto hardware queues; the block scheduler distributes blocks to SMs; each SM's warp schedulers pick ready warps cycle-by-cycle. As an engineer you mostly control layer 1 (streams, graphs, batching) and let the hardware handle the rest — and CUDA Graphs collapse a whole stream of launches into one replayable unit, killing per-kernel overhead in autoregressive decode.

Why the H100 is fast — synthesis

It is not one thing; it is a stack of reinforcing decisions, all pointed at feeding matmuls and moving bytes. (1) HBM3 at ~3.35 TB/s directly raises the decode token/sec ceiling because decode is bandwidth-bound — the biggest lever. (2) 4th-gen Tensor Cores + FP8 roughly double usable math throughput vs BF16 for prefill and training, and FP8 also halves the bytes moved per weight, easing the bandwidth wall from the other side.

(3) The Transformer Engine automates FP8 scaling per layer so you actually realize that speedup without numerical blowups. (4) NVLink 4 (~900 GB/s) + NVSwitch make 8-GPU tensor parallelism efficient, so large models run without communication becoming the bottleneck. (5) A large L2 (~50 MB) + TMA + big shared memory keep more data on-chip, meaning fewer HBM round-trips and more useful work per byte of bandwidth.

Every item either adds bandwidth or reduces the bytes that must cross it. That is the throughline of the whole module: since inference decode is , NVIDIA's fastest inference gains come from HBM-generation jumps and precision reduction (FP16 → FP8 → FP4), not from piling on FP32 FLOPs.

4090 vs H100 vs B200 vs GB200

The figures below are widely documented, approximate and dtype-dependent (dense FP16 Tensor Core throughput is quoted; vendor 'PFLOPS' headlines often add sparsity and lower precision, so confirm the footnote before capacity planning). Read the table as an inference engineer and a few rules fall out.

Lead with bandwidth, then capacity. Decode throughput tracks memory bandwidth, so the 4090's ~1.0 TB/s vs H100's ~3.35 TB/s is a bigger practical gap for single-stream generation than any FLOP comparison, and B200's ~8 TB/s HBM3e is the headline reason it roughly doubles inference over H100. Capacity decides whether the model fits at all — a 70B FP16 model (~140 GB) doesn't fit on one 4090 or even one H100, but fits comfortably on one B200 (~192 GB), collapsing an 8-GPU-with-NVSwitch problem into a single card. That is often a larger cost win than raw speed.

The 4090 has no , so multi-4090 setups are capped at PCIe (~32–64 GB/s) — great for one model on one card, poor for scaling a big model across cards. FP8/FP4 support (H100 onward; FP4 on Blackwell) means newer parts read fewer bytes per weight, hitting the bandwidth wall from both sides. And TDP is your real TCO input: GB200's ~2.7 kW is a whole module (2 GPUs + Grace CPU), so perf-per-watt — not peak PFLOPS — is what shows up on the bill.

GB200 is not a single GPU. It is a superchip: 2× B200 + 1 Grace CPU joined by NVLink-C2C, a coherent ~900 GB/s CPU↔GPU link that lets the GPUs treat Grace's large LPDDR5X pool as fast, coherent extended memory. NVIDIA packages these into GB200 NVL72 racks (72 GPUs in one NVLink domain), so 'GB200' can mean the 2-GPU superchip or the 72-GPU rack — numbers differ by ~36×. Net: for a home lab or one small model, the 4090 is unbeatable value; for production serving, choose on memory bandwidth and capacity within an NVLink domain — FLOPs are rarely your binding constraint at decode time.

Memory bandwidth — the decode ceiling

Realized HBM / GDDR bandwidth (TB/s) · blue = H100, gray = the others

Decode tokens/sec tracks this bar, not FLOPs. Note GB200 is an aggregate over 2× B200 in one superchip, not a single GPU — see the table below.
RTX 4090Ada LovelaceConsumer24 GBGDDR6X~1.0 TB/snone (PCIe 4.0)~330 TFLOPs~450 WGaming / workstation / small-model dev
H100 SXM5HopperDatacenter80 GBHBM3~3.35 TB/sNVLink 4 ~900 GB/s + NVSwitch~990 TFLOPs~700 WProduction training + inference
B200BlackwellDatacenter~192 GBHBM3e~8.0 TB/sNVLink 5 ~1.8 TB/s~2250 TFLOPs~1000 WNext-gen training + inference
GB200 (superchip)superchipBlackwell + GraceRack module~384 GB (2× B200) + Grace LPDDR5XHBM3e~16 (aggregate, 2× B200) TB/sNVLink 5 + NVLink-C2C to Grace~4500 TFLOPs~2700 WRack-scale training + large-model inference
Approximate, dtype-dependent vendor/spec figures. Dense FP16 Tensor Core throughput is shown; sparse/FP8/FP4 headline numbers are higher. GB200 is a 2×B200 + Grace superchip — not a single GPU.

Glossary

Every dotted-underlined term on this page links back here. Hover any of them in context, or browse the full definitions below.

Token

A token is a subword unit produced by the tokenizer (typically byte-level BPE). Common words are single tokens; rare words split into several. As a rule of thumb ~1 token ≈ 0.75 English words. The model only ever sees token IDs, never raw text.

Tokenizer

The tokenizer converts a UTF-8 string into a list of integer IDs and back. Byte-level BPE guarantees any input is representable (no out-of-vocabulary) by starting from raw bytes and greedily merging frequent pairs. Vocabulary sizes are typically 32k–256k.

Embedding

The embedding matrix has shape [vocab_size, d_model]. Converting a token ID to its vector is a pure row lookup — no matrix multiply. The same matrix is often tied to the output projection that produces logits.

d_model (hidden size)

d_model is the size of the hidden state carried through the network (e.g. 4096 for Llama-2-7B). Most weight matrices are O(d_model²), so it dominates parameter count and per-token compute.

Attention

Scaled dot-product attention computes softmax(QKᵀ/√d_k)·V. Each query token scores every key, normalizes with softmax, and gathers a weighted sum of value vectors. In decoders it is causal: a token may only attend to earlier positions.

KV cache

During generation, the K and V projections of every previous token are cached in HBM so they are not recomputed. Size grows linearly with sequence length and batch: 2 · n_layers · n_kv_heads · head_dim · dtype_bytes per token. It is the dominant runtime memory consumer and a key reason decode is memory-bound.

Prefill

Prefill runs one forward pass over all N prompt tokens at once. Because it is a big matrix–matrix multiply, it is compute-bound and saturates the GPU's Tensor Cores. Its latency determines time-to-first-token.

Decode

Decode produces tokens one by one; each step reads all model weights plus the KV cache from HBM to compute a single new token. It is a matrix–vector operation with arithmetic intensity ~1 FLOP/byte — deeply memory-bandwidth-bound.

Autoregressive generation

The model outputs a probability distribution over the next token, one is sampled, appended, and fed back in. Token t+1 cannot be computed until token t exists, which is why decode cannot be parallelized across time within a single sequence.

Logits

The final hidden state is projected to a vector of size vocab_size. These logits are turned into probabilities by softmax (after temperature scaling and top-k/top-p filtering) and then sampled.

Sampling

Greedy takes the argmax. Temperature scales logits before softmax (higher = more random). Top-k keeps the k highest-probability tokens; top-p (nucleus) keeps the smallest set whose cumulative probability exceeds p. These trade determinism for diversity.

Grouped-query attention (GQA)

GQA sits between multi-head attention (one KV head per query head) and multi-query attention (one KV head total). By using e.g. 8 KV heads for 64 query heads, it cuts KV-cache size and bandwidth several-fold with minimal quality loss — which is why modern models use it.

Memory-bound

A kernel is memory-bound when its arithmetic intensity (FLOPs per byte moved) is below the hardware's ridge point. Single-stream LLM decode moves ~1–2 FLOPs per byte against a GPU that wants a few hundred, so it uses only single-digit percentages of peak compute.

Arithmetic intensity

Defined as total FLOPs ÷ bytes read/written to HBM. Compared against a GPU's ridge point (peak FLOP/s ÷ peak bytes/s), it predicts whether a kernel is limited by math or memory. Batching raises effective intensity by reusing weights across many sequences.

HBM

HBM2e/HBM3/HBM3e are 3D-stacked DRAM dies giving enormous bandwidth (H100 HBM3 ≈ 3.35 TB/s) at capacities of 24–192 GB. Because decode is memory-bound, HBM bandwidth — not FLOPs — is usually the ceiling on tokens/sec.

VRAM

On discrete GPUs, VRAM is separate HBM/GDDR the CPU cannot directly address. A model runs only if weights + KV cache + activation/workspace buffers all fit. On Apple Silicon there is no separate VRAM — see unified memory.

Quantization

Reducing precision from FP16 (2 bytes) to INT8/INT4 (1 byte / 0.5 byte) shrinks the model and, because decode is memory-bound, speeds it up roughly proportionally. Methods like GPTQ and AWQ minimize the resulting accuracy loss.

GGUF

GGUF packs the tensors, tokenizer and hyperparameters into one file designed to be mmap'd, so the OS pages weights in on demand. It is the standard format for CPU/Apple-Silicon inference and for the Q2–Q8 k-quant schemes.

Safetensors

Safetensors stores a JSON header of tensor offsets followed by raw bytes, enabling zero-copy memory-mapped loading with no arbitrary code execution (unlike pickle). It is the default for Hugging Face FP16/BF16 checkpoints served by vLLM and TensorRT-LLM.

Unified memory

On Apple Silicon (and Grace-Hopper style parts) CPU and GPU address the same RAM, so there is no PCIe copy and the whole capacity can hold model weights. A 128 GB Mac can therefore run models a 24 GB discrete GPU cannot — but at the memory's lower bandwidth.

CUDA

CUDA exposes the GPU as a grid of thread blocks running the same kernel (SIMT). Threads are grouped into 32-wide warps scheduled on streaming multiprocessors. The mature CUDA ecosystem (cuBLAS, cuDNN, NCCL) is a major reason NVIDIA dominates inference.

Streaming Multiprocessor (SM)

A datacenter GPU has ~100+ SMs, each with warp schedulers, a large register file, L1/shared memory, CUDA cores and Tensor Cores. Thread blocks are assigned to SMs; occupancy (active warps per SM) hides memory latency.

Tensor Core

Tensor Cores perform fused matrix multiply-accumulate on small tiles in one operation, delivering the bulk of a GPU's FLOPs for FP16/BF16/FP8/INT8. They are why modern GPUs are orders of magnitude faster at matmuls than CUDA cores alone.

PCIe

PCIe Gen5 x16 offers ~64 GB/s per direction — roughly an order of magnitude below NVLink. It is fine for feeding data to a single GPU but a bottleneck for tensor parallelism, which is why NVLink exists.

MIG (Multi-Instance GPU)

MIG splits an A100/H100 into up to seven instances with dedicated SMs, cache and memory bandwidth, giving hard isolation for multi-tenancy or for serving small models that would otherwise waste a whole GPU.

TTFT (time to first token)

Time-to-first-token measures responsiveness: how long the user waits before text starts streaming. It is set by queueing + prefill time and scales with prompt length. Distinct from per-token speed (TPOT).

TPOT / ITL

TPOT is the average time between successive generated tokens during decode. Because decode is memory-bound, TPOT is roughly bytes-moved-per-token ÷ HBM bandwidth. Its reciprocal is per-stream tokens/sec.

Throughput vs latency

Batching many requests raises aggregate throughput (and GPU utilization) by reusing weight reads, but can raise individual latency. Serving is the art of maximizing throughput within a latency SLA (TTFT/TPOT targets).

FAQ

Yes. The hardware always schedules in 32-thread warps, so a block of, say, 100 threads still occupies four warps (128 lanes) with 28 lanes masked off and wasted. Standard block sizes are multiples of 32 — 128 or 256 are common — to avoid partially-filled warps and to divide cleanly into the SM's resident-warp and register limits.

Tensor Cores are enormous matmul engines, but single-stream decode only does a matrix–vector multiply per token — it reads every weight (and the whole KV cache) from HBM to produce one token, an arithmetic intensity of ~1 FLOP/byte. The math finishes long before the bytes arrive, so you are bandwidth-limited and the Tensor Cores sit mostly idle. Prefill and large batches are what actually feed them.

No. Occupancy just measures how many warps are resident to hide latency, and you only need 'enough', not 'maximum'. High-performance kernels like tiled GEMM and FlashAttention deliberately spend lots of registers and shared memory per thread for data reuse, running at modest occupancy but near-peak throughput. Occupancy is a means, not the goal — throughput is the goal.

Use MIG when you need hardware-enforced isolation — several small models or multi-tenant endpoints that each require guaranteed, predictable capacity with no noisy neighbors. Time-slicing and MPS let processes share the GPU but give no hard isolation. Avoid MIG for a single model larger than one slice or for bursty workloads, since partitions are static-ish and reconfiguring requires draining.

For one small model on one card, a 4090 is excellent value. The problem is scaling: the 4090 has no NVLink, so multi-GPU traffic is capped at PCIe (~32–64 GB/s), which throttles the per-layer all-reduce that tensor parallelism needs. Datacenter parts give you ~900 GB/s NVLink plus NVSwitch, far more HBM capacity and bandwidth, ECC, and Transformer Engine tooling — all of which matter the moment a model no longer fits on one card.

No. GB200 is a superchip of 2× B200 GPUs plus a Grace CPU, and the biggest headline numbers usually refer to the GB200 NVL72 rack of 72 GPUs, or quote FP4 with sparsity. Always ask which unit (single GPU, 2-GPU superchip, or 72-GPU rack) and which dtype/sparsity a figure refers to — they can differ by more than an order of magnitude.

References

The works the paper builds on — every entry links to the cited paper.

  1. [1]

    Vaswani et al. (2017). Attention Is All You Need. NeurIPS — the Transformer architecture

  2. [2]

    Kwon et al. (2023). Efficient Memory Management for LLM Serving with PagedAttention (vLLM). SOSP

  3. [3]

    Dao et al. (2022). FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. NeurIPS

  4. [4]

    Ainslie et al. (2023). GQA: Training Generalized Multi-Query Transformer Models. EMNLP

  5. [5]

    Frantar et al. (2022). GPTQ: Accurate Post-Training Quantization for Generative Pretrained Transformers. ICLR 2023

  6. [6]

    Lin et al. (2023). AWQ: Activation-aware Weight Quantization for LLM Compression. MLSys 2024

  7. [7]

    NVIDIA (2022). NVIDIA H100 Tensor Core GPU Architecture (Hopper Whitepaper). NVIDIA technical whitepaper

  8. [8]

    Gerganov et al. (2023). llama.cpp — LLM inference in C/C++ (GGUF). GitHub

  9. [9]

    Yu et al. (2022). Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI — iteration-level (continuous) batching

← Previous

2. Running Models Locally

Running locally

Next →

4. Inference Engines, Internally

Coming soon