Skip to content

Commit 09b8feb

Browse files
committed
Stage compact NVFP4 weight scales with TMA
1 parent 1469d0c commit 09b8feb

5 files changed

Lines changed: 437 additions & 24 deletions

File tree

benchmarks/tma_m1_scaled_mm_heatmap.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
select_mxfp8_tma_compute_warps,
2222
select_nvfp4_tma_config,
2323
select_nvfp4_tma_split_k,
24+
select_nvfp4_tma_stage_weight_scales,
2425
)
2526
from transformer_nuggets.utils.benchmark import benchmark_cuda_function_in_microseconds
2627

@@ -35,6 +36,7 @@ class KernelConfig:
3536
num_compute_warps: int
3637
grid_scheduler: GridScheduler
3738
split_k: int = 1
39+
stage_weight_scales: bool = False
3840

3941

4042
@dataclass(frozen=True)
@@ -50,6 +52,7 @@ class BenchmarkResult:
5052
num_compute_warps: int | None
5153
grid_scheduler: str
5254
split_k: int
55+
stage_weight_scales: bool
5356

5457

5558
def parse_sizes(value: str, dimension: str, multiple: int) -> list[int]:
@@ -168,12 +171,22 @@ def select_nvfp4_config(n: int, k: int, device: torch.device) -> KernelConfig:
168171
elif n >= 12288 and k == 12288:
169172
block_n, num_compute_warps = 16, 4
170173
split_k = select_nvfp4_tma_split_k(n, k, device)
174+
stage_weight_scales = select_nvfp4_tma_stage_weight_scales(
175+
n,
176+
k,
177+
block_n,
178+
num_compute_warps,
179+
GridScheduler.STATIC,
180+
split_k,
181+
device,
182+
)
171183
return KernelConfig(
172184
block_n,
173185
num_stages,
174186
num_compute_warps,
175187
GridScheduler.STATIC,
176188
split_k,
189+
stage_weight_scales,
177190
)
178191

179192

@@ -266,6 +279,7 @@ def run_mxfp8(n: int, k: int, rounds: int, iterations: int, seed: int) -> Benchm
266279
config.num_compute_warps,
267280
config.grid_scheduler.value,
268281
config.split_k,
282+
config.stage_weight_scales,
269283
)
270284

271285

@@ -289,6 +303,7 @@ def run_nvfp4(n: int, k: int, rounds: int, iterations: int, seed: int) -> Benchm
289303
num_compute_warps=config.num_compute_warps,
290304
grid_scheduler=config.grid_scheduler,
291305
split_k=config.split_k,
306+
stage_weight_scales=config.stage_weight_scales,
292307
output=output,
293308
partial_output=partial_output,
294309
)
@@ -308,6 +323,7 @@ def run_nvfp4(n: int, k: int, rounds: int, iterations: int, seed: int) -> Benchm
308323
config.num_compute_warps,
309324
config.grid_scheduler.value,
310325
config.split_k,
326+
config.stage_weight_scales,
311327
)
312328

313329

test/test_nvfp4_tma.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
select_nvfp4_tma_compute_warps,
2020
select_nvfp4_tma_config,
2121
select_nvfp4_tma_split_k,
22+
select_nvfp4_tma_stage_weight_scales,
2223
)
2324
from transformer_nuggets.cute.profiler import profile_session
2425
from transformer_nuggets.cute.profiler.host import decode_events
@@ -54,6 +55,7 @@ def test_select_nvfp4_tma_compute_warps(k, block_n, expected):
5455
(14336, 16384, (8, 3, 4)),
5556
(24576, 24576, (8, 2, 4)),
5657
(32768, 32768, (8, 3, 4)),
58+
(16384, 8192, (8, 2, 4)),
5759
],
5860
)
5961
def test_select_nvfp4_tma_config(n, k, expected):
@@ -78,6 +80,38 @@ def test_select_nvfp4_tma_split_k(n, k, expected):
7880
assert select_nvfp4_tma_split_k(n, k) == expected
7981

8082

