GB10 really does hit ~1 PFLOP NVFP4 (2:4 sparse) — measured, with an open-source tool to reproduce it
TL;DR. I built a small CLI, nvfp4bench, to measure peak achievable NVFP4 tensor-core
throughput on DGX Spark (GB10, sm_121a, CUDA 13) and check it against NVIDIA’s headline
figures, which so many times have been called into question.
On my unit the pure tensor-core peak reaches ~511 TFLOPS dense (102% of the 500
spec) and ~1014–1022 TFLOPS for 2:4-sparse NVFP4 (102% of the 1 PFLOP spec). So the 1 PFLOP
number is real silicon — but only via the native packed mxf4nvf4 instruction plus 2:4
sparsity. The byte-padded mxf8f6f4 path that hand-written code often starts from runs at
exactly half rate. Tool + full write-up:
The measurement
The peak is measured with a register-resident microbenchmark: many independent back-to-back
warp MMAs, no global/shared memory traffic, so it isolates the tensor-core issue rate from the
LPDDR5 bandwidth ceiling. Four rungs:
| Instruction | Format | K/instr | Measured | vs spec |
|---|---|---|---|---|
mma.sync … kind::mxf8f6f4 |
byte-padded FP4, 1 code/byte | 32 | ~256 TFLOPS | 51% of 500 |
mma.sp … kind::mxf8f6f4 |
+ 2:4 sparse | 64 | ~511 TFLOPS | 51% of 1000 |
mma.sync … kind::mxf4nvf4 |
packed FP4, 2 codes/byte | 64 | ~511 TFLOPS | 102% of 500 |
mma.sp … kind::mxf4nvf4 |
packed + 2:4 sparse | 128 | ~1014–1022 TFLOPS | 102% of 1000 |
A clean 256 → 512 → 512 → 1022: packed = 2.00×, sparse = 2.00×, combined = 4.00×. Both
top rungs sit at ~101–102% of spec (running at the observed ~2.6 GHz under load).
The practical catch
The thing worth flagging for anyone writing FP4 kernels by hand: the headline numbers require
the native packed kind::mxf4nvf4 MMA (2 codes/byte, m16n8k64 dense / m16n8k128 sparse).
The mixed kind::mxf8f6f4 path (one FP4 code per byte) is the natural first thing to reach for,
and it tops out at exactly half rate because it moves half the K per instruction. If your
hand-rolled FP4 GEMM is landing around 250 dense / 500 sparse, this is very likely why.
A couple of things that surprised me on GB10
- GB10 does expose the warp-level sparse block-scaled FP4 MMA (
mma.sp::ordered_metadata … block_scale). I had expected it to be a datacentersm_100/tcgen05feature, but the raw
PTX assembles and runs onsm_121aand delivers the full 2× of the dense path. - GB10 has no
tcgen05/TMEM/2-SM MMA — NVFP4 runs on the GeForce-style warp-level
mma.sync/mma.sp … block_scale. - For
kind::mxf4nvf4,ue8m0scales cap atscale_vec::2X;scale_vec::4Xrequires
ue4m3(the block-16 NVFP4 microscale). ptxas rejects4X+ue8m0. - Toolchain gotcha that cost me a while: you must pass the accelerated arch explicitly,
-gencode=arch=compute_121a,code=sm_121a. The-arch=sm_121ashorthand (and some CMake
paths) silently drops the trailinga, producing plainsm_121, where the block-scaled FP4
MMA either isn’t emitted or aborts at runtime.
Real GEMMs vs the peak (the honest part)
The 1 PFLOP is a pure tensor-core ceiling. Any real GEMM on GB10 is bandwidth-bound far
below it by the 273 GB/s LPDDR5:
- A CUTLASS NVFP4 GEMM (CUTLASS ≥ 4.2.1, ArchTag
Sm120) peaks around ~375 TFLOPS dense at a favorable shape (e.g.4096×14336×4096) — that’s its memory ceiling, not the compute ceiling. Large square shapes (≥8192³) also thermally throttle, so the tool reports both burst peak and sustained. - My from-scratch packed
mxf4nvf4warp-GEMM validates bit-exact against an FP32 oracle and runs at ~112 TFLOPS at 2048³ (L2-resident) — 2.3× a byte-padded version built from the
same source. It’s not competitive with CUTLASS (noldmatrix/TMA-class machinery), but it’s a
fully validated kernel built directly on the reverse-engineered fragment layout.
What’s in the tool
--peak— the register-resident MMA ladder above (this is where the 1 PFLOP shows up).--kernel cutlass— a trusted CUTLASS NVFP4 baseline.--kernel custom— the hand-written, validated packedmxf4nvf4GEMM.--bandwidth— a STREAM-triad roofline probe (to show where the memory wall is).- The reverse-engineering probes that made the custom kernel possible: the (undocumented)
SM120/sm_121ablock-scaled FP4 decode model and fragment layout for both the
byte-padded and packed formats, extracted by one-hot hardware probing and self-validated.
The packed model: true E2M1 decode (0, .5, 1, 1.5, 2, 3, 4, 6),ue8m0scale2^(E−127)
(0x7F= unit), two scale bytes perscale_vec::2Xeach covering one 32-wide K-block.
Reproduce
git clone https://github.com/secYOUre/nvfp4bench
cd nvfp4bench
# headline peak (no CUTLASS needed):
nvcc -gencode=arch=compute_121a,code=sm_121a -O3 -o peak_mma src/peak_mma.cu && ./peak_mma
# or the full tool:
cmake -B build && cmake --build build -j
./build/nvfp4bench --peak
I’d love for others with a DGX Spark to run --peak and post their numbers — curious how much
unit-to-unit and thermal variation there is, and whether anyone sees the ue4m3/scale_vec::4X
path behave differently. Feedback, corrections, and PRs very welcome; some of the layout work is
reverse-engineered and I’d be glad to have it scrutinized.
(Measured on a single GB10 / DGX Spark, CUDA 13.0, driver 580.x. Numbers are from one unit and
will vary with clocks/thermals.)
