main
py 434 lines 13.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # ruff: noqa: E501
4 import time
5
6 import torch
7
8 from vllm import _custom_ops as ops
9 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
10 per_token_group_quant_fp8,
11 w8a8_triton_block_scaled_mm,
12 )
13 from vllm.triton_utils import triton
14 from vllm.utils.deep_gemm import (
15 calc_diff,
16 fp8_gemm_nt,
17 per_block_cast_to_fp8,
18 )
19 from vllm.utils.torch_utils import set_random_seed
20
21
22 def benchmark_shape(
23 m: int,
24 n: int,
25 k: int,
26 warmup: int = 100,
27 repeat: int = 10000,
28 verbose: bool = False,
29 ) -> dict:
30 """Benchmark all implementations for a specific (m, n, k) shape."""
31 if verbose:
32 print(f"\n=== Benchmarking shape: m={m}, n={n}, k={k} ===")
33
34 # Create test tensors
35 A = torch.randn((m, k), device="cuda", dtype=torch.bfloat16)
36 B = torch.randn((n, k), device="cuda", dtype=torch.bfloat16)
37
38 # Reference result in BF16
39 torch.accelerator.synchronize()
40 C_ref = A @ B.t()
41
42 # Pre-quantize B for all implementations
43 # (weights can be pre-quantized offline)
44 B_deepgemm, B_scale_deepgemm = per_block_cast_to_fp8(B, [128, 128], use_ue8m0=True)
45 B_vllm, B_scale_vllm = per_block_cast_to_fp8(B, [128, 128], use_ue8m0=True)
46
47 # Block size configuration
48 block_size = [128, 128]
49
50 # Pre-quantize A for all implementations
51 A_deepgemm, A_scale_deepgemm = per_token_group_quant_fp8(
52 A, block_size[1], column_major_scales=True, tma_aligned_scales=True
53 )
54 C_deepgemm = torch.empty((m, n), device="cuda", dtype=torch.bfloat16)
55 A_vllm, A_scale_vllm = per_token_group_quant_fp8(A, block_size[1])
56 A_vllm_cutlass, A_scale_vllm_cutlass = per_token_group_quant_fp8(
57 A, block_size[1], column_major_scales=True
58 )
59
60 # === DeepGEMM Implementation ===
61 def deepgemm_gemm():
62 fp8_gemm_nt(
63 (A_deepgemm, A_scale_deepgemm), (B_deepgemm, B_scale_deepgemm), C_deepgemm
64 )
65 return C_deepgemm
66
67 # === vLLM Triton Implementation ===
68 def vllm_triton_gemm():
69 return w8a8_triton_block_scaled_mm(
70 A_vllm,
71 B_vllm,
72 A_scale_vllm,
73 B_scale_vllm,
74 block_size,
75 output_dtype=torch.bfloat16,
76 )
77
78 # === vLLM CUTLASS Implementation ===
79 def vllm_cutlass_gemm():
80 return ops.cutlass_scaled_mm(
81 A_vllm_cutlass,
82 B_vllm.T,
83 scale_a=A_scale_vllm_cutlass,
84 scale_b=B_scale_vllm.T,
85 out_dtype=torch.bfloat16,
86 )
87
88 # Run correctness check first
89 if verbose:
90 print("Running correctness check...")
91 C_deepgemm = deepgemm_gemm()
92 C_vllm_triton = vllm_triton_gemm()
93 C_vllm_cutlass = vllm_cutlass_gemm()
94
95 deepgemm_diff = calc_diff(C_deepgemm, C_ref)
96 vllm_triton_diff = calc_diff(C_vllm_triton, C_ref)
97 vllm_cutlass_diff = calc_diff(C_vllm_cutlass, C_ref)
98
99 if verbose:
100 print(f"DeepGEMM vs Reference difference: {deepgemm_diff:.6f}")
101 print(f"vLLM Triton vs Reference difference: {vllm_triton_diff:.6f}")
102 print(f"vLLM CUTLASS vs Reference difference: {vllm_cutlass_diff:.6f}")
103 print(
104 "vLLM Triton vs DeepGEMM difference: "
105 f"{calc_diff(C_vllm_triton, C_deepgemm):.6f}"
106 )
107 print(
108 "vLLM CUTLASS vs DeepGEMM difference: "
109 f"{calc_diff(C_vllm_cutlass, C_deepgemm):.6f}"
110 )
111
112 # Benchmark implementations
113 implementations = {
114 "DeepGEMM": deepgemm_gemm,
115 "vLLM Triton": vllm_triton_gemm,
116 "vLLM CUTLASS": vllm_cutlass_gemm,
117 }
118
119 benchmark_results = {"shape": {"m": m, "n": n, "k": k}, "implementations": {}}
120
121 for name, func in implementations.items():
122 # Warmup
123 for _ in range(warmup):
124 func()
125 torch.accelerator.synchronize()
126
127 # Timing loop
128 torch.accelerator.synchronize()
129 start = time.time()
130 for _ in range(repeat):
131 func()
132 torch.accelerator.synchronize()
133 end = time.time()
134
135 # Calculate timing and TFLOPS
136 avg_time_ms = (end - start) / repeat * 1000
137 avg_time_us = avg_time_ms * 1000
138 tflops = 2 * m * n * k / (avg_time_ms * 1e-3) / 1e12
139 gb_s = (m * k + k * n + m * n * 2) / 1e9 / (avg_time_ms * 1e-3)
140
141 benchmark_results["implementations"][name] = {
142 "time_ms": avg_time_ms,
143 "time_us": avg_time_us,
144 "tflops": tflops,
145 "gb_s": gb_s,
146 "diff": {
147 "DeepGEMM": 0.0
148 if name == "DeepGEMM"
149 else calc_diff(func(), C_deepgemm),
150 "Reference": deepgemm_diff
151 if name == "DeepGEMM"
152 else (vllm_triton_diff if name == "vLLM Triton" else vllm_cutlass_diff),
153 },
154 }
155
156 if verbose:
157 print(f"{name}: {avg_time_ms:.3f} ms, {tflops:.2f} TFLOPS, {gb_s:.2f} GB/s")
158
159 # Calculate speedups
160 baseline = benchmark_results["implementations"]["DeepGEMM"]["time_ms"]
161 for name, data in benchmark_results["implementations"].items():
162 if name != "DeepGEMM":
163 speedup = baseline / data["time_ms"]
164 benchmark_results["implementations"][name]["speedup_vs_deepgemm"] = speedup
165 if verbose:
166 print(
167 f"DeepGEMM is {1 / speedup:.2f}x "
168 f"{'faster' if 1 / speedup > 1 else 'slower'} than {name}"
169 )
170
171 vllm_triton_time = benchmark_results["implementations"]["vLLM Triton"]["time_ms"]
172 vllm_cutlass_time = benchmark_results["implementations"]["vLLM CUTLASS"]["time_ms"]
173 cutlass_vs_triton = vllm_triton_time / vllm_cutlass_time
174 benchmark_results["implementations"]["vLLM CUTLASS"]["speedup_vs_triton"] = (
175 cutlass_vs_triton
176 )
177 if verbose:
178 print(
179 f"vLLM CUTLASS is {cutlass_vs_triton:.2f}x "
180 f"{'faster' if cutlass_vs_triton > 1 else 'slower'} than vLLM Triton"
181 )
182
183 return benchmark_results
184
185
186 def format_table_row(values, widths):
187 """Format a row with specified column widths."""
188 return "| " + " | ".join(f"{val:{w}}" for val, w in zip(values, widths)) + " |"
189
190
191 def print_table(headers, rows, title=None):
192 """Print a table with headers and rows."""
193 if title:
194 print(f"\n{title}")
195
196 # Calculate column widths based on headers and data
197 widths = [
198 max(len(str(h)), max(len(str(row[i])) for row in rows))
199 for i, h in enumerate(headers)
200 ]
201
202 # Create separator line
203 separator = "+-" + "-+-".join("-" * w for w in widths) + "-+"
204
205 # Print table
206 print(separator)
207 print(format_table_row(headers, widths))
208 print(separator)
209 for row in rows:
210 print(format_table_row(row, widths))
211 print(separator)
212
213
214 def format_speedup(value):
215 """Format speedup value with indicator if it's faster or slower."""
216 return f"{value:.2f}x {'faster' if value > 1.0 else 'slower'}"
217
218
219 def run_benchmarks(verbose: bool = False):
220 """Run benchmarks for a set of common shapes."""
221 print("===== STARTING FP8 GEMM BENCHMARK =====")
222
223 # Make sure we're using the GPU
224 if not torch.cuda.is_available():
225 print("CUDA not available! Tests require GPU.")
226 return
227
228 # Print system information
229 print(f"PyTorch version: {torch.__version__}")
230 print(f"CUDA version: {torch.version.cuda}")
231 print(f"Triton version: {triton.__version__}")
232 print(f"Using device: {torch.cuda.get_device_name()}")
233
234 # Enable TF32 for better performance
235 torch.backends.cuda.matmul.allow_tf32 = True
236 torch.backends.cudnn.allow_tf32 = True
237
238 # Set seeds for reproducibility
239 set_random_seed(42)
240 # Define benchmark shapes (m, n, k)
241 shapes = [
242 (8, 4096, 7168),
243 (8, 7168, 18432),
244 (8, 18432, 7168),
245 (64, 4096, 7168),
246 (64, 7168, 18432),
247 (64, 18432, 7168),
248 (64, 24576, 1536),
249 (64, 32768, 512),
250 (64, 7168, 16384),
251 (128, 4096, 7168),
252 (128, 7168, 18432),
253 (128, 18432, 7168),
254 (1024, 4096, 7168),
255 (1024, 18432, 7168),
256 (2048, 4096, 7168),
257 (4096, 4096, 7168),
258 ]
259 shapes = [
260 # (64, 2112, 7168),
261 (64, 24576, 1536),
262 (64, 32768, 512),
263 (64, 7168, 16384),
264 (64, 4096, 7168),
265 (64, 7168, 2048),
266 # (128, 2112, 7168),
267 (128, 24576, 1536),
268 (128, 32768, 512),
269 (128, 7168, 16384),
270 (128, 4096, 7168),
271 (128, 7168, 2048),
272 # (4096, 2112, 7168),
273 (4096, 24576, 1536),
274 (4096, 32768, 512),
275 (4096, 7168, 16384),
276 (4096, 4096, 7168),
277 (4096, 7168, 2048),
278 ]
279
280 all_results = []
281 for m, n, k in shapes:
282 result = benchmark_shape(m, n, k, verbose=verbose)
283 all_results.append(result)
284
285 # Print results in a nicely formatted table
286 print("\n===== PERFORMANCE COMPARISON =====")
287
288 # Print DeepGEMM table
289 deepgemm_headers = ["m", "n", "k", "Time (μs)", "TFLOPS", "GB/s"]
290 deepgemm_rows = []
291 for result in all_results:
292 shape = result["shape"]
293 impl_data = result["implementations"]["DeepGEMM"]
294 deepgemm_rows.append(
295 [
296 shape["m"],
297 shape["n"],
298 shape["k"],
299 f"{impl_data['time_us']:.1f}",
300 f"{impl_data['tflops']:.1f}",
301 f"{impl_data['gb_s']:.1f}",
302 ]
303 )
304
305 print_table(deepgemm_headers, deepgemm_rows, title="DeepGEMM Implementation:")
306
307 # Print vLLM Triton table
308 triton_headers = ["m", "n", "k", "Time (μs)", "TFLOPS", "GB/s", "vs DeepGEMM"]
309 triton_rows = []
310 for result in all_results:
311 shape = result["shape"]
312 impl_data = result["implementations"]["vLLM Triton"]
313 speedup = impl_data.get("speedup_vs_deepgemm", 1.0)
314 triton_rows.append(
315 [
316 shape["m"],
317 shape["n"],
318 shape["k"],
319 f"{impl_data['time_us']:.1f}",
320 f"{impl_data['tflops']:.1f}",
321 f"{impl_data['gb_s']:.1f}",
322 format_speedup(speedup),
323 ]
324 )
325
326 print_table(triton_headers, triton_rows, title="vLLM Triton Implementation:")
327
328 # Print vLLM CUTLASS table
329 cutlass_headers = [
330 "m",
331 "n",
332 "k",
333 "Time (μs)",
334 "TFLOPS",
335 "GB/s",
336 "vs DeepGEMM",
337 "vs Triton",
338 ]
339 cutlass_rows = []
340 for result in all_results:
341 shape = result["shape"]
342 impl_data = result["implementations"]["vLLM CUTLASS"]
343 vs_deepgemm = impl_data.get("speedup_vs_deepgemm", 1.0)
344 vs_triton = impl_data.get("speedup_vs_triton", 1.0)
345 cutlass_rows.append(
346 [
347 shape["m"],
348 shape["n"],
349 shape["k"],
350 f"{impl_data['time_us']:.1f}",
351 f"{impl_data['tflops']:.1f}",
352 f"{impl_data['gb_s']:.1f}",
353 format_speedup(vs_deepgemm),
354 format_speedup(vs_triton),
355 ]
356 )
357
358 print_table(cutlass_headers, cutlass_rows, title="vLLM CUTLASS Implementation:")
359
360 # Calculate and print averages
361 print("\n===== AVERAGE PERFORMANCE =====")
362
363 implementations = ["DeepGEMM", "vLLM Triton", "vLLM CUTLASS"]
364 avg_metrics = {
365 impl: {"tflops": 0, "gb_s": 0, "time_ms": 0} for impl in implementations
366 }
367
368 for result in all_results:
369 for impl in implementations:
370 impl_data = result["implementations"][impl]
371 avg_metrics[impl]["tflops"] += impl_data["tflops"]
372 avg_metrics[impl]["gb_s"] += impl_data["gb_s"]
373 avg_metrics[impl]["time_ms"] += impl_data["time_ms"]
374
375 num_shapes = len(all_results)
376 avg_headers = ["Implementation", "Avg TFLOPS", "Avg GB/s", "Avg Time (ms)"]
377 avg_rows = []
378
379 for impl in implementations:
380 avg_tflops = avg_metrics[impl]["tflops"] / num_shapes
381 avg_mem_bw = avg_metrics[impl]["gb_s"] / num_shapes
382 avg_time = avg_metrics[impl]["time_ms"] / num_shapes
383 avg_rows.append(
384 [impl, f"{avg_tflops:.2f}", f"{avg_mem_bw:.2f}", f"{avg_time:.2f}"]
385 )
386
387 print_table(avg_headers, avg_rows)
388
389 # Calculate average speedups
390 avg_speedups = {
391 "DeepGEMM vs vLLM Triton": 0,
392 "DeepGEMM vs vLLM CUTLASS": 0,
393 "vLLM CUTLASS vs vLLM Triton": 0,
394 }
395
396 for result in all_results:
397 deepgemm_time = result["implementations"]["DeepGEMM"]["time_ms"]
398 vllm_triton_time = result["implementations"]["vLLM Triton"]["time_ms"]
399 vllm_cutlass_time = result["implementations"]["vLLM CUTLASS"]["time_ms"]
400
401 avg_speedups["DeepGEMM vs vLLM Triton"] += vllm_triton_time / deepgemm_time
402 avg_speedups["DeepGEMM vs vLLM CUTLASS"] += vllm_cutlass_time / deepgemm_time
403 avg_speedups["vLLM CUTLASS vs vLLM Triton"] += (
404 vllm_triton_time / vllm_cutlass_time
405 )
406
407 print("\n===== AVERAGE SPEEDUPS =====")
408 speedup_headers = ["Comparison", "Speedup"]
409 speedup_rows = []
410 for comparison, total in avg_speedups.items():
411 avg_speedup = total / num_shapes
412 status = "faster" if avg_speedup > 1 else "slower"
413 speedup_rows.append([comparison, f"{avg_speedup:.2f}x {status}"])
414
415 print_table(speedup_headers, speedup_rows)
416
417 # Average accuracy comparison
418 print("\n===== ACCURACY COMPARISON =====")
419 avg_diff = {impl: 0 for impl in implementations}
420
421 for result in all_results:
422 for impl in implementations:
423 avg_diff[impl] += result["implementations"][impl]["diff"]["Reference"]
424
425 diff_headers = ["Implementation", "Avg Diff vs Reference"]
426 diff_rows = []
427 for impl in implementations:
428 diff_rows.append([impl, f"{avg_diff[impl] / num_shapes:.6f}"])
429
430 print_table(diff_headers, diff_rows)
431
432
433 if __name__ == "__main__":
434 run_benchmarks(verbose=False)