@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c6e3bba8e6
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 15, 04:45 UTC

[Feature] Integrate new deepgemm (#19820)

Signed-off-by: yewentao256 <zhyanwentao@126.com>

Wentao Ye committed Jun 24, 2025 at 15:51 UTC c6e3bba8e68ef586e346a0a8f5c3af168148fd63
8 files changed +234 -268
benchmarks/kernels/benchmark_moe.py
+3
@@ -86,6 +86,9 @@ def benchmark_config(
86 (num_experts, 2 * shard_intermediate_size), dtype=torch.float32
87 )
88 w2_scale = torch.randn((hidden_size, num_experts), dtype=torch.float32)
89 + if use_deep_gemm:
90 + # we use the default block shape for deepgemm
91 + block_quant_shape = [128, 128]
92 if use_fp8_w8a8:
93 if block_quant_shape:
94 block_n, block_k = block_quant_shape[0], block_quant_shape[1]
benchmarks/kernels/deepgemm/benchmark_fp8_block_dense_gemm.py
+134 -186
@@ -1,13 +1,11 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -# fmt: off
4 -# ruff: noqa: E501
3 import time
4
7 -# Import DeepGEMM functions
8 -import deep_gemm
5 import torch
10 -from deep_gemm import calc_diff, ceil_div, get_col_major_tma_aligned_tensor
6 +from deep_gemm import fp8_gemm_nt
7 +from deep_gemm.testing.numeric import calc_diff
8 +from deep_gemm.utils.math import ceil_div, per_block_cast_to_fp8, per_token_cast_to_fp8
9
10 # Import vLLM functions
11 from vllm import _custom_ops as ops
@@ -18,107 +16,84 @@ from vllm.model_executor.layers.quantization.utils.fp8_utils import (
16 from vllm.triton_utils import triton
17
18
21 -# Copied from
22 -# https://github.com/deepseek-ai/DeepGEMM/blob/78cacf70d41d15d688bd493ebc85845f7f2a3d5d/tests/test_core.py#L9
23 -def per_token_cast_to_fp8(
24 - x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
25 - """Convert tensor to FP8 format with per-token scaling."""
26 - assert x.dim() == 2 and x.size(1) % 128 == 0
27 - m, n = x.shape
28 - x_view = x.view(m, -1, 128)
29 - x_amax = x_view.abs().float().amax(dim=2).view(m, -1).clamp(1e-4)
30 - return (x_view * (448.0 / x_amax.unsqueeze(2))).to(
31 - torch.float8_e4m3fn).view(m, n), (x_amax / 448.0).view(m, -1)
32 -
33 -
19 # Copied from
20 # https://github.com/deepseek-ai/DeepGEMM/blob/78cacf70d41d15d688bd493ebc85845f7f2a3d5d/tests/test_core.py#L17
36 -def per_block_cast_to_fp8(
37 - x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
21 +def per_block_cast_to_fp8_vllm(x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
22 """Convert tensor to FP8 format with per-block scaling."""
23 assert x.dim() == 2
24 m, n = x.shape
41 - x_padded = torch.zeros((ceil_div(m, 128) * 128, ceil_div(n, 128) * 128),
42 - dtype=x.dtype,
43 - device=x.device)
25 + x_padded = torch.zeros(
26 + (ceil_div(m, 128) * 128, ceil_div(n, 128) * 128), dtype=x.dtype, device=x.device
27 + )
28 x_padded[:m, :n] = x
29 x_view = x_padded.view(-1, 128, x_padded.size(1) // 128, 128)
30 x_amax = x_view.abs().float().amax(dim=(1, 3), keepdim=True).clamp(1e-4)
31 x_scaled = (x_view * (448.0 / x_amax)).to(torch.float8_e4m3fn)
48 - return x_scaled.view_as(x_padded)[:m, :n].contiguous(), (
49 - x_amax / 448.0).view(x_view.size(0), x_view.size(2))
50 -
51 -
52 -def benchmark_shape(m: int,
53 - n: int,
54 - k: int,
55 - warmup: int = 100,
56 - repeat: int = 10000,
57 - verbose: bool = False) -> dict:
32 + return x_scaled.view_as(x_padded)[:m, :n].contiguous(), (x_amax / 448.0).view(
33 + x_view.size(0), x_view.size(2)
34 + )
35 +
36 +
37 +def benchmark_shape(
38 + m: int,
39 + n: int,
40 + k: int,
41 + warmup: int = 100,
42 + repeat: int = 10000,
43 + verbose: bool = False,
44 +) -> dict:
45 """Benchmark all implementations for a specific (m, n, k) shape."""
46 if verbose:
47 print(f"\n=== Benchmarking shape: m={m}, n={n}, k={k} ===")
48
62 - # Create test tensors
63 - A = torch.randn((m, k), device='cuda', dtype=torch.bfloat16)
64 - B = torch.randn((n, k), device='cuda', dtype=torch.bfloat16)
65 -
66 - # Reference result in BF16
49 + A = torch.randn((m, k), device="cuda", dtype=torch.bfloat16)
50 + B = torch.randn((n, k), device="cuda", dtype=torch.bfloat16)
51 torch.cuda.synchronize()
52 C_ref = A @ B.t()
53
54 # Pre-quantize B for all implementations
55 # (weights can be pre-quantized offline)
56 B_deepgemm, B_scale_deepgemm = per_block_cast_to_fp8(B)
73 - B_vllm, B_scale_vllm = per_block_cast_to_fp8(B)
57 + B_vllm, B_scale_vllm = per_block_cast_to_fp8_vllm(B)
58
59 # Block size configuration
60 block_size = [128, 128]
61
62 # Pre-quantize A for all implementations
63 A_deepgemm, A_scale_deepgemm = per_token_cast_to_fp8(A)
80 - A_scale_deepgemm = get_col_major_tma_aligned_tensor(A_scale_deepgemm)
81 - C_deepgemm = torch.empty((m, n), device='cuda', dtype=torch.bfloat16)
64 + C_deepgemm = (
65 + torch.empty((n, m), device="cuda", dtype=torch.bfloat16).t().contiguous()
66 + )
67 A_vllm, A_scale_vllm = per_token_group_quant_fp8(A, block_size[1])
68 A_vllm_cutlass, A_scale_vllm_cutlass = per_token_group_quant_fp8(
84 - A, block_size[1], column_major_scales=True)
69 + A, block_size[1], column_major_scales=True
70 + )
71
86 - # === DeepGEMM Implementation ===
72 def deepgemm_gemm():
88 - # A quantization is inside the loop as it depends on activations
89 - # A_deepgemm, A_scale_deepgemm = per_token_cast_to_fp8(A)
90 - # A_deepgemm, A_scale_deepgemm = per_token_group_quant_fp8(
91 - # A, block_size[1])
92 - # A_scale_aligned = get_col_major_tma_aligned_tensor(A_scale_deepgemm)
93 - # C_deepgemm = torch.empty((m, n), device='cuda', dtype=torch.bfloat16)
94 - deep_gemm.gemm_fp8_fp8_bf16_nt((A_deepgemm, A_scale_deepgemm),
95 - (B_deepgemm, B_scale_deepgemm),
96 - C_deepgemm)
73 + fp8_gemm_nt(
74 + (A_deepgemm, A_scale_deepgemm), (B_deepgemm, B_scale_deepgemm), C_deepgemm
75 + )
76 return C_deepgemm
77
99 - # === vLLM Triton Implementation ===
78 def vllm_triton_gemm():
101 - # A quantization is inside the loop as it depends on activations
102 - # A_vllm, A_scale_vllm = per_token_group_quant_fp8(A, block_size[1])
103 - return w8a8_block_fp8_matmul(A_vllm,
104 - B_vllm,
105 - A_scale_vllm,
106 - B_scale_vllm,
107 - block_size,
108 - output_dtype=torch.bfloat16)
109 -
110 - # === vLLM CUTLASS Implementation ===
79 + return w8a8_block_fp8_matmul(
80 + A_vllm,
81 + B_vllm,
82 + A_scale_vllm,
83 + B_scale_vllm,
84 + block_size,
85 + output_dtype=torch.bfloat16,
86 + )
87 +
88 def vllm_cutlass_gemm():
112 - # A quantization is inside the loop as it depends on activations
113 - # A_vllm_cutlass, A_scale_vllm_cutlass = per_token_group_quant_fp8(
114 - # A, block_size[1], column_major_scales=True)
115 - return ops.cutlass_scaled_mm(A_vllm_cutlass,
116 - B_vllm.T,
117 - scale_a=A_scale_vllm_cutlass,
118 - scale_b=B_scale_vllm.T,
119 - out_dtype=torch.bfloat16)
120 -
121 - # Run correctness check first
89 + return ops.cutlass_scaled_mm(
90 + A_vllm_cutlass,
91 + B_vllm.T,
92 + scale_a=A_scale_vllm_cutlass,
93 + scale_b=B_scale_vllm.T,
94 + out_dtype=torch.bfloat16,
95 + )
96 +
97 if verbose:
98 print("Running correctness check...")
99 C_deepgemm = deepgemm_gemm()
@@ -133,26 +108,22 @@ def benchmark_shape(m: int,
108 print(f"DeepGEMM vs Reference difference: {deepgemm_diff:.6f}")
109 print(f"vLLM Triton vs Reference difference: {vllm_triton_diff:.6f}")
110 print(f"vLLM CUTLASS vs Reference difference: {vllm_cutlass_diff:.6f}")
136 - print("vLLM Triton vs DeepGEMM difference: "
137 - f"{calc_diff(C_vllm_triton, C_deepgemm):.6f}")
138 - print("vLLM CUTLASS vs DeepGEMM difference: "
139 - f"{calc_diff(C_vllm_cutlass, C_deepgemm):.6f}")
111 + print(
112 + "vLLM Triton vs DeepGEMM difference: "
113 + f"{calc_diff(C_vllm_triton, C_deepgemm):.6f}"
114 + )
115 + print(
116 + "vLLM CUTLASS vs DeepGEMM difference: "
117 + f"{calc_diff(C_vllm_cutlass, C_deepgemm):.6f}"
118 + )
119
141 - # Benchmark implementations
120 implementations = {
121 "DeepGEMM": deepgemm_gemm,
122 "vLLM Triton": vllm_triton_gemm,
145 - "vLLM CUTLASS": vllm_cutlass_gemm
123 + "vLLM CUTLASS": vllm_cutlass_gemm,
124 }
125
148 - benchmark_results = {
149 - "shape": {
150 - "m": m,
151 - "n": n,
152 - "k": k
153 - },
154 - "implementations": {}
155 - }
126 + benchmark_results = {"shape": {"m": m, "n": n, "k": k}, "implementations": {}}
127
128 for name, func in implementations.items():
129 # Warmup
@@ -180,38 +151,36 @@ def benchmark_shape(m: int,
151 "tflops": tflops,
152 "gb_s": gb_s,
153 "diff": {
183 - "DeepGEMM":
184 - 0.0 if name == "DeepGEMM" else calc_diff(func(), C_deepgemm),
185 - "Reference":
186 - deepgemm_diff if name == "DeepGEMM" else
187 - (vllm_triton_diff
188 - if name == "vLLM Triton" else vllm_cutlass_diff)
189 - }
154 + "DeepGEMM": 0.0
155 + if name == "DeepGEMM"
156 + else calc_diff(func(), C_deepgemm),
157 + "Reference": deepgemm_diff
158 + if name == "DeepGEMM"
159 + else (vllm_triton_diff if name == "vLLM Triton" else vllm_cutlass_diff),
160 + },
161 }
162
163 if verbose:
193 - print(
194 - f"{name}: {avg_time_ms:.3f} ms, {tflops:.2f} TFLOPS, {gb_s:.2f} GB/s"
195 - )
164 + print(f"{name}: {avg_time_ms:.3f} ms, {tflops:.2f} TFLOPS, {gb_s:.2f} GB/s")
165
166 # Calculate speedups
167 baseline = benchmark_results["implementations"]["DeepGEMM"]["time_ms"]
168 for name, data in benchmark_results["implementations"].items():
169 if name != "DeepGEMM":
170 speedup = baseline / data["time_ms"]
202 - benchmark_results["implementations"][name][
203 - "speedup_vs_deepgemm"] = speedup
171 + benchmark_results["implementations"][name]["speedup_vs_deepgemm"] = speedup
172 if verbose:
205 - print(f"DeepGEMM is {1/speedup:.2f}x "
206 - f"{'faster' if 1/speedup > 1 else 'slower'} than {name}")
173 + print(
174 + f"DeepGEMM is {1 / speedup:.2f}x "
175 + f"{'faster' if 1 / speedup > 1 else 'slower'} than {name}"
176 + )
177
208 - vllm_triton_time = benchmark_results["implementations"]["vLLM Triton"][
209 - "time_ms"]
210 - vllm_cutlass_time = benchmark_results["implementations"]["vLLM CUTLASS"][
211 - "time_ms"]
178 + vllm_triton_time = benchmark_results["implementations"]["vLLM Triton"]["time_ms"]
179 + vllm_cutlass_time = benchmark_results["implementations"]["vLLM CUTLASS"]["time_ms"]
180 cutlass_vs_triton = vllm_triton_time / vllm_cutlass_time
213 - benchmark_results["implementations"]["vLLM CUTLASS"][
214 - "speedup_vs_triton"] = cutlass_vs_triton
181 + benchmark_results["implementations"]["vLLM CUTLASS"]["speedup_vs_triton"] = (
182 + cutlass_vs_triton
183 + )
184 if verbose:
185 print(
186 f"vLLM CUTLASS is {cutlass_vs_triton:.2f}x "
@@ -223,8 +192,7 @@ def benchmark_shape(m: int,
192
193 def format_table_row(values, widths):
194 """Format a row with specified column widths."""
226 - return "| " + " | ".join(f"{val:{w}}"
227 - for val, w in zip(values, widths)) + " |"
195 + return "| " + " | ".join(f"{val:{w}}" for val, w in zip(values, widths)) + " |"
196
197
198 def print_table(headers, rows, title=None):
@@ -232,16 +200,12 @@ def print_table(headers, rows, title=None):
200 if title:
201 print(f"\n{title}")
202
235 - # Calculate column widths based on headers and data
203 widths = [
204 max(len(str(h)), max(len(str(row[i])) for row in rows))
205 for i, h in enumerate(headers)
206 ]
207
241 - # Create separator line
208 separator = "+-" + "-+-".join("-" * w for w in widths) + "-+"
243 -
244 - # Print table
209 print(separator)
210 print(format_table_row(headers, widths))
211 print(separator)
@@ -259,44 +223,22 @@ def run_benchmarks(verbose: bool = False):
223 """Run benchmarks for a set of common shapes."""
224 print("===== STARTING FP8 GEMM BENCHMARK =====")
225
262 - # Make sure we're using the GPU
226 if not torch.cuda.is_available():
227 print("CUDA not available! Tests require GPU.")
228 return
229
267 - # Print system information
230 print(f"PyTorch version: {torch.__version__}")
231 print(f"CUDA version: {torch.version.cuda}")
232 print(f"Triton version: {triton.__version__}")
233 print(f"Using device: {torch.cuda.get_device_name()}")
234
273 - # Enable TF32 for better performance
235 torch.backends.cuda.matmul.allow_tf32 = True
236 torch.backends.cudnn.allow_tf32 = True
237
277 - # Set seeds for reproducibility
238 torch.manual_seed(42)
239 torch.cuda.manual_seed(42)
240
241 # Define benchmark shapes (m, n, k)
282 - shapes = [
283 - (8, 4096, 7168),
284 - (8, 7168, 18432),
285 - (8, 18432, 7168),
286 - (64, 4096, 7168),
287 - (64, 7168, 18432),
288 - (64, 18432, 7168),
289 - (64, 24576, 1536),
290 - (64, 32768, 512),
291 - (64, 7168, 16384),
292 - (128, 4096, 7168),
293 - (128, 7168, 18432),
294 - (128, 18432, 7168),
295 - (1024, 4096, 7168),
296 - (1024, 18432, 7168),
297 - (2048, 4096, 7168),
298 - (4096, 4096, 7168),
299 - ]
242 shapes = [
243 # (64, 2112, 7168),
244 (64, 24576, 1536),
@@ -323,7 +265,6 @@ def run_benchmarks(verbose: bool = False):
265 result = benchmark_shape(m, n, k, verbose=verbose)
266 all_results.append(result)
267
326 - # Print results in a nicely formatted table
268 print("\n===== PERFORMANCE COMPARISON =====")
269
270 # Print DeepGEMM table
@@ -332,38 +273,50 @@ def run_benchmarks(verbose: bool = False):
273 for result in all_results:
274 shape = result["shape"]
275 impl_data = result["implementations"]["DeepGEMM"]
335 - deepgemm_rows.append([
336 - shape["m"], shape["n"], shape["k"], f"{impl_data['time_us']:.1f}",
337 - f"{impl_data['tflops']:.1f}", f"{impl_data['gb_s']:.1f}"
338 - ])
276 + deepgemm_rows.append(
277 + [
278 + shape["m"],
279 + shape["n"],
280 + shape["k"],
281 + f"{impl_data['time_us']:.1f}",
282 + f"{impl_data['tflops']:.1f}",
283 + f"{impl_data['gb_s']:.1f}",
284 + ]
285 + )
286
340 - print_table(deepgemm_headers,
341 - deepgemm_rows,
342 - title="DeepGEMM Implementation:")
287 + print_table(deepgemm_headers, deepgemm_rows, title="DeepGEMM Implementation:")
288
289 # Print vLLM Triton table
345 - triton_headers = [
346 - "m", "n", "k", "Time (μs)", "TFLOPS", "GB/s", "vs DeepGEMM"
347 - ]
290 + triton_headers = ["m", "n", "k", "Time (μs)", "TFLOPS", "GB/s", "vs DeepGEMM"]
291 triton_rows = []
292 for result in all_results:
293 shape = result["shape"]
294 impl_data = result["implementations"]["vLLM Triton"]
295 speedup = impl_data.get("speedup_vs_deepgemm", 1.0)
353 - triton_rows.append([
354 - shape["m"], shape["n"], shape["k"], f"{impl_data['time_us']:.1f}",
355 - f"{impl_data['tflops']:.1f}", f"{impl_data['gb_s']:.1f}",
356 - format_speedup(speedup)
357 - ])
296 + triton_rows.append(
297 + [
298 + shape["m"],
299 + shape["n"],
300 + shape["k"],
301 + f"{impl_data['time_us']:.1f}",
302 + f"{impl_data['tflops']:.1f}",
303 + f"{impl_data['gb_s']:.1f}",
304 + format_speedup(speedup),
305 + ]
306 + )
307
359 - print_table(triton_headers,
360 - triton_rows,
361 - title="vLLM Triton Implementation:")
308 + print_table(triton_headers, triton_rows, title="vLLM Triton Implementation:")
309
310 # Print vLLM CUTLASS table
311 cutlass_headers = [
365 - "m", "n", "k", "Time (μs)", "TFLOPS", "GB/s", "vs DeepGEMM",
366 - "vs Triton"
312 + "m",
313 + "n",
314 + "k",
315 + "Time (μs)",
316 + "TFLOPS",
317 + "GB/s",
318 + "vs DeepGEMM",
319 + "vs Triton",
320 ]
321 cutlass_rows = []
322 for result in all_results:
@@ -371,28 +324,27 @@ def run_benchmarks(verbose: bool = False):
324 impl_data = result["implementations"]["vLLM CUTLASS"]
325 vs_deepgemm = impl_data.get("speedup_vs_deepgemm", 1.0)
326 vs_triton = impl_data.get("speedup_vs_triton", 1.0)
374 - cutlass_rows.append([
375 - shape["m"], shape["n"], shape["k"], f"{impl_data['time_us']:.1f}",
376 - f"{impl_data['tflops']:.1f}", f"{impl_data['gb_s']:.1f}",
377 - format_speedup(vs_deepgemm),
378 - format_speedup(vs_triton)
379 - ])
327 + cutlass_rows.append(
328 + [
329 + shape["m"],
330 + shape["n"],
331 + shape["k"],
332 + f"{impl_data['time_us']:.1f}",
333 + f"{impl_data['tflops']:.1f}",
334 + f"{impl_data['gb_s']:.1f}",
335 + format_speedup(vs_deepgemm),
336 + format_speedup(vs_triton),
337 + ]
338 + )
339
381 - print_table(cutlass_headers,
382 - cutlass_rows,
383 - title="vLLM CUTLASS Implementation:")
340 + print_table(cutlass_headers, cutlass_rows, title="vLLM CUTLASS Implementation:")
341
342 # Calculate and print averages
343 print("\n===== AVERAGE PERFORMANCE =====")
344
345 implementations = ["DeepGEMM", "vLLM Triton", "vLLM CUTLASS"]
346 avg_metrics = {
390 - impl: {
391 - "tflops": 0,
392 - "gb_s": 0,
393 - "time_ms": 0
394 - }
395 - for impl in implementations
347 + impl: {"tflops": 0, "gb_s": 0, "time_ms": 0} for impl in implementations
348 }
349
350 for result in all_results:
@@ -410,9 +362,9 @@ def run_benchmarks(verbose: bool = False):
362 avg_tflops = avg_metrics[impl]["tflops"] / num_shapes
363 avg_mem_bw = avg_metrics[impl]["gb_s"] / num_shapes
364 avg_time = avg_metrics[impl]["time_ms"] / num_shapes
413 - avg_rows.append([
414 - impl, f"{avg_tflops:.2f}", f"{avg_mem_bw:.2f}", f"{avg_time:.2f}"
415 - ])
365 + avg_rows.append(
366 + [impl, f"{avg_tflops:.2f}", f"{avg_mem_bw:.2f}", f"{avg_time:.2f}"]
367 + )
368
369 print_table(avg_headers, avg_rows)
370
@@ -420,21 +372,19 @@ def run_benchmarks(verbose: bool = False):
372 avg_speedups = {
373 "DeepGEMM vs vLLM Triton": 0,
374 "DeepGEMM vs vLLM CUTLASS": 0,
423 - "vLLM CUTLASS vs vLLM Triton": 0
375 + "vLLM CUTLASS vs vLLM Triton": 0,
376 }
377
378 for result in all_results:
379 deepgemm_time = result["implementations"]["DeepGEMM"]["time_ms"]
380 vllm_triton_time = result["implementations"]["vLLM Triton"]["time_ms"]
429 - vllm_cutlass_time = result["implementations"]["vLLM CUTLASS"][
430 - "time_ms"]
381 + vllm_cutlass_time = result["implementations"]["vLLM CUTLASS"]["time_ms"]
382
432 - avg_speedups[
433 - "DeepGEMM vs vLLM Triton"] += vllm_triton_time / deepgemm_time
434 - avg_speedups[
435 - "DeepGEMM vs vLLM CUTLASS"] += vllm_cutlass_time / deepgemm_time
436 - avg_speedups[
437 - "vLLM CUTLASS vs vLLM Triton"] += vllm_triton_time / vllm_cutlass_time
383 + avg_speedups["DeepGEMM vs vLLM Triton"] += vllm_triton_time / deepgemm_time
384 + avg_speedups["DeepGEMM vs vLLM CUTLASS"] += vllm_cutlass_time / deepgemm_time
385 + avg_speedups["vLLM CUTLASS vs vLLM Triton"] += (
386 + vllm_triton_time / vllm_cutlass_time
387 + )
388
389 print("\n===== AVERAGE SPEEDUPS =====")
390 speedup_headers = ["Comparison", "Speedup"]
@@ -446,14 +396,12 @@ def run_benchmarks(verbose: bool = False):
396
397 print_table(speedup_headers, speedup_rows)
398
449 - # Average accuracy comparison
399 print("\n===== ACCURACY COMPARISON =====")
400 avg_diff = {impl: 0 for impl in implementations}
401
402 for result in all_results:
403 for impl in implementations:
455 - avg_diff[impl] += result["implementations"][impl]["diff"][
456 - "Reference"]
404 + avg_diff[impl] += result["implementations"][impl]["diff"]["Reference"]
405
406 diff_headers = ["Implementation", "Avg Diff vs Reference"]
407 diff_rows = []
tests/kernels/moe/test_deepep_deepgemm_moe.py
+2 -21
@@ -66,25 +66,6 @@ def next_power_of_2(x):
66 return 2**math.ceil(math.log2(x))
67
68
69 -def per_block_cast_to_fp8(
70 - x: torch.Tensor,
71 - block_size_n: int = 128) -> tuple[torch.Tensor, torch.Tensor]:
72 - assert x.dim() == 2
73 - m, n = x.shape
74 - x_padded = torch.zeros(
75 - (deep_gemm.ceil_div(m, 128) * 128,
76 - deep_gemm.ceil_div(n, block_size_n) * block_size_n),
77 - dtype=x.dtype,
78 - device=x.device)
79 - x_padded[:m, :n] = x
80 - x_view = x_padded.view(-1, 128, x_padded.size(1) // 128, block_size_n)
81 - x_amax = x_view.abs().float().amax(dim=(1, 3), keepdim=True).clamp(1e-4)
82 - x_scaled = (x_view * (448.0 / x_amax)).to(torch.float8_e4m3fn)
83 - x_scaled_sub = x_scaled.view_as(x_padded)[:m, :n].contiguous()
84 - scales = (x_amax / 448.0).view(x_view.size(0), x_view.size(2))
85 - return x_scaled_sub, scales
86 -
87 -
69 def make_block_quant_fp8_weights(
70 e: int,
71 n: int,
@@ -125,8 +106,8 @@ def make_block_quant_fp8_weights(
106 assert (w2.shape[-2] + block_n - 1) // block_n == w2_s.shape[-2]
107
108 for i in range(e):
128 - w1[i], w1_s[i] = per_block_cast_to_fp8(w1_bf16[i])
129 - w2[i], w2_s[i] = per_block_cast_to_fp8(w2_bf16[i])
109 + w1[i], w1_s[i] = deep_gemm.utils.math.per_block_cast_to_fp8(w1_bf16[i])
110 + w2[i], w2_s[i] = deep_gemm.utils.math.per_block_cast_to_fp8(w2_bf16[i])
111
112 return w1, w2, w1_s, w2_s
113
tests/kernels/quantization/test_block_fp8.py
+14 -41
@@ -18,7 +18,8 @@ from vllm.model_executor.layers.fused_moe.fused_moe import (
18 from vllm.model_executor.layers.fused_moe.moe_align_block_size import (
19 moe_align_block_size)
20 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
21 - per_token_group_quant_fp8, w8a8_block_fp8_matmul)
21 + get_col_major_tma_aligned_tensor, per_token_group_quant_fp8,
22 + w8a8_block_fp8_matmul)
23 from vllm.platforms import current_platform
24
25 dg_available = False
@@ -263,25 +264,6 @@ def test_w8a8_block_fp8_fused_moe(M, N, K, E, topk, block_size, dtype, seed):
264 assert rel_diff < 0.03
265
266
266 -def per_block_cast_to_fp8(
267 - x: torch.Tensor,
268 - block_size_n: int = 128) -> tuple[torch.Tensor, torch.Tensor]:
269 - assert x.dim() == 2
270 - m, n = x.shape
271 - x_padded = torch.zeros(
272 - (deep_gemm.ceil_div(m, 128) * 128,
273 - deep_gemm.ceil_div(n, block_size_n) * block_size_n),
274 - dtype=x.dtype,
275 - device=x.device)
276 - x_padded[:m, :n] = x
277 - x_view = x_padded.view(-1, 128, x_padded.size(1) // 128, block_size_n)
278 - x_amax = x_view.abs().float().amax(dim=(1, 3), keepdim=True).clamp(1e-4)
279 - x_scaled = (x_view * (448.0 / x_amax)).to(torch.float8_e4m3fn)
280 - x_scaled_sub = x_scaled.view_as(x_padded)[:m, :n].contiguous()
281 - scales = (x_amax / 448.0).view(x_view.size(0), x_view.size(2))
282 - return x_scaled_sub, scales
283 -
284 -
267 @pytest.mark.parametrize(
268 "M,N,K,block_size,out_dtype,seed",
269 itertools.product(M, N, K, BLOCK_SIZE, OUT_DTYPES, SEEDS))
@@ -299,10 +281,8 @@ def test_w8a8_block_fp8_deep_gemm_matmul(M, N, K, block_size, out_dtype, seed):
281 A_fp32 = (torch.rand(M, K, dtype=torch.float32) - 0.5) * 2 * fp8_max
282 B_fp32 = (torch.rand(N, K, dtype=torch.float32) - 0.5) * 2 * fp8_max
283
302 - _, block_k = block_size[0], block_size[1]
303 -
304 - A_fp8, As_fp8 = per_token_group_quant_fp8(A_fp32, block_k)
305 - B_fp8, Bs_fp8 = per_block_cast_to_fp8(B_fp32)
284 + A_fp8, As_fp8 = deep_gemm.utils.math.per_token_cast_to_fp8(A_fp32)
285 + B_fp8, Bs_fp8 = deep_gemm.utils.math.per_block_cast_to_fp8(B_fp32)
286
287 As = As_fp8.to(torch.float32)
288 Bs = Bs_fp8.to(torch.float32)
@@ -310,15 +290,12 @@ def test_w8a8_block_fp8_deep_gemm_matmul(M, N, K, block_size, out_dtype, seed):
290 ref_out = native_w8a8_block_matmul(A_fp8, B_fp8, As, Bs, block_size,
291 out_dtype)
292
313 - # Transpose earlier so that the testing will not trigger transposing kernels
314 - As_fp8 = deep_gemm.get_col_major_tma_aligned_tensor(As_fp8)
315 -
293 out = torch.zeros((M, N), device='cuda', dtype=out_dtype)
294
295 assert As_fp8.shape == (M, (K + 127) //
296 128), f"{As_fp8.shape} != {(M, (K + 127) // 128)}"
297
321 - deep_gemm.gemm_fp8_fp8_bf16_nt((A_fp8, As_fp8), (B_fp8, Bs_fp8), out)
298 + deep_gemm.fp8_gemm_nt((A_fp8, As_fp8), (B_fp8, Bs_fp8), out)
299
300 rel_diff = (torch.mean(
301 torch.abs(out.to(torch.float32) - ref_out.to(torch.float32))) /
@@ -382,16 +359,16 @@ def deep_gemm_w8a8_block_fp8_moe(M, K, a, w1, w2, w1_s, w2_s, score, topk,
359 dtype=torch.bfloat16,
360 device=a.device)
361
385 - deep_gemm.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous((a_q, a_s), (w1, w1_s),
386 - inter_out, m_indices)
362 + deep_gemm.m_grouped_fp8_gemm_nt_contiguous((a_q, a_s), (w1, w1_s),
363 + inter_out, m_indices)
364
365 act_out = SiluAndMul().forward_native(inter_out)
366 act_out_q, act_out_s = per_token_group_quant_fp8(act_out, block_k)
367
368 out = torch.zeros(a_q.shape[0], K, dtype=torch.bfloat16, device=a.device)
369
393 - deep_gemm.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous(
394 - (act_out_q, act_out_s), (w2, w2_s), out, m_indices)
370 + deep_gemm.m_grouped_fp8_gemm_nt_contiguous((act_out_q, act_out_s),
371 + (w2, w2_s), out, m_indices)
372
373 final_out = _moe_unpermute(out, inv_perm, topk, K, topk_weight)
374
@@ -441,15 +418,15 @@ def test_w8a8_block_fp8_deep_gemm_fused_moe(M, N, K, E, topk, seed):
418 w1_s = torch.empty((E, n_tiles_w1, k_tiles_w1), dtype=torch.float32)
419 w2_s = torch.empty((E, n_tiles_w2, k_tiles_w2), dtype=torch.float32)
420
444 - w1_s = deep_gemm.get_col_major_tma_aligned_tensor(w1_s).contiguous()
445 - w2_s = deep_gemm.get_col_major_tma_aligned_tensor(w2_s).contiguous()
421 + w1_s = get_col_major_tma_aligned_tensor(w1_s).contiguous()
422 + w2_s = get_col_major_tma_aligned_tensor(w2_s).contiguous()
423
424 assert w1_s.shape == (E, (2 * N + 127) // 128, (K + 127) // 128)
425 assert (w2.shape[-2] + block_n - 1) // block_n == w2_s.shape[-2]
426
427 for i in range(E):
451 - w1[i], w1_s[i] = per_block_cast_to_fp8(w1_bf16[i])
452 - w2[i], w2_s[i] = per_block_cast_to_fp8(w2_bf16[i])
428 + w1[i], w1_s[i] = deep_gemm.utils.math.per_block_cast_to_fp8(w1_bf16[i])
429 + w2[i], w2_s[i] = deep_gemm.utils.math.per_block_cast_to_fp8(w2_bf16[i])
430
431 # Set the context to avoid lots of warning spam.
432 with set_current_vllm_config(vllm_config):
@@ -460,14 +437,10 @@ def test_w8a8_block_fp8_deep_gemm_fused_moe(M, N, K, E, topk, seed):
437 ref_out = torch_w8a8_block_fp8_moe(a, w1, w2, w1_s, w2_s, score,
438 topk, block_size)
439
463 - topk_weights, topk_ids, token_expert_indices = fused_topk(
464 - a, score.float(), topk, False)
440 + topk_weights, topk_ids, _ = fused_topk(a, score.float(), topk, False)
441
442 out = deep_gemm_moe_fp8(a, w1, w2, w1_s, w2_s, topk_weights, topk_ids)
443
468 - #print(f"{out.sum()=}")
469 - #print(f"{ref_out.sum()=}")
470 -
444 rel_diff = (torch.mean(
445 torch.abs(out.to(torch.float32) - ref_out.to(torch.float32))) /
446 torch.mean(torch.abs(ref_out.to(torch.float32))))
vllm/model_executor/layers/fused_moe/batched_deep_gemm_moe.py
+8 -11
@@ -266,19 +266,16 @@ class BatchedDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
266 # for the M expectation of each batch, correctly setting this value
267 # may lead to better performance.
268 expected_m = max_num_tokens
269 -
270 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_masked((a1q, a1q_scale),
271 - (w1, w1_scale),
272 - out=workspace1,
273 - masked_m=expert_num_tokens,
274 - expected_m=expected_m)
269 + dg.fp8_m_grouped_gemm_nt_masked((a1q, a1q_scale), (w1, w1_scale),
270 + out=workspace1,
271 + masked_m=expert_num_tokens,
272 + expected_m=expected_m)
273
274 assert expert_num_tokens is not None
275 a2q, a2q_scale = silu_mul_fp8_quant_deep_gemm(workspace1,
276 expert_num_tokens)
277
280 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_masked((a2q, a2q_scale),
281 - (w2, w2_scale),
282 - out=output,
283 - masked_m=expert_num_tokens,
284 - expected_m=expected_m)
278 + dg.fp8_m_grouped_gemm_nt_masked((a2q, a2q_scale), (w2, w2_scale),
279 + out=output,
280 + masked_m=expert_num_tokens,
281 + expected_m=expected_m)
vllm/model_executor/layers/fused_moe/deep_gemm_moe.py
+4 -5
@@ -144,8 +144,8 @@ class DeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
144 (M_sum, N // 2))
145 mm2_out = _resize_cache(workspace2, (M_sum, K))
146
147 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous(
148 - (a1q, a1q_scale), (w1, w1_scale), mm1_out, expert_ids)
147 + dg.m_grouped_fp8_gemm_nt_contiguous((a1q, a1q_scale), (w1, w1_scale),
148 + mm1_out, expert_ids)
149
150 self.activation(activation, act_out, mm1_out.view(-1, N))
151
@@ -154,9 +154,8 @@ class DeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
154 self.block_shape[1],
155 column_major_scales=True,
156 out_q=quant_out)
157 -
158 - dg.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous(
159 - (a2q, a2q_scale), (w2, w2_scale), mm2_out, expert_ids)
157 + dg.m_grouped_fp8_gemm_nt_contiguous((a2q, a2q_scale), (w2, w2_scale),
158 + mm2_out, expert_ids)
159
160 torch.index_select(mm2_out, 0, inv_perm, out=output)
161
vllm/model_executor/layers/quantization/deepgemm.py
+1 -1
@@ -58,7 +58,7 @@ def w8a8_block_fp8_matmul_deepgemm(
58 output_dtype)
59 # Deepgemm only supports output tensor type as bfloat16
60 assert C.dtype == torch.bfloat16
61 - deep_gemm.gemm_fp8_fp8_bf16_nt((A, As), (B, Bs), C)
61 + deep_gemm.fp8_gemm_nt((A, As), (B, Bs), C)
62 return C
63
64
vllm/model_executor/layers/quantization/utils/fp8_utils.py
+68 -3
@@ -114,6 +114,10 @@ def should_use_deepgemm(output_dtype: torch.dtype, weight: torch.Tensor):
114 and weight.shape[0] % 128 == 0 and weight.shape[1] % 128 == 0)
115
116
117 +def ceil_div(x: int, y: int) -> int:
118 + return (x + y - 1) // y
119 +
120 +
121 # TODO fix ROCm->Triton custom path:
122 # https://github.com/vllm-project/vllm/issues/14397
123 def apply_w8a8_block_fp8_linear(
@@ -158,9 +162,6 @@ def apply_w8a8_block_fp8_linear(
162 if current_platform.is_cuda():
163 if current_platform.has_device_capability(100):
164
161 - def ceil_div(x: int, y: int) -> int:
162 - return (x + y - 1) // y
163 -
165 use_cutlass = cutlass_block_fp8_supported and (
166 ceil_div(weight.shape[0], 128) == weight_scale.shape[0]
167 and ceil_div(weight.shape[1], 128) == weight_scale.shape[1])
@@ -655,3 +656,67 @@ def w8a8_block_fp8_matmul(
656 )
657
658 return C
659 +
660 +
661 +# Taken from https://github.com/deepseek-ai/DeepGEMM/blob/0c88cd01392c1073c7049a97d6328c7bba9b3947
662 +# TODO(wentao): remove this function when DeepGEMM exposes this function
663 +def get_tma_aligned_size(x: int, element_size: int) -> int:
664 + """
665 + Global memory address of TMA must be 16-byte aligned.
666 + Since we use column-major layout for the LHS scaling tensor,
667 + the M-axis of the LHS scaling tensor needs to be padded to a multiple of
668 + 16 bytes.
669 +
670 + Arguments:
671 + x: original M-axis shape of the LHS scaling tensor.
672 + element_size: element size of the LHS scaling tensor.
673 +
674 + Returns:
675 + M-axis shape of the LHS scaling tensor after padding.
676 + """
677 + tma_alignment_bytes = 16
678 + assert tma_alignment_bytes % element_size == 0
679 + alignment = tma_alignment_bytes // element_size
680 + return ceil_div(x, alignment) * alignment
681 +
682 +
683 +# Taken from https://github.com/deepseek-ai/DeepGEMM/blob/0c88cd01392c1073c7049a97d6328c7bba9b3947
684 +# TODO(wentao): remove this function when DeepGEMM exposes this function
685 +def get_col_major_tma_aligned_tensor(x: torch.Tensor) -> torch.Tensor:
686 + """
687 + Returns TMA-aligned transposed format of the input tensor. `torch.transpose`
688 + will be called if necessary.
689 + If the input tensor is already column-major layout and 16-byte aligned along
690 + the M axis (thus meets the requirement of LHS scaling tensor in
691 + DeepGEMM), this function will do nothing.
692 +
693 + Arguments:
694 + x: usually the LHS scaling tensor in GEMM.
695 +
696 + Returns:
697 + The LHS scaling tensor of TMA-aligned transposed format.
698 + """
699 + # NOTES: for the extreme performance, you may rewrite/fuse this function in
700 + # CUDA
701 + assert x.dim() in (2, 3)
702 + remove_dim = False
703 + m, n = x.shape[-2], x.shape[-1]
704 + aligned_m = get_tma_aligned_size(m, x.element_size())
705 + if x.dim() == 2:
706 + if x.stride(0) == 1 and x.stride(1) == aligned_m:
707 + return x
708 + x, remove_dim = x.unsqueeze(0), True
709 +
710 + b = x.shape[0]
711 +
712 + # The last kernel gives a column-major TMA aligned layout
713 + if x.stride(0) == aligned_m * n and x.stride(1) == 1 and x.stride(
714 + 2) == aligned_m:
715 + return x.squeeze(0) if remove_dim else x
716 +
717 + # Normal layout requires transposing
718 + aligned_x = torch.transpose(
719 + torch.empty((b, n, aligned_m), device=x.device, dtype=x.dtype), 1, 2)
720 + aligned_x[:, :m, :] = x
721 + aligned_x = aligned_x[:, :m, :]
722 + return aligned_x.squeeze(0) if remove_dim else aligned_x