| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from collections.abc import Callable, Iterable |
| 5 | from dataclasses import dataclass |
| 6 | from itertools import product |
| 7 | |
| 8 | import torch |
| 9 | import torch.nn.functional as F |
| 10 | import torch.utils.benchmark as TBenchmark |
| 11 | from torch.utils.benchmark import Measurement as TMeasurement |
| 12 | from tqdm import tqdm |
| 13 | |
| 14 | import vllm._custom_ops as ops |
| 15 | from vllm.model_executor.layers.quantization.utils.fp8_utils import ( |
| 16 | per_token_group_quant_fp8, |
| 17 | ) |
| 18 | |
| 19 | |
| 20 | @dataclass |
| 21 | class bench_params_t: |
| 22 | num_tokens: int |
| 23 | hidden_size: int |
| 24 | dtype: torch.dtype |
| 25 | group_size: int # Changed from list[int] to int |
| 26 | |
| 27 | def description(self): |
| 28 | return ( |
| 29 | f"N {self.num_tokens} " |
| 30 | f"x D {self.hidden_size} " |
| 31 | f"x DT {self.dtype} " |
| 32 | f"x GS {self.group_size}" |
| 33 | ) |
| 34 | |
| 35 | |
| 36 | def get_bench_params() -> list[bench_params_t]: |
| 37 | """Test configurations covering common model sizes.""" |
| 38 | NUM_TOKENS = [16, 128, 512, 2048] |
| 39 | HIDDEN_SIZES = [1024, 2048, 4096, 5120, 14336] # Common FFN sizes |
| 40 | DTYPES = [torch.float16, torch.bfloat16] |
| 41 | GROUP_SIZES = [64, 128] # Changed from [[1, 64], [1, 128]] |
| 42 | |
| 43 | combinations = product(NUM_TOKENS, HIDDEN_SIZES, DTYPES, GROUP_SIZES) |
| 44 | bench_params = list( |
| 45 | map(lambda x: bench_params_t(x[0], x[1], x[2], x[3]), combinations) |
| 46 | ) |
| 47 | return bench_params |
| 48 | |
| 49 | |
| 50 | # Reference implementations |
| 51 | def unfused_fp8_impl( |
| 52 | x: torch.Tensor, |
| 53 | quant_dtype: torch.dtype, |
| 54 | group_size: int, # Changed from list[int] |
| 55 | ): |
| 56 | """Unfused: SiLU+Mul then per-tensor quantize.""" |
| 57 | hidden = x.shape[-1] // 2 |
| 58 | gate, up = x.split(hidden, dim=-1) |
| 59 | |
| 60 | # SiLU(gate) * up |
| 61 | silu_out = F.silu(gate) * up |
| 62 | |
| 63 | # Per-tensor quantize (no group_size used here) |
| 64 | silu_out, _ = ops.scaled_fp8_quant(silu_out) |
| 65 | |
| 66 | |
| 67 | def unfused_groupwise_fp8_impl( |
| 68 | x: torch.Tensor, |
| 69 | quant_dtype: torch.dtype, |
| 70 | group_size: int, # Changed from list[int] |
| 71 | ): |
| 72 | """Unfused: SiLU+Mul then group-wise quantize.""" |
| 73 | hidden = x.shape[-1] // 2 |
| 74 | gate, up = x.split(hidden, dim=-1) |
| 75 | |
| 76 | # SiLU(gate) * up |
| 77 | silu_out = F.silu(gate) * up |
| 78 | |
| 79 | # Group quantize - use group_size directly |
| 80 | silu_out, _ = per_token_group_quant_fp8( |
| 81 | silu_out, group_size=group_size, use_ue8m0=False |
| 82 | ) |
| 83 | |
| 84 | |
| 85 | def fused_impl( |
| 86 | x: torch.Tensor, |
| 87 | quant_dtype: torch.dtype, |
| 88 | group_size: int, |
| 89 | ): |
| 90 | """Fused: SiLU+Mul+Block Quantization in single kernel.""" |
| 91 | out, _ = ops.silu_and_mul_per_block_quant( |
| 92 | x, |
| 93 | group_size=group_size, |
| 94 | quant_dtype=quant_dtype, |
| 95 | is_scale_transposed=False, |
| 96 | ) |
| 97 | |
| 98 | |
| 99 | # Bench functions |
| 100 | def bench_fn( |
| 101 | x: torch.Tensor, |
| 102 | quant_dtype: torch.dtype, |
| 103 | group_size: int, |
| 104 | label: str, |
| 105 | sub_label: str, |
| 106 | fn: Callable, |
| 107 | description: str, |
| 108 | ) -> TMeasurement: |
| 109 | min_run_time = 1 |
| 110 | |
| 111 | globals = { |
| 112 | "x": x, |
| 113 | "quant_dtype": quant_dtype, |
| 114 | "group_size": group_size, |
| 115 | "fn": fn, |
| 116 | } |
| 117 | return TBenchmark.Timer( |
| 118 | stmt="fn(x, quant_dtype, group_size)", |
| 119 | globals=globals, |
| 120 | label=label, |
| 121 | sub_label=sub_label, |
| 122 | description=description, |
| 123 | ).blocked_autorange(min_run_time=min_run_time) |
| 124 | |
| 125 | |
| 126 | def bench(params: bench_params_t, label: str, sub_label: str) -> Iterable[TMeasurement]: |
| 127 | """Run benchmarks for all implementations.""" |
| 128 | # Make inputs: [num_tokens, hidden_size * 2] for [gate || up] |
| 129 | scale = 1 / params.hidden_size |
| 130 | x = ( |
| 131 | torch.randn( |
| 132 | params.num_tokens, |
| 133 | params.hidden_size * 2, |
| 134 | dtype=params.dtype, |
| 135 | device="cuda", |
| 136 | ) |
| 137 | * scale |
| 138 | ) |
| 139 | |
| 140 | timers = [] |
| 141 | |
| 142 | # Unfused per-tensor FP8 |
| 143 | timers.append( |
| 144 | bench_fn( |
| 145 | x, |
| 146 | torch.float8_e4m3fn, |
| 147 | params.group_size, |
| 148 | label, |
| 149 | sub_label, |
| 150 | unfused_fp8_impl, |
| 151 | "unfused_fp8_impl", |
| 152 | ) |
| 153 | ) |
| 154 | |
| 155 | # Unfused group-wise FP8 |
| 156 | timers.append( |
| 157 | bench_fn( |
| 158 | x, |
| 159 | torch.float8_e4m3fn, |
| 160 | params.group_size, |
| 161 | label, |
| 162 | sub_label, |
| 163 | unfused_groupwise_fp8_impl, |
| 164 | "unfused_groupwise_fp8_impl", |
| 165 | ) |
| 166 | ) |
| 167 | |
| 168 | # Fused group-wise FP8 |
| 169 | timers.append( |
| 170 | bench_fn( |
| 171 | x, |
| 172 | torch.float8_e4m3fn, |
| 173 | params.group_size, |
| 174 | label, |
| 175 | sub_label, |
| 176 | fused_impl, |
| 177 | "fused_groupwise_fp8_impl", |
| 178 | ) |
| 179 | ) |
| 180 | |
| 181 | return timers |
| 182 | |
| 183 | |
| 184 | def print_timers(timers: Iterable[TMeasurement]): |
| 185 | compare = TBenchmark.Compare(timers) |
| 186 | compare.print() |
| 187 | |
| 188 | |
| 189 | def main(): |
| 190 | torch.set_default_device("cuda") |
| 191 | bench_params = get_bench_params() |
| 192 | |
| 193 | print(f"Running {len(bench_params)} benchmark configurations...") |
| 194 | print( |
| 195 | f"This will take approximately {len(bench_params) * 3} seconds (1s per variant)" |
| 196 | ) |
| 197 | print() |
| 198 | |
| 199 | timers = [] |
| 200 | for bp in tqdm(bench_params): |
| 201 | result_timers = bench(bp, "silu-mul-block-quant", bp.description()) |
| 202 | timers.extend(result_timers) |
| 203 | |
| 204 | print("\n" + "=" * 80) |
| 205 | print("FINAL COMPARISON - ALL RESULTS") |
| 206 | print("=" * 80) |
| 207 | print_timers(timers) |
| 208 | |
| 209 | |
| 210 | if __name__ == "__main__": |
| 211 | main() |