main
py 272 lines 8.09 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import itertools
4 from collections.abc import Callable
5 from unittest.mock import patch
6
7 import pandas as pd
8 import torch
9
10 from vllm.benchmarks.lib.utils import default_vllm_config
11 from vllm.model_executor.layers.quantization.input_quant_fp8 import QuantFP8
12 from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
13 from vllm.triton_utils import triton
14 from vllm.utils.argparse_utils import FlexibleArgumentParser
15 from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE
16
17
18 def with_triton_mode(fn):
19 """Temporarily force the Triton fallback path."""
20
21 def wrapped(*args, **kwargs):
22 with patch("vllm.platforms.current_platform.is_cuda", return_value=False):
23 return fn(*args, **kwargs)
24
25 return wrapped
26
27
28 # TODO(luka): use standalone_compile utility
29 def with_dyn_arg(fn: Callable, arg_index: int, dim_index: int):
30 def inner(*args):
31 torch._dynamo.mark_dynamic(args[arg_index], dim_index)
32 return fn(*args)
33
34 return inner
35
36
37 def bench_compile(fn: Callable):
38 # recompile for different shapes
39 fwd = torch.compile(fn, fullgraph=True, dynamic=False)
40
41 # First dim is explicitly dynamic to simulate vLLM usage
42 return with_dyn_arg(fwd, 0, 0)
43
44
45 torch._dynamo.config.recompile_limit = 8888
46
47
48 def calculate_diff(
49 batch_size: int,
50 hidden_size: int,
51 group_shape: GroupShape,
52 dtype: torch.dtype,
53 ):
54 """Calculate the difference between Inductor and CUDA implementations."""
55 device = torch.device("cuda")
56 x = torch.randn((batch_size, hidden_size), dtype=dtype, device=device)
57
58 quant_fp8 = QuantFP8(False, group_shape, column_major_scales=False)
59
60 torch_out, torch_scale = bench_compile(quant_fp8.forward_native)(x)
61 torch_eager_out, torch_eager_scale = quant_fp8.forward_native(x)
62 cuda_out, cuda_scale = quant_fp8.forward_cuda(x)
63
64 try:
65 torch.testing.assert_close(
66 cuda_out.to(torch.float32),
67 torch_out.to(torch.float32),
68 rtol=1e-3,
69 atol=1e-5,
70 )
71 torch.testing.assert_close(cuda_scale, torch_scale, rtol=1e-3, atol=1e-5)
72 torch.testing.assert_close(
73 cuda_out.to(torch.float32),
74 torch_eager_out.to(torch.float32),
75 rtol=1e-3,
76 atol=1e-5,
77 )
78 torch.testing.assert_close(cuda_scale, torch_eager_scale, rtol=1e-3, atol=1e-5)
79 print("✅ All implementations match")
80 except AssertionError as e:
81 print("❌ Implementations differ")
82 print(e)
83
84
85 configs = []
86
87
88 @default_vllm_config()
89 def benchmark_quantization(
90 batch_size,
91 hidden_size,
92 provider,
93 group_shape: GroupShape,
94 col_major: bool,
95 dtype: torch.dtype,
96 ):
97 device = torch.device("cuda")
98
99 x = torch.randn(batch_size, hidden_size, device=device, dtype=dtype)
100
101 quantiles = [0.5, 0.2, 0.8]
102 quant_fp8 = QuantFP8(False, group_shape, column_major_scales=col_major)
103
104 if provider == "torch":
105 fn = lambda: bench_compile(quant_fp8.forward_native)(x.clone())
106 elif provider == "cuda":
107 fn = lambda: quant_fp8.forward_cuda(x.clone())
108 elif provider == "triton":
109 if not group_shape.is_per_group():
110 # Triton only supported for per-group
111 return 0, 0, 0
112
113 fn = lambda: with_triton_mode(quant_fp8.forward_cuda)(x.clone())
114
115 ms, min_ms, max_ms = triton.testing.do_bench_cudagraph(fn, quantiles=quantiles)
116
117 return 1000 * ms, 1000 * max_ms, 1000 * min_ms
118
119
120 # TODO(luka) extract to utils
121 def compute_geomean_speedups(
122 df: pd.DataFrame,
123 baseline_col: str,
124 speedup_cols: list[str],
125 groupby_cols: list[str] | None = None,
126 ) -> pd.DataFrame:
127 """Compute geometric mean speedups over a baseline column.
128
129 Args:
130 df: Input dataframe
131 baseline_col: Column to use as baseline
132 speedup_cols: Columns to compute speedups for
133 groupby_cols: Columns to group by. If None, compute over entire df.
134
135 Returns:
136 pd.DataFrame with geometric mean speedups
137
138 """
139 from scipy.stats import gmean
140
141 def geo_speedup(group: pd.DataFrame) -> pd.Series:
142 ratios = {
143 col: (group[baseline_col] / group[col]).values for col in speedup_cols
144 }
145 return pd.Series({col: gmean(vals) for col, vals in ratios.items()})
146
147 if groupby_cols is None:
148 result = geo_speedup(df).to_frame().T
149 else:
150 result = (
151 df.groupby(groupby_cols)
152 .apply(geo_speedup, include_groups=False)
153 .reset_index()
154 )
155
156 return result
157
158
159 if __name__ == "__main__":
160 parser = FlexibleArgumentParser(
161 description="Benchmark the various implementations of QuantFP8 (dynamic-only)"
162 )
163 parser.add_argument("-c", "--check", action="store_true")
164 parser.add_argument(
165 "--dtype", type=str, choices=["half", "bfloat16", "float"], default="bfloat16"
166 )
167 parser.add_argument(
168 "--hidden-sizes",
169 type=int,
170 nargs="+",
171 default=[896, 1024, 2048, 4096, 7168],
172 help="Hidden sizes to benchmark",
173 )
174 parser.add_argument(
175 "--batch-sizes",
176 type=int,
177 nargs="+",
178 default=[1, 16, 128, 512, 1024],
179 help="Batch sizes to benchmark",
180 )
181 parser.add_argument(
182 "--group-sizes",
183 type=int,
184 nargs="+",
185 default=None,
186 help="Group sizes for GroupShape(1,N) to benchmark. "
187 "Use 0 for PER_TENSOR, -1 for PER_TOKEN (default: 0,-1,64,128)",
188 )
189 parser.add_argument(
190 "--no-column-major",
191 action="store_true",
192 help="Disable column-major scales testing",
193 )
194
195 args = parser.parse_args()
196 assert args
197
198 dtype = STR_DTYPE_TO_TORCH_DTYPE[args.dtype]
199
200 hidden_sizes = args.hidden_sizes
201 batch_sizes = args.batch_sizes
202
203 if args.group_sizes is not None:
204 group_shapes = []
205 for size in args.group_sizes:
206 if size == 0:
207 group_shapes.append(GroupShape.PER_TENSOR)
208 elif size == -1:
209 group_shapes.append(GroupShape.PER_TOKEN)
210 else:
211 group_shapes.append(GroupShape(1, size))
212 else:
213 group_shapes = [
214 GroupShape.PER_TENSOR,
215 GroupShape.PER_TOKEN,
216 GroupShape(1, 64),
217 GroupShape(1, 128),
218 ]
219
220 column_major_scales = [False] if args.no_column_major else [True, False]
221
222 config_gen = itertools.product(
223 group_shapes,
224 column_major_scales,
225 batch_sizes,
226 hidden_sizes,
227 )
228
229 # filter out column-major scales for non-group, reverse order
230 configs.extend(c[::-1] for c in config_gen if (c[0].is_per_group() or not c[1]))
231
232 print(f"Running {len(configs)} configurations:")
233 print(f" Hidden sizes: {hidden_sizes}")
234 print(f" Batch sizes: {batch_sizes}")
235 print(f" Group shapes: {[str(g) for g in group_shapes]}")
236 print(f" Column major scales: {column_major_scales}")
237 print()
238
239 if args.check:
240 for group_shape in group_shapes:
241 group_size = group_shape[1]
242 print(f"{group_size=}")
243 calculate_diff(
244 batch_size=4, hidden_size=4096, group_shape=group_shape, dtype=dtype
245 )
246
247 benchmark = triton.testing.perf_report(
248 triton.testing.Benchmark(
249 x_names=["hidden_size", "batch_size", "col_major", "group_shape"],
250 x_vals=configs,
251 line_arg="provider",
252 line_vals=["torch", "cuda", "triton"],
253 line_names=["Torch (Compiled)", "CUDA", "Triton"],
254 styles=[("blue", "-"), ("green", "-"), ("black", "-")],
255 ylabel="us",
256 plot_name="QuantFP8 performance",
257 args={},
258 )
259 )(benchmark_quantization)
260
261 df = benchmark.run(print_data=True, dtype=dtype, return_df=True)
262
263 # Print geomean speedups
264 geo_table_grouped = compute_geomean_speedups(
265 df,
266 baseline_col="Torch (Compiled)",
267 speedup_cols=["CUDA", "Triton"],
268 groupby_cols=["col_major", "group_shape"],
269 )
270
271 print("Speedup over Torch (Compiled)")
272 print(geo_table_grouped.to_string(index=False))