Skip to content

Commit 20fbdc1

Browse files
committed
Add MXFP8 profiling CLI
1 parent 61764a4 commit 20fbdc1

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

test/test_mxfp8_tma.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
import torch
3+
from typer.testing import CliRunner
34

45

56
if not torch.cuda.is_available():
@@ -13,6 +14,7 @@
1314
get_mxfp8_tma_gemv,
1415
mxfp8_tma_gemv,
1516
)
17+
from transformer_nuggets.cute.mxfp8_tma import app
1618
from transformer_nuggets.cute.profiler import profile_session
1719
from transformer_nuggets.cute.profiler.host import decode_events
1820
except ImportError:
@@ -155,6 +157,27 @@ def test_mxfp8_tma_gemv_combines_cancelling_scales(input_byte, weight_byte):
155157
torch.testing.assert_close(actual, torch.full_like(actual, k), rtol=0, atol=0)
156158

157159

160+
def test_mxfp8_tma_cli_writes_pftrace(tmp_path):
161+
"""Run the module CLI and write a nonempty native Perfetto trace."""
162+
trace_path = tmp_path / "mxfp8_tma.pftrace"
163+
result = CliRunner().invoke(
164+
app,
165+
[
166+
"--n",
167+
"128",
168+
"--k",
169+
"2048",
170+
"--block-n",
171+
"4",
172+
"--output",
173+
str(trace_path),
174+
],
175+
)
176+
177+
assert result.exit_code == 0, result.output
178+
assert trace_path.stat().st_size > 0
179+
180+
158181
def test_mxfp8_tma_gemv_preserves_nan_scale():
159182
"""Decode the reserved E8M0 byte as NaN rather than infinity."""
160183
k = 2048

transformer_nuggets/cute/mxfp8_tma.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import operator
66
from functools import cache
7+
from pathlib import Path
78

89
import torch
10+
import typer
911

1012
import cutlass
1113
import cutlass.cute as cute
@@ -15,6 +17,7 @@
1517

1618
from transformer_nuggets.cute.base import CuteOp
1719
from transformer_nuggets.cute.cache import compile_tvm_ffi_and_cache
20+
from transformer_nuggets.cute.profiler import group_by_unit, profile_session
1821
from transformer_nuggets.cute.profiler.ops import profile_region
1922
from transformer_nuggets.cute.utils import fake_stream, make_fake_compact_tensor
2023

@@ -582,3 +585,86 @@ def mxfp8_tma_gemv(
582585
output,
583586
profile_buffer,
584587
)
588+
589+
590+
def quantize_mxfp8_tensor(value: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
591+
"""Create E4M3 values and raw E8M0 block scales for profiling inputs."""
592+
blocks = value.float().reshape(value.shape[0], -1, 32)
593+
max_abs = blocks.abs().amax(dim=-1).clamp_min(torch.finfo(torch.float32).tiny)
594+
exponent = torch.ceil(torch.log2(max_abs / 448.0)).clamp(-126, 127)
595+
scale = torch.exp2(exponent).unsqueeze(-1)
596+
quantized = (blocks / scale).clamp(-448, 448).to(torch.float8_e4m3fn)
597+
return quantized.reshape_as(value), (exponent + 127).to(torch.uint8)
598+
599+
600+
app = typer.Typer(help="Run the MXFP8 TMA GEMV with labeled intra-kernel profiling.")
601+
602+
603+
@app.command()
604+
def profile_mxfp8_tma(
605+
n: int = 4096,
606+
k: int = 8192,
607+
block_n: int = 4,
608+
num_stages: int = 2,
609+
output: Path = Path("mxfp8_tma.pftrace"),
610+
seed: int = 0,
611+
warmups: int = 1,
612+
device: str = "cuda",
613+
) -> None:
614+
"""Generate a Perfetto trace for one warm MXFP8 TMA GEMV launch."""
615+
torch_device = torch.device(device)
616+
if torch_device.type != "cuda" or not torch.cuda.is_available():
617+
raise typer.BadParameter("device must name an available CUDA device")
618+
if warmups < 0:
619+
raise typer.BadParameter("warmups must be non-negative")
620+
621+
torch.manual_seed(seed)
622+
q_input, input_scale = quantize_mxfp8_tensor(
623+
torch.randn((1, k), dtype=torch.bfloat16, device=torch_device)
624+
)
625+
weight, weight_scale = quantize_mxfp8_tensor(
626+
torch.randn((n, k), dtype=torch.bfloat16, device=torch_device)
627+
)
628+
op = get_mxfp8_tma_gemv(
629+
n,
630+
k,
631+
block_n,
632+
num_stages,
633+
enable_profiling=True,
634+
)
635+
output.parent.mkdir(parents=True, exist_ok=True)
636+
637+
with profile_session(
638+
max_events_per_unit=op.max_profile_events_per_cta,
639+
num_units=(op.num_profile_units, "CTA"),
640+
tag_names=list(op.profile_tags),
641+
trace_path=str(output),
642+
device=torch_device,
643+
post_process_events=group_by_unit,
644+
) as (prof, _):
645+
result = torch.empty((1, n), dtype=torch.bfloat16, device=torch_device)
646+
for _ in range(warmups):
647+
op.interface(
648+
q_input,
649+
weight,
650+
input_scale,
651+
weight_scale,
652+
output=result,
653+
profile_buffer=prof.tensor,
654+
)
655+
torch.cuda.synchronize(torch_device)
656+
prof.tensor.zero_()
657+
op.interface(
658+
q_input,
659+
weight,
660+
input_scale,
661+
weight_scale,
662+
output=result,
663+
profile_buffer=prof.tensor,
664+
)
665+
666+
typer.echo(f"Wrote {output.resolve()}")
667+
668+
669+
if __name__ == "__main__":
670+
app()

0 commit comments

Comments
 (0)