@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 009d9e7590
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, 11:00 UTC

Convert `benchmarks` to `ruff format` (#18068)

Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Harry Mellor committed May 13, 2025 at 14:43 UTC 009d9e759070422b3f3bbc1cd7d77b5882f905ad
41 files changed +4003 -2961
.buildkite/pyproject.toml
-4
@@ -1,13 +1,9 @@
1 # This local pyproject file is part of the migration from yapf to ruff format.
2 # It uses the same core rules as the main pyproject.toml file, but with the
3 # following differences:
4 -# - isort profile is set to black
4 # - ruff line length is overridden to 88
5 # - deprecated typing ignores (UP006, UP035) have been removed
6
8 -[tool.isort]
9 -profile = "black"
10 -
7 [tool.ruff]
8 line-length = 88
9 exclude = [
.pre-commit-config.yaml
+1 -3
@@ -17,7 +17,7 @@ repos:
17 - id: ruff
18 args: [--output-format, github, --fix]
19 - id: ruff-format
20 - files: ^(.buildkite).*
20 + files: ^(.buildkite|benchmarks)/.*
21 - repo: https://github.com/codespell-project/codespell
22 rev: v2.4.1
23 hooks:
@@ -28,8 +28,6 @@ repos:
28 rev: 6.0.1
29 hooks:
30 - id: isort
31 - # necessary during the transition from yapf to ruff format
32 - args: [--resolve-all-configs, --config-root, .]
31 - repo: https://github.com/pre-commit/mirrors-clang-format
32 rev: v20.1.3
33 hooks:
benchmarks/backend_request_func.py
+94 -95
@@ -12,8 +12,7 @@ from typing import Optional, Union
12 import aiohttp
13 import huggingface_hub.constants
14 from tqdm.asyncio import tqdm
15 -from transformers import (AutoTokenizer, PreTrainedTokenizer,
16 - PreTrainedTokenizerFast)
15 +from transformers import AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast
16
17 # NOTE(simon): do not import vLLM here so the benchmark script
18 # can run without vLLM installed.
@@ -43,8 +42,7 @@ class RequestFuncOutput:
42 latency: float = 0.0
43 output_tokens: int = 0
44 ttft: float = 0.0 # Time to first token
46 - itl: list[float] = field(
47 - default_factory=list) # list of inter-token latencies
45 + itl: list[float] = field(default_factory=list) # list of inter-token latencies
46 tpot: float = 0.0 # avg next-token latencies
47 prompt_len: int = 0
48 error: str = ""
@@ -57,8 +55,9 @@ async def async_request_tgi(
55 api_url = request_func_input.api_url
56 assert api_url.endswith("generate_stream")
57
60 - async with aiohttp.ClientSession(trust_env=True,
61 - timeout=AIOHTTP_TIMEOUT) as session:
58 + async with aiohttp.ClientSession(
59 + trust_env=True, timeout=AIOHTTP_TIMEOUT
60 + ) as session:
61 params = {
62 "max_new_tokens": request_func_input.output_len,
63 "do_sample": True,
@@ -105,8 +104,7 @@ async def async_request_tgi(
104
105 # Decoding phase
106 else:
108 - output.itl.append(timestamp -
109 - most_recent_timestamp)
107 + output.itl.append(timestamp - most_recent_timestamp)
108
109 most_recent_timestamp = timestamp
110
@@ -133,8 +131,9 @@ async def async_request_trt_llm(
131 api_url = request_func_input.api_url
132 assert api_url.endswith("generate_stream")
133
136 - async with aiohttp.ClientSession(trust_env=True,
137 - timeout=AIOHTTP_TIMEOUT) as session:
134 + async with aiohttp.ClientSession(
135 + trust_env=True, timeout=AIOHTTP_TIMEOUT
136 + ) as session:
137 payload = {
138 "accumulate_tokens": True,
139 "text_input": request_func_input.prompt,
@@ -159,8 +158,7 @@ async def async_request_trt_llm(
158 if not chunk_bytes:
159 continue
160
162 - chunk = chunk_bytes.decode("utf-8").removeprefix(
163 - "data:")
161 + chunk = chunk_bytes.decode("utf-8").removeprefix("data:")
162
163 data = json.loads(chunk)
164 output.generated_text += data["text_output"]
@@ -172,8 +170,7 @@ async def async_request_trt_llm(
170
171 # Decoding phase
172 else:
175 - output.itl.append(timestamp -
176 - most_recent_timestamp)
173 + output.itl.append(timestamp - most_recent_timestamp)
174
175 most_recent_timestamp = timestamp
176
@@ -197,9 +194,9 @@ async def async_request_deepspeed_mii(
194 request_func_input: RequestFuncInput,
195 pbar: Optional[tqdm] = None,
196 ) -> RequestFuncOutput:
200 - async with aiohttp.ClientSession(trust_env=True,
201 - timeout=AIOHTTP_TIMEOUT) as session:
202 -
197 + async with aiohttp.ClientSession(
198 + trust_env=True, timeout=AIOHTTP_TIMEOUT
199 + ) as session:
200 payload = {
201 "model": request_func_input.model,
202 "prompt": request_func_input.prompt,
@@ -217,19 +214,21 @@ async def async_request_deepspeed_mii(
214
215 st = time.perf_counter()
216 try:
220 - async with session.post(url=request_func_input.api_url,
221 - json=payload) as response:
217 + async with session.post(
218 + url=request_func_input.api_url, json=payload
219 + ) as response:
220 if response.status == 200:
221 parsed_resp = await response.json()
222 output.latency = time.perf_counter() - st
223 if "choices" in parsed_resp:
226 - output.generated_text = parsed_resp["choices"][0][
227 - "text"]
224 + output.generated_text = parsed_resp["choices"][0]["text"]
225 elif "text" in parsed_resp:
226 output.generated_text = parsed_resp["text"][0]
227 else:
231 - output.error = ("Unexpected response format: "
232 - "neither 'choices' nor 'text' found")
228 + output.error = (
229 + "Unexpected response format: "
230 + "neither 'choices' nor 'text' found"
231 + )
232 output.success = False
233 output.success = True
234 else:
@@ -250,15 +249,17 @@ async def async_request_openai_completions(
249 pbar: Optional[tqdm] = None,
250 ) -> RequestFuncOutput:
251 api_url = request_func_input.api_url
253 - assert api_url.endswith(
254 - ("completions", "profile")
255 - ), "OpenAI Completions API URL must end with 'completions' or 'profile'."
252 + assert api_url.endswith(("completions", "profile")), (
253 + "OpenAI Completions API URL must end with 'completions' or 'profile'."
254 + )
255
257 - async with aiohttp.ClientSession(trust_env=True,
258 - timeout=AIOHTTP_TIMEOUT) as session:
256 + async with aiohttp.ClientSession(
257 + trust_env=True, timeout=AIOHTTP_TIMEOUT
258 + ) as session:
259 payload = {
260 - "model": request_func_input.model_name \
261 - if request_func_input.model_name else request_func_input.model,
260 + "model": request_func_input.model_name
261 + if request_func_input.model_name
262 + else request_func_input.model,
263 "prompt": request_func_input.prompt,
264 "temperature": 0.0,
265 "repetition_penalty": 1.0,
@@ -273,9 +274,7 @@ async def async_request_openai_completions(
274 payload["ignore_eos"] = request_func_input.ignore_eos
275 if request_func_input.extra_body:
276 payload.update(request_func_input.extra_body)
276 - headers = {
277 - "Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"
278 - }
277 + headers = {"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"}
278
279 output = RequestFuncOutput()
280 output.prompt_len = request_func_input.prompt_len
@@ -284,8 +283,9 @@ async def async_request_openai_completions(
283 st = time.perf_counter()
284 most_recent_timestamp = st
285 try:
287 - async with session.post(url=api_url, json=payload,
288 - headers=headers) as response:
286 + async with session.post(
287 + url=api_url, json=payload, headers=headers
288 + ) as response:
289 if response.status == 200:
290 first_chunk_received = False
291 async for chunk_bytes in response.content:
@@ -293,8 +293,7 @@ async def async_request_openai_completions(
293 if not chunk_bytes:
294 continue
295
296 - chunk = chunk_bytes.decode("utf-8").removeprefix(
297 - "data: ")
296 + chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
297 if chunk != "[DONE]":
298 data = json.loads(chunk)
299
@@ -314,21 +313,20 @@ async def async_request_openai_completions(
313
314 # Decoding phase
315 else:
317 - output.itl.append(timestamp -
318 - most_recent_timestamp)
316 + output.itl.append(timestamp - most_recent_timestamp)
317
318 most_recent_timestamp = timestamp
319 generated_text += text or ""
320 elif usage := data.get("usage"):
323 - output.output_tokens = usage.get(
324 - "completion_tokens")
321 + output.output_tokens = usage.get("completion_tokens")
322 if first_chunk_received:
323 output.success = True
324 else:
325 output.success = False
326 output.error = (
327 "Never received a valid chunk to calculate TTFT."
331 - "This response will be marked as failed!")
328 + "This response will be marked as failed!"
329 + )
330 output.generated_text = generated_text
331 output.latency = most_recent_timestamp - st
332 else:
@@ -349,23 +347,22 @@ async def async_request_openai_chat_completions(
347 pbar: Optional[tqdm] = None,
348 ) -> RequestFuncOutput:
349 api_url = request_func_input.api_url
352 - assert api_url.endswith(
353 - ("chat/completions", "profile")
354 - ), "OpenAI Chat Completions API URL must end with 'chat/completions'."
350 + assert api_url.endswith(("chat/completions", "profile")), (
351 + "OpenAI Chat Completions API URL must end with 'chat/completions'."
352 + )
353
356 - async with aiohttp.ClientSession(trust_env=True,
357 - timeout=AIOHTTP_TIMEOUT) as session:
354 + async with aiohttp.ClientSession(
355 + trust_env=True, timeout=AIOHTTP_TIMEOUT
356 + ) as session:
357 content = [{"type": "text", "text": request_func_input.prompt}]
358 if request_func_input.multi_modal_content:
359 content.append(request_func_input.multi_modal_content)
360 payload = {
362 - "model": request_func_input.model_name \
363 - if request_func_input.model_name else request_func_input.model,
361 + "model": request_func_input.model_name
362 + if request_func_input.model_name
363 + else request_func_input.model,
364 "messages": [
365 - {
366 - "role": "user",
367 - "content": content
368 - },
365 + {"role": "user", "content": content},
366 ],
367 "temperature": 0.0,
368 "max_completion_tokens": request_func_input.output_len,
@@ -391,16 +388,16 @@ async def async_request_openai_chat_completions(
388 st = time.perf_counter()
389 most_recent_timestamp = st
390 try:
394 - async with session.post(url=api_url, json=payload,
395 - headers=headers) as response:
391 + async with session.post(
392 + url=api_url, json=payload, headers=headers
393 + ) as response:
394 if response.status == 200:
395 async for chunk_bytes in response.content:
396 chunk_bytes = chunk_bytes.strip()
397 if not chunk_bytes:
398 continue
399
402 - chunk = chunk_bytes.decode("utf-8").removeprefix(
403 - "data: ")
400 + chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
401 if chunk != "[DONE]":
402 timestamp = time.perf_counter()
403 data = json.loads(chunk)
@@ -414,13 +411,11 @@ async def async_request_openai_chat_completions(
411
412 # Decoding phase
413 else:
417 - output.itl.append(timestamp -
418 - most_recent_timestamp)
414 + output.itl.append(timestamp - most_recent_timestamp)
415
416 generated_text += content or ""
417 elif usage := data.get("usage"):
422 - output.output_tokens = usage.get(
423 - "completion_tokens")
418 + output.output_tokens = usage.get("completion_tokens")
419
420 most_recent_timestamp = timestamp
421
@@ -446,25 +441,28 @@ async def async_request_openai_audio(
441 ) -> RequestFuncOutput:
442 # Lazy import without PlaceholderModule to avoid vllm dep.
443 import soundfile
444 +
445 api_url = request_func_input.api_url
450 - assert api_url.endswith(
451 - ("transcriptions", "translations"
452 - )), "OpenAI Chat Completions API URL must end with 'transcriptions' "
446 + assert api_url.endswith(("transcriptions", "translations")), (
447 + "OpenAI Chat Completions API URL must end with 'transcriptions' "
448 + )
449 "or `translations`."
450
455 - async with aiohttp.ClientSession(trust_env=True,
456 - timeout=AIOHTTP_TIMEOUT) as session:
451 + async with aiohttp.ClientSession(
452 + trust_env=True, timeout=AIOHTTP_TIMEOUT
453 + ) as session:
454 content = [{"type": "text", "text": request_func_input.prompt}]
455 payload = {
459 - "model": request_func_input.model_name \
460 - if request_func_input.model_name else request_func_input.model,
456 + "model": request_func_input.model_name
457 + if request_func_input.model_name
458 + else request_func_input.model,
459 "temperature": 0.0,
460 "max_completion_tokens": request_func_input.output_len,
461 "stream": True,
462 "language": "en",
463 # Flattened due to multipart/form-data
464 "stream_include_usage": True,
467 - "stream_continuous_usage_stats": True
465 + "stream_continuous_usage_stats": True,
466 }
467 if request_func_input.extra_body:
468 payload.update(request_func_input.extra_body)
@@ -479,9 +477,9 @@ async def async_request_openai_audio(
477 buffer.seek(0)
478 return buffer
479
482 - with to_bytes(*request_func_input.multi_modal_content['audio']) as f:
480 + with to_bytes(*request_func_input.multi_modal_content["audio"]) as f:
481 form = aiohttp.FormData()
484 - form.add_field('file', f, content_type='audio/wav')
482 + form.add_field("file", f, content_type="audio/wav")
483 for key, value in payload.items():
484 form.add_field(key, str(value))
485
@@ -493,24 +491,22 @@ async def async_request_openai_audio(
491 st = time.perf_counter()
492 most_recent_timestamp = st
493 try:
496 - async with session.post(url=api_url,
497 - data=form,
498 - headers=headers) as response:
494 + async with session.post(
495 + url=api_url, data=form, headers=headers
496 + ) as response:
497 if response.status == 200:
498 async for chunk_bytes in response.content:
499 chunk_bytes = chunk_bytes.strip()
500 if not chunk_bytes:
501 continue
502
505 - chunk = chunk_bytes.decode("utf-8").removeprefix(
506 - "data: ")
503 + chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
504 if chunk != "[DONE]":
505 timestamp = time.perf_counter()
506 data = json.loads(chunk)
507
508 if choices := data.get("choices"):
512 - content = choices[0]["delta"].get(
513 - "content")
509 + content = choices[0]["delta"].get("content")
510 # First token
511 if ttft == 0.0:
512 ttft = timestamp - st
@@ -519,12 +515,14 @@ async def async_request_openai_audio(
515 # Decoding phase
516 else:
517 output.itl.append(
522 - timestamp - most_recent_timestamp)
518 + timestamp - most_recent_timestamp
519 + )
520
521 generated_text += content or ""
522 elif usage := data.get("usage"):
523 output.output_tokens = usage.get(
527 - "completion_tokens")
524 + "completion_tokens"
525 + )
526
527 most_recent_timestamp = timestamp
528
@@ -545,7 +543,7 @@ async def async_request_openai_audio(
543
544
545 def get_model(pretrained_model_name_or_path: str) -> str:
548 - if os.getenv('VLLM_USE_MODELSCOPE', 'False').lower() == 'true':
546 + if os.getenv("VLLM_USE_MODELSCOPE", "False").lower() == "true":
547 from modelscope import snapshot_download
548
549 from vllm.model_executor.model_loader.weight_utils import get_lock
@@ -556,7 +554,8 @@ def get_model(pretrained_model_name_or_path: str) -> str:
554 model_path = snapshot_download(
555 model_id=pretrained_model_name_or_path,
556 local_files_only=huggingface_hub.constants.HF_HUB_OFFLINE,
559 - ignore_file_pattern=[".*.pt", ".*.safetensors", ".*.bin"])
557 + ignore_file_pattern=[".*.pt", ".*.safetensors", ".*.bin"],
558 + )
559
560 return model_path
561 return pretrained_model_name_or_path
@@ -569,23 +568,23 @@ def get_tokenizer(
568 **kwargs,
569 ) -> Union[PreTrainedTokenizer, PreTrainedTokenizerFast]:
570 if pretrained_model_name_or_path is not None and not os.path.exists(
572 - pretrained_model_name_or_path):
573 - pretrained_model_name_or_path = get_model(
574 - pretrained_model_name_or_path)
571 + pretrained_model_name_or_path
572 + ):
573 + pretrained_model_name_or_path = get_model(pretrained_model_name_or_path)
574 if tokenizer_mode == "slow":
575 if kwargs.get("use_fast", False):
577 - raise ValueError(
578 - "Cannot use the fast tokenizer in slow tokenizer mode.")
576 + raise ValueError("Cannot use the fast tokenizer in slow tokenizer mode.")
577 kwargs["use_fast"] = False
578 if tokenizer_mode == "mistral":
579 try:
580 from vllm.transformers_utils.tokenizer import MistralTokenizer
581 except ImportError as e:
584 - raise ImportError("MistralTokenizer requires vllm package.\n"
585 - "Please install it with `pip install vllm` "
586 - "to use mistral tokenizer mode.") from e
587 - return MistralTokenizer.from_pretrained(
588 - str(pretrained_model_name_or_path))
582 + raise ImportError(
583 + "MistralTokenizer requires vllm package.\n"
584 + "Please install it with `pip install vllm` "
585 + "to use mistral tokenizer mode."
586 + ) from e
587 + return MistralTokenizer.from_pretrained(str(pretrained_model_name_or_path))
588 else:
589 return AutoTokenizer.from_pretrained(
590 pretrained_model_name_or_path,
@@ -608,7 +607,7 @@ ASYNC_REQUEST_FUNCS = {
607 }
608
609 OPENAI_COMPATIBLE_BACKENDS = [
611 - k for k, v in ASYNC_REQUEST_FUNCS.items()
612 - if v in (async_request_openai_completions,
613 - async_request_openai_chat_completions)
610 + k
611 + for k, v in ASYNC_REQUEST_FUNCS.items()
612 + if v in (async_request_openai_completions, async_request_openai_chat_completions)
613 ]
benchmarks/benchmark_dataset.py
+186 -169
@@ -82,14 +82,12 @@ class BenchmarkDataset(ABC):
82 self.dataset_path = dataset_path
83 # Set the random seed, ensuring that a None value is replaced with the
84 # default seed.
85 - self.random_seed = (random_seed
86 - if random_seed is not None else self.DEFAULT_SEED)
85 + self.random_seed = random_seed if random_seed is not None else self.DEFAULT_SEED
86 self.data = None
87
88 def apply_multimodal_chat_transformation(
90 - self,
91 - prompt: str,
92 - mm_content: Optional[MultiModalDataDict] = None) -> list[dict]:
89 + self, prompt: str, mm_content: Optional[MultiModalDataDict] = None
90 + ) -> list[dict]:
91 """
92 Transform a prompt and optional multimodal content into a chat format.
93 This method is used for chat models that expect a specific conversation
@@ -111,8 +109,7 @@ class BenchmarkDataset(ABC):
109 NotImplementedError: If a subclass does not implement this method.
110 """
111 # TODO (jenniferzhao): add support for downloading data
114 - raise NotImplementedError(
115 - "load_data must be implemented in subclasses.")
112 + raise NotImplementedError("load_data must be implemented in subclasses.")
113
114 def get_random_lora_request(
115 self,
@@ -158,8 +155,9 @@ class BenchmarkDataset(ABC):
155 return lora_request, lora_tokenizer_cache[lora_id] or tokenizer
156
157 @abstractmethod
161 - def sample(self, tokenizer: PreTrainedTokenizerBase,
162 - num_requests: int) -> list[SampleRequest]:
158 + def sample(
159 + self, tokenizer: PreTrainedTokenizerBase, num_requests: int
160 + ) -> list[SampleRequest]:
161 """
162 Abstract method to generate sample requests from the dataset.
163
@@ -177,8 +175,9 @@ class BenchmarkDataset(ABC):
175 """
176 raise NotImplementedError("sample must be implemented in subclasses.")
177
180 - def maybe_oversample_requests(self, requests: list[SampleRequest],
181 - num_requests: int) -> None:
178 + def maybe_oversample_requests(
179 + self, requests: list[SampleRequest], num_requests: int
180 + ) -> None:
181 """
182 Oversamples the list of requests if its size is less than the desired
183 number.
@@ -189,11 +188,9 @@ class BenchmarkDataset(ABC):
188 """
189 if len(requests) < num_requests:
190 random.seed(self.random_seed)
192 - additional = random.choices(requests,
193 - k=num_requests - len(requests))
191 + additional = random.choices(requests, k=num_requests - len(requests))
192 requests.extend(additional)
195 - logger.info("Oversampled requests to reach %d total samples.",
196 - num_requests)
193 + logger.info("Oversampled requests to reach %d total samples.", num_requests)
194
195
196 # -----------------------------------------------------------------------------
@@ -218,14 +215,14 @@ def is_valid_sequence(
215 """
216 # Check for invalid conditions
217 prompt_too_short = prompt_len < min_len
221 - output_too_short = (not skip_min_output_len_check) and (output_len
222 - < min_len)
218 + output_too_short = (not skip_min_output_len_check) and (output_len < min_len)
219 prompt_too_long = prompt_len > max_prompt_len
220 combined_too_long = (prompt_len + output_len) > max_total_len
221
222 # Return True if none of the invalid conditions are met
227 - return not (prompt_too_short or output_too_short or prompt_too_long
228 - or combined_too_long)
223 + return not (
224 + prompt_too_short or output_too_short or prompt_too_long or combined_too_long
225 + )
226
227
228 @cache
@@ -257,28 +254,28 @@ def process_image(image: Any) -> Mapping[str, Any]:
254 Raises:
255 ValueError: If the input is not a supported type.
256 """
260 - if isinstance(image, dict) and 'bytes' in image:
261 - image = Image.open(BytesIO(image['bytes']))
257 + if isinstance(image, dict) and "bytes" in image:
258 + image = Image.open(BytesIO(image["bytes"]))
259 if isinstance(image, Image.Image):
260 image = image.convert("RGB")
261 with io.BytesIO() as image_data:
262 image.save(image_data, format="JPEG")
266 - image_base64 = base64.b64encode(
267 - image_data.getvalue()).decode("utf-8")
263 + image_base64 = base64.b64encode(image_data.getvalue()).decode("utf-8")
264 return {
265 "type": "image_url",
270 - "image_url": {
271 - "url": f"data:image/jpeg;base64,{image_base64}"
272 - },
266 + "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"},
267 }
268
269 if isinstance(image, str):
276 - image_url = (image if image.startswith(
277 - ("http://", "file://")) else f"file://{image}")
270 + image_url = (
271 + image if image.startswith(("http://", "file://")) else f"file://{image}"
272 + )
273 return {"type": "image_url", "image_url": {"url": image_url}}
274
280 - raise ValueError(f"Invalid image input {image}. Must be a PIL.Image.Image"
281 - " or str or dictionary with raw image bytes.")
275 + raise ValueError(
276 + f"Invalid image input {image}. Must be a PIL.Image.Image"
277 + " or str or dictionary with raw image bytes."
278 + )
279
280
281 # -----------------------------------------------------------------------------
@@ -318,8 +315,11 @@ class RandomDataset(BenchmarkDataset):
315 num_special_tokens = tokenizer.num_special_tokens_to_add()
316 real_input_len = input_len - num_special_tokens
317
321 - prefix_token_ids = (np.random.randint(
322 - 0, vocab_size, size=prefix_len).tolist() if prefix_len > 0 else [])
318 + prefix_token_ids = (
319 + np.random.randint(0, vocab_size, size=prefix_len).tolist()
320 + if prefix_len > 0
321 + else []
322 + )
323
324 # New sampling logic: [X * (1 - b), X * (1 + b)]
325 input_low = int(real_input_len * (1 - range_ratio))
@@ -329,21 +329,17 @@ class RandomDataset(BenchmarkDataset):
329
330 # Add logging for debugging
331 logger.info("Sampling input_len from [%s, %s]", input_low, input_high)
332 - logger.info("Sampling output_len from [%s, %s]", output_low,
333 - output_high)
334 -
335 - input_lens = np.random.randint(input_low,
336 - input_high + 1,
337 - size=num_requests)
338 - output_lens = np.random.randint(output_low,
339 - output_high + 1,
340 - size=num_requests)
332 + logger.info("Sampling output_len from [%s, %s]", output_low, output_high)
333 +
334 + input_lens = np.random.randint(input_low, input_high + 1, size=num_requests)
335 + output_lens = np.random.randint(output_low, output_high + 1, size=num_requests)
336 offsets = np.random.randint(0, vocab_size, size=num_requests)
337
338 requests = []
339 for i in range(num_requests):
345 - inner_seq = ((offsets[i] + i + np.arange(input_lens[i])) %
346 - vocab_size).tolist()
340 + inner_seq = (
341 + (offsets[i] + i + np.arange(input_lens[i])) % vocab_size
342 + ).tolist()
343 token_sequence = prefix_token_ids + inner_seq
344 prompt = tokenizer.decode(token_sequence)
345 # After decoding the prompt we have to encode and decode it again.
@@ -354,8 +350,9 @@ class RandomDataset(BenchmarkDataset):
350 # [1650, 939, 486] -> ['Ġcall', 'sh', 'ere']
351 # To avoid uncontrolled change of the prompt length,
352 # the encoded sequence is truncated before being decode again.
357 - re_encoded_sequence = tokenizer.encode(
358 - prompt, add_special_tokens=False)[:input_lens[i]]
353 + re_encoded_sequence = tokenizer.encode(prompt, add_special_tokens=False)[
354 + : input_lens[i]
355 + ]
356 prompt = tokenizer.decode(re_encoded_sequence)
357 total_input_len = prefix_len + int(input_lens[i])
358 requests.append(
@@ -363,7 +360,8 @@ class RandomDataset(BenchmarkDataset):
360 prompt=prompt,
361 prompt_len=total_input_len,
362 expected_output_len=int(output_lens[i]),
366 - ))
363 + )
364 + )
365 return requests
366
367
@@ -390,7 +388,8 @@ class ShareGPTDataset(BenchmarkDataset):
388 self.data = json.load(f)
389 # Filter entries with at least two conversation turns.
390 self.data = [
393 - entry for entry in self.data
391 + entry
392 + for entry in self.data
393 if "conversations" in entry and len(entry["conversations"]) >= 2
394 ]
395 random.seed(self.random_seed)
@@ -416,27 +415,28 @@ class ShareGPTDataset(BenchmarkDataset):
415 )
416
417 lora_request, tokenizer = self.get_random_lora_request(
419 - tokenizer=tokenizer, max_loras=max_loras, lora_path=lora_path)
418 + tokenizer=tokenizer, max_loras=max_loras, lora_path=lora_path
419 + )
420 prompt_ids = tokenizer(prompt).input_ids
421 completion_ids = tokenizer(completion).input_ids
422 prompt_len = len(prompt_ids)
423 - new_output_len = (len(completion_ids)
424 - if output_len is None else output_len)
425 - if not is_valid_sequence(prompt_len,
426 - new_output_len,
427 - skip_min_output_len_check=output_len
428 - is not None):
423 + new_output_len = len(completion_ids) if output_len is None else output_len
424 + if not is_valid_sequence(
425 + prompt_len,
426 + new_output_len,
427 + skip_min_output_len_check=output_len is not None,
428 + ):
429 continue
430 if enable_multimodal_chat:
431 - prompt = self.apply_multimodal_chat_transformation(
432 - prompt, None)
431 + prompt = self.apply_multimodal_chat_transformation(prompt, None)
432 samples.append(
433 SampleRequest(
434 prompt=prompt,
435 prompt_len=prompt_len,
436 expected_output_len=new_output_len,
437 lora_request=lora_request,
439 - ))
438 + )
439 + )
440 self.maybe_oversample_requests(samples, num_requests)
441 return samples
442
@@ -482,20 +482,20 @@ class SonnetDataset(BenchmarkDataset):
482 ) -> list:
483 # Calculate average token length for a poem line.
484 tokenized_lines = [tokenizer(line).input_ids for line in self.data]
485 - avg_len = sum(len(tokens)
486 - for tokens in tokenized_lines) / len(tokenized_lines)
485 + avg_len = sum(len(tokens) for tokens in tokenized_lines) / len(tokenized_lines)
486
487 # Build the base prompt.
488 base_prompt = "Pick as many lines as you can from these poem lines:\n"
489 base_msg = [{"role": "user", "content": base_prompt}]
491 - base_fmt = tokenizer.apply_chat_template(base_msg,
492 - add_generation_prompt=True,
493 - tokenize=False)
490 + base_fmt = tokenizer.apply_chat_template(
491 + base_msg, add_generation_prompt=True, tokenize=False
492 + )
493 base_offset = len(tokenizer(base_fmt).input_ids)
494 if input_len <= base_offset:
495 raise ValueError(
496 f"'input_len' must be higher than the base prompt length "
498 - f"({base_offset}).")
497 + f"({base_offset})."
498 + )
499
500 # Determine how many poem lines to use.
501 num_input_lines = round((input_len - base_offset) / avg_len)
@@ -504,21 +504,23 @@ class SonnetDataset(BenchmarkDataset):
504
505 samples = []
506 while len(samples) < num_requests:
507 - extra_lines = random.choices(self.data,
508 - k=num_input_lines - num_prefix_lines)
507 + extra_lines = random.choices(
508 + self.data, k=num_input_lines - num_prefix_lines
509 + )
510 prompt = f"{base_prompt}{''.join(prefix_lines + extra_lines)}"
511 msg = [{"role": "user", "content": prompt}]
512 prompt_formatted = tokenizer.apply_chat_template(
512 - msg, add_generation_prompt=True, tokenize=False)
513 + msg, add_generation_prompt=True, tokenize=False
514 + )
515 prompt_len = len(tokenizer(prompt_formatted).input_ids)
516 if prompt_len <= input_len:
517 samples.append(
518 SampleRequest(
517 - prompt=prompt_formatted
518 - if return_prompt_formatted else prompt,
519 + prompt=prompt_formatted if return_prompt_formatted else prompt,
520 prompt_len=prompt_len,
521 expected_output_len=output_len,
521 - ))
522 + )
523 + )
524 return samples
525
526
@@ -538,7 +540,9 @@ class BurstGPTDataset(BenchmarkDataset):
540 super().__init__(**kwargs)
541 self.load_data()
542
541 - def load_data(self, ):
543 + def load_data(
544 + self,
545 + ):
546 if self.dataset_path is None:
547 raise ValueError("dataset_path must be provided for loading data.")
548
@@ -552,8 +556,7 @@ class BurstGPTDataset(BenchmarkDataset):
556
557 def _sample_loaded_data(self, num_requests: int) -> list:
558 if num_requests <= len(self.data):
555 - data = self.data.sample(n=num_requests,
556 - random_state=self.random_seed)
559 + data = self.data.sample(n=num_requests, random_state=self.random_seed)
560 else:
561 data = self.data.sample(
562 n=num_requests,
@@ -577,7 +580,8 @@ class BurstGPTDataset(BenchmarkDataset):
580 input_len = int(data[i][2])
581 output_len = int(data[i][3])
582 lora_req, tokenizer = self.get_random_lora_request(
580 - tokenizer=tokenizer, max_loras=max_loras, lora_path=lora_path)
583 + tokenizer=tokenizer, max_loras=max_loras, lora_path=lora_path
584 + )
585 vocab_size = tokenizer.vocab_size
586 # Generate a synthetic prompt: a list of token IDs computed as (i +
587 # j) modulo vocab_size.
@@ -589,7 +593,8 @@ class BurstGPTDataset(BenchmarkDataset):
593 prompt_len=input_len,
594 expected_output_len=output_len,
595 lora_request=lora_req,
592 - ))
596 + )
597 + )
598 return samples
599
600
@@ -632,20 +637,23 @@ class HuggingFaceDataset(BenchmarkDataset):
637
638 class ConversationDataset(HuggingFaceDataset):
639 """Dataset for conversation data with multimodal support."""
640 +
641 SUPPORTED_DATASET_PATHS = {
636 - 'lmms-lab/LLaVA-OneVision-Data', 'Aeala/ShareGPT_Vicuna_unfiltered'
642 + "lmms-lab/LLaVA-OneVision-Data",
643 + "Aeala/ShareGPT_Vicuna_unfiltered",
644 }
645 IS_MULTIMODAL = True
646
640 - def sample(self,
641 - tokenizer: PreTrainedTokenizerBase,
642 - num_requests: int,
643 - output_len: Optional[int] = None,
644 - enable_multimodal_chat: bool = False,
645 - **kwargs) -> list:
647 + def sample(
648 + self,
649 + tokenizer: PreTrainedTokenizerBase,
650 + num_requests: int,
651 + output_len: Optional[int] = None,
652 + enable_multimodal_chat: bool = False,
653 + **kwargs,
654 + ) -> list:
655 # Filter examples with at least 2 conversations
647 - filtered_data = self.data.filter(
648 - lambda x: len(x["conversations"]) >= 2)
656 + filtered_data = self.data.filter(lambda x: len(x["conversations"]) >= 2)
657 sampled_requests = []
658 dynamic_output = output_len is None
659
@@ -661,24 +669,22 @@ class ConversationDataset(HuggingFaceDataset):
669 completion_len = len(completion_ids)
670 output_len = completion_len if dynamic_output else output_len
671 assert isinstance(output_len, int) and output_len > 0
664 - if dynamic_output and not is_valid_sequence(
665 - prompt_len, completion_len):
672 + if dynamic_output and not is_valid_sequence(prompt_len, completion_len):
673 continue
667 - mm_content = process_image(
668 - item["image"]) if "image" in item else None
674 + mm_content = process_image(item["image"]) if "image" in item else None
675 if enable_multimodal_chat:
676 # Note: when chat is enabled the request prompt_len is no longer
677 # accurate and we will be using request output to count the
678 # actual prompt len and output len
673 - prompt = self.apply_multimodal_chat_transformation(
674 - prompt, mm_content)
679 + prompt = self.apply_multimodal_chat_transformation(prompt, mm_content)
680 sampled_requests.append(
681 SampleRequest(
682 prompt=prompt,
683 prompt_len=prompt_len,
684 expected_output_len=output_len,
685 multi_modal_data=mm_content,
681 - ))
686 + )
687 + )
688 self.maybe_oversample_requests(sampled_requests, num_requests)
689 return sampled_requests
690
@@ -695,10 +701,8 @@ class VisionArenaDataset(HuggingFaceDataset):
701
702 DEFAULT_OUTPUT_LEN = 128
703 SUPPORTED_DATASET_PATHS = {
698 - "lmarena-ai/VisionArena-Chat":
699 - lambda x: x["conversation"][0][0]["content"],
700 - "lmarena-ai/vision-arena-bench-v0.1":
701 - lambda x: x["turns"][0][0]["content"]
704 + "lmarena-ai/VisionArena-Chat": lambda x: x["conversation"][0][0]["content"],
705 + "lmarena-ai/vision-arena-bench-v0.1": lambda x: x["turns"][0][0]["content"],
706 }
707 IS_MULTIMODAL = True
708
@@ -710,16 +714,14 @@ class VisionArenaDataset(HuggingFaceDataset):
714 enable_multimodal_chat: bool = False,
715 **kwargs,
716 ) -> list:
713 - output_len = (output_len
714 - if output_len is not None else self.DEFAULT_OUTPUT_LEN)
717 + output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
718 sampled_requests = []
719 for item in self.data:
720 if len(sampled_requests) >= num_requests:
721 break
722 parser_fn = self.SUPPORTED_DATASET_PATHS.get(self.dataset_path)
723 if parser_fn is None:
721 - raise ValueError(
722 - f"Unsupported dataset path: {self.dataset_path}")
724 + raise ValueError(f"Unsupported dataset path: {self.dataset_path}")
725 prompt = parser_fn(item)
726 mm_content = process_image(item["images"][0])
727 prompt_len = len(tokenizer(prompt).input_ids)
@@ -727,15 +729,15 @@ class VisionArenaDataset(HuggingFaceDataset):
729 # Note: when chat is enabled the request prompt_len is no longer
730 # accurate and we will be using request output to count the
731 # actual prompt len
730 - prompt = self.apply_multimodal_chat_transformation(
731 - prompt, mm_content)
732 + prompt = self.apply_multimodal_chat_transformation(prompt, mm_content)
733 sampled_requests.append(
734 SampleRequest(
735 prompt=prompt,
736 prompt_len=prompt_len,
737 expected_output_len=output_len,
738 multi_modal_data=mm_content,
738 - ))
739 + )
740 + )
741 self.maybe_oversample_requests(sampled_requests, num_requests)
742 return sampled_requests
743
@@ -760,14 +762,15 @@ class InstructCoderDataset(HuggingFaceDataset):
762 "likaixin/InstructCoder",
763 }
764
763 - def sample(self,
764 - tokenizer: PreTrainedTokenizerBase,
765 - num_requests: int,
766 - output_len: Optional[int] = None,
767 - enable_multimodal_chat: bool = False,
768 - **kwargs) -> list:
769 - output_len = (output_len
770 - if output_len is not None else self.DEFAULT_OUTPUT_LEN)
765 + def sample(
766 + self,
767 + tokenizer: PreTrainedTokenizerBase,
768 + num_requests: int,
769 + output_len: Optional[int] = None,
770 + enable_multimodal_chat: bool = False,
771 + **kwargs,
772 + ) -> list:
773 + output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
774 sampled_requests = []
775 for item in self.data:
776 if len(sampled_requests) >= num_requests:
@@ -779,7 +782,8 @@ class InstructCoderDataset(HuggingFaceDataset):
782 prompt=prompt,
783 prompt_len=prompt_len,
784 expected_output_len=output_len,
782 - ))
785 + )
786 + )
787 self.maybe_oversample_requests(sampled_requests, num_requests)
788 return sampled_requests
789
@@ -794,38 +798,38 @@ class MTBenchDataset(HuggingFaceDataset):
798 MT-Bench Dataset.
799 https://huggingface.co/datasets/philschmid/mt-bench
800
797 - We create a single turn dataset for MT-Bench.
801 + We create a single turn dataset for MT-Bench.
802 This is similar to Spec decoding benchmark setup in vLLM
803 https://github.com/vllm-project/vllm/blob/9d98ab5ec/examples/offline_inference/eagle.py#L14-L18
800 - """ # noqa: E501
804 + """ # noqa: E501
805
806 DEFAULT_OUTPUT_LEN = 256 # avg len used in SD bench in vLLM
807 SUPPORTED_DATASET_PATHS = {
808 "philschmid/mt-bench",
809 }
810
807 - def sample(self,
808 - tokenizer: PreTrainedTokenizerBase,
809 - num_requests: int,
810 - output_len: Optional[int] = None,
811 - enable_multimodal_chat: bool = False,
812 - **kwargs) -> list:
813 - output_len = (output_len
814 - if output_len is not None else self.DEFAULT_OUTPUT_LEN)
811 + def sample(
812 + self,
813 + tokenizer: PreTrainedTokenizerBase,
814 + num_requests: int,
815 + output_len: Optional[int] = None,
816 + enable_multimodal_chat: bool = False,
817 + **kwargs,
818 + ) -> list:
819 + output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
820 sampled_requests = []
821
822 for item in self.data:
823 if len(sampled_requests) >= num_requests:
824 break
820 - prompt = item['turns'][0]
825 + prompt = item["turns"][0]
826
827 # apply template
823 - prompt = tokenizer.apply_chat_template([{
824 - "role": "user",
825 - "content": prompt
826 - }],
827 - add_generation_prompt=True,
828 - tokenize=False)
828 + prompt = tokenizer.apply_chat_template(
829 + [{"role": "user", "content": prompt}],
830 + add_generation_prompt=True,
831 + tokenize=False,
832 + )
833
834 prompt_len = len(tokenizer(prompt).input_ids)
835 sampled_requests.append(
@@ -833,7 +837,8 @@ class MTBenchDataset(HuggingFaceDataset):
837 prompt=prompt,
838 prompt_len=prompt_len,
839 expected_output_len=output_len,
836 - ))
840 + )
841 + )
842 self.maybe_oversample_requests(sampled_requests, num_requests)
843 return sampled_requests
844
@@ -847,23 +852,27 @@ class AIMODataset(HuggingFaceDataset):
852 """
853 Dataset class for processing a AIMO dataset with reasoning questions.
854 """
855 +
856 SUPPORTED_DATASET_PATHS = {
851 - "AI-MO/aimo-validation-aime", "AI-MO/NuminaMath-1.5",
852 - "AI-MO/NuminaMath-CoT"
857 + "AI-MO/aimo-validation-aime",
858 + "AI-MO/NuminaMath-1.5",
859 + "AI-MO/NuminaMath-CoT",
860 }
861
855 - def sample(self,
856 - tokenizer: PreTrainedTokenizerBase,
857 - num_requests: int,
858 - output_len: Optional[int] = None,
859 - **kwargs) -> list:
862 + def sample(
863 + self,
864 + tokenizer: PreTrainedTokenizerBase,
865 + num_requests: int,
866 + output_len: Optional[int] = None,
867 + **kwargs,
868 + ) -> list:
869 sampled_requests = []
870 dynamic_output = output_len is None
871
872 for item in self.data:
873 if len(sampled_requests) >= num_requests:
874 break
866 - prompt, completion = item['problem'], item["solution"]
875 + prompt, completion = item["problem"], item["solution"]
876
877 prompt_ids = tokenizer(prompt).input_ids
878 completion_ids = tokenizer(completion).input_ids
@@ -871,10 +880,9 @@ class AIMODataset(HuggingFaceDataset):
880 completion_len = len(completion_ids)
881 output_len = completion_len if dynamic_output else output_len
882 assert isinstance(output_len, int) and output_len > 0
874 - if dynamic_output and not is_valid_sequence(prompt_len,
875 - completion_len,
876 - max_prompt_len=2048,
877 - max_total_len=32000):
883 + if dynamic_output and not is_valid_sequence(
884 + prompt_len, completion_len, max_prompt_len=2048, max_total_len=32000
885 + ):
886 continue
887 sampled_requests.append(
888 SampleRequest(
@@ -882,7 +890,8 @@ class AIMODataset(HuggingFaceDataset):
890 prompt_len=prompt_len,
891 expected_output_len=output_len,
892 multi_modal_data=None,
885 - ))
893 + )
894 + )
895 self.maybe_oversample_requests(sampled_requests, num_requests)
896 return sampled_requests
897
@@ -905,25 +914,25 @@ You are a code completion assistant and your task is to analyze user edits and t
914
915 ### Response:
916
908 -""" # noqa: E501
917 +""" # noqa: E501
918
919
920 def _format_zeta_prompt(
912 - sample: dict,
913 - original_start_marker: str = "<|editable_region_start|>") -> dict:
921 + sample: dict, original_start_marker: str = "<|editable_region_start|>"
922 +) -> dict:
923 """Format the zeta prompt for the Next Edit Prediction (NEP) dataset.
915 -
916 - This function formats examples from the NEP dataset
917 - into prompts and expected outputs. It could be
924 +
925 + This function formats examples from the NEP dataset
926 + into prompts and expected outputs. It could be
927 further extended to support more NEP datasets.
919 -
928 +
929 Args:
921 - sample: The dataset sample containing events,
930 + sample: The dataset sample containing events,
931 inputs, and outputs.
923 - original_start_marker: The marker indicating the
924 - start of the editable region. Defaults to
932 + original_start_marker: The marker indicating the
933 + start of the editable region. Defaults to
934 "<|editable_region_start|>".
926 -
935 +
936 Returns:
937 A dictionary with the formatted prompts and expected outputs.
938 """
@@ -953,10 +962,8 @@ class NextEditPredictionDataset(HuggingFaceDataset):
962 "zed-industries/zeta": _format_zeta_prompt,
963 }
964
956 - def sample(self, tokenizer: PreTrainedTokenizerBase, num_requests: int,
957 - **kwargs):
958 - formatting_prompt_func = self.MAPPING_PROMPT_FUNCS.get(
959 - self.dataset_path)
965 + def sample(self, tokenizer: PreTrainedTokenizerBase, num_requests: int, **kwargs):
966 + formatting_prompt_func = self.MAPPING_PROMPT_FUNCS.get(self.dataset_path)
967 if formatting_prompt_func is None:
968 raise ValueError(f"Unsupported dataset path: {self.dataset_path}")
969 samples = []
@@ -967,8 +974,10 @@ class NextEditPredictionDataset(HuggingFaceDataset):
974 prompt=sample["prompt"],
975 prompt_len=len(tokenizer(sample["prompt"]).input_ids),
976 expected_output_len=len(
970 - tokenizer(sample["expected_output"]).input_ids),
971 - ))
977 + tokenizer(sample["expected_output"]).input_ids
978 + ),
979 + )
980 + )
981 if len(samples) >= num_requests:
982 break
983 self.maybe_oversample_requests(samples, num_requests)
@@ -997,18 +1006,22 @@ class ASRDataset(HuggingFaceDataset):
1006 | AMI | Meetings | Spontaneous | ihm, sdm |
1007 +----------------+----------------------------------------+--------------------------+-----------------------------+
1008
1000 - """ # noqa: E501
1009 + """ # noqa: E501
1010 +
1011 SUPPORTED_DATASET_PATHS = {
1002 - "openslr/librispeech_asr", "facebook/voxpopuli", "LIUM/tedlium",
1003 - "edinburghcstr/ami", "speechcolab/gigaspeech", "kensho/spgispeech"
1012 + "openslr/librispeech_asr",
1013 + "facebook/voxpopuli",
1014 + "LIUM/tedlium",
1015 + "edinburghcstr/ami",
1016 + "speechcolab/gigaspeech",
1017 + "kensho/spgispeech",
1018 }
1019
1020 DEFAULT_OUTPUT_LEN = 128
1021 IS_MULTIMODAL = True
1022
1023 # TODO Whisper-specific. Abstract interface when more models are supported.
1010 - TRANSCRIPTION_PREAMBLE = "<|startoftranscript|><|en|><|transcribe|>"\
1011 - "<|notimestamps|>"
1024 + TRANSCRIPTION_PREAMBLE = "<|startoftranscript|><|en|><|transcribe|><|notimestamps|>"
1025 skip_long_audios: bool = True
1026
1027 def sample(
@@ -1019,8 +1032,8 @@ class ASRDataset(HuggingFaceDataset):
1032 **kwargs,
1033 ) -> list:
1034 import librosa
1022 - output_len = (output_len
1023 - if output_len is not None else self.DEFAULT_OUTPUT_LEN)
1035 +
1036 + output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
1037 prompt = ASRDataset.TRANSCRIPTION_PREAMBLE
1038 prompt_len = len(tokenizer(prompt).input_ids)
1039 sampled_requests = []
@@ -1043,10 +1056,14 @@ class ASRDataset(HuggingFaceDataset):
1056 prompt_len=prompt_len,
1057 expected_output_len=output_len,
1058 multi_modal_data=mm_content,
1046 - ))
1059 + )
1060 + )
1061 if skipped:
1048 - logger.warning("%d samples discarded from dataset due to" \
1049 - " their length being greater than" \
1050 - " what Whisper supports.", skipped)
1062 + logger.warning(
1063 + "%d samples discarded from dataset due to"
1064 + " their length being greater than"
1065 + " what Whisper supports.",
1066 + skipped,
1067 + )
1068 self.maybe_oversample_requests(sampled_requests, num_requests)
1069 return sampled_requests
benchmarks/benchmark_latency.py
+41 -34
@@ -11,9 +11,9 @@ from typing import Any, Optional
11
12 import numpy as np
13 import torch
14 -from benchmark_utils import convert_to_pytorch_benchmark_format, write_to_json
14 from tqdm import tqdm
15
16 +from benchmark_utils import convert_to_pytorch_benchmark_format, write_to_json
17 from vllm import LLM, SamplingParams
18 from vllm.engine.arg_utils import EngineArgs
19 from vllm.inputs import PromptType
@@ -21,13 +21,14 @@ from vllm.sampling_params import BeamSearchParams
21 from vllm.utils import FlexibleArgumentParser
22
23
24 -def save_to_pytorch_benchmark_format(args: argparse.Namespace,
25 - results: dict[str, Any]) -> None:
24 +def save_to_pytorch_benchmark_format(
25 + args: argparse.Namespace, results: dict[str, Any]
26 +) -> None:
27 pt_records = convert_to_pytorch_benchmark_format(
28 args=args,
29 metrics={"latency": results["latencies"]},
29 - extra_info={k: results[k]
30 - for k in ["avg_latency", "percentiles"]})
30 + extra_info={k: results[k] for k in ["avg_latency", "percentiles"]},
31 + )
32 if pt_records:
33 pt_file = f"{os.path.splitext(args.output_json)[0]}.pytorch.json"
34 write_to_json(pt_file, pt_records)
@@ -42,9 +43,11 @@ def main(args: argparse.Namespace):
43 # the engine will automatically process the request in multiple batches.
44 llm = LLM(**dataclasses.asdict(engine_args))
45 assert llm.llm_engine.model_config.max_model_len >= (
45 - args.input_len +
46 - args.output_len), ("Please ensure that max_model_len is greater than"
47 - " the sum of input_len and output_len.")
46 + args.input_len + args.output_len
47 + ), (
48 + "Please ensure that max_model_len is greater than"
49 + " the sum of input_len and output_len."
50 + )
51
52 sampling_params = SamplingParams(
53 n=args.n,
@@ -55,18 +58,16 @@ def main(args: argparse.Namespace):
58 detokenize=not args.disable_detokenize,
59 )
60 print(sampling_params)
58 - dummy_prompt_token_ids = np.random.randint(10000,
59 - size=(args.batch_size,
60 - args.input_len))
61 - dummy_prompts: list[PromptType] = [{
62 - "prompt_token_ids": batch
63 - } for batch in dummy_prompt_token_ids.tolist()]
61 + dummy_prompt_token_ids = np.random.randint(
62 + 10000, size=(args.batch_size, args.input_len)
63 + )
64 + dummy_prompts: list[PromptType] = [
65 + {"prompt_token_ids": batch} for batch in dummy_prompt_token_ids.tolist()
66 + ]
67
68 def llm_generate():
69 if not args.use_beam_search:
67 - llm.generate(dummy_prompts,
68 - sampling_params=sampling_params,
69 - use_tqdm=False)
70 + llm.generate(dummy_prompts, sampling_params=sampling_params, use_tqdm=False)
71 else:
72 llm.beam_search(
73 dummy_prompts,
@@ -80,12 +81,13 @@ def main(args: argparse.Namespace):
81 def run_to_completion(profile_dir: Optional[str] = None):
82 if profile_dir:
83 with torch.profiler.profile(
83 - activities=[
84 - torch.profiler.ProfilerActivity.CPU,
85 - torch.profiler.ProfilerActivity.CUDA,
86 - ],
87 - on_trace_ready=torch.profiler.tensorboard_trace_handler(
88 - str(profile_dir)),
84 + activities=[
85 + torch.profiler.ProfilerActivity.CPU,
86 + torch.profiler.ProfilerActivity.CUDA,
87 + ],
88 + on_trace_ready=torch.profiler.tensorboard_trace_handler(
89 + str(profile_dir)
90 + ),
91 ) as p:
92 llm_generate()
93 print(p.key_averages().table(sort_by="self_cuda_time_total"))
@@ -103,8 +105,9 @@ def main(args: argparse.Namespace):
105 if args.profile:
106 profile_dir = args.profile_result_dir
107 if not profile_dir:
106 - profile_dir = (Path(".") / "vllm_benchmark_result" /
107 - f"latency_result_{time.time()}")
108 + profile_dir = (
109 + Path(".") / "vllm_benchmark_result" / f"latency_result_{time.time()}"
110 + )
111 print(f"Profiling (results will be saved to '{profile_dir}')...")
112 run_to_completion(profile_dir=profile_dir)
113 return
@@ -135,7 +138,8 @@ def main(args: argparse.Namespace):
138 if __name__ == "__main__":
139 parser = FlexibleArgumentParser(
140 description="Benchmark the latency of processing a single batch of "
138 - "requests till completion.")
141 + "requests till completion."
142 + )
143 parser.add_argument("--input-len", type=int, default=32)
144 parser.add_argument("--output-len", type=int, default=128)
145 parser.add_argument("--batch-size", type=int, default=8)
@@ -152,10 +156,9 @@ if __name__ == "__main__":
156 default=10,
157 help="Number of iterations to run for warmup.",
158 )
155 - parser.add_argument("--num-iters",
156 - type=int,
157 - default=30,
158 - help="Number of iterations to run.")
159 + parser.add_argument(
160 + "--num-iters", type=int, default=30, help="Number of iterations to run."
161 + )
162 parser.add_argument(
163 "--profile",
164 action="store_true",
@@ -165,8 +168,10 @@ if __name__ == "__main__":
168 "--profile-result-dir",
169 type=str,
170 default=None,
168 - help=("path to save the pytorch profiler output. Can be visualized "
169 - "with ui.perfetto.dev or Tensorboard."),
171 + help=(
172 + "path to save the pytorch profiler output. Can be visualized "
173 + "with ui.perfetto.dev or Tensorboard."
174 + ),
175 )
176 parser.add_argument(
177 "--output-json",
@@ -177,8 +182,10 @@ if __name__ == "__main__":
182 parser.add_argument(
183 "--disable-detokenize",
184 action="store_true",
180 - help=("Do not detokenize responses (i.e. do not include "
181 - "detokenization time in the latency measurement)"),
185 + help=(
186 + "Do not detokenize responses (i.e. do not include "
187 + "detokenization time in the latency measurement)"
188 + ),
189 )
190
191 parser = EngineArgs.add_cli_args(parser)
benchmarks/benchmark_long_document_qa_throughput.py
+51 -40
@@ -76,7 +76,7 @@ def repeat_prompts(prompts, repeat_count, mode: str):
76 - 'random': Shuffle the prompts randomly after repetition.
77 - 'tile': Repeat the entire prompt list in sequence.
78 Example: [1, 2, 3] -> [1, 2, 3, 1, 2, 3].
79 - - 'interleave': Repeat each prompt consecutively before moving to
79 + - 'interleave': Repeat each prompt consecutively before moving to
80 the next. Example: [1, 2, 3] -> [1, 1, 2, 2, 3, 3].
81
82 Returns:
@@ -86,20 +86,21 @@ def repeat_prompts(prompts, repeat_count, mode: str):
86 ValueError: If an invalid mode is provided.
87 """
88 print("Repeat mode: ", mode)
89 - if mode == 'random':
89 + if mode == "random":
90 repeated_prompts = prompts * repeat_count
91 random.shuffle(repeated_prompts)
92 return repeated_prompts
93 - elif mode == 'tile':
93 + elif mode == "tile":
94 return prompts * repeat_count
95 - elif mode == 'interleave':
95 + elif mode == "interleave":
96 repeated_prompts = []
97 for prompt in prompts:
98 repeated_prompts.extend([prompt] * repeat_count)
99 return repeated_prompts
100 else:
101 - raise ValueError(f"Invalid mode: {mode}, only support "
102 - "'random', 'tile', 'interleave'")
101 + raise ValueError(
102 + f"Invalid mode: {mode}, only support 'random', 'tile', 'interleave'"
103 + )
104
105
106 def main(args):
@@ -109,16 +110,16 @@ def main(args):
110 # we append the document id at the beginning to avoid any of the document
111 # being the prefix of other documents
112 prompts = [
112 - str(i) + ' '.join(['hi'] * args.document_length)
113 + str(i) + " ".join(["hi"] * args.document_length)
114 for i in range(args.num_documents)
115 ]
116
117 prompts = repeat_prompts(prompts, args.repeat_count, mode=args.repeat_mode)
118
119 warmup_prompts = [
119 - "This is warm up request " + str(i) + \
120 - ' '.join(['hi'] * args.document_length)
121 - for i in range(args.num_documents)]
120 + "This is warm up request " + str(i) + " ".join(["hi"] * args.document_length)
121 + for i in range(args.num_documents)
122 + ]
123
124 # Create the LLM engine
125 engine_args = EngineArgs.from_cli_args(args)
@@ -142,42 +143,52 @@ def main(args):
143
144 if __name__ == "__main__":
145 parser = FlexibleArgumentParser(
145 - description=
146 - 'Benchmark the performance with or without automatic prefix caching.')
146 + description="Benchmark the performance with or "
147 + "without automatic prefix caching."
148 + )
149
150 parser.add_argument(
149 - '--document-length',
151 + "--document-length",
152 type=int,
153 # Roughly the number of tokens for a system paper,
154 # excluding images
155 default=20000,
154 - help='Range of input lengths for sampling prompts,'
155 - 'specified as "min:max" (e.g., "128:256").')
156 -
157 - parser.add_argument('--num-documents',
158 - type=int,
159 - default=8,
160 - help='Range of input lengths for sampling prompts,'
161 - 'specified as "min:max" (e.g., "128:256").')
162 -
163 - parser.add_argument('--output-len', type=int, default=10)
164 -
165 - parser.add_argument('--repeat-count',
166 - type=int,
167 - default=2,
168 - help='Number of times to repeat each prompt')
169 -
170 - parser.add_argument("--repeat-mode",
171 - type=str,
172 - default='random',
173 - help='The mode to repeat prompts. The supported '
174 - 'modes are "random", "tile", and "interleave". '
175 - 'See repeat_prompts() in the source code for details.')
176 -
177 - parser.add_argument("--shuffle-seed",
178 - type=int,
179 - default=0,
180 - help='Random seed when the repeat mode is "random"')
156 + help="Range of input lengths for sampling prompts, "
157 + 'specified as "min:max" (e.g., "128:256").',
158 + )
159 +
160 + parser.add_argument(
161 + "--num-documents",
162 + type=int,
163 + default=8,
164 + help="Range of input lengths for sampling prompts, "
165 + 'specified as "min:max" (e.g., "128:256").',
166 + )
167 +
168 + parser.add_argument("--output-len", type=int, default=10)
169 +
170 + parser.add_argument(
171 + "--repeat-count",
172 + type=int,
173 + default=2,
174 + help="Number of times to repeat each prompt",
175 + )
176 +
177 + parser.add_argument(
178 + "--repeat-mode",
179 + type=str,
180 + default="random",
181 + help="The mode to repeat prompts. The supported "
182 + 'modes are "random", "tile", and "interleave". '
183 + "See repeat_prompts() in the source code for details.",
184 + )
185 +
186 + parser.add_argument(
187 + "--shuffle-seed",
188 + type=int,
189 + default=0,
190 + help='Random seed when the repeat mode is "random"',
191 + )
192
193 parser = EngineArgs.add_cli_args(parser)
194 args = parser.parse_args()
benchmarks/benchmark_prefix_caching.py
+64 -52
@@ -63,8 +63,7 @@ class Request:
63 output_len: int
64
65
66 -def sample_tokens(tokenizer: PreTrainedTokenizerBase,
67 - length: int) -> list[int]:
66 +def sample_tokens(tokenizer: PreTrainedTokenizerBase, length: int) -> list[int]:
67 vocab = tokenizer.get_vocab()
68 all_special_ids = set(tokenizer.all_special_ids)
69
@@ -91,8 +90,10 @@ def sample_requests_from_dataset(
90 # Filter out the conversations with less than 2 turns.
91 dataset = [data for data in dataset if len(data["conversations"]) >= 2]
92 # Only keep the first two turns of each conversation.
94 - dataset = [(data["conversations"][0]["value"],
95 - data["conversations"][1]["value"]) for data in dataset]
93 + dataset = [
94 + (data["conversations"][0]["value"], data["conversations"][1]["value"])
95 + for data in dataset
96 + ]
97
98 # Shuffle the dataset.
99 random.shuffle(dataset)
@@ -113,8 +114,9 @@ def sample_requests_from_dataset(
114 completion = dataset[i][1]
115 completion_token_ids = tokenizer(completion).input_ids
116 prompt_len = len(prompt_token_ids)
116 - output_len = (len(completion_token_ids)
117 - if fixed_output_len is None else fixed_output_len)
117 + output_len = (
118 + len(completion_token_ids) if fixed_output_len is None else fixed_output_len
119 + )
120 if min_len <= prompt_len <= max_len:
121 filtered_requests.append(Request(prompt, prompt_len, output_len))
122
@@ -128,27 +130,27 @@ def sample_requests_from_random(
130 fixed_output_len: Optional[int],
131 prefix_len: int,
132 ) -> list[Request]:
131 -
133 requests = []
134 prefix_token_ids = sample_tokens(tokenizer, prefix_len)
135 min_len, max_len = input_length_range
136
137 for i in range(num_requests):
138 unique_part_token_ids = sample_tokens(
138 - tokenizer,
139 - random.randint(min_len - prefix_len, max_len - prefix_len))
139 + tokenizer, random.randint(min_len - prefix_len, max_len - prefix_len)
140 + )
141 prompt_token_ids = prefix_token_ids + unique_part_token_ids
142 prompt = tokenizer.decode(prompt_token_ids)
143 prompt_len = len(prompt_token_ids)
143 - assert (min_len <= prompt_len <= max_len
144 - ), f"prompt_len {prompt_len} out of range {min_len}:{max_len}"
144 + assert min_len <= prompt_len <= max_len, (
145 + f"prompt_len {prompt_len} out of range {min_len}:{max_len}"
146 + )
147 requests.append(Request(prompt, prompt_len, fixed_output_len))
148 return requests
149
150
149 -def repeat_and_sort_requests(requests: list[Request],
150 - repeat_count: int,
151 - sort: bool = False) -> list[str]:
151 +def repeat_and_sort_requests(
152 + requests: list[Request], repeat_count: int, sort: bool = False
153 +) -> list[str]:
154 repeated_requests = requests * repeat_count
155 if sort:
156 repeated_requests.sort(key=lambda x: x[1])
@@ -159,14 +161,14 @@ def repeat_and_sort_requests(requests: list[Request],
161
162 def main(args):
163 tokenizer = get_tokenizer(args.model, trust_remote_code=True)
162 - input_length_range = tuple(map(int, args.input_length_range.split(':')))
164 + input_length_range = tuple(map(int, args.input_length_range.split(":")))
165 random.seed(args.seed)
166 if args.dataset_path is not None:
167 if args.prefix_len > 0:
166 - raise ValueError("prefix-len is not supported when "
167 - "dataset-path is provided.")
168 - print(f"Start to sample {args.num_prompts} prompts "
169 - f"from {args.dataset_path}")
168 + raise ValueError(
169 + "prefix-len is not supported when dataset-path is provided."
170 + )
171 + print(f"Start to sample {args.num_prompts} prompts from {args.dataset_path}")
172 filtered_requests = sample_requests_from_dataset(
173 dataset_path=args.dataset_path,
174 num_requests=args.num_prompts,
@@ -196,14 +198,16 @@ def main(args):
198
199 llm = LLM(**dataclasses.asdict(engine_args))
200
199 - sampling_params = SamplingParams(temperature=0,
200 - max_tokens=args.output_len,
201 - detokenize=not args.disable_detokenize)
201 + sampling_params = SamplingParams(
202 + temperature=0,
203 + max_tokens=args.output_len,
204 + detokenize=not args.disable_detokenize,
205 + )
206
207 print("Testing filtered requests")
204 - prompts = repeat_and_sort_requests(filtered_requests,
205 - repeat_count=args.repeat_count,
206 - sort=args.sort)
208 + prompts = repeat_and_sort_requests(
209 + filtered_requests, repeat_count=args.repeat_count, sort=args.sort
210 + )
211
212 print("------start generating------")
213 test_prefix(
@@ -215,29 +219,35 @@ def main(args):
219
220 if __name__ == "__main__":
221 parser = FlexibleArgumentParser(
218 - description=
219 - 'Benchmark the performance with or without automatic prefix caching.')
220 - parser.add_argument("--dataset-path",
221 - type=str,
222 - default=None,
223 - help="Path to the dataset.")
224 - parser.add_argument('--output-len', type=int, default=10)
225 - parser.add_argument('--num-prompts',
226 - type=int,
227 - required=True,
228 - help="Number of the prompts sampled from dataset")
229 - parser.add_argument('--repeat-count',
230 - type=int,
231 - default=1,
232 - help='Number of times to repeat each prompt')
233 - parser.add_argument('--sort',
234 - action='store_true',
235 - help='Sort prompts by input length')
236 - parser.add_argument('--input-length-range',
237 - type=str,
238 - required=True,
239 - help='Range of input lengths for sampling prompts,'
240 - 'specified as "min:max" (e.g., "128:256").')
222 + description="Benchmark the performance with or without "
223 + "automatic prefix caching."
224 + )
225 + parser.add_argument(
226 + "--dataset-path", type=str, default=None, help="Path to the dataset."
227 + )
228 + parser.add_argument("--output-len", type=int, default=10)
229 + parser.add_argument(
230 + "--num-prompts",
231 + type=int,
232 + required=True,
233 + help="Number of the prompts sampled from dataset",
234 + )
235 + parser.add_argument(
236 + "--repeat-count",
237 + type=int,
238 + default=1,
239 + help="Number of times to repeat each prompt",
240 + )
241 + parser.add_argument(
242 + "--sort", action="store_true", help="Sort prompts by input length"
243 + )
244 + parser.add_argument(
245 + "--input-length-range",
246 + type=str,
247 + required=True,
248 + help="Range of input lengths for sampling prompts,"
249 + 'specified as "min:max" (e.g., "128:256").',
250 + )
251 parser.add_argument(
252 "--prefix-len",
253 type=int,
@@ -248,10 +258,12 @@ if __name__ == "__main__":
258 "when dataset-path is not provided.",
259 )
260 parser.add_argument(
251 - '--disable-detokenize',
252 - action='store_true',
253 - help=("Do not detokenize responses (i.e. do not include "
254 - "detokenization time in the latency measurement)"),
261 + "--disable-detokenize",
262 + action="store_true",
263 + help=(
264 + "Do not detokenize responses (i.e. do not include "
265 + "detokenization time in the latency measurement)"
266 + ),
267 )
268
269 parser = EngineArgs.add_cli_args(parser)
benchmarks/benchmark_prioritization.py
+70 -52
@@ -1,5 +1,6 @@
1 # SPDX-License-Identifier: Apache-2.0
2 """Benchmark offline prioritization."""
3 +
4 import argparse
5 import dataclasses
6 import json
@@ -13,7 +14,7 @@ from vllm.engine.arg_utils import EngineArgs
14 from vllm.utils import FlexibleArgumentParser
15
16
16 -#Select a equi-probable random priority
17 +# Select a equi-probable random priority
18 def get_random_flag():
19 return 0 if random.random() < 0.5 else 1
20
@@ -33,8 +34,10 @@ def sample_requests(
34 # Filter out the conversations with less than 2 turns.
35 dataset = [data for data in dataset if len(data["conversations"]) >= 2]
36 # Only keep the first two turns of each conversation.
36 - dataset = [(data["conversations"][0]["value"],
37 - data["conversations"][1]["value"]) for data in dataset]
37 + dataset = [
38 + (data["conversations"][0]["value"], data["conversations"][1]["value"])
39 + for data in dataset
40 + ]
41
42 # Shuffle the dataset.
43 random.shuffle(dataset)
@@ -51,8 +54,9 @@ def sample_requests(
54 completion = dataset[i][1]
55 completion_token_ids = tokenizer(completion).input_ids
56 prompt_len = len(prompt_token_ids)
54 - output_len = len(completion_token_ids
55 - ) if fixed_output_len is None else fixed_output_len
57 + output_len = (
58 + len(completion_token_ids) if fixed_output_len is None else fixed_output_len
59 + )
60 if prompt_len < 4 or output_len < 4:
61 # Prune too short sequences.
62 continue
@@ -74,13 +78,16 @@ def run_vllm(
78 disable_detokenize: bool = False,
79 ) -> float:
80 from vllm import LLM, SamplingParams
81 +
82 llm = LLM(**dataclasses.asdict(engine_args))
83
84 assert all(
85 llm.llm_engine.model_config.max_model_len >= (request[1] + request[2])
81 - for request in requests), (
82 - "Please ensure that max_model_len is greater than the sum of"
83 - " input_len and output_len for all requests.")
86 + for request in requests
87 + ), (
88 + "Please ensure that max_model_len is greater than the sum of"
89 + " input_len and output_len for all requests."
90 + )
91
92 # Add the requests to the engine.
93 prompts = []
@@ -97,7 +104,8 @@ def run_vllm(
104 ignore_eos=True,
105 max_tokens=output_len,
106 detokenize=not disable_detokenize,
100 - ))
107 + )
108 + )
109
110 start = time.perf_counter()
111 llm.generate(prompts, sampling_params, priority=priority, use_tqdm=True)
@@ -111,26 +119,33 @@ def main(args: argparse.Namespace):
119
120 # Sample the requests.
121 tokenizer = AutoTokenizer.from_pretrained(
114 - args.tokenizer, trust_remote_code=args.trust_remote_code)
122 + args.tokenizer, trust_remote_code=args.trust_remote_code
123 + )
124 if args.dataset is None:
125 # Synthesize a prompt with the given input length.
126 prompt = "hi" * (args.input_len - 1)
118 - requests = [(prompt, args.input_len, args.output_len,
119 - get_random_flag()) for _ in range(args.num_prompts)]
127 + requests = [
128 + (prompt, args.input_len, args.output_len, get_random_flag())
129 + for _ in range(args.num_prompts)
130 + ]
131 else:
121 - requests = sample_requests(args.dataset, args.num_prompts, tokenizer,
122 - args.output_len)
132 + requests = sample_requests(
133 + args.dataset, args.num_prompts, tokenizer, args.output_len
134 + )
135
136 if args.backend == "vllm":
125 - elapsed_time = run_vllm(requests, args.n,
126 - EngineArgs.from_cli_args(args),
127 - args.disable_detokenize)
137 + elapsed_time = run_vllm(
138 + requests, args.n, EngineArgs.from_cli_args(args), args.disable_detokenize
139 + )
140 else:
141 raise ValueError(f"Unknown backend: {args.backend}")
130 - total_num_tokens = sum(prompt_len + output_len
131 - for _, prompt_len, output_len, priority in requests)
132 - print(f"Throughput: {len(requests) / elapsed_time:.2f} requests/s, "
133 - f"{total_num_tokens / elapsed_time:.2f} tokens/s")
142 + total_num_tokens = sum(
143 + prompt_len + output_len for _, prompt_len, output_len, priority in requests
144 + )
145 + print(
146 + f"Throughput: {len(requests) / elapsed_time:.2f} requests/s, "
147 + f"{total_num_tokens / elapsed_time:.2f} tokens/s"
148 + )
149
150 # Output JSON results if specified
151 if args.output_json:
@@ -147,41 +162,44 @@ def main(args: argparse.Namespace):
162
163 if __name__ == "__main__":
164 parser = FlexibleArgumentParser(description="Benchmark the throughput.")
150 - parser.add_argument("--backend",
151 - type=str,
152 - choices=["vllm", "hf", "mii"],
153 - default="vllm")
154 - parser.add_argument("--dataset",
155 - type=str,
156 - default=None,
157 - help="Path to the dataset.")
158 - parser.add_argument("--input-len",
159 - type=int,
160 - default=None,
161 - help="Input prompt length for each request")
162 - parser.add_argument("--output-len",
163 - type=int,
164 - default=None,
165 - help="Output length for each request. Overrides the "
166 - "output length from the dataset.")
167 - parser.add_argument("--n",
168 - type=int,
169 - default=1,
170 - help="Number of generated sequences per prompt.")
171 - parser.add_argument("--num-prompts",
172 - type=int,
173 - default=200,
174 - help="Number of prompts to process.")
165 parser.add_argument(
176 - '--output-json',
166 + "--backend", type=str, choices=["vllm", "hf", "mii"], default="vllm"
167 + )
168 + parser.add_argument(
169 + "--dataset", type=str, default=None, help="Path to the dataset."
170 + )
171 + parser.add_argument(
172 + "--input-len",
173 + type=int,
174 + default=None,
175 + help="Input prompt length for each request",
176 + )
177 + parser.add_argument(
178 + "--output-len",
179 + type=int,
180 + default=None,
181 + help="Output length for each request. Overrides the "
182 + "output length from the dataset.",
183 + )
184 + parser.add_argument(
185 + "--n", type=int, default=1, help="Number of generated sequences per prompt."
186 + )
187 + parser.add_argument(
188 + "--num-prompts", type=int, default=200, help="Number of prompts to process."
189 + )
190 + parser.add_argument(
191 + "--output-json",
192 type=str,
193 default=None,
179 - help='Path to save the throughput results in JSON format.')
194 + help="Path to save the throughput results in JSON format.",
195 + )
196 parser.add_argument(
181 - '--disable-detokenize',
182 - action='store_true',
183 - help=("Do not detokenize responses (i.e. do not include "
184 - "detokenization time in the latency measurement)"),
197 + "--disable-detokenize",
198 + action="store_true",
199 + help=(
200 + "Do not detokenize responses (i.e. do not include "
201 + "detokenization time in the latency measurement)"
202 + ),
203 )
204
205 parser = EngineArgs.add_cli_args(parser)
benchmarks/benchmark_serving.py
+337 -248
@@ -20,6 +20,7 @@ On the client side, run:
20 --endpoint /generate_stream
21 to the end of the command above.
22 """
23 +
24 import argparse
25 import asyncio
26 import gc
@@ -34,12 +35,16 @@ from datetime import datetime
35 from typing import Any, Optional
36
37 import numpy as np
37 -from backend_request_func import (ASYNC_REQUEST_FUNCS,
38 - OPENAI_COMPATIBLE_BACKENDS, RequestFuncInput,
39 - RequestFuncOutput)
38 from tqdm.asyncio import tqdm
39 from transformers import PreTrainedTokenizerBase
40
41 +from backend_request_func import (
42 + ASYNC_REQUEST_FUNCS,
43 + OPENAI_COMPATIBLE_BACKENDS,
44 + RequestFuncInput,
45 + RequestFuncOutput,
46 +)
47 +
48 try:
49 from vllm.transformers_utils.tokenizer import get_tokenizer
50 except ImportError:
@@ -50,12 +55,21 @@ try:
55 except ImportError:
56 from argparse import ArgumentParser as FlexibleArgumentParser
57
53 -from benchmark_dataset import (AIMODataset, ASRDataset, BurstGPTDataset,
54 - ConversationDataset, HuggingFaceDataset,
55 - InstructCoderDataset, MTBenchDataset,
56 - NextEditPredictionDataset, RandomDataset,
57 - SampleRequest, ShareGPTDataset, SonnetDataset,
58 - VisionArenaDataset)
58 +from benchmark_dataset import (
59 + AIMODataset,
60 + ASRDataset,
61 + BurstGPTDataset,
62 + ConversationDataset,
63 + HuggingFaceDataset,
64 + InstructCoderDataset,
65 + MTBenchDataset,
66 + NextEditPredictionDataset,
67 + RandomDataset,
68 + SampleRequest,
69 + ShareGPTDataset,
70 + SonnetDataset,
71 + VisionArenaDataset,
72 +)
73 from benchmark_utils import convert_to_pytorch_benchmark_format, write_to_json
74
75 MILLISECONDS_TO_SECONDS_CONVERSION = 1000
@@ -118,7 +132,8 @@ async def get_request(
132
133 # Calculate scale parameter theta to maintain the desired request_rate.
134 assert burstiness > 0, (
121 - f"A positive burstiness factor is expected, but given {burstiness}.")
135 + f"A positive burstiness factor is expected, but given {burstiness}."
136 + )
137 theta = 1.0 / (request_rate * burstiness)
138
139 for request in input_requests:
@@ -164,8 +179,10 @@ def calculate_metrics(
179 # bundled together
180 # Note : this may inflate the output token count slightly
181 output_len = len(
167 - tokenizer(outputs[i].generated_text,
168 - add_special_tokens=False).input_ids)
182 + tokenizer(
183 + outputs[i].generated_text, add_special_tokens=False
184 + ).input_ids
185 + )
186 actual_output_lens.append(output_len)
187 total_input += input_requests[i].prompt_len
188 tpot = 0
@@ -188,16 +205,19 @@ def calculate_metrics(
205
206 if "ttft" in goodput_config_dict:
207 valid_metrics.append(ttfts)
191 - slo_values.append(goodput_config_dict["ttft"] /
192 - MILLISECONDS_TO_SECONDS_CONVERSION)
208 + slo_values.append(
209 + goodput_config_dict["ttft"] / MILLISECONDS_TO_SECONDS_CONVERSION
210 + )
211 if "tpot" in goodput_config_dict:
212 valid_metrics.append(all_tpots)
195 - slo_values.append(goodput_config_dict["tpot"] /
196 - MILLISECONDS_TO_SECONDS_CONVERSION)
213 + slo_values.append(
214 + goodput_config_dict["tpot"] / MILLISECONDS_TO_SECONDS_CONVERSION
215 + )
216 if "e2el" in goodput_config_dict:
217 valid_metrics.append(e2els)
199 - slo_values.append(goodput_config_dict["e2el"] /
200 - MILLISECONDS_TO_SECONDS_CONVERSION)
218 + slo_values.append(
219 + goodput_config_dict["e2el"] / MILLISECONDS_TO_SECONDS_CONVERSION
220 + )
221
222 for req_metric in zip(*valid_metrics):
223 is_good_req = all([s >= r for s, r in zip(slo_values, req_metric)])
@@ -208,7 +228,8 @@ def calculate_metrics(
228 warnings.warn(
229 "All requests failed. This is likely due to a misconfiguration "
230 "on the benchmark arguments.",
211 - stacklevel=2)
231 + stacklevel=2,
232 + )
233 metrics = BenchmarkMetrics(
234 completed=completed,
235 total_input=total_input,
@@ -217,27 +238,31 @@ def calculate_metrics(
238 request_goodput=good_completed / dur_s,
239 output_throughput=sum(actual_output_lens) / dur_s,
240 total_token_throughput=(total_input + sum(actual_output_lens)) / dur_s,
220 - mean_ttft_ms=np.mean(ttfts or 0) *
221 - 1000, # ttfts is empty if streaming is not supported by backend
241 + mean_ttft_ms=np.mean(ttfts or 0)
242 + * 1000, # ttfts is empty if streaming is not supported by backend
243 std_ttft_ms=np.std(ttfts or 0) * 1000,
244 median_ttft_ms=np.median(ttfts or 0) * 1000,
224 - percentiles_ttft_ms=[(p, np.percentile(ttfts or 0, p) * 1000)
225 - for p in selected_percentiles],
245 + percentiles_ttft_ms=[
246 + (p, np.percentile(ttfts or 0, p) * 1000) for p in selected_percentiles
247 + ],
248 mean_tpot_ms=np.mean(tpots or 0) * 1000,
249 std_tpot_ms=np.std(tpots or 0) * 1000,
250 median_tpot_ms=np.median(tpots or 0) * 1000,
229 - percentiles_tpot_ms=[(p, np.percentile(tpots or 0, p) * 1000)
230 - for p in selected_percentiles],
251 + percentiles_tpot_ms=[
252 + (p, np.percentile(tpots or 0, p) * 1000) for p in selected_percentiles
253 + ],
254 mean_itl_ms=np.mean(itls or 0) * 1000,
255 std_itl_ms=np.std(itls or 0) * 1000,
256 median_itl_ms=np.median(itls or 0) * 1000,
234 - percentiles_itl_ms=[(p, np.percentile(itls or 0, p) * 1000)
235 - for p in selected_percentiles],
257 + percentiles_itl_ms=[
258 + (p, np.percentile(itls or 0, p) * 1000) for p in selected_percentiles
259 + ],
260 mean_e2el_ms=np.mean(e2els or 0) * 1000,
261 std_e2el_ms=np.std(e2els or 0) * 1000,
262 median_e2el_ms=np.median(e2els or 0) * 1000,
239 - percentiles_e2el_ms=[(p, np.percentile(e2els or 0, p) * 1000)
240 - for p in selected_percentiles],
263 + percentiles_e2el_ms=[
264 + (p, np.percentile(e2els or 0, p) * 1000) for p in selected_percentiles
265 + ],
266 )
267
268 return metrics, actual_output_lens
@@ -270,10 +295,12 @@ async def benchmark(
295 raise ValueError(f"Unknown backend: {backend}")
296
297 print("Starting initial single prompt test run...")
273 - test_prompt, test_prompt_len, test_output_len, test_mm_content = \
274 - input_requests[0].prompt, input_requests[0].prompt_len, \
275 - input_requests[0].expected_output_len, \
276 - input_requests[0].multi_modal_data
298 + test_prompt, test_prompt_len, test_output_len, test_mm_content = (
299 + input_requests[0].prompt,
300 + input_requests[0].prompt_len,
301 + input_requests[0].expected_output_len,
302 + input_requests[0].multi_modal_data,
303 + )
304
305 assert test_mm_content is None or isinstance(test_mm_content, dict)
306 test_input = RequestFuncInput(
@@ -293,36 +320,36 @@ async def benchmark(
320 if not test_output.success:
321 raise ValueError(
322 "Initial test run failed - Please make sure benchmark arguments "
296 - f"are correctly specified. Error: {test_output.error}")
323 + f"are correctly specified. Error: {test_output.error}"
324 + )
325 else:
326 print("Initial test run completed. Starting main benchmark run...")
327
328 if lora_modules:
329 # For each input request, choose a LoRA module at random.
330 lora_modules = iter(
303 - [random.choice(lora_modules) \
304 - for _ in range(len(input_requests))])
331 + [random.choice(lora_modules) for _ in range(len(input_requests))]
332 + )
333
334 if profile:
335 print("Starting profiler...")
308 - profile_input = RequestFuncInput(model=model_id,
309 - model_name=model_name,
310 - prompt=test_prompt,
311 - api_url=base_url + "/start_profile",
312 - prompt_len=test_prompt_len,
313 - output_len=test_output_len,
314 - logprobs=logprobs,
315 - multi_modal_content=test_mm_content,
316 - ignore_eos=ignore_eos,
317 - extra_body=extra_body)
336 + profile_input = RequestFuncInput(
337 + model=model_id,
338 + model_name=model_name,
339 + prompt=test_prompt,
340 + api_url=base_url + "/start_profile",
341 + prompt_len=test_prompt_len,
342 + output_len=test_output_len,
343 + logprobs=logprobs,
344 + multi_modal_content=test_mm_content,
345 + ignore_eos=ignore_eos,
346 + extra_body=extra_body,
347 + )
348 profile_output = await request_func(request_func_input=profile_input)
349 if profile_output.success:
350 print("Profiler started")
351
322 - if burstiness == 1.0:
323 - distribution = "Poisson process"
324 - else:
325 - distribution = "Gamma distribution"
352 + distribution = "Poisson process" if burstiness == 1.0 else "Gamma distribution"
353
354 print(f"Traffic request rate: {request_rate}")
355 print(f"Burstiness factor: {burstiness} ({distribution})")
@@ -334,42 +361,45 @@ async def benchmark(
361 # and it will simplify the code in limited_request_func.
362 # semaphore = (asyncio.Semaphore(max_concurrency)
363 # if max_concurrency else contextlib.nullcontext())
337 - semaphore = (asyncio.Semaphore(max_concurrency)
338 - if max_concurrency else None)
364 + semaphore = asyncio.Semaphore(max_concurrency) if max_concurrency else None
365
366 async def limited_request_func(request_func_input, pbar):
367 if semaphore is None:
342 - return await request_func(request_func_input=request_func_input,
343 - pbar=pbar)
368 + return await request_func(request_func_input=request_func_input, pbar=pbar)
369 async with semaphore:
345 - return await request_func(request_func_input=request_func_input,
346 - pbar=pbar)
370 + return await request_func(request_func_input=request_func_input, pbar=pbar)
371
372 benchmark_start_time = time.perf_counter()
373 tasks: list[asyncio.Task] = []
374 async for request in get_request(input_requests, request_rate, burstiness):
351 - prompt, prompt_len, output_len, mm_content = request.prompt, \
352 - request.prompt_len, request.expected_output_len, \
353 - request.multi_modal_data
375 + prompt, prompt_len, output_len, mm_content = (
376 + request.prompt,
377 + request.prompt_len,
378 + request.expected_output_len,
379 + request.multi_modal_data,
380 + )
381 req_model_id, req_model_name = model_id, model_name
382 if lora_modules:
383 req_lora_module = next(lora_modules)
384 req_model_id, req_model_name = req_lora_module, req_lora_module
385
359 - request_func_input = RequestFuncInput(model=req_model_id,
360 - model_name=req_model_name,
361 - prompt=prompt,
362 - api_url=api_url,
363 - prompt_len=prompt_len,
364 - output_len=output_len,
365 - logprobs=logprobs,
366 - multi_modal_content=mm_content,
367 - ignore_eos=ignore_eos,
368 - extra_body=extra_body)
386 + request_func_input = RequestFuncInput(
387 + model=req_model_id,
388 + model_name=req_model_name,
389 + prompt=prompt,
390 + api_url=api_url,
391 + prompt_len=prompt_len,
392 + output_len=output_len,
393 + logprobs=logprobs,
394 + multi_modal_content=mm_content,
395 + ignore_eos=ignore_eos,
396 + extra_body=extra_body,
397 + )
398 tasks.append(
399 asyncio.create_task(
371 - limited_request_func(request_func_input=request_func_input,
372 - pbar=pbar)))
400 + limited_request_func(request_func_input=request_func_input, pbar=pbar)
401 + )
402 + )
403 outputs: list[RequestFuncOutput] = await asyncio.gather(*tasks)
404
405 if profile:
@@ -401,22 +431,32 @@ async def benchmark(
431 goodput_config_dict=goodput_config_dict,
432 )
433
404 - print("{s:{c}^{n}}".format(s=' Serving Benchmark Result ', n=50, c='='))
434 + print("{s:{c}^{n}}".format(s=" Serving Benchmark Result ", n=50, c="="))
435 print("{:<40} {:<10}".format("Successful requests:", metrics.completed))
406 - print("{:<40} {:<10.2f}".format("Benchmark duration (s):",
407 - benchmark_duration))
436 + print("{:<40} {:<10.2f}".format("Benchmark duration (s):", benchmark_duration))
437 print("{:<40} {:<10}".format("Total input tokens:", metrics.total_input))
409 - print("{:<40} {:<10}".format("Total generated tokens:",
410 - metrics.total_output))
411 - print("{:<40} {:<10.2f}".format("Request throughput (req/s):",
412 - metrics.request_throughput))
438 + print("{:<40} {:<10}".format("Total generated tokens:", metrics.total_output))
439 + print(
440 + "{:<40} {:<10.2f}".format(
441 + "Request throughput (req/s):", metrics.request_throughput
442 + )
443 + )
444 if goodput_config_dict:
414 - print("{:<40} {:<10.2f}".format("Request goodput (req/s):",
415 - metrics.request_goodput))
416 - print("{:<40} {:<10.2f}".format("Output token throughput (tok/s):",
417 - metrics.output_throughput))
418 - print("{:<40} {:<10.2f}".format("Total Token throughput (tok/s):",
419 - metrics.total_token_throughput))
445 + print(
446 + "{:<40} {:<10.2f}".format(
447 + "Request goodput (req/s):", metrics.request_goodput
448 + )
449 + )
450 + print(
451 + "{:<40} {:<10.2f}".format(
452 + "Output token throughput (tok/s):", metrics.output_throughput
453 + )
454 + )
455 + print(
456 + "{:<40} {:<10.2f}".format(
457 + "Total Token throughput (tok/s):", metrics.total_token_throughput
458 + )
459 + )
460
461 result = {
462 "duration": benchmark_duration,
@@ -424,8 +464,7 @@ async def benchmark(
464 "total_input_tokens": metrics.total_input,
465 "total_output_tokens": metrics.total_output,
466 "request_throughput": metrics.request_throughput,
427 - "request_goodput:":
428 - metrics.request_goodput if goodput_config_dict else None,
467 + "request_goodput:": metrics.request_goodput if goodput_config_dict else None,
468 "output_throughput": metrics.output_throughput,
469 "total_token_throughput": metrics.total_token_throughput,
470 "input_lens": [output.prompt_len for output in outputs],
@@ -448,29 +487,35 @@ async def benchmark(
487 # metric.
488 if metric_attribute_name not in selected_percentile_metrics:
489 return
451 - print("{s:{c}^{n}}".format(s=metric_header, n=50, c='-'))
452 - print("{:<40} {:<10.2f}".format(
453 - f"Mean {metric_name} (ms):",
454 - getattr(metrics, f"mean_{metric_attribute_name}_ms")))
455 - print("{:<40} {:<10.2f}".format(
456 - f"Median {metric_name} (ms):",
457 - getattr(metrics, f"median_{metric_attribute_name}_ms")))
490 + print("{s:{c}^{n}}".format(s=metric_header, n=50, c="-"))
491 + print(
492 + "{:<40} {:<10.2f}".format(
493 + f"Mean {metric_name} (ms):",
494 + getattr(metrics, f"mean_{metric_attribute_name}_ms"),
495 + )
496 + )
497 + print(
498 + "{:<40} {:<10.2f}".format(
499 + f"Median {metric_name} (ms):",
500 + getattr(metrics, f"median_{metric_attribute_name}_ms"),
501 + )
502 + )
503 result[f"mean_{metric_attribute_name}_ms"] = getattr(
459 - metrics, f"mean_{metric_attribute_name}_ms")
504 + metrics, f"mean_{metric_attribute_name}_ms"
505 + )
506 result[f"median_{metric_attribute_name}_ms"] = getattr(
461 - metrics, f"median_{metric_attribute_name}_ms")
507 + metrics, f"median_{metric_attribute_name}_ms"
508 + )
509 result[f"std_{metric_attribute_name}_ms"] = getattr(
463 - metrics, f"std_{metric_attribute_name}_ms")
464 - for p, value in getattr(metrics,
465 - f"percentiles_{metric_attribute_name}_ms"):
510 + metrics, f"std_{metric_attribute_name}_ms"
511 + )
512 + for p, value in getattr(metrics, f"percentiles_{metric_attribute_name}_ms"):
513 p_word = str(int(p)) if int(p) == p else str(p)
467 - print("{:<40} {:<10.2f}".format(f"P{p_word} {metric_name} (ms):",
468 - value))
514 + print("{:<40} {:<10.2f}".format(f"P{p_word} {metric_name} (ms):", value))
515 result[f"p{p_word}_{metric_attribute_name}_ms"] = value
516
517 process_one_metric("ttft", "TTFT", "Time to First Token")
472 - process_one_metric("tpot", "TPOT",
473 - "Time per Output Token (excl. 1st token)")
518 + process_one_metric("tpot", "TPOT", "Time per Output Token (excl. 1st token)")
519 process_one_metric("itl", "ITL", "Inter-token Latency")
520 process_one_metric("e2el", "E2EL", "End-to-end Latency")
521
@@ -490,12 +535,14 @@ def check_goodput_args(args):
535 raise ValueError(
536 f"Invalid metric name found, {slo_name}: {slo_val}. "
537 "The service level objective name should be one of "
493 - f"{str(VALID_NAMES)}. ")
538 + f"{str(VALID_NAMES)}. "
539 + )
540 if slo_val < 0:
541 raise ValueError(
542 f"Invalid value found, {slo_name}: {slo_val}. "
543 "The service level objective value should be "
498 - "non-negative.")
544 + "non-negative."
545 + )
546 return goodput_config_dict
547
548
@@ -508,31 +555,42 @@ def parse_goodput(slo_pairs):
555 except ValueError as err:
556 raise argparse.ArgumentTypeError(
557 "Invalid format found for service level objectives. "
511 - "Specify service level objectives for goodput as \"KEY:VALUE\" "
558 + 'Specify service level objectives for goodput as "KEY:VALUE" '
559 "pairs, where the key is a metric name, and the value is a "
513 - "number in milliseconds.") from err
560 + "number in milliseconds."
561 + ) from err
562 return goodput_config_dict
563
564
517 -def save_to_pytorch_benchmark_format(args: argparse.Namespace,
518 - results: dict[str, Any],
519 - file_name: str) -> None:
565 +def save_to_pytorch_benchmark_format(
566 + args: argparse.Namespace, results: dict[str, Any], file_name: str
567 +) -> None:
568 metrics = [
521 - "median_ttft_ms", "mean_ttft_ms", "std_ttft_ms", "p99_ttft_ms",
522 - "mean_tpot_ms", "median_tpot_ms", "std_tpot_ms", "p99_tpot_ms",
523 - "median_itl_ms", "mean_itl_ms", "std_itl_ms", "p99_itl_ms"
569 + "median_ttft_ms",
570 + "mean_ttft_ms",
571 + "std_ttft_ms",
572 + "p99_ttft_ms",
573 + "mean_tpot_ms",
574 + "median_tpot_ms",
575 + "std_tpot_ms",
576 + "p99_tpot_ms",
577 + "median_itl_ms",
578 + "mean_itl_ms",
579 + "std_itl_ms",
580 + "p99_itl_ms",
581 ]
582 # These raw data might be useful, but they are rather big. They can be added
583 # later if needed
584 ignored_metrics = ["ttfts", "itls", "generated_texts", "errors"]
585 pt_records = convert_to_pytorch_benchmark_format(
586 args=args,
530 - metrics={k: [results[k]]
531 - for k in metrics},
587 + metrics={k: [results[k]] for k in metrics},
588 extra_info={
589 k: results[k]
534 - for k in results if k not in metrics and k not in ignored_metrics
535 - })
590 + for k in results
591 + if k not in metrics and k not in ignored_metrics
592 + },
593 + )
594 if pt_records:
595 # Don't use json suffix here as we don't want CI to pick it up
596 pt_file = f"{os.path.splitext(file_name)[0]}.pytorch.json"
@@ -557,34 +615,42 @@ def main(args: argparse.Namespace):
615 api_url = f"http://{args.host}:{args.port}{args.endpoint}"
616 base_url = f"http://{args.host}:{args.port}"
617
560 - tokenizer = get_tokenizer(tokenizer_id,
561 - tokenizer_mode=tokenizer_mode,
562 - trust_remote_code=args.trust_remote_code)
618 + tokenizer = get_tokenizer(
619 + tokenizer_id,
620 + tokenizer_mode=tokenizer_mode,
621 + trust_remote_code=args.trust_remote_code,
622 + )
623
624 if args.dataset_name is None:
625 raise ValueError(
626 "Please specify '--dataset-name' and the corresponding "
567 - "'--dataset-path' if required.")
627 + "'--dataset-path' if required."
628 + )
629
630 if args.dataset_name == "sonnet":
631 dataset = SonnetDataset(dataset_path=args.dataset_path)
632 # For the "sonnet" dataset, formatting depends on the backend.
633 if args.backend == "openai-chat":
573 - input_requests = dataset.sample(num_requests=args.num_prompts,
574 - input_len=args.sonnet_input_len,
575 - output_len=args.sonnet_output_len,
576 - prefix_len=args.sonnet_prefix_len,
577 - tokenizer=tokenizer,
578 - return_prompt_formatted=False)
634 + input_requests = dataset.sample(
635 + num_requests=args.num_prompts,
636 + input_len=args.sonnet_input_len,
637 + output_len=args.sonnet_output_len,
638 + prefix_len=args.sonnet_prefix_len,
639 + tokenizer=tokenizer,
640 + return_prompt_formatted=False,
641 + )
642 else:
643 assert tokenizer.chat_template or tokenizer.default_chat_template, (
581 - "Tokenizer/model must have chat template for sonnet dataset.")
582 - input_requests = dataset.sample(num_requests=args.num_prompts,
583 - input_len=args.sonnet_input_len,
584 - output_len=args.sonnet_output_len,
585 - prefix_len=args.sonnet_prefix_len,
586 - tokenizer=tokenizer,
587 - return_prompt_formatted=True)
644 + "Tokenizer/model must have chat template for sonnet dataset."
645 + )
646 + input_requests = dataset.sample(
647 + num_requests=args.num_prompts,
648 + input_len=args.sonnet_input_len,
649 + output_len=args.sonnet_output_len,
650 + prefix_len=args.sonnet_prefix_len,
651 + tokenizer=tokenizer,
652 + return_prompt_formatted=True,
653 + )
654
655 elif args.dataset_name == "hf":
656 # all following datasets are implemented from the
@@ -611,23 +677,30 @@ def main(args: argparse.Namespace):
677 dataset_class = ASRDataset
678 args.hf_split = "train"
679 else:
614 - supported_datasets = set([
615 - dataset_name for cls in HuggingFaceDataset.__subclasses__()
616 - for dataset_name in cls.SUPPORTED_DATASET_PATHS
617 - ])
680 + supported_datasets = set(
681 + [
682 + dataset_name
683 + for cls in HuggingFaceDataset.__subclasses__()
684 + for dataset_name in cls.SUPPORTED_DATASET_PATHS
685 + ]
686 + )
687 raise ValueError(
688 f"Unsupported dataset path: {args.dataset_path}. "
689 "Huggingface dataset only supports dataset_path"
690 f" from one of following: {supported_datasets}. "
691 "Please consider contributing if you would "
623 - "like to add support for additional dataset formats.")
692 + "like to add support for additional dataset formats."
693 + )
694
625 - if (dataset_class.IS_MULTIMODAL and backend not in \
626 - ["openai-chat", "openai-audio"]):
695 + if dataset_class.IS_MULTIMODAL and backend not in [
696 + "openai-chat",
697 + "openai-audio",
698 + ]:
699 # multi-modal benchmark is only available on OpenAI Chat backend.
700 raise ValueError(
629 - "Multi-modal content is only supported on 'openai-chat' and " \
630 - "'openai-audio' backend.")
701 + "Multi-modal content is only supported on 'openai-chat' and "
702 + "'openai-audio' backend."
703 + )
704 input_requests = dataset_class(
705 dataset_path=args.dataset_path,
706 dataset_subset=args.hf_subset,
@@ -642,26 +715,24 @@ def main(args: argparse.Namespace):
715 else:
716 # For datasets that follow a similar structure, use a mapping.
717 dataset_mapping = {
645 - "sharegpt":
646 - lambda: ShareGPTDataset(random_seed=args.seed,
647 - dataset_path=args.dataset_path).sample(
648 - tokenizer=tokenizer,
649 - num_requests=args.num_prompts,
650 - output_len=args.sharegpt_output_len,
651 - ),
652 - "burstgpt":
653 - lambda: BurstGPTDataset(random_seed=args.seed,
654 - dataset_path=args.dataset_path).
655 - sample(tokenizer=tokenizer, num_requests=args.num_prompts),
656 - "random":
657 - lambda: RandomDataset(dataset_path=args.dataset_path).sample(
718 + "sharegpt": lambda: ShareGPTDataset(
719 + random_seed=args.seed, dataset_path=args.dataset_path
720 + ).sample(
721 + tokenizer=tokenizer,
722 + num_requests=args.num_prompts,
723 + output_len=args.sharegpt_output_len,
724 + ),
725 + "burstgpt": lambda: BurstGPTDataset(
726 + random_seed=args.seed, dataset_path=args.dataset_path
727 + ).sample(tokenizer=tokenizer, num_requests=args.num_prompts),
728 + "random": lambda: RandomDataset(dataset_path=args.dataset_path).sample(
729 tokenizer=tokenizer,
730 num_requests=args.num_prompts,
731 prefix_len=args.random_prefix_len,
732 input_len=args.random_input_len,
733 output_len=args.random_output_len,
734 range_ratio=args.random_range_ratio,
664 - )
735 + ),
736 }
737
738 try:
@@ -677,15 +748,16 @@ def main(args: argparse.Namespace):
748 "top_p": args.top_p,
749 "top_k": args.top_k,
750 "min_p": args.min_p,
680 - "temperature": args.temperature
681 - }.items() if v is not None
751 + "temperature": args.temperature,
752 + }.items()
753 + if v is not None
754 }
755
756 # Sampling parameters are only supported by openai-compatible backend.
757 if sampling_params and args.backend not in OPENAI_COMPATIBLE_BACKENDS:
758 raise ValueError(
687 - "Sampling parameters are only supported by openai-compatible "
688 - "backends.")
759 + "Sampling parameters are only supported by openai-compatible backends."
760 + )
761
762 if "temperature" not in sampling_params:
763 sampling_params["temperature"] = 0.0 # Default to greedy decoding.
@@ -709,15 +781,14 @@ def main(args: argparse.Namespace):
781 disable_tqdm=args.disable_tqdm,
782 profile=args.profile,
783 selected_percentile_metrics=args.percentile_metrics.split(","),
712 - selected_percentiles=[
713 - float(p) for p in args.metric_percentiles.split(",")
714 - ],
784 + selected_percentiles=[float(p) for p in args.metric_percentiles.split(",")],
785 ignore_eos=args.ignore_eos,
786 goodput_config_dict=goodput_config_dict,
787 max_concurrency=args.max_concurrency,
788 lora_modules=args.lora_modules,
789 extra_body=sampling_params,
720 - ))
790 + )
791 + )
792
793 # Save config and results to json
794 if args.save_result or args.append_result:
@@ -742,8 +813,9 @@ def main(args: argparse.Namespace):
813 "Invalid metadata format. Please use KEY=VALUE format."
814 )
815 # Traffic
745 - result_json["request_rate"] = (args.request_rate if args.request_rate
746 - < float("inf") else "inf")
816 + result_json["request_rate"] = (
817 + args.request_rate if args.request_rate < float("inf") else "inf"
818 + )
819 result_json["burstiness"] = args.burstiness
820 result_json["max_concurrency"] = args.max_concurrency
821
@@ -753,24 +825,31 @@ def main(args: argparse.Namespace):
825 if not args.save_detailed:
826 # Remove fields with too many data points
827 for field in [
756 - "input_lens", "output_lens", "ttfts", "itls",
757 - "generated_texts", "errors"
828 + "input_lens",
829 + "output_lens",
830 + "ttfts",
831 + "itls",
832 + "generated_texts",
833 + "errors",
834 ]:
835 if field in result_json:
836 del result_json[field]
837
838 # Save to file
839 base_model_id = model_id.split("/")[-1]
764 - max_concurrency_str = (f"-concurrency{args.max_concurrency}"
765 - if args.max_concurrency is not None else "")
766 - file_name = f"{backend}-{args.request_rate}qps{max_concurrency_str}-{base_model_id}-{current_dt}.json" #noqa
840 + max_concurrency_str = (
841 + f"-concurrency{args.max_concurrency}"
842 + if args.max_concurrency is not None
843 + else ""
844 + )
845 + file_name = f"{backend}-{args.request_rate}qps{max_concurrency_str}-{base_model_id}-{current_dt}.json" # noqa
846 if args.result_filename:
847 file_name = args.result_filename
848 if args.result_dir:
849 file_name = os.path.join(args.result_dir, file_name)
771 - with open(file_name,
772 - mode="a+" if args.append_result else "w",
773 - encoding='utf-8') as outfile:
850 + with open(
851 + file_name, mode="a+" if args.append_result else "w", encoding="utf-8"
852 + ) as outfile:
853 # Append a newline.
854 if args.append_result and outfile.tell() != 0:
855 outfile.write("\n")
@@ -780,7 +859,8 @@ def main(args: argparse.Namespace):
859
860 if __name__ == "__main__":
861 parser = FlexibleArgumentParser(
783 - description="Benchmark the online serving throughput.")
862 + description="Benchmark the online serving throughput."
863 + )
864 parser.add_argument(
865 "--backend",
866 type=str,
@@ -809,11 +889,13 @@ if __name__ == "__main__":
889 choices=["sharegpt", "burstgpt", "sonnet", "random", "hf"],
890 help="Name of the dataset to benchmark on.",
891 )
812 - parser.add_argument("--dataset-path",
813 - type=str,
814 - default=None,
815 - help="Path to the sharegpt/sonnet dataset. "
816 - "Or the huggingface dataset ID if using HF dataset.")
892 + parser.add_argument(
893 + "--dataset-path",
894 + type=str,
895 + default=None,
896 + help="Path to the sharegpt/sonnet dataset. "
897 + "Or the huggingface dataset ID if using HF dataset.",
898 + )
899 parser.add_argument(
900 "--max-concurrency",
901 type=int,
@@ -825,7 +907,8 @@ if __name__ == "__main__":
907 "initiated, this argument will control how many are actually allowed "
908 "to execute at a time. This means that when used in combination, the "
909 "actual request rate may be lower than specified with --request-rate, "
828 - "if the server is not processing requests fast enough to keep up.")
910 + "if the server is not processing requests fast enough to keep up.",
911 + )
912
913 parser.add_argument(
914 "--model",
@@ -836,8 +919,7 @@ if __name__ == "__main__":
919 parser.add_argument(
920 "--tokenizer",
921 type=str,
839 - help=
840 - "Name or path of the tokenizer, if not using the default tokenizer.", # noqa: E501
922 + help="Name or path of the tokenizer, if not using the default tokenizer.", # noqa: E501
923 )
924 parser.add_argument("--use-beam-search", action="store_true")
925 parser.add_argument(
@@ -850,11 +932,13 @@ if __name__ == "__main__":
932 "--logprobs",
933 type=int,
934 default=None,
853 - help=("Number of logprobs-per-token to compute & return as part of "
854 - "the request. If unspecified, then either (1) if beam search "
855 - "is disabled, no logprobs are computed & a single dummy "
856 - "logprob is returned for each token; or (2) if beam search "
857 - "is enabled 1 logprob per token is computed"),
935 + help=(
936 + "Number of logprobs-per-token to compute & return as part of "
937 + "the request. If unspecified, then either (1) if beam search "
938 + "is disabled, no logprobs are computed & a single dummy "
939 + "logprob is returned for each token; or (2) if beam search "
940 + "is enabled 1 logprob per token is computed"
941 + ),
942 )
943 parser.add_argument(
944 "--request-rate",
@@ -938,35 +1022,38 @@ if __name__ == "__main__":
1022 "--ignore-eos",
1023 action="store_true",
1024 help="Set ignore_eos flag when sending the benchmark request."
941 - "Warning: ignore_eos is not supported in deepspeed_mii and tgi.")
1025 + "Warning: ignore_eos is not supported in deepspeed_mii and tgi.",
1026 + )
1027 parser.add_argument(
1028 "--percentile-metrics",
1029 type=str,
1030 default="ttft,tpot,itl",
1031 help="Comma-separated list of selected metrics to report percentils. "
1032 "This argument specifies the metrics to report percentiles. "
948 - "Allowed metric names are \"ttft\", \"tpot\", \"itl\", \"e2el\". "
949 - "Default value is \"ttft,tpot,itl\".")
1033 + 'Allowed metric names are "ttft", "tpot", "itl", "e2el". '
1034 + 'Default value is "ttft,tpot,itl".',
1035 + )
1036 parser.add_argument(
1037 "--metric-percentiles",
1038 type=str,
1039 default="99",
1040 help="Comma-separated list of percentiles for selected metrics. "
955 - "To report 25-th, 50-th, and 75-th percentiles, use \"25,50,75\". "
956 - "Default value is \"99\". "
957 - "Use \"--percentile-metrics\" to select metrics.",
1041 + 'To report 25-th, 50-th, and 75-th percentiles, use "25,50,75". '
1042 + 'Default value is "99". '
1043 + 'Use "--percentile-metrics" to select metrics.',
1044 )
1045 parser.add_argument(
1046 "--goodput",
1047 nargs="+",
1048 required=False,
963 - help="Specify service level objectives for goodput as \"KEY:VALUE\" "
1049 + help='Specify service level objectives for goodput as "KEY:VALUE" '
1050 "pairs, where the key is a metric name, and the value is in "
965 - "milliseconds. Multiple \"KEY:VALUE\" pairs can be provided, "
1051 + 'milliseconds. Multiple "KEY:VALUE" pairs can be provided, '
1052 "separated by spaces. Allowed request level metric names are "
967 - "\"ttft\", \"tpot\", \"e2el\". For more context on the definition of "
1053 + '"ttft", "tpot", "e2el". For more context on the definition of '
1054 "goodput, refer to DistServe paper: https://arxiv.org/pdf/2401.09670 "
969 - "and the blog: https://hao-ai-lab.github.io/blogs/distserve")
1055 + "and the blog: https://hao-ai-lab.github.io/blogs/distserve",
1056 + )
1057
1058 # group for dataset specific arguments
1059 sonnet_group = parser.add_argument_group("sonnet dataset options")
@@ -974,22 +1061,19 @@ if __name__ == "__main__":
1061 "--sonnet-input-len",
1062 type=int,
1063 default=550,
977 - help=
978 - "Number of input tokens per request, used only for sonnet dataset.",
1064 + help="Number of input tokens per request, used only for sonnet dataset.",
1065 )
1066 sonnet_group.add_argument(
1067 "--sonnet-output-len",
1068 type=int,
1069 default=150,
984 - help=
985 - "Number of output tokens per request, used only for sonnet dataset.",
1070 + help="Number of output tokens per request, used only for sonnet dataset.",
1071 )
1072 sonnet_group.add_argument(
1073 "--sonnet-prefix-len",
1074 type=int,
1075 default=200,
991 - help=
992 - "Number of prefix tokens per request, used only for sonnet dataset.",
1076 + help="Number of prefix tokens per request, used only for sonnet dataset.",
1077 )
1078
1079 sharegpt_group = parser.add_argument_group("sharegpt dataset options")
@@ -998,22 +1082,21 @@ if __name__ == "__main__":
1082 type=int,
1083 default=None,
1084 help="Output length for each request. Overrides the output length "
1001 - "from the ShareGPT dataset.")
1085 + "from the ShareGPT dataset.",
1086 + )
1087
1088 random_group = parser.add_argument_group("random dataset options")
1089 random_group.add_argument(
1090 "--random-input-len",
1091 type=int,
1092 default=1024,
1008 - help=
1009 - "Number of input tokens per request, used only for random sampling.",
1093 + help="Number of input tokens per request, used only for random sampling.",
1094 )
1095 random_group.add_argument(
1096 "--random-output-len",
1097 type=int,
1098 default=128,
1015 - help=
1016 - "Number of output tokens per request, used only for random sampling.",
1099 + help="Number of output tokens per request, used only for random sampling.",
1100 )
1101 random_group.add_argument(
1102 "--random-range-ratio",
@@ -1028,23 +1111,23 @@ if __name__ == "__main__":
1111 "--random-prefix-len",
1112 type=int,
1113 default=0,
1031 - help=("Number of fixed prefix tokens before the random context "
1032 - "in a request. "
1033 - "The total input length is the sum of `random-prefix-len` and "
1034 - "a random "
1035 - "context length sampled from [input_len * (1 - range_ratio), "
1036 - "input_len * (1 + range_ratio)]."),
1114 + help=(
1115 + "Number of fixed prefix tokens before the random context "
1116 + "in a request. "
1117 + "The total input length is the sum of `random-prefix-len` and "
1118 + "a random "
1119 + "context length sampled from [input_len * (1 - range_ratio), "
1120 + "input_len * (1 + range_ratio)]."
1121 + ),
1122 )
1123
1124 hf_group = parser.add_argument_group("hf dataset options")
1040 - hf_group.add_argument("--hf-subset",
1041 - type=str,
1042 - default=None,
1043 - help="Subset of the HF dataset.")
1044 - hf_group.add_argument("--hf-split",
1045 - type=str,
1046 - default=None,
1047 - help="Split of the HF dataset.")
1125 + hf_group.add_argument(
1126 + "--hf-subset", type=str, default=None, help="Subset of the HF dataset."
1127 + )
1128 + hf_group.add_argument(
1129 + "--hf-split", type=str, default=None, help="Split of the HF dataset."
1130 + )
1131 hf_group.add_argument(
1132 "--hf-output-len",
1133 type=int,
@@ -1058,52 +1141,58 @@ if __name__ == "__main__":
1141 "--top-p",
1142 type=float,
1143 default=None,
1061 - help="Top-p sampling parameter. Only has effect on openai-compatible "
1062 - "backends.")
1144 + help="Top-p sampling parameter. Only has effect on openai-compatible backends.",
1145 + )
1146 sampling_group.add_argument(
1147 "--top-k",
1148 type=int,
1149 default=None,
1067 - help="Top-k sampling parameter. Only has effect on openai-compatible "
1068 - "backends.")
1150 + help="Top-k sampling parameter. Only has effect on openai-compatible backends.",
1151 + )
1152 sampling_group.add_argument(
1153 "--min-p",
1154 type=float,
1155 default=None,
1073 - help="Min-p sampling parameter. Only has effect on openai-compatible "
1074 - "backends.")
1156 + help="Min-p sampling parameter. Only has effect on openai-compatible backends.",
1157 + )
1158 sampling_group.add_argument(
1159 "--temperature",
1160 type=float,
1161 default=None,
1162 help="Temperature sampling parameter. Only has effect on "
1163 "openai-compatible backends. If not specified, default to greedy "
1081 - "decoding (i.e. temperature==0.0).")
1164 + "decoding (i.e. temperature==0.0).",
1165 + )
1166
1167 parser.add_argument(
1084 - '--tokenizer-mode',
1168 + "--tokenizer-mode",
1169 type=str,
1170 default="auto",
1087 - choices=['auto', 'slow', 'mistral', 'custom'],
1171 + choices=["auto", "slow", "mistral", "custom"],
1172 help='The tokenizer mode.\n\n* "auto" will use the '
1173 'fast tokenizer if available.\n* "slow" will '
1090 - 'always use the slow tokenizer. \n* '
1174 + "always use the slow tokenizer. \n* "
1175 '"mistral" will always use the `mistral_common` tokenizer. \n*'
1092 - '"custom" will use --tokenizer to select the preregistered tokenizer.')
1093 -
1094 - parser.add_argument("--served-model-name",
1095 - type=str,
1096 - default=None,
1097 - help="The model name used in the API. "
1098 - "If not specified, the model name will be the "
1099 - "same as the ``--model`` argument. ")
1100 -
1101 - parser.add_argument("--lora-modules",
1102 - nargs='+',
1103 - default=None,
1104 - help="A subset of LoRA module names passed in when "
1105 - "launching the server. For each request, the "
1106 - "script chooses a LoRA module at random.")
1176 + '"custom" will use --tokenizer to select the preregistered tokenizer.',
1177 + )
1178 +
1179 + parser.add_argument(
1180 + "--served-model-name",
1181 + type=str,
1182 + default=None,
1183 + help="The model name used in the API. "
1184 + "If not specified, the model name will be the "
1185 + "same as the ``--model`` argument. ",
1186 + )
1187 +
1188 + parser.add_argument(
1189 + "--lora-modules",
1190 + nargs="+",
1191 + default=None,
1192 + help="A subset of LoRA module names passed in when "
1193 + "launching the server. For each request, the "
1194 + "script chooses a LoRA module at random.",
1195 + )
1196
1197 args = parser.parse_args()
1198
benchmarks/benchmark_serving_structured_output.py
+263 -226
@@ -19,6 +19,7 @@ On the client side, run:
19 --endpoint /generate_stream
20 to the end of the command above.
21 """
22 +
23 import argparse
24 import asyncio
25 import copy
@@ -36,11 +37,15 @@ from typing import Optional
37 import datasets
38 import numpy as np
39 import pandas as pd
39 -from backend_request_func import (ASYNC_REQUEST_FUNCS, RequestFuncInput,
40 - RequestFuncOutput)
40 from tqdm.asyncio import tqdm
41 from transformers import PreTrainedTokenizerBase
42
43 +from backend_request_func import (
44 + ASYNC_REQUEST_FUNCS,
45 + RequestFuncInput,
46 + RequestFuncOutput,
47 +)
48 +
49 try:
50 from vllm.transformers_utils.tokenizer import get_tokenizer
51 except ImportError:
@@ -52,7 +57,8 @@ except ImportError:
57 from argparse import ArgumentParser as FlexibleArgumentParser
58
59 from vllm.v1.structured_output.backend_xgrammar import (
55 - has_xgrammar_unsupported_json_features)
60 + has_xgrammar_unsupported_json_features,
61 +)
62
63 MILLISECONDS_TO_SECONDS_CONVERSION = 1000
64
@@ -98,6 +104,7 @@ class SampleRequest:
104 prompt_len: The length of the prompt in tokens.
105 expected_output_len: The expected length of the output in tokens.
106 """
107 +
108 prompt: str
109 prompt_len: int
110 expected_output_len: int
@@ -106,32 +113,28 @@ class SampleRequest:
113 completion: str = None
114
115
109 -def sample_requests(tokenizer: PreTrainedTokenizerBase,
110 - args: argparse.Namespace) -> list[SampleRequest]:
111 - if args.dataset == 'json' or args.dataset == 'json-unique':
116 +def sample_requests(
117 + tokenizer: PreTrainedTokenizerBase, args: argparse.Namespace
118 +) -> list[SampleRequest]:
119 + if args.dataset == "json" or args.dataset == "json-unique":
120 if args.json_schema_path is None:
121 dir_path = os.path.dirname(os.path.realpath(__file__))
114 - args.json_schema_path = os.path.join(dir_path,
115 - "structured_schemas",
116 - "structured_schema_1.json")
122 + args.json_schema_path = os.path.join(
123 + dir_path, "structured_schemas", "structured_schema_1.json"
124 + )
125 json_schemas = []
126 with open(args.json_schema_path) as f:
127 schema = json.load(f)
128
121 - if args.dataset == 'json-unique':
122 - json_schemas = [
123 - copy.deepcopy(schema) for _ in range(args.num_prompts)
124 - ]
129 + if args.dataset == "json-unique":
130 + json_schemas = [copy.deepcopy(schema) for _ in range(args.num_prompts)]
131 for i in range(len(json_schemas)):
132 if "properties" not in json_schemas[i]:
133 json_schemas[i]["properties"] = {}
128 - json_schemas[i]["properties"][
129 - f"__optional_field_{uuid.uuid4()}"] = {
130 - "type":
131 - "string",
132 - "description":
133 - "An unique optional field to avoid cached schemas"
134 - }
134 + json_schemas[i]["properties"][f"__optional_field_{uuid.uuid4()}"] = {
135 + "type": "string",
136 + "description": "An unique optional field to avoid cached schemas",
137 + }
138 else:
139 json_schemas = [schema] * args.num_prompts
140
@@ -142,11 +145,13 @@ def sample_requests(tokenizer: PreTrainedTokenizerBase,
145 return json_schemas[index % len(json_schemas)]
146
147 requests = [
145 - SampleRequest(prompt=gen_prompt(i),
146 - prompt_len=len(tokenizer(gen_prompt(i)).input_ids),
147 - expected_output_len=args.output_len,
148 - schema=get_schema(i),
149 - structure_type=args.structure_type)
148 + SampleRequest(
149 + prompt=gen_prompt(i),
150 + prompt_len=len(tokenizer(gen_prompt(i)).input_ids),
151 + expected_output_len=args.output_len,
152 + schema=get_schema(i),
153 + structure_type=args.structure_type,
154 + )
155 for i in range(args.num_prompts)
156 ]
157
@@ -170,11 +175,13 @@ def sample_requests(tokenizer: PreTrainedTokenizerBase,
175 input_len = len(tokenizer(prompt).input_ids)
176 print(f"Input length of the prompt: {input_len} tokens")
177 requests = [
173 - SampleRequest(prompt=prompt,
174 - prompt_len=input_len,
175 - expected_output_len=args.output_len,
176 - schema=schema,
177 - structure_type=args.structure_type)
178 + SampleRequest(
179 + prompt=prompt,
180 + prompt_len=input_len,
181 + expected_output_len=args.output_len,
182 + schema=schema,
183 + structure_type=args.structure_type,
184 + )
185 for _ in range(args.num_prompts)
186 ]
187
@@ -188,11 +195,13 @@ def sample_requests(tokenizer: PreTrainedTokenizerBase,
195 input_len = len(tokenizer(prompt).input_ids)
196 print(f"Input length of the prompt: {input_len} tokens")
197 requests = [
191 - SampleRequest(prompt=prompt,
192 - prompt_len=input_len,
193 - expected_output_len=args.output_len,
194 - schema=regex,
195 - structure_type=args.structure_type)
198 + SampleRequest(
199 + prompt=prompt,
200 + prompt_len=input_len,
201 + expected_output_len=args.output_len,
202 + schema=regex,
203 + structure_type=args.structure_type,
204 + )
205 for _ in range(args.num_prompts)
206 ]
207
@@ -203,48 +212,55 @@ def sample_requests(tokenizer: PreTrainedTokenizerBase,
212 input_len = len(tokenizer(prompt).input_ids)
213 print(f"Input length of the prompt: {input_len} tokens")
214 requests = [
206 - SampleRequest(prompt=prompt,
207 - prompt_len=input_len,
208 - expected_output_len=args.output_len,
209 - schema=choice,
210 - structure_type=args.structure_type)
215 + SampleRequest(
216 + prompt=prompt,
217 + prompt_len=input_len,
218 + expected_output_len=args.output_len,
219 + schema=choice,
220 + structure_type=args.structure_type,
221 + )
222 for _ in range(args.num_prompts)
223 ]
224
225 elif args.dataset == "xgrammar_bench":
226 requests: list[SampleRequest] = []
216 - dataset = datasets.load_dataset("NousResearch/json-mode-eval",
217 - split="train")
227 + dataset = datasets.load_dataset("NousResearch/json-mode-eval", split="train")
228 full_dataset_len = len(dataset)
229
230 def _filter_func(item):
231 import json
232 +
233 schema = json.loads(item["schema"])
234 return not has_xgrammar_unsupported_json_features(schema)
235
236 dataset = dataset.filter(_filter_func)
237 num_filtered_out = full_dataset_len - len(dataset)
227 - print(f"dataset has {len(dataset)} entries after filtering "
228 - f"out {num_filtered_out} entries with unsupported features")
238 + print(
239 + f"dataset has {len(dataset)} entries after filtering "
240 + f"out {num_filtered_out} entries with unsupported features"
241 + )
242 len_dataset = len(dataset)
243 for data_point_idx in range(args.num_prompts):
244 idx = data_point_idx
245 while idx >= len_dataset:
246 idx -= len_dataset
247 schema = dataset["schema"][idx]
235 - prompt = tokenizer.apply_chat_template(dataset["prompt"][idx],
236 - tokenize=False,
237 - add_generation_prompt=True)
248 + prompt = tokenizer.apply_chat_template(
249 + dataset["prompt"][idx], tokenize=False, add_generation_prompt=True
250 + )
251 input_len = len(tokenizer(prompt).input_ids)
252 completion = dataset["completion"][idx]
253
254 requests.append(
242 - SampleRequest(prompt=prompt,
243 - prompt_len=input_len,
244 - expected_output_len=args.output_len,
245 - schema=schema,
246 - structure_type=args.structure_type,
247 - completion=completion))
255 + SampleRequest(
256 + prompt=prompt,
257 + prompt_len=input_len,
258 + expected_output_len=args.output_len,
259 + schema=schema,
260 + structure_type=args.structure_type,
261 + completion=completion,
262 + )
263 + )
264
265 return requests
266
@@ -276,7 +292,8 @@ async def get_request(
292
293 # Calculate scale parameter theta to maintain the desired request_rate.
294 assert burstiness > 0, (
279 - f"A positive burstiness factor is expected, but given {burstiness}.")
295 + f"A positive burstiness factor is expected, but given {burstiness}."
296 + )
297 theta = 1.0 / (request_rate * burstiness)
298
299 for i, request in enumerate(input_requests):
@@ -318,8 +335,8 @@ def calculate_metrics(
335 # multiple output tokens may be bundled together
336 # Note : this may inflate the output token count slightly
337 output_len = len(
321 - tokenizer(outputs[i].generated_text,
322 - add_special_tokens=False).input_ids)
338 + tokenizer(outputs[i].generated_text, add_special_tokens=False).input_ids
339 + )
340 actual_output_lens.append(output_len)
341 total_input += input_requests[i].prompt_len
342 tpot = 0
@@ -343,16 +360,19 @@ def calculate_metrics(
360
361 if "ttft" in goodput_config_dict:
362 valid_metrics.append(ttfts)
346 - slo_values.append(goodput_config_dict["ttft"] /
347 - MILLISECONDS_TO_SECONDS_CONVERSION)
363 + slo_values.append(
364 + goodput_config_dict["ttft"] / MILLISECONDS_TO_SECONDS_CONVERSION
365 + )
366 if "tpot" in goodput_config_dict:
367 valid_metrics.append(all_tpots)
350 - slo_values.append(goodput_config_dict["tpot"] /
351 - MILLISECONDS_TO_SECONDS_CONVERSION)
368 + slo_values.append(
369 + goodput_config_dict["tpot"] / MILLISECONDS_TO_SECONDS_CONVERSION
370 + )
371 if "e2el" in goodput_config_dict:
372 valid_metrics.append(e2els)
354 - slo_values.append(goodput_config_dict["e2el"] /
355 - MILLISECONDS_TO_SECONDS_CONVERSION)
373 + slo_values.append(
374 + goodput_config_dict["e2el"] / MILLISECONDS_TO_SECONDS_CONVERSION
375 + )
376
377 for req_metric in zip(*valid_metrics):
378 is_good_req = all([s >= r for s, r in zip(slo_values, req_metric)])
@@ -363,7 +383,8 @@ def calculate_metrics(
383 warnings.warn(
384 "All requests failed. This is likely due to a misconfiguration "
385 "on the benchmark arguments.",
366 - stacklevel=2)
386 + stacklevel=2,
387 + )
388 metrics = BenchmarkMetrics(
389 completed=completed,
390 total_input=total_input,
@@ -372,27 +393,31 @@ def calculate_metrics(
393 request_goodput=good_completed / dur_s,
394 output_throughput=sum(actual_output_lens) / dur_s,
395 total_token_throughput=(total_input + sum(actual_output_lens)) / dur_s,
375 - mean_ttft_ms=np.mean(ttfts or 0) *
376 - 1000, # ttfts is empty if streaming is not supported by backend
396 + mean_ttft_ms=np.mean(ttfts or 0)
397 + * 1000, # ttfts is empty if streaming is not supported by backend
398 std_ttft_ms=np.std(ttfts or 0) * 1000,
399 median_ttft_ms=np.median(ttfts or 0) * 1000,
379 - percentiles_ttft_ms=[(p, np.percentile(ttfts or 0, p) * 1000)
380 - for p in selected_percentiles],
400 + percentiles_ttft_ms=[
401 + (p, np.percentile(ttfts or 0, p) * 1000) for p in selected_percentiles
402 + ],
403 mean_tpot_ms=np.mean(tpots or 0) * 1000,
404 std_tpot_ms=np.std(tpots or 0) * 1000,
405 median_tpot_ms=np.median(tpots or 0) * 1000,
384 - percentiles_tpot_ms=[(p, np.percentile(tpots or 0, p) * 1000)
385 - for p in selected_percentiles],
406 + percentiles_tpot_ms=[
407 + (p, np.percentile(tpots or 0, p) * 1000) for p in selected_percentiles
408 + ],
409 mean_itl_ms=np.mean(itls or 0) * 1000,
410 std_itl_ms=np.std(itls or 0) * 1000,
411 median_itl_ms=np.median(itls or 0) * 1000,
389 - percentiles_itl_ms=[(p, np.percentile(itls or 0, p) * 1000)
390 - for p in selected_percentiles],
412 + percentiles_itl_ms=[
413 + (p, np.percentile(itls or 0, p) * 1000) for p in selected_percentiles
414 + ],
415 mean_e2el_ms=np.mean(e2els or 0) * 1000,
416 std_e2el_ms=np.std(e2els or 0) * 1000,
417 median_e2el_ms=np.median(e2els or 0) * 1000,
394 - percentiles_e2el_ms=[(p, np.percentile(e2els or 0, p) * 1000)
395 - for p in selected_percentiles],
418 + percentiles_e2el_ms=[
419 + (p, np.percentile(e2els or 0, p) * 1000) for p in selected_percentiles
420 + ],
421 )
422
423 return metrics, actual_output_lens
@@ -429,12 +454,13 @@ async def benchmark(
454
455 print("Starting initial single prompt test run...")
456 structured_output_req_idx = random.sample(
432 - range(len(input_requests)),
433 - int(len(input_requests) * structured_output_ratio))
457 + range(len(input_requests)), int(len(input_requests) * structured_output_ratio)
458 + )
459
460 test_request = input_requests[0]
436 - test_req_extra_body = (prepare_extra_body(test_request)
437 - if 0 in structured_output_req_idx else None)
461 + test_req_extra_body = (
462 + prepare_extra_body(test_request) if 0 in structured_output_req_idx else None
463 + )
464 test_input = RequestFuncInput(
465 model=model_id,
466 prompt=test_request.prompt,
@@ -448,7 +474,8 @@ async def benchmark(
474 if not test_output.success:
475 raise ValueError(
476 "Initial test run failed - Please make sure benchmark arguments "
451 - f"are correctly specified. Error: {test_output.error}")
477 + f"are correctly specified. Error: {test_output.error}"
478 + )
479 else:
480 print("Initial test run completed. Starting main benchmark run...")
481
@@ -467,10 +494,7 @@ async def benchmark(
494 if profile_output.success:
495 print("Profiler started")
496
470 - if burstiness == 1.0:
471 - distribution = "Poisson process"
472 - else:
473 - distribution = "Gamma distribution"
497 + distribution = "Poisson process" if burstiness == 1.0 else "Gamma distribution"
498
499 print(f"Traffic request rate: {request_rate}")
500 print(f"Burstiness factor: {burstiness} ({distribution})")
@@ -482,24 +506,21 @@ async def benchmark(
506 # and it will simplify the code in limited_request_func.
507 # semaphore = (asyncio.Semaphore(max_concurrency)
508 # if max_concurrency else contextlib.nullcontext())
485 - semaphore = (asyncio.Semaphore(max_concurrency)
486 - if max_concurrency else None)
509 + semaphore = asyncio.Semaphore(max_concurrency) if max_concurrency else None
510
511 async def limited_request_func(request_func_input, pbar):
512 if semaphore is None:
490 - return await request_func(request_func_input=request_func_input,
491 - pbar=pbar)
513 + return await request_func(request_func_input=request_func_input, pbar=pbar)
514 async with semaphore:
493 - return await request_func(request_func_input=request_func_input,
494 - pbar=pbar)
515 + return await request_func(request_func_input=request_func_input, pbar=pbar)
516
517 benchmark_start_time = time.perf_counter()
518 tasks: list[asyncio.Task] = []
519 expected: list[str] = []
499 - async for i, request in get_request(input_requests, request_rate,
500 - burstiness):
501 - extra_body = prepare_extra_body(
502 - request) if i in structured_output_req_idx else None
520 + async for i, request in get_request(input_requests, request_rate, burstiness):
521 + extra_body = (
522 + prepare_extra_body(request) if i in structured_output_req_idx else None
523 + )
524 request_func_input = RequestFuncInput(
525 model=model_id,
526 prompt=request.prompt,
@@ -512,8 +533,9 @@ async def benchmark(
533 expected.append(request.completion)
534 tasks.append(
535 asyncio.create_task(
515 - limited_request_func(request_func_input=request_func_input,
516 - pbar=pbar)))
536 + limited_request_func(request_func_input=request_func_input, pbar=pbar)
537 + )
538 + )
539 outputs: list[RequestFuncOutput] = await asyncio.gather(*tasks)
540
541 if profile:
@@ -545,54 +567,58 @@ async def benchmark(
567 goodput_config_dict=goodput_config_dict,
568 )
569
548 - print("{s:{c}^{n}}".format(s=' Serving Benchmark Result ', n=50, c='='))
570 + print("{s:{c}^{n}}".format(s=" Serving Benchmark Result ", n=50, c="="))
571 print("{:<40} {:<10}".format("Successful requests:", metrics.completed))
550 - print("{:<40} {:<10.2f}".format("Benchmark duration (s):",
551 - benchmark_duration))
572 + print("{:<40} {:<10.2f}".format("Benchmark duration (s):", benchmark_duration))
573 print("{:<40} {:<10}".format("Total input tokens:", metrics.total_input))
553 - print("{:<40} {:<10}".format("Total generated tokens:",
554 - metrics.total_output))
555 - print("{:<40} {:<10.2f}".format("Request throughput (req/s):",
556 - metrics.request_throughput))
574 + print("{:<40} {:<10}".format("Total generated tokens:", metrics.total_output))
575 + print(
576 + "{:<40} {:<10.2f}".format(
577 + "Request throughput (req/s):", metrics.request_throughput
578 + )
579 + )
580 if goodput_config_dict:
558 - print("{:<40} {:<10.2f}".format("Request goodput (req/s):",
559 - metrics.request_goodput))
560 - print("{:<40} {:<10.2f}".format("Output token throughput (tok/s):",
561 - metrics.output_throughput))
562 - print("{:<40} {:<10.2f}".format("Total Token throughput (tok/s):",
563 - metrics.total_token_throughput))
581 + print(
582 + "{:<40} {:<10.2f}".format(
583 + "Request goodput (req/s):", metrics.request_goodput
584 + )
585 + )
586 + print(
587 + "{:<40} {:<10.2f}".format(
588 + "Output token throughput (tok/s):", metrics.output_throughput
589 + )
590 + )
591 + print(
592 + "{:<40} {:<10.2f}".format(
593 + "Total Token throughput (tok/s):", metrics.total_token_throughput
594 + )
595 + )
596
597 result = {
566 - "duration":
567 - benchmark_duration,
568 - "completed":
569 - metrics.completed,
570 - "total_input_tokens":
571 - metrics.total_input,
572 - "total_output_tokens":
573 - metrics.total_output,
574 - "request_throughput":
575 - metrics.request_throughput,
576 - "output_throughput":
577 - metrics.output_throughput,
578 - "total_token_throughput":
579 - metrics.total_token_throughput,
580 - "ttft_description":
581 - pd.Series([output.ttft for output in outputs]).describe().to_dict(),
582 - "tpot_description":
583 - pd.Series([output.tpot for output in outputs]).describe().to_dict(),
598 + "duration": benchmark_duration,
599 + "completed": metrics.completed,
600 + "total_input_tokens": metrics.total_input,
601 + "total_output_tokens": metrics.total_output,
602 + "request_throughput": metrics.request_throughput,
603 + "output_throughput": metrics.output_throughput,
604 + "total_token_throughput": metrics.total_token_throughput,
605 + "ttft_description": pd.Series([output.ttft for output in outputs])
606 + .describe()
607 + .to_dict(),
608 + "tpot_description": pd.Series([output.tpot for output in outputs])
609 + .describe()
610 + .to_dict(),
611 "input_lens": [output.prompt_len for output in outputs],
585 - "output_lens":
586 - actual_output_lens,
612 + "output_lens": actual_output_lens,
613 "ttfts": [output.ttft for output in outputs],
614 "itls": [output.itl for output in outputs],
615 "errors": [output.error for output in outputs],
616 }
617
592 - ret = [{
593 - 'generated': output.generated_text,
594 - 'expected': gt
595 - } for output, gt in zip(outputs, expected)]
618 + ret = [
619 + {"generated": output.generated_text, "expected": gt}
620 + for output, gt in zip(outputs, expected)
621 + ]
622
623 def process_one_metric(
624 # E.g., "ttft"
@@ -606,29 +632,35 @@ async def benchmark(
632 # metric.
633 if metric_attribute_name not in selected_percentile_metrics:
634 return
609 - print("{s:{c}^{n}}".format(s=metric_header, n=50, c='-'))
610 - print("{:<40} {:<10.2f}".format(
611 - f"Mean {metric_name} (ms):",
612 - getattr(metrics, f"mean_{metric_attribute_name}_ms")))
613 - print("{:<40} {:<10.2f}".format(
614 - f"Median {metric_name} (ms):",
615 - getattr(metrics, f"median_{metric_attribute_name}_ms")))
635 + print("{s:{c}^{n}}".format(s=metric_header, n=50, c="-"))
636 + print(
637 + "{:<40} {:<10.2f}".format(
638 + f"Mean {metric_name} (ms):",
639 + getattr(metrics, f"mean_{metric_attribute_name}_ms"),
640 + )
641 + )
642 + print(
643 + "{:<40} {:<10.2f}".format(
644 + f"Median {metric_name} (ms):",
645 + getattr(metrics, f"median_{metric_attribute_name}_ms"),
646 + )
647 + )
648 result[f"mean_{metric_attribute_name}_ms"] = getattr(
617 - metrics, f"mean_{metric_attribute_name}_ms")
649 + metrics, f"mean_{metric_attribute_name}_ms"
650 + )
651 result[f"median_{metric_attribute_name}_ms"] = getattr(
619 - metrics, f"median_{metric_attribute_name}_ms")
652 + metrics, f"median_{metric_attribute_name}_ms"
653 + )
654 result[f"std_{metric_attribute_name}_ms"] = getattr(
621 - metrics, f"std_{metric_attribute_name}_ms")
622 - for p, value in getattr(metrics,
623 - f"percentiles_{metric_attribute_name}_ms"):
655 + metrics, f"std_{metric_attribute_name}_ms"
656 + )
657 + for p, value in getattr(metrics, f"percentiles_{metric_attribute_name}_ms"):
658 p_word = str(int(p)) if int(p) == p else str(p)
625 - print("{:<40} {:<10.2f}".format(f"P{p_word} {metric_name} (ms):",
626 - value))
659 + print("{:<40} {:<10.2f}".format(f"P{p_word} {metric_name} (ms):", value))
660 result[f"p{p_word}_{metric_attribute_name}_ms"] = value
661
662 process_one_metric("ttft", "TTFT", "Time to First Token")
630 - process_one_metric("tpot", "TPOT",
631 - "Time per Output Token (excl. 1st token)")
663 + process_one_metric("tpot", "TPOT", "Time per Output Token (excl. 1st token)")
664 process_one_metric("itl", "ITL", "Inter-token Latency")
665 process_one_metric("e2el", "E2EL", "End-to-end Latency")
666
@@ -638,13 +670,13 @@ async def benchmark(
670
671
672 def evaluate(ret, args):
641 -
673 def _eval_correctness_json(expected, actual):
674 # extract json string from string using regex
675 import re
645 - actual = actual.replace('\n', '').replace(' ', '').strip()
676 +
677 + actual = actual.replace("\n", "").replace(" ", "").strip()
678 try:
647 - actual = re.search(r'\{.*\}', actual).group()
679 + actual = re.search(r"\{.*\}", actual).group()
680 actual = json.loads(actual)
681 except Exception:
682 return False
@@ -656,28 +688,32 @@ def evaluate(ret, args):
688
689 def _eval_correctness_regex(expected, actual):
690 import re
691 +
692 return re.match(args.regex, actual) is not None
693
694 def _eval_correctness(expected, actual):
662 - if args.structure_type == 'guided_json':
695 + if args.structure_type == "guided_json":
696 return _eval_correctness_json(expected, actual)
664 - elif args.structure_type == 'guided_regex':
697 + elif args.structure_type == "guided_regex":
698 return _eval_correctness_regex(expected, actual)
666 - elif args.structure_type == 'guided_choice':
699 + elif args.structure_type == "guided_choice":
700 return _eval_correctness_choice(expected, actual)
701 else:
702 return None
703
704 scores = []
705 for res in ret:
673 - score = _eval_correctness(res['expected'], res['generated'])
674 - res['correctness'] = score
706 + score = _eval_correctness(res["expected"], res["generated"])
707 + res["correctness"] = score
708 scores.append(score)
709
710 not_none_scores = [score for score in scores if score is not None]
711
679 - return (sum(not_none_scores) / len(not_none_scores) *
680 - 100) if len(not_none_scores) > 0 else None
712 + return (
713 + (sum(not_none_scores) / len(not_none_scores) * 100)
714 + if len(not_none_scores) > 0
715 + else None
716 + )
717
718
719 def parse_goodput(slo_pairs):
@@ -689,9 +725,10 @@ def parse_goodput(slo_pairs):
725 except ValueError as err:
726 raise argparse.ArgumentTypeError(
727 "Invalid format found for service level objectives. "
692 - "Specify service level objectives for goodput as \"KEY:VALUE\" "
728 + 'Specify service level objectives for goodput as "KEY:VALUE" '
729 "pairs, where the key is a metric name, and the value is a "
694 - "number in milliseconds.") from err
730 + "number in milliseconds."
731 + ) from err
732 return goodput_config_dict
733
734
@@ -705,12 +742,14 @@ def check_goodput_args(args):
742 raise ValueError(
743 f"Invalid metric name found, {slo_name}: {slo_val}. "
744 "The service level objective name should be one of "
708 - f"{str(VALID_NAMES)}. ")
745 + f"{str(VALID_NAMES)}. "
746 + )
747 if slo_val < 0:
748 raise ValueError(
749 f"Invalid value found, {slo_name}: {slo_val}. "
750 "The service level objective value should be "
713 - "non-negative.")
751 + "non-negative."
752 + )
753 return goodput_config_dict
754
755
@@ -736,19 +775,19 @@ def main(args: argparse.Namespace):
775 tokenizer_mode=args.tokenizer_mode,
776 )
777
739 - if args.dataset == 'grammar':
740 - args.structure_type = 'guided_grammar'
741 - elif args.dataset == 'regex':
742 - args.structure_type = 'guided_regex'
743 - elif args.dataset == 'choice':
744 - args.structure_type = 'guided_choice'
778 + if args.dataset == "grammar":
779 + args.structure_type = "guided_grammar"
780 + elif args.dataset == "regex":
781 + args.structure_type = "guided_regex"
782 + elif args.dataset == "choice":
783 + args.structure_type = "guided_choice"
784 else:
746 - args.structure_type = 'guided_json'
785 + args.structure_type = "guided_json"
786
787 if args.no_structured_output:
788 args.structured_output_ratio = 0
789 if args.save_results:
751 - result_file_name = f'{args.structured_output_ratio}guided'
790 + result_file_name = f"{args.structured_output_ratio}guided"
791 result_file_name += f"_{backend}"
792 result_file_name += f"_{args.request_rate}qps"
793 result_file_name += f"_{args.model.split('/')[-1]}"
@@ -776,36 +815,29 @@ def main(args: argparse.Namespace):
815 disable_tqdm=args.disable_tqdm,
816 profile=args.profile,
817 selected_percentile_metrics=args.percentile_metrics.split(","),
779 - selected_percentiles=[
780 - float(p) for p in args.metric_percentiles.split(",")
781 - ],
818 + selected_percentiles=[float(p) for p in args.metric_percentiles.split(",")],
819 ignore_eos=args.ignore_eos,
820 max_concurrency=args.max_concurrency,
821 structured_output_ratio=args.structured_output_ratio,
822 goodput_config_dict=goodput_config_dict,
786 - ))
823 + )
824 + )
825
826 # Save config and results to json
827 score = evaluate(ret, args)
790 - print("correct_rate(%)", score, '\n')
828 + print("correct_rate(%)", score, "\n")
829 if args.save_results:
830 results = {
793 - "backend":
794 - backend,
795 - "model_id":
796 - model_id,
797 - "tokenizer_id":
798 - tokenizer_id,
799 - "num_prompts":
800 - args.num_prompts,
801 - "request_rate":
802 - args.request_rate if args.request_rate < float("inf") else "inf",
803 - "burstiness":
804 - args.burstiness,
805 - "max_concurrency":
806 - args.max_concurrency,
807 - "correct_rate(%)":
808 - score
831 + "backend": backend,
832 + "model_id": model_id,
833 + "tokenizer_id": tokenizer_id,
834 + "num_prompts": args.num_prompts,
835 + "request_rate": args.request_rate
836 + if args.request_rate < float("inf")
837 + else "inf",
838 + "burstiness": args.burstiness,
839 + "max_concurrency": args.max_concurrency,
840 + "correct_rate(%)": score,
841 }
842 results = {"outputs": ret, **results, **benchmark_result}
843
@@ -814,13 +846,14 @@ def main(args: argparse.Namespace):
846 result_file_name = args.result_filename
847 if args.result_dir:
848 result_file_name = os.path.join(args.result_dir, result_file_name)
817 - with open(result_file_name, "w", encoding='utf-8') as outfile:
849 + with open(result_file_name, "w", encoding="utf-8") as outfile:
850 json.dump(results, outfile, indent=4)
851
852
853 if __name__ == "__main__":
854 parser = FlexibleArgumentParser(
823 - description="Benchmark the online serving throughput.")
855 + description="Benchmark the online serving throughput."
856 + )
857 parser.add_argument(
858 "--backend",
859 type=str,
@@ -842,16 +875,14 @@ if __name__ == "__main__":
875 default="/v1/completions",
876 help="API endpoint.",
877 )
845 - parser.add_argument("--dataset",
846 - default='json',
847 - choices=[
848 - 'json', 'json-unique', 'grammar', 'regex',
849 - 'choice', 'xgrammar_bench'
850 - ])
851 - parser.add_argument("--json-schema-path",
852 - type=str,
853 - default=None,
854 - help="Path to json schema.")
878 + parser.add_argument(
879 + "--dataset",
880 + default="json",
881 + choices=["json", "json-unique", "grammar", "regex", "choice", "xgrammar_bench"],
882 + )
883 + parser.add_argument(
884 + "--json-schema-path", type=str, default=None, help="Path to json schema."
885 + )
886 parser.add_argument(
887 "--max-concurrency",
888 type=int,
@@ -863,7 +894,8 @@ if __name__ == "__main__":
894 "initiated, this argument will control how many are actually allowed "
895 "to execute at a time. This means that when used in combination, the "
896 "actual request rate may be lower than specified with --request-rate, "
866 - "if the server is not processing requests fast enough to keep up.")
897 + "if the server is not processing requests fast enough to keep up.",
898 + )
899 parser.add_argument(
900 "--model",
901 type=str,
@@ -873,15 +905,13 @@ if __name__ == "__main__":
905 parser.add_argument(
906 "--tokenizer",
907 type=str,
876 - help=
877 - "Name or path of the tokenizer, if not using the default tokenizer.", # noqa: E501
908 + help="Name or path of the tokenizer, if not using the default tokenizer.", # noqa: E501
909 )
910 parser.add_argument(
911 "--tokenizer-mode",
912 type=str,
913 default="auto",
883 - help=
884 - "Name or path of the tokenizer, if not using the default tokenizer.", # noqa: E501
914 + help="Name or path of the tokenizer, if not using the default tokenizer.", # noqa: E501
915 )
916 parser.add_argument(
917 "--num-prompts",
@@ -958,44 +988,51 @@ if __name__ == "__main__":
988 "--ignore-eos",
989 action="store_true",
990 help="Set ignore_eos flag when sending the benchmark request."
961 - "Warning: ignore_eos is not supported in deepspeed_mii and tgi.")
991 + "Warning: ignore_eos is not supported in deepspeed_mii and tgi.",
992 + )
993 parser.add_argument(
994 "--percentile-metrics",
995 type=str,
996 default="ttft,tpot,itl",
997 help="Comma-separated list of selected metrics to report percentils. "
998 "This argument specifies the metrics to report percentiles. "
968 - "Allowed metric names are \"ttft\", \"tpot\", \"itl\", \"e2el\". "
969 - "Default value is \"ttft,tpot,itl\".")
999 + 'Allowed metric names are "ttft", "tpot", "itl", "e2el". '
1000 + 'Default value is "ttft,tpot,itl".',
1001 + )
1002 parser.add_argument(
1003 "--metric-percentiles",
1004 type=str,
1005 default="99",
1006 help="Comma-separated list of percentiles for selected metrics. "
975 - "To report 25-th, 50-th, and 75-th percentiles, use \"25,50,75\". "
976 - "Default value is \"99\". "
977 - "Use \"--percentile-metrics\" to select metrics.",
1007 + 'To report 25-th, 50-th, and 75-th percentiles, use "25,50,75". '
1008 + 'Default value is "99". '
1009 + 'Use "--percentile-metrics" to select metrics.',
1010 )
1011 parser.add_argument(
1012 "--goodput",
1013 nargs="+",
1014 required=False,
983 - help="Specify service level objectives for goodput as \"KEY:VALUE\" "
1015 + help='Specify service level objectives for goodput as "KEY:VALUE" '
1016 "pairs, where the key is a metric name, and the value is in "
985 - "milliseconds. Multiple \"KEY:VALUE\" pairs can be provided, "
1017 + 'milliseconds. Multiple "KEY:VALUE" pairs can be provided, '
1018 "separated by spaces. Allowed request level metric names are "
987 - "\"ttft\", \"tpot\", \"e2el\". For more context on the definition of "
1019 + '"ttft", "tpot", "e2el". For more context on the definition of '
1020 "goodput, refer to DistServe paper: https://arxiv.org/pdf/2401.09670 "
989 - "and the blog: https://hao-ai-lab.github.io/blogs/distserve")
990 -
991 - parser.add_argument("--no-structured-output",
992 - action='store_true',
993 - default=False,
994 - help="Whether to disable JSON decoding or not.")
995 - parser.add_argument("--structured-output-ratio",
996 - type=float,
997 - default=1.0,
998 - help="Ratio of Structured Outputs requests")
1021 + "and the blog: https://hao-ai-lab.github.io/blogs/distserve",
1022 + )
1023 +
1024 + parser.add_argument(
1025 + "--no-structured-output",
1026 + action="store_true",
1027 + default=False,
1028 + help="Whether to disable JSON decoding or not.",
1029 + )
1030 + parser.add_argument(
1031 + "--structured-output-ratio",
1032 + type=float,
1033 + default=1.0,
1034 + help="Ratio of Structured Outputs requests",
1035 + )
1036
1037 args = parser.parse_args()
1038 main(args)
benchmarks/benchmark_throughput.py
+257 -180
@@ -1,5 +1,6 @@
1 # SPDX-License-Identifier: Apache-2.0
2 """Benchmark offline inference throughput."""
3 +
4 import argparse
5 import dataclasses
6 import json
@@ -11,18 +12,25 @@ from typing import Any, Optional, Union
12
13 import torch
14 import uvloop
14 -from benchmark_dataset import (AIMODataset, BurstGPTDataset,
15 - ConversationDataset, InstructCoderDataset,
16 - RandomDataset, SampleRequest, ShareGPTDataset,
17 - SonnetDataset, VisionArenaDataset)
18 -from benchmark_utils import convert_to_pytorch_benchmark_format, write_to_json
15 from tqdm import tqdm
20 -from transformers import (AutoModelForCausalLM, AutoTokenizer,
21 - PreTrainedTokenizerBase)
22 -
16 +from transformers import AutoModelForCausalLM, AutoTokenizer, PreTrainedTokenizerBase
17 +
18 +from benchmark_dataset import (
19 + AIMODataset,
20 + BurstGPTDataset,
21 + ConversationDataset,
22 + InstructCoderDataset,
23 + RandomDataset,
24 + SampleRequest,
25 + ShareGPTDataset,
26 + SonnetDataset,
27 + VisionArenaDataset,
28 +)
29 +from benchmark_utils import convert_to_pytorch_benchmark_format, write_to_json
30 from vllm.engine.arg_utils import AsyncEngineArgs, EngineArgs
31 from vllm.entrypoints.openai.api_server import (
25 - build_async_engine_client_from_engine_args)
32 + build_async_engine_client_from_engine_args,
33 +)
34 from vllm.inputs import TextPrompt, TokensPrompt
35 from vllm.lora.request import LoRARequest
36 from vllm.outputs import RequestOutput
@@ -37,23 +45,30 @@ def run_vllm(
45 disable_detokenize: bool = False,
46 ) -> tuple[float, Optional[list[RequestOutput]]]:
47 from vllm import LLM, SamplingParams
48 +
49 llm = LLM(**dataclasses.asdict(engine_args))
50 assert all(
42 - llm.llm_engine.model_config.max_model_len >= (
43 - request.prompt_len + request.expected_output_len)
44 - for request in requests), (
45 - "Please ensure that max_model_len is greater than the sum of"
46 - " prompt_len and expected_output_len for all requests.")
51 + llm.llm_engine.model_config.max_model_len
52 + >= (request.prompt_len + request.expected_output_len)
53 + for request in requests
54 + ), (
55 + "Please ensure that max_model_len is greater than the sum of"
56 + " prompt_len and expected_output_len for all requests."
57 + )
58 # Add the requests to the engine.
59 prompts: list[Union[TextPrompt, TokensPrompt]] = []
60 sampling_params: list[SamplingParams] = []
61 for request in requests:
62 prompts.append(
52 - TokensPrompt(prompt_token_ids=request.prompt["prompt_token_ids"],
53 - multi_modal_data=request.multi_modal_data)
54 - if "prompt_token_ids" in request.prompt else \
55 - TextPrompt(prompt=request.prompt,
56 - multi_modal_data=request.multi_modal_data))
63 + TokensPrompt(
64 + prompt_token_ids=request.prompt["prompt_token_ids"],
65 + multi_modal_data=request.multi_modal_data,
66 + )
67 + if "prompt_token_ids" in request.prompt
68 + else TextPrompt(
69 + prompt=request.prompt, multi_modal_data=request.multi_modal_data
70 + )
71 + )
72 sampling_params.append(
73 SamplingParams(
74 n=n,
@@ -62,7 +77,8 @@ def run_vllm(
77 ignore_eos=True,
78 max_tokens=request.expected_output_len,
79 detokenize=not disable_detokenize,
65 - ))
80 + )
81 + )
82 lora_requests: Optional[list[LoRARequest]] = None
83 if engine_args.enable_lora:
84 lora_requests = [request.lora_request for request in requests]
@@ -72,10 +88,9 @@ def run_vllm(
88 outputs = None
89 if not use_beam_search:
90 start = time.perf_counter()
75 - outputs = llm.generate(prompts,
76 - sampling_params,
77 - lora_request=lora_requests,
78 - use_tqdm=True)
91 + outputs = llm.generate(
92 + prompts, sampling_params, lora_request=lora_requests, use_tqdm=True
93 + )
94 end = time.perf_counter()
95 else:
96 assert lora_requests is None, "BeamSearch API does not support LoRA"
@@ -91,30 +106,35 @@ def run_vllm(
106 beam_width=n,
107 max_tokens=output_len,
108 ignore_eos=True,
94 - ))
109 + ),
110 + )
111 end = time.perf_counter()
112 return end - start, outputs
113
114
115 def run_vllm_chat(
100 - requests: list[SampleRequest],
101 - n: int,
102 - engine_args: EngineArgs,
103 - disable_detokenize: bool = False) -> tuple[float, list[RequestOutput]]:
116 + requests: list[SampleRequest],
117 + n: int,
118 + engine_args: EngineArgs,
119 + disable_detokenize: bool = False,
120 +) -> tuple[float, list[RequestOutput]]:
121 """
122 Run vLLM chat benchmark. This function is recommended ONLY for benchmarking
123 multimodal models as it properly handles multimodal inputs and chat
124 formatting. For non-multimodal models, use run_vllm() instead.
125 """
126 from vllm import LLM, SamplingParams
127 +
128 llm = LLM(**dataclasses.asdict(engine_args))
129
130 assert all(
113 - llm.llm_engine.model_config.max_model_len >= (
114 - request.prompt_len + request.expected_output_len)
115 - for request in requests), (
116 - "Please ensure that max_model_len is greater than the sum of "
117 - "prompt_len and expected_output_len for all requests.")
131 + llm.llm_engine.model_config.max_model_len
132 + >= (request.prompt_len + request.expected_output_len)
133 + for request in requests
134 + ), (
135 + "Please ensure that max_model_len is greater than the sum of "
136 + "prompt_len and expected_output_len for all requests."
137 + )
138
139 prompts = []
140 sampling_params: list[SamplingParams] = []
@@ -128,7 +148,8 @@ def run_vllm_chat(
148 ignore_eos=True,
149 max_tokens=request.expected_output_len,
150 detokenize=not disable_detokenize,
131 - ))
151 + )
152 + )
153 start = time.perf_counter()
154 outputs = llm.chat(prompts, sampling_params, use_tqdm=True)
155 end = time.perf_counter()
@@ -145,14 +166,17 @@ async def run_vllm_async(
166 from vllm import SamplingParams
167
168 async with build_async_engine_client_from_engine_args(
148 - engine_args, disable_frontend_multiprocessing) as llm:
169 + engine_args, disable_frontend_multiprocessing
170 + ) as llm:
171 model_config = await llm.get_model_config()
172 assert all(
151 - model_config.max_model_len >= (request.prompt_len +
152 - request.expected_output_len)
153 - for request in requests), (
154 - "Please ensure that max_model_len is greater than the sum of"
155 - " prompt_len and expected_output_len for all requests.")
173 + model_config.max_model_len
174 + >= (request.prompt_len + request.expected_output_len)
175 + for request in requests
176 + ), (
177 + "Please ensure that max_model_len is greater than the sum of"
178 + " prompt_len and expected_output_len for all requests."
179 + )
180
181 # Add the requests to the engine.
182 prompts: list[Union[TextPrompt, TokensPrompt]] = []
@@ -160,11 +184,15 @@ async def run_vllm_async(
184 lora_requests: list[Optional[LoRARequest]] = []
185 for request in requests:
186 prompts.append(
163 - TokensPrompt(prompt_token_ids=request.prompt["prompt_token_ids"],
164 - multi_modal_data=request.multi_modal_data)
165 - if "prompt_token_ids" in request.prompt else \
166 - TextPrompt(prompt=request.prompt,
167 - multi_modal_data=request.multi_modal_data))
187 + TokensPrompt(
188 + prompt_token_ids=request.prompt["prompt_token_ids"],
189 + multi_modal_data=request.multi_modal_data,
190 + )
191 + if "prompt_token_ids" in request.prompt
192 + else TextPrompt(
193 + prompt=request.prompt, multi_modal_data=request.multi_modal_data
194 + )
195 + )
196 sampling_params.append(
197 SamplingParams(
198 n=n,
@@ -173,17 +201,16 @@ async def run_vllm_async(
201 ignore_eos=True,
202 max_tokens=request.expected_output_len,
203 detokenize=not disable_detokenize,
176 - ))
204 + )
205 + )
206 lora_requests.append(request.lora_request)
207
208 generators = []
209 start = time.perf_counter()
181 - for i, (prompt, sp,
182 - lr) in enumerate(zip(prompts, sampling_params, lora_requests)):
183 - generator = llm.generate(prompt,
184 - sp,
185 - lora_request=lr,
186 - request_id=f"test{i}")
210 + for i, (prompt, sp, lr) in enumerate(
211 + zip(prompts, sampling_params, lora_requests)
212 + ):
213 + generator = llm.generate(prompt, sp, lora_request=lr, request_id=f"test{i}")
214 generators.append(generator)
215 all_gens = merge_async_iterators(*generators)
216 async for i, res in all_gens:
@@ -202,7 +229,8 @@ def run_hf(
229 disable_detokenize: bool = False,
230 ) -> float:
231 llm = AutoModelForCausalLM.from_pretrained(
205 - model, torch_dtype=torch.float16, trust_remote_code=trust_remote_code)
232 + model, torch_dtype=torch.float16, trust_remote_code=trust_remote_code
233 + )
234 if llm.config.model_type == "llama":
235 # To enable padding in the HF backend.
236 tokenizer.pad_token = tokenizer.eos_token
@@ -225,14 +253,15 @@ def run_hf(
253 # Check if we can add more requests to the batch.
254 next_prompt_len = requests[i + 1].prompt_len
255 next_output_len = requests[i + 1].expected_output_len
228 - if (max(max_prompt_len, next_prompt_len) +
229 - max(max_output_len, next_output_len)) <= 2048:
256 + if (
257 + max(max_prompt_len, next_prompt_len)
258 + + max(max_output_len, next_output_len)
259 + ) <= 2048:
260 # We can add more requests to the batch.
261 continue
262
263 # Generate the sequences.
234 - input_ids = tokenizer(batch, return_tensors="pt",
235 - padding=True).input_ids
264 + input_ids = tokenizer(batch, return_tensors="pt", padding=True).input_ids
265 llm_outputs = llm.generate(
266 input_ids=input_ids.cuda(),
267 do_sample=True,
@@ -262,6 +291,7 @@ def run_mii(
291 output_len: int,
292 ) -> float:
293 from mii import client, serve
294 +
295 llm = serve(model, tensor_parallel=tensor_parallel_size)
296 prompts = [request.prompt for request in requests]
297
@@ -273,8 +303,9 @@ def run_mii(
303 return end - start
304
305
276 -def save_to_pytorch_benchmark_format(args: argparse.Namespace,
277 - results: dict[str, Any]) -> None:
306 +def save_to_pytorch_benchmark_format(
307 + args: argparse.Namespace, results: dict[str, Any]
308 +) -> None:
309 pt_records = convert_to_pytorch_benchmark_format(
310 args=args,
311 metrics={
@@ -282,9 +313,9 @@ def save_to_pytorch_benchmark_format(args: argparse.Namespace,
313 "tokens_per_second": [results["tokens_per_second"]],
314 },
315 extra_info={
285 - k: results[k]
286 - for k in ["elapsed_time", "num_requests", "total_num_tokens"]
287 - })
316 + k: results[k] for k in ["elapsed_time", "num_requests", "total_num_tokens"]
317 + },
318 + )
319 if pt_records:
320 # Don't use json suffix here as we don't want CI to pick it up
321 pt_file = f"{os.path.splitext(args.output_json)[0]}.pytorch.json"
@@ -316,7 +347,8 @@ def get_requests(args, tokenizer):
347 sample_kwargs["enable_multimodal_chat"] = True
348 elif args.dataset_name == "sonnet":
349 assert tokenizer.chat_template or tokenizer.default_chat_template, (
319 - "Tokenizer/model must have chat template for sonnet dataset.")
350 + "Tokenizer/model must have chat template for sonnet dataset."
351 + )
352 dataset_cls = SonnetDataset
353 sample_kwargs["prefix_len"] = args.prefix_len
354 sample_kwargs["return_prompt_formatted"] = True
@@ -325,21 +357,21 @@ def get_requests(args, tokenizer):
357 elif args.dataset_name == "hf":
358 if args.dataset_path in VisionArenaDataset.SUPPORTED_DATASET_PATHS:
359 dataset_cls = VisionArenaDataset
328 - common_kwargs['dataset_subset'] = None
329 - common_kwargs['dataset_split'] = "train"
360 + common_kwargs["dataset_subset"] = None
361 + common_kwargs["dataset_split"] = "train"
362 sample_kwargs["enable_multimodal_chat"] = True
363 elif args.dataset_path in InstructCoderDataset.SUPPORTED_DATASET_PATHS:
364 dataset_cls = InstructCoderDataset
333 - common_kwargs['dataset_split'] = "train"
365 + common_kwargs["dataset_split"] = "train"
366 elif args.dataset_path in ConversationDataset.SUPPORTED_DATASET_PATHS:
367 dataset_cls = ConversationDataset
336 - common_kwargs['dataset_subset'] = args.hf_subset
337 - common_kwargs['dataset_split'] = args.hf_split
368 + common_kwargs["dataset_subset"] = args.hf_subset
369 + common_kwargs["dataset_split"] = args.hf_split
370 sample_kwargs["enable_multimodal_chat"] = True
371 elif args.dataset_path in AIMODataset.SUPPORTED_DATASET_PATHS:
372 dataset_cls = AIMODataset
341 - common_kwargs['dataset_subset'] = None
342 - common_kwargs['dataset_split'] = "train"
373 + common_kwargs["dataset_subset"] = None
374 + common_kwargs["dataset_split"] = "train"
375 else:
376 raise ValueError(f"Unknown dataset name: {args.dataset_name}")
377 # Remove None values
@@ -354,10 +386,10 @@ def main(args: argparse.Namespace):
386 random.seed(args.seed)
387 # Sample the requests.
388 tokenizer = AutoTokenizer.from_pretrained(
357 - args.tokenizer, trust_remote_code=args.trust_remote_code)
389 + args.tokenizer, trust_remote_code=args.trust_remote_code
390 + )
391 requests = get_requests(args, tokenizer)
359 - is_multi_modal = any(request.multi_modal_data is not None
360 - for request in requests)
392 + is_multi_modal = any(request.multi_modal_data is not None for request in requests)
393 request_outputs: Optional[list[RequestOutput]] = None
394 if args.backend == "vllm":
395 if args.async_engine:
@@ -368,23 +400,34 @@ def main(args: argparse.Namespace):
400 AsyncEngineArgs.from_cli_args(args),
401 args.disable_frontend_multiprocessing,
402 args.disable_detokenize,
371 - ))
403 + )
404 + )
405 else:
406 elapsed_time, request_outputs = run_vllm(
374 - requests, args.n, EngineArgs.from_cli_args(args),
375 - args.disable_detokenize)
407 + requests,
408 + args.n,
409 + EngineArgs.from_cli_args(args),
410 + args.disable_detokenize,
411 + )
412 elif args.backend == "hf":
413 assert args.tensor_parallel_size == 1
378 - elapsed_time = run_hf(requests, args.model, tokenizer, args.n,
379 - args.hf_max_batch_size, args.trust_remote_code,
380 - args.disable_detokenize)
414 + elapsed_time = run_hf(
415 + requests,
416 + args.model,
417 + tokenizer,
418 + args.n,
419 + args.hf_max_batch_size,
420 + args.trust_remote_code,
421 + args.disable_detokenize,
422 + )
423 elif args.backend == "mii":
382 - elapsed_time = run_mii(requests, args.model, args.tensor_parallel_size,
383 - args.output_len)
424 + elapsed_time = run_mii(
425 + requests, args.model, args.tensor_parallel_size, args.output_len
426 + )
427 elif args.backend == "vllm-chat":
428 elapsed_time, request_outputs = run_vllm_chat(
386 - requests, args.n, EngineArgs.from_cli_args(args),
387 - args.disable_detokenize)
429 + requests, args.n, EngineArgs.from_cli_args(args), args.disable_detokenize
430 + )
431 else:
432 raise ValueError(f"Unknown backend: {args.backend}")
433
@@ -396,28 +439,31 @@ def main(args: argparse.Namespace):
439 for ro in request_outputs:
440 if not isinstance(ro, RequestOutput):
441 continue
399 - total_prompt_tokens += len(
400 - ro.prompt_token_ids) if ro.prompt_token_ids else 0
401 - total_output_tokens += sum(
402 - len(o.token_ids) for o in ro.outputs if o)
442 + total_prompt_tokens += (
443 + len(ro.prompt_token_ids) if ro.prompt_token_ids else 0
444 + )
445 + total_output_tokens += sum(len(o.token_ids) for o in ro.outputs if o)
446 total_num_tokens = total_prompt_tokens + total_output_tokens
447 else:
405 - total_num_tokens = sum(r.prompt_len + r.expected_output_len
406 - for r in requests)
448 + total_num_tokens = sum(r.prompt_len + r.expected_output_len for r in requests)
449 total_output_tokens = sum(r.expected_output_len for r in requests)
450 total_prompt_tokens = total_num_tokens - total_output_tokens
451
452 if is_multi_modal and args.backend != "vllm-chat":
411 - print("\033[91mWARNING\033[0m: Multi-modal request with "
412 - f"{args.backend} backend detected. The "
413 - "following metrics are not accurate because image tokens are not"
414 - " counted. See vllm-project/vllm/issues/9778 for details.")
453 + print(
454 + "\033[91mWARNING\033[0m: Multi-modal request with "
455 + f"{args.backend} backend detected. The "
456 + "following metrics are not accurate because image tokens are not"
457 + " counted. See vllm-project/vllm/issues/9778 for details."
458 + )
459 # TODO(vllm-project/vllm/issues/9778): Count multi-modal token length.
460 # vllm-chat backend counts the image tokens now
461
418 - print(f"Throughput: {len(requests) / elapsed_time:.2f} requests/s, "
419 - f"{total_num_tokens / elapsed_time:.2f} total tokens/s, "
420 - f"{total_output_tokens / elapsed_time:.2f} output tokens/s")
462 + print(
463 + f"Throughput: {len(requests) / elapsed_time:.2f} requests/s, "
464 + f"{total_num_tokens / elapsed_time:.2f} total tokens/s, "
465 + f"{total_output_tokens / elapsed_time:.2f} output tokens/s"
466 + )
467 print(f"Total num prompt tokens: {total_prompt_tokens}")
468 print(f"Total num output tokens: {total_output_tokens}")
469
@@ -445,7 +491,8 @@ def validate_args(args):
491 warnings.warn(
492 "The '--dataset' argument will be deprecated in the next release. "
493 "Please use '--dataset-name' and '--dataset-path' instead.",
448 - stacklevel=2)
494 + stacklevel=2,
495 + )
496 args.dataset_path = args.dataset
497
498 if not getattr(args, "tokenizer", None):
@@ -458,9 +505,8 @@ def validate_args(args):
505
506 # === Dataset Configuration ===
507 if not args.dataset and not args.dataset_path:
461 - print(
462 - "When dataset path is not set, it will default to random dataset")
463 - args.dataset_name = 'random'
508 + print("When dataset path is not set, it will default to random dataset")
509 + args.dataset_name = "random"
510 if args.input_len is None:
511 raise ValueError("input_len must be provided for a random dataset")
512
@@ -468,41 +514,55 @@ def validate_args(args):
514 # --hf-subset and --hf-split: only used
515 # when dataset_name is 'hf'
516 if args.dataset_name != "hf" and (
471 - getattr(args, "hf_subset", None) is not None
472 - or getattr(args, "hf_split", None) is not None):
473 - warnings.warn("--hf-subset and --hf-split will be ignored \
517 + getattr(args, "hf_subset", None) is not None
518 + or getattr(args, "hf_split", None) is not None
519 + ):
520 + warnings.warn(
521 + "--hf-subset and --hf-split will be ignored \
522 since --dataset-name is not 'hf'.",
475 - stacklevel=2)
523 + stacklevel=2,
524 + )
525 elif args.dataset_name == "hf":
526 if args.dataset_path in (
478 - VisionArenaDataset.SUPPORTED_DATASET_PATHS.keys()
479 - | ConversationDataset.SUPPORTED_DATASET_PATHS):
480 - assert args.backend == "vllm-chat", f"{args.dataset_path} needs to use vllm-chat as the backend." #noqa: E501
481 - elif args.dataset_path in (InstructCoderDataset.SUPPORTED_DATASET_PATHS
482 - | AIMODataset.SUPPORTED_DATASET_PATHS):
483 - assert args.backend == "vllm", f"{args.dataset_path} needs to use vllm as the backend." #noqa: E501
527 + VisionArenaDataset.SUPPORTED_DATASET_PATHS.keys()
528 + | ConversationDataset.SUPPORTED_DATASET_PATHS
529 + ):
530 + assert args.backend == "vllm-chat", (
531 + f"{args.dataset_path} needs to use vllm-chat as the backend."
532 + ) # noqa: E501
533 + elif args.dataset_path in (
534 + InstructCoderDataset.SUPPORTED_DATASET_PATHS
535 + | AIMODataset.SUPPORTED_DATASET_PATHS
536 + ):
537 + assert args.backend == "vllm", (
538 + f"{args.dataset_path} needs to use vllm as the backend."
539 + ) # noqa: E501
540 else:
485 - raise ValueError(
486 - f"{args.dataset_path} is not supported by hf dataset.")
541 + raise ValueError(f"{args.dataset_path} is not supported by hf dataset.")
542
543 # --random-range-ratio: only used when dataset_name is 'random'
489 - if args.dataset_name != 'random' and args.random_range_ratio is not None:
490 - warnings.warn("--random-range-ratio will be ignored since \
544 + if args.dataset_name != "random" and args.random_range_ratio is not None:
545 + warnings.warn(
546 + "--random-range-ratio will be ignored since \
547 --dataset-name is not 'random'.",
492 - stacklevel=2)
548 + stacklevel=2,
549 + )
550
551 # --prefix-len: only used when dataset_name is 'random', 'sonnet', or not
552 # set.
496 - if args.dataset_name not in {"random", "sonnet", None
497 - } and args.prefix_len is not None:
498 - warnings.warn("--prefix-len will be ignored since --dataset-name\
553 + if (
554 + args.dataset_name not in {"random", "sonnet", None}
555 + and args.prefix_len is not None
556 + ):
557 + warnings.warn(
558 + "--prefix-len will be ignored since --dataset-name\
559 is not 'random', 'sonnet', or not set.",
500 - stacklevel=2)
560 + stacklevel=2,
561 + )
562
563 # === LoRA Settings ===
564 if getattr(args, "enable_lora", False) and args.backend != "vllm":
504 - raise ValueError(
505 - "LoRA benchmarking is only supported for vLLM backend")
565 + raise ValueError("LoRA benchmarking is only supported for vLLM backend")
566 if getattr(args, "enable_lora", False) and args.lora_path is None:
567 raise ValueError("LoRA path must be provided when enable_lora is True")
568
@@ -512,8 +572,10 @@ def validate_args(args):
572 if args.backend != "hf" and args.hf_max_batch_size is not None:
573 raise ValueError("HF max batch size is only for HF backend.")
574
515 - if args.backend in {"hf", "mii"} and getattr(args, "quantization",
516 - None) is not None:
575 + if (
576 + args.backend in {"hf", "mii"}
577 + and getattr(args, "quantization", None) is not None
578 + ):
579 raise ValueError("Quantization is only for vLLM backend.")
580
581 if args.backend == "mii" and args.dtype != "auto":
@@ -521,29 +583,32 @@ def validate_args(args):
583 if args.backend == "mii" and args.n != 1:
584 raise ValueError("n must be 1 for MII backend.")
585 if args.backend == "mii" and args.tokenizer != args.model:
524 - raise ValueError(
525 - "Tokenizer must be the same as the model for MII backend.")
586 + raise ValueError("Tokenizer must be the same as the model for MII backend.")
587
588 # --data-parallel is not supported currently.
589 # https://github.com/vllm-project/vllm/issues/16222
590 if args.data_parallel_size > 1:
591 raise ValueError(
592 "Data parallel is not supported in offline benchmark, \
532 - please use benchmark serving instead")
593 + please use benchmark serving instead"
594 + )
595
596
597 if __name__ == "__main__":
598 parser = FlexibleArgumentParser(description="Benchmark the throughput.")
537 - parser.add_argument("--backend",
538 - type=str,
539 - choices=["vllm", "hf", "mii", "vllm-chat"],
540 - default="vllm")
599 + parser.add_argument(
600 + "--backend",
601 + type=str,
602 + choices=["vllm", "hf", "mii", "vllm-chat"],
603 + default="vllm",
604 + )
605 parser.add_argument(
606 "--dataset-name",
607 type=str,
608 choices=["sharegpt", "random", "sonnet", "burstgpt", "hf"],
609 help="Name of the dataset to benchmark on.",
546 - default="sharegpt")
610 + default="sharegpt",
611 + )
612 parser.add_argument(
613 "--dataset",
614 type=str,
@@ -551,57 +616,70 @@ if __name__ == "__main__":
616 help="Path to the ShareGPT dataset, will be deprecated in\
617 the next release. The dataset is expected to "
618 "be a json in form of list[dict[..., conversations: "
554 - "list[dict[..., value: <prompt_or_response>]]]]")
555 - parser.add_argument("--dataset-path",
556 - type=str,
557 - default=None,
558 - help="Path to the dataset")
559 - parser.add_argument("--input-len",
560 - type=int,
561 - default=None,
562 - help="Input prompt length for each request")
563 - parser.add_argument("--output-len",
564 - type=int,
565 - default=None,
566 - help="Output length for each request. Overrides the "
567 - "output length from the dataset.")
568 - parser.add_argument("--n",
569 - type=int,
570 - default=1,
571 - help="Number of generated sequences per prompt.")
572 - parser.add_argument("--num-prompts",
573 - type=int,
574 - default=1000,
575 - help="Number of prompts to process.")
576 - parser.add_argument("--hf-max-batch-size",
577 - type=int,
578 - default=None,
579 - help="Maximum batch size for HF backend.")
619 + "list[dict[..., value: <prompt_or_response>]]]]",
620 + )
621 + parser.add_argument(
622 + "--dataset-path", type=str, default=None, help="Path to the dataset"
623 + )
624 + parser.add_argument(
625 + "--input-len",
626 + type=int,
627 + default=None,
628 + help="Input prompt length for each request",
629 + )
630 + parser.add_argument(
631 + "--output-len",
632 + type=int,
633 + default=None,
634 + help="Output length for each request. Overrides the "
635 + "output length from the dataset.",
636 + )
637 + parser.add_argument(
638 + "--n", type=int, default=1, help="Number of generated sequences per prompt."
639 + )
640 parser.add_argument(
581 - '--output-json',
641 + "--num-prompts", type=int, default=1000, help="Number of prompts to process."
642 + )
643 + parser.add_argument(
644 + "--hf-max-batch-size",
645 + type=int,
646 + default=None,
647 + help="Maximum batch size for HF backend.",
648 + )
649 + parser.add_argument(
650 + "--output-json",
651 type=str,
652 default=None,
584 - help='Path to save the throughput results in JSON format.')
585 - parser.add_argument("--async-engine",
586 - action='store_true',
587 - default=False,
588 - help="Use vLLM async engine rather than LLM class.")
589 - parser.add_argument("--disable-frontend-multiprocessing",
590 - action='store_true',
591 - default=False,
592 - help="Disable decoupled async engine frontend.")
653 + help="Path to save the throughput results in JSON format.",
654 + )
655 + parser.add_argument(
656 + "--async-engine",
657 + action="store_true",
658 + default=False,
659 + help="Use vLLM async engine rather than LLM class.",
660 + )
661 + parser.add_argument(
662 + "--disable-frontend-multiprocessing",
663 + action="store_true",
664 + default=False,
665 + help="Disable decoupled async engine frontend.",
666 + )
667 parser.add_argument(
668 "--disable-detokenize",
669 action="store_true",
596 - help=("Do not detokenize the response (i.e. do not include "
597 - "detokenization time in the measurement)"))
670 + help=(
671 + "Do not detokenize the response (i.e. do not include "
672 + "detokenization time in the measurement)"
673 + ),
674 + )
675 # LoRA
676 parser.add_argument(
677 "--lora-path",
678 type=str,
679 default=None,
680 help="Path to the LoRA adapters to use. This can be an absolute path, "
604 - "a relative path, or a Hugging Face model identifier.")
681 + "a relative path, or a Hugging Face model identifier.",
682 + )
683 parser.add_argument(
684 "--prefix-len",
685 type=int,
@@ -615,7 +693,8 @@ if __name__ == "__main__":
693 f"prefix_len (default: {SonnetDataset.DEFAULT_PREFIX_LEN}) "
694 "controls how much of the input is fixed lines versus "
695 "random lines, but the total input length remains approximately "
618 - "input_len tokens.")
696 + "input_len tokens.",
697 + )
698 # random dataset
699 parser.add_argument(
700 "--random-range-ratio",
@@ -629,14 +708,12 @@ if __name__ == "__main__":
708 )
709
710 # hf dtaset
632 - parser.add_argument("--hf-subset",
633 - type=str,
634 - default=None,
635 - help="Subset of the HF dataset.")
636 - parser.add_argument("--hf-split",
637 - type=str,
638 - default=None,
639 - help="Split of the HF dataset.")
711 + parser.add_argument(
712 + "--hf-subset", type=str, default=None, help="Subset of the HF dataset."
713 + )
714 + parser.add_argument(
715 + "--hf-split", type=str, default=None, help="Split of the HF dataset."
716 + )
717
718 parser = AsyncEngineArgs.add_cli_args(parser)
719 args = parser.parse_args()
benchmarks/benchmark_utils.py
+7 -8
@@ -7,9 +7,9 @@ import os
7 from typing import Any
8
9
10 -def convert_to_pytorch_benchmark_format(args: argparse.Namespace,
11 - metrics: dict[str, list],
12 - extra_info: dict[str, Any]) -> list:
10 +def convert_to_pytorch_benchmark_format(
11 + args: argparse.Namespace, metrics: dict[str, list], extra_info: dict[str, Any]
12 +) -> list:
13 """
14 Save the benchmark results in the format used by PyTorch OSS benchmark with
15 on metric per record
@@ -37,12 +37,12 @@ def convert_to_pytorch_benchmark_format(args: argparse.Namespace,
37 },
38 }
39
40 - tp = record["benchmark"]["extra_info"]["args"].get(
41 - "tensor_parallel_size")
40 + tp = record["benchmark"]["extra_info"]["args"].get("tensor_parallel_size")
41 # Save tensor_parallel_size parameter if it's part of the metadata
42 if not tp and "tensor_parallel_size" in extra_info:
44 - record["benchmark"]["extra_info"]["args"][
45 - "tensor_parallel_size"] = extra_info["tensor_parallel_size"]
43 + record["benchmark"]["extra_info"]["args"]["tensor_parallel_size"] = (
44 + extra_info["tensor_parallel_size"]
45 + )
46
47 records.append(record)
48
@@ -50,7 +50,6 @@ def convert_to_pytorch_benchmark_format(args: argparse.Namespace,
50
51
52 class InfEncoder(json.JSONEncoder):
53 -
53 def clear_inf(self, o: Any):
54 if isinstance(o, dict):
55 return {k: self.clear_inf(v) for k, v in o.items()}
benchmarks/cutlass_benchmarks/sparse_benchmarks.py
+250 -122
@@ -23,8 +23,9 @@ DEFAULT_TP_SIZES = [1]
23
24
25 # bench
26 -def bench_fn(label: str, sub_label: str, description: str, fn: Callable, *args,
27 - **kwargs) -> TMeasurement:
26 +def bench_fn(
27 + label: str, sub_label: str, description: str, fn: Callable, *args, **kwargs
28 +) -> TMeasurement:
29 min_run_time = 1
30
31 globals = {
@@ -41,16 +42,18 @@ def bench_fn(label: str, sub_label: str, description: str, fn: Callable, *args,
42 ).blocked_autorange(min_run_time=min_run_time)
43
44
44 -def bench_int8(dtype: torch.dtype, m: int, k: int, n: int, label: str,
45 - sub_label: str) -> Iterable[TMeasurement]:
45 +def bench_int8(
46 + dtype: torch.dtype, m: int, k: int, n: int, label: str, sub_label: str
47 +) -> Iterable[TMeasurement]:
48 assert dtype == torch.int8
49 b_compressed, e, a, b = make_rand_sparse_tensors(torch.int8, m, n, k)
50 scale_a = torch.tensor(1.0, device="cuda", dtype=torch.float32)
51 scale_b = torch.tensor(1.0, device="cuda", dtype=torch.float32)
50 - bias = torch.zeros((n, ), device="cuda", dtype=torch.bfloat16)
52 + bias = torch.zeros((n,), device="cuda", dtype=torch.bfloat16)
53
52 - out = ops.cutlass_scaled_sparse_mm(a, b_compressed, e, scale_a, scale_b,
53 - torch.bfloat16)
54 + out = ops.cutlass_scaled_sparse_mm(
55 + a, b_compressed, e, scale_a, scale_b, torch.bfloat16
56 + )
57 out_ref = ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.bfloat16)
58
59 if not torch.allclose(out, out_ref):
@@ -63,54 +66,107 @@ def bench_int8(dtype: torch.dtype, m: int, k: int, n: int, label: str,
66 timers = []
67 # pytorch impl - bfloat16
68 timers.append(
66 - bench_fn(label, sub_label, "pytorch_bf16_bf16_bf16_matmul-no-scales",
67 - torch.mm, a.to(dtype=torch.bfloat16),
68 - b.to(dtype=torch.bfloat16)))
69 + bench_fn(
70 + label,
71 + sub_label,
72 + "pytorch_bf16_bf16_bf16_matmul-no-scales",
73 + torch.mm,
74 + a.to(dtype=torch.bfloat16),
75 + b.to(dtype=torch.bfloat16),
76 + )
77 + )
78
79 # pytorch impl - float16
80 timers.append(
72 - bench_fn(label, sub_label,
73 - "pytorch_fp16_fp16_fp16_matmul-no-scales", torch.mm,
74 - a.to(dtype=torch.float16), b.to(dtype=torch.float16)))
81 + bench_fn(
82 + label,
83 + sub_label,
84 + "pytorch_fp16_fp16_fp16_matmul-no-scales",
85 + torch.mm,
86 + a.to(dtype=torch.float16),
87 + b.to(dtype=torch.float16),
88 + )
89 + )
90
91 # cutlass impl
92 timers.append(
78 - bench_fn(label, sub_label, "cutlass_i8_i8_bf16_scaled_mm",
79 - ops.cutlass_scaled_mm, a, b, scale_a, scale_b,
80 - torch.bfloat16))
93 + bench_fn(
94 + label,
95 + sub_label,
96 + "cutlass_i8_i8_bf16_scaled_mm",
97 + ops.cutlass_scaled_mm,
98 + a,
99 + b,
100 + scale_a,
101 + scale_b,
102 + torch.bfloat16,
103 + )
104 + )
105
106 # cutlass with bias
107 timers.append(
84 - bench_fn(label, sub_label, "cutlass_i8_i8_bf16_scaled_mm_bias",
85 - ops.cutlass_scaled_mm, a, b, scale_a, scale_b, torch.bfloat16,
86 - bias))
108 + bench_fn(
109 + label,
110 + sub_label,
111 + "cutlass_i8_i8_bf16_scaled_mm_bias",
112 + ops.cutlass_scaled_mm,
113 + a,
114 + b,
115 + scale_a,
116 + scale_b,
117 + torch.bfloat16,
118 + bias,
119 + )
120 + )
121
122 # cutlass sparse impl
123 timers.append(
90 - bench_fn(label, sub_label, "cutlass_i8_i8_bf16_scaled_sparse_mm",
91 - ops.cutlass_scaled_sparse_mm, a, b_compressed, e, scale_a,
92 - scale_b, torch.bfloat16))
124 + bench_fn(
125 + label,
126 + sub_label,
127 + "cutlass_i8_i8_bf16_scaled_sparse_mm",
128 + ops.cutlass_scaled_sparse_mm,
129 + a,
130 + b_compressed,
131 + e,
132 + scale_a,
133 + scale_b,
134 + torch.bfloat16,
135 + )
136 + )
137
138 # cutlass sparse with bias
139 timers.append(
96 - bench_fn(label, sub_label, "cutlass_i8_i8_bf16_scaled_sparse_mm_bias",
97 - ops.cutlass_scaled_sparse_mm, a, b_compressed, e, scale_a,
98 - scale_b, torch.bfloat16, bias))
140 + bench_fn(
141 + label,
142 + sub_label,
143 + "cutlass_i8_i8_bf16_scaled_sparse_mm_bias",
144 + ops.cutlass_scaled_sparse_mm,
145 + a,
146 + b_compressed,
147 + e,
148 + scale_a,
149 + scale_b,
150 + torch.bfloat16,
151 + bias,
152 + )
153 + )
154
155 return timers
156
157
103 -def bench_fp8(dtype: torch.dtype, m: int, k: int, n: int, label: str,
104 - sub_label: str) -> Iterable[TMeasurement]:
158 +def bench_fp8(
159 + dtype: torch.dtype, m: int, k: int, n: int, label: str, sub_label: str
160 +) -> Iterable[TMeasurement]:
161 assert dtype == torch.float8_e4m3fn
106 - b_compressed, e, a, b = make_rand_sparse_tensors(torch.float8_e4m3fn, m, n,
107 - k)
162 + b_compressed, e, a, b = make_rand_sparse_tensors(torch.float8_e4m3fn, m, n, k)
163 scale_a = torch.tensor(1.0, device="cuda", dtype=torch.float32)
164 scale_b = torch.tensor(1.0, device="cuda", dtype=torch.float32)
110 - bias = torch.zeros((n, ), device="cuda", dtype=torch.bfloat16)
165 + bias = torch.zeros((n,), device="cuda", dtype=torch.bfloat16)
166
112 - out = ops.cutlass_scaled_sparse_mm(a, b_compressed, e, scale_a, scale_b,
113 - torch.bfloat16)
167 + out = ops.cutlass_scaled_sparse_mm(
168 + a, b_compressed, e, scale_a, scale_b, torch.bfloat16
169 + )
170 out_ref = ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.bfloat16)
171
172 if not torch.allclose(out, out_ref):
@@ -124,97 +180,165 @@ def bench_fp8(dtype: torch.dtype, m: int, k: int, n: int, label: str,
180
181 # pytorch impl w. bf16
182 timers.append(
127 - bench_fn(label, sub_label, "pytorch_bf16_bf16_bf16_matmul-no-scales",
128 - torch.mm, a.to(dtype=torch.bfloat16, device="cuda"),
129 - b.to(dtype=torch.bfloat16, device="cuda")))
183 + bench_fn(
184 + label,
185 + sub_label,
186 + "pytorch_bf16_bf16_bf16_matmul-no-scales",
187 + torch.mm,
188 + a.to(dtype=torch.bfloat16, device="cuda"),
189 + b.to(dtype=torch.bfloat16, device="cuda"),
190 + )
191 + )
192
193 # pytorch impl: bf16 output, without fp8 fast accum
194 timers.append(
133 - bench_fn(label,
134 - sub_label,
135 - "pytorch_fp8_fp8_bf16_scaled_mm",
136 - torch._scaled_mm,
137 - a,
138 - b,
139 - scale_a=scale_a,
140 - scale_b=scale_b,
141 - out_dtype=torch.bfloat16))
195 + bench_fn(
196 + label,
197 + sub_label,
198 + "pytorch_fp8_fp8_bf16_scaled_mm",
199 + torch._scaled_mm,
200 + a,
201 + b,
202 + scale_a=scale_a,
203 + scale_b=scale_b,
204 + out_dtype=torch.bfloat16,
205 + )
206 + )
207
208 # pytorch impl: bf16 output, with fp8 fast accum
209 timers.append(
145 - bench_fn(label,
146 - sub_label,
147 - "pytorch_fp8_fp8_bf16_scaled_mm_fast_accum",
148 - torch._scaled_mm,
149 - a,
150 - b,
151 - scale_a=scale_a,
152 - scale_b=scale_b,
153 - out_dtype=torch.bfloat16,
154 - use_fast_accum=True))
210 + bench_fn(
211 + label,
212 + sub_label,
213 + "pytorch_fp8_fp8_bf16_scaled_mm_fast_accum",
214 + torch._scaled_mm,
215 + a,
216 + b,
217 + scale_a=scale_a,
218 + scale_b=scale_b,
219 + out_dtype=torch.bfloat16,
220 + use_fast_accum=True,
221 + )
222 + )
223
224 # pytorch impl: fp16 output, without fp8 fast accum
225 timers.append(
158 - bench_fn(label,
159 - sub_label,
160 - "pytorch_fp8_fp8_fp16_scaled_mm",
161 - torch._scaled_mm,
162 - a,
163 - b,
164 - scale_a=scale_a,
165 - scale_b=scale_b,
166 - out_dtype=torch.float16))
226 + bench_fn(
227 + label,
228 + sub_label,
229 + "pytorch_fp8_fp8_fp16_scaled_mm",
230 + torch._scaled_mm,
231 + a,
232 + b,
233 + scale_a=scale_a,
234 + scale_b=scale_b,
235 + out_dtype=torch.float16,
236 + )
237 + )
238
239 # pytorch impl: fp16 output, with fp8 fast accum
240 timers.append(
170 - bench_fn(label,
171 - sub_label,
172 - "pytorch_fp8_fp8_fp16_scaled_mm_fast_accum",
173 - torch._scaled_mm,
174 - a,
175 - b,
176 - scale_a=scale_a,
177 - scale_b=scale_b,
178 - out_dtype=torch.float16,
179 - use_fast_accum=True))
241 + bench_fn(
242 + label,
243 + sub_label,
244 + "pytorch_fp8_fp8_fp16_scaled_mm_fast_accum",
245 + torch._scaled_mm,
246 + a,
247 + b,
248 + scale_a=scale_a,
249 + scale_b=scale_b,
250 + out_dtype=torch.float16,
251 + use_fast_accum=True,
252 + )
253 + )
254
255 # cutlass impl: bf16 output
256 timers.append(
183 - bench_fn(label, sub_label, "cutlass_fp8_fp8_bf16_scaled_mm",
184 - ops.cutlass_scaled_mm, a, b, scale_a, scale_b,
185 - torch.bfloat16))
257 + bench_fn(
258 + label,
259 + sub_label,
260 + "cutlass_fp8_fp8_bf16_scaled_mm",
261 + ops.cutlass_scaled_mm,
262 + a,
263 + b,
264 + scale_a,
265 + scale_b,
266 + torch.bfloat16,
267 + )
268 + )
269
270 # cutlass impl: bf16 output
271 timers.append(
189 - bench_fn(label, sub_label, "cutlass_fp8_fp8_bf16_scaled_sparse_mm",
190 - ops.cutlass_scaled_sparse_mm, a, b_compressed, e, scale_a,
191 - scale_b, torch.bfloat16))
272 + bench_fn(
273 + label,
274 + sub_label,
275 + "cutlass_fp8_fp8_bf16_scaled_sparse_mm",
276 + ops.cutlass_scaled_sparse_mm,
277 + a,
278 + b_compressed,
279 + e,
280 + scale_a,
281 + scale_b,
282 + torch.bfloat16,
283 + )
284 + )
285
286 # cutlass impl: fp16 output
287 timers.append(
195 - bench_fn(label, sub_label, "cutlass_fp8_fp8_fp16_scaled_sparse_mm",
196 - ops.cutlass_scaled_sparse_mm, a, b_compressed, e, scale_a,
197 - scale_b, torch.float16))
288 + bench_fn(
289 + label,
290 + sub_label,
291 + "cutlass_fp8_fp8_fp16_scaled_sparse_mm",
292 + ops.cutlass_scaled_sparse_mm,
293 + a,
294 + b_compressed,
295 + e,
296 + scale_a,
297 + scale_b,
298 + torch.float16,
299 + )
300 + )
301
302 # cutlass impl: bf16 output, with bias
303 timers.append(
201 - bench_fn(label, sub_label,
202 - "cutlass_fp8_fp8_bf16_scaled_sparse_mm_bias",
203 - ops.cutlass_scaled_sparse_mm, a, b_compressed, e, scale_a,
204 - scale_b, torch.bfloat16, bias))
304 + bench_fn(
305 + label,
306 + sub_label,
307 + "cutlass_fp8_fp8_bf16_scaled_sparse_mm_bias",
308 + ops.cutlass_scaled_sparse_mm,
309 + a,
310 + b_compressed,
311 + e,
312 + scale_a,
313 + scale_b,
314 + torch.bfloat16,
315 + bias,
316 + )
317 + )
318
319 # cutlass impl: fp16 output, with bias
320 timers.append(
208 - bench_fn(label, sub_label,
209 - "cutlass_fp8_fp8_fp16_scaled_sparse_mm_bias",
210 - ops.cutlass_scaled_sparse_mm, a, b_compressed, e, scale_a,
211 - scale_b, torch.float16, bias.to(dtype=torch.float16)))
321 + bench_fn(
322 + label,
323 + sub_label,
324 + "cutlass_fp8_fp8_fp16_scaled_sparse_mm_bias",
325 + ops.cutlass_scaled_sparse_mm,
326 + a,
327 + b_compressed,
328 + e,
329 + scale_a,
330 + scale_b,
331 + torch.float16,
332 + bias.to(dtype=torch.float16),
333 + )
334 + )
335
336 return timers
337
338
216 -def bench(dtype: torch.dtype, m: int, k: int, n: int, label: str,
217 - sub_label: str) -> Iterable[TMeasurement]:
339 +def bench(
340 + dtype: torch.dtype, m: int, k: int, n: int, label: str, sub_label: str
341 +) -> Iterable[TMeasurement]:
342 if dtype == torch.int8:
343 return bench_int8(dtype, m, k, n, label, sub_label)
344 if dtype == torch.float8_e4m3fn:
@@ -228,12 +352,12 @@ def print_timers(timers: Iterable[TMeasurement]):
352 compare.print()
353
354
231 -def run(dtype: torch.dtype,
232 - MKNs: Iterable[tuple[int, int, int]]) -> Iterable[TMeasurement]:
355 +def run(
356 + dtype: torch.dtype, MKNs: Iterable[tuple[int, int, int]]
357 +) -> Iterable[TMeasurement]:
358 results = []
359 for m, k, n in MKNs:
235 - timers = bench(dtype, m, k, n, f"scaled-{dtype}-gemm",
236 - f"MKN=({m}x{k}x{n})")
360 + timers = bench(dtype, m, k, n, f"scaled-{dtype}-gemm", f"MKN=({m}x{k}x{n})")
361 print_timers(timers)
362 results.extend(timers)
363
@@ -241,10 +365,12 @@ def run(dtype: torch.dtype,
365
366
367 # output makers
244 -def make_output(data: Iterable[TMeasurement],
245 - MKNs: Iterable[tuple[int, int, int]],
246 - base_description: str,
247 - timestamp=None):
368 +def make_output(
369 + data: Iterable[TMeasurement],
370 + MKNs: Iterable[tuple[int, int, int]],
371 + base_description: str,
372 + timestamp=None,
373 +):
374 print(f"== All Results {base_description} ====")
375 print_timers(data)
376
@@ -258,8 +384,7 @@ def make_output(data: Iterable[TMeasurement],
384
385
386 def run_square_bench(args):
261 - dim_sizes = list(
262 - range(args.dim_start, args.dim_end + 1, args.dim_increment))
387 + dim_sizes = list(range(args.dim_start, args.dim_end + 1, args.dim_increment))
388 MKNs = list(zip(dim_sizes, dim_sizes, dim_sizes))
389 data = run(args.dtype, MKNs)
390
@@ -319,7 +444,7 @@ def run_model_bench(args):
444 pkl.dump(all_data, f)
445
446
322 -if __name__ == '__main__':
447 +if __name__ == "__main__":
448
449 def to_torch_dtype(dt):
450 if dt == "int8":
@@ -344,12 +469,15 @@ Benchmark Cutlass GEMM.
469 Output:
470 - a .pkl file, that is a list of raw torch.benchmark.utils.Measurements for the pytorch and cutlass implementations for the various GEMMs.
471 """, # noqa: E501
347 - formatter_class=argparse.RawTextHelpFormatter)
348 -
349 - parser.add_argument("--dtype",
350 - type=to_torch_dtype,
351 - required=True,
352 - help="Available options are ['int8', 'fp8']")
472 + formatter_class=argparse.RawTextHelpFormatter,
473 + )
474 +
475 + parser.add_argument(
476 + "--dtype",
477 + type=to_torch_dtype,
478 + required=True,
479 + help="Available options are ['int8', 'fp8']",
480 + )
481 subparsers = parser.add_subparsers(dest="cmd")
482
483 square_parser = subparsers.add_parser("square_bench")
@@ -368,19 +496,19 @@ Benchmark Cutlass GEMM.
496 range_parser.set_defaults(func=run_range_bench)
497
498 model_parser = subparsers.add_parser("model_bench")
371 - model_parser.add_argument("--models",
372 - nargs="+",
373 - type=str,
374 - default=DEFAULT_MODELS,
375 - choices=WEIGHT_SHAPES.keys())
376 - model_parser.add_argument("--tp-sizes",
377 - nargs="+",
378 - type=int,
379 - default=DEFAULT_TP_SIZES)
380 - model_parser.add_argument("--batch-sizes",
381 - nargs="+",
382 - type=int,
383 - default=DEFAULT_BATCH_SIZES)
499 + model_parser.add_argument(
500 + "--models",
501 + nargs="+",
502 + type=str,
503 + default=DEFAULT_MODELS,
504 + choices=WEIGHT_SHAPES.keys(),
505 + )
506 + model_parser.add_argument(
507 + "--tp-sizes", nargs="+", type=int, default=DEFAULT_TP_SIZES
508 + )
509 + model_parser.add_argument(
510 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
511 + )
512 model_parser.set_defaults(func=run_model_bench)
513
514 args = parser.parse_args()
benchmarks/cutlass_benchmarks/utils.py
+17 -16
@@ -10,8 +10,9 @@ import vllm._custom_ops as ops
10
11 def to_fp8(tensor: torch.Tensor) -> torch.Tensor:
12 finfo = torch.finfo(torch.float8_e4m3fn)
13 - return torch.round(tensor.clamp(
14 - min=finfo.min, max=finfo.max)).to(dtype=torch.float8_e4m3fn)
13 + return torch.round(tensor.clamp(min=finfo.min, max=finfo.max)).to(
14 + dtype=torch.float8_e4m3fn
15 + )
16
17
18 def to_int8(tensor: torch.Tensor) -> torch.Tensor:
@@ -26,10 +27,11 @@ def to_fp16(tensor: torch.Tensor) -> torch.Tensor:
27 return tensor.to(dtype=torch.float16)
28
29
29 -def make_rand_tensors(dtype: torch.dtype, m: int, n: int,
30 - k: int) -> tuple[torch.Tensor, torch.Tensor]:
31 - a = torch.randn((m, k), device='cuda') * 5
32 - b = torch.randn((n, k), device='cuda').t() * 5
30 +def make_rand_tensors(
31 + dtype: torch.dtype, m: int, n: int, k: int
32 +) -> tuple[torch.Tensor, torch.Tensor]:
33 + a = torch.randn((m, k), device="cuda") * 5
34 + b = torch.randn((n, k), device="cuda").t() * 5
35
36 if dtype == torch.int8:
37 return to_int8(a), to_int8(b)
@@ -49,9 +51,7 @@ def prune_to_2_4(tensor):
51
52 # Create binary mask
53 mask = torch.zeros_like(reshaped)
52 - mask.scatter_(dim=1,
53 - index=indices,
54 - src=torch.ones_like(indices, dtype=mask.dtype))
54 + mask.scatter_(dim=1, index=indices, src=torch.ones_like(indices, dtype=mask.dtype))
55
56 # Apply mask and reshape back
57 pruned = reshaped * mask
@@ -62,10 +62,11 @@ def prune_to_2_4(tensor):
62 return pruned.reshape(original_shape)
63
64
65 -def make_rand_sparse_tensors(dtype: torch.dtype, m: int, n: int,
66 - k: int) -> tuple[torch.Tensor, torch.Tensor]:
67 - a = torch.randn((m, k), device='cuda') * 5
68 - b = torch.randn((n, k), device='cuda').t() * 5
65 +def make_rand_sparse_tensors(
66 + dtype: torch.dtype, m: int, n: int, k: int
67 +) -> tuple[torch.Tensor, torch.Tensor]:
68 + a = torch.randn((m, k), device="cuda") * 5
69 + b = torch.randn((n, k), device="cuda").t() * 5
70
71 b = prune_to_2_4(b.t()).t()
72
@@ -86,9 +87,9 @@ def make_rand_sparse_tensors(dtype: torch.dtype, m: int, n: int,
87 return b_compressed, e, a, b
88
89
89 -def make_n_rand_sparse_tensors(num_tensors: int, dtype: torch.dtype,
90 - m: int, n: int, k: int) -> \
91 - tuple[Iterable[torch.Tensor], Iterable[torch.Tensor]]:
90 +def make_n_rand_sparse_tensors(
91 + num_tensors: int, dtype: torch.dtype, m: int, n: int, k: int
92 +) -> tuple[Iterable[torch.Tensor], Iterable[torch.Tensor]]:
93 ABs = []
94 for _ in range(num_tensors):
95 b_comp, e, a, b = make_rand_sparse_tensors(dtype, m, n, k)
benchmarks/cutlass_benchmarks/w8a8_benchmarks.py
+140 -134
@@ -16,7 +16,8 @@ from weight_shapes import WEIGHT_SHAPES
16
17 from vllm import _custom_ops as ops
18 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
19 - w8a8_block_fp8_matmul)
19 + w8a8_block_fp8_matmul,
20 +)
21 from vllm.utils import FlexibleArgumentParser
22
23 DEFAULT_MODELS = list(WEIGHT_SHAPES.keys())
@@ -25,8 +26,9 @@ DEFAULT_TP_SIZES = [1]
26
27
28 # bench
28 -def bench_fn(label: str, sub_label: str, description: str, fn: Callable, *args,
29 - **kwargs) -> TMeasurement:
29 +def bench_fn(
30 + label: str, sub_label: str, description: str, fn: Callable, *args, **kwargs
31 +) -> TMeasurement:
32 min_run_time = 1
33
34 globals = {
@@ -44,45 +46,48 @@ def bench_fn(label: str, sub_label: str, description: str, fn: Callable, *args,
46
47
48 def bench_int8(
47 - dtype: torch.dtype,
48 - m: int,
49 - k: int,
50 - n: int,
51 - label: str,
52 - sub_label: str,
53 - bench_kernels: Optional[list[str]] = None) -> Iterable[TMeasurement]:
49 + dtype: torch.dtype,
50 + m: int,
51 + k: int,
52 + n: int,
53 + label: str,
54 + sub_label: str,
55 + bench_kernels: Optional[list[str]] = None,
56 +) -> Iterable[TMeasurement]:
57 """Benchmark INT8-based kernels."""
58 assert dtype == torch.int8
59 a, b = make_rand_tensors(torch.int8, m, n, k)
60 scale_a = torch.tensor(1.0, device="cuda", dtype=torch.float32)
61 scale_b = torch.tensor(1.0, device="cuda", dtype=torch.float32)
59 - bias = torch.zeros((n, ), device="cuda", dtype=torch.bfloat16)
60 - azp = torch.zeros((m, ), device="cuda", dtype=torch.int32)
61 - azp_adj = torch.zeros((n, ), device="cuda", dtype=torch.int32)
62 + bias = torch.zeros((n,), device="cuda", dtype=torch.bfloat16)
63 + azp = torch.zeros((m,), device="cuda", dtype=torch.int32)
64 + azp_adj = torch.zeros((n,), device="cuda", dtype=torch.int32)
65
66 bench_fns = {
64 - "pytorch_bf16_bf16_bf16_matmul-no-scales":
65 - lambda: torch.mm(a.to(dtype=torch.bfloat16), b.to(dtype=torch.bfloat16)
66 - ),
67 - "pytorch_fp16_fp16_fp16_matmul-no-scales":
68 - lambda: torch.mm(a.to(dtype=torch.float16), b.to(dtype=torch.float16)),
69 - "cutlass_i8_i8_bf16_scaled_mm":
70 - lambda: ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.bfloat16),
71 - "cutlass_i8_i8_bf16_scaled_mm_bias":
72 - lambda: ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.bfloat16,
73 - bias),
74 - "cutlass_i8_i8_bf16_scaled_mm_azp":
75 - lambda: ops.cutlass_scaled_mm_azp(a, b, scale_a, scale_b, torch.
76 - bfloat16, azp_adj),
77 - "cutlass_i8_i8_bf16_scaled_mm_azp_bias":
78 - lambda: ops.cutlass_scaled_mm_azp(a, b, scale_a, scale_b, torch.
79 - bfloat16, azp_adj, None, bias),
80 - "cutlass_i8_i8_bf16_scaled_mm_azp_pt":
81 - lambda: ops.cutlass_scaled_mm_azp(a, b, scale_a, scale_b, torch.
82 - bfloat16, azp_adj, azp),
83 - "cutlass_i8_i8_bf16_scaled_mm_azp_pt_bias":
84 - lambda: ops.cutlass_scaled_mm_azp(a, b, scale_a, scale_b, torch.
85 - bfloat16, azp_adj, azp, bias),
67 + "pytorch_bf16_bf16_bf16_matmul-no-scales": lambda: torch.mm(
68 + a.to(dtype=torch.bfloat16), b.to(dtype=torch.bfloat16)
69 + ),
70 + "pytorch_fp16_fp16_fp16_matmul-no-scales": lambda: torch.mm(
71 + a.to(dtype=torch.float16), b.to(dtype=torch.float16)
72 + ),
73 + "cutlass_i8_i8_bf16_scaled_mm": lambda: ops.cutlass_scaled_mm(
74 + a, b, scale_a, scale_b, torch.bfloat16
75 + ),
76 + "cutlass_i8_i8_bf16_scaled_mm_bias": lambda: ops.cutlass_scaled_mm(
77 + a, b, scale_a, scale_b, torch.bfloat16, bias
78 + ),
79 + "cutlass_i8_i8_bf16_scaled_mm_azp": lambda: ops.cutlass_scaled_mm_azp(
80 + a, b, scale_a, scale_b, torch.bfloat16, azp_adj
81 + ),
82 + "cutlass_i8_i8_bf16_scaled_mm_azp_bias": lambda: ops.cutlass_scaled_mm_azp(
83 + a, b, scale_a, scale_b, torch.bfloat16, azp_adj, None, bias
84 + ),
85 + "cutlass_i8_i8_bf16_scaled_mm_azp_pt": lambda: ops.cutlass_scaled_mm_azp(
86 + a, b, scale_a, scale_b, torch.bfloat16, azp_adj, azp
87 + ),
88 + "cutlass_i8_i8_bf16_scaled_mm_azp_pt_bias": lambda: ops.cutlass_scaled_mm_azp(
89 + a, b, scale_a, scale_b, torch.bfloat16, azp_adj, azp, bias
90 + ),
91 }
92
93 timers = []
@@ -96,73 +101,65 @@ def bench_int8(
101
102
103 def bench_fp8(
99 - dtype: torch.dtype,
100 - m: int,
101 - k: int,
102 - n: int,
103 - label: str,
104 - sub_label: str,
105 - bench_kernels: Optional[list[str]] = None) -> Iterable[TMeasurement]:
104 + dtype: torch.dtype,
105 + m: int,
106 + k: int,
107 + n: int,
108 + label: str,
109 + sub_label: str,
110 + bench_kernels: Optional[list[str]] = None,
111 +) -> Iterable[TMeasurement]:
112 """Benchmark FP8-based kernels."""
113 assert dtype == torch.float8_e4m3fn
114 a, b = make_rand_tensors(torch.float8_e4m3fn, m, n, k)
115 a_cont = a.contiguous()
116 scale_a = torch.tensor(1.0, device="cuda", dtype=torch.float32)
117 scale_b = torch.tensor(1.0, device="cuda", dtype=torch.float32)
112 - block_scale_a = torch.rand((m, k // 128),
113 - device="cuda",
114 - dtype=torch.float32)
115 - block_scale_b = torch.rand((k // 128, n // 128),
116 - device="cuda",
117 - dtype=torch.float32)
118 + block_scale_a = torch.rand((m, k // 128), device="cuda", dtype=torch.float32)
119 + block_scale_b = torch.rand((k // 128, n // 128), device="cuda", dtype=torch.float32)
120 block_scale_a_M_major = block_scale_a.t().contiguous().t()
121 block_scale_b_K_major = block_scale_b.t().contiguous().t()
120 - bias = torch.zeros((n, ), device="cuda", dtype=torch.bfloat16)
122 + bias = torch.zeros((n,), device="cuda", dtype=torch.bfloat16)
123
124 print(m, k, n)
125
126 bench_fns = {
125 - "pytorch_bf16_bf16_bf16_matmul-no-scales":
126 - lambda: torch.mm(a.to(dtype=torch.bfloat16), b.to(dtype=torch.bfloat16)
127 - ),
128 - "pytorch_fp16_fp16_fp16_matmul-no-scales":
129 - lambda: torch.mm(a.to(dtype=torch.float16), b.to(dtype=torch.float16)),
130 - "pytorch_fp8_fp8_fp16_scaled_mm":
131 - lambda: torch._scaled_mm(
132 - a, b, scale_a, scale_b, out_dtype=torch.float16),
133 - "pytorch_fp8_fp8_fp16_scaled_mm_fast_accum":
134 - lambda: torch._scaled_mm(a,
135 - b,
136 - scale_a,
137 - scale_b,
138 - out_dtype=torch.float16,
139 - use_fast_accum=True),
140 - "pytorch_fp8_fp8_bf16_scaled_mm":
141 - lambda: torch._scaled_mm(
142 - a, b, scale_a, scale_b, out_dtype=torch.bfloat16),
143 - "pytorch_fp8_fp8_bf16_scaled_mm_fast_accum":
144 - lambda: torch._scaled_mm(a,
145 - b,
146 - scale_a,
147 - scale_b,
148 - out_dtype=torch.bfloat16,
149 - use_fast_accum=True),
150 - "cutlass_fp8_fp8_bf16_scaled_mm":
151 - lambda: ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.bfloat16),
152 - "cutlass_fp8_fp8_fp16_scaled_mm":
153 - lambda: ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.float16),
154 - "cutlass_fp8_fp8_bf16_scaled_mm_bias":
155 - lambda: ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.bfloat16,
156 - bias),
157 - "cutlass_fp8_fp8_fp16_scaled_mm_bias":
158 - lambda: ops.cutlass_scaled_mm(a, b, scale_a, scale_b, torch.float16,
159 - bias.to(dtype=torch.float16)),
160 - "triton_fp8_fp8_fp16_scaled_mm_blockwise":
161 - lambda: w8a8_block_fp8_matmul(a_cont, b.t(), block_scale_a,
162 - block_scale_b.t(), (128, 128)),
163 - "cutlass_fp8_fp8_fp16_scaled_mm_blockwise":
164 - lambda: ops.cutlass_scaled_mm(a, b, block_scale_a_M_major,
165 - block_scale_b_K_major, torch.float16),
127 + "pytorch_bf16_bf16_bf16_matmul-no-scales": lambda: torch.mm(
128 + a.to(dtype=torch.bfloat16), b.to(dtype=torch.bfloat16)
129 + ),
130 + "pytorch_fp16_fp16_fp16_matmul-no-scales": lambda: torch.mm(
131 + a.to(dtype=torch.float16), b.to(dtype=torch.float16)
132 + ),
133 + "pytorch_fp8_fp8_fp16_scaled_mm": lambda: torch._scaled_mm(
134 + a, b, scale_a, scale_b, out_dtype=torch.float16
135 + ),
136 + "pytorch_fp8_fp8_fp16_scaled_mm_fast_accum": lambda: torch._scaled_mm(
137 + a, b, scale_a, scale_b, out_dtype=torch.float16, use_fast_accum=True
138 + ),
139 + "pytorch_fp8_fp8_bf16_scaled_mm": lambda: torch._scaled_mm(
140 + a, b, scale_a, scale_b, out_dtype=torch.bfloat16
141 + ),
142 + "pytorch_fp8_fp8_bf16_scaled_mm_fast_accum": lambda: torch._scaled_mm(
143 + a, b, scale_a, scale_b, out_dtype=torch.bfloat16, use_fast_accum=True
144 + ),
145 + "cutlass_fp8_fp8_bf16_scaled_mm": lambda: ops.cutlass_scaled_mm(
146 + a, b, scale_a, scale_b, torch.bfloat16
147 + ),
148 + "cutlass_fp8_fp8_fp16_scaled_mm": lambda: ops.cutlass_scaled_mm(
149 + a, b, scale_a, scale_b, torch.float16
150 + ),
151 + "cutlass_fp8_fp8_bf16_scaled_mm_bias": lambda: ops.cutlass_scaled_mm(
152 + a, b, scale_a, scale_b, torch.bfloat16, bias
153 + ),
154 + "cutlass_fp8_fp8_fp16_scaled_mm_bias": lambda: ops.cutlass_scaled_mm(
155 + a, b, scale_a, scale_b, torch.float16, bias.to(dtype=torch.float16)
156 + ),
157 + "triton_fp8_fp8_fp16_scaled_mm_blockwise": lambda: w8a8_block_fp8_matmul(
158 + a_cont, b.t(), block_scale_a, block_scale_b.t(), (128, 128)
159 + ),
160 + "cutlass_fp8_fp8_fp16_scaled_mm_blockwise": lambda: ops.cutlass_scaled_mm(
161 + a, b, block_scale_a_M_major, block_scale_b_K_major, torch.float16
162 + ),
163 }
164
165 timers = []
@@ -175,13 +172,15 @@ def bench_fp8(
172 return timers
173
174
178 -def bench(dtype: torch.dtype,
179 - m: int,
180 - k: int,
181 - n: int,
182 - label: str,
183 - sub_label: str,
184 - bench_kernels: Optional[list[str]] = None) -> Iterable[TMeasurement]:
175 +def bench(
176 + dtype: torch.dtype,
177 + m: int,
178 + k: int,
179 + n: int,
180 + label: str,
181 + sub_label: str,
182 + bench_kernels: Optional[list[str]] = None,
183 +) -> Iterable[TMeasurement]:
184 if dtype == torch.int8:
185 return bench_int8(dtype, m, k, n, label, sub_label, bench_kernels)
186 if dtype == torch.float8_e4m3fn:
@@ -195,27 +194,33 @@ def print_timers(timers: Iterable[TMeasurement]):
194 compare.print()
195
196
198 -def run(dtype: torch.dtype,
199 - MKNs: Iterable[tuple[int, int, int]],
200 - bench_kernels: Optional[list[str]] = None) -> Iterable[TMeasurement]:
197 +def run(
198 + dtype: torch.dtype,
199 + MKNs: Iterable[tuple[int, int, int]],
200 + bench_kernels: Optional[list[str]] = None,
201 +) -> Iterable[TMeasurement]:
202 results = []
203 for m, k, n in MKNs:
203 - timers = bench(dtype,
204 - m,
205 - k,
206 - n,
207 - f"scaled-{dtype}-gemm",
208 - f"MKN=({m}x{k}x{n})",
209 - bench_kernels=bench_kernels)
204 + timers = bench(
205 + dtype,
206 + m,
207 + k,
208 + n,
209 + f"scaled-{dtype}-gemm",
210 + f"MKN=({m}x{k}x{n})",
211 + bench_kernels=bench_kernels,
212 + )
213 print_timers(timers)
214 results.extend(timers)
215 return results
216
217
215 -def make_output(data: Iterable[TMeasurement],
216 - MKNs: Iterable[tuple[int, int, int]],
217 - base_description: str,
218 - timestamp=None):
218 +def make_output(
219 + data: Iterable[TMeasurement],
220 + MKNs: Iterable[tuple[int, int, int]],
221 + base_description: str,
222 + timestamp=None,
223 +):
224 print(f"== All Results {base_description} ====")
225 print_timers(data)
226
@@ -226,8 +231,7 @@ def make_output(data: Iterable[TMeasurement],
231
232
233 def run_square_bench(args):
229 - dim_sizes = list(
230 - range(args.dim_start, args.dim_end + 1, args.dim_increment))
234 + dim_sizes = list(range(args.dim_start, args.dim_end + 1, args.dim_increment))
235 MKNs = list(zip(dim_sizes, dim_sizes, dim_sizes))
236 data = run(args.dtype, MKNs, bench_kernels=args.kernels)
237 make_output(data, MKNs, f"square_bench-{args.dtype}")
@@ -285,7 +289,7 @@ def run_model_bench(args):
289 pkl.dump(all_data, f)
290
291
288 -if __name__ == '__main__':
292 +if __name__ == "__main__":
293
294 def to_torch_dtype(dt):
295 if dt == "int8":
@@ -310,19 +314,21 @@ Benchmark Cutlass GEMM.
314 Output:
315 - a .pkl file, that is a list of raw torch.benchmark.utils.Measurements for the pytorch and cutlass implementations for the various GEMMs.
316 """, # noqa: E501
313 - formatter_class=argparse.RawTextHelpFormatter)
317 + formatter_class=argparse.RawTextHelpFormatter,
318 + )
319
315 - parser.add_argument("--dtype",
316 - type=to_torch_dtype,
317 - required=True,
318 - help="Available options are ['int8', 'fp8']")
320 + parser.add_argument(
321 + "--dtype",
322 + type=to_torch_dtype,
323 + required=True,
324 + help="Available options are ['int8', 'fp8']",
325 + )
326 parser.add_argument(
327 "--kernels",
328 nargs="+",
329 type=str,
330 default=None,
324 - help=
325 - "Exact names of the kernels to benchmark. If not set, runs all kernels."
331 + help="Exact names of the kernels to benchmark. If not set, runs all kernels.",
332 )
333
334 subparsers = parser.add_subparsers(dest="cmd")
@@ -343,19 +349,19 @@ Benchmark Cutlass GEMM.
349 range_parser.set_defaults(func=run_range_bench)
350
351 model_parser = subparsers.add_parser("model_bench")
346 - model_parser.add_argument("--models",
347 - nargs="+",
348 - type=str,
349 - default=DEFAULT_MODELS,
350 - choices=WEIGHT_SHAPES.keys())
351 - model_parser.add_argument("--tp-sizes",
352 - nargs="+",
353 - type=int,
354 - default=DEFAULT_TP_SIZES)
355 - model_parser.add_argument("--batch-sizes",
356 - nargs="+",
357 - type=int,
358 - default=DEFAULT_BATCH_SIZES)
352 + model_parser.add_argument(
353 + "--models",
354 + nargs="+",
355 + type=str,
356 + default=DEFAULT_MODELS,
357 + choices=WEIGHT_SHAPES.keys(),
358 + )
359 + model_parser.add_argument(
360 + "--tp-sizes", nargs="+", type=int, default=DEFAULT_TP_SIZES
361 + )
362 + model_parser.add_argument(
363 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
364 + )
365 model_parser.set_defaults(func=run_model_bench)
366
367 args = parser.parse_args()
benchmarks/cutlass_benchmarks/weight_shapes.py
+1 -1
@@ -42,4 +42,4 @@ WEIGHT_SHAPES = {
42 ([8192, 57344], 1),
43 ([28672, 8192], 0),
44 ],
45 -}
\ No newline at end of file
45 +}
benchmarks/disagg_benchmarks/disagg_prefill_proxy_server.py
+13 -14
@@ -12,39 +12,37 @@ app = Quart(__name__)
12
13 async def forward_request(url, data):
14 async with aiohttp.ClientSession(timeout=AIOHTTP_TIMEOUT) as session:
15 - headers = {
16 - "Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"
17 - }
18 - async with session.post(url=url, json=data,
19 - headers=headers) as response:
15 + headers = {"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"}
16 + async with session.post(url=url, json=data, headers=headers) as response:
17 if response.status == 200:
18 # if response.headers.get('Transfer-Encoding') == 'chunked':
19 if True:
23 - async for chunk_bytes in response.content.iter_chunked(
24 - 1024):
20 + async for chunk_bytes in response.content.iter_chunked(1024):
21 yield chunk_bytes
22 else:
23 content = await response.read()
24 yield content
25
26
31 -@app.route('/v1/completions', methods=['POST'])
27 +@app.route("/v1/completions", methods=["POST"])
28 async def handle_request():
29 try:
30 original_request_data = await request.get_json()
31
32 prefill_request = original_request_data.copy()
33 # change max_tokens = 1 to let it only do prefill
38 - prefill_request['max_tokens'] = 1
34 + prefill_request["max_tokens"] = 1
35
36 # finish prefill
41 - async for _ in forward_request('http://localhost:8100/v1/completions',
42 - prefill_request):
37 + async for _ in forward_request(
38 + "http://localhost:8100/v1/completions", prefill_request
39 + ):
40 continue
41
42 # return decode
46 - generator = forward_request('http://localhost:8200/v1/completions',
47 - original_request_data)
43 + generator = forward_request(
44 + "http://localhost:8200/v1/completions", original_request_data
45 + )
46 response = await make_response(generator)
47 response.timeout = None
48
@@ -53,11 +51,12 @@ async def handle_request():
51 except Exception as e:
52 import sys
53 import traceback
54 +
55 exc_info = sys.exc_info()
56 print("Error occurred in disagg prefill proxy server")
57 print(e)
58 print("".join(traceback.format_exception(*exc_info)))
59
60
62 -if __name__ == '__main__':
61 +if __name__ == "__main__":
62 app.run(port=8000)
benchmarks/disagg_benchmarks/round_robin_proxy.py
+10 -10
@@ -8,7 +8,6 @@ from aiohttp import web
8
9
10 class RoundRobinProxy:
11 -
11 def __init__(self, target_ports):
12 self.target_ports = target_ports
13 self.port_cycle = itertools.cycle(self.target_ports)
@@ -21,14 +20,15 @@ class RoundRobinProxy:
20 try:
21 # Forward the request
22 async with session.request(
24 - method=request.method,
25 - url=target_url,
26 - headers=request.headers,
27 - data=request.content,
23 + method=request.method,
24 + url=target_url,
25 + headers=request.headers,
26 + data=request.content,
27 ) as response:
28 # Start sending the response
30 - resp = web.StreamResponse(status=response.status,
31 - headers=response.headers)
29 + resp = web.StreamResponse(
30 + status=response.status, headers=response.headers
31 + )
32 await resp.prepare(request)
33
34 # Stream the response content
@@ -45,11 +45,11 @@ class RoundRobinProxy:
45 async def main():
46 proxy = RoundRobinProxy([8100, 8200])
47 app = web.Application()
48 - app.router.add_route('*', '/{path:.*}', proxy.handle_request)
48 + app.router.add_route("*", "/{path:.*}", proxy.handle_request)
49
50 runner = web.AppRunner(app)
51 await runner.setup()
52 - site = web.TCPSite(runner, 'localhost', 8000)
52 + site = web.TCPSite(runner, "localhost", 8000)
53 await site.start()
54
55 print("Proxy server started on http://localhost:8000")
@@ -58,5 +58,5 @@ async def main():
58 await asyncio.Event().wait()
59
60
61 -if __name__ == '__main__':
61 +if __name__ == "__main__":
62 asyncio.run(main())
benchmarks/disagg_benchmarks/visualize_benchmark_results.py
+21 -23
@@ -6,43 +6,41 @@ import matplotlib.pyplot as plt
6 import pandas as pd
7
8 if __name__ == "__main__":
9 -
9 data = []
11 - for name in ['disagg_prefill', 'chunked_prefill']:
10 + for name in ["disagg_prefill", "chunked_prefill"]:
11 for qps in [2, 4, 6, 8]:
12 with open(f"results/{name}-qps-{qps}.json") as f:
13 x = json.load(f)
15 - x['name'] = name
16 - x['qps'] = qps
14 + x["name"] = name
15 + x["qps"] = qps
16 data.append(x)
17
18 df = pd.DataFrame.from_dict(data)
20 - dis_df = df[df['name'] == 'disagg_prefill']
21 - chu_df = df[df['name'] == 'chunked_prefill']
19 + dis_df = df[df["name"] == "disagg_prefill"]
20 + chu_df = df[df["name"] == "chunked_prefill"]
21
23 - plt.style.use('bmh')
24 - plt.rcParams['font.size'] = 20
22 + plt.style.use("bmh")
23 + plt.rcParams["font.size"] = 20
24
25 for key in [
27 - 'mean_ttft_ms', 'median_ttft_ms', 'p99_ttft_ms', 'mean_itl_ms',
28 - 'median_itl_ms', 'p99_itl_ms'
26 + "mean_ttft_ms",
27 + "median_ttft_ms",
28 + "p99_ttft_ms",
29 + "mean_itl_ms",
30 + "median_itl_ms",
31 + "p99_itl_ms",
32 ]:
30 -
33 fig, ax = plt.subplots(figsize=(11, 7))
32 - plt.plot(dis_df['qps'],
33 - dis_df[key],
34 - label='disagg_prefill',
35 - marker='o',
36 - linewidth=4)
37 - plt.plot(chu_df['qps'],
38 - chu_df[key],
39 - label='chunked_prefill',
40 - marker='o',
41 - linewidth=4)
34 + plt.plot(
35 + dis_df["qps"], dis_df[key], label="disagg_prefill", marker="o", linewidth=4
36 + )
37 + plt.plot(
38 + chu_df["qps"], chu_df[key], label="chunked_prefill", marker="o", linewidth=4
39 + )
40 ax.legend()
41
44 - ax.set_xlabel('QPS')
42 + ax.set_xlabel("QPS")
43 ax.set_ylabel(key)
44 ax.set_ylim(bottom=0)
47 - fig.savefig(f'results/{key}.png')
45 + fig.savefig(f"results/{key}.png")
46 plt.close(fig)
benchmarks/fused_kernels/layernorm_rms_benchmarks.py
+96 -45
@@ -24,10 +24,12 @@ class bench_params_t:
24 dtype: torch.dtype
25
26 def description(self):
27 - return (f'N {self.num_tokens} '
28 - f'x D {self.hidden_size} '
29 - f'x R {self.add_residual} '
30 - f'x DT {self.dtype}')
27 + return (
28 + f"N {self.num_tokens} "
29 + f"x D {self.hidden_size} "
30 + f"x R {self.add_residual} "
31 + f"x DT {self.dtype}"
32 + )
33
34
35 def get_bench_params() -> list[bench_params_t]:
@@ -38,15 +40,19 @@ def get_bench_params() -> list[bench_params_t]:
40 DTYPES = [torch.bfloat16, torch.float]
41
42 combinations = product(NUM_TOKENS, HIDDEN_SIZES, ADD_RESIDUAL, DTYPES)
41 - bench_params = list(map(lambda x: \
42 - bench_params_t(x[0], x[1], x[2], x[3]), combinations))
43 + bench_params = list(
44 + map(lambda x: bench_params_t(x[0], x[1], x[2], x[3]), combinations)
45 + )
46 return bench_params
47
48
49 # Reference impls
47 -def unfused_int8_impl(rms_norm_layer: RMSNorm, x: torch.Tensor,
48 - residual: Optional[torch.Tensor],
49 - quant_dtype: torch.dtype):
50 +def unfused_int8_impl(
51 + rms_norm_layer: RMSNorm,
52 + x: torch.Tensor,
53 + residual: Optional[torch.Tensor],
54 + quant_dtype: torch.dtype,
55 +):
56 # Norm
57 torch_out = None
58 if residual is None:
@@ -58,9 +64,12 @@ def unfused_int8_impl(rms_norm_layer: RMSNorm, x: torch.Tensor,
64 torch_out, _, _ = ops.scaled_int8_quant(torch_out)
65
66
61 -def unfused_fp8_impl(rms_norm_layer: RMSNorm, x: torch.Tensor,
62 - residual: Optional[torch.Tensor],
63 - quant_dtype: torch.dtype):
67 +def unfused_fp8_impl(
68 + rms_norm_layer: RMSNorm,
69 + x: torch.Tensor,
70 + residual: Optional[torch.Tensor],
71 + quant_dtype: torch.dtype,
72 +):
73 # Norm
74 torch_out = None
75 if residual is None:
@@ -73,22 +82,27 @@ def unfused_fp8_impl(rms_norm_layer: RMSNorm, x: torch.Tensor,
82
83
84 def fused_impl(
76 - rms_norm_layer: RMSNorm, # this stores the weights
77 - x: torch.Tensor,
78 - residual: Optional[torch.Tensor],
79 - quant_dtype: torch.dtype):
80 - out, _ = ops.rms_norm_dynamic_per_token_quant(x,
81 - rms_norm_layer.weight,
82 - 1e-6,
83 - quant_dtype,
84 - residual=residual)
85 + rms_norm_layer: RMSNorm, # this stores the weights
86 + x: torch.Tensor,
87 + residual: Optional[torch.Tensor],
88 + quant_dtype: torch.dtype,
89 +):
90 + out, _ = ops.rms_norm_dynamic_per_token_quant(
91 + x, rms_norm_layer.weight, 1e-6, quant_dtype, residual=residual
92 + )
93
94
95 # Bench functions
88 -def bench_fn(rms_norm_layer: RMSNorm, x: torch.Tensor, residual: torch.Tensor,
89 - quant_dtype: torch.dtype, label: str, sub_label: str,
90 - fn: Callable, description: str) -> TMeasurement:
91 -
96 +def bench_fn(
97 + rms_norm_layer: RMSNorm,
98 + x: torch.Tensor,
99 + residual: torch.Tensor,
100 + quant_dtype: torch.dtype,
101 + label: str,
102 + sub_label: str,
103 + fn: Callable,
104 + description: str,
105 +) -> TMeasurement:
106 min_run_time = 1
107
108 globals = {
@@ -106,43 +120,81 @@ def bench_fn(rms_norm_layer: RMSNorm, x: torch.Tensor, residual: torch.Tensor,
120 description=description,
121 ).blocked_autorange(min_run_time=min_run_time)
122
109 -def bench(params: bench_params_t, label: str, sub_label: str) \
110 - -> Iterable[TMeasurement]:
123
124 +def bench(params: bench_params_t, label: str, sub_label: str) -> Iterable[TMeasurement]:
125 # Make inputs
126 layer = RMSNorm(params.hidden_size, 1e-6).to(dtype=params.dtype)
127 # Make weights
128 layer.weight.data.normal_(mean=1.0, std=0.1)
129 # Make inputs
130 scale = 1 / params.hidden_size
118 - x = torch.randn(params.num_tokens,
119 - params.hidden_size,
120 - dtype=params.dtype,
121 - device='cuda') * scale
122 - residual = (torch.randn_like(x) * scale).to(device='cuda') \
123 - if params.add_residual else None
131 + x = (
132 + torch.randn(
133 + params.num_tokens, params.hidden_size, dtype=params.dtype, device="cuda"
134 + )
135 + * scale
136 + )
137 + residual = (
138 + (torch.randn_like(x) * scale).to(device="cuda") if params.add_residual else None
139 + )
140
141 timers = []
142
143 # unfused int8 impl.
144 timers.append(
129 - bench_fn(layer, x, residual, torch.int8, label, sub_label,
130 - unfused_int8_impl, "unfused_int8_impl"))
145 + bench_fn(
146 + layer,
147 + x,
148 + residual,
149 + torch.int8,
150 + label,
151 + sub_label,
152 + unfused_int8_impl,
153 + "unfused_int8_impl",
154 + )
155 + )
156
157 # unfused fp8 impl.
158 timers.append(
134 - bench_fn(layer, x, residual, torch.float8_e4m3fn, label, sub_label,
135 - unfused_fp8_impl, "unfused_fp8_impl"))
159 + bench_fn(
160 + layer,
161 + x,
162 + residual,
163 + torch.float8_e4m3fn,
164 + label,
165 + sub_label,
166 + unfused_fp8_impl,
167 + "unfused_fp8_impl",
168 + )
169 + )
170
171 # fused int8 impl.
172 timers.append(
139 - bench_fn(layer, x, residual, torch.int8, label, sub_label, fused_impl,
140 - "fused_int8_impl"))
173 + bench_fn(
174 + layer,
175 + x,
176 + residual,
177 + torch.int8,
178 + label,
179 + sub_label,
180 + fused_impl,
181 + "fused_int8_impl",
182 + )
183 + )
184
185 # fused fp8 impl.
186 timers.append(
144 - bench_fn(layer, x, residual, torch.float8_e4m3fn, label, sub_label,
145 - fused_impl, "fused_fp8_impl"))
187 + bench_fn(
188 + layer,
189 + x,
190 + residual,
191 + torch.float8_e4m3fn,
192 + label,
193 + sub_label,
194 + fused_impl,
195 + "fused_fp8_impl",
196 + )
197 + )
198
199 print_timers(timers)
200
@@ -157,13 +209,12 @@ def print_timers(timers: Iterable[TMeasurement]):
209
210
211 def main():
160 - torch.set_default_device('cuda')
212 + torch.set_default_device("cuda")
213 bench_params = get_bench_params()
214
215 timers = []
216 for bp in tqdm(bench_params):
165 - timers.extend(
166 - bench(bp, "rms-norm-dynamic-per-token-quant", bp.description()))
217 + timers.extend(bench(bp, "rms-norm-dynamic-per-token-quant", bp.description()))
218 print_timers(timers)
219
220 # pickle all the results
@@ -172,5 +223,5 @@ def main():
223 pkl.dump(timers, f)
224
225
175 -if __name__ == '__main__':
226 +if __name__ == "__main__":
227 main()
benchmarks/kernels/benchmark_aqlm.py
+118 -78
@@ -9,32 +9,39 @@ import torch.nn.functional as F
9
10 from vllm import _custom_ops as ops
11 from vllm.model_executor.layers.quantization.aqlm import (
12 - dequantize_weight, generic_dequantize_gemm, get_int_dtype,
13 - optimized_dequantize_gemm)
12 + dequantize_weight,
13 + generic_dequantize_gemm,
14 + get_int_dtype,
15 + optimized_dequantize_gemm,
16 +)
17 from vllm.utils import FlexibleArgumentParser
18
16 -os.environ['CUDA_VISIBLE_DEVICES'] = '0'
19 +os.environ["CUDA_VISIBLE_DEVICES"] = "0"
20
21
22 def torch_mult(
20 - input: torch.Tensor, # [..., in_features]
21 - weights: torch.Tensor,
22 - scales: torch.Tensor, # [num_out_groups, 1, 1, 1]
23 + # [..., in_features]
24 + input: torch.Tensor,
25 + weights: torch.Tensor,
26 + # [num_out_groups, 1, 1, 1]
27 + scales: torch.Tensor,
28 ) -> torch.Tensor:
29 output = F.linear(input, weights)
30 return output
31
32
33 def dequant_out_scale(
29 - input: torch.Tensor, # [..., in_features]
30 - codes: torch.IntTensor, # [num_out_groups, num_in_groups, num_codebooks]
31 - codebooks: torch.
32 - Tensor, # [num_codebooks, codebook_size, out_group_size, in_group_size]
33 - scales: torch.Tensor, # [num_out_groups, 1, 1, 1]
34 + # [..., in_features]
35 + input: torch.Tensor,
36 + # [num_out_groups, num_in_groups, num_codebooks]
37 + codes: torch.IntTensor,
38 + # [num_codebooks, codebook_size, out_group_size, in_group_size]
39 + codebooks: torch.Tensor,
40 + # [num_out_groups, 1, 1, 1]
41 + scales: torch.Tensor,
42 output_partition_sizes: torch.IntTensor,
43 bias: Optional[torch.Tensor],
44 ) -> torch.Tensor:
37 -
45 weights = ops.aqlm_dequant(codes, codebooks, output_partition_sizes)
46
47 if bias is None:
@@ -46,40 +53,42 @@ def dequant_out_scale(
53 flattened_output *= b_scales
54 return flattened_output.view(orig_shape)
55 else:
49 - b_scales = scales.view(scales.shape[:-3] + (-1, )).expand(
50 - -1, weights.shape[1])
56 + b_scales = scales.view(scales.shape[:-3] + (-1,)).expand(-1, weights.shape[1])
57 weights *= b_scales
58 return F.linear(input, weights, bias)
59
60
61 def dequant_weight_scale(
56 - input: torch.Tensor, # [..., in_features]
57 - codes: torch.IntTensor, # [num_out_groups, num_in_groups, num_codebooks]
58 - codebooks: torch.
59 - Tensor, # [num_codebooks, codebook_size, out_group_size, in_group_size]
60 - scales: torch.Tensor, # [num_out_groups, 1, 1, 1]
62 + # [..., in_features]
63 + input: torch.Tensor,
64 + # [num_out_groups, num_in_groups, num_codebooks]
65 + codes: torch.IntTensor,
66 + # [num_codebooks, codebook_size, out_group_size, in_group_size]
67 + codebooks: torch.Tensor,
68 + # [num_out_groups, 1, 1, 1]
69 + scales: torch.Tensor,
70 output_partition_sizes: torch.IntTensor,
71 bias: Optional[torch.Tensor],
72 ) -> torch.Tensor:
64 -
73 weights = ops.aqlm_dequant(codes, codebooks, output_partition_sizes)
74
67 - b_scales = scales.view(scales.shape[:-3] + (-1, )).expand(
68 - -1, weights.shape[1])
75 + b_scales = scales.view(scales.shape[:-3] + (-1,)).expand(-1, weights.shape[1])
76 weights *= b_scales
77 return F.linear(input, weights, bias)
78
79
80 def dequant_no_scale(
74 - input: torch.Tensor, # [..., in_features]
75 - codes: torch.IntTensor, # [num_out_groups, num_in_groups, num_codebooks]
76 - codebooks: torch.
77 - Tensor, # [num_codebooks, codebook_size, out_group_size, in_group_size]
78 - scales: torch.Tensor, # [num_out_groups, 1, 1, 1]
81 + # [..., in_features]
82 + input: torch.Tensor,
83 + # [num_out_groups, num_in_groups, num_codebooks]
84 + codes: torch.IntTensor,
85 + # [num_codebooks, codebook_size, out_group_size, in_group_size]
86 + codebooks: torch.Tensor,
87 + # [num_out_groups, 1, 1, 1]
88 + scales: torch.Tensor,
89 output_partition_sizes: torch.IntTensor,
90 bias: Optional[torch.Tensor],
91 ) -> torch.Tensor:
82 -
92 weights = ops.aqlm_dequant(codes, codebooks, output_partition_sizes)
93
94 return F.linear(input, weights, bias)
@@ -89,23 +98,26 @@ def dequant_no_scale(
98 # the generic pytorch version.
99 # Just visual comparison.
100 def dequant_test(k: int, parts: torch.Tensor, nbooks: int, bits: int) -> None:
92 -
101 n = int(parts.sum().item())
102
95 - device = torch.device('cuda:0')
103 + device = torch.device("cuda:0")
104
105 code_range = (1 << bits) // 2
106 ingroups = 8
107
100 - codes = torch.randint(-code_range,
101 - code_range,
102 - size=(n, k // ingroups, nbooks),
103 - dtype=get_int_dtype(bits),
104 - device=device)
108 + codes = torch.randint(
109 + -code_range,
110 + code_range,
111 + size=(n, k // ingroups, nbooks),
112 + dtype=get_int_dtype(bits),
113 + device=device,
114 + )
115
106 - codebooks = torch.randn(size=(parts.shape[0] * nbooks, 1 << bits, 1, 8),
107 - dtype=torch.float16,
108 - device=device)
116 + codebooks = torch.randn(
117 + size=(parts.shape[0] * nbooks, 1 << bits, 1, 8),
118 + dtype=torch.float16,
119 + device=device,
120 + )
121
122 count = 0
123 for index in range(16):
@@ -138,24 +150,25 @@ def dequant_test(k: int, parts: torch.Tensor, nbooks: int, bits: int) -> None:
150
151
152 def main():
141 -
153 parser = FlexibleArgumentParser(description="Benchmark aqlm performance.")
154
155 # Add arguments
145 - parser.add_argument("--nbooks",
146 - type=int,
147 - default=1,
148 - help="Number of codebooks (default: 1)")
149 - parser.add_argument("--bits",
150 - type=int,
151 - default=16,
152 - help="Number of bits per code element (default: 16)")
156 + parser.add_argument(
157 + "--nbooks", type=int, default=1, help="Number of codebooks (default: 1)"
158 + )
159 + parser.add_argument(
160 + "--bits",
161 + type=int,
162 + default=16,
163 + help="Number of bits per code element (default: 16)",
164 + )
165 parser.add_argument(
166 "--test",
167 type=bool,
168 default=False,
169 help="Run the decompression/dequant tester rather than benchmarking "
158 - "(default: False)")
170 + "(default: False)",
171 + )
172
173 # Parse the arguments
174 args = parser.parse_args()
@@ -165,7 +178,7 @@ def main():
178 bits = args.bits
179
180 if args.test:
168 - dequant_test(4096, torch.tensor((4096, )), nbooks, bits)
181 + dequant_test(4096, torch.tensor((4096,)), nbooks, bits)
182 return
183
184 # Otherwise, benchmark.
@@ -184,31 +197,54 @@ def main():
197 with open(filename, "w") as f:
198 sys.stdout = f
199
187 - print('m | k | n | n parts', end='')
200 + print("m | k | n | n parts", end="")
201 for method in methods:
189 - print(f" | {method.__name__.replace('_', ' ')} (µs)", end='')
190 - print('')
202 + print(f" | {method.__name__.replace('_', ' ')} (µs)", end="")
203 + print("")
204
205 # These are reasonable prefill sizes.
193 - ksandpartions = ((4096, (4096, 4096, 4096)), (4096, (4096, )),
194 - (4096, (11008, 11008)), (11008, (4096, )))
206 + ksandpartions = (
207 + (4096, (4096, 4096, 4096)),
208 + (4096, (4096,)),
209 + (4096, (11008, 11008)),
210 + (11008, (4096,)),
211 + )
212
213 # reasonable ranges for m.
214 for m in [
198 - 1, 2, 4, 8, 10, 12, 14, 16, 24, 32, 48, 52, 56, 64, 96, 112,
199 - 128, 256, 512, 1024, 1536, 2048, 3072, 4096
215 + 1,
216 + 2,
217 + 4,
218 + 8,
219 + 10,
220 + 12,
221 + 14,
222 + 16,
223 + 24,
224 + 32,
225 + 48,
226 + 52,
227 + 56,
228 + 64,
229 + 96,
230 + 112,
231 + 128,
232 + 256,
233 + 512,
234 + 1024,
235 + 1536,
236 + 2048,
237 + 3072,
238 + 4096,
239 ]:
201 - print(f'{m}', file=sys.__stdout__)
240 + print(f"{m}", file=sys.__stdout__)
241 for ksp in ksandpartions:
203 - run_grid(m, ksp[0], torch.tensor(ksp[1]), nbooks, bits,
204 - methods)
242 + run_grid(m, ksp[0], torch.tensor(ksp[1]), nbooks, bits, methods)
243
244 sys.stdout = sys.__stdout__
245
246
209 -def run_grid(m: int, k: int, parts: torch.Tensor, nbooks: int, bits: int,
210 - methods):
211 -
247 +def run_grid(m: int, k: int, parts: torch.Tensor, nbooks: int, bits: int, methods):
248 # I didn't see visible improvements from increasing these, but feel free :)
249 num_warmup_trials = 1
250 num_trials = 1
@@ -229,7 +265,7 @@ def run_grid(m: int, k: int, parts: torch.Tensor, nbooks: int, bits: int,
265 )
266
267 n = parts.sum().item()
232 - print(f'{m} | {k} | {n} | {parts.tolist()}', end='')
268 + print(f"{m} | {k} | {n} | {parts.tolist()}", end="")
269
270 for method in methods:
271 best_time_us = 1e20
@@ -249,32 +285,36 @@ def run_grid(m: int, k: int, parts: torch.Tensor, nbooks: int, bits: int,
285 if kernel_dur_us < best_time_us:
286 best_time_us = kernel_dur_us
287
252 - print(f' | {kernel_dur_us:.0f}', end='')
288 + print(f" | {kernel_dur_us:.0f}", end="")
289
254 - print('')
290 + print("")
291
292
257 -def run_timing(num_calls: int, m: int, k: int, parts: torch.Tensor,
258 - nbooks: int, bits: int, method) -> float:
259 -
293 +def run_timing(
294 + num_calls: int, m: int, k: int, parts: torch.Tensor, nbooks: int, bits: int, method
295 +) -> float:
296 n = int(parts.sum().item())
297
262 - device = torch.device('cuda:0')
298 + device = torch.device("cuda:0")
299
300 input = torch.randn((1, m, k), dtype=torch.float16, device=device)
301
302 code_range = (1 << bits) // 2
303 ingroups = 8
304
269 - codes = torch.randint(-code_range,
270 - code_range,
271 - size=(n, k // ingroups, nbooks),
272 - dtype=get_int_dtype(bits),
273 - device=device)
274 -
275 - codebooks = torch.randn(size=(parts.shape[0] * nbooks, 1 << bits, 1, 8),
276 - dtype=torch.float16,
277 - device=device)
305 + codes = torch.randint(
306 + -code_range,
307 + code_range,
308 + size=(n, k // ingroups, nbooks),
309 + dtype=get_int_dtype(bits),
310 + device=device,
311 + )
312 +
313 + codebooks = torch.randn(
314 + size=(parts.shape[0] * nbooks, 1 << bits, 1, 8),
315 + dtype=torch.float16,
316 + device=device,
317 + )
318
319 scales = torch.randn(size=(n, 1, 1, 1), dtype=torch.float16, device=device)
320
benchmarks/kernels/benchmark_bitblas.py
+33 -28
@@ -3,27 +3,33 @@
3 # Licensed under the MIT License.
4
5 from vllm.model_executor.layers.quantization.utils.bitblas_utils import (
6 - MINIMUM_BITBLAS_VERSION)
6 + MINIMUM_BITBLAS_VERSION,
7 +)
8
9 try:
10 import bitblas
11 +
12 if bitblas.__version__ < MINIMUM_BITBLAS_VERSION:
11 - raise ImportError("bitblas version is wrong. Please "
12 - f"install bitblas>={MINIMUM_BITBLAS_VERSION}")
13 + raise ImportError(
14 + "bitblas version is wrong. Please "
15 + f"install bitblas>={MINIMUM_BITBLAS_VERSION}"
16 + )
17 except ImportError as e:
18 bitblas_import_exception = e
15 - raise ValueError("Trying to use the bitblas backend, but could not import"
16 - f"with the following error: {bitblas_import_exception}. "
17 - "Please install bitblas through the following command: "
18 - f"`pip install bitblas>={MINIMUM_BITBLAS_VERSION}`"
19 - ) from bitblas_import_exception
19 + raise ValueError(
20 + "Trying to use the bitblas backend, but could not import"
21 + f"with the following error: {bitblas_import_exception}. "
22 + "Please install bitblas through the following command: "
23 + f"`pip install bitblas>={MINIMUM_BITBLAS_VERSION}`"
24 + ) from bitblas_import_exception
25
26 from bitblas import Matmul, MatmulConfig, auto_detect_nvidia_target
27
28 from vllm.utils import FlexibleArgumentParser
29
30 parser = FlexibleArgumentParser(
26 - description="Benchmark BitBLAS int4 on a specific target.")
31 + description="Benchmark BitBLAS int4 on a specific target."
32 +)
33
34 # Add arguments to the parser
35 parser.add_argument(
@@ -32,10 +38,9 @@ parser.add_argument(
38 default=auto_detect_nvidia_target(),
39 help="Specify the target device for benchmarking.",
40 )
35 -parser.add_argument("--group_size",
36 - type=int,
37 - default=None,
38 - help="Group size for grouped quantization.")
41 +parser.add_argument(
42 + "--group_size", type=int, default=None, help="Group size for grouped quantization."
43 +)
44 parser.add_argument(
45 "--A_dtype",
46 type=str,
@@ -82,17 +87,17 @@ parser.add_argument(
87 choices=["nt", "nn"],
88 help="Matrix layout, 'nt' for non-transpose A and transpose W.",
89 )
85 -parser.add_argument("--with_bias",
86 - action="store_true",
87 - help="Include bias in the benchmark.")
90 +parser.add_argument(
91 + "--with_bias", action="store_true", help="Include bias in the benchmark."
92 +)
93 parser.add_argument(
94 "--with_scaling",
95 action="store_true",
96 help="Include scaling factor in the quantization.",
97 )
93 -parser.add_argument("--with_zeros",
94 - action="store_true",
95 - help="Include zeros in the quantization.")
98 +parser.add_argument(
99 + "--with_zeros", action="store_true", help="Include zeros in the quantization."
100 +)
101 parser.add_argument(
102 "--zeros_mode",
103 type=str,
@@ -170,8 +175,7 @@ shapes = [
175 ]
176
177 # Build test shapes with all the shared arguments
173 -test_shapes = [(MatmulConfig, Matmul, (*shape, *shared_args))
174 - for shape in shapes]
178 +test_shapes = [(MatmulConfig, Matmul, (*shape, *shared_args)) for shape in shapes]
179
180 benchmark_sets = []
181 benchmark_sets.extend(test_shapes)
@@ -206,12 +210,12 @@ for config_key, values in benchmark_results.items():
210 func_name = args_split[0]
211 input_args_str = "-".join(args_split[1:])
212 col_widths[0] = max(col_widths[0], len(func_name) + 2, len(headers[0]) + 2)
209 - col_widths[1] = max(col_widths[1],
210 - len(input_args_str) + 2,
211 - len(headers[1]) + 2)
212 - col_widths[2] = max(col_widths[2],
213 - len(f"{values['BitBLAS_top20_latency']:.3f} ms") + 2,
214 - len(headers[2]) + 2)
213 + col_widths[1] = max(col_widths[1], len(input_args_str) + 2, len(headers[1]) + 2)
214 + col_widths[2] = max(
215 + col_widths[2],
216 + len(f"{values['BitBLAS_top20_latency']:.3f} ms") + 2,
217 + len(headers[2]) + 2,
218 + )
219 # break only if you want to measure widths from a single example;
220 # otherwise, let it loop over all items.
221
@@ -232,5 +236,6 @@ for config_key, values in benchmark_results.items():
236 f"{values['BitBLAS_top20_latency']:.3f} ms",
237 ]
238 row_str = "".join(
235 - [str(cell).ljust(col_widths[idx]) for idx, cell in enumerate(row)])
239 + [str(cell).ljust(col_widths[idx]) for idx, cell in enumerate(row)]
240 + )
241 print(row_str)
benchmarks/kernels/benchmark_cutlass_fp4_moe.py
+249 -168
@@ -5,6 +5,7 @@ kernel. The cutlass_moe_fp4 kernel takes in fp4 quantized weights and 16-bit
5 activations. The triton_moe kernel takes in fp8 weights(tensor scaled to fp8)
6 and 16-bit activations.
7 """
8 +
9 import nvtx
10 import torch
11 import torch.utils.benchmark as benchmark
@@ -12,8 +13,7 @@ import torch.utils.benchmark as benchmark
13 from vllm import _custom_ops as ops
14 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
15 from vllm.model_executor.layers.fused_moe.cutlass_moe import cutlass_moe_fp4
15 -from vllm.model_executor.layers.fused_moe.fused_moe import (fused_experts,
16 - fused_topk)
16 +from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts, fused_topk
17 from vllm.scalar_type import scalar_types
18 from vllm.utils import FlexibleArgumentParser
19
@@ -38,19 +38,27 @@ FLOAT8_E4M3_MAX = torch.finfo(torch.float8_e4m3fn).max
38
39 def to_fp8(tensor: torch.Tensor):
40 finfo = torch.finfo(torch.float8_e4m3fn)
41 - return torch.round(tensor.clamp(
42 - min=finfo.min, max=finfo.max)).to(dtype=torch.float8_e4m3fn)
41 + return torch.round(tensor.clamp(min=finfo.min, max=finfo.max)).to(
42 + dtype=torch.float8_e4m3fn
43 + )
44
45
45 -def bench_run(results: list[benchmark.Measurement], model: str,
46 - num_experts: int, topk: int, per_act_token: bool,
47 - per_out_ch: bool, mkn: tuple[int, int, int]):
46 +def bench_run(
47 + results: list[benchmark.Measurement],
48 + model: str,
49 + num_experts: int,
50 + topk: int,
51 + per_act_token: bool,
52 + per_out_ch: bool,
53 + mkn: tuple[int, int, int],
54 +):
55 label = "NVFP4 Blockscaled CUTLASS MOE vs FP8 Tensor Scaled Triton"
56
57 sub_label = (
51 - "{}, num_experts={}, topk={}, per_act_token={} per_out_ch={}, "
52 - "MKN=({})".format(model, num_experts, topk, per_act_token, per_out_ch,
53 - mkn))
58 + "{}, num_experts={}, topk={}, per_act_token={} per_out_ch={}, MKN=({})".format(
59 + model, num_experts, topk, per_act_token, per_out_ch, mkn
60 + )
61 + )
62
63 print(f"Testing: {sub_label}")
64
@@ -64,18 +72,12 @@ def bench_run(results: list[benchmark.Measurement], model: str,
72
73 _, a_fp8_scale = ops.scaled_fp8_quant(a)
74
67 - w1_fp8q = torch.empty((num_experts, 2 * n, k),
68 - device=device,
69 - dtype=torch.float8_e4m3fn)
70 - w2_fp8q = torch.empty((num_experts, k, n),
71 - device=device,
72 - dtype=torch.float8_e4m3fn)
73 - w1_fp8scale = torch.empty((num_experts, 1, 1),
74 - device=device,
75 - dtype=torch.float32)
76 - w2_fp8scale = torch.empty((num_experts, 1, 1),
77 - device=device,
78 - dtype=torch.float32)
75 + w1_fp8q = torch.empty(
76 + (num_experts, 2 * n, k), device=device, dtype=torch.float8_e4m3fn
77 + )
78 + w2_fp8q = torch.empty((num_experts, k, n), device=device, dtype=torch.float8_e4m3fn)
79 + w1_fp8scale = torch.empty((num_experts, 1, 1), device=device, dtype=torch.float32)
80 + w2_fp8scale = torch.empty((num_experts, 1, 1), device=device, dtype=torch.float32)
81
82 for expert in range(num_experts):
83 w1_fp8q[expert], w1_fp8scale[expert] = ops.scaled_fp8_quant(w1[expert])
@@ -91,26 +93,24 @@ def bench_run(results: list[benchmark.Measurement], model: str,
93 topk_weights, topk_ids = fused_topk(a, score, topk, renormalize=False)
94
95 quant_blocksize = 16
94 - w1_blockscale = torch.empty((num_experts, 2 * n, k // quant_blocksize),
95 - device=device,
96 - dtype=torch.float8_e4m3fn)
97 - w2_blockscale = torch.empty((num_experts, k, n // quant_blocksize),
98 - device=device,
99 - dtype=torch.float8_e4m3fn)
96 + w1_blockscale = torch.empty(
97 + (num_experts, 2 * n, k // quant_blocksize),
98 + device=device,
99 + dtype=torch.float8_e4m3fn,
100 + )
101 + w2_blockscale = torch.empty(
102 + (num_experts, k, n // quant_blocksize), device=device, dtype=torch.float8_e4m3fn
103 + )
104
105 # n_b_scales = 2 * n if per_out_ch else 1
106 # k_b_scales = k if per_out_ch else 1
103 - w1_fp4 = torch.empty((num_experts, 2 * n, k // 2),
104 - device=device,
105 - dtype=torch.uint8)
106 - w2_fp4 = torch.empty((num_experts, k, n // 2),
107 - device=device,
108 - dtype=torch.uint8)
109 -
110 - w1_gs = torch.empty((num_experts, ), device=device, dtype=torch.float32)
111 - w2_gs = torch.empty((num_experts, ), device=device, dtype=torch.float32)
112 - a1_gs = torch.ones((num_experts, ), device=device, dtype=torch.float32)
113 - a2_gs = torch.ones((num_experts, ), device=device, dtype=torch.float32)
107 + w1_fp4 = torch.empty((num_experts, 2 * n, k // 2), device=device, dtype=torch.uint8)
108 + w2_fp4 = torch.empty((num_experts, k, n // 2), device=device, dtype=torch.uint8)
109 +
110 + w1_gs = torch.empty((num_experts,), device=device, dtype=torch.float32)
111 + w2_gs = torch.empty((num_experts,), device=device, dtype=torch.float32)
112 + a1_gs = torch.ones((num_experts,), device=device, dtype=torch.float32)
113 + a2_gs = torch.ones((num_experts,), device=device, dtype=torch.float32)
114
115 for expert in range(num_experts):
116 w1_e = w1[expert]
@@ -121,96 +121,141 @@ def bench_run(results: list[benchmark.Measurement], model: str,
121 w2_gs[expert] = FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX / w2_amax
122
123 w1_fp4[expert], w1_blockscale[expert] = ops.scaled_fp4_quant(
124 - w1_e, w1_gs[expert])
124 + w1_e, w1_gs[expert]
125 + )
126
127 w2_fp4[expert], w2_blockscale[expert] = ops.scaled_fp4_quant(
127 - w2_e, w2_gs[expert])
128 -
129 - def run_triton_moe(a: torch.Tensor, w1: torch.Tensor, w2: torch.Tensor,
130 - topk_weights: torch.Tensor, topk_ids: torch.Tensor,
131 - w1_scale: torch.Tensor, w2_scale: torch.Tensor,
132 - a_fp8_scale: torch.Tensor, num_repeats: int):
128 + w2_e, w2_gs[expert]
129 + )
130 +
131 + def run_triton_moe(
132 + a: torch.Tensor,
133 + w1: torch.Tensor,
134 + w2: torch.Tensor,
135 + topk_weights: torch.Tensor,
136 + topk_ids: torch.Tensor,
137 + w1_scale: torch.Tensor,
138 + w2_scale: torch.Tensor,
139 + a_fp8_scale: torch.Tensor,
140 + num_repeats: int,
141 + ):
142 for _ in range(num_repeats):
134 - fused_experts(a,
135 - w1,
136 - w2,
137 - topk_weights,
138 - topk_ids,
139 - use_fp8_w8a8=True,
140 - w1_scale=w1_scale,
141 - w2_scale=w2_scale,
142 - a1_scale=a_fp8_scale)
143 -
144 - def run_cutlass_moe_fp4(a: torch.Tensor, w1_fp4: torch.Tensor,
145 - w2_fp4: torch.Tensor, w1_blockscale: torch.Tensor,
146 - w2_blockscale: torch.Tensor, w1_gs: torch.Tensor,
147 - w2_gs: torch.Tensor, a1_gs: torch.Tensor,
148 - a2_gs: torch.Tensor, topk_weights: torch.Tensor,
149 - topk_ids: torch.Tensor, m: int, n: int, k: int,
150 - e: int, device: torch.device, num_repeats: int):
143 + fused_experts(
144 + a,
145 + w1,
146 + w2,
147 + topk_weights,
148 + topk_ids,
149 + use_fp8_w8a8=True,
150 + w1_scale=w1_scale,
151 + w2_scale=w2_scale,
152 + a1_scale=a_fp8_scale,
153 + )
154 +
155 + def run_cutlass_moe_fp4(
156 + a: torch.Tensor,
157 + w1_fp4: torch.Tensor,
158 + w2_fp4: torch.Tensor,
159 + w1_blockscale: torch.Tensor,
160 + w2_blockscale: torch.Tensor,
161 + w1_gs: torch.Tensor,
162 + w2_gs: torch.Tensor,
163 + a1_gs: torch.Tensor,
164 + a2_gs: torch.Tensor,
165 + topk_weights: torch.Tensor,
166 + topk_ids: torch.Tensor,
167 + m: int,
168 + n: int,
169 + k: int,
170 + e: int,
171 + device: torch.device,
172 + num_repeats: int,
173 + ):
174 for _ in range(num_repeats):
175 with nvtx.annotate("cutlass_moe_fp4", color="green"):
153 - cutlass_moe_fp4(a=a,
154 - a1_gscale=a1_gs,
155 - a2_gscale=a2_gs,
156 - w1_fp4=w1_fp4,
157 - w1_blockscale=w1_blockscale,
158 - w1_alphas=w1_gs,
159 - w2_fp4=w2_fp4,
160 - w2_blockscale=w2_blockscale,
161 - w2_alphas=w2_gs,
162 - topk_weights=topk_weights,
163 - topk_ids=topk_ids,
164 - m=m,
165 - n=n,
166 - k=k,
167 - e=num_experts,
168 - device=device)
176 + cutlass_moe_fp4(
177 + a=a,
178 + a1_gscale=a1_gs,
179 + a2_gscale=a2_gs,
180 + w1_fp4=w1_fp4,
181 + w1_blockscale=w1_blockscale,
182 + w1_alphas=w1_gs,
183 + w2_fp4=w2_fp4,
184 + w2_blockscale=w2_blockscale,
185 + w2_alphas=w2_gs,
186 + topk_weights=topk_weights,
187 + topk_ids=topk_ids,
188 + m=m,
189 + n=n,
190 + k=k,
191 + e=num_experts,
192 + device=device,
193 + )
194
195 def run_cutlass_from_graph(
171 - a: torch.Tensor, a1_gscale: torch.Tensor, w1_fp4: torch.Tensor,
172 - w1_blockscale: torch.Tensor, w1_alphas: torch.Tensor,
173 - a2_gscale: torch.Tensor, w2_fp4: torch.Tensor,
174 - w2_blockscale: torch.Tensor, w2_alphas: torch.Tensor,
175 - topk_weights: torch.Tensor, topk_ids: torch.Tensor, m: int, n: int,
176 - k: int, e: int, device: torch.device):
196 + a: torch.Tensor,
197 + a1_gscale: torch.Tensor,
198 + w1_fp4: torch.Tensor,
199 + w1_blockscale: torch.Tensor,
200 + w1_alphas: torch.Tensor,
201 + a2_gscale: torch.Tensor,
202 + w2_fp4: torch.Tensor,
203 + w2_blockscale: torch.Tensor,
204 + w2_alphas: torch.Tensor,
205 + topk_weights: torch.Tensor,
206 + topk_ids: torch.Tensor,
207 + m: int,
208 + n: int,
209 + k: int,
210 + e: int,
211 + device: torch.device,
212 + ):
213 with set_current_vllm_config(
178 - VllmConfig(parallel_config=ParallelConfig(
179 - pipeline_parallel_size=1))):
180 - return cutlass_moe_fp4(a=a,
181 - a1_gscale=a1_gs,
182 - w1_fp4=w1_fp4,
183 - w1_blockscale=w1_blockscale,
184 - w1_alphas=w1_alphas,
185 - a2_gscale=a2_gs,
186 - w2_fp4=w2_fp4,
187 - w2_blockscale=w2_blockscale,
188 - w2_alphas=w2_alphas,
189 - topk_weights=topk_weights,
190 - topk_ids=topk_ids,
191 - m=m,
192 - n=n,
193 - k=k,
194 - e=num_experts,
195 - device=device)
196 -
197 - def run_triton_from_graph(a: torch.Tensor, w1: torch.Tensor,
198 - w2: torch.Tensor, topk_weights: torch.Tensor,
199 - topk_ids: torch.Tensor, w1_scale: torch.Tensor,
200 - w2_scale: torch.Tensor,
201 - a_fp8_scale: torch.Tensor):
214 + VllmConfig(parallel_config=ParallelConfig(pipeline_parallel_size=1))
215 + ):
216 + return cutlass_moe_fp4(
217 + a=a,
218 + a1_gscale=a1_gs,
219 + w1_fp4=w1_fp4,
220 + w1_blockscale=w1_blockscale,
221 + w1_alphas=w1_alphas,
222 + a2_gscale=a2_gs,
223 + w2_fp4=w2_fp4,
224 + w2_blockscale=w2_blockscale,
225 + w2_alphas=w2_alphas,
226 + topk_weights=topk_weights,
227 + topk_ids=topk_ids,
228 + m=m,
229 + n=n,
230 + k=k,
231 + e=num_experts,
232 + device=device,
233 + )
234 +
235 + def run_triton_from_graph(
236 + a: torch.Tensor,
237 + w1: torch.Tensor,
238 + w2: torch.Tensor,
239 + topk_weights: torch.Tensor,
240 + topk_ids: torch.Tensor,
241 + w1_scale: torch.Tensor,
242 + w2_scale: torch.Tensor,
243 + a_fp8_scale: torch.Tensor,
244 + ):
245 with set_current_vllm_config(
203 - VllmConfig(parallel_config=ParallelConfig(
204 - pipeline_parallel_size=1))):
205 - return fused_experts(a,
206 - w1,
207 - w2,
208 - topk_weights,
209 - topk_ids,
210 - use_fp8_w8a8=True,
211 - w1_scale=w1_scale,
212 - w2_scale=w2_scale,
213 - a1_scale=a_fp8_scale)
246 + VllmConfig(parallel_config=ParallelConfig(pipeline_parallel_size=1))
247 + ):
248 + return fused_experts(
249 + a,
250 + w1,
251 + w2,
252 + topk_weights,
253 + topk_ids,
254 + use_fp8_w8a8=True,
255 + w1_scale=w1_scale,
256 + w2_scale=w2_scale,
257 + a1_scale=a_fp8_scale,
258 + )
259
260 def replay_graph(graph, num_repeats):
261 for _ in range(num_repeats):
@@ -220,30 +265,39 @@ def bench_run(results: list[benchmark.Measurement], model: str,
265 cutlass_stream = torch.cuda.Stream()
266 cutlass_graph = torch.cuda.CUDAGraph()
267 with torch.cuda.graph(cutlass_graph, stream=cutlass_stream):
223 - run_cutlass_from_graph(a=a,
224 - a1_gscale=a1_gs,
225 - w1_fp4=w1_fp4,
226 - w1_blockscale=w1_blockscale,
227 - w1_alphas=w1_gs,
228 - a2_gscale=a2_gs,
229 - w2_fp4=w2_fp4,
230 - w2_blockscale=w2_blockscale,
231 - w2_alphas=w2_gs,
232 - topk_weights=topk_weights,
233 - topk_ids=topk_ids,
234 - m=m,
235 - n=n,
236 - k=k,
237 - e=num_experts,
238 - device=device)
268 + run_cutlass_from_graph(
269 + a=a,
270 + a1_gscale=a1_gs,
271 + w1_fp4=w1_fp4,
272 + w1_blockscale=w1_blockscale,
273 + w1_alphas=w1_gs,
274 + a2_gscale=a2_gs,
275 + w2_fp4=w2_fp4,
276 + w2_blockscale=w2_blockscale,
277 + w2_alphas=w2_gs,
278 + topk_weights=topk_weights,
279 + topk_ids=topk_ids,
280 + m=m,
281 + n=n,
282 + k=k,
283 + e=num_experts,
284 + device=device,
285 + )
286 torch.cuda.synchronize()
287
288 triton_stream = torch.cuda.Stream()
289 triton_graph = torch.cuda.CUDAGraph()
290 with torch.cuda.graph(triton_graph, stream=triton_stream):
244 - run_triton_from_graph(a, w1_fp8q_notransp, w2_fp8q_notransp,
245 - topk_weights, topk_ids, w1_fp8scale, w2_fp8scale,
246 - a_fp8_scale)
291 + run_triton_from_graph(
292 + a,
293 + w1_fp8q_notransp,
294 + w2_fp8q_notransp,
295 + topk_weights,
296 + topk_ids,
297 + w1_fp8scale,
298 + w2_fp8scale,
299 + a_fp8_scale,
300 + )
301 torch.cuda.synchronize()
302
303 min_run_time = 5
@@ -290,18 +344,27 @@ def bench_run(results: list[benchmark.Measurement], model: str,
344 }
345
346 # Warmup
293 - run_triton_moe(a, w1_fp8q_notransp, w2_fp8q_notransp, topk_weights,
294 - topk_ids, w1_fp8scale, w2_fp8scale, a_fp8_scale, num_warmup)
347 + run_triton_moe(
348 + a,
349 + w1_fp8q_notransp,
350 + w2_fp8q_notransp,
351 + topk_weights,
352 + topk_ids,
353 + w1_fp8scale,
354 + w2_fp8scale,
355 + a_fp8_scale,
356 + num_warmup,
357 + )
358
359 results.append(
360 benchmark.Timer(
298 - stmt=
299 - "run_triton_moe(a, w1_fp8q_notransp, w2_fp8q_notransp, topk_weights, topk_ids, w1_fp8scale, w2_fp8scale, a_fp8_scale, num_runs)", # noqa: E501
361 + stmt="run_triton_moe(a, w1_fp8q_notransp, w2_fp8q_notransp, topk_weights, topk_ids, w1_fp8scale, w2_fp8scale, a_fp8_scale, num_runs)", # noqa: E501
362 globals=globals,
363 label=label,
364 sub_label=sub_label,
365 description="triton_moe",
304 - ).blocked_autorange(min_run_time=min_run_time))
366 + ).blocked_autorange(min_run_time=min_run_time)
367 + )
368
369 # Warmup
370 replay_graph(triton_graph, num_warmup)
@@ -313,23 +376,40 @@ def bench_run(results: list[benchmark.Measurement], model: str,
376 label=label,
377 sub_label=sub_label,
378 description="triton_moe_cuda_graphs",
316 - ).blocked_autorange(min_run_time=min_run_time))
379 + ).blocked_autorange(min_run_time=min_run_time)
380 + )
381
382 # Warmup
383
320 - run_cutlass_moe_fp4(a, w1_fp4, w2_fp4, w1_blockscale, w2_blockscale, w1_gs,
321 - w2_gs, a1_gs, a2_gs, topk_weights, topk_ids, m, n, k,
322 - num_experts, device, num_warmup)
384 + run_cutlass_moe_fp4(
385 + a,
386 + w1_fp4,
387 + w2_fp4,
388 + w1_blockscale,
389 + w2_blockscale,
390 + w1_gs,
391 + w2_gs,
392 + a1_gs,
393 + a2_gs,
394 + topk_weights,
395 + topk_ids,
396 + m,
397 + n,
398 + k,
399 + num_experts,
400 + device,
401 + num_warmup,
402 + )
403
404 results.append(
405 benchmark.Timer(
326 - stmt=
327 - "run_cutlass_moe_fp4(a, w1_fp4, w2_fp4, w1_blockscale, w2_blockscale, w1_alphas, w2_alphas, a1_gscale, a2_gscale, topk_weights, topk_ids, m, n, k, e, device, num_runs)", # noqa: E501
406 + stmt="run_cutlass_moe_fp4(a, w1_fp4, w2_fp4, w1_blockscale, w2_blockscale, w1_alphas, w2_alphas, a1_gscale, a2_gscale, topk_weights, topk_ids, m, n, k, e, device, num_runs)", # noqa: E501
407 globals=globals,
408 label=label,
409 sub_label=sub_label,
410 description="cutlass_moe_fp4",
332 - ).blocked_autorange(min_run_time=min_run_time))
411 + ).blocked_autorange(min_run_time=min_run_time)
412 + )
413
414 # Warmup
415 replay_graph(cutlass_graph, num_warmup)
@@ -341,7 +421,8 @@ def bench_run(results: list[benchmark.Measurement], model: str,
421 label=label,
422 sub_label=sub_label,
423 description="cutlass_moe_fp4_cuda_graphs",
344 - ).blocked_autorange(min_run_time=min_run_time))
424 + ).blocked_autorange(min_run_time=min_run_time)
425 + )
426
427
428 def main(args):
@@ -369,8 +450,15 @@ def main(args):
450 for per_out_ch in PER_OUT_CH_OPTS:
451 for size_m in args.batch_sizes:
452 mkn = (size_m, size_k, size_n)
372 - bench_run(results, model, num_experts, topk,
373 - per_act_token, per_out_ch, mkn)
453 + bench_run(
454 + results,
455 + model,
456 + num_experts,
457 + topk,
458 + per_act_token,
459 + per_out_ch,
460 + mkn,
461 + )
462
463 compare = benchmark.Compare(results)
464 compare.print()
@@ -378,8 +466,8 @@ def main(args):
466
467 if __name__ == "__main__":
468 parser = FlexibleArgumentParser(
381 - description="Benchmark NVFP4 CUTLASS MOE across specified "
382 - "models/shapes/batches")
469 + description="Benchmark NVFP4 CUTLASS MOE across specified models/shapes/batches"
470 + )
471 parser.add_argument(
472 "--models",
473 nargs="+",
@@ -387,21 +475,14 @@ if __name__ == "__main__":
475 default=DEFAULT_MODELS,
476 choices=WEIGHT_SHAPES_MOE.keys(),
477 )
390 - parser.add_argument("--tp-sizes",
391 - nargs="+",
392 - type=int,
393 - default=DEFAULT_TP_SIZES)
394 - parser.add_argument("--batch-sizes",
395 - nargs="+",
396 - type=int,
397 - default=DEFAULT_BATCH_SIZES)
478 + parser.add_argument("--tp-sizes", nargs="+", type=int, default=DEFAULT_TP_SIZES)
479 + parser.add_argument(
480 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
481 + )
482 parser.add_argument("--limit-k", nargs="+", type=int, default=[])
483 parser.add_argument("--limit-n", nargs="+", type=int, default=[])
484 parser.add_argument("--limit-num-groups", nargs="+", type=int, default=[])
401 - parser.add_argument("--limit-per-act-token",
402 - nargs="+",
403 - type=int,
404 - default=[])
485 + parser.add_argument("--limit-per-act-token", nargs="+", type=int, default=[])
486 parser.add_argument("--limit-per-out-ch", nargs="+", type=int, default=[])
487
488 args = parser.parse_args()
benchmarks/kernels/benchmark_grouped_gemm_cutlass.py
+219 -144
@@ -6,14 +6,18 @@ from benchmark_shapes import WEIGHT_SHAPES_MOE
6
7 from vllm import _custom_ops as ops
8 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
9 -from vllm.model_executor.layers.fused_moe.fused_moe import (cutlass_moe_fp8,
10 - fused_experts,
11 - fused_topk)
9 +from vllm.model_executor.layers.fused_moe.fused_moe import (
10 + cutlass_moe_fp8,
11 + fused_experts,
12 + fused_topk,
13 +)
14 from vllm.utils import FlexibleArgumentParser
15
16 DEFAULT_MODELS = [
15 - "nm-testing/Mixtral-8x7B-Instruct-v0.1", "nm-testing/deepseekv2-lite",
16 - "ibm-granite/granite-3.0-1b-a400m", "ibm-granite/granite-3.0-3b-a800m"
17 + "nm-testing/Mixtral-8x7B-Instruct-v0.1",
18 + "nm-testing/deepseekv2-lite",
19 + "ibm-granite/granite-3.0-1b-a400m",
20 + "ibm-granite/granite-3.0-3b-a800m",
21 ]
22 DEFAULT_BATCH_SIZES = [1, 4, 8, 16, 32, 64, 128, 256, 512]
23 DEFAULT_TP_SIZES = [1]
@@ -24,19 +28,27 @@ PER_OUT_CH_OPTS = [False]
28
29 def to_fp8(tensor: torch.Tensor):
30 finfo = torch.finfo(torch.float8_e4m3fn)
27 - return torch.round(tensor.clamp(
28 - min=finfo.min, max=finfo.max)).to(dtype=torch.float8_e4m3fn)
31 + return torch.round(tensor.clamp(min=finfo.min, max=finfo.max)).to(
32 + dtype=torch.float8_e4m3fn
33 + )
34
35
31 -def bench_run(results: list[benchmark.Measurement], model: str,
32 - num_experts: int, topk: int, per_act_token: bool,
33 - per_out_ch: bool, mkn: tuple[int, int, int]):
36 +def bench_run(
37 + results: list[benchmark.Measurement],
38 + model: str,
39 + num_experts: int,
40 + topk: int,
41 + per_act_token: bool,
42 + per_out_ch: bool,
43 + mkn: tuple[int, int, int],
44 +):
45 label = "Quant Matmul"
46
47 sub_label = (
37 - "{}, num_experts={}, topk={}, per_act_token={} per_out_ch={}, "
38 - "MKN=({})".format(model, num_experts, topk, per_act_token, per_out_ch,
39 - mkn))
48 + "{}, num_experts={}, topk={}, per_act_token={} per_out_ch={}, MKN=({})".format(
49 + model, num_experts, topk, per_act_token, per_out_ch, mkn
50 + )
51 + )
52
53 print(f"Testing: {sub_label}")
54
@@ -50,35 +62,17 @@ def bench_run(results: list[benchmark.Measurement], model: str,
62
63 _, a_scale = ops.scaled_fp8_quant(a)
64
53 - w1_q = torch.empty((num_experts, 2 * n, k),
54 - device="cuda",
55 - dtype=torch.float8_e4m3fn)
56 - w2_q = torch.empty((num_experts, k, n),
57 - device="cuda",
58 - dtype=torch.float8_e4m3fn)
59 - w1_scale = torch.empty((num_experts, 1, 1),
60 - device="cuda",
61 - dtype=torch.float32)
62 - w2_scale = torch.empty((num_experts, 1, 1),
63 - device="cuda",
64 - dtype=torch.float32)
65 -
66 - ab_strides1 = torch.full((num_experts, ),
67 - k,
68 - device="cuda",
69 - dtype=torch.int64)
70 - c_strides1 = torch.full((num_experts, ),
71 - 2 * n,
72 - device="cuda",
73 - dtype=torch.int64)
74 - ab_strides2 = torch.full((num_experts, ),
75 - n,
76 - device="cuda",
77 - dtype=torch.int64)
78 - c_strides2 = torch.full((num_experts, ),
79 - k,
80 - device="cuda",
81 - dtype=torch.int64)
65 + w1_q = torch.empty(
66 + (num_experts, 2 * n, k), device="cuda", dtype=torch.float8_e4m3fn
67 + )
68 + w2_q = torch.empty((num_experts, k, n), device="cuda", dtype=torch.float8_e4m3fn)
69 + w1_scale = torch.empty((num_experts, 1, 1), device="cuda", dtype=torch.float32)
70 + w2_scale = torch.empty((num_experts, 1, 1), device="cuda", dtype=torch.float32)
71 +
72 + ab_strides1 = torch.full((num_experts,), k, device="cuda", dtype=torch.int64)
73 + c_strides1 = torch.full((num_experts,), 2 * n, device="cuda", dtype=torch.int64)
74 + ab_strides2 = torch.full((num_experts,), n, device="cuda", dtype=torch.int64)
75 + c_strides2 = torch.full((num_experts,), k, device="cuda", dtype=torch.int64)
76
77 for expert in range(num_experts):
78 w1_q[expert], w1_scale[expert] = ops.scaled_fp8_quant(w1[expert])
@@ -91,82 +85,120 @@ def bench_run(results: list[benchmark.Measurement], model: str,
85 score = torch.randn((m, num_experts), device="cuda", dtype=dtype)
86
87 topk_weights, topk_ids, token_expert_indices = fused_topk(
94 - a, score, topk, renormalize=False)
88 + a, score, topk, renormalize=False
89 + )
90
96 - def run_triton_moe(a: torch.Tensor, w1: torch.Tensor, w2: torch.Tensor,
97 - topk_weights: torch.Tensor, topk_ids: torch.Tensor,
98 - w1_scale: torch.Tensor, w2_scale: torch.Tensor,
99 - a_scale: torch.Tensor, num_repeats: int):
91 + def run_triton_moe(
92 + a: torch.Tensor,
93 + w1: torch.Tensor,
94 + w2: torch.Tensor,
95 + topk_weights: torch.Tensor,
96 + topk_ids: torch.Tensor,
97 + w1_scale: torch.Tensor,
98 + w2_scale: torch.Tensor,
99 + a_scale: torch.Tensor,
100 + num_repeats: int,
101 + ):
102 for _ in range(num_repeats):
101 - fused_experts(a,
102 - w1,
103 - w2,
104 - topk_weights,
105 - topk_ids,
106 - use_fp8_w8a8=True,
107 - w1_scale=w1_scale,
108 - w2_scale=w2_scale,
109 - a1_scale=a_scale)
110 -
111 - def run_cutlass_moe(a: torch.Tensor, a_scale: torch.Tensor,
112 - w1: torch.Tensor, w2: torch.Tensor,
113 - w1_scale: torch.Tensor, w2_scale: torch.Tensor,
114 - topk_weights: torch.Tensor, topk_ids: torch.Tensor,
115 - ab_strides1: torch.Tensor, c_strides1: torch.Tensor,
116 - ab_strides2: torch.Tensor, c_strides2: torch.Tensor,
117 - num_repeats: int):
103 + fused_experts(
104 + a,
105 + w1,
106 + w2,
107 + topk_weights,
108 + topk_ids,
109 + use_fp8_w8a8=True,
110 + w1_scale=w1_scale,
111 + w2_scale=w2_scale,
112 + a1_scale=a_scale,
113 + )
114 +
115 + def run_cutlass_moe(
116 + a: torch.Tensor,
117 + a_scale: torch.Tensor,
118 + w1: torch.Tensor,
119 + w2: torch.Tensor,
120 + w1_scale: torch.Tensor,
121 + w2_scale: torch.Tensor,
122 + topk_weights: torch.Tensor,
123 + topk_ids: torch.Tensor,
124 + ab_strides1: torch.Tensor,
125 + c_strides1: torch.Tensor,
126 + ab_strides2: torch.Tensor,
127 + c_strides2: torch.Tensor,
128 + num_repeats: int,
129 + ):
130 for _ in range(num_repeats):
119 - cutlass_moe_fp8(a,
120 - w1,
121 - w2,
122 - w1_scale,
123 - w2_scale,
124 - topk_weights,
125 - topk_ids,
126 - ab_strides1,
127 - c_strides1,
128 - ab_strides2,
129 - c_strides2,
130 - a1_scale=a_scale)
131 + cutlass_moe_fp8(
132 + a,
133 + w1,
134 + w2,
135 + w1_scale,
136 + w2_scale,
137 + topk_weights,
138 + topk_ids,
139 + ab_strides1,
140 + c_strides1,
141 + ab_strides2,
142 + c_strides2,
143 + a1_scale=a_scale,
144 + )
145
146 def run_cutlass_from_graph(
133 - a: torch.Tensor, a_scale: torch.Tensor, w1_q: torch.Tensor,
134 - w2_q: torch.Tensor, w1_scale: torch.Tensor, w2_scale: torch.Tensor,
135 - topk_weights: torch.Tensor, topk_ids: torch.Tensor,
136 - ab_strides1: torch.Tensor, c_strides1: torch.Tensor,
137 - ab_strides2: torch.Tensor, c_strides2: torch.Tensor):
147 + a: torch.Tensor,
148 + a_scale: torch.Tensor,
149 + w1_q: torch.Tensor,
150 + w2_q: torch.Tensor,
151 + w1_scale: torch.Tensor,
152 + w2_scale: torch.Tensor,
153 + topk_weights: torch.Tensor,
154 + topk_ids: torch.Tensor,
155 + ab_strides1: torch.Tensor,
156 + c_strides1: torch.Tensor,
157 + ab_strides2: torch.Tensor,
158 + c_strides2: torch.Tensor,
159 + ):
160 with set_current_vllm_config(
139 - VllmConfig(parallel_config=ParallelConfig(
140 - pipeline_parallel_size=1))):
141 - return cutlass_moe_fp8(a,
142 - w1_q,
143 - w2_q,
144 - w1_scale,
145 - w2_scale,
146 - topk_weights,
147 - topk_ids,
148 - ab_strides1,
149 - c_strides1,
150 - ab_strides2,
151 - c_strides2,
152 - a1_scale=a_scale)
153 -
154 - def run_triton_from_graph(a: torch.Tensor, w1: torch.Tensor,
155 - w2: torch.Tensor, topk_weights: torch.Tensor,
156 - topk_ids: torch.Tensor, w1_scale: torch.Tensor,
157 - w2_scale: torch.Tensor, a_scale: torch.Tensor):
161 + VllmConfig(parallel_config=ParallelConfig(pipeline_parallel_size=1))
162 + ):
163 + return cutlass_moe_fp8(
164 + a,
165 + w1_q,
166 + w2_q,
167 + w1_scale,
168 + w2_scale,
169 + topk_weights,
170 + topk_ids,
171 + ab_strides1,
172 + c_strides1,
173 + ab_strides2,
174 + c_strides2,
175 + a1_scale=a_scale,
176 + )
177 +
178 + def run_triton_from_graph(
179 + a: torch.Tensor,
180 + w1: torch.Tensor,
181 + w2: torch.Tensor,
182 + topk_weights: torch.Tensor,
183 + topk_ids: torch.Tensor,
184 + w1_scale: torch.Tensor,
185 + w2_scale: torch.Tensor,
186 + a_scale: torch.Tensor,
187 + ):
188 with set_current_vllm_config(
159 - VllmConfig(parallel_config=ParallelConfig(
160 - pipeline_parallel_size=1))):
161 - return fused_experts(a,
162 - w1,
163 - w2,
164 - topk_weights,
165 - topk_ids,
166 - use_fp8_w8a8=True,
167 - w1_scale=w1_scale,
168 - w2_scale=w2_scale,
169 - a1_scale=a_scale)
189 + VllmConfig(parallel_config=ParallelConfig(pipeline_parallel_size=1))
190 + ):
191 + return fused_experts(
192 + a,
193 + w1,
194 + w2,
195 + topk_weights,
196 + topk_ids,
197 + use_fp8_w8a8=True,
198 + w1_scale=w1_scale,
199 + w2_scale=w2_scale,
200 + a1_scale=a_scale,
201 + )
202
203 def replay_graph(graph, num_repeats):
204 for _ in range(num_repeats):
@@ -176,16 +208,35 @@ def bench_run(results: list[benchmark.Measurement], model: str,
208 cutlass_stream = torch.cuda.Stream()
209 cutlass_graph = torch.cuda.CUDAGraph()
210 with torch.cuda.graph(cutlass_graph, stream=cutlass_stream):
179 - run_cutlass_from_graph(a, a_scale, w1_q, w2_q, w1_scale, w2_scale,
180 - topk_weights, topk_ids, ab_strides1, c_strides1,
181 - ab_strides2, c_strides2)
211 + run_cutlass_from_graph(
212 + a,
213 + a_scale,
214 + w1_q,
215 + w2_q,
216 + w1_scale,
217 + w2_scale,
218 + topk_weights,
219 + topk_ids,
220 + ab_strides1,
221 + c_strides1,
222 + ab_strides2,
223 + c_strides2,
224 + )
225 torch.cuda.synchronize()
226
227 triton_stream = torch.cuda.Stream()
228 triton_graph = torch.cuda.CUDAGraph()
229 with torch.cuda.graph(triton_graph, stream=triton_stream):
187 - run_triton_from_graph(a, w1_q_notransp, w2_q_notransp, topk_weights,
188 - topk_ids, w1_scale, w2_scale, a_scale)
230 + run_triton_from_graph(
231 + a,
232 + w1_q_notransp,
233 + w2_q_notransp,
234 + topk_weights,
235 + topk_ids,
236 + w1_scale,
237 + w2_scale,
238 + a_scale,
239 + )
240 torch.cuda.synchronize()
241
242 min_run_time = 5
@@ -225,18 +276,27 @@ def bench_run(results: list[benchmark.Measurement], model: str,
276 }
277
278 # Warmup
228 - run_triton_moe(a, w1_q_notransp, w2_q_notransp, topk_weights, topk_ids,
229 - w1_scale, w2_scale, a_scale, num_warmup)
279 + run_triton_moe(
280 + a,
281 + w1_q_notransp,
282 + w2_q_notransp,
283 + topk_weights,
284 + topk_ids,
285 + w1_scale,
286 + w2_scale,
287 + a_scale,
288 + num_warmup,
289 + )
290
291 results.append(
292 benchmark.Timer(
233 - stmt=
234 - "run_triton_moe(a, w1_q_notransp, w2_q_notransp, topk_weights, topk_ids, w1_scale, w2_scale, a_scale, num_runs)", # noqa: E501
293 + stmt="run_triton_moe(a, w1_q_notransp, w2_q_notransp, topk_weights, topk_ids, w1_scale, w2_scale, a_scale, num_runs)", # noqa: E501
294 globals=globals,
295 label=label,
296 sub_label=sub_label,
297 description="triton_moe",
239 - ).blocked_autorange(min_run_time=min_run_time))
298 + ).blocked_autorange(min_run_time=min_run_time)
299 + )
300
301 # Warmup
302 replay_graph(triton_graph, num_warmup)
@@ -248,22 +308,35 @@ def bench_run(results: list[benchmark.Measurement], model: str,
308 label=label,
309 sub_label=sub_label,
310 description="triton_moe_cuda_graphs",
251 - ).blocked_autorange(min_run_time=min_run_time))
311 + ).blocked_autorange(min_run_time=min_run_time)
312 + )
313
314 # Warmup
254 - run_cutlass_moe(a, a_scale, w1_q, w2_q, w1_scale, w2_scale, topk_weights,
255 - topk_ids, ab_strides1, c_strides1, ab_strides2, c_strides2,
256 - num_warmup)
315 + run_cutlass_moe(
316 + a,
317 + a_scale,
318 + w1_q,
319 + w2_q,
320 + w1_scale,
321 + w2_scale,
322 + topk_weights,
323 + topk_ids,
324 + ab_strides1,
325 + c_strides1,
326 + ab_strides2,
327 + c_strides2,
328 + num_warmup,
329 + )
330
331 results.append(
332 benchmark.Timer(
260 - stmt=
261 - "run_cutlass_moe(a, a_scale, w1_q, w2_q, w1_scale, w2_scale, topk_weights, topk_ids, ab_strides1, c_strides1, ab_strides2, c_strides2, num_runs)", # noqa: E501
333 + stmt="run_cutlass_moe(a, a_scale, w1_q, w2_q, w1_scale, w2_scale, topk_weights, topk_ids, ab_strides1, c_strides1, ab_strides2, c_strides2, num_runs)", # noqa: E501
334 globals=globals,
335 label=label,
336 sub_label=sub_label,
337 description="grouped_gemm_moe",
266 - ).blocked_autorange(min_run_time=min_run_time))
338 + ).blocked_autorange(min_run_time=min_run_time)
339 + )
340
341 # Warmup
342 replay_graph(cutlass_graph, num_warmup)
@@ -275,7 +348,8 @@ def bench_run(results: list[benchmark.Measurement], model: str,
348 label=label,
349 sub_label=sub_label,
350 description="grouped_gemm_moe_cuda_graphs",
278 - ).blocked_autorange(min_run_time=min_run_time))
351 + ).blocked_autorange(min_run_time=min_run_time)
352 + )
353
354
355 def main(args):
@@ -303,8 +377,15 @@ def main(args):
377 for per_out_ch in PER_OUT_CH_OPTS:
378 for size_m in DEFAULT_BATCH_SIZES:
379 mkn = (size_m, size_k, size_n)
306 - bench_run(results, model, num_experts, topk,
307 - per_act_token, per_out_ch, mkn)
380 + bench_run(
381 + results,
382 + model,
383 + num_experts,
384 + topk,
385 + per_act_token,
386 + per_out_ch,
387 + mkn,
388 + )
389
390 compare = benchmark.Compare(results)
391 compare.print()
@@ -312,7 +393,8 @@ def main(args):
393
394 if __name__ == "__main__":
395 parser = FlexibleArgumentParser(
315 - description="Benchmark Marlin across specified models/shapes/batches")
396 + description="Benchmark Marlin across specified models/shapes/batches"
397 + )
398 parser.add_argument(
399 "--models",
400 nargs="+",
@@ -320,21 +402,14 @@ if __name__ == "__main__":
402 default=DEFAULT_MODELS,
403 choices=WEIGHT_SHAPES_MOE.keys(),
404 )
323 - parser.add_argument("--tp-sizes",
324 - nargs="+",
325 - type=int,
326 - default=DEFAULT_TP_SIZES)
327 - parser.add_argument("--batch-sizes",
328 - nargs="+",
329 - type=int,
330 - default=DEFAULT_BATCH_SIZES)
405 + parser.add_argument("--tp-sizes", nargs="+", type=int, default=DEFAULT_TP_SIZES)
406 + parser.add_argument(
407 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
408 + )
409 parser.add_argument("--limit-k", nargs="+", type=int, default=[])
410 parser.add_argument("--limit-n", nargs="+", type=int, default=[])
411 parser.add_argument("--limit-num-groups", nargs="+", type=int, default=[])
334 - parser.add_argument("--limit-per-act-token",
335 - nargs="+",
336 - type=int,
337 - default=[])
412 + parser.add_argument("--limit-per-act-token", nargs="+", type=int, default=[])
413 parser.add_argument("--limit-per-out-ch", nargs="+", type=int, default=[])
414
415 args = parser.parse_args()
benchmarks/kernels/benchmark_layernorm.py
+32 -28
@@ -10,14 +10,16 @@ from vllm.utils import STR_DTYPE_TO_TORCH_DTYPE, FlexibleArgumentParser
10
11
12 @torch.inference_mode()
13 -def main(num_tokens: int,
14 - hidden_size: int,
15 - add_residual: bool,
16 - dtype: torch.dtype,
17 - seed: int = 0,
18 - do_profile: bool = False,
19 - num_warmup_iters: int = 5,
20 - num_iters: int = 100) -> None:
13 +def main(
14 + num_tokens: int,
15 + hidden_size: int,
16 + add_residual: bool,
17 + dtype: torch.dtype,
18 + seed: int = 0,
19 + do_profile: bool = False,
20 + num_warmup_iters: int = 5,
21 + num_iters: int = 100,
22 +) -> None:
23 current_platform.seed_everything(seed)
24 torch.set_default_device("cuda")
25
@@ -56,33 +58,35 @@ def main(num_tokens: int,
58 print(f"Kernel running time: {latency * 1000000:.3f} us")
59
60
59 -if __name__ == '__main__':
60 - parser = FlexibleArgumentParser(
61 - description="Benchmark the layernorm kernel.")
61 +if __name__ == "__main__":
62 + parser = FlexibleArgumentParser(description="Benchmark the layernorm kernel.")
63 parser.add_argument("--num-tokens", type=int, default=4096)
64 parser.add_argument("--hidden-size", type=int, default=8192)
65 parser.add_argument("--add-residual", action="store_true")
65 - parser.add_argument("--dtype",
66 - type=str,
67 - choices=["half", "bfloat16", "float"],
68 - default="half")
66 + parser.add_argument(
67 + "--dtype", type=str, choices=["half", "bfloat16", "float"], default="half"
68 + )
69 parser.add_argument("--seed", type=int, default=0)
70 parser.add_argument("--profile", action="store_true")
71 parser.add_argument("--num-warmup-iters", type=int, default=5)
72 - parser.add_argument("--num-iters",
73 - type=int,
74 - default=100,
75 - help="Number of benchmark iterations. "
76 - "If --profile is set, this number is ignored")
72 + parser.add_argument(
73 + "--num-iters",
74 + type=int,
75 + default=100,
76 + help="Number of benchmark iterations. "
77 + "If --profile is set, this number is ignored",
78 + )
79
80 args = parser.parse_args()
81 print(args)
82
81 - main(num_tokens=args.num_tokens,
82 - hidden_size=args.hidden_size,
83 - add_residual=args.add_residual,
84 - dtype=STR_DTYPE_TO_TORCH_DTYPE[args.dtype],
85 - seed=args.seed,
86 - do_profile=args.profile,
87 - num_warmup_iters=args.num_warmup_iters,
88 - num_iters=args.num_iters)
83 + main(
84 + num_tokens=args.num_tokens,
85 + hidden_size=args.hidden_size,
86 + add_residual=args.add_residual,
87 + dtype=STR_DTYPE_TO_TORCH_DTYPE[args.dtype],
88 + seed=args.seed,
89 + do_profile=args.profile,
90 + num_warmup_iters=args.num_warmup_iters,
91 + num_iters=args.num_iters,
92 + )
benchmarks/kernels/benchmark_lora.py
+378 -275
@@ -20,18 +20,36 @@ from weight_shapes import WEIGHT_SHAPES
20 from vllm.triton_utils import HAS_TRITON
21
22 if HAS_TRITON:
23 - from vllm.lora.ops.triton_ops import (LoRAKernelMeta, lora_expand,
24 - lora_shrink)
25 - from vllm.lora.ops.triton_ops.utils import (_LORA_A_PTR_DICT,
26 - _LORA_B_PTR_DICT)
23 + from vllm.lora.ops.triton_ops import LoRAKernelMeta, lora_expand, lora_shrink
24 + from vllm.lora.ops.triton_ops.utils import _LORA_A_PTR_DICT, _LORA_B_PTR_DICT
25
26 from vllm.utils import FlexibleArgumentParser
27
28 DEFAULT_MODELS = list(WEIGHT_SHAPES.keys())
29 DEFAULT_TP_SIZES = [1]
30 DEFAULT_BATCH_SIZES = [
33 - 1, 16, 32, 64, 128, 192, 256, 320, 384, 448, 512, 640, 768, 896, 1024,
34 - 2048, 3072, 4096, 5120, 6144, 7168, 8192
31 + 1,
32 + 16,
33 + 32,
34 + 64,
35 + 128,
36 + 192,
37 + 256,
38 + 320,
39 + 384,
40 + 448,
41 + 512,
42 + 640,
43 + 768,
44 + 896,
45 + 1024,
46 + 2048,
47 + 3072,
48 + 4096,
49 + 5120,
50 + 6144,
51 + 7168,
52 + 8192,
53 ]
54 DEFAULT_HIDDEN_SIZES = [1024, 2048, 4096, 8192, 16384]
55 DEFAULT_LORA_RANKS = [16]
@@ -52,12 +70,9 @@ def dtype_to_str(dtype: torch.dtype):
70 raise ValueError(f"Unsupported dtype {dtype}")
71
72
55 -def make_rand_lora_weight_tensor(k: int,
56 - n: int,
57 - num_loras: int,
58 - dtype: torch.dtype,
59 - device: str = "cuda") -> torch.Tensor:
60 -
73 +def make_rand_lora_weight_tensor(
74 + k: int, n: int, num_loras: int, dtype: torch.dtype, device: str = "cuda"
75 +) -> torch.Tensor:
76 # LoRA weights column major
77 return torch.rand((num_loras, n, k), dtype=dtype).to(device)
78
@@ -78,18 +93,15 @@ def make_rand_tensors(
93 A = torch.rand(a_shape, dtype=a_dtype).to(device)
94
95 # LoRA weights column major
81 - Bs = [
82 - torch.rand(b_shape, dtype=b_dtype).to(device)
83 - for _ in range(num_slices)
84 - ]
96 + Bs = [torch.rand(b_shape, dtype=b_dtype).to(device) for _ in range(num_slices)]
97
98 C = torch.zeros(c_shape, dtype=c_dtype).to(device)
99 return A, Bs, C
100
101
90 -def make_prompt_lora_mapping(num_prompts: int, num_active_loras: int,
91 - sort_by_lora_id: bool,
92 - device: str) -> torch.Tensor:
102 +def make_prompt_lora_mapping(
103 + num_prompts: int, num_active_loras: int, sort_by_lora_id: bool, device: str
104 +) -> torch.Tensor:
105 """
106 All prompts are mapped to a LoRA ID in range [0, num_active_loras).
107 where 0 refers to first lora, 1 refers to second lora and so on.
@@ -97,9 +109,7 @@ def make_prompt_lora_mapping(num_prompts: int, num_active_loras: int,
109 assert num_active_loras > 0
110
111 if not sort_by_lora_id:
100 - return torch.randint(0,
101 - num_active_loras, (num_prompts, ),
102 - dtype=torch.long)
112 + return torch.randint(0, num_active_loras, (num_prompts,), dtype=torch.long)
113
114 # Divide LoRAs equally and in order.
115 part_size = num_prompts // num_active_loras
@@ -110,14 +120,18 @@ def make_prompt_lora_mapping(num_prompts: int, num_active_loras: int,
120 while len(prompt_lora_mapping) < num_prompts:
121 prompt_lora_mapping.extend([lora_id] * part_size)
122 lora_id = lora_id + 1 if lora_id + 1 < num_active_loras else lora_id
113 - return torch.tensor(prompt_lora_mapping[:num_prompts],
114 - dtype=torch.long,
115 - device=device)
116 -
117 -
118 -def make_token_lora_mapping(num_tokens: int, num_prompts: int,
119 - prompt_lora_mapping: torch.Tensor,
120 - seq_len_tensor: torch.Tensor, device: str):
123 + return torch.tensor(
124 + prompt_lora_mapping[:num_prompts], dtype=torch.long, device=device
125 + )
126 +
127 +
128 +def make_token_lora_mapping(
129 + num_tokens: int,
130 + num_prompts: int,
131 + prompt_lora_mapping: torch.Tensor,
132 + seq_len_tensor: torch.Tensor,
133 + device: str,
134 +):
135 """
136 Make token_lora_mapping from prompt_lora_mapping and seq_lens_tensor
137 """
@@ -136,11 +150,15 @@ def make_token_lora_mapping(num_tokens: int, num_prompts: int,
150 return torch.tensor(token_lora_mapping, dtype=torch.long, device=device)
151
152
139 -def ref_group_gemm(ref_out: torch.Tensor, input: torch.Tensor,
140 - lora_weights: list[torch.Tensor],
141 - seq_lens_cpu: torch.Tensor,
142 - prompt_lora_mapping_cpu: torch.Tensor, scaling: float,
143 - add_inputs: Optional[bool]):
153 +def ref_group_gemm(
154 + ref_out: torch.Tensor,
155 + input: torch.Tensor,
156 + lora_weights: list[torch.Tensor],
157 + seq_lens_cpu: torch.Tensor,
158 + prompt_lora_mapping_cpu: torch.Tensor,
159 + scaling: float,
160 + add_inputs: Optional[bool],
161 +):
162 """
163 Torch group gemm reference implementation to test correctness of
164 benchmarking operations.
@@ -149,7 +167,7 @@ def ref_group_gemm(ref_out: torch.Tensor, input: torch.Tensor,
167 out_list = []
168 current_offset = 0
169 for lora_index, b_length in zip(range(batches), seq_lens_cpu):
152 - x = input[current_offset:b_length + current_offset, :]
170 + x = input[current_offset : b_length + current_offset, :]
171 current_offset += b_length
172 w = lora_weights[prompt_lora_mapping_cpu[lora_index]]
173 result = torch.nn.functional.linear(x, w)
@@ -168,6 +186,7 @@ class OpType(Enum):
186 """
187 LoRA Ops to benchmark and its properties.
188 """
189 +
190 LORA_SHRINK = auto()
191 LORA_EXPAND = auto()
192
@@ -188,8 +207,9 @@ class OpType(Enum):
207 def num_slices(self) -> list[int]:
208 return [1, 2, 3]
209
191 - def mkn(self, batch_size: int, seq_length: int, hidden_size: int,
192 - lora_rank: int) -> tuple[int, int, int]:
210 + def mkn(
211 + self, batch_size: int, seq_length: int, hidden_size: int, lora_rank: int
212 + ) -> tuple[int, int, int]:
213 num_tokens = batch_size * seq_length
214 if self.is_shrink_fn():
215 m = num_tokens
@@ -203,7 +223,7 @@ class OpType(Enum):
223 return m, k, n
224
225 def matmul_dtypes(
206 - self, op_dtype: torch.dtype
226 + self, op_dtype: torch.dtype
227 ) -> tuple[torch.dtype, torch.dtype, torch.dtype]:
228 """
229 return a type, b type and c type for A x B = C
@@ -215,9 +235,14 @@ class OpType(Enum):
235 return torch.float32, op_dtype, op_dtype
236
237 def matmul_shapes(
218 - self, batch_size: int, seq_length: int, hidden_size: int,
219 - lora_rank: int, num_loras: int,
220 - num_slices: int) -> tuple[tuple[int], tuple[int], tuple[int]]:
238 + self,
239 + batch_size: int,
240 + seq_length: int,
241 + hidden_size: int,
242 + lora_rank: int,
243 + num_loras: int,
244 + num_slices: int,
245 + ) -> tuple[tuple[int], tuple[int], tuple[int]]:
246 """
247 Given num_slices, return the shapes of the A, B, and C matrices
248 in A x B = C, for the op_type
@@ -241,31 +266,38 @@ class OpType(Enum):
266
267 raise ValueError(f"Unrecognized optype {self}")
268
244 - def run_ref_group_gemm(self, output: torch.Tensor, input: torch.Tensor,
245 - lora_weights: list[torch.Tensor],
246 - **kwargs) -> Callable:
269 + def run_ref_group_gemm(
270 + self,
271 + output: torch.Tensor,
272 + input: torch.Tensor,
273 + lora_weights: list[torch.Tensor],
274 + **kwargs,
275 + ) -> Callable:
276 """Each benchmark operation expects the input, lora_weights and outputs
248 - in a slightly different format. Refer to self.matmul_shapes().
249 - run_ref_group_gemm accounts for those differences in executing a
250 - reference group gemm for correctness testing.
277 + in a slightly different format. Refer to self.matmul_shapes().
278 + run_ref_group_gemm accounts for those differences in executing a
279 + reference group gemm for correctness testing.
280 """
281 w_dtype = lora_weights[0].dtype
282 num_slices = len(lora_weights)
283 if self in [OpType.LORA_SHRINK]:
284 for slice_idx in range(num_slices):
256 - ref_group_gemm(ref_out=output[slice_idx, :],
257 - input=input,
258 - lora_weights=lora_weights[slice_idx],
259 - **kwargs)
285 + ref_group_gemm(
286 + ref_out=output[slice_idx, :],
287 + input=input,
288 + lora_weights=lora_weights[slice_idx],
289 + **kwargs,
290 + )
291 elif self in [OpType.LORA_EXPAND]:
292 hidden_size = lora_weights[0].shape[1]
293 for slice_idx in range(num_slices):
294 slice_offset = slice_idx * hidden_size
295 ref_group_gemm(
265 - ref_out=output[:, slice_offset:slice_offset + hidden_size],
296 + ref_out=output[:, slice_offset : slice_offset + hidden_size],
297 input=input[slice_idx].clone().to(dtype=w_dtype),
298 lora_weights=lora_weights[slice_idx],
268 - **kwargs)
299 + **kwargs,
300 + )
301 else:
302 raise ValueError(f"Unrecognized optype {self}")
303
@@ -275,6 +307,7 @@ class BenchmarkContext:
307 """
308 LoRA benchmark context
309 """
310 +
311 batch_size: int
312 hidden_size: int
313 num_loras: int
@@ -299,17 +332,18 @@ class BenchmarkContext:
332 return f"lora-{self.dtype}"
333
334 def bench_sublabel(self, op_type: OpType) -> str:
302 - m, k, n = op_type.mkn(self.batch_size, self.seq_length,
303 - self.hidden_size, self.lora_rank)
335 + m, k, n = op_type.mkn(
336 + self.batch_size, self.seq_length, self.hidden_size, self.lora_rank
337 + )
338 desc = {
305 - 'bs': self.batch_size,
306 - 'sl': self.seq_length,
307 - 'm': m,
308 - 'k': k,
309 - 'n': n,
310 - 'num_loras': self.num_loras,
311 - 'sort_by_lora': self.sort_by_lora_id,
312 - 'num_slices': self.num_slices,
339 + "bs": self.batch_size,
340 + "sl": self.seq_length,
341 + "m": m,
342 + "k": k,
343 + "n": n,
344 + "num_loras": self.num_loras,
345 + "sort_by_lora": self.sort_by_lora_id,
346 + "num_slices": self.num_slices,
347 }
348 return json.dumps(desc)
349
@@ -319,6 +353,7 @@ class BenchmarkTensors:
353 """
354 Input/Output tensors used for benchmarks
355 """
356 +
357 # matmul tensors
358 input: torch.Tensor
359 lora_weights_lst: list[torch.Tensor]
@@ -330,23 +365,29 @@ class BenchmarkTensors:
365 prompt_lora_mapping: torch.Tensor
366
367 def io_types(self) -> str:
333 - return (f"{dtype_to_str(self.input.dtype)}x"
334 - f"{dtype_to_str(self.lora_weights_lst[0].dtype)}=>"
335 - f"{dtype_to_str(self.output.dtype)}")
368 + return (
369 + f"{dtype_to_str(self.input.dtype)}x"
370 + f"{dtype_to_str(self.lora_weights_lst[0].dtype)}=>"
371 + f"{dtype_to_str(self.output.dtype)}"
372 + )
373
374 @staticmethod
338 - def make(ctx: BenchmarkContext,
339 - op_type: OpType,
340 - device: str = "cuda") -> "BenchmarkTensors":
341 -
375 + def make(
376 + ctx: BenchmarkContext, op_type: OpType, device: str = "cuda"
377 + ) -> "BenchmarkTensors":
378 # Make input / output matmul tensors.
379 a_shape, b_shape, c_shape = op_type.matmul_shapes(
344 - ctx.batch_size, ctx.seq_length, ctx.hidden_size, ctx.lora_rank,
345 - ctx.num_loras, ctx.num_slices)
380 + ctx.batch_size,
381 + ctx.seq_length,
382 + ctx.hidden_size,
383 + ctx.lora_rank,
384 + ctx.num_loras,
385 + ctx.num_slices,
386 + )
387 a_type, b_type, c_type = op_type.matmul_dtypes(ctx.dtype)
347 - input_tensor, lora_weights, output_tensor = \
348 - make_rand_tensors(a_shape, b_shape, c_shape, a_type, b_type, c_type,
349 - num_slices = ctx.num_slices)
388 + input_tensor, lora_weights, output_tensor = make_rand_tensors(
389 + a_shape, b_shape, c_shape, a_type, b_type, c_type, num_slices=ctx.num_slices
390 + )
391
392 # Make metadata tensors.
393 # Keep the metadata tensors in the CPU for further processing if needed.
@@ -356,27 +397,38 @@ class BenchmarkTensors:
397
398 # Make metadata tensors involved in correctness testing.
399 # Prepare seq lens tensor
359 - seq_len_tensor = torch.randint(ctx.seq_length, ctx.seq_length + 1,
360 - (ctx.batch_size, ))
400 + seq_len_tensor = torch.randint(
401 + ctx.seq_length, ctx.seq_length + 1, (ctx.batch_size,)
402 + )
403 assert total_tokens == seq_len_tensor.sum()
404 # Prepare prompt lora indices tensor
405 prompt_lora_indices_tensor = make_prompt_lora_mapping(
364 - ctx.batch_size, ctx.num_active_loras, ctx.sort_by_lora_id, "cpu")
406 + ctx.batch_size, ctx.num_active_loras, ctx.sort_by_lora_id, "cpu"
407 + )
408
409 # Make LoRAKernelMeta
410 token_lora_indices_tensor = make_token_lora_mapping(
368 - total_tokens, ctx.batch_size, prompt_lora_indices_tensor,
369 - seq_len_tensor, "cpu")
411 + total_tokens,
412 + ctx.batch_size,
413 + prompt_lora_indices_tensor,
414 + seq_len_tensor,
415 + "cpu",
416 + )
417 lora_kernel_meta = LoRAKernelMeta.make(
418 max_loras=ctx.num_loras,
419 max_num_tokens=token_lora_indices_tensor.size(0),
373 - device="cpu")
374 - lora_kernel_meta.prepare_tensors(
375 - token_lora_mapping=token_lora_indices_tensor)
376 -
377 - return BenchmarkTensors(input_tensor, lora_weights, output_tensor,
378 - lora_kernel_meta, seq_len_tensor,
379 - prompt_lora_indices_tensor)
420 + device="cpu",
421 + )
422 + lora_kernel_meta.prepare_tensors(token_lora_mapping=token_lora_indices_tensor)
423 +
424 + return BenchmarkTensors(
425 + input_tensor,
426 + lora_weights,
427 + output_tensor,
428 + lora_kernel_meta,
429 + seq_len_tensor,
430 + prompt_lora_indices_tensor,
431 + )
432
433 def sanity_check(self) -> None:
434 """
@@ -386,7 +438,7 @@ class BenchmarkTensors:
438 # check metadata tensors
439 assert torch.sum(self.seq_lens) == num_tokens
440 num_seqs = self.seq_lens.shape[0]
389 - #assert self.seq_start_loc.shape[0] == num_seqs
441 + # assert self.seq_start_loc.shape[0] == num_seqs
442 assert self.prompt_lora_mapping.shape[0] == num_seqs
443 assert self.lora_kernel_meta.token_lora_mapping.shape[0] == num_tokens
444
@@ -430,8 +482,11 @@ class BenchmarkTensors:
482 _, num_tokens, _, num_slices = self.metadata()
483
484 # Sanity check matrix shapes.
433 - i_shape, lw_shape, o_shape = self.input.shape, self.lora_weights_lst[
434 - 0].shape, self.output.shape
485 + i_shape, lw_shape, o_shape = (
486 + self.input.shape,
487 + self.lora_weights_lst[0].shape,
488 + self.output.shape,
489 + )
490 # Expected input shape [num_tokens, hidden_size]
491 assert len(i_shape) == 2
492 assert i_shape[0] == num_tokens
@@ -445,16 +500,17 @@ class BenchmarkTensors:
500 assert o_shape == (num_slices, num_tokens, lora_rank)
501
502 return {
448 - 'inputs': self.input,
449 - 'lora_a_weights': self.lora_weights_lst,
450 - 'output_tensor': self.output,
451 - 'token_lora_mapping': self.lora_kernel_meta.token_lora_mapping,
452 - 'token_indices_sorted_by_lora_ids':
453 - self.lora_kernel_meta.token_indices_sorted_by_lora_ids,
454 - 'num_tokens_per_lora': self.lora_kernel_meta.num_tokens_per_lora,
455 - 'lora_token_start_loc': self.lora_kernel_meta.lora_token_start_loc,
456 - 'lora_ids': self.lora_kernel_meta.active_lora_ids,
457 - 'scaling': 1.0,
503 + "inputs": self.input,
504 + "lora_a_weights": self.lora_weights_lst,
505 + "output_tensor": self.output,
506 + "token_lora_mapping": self.lora_kernel_meta.token_lora_mapping,
507 + "token_indices_sorted_by_lora_ids": (
508 + self.lora_kernel_meta.token_indices_sorted_by_lora_ids
509 + ),
510 + "num_tokens_per_lora": self.lora_kernel_meta.num_tokens_per_lora,
511 + "lora_token_start_loc": self.lora_kernel_meta.lora_token_start_loc,
512 + "lora_ids": self.lora_kernel_meta.active_lora_ids,
513 + "scaling": 1.0,
514 }
515
516 def as_lora_expand_kwargs(self, add_inputs: bool) -> dict[str, Any]:
@@ -464,8 +520,11 @@ class BenchmarkTensors:
520 _, num_tokens, _, num_slices = self.metadata()
521
522 # Sanity check matrix shapes.
467 - i_shape, lw_shape, o_shape = self.input.shape, self.lora_weights_lst[
468 - 0].shape, self.output.shape
523 + i_shape, lw_shape, o_shape = (
524 + self.input.shape,
525 + self.lora_weights_lst[0].shape,
526 + self.output.shape,
527 + )
528 # Expected input shape : [num_slices, num_tokens, lora_rank]
529 assert len(i_shape) == 3
530 assert i_shape[0] == num_slices
@@ -480,22 +539,23 @@ class BenchmarkTensors:
539 assert o_shape == (num_tokens, hidden_size * num_slices)
540
541 return {
483 - 'inputs': self.input,
484 - 'lora_b_weights': self.lora_weights_lst,
485 - 'output_tensor': self.output,
486 - 'token_lora_mapping': self.lora_kernel_meta.token_lora_mapping,
487 - 'token_indices_sorted_by_lora_ids':
488 - self.lora_kernel_meta.token_indices_sorted_by_lora_ids,
489 - 'num_tokens_per_lora': self.lora_kernel_meta.num_tokens_per_lora,
490 - 'lora_token_start_loc': self.lora_kernel_meta.lora_token_start_loc,
491 - 'lora_ids': self.lora_kernel_meta.active_lora_ids,
492 - 'offset_start': 0,
493 - 'add_inputs': add_inputs,
542 + "inputs": self.input,
543 + "lora_b_weights": self.lora_weights_lst,
544 + "output_tensor": self.output,
545 + "token_lora_mapping": self.lora_kernel_meta.token_lora_mapping,
546 + "token_indices_sorted_by_lora_ids": (
547 + self.lora_kernel_meta.token_indices_sorted_by_lora_ids
548 + ),
549 + "num_tokens_per_lora": self.lora_kernel_meta.num_tokens_per_lora,
550 + "lora_token_start_loc": self.lora_kernel_meta.lora_token_start_loc,
551 + "lora_ids": self.lora_kernel_meta.active_lora_ids,
552 + "offset_start": 0,
553 + "add_inputs": add_inputs,
554 }
555
496 - def bench_fn_kwargs(self,
497 - op_type: OpType,
498 - add_inputs: Optional[bool] = None) -> dict[str, Any]:
556 + def bench_fn_kwargs(
557 + self, op_type: OpType, add_inputs: Optional[bool] = None
558 + ) -> dict[str, Any]:
559 if op_type.is_shrink_fn():
560 assert add_inputs is None
561 else:
@@ -507,8 +567,9 @@ class BenchmarkTensors:
567 return self.as_lora_expand_kwargs(add_inputs)
568 raise ValueError(f"Unrecognized optype {self}")
569
510 - def test_correctness(self, op_type: OpType,
511 - expand_fn_add_inputs: Optional[bool]) -> bool:
570 + def test_correctness(
571 + self, op_type: OpType, expand_fn_add_inputs: Optional[bool]
572 + ) -> bool:
573 """
574 Test correctness of op_type implementation against a grouped gemm
575 reference implementation.
@@ -518,8 +579,7 @@ class BenchmarkTensors:
579 ref_output = self.output.clone()
580
581 self.output.zero_()
521 - op_type.bench_fn()(
522 - **self.bench_fn_kwargs(op_type, expand_fn_add_inputs))
582 + op_type.bench_fn()(**self.bench_fn_kwargs(op_type, expand_fn_add_inputs))
583
584 op_type.run_ref_group_gemm(
585 ref_output,
@@ -528,7 +588,8 @@ class BenchmarkTensors:
588 seq_lens_cpu=seq_lens_cpu,
589 prompt_lora_mapping_cpu=prompt_lora_mapping_cpu,
590 scaling=1.0,
531 - add_inputs=expand_fn_add_inputs)
591 + add_inputs=expand_fn_add_inputs,
592 + )
593
594 rtol, atol = {
595 torch.float16: (6e-2, 6e-2),
@@ -539,13 +600,14 @@ class BenchmarkTensors:
600 return torch.allclose(ref_output, self.output, rtol=rtol, atol=atol)
601
602
542 -def bench_optype(ctx: BenchmarkContext,
543 - arg_pool_size: int,
544 - op_type: OpType,
545 - cuda_graph_nops: Optional[int] = None,
546 - expand_fn_add_inputs: Optional[bool] = None,
547 - test_correctness: bool = False) -> TMeasurement:
548 -
603 +def bench_optype(
604 + ctx: BenchmarkContext,
605 + arg_pool_size: int,
606 + op_type: OpType,
607 + cuda_graph_nops: Optional[int] = None,
608 + expand_fn_add_inputs: Optional[bool] = None,
609 + test_correctness: bool = False,
610 +) -> TMeasurement:
611 assert arg_pool_size >= 1
612 if op_type.is_shrink_fn():
613 assert expand_fn_add_inputs is None
@@ -553,17 +615,17 @@ def bench_optype(ctx: BenchmarkContext,
615 assert expand_fn_add_inputs is not None
616
617 # BenchmarkContext -> BenchmarkTensors
556 - bench_tensors : list[BenchmarkTensors] = \
557 - [BenchmarkTensors.make(ctx, op_type) for _ in range(arg_pool_size)]
618 + bench_tensors: list[BenchmarkTensors] = [
619 + BenchmarkTensors.make(ctx, op_type) for _ in range(arg_pool_size)
620 + ]
621 for bt in bench_tensors:
622 bt.sanity_check()
623
624 # Test correctness of our implementation.
625 if test_correctness:
563 - assert all([
564 - bt.test_correctness(op_type, expand_fn_add_inputs)
565 - for bt in bench_tensors
566 - ])
626 + assert all(
627 + [bt.test_correctness(op_type, expand_fn_add_inputs) for bt in bench_tensors]
628 + )
629
630 # BenchmarkTensors -> dict (kwargs)
631 kwargs_list = [
@@ -585,40 +647,49 @@ def bench_optype(ctx: BenchmarkContext,
647 for k, v in _kwargs.items():
648 kwargs[k].values.append(v)
649
588 - describe_args = (f"add_inputs={expand_fn_add_inputs}"
589 - if expand_fn_add_inputs is not None else "")
590 - description = (
591 - f"{op_type.name}({describe_args}) ({bench_tensors[0].io_types()})")
650 + describe_args = (
651 + f"add_inputs={expand_fn_add_inputs}" if expand_fn_add_inputs is not None else ""
652 + )
653 + description = f"{op_type.name}({describe_args}) ({bench_tensors[0].io_types()})"
654
655 cuda_graph_params = None
656 if cuda_graph_nops:
657 cuda_graph_params = CudaGraphBenchParams(cuda_graph_nops)
658 timer = None
597 - with Bench(cuda_graph_params,
598 - ctx.bench_label(), ctx.bench_sublabel(op_type), description,
599 - op_type.bench_fn(), **kwargs) as bench:
659 + with Bench(
660 + cuda_graph_params,
661 + ctx.bench_label(),
662 + ctx.bench_sublabel(op_type),
663 + description,
664 + op_type.bench_fn(),
665 + **kwargs,
666 + ) as bench:
667 timer = bench.run()
668 return timer
669
670
604 -def bench_torch_mm(ctx: BenchmarkContext,
605 - arg_pool_size: int,
606 - op_type: OpType,
607 - cuda_graph_nops: Optional[int] = None) -> TMeasurement:
671 +def bench_torch_mm(
672 + ctx: BenchmarkContext,
673 + arg_pool_size: int,
674 + op_type: OpType,
675 + cuda_graph_nops: Optional[int] = None,
676 +) -> TMeasurement:
677 """
678 Benchmark basic torch.mm as a roofline.
679
680 When all the input tokens have the same LoRA ID, the LoRA kernels are just
612 - a matmul. This torch.mm benchmark serves as a roofline for that case.
681 + a matmul. This torch.mm benchmark serves as a roofline for that case.
682
683 input op_type is used in determining the m, k, n dimensions for the matmul.
684 """
685
617 - batch_size, hidden_size, lora_rank, seq_length, dtype = (ctx.batch_size,
618 - ctx.hidden_size,
619 - ctx.lora_rank,
620 - ctx.seq_length,
621 - ctx.dtype)
686 + batch_size, hidden_size, lora_rank, seq_length, dtype = (
687 + ctx.batch_size,
688 + ctx.hidden_size,
689 + ctx.lora_rank,
690 + ctx.seq_length,
691 + ctx.dtype,
692 + )
693
694 m, k, n = op_type.mkn(batch_size, seq_length, hidden_size, lora_rank)
695 # For a fairer comparison.
@@ -632,18 +703,24 @@ def bench_torch_mm(ctx: BenchmarkContext,
703 Cs.append(torch.rand((m, n), dtype=dtype).to("cuda"))
704
705 # Make torch.mm kwargs
635 - mm_kwargs = {'input': ArgPool(As), 'mat2': ArgPool(Bs), 'out': ArgPool(Cs)}
706 + mm_kwargs = {"input": ArgPool(As), "mat2": ArgPool(Bs), "out": ArgPool(Cs)}
707
708 description = (
709 f"single-lora roofline using torch.mm ({dtype_to_str(dtype)}"
710 f"x{dtype_to_str(dtype)}"
640 - f"=>{dtype_to_str(dtype)})")
711 + f"=>{dtype_to_str(dtype)})"
712 + )
713 cuda_graph_params = None
714 if cuda_graph_nops:
715 cuda_graph_params = CudaGraphBenchParams(cuda_graph_nops)
644 - with Bench(cuda_graph_params, ctx.bench_label(),
645 - ctx.bench_sublabel(op_type), description, torch.mm,
646 - **mm_kwargs) as bench:
716 + with Bench(
717 + cuda_graph_params,
718 + ctx.bench_label(),
719 + ctx.bench_sublabel(op_type),
720 + description,
721 + torch.mm,
722 + **mm_kwargs,
723 + ) as bench:
724 return bench.run()
725
726
@@ -660,8 +737,7 @@ def use_cuda_graph_recommendation() -> str:
737 """
738
739
663 -def print_timers(timers: list[TMeasurement],
664 - args: Optional[argparse.Namespace] = None):
740 +def print_timers(timers: list[TMeasurement], args: Optional[argparse.Namespace] = None):
741 compare = TBenchmark.Compare(timers)
742 compare.print()
743
@@ -670,22 +746,23 @@ def print_timers(timers: list[TMeasurement],
746 f"Note : The timings reported above is for {args.cuda_graph_nops} "
747 "consecutive invocations of the benchmarking functions. "
748 f"Please divide by {args.cuda_graph_nops} for single invocation "
673 - "timings.")
749 + "timings."
750 + )
751
675 - print("Note on Comparison with torch.mm : The torch.mm numbers are "
676 - "benchmark numbers of a simple matmul emulating the single lora "
677 - "case. It is provided as a roofline for comparing our LoRA Kernel "
678 - "implementations. It is expected that the LoRA kernels will be "
679 - "slower than torch.mm in cases where num_loras is big. But for "
680 - "small num_loras the goal should be to match the torch.mm numbers.")
752 + print(
753 + "Note on Comparison with torch.mm : The torch.mm numbers are "
754 + "benchmark numbers of a simple matmul emulating the single lora "
755 + "case. It is provided as a roofline for comparing our LoRA Kernel "
756 + "implementations. It is expected that the LoRA kernels will be "
757 + "slower than torch.mm in cases where num_loras is big. But for "
758 + "small num_loras the goal should be to match the torch.mm numbers."
759 + )
760
761
762 def run(args: argparse.Namespace, bench_ctxs: list[BenchmarkContext]):
684 -
763 if args.cuda_graph_nops is not None:
764 assert args.cuda_graph_nops > 0
687 - print(f"Benchmarking {args.cuda_graph_nops} invocations inside a CUDA "
688 - "Graph")
765 + print(f"Benchmarking {args.cuda_graph_nops} invocations inside a CUDA Graph")
766 else:
767 print(f"CUDA Graphs not enabled.\n{use_cuda_graph_recommendation()}")
768
@@ -697,21 +774,30 @@ def run(args: argparse.Namespace, bench_ctxs: list[BenchmarkContext]):
774 for bench_op in bench_ops:
775 for num_slices in bench_op.num_slices():
776 _ctx = bench_ctx.with_seq_length(seq_len).with_num_slices(
700 - num_slices)
777 + num_slices
778 + )
779 # Benchmark torch.mm as a roofline
780 seq_len_timers.append(
703 - bench_torch_mm(_ctx, args.arg_pool_size, bench_op,
704 - args.cuda_graph_nops))
781 + bench_torch_mm(
782 + _ctx, args.arg_pool_size, bench_op, args.cuda_graph_nops
783 + )
784 + )
785
786 # Benchmark bench_op
707 - expand_fn_add_inputs = [
708 - None
709 - ] if bench_op.is_shrink_fn() else args.expand_fn_add_inputs
787 + expand_fn_add_inputs = (
788 + [None] if bench_op.is_shrink_fn() else args.expand_fn_add_inputs
789 + )
790 for add_input_arg in expand_fn_add_inputs:
791 seq_len_timers.append(
712 - bench_optype(_ctx, args.arg_pool_size, bench_op,
713 - args.cuda_graph_nops, add_input_arg,
714 - args.test_correctness))
792 + bench_optype(
793 + _ctx,
794 + args.arg_pool_size,
795 + bench_op,
796 + args.cuda_graph_nops,
797 + add_input_arg,
798 + args.test_correctness,
799 + )
800 + )
801
802 print_timers(seq_len_timers)
803 timers.extend(seq_len_timers)
@@ -733,13 +819,17 @@ def run(args: argparse.Namespace, bench_ctxs: list[BenchmarkContext]):
819 pickle.dump(timers, f)
820
821
736 -def as_benchmark_contexts(hidden_sizes: list[int], lora_ranks: list[int],
737 - args: argparse.Namespace) -> list[BenchmarkContext]:
738 -
822 +def as_benchmark_contexts(
823 + hidden_sizes: list[int], lora_ranks: list[int], args: argparse.Namespace
824 +) -> list[BenchmarkContext]:
825 ctxs: list[BenchmarkContext] = []
826 for batch_size, hidden_size, lora_rank, num_loras, sort_by_lora_id in product( # noqa
741 - args.batch_sizes, list(hidden_sizes), lora_ranks, args.num_loras,
742 - args.sort_by_lora_id):
827 + args.batch_sizes,
828 + list(hidden_sizes),
829 + lora_ranks,
830 + args.num_loras,
831 + args.sort_by_lora_id,
832 + ):
833 ctxs.append(
834 BenchmarkContext(
835 batch_size=batch_size,
@@ -747,13 +837,16 @@ def as_benchmark_contexts(hidden_sizes: list[int], lora_ranks: list[int],
837 lora_rank=lora_rank,
838 num_loras=num_loras,
839 num_active_loras=args.num_active_loras
750 - if args.num_active_loras else num_loras,
840 + if args.num_active_loras
841 + else num_loras,
842 # To be filled based on the OpType to benchmark
843 seq_length=None,
844 sort_by_lora_id=sort_by_lora_id,
845 dtype=args.dtype,
846 # To be filled based on the OpType to benchmark
756 - num_slices=None))
847 + num_slices=None,
848 + )
849 + )
850
851 return ctxs
852
@@ -761,13 +854,16 @@ def as_benchmark_contexts(hidden_sizes: list[int], lora_ranks: list[int],
854 def run_list_bench(args: argparse.Namespace):
855 print(args)
856
764 - print("List bench :\n"
765 - f" Hidden Sizes {args.hidden_sizes}"
766 - f" LoRA Ranks {args.lora_ranks}")
857 + print(
858 + "List bench :\n"
859 + f" Hidden Sizes {args.hidden_sizes}"
860 + f" LoRA Ranks {args.lora_ranks}"
861 + )
862
863 # Get all benchmarking contexts
864 bench_contexts: list[BenchmarkContext] = as_benchmark_contexts(
770 - hidden_sizes=args.hidden_sizes, lora_ranks=args.lora_ranks, args=args)
865 + hidden_sizes=args.hidden_sizes, lora_ranks=args.lora_ranks, args=args
866 + )
867
868 run(args, bench_contexts)
869
@@ -776,19 +872,22 @@ def run_range_bench(args: argparse.Namespace):
872 print(args)
873
874 hidden_sizes = list(
779 - range(args.hidden_sizes_start, args.hidden_sizes_end + 1,
780 - args.hidden_sizes_increment))
875 + range(
876 + args.hidden_sizes_start,
877 + args.hidden_sizes_end + 1,
878 + args.hidden_sizes_increment,
879 + )
880 + )
881 lora_ranks = list(
782 - range(args.lora_ranks_start, args.lora_ranks_end + 1,
783 - args.lora_ranks_increment))
882 + range(args.lora_ranks_start, args.lora_ranks_end + 1, args.lora_ranks_increment)
883 + )
884
785 - print("Range bench :\n"
786 - f" Hidden Sizes {hidden_sizes}"
787 - f" LoRA Ranks {lora_ranks}")
885 + print(f"Range bench :\n Hidden Sizes {hidden_sizes} LoRA Ranks {lora_ranks}")
886
887 # Get all benchmarking contexts
888 bench_contexts: list[BenchmarkContext] = as_benchmark_contexts(
791 - hidden_sizes=hidden_sizes, lora_ranks=lora_ranks, args=args)
889 + hidden_sizes=hidden_sizes, lora_ranks=lora_ranks, args=args
890 + )
891
892 run(args, bench_contexts)
893
@@ -806,21 +905,19 @@ def run_model_bench(args: argparse.Namespace):
905 # Get all hidden sizes
906 hidden_sizes: set[int] = set()
907 for model_name, tp_size in product(args.models, args.tp_sizes):
809 - hidden_sizes = hidden_sizes.union(
810 - hidden_sizes_from_model(model_name, tp_size))
908 + hidden_sizes = hidden_sizes.union(hidden_sizes_from_model(model_name, tp_size))
909
812 - print("Model bench :\n"
813 - f" Hidden Sizes {hidden_sizes}"
814 - f" LoRA Ranks {args.lora_ranks}")
910 + print(f"Model bench :\n Hidden Sizes {hidden_sizes} LoRA Ranks {args.lora_ranks}")
911
912 # Get all benchmarking contexts
913 bench_contexts: list[BenchmarkContext] = as_benchmark_contexts(
818 - hidden_sizes=hidden_sizes, lora_ranks=args.lora_ranks, args=args)
914 + hidden_sizes=hidden_sizes, lora_ranks=args.lora_ranks, args=args
915 + )
916
917 run(args, bench_contexts)
918
919
823 -if __name__ == '__main__':
920 +if __name__ == "__main__":
921
922 def to_torch_dtype(dt):
923 if dt == "torch.float16":
@@ -830,14 +927,15 @@ if __name__ == '__main__':
927 raise ValueError("unsupported dtype")
928
929 def get_bool(s: str) -> bool:
833 - return s.lower() in ['true', '1']
930 + return s.lower() in ["true", "1"]
931
932 def add_common_command_args(p: argparse.ArgumentParser):
933 p.add_argument(
934 "--dtype",
935 type=to_torch_dtype,
936 required=True,
840 - help="Available options are ['torch.float16', 'torch.bfloat16']")
937 + help="Available options are ['torch.float16', 'torch.bfloat16']",
938 + )
939
940 p.add_argument(
941 "--arg-pool-size",
@@ -845,56 +943,66 @@ if __name__ == '__main__':
943 default=32,
944 help="Run profiles with a pool of input/output/meta tensors instead"
945 "of simply reusing the same tensors for all runs. A bigger arg-pool"
848 - "mitigates hardware caching effects during benchmarking.")
946 + "mitigates hardware caching effects during benchmarking.",
947 + )
948
949 p.add_argument(
950 "--cuda-graph-nops",
951 type=int,
853 - help=("when set profiling is done using cudagraph, "
854 - "with the given number of operations in a graph."
855 - "Note that the measurement returned is the time "
856 - "taken for N consecutive executions of the benchmarking "
857 - "functions, where N is the value of this argument."))
858 - p.add_argument("--num-loras",
859 - nargs="+",
860 - type=int,
861 - default=DEFAULT_NUM_LORAS)
862 - p.add_argument("--num-active-loras",
863 - type=int,
864 - default=None,
865 - help="Active LoRAs. When None, all LoRAs are active")
866 - p.add_argument("--sort-by-lora-id",
867 - nargs="+",
868 - type=get_bool,
869 - default=DEFAULT_SORT_BY_LORA_IDS)
870 - p.add_argument("--op-types",
871 - nargs="+",
872 - type=OpType.from_str,
873 - default=list(OpType))
874 - p.add_argument('--seq-lengths',
875 - nargs="+",
876 - type=int,
877 - default=DEFAULT_SEQ_LENGTHS)
878 - p.add_argument("--batch-sizes",
879 - nargs="+",
880 - type=int,
881 - default=DEFAULT_BATCH_SIZES)
882 - p.add_argument("--expand-fn-add-inputs",
883 - nargs="+",
884 - type=get_bool,
885 - default=DEFAULT_EXPAND_FN_ADD_INPUTS)
952 + help=(
953 + "when set profiling is done using cudagraph, "
954 + "with the given number of operations in a graph."
955 + "Note that the measurement returned is the time "
956 + "taken for N consecutive executions of the benchmarking "
957 + "functions, where N is the value of this argument."
958 + ),
959 + )
960 + p.add_argument("--num-loras", nargs="+", type=int, default=DEFAULT_NUM_LORAS)
961 + p.add_argument(
962 + "--num-active-loras",
963 + type=int,
964 + default=None,
965 + help="Active LoRAs. When None, all LoRAs are active",
966 + )
967 + p.add_argument(
968 + "--sort-by-lora-id",
969 + nargs="+",
970 + type=get_bool,
971 + default=DEFAULT_SORT_BY_LORA_IDS,
972 + )
973 + p.add_argument(
974 + "--op-types", nargs="+", type=OpType.from_str, default=list(OpType)
975 + )
976 + p.add_argument(
977 + "--seq-lengths", nargs="+", type=int, default=DEFAULT_SEQ_LENGTHS
978 + )
979 + p.add_argument(
980 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
981 + )
982 + p.add_argument(
983 + "--expand-fn-add-inputs",
984 + nargs="+",
985 + type=get_bool,
986 + default=DEFAULT_EXPAND_FN_ADD_INPUTS,
987 + )
988 p.add_argument(
887 - '-o',
888 - '--output-directory',
989 + "-o",
990 + "--output-directory",
991 type=str,
890 - help=("Output directory to store a the list of benchmarking"
891 - "TMeasurement objects as a pickle file"))
992 + help=(
993 + "Output directory to store a the list of benchmarking"
994 + "TMeasurement objects as a pickle file"
995 + ),
996 + )
997
998 p.add_argument(
999 "--test-correctness",
895 - action='store_true',
896 - help=("When enabled, the benchmarking functions are tested"
897 - "for correctness before the actual benchmarking"))
1000 + action="store_true",
1001 + help=(
1002 + "When enabled, the benchmarking functions are tested"
1003 + "for correctness before the actual benchmarking"
1004 + ),
1005 + )
1006
1007 parser = FlexibleArgumentParser(
1008 description=f"""
@@ -910,50 +1018,45 @@ Benchmark LoRA kernels:
1018 range_bench example:
1019 python3 benchmarks/kernels/benchmark_lora.py range_bench --arg-pool-size 32 --batch-sizes 1 16 32 --dtype torch.float16 --num-loras 1 4 --op-types lora_shrink lora_expand --seq-lengths 1 16 --sort-by-lora-id 1 --cuda-graph-nops 32 --hidden-sizes-start 1024 --hidden-sizes-end 4096 --hidden-sizes-increment 1024 --lora-ranks-start 8 --lora-ranks-end 24 --lora-ranks-increment 8
1020 """, # noqa: E501
913 - formatter_class=argparse.RawTextHelpFormatter)
1021 + formatter_class=argparse.RawTextHelpFormatter,
1022 + )
1023
1024 subparsers = parser.add_subparsers(dest="cmd", required=True)
1025
1026 list_parser = subparsers.add_parser("list_bench")
918 - list_parser.add_argument("--hidden-sizes",
919 - nargs="+",
920 - type=int,
921 - default=DEFAULT_HIDDEN_SIZES)
922 - list_parser.add_argument("--lora-ranks",
923 - nargs="+",
924 - type=int,
925 - default=DEFAULT_LORA_RANKS)
1027 + list_parser.add_argument(
1028 + "--hidden-sizes", nargs="+", type=int, default=DEFAULT_HIDDEN_SIZES
1029 + )
1030 + list_parser.add_argument(
1031 + "--lora-ranks", nargs="+", type=int, default=DEFAULT_LORA_RANKS
1032 + )
1033 add_common_command_args(list_parser)
1034 list_parser.set_defaults(func=run_list_bench)
1035
1036 range_parser = subparsers.add_parser("range_bench")
1037 range_parser.add_argument("--hidden-sizes-start", type=int, required=True)
1038 range_parser.add_argument("--hidden-sizes-end", type=int, required=True)
932 - range_parser.add_argument("--hidden-sizes-increment",
933 - type=int,
934 - required=True)
1039 + range_parser.add_argument("--hidden-sizes-increment", type=int, required=True)
1040 range_parser.add_argument("--lora-ranks-start", type=int, required=True)
1041 range_parser.add_argument("--lora-ranks-end", type=int, required=True)
937 - range_parser.add_argument("--lora-ranks-increment",
938 - type=int,
939 - required=True)
1042 + range_parser.add_argument("--lora-ranks-increment", type=int, required=True)
1043 add_common_command_args(range_parser)
1044 range_parser.set_defaults(func=run_range_bench)
1045
1046 model_parser = subparsers.add_parser("model_bench")
944 - model_parser.add_argument("--models",
945 - nargs="+",
946 - type=str,
947 - default=DEFAULT_MODELS,
948 - choices=WEIGHT_SHAPES.keys())
949 - model_parser.add_argument("--tp-sizes",
950 - nargs="+",
951 - type=int,
952 - default=DEFAULT_TP_SIZES)
953 - model_parser.add_argument("--lora-ranks",
954 - nargs="+",
955 - type=int,
956 - default=DEFAULT_LORA_RANKS)
1047 + model_parser.add_argument(
1048 + "--models",
1049 + nargs="+",
1050 + type=str,
1051 + default=DEFAULT_MODELS,
1052 + choices=WEIGHT_SHAPES.keys(),
1053 + )
1054 + model_parser.add_argument(
1055 + "--tp-sizes", nargs="+", type=int, default=DEFAULT_TP_SIZES
1056 + )
1057 + model_parser.add_argument(
1058 + "--lora-ranks", nargs="+", type=int, default=DEFAULT_LORA_RANKS
1059 + )
1060 add_common_command_args(model_parser)
1061 model_parser.set_defaults(func=run_model_bench)
1062
benchmarks/kernels/benchmark_machete.py
+212 -157
@@ -20,12 +20,18 @@ from weight_shapes import WEIGHT_SHAPES
20
21 from vllm import _custom_ops as ops
22 from vllm.model_executor.layers.quantization.utils.marlin_utils import (
23 - GPTQ_MARLIN_MAX_PARALLEL, GPTQ_MARLIN_MIN_THREAD_N, marlin_permute_scales,
24 - marlin_zero_points)
23 + GPTQ_MARLIN_MAX_PARALLEL,
24 + GPTQ_MARLIN_MIN_THREAD_N,
25 + marlin_permute_scales,
26 + marlin_zero_points,
27 +)
28 from vllm.model_executor.layers.quantization.utils.marlin_utils_test import (
26 - MarlinWorkspace)
29 + MarlinWorkspace,
30 +)
31 from vllm.model_executor.layers.quantization.utils.quant_utils import (
28 - pack_rows, quantize_weights)
32 + pack_rows,
33 + quantize_weights,
34 +)
35 from vllm.scalar_type import ScalarType, scalar_types
36 from vllm.utils import FlexibleArgumentParser
37
@@ -82,12 +88,14 @@ def rand_data(shape, dtype=torch.float16, scale=1):
88 return torch.randint(-15, 15, shape, dtype=dtype, device="cuda")
89
90
85 -def quantize_and_pack(atype: torch.dtype,
86 - w: torch.Tensor,
87 - wtype: ScalarType,
88 - stype: Optional[torch.dtype],
89 - group_size: Optional[int],
90 - zero_points: bool = False):
91 +def quantize_and_pack(
92 + atype: torch.dtype,
93 + w: torch.Tensor,
94 + wtype: ScalarType,
95 + stype: Optional[torch.dtype],
96 + group_size: Optional[int],
97 + zero_points: bool = False,
98 +):
99 assert wtype.is_integer(), "TODO: support floating point weights"
100
101 w_ref, w_q, w_s, w_zp = quantize_weights(
@@ -96,21 +104,24 @@ def quantize_and_pack(atype: torch.dtype,
104 group_size=group_size,
105 zero_points=zero_points,
106 # to match how the kernel applies zps
99 - ref_zero_points_after_scales=True)
107 + ref_zero_points_after_scales=True,
108 + )
109
110 w_q = pack_rows(w_q, wtype.size_bits, *w_q.shape)
111 return w_ref, w_q, w_s, w_zp
112
113
105 -def create_bench_tensors(shape: tuple[int, int, int], types: TypeConfig,
106 - group_size: Optional[int]) -> list[BenchmarkTensors]:
114 +def create_bench_tensors(
115 + shape: tuple[int, int, int], types: TypeConfig, group_size: Optional[int]
116 +) -> list[BenchmarkTensors]:
117 m, n, k = shape
118
119 # we want to make sure that weights don't fit into L2 cache between runs so
120 # we construct enough weights to exceed L2 cache, which is 50mb on a H100
121 # so we target total weight size > 2*50mb
112 - num_weights = math.ceil(2 * 50 * 1024**2 * 8 /
113 - (k * n * types.weight_type.size_bits))
122 + num_weights = math.ceil(
123 + 2 * 50 * 1024**2 * 8 / (k * n * types.weight_type.size_bits)
124 + )
125
126 a = rand_data((m, k), types.act_type, scale=5)
127
@@ -124,8 +135,13 @@ def create_bench_tensors(shape: tuple[int, int, int], types: TypeConfig,
135 w = w.to(torch.float16)
136
137 w_ref, w_q_packed, w_s, w_zp = quantize_and_pack(
127 - a.dtype, w, types.weight_type, types.group_scale_type, group_size,
128 - types.group_zero_type is not None)
138 + a.dtype,
139 + w,
140 + types.weight_type,
141 + types.group_scale_type,
142 + group_size,
143 + types.group_zero_type is not None,
144 + )
145
146 if not a.dtype.is_floating_point:
147 aiinfo = torch.iinfo(a.dtype)
@@ -133,21 +149,30 @@ def create_bench_tensors(shape: tuple[int, int, int], types: TypeConfig,
149
150 w_ref = w_ref.to(torch.float32)
151
136 - w_ch_s = None if types.channel_scale_type is None else\
137 - rand_data((n,), types.channel_scale_type)
138 - w_tok_s = None if types.token_scale_type is None else\
139 - rand_data((m,), types.token_scale_type)
152 + w_ch_s = (
153 + None
154 + if types.channel_scale_type is None
155 + else rand_data((n,), types.channel_scale_type)
156 + )
157 + w_tok_s = (
158 + None
159 + if types.token_scale_type is None
160 + else rand_data((m,), types.token_scale_type)
161 + )
162
163 benchmark_tensors.append(
142 - BenchmarkTensors(w_ref=w_ref,
143 - a=a,
144 - w_q=w_q_packed,
145 - wtype=types.weight_type,
146 - w_g_s=w_s,
147 - w_g_zp=w_zp,
148 - group_size=group_size,
149 - w_ch_s=w_ch_s,
150 - w_tok_s=w_tok_s))
164 + BenchmarkTensors(
165 + w_ref=w_ref,
166 + a=a,
167 + w_q=w_q_packed,
168 + wtype=types.weight_type,
169 + w_g_s=w_s,
170 + w_g_zp=w_zp,
171 + group_size=group_size,
172 + w_ch_s=w_ch_s,
173 + w_tok_s=w_tok_s,
174 + )
175 + )
176
177 return benchmark_tensors
178
@@ -170,50 +195,57 @@ def cutlass_scaled_mm_create_bench_fn(bt: BenchmarkTensors) -> Callable:
195 scale_b = torch.tensor(1.0, dtype=torch.float32, device=bt.a.device)
196 w_col_major = bt.w_ref.to(bt.a.dtype).t().contiguous().t()
197 return lambda: ops.cutlass_scaled_mm(
173 - bt.a, w_col_major, scale_a, scale_b, out_dtype=torch.float16)
198 + bt.a, w_col_major, scale_a, scale_b, out_dtype=torch.float16
199 + )
200
201
202 def marlin_create_bench_fn(bt: BenchmarkTensors) -> Callable:
203 device = bt.a.device
204
179 - workspace = MarlinWorkspace(bt.w_ref.shape[1], GPTQ_MARLIN_MIN_THREAD_N,
180 - GPTQ_MARLIN_MAX_PARALLEL)
205 + workspace = MarlinWorkspace(
206 + bt.w_ref.shape[1], GPTQ_MARLIN_MIN_THREAD_N, GPTQ_MARLIN_MAX_PARALLEL
207 + )
208
209 if bt.w_g_zp is None:
210 w_zp = torch.empty(0, dtype=torch.int, device=device)
211 else:
185 - w_zp = marlin_zero_points(bt.w_g_zp, bt.w_ref.shape[0],
186 - bt.w_ref.shape[1], bt.wtype.size_bits)
212 + w_zp = marlin_zero_points(
213 + bt.w_g_zp, bt.w_ref.shape[0], bt.w_ref.shape[1], bt.wtype.size_bits
214 + )
215
216 if bt.group_size is None:
217 w_s = torch.tensor([], device="cuda", dtype=torch.half)
218 else:
191 - w_s = marlin_permute_scales(bt.w_g_s, bt.w_ref.shape[0],
192 - bt.w_ref.shape[1], bt.group_size)
219 + w_s = marlin_permute_scales(
220 + bt.w_g_s, bt.w_ref.shape[0], bt.w_ref.shape[1], bt.group_size
221 + )
222
223 sort_indices = torch.empty(0, dtype=torch.int, device=device)
224 g_idx = torch.empty(0, dtype=torch.int, device=device)
196 - w_q = ops.gptq_marlin_repack(bt.w_q, sort_indices, bt.w_ref.shape[0],
197 - bt.w_ref.shape[1], bt.wtype.size_bits)
225 + w_q = ops.gptq_marlin_repack(
226 + bt.w_q, sort_indices, bt.w_ref.shape[0], bt.w_ref.shape[1], bt.wtype.size_bits
227 + )
228
229 if bt.a.dtype.is_floating_point:
230 assert bt.w_ch_s is None
231 assert bt.w_tok_s is None
232 assert bt.group_size is not None
233
204 - fn = lambda: ops.gptq_marlin_gemm(a=bt.a,
205 - b_q_weight=w_q,
206 - b_scales=w_s,
207 - b_zeros=w_zp,
208 - g_idx=g_idx,
209 - perm=sort_indices,
210 - workspace=workspace.scratch,
211 - b_q_type=bt.wtype,
212 - size_m=bt.a.shape[0],
213 - size_n=bt.w_ref.shape[1],
214 - size_k=bt.w_ref.shape[0],
215 - is_k_full=True,
216 - is_zp_float=False)
234 + fn = lambda: ops.gptq_marlin_gemm(
235 + a=bt.a,
236 + b_q_weight=w_q,
237 + b_scales=w_s,
238 + b_zeros=w_zp,
239 + g_idx=g_idx,
240 + perm=sort_indices,
241 + workspace=workspace.scratch,
242 + b_q_type=bt.wtype,
243 + size_m=bt.a.shape[0],
244 + size_n=bt.w_ref.shape[1],
245 + size_k=bt.w_ref.shape[0],
246 + is_k_full=True,
247 + is_zp_float=False,
248 + )
249 else:
250 assert bt.a.dtype == torch.int8
251 assert bt.wtype == scalar_types.uint4b8
@@ -221,36 +253,35 @@ def marlin_create_bench_fn(bt: BenchmarkTensors) -> Callable:
253 if bt.w_ch_s is not None:
254 s_ch = bt.w_ch_s.to(torch.float32)
255 else:
224 - s_ch = torch.ones(bt.w_ref.shape[1],
225 - dtype=torch.float32,
226 - device=device)
256 + s_ch = torch.ones(bt.w_ref.shape[1], dtype=torch.float32, device=device)
257
258 if bt.w_tok_s is not None:
259 s_tok = bt.w_tok_s.to(torch.float32)
260 else:
231 - s_tok = torch.ones(bt.a.shape[0],
232 - dtype=torch.float32,
233 - device=device)
234 -
235 - fn = lambda: ops.marlin_qqq_gemm(a=bt.a,
236 - b_q_weight=w_q,
237 - s_group=w_s,
238 - s_tok=s_tok,
239 - s_ch=s_ch,
240 - workspace=workspace.scratch,
241 - size_m=bt.a.shape[0],
242 - size_n=bt.w_ref.shape[1],
243 - size_k=bt.w_ref.shape[0])
261 + s_tok = torch.ones(bt.a.shape[0], dtype=torch.float32, device=device)
262 +
263 + fn = lambda: ops.marlin_qqq_gemm(
264 + a=bt.a,
265 + b_q_weight=w_q,
266 + s_group=w_s,
267 + s_tok=s_tok,
268 + s_ch=s_ch,
269 + workspace=workspace.scratch,
270 + size_m=bt.a.shape[0],
271 + size_n=bt.w_ref.shape[1],
272 + size_k=bt.w_ref.shape[0],
273 + )
274
275 return fn
276
277
248 -def machete_create_bench_fn(bt: BenchmarkTensors,
249 - out_type=torch.dtype,
250 - schedule=None) -> Callable:
278 +def machete_create_bench_fn(
279 + bt: BenchmarkTensors, out_type=torch.dtype, schedule=None
280 +) -> Callable:
281 w_q = bt.w_q.t().contiguous().t() # make col major
252 - w_q = ops.machete_prepack_B(w_q, bt.a.dtype, bt.wtype,
253 - None if bt.w_g_s is None else bt.w_g_s.dtype)
282 + w_q = ops.machete_prepack_B(
283 + w_q, bt.a.dtype, bt.wtype, None if bt.w_g_s is None else bt.w_g_s.dtype
284 + )
285
286 w_g_zp = bt.w_g_zp
287 if w_g_zp is not None:
@@ -275,26 +306,24 @@ def machete_create_bench_fn(bt: BenchmarkTensors,
306 # bench
307
308
278 -def bench_fns(label: str, sub_label: str, description: str,
279 - fns: list[Callable]):
280 -
309 +def bench_fns(label: str, sub_label: str, description: str, fns: list[Callable]):
310 min_run_time = 1 if not NVTX_PROFILE else 0.1
311 res = TBenchmark.Timer(
312 stmt="""
313 for fn in fns:
314 fn()
315 """,
287 - globals={
288 - "fns": fns
289 - },
316 + globals={"fns": fns},
317 label=label,
318 sub_label=sub_label,
319 description=description,
320 ).blocked_autorange(min_run_time=min_run_time)
321
322 if NVTX_PROFILE:
296 - with nvtx.annotate("mm-bench"), nvtx.annotate(
297 - f"{label}|{sub_label}|{description}"):
323 + with (
324 + nvtx.annotate("mm-bench"),
325 + nvtx.annotate(f"{label}|{sub_label}|{description}"),
326 + ):
327 fns[0]()
328
329 return res
@@ -304,19 +333,20 @@ _SWEEP_SCHEDULES_RESULTS: Optional[pd.DataFrame] = None
333 _SWEEP_SCHEDULES_RESULTS_CSV: Optional[str] = None
334
335
307 -def bench(types: TypeConfig,
308 - group_size: int,
309 - m: int,
310 - k: int,
311 - n: int,
312 - label: str,
313 - sub_label: str,
314 - sweep_schedules: bool = True) -> list[TMeasurement]:
336 +def bench(
337 + types: TypeConfig,
338 + group_size: int,
339 + m: int,
340 + k: int,
341 + n: int,
342 + label: str,
343 + sub_label: str,
344 + sweep_schedules: bool = True,
345 +) -> list[TMeasurement]:
346 benchmark_tensors = create_bench_tensors((m, n, k), types, group_size)
347 sub_label += f", L={len(benchmark_tensors)}"
348
318 - name_type_string = f"W{types.weight_type}"+\
319 - f"-A{terse_type_name(types.act_type)}"
349 + name_type_string = f"W{types.weight_type}" + f"-A{terse_type_name(types.act_type)}"
350 if types.group_scale_type is not None:
351 name_type_string += f"-GS{terse_type_name(types.group_scale_type)}"
352 if types.group_zero_type is not None:
@@ -332,31 +362,45 @@ def bench(types: TypeConfig,
362 # pytorch impl
363 timers.append(
364 bench_fns(
335 - label, sub_label, "torch.matmul (fp16)",
336 - [torch_matmul_f16_create_bench_fn(bt)
337 - for bt in benchmark_tensors]))
365 + label,
366 + sub_label,
367 + "torch.matmul (fp16)",
368 + [torch_matmul_f16_create_bench_fn(bt) for bt in benchmark_tensors],
369 + )
370 + )
371
372 if types.act_type == torch.int8 or types.act_type == torch.float8_e4m3fn:
373 timers.append(
374 bench_fns(
342 - label, sub_label,
343 - f"cutlass_scaled_mm ({terse_type_name(types.act_type)})", [
344 - cutlass_scaled_mm_create_bench_fn(bt)
345 - for bt in benchmark_tensors
346 - ]))
375 + label,
376 + sub_label,
377 + f"cutlass_scaled_mm ({terse_type_name(types.act_type)})",
378 + [cutlass_scaled_mm_create_bench_fn(bt) for bt in benchmark_tensors],
379 + )
380 + )
381
382 if types.act_type != torch.float8_e4m3fn:
383 timers.append(
350 - bench_fns(label, sub_label, f"marlin ({name_type_string})",
351 - [marlin_create_bench_fn(bt)
352 - for bt in benchmark_tensors]))
384 + bench_fns(
385 + label,
386 + sub_label,
387 + f"marlin ({name_type_string})",
388 + [marlin_create_bench_fn(bt) for bt in benchmark_tensors],
389 + )
390 + )
391
392 # machete
393 timers.append(
356 - bench_fns(label, sub_label, f"machete ({name_type_string})", [
357 - machete_create_bench_fn(bt, out_type=types.output_type)
358 - for bt in benchmark_tensors
359 - ]))
394 + bench_fns(
395 + label,
396 + sub_label,
397 + f"machete ({name_type_string})",
398 + [
399 + machete_create_bench_fn(bt, out_type=types.output_type)
400 + for bt in benchmark_tensors
401 + ],
402 + )
403 + )
404
405 if sweep_schedules:
406 global _SWEEP_SCHEDULES_RESULTS
@@ -371,7 +415,8 @@ def bench(types: TypeConfig,
415 group_zeros_type=types.group_zero_type,
416 token_scales_type=types.token_scale_type,
417 channel_scales_type=types.channel_scale_type,
374 - out_type=types.output_type)
418 + out_type=types.output_type,
419 + )
420
421 if schedules is None or len(schedules) == 0:
422 raise ValueError("No schedules found to sweep")
@@ -383,11 +428,17 @@ def bench(types: TypeConfig,
428 if schedule_M >= 2 * max(m, 16) or schedule_M < m // 4:
429 continue
430
386 - res = bench_fns(label, sub_label, "machete_best", [
387 - machete_create_bench_fn(
388 - bt, out_type=types.output_type, schedule=schedule)
389 - for bt in benchmark_tensors
390 - ])
431 + res = bench_fns(
432 + label,
433 + sub_label,
434 + "machete_best",
435 + [
436 + machete_create_bench_fn(
437 + bt, out_type=types.output_type, schedule=schedule
438 + )
439 + for bt in benchmark_tensors
440 + ],
441 + )
442
443 results_row = {
444 "M": m,
@@ -398,10 +449,8 @@ def bench(types: TypeConfig,
449 "median": res.median,
450 }
451 if _SWEEP_SCHEDULES_RESULTS is None:
401 - _SWEEP_SCHEDULES_RESULTS = pd.DataFrame(
402 - columns=results_row.keys())
403 - _SWEEP_SCHEDULES_RESULTS.\
404 - loc[len(_SWEEP_SCHEDULES_RESULTS)] = results_row
452 + _SWEEP_SCHEDULES_RESULTS = pd.DataFrame(columns=results_row.keys())
453 + _SWEEP_SCHEDULES_RESULTS.loc[len(_SWEEP_SCHEDULES_RESULTS)] = results_row
454
455 print(f" {res.median:5.5} ", schedule)
456 if not best or res.median < best.median:
@@ -422,8 +471,9 @@ def print_timers(timers: list[TMeasurement]):
471 def run(args, MKNs: Iterable[tuple[int, int, int]]) -> Iterable[TMeasurement]:
472 types = TypeConfig(
473 act_type=args.act_type,
425 - weight_type=scalar_types.uint4b8 if args.group_zero_type is None \
426 - else scalar_types.uint4,
474 + weight_type=scalar_types.uint4b8
475 + if args.group_zero_type is None
476 + else scalar_types.uint4,
477 output_type=args.out_type,
478 group_scale_type=args.group_scale_type,
479 group_zero_type=args.group_zero_type,
@@ -433,14 +483,16 @@ def run(args, MKNs: Iterable[tuple[int, int, int]]) -> Iterable[TMeasurement]:
483
484 results: list[TMeasurement] = []
485 for m, k, n in MKNs:
436 - timers = bench(types,
437 - args.group_size,
438 - m,
439 - k,
440 - n,
441 - f"{args.act_type}-gemm",
442 - f"MKN=({m}x{k}x{n})",
443 - sweep_schedules=args.sweep_schedules)
486 + timers = bench(
487 + types,
488 + args.group_size,
489 + m,
490 + k,
491 + n,
492 + f"{args.act_type}-gemm",
493 + f"MKN=({m}x{k}x{n})",
494 + sweep_schedules=args.sweep_schedules,
495 + )
496 print_timers(timers)
497 results.extend(timers)
498
@@ -454,7 +506,6 @@ def make_output(
506 base_description: str,
507 timestamp=None,
508 ):
457 -
509 print(f"== All Results {base_description} ====")
510 print_timers(data)
511
@@ -468,8 +519,7 @@ def make_output(
519
520
521 def run_square_bench(args):
471 - dim_sizes = list(
472 - range(args.dim_start, args.dim_end + 1, args.dim_increment))
522 + dim_sizes = list(range(args.dim_start, args.dim_end + 1, args.dim_increment))
523 MKNs = list(zip(dim_sizes, dim_sizes, dim_sizes))
524 data = run(args.dtype, args.sweep_schedules, MKNs)
525
@@ -479,8 +529,9 @@ def run_square_bench(args):
529 def run_range_bench(args):
530 m_start, k_start, n_start = (int(x) for x in args.dim_start.split(","))
531 m_end, k_end, n_end = (int(x) for x in args.dim_end.split(","))
482 - m_increment, k_increment, n_increment = \
483 - (int(x) for x in args.dim_increment.split(","))
532 + m_increment, k_increment, n_increment = (
533 + int(x) for x in args.dim_increment.split(",")
534 + )
535 Ms = list(range(m_start, m_end + 1, m_increment))
536 Ks = list(range(k_start, k_end + 1, k_increment))
537 Ns = list(range(n_start, n_end + 1, n_increment))
@@ -492,7 +543,6 @@ def run_range_bench(args):
543
544
545 def run_model_bench(args):
495 -
546 print("Benchmarking models:")
547 for i, model in enumerate(args.models):
548 print(f"[{i}] {model}")
@@ -535,10 +585,13 @@ def run_model_bench(args):
585 with open(f"model_bench-{type_string}-{timestr}.pkl", "wb") as f:
586 args_dict = vars(args)
587 args_dict.pop("func")
538 - pkl.dump({
539 - "args": args_dict,
540 - "results": all_results,
541 - }, f)
588 + pkl.dump(
589 + {
590 + "args": args_dict,
591 + "results": all_results,
592 + },
593 + f,
594 + )
595
596
597 if __name__ == "__main__":
@@ -554,7 +607,6 @@ if __name__ == "__main__":
607 }[dt]
608
609 class ToTorchDtype(argparse.Action):
557 -
610 def __call__(self, parser, namespace, values, option_string=None):
611 setattr(namespace, self.dest, to_torch_dtype(values))
612
@@ -580,32 +632,32 @@ Benchmark Machete GEMM.
632 "--act-type",
633 action=ToTorchDtype,
634 required=True,
583 - choices=['bfloat16', 'float16', 'int8', 'float8_e4m3fn'],
635 + choices=["bfloat16", "float16", "int8", "float8_e4m3fn"],
636 )
637 parser.add_argument(
638 "--group-scale-type",
639 action=ToTorchDtype,
588 - choices=['bfloat16', 'float16'],
640 + choices=["bfloat16", "float16"],
641 )
642 parser.add_argument(
643 "--group-zero-type",
644 type=to_torch_dtype,
593 - choices=['bfloat16', 'float16'],
645 + choices=["bfloat16", "float16"],
646 )
647 parser.add_argument(
648 "--channel-scale-type",
649 action=ToTorchDtype,
598 - choices=['float'],
650 + choices=["float"],
651 )
652 parser.add_argument(
653 "--token-scale-type",
654 action=ToTorchDtype,
603 - choices=['float'],
655 + choices=["float"],
656 )
657 parser.add_argument(
658 "--out-type",
659 action=ToTorchDtype,
608 - choices=['bfloat16', 'float16'],
660 + choices=["bfloat16", "float16"],
661 )
662 parser.add_argument(
663 "--group-size",
@@ -618,9 +670,11 @@ Benchmark Machete GEMM.
670 action="store_true",
671 help="Run a sweep over all supported schedules",
672 )
621 - parser.add_argument("--sweep-csv-out",
622 - help="CSV to store sweep results",
623 - default="sch_sweep_results.csv")
673 + parser.add_argument(
674 + "--sweep-csv-out",
675 + help="CSV to store sweep results",
676 + default="sch_sweep_results.csv",
677 + )
678 subparsers = parser.add_subparsers(dest="cmd", required=True)
679
680 square_parser = subparsers.add_parser("square_bench")
@@ -634,17 +688,20 @@ Benchmark Machete GEMM.
688 "--dim-start",
689 type=str,
690 required=True,
637 - help="Start value for M,K,N as common separated list")
691 + help="Start value for M,K,N as common separated list",
692 + )
693 range_parser.add_argument(
694 "--dim-end",
695 type=str,
696 required=True,
642 - help="End value (inclusive) for M,K,N as common separated list")
697 + help="End value (inclusive) for M,K,N as common separated list",
698 + )
699 range_parser.add_argument(
700 "--dim-increment",
701 type=str,
702 required=True,
647 - help="Increment value for M,K,N as common separated list")
703 + help="Increment value for M,K,N as common separated list",
704 + )
705 range_parser.set_defaults(func=run_range_bench)
706
707 model_parser = subparsers.add_parser("model_bench")
@@ -655,14 +712,12 @@ Benchmark Machete GEMM.
712 default=DEFAULT_MODELS,
713 choices=WEIGHT_SHAPES.keys(),
714 )
658 - model_parser.add_argument("--tp-sizes",
659 - nargs="+",
660 - type=int,
661 - default=DEFAULT_TP_SIZES)
662 - model_parser.add_argument("--batch-sizes",
663 - nargs="+",
664 - type=int,
665 - default=DEFAULT_BATCH_SIZES)
715 + model_parser.add_argument(
716 + "--tp-sizes", nargs="+", type=int, default=DEFAULT_TP_SIZES
717 + )
718 + model_parser.add_argument(
719 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
720 + )
721 model_parser.set_defaults(func=run_model_bench)
722
723 args = parser.parse_args()
benchmarks/kernels/benchmark_marlin.py
+117 -73
@@ -6,19 +6,34 @@ from benchmark_shapes import WEIGHT_SHAPES
6
7 from vllm import _custom_ops as ops
8 from vllm.model_executor.layers.quantization.gptq_marlin_24 import (
9 - GPTQ_MARLIN_24_MAX_PARALLEL, GPTQ_MARLIN_24_MIN_THREAD_N,
10 - GPTQ_MARLIN_24_SUPPORTED_GROUP_SIZES, GPTQ_MARLIN_24_SUPPORTED_QUANT_TYPES)
9 + GPTQ_MARLIN_24_MAX_PARALLEL,
10 + GPTQ_MARLIN_24_MIN_THREAD_N,
11 + GPTQ_MARLIN_24_SUPPORTED_GROUP_SIZES,
12 + GPTQ_MARLIN_24_SUPPORTED_QUANT_TYPES,
13 +)
14 from vllm.model_executor.layers.quantization.utils.allspark_utils import (
12 - ALLSPARK_AMPERE_M_CUBLAS_THRESHOLD, ALLSPARK_SUPPORTED_QUANT_TYPES)
15 + ALLSPARK_AMPERE_M_CUBLAS_THRESHOLD,
16 + ALLSPARK_SUPPORTED_QUANT_TYPES,
17 +)
18 from vllm.model_executor.layers.quantization.utils.marlin_utils import (
14 - GPTQ_MARLIN_MAX_PARALLEL, GPTQ_MARLIN_MIN_THREAD_N,
15 - MARLIN_SUPPORTED_GROUP_SIZES, query_marlin_supported_quant_types)
19 + GPTQ_MARLIN_MAX_PARALLEL,
20 + GPTQ_MARLIN_MIN_THREAD_N,
21 + MARLIN_SUPPORTED_GROUP_SIZES,
22 + query_marlin_supported_quant_types,
23 +)
24 from vllm.model_executor.layers.quantization.utils.marlin_utils_test import (
17 - MarlinWorkspace, marlin_quantize)
25 + MarlinWorkspace,
26 + marlin_quantize,
27 +)
28 from vllm.model_executor.layers.quantization.utils.marlin_utils_test_24 import (
19 - marlin_24_quantize)
29 + marlin_24_quantize,
30 +)
31 from vllm.model_executor.layers.quantization.utils.quant_utils import (
21 - gptq_pack, gptq_quantize_weights, quantize_weights, sort_weights)
32 + gptq_pack,
33 + gptq_quantize_weights,
34 + quantize_weights,
35 + sort_weights,
36 +)
37 from vllm.scalar_type import ScalarType
38 from vllm.utils import FlexibleArgumentParser
39
@@ -29,22 +44,29 @@ ACT_ORDER_OPTS = [False, True]
44 K_FULL_OPTS = [False, True]
45
46
32 -def bench_run(results: list[benchmark.Measurement], model: str,
33 - act_order: bool, is_k_full: bool, quant_type: ScalarType,
34 - group_size: int, size_m: int, size_k: int, size_n: int):
47 +def bench_run(
48 + results: list[benchmark.Measurement],
49 + model: str,
50 + act_order: bool,
51 + is_k_full: bool,
52 + quant_type: ScalarType,
53 + group_size: int,
54 + size_m: int,
55 + size_k: int,
56 + size_n: int,
57 +):
58 label = "Quant Matmul"
59
37 - sub_label = ("{}, act={} k_full={}, q={}, g={}, "
38 - "MKN=({}x{}x{})".format(model, act_order, is_k_full,
39 - str(quant_type), group_size, size_m,
40 - size_k, size_n))
60 + sub_label = "{}, act={} k_full={}, q={}, g={}, MKN=({}x{}x{})".format(
61 + model, act_order, is_k_full, str(quant_type), group_size, size_m, size_k, size_n
62 + )
63
64 print(f"Testing: {sub_label}")
65
66 a = torch.randn(size_m, size_k).to(torch.half).cuda()
67 b = torch.rand(size_k, size_n).to(torch.half).cuda()
68
47 - a_tmp = (torch.zeros(size_m, size_k).to(torch.half).cuda())
69 + a_tmp = torch.zeros(size_m, size_k).to(torch.half).cuda()
70
71 # Marlin quant
72 (
@@ -57,14 +79,16 @@ def bench_run(results: list[benchmark.Measurement], model: str,
79 ) = marlin_quantize(b, quant_type, group_size, act_order)
80
81 # Marlin_24 quant
60 - (marlin_24_w_ref, marlin_24_q_w_comp, marlin_24_meta,
61 - marlin_24_s) = marlin_24_quantize(b, quant_type, group_size)
82 + (marlin_24_w_ref, marlin_24_q_w_comp, marlin_24_meta, marlin_24_s) = (
83 + marlin_24_quantize(b, quant_type, group_size)
84 + )
85
86 marlin_zp = torch.empty(0, dtype=torch.int, device=b.device)
87
88 # GPTQ quant
66 - (w_ref, q_w, s, g_idx,
67 - rand_perm) = gptq_quantize_weights(b, quant_type, group_size, act_order)
89 + (w_ref, q_w, s, g_idx, rand_perm) = gptq_quantize_weights(
90 + b, quant_type, group_size, act_order
91 + )
92 q_w_gptq = gptq_pack(q_w, quant_type.size_bits, size_k, size_n)
93
94 # For act_order, sort the "weights" and "g_idx"
@@ -74,32 +98,37 @@ def bench_run(results: list[benchmark.Measurement], model: str,
98 (q_w, g_idx, repack_sort_indices) = sort_weights(q_w, g_idx)
99
100 # Prepare
77 - marlin_workspace = MarlinWorkspace(size_n, GPTQ_MARLIN_MIN_THREAD_N,
78 - GPTQ_MARLIN_MAX_PARALLEL)
101 + marlin_workspace = MarlinWorkspace(
102 + size_n, GPTQ_MARLIN_MIN_THREAD_N, GPTQ_MARLIN_MAX_PARALLEL
103 + )
104
80 - marlin_24_workspace = MarlinWorkspace(size_n, GPTQ_MARLIN_24_MIN_THREAD_N,
81 - GPTQ_MARLIN_24_MAX_PARALLEL)
105 + marlin_24_workspace = MarlinWorkspace(
106 + size_n, GPTQ_MARLIN_24_MIN_THREAD_N, GPTQ_MARLIN_24_MAX_PARALLEL
107 + )
108 marlin_zp = torch.zeros_like(marlin_s, dtype=torch.int)
109
110 # AllSpark W8A16 quant
85 - as_supported_case = (quant_type in ALLSPARK_SUPPORTED_QUANT_TYPES
86 - and group_size == -1 and not act_order and is_k_full)
111 + as_supported_case = (
112 + quant_type in ALLSPARK_SUPPORTED_QUANT_TYPES
113 + and group_size == -1
114 + and not act_order
115 + and is_k_full
116 + )
117 if as_supported_case:
118 properties = torch.cuda.get_device_properties(b.device.index)
119 sm_count = properties.multi_processor_count
120 sm_version = properties.major * 10 + properties.minor
121
92 - supported_arch = (sm_version >= 80 and sm_version < 90)
122 + supported_arch = sm_version >= 80 and sm_version < 90
123 as_supported_case = as_supported_case and supported_arch
124 if supported_arch:
125 has_zp = False
96 - w_ref, qw, s, zp = quantize_weights(b, quant_type, group_size,
97 - has_zp)
126 + w_ref, qw, s, zp = quantize_weights(b, quant_type, group_size, has_zp)
127 qw = qw.to(torch.uint8)
128
100 - qw_reorder, s_reorder, zp_reorder = \
101 - ops.allspark_repack_weight(
102 - qw, s, zp, has_zp)
129 + qw_reorder, s_reorder, zp_reorder = ops.allspark_repack_weight(
130 + qw, s, zp, has_zp
131 + )
132 CUBLAS_M_THRESHOLD = ALLSPARK_AMPERE_M_CUBLAS_THRESHOLD
133
134 globals = {
@@ -136,8 +165,7 @@ def bench_run(results: list[benchmark.Measurement], model: str,
165 "zp_reorder": zp_reorder if as_supported_case else None,
166 "sm_count": sm_count if as_supported_case else None,
167 "sm_version": sm_version if as_supported_case else None,
139 - "CUBLAS_M_THRESHOLD":
140 - CUBLAS_M_THRESHOLD if as_supported_case else None,
168 + "CUBLAS_M_THRESHOLD": CUBLAS_M_THRESHOLD if as_supported_case else None,
169 # Kernels
170 "gptq_marlin_gemm": ops.gptq_marlin_gemm,
171 "gptq_marlin_24_gemm": ops.gptq_marlin_24_gemm,
@@ -158,60 +186,63 @@ def bench_run(results: list[benchmark.Measurement], model: str,
186 label=label,
187 sub_label=sub_label,
188 description="pytorch_gemm",
161 - ).blocked_autorange(min_run_time=min_run_time))
189 + ).blocked_autorange(min_run_time=min_run_time)
190 + )
191
192 results.append(
193 benchmark.Timer(
165 - stmt=
166 - "output = gptq_marlin_gemm(a, marlin_q_w, marlin_s, marlin_zp, marlin_g_idx, marlin_sort_indices, marlin_workspace.scratch, quant_type, size_m, size_n, size_k, is_k_full, False, False, False)", # noqa: E501
194 + stmt="output = gptq_marlin_gemm(a, marlin_q_w, marlin_s, marlin_zp, marlin_g_idx, marlin_sort_indices, marlin_workspace.scratch, quant_type, size_m, size_n, size_k, is_k_full, False, False, False)", # noqa: E501
195 globals=globals,
196 label=label,
197 sub_label=sub_label,
198 description="gptq_marlin_gemm_fp16",
171 - ).blocked_autorange(min_run_time=min_run_time))
199 + ).blocked_autorange(min_run_time=min_run_time)
200 + )
201
202 results.append(
203 benchmark.Timer(
175 - stmt=
176 - "output = gptq_marlin_gemm(a, marlin_q_w, marlin_s, marlin_zp, marlin_g_idx, marlin_sort_indices, marlin_workspace.scratch, quant_type, size_m, size_n, size_k, is_k_full, False, True, False)", # noqa: E501
204 + stmt="output = gptq_marlin_gemm(a, marlin_q_w, marlin_s, marlin_zp, marlin_g_idx, marlin_sort_indices, marlin_workspace.scratch, quant_type, size_m, size_n, size_k, is_k_full, False, True, False)", # noqa: E501
205 globals=globals,
206 label=label,
207 sub_label=sub_label,
208 description="gptq_marlin_gemm_fp32",
181 - ).blocked_autorange(min_run_time=min_run_time))
209 + ).blocked_autorange(min_run_time=min_run_time)
210 + )
211
183 - if (quant_type in GPTQ_MARLIN_24_SUPPORTED_QUANT_TYPES
184 - and group_size in GPTQ_MARLIN_24_SUPPORTED_GROUP_SIZES):
212 + if (
213 + quant_type in GPTQ_MARLIN_24_SUPPORTED_QUANT_TYPES
214 + and group_size in GPTQ_MARLIN_24_SUPPORTED_GROUP_SIZES
215 + ):
216 results.append(
217 benchmark.Timer(
187 - stmt=
188 - "output = gptq_marlin_24_gemm(a, marlin_24_q_w_comp, marlin_24_meta, marlin_24_s, marlin_24_workspace.scratch, quant_type, size_m, size_n, size_k)", # noqa: E501
218 + stmt="output = gptq_marlin_24_gemm(a, marlin_24_q_w_comp, marlin_24_meta, marlin_24_s, marlin_24_workspace.scratch, quant_type, size_m, size_n, size_k)", # noqa: E501
219 globals=globals,
220 label=label,
221 sub_label=sub_label,
222 description="gptq_marlin_24_gemm",
193 - ).blocked_autorange(min_run_time=min_run_time))
223 + ).blocked_autorange(min_run_time=min_run_time)
224 + )
225
226 results.append(
227 benchmark.Timer(
197 - stmt=
198 - "q_res = gptq_marlin_repack(q_w_gptq, repack_sort_indices, size_k, size_n, quant_type.size_bits)", # noqa: E501
228 + stmt="q_res = gptq_marlin_repack(q_w_gptq, repack_sort_indices, size_k, size_n, quant_type.size_bits)", # noqa: E501
229 globals=globals,
230 label=label,
231 sub_label=sub_label,
232 description="gptq_marlin_repack",
203 - ).blocked_autorange(min_run_time=min_run_time))
233 + ).blocked_autorange(min_run_time=min_run_time)
234 + )
235
236 if as_supported_case:
237 results.append(
238 benchmark.Timer(
208 - stmt=
209 - "output = allspark_w8a16_gemm(a, qw_reorder, s_reorder, zp_reorder, size_n, group_size, sm_count, sm_version, CUBLAS_M_THRESHOLD, False, True)", # noqa: E501
239 + stmt="output = allspark_w8a16_gemm(a, qw_reorder, s_reorder, zp_reorder, size_n, group_size, sm_count, sm_version, CUBLAS_M_THRESHOLD, False, True)", # noqa: E501
240 globals=globals,
241 label=label,
242 sub_label=sub_label,
243 description="allspark_w8a16_gemm_fp32",
214 - ).blocked_autorange(min_run_time=min_run_time))
244 + ).blocked_autorange(min_run_time=min_run_time)
245 + )
246
247
248 def main(args):
@@ -233,37 +264,50 @@ def main(args):
264 continue
265
266 for act_order in ACT_ORDER_OPTS:
236 - if len(args.limit_act_order
237 - ) > 0 and act_order not in args.limit_act_order:
267 + if (
268 + len(args.limit_act_order) > 0
269 + and act_order not in args.limit_act_order
270 + ):
271 continue
272
273 for is_k_full in K_FULL_OPTS:
241 - if len(args.limit_k_full
242 - ) > 0 and is_k_full not in args.limit_k_full:
274 + if (
275 + len(args.limit_k_full) > 0
276 + and is_k_full not in args.limit_k_full
277 + ):
278 continue
279
245 - for quant_type in query_marlin_supported_quant_types(
246 - False):
247 - if len(args.limit_num_bits) > 0 and \
248 - quant_type.size_bits not in args.limit_num_bits:
280 + for quant_type in query_marlin_supported_quant_types(False):
281 + if (
282 + len(args.limit_num_bits) > 0
283 + and quant_type.size_bits not in args.limit_num_bits
284 + ):
285 continue
286
287 for group_size in MARLIN_SUPPORTED_GROUP_SIZES:
252 - if len(
253 - args.limit_group_size
254 - ) > 0 and group_size not in args.limit_group_size:
288 + if (
289 + len(args.limit_group_size) > 0
290 + and group_size not in args.limit_group_size
291 + ):
292 continue
293
294 # For act_order, the group_size must be less than
295 # size_k
259 - if act_order and (group_size == size_k
260 - or group_size == -1):
296 + if act_order and (group_size == size_k or group_size == -1):
297 continue
298
299 for size_m in args.batch_sizes:
264 - bench_run(results, model, act_order, is_k_full,
265 - quant_type, group_size, size_m,
266 - size_k, size_n)
300 + bench_run(
301 + results,
302 + model,
303 + act_order,
304 + is_k_full,
305 + quant_type,
306 + group_size,
307 + size_m,
308 + size_k,
309 + size_n,
310 + )
311
312 compare = benchmark.Compare(results)
313 compare.print()
@@ -274,7 +318,8 @@ def main(args):
318 #
319 if __name__ == "__main__":
320 parser = FlexibleArgumentParser(
277 - description="Benchmark Marlin across specified models/shapes/batches")
321 + description="Benchmark Marlin across specified models/shapes/batches"
322 + )
323 parser.add_argument(
324 "--models",
325 nargs="+",
@@ -282,10 +327,9 @@ if __name__ == "__main__":
327 default=DEFAULT_MODELS,
328 choices=WEIGHT_SHAPES.keys(),
329 )
285 - parser.add_argument("--batch-sizes",
286 - nargs="+",
287 - type=int,
288 - default=DEFAULT_BATCH_SIZES)
330 + parser.add_argument(
331 + "--batch-sizes", nargs="+", type=int, default=DEFAULT_BATCH_SIZES
332 + )
333 parser.add_argument("--limit-k", nargs="+", type=int, default=[])
334 parser.add_argument("--limit-n", nargs="+", type=int, default=[])
335 parser.add_argument("--limit-group-size", nargs="+", type=int, default=[])
benchmarks/kernels/benchmark_moe.py
+237 -165
@@ -31,56 +31,60 @@ class BenchmarkConfig(TypedDict):
31 num_stages: int
32
33
34 -def benchmark_config(config: BenchmarkConfig,
35 - num_tokens: int,
36 - num_experts: int,
37 - shard_intermediate_size: int,
38 - hidden_size: int,
39 - topk: int,
40 - dtype: torch.dtype,
41 - use_fp8_w8a8: bool,
42 - use_int8_w8a16: bool,
43 - num_iters: int = 100,
44 - block_quant_shape: List[int] = None,
45 - use_deep_gemm: bool = False) -> float:
34 +def benchmark_config(
35 + config: BenchmarkConfig,
36 + num_tokens: int,
37 + num_experts: int,
38 + shard_intermediate_size: int,
39 + hidden_size: int,
40 + topk: int,
41 + dtype: torch.dtype,
42 + use_fp8_w8a8: bool,
43 + use_int8_w8a16: bool,
44 + num_iters: int = 100,
45 + block_quant_shape: List[int] = None,
46 + use_deep_gemm: bool = False,
47 +) -> float:
48 init_dtype = torch.float16 if use_fp8_w8a8 else dtype
49 x = torch.randn(num_tokens, hidden_size, dtype=dtype)
50 if use_int8_w8a16:
49 - w1 = torch.randint(-127,
50 - 127, (
51 - num_experts,
52 - shard_intermediate_size,
53 - hidden_size,
54 - ),
55 - dtype=torch.int8)
56 - w2 = torch.randint(-127,
57 - 127, (
58 - num_experts,
59 - hidden_size,
60 - shard_intermediate_size // 2,
61 - ),
62 - dtype=torch.int8)
51 + w1 = torch.randint(
52 + -127,
53 + 127,
54 + (
55 + num_experts,
56 + shard_intermediate_size,
57 + hidden_size,
58 + ),
59 + dtype=torch.int8,
60 + )
61 + w2 = torch.randint(
62 + -127,
63 + 127,
64 + (
65 + num_experts,
66 + hidden_size,
67 + shard_intermediate_size // 2,
68 + ),
69 + dtype=torch.int8,
70 + )
71 else:
64 - w1 = torch.randn(num_experts,
65 - shard_intermediate_size,
66 - hidden_size,
67 - dtype=init_dtype)
68 - w2 = torch.randn(num_experts,
69 - hidden_size,
70 - shard_intermediate_size // 2,
71 - dtype=init_dtype)
72 - gating_output = torch.randn(num_iters,
73 - num_tokens,
74 - num_experts,
75 - dtype=torch.float32)
72 + w1 = torch.randn(
73 + num_experts, shard_intermediate_size, hidden_size, dtype=init_dtype
74 + )
75 + w2 = torch.randn(
76 + num_experts, hidden_size, shard_intermediate_size // 2, dtype=init_dtype
77 + )
78 + gating_output = torch.randn(num_iters, num_tokens, num_experts, dtype=torch.float32)
79
80 w1_scale = None
81 w2_scale = None
82 a1_scale = None
83 a2_scale = None
84 if use_int8_w8a16:
82 - w1_scale = torch.randn((num_experts, 2 * shard_intermediate_size),
83 - dtype=torch.float32)
85 + w1_scale = torch.randn(
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_fp8_w8a8:
90 if block_quant_shape:
@@ -93,10 +97,14 @@ def benchmark_config(config: BenchmarkConfig,
97 n_tiles_w2 = (K + block_n - 1) // block_n
98 k_tiles_w1 = (K + block_k - 1) // block_k
99 k_tiles_w2 = (N + block_k - 1) // block_k
96 - w1_scale = torch.rand((E, n_tiles_w1, k_tiles_w1),
97 - dtype=torch.float32) * factor_for_scale
98 - w2_scale = torch.rand((E, n_tiles_w2, k_tiles_w2),
99 - dtype=torch.float32) * factor_for_scale
100 + w1_scale = (
101 + torch.rand((E, n_tiles_w1, k_tiles_w1), dtype=torch.float32)
102 + * factor_for_scale
103 + )
104 + w2_scale = (
105 + torch.rand((E, n_tiles_w2, k_tiles_w2), dtype=torch.float32)
106 + * factor_for_scale
107 + )
108 else:
109 w1_scale = torch.randn(num_experts, dtype=torch.float32)
110 w2_scale = torch.randn(num_experts, dtype=torch.float32)
@@ -114,10 +122,12 @@ def benchmark_config(config: BenchmarkConfig,
122
123 def run():
124 from vllm.model_executor.layers.fused_moe import override_config
125 +
126 with override_config(config):
127 if use_deep_gemm:
128 topk_weights, topk_ids, token_expert_indices = fused_topk(
120 - x, input_gating, topk, False)
129 + x, input_gating, topk, False
130 + )
131 return fused_experts(
132 x,
133 w1,
@@ -213,8 +223,7 @@ def get_rocm_tuning_space(use_fp16):
223 return param_ranges
224
225
216 -def get_configs_compute_bound(use_fp16,
217 - block_quant_shape) -> list[dict[str, int]]:
226 +def get_configs_compute_bound(use_fp16, block_quant_shape) -> list[dict[str, int]]:
227 configs: list[BenchmarkConfig] = []
228
229 if current_platform.is_rocm():
@@ -250,20 +259,25 @@ def get_configs_compute_bound(use_fp16,
259 if block_quant_shape is not None and not use_fp16:
260 block_n, block_k = block_quant_shape[0], block_quant_shape[1]
261 for config in configs[:]:
253 - if config["BLOCK_SIZE_K"] % block_k != 0 or config[
254 - "BLOCK_SIZE_N"] % block_n != 0:
262 + if (
263 + config["BLOCK_SIZE_K"] % block_k != 0
264 + or config["BLOCK_SIZE_N"] % block_n != 0
265 + ):
266 configs.remove(config)
267 return configs
268
269
259 -def prune_rocm_search_space(num_tokens, shard_intermediate_size, hidden_size,
260 - search_space, is_fp16, topk):
270 +def prune_rocm_search_space(
271 + num_tokens, shard_intermediate_size, hidden_size, search_space, is_fp16, topk
272 +):
273 N1, K1 = shard_intermediate_size, hidden_size
274 N2, K2 = hidden_size, shard_intermediate_size // 2
263 - pruned_space_1 = prune_rocm_configs(num_tokens * topk, N1, K1,
264 - search_space, is_fp16)
265 - pruned_space_2 = prune_rocm_configs(num_tokens * topk, N2, K2,
266 - search_space, is_fp16)
275 + pruned_space_1 = prune_rocm_configs(
276 + num_tokens * topk, N1, K1, search_space, is_fp16
277 + )
278 + pruned_space_2 = prune_rocm_configs(
279 + num_tokens * topk, N2, K2, search_space, is_fp16
280 + )
281 search_space = merge_unique_dicts(pruned_space_1, pruned_space_2)
282 return search_space
283
@@ -301,14 +315,14 @@ def prune_rocm_configs(M, N, K, configs, is_fp16=True):
315 SPLIT_K = config.get("SPLIT_K", 1)
316 GROUP_M = config.get("GROUP_SIZE_M")
317 if is_fp16:
304 - if (matrix_instr_nonkdim > BLOCK_SIZE_M
305 - or matrix_instr_nonkdim > BLOCK_SIZE_N):
318 + if (
319 + matrix_instr_nonkdim > BLOCK_SIZE_M
320 + or matrix_instr_nonkdim > BLOCK_SIZE_N
321 + ):
322 continue
307 - if (matrix_instr_nonkdim >= M
308 - and matrix_instr_nonkdim != BLOCK_SIZE_M):
323 + if matrix_instr_nonkdim >= M and matrix_instr_nonkdim != BLOCK_SIZE_M:
324 continue
310 - if (matrix_instr_nonkdim >= N
311 - and matrix_instr_nonkdim != BLOCK_SIZE_N):
325 + if matrix_instr_nonkdim >= N and matrix_instr_nonkdim != BLOCK_SIZE_N:
326 continue
327 # Skip BLOCK_SIZE that is too large compare to M/N
328 # unless BLOCK_SIZE is already small enough
@@ -329,8 +343,10 @@ def prune_rocm_configs(M, N, K, configs, is_fp16=True):
343 continue
344 # out of shared memory resource
345 # TODO (zhanglx): This does not consider the LDS usage in the epilogue
332 - LDS = (BLOCK_SIZE_K * BLOCK_SIZE_M * elemBytes_a +
333 - BLOCK_SIZE_K * BLOCK_SIZE_N * elemBytes_b)
346 + LDS = (
347 + BLOCK_SIZE_K * BLOCK_SIZE_M * elemBytes_a
348 + + BLOCK_SIZE_K * BLOCK_SIZE_N * elemBytes_b
349 + )
350 if LDS > 65536:
351 continue
352 # Skip small block sizes and num_warps for large gemm
@@ -364,7 +380,6 @@ def merge_unique_dicts(list1, list2):
380
381 @ray.remote(num_gpus=1)
382 class BenchmarkWorker:
367 -
383 def __init__(self, seed: int) -> None:
384 torch.set_default_device("cuda")
385 current_platform.seed_everything(seed)
@@ -388,36 +403,40 @@ class BenchmarkWorker:
403 use_deep_gemm: bool = False,
404 ) -> tuple[dict[str, int], float]:
405 current_platform.seed_everything(self.seed)
391 - dtype_str = get_config_dtype_str(dtype,
392 - use_int8_w8a16=use_int8_w8a16,
393 - use_fp8_w8a8=use_fp8_w8a8)
406 + dtype_str = get_config_dtype_str(
407 + dtype, use_int8_w8a16=use_int8_w8a16, use_fp8_w8a8=use_fp8_w8a8
408 + )
409 # NOTE(woosuk): The current naming convention uses w2.shape[2], which
410 # is the intermediate size after silu_and_mul.
396 - op_config = get_moe_configs(num_experts, shard_intermediate_size // 2,
397 - dtype_str)
411 + op_config = get_moe_configs(
412 + num_experts, shard_intermediate_size // 2, dtype_str
413 + )
414 if op_config is None:
399 - config = get_default_config(num_tokens,
400 - num_experts,
401 - shard_intermediate_size,
402 - hidden_size,
403 - topk,
404 - dtype_str,
405 - is_marlin=False)
415 + config = get_default_config(
416 + num_tokens,
417 + num_experts,
418 + shard_intermediate_size,
419 + hidden_size,
420 + topk,
421 + dtype_str,
422 + is_marlin=False,
423 + )
424 else:
407 - config = op_config[min(op_config.keys(),
408 - key=lambda x: abs(x - num_tokens))]
409 - kernel_time = benchmark_config(config,
410 - num_tokens,
411 - num_experts,
412 - shard_intermediate_size,
413 - hidden_size,
414 - topk,
415 - dtype,
416 - use_fp8_w8a8,
417 - use_int8_w8a16,
418 - num_iters=100,
419 - block_quant_shape=block_quant_shape,
420 - use_deep_gemm=use_deep_gemm)
425 + config = op_config[min(op_config.keys(), key=lambda x: abs(x - num_tokens))]
426 + kernel_time = benchmark_config(
427 + config,
428 + num_tokens,
429 + num_experts,
430 + shard_intermediate_size,
431 + hidden_size,
432 + topk,
433 + dtype,
434 + use_fp8_w8a8,
435 + use_int8_w8a16,
436 + num_iters=100,
437 + block_quant_shape=block_quant_shape,
438 + use_deep_gemm=use_deep_gemm,
439 + )
440 return config, kernel_time
441
442 def tune(
@@ -438,10 +457,14 @@ class BenchmarkWorker:
457 best_time = float("inf")
458 if current_platform.is_rocm():
459 is_fp16 = not (use_fp8_w8a8 or use_int8_w8a16)
441 - search_space = prune_rocm_search_space(num_tokens,
442 - shard_intermediate_size,
443 - hidden_size, search_space,
444 - is_fp16, topk)
460 + search_space = prune_rocm_search_space(
461 + num_tokens,
462 + shard_intermediate_size,
463 + hidden_size,
464 + search_space,
465 + is_fp16,
466 + topk,
467 + )
468
469 need_device_guard = False
470 if current_platform.is_rocm():
@@ -449,8 +472,7 @@ class BenchmarkWorker:
472 if visible_device != f"{self.device_id}":
473 need_device_guard = True
474
452 - with torch.cuda.device(
453 - self.device_id) if need_device_guard else nullcontext():
475 + with torch.cuda.device(self.device_id) if need_device_guard else nullcontext():
476 for config in tqdm(search_space):
477 try:
478 kernel_time = benchmark_config(
@@ -465,7 +487,8 @@ class BenchmarkWorker:
487 use_int8_w8a16,
488 num_iters=20,
489 block_quant_shape=block_quant_shape,
468 - use_deep_gemm=use_deep_gemm)
490 + use_deep_gemm=use_deep_gemm,
491 + )
492 except triton.runtime.autotuner.OutOfResources:
493 # Some configurations may be invalid and fail to compile.
494 continue
@@ -481,42 +504,44 @@ class BenchmarkWorker:
504
505 def sort_config(config: BenchmarkConfig) -> BenchmarkConfig:
506 return {
484 - "BLOCK_SIZE_M":
485 - config["BLOCK_SIZE_M"],
486 - "BLOCK_SIZE_N":
487 - config["BLOCK_SIZE_N"],
488 - "BLOCK_SIZE_K":
489 - config["BLOCK_SIZE_K"],
490 - "GROUP_SIZE_M":
491 - config["GROUP_SIZE_M"],
492 - "num_warps":
493 - config["num_warps"],
494 - "num_stages":
495 - config["num_stages"],
496 - **({
497 - "waves_per_eu": config["waves_per_eu"]
498 - } if "waves_per_eu" in config else {}),
499 - **({
500 - "matrix_instr_nonkdim": config["matrix_instr_nonkdim"]
501 - } if "matrix_instr_nonkdim" in config else {}),
502 - **({
503 - "kpack": config["kpack"]
504 - } if "kpack" in config else {}),
507 + "BLOCK_SIZE_M": config["BLOCK_SIZE_M"],
508 + "BLOCK_SIZE_N": config["BLOCK_SIZE_N"],
509 + "BLOCK_SIZE_K": config["BLOCK_SIZE_K"],
510 + "GROUP_SIZE_M": config["GROUP_SIZE_M"],
511 + "num_warps": config["num_warps"],
512 + "num_stages": config["num_stages"],
513 + **(
514 + {"waves_per_eu": config["waves_per_eu"]} if "waves_per_eu" in config else {}
515 + ),
516 + **(
517 + {"matrix_instr_nonkdim": config["matrix_instr_nonkdim"]}
518 + if "matrix_instr_nonkdim" in config
519 + else {}
520 + ),
521 + **({"kpack": config["kpack"]} if "kpack" in config else {}),
522 }
523
524
508 -def save_configs(configs: dict[int, BenchmarkConfig], num_experts: int,
509 - shard_intermediate_size: int, hidden_size: int, topk: int,
510 - dtype: torch.dtype, use_fp8_w8a8: bool, use_int8_w8a16: bool,
511 - block_quant_shape: List[int]) -> None:
512 - dtype_str = get_config_dtype_str(dtype,
513 - use_int8_w8a16=use_int8_w8a16,
514 - use_fp8_w8a8=use_fp8_w8a8)
525 +def save_configs(
526 + configs: dict[int, BenchmarkConfig],
527 + num_experts: int,
528 + shard_intermediate_size: int,
529 + hidden_size: int,
530 + topk: int,
531 + dtype: torch.dtype,
532 + use_fp8_w8a8: bool,
533 + use_int8_w8a16: bool,
534 + block_quant_shape: List[int],
535 +) -> None:
536 + dtype_str = get_config_dtype_str(
537 + dtype, use_int8_w8a16=use_int8_w8a16, use_fp8_w8a8=use_fp8_w8a8
538 + )
539
540 # NOTE(woosuk): The current naming convention uses w2.shape[2], which
541 # is the intermediate size after silu_and_mul.
518 - filename = get_config_file_name(num_experts, shard_intermediate_size // 2,
519 - dtype_str, block_quant_shape)
542 + filename = get_config_file_name(
543 + num_experts, shard_intermediate_size // 2, dtype_str, block_quant_shape
544 + )
545
546 print(f"Writing best config to {filename}...")
547 with open(filename, "w") as f:
@@ -525,18 +550,16 @@ def save_configs(configs: dict[int, BenchmarkConfig], num_experts: int,
550
551
552 def get_weight_block_size_safety(config, default_value=None):
528 -
529 - quantization_config = getattr(config, 'quantization_config', {})
553 + quantization_config = getattr(config, "quantization_config", {})
554 if isinstance(quantization_config, dict):
531 - return quantization_config.get('weight_block_size', default_value)
555 + return quantization_config.get("weight_block_size", default_value)
556 return default_value
557
558
559 def main(args: argparse.Namespace):
560 print(args)
561
538 - config = get_config(model=args.model,
539 - trust_remote_code=args.trust_remote_code)
562 + config = get_config(model=args.model, trust_remote_code=args.trust_remote_code)
563 if args.model_prefix:
564 config = getattr(config, args.model_prefix)
565 config = SimpleNamespace(**config)
@@ -551,14 +574,12 @@ def main(args: argparse.Namespace):
574 topk = config.num_experts_per_tok
575 intermediate_size = config.intermediate_size
576 shard_intermediate_size = 2 * intermediate_size // args.tp_size
554 - elif (config.architectures[0]
555 - in ("DeepseekV3ForCausalLM", "DeepseekV2ForCausalLM")):
577 + elif config.architectures[0] in ("DeepseekV3ForCausalLM", "DeepseekV2ForCausalLM"):
578 E = config.n_routed_experts
579 topk = config.num_experts_per_tok
580 intermediate_size = config.moe_intermediate_size
581 shard_intermediate_size = 2 * intermediate_size // args.tp_size
560 - elif config.architectures[0] in ("Qwen2MoeForCausalLM",
561 - "Qwen3MoeForCausalLM"):
582 + elif config.architectures[0] in ("Qwen2MoeForCausalLM", "Qwen3MoeForCausalLM"):
583 E = config.num_experts
584 topk = config.num_experts_per_tok
585 intermediate_size = config.moe_intermediate_size
@@ -573,16 +594,35 @@ def main(args: argparse.Namespace):
594 shard_intermediate_size = 2 * intermediate_size // args.tp_size
595
596 hidden_size = config.hidden_size
576 - dtype = torch.float16 if current_platform.is_rocm() else getattr(
577 - torch, config.torch_dtype)
597 + dtype = (
598 + torch.float16
599 + if current_platform.is_rocm()
600 + else getattr(torch, config.torch_dtype)
601 + )
602 use_fp8_w8a8 = args.dtype == "fp8_w8a8"
603 use_int8_w8a16 = args.dtype == "int8_w8a16"
604 block_quant_shape = get_weight_block_size_safety(config)
605
606 if args.batch_size is None:
607 batch_sizes = [
584 - 1, 2, 4, 8, 16, 24, 32, 48, 64, 96, 128, 256, 512, 1024, 1536,
585 - 2048, 3072, 4096
608 + 1,
609 + 2,
610 + 4,
611 + 8,
612 + 16,
613 + 24,
614 + 32,
615 + 48,
616 + 64,
617 + 96,
618 + 128,
619 + 256,
620 + 512,
621 + 1024,
622 + 1536,
623 + 2048,
624 + 3072,
625 + 4096,
626 ]
627 else:
628 batch_sizes = [args.batch_size]
@@ -593,7 +633,8 @@ def main(args: argparse.Namespace):
633 # Ray will set ROCR_VISIBLE_DEVICES for device visibility
634 logger.warning(
635 "Ray uses ROCR_VISIBLE_DEVICES to control device accessibility."
596 - "Replacing HIP_VISIBLE_DEVICES with ROCR_VISIBLE_DEVICES.")
636 + "Replacing HIP_VISIBLE_DEVICES with ROCR_VISIBLE_DEVICES."
637 + )
638 val = os.environ["HIP_VISIBLE_DEVICES"]
639 os.environ["ROCR_VISIBLE_DEVICES"] = val
640 del os.environ["HIP_VISIBLE_DEVICES"]
@@ -620,25 +661,59 @@ def main(args: argparse.Namespace):
661
662 start = time.time()
663 configs = _distribute(
623 - "tune", [(batch_size, E, shard_intermediate_size, hidden_size,
624 - topk, dtype, use_fp8_w8a8, use_int8_w8a16, search_space,
625 - block_quant_shape, use_deep_gemm)
626 - for batch_size in batch_sizes])
664 + "tune",
665 + [
666 + (
667 + batch_size,
668 + E,
669 + shard_intermediate_size,
670 + hidden_size,
671 + topk,
672 + dtype,
673 + use_fp8_w8a8,
674 + use_int8_w8a16,
675 + search_space,
676 + block_quant_shape,
677 + use_deep_gemm,
678 + )
679 + for batch_size in batch_sizes
680 + ],
681 + )
682 best_configs = {
628 - M: sort_config(config)
629 - for M, config in zip(batch_sizes, configs)
683 + M: sort_config(config) for M, config in zip(batch_sizes, configs)
684 }
631 - save_configs(best_configs, E, shard_intermediate_size, hidden_size,
632 - topk, dtype, use_fp8_w8a8, use_int8_w8a16,
633 - block_quant_shape)
685 + save_configs(
686 + best_configs,
687 + E,
688 + shard_intermediate_size,
689 + hidden_size,
690 + topk,
691 + dtype,
692 + use_fp8_w8a8,
693 + use_int8_w8a16,
694 + block_quant_shape,
695 + )
696 end = time.time()
697 print(f"Tuning took {end - start:.2f} seconds")
698 else:
699 outputs = _distribute(
700 "benchmark",
639 - [(batch_size, E, shard_intermediate_size, hidden_size, topk, dtype,
640 - use_fp8_w8a8, use_int8_w8a16, block_quant_shape, use_deep_gemm)
641 - for batch_size in batch_sizes])
701 + [
702 + (
703 + batch_size,
704 + E,
705 + shard_intermediate_size,
706 + hidden_size,
707 + topk,
708 + dtype,
709 + use_fp8_w8a8,
710 + use_int8_w8a16,
711 + block_quant_shape,
712 + use_deep_gemm,
713 + )
714 + for batch_size in batch_sizes
715 + ],
716 + )
717
718 for batch_size, (config, kernel_time) in zip(batch_sizes, outputs):
719 print(f"Batch size: {batch_size}, config: {config}")
@@ -647,18 +722,15 @@ def main(args: argparse.Namespace):
722
723 if __name__ == "__main__":
724 parser = FlexibleArgumentParser()
650 - parser.add_argument("--model",
651 - type=str,
652 - default="mistralai/Mixtral-8x7B-Instruct-v0.1")
653 - parser.add_argument("--tp-size",
654 - "-tp",
655 - "--tensor-parallel-size",
656 - type=int,
657 - default=2)
658 - parser.add_argument("--dtype",
659 - type=str,
660 - choices=["auto", "fp8_w8a8", "int8_w8a16"],
661 - default="auto")
725 + parser.add_argument(
726 + "--model", type=str, default="mistralai/Mixtral-8x7B-Instruct-v0.1"
727 + )
728 + parser.add_argument(
729 + "--tp-size", "-tp", "--tensor-parallel-size", type=int, default=2
730 + )
731 + parser.add_argument(
732 + "--dtype", type=str, choices=["auto", "fp8_w8a8", "int8_w8a16"], default="auto"
733 + )
734 parser.add_argument("--use-deep-gemm", action="store_true")
735 parser.add_argument("--seed", type=int, default=0)
736 parser.add_argument("--batch-size", type=int, required=False)
benchmarks/kernels/benchmark_moe_permute_unpermute.py
+160 -93
@@ -8,7 +8,9 @@ import torch
8 from transformers import AutoConfig
9
10 from vllm.model_executor.layers.fused_moe.deep_gemm_moe import (
11 - _moe_permute, _moe_unpermute_and_reduce)
11 + _moe_permute,
12 + _moe_unpermute_and_reduce,
13 +)
14 from vllm.model_executor.layers.fused_moe.fused_moe import *
15 from vllm.model_executor.layers.fused_moe.moe_permute_unpermute import *
16 from vllm.model_executor.layers.fused_moe.utils import _fp8_quantize
@@ -27,15 +29,17 @@ class BenchmarkConfig(TypedDict):
29 num_stages: int
30
31
30 -def benchmark_permute(num_tokens: int,
31 - num_experts: int,
32 - hidden_size: int,
33 - topk: int,
34 - dtype: torch.dtype,
35 - use_fp8_w8a8: bool,
36 - use_int8_w8a16: bool,
37 - num_iters: int = 100,
38 - use_customized_permute: bool = False) -> float:
32 +def benchmark_permute(
33 + num_tokens: int,
34 + num_experts: int,
35 + hidden_size: int,
36 + topk: int,
37 + dtype: torch.dtype,
38 + use_fp8_w8a8: bool,
39 + use_int8_w8a16: bool,
40 + num_iters: int = 100,
41 + use_customized_permute: bool = False,
42 +) -> float:
43 # init_dtype = torch.float16 if use_fp8_w8a8 else dtype
44 hidden_states = torch.randn(num_tokens, hidden_size, dtype=dtype)
45 # output_hidden_states = torch.empty_like(hidden_states)
@@ -46,36 +50,41 @@ def benchmark_permute(num_tokens: int,
50 align_block_size = None
51 qhidden_states = hidden_states
52
49 - gating_output = torch.randn(num_iters,
50 - num_tokens,
51 - num_experts,
52 - dtype=torch.float32)
53 + gating_output = torch.randn(num_iters, num_tokens, num_experts, dtype=torch.float32)
54
55 input_gating = torch.randn(num_tokens, num_experts, dtype=torch.float32)
56 topk_weights, topk_ids, token_expert_indices = fused_topk(
56 - qhidden_states, input_gating, topk, False)
57 + qhidden_states, input_gating, topk, False
58 + )
59
60 def prepare(i: int):
61 input_gating.copy_(gating_output[i])
62
63 def run():
64 if use_customized_permute:
63 - (permuted_hidden_states, first_token_off, inv_perm_idx,
64 - m_indices) = moe_permute(
65 - qhidden_states,
66 - topk_weights=topk_weights,
67 - topk_ids=topk_ids,
68 - token_expert_indices=token_expert_indices,
69 - topk=topk,
70 - n_expert=num_experts,
71 - n_local_expert=num_experts,
72 - expert_map=None,
73 - align_block_size=align_block_size,
74 - )
65 + (permuted_hidden_states, first_token_off, inv_perm_idx, m_indices) = (
66 + moe_permute(
67 + qhidden_states,
68 + topk_weights=topk_weights,
69 + topk_ids=topk_ids,
70 + token_expert_indices=token_expert_indices,
71 + topk=topk,
72 + n_expert=num_experts,
73 + n_local_expert=num_experts,
74 + expert_map=None,
75 + align_block_size=align_block_size,
76 + )
77 + )
78 else:
76 - (permuted_hidden_states, a1q_scale, sorted_token_ids, expert_ids,
77 - inv_perm) = _moe_permute(qhidden_states, None, topk_ids,
78 - num_experts, None, align_block_size)
79 + (
80 + permuted_hidden_states,
81 + a1q_scale,
82 + sorted_token_ids,
83 + expert_ids,
84 + inv_perm,
85 + ) = _moe_permute(
86 + qhidden_states, None, topk_ids, num_experts, None, align_block_size
87 + )
88
89 # JIT compilation & warmup
90 run()
@@ -111,15 +120,17 @@ def benchmark_permute(num_tokens: int,
120 return avg
121
122
114 -def benchmark_unpermute(num_tokens: int,
115 - num_experts: int,
116 - hidden_size: int,
117 - topk: int,
118 - dtype: torch.dtype,
119 - use_fp8_w8a8: bool,
120 - use_int8_w8a16: bool,
121 - num_iters: int = 100,
122 - use_customized_permute: bool = False) -> float:
123 +def benchmark_unpermute(
124 + num_tokens: int,
125 + num_experts: int,
126 + hidden_size: int,
127 + topk: int,
128 + dtype: torch.dtype,
129 + use_fp8_w8a8: bool,
130 + use_int8_w8a16: bool,
131 + num_iters: int = 100,
132 + use_customized_permute: bool = False,
133 +) -> float:
134 # init_dtype = torch.float16 if use_fp8_w8a8 else dtype
135 hidden_states = torch.randn(num_tokens, hidden_size, dtype=dtype)
136 output_hidden_states = torch.empty_like(hidden_states)
@@ -133,46 +144,74 @@ def benchmark_unpermute(num_tokens: int,
144 input_gating = torch.randn(num_tokens, num_experts, dtype=torch.float32)
145
146 topk_weights, topk_ids, token_expert_indices = fused_topk(
136 - qhidden_states, input_gating, topk, False)
147 + qhidden_states, input_gating, topk, False
148 + )
149
150 def prepare():
151 if use_customized_permute:
140 - (permuted_hidden_states, first_token_off, inv_perm_idx,
141 - m_indices) = moe_permute(
142 - qhidden_states,
143 - topk_weights=topk_weights,
144 - topk_ids=topk_ids,
145 - token_expert_indices=token_expert_indices,
146 - topk=topk,
147 - n_expert=num_experts,
148 - n_local_expert=num_experts,
149 - expert_map=None,
150 - align_block_size=align_block_size,
151 - )
152 + (permuted_hidden_states, first_token_off, inv_perm_idx, m_indices) = (
153 + moe_permute(
154 + qhidden_states,
155 + topk_weights=topk_weights,
156 + topk_ids=topk_ids,
157 + token_expert_indices=token_expert_indices,
158 + topk=topk,
159 + n_expert=num_experts,
160 + n_local_expert=num_experts,
161 + expert_map=None,
162 + align_block_size=align_block_size,
163 + )
164 + )
165 # convert to fp16/bf16 as gemm output
153 - return (permuted_hidden_states.to(dtype), first_token_off,
154 - inv_perm_idx, m_indices)
166 + return (
167 + permuted_hidden_states.to(dtype),
168 + first_token_off,
169 + inv_perm_idx,
170 + m_indices,
171 + )
172 else:
156 - (permuted_qhidden_states, a1q_scale, sorted_token_ids, expert_ids,
157 - inv_perm) = _moe_permute(qhidden_states, None, topk_ids,
158 - num_experts, None, align_block_size)
173 + (
174 + permuted_qhidden_states,
175 + a1q_scale,
176 + sorted_token_ids,
177 + expert_ids,
178 + inv_perm,
179 + ) = _moe_permute(
180 + qhidden_states, None, topk_ids, num_experts, None, align_block_size
181 + )
182 # convert to fp16/bf16 as gemm output
160 - return (permuted_qhidden_states.to(dtype), a1q_scale,
161 - sorted_token_ids, expert_ids, inv_perm)
183 + return (
184 + permuted_qhidden_states.to(dtype),
185 + a1q_scale,
186 + sorted_token_ids,
187 + expert_ids,
188 + inv_perm,
189 + )
190
191 def run(input: tuple):
192 if use_customized_permute:
165 - (permuted_hidden_states, first_token_off, inv_perm_idx,
166 - m_indices) = input
167 - moe_unpermute(permuted_hidden_states, topk_weights, topk_ids,
168 - inv_perm_idx, first_token_off, topk, num_experts,
169 - num_experts)
193 + (permuted_hidden_states, first_token_off, inv_perm_idx, m_indices) = input
194 + moe_unpermute(
195 + permuted_hidden_states,
196 + topk_weights,
197 + topk_ids,
198 + inv_perm_idx,
199 + first_token_off,
200 + topk,
201 + num_experts,
202 + num_experts,
203 + )
204 else:
171 - (permuted_hidden_states, a1q_scale, sorted_token_ids, expert_ids,
172 - inv_perm) = input
173 - _moe_unpermute_and_reduce(output_hidden_states,
174 - permuted_hidden_states, inv_perm,
175 - topk_weights)
205 + (
206 + permuted_hidden_states,
207 + a1q_scale,
208 + sorted_token_ids,
209 + expert_ids,
210 + inv_perm,
211 + ) = input
212 + _moe_unpermute_and_reduce(
213 + output_hidden_states, permuted_hidden_states, inv_perm, topk_weights
214 + )
215
216 # JIT compilation & warmup
217 input = prepare()
@@ -209,7 +248,6 @@ def benchmark_unpermute(num_tokens: int,
248
249 @ray.remote(num_gpus=1)
250 class BenchmarkWorker:
212 -
251 def __init__(self, seed: int) -> None:
252 torch.set_default_device("cuda")
253 current_platform.seed_everything(seed)
@@ -241,7 +279,8 @@ class BenchmarkWorker:
279 use_fp8_w8a8,
280 use_int8_w8a16,
281 num_iters=100,
244 - use_customized_permute=use_customized_permute)
282 + use_customized_permute=use_customized_permute,
283 + )
284 unpermute_time = benchmark_unpermute(
285 num_tokens,
286 num_experts,
@@ -251,15 +290,15 @@ class BenchmarkWorker:
290 use_fp8_w8a8,
291 use_int8_w8a16,
292 num_iters=100,
254 - use_customized_permute=use_customized_permute)
293 + use_customized_permute=use_customized_permute,
294 + )
295 return permute_time, unpermute_time
296
297
298 def get_weight_block_size_safety(config, default_value=None):
259 -
260 - quantization_config = getattr(config, 'quantization_config', {})
299 + quantization_config = getattr(config, "quantization_config", {})
300 if isinstance(quantization_config, dict):
262 - return quantization_config.get('weight_block_size', default_value)
301 + return quantization_config.get("weight_block_size", default_value)
302 return default_value
303
304
@@ -267,20 +306,21 @@ def main(args: argparse.Namespace):
306 print(args)
307
308 config = AutoConfig.from_pretrained(
270 - args.model, trust_remote_code=args.trust_remote_code)
309 + args.model, trust_remote_code=args.trust_remote_code
310 + )
311 if config.architectures[0] == "DbrxForCausalLM":
312 E = config.ffn_config.moe_num_experts
313 topk = config.ffn_config.moe_top_k
314 elif config.architectures[0] == "JambaForCausalLM":
315 E = config.num_experts
316 topk = config.num_experts_per_tok
277 - elif (config.architectures[0] == "DeepseekV3ForCausalLM"
278 - or config.architectures[0] == "DeepseekV2ForCausalLM"):
317 + elif (
318 + config.architectures[0] == "DeepseekV3ForCausalLM"
319 + or config.architectures[0] == "DeepseekV2ForCausalLM"
320 + ):
321 E = config.n_routed_experts
322 topk = config.num_experts_per_tok
281 - elif config.architectures[0] in [
282 - "Qwen2MoeForCausalLM", "Qwen3MoeForCausalLM"
283 - ]:
323 + elif config.architectures[0] in ["Qwen2MoeForCausalLM", "Qwen3MoeForCausalLM"]:
324 E = config.num_experts
325 topk = config.num_experts_per_tok
326
@@ -299,8 +339,24 @@ def main(args: argparse.Namespace):
339
340 if args.batch_size is None:
341 batch_sizes = [
302 - 1, 2, 4, 8, 16, 24, 32, 48, 64, 96, 128, 256, 512, 1024, 1536,
303 - 2048, 3072, 4096
342 + 1,
343 + 2,
344 + 4,
345 + 8,
346 + 16,
347 + 24,
348 + 32,
349 + 48,
350 + 64,
351 + 96,
352 + 128,
353 + 256,
354 + 512,
355 + 1024,
356 + 1536,
357 + 2048,
358 + 3072,
359 + 4096,
360 ]
361 else:
362 batch_sizes = [args.batch_size]
@@ -321,9 +377,21 @@ def main(args: argparse.Namespace):
377 return ray.get(outputs)
378
379 outputs = _distribute(
324 - "benchmark", [(batch_size, E, hidden_size, topk, dtype, use_fp8_w8a8,
325 - use_int8_w8a16, use_customized_permute)
326 - for batch_size in batch_sizes])
380 + "benchmark",
381 + [
382 + (
383 + batch_size,
384 + E,
385 + hidden_size,
386 + topk,
387 + dtype,
388 + use_fp8_w8a8,
389 + use_int8_w8a16,
390 + use_customized_permute,
391 + )
392 + for batch_size in batch_sizes
393 + ],
394 + )
395
396 for batch_size, (permute, unpermute) in zip(batch_sizes, outputs):
397 print(f"Batch size: {batch_size}")
@@ -333,13 +401,12 @@ def main(args: argparse.Namespace):
401
402 if __name__ == "__main__":
403 parser = FlexibleArgumentParser()
336 - parser.add_argument("--model",
337 - type=str,
338 - default="mistralai/Mixtral-8x7B-Instruct-v0.1")
339 - parser.add_argument("--dtype",
340 - type=str,
341 - choices=["auto", "fp8_w8a8", "int8_w8a16"],
342 - default="auto")
404 + parser.add_argument(
405 + "--model", type=str, default="mistralai/Mixtral-8x7B-Instruct-v0.1"
406 + )
407 + parser.add_argument(
408 + "--dtype", type=str, choices=["auto", "fp8_w8a8", "int8_w8a16"], default="auto"
409 + )
410 parser.add_argument("--use-customized-permute", action="store_true")
411 parser.add_argument("--seed", type=int, default=0)
412 parser.add_argument("--batch-size", type=int, required=False)
benchmarks/kernels/benchmark_paged_attention.py
+45 -52
@@ -9,8 +9,11 @@ import torch
9 from vllm import _custom_ops as ops
10 from vllm.logger import init_logger
11 from vllm.platforms import current_platform
12 -from vllm.utils import (STR_DTYPE_TO_TORCH_DTYPE, FlexibleArgumentParser,
13 - create_kv_caches_with_random)
12 +from vllm.utils import (
13 + STR_DTYPE_TO_TORCH_DTYPE,
14 + FlexibleArgumentParser,
15 + create_kv_caches_with_random,
16 +)
17
18 logger = init_logger(__name__)
19
@@ -38,19 +41,15 @@ def main(
41 current_platform.seed_everything(seed)
42
43 scale = float(1.0 / (head_size**0.5))
41 - query = torch.empty(num_seqs,
42 - num_query_heads,
43 - head_size,
44 - dtype=dtype,
45 - device=device)
44 + query = torch.empty(
45 + num_seqs, num_query_heads, head_size, dtype=dtype, device=device
46 + )
47 query.uniform_(-scale, scale)
48
49 assert num_query_heads % num_kv_heads == 0
50 alibi_slopes = None
51 if use_alibi:
51 - alibi_slopes = torch.randn(num_query_heads,
52 - dtype=torch.float,
53 - device=device)
52 + alibi_slopes = torch.randn(num_query_heads, dtype=torch.float, device=device)
53
54 seq_lens = [seq_len for _ in range(num_seqs)]
55 max_seq_len = max(seq_lens)
@@ -61,24 +60,23 @@ def main(
60 block_tables_lst: list[list[int]] = []
61 for _ in range(num_seqs):
62 block_table = [
64 - random.randint(0, NUM_BLOCKS - 1)
65 - for _ in range(max_num_blocks_per_seq)
63 + random.randint(0, NUM_BLOCKS - 1) for _ in range(max_num_blocks_per_seq)
64 ]
65 block_tables_lst.append(block_table)
66
69 - block_tables = torch.tensor(block_tables_lst,
70 - dtype=torch.int,
71 - device=device)
67 + block_tables = torch.tensor(block_tables_lst, dtype=torch.int, device=device)
68
69 # Create the KV cache.
74 - key_caches, value_caches = create_kv_caches_with_random(NUM_BLOCKS,
75 - block_size,
76 - 1,
77 - num_kv_heads,
78 - head_size,
79 - kv_cache_dtype,
80 - dtype,
81 - device=device)
70 + key_caches, value_caches = create_kv_caches_with_random(
71 + NUM_BLOCKS,
72 + block_size,
73 + 1,
74 + num_kv_heads,
75 + head_size,
76 + kv_cache_dtype,
77 + dtype,
78 + device=device,
79 + )
80 key_cache, value_cache = key_caches[0], value_caches[0]
81
82 # Prepare for the paged attention kernel.
@@ -86,11 +84,8 @@ def main(
84 if version == "v2":
85 if current_platform.is_rocm():
86 global PARTITION_SIZE
89 - if not args.custom_paged_attn:
90 - PARTITION_SIZE = 1024
91 - else:
92 - PARTITION_SIZE = PARTITION_SIZE_ROCM
93 - num_partitions = ((max_seq_len + PARTITION_SIZE - 1) // PARTITION_SIZE)
87 + PARTITION_SIZE = 1024 if not args.custom_paged_attn else PARTITION_SIZE_ROCM
88 + num_partitions = (max_seq_len + PARTITION_SIZE - 1) // PARTITION_SIZE
89 tmp_output = torch.empty(
90 size=(num_seqs, num_query_heads, num_partitions, head_size),
91 dtype=output.dtype,
@@ -110,9 +105,7 @@ def main(
105 start_time = time.perf_counter()
106
107 # Using default kv_scale
113 - k_scale = v_scale = torch.tensor(1.0,
114 - dtype=torch.float32,
115 - device=device)
108 + k_scale = v_scale = torch.tensor(1.0, dtype=torch.float32, device=device)
109
110 for _ in range(num_iters):
111 if version == "v1":
@@ -195,30 +188,29 @@ def main(
188 print(f"Kernel running time: {latency * 1000000:.3f} us")
189
190
198 -if __name__ == '__main__':
199 - logger.warning("This script benchmarks the paged attention kernel. "
200 - "By default this is no longer used in vLLM inference.")
191 +if __name__ == "__main__":
192 + logger.warning(
193 + "This script benchmarks the paged attention kernel. "
194 + "By default this is no longer used in vLLM inference."
195 + )
196
202 - parser = FlexibleArgumentParser(
203 - description="Benchmark the paged attention kernel.")
204 - parser.add_argument("--version",
205 - type=str,
206 - choices=["v1", "v2"],
207 - default="v2")
197 + parser = FlexibleArgumentParser(description="Benchmark the paged attention kernel.")
198 + parser.add_argument("--version", type=str, choices=["v1", "v2"], default="v2")
199 parser.add_argument("--batch-size", type=int, default=8)
200 parser.add_argument("--seq-len", type=int, default=4096)
201 parser.add_argument("--num-query-heads", type=int, default=64)
202 parser.add_argument("--num-kv-heads", type=int, default=8)
212 - parser.add_argument("--head-size",
213 - type=int,
214 - choices=[64, 80, 96, 112, 120, 128, 192, 256],
215 - default=128)
203 + parser.add_argument(
204 + "--head-size",
205 + type=int,
206 + choices=[64, 80, 96, 112, 120, 128, 192, 256],
207 + default=128,
208 + )
209 parser.add_argument("--block-size", type=int, choices=[16, 32], default=16)
210 parser.add_argument("--use-alibi", action="store_true")
218 - parser.add_argument("--dtype",
219 - type=str,
220 - choices=["half", "bfloat16", "float"],
221 - default="half")
211 + parser.add_argument(
212 + "--dtype", type=str, choices=["half", "bfloat16", "float"], default="half"
213 + )
214 parser.add_argument("--seed", type=int, default=0)
215 parser.add_argument("--profile", action="store_true")
216 parser.add_argument(
@@ -228,10 +220,11 @@ if __name__ == '__main__':
220 default="auto",
221 help="Data type for kv cache storage. If 'auto', will use model "
222 "data type. CUDA 11.8+ supports fp8 (=fp8_e4m3) and fp8_e5m2. "
231 - "ROCm (AMD GPU) supports fp8 (=fp8_e4m3)")
232 - parser.add_argument("--custom-paged-attn",
233 - action="store_true",
234 - help="Use custom paged attention")
223 + "ROCm (AMD GPU) supports fp8 (=fp8_e4m3)",
224 + )
225 + parser.add_argument(
226 + "--custom-paged-attn", action="store_true", help="Use custom paged attention"
227 + )
228 args = parser.parse_args()
229 print(args)
230
benchmarks/kernels/benchmark_quant.py
+38 -33
@@ -10,15 +10,17 @@ from vllm.utils import STR_DTYPE_TO_TORCH_DTYPE, FlexibleArgumentParser
10
11
12 @torch.inference_mode()
13 -def main(num_tokens: int,
14 - hidden_size: int,
15 - static_scale: bool,
16 - quant_dtype: torch.dtype,
17 - dtype: torch.dtype,
18 - seed: int = 0,
19 - do_profile: bool = False,
20 - num_warmup_iters: int = 5,
21 - num_iters: int = 100) -> None:
13 +def main(
14 + num_tokens: int,
15 + hidden_size: int,
16 + static_scale: bool,
17 + quant_dtype: torch.dtype,
18 + dtype: torch.dtype,
19 + seed: int = 0,
20 + do_profile: bool = False,
21 + num_warmup_iters: int = 5,
22 + num_iters: int = 100,
23 +) -> None:
24 current_platform.seed_everything(seed)
25 torch.set_default_device("cuda")
26
@@ -56,7 +58,7 @@ def main(num_tokens: int,
58 print(f"Kernel running time: {latency * 1000000:.3f} us")
59
60
59 -if __name__ == '__main__':
61 +if __name__ == "__main__":
62
63 def to_torch_dtype(dt):
64 if dt == "int8":
@@ -66,37 +68,40 @@ if __name__ == '__main__':
68 raise ValueError(f"Unsupported dtype: {dt}")
69
70 parser = FlexibleArgumentParser(
69 - description="Benchmark the quantization (fp8 or int8) kernel.")
71 + description="Benchmark the quantization (fp8 or int8) kernel."
72 + )
73 parser.add_argument("--num-tokens", type=int, default=4096)
74 parser.add_argument("--hidden-size", type=int, default=8192)
75 parser.add_argument("--static-scale", action="store_true")
73 - parser.add_argument("--quant-dtype",
74 - type=str,
75 - choices=["fp8", "int8"],
76 - default="int8")
77 - parser.add_argument("--dtype",
78 - type=str,
79 - choices=["half", "bfloat16", "float"],
80 - default="half")
76 + parser.add_argument(
77 + "--quant-dtype", type=str, choices=["fp8", "int8"], default="int8"
78 + )
79 + parser.add_argument(
80 + "--dtype", type=str, choices=["half", "bfloat16", "float"], default="half"
81 + )
82
83 parser.add_argument("--seed", type=int, default=0)
84 parser.add_argument("--profile", action="store_true")
85 parser.add_argument("--num-warmup-iters", type=int, default=5)
85 - parser.add_argument("--num-iters",
86 - type=int,
87 - default=100,
88 - help="Number of benchmark iterations. "
89 - "If --profile is set, this number is ignored")
86 + parser.add_argument(
87 + "--num-iters",
88 + type=int,
89 + default=100,
90 + help="Number of benchmark iterations. "
91 + "If --profile is set, this number is ignored",
92 + )
93
94 args = parser.parse_args()
95 print(args)
96
94 - main(num_tokens=args.num_tokens,
95 - hidden_size=args.hidden_size,
96 - static_scale=args.static_scale,
97 - quant_dtype=to_torch_dtype(args.quant_dtype),
98 - dtype=STR_DTYPE_TO_TORCH_DTYPE[args.dtype],
99 - seed=args.seed,
100 - do_profile=args.profile,
101 - num_warmup_iters=args.num_warmup_iters,
102 - num_iters=args.num_iters)
97 + main(
98 + num_tokens=args.num_tokens,
99 + hidden_size=args.hidden_size,
100 + static_scale=args.static_scale,
101 + quant_dtype=to_torch_dtype(args.quant_dtype),
102 + dtype=STR_DTYPE_TO_TORCH_DTYPE[args.dtype],
103 + seed=args.seed,
104 + do_profile=args.profile,
105 + num_warmup_iters=args.num_warmup_iters,
106 + num_iters=args.num_iters,
107 + )
benchmarks/kernels/benchmark_rmsnorm.py
+24 -33
@@ -12,7 +12,6 @@ from vllm.triton_utils import triton
12
13
14 class HuggingFaceRMSNorm(nn.Module):
15 -
15 def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
16 super().__init__()
17 self.weight = nn.Parameter(torch.ones(hidden_size))
@@ -114,23 +113,19 @@ def rmsnorm_vllm(
113
114 def calculate_diff(batch_size, seq_len, hidden_size, use_residual=True):
115 dtype = torch.bfloat16
117 - x = torch.randn(batch_size,
118 - seq_len,
119 - hidden_size,
120 - dtype=dtype,
121 - device="cuda")
116 + x = torch.randn(batch_size, seq_len, hidden_size, dtype=dtype, device="cuda")
117 weight = torch.ones(hidden_size, dtype=dtype, device="cuda")
118 residual = torch.randn_like(x) if use_residual else None
119
120 output_naive = rmsnorm_naive(
126 - x.clone(), weight,
127 - residual.clone() if residual is not None else None)
121 + x.clone(), weight, residual.clone() if residual is not None else None
122 + )
123 output_flashinfer = rmsnorm_flashinfer(
129 - x.clone(), weight,
130 - residual.clone() if residual is not None else None)
124 + x.clone(), weight, residual.clone() if residual is not None else None
125 + )
126 output_vllm = rmsnorm_vllm(
132 - x.clone(), weight,
133 - residual.clone() if residual is not None else None)
127 + x.clone(), weight, residual.clone() if residual is not None else None
128 + )
129
130 if use_residual:
131 output_naive = output_naive[0]
@@ -141,9 +136,9 @@ def calculate_diff(batch_size, seq_len, hidden_size, use_residual=True):
136 print(f"FlashInfer output={output_flashinfer}")
137 print(f"vLLM output={output_vllm}")
138
144 - if torch.allclose(output_naive, output_flashinfer, atol=1e-2,
145 - rtol=1e-2) and torch.allclose(
146 - output_naive, output_vllm, atol=1e-2, rtol=1e-2):
139 + if torch.allclose(
140 + output_naive, output_flashinfer, atol=1e-2, rtol=1e-2
141 + ) and torch.allclose(output_naive, output_vllm, atol=1e-2, rtol=1e-2):
142 print("✅ All implementations match")
143 else:
144 print("❌ Implementations differ")
@@ -152,12 +147,10 @@ def calculate_diff(batch_size, seq_len, hidden_size, use_residual=True):
147 batch_size_range = [2**i for i in range(0, 7, 2)]
148 seq_length_range = [2**i for i in range(6, 11, 1)]
149 head_num_range = [32, 48]
155 -configs = list(
156 - itertools.product(head_num_range, batch_size_range, seq_length_range))
150 +configs = list(itertools.product(head_num_range, batch_size_range, seq_length_range))
151
152
153 def get_benchmark(use_residual):
160 -
154 @triton.testing.perf_report(
155 triton.testing.Benchmark(
156 x_names=["head_num", "batch_size", "seq_len"],
@@ -167,19 +160,15 @@ def get_benchmark(use_residual):
160 line_names=["HuggingFace", "FlashInfer", "vLLM"],
161 styles=[("blue", "-"), ("green", "-"), ("red", "-")],
162 ylabel="us",
170 - plot_name=
171 - f"rmsnorm-perf-{'with' if use_residual else 'without'}-residual",
163 + plot_name=f"rmsnorm-perf-{'with' if use_residual else 'without'}-residual",
164 args={},
173 - ))
165 + )
166 + )
167 def benchmark(head_num, batch_size, seq_len, provider):
168 dtype = torch.bfloat16
169 hidden_size = head_num * 128 # assuming head_dim = 128
170
178 - x = torch.randn(batch_size,
179 - seq_len,
180 - hidden_size,
181 - dtype=dtype,
182 - device="cuda")
171 + x = torch.randn(batch_size, seq_len, hidden_size, dtype=dtype, device="cuda")
172 weight = torch.ones(hidden_size, dtype=dtype, device="cuda")
173 residual = torch.randn_like(x) if use_residual else None
174
@@ -240,9 +229,9 @@ if __name__ == "__main__":
229 default=4096,
230 help="Hidden size (2nd dimension) of the sequence",
231 )
243 - parser.add_argument("--use-residual",
244 - action="store_true",
245 - help="Whether to use residual connection")
232 + parser.add_argument(
233 + "--use-residual", action="store_true", help="Whether to use residual connection"
234 + )
235 parser.add_argument(
236 "--save-path",
237 type=str,
@@ -253,10 +242,12 @@ if __name__ == "__main__":
242 args = parser.parse_args()
243
244 # Run correctness test
256 - calculate_diff(batch_size=args.batch_size,
257 - seq_len=args.seq_len,
258 - hidden_size=args.hidden_size,
259 - use_residual=args.use_residual)
245 + calculate_diff(
246 + batch_size=args.batch_size,
247 + seq_len=args.seq_len,
248 + hidden_size=args.hidden_size,
249 + use_residual=args.use_residual,
250 + )
251
252 # Get the benchmark function with proper use_residual setting
253 benchmark = get_benchmark(args.use_residual)
benchmarks/kernels/benchmark_rope.py
+46 -37
@@ -6,8 +6,7 @@ from typing import Optional
6 import nvtx
7 import torch
8
9 -from vllm.model_executor.layers.rotary_embedding import (RotaryEmbedding,
10 - get_rope)
9 +from vllm.model_executor.layers.rotary_embedding import RotaryEmbedding, get_rope
10 from vllm.platforms import current_platform
11 from vllm.utils import FlexibleArgumentParser
12
@@ -32,40 +31,49 @@ def benchmark_rope_kernels_multi_lora(
31 # silulating serving 4 LoRAs
32 scaling_factors = [1, 2, 4, 8]
33 # batched RoPE can take multiple scaling factors
35 - batched_rope = get_rope(head_size, rotary_dim, max_position, base,
36 - is_neox_style, {
37 - "rope_type": "linear",
38 - "factor": tuple(scaling_factors)
39 - })
34 + batched_rope = get_rope(
35 + head_size,
36 + rotary_dim,
37 + max_position,
38 + base,
39 + is_neox_style,
40 + {"rope_type": "linear", "factor": tuple(scaling_factors)},
41 + )
42 # non-batched RoPE takes only one scaling factor, we create multiple
43 # instances to simulate the same behavior
44 non_batched_ropes: list[RotaryEmbedding] = []
45 for scaling_factor in scaling_factors:
46 non_batched_ropes.append(
45 - get_rope(head_size, rotary_dim, max_position, base, is_neox_style,
46 - {
47 - "rope_type": "linear",
48 - "factor": (scaling_factor, )
49 - }))
47 + get_rope(
48 + head_size,
49 + rotary_dim,
50 + max_position,
51 + base,
52 + is_neox_style,
53 + {"rope_type": "linear", "factor": (scaling_factor,)},
54 + )
55 + )
56
57 positions = torch.randint(0, max_position, (batch_size, seq_len))
52 - query = torch.randn(batch_size,
53 - seq_len,
54 - num_heads * head_size,
55 - dtype=dtype)
58 + query = torch.randn(batch_size, seq_len, num_heads * head_size, dtype=dtype)
59 key = torch.randn_like(query)
60
61 # create query offsets for batched RoPE, we concat multiple kv cache
62 # together and each query needs to find the right kv cache of its type
63 offset_map = torch.tensor(
64 list(
62 - accumulate([0] + [
63 - max_position * scaling_factor * 2
64 - for scaling_factor in scaling_factors[:-1]
65 - ])))
66 - query_types = torch.randint(0,
67 - len(scaling_factors), (batch_size, seq_len),
68 - device=device)
65 + accumulate(
66 + [0]
67 + + [
68 + max_position * scaling_factor * 2
69 + for scaling_factor in scaling_factors[:-1]
70 + ]
71 + )
72 + )
73 + )
74 + query_types = torch.randint(
75 + 0, len(scaling_factors), (batch_size, seq_len), device=device
76 + )
77 # map query types to offsets
78 query_offsets = offset_map[query_types]
79 # the kernel takes flattened offsets
@@ -86,27 +94,28 @@ def benchmark_rope_kernels_multi_lora(
94 torch.cuda.synchronize()
95
96
89 -if __name__ == '__main__':
97 +if __name__ == "__main__":
98 parser = FlexibleArgumentParser(
91 - description="Benchmark the rotary embedding kernels.")
99 + description="Benchmark the rotary embedding kernels."
100 + )
101 parser.add_argument("--is-neox-style", type=bool, default=True)
102 parser.add_argument("--batch-size", type=int, default=16)
103 parser.add_argument("--seq-len", type=int, default=512)
104 parser.add_argument("--num-heads", type=int, default=8)
96 - parser.add_argument("--head-size",
97 - type=int,
98 - choices=[64, 80, 96, 112, 120, 128, 192, 256],
99 - default=128)
105 + parser.add_argument(
106 + "--head-size",
107 + type=int,
108 + choices=[64, 80, 96, 112, 120, 128, 192, 256],
109 + default=128,
110 + )
111 parser.add_argument("--rotary-dim", type=int, choices=[16, 32], default=32)
101 - parser.add_argument("--dtype",
102 - type=str,
103 - choices=["bfloat16", "float"],
104 - default="float")
112 + parser.add_argument(
113 + "--dtype", type=str, choices=["bfloat16", "float"], default="float"
114 + )
115 parser.add_argument("--seed", type=int, default=0)
106 - parser.add_argument("--device",
107 - type=str,
108 - choices=["cuda:0", "cuda:1"],
109 - default="cuda:0")
116 + parser.add_argument(
117 + "--device", type=str, choices=["cuda:0", "cuda:1"], default="cuda:0"
118 + )
119 args = parser.parse_args()
120 print(args)
121
benchmarks/kernels/benchmark_w8a8_block_fp8.py
+53 -60
@@ -14,14 +14,16 @@ import tqdm
14 import triton
15
16 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
17 - _w8a8_block_fp8_matmul)
17 + _w8a8_block_fp8_matmul,
18 +)
19 from vllm.platforms import current_platform
20 from vllm.utils import FlexibleArgumentParser
21
22 mp.set_start_method("spawn", force=True)
23
23 -assert current_platform.is_cuda(
24 -), "Only support tune w8a8 block fp8 kernel on CUDA device."
24 +assert current_platform.is_cuda(), (
25 + "Only support tune w8a8 block fp8 kernel on CUDA device."
26 +)
27
28 DTYPE_MAP = {
29 "float32": torch.float32,
@@ -40,7 +42,7 @@ def w8a8_block_matmul(
42 config: dict[str, Any],
43 output_dtype: torch.dtype = torch.float16,
44 ) -> torch.Tensor:
43 - """This function performs matrix multiplication with
45 + """This function performs matrix multiplication with
46 block-wise quantization.
47
48 It takes two input tensors `A` and `B` with scales `As` and `Bs`.
@@ -51,7 +53,7 @@ def w8a8_block_matmul(
53 B: The input tensor, e.g., weight.
54 As: The per-token-group quantization scale for `A`.
55 Bs: The per-block quantization scale for `B`.
54 - block_size: The block size for per-block quantization.
56 + block_size: The block size for per-block quantization.
57 It should be 2-dim, e.g., [128, 128].
58 output_dytpe: The dtype of the returned tensor.
59
@@ -71,18 +73,18 @@ def w8a8_block_matmul(
73 assert triton.cdiv(N, block_n) == Bs.shape[0]
74 assert triton.cdiv(K, block_k) == Bs.shape[1]
75
74 - C_shape = A.shape[:-1] + (N, )
76 + C_shape = A.shape[:-1] + (N,)
77 C = A.new_empty(C_shape, dtype=output_dtype)
78
79 def grid(META):
78 - return (triton.cdiv(M, META["BLOCK_SIZE_M"]) *
79 - triton.cdiv(N, META["BLOCK_SIZE_N"]), )
80 + return (
81 + triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),
82 + )
83
84 if A.dtype == torch.float8_e4m3fn:
85 kernel = _w8a8_block_fp8_matmul
86 else:
84 - raise RuntimeError(
85 - "Currently, only support tune w8a8 block fp8 kernel.")
87 + raise RuntimeError("Currently, only support tune w8a8 block fp8 kernel.")
88
89 kernel[grid](
90 A,
@@ -119,14 +121,16 @@ def get_configs_compute_bound():
121 for block_n in [32, 64, 128, 256]:
122 for num_warps in [4, 8]:
123 for group_size in [1, 16, 32, 64]:
122 - configs.append({
123 - "BLOCK_SIZE_M": block_m,
124 - "BLOCK_SIZE_N": block_n,
125 - "BLOCK_SIZE_K": block_k,
126 - "GROUP_SIZE_M": group_size,
127 - "num_warps": num_warps,
128 - "num_stages": num_stages,
129 - })
124 + configs.append(
125 + {
126 + "BLOCK_SIZE_M": block_m,
127 + "BLOCK_SIZE_N": block_n,
128 + "BLOCK_SIZE_K": block_k,
129 + "GROUP_SIZE_M": group_size,
130 + "num_warps": num_warps,
131 + "num_stages": num_stages,
132 + }
133 + )
134 return configs
135
136
@@ -165,15 +169,9 @@ def get_weight_shapes(tp_size):
169 return weight_shapes
170
171
168 -def benchmark_config(A,
169 - B,
170 - As,
171 - Bs,
172 - block_size,
173 - config,
174 - out_dtype=torch.float16,
175 - num_iters=10):
176 -
172 +def benchmark_config(
173 + A, B, As, Bs, block_size, config, out_dtype=torch.float16, num_iters=10
174 +):
175 def run():
176 w8a8_block_matmul(A, B, As, Bs, block_size, config, out_dtype)
177
@@ -206,26 +204,26 @@ def tune(M, N, K, block_size, out_dtype, search_space, input_type):
204 fp8_max, fp8_min = fp8_info.max, fp8_info.min
205
206 A_fp32 = (
209 - (torch.rand(M, K, dtype=torch.float32, device="cuda") - 0.5) * 2 *
210 - fp8_max)
207 + (torch.rand(M, K, dtype=torch.float32, device="cuda") - 0.5) * 2 * fp8_max
208 + )
209 A = A_fp32.clamp(min=fp8_min, max=fp8_max).to(torch.float8_e4m3fn)
210
211 B_fp32 = (
214 - (torch.rand(N, K, dtype=torch.float32, device="cuda") - 0.5) * 2 *
215 - fp8_max)
212 + (torch.rand(N, K, dtype=torch.float32, device="cuda") - 0.5) * 2 * fp8_max
213 + )
214 B = B_fp32.clamp(min=fp8_min, max=fp8_max).to(torch.float8_e4m3fn)
215 else:
218 - raise RuntimeError(
219 - "Currently, only support tune w8a8 block fp8 kernel.")
216 + raise RuntimeError("Currently, only support tune w8a8 block fp8 kernel.")
217
218 block_n, block_k = block_size[0], block_size[1]
219 n_tiles = (N + block_n - 1) // block_n
220 k_tiles = (K + block_k - 1) // block_k
221
225 - As = torch.rand(M, k_tiles, dtype=torch.float32,
226 - device="cuda") * factor_for_scale
227 - Bs = (torch.rand(n_tiles, k_tiles, dtype=torch.float32, device="cuda") *
228 - factor_for_scale)
222 + As = torch.rand(M, k_tiles, dtype=torch.float32, device="cuda") * factor_for_scale
223 + Bs = (
224 + torch.rand(n_tiles, k_tiles, dtype=torch.float32, device="cuda")
225 + * factor_for_scale
226 + )
227
228 best_config = None
229 best_time = float("inf")
@@ -267,7 +265,8 @@ def save_configs(
265 device_name = current_platform.get_device_name().replace(" ", "_")
266 json_file_name = (
267 f"N={N},K={K},device_name={device_name},dtype={input_type}_w8a8,"
270 - f"block_shape=[{block_n},{block_k}].json")
268 + f"block_shape=[{block_n},{block_k}].json"
269 + )
270
271 config_file_path = os.path.join(save_path, json_file_name)
272 print(f"Writing best config to {config_file_path}...")
@@ -295,8 +294,7 @@ def tune_on_gpu(args_dict):
294
295 search_space = get_configs_compute_bound()
296 search_space = [
298 - config for config in search_space
299 - if block_k % config["BLOCK_SIZE_K"] == 0
297 + config for config in search_space if block_k % config["BLOCK_SIZE_K"] == 0
298 ]
299
300 start = time.time()
@@ -312,15 +310,11 @@ def tune_on_gpu(args_dict):
310 out_dtype,
311 search_space,
312 input_type,
315 - ) for batch_size in tqdm(batch_sizes,
316 - desc=f"GPU {gpu_id} - Batch sizes")
313 + )
314 + for batch_size in tqdm(batch_sizes, desc=f"GPU {gpu_id} - Batch sizes")
315 ]
318 - best_configs = {
319 - M: config
320 - for M, config in zip(batch_sizes, benchmark_results)
321 - }
322 - save_configs(N, K, block_n, block_k, best_configs, save_path,
323 - input_type)
316 + best_configs = {M: config for M, config in zip(batch_sizes, benchmark_results)}
317 + save_configs(N, K, block_n, block_k, best_configs, save_path, input_type)
318
319 end = time.time()
320 print(f"Tuning on GPU {gpu_id} took {end - start:.2f} seconds")
@@ -376,13 +370,14 @@ def main(args):
370
371 process_args = []
372 for gpu_id in range(num_gpus):
379 - process_args.append({
380 - "gpu_id": gpu_id,
381 - "batch_sizes": batches_per_gpu[gpu_id],
382 - "weight_shapes":
383 - weight_shapes, # Each GPU processes all weight shapes
384 - "args": args,
385 - })
373 + process_args.append(
374 + {
375 + "gpu_id": gpu_id,
376 + "batch_sizes": batches_per_gpu[gpu_id],
377 + "weight_shapes": weight_shapes, # Each GPU processes all weight shapes
378 + "args": args,
379 + }
380 + )
381
382 ctx = mp.get_context("spawn")
383 with ctx.Pool(num_gpus) as pool:
@@ -398,13 +393,11 @@ Tune triton w8a8 block fp8 for DeepSeek-V3/DeepSeek-R1:
393 python3 benchmark_w8a8_block_fp8.py --tp-size 8 --input-type fp8
394 Then copy to model_executor/layers/quantization/utils/configs
395 """,
401 - formatter_class=argparse.RawTextHelpFormatter)
396 + formatter_class=argparse.RawTextHelpFormatter,
397 + )
398
399 parser.add_argument("--tp-size", "-tp", type=int, default=8)
404 - parser.add_argument("--input-type",
405 - type=str,
406 - choices=["fp8"],
407 - default="fp8")
400 + parser.add_argument("--input-type", type=str, choices=["fp8"], default="fp8")
401 parser.add_argument(
402 "--out-dtype",
403 type=str,
benchmarks/kernels/deepgemm/benchmark_fp8_block_dense_gemm.py
+3 -1
@@ -11,7 +11,9 @@ from deep_gemm import calc_diff, ceil_div, get_col_major_tma_aligned_tensor
11 # Import vLLM functions
12 from vllm import _custom_ops as ops
13 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
14 - per_token_group_quant_fp8, w8a8_block_fp8_matmul)
14 + per_token_group_quant_fp8,
15 + w8a8_block_fp8_matmul,
16 +)
17 from vllm.triton_utils import triton
18
19
benchmarks/kernels/graph_machete_bench.py
+16 -17
@@ -14,13 +14,14 @@ from vllm.utils import FlexibleArgumentParser
14
15 if __name__ == "__main__":
16 parser = FlexibleArgumentParser(
17 - description='Benchmark the latency of processing a single batch of '
18 - 'requests till completion.')
19 - parser.add_argument('filename', type=str)
17 + description="Benchmark the latency of processing a single batch of "
18 + "requests till completion."
19 + )
20 + parser.add_argument("filename", type=str)
21
22 args = parser.parse_args()
23
23 - with open(args.filename, 'rb') as f:
24 + with open(args.filename, "rb") as f:
25 data = pickle.load(f)
26 raw_results: list[TMeasurement] = data["results"]
27
@@ -38,11 +39,7 @@ if __name__ == "__main__":
39 raise Exception("MKN not found")
40
41 kernel = v.task_spec.description
41 - results[KN].append({
42 - "kernel": kernel,
43 - "batch_size": M,
44 - "median": v.median
45 - })
42 + results[KN].append({"kernel": kernel, "batch_size": M, "median": v.median})
43
44 rows = int(math.ceil(len(results) / 2))
45 fig, axs = plt.subplots(rows, 2, figsize=(12, 5 * rows))
@@ -50,14 +47,16 @@ if __name__ == "__main__":
47 for axs_idx, (shape, data) in enumerate(results.items()):
48 plt.sca(axs[axs_idx])
49 df = pd.DataFrame(data)
53 - sns.lineplot(data=df,
54 - x="batch_size",
55 - y="median",
56 - hue="kernel",
57 - style="kernel",
58 - markers=True,
59 - dashes=False,
60 - palette="Dark2")
50 + sns.lineplot(
51 + data=df,
52 + x="batch_size",
53 + y="median",
54 + hue="kernel",
55 + style="kernel",
56 + markers=True,
57 + dashes=False,
58 + palette="Dark2",
59 + )
60 plt.title(f"Shape: {shape}")
61 plt.ylabel("time (median, s)")
62 plt.tight_layout()
benchmarks/kernels/utils.py
+26 -26
@@ -23,6 +23,7 @@ class ArgPool:
23 For every invocation during a benchmarking run, it will choose a
24 different value from the list.
25 """
26 +
27 values: Iterable[Any]
28
29 def __getitem__(self, index):
@@ -30,9 +31,7 @@ class ArgPool:
31
32
33 class Bench:
33 -
34 class ArgsIterator:
35 -
35 def __init__(self, args_list, kwargs_list):
36 assert len(args_list) == len(kwargs_list)
37 self.args_list = args_list
@@ -53,10 +52,16 @@ class Bench:
52 def n_args(self):
53 return self.n
54
56 - def __init__(self, cuda_graph_params: Optional[CudaGraphBenchParams],
57 - label: str, sub_label: str, description: str, fn: Callable,
58 - *args, **kwargs):
59 -
55 + def __init__(
56 + self,
57 + cuda_graph_params: Optional[CudaGraphBenchParams],
58 + label: str,
59 + sub_label: str,
60 + description: str,
61 + fn: Callable,
62 + *args,
63 + **kwargs,
64 + ):
65 self.cuda_graph_params = cuda_graph_params
66 self.use_cuda_graph = self.cuda_graph_params is not None
67 self.label = label
@@ -67,10 +72,8 @@ class Bench:
72 # Process args
73 self._args = args
74 self._kwargs = kwargs
70 - self.args_list, self.kwargs_list = self.collapse_argpool(
71 - *args, **kwargs)
72 - self.args_iterator = self.ArgsIterator(self.args_list,
73 - self.kwargs_list)
75 + self.args_list, self.kwargs_list = self.collapse_argpool(*args, **kwargs)
76 + self.args_iterator = self.ArgsIterator(self.args_list, self.kwargs_list)
77
78 # Cudagraph runner
79 self.g = None
@@ -100,16 +103,13 @@ class Bench:
103
104 for i in range(argpool_size):
105 # collapse args; Just pick the ith value
103 - args_list[i] = tuple([
104 - arg[i] if isinstance(arg, ArgPool) else arg
105 - for arg in args_list[i]
106 - ])
106 + args_list[i] = tuple(
107 + [arg[i] if isinstance(arg, ArgPool) else arg for arg in args_list[i]]
108 + )
109
110 # collapse kwargs
111 kwargs_i = kwargs_list[i]
110 - arg_pool_keys = [
111 - k for k, v in kwargs_i.items() if isinstance(v, ArgPool)
112 - ]
112 + arg_pool_keys = [k for k, v in kwargs_i.items() if isinstance(v, ArgPool)]
113 for k in arg_pool_keys:
114 # again just pick the ith value
115 kwargs_i[k] = kwargs_i[k][i]
@@ -142,7 +142,7 @@ class Bench:
142
143 def run_cudagrah(self) -> TMeasurement:
144 assert self.use_cuda_graph
145 - globals = {'g': self.g}
145 + globals = {"g": self.g}
146
147 return TBenchmark.Timer(
148 stmt="g.replay()",
@@ -162,15 +162,15 @@ class Bench:
162
163 has_arg_pool = self.args_iterator.n_args > 1
164 if has_arg_pool:
165 - setup = '''
165 + setup = """
166 args_iterator.reset()
167 args_it = args_iterator.__next__()
168 - '''
169 - stmt = '''
168 + """
169 + stmt = """
170 args, kwargs = next(args_it)
171 fn(*args, **kwargs)
172 - '''
173 - globals = {'fn': self.fn, 'args_iterator': self.args_iterator}
172 + """
173 + globals = {"fn": self.fn, "args_iterator": self.args_iterator}
174 else:
175 # no arg pool. Just use the args and kwargs directly
176 self.args_iterator.reset()
@@ -178,10 +178,10 @@ class Bench:
178 args, kwargs = next(args_it)
179
180 setup = ""
181 - stmt = '''
181 + stmt = """
182 fn(*args, **kwargs)
183 - '''
184 - globals = {'fn': self.fn, 'args': args, 'kwargs': kwargs}
183 + """
184 + globals = {"fn": self.fn, "args": args, "kwargs": kwargs}
185
186 return TBenchmark.Timer(
187 stmt=stmt,
benchmarks/overheads/benchmark_hashing.py
+19 -17
@@ -7,9 +7,8 @@ from vllm import LLM, SamplingParams
7 from vllm.utils import FlexibleArgumentParser
8
9 # A very long prompt, total number of tokens is about 15k.
10 -LONG_PROMPT = ["You are an expert in large language models, aren't you?"
11 - ] * 1000
12 -LONG_PROMPT = ' '.join(LONG_PROMPT)
10 +LONG_PROMPT = ["You are an expert in large language models, aren't you?"] * 1000
11 +LONG_PROMPT = " ".join(LONG_PROMPT)
12
13
14 def main(args):
@@ -30,32 +29,35 @@ def main(args):
29
30 print("------start generating------")
31 for i in range(3):
33 - profiler.runctx('llm.generate(LONG_PROMPT, sampling_params)',
34 - globals(), locals())
32 + profiler.runctx(
33 + "llm.generate(LONG_PROMPT, sampling_params)", globals(), locals()
34 + )
35
36 # analyze the runtime of hashing function
37 stats = pstats.Stats(profiler)
38 - stats.sort_stats('cumulative')
38 + stats.sort_stats("cumulative")
39 total_time = 0
40 total_calls = 0
41 for func in stats.stats:
42 - if 'hash_of_block' in func[2]:
42 + if "hash_of_block" in func[2]:
43 total_time = stats.stats[func][3]
44 total_calls = stats.stats[func][0]
45 percentage = (total_time / stats.total_tt) * 100
46 - print(f"Hashing took {total_time:.2f} seconds,"
47 - f"{percentage:.2f}% of the total runtime.")
46 + print(
47 + f"Hashing took {total_time:.2f} seconds,{percentage:.2f}% of the total runtime."
48 + )
49
50
51 if __name__ == "__main__":
52 parser = FlexibleArgumentParser(
52 - description='Benchmark the performance of hashing function in'
53 - 'automatic prefix caching.')
54 - parser.add_argument('--model', type=str, default='lmsys/longchat-7b-16k')
55 - parser.add_argument('--tensor-parallel-size', '-tp', type=int, default=1)
56 - parser.add_argument('--output-len', type=int, default=10)
57 - parser.add_argument('--enable-prefix-caching',
58 - action='store_true',
59 - help='enable prefix caching')
53 + description="Benchmark the performance of hashing function in"
54 + "automatic prefix caching."
55 + )
56 + parser.add_argument("--model", type=str, default="lmsys/longchat-7b-16k")
57 + parser.add_argument("--tensor-parallel-size", "-tp", type=int, default=1)
58 + parser.add_argument("--output-len", type=int, default=10)
59 + parser.add_argument(
60 + "--enable-prefix-caching", action="store_true", help="enable prefix caching"
61 + )
62 args = parser.parse_args()
63 main(args)
benchmarks/pyproject.toml new
+54
@@ -0,0 +1,54 @@
1 +# This local pyproject file is part of the migration from yapf to ruff format.
2 +# It uses the same core rules as the main pyproject.toml file, but with the
3 +# following differences:
4 +# - ruff line length is overridden to 88
5 +# - deprecated typing ignores (UP006, UP035) have been removed
6 +
7 +[tool.ruff]
8 +line-length = 88
9 +exclude = [
10 + # External file, leaving license intact
11 + "examples/other/fp8/quantizer/quantize.py",
12 + "vllm/vllm_flash_attn/flash_attn_interface.pyi"
13 +]
14 +
15 +[tool.ruff.lint.per-file-ignores]
16 +"vllm/third_party/**" = ["ALL"]
17 +"vllm/version.py" = ["F401"]
18 +"vllm/_version.py" = ["ALL"]
19 +
20 +[tool.ruff.lint]
21 +select = [
22 + # pycodestyle
23 + "E",
24 + # Pyflakes
25 + "F",
26 + # pyupgrade
27 + "UP",
28 + # flake8-bugbear
29 + "B",
30 + # flake8-simplify
31 + "SIM",
32 + # isort
33 + "I",
34 + # flake8-logging-format
35 + "G",
36 +]
37 +ignore = [
38 + # star imports
39 + "F405", "F403",
40 + # lambda expression assignment
41 + "E731",
42 + # Loop control variable not used within loop body
43 + "B007",
44 + # f-string format
45 + "UP032",
46 + # Can remove once 3.10+ is the minimum Python version
47 + "UP007",
48 +]
49 +
50 +[tool.ruff.lint.isort]
51 +known-first-party = ["vllm"]
52 +
53 +[tool.ruff.format]
54 +docstring-code-format = true
\ No newline at end of file
pyproject.toml
+5
@@ -54,6 +54,7 @@ include = ["vllm*"]
54 [tool.yapfignore]
55 ignore_patterns = [
56 ".buildkite/**",
57 + "benchmarks/**",
58 "build/**",
59 ]
60
@@ -155,6 +156,10 @@ ignore-words-list = "dout, te, indicies, subtile, ElementE"
156 skip = "tests/models/fixtures/*,tests/prompts/*,benchmarks/sonnet.txt,tests/lora/data/*,build/*,vllm/third_party/*"
157
158 [tool.isort]
159 +skip_glob = [
160 + ".buildkite/*",
161 + "benchmarks/*",
162 +]
163 use_parentheses = true
164 skip_gitignore = true
165