83+
@pytest.mark.parametrize(
84+
("n", "k", "block_n", "num_compute_warps", "grid_scheduler", "split_k", "expected"),
85+
[
86+
(16384, 6144, 8, 4, GridScheduler.STATIC, 1, True),
87+
(32768, 8192, 8, 4, GridScheduler.STATIC, 1, True),
88+
(14336, 8192, 8, 4, GridScheduler.STATIC, 1, False),
89+
(16384, 12288, 8, 4, GridScheduler.STATIC, 1, False),
90+
(16384, 8192, 16, 4, GridScheduler.STATIC, 1, False),
91+
(16384, 8192, 8, 2, GridScheduler.STATIC, 1, False),
92+
(16384, 8192, 8, 4, GridScheduler.PERSISTENT, 1, False),
93+
(16384, 8192, 8, 4, GridScheduler.STATIC, 2, False),
94+
],
95+
)
96+
def test_select_nvfp4_tma_stage_weight_scales(
97+
n, k, block_n, num_compute_warps, grid_scheduler, split_k, expected
98+
):
99+
"""Stage physical scale subsets only in the measured B200 regime."""
100+
if torch.cuda.get_device_capability() != (10, 0):
101+
expected = False
102+
assert (
103+
select_nvfp4_tma_stage_weight_scales(
104+
n,
105+
k,
106+
block_n,
107+
num_compute_warps,
108+
grid_scheduler,
109+
split_k,
110+
)
111+
is expected
112+
)
113+
114+
81115
def pack_fp4(codes: torch.Tensor) -> torch.Tensor:
82116
"""Pack low-nibble-first E2M1 codes into the PyTorch FP4 shell dtype."""
83117
packed = codes[:, 0::2] | (codes[:, 1::2] << 4)
@@ -178,6 +212,23 @@ def test_nvfp4_tma_scale_layout_and_paired_loads(block_n, num_compute_warps):
178212
torch.testing.assert_close(actual, expected, atol=2.0, rtol=0.05)
179213

180214

215+
def test_nvfp4_tma_compact_scale_tma_matches_blocked_layout():
216+
"""Match exact scales across physical 128-row atoms with compact TMA staging."""
217+
q_input, weight, input_scale, weight_scale, expected, _ = make_case(264, 6144)
218+
actual = nvfp4_tma_gemv(
219+
q_input,
220+
weight,
221+
input_scale,
222+
weight_scale,
223+
block_n=8,
224+
num_stages=3,
225+
num_compute_warps=4,
226+
stage_weight_scales=True,
227+
)
228+
torch.cuda.synchronize()
229+
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
230+
231+
181232
def test_nvfp4_tma_fp16_partial_reduction_preserves_extreme_values():
182233
"""Keep exact FP16 partial sums before accumulating them in FP32."""
183234
n, k = 128, 2048
@@ -374,8 +425,11 @@ def test_nvfp4_tma_split_k_accepts_scalar_aligned_workspace():
374425
torch.testing.assert_close(output, expected, atol=2.0, rtol=0.05)
375426

376427

377-
@pytest.mark.parametrize(("k", "split_k", "num_stages"), [(8192, 2, 2), (16384, 4, 3)])
378-
def test_nvfp4_tma_split_k_cuda_graph(k, split_k, num_stages):
428+
@pytest.mark.parametrize(
429+
("k", "split_k", "num_stages", "stage_weight_scales"),
430+
[(8192, 2, 2, True), (16384, 4, 3, False)],
431+
)
432+
def test_nvfp4_tma_split_k_cuda_graph(k, split_k, num_stages, stage_weight_scales):
379433
"""Reduce parallel K partitions in FP32 before the final BF16 conversion."""
380434
n = 128
381435
q_input, weight, input_scale, weight_scale, expected, _ = make_case(n, k)
@@ -386,6 +440,7 @@ def test_nvfp4_tma_split_k_cuda_graph(k, split_k, num_stages):
386440
"num_compute_warps": 4,
387441
"num_stages": num_stages,
388442
"split_k": split_k,
443+
"stage_weight_scales": stage_weight_scales,
389444
"output": output,
390445
"partial_output": partial_output,
391446
}
@@ -398,6 +453,24 @@ def test_nvfp4_tma_split_k_cuda_graph(k, split_k, num_stages):
398453
torch.testing.assert_close(output, expected, atol=2.0, rtol=0.05)
399454

400455

456+
def test_nvfp4_tma_compact_scale_tma_persistent_reuse():
457+
"""Reuse compact scale stages across persistent output tiles."""
458+
q_input, weight, input_scale, weight_scale, expected, _ = make_case(256, 2048)
459+
actual = nvfp4_tma_gemv(
460+
q_input,
461+
weight,
462+
input_scale,
463+
weight_scale,
464+
block_n=8,
465+
num_compute_warps=4,
466+
grid_scheduler=GridScheduler.PERSISTENT,
467+
num_persistent_ctas=4,
468+
stage_weight_scales=True,
469+
)
470+
torch.cuda.synchronize()
471+
torch.testing.assert_close(actual, expected, atol=2.0, rtol=0.05)
472+
473+
401474
def test_nvfp4_tma_persistent_cuda_graph():
402475
"""Replay persistent NVFP4 GEMV into caller-owned output."""
403476
q_input, weight, input_scale, weight_scale, expected, _ = make_case(256, 2048)

transformer_nuggets/cute/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"select_nvfp4_tma_compute_warps",
3939
"select_nvfp4_tma_config",
4040
"select_nvfp4_tma_split_k",
41+
"select_nvfp4_tma_stage_weight_scales",
4142
}
4243

4344
_SYMMETRIC_MEMORY_EXPORTS = {

0 commit comments

Comments
 (0)