-
Notifications
You must be signed in to change notification settings - Fork 9.2k
Add dsv3 router gemm benchmark on blackwell #17707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3484058
d1b7e72
dd79d06
8691db2
c1029b9
fe0dc0a
fb4533e
b1ba905
0e856ff
42bc2e9
1c3d987
2736f14
bfcbaeb
1eefecc
9d2797a
02b430f
41b9cbd
f668bc5
3b9a3d5
0cb4afa
77ff69f
8356ed3
6f29925
95fa9dc
1c91067
7d45a2c
52e64d8
a9b5084
78da480
5062d3c
cb82f32
1cea3fb
0b51493
977f806
b8f1506
c99c9b0
43e1d09
710efba
dfe9e4a
64eb56b
5903334
4a35330
72b2f4c
f40fde4
41f081f
d60330a
446b052
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| import argparse | ||
| import os | ||
| from typing import List | ||
|
|
||
| import torch | ||
| import triton | ||
| from flashinfer.gemm import mm_M1_16_K7168_N256 | ||
| from sgl_kernel import dsv3_router_gemm | ||
|
|
||
| N = 256 | ||
| K = 7168 | ||
|
|
||
|
|
||
| def create_benchmark_configs(tp_sizes: List[int]): | ||
| configs = [] | ||
| for tp_size in tp_sizes: | ||
| for m in range(1, 17): | ||
| configs.append((m, N, K, tp_size)) | ||
| return configs | ||
|
|
||
|
|
||
| def dsv3_router_gemm_flashinfer( | ||
| hidden_states: torch.Tensor, | ||
| router_weights: torch.Tensor, | ||
| ): | ||
| """Flashinfer implementation of dsv3 router gemm""" | ||
| output = torch.empty( | ||
| hidden_states.shape[0], | ||
| router_weights.shape[0], | ||
| device="cuda", | ||
| dtype=torch.float32, | ||
| ) | ||
| mm_M1_16_K7168_N256( | ||
| hidden_states, router_weights.t(), output, launch_with_pdl=args.use_pdl | ||
| ) | ||
| return output | ||
|
|
||
|
|
||
| def dsv3_router_gemm_sgl( | ||
| hidden_states: torch.Tensor, | ||
| router_weights: torch.Tensor, | ||
| ): | ||
| """SGLang implementation of dsv3 router gemm""" | ||
| output = dsv3_router_gemm( | ||
| hidden_states, | ||
| router_weights, | ||
| out_dtype=torch.float32, | ||
| ) | ||
| return output | ||
|
|
||
|
|
||
| def check_accuracy(a, b, atol, rtol, percent): | ||
| """Unified accuracy checking function with detailed error reporting.""" | ||
| if not torch.isfinite(a).all(): | ||
| print("Non-finite values in reference output") | ||
| return False | ||
| if not torch.isfinite(b).all(): | ||
| print("Non-finite values in actual output") | ||
| return False | ||
| assert a.shape == b.shape, f"Shape mismatch: {a.shape} vs {b.shape}" | ||
|
|
||
| close = torch.isclose(a, b, atol=atol, rtol=rtol) | ||
| match_ratio = close.float().mean() | ||
| if match_ratio >= percent: | ||
| return True | ||
|
|
||
| mismatch_percent = 1.0 - match_ratio.item() | ||
| if mismatch_percent > 1 - percent: | ||
| print( | ||
| f"Mismatch percentage is {mismatch_percent:.4f} for rtol {rtol} " | ||
| f"(threshold: {1 - percent:.4f})" | ||
| ) | ||
| return False | ||
|
|
||
|
|
||
| def calculate_diff(m: int, n: int, k: int): | ||
| hidden_states = torch.randn((m, k), device="cuda", dtype=torch.bfloat16) | ||
| router_weights = torch.randn((n, k), device="cuda", dtype=torch.bfloat16) | ||
|
|
||
| out_flashinfer = dsv3_router_gemm_flashinfer( | ||
| hidden_states.clone(memory_format=torch.contiguous_format), | ||
| router_weights.clone(memory_format=torch.contiguous_format), | ||
| ) | ||
|
|
||
| out_sgl = dsv3_router_gemm_sgl( | ||
| hidden_states.clone(memory_format=torch.contiguous_format), | ||
| router_weights.clone(memory_format=torch.contiguous_format), | ||
| ) | ||
|
|
||
| print(f"Shape m={m}, n={n}, k={k}:") | ||
| print(f"Using PDL={args.use_pdl}") | ||
| print(f"Flashinfer output: {out_flashinfer[0, 0:5]}") | ||
| print(f"SGLang output: {out_sgl[0, 0:5]}") | ||
|
|
||
| flashinfer_sgl_match = check_accuracy(out_flashinfer, out_sgl, 0.1, 0.6, 0.95) | ||
| print("Correctness check:") | ||
| print(f" - Flashinfer vs SGLang: {'✅' if flashinfer_sgl_match else '❌'}") | ||
|
|
||
|
|
||
| def _benchmark(m, n, k, tp_size, provider): | ||
| print(f"Shape (m={m}, n={n}, k={k}, tp={tp_size}), Provider: {provider}") | ||
| hidden_states = torch.randn( | ||
| (m, k), device="cuda", dtype=torch.bfloat16 | ||
| ).contiguous() | ||
| router_weights = torch.randn( | ||
| (n, k), device="cuda", dtype=torch.bfloat16 | ||
| ).contiguous() | ||
|
|
||
| quantiles = [0.5, 0.2, 0.8] | ||
|
|
||
| if provider == "sglang": | ||
| ms, min_ms, max_ms = triton.testing.do_bench( | ||
| lambda: dsv3_router_gemm_sgl( | ||
| hidden_states.clone(memory_format=torch.contiguous_format), | ||
| router_weights.clone(memory_format=torch.contiguous_format), | ||
| ), | ||
| quantiles=quantiles, | ||
| ) | ||
| elif provider == "flashinfer": | ||
| ms, min_ms, max_ms = triton.testing.do_bench( | ||
| lambda: dsv3_router_gemm_flashinfer( | ||
| hidden_states.clone(memory_format=torch.contiguous_format), | ||
| router_weights.clone(memory_format=torch.contiguous_format), | ||
| ), | ||
| quantiles=quantiles, | ||
| ) | ||
|
|
||
| # Calculate TFLOPS | ||
| flops = 2 * m * n * k # multiply-adds | ||
| tflops = flops / (ms * 1e-3) / 1e12 | ||
|
|
||
| # Print shape-specific results with TFLOPS | ||
| print(f"Time: {ms*1000:.2f} us, TFLOPS: {tflops:.2f}") | ||
| return ms, max_ms, min_ms | ||
|
Comment on lines
+111
to
+134
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handling of timing results from I suggest refactoring to use clearer variable names and a more direct data flow. This makes the code easier to understand and less prone to bugs. The suggested change also removes the redundant You can refactor def benchmark(cfg_id, provider):
m, n, k, tp_size, launch_with_pdl = all_configs[cfg_id]
median_ms, min_ms, max_ms = _benchmark(m, n, k, tp_size, launch_with_pdl, provider)
return median_ms * 1000, min_ms * 1000, max_ms * 1000 if provider == "sglang":
median_ms, min_ms, max_ms = triton.testing.do_bench(
lambda: dsv3_router_gemm_sgl(
hidden_states.clone(),
router_weights.clone(),
),
quantiles=quantiles,
)
elif provider == "flashinfer":
median_ms, min_ms, max_ms = triton.testing.do_bench(
lambda: dsv3_router_gemm_flashinfer(
hidden_states.clone(),
router_weights.clone(),
launch_with_pdl,
),
quantiles=quantiles,
)
# Calculate TFLOPS
flops = 2 * m * n * k # multiply-adds
tflops = flops / (median_ms * 1e-3) / 1e12
# Print shape-specific results with TFLOPS
print(f"Time: {median_ms*1000:.2f} us, TFLOPS: {tflops:.2f}")
return median_ms, min_ms, max_ms |
||
|
|
||
|
|
||
| def get_benchmark_plot_friendly(tp_sizes): | ||
| all_configs = create_benchmark_configs(tp_sizes) | ||
| x_vals = list(range(len(all_configs))) | ||
|
|
||
| @triton.testing.perf_report( | ||
| triton.testing.Benchmark( | ||
| x_names=["cfg_id"], | ||
| x_vals=x_vals, | ||
| line_arg="provider", | ||
| line_vals=["sglang", "flashinfer"], | ||
| line_names=["SGLang", "Flashinfer"], | ||
| styles=[("blue", "-"), ("red", "-")], | ||
| ylabel="us", | ||
| plot_name=f"fp8-gemm-performance-comparison-tp-{"-".join(str(tp) for tp in tp_sizes)}", | ||
| args={}, | ||
| ) | ||
| ) | ||
| def benchmark(cfg_id, provider): | ||
| m, n, k, tp_size = all_configs[cfg_id] | ||
| ms, min_ms, max_ms = _benchmark(m, n, k, tp_size, provider) | ||
| return ms * 1000, max_ms * 1000, min_ms * 1000 # convert to ms | ||
|
|
||
| return benchmark | ||
|
|
||
|
|
||
| def get_benchmark(tp_sizes): | ||
| all_configs = create_benchmark_configs(tp_sizes) | ||
|
|
||
| @triton.testing.perf_report( | ||
| triton.testing.Benchmark( | ||
| x_names=[ | ||
| "m", | ||
| "n", | ||
| "k", | ||
| "tp_size", | ||
| ], | ||
| x_vals=[list(config) for config in all_configs], | ||
| line_arg="provider", | ||
| line_vals=["sglang", "flashinfer"], | ||
| line_names=["SGLang", "Flashinfer"], | ||
| styles=[("blue", "-"), ("red", "-")], | ||
| ylabel="us", | ||
| plot_name=f"fp8-gemm-performance-comparison-tp-{"-".join(str(tp) for tp in tp_sizes)}", | ||
| args={}, | ||
| ) | ||
| ) | ||
| def benchmark(m, n, k, tp_size, provider): | ||
| ms, min_ms, max_ms = _benchmark(m, n, k, tp_size, provider) | ||
| return ms * 1000, max_ms * 1000, min_ms * 1000 # convert to ms | ||
|
|
||
| return benchmark | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| if not torch.cuda.is_available() or torch.cuda.get_device_capability()[0] != 10: | ||
| print("Skipping benchmark because the device is not supported") | ||
| exit(0) | ||
|
|
||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument( | ||
| "--save-path", | ||
| type=str, | ||
| default="./configs/benchmark_ops/dsv3_router_gemm/", | ||
| help="Path to save dsv3 router gemm benchmark results", | ||
| ) | ||
| parser.add_argument( | ||
| "--run-correctness", | ||
| action="store_true", | ||
| default=True, | ||
| help="Whether to run correctness test", | ||
|
Comment on lines
+203
to
+206
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The argument To make this flag work as intended (i.e., run correctness tests only when the flag is present), you should remove "--run-correctness",
action="store_true",
help="Whether to run correctness test", |
||
| ) | ||
| parser.add_argument( | ||
| "--tp-sizes", | ||
| type=int, | ||
| nargs="+", | ||
| default=[1], | ||
| help="List of tensor parallelism sizes to benchmark", | ||
| ) | ||
| parser.add_argument( | ||
| "--plot-friendly", | ||
| action="store_true", | ||
| default=False, | ||
| help="Plot x axis as the config index instead of the m", | ||
| ) | ||
| parser.add_argument( | ||
| "--use-pdl", | ||
| action="store_true", | ||
| default=False, | ||
| help="Use PDL if true.", | ||
| ) | ||
| args = parser.parse_args() | ||
|
|
||
| # Set random seed for reproducibility | ||
| torch.manual_seed(0) | ||
| torch.cuda.manual_seed(0) | ||
|
|
||
| if args.use_pdl: | ||
| os.environ["TRTLLM_ENABLE_PDL"] = "1" | ||
|
|
||
| # Run correctness tests on a few examples | ||
| if args.run_correctness: | ||
| print("Running correctness tests...") | ||
| for m, n, k, _ in create_benchmark_configs(args.tp_sizes): | ||
| calculate_diff(m, n, k) | ||
|
|
||
| # Get the benchmark function with the specified tp_size | ||
| benchmark = ( | ||
| get_benchmark_plot_friendly(args.tp_sizes) | ||
| if args.plot_friendly | ||
| else get_benchmark(args.tp_sizes) | ||
| ) | ||
|
Comment on lines
+243
to
+247
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line uses a ternary operator inside parentheses to select the benchmark function. While it works, a standard if args.plot_friendly:
benchmark = get_benchmark_plot_friendly(args.tp_sizes)
else:
benchmark = get_benchmark(args.tp_sizes) |
||
|
|
||
| print(f"Running performance benchmark for TP sizes = {args.tp_sizes}...") | ||
| benchmark.run(print_data=True, save_path=args.save_path) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition
if mismatch_percent > 1 - percent:is redundant. If the code reaches this point, it meansmatch_ratio < percent, which is equivalent tomismatch_percent > 1 - percent. This condition will always be true. You can simplify the logic by removing thisifstatement, making the code easier to understand.