Module 03 — Blackwell Hardware: What sm_120 Actually Accelerates¶
Collection: Hardware-Aware LLM Quantization | Previous: ← Module 02 | Next: Module 04 →
Module 02 treated formats as mathematics. This module treats them as instructions. A format is only fast if the silicon can consume it without a detour, and the gap between "fewer bits" and "faster" is where most quantization projects quietly fail.
The governing rule of this module:
A bit-width you cannot feed to a tensor core is a bit-width you are emulating. Emulation costs alignment, issue slots, and the native MMA path — and it usually costs more than the bytes it saved.
Learning objectives¶
By the end of this module you should be able to:
- Describe the RTX 5090's memory system and compute roofline, and derive ridge points per precision.
- Distinguish the consumer Blackwell (
sm_120) tensor-core path from the datacenter Blackwell (sm_100) one, and name the instruction family each uses. - Explain why odd bit-widths (3-bit, 5-bit) lose effective bandwidth even when they reduce nominal bytes.
- Compute the break-even achieved-bandwidth threshold for a non-native format.
- Read a Nsight Compute report and confirm you are on the fast path rather than a dequant path.
1. The machine¶
NVIDIA GeForce RTX 5090 — GB202, compute capability 12.0 (sm_120)
┌──────────────────────────────────────────────────────────────┐
│ 170 SMs × 128 FP32 lanes = 21,760 CUDA cores │
│ 5th-generation Tensor Cores (FP4 / FP6 / FP8 / BF16 / FP16) │
│ boost ~2.41 GHz │
├──────────────────────────────────────────────────────────────┤
│ L2 cache: tens of MB (irrelevant here — see §2) │
├──────────────────────────────────────────────────────────────┤
│ 32 GB GDDR7, 512-bit bus @ 28 Gbps → 1792 GB/s │
└──────────────────────────────────────────────────────────────┘
Dense tensor-core throughput roughly doubles per precision step:
| Precision | Dense TFLOP/s (approx.) | Ridge point (FLOP/byte) |
|---|---|---|
| FP32 (shader) | 105 | 59 |
| BF16 / FP16 | 419 | 234 |
| FP8 (E4M3/E5M2) | 838 | 468 |
| NVFP4 | 1676 | 935 |
NVIDIA's headline "3352 AI TOPS" for this part is FP4 with 2:4 structured sparsity. Dense FP4 is half that. Sparsity requires a pruned model with the 2:4 pattern enforced and a sparsity-aware kernel; if you have not deliberately done that work, the number that applies to you is 1676, and quoting 3352 in a roofline will make you think you have 2× more compute headroom than you do.
2. Why the L2 cache does not save you¶
GB202's L2 is large by GPU standards — tens of megabytes. Your decode working set is ~16 GB of weights, streamed exactly once per token.
working set per token ≈ 15.9 GB
L2 capacity ≈ 0.1 GB
reuse within one token = ~1× (each weight read once, used once)
reuse across tokens = 0× (evicted long before the next token needs it)
Cache hit rate on the weight stream is essentially zero, and no amount of L2 fixes that. This is why the roofline in Module 01 uses HBM/GDDR bandwidth directly with no cache correction — for batch-1 LLM decode, the cache hierarchy is a bystander.
Two practical consequences:
- The only way to reduce weight traffic is to make the weights smaller. There is no locality trick available.
- The KV cache is small enough to benefit from L2 at short context, which is one reason short-context decode outperforms the naive roofline slightly, and why that advantage evaporates as context grows (Module 09).
3. Two Blackwells, two tensor-core programming models¶
This is the single most misattributed fact in current Blackwell material, so state it precisely:
HOPPER (sm_90) : wgmma — warpgroup MMA, async, operands in shared memory
BLACKWELL datacenter
(sm_100) : tcgen05 — 5th-gen MMA + Tensor Memory (TMEM), B200 / GB200
BLACKWELL consumer
(sm_120) : mma.sync — warp-level MMA with BLOCK-SCALED FP4/FP8 operands
RTX 50-series / GB202. No TMEM, no tcgen05.
Both Blackwell variants execute NVFP4 natively, but through different instructions with different tiling requirements, which means:
- A kernel tuned for B200 (
sm_100a) does not simply recompile for RTX 5090. - CUTLASS carries separate collective/kernel schedules for the two targets; you must build with the right arch (
sm_120a) to get the block-scaled path at all. - Library coverage differs. Something that has a fast NVFP4 kernel on B200 may fall back to a generic path on
sm_120.
Verify, do not assume. "Blackwell supports NVFP4" is true and insufficient. The question is always: does this runtime, at this version, have a block-scaled kernel compiled for
sm_120afor this operator shape? Answer it with a profiler, not a datasheet.
4. The native/emulated boundary¶
NATIVE (tensor core consumes the format directly, block scales in hardware)
├── NVFP4 (E2M1 + E4M3 block scale) ← the target
├── MXFP4 (E2M1 + E8M0 block scale)
├── FP6 (E3M2 / E2M3)
├── FP8 (E4M3 / E5M2)
└── BF16 / FP16
EMULATED (must be unpacked to a wider type before any MMA)
├── INT4 with arbitrary group sizes (fast kernels exist — Marlin-class — but hand-written)
├── 3-bit, 5-bit, 6-bit integer ← no native path, no tuned kernels
└── 2-bit ← worst alignment behaviour
Why odd bit-widths lose effective bandwidth¶
The naive argument for 3-bit is compelling. Compare per-weight storage:
NVFP4 : 4 + 8/16 = 4.50 bits = 0.5625 bytes/weight
INT3 : 3 + 16/128 = 3.125 bits = 0.3906 bytes/weight → 1.44× fewer bytes
If decode is bandwidth-bound, 1.44× fewer bytes should mean 1.44× more tok/s. It usually does not, and the reason is not primarily the arithmetic cost of unpacking:
4-bit: 8 weights pack into one 32-bit word, exactly.
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ w0 │ w1 │ w2 │ w3 │ w4 │ w5 │ w6 │ w7 │ aligned, coalesced,
└────┴────┴────┴────┴────┴────┴────┴────┘ one shift+mask per weight
3-bit: 10.67 weights per 32-bit word. Values STRADDLE word boundaries.
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬─┐┌─┬ ...
│w0 │w1 │w2 │w3 │w4 │w5 │w6 │w7 │w8 │w9 │w│││10 ...
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴─┘└─┴ ...
▲
split across two words
Straddling forces either a bit-shuffled storage layout (which breaks the natural coalescing of a GEMV's access pattern) or multi-word reads with cross-lane shuffles. Either way achieved bandwidth drops, and achieved bandwidth is the numerator of your throughput equation.
The break-even rule¶
Make it quantitative. A format wins only if it delivers more tokens per second overall:
Setting NVFP4 (achieving 92 % of peak) equal to INT3 (achieving x of peak):
An INT3 kernel must sustain ≥ 64 % of peak memory bandwidth just to tie NVFP4. If your hand-written 3-bit GEMV achieves 55 % — an entirely typical result for an unaligned layout — you have shipped a model that is 14 % smaller and 14 % slower.
Generalize it. For any candidate format:
Compute this before writing the kernel. It tells you the bar, and it frequently tells you not to bother.
The second penalty: leaving the native path¶
At batch 1 the above is the whole story. The moment you batch — speculative decoding verifies K+1 tokens per pass, which is a small batch — an emulated format also forfeits the FP4 MMA throughput:
NVFP4 native : block-scaled mma.sync → up to 1676 TFLOP/s
INT3 emulated : unpack → BF16 mma.sync → up to 419 TFLOP/s (4× less)
This matters specifically because Module 10's speculation raises your effective batch. A format choice that looks free at batch 1 can cap your speculative gains.
5. What the fast path looks like in a profiler¶
Do not trust the format name in your config. Confirm the kernel. In Nsight Compute:
| Signal | Fast path (native NVFP4) | Slow path (dequant emulation) |
|---|---|---|
| Kernel name | contains nvfp4 / mxf4 / blockscaled / CUTLASS sm120 schedule |
contains dequant, unpack, or a generic gemv |
sm__inst_executed_pipe_tensor |
high | low or zero |
| Integer/ALU instruction share | low | high — the unpack |
dram__throughput.avg.pct_of_peak_sustained_elapsed |
85–93 % | often 50–70 % |
| Register pressure / occupancy | moderate | elevated pressure, reduced occupancy |
The quickest single check is the one that costs nothing:
# Are we even launching a block-scaled kernel?
nsys profile -o trace ./run_decode.sh
nsys stats --report cuda_gpu_kern_sum trace.nsys-rep | head -20
# Then the decisive counter:
ncu --metrics dram__throughput.avg.pct_of_peak_sustained_elapsed,\
sm__inst_executed_pipe_tensor.avg.pct_of_peak_sustained_active \
--kernel-name-base demangled ./run_decode.sh
If dram__throughput is 72 % and the tensor pipe is idle, you are not bandwidth-limited by physics — you are limited by your kernel. That is exactly the situation Module 01 §5 predicted for the case-study model, and it is a kernel problem, not a quantization problem.
6. The hardware-speedup column, filled in¶
Returning to the opportunity framework from Module 01, here is the HardwareSpeedup factor for sm_120:
| Target format | Bytes/weight | Native on sm_120? |
Speedup factor | Verdict |
|---|---|---|---|---|
| BF16 → FP8 | 2.0 → 1.0 | yes | 2.0× | safe, always worth it on hot tensors |
| BF16 → NVFP4 | 2.0 → 0.5625 | yes | 3.56× | the main lever |
| NVFP4 → MXFP4 | 0.5625 → 0.5312 | yes | 1.06× | not worth the error increase |
| NVFP4 → INT3 | 0.5625 → 0.3906 | no | ≤1.44× nominal, <1.0× realistic | do not |
| NVFP4 → INT2 | 0.5625 → 0.2656 | no | nominal 2.1×, realistic ≪1 | do not |
Which gives the practical rule for this silicon:
Every hot tensor should be NVFP4 or FP8.
Nothing should be below 4 bits.
The remaining wins are in WHICH tensors and in kernel quality — not in lower bit-widths.
That is a narrow design space, and narrowing it is the point. It means Modules 04–11 can focus entirely on allocation — which tensor gets FP8 and which gets NVFP4 — rather than on an unbounded search over exotic formats.
Checkpoint¶
You should now be able to:
- State the RTX 5090's bandwidth (1792 GB/s), dense FP4 throughput (~1676 TFLOP/s), and FP4 ridge point (~935 FLOP/byte).
- Explain why "3352 AI TOPS" should not appear in your roofline.
- Name the tensor-core instruction family for
sm_90,sm_100, andsm_120without confusing them. - Explain why a 44 %-smaller 3-bit format can be slower, in terms of alignment and achieved bandwidth.
- Compute the break-even achieved-bandwidth threshold for any candidate format.
- Name the two Nsight counters that distinguish a native kernel from a dequant kernel.
Ship it¶
Produce a format feasibility table for your own target GPU:
- measured peak bandwidth (run a STREAM-style or
bandwidthTestcopy benchmark; do not use the datasheet) - dense TFLOP/s per precision, and the ridge point for each
- for each format you are considering: bytes/weight, native or emulated, break-even achieved-bandwidth threshold
- a profiler trace of one decode step, with the kernel names and
dram__throughputfor the top three kernels
Anyone reading that table should be able to say which formats are viable on your machine without running a single quantization experiment.
Current as of¶
- Timeless: the roofline, the native-vs-emulated distinction, the alignment argument, the break-even derivation.
- 2026 hardware pins: RTX 5090 / GB202 /
sm_120/ CC 12.0, 1792 GB/s, ~1676 dense FP4 TFLOP/s. Datacenter Blackwell =sm_100withtcgen05+ TMEM; consumer Blackwell =sm_120with block-scaledmma.sync. Hopper =sm_90withwgmma. - Refresh surface — this is the most perishable module in the course. Kernel coverage in CUTLASS, TensorRT-LLM, and vLLM for
sm_120ablock-scaled GEMMs changes release to release. Re-run the profiler checks in §5 after every runtime upgrade; a version bump can silently move you between the fast and slow paths.