main
py 376 lines 10.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import argparse
5 from typing import Any, TypedDict
6
7 import ray
8 import torch
9 from transformers import AutoConfig
10
11 from vllm.model_executor.layers.fused_moe import fused_topk
12 from vllm.model_executor.layers.fused_moe.moe_permute_unpermute import (
13 MoEPermuteScratch,
14 moe_permute,
15 moe_unpermute,
16 )
17 from vllm.model_executor.layers.fused_moe.utils import _fp8_quantize
18 from vllm.platforms import current_platform
19 from vllm.utils.argparse_utils import FlexibleArgumentParser
20 from vllm.utils.torch_utils import set_random_seed
21
22 FP8_DTYPE = current_platform.fp8_dtype()
23
24
25 class BenchmarkConfig(TypedDict):
26 BLOCK_SIZE_M: int
27 BLOCK_SIZE_N: int
28 BLOCK_SIZE_K: int
29 GROUP_SIZE_M: int
30 num_warps: int
31 num_stages: int
32
33
34 def benchmark_permute(
35 num_tokens: int,
36 num_experts: int,
37 hidden_size: int,
38 topk: int,
39 dtype: torch.dtype,
40 use_fp8_w8a8: bool,
41 use_int8_w8a16: bool,
42 num_iters: int = 100,
43 ) -> float:
44 # init_dtype = torch.float16 if use_fp8_w8a8 else dtype
45 hidden_states = torch.randn(num_tokens, hidden_size, dtype=dtype)
46 # output_hidden_states = torch.empty_like(hidden_states)
47 if use_fp8_w8a8:
48 qhidden_states, scale = _fp8_quantize(hidden_states, None, None)
49 else:
50 qhidden_states = hidden_states
51
52 gating_output = torch.randn(num_iters, num_tokens, num_experts, dtype=torch.float32)
53
54 input_gating = torch.randn(num_tokens, num_experts, dtype=torch.float32)
55 topk_weights, topk_ids, token_expert_indices = fused_topk(
56 qhidden_states, input_gating, topk, False
57 )
58 scratch = MoEPermuteScratch(
59 max_num_tokens=num_tokens,
60 topk=topk,
61 num_experts=num_experts,
62 num_local_experts=num_experts,
63 device=qhidden_states.device,
64 hidden_size=hidden_size,
65 hidden_dtype=qhidden_states.dtype,
66 )
67
68 def prepare(i: int):
69 input_gating.copy_(gating_output[i])
70
71 def run():
72 moe_permute(
73 qhidden_states,
74 a1q_scale=None,
75 topk_ids=topk_ids,
76 n_expert=num_experts,
77 expert_map=None,
78 scratch=scratch,
79 )
80
81 # JIT compilation & warmup
82 run()
83 torch.accelerator.synchronize()
84
85 # Capture 10 invocations with CUDA graph
86 graph = torch.cuda.CUDAGraph()
87 with torch.cuda.graph(graph):
88 for _ in range(10):
89 run()
90 torch.accelerator.synchronize()
91
92 # Warmup
93 for _ in range(5):
94 graph.replay()
95 torch.accelerator.synchronize()
96
97 start_event = torch.Event(enable_timing=True)
98 end_event = torch.Event(enable_timing=True)
99
100 latencies: list[float] = []
101 for i in range(num_iters):
102 prepare(i)
103 torch.accelerator.synchronize()
104
105 start_event.record()
106 graph.replay()
107 end_event.record()
108 end_event.synchronize()
109 latencies.append(start_event.elapsed_time(end_event))
110 avg = sum(latencies) / (num_iters * 10) * 1000 # us
111 graph.reset()
112 return avg
113
114
115 def benchmark_unpermute(
116 num_tokens: int,
117 num_experts: int,
118 hidden_size: int,
119 topk: int,
120 dtype: torch.dtype,
121 use_fp8_w8a8: bool,
122 use_int8_w8a16: bool,
123 num_iters: int = 100,
124 ) -> float:
125 # init_dtype = torch.float16 if use_fp8_w8a8 else dtype
126 hidden_states = torch.randn(num_tokens, hidden_size, dtype=dtype)
127 if use_fp8_w8a8:
128 qhidden_states, scale = _fp8_quantize(hidden_states, None, None)
129 else:
130 qhidden_states = hidden_states
131
132 input_gating = torch.randn(num_tokens, num_experts, dtype=torch.float32)
133
134 topk_weights, topk_ids, token_expert_indices = fused_topk(
135 qhidden_states, input_gating, topk, False
136 )
137 scratch = MoEPermuteScratch(
138 max_num_tokens=num_tokens,
139 topk=topk,
140 num_experts=num_experts,
141 num_local_experts=num_experts,
142 device=qhidden_states.device,
143 hidden_size=hidden_size,
144 hidden_dtype=qhidden_states.dtype,
145 )
146
147 def prepare():
148 (
149 permuted_hidden_states,
150 _,
151 first_token_off,
152 inv_perm_idx,
153 _,
154 ) = moe_permute(
155 qhidden_states,
156 a1q_scale=None,
157 topk_ids=topk_ids,
158 n_expert=num_experts,
159 expert_map=None,
160 scratch=scratch,
161 )
162 # convert to fp16/bf16 as gemm output
163 return (
164 permuted_hidden_states.to(dtype),
165 first_token_off,
166 inv_perm_idx,
167 )
168
169 def run(input: tuple):
170 (permuted_hidden_states, first_token_off, inv_perm_idx) = input
171 output = torch.empty_like(hidden_states)
172 moe_unpermute(
173 output,
174 permuted_hidden_states,
175 topk_weights,
176 inv_perm_idx,
177 first_token_off,
178 )
179
180 # JIT compilation & warmup
181 input = prepare()
182 run(input)
183 torch.accelerator.synchronize()
184
185 # Capture 10 invocations with CUDA graph
186 graph = torch.cuda.CUDAGraph()
187 with torch.cuda.graph(graph):
188 for _ in range(10):
189 run(input)
190 torch.accelerator.synchronize()
191
192 # Warmup
193 for _ in range(5):
194 graph.replay()
195 torch.accelerator.synchronize()
196
197 start_event = torch.Event(enable_timing=True)
198 end_event = torch.Event(enable_timing=True)
199
200 latencies: list[float] = []
201 for i in range(num_iters):
202 torch.accelerator.synchronize()
203 start_event.record()
204 graph.replay()
205 end_event.record()
206 end_event.synchronize()
207 latencies.append(start_event.elapsed_time(end_event))
208 avg = sum(latencies) / (num_iters * 10) * 1000 # us
209 graph.reset()
210 return avg
211
212
213 @ray.remote(num_gpus=1)
214 class BenchmarkWorker:
215 def __init__(self, seed: int) -> None:
216 torch.set_default_device("cuda")
217 set_random_seed(seed)
218 self.seed = seed
219 # Get the device ID to allocate tensors and kernels
220 # on the respective GPU. This is required for Ray to work
221 # correctly with multi-GPU tuning on the ROCm platform.
222 self.device_id = int(ray.get_gpu_ids()[0])
223
224 def benchmark(
225 self,
226 num_tokens: int,
227 num_experts: int,
228 hidden_size: int,
229 topk: int,
230 dtype: torch.dtype,
231 use_fp8_w8a8: bool,
232 use_int8_w8a16: bool,
233 ) -> tuple[float, float]:
234 set_random_seed(self.seed)
235
236 permute_time = benchmark_permute(
237 num_tokens,
238 num_experts,
239 hidden_size,
240 topk,
241 dtype,
242 use_fp8_w8a8,
243 use_int8_w8a16,
244 num_iters=100,
245 )
246 unpermute_time = benchmark_unpermute(
247 num_tokens,
248 num_experts,
249 hidden_size,
250 topk,
251 dtype,
252 use_fp8_w8a8,
253 use_int8_w8a16,
254 num_iters=100,
255 )
256 return permute_time, unpermute_time
257
258
259 def get_weight_block_size_safety(config, default_value=None):
260 quantization_config = getattr(config, "quantization_config", {})
261 if isinstance(quantization_config, dict):
262 return quantization_config.get("weight_block_size", default_value)
263 return default_value
264
265
266 def main(args: argparse.Namespace):
267 print(args)
268
269 config = AutoConfig.from_pretrained(
270 args.model, trust_remote_code=args.trust_remote_code
271 )
272 if config.architectures[0] == "DbrxForCausalLM":
273 E = config.ffn_config.moe_num_experts
274 topk = config.ffn_config.moe_top_k
275 elif config.architectures[0] == "JambaForCausalLM":
276 E = config.num_experts
277 topk = config.num_experts_per_tok
278 elif (
279 config.architectures[0] == "DeepseekV3ForCausalLM"
280 or config.architectures[0] == "DeepseekV2ForCausalLM"
281 or config.architectures[0] == "Glm4MoeForCausalLM"
282 or config.architectures[0] == "Glm4MoeLiteForCausalLM"
283 ):
284 E = config.n_routed_experts
285 topk = config.num_experts_per_tok
286 elif config.architectures[0] in ["Qwen2MoeForCausalLM", "Qwen3MoeForCausalLM"]:
287 E = config.num_experts
288 topk = config.num_experts_per_tok
289
290 else:
291 # Support for llama4
292 config = config.get_text_config()
293 # Default: Mixtral.
294 E = config.num_local_experts
295 topk = config.num_experts_per_tok
296
297 hidden_size = config.hidden_size
298 dtype = torch.float16 if current_platform.is_rocm() else config.dtype
299 use_fp8_w8a8 = args.dtype == "fp8_w8a8"
300 use_int8_w8a16 = args.dtype == "int8_w8a16"
301
302 if args.batch_size is None:
303 batch_sizes = [
304 1,
305 2,
306 4,
307 8,
308 16,
309 24,
310 32,
311 48,
312 64,
313 96,
314 128,
315 256,
316 512,
317 1024,
318 1536,
319 2048,
320 3072,
321 4096,
322 ]
323 else:
324 batch_sizes = [args.batch_size]
325
326 ray.init()
327 num_gpus = int(ray.available_resources()["GPU"])
328 workers = [BenchmarkWorker.remote(args.seed) for _ in range(num_gpus)]
329
330 def _distribute(method: str, inputs: list[Any]) -> list[Any]:
331 outputs = []
332 worker_idx = 0
333 for input_args in inputs:
334 worker = workers[worker_idx]
335 worker_method = getattr(worker, method)
336 output = worker_method.remote(*input_args)
337 outputs.append(output)
338 worker_idx = (worker_idx + 1) % num_gpus
339 return ray.get(outputs)
340
341 outputs = _distribute(
342 "benchmark",
343 [
344 (
345 batch_size,
346 E,
347 hidden_size,
348 topk,
349 dtype,
350 use_fp8_w8a8,
351 use_int8_w8a16,
352 )
353 for batch_size in batch_sizes
354 ],
355 )
356
357 for batch_size, (permute, unpermute) in zip(batch_sizes, outputs):
358 print(f"Batch size: {batch_size}")
359 print(f"Permute time: {permute:.2f} us")
360 print(f"Unpermute time: {unpermute:.2f} us")
361
362
363 if __name__ == "__main__":
364 parser = FlexibleArgumentParser()
365 parser.add_argument(
366 "--model", type=str, default="mistralai/Mixtral-8x7B-Instruct-v0.1"
367 )
368 parser.add_argument(
369 "--dtype", type=str, choices=["auto", "fp8_w8a8", "int8_w8a16"], default="auto"
370 )
371 parser.add_argument("--seed", type=int, default=0)
372 parser.add_argument("--batch-size", type=int, required=False)
373 parser.add_argument("--trust-remote-code", action="store_true")
374 args = parser.parse_args()
375
376 main(args)