fp4-fused-ops / benchmarks /benchmark.py
liangsu9988's picture
Uploaded using `kernel-builder`.
c77ac5a verified
Raw
History Blame
6.4 kB
#!/usr/bin/env python3
"""Benchmark fp4-fused-ops."""
from __future__ import annotations
import argparse
import importlib.util
import json
import sys
from dataclasses import asdict, dataclass
from pathlib import Path
import torch
ROOT = Path(__file__).resolve().parents[2]
TEST_FILE = ROOT / "fp4-fused-ops" / "tests" / "test_fp4_fused_ops.py"
@dataclass
class BenchResult:
case: str
rows: int
dim: int
workload: str
reference_us: float | None
flashrt_us: float
speedup: float | None
status: str
def load_test_module():
spec = importlib.util.spec_from_file_location("fp4_fused_ops_test_helpers", TEST_FILE)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot load helpers from {TEST_FILE}")
module = importlib.util.module_from_spec(spec)
sys.modules["fp4_fused_ops_test_helpers"] = module
spec.loader.exec_module(module)
return module
def measure(fn, warmup: int, iters: int) -> float:
for _ in range(warmup):
fn()
torch.cuda.synchronize()
start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)
start.record()
for _ in range(iters):
fn()
end.record()
torch.cuda.synchronize()
return float(start.elapsed_time(end) * 1000.0 / iters)
def bench_case(helpers, ops, rows: int, dim: int, warmup: int, iters: int) -> list[BenchResult]:
make_fp16 = helpers.make_fp16
results: list[BenchResult] = []
residual = make_fp16((rows, dim), seed=100 + rows + dim)
x = make_fp16((rows, dim), seed=200 + rows + dim)
packed, sfa = ops.alloc(rows, dim)
if dim <= 2048:
residual_v1 = residual.clone()
packed_v1, sfa_v1 = ops.alloc(rows, dim)
ref_us = measure(
lambda: ops.residual_add_rms_norm_fp4_sfa_fp16(residual_v1.copy_(residual), x, packed_v1, sfa_v1),
warmup,
iters,
)
else:
ref_us = None
residual_v2 = residual.clone()
f3_us = measure(
lambda: ops.residual_add_rms_norm_fp4_sfa_v2_fp16(residual_v2.copy_(residual), x, packed, sfa),
warmup,
iters,
)
results.append(
BenchResult(
case=f"rows{rows}_dim{dim}",
rows=rows,
dim=dim,
workload="residual_add_rms_norm_fp4_sfa_v2",
reference_us=ref_us,
flashrt_us=f3_us,
speedup=(ref_us / f3_us) if ref_us else None,
status="v1 reference" if ref_us else "v2 only; v1 rejects this dim",
)
)
merged = make_fp16((rows, dim * 2), seed=300 + rows + dim)
packed_v1, sfa_v1 = ops.alloc(rows, dim)
packed_v2, sfa_v2 = ops.alloc(rows, dim)
ref_us = measure(lambda: ops.silu_mul_fp4_sfa_fp16(merged, packed_v1, sfa_v1), warmup, iters)
f4_us = measure(lambda: ops.silu_mul_fp4_sfa_v2_fp16(merged, packed_v2, sfa_v2), warmup, iters)
results.append(
BenchResult(
case=f"rows{rows}_dim{dim}",
rows=rows,
dim=dim,
workload="silu_mul_fp4_sfa_v2",
reference_us=ref_us,
flashrt_us=f4_us,
speedup=ref_us / f4_us,
status="v1 reference",
)
)
inv_s = (torch.rand((dim,), device="cuda") * 0.25 + 0.875).to(torch.float16).contiguous()
awq_us = measure(lambda: ops.silu_mul_mul_fp4_sfa_v2_fp16(merged, inv_s, packed_v2, sfa_v2), warmup, iters)
results.append(
BenchResult(
case=f"rows{rows}_dim{dim}",
rows=rows,
dim=dim,
workload="silu_mul_mul_fp4_sfa_v2",
reference_us=None,
flashrt_us=awq_us,
speedup=None,
status="fused AWQ producer latency",
)
)
gate_packed, gate_sfa = ops.alloc(rows, dim)
up_packed, up_sfa = ops.alloc(rows, dim)
out_packed, out_sfa = ops.alloc(rows, dim)
ops.silu_mul_fp4_sfa_v2_fp16(make_fp16((rows, dim * 2), seed=400 + rows + dim), gate_packed, gate_sfa)
ops.silu_mul_fp4_sfa_v2_fp16(make_fp16((rows, dim * 2), seed=500 + rows + dim), up_packed, up_sfa)
two_us = measure(
lambda: ops.silu_mul_two_fp4_to_fp4(gate_packed, gate_sfa, up_packed, up_sfa, out_packed, out_sfa),
warmup,
iters,
)
two_mul_us = measure(
lambda: ops.silu_mul_two_mul_fp4_to_fp4(gate_packed, gate_sfa, up_packed, up_sfa, inv_s, out_packed, out_sfa),
warmup,
iters,
)
results.append(
BenchResult(
case=f"rows{rows}_dim{dim}",
rows=rows,
dim=dim,
workload="silu_mul_two_fp4_to_fp4",
reference_us=None,
flashrt_us=two_us,
speedup=None,
status="FP4-to-FP4 combiner latency",
)
)
results.append(
BenchResult(
case=f"rows{rows}_dim{dim}",
rows=rows,
dim=dim,
workload="silu_mul_two_mul_fp4_to_fp4",
reference_us=None,
flashrt_us=two_mul_us,
speedup=None,
status="FP4-to-FP4 AWQ combiner latency",
)
)
return results
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--mode", choices=["smoke", "headline"], default="headline")
parser.add_argument("--warmup", type=int, default=20)
parser.add_argument("--iterations", type=int, default=100)
parser.add_argument("--json-out", default=None)
args = parser.parse_args()
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required")
helpers = load_test_module()
ops = helpers.load_source_ops()
shapes = [(1, 1024), (10, 2048)] if args.mode == "smoke" else [(1, 1024), (10, 2048), (64, 2048), (128, 4096)]
results: list[BenchResult] = []
for rows, dim in shapes:
results.extend(bench_case(helpers, ops, rows, dim, args.warmup, args.iterations))
payload = {
"mode": args.mode,
"device": torch.cuda.get_device_name(),
"torch": torch.__version__,
"results": [asdict(item) for item in results],
}
print(json.dumps(payload, indent=2))
if args.json_out:
out = Path(args.json_out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(payload, indent=2) + "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())