KVTop
Kernels · Measurement · RTX 5090 (SM120)

Why can the same NVFP4 GEMM report 1,200 TFLOPS on one input and 1,700 on another?

99%NVFP4 MMA per cycle4,057 of 4,096 FLOP/clk/SM, register-only microbenchmark
1,203random inputs, 16384³TFLOPS at ≈1.83 GHz, 532 W
1,734zero inputs, same kernelTFLOPS at ≈2.67 GHz, 391 W (diagnostic)
n = 1evidencepreliminary; one sequence per condition on one card, clocks unlocked, not yet repeated
Core result · measured
RandomGaussian25% zeros50% zeros75% zeros90% zerosConstantZerosTFLOPS1,203.11,204.61,213.81,274.71,394.51,531.41,671.31,734.2SM clock (MHz)1,8301,8441,8481,9472,1212,3282,5962,670board power (W)532481486486520535524391
Were we blaming the kernel for a clock difference? Same binary, shape (16384³) and configuration; only the input values change. Throughput moves with the average SM clock, from 1,830 MHz with random inputs to 2,670 MHz with all-zero inputs. Zero-fraction, constant and zero inputs are diagnostics, not model workloads. Not every condition sat at the 530 W limit (Gaussian 481 W, 25% and 50% zeros 486 W, all zeros 391 W), so this shows a data-dependent power and frequency effect, not proof that every condition was power-capped. Rows are drawn in order of zero fraction; the file logs them in a different order. The same file holds a second, later sequence: random 1,204.4 and constant 1,671.1 TFLOPS; its other rows change which operand is constant and are not drawn. Clock, power and temperature are averages of nvidia-smi samples taken during each condition; the sampling interval and sample filter used for this run were not recorded (the sweep script was edited after the run). Code revision not recorded; values checked byte-for-byte against the original run output.
GPU
One RTX 5090 (our workstation), 170 SMs
driver
595.84
power
530 W limit (default 575 W), clocks unlocked
run
300 back-to-back calls per condition, one sequence (n = 1); at the logged rates that is about 1.5–2.2 s of GEMM work per condition (computed, not timed)
telemetry
nvidia-smi samples averaged per condition; sampling interval and sample filter for this run not recorded
not recorded
which operand(s) and scales each distribution changed beyond the label; temperature start state; throttle reasons
code revision
not recorded

Source: results/run10/power_2_16384.txt:L1–L9 (drawn), L11 and L17 (second sequence) · raw output files; research directory not yet in a public repository

We thought we had a kernel-efficiency problem. On our RTX 5090, top-dispatcher—a shape-based selection of CUTLASS configurations—reported about 1,146–1,338 TFLOPS across eight matrix shapes. Our instruction microbenchmark was near 2,000 TFLOPS. We looked at the gap and thought: where did a third of the hardware go?

That was an uncomfortable conclusion to revisit. We had compared two measurements without checking whether the GPU was running at the same clock.

Was the instruction itself being held back?

We started with a register-only microbenchmark, normalized by clock64. The NVFP4 block-scaled MMA (mma.sync … kind::mxf4nvf4 … m16n8k64) delivered 4,057 FLOP per SM per cycle—about 99% of the 4,096 we expected—at roughly 2.9 GHz. We did not observe an extra throughput limit on this instruction path.

The same test showed the familiar GeForce pattern on older paths: FP16 and FP8 with FP32 accumulation run at half the rate of their FP16-accumulate versions.

instruction (all 170 SMs) FLOP / clk / SM
NVFP4 mxf4nvf4, UE4M3 scales, m16n8k64 4,057
FP4 through f8f6f4 (no block scale), m16n8k32 1,024
FP8 E4M3, FP16 accumulate 2,041
FP8 E4M3, FP32 accumulate 1,024
BF16, FP32 accumulate 512

A separate instruction probe also included structured-sparse paths. We keep it separate here: its dense NVFP4 observation was 4,093.1 FLOP per SM per cycle, rather than the earlier 4,057. The sparse rows count dense-equivalent arithmetic, so their higher rates do not mean the same dense model automatically runs twice as fast.

Figure 1 · measured
NVFP4 dense4,093.1kind::mxf4nvf4.e2m1.ue4m3.m16n8k64 warps= 8 bps=2 ilp=4 sms=170NVFP4 2:4 sparse8,171.4sp.kind::mxf4nvf4.ue4m3.m16n8k128 warps= 8 bps=2 ilp=4 sms=170FP8 FP32-acc dense1,024e4m3.f32acc.m16n8k32 warps= 8 bps=2 ilp=4 sms=170FP8 FP32-acc 2:4 sparse2,047.4sp.e4m3.f32acc.m16n8k64 warps= 8 bps=2 ilp=4 sms=170MXFP8 block-scaled dense2,046.6kind::mxf8f6f4.e4m3.ue8m0.m16n8k32 warps= 8 bps=2 ilp=4 sms=170Instruction arithmetic rate (FLOP/clk/SM)
Which instruction paths sustain the larger per-cycle rate? Historical register-only probes normalized by clock64, 170 SMs, warps=8, bps=2, ilp=4. These five rows come from sparse.txt; its dense NVFP4 observation is 4093.1 FLOP/clock/SM, distinct from the earlier 4057 observation in the article. Sparse counts are dense-equivalent arithmetic work (including products structurally skipped), not the number of nonzero multiplications executed. Do not interpret a sparse instruction rate as free acceleration of unchanged dense model weights. Code revision not recorded; values checked byte-for-byte against the original run output.
GPU
One RTX 5090 (our workstation)
driver
595.84
CUDA
13.2.86
power
530 W limit, clocks unlocked
evidence
Historical raw result files (Oct 25–26, 2026), re-read in full; no experiments rerun
repeats
One logged observation per path; independent-repeat count not established
clock
Reported about 2902–2914 MHz; not a sustained GEMM clock
sparse_accounting
Dense-equivalent FLOPs; underlying benchmark counting code not supplied for independent audit
code revision
not recorded

Source: results/run1/sparse.txt:L1–L5 · raw output files; research directory not yet in a public repository

We also inspected the listed tensors from a Qwen 27B NVFP4 weight set. The file reports that only 2.77% of the inspected groups already contain at least two zeros. Its reported extra-zeroing requirement is a structural check, not evidence that pruning would preserve model quality.

Figure 2 · measured
L0 Linear in_proj_aL0 Linear in_proj_bL0 Linear in_proj_qkvL0 Linear in_proj_zL0 Linear out_projL0 MLP down_projL0 MLP gate_projL0 MLP up_projL3 MLP down_projL3 MLP gate_projL3 MLP up_projL3 Attn k_projL3 Attn o_projL3 Attn q_projL3 Attn v_projL31 MLP down_projL31 MLP gate_projL31 MLP up_projL31 Attn k_projL31 Attn o_projL31 Attn q_projL31 Attn v_projL63 MLP down_projL63 MLP gate_projL63 MLP up_projL63 Attn k_projL63 Attn o_projL63 Attn q_projL63 Attn v_projGroups with ≥2 zeros (%)2.652.672.632.72.932.672.642.632.642.632.642.842.762.712.822.752.762.832.782.773.213.172.762.74.363.582.93.63Reported extra zeros needed (%)43.243.243.143.142.843.143.143.243.143.143.142.94343.142.9434342.942.7434342.542.54343.141.44242.942.1
Do the measured NVFP4 weights already satisfy 2:4 sparsity? Historical inspection of the 29 listed tensor rows from Qwen 27B NVFP4. The supplied ALL row reports 2.77% of inspected groups with at least two zeros; it is not recomputed as an unweighted mean of tensors and does not establish a whole-model statistic. Extra zeros needed is reproduced as logged; its denominator and pruning implementation require the source script to verify. No accuracy, recovery-training or serving-speed result is provided. Code revision not recorded; values checked byte-for-byte against the original run output.
scope
The listed tensors only; preserve the supplied truncated tensor names in source_rows
grouping
Groups of four as used by the inspection script; axis/packing verification requires that script
repeat_unit
One supplied weight-inspection output, not an inference benchmark
code revision
not recorded

Source: results/run12/qwen27b_nvfp4_24.txt:L1–L31 · raw output files; research directory not yet in a public repository

What clock was the GEMM actually running at?

This is where our original comparison fell apart. During repeated large GEMMs, the clock fell far below 2.9 GHz. The same 16,384³ kernel reported 1,203 TFLOPS at about 1.83 GHz with random inputs, and 1,734 TFLOPS at about 2.67 GHz with all-zero inputs. As far as the run records show, nothing else changed; how operands and scales were generated, and the starting state, are still to verify.

We left the power limit at 530 W and did not lock clocks. These runs show a large data-dependent power and frequency effect. They do not establish that every NVFP4 GEMM is power-limited: the Gaussian and zero conditions drew 481 W and 391 W.

Zero-filled and constant-filled matrices are diagnostic inputs, not model workloads or evidence for a performance claim. We include them because they expose something a single throughput number leaves out. Without stating the input values, a benchmark can report almost any number in this range.

Run length gave us another reason to be careful. A 4,096³ ping-pong kernel reported 1,264 TFLOPS over 40 calls and 1,144 over 4,000. Even 4,000 calls lasted under half a second. Neither measurement was a thermal steady state.

Figure 3 · measured
40 calls, first sequence1,264.1≈ 4 ms of GPU work; file line 240 calls, second sequence1,258.2≈ 4 ms; file line 34,000 calls1,144.5≈ 0.48 s; 9.5% below the first short loop; file line 4Reported NVFP4 throughput (TFLOPS)
How much can the measurement window change the result? 128×128×256 ping-pong, 4096³, one run. The 4,000-call sequence reports 1,144.5 TFLOPS, 9.5% below 1,264.1 and 9.0% below 1,258.2. The same file shows the same pattern for two other configurations: 2-stage ping-pong 1,262.0 / 1,268.8 → 1,142.3, and 256×128×128 1,218.2 / 1,201.8 → 1,130.8. Even 4,000 calls last under half a second, so no condition is a thermal steady state; starting temperature and clock were not recorded. Three observations per configuration, not a decay curve. Code revision not recorded; values checked byte-for-byte against the original run output.
GPU
one RTX 5090 (our workstation), 530 W limit, clocks not locked
run
one run, results/run6/1_4096_clk.txt lines 2–10
code revision
not recorded

Source: results/run6/1_4096_clk.txt · raw output files; research directory not yet in a public repository

What did we get wrong about “57–66% of peak”?

We treated the gap as if it measured time lost to operand loads, synchronization or the epilogue. It did not. Dividing application throughput by a peak measured at a different frequency cannot tell us that.

Profiling helps us locate where warps are waiting, but those samples have their own denominator. The following two captures use different shapes and schedules; their sample shares cannot be read as fractions of elapsed kernel time.

Figure 4 · measured
Before main loopMain loopAfter main loop4096³ ping-pong · sample %3.640.855.68192³ cooperative · sample %2.579.418.1
Where did the profiler collect its warp-state samples? Two separate historical profiler captures: 4096³ ping-pong and 8192³ cooperative. The shares classify sampled instruction locations before, within and after each main loop. Counts are 10,340 and 71,104 profiler samples, not independent experimental repetitions. Different shapes and schedules prevent a one-variable comparison. Sample shares do not equal kernel-time shares, energy shares, or recoverable speedup. Code revision not recorded; values checked byte-for-byte against the original run output.
GPU
One RTX 5090 (our workstation)
driver
595.84
CUDA
13.2.86
power
530 W limit, clocks unlocked
evidence
Historical raw result files (Oct 25–26, 2026), re-read in full; no experiments rerun
metric
Share of collected warp-state samples by code region
repeats
One supplied capture per shape/configuration; profiler replay count not supplied
code revision
not recorded

Source: results/run6/3_stall_summary.txt:L1–L4; results/run6/3_stall_summary.txt:L22–L25 · raw output files; research directory not yet in a public repository

For frequency-normalized efficiency, we need effective FLOPs divided by SM count × per-cycle peak × the clock integrated over the same measurement window. That requires clock telemetry from the same run; a separate profiler replay will not supply the missing denominator.

Figure 5 · illustration, not data
SCHEMATIC · define the denominator before reporting efficiencyMismatched comparisonApplication TFLOPS ÷ peak TFLOPS measured at a different clockThis ratio cannot isolate time lost to loads, synchronization or the epilogue.Frequency-normalized efficiencyeffective FLOPs / [SM count × peak FLOPs per SM per cycle × ∫ f(t) dt]Integrate the SM clock over the same window as the measured work.Use a matching instruction path and FLOP convention; this is not tensor-pipe active cycles.
Which clock belongs in the efficiency denominator? Illustration only. The denominator is SM count times the matching per-cycle arithmetic peak times the clock integral over the same measurement window. Equivalently, divide throughput by the peak at the time-averaged clock from that window. Tensor-pipe active-cycle counters measure a different quantity; this figure does not assign an efficiency to the historical GEMM runs.
scope
Definition for one GPU with a representative SM clock; otherwise sum per-SM clock integrals
measurement
Effective work and elapsed window must match; independent profiler replay is not substitute telemetry

Source: Explanatory illustration for the section: What did we get wrong about “57–66% of peak”?, lines 250–254

We also have to apply that scrutiny to our own dispatcher. Its 1.03–1.16× over torch._scaled_mm came from a benchmark that always timed our kernel first. We need a balanced-order retest before calling it a speedup. That is the next note.

How much can we conclude from these runs?

Each condition is one historical sequence on one card (n = 1), with low-rate nvidia-smi sampling and no locked clock. The results were enough to make us withdraw our original explanation. Strengthening the causal claim still needs repeats, time-based windows, logged throttle reasons, and a fixed-clock run at a frequency every input can hold.

Method

GPUOne RTX 5090 (our workstation), 170 SMs, driver 595.84, power limit 530 W (default 575 W)
Clocksnot locked
SoftwareCUDA 13.2.86, torch 2.13.0+cu130; CUTLASS collectives (version at build time not recorded)
Instruction testmma.sync per PTX variant, clock64-normalized, warps × ILP × SM sweeps
GEMM16384³, 256×128×128 cooperative; 4096³, 128×128×256 ping-pong
Inputsrandom E2M1, Gaussian-shaped, fixed zero fractions, constant, zero
Run length300 calls per condition (16384³), about 1.5–2.2 s of GEMM work computed from the logged rates, not timed; 40 vs 4,000 calls (4096³)
Telemetrynvidia-smi samples averaged per condition; sampling interval and sample filter for this run not recorded
Throttle reasons per windownot recorded
Temperature start statenot recorded
Repeats1 sequence per condition

Evidence and references

Code and data for this note will be released as open source; more is coming.

What this does not show

One sequence per condition, low-rate telemetry, no fixed-clock control, so the power and frequency explanation is supported but not isolated. Not every condition sat at the power limit. Zero and constant inputs are diagnostics, not workloads. A tensor-pipe activity counter is not the fraction of useful arithmetic at peak rate. 300 calls of 16384³ amount to about 1.5–2.2 s of GEMM work (computed from the logged rates), which is not a thermal steady state.