[MyPy] Fix mypy for `vllm/benchmarks` (#39896)

Signed-off-by: Martin Hickey <martin.hickey@ie.ibm.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Martin Hickey committed Jun 23, 2026 at 16:22 UTC 275b43183c7253a45bd0214d165b0d5347de1ec6
24 files changed +296 -150
tools/pre_commit/mypy.py
-2
@@ -31,8 +31,6 @@ SEPARATE_GROUPS = [
31 EXCLUDE = [
32 "vllm/model_executor/models",
33 "vllm/model_executor/layers/fla/ops",
34 - # TODO: Remove these entries after fixing mypy errors.
35 - "vllm/benchmarks",
34 ]
35
36
vllm/benchmarks/datasets/create_txt_slices_dataset.py
+2 -2
@@ -30,7 +30,6 @@ The resulting JSONL file can then be used with the serving benchmark::
30
31 from __future__ import annotations
32
33 -import argparse
33 import json
34 import logging
35 import random
@@ -40,6 +39,7 @@ import numpy as np
39 from transformers import AutoTokenizer
40
41 from vllm.benchmarks.datasets.utils import RangeRatio, get_sampling_params
42 +from vllm.utils.argparse_utils import FlexibleArgumentParser
43
44 logger = logging.getLogger(__name__)
45
@@ -121,7 +121,7 @@ def create_txt_slices_jsonl(
121
122
123 def main(argv: list[str] | None = None) -> None:
124 - parser = argparse.ArgumentParser(
124 + parser = FlexibleArgumentParser(
125 description="Convert a plain-text file into a JSONL dataset "
126 "for CustomDataset (--dataset-name custom).",
127 )
vllm/benchmarks/datasets/datasets.py
+140 -63
@@ -81,7 +81,7 @@ class SampleRequest:
81
82 prompt: str | list[str] | list[dict]
83 prompt_len: int
84 - expected_output_len: int | None
84 + expected_output_len: int = 0
85 multi_modal_data: MultiModalDataDict | dict | list[dict] | None = None
86 lora_request: LoRARequest | None = None
87 request_id: str | None = None
@@ -634,7 +634,7 @@ class RandomDataset(BenchmarkDataset):
634 # Generate prefix once
635 prefix_token_ids = self.get_prefix(tokenizer, allowed_tokens, prefix_len)
636
637 - requests = []
637 + requests: list[SampleRequest] = []
638 token_mismatch_total = 0
639 for i in range(num_requests):
640 prompt, total_input_len, token_mismatch = self.generate_token_sequence( # noqa: E501
@@ -665,15 +665,14 @@ class RandomDataset(BenchmarkDataset):
665 )
666 # only used for embeddings benchmark.
667 if batchsize > 1:
668 - batch_requests = []
668 + batch_requests: list[SampleRequest] = []
669 # Create batched requests
670 for i in range(0, num_requests, batchsize):
671 batch = requests[i : i + batchsize]
672 batch_requests.append(
673 SampleRequest(
674 - prompt=[req.prompt for req in batch],
674 + prompt=[req.prompt for req in batch], # type: ignore[arg-type]
675 prompt_len=sum(req.prompt_len for req in batch),
676 - expected_output_len=0,
676 request_id=request_id_prefix + str(i // batchsize),
677 )
678 )
@@ -797,6 +796,9 @@ class RandomDatasetForReranking(RandomDataset):
796 input_len: int = RandomDataset.DEFAULT_INPUT_LEN,
797 output_len: int = RandomDataset.DEFAULT_OUTPUT_LEN,
798 batchsize: int = 1,
799 + max_loras: int | None = None,
800 + lora_path: str | None = None,
801 + lora_assignment: str = "random",
802 is_reranker: bool = True,
803 **kwargs,
804 ) -> list[SampleRequest]:
@@ -1207,6 +1209,9 @@ class RandomMultiModalDataset(RandomDataset):
1209 input_len: int = RandomDataset.DEFAULT_INPUT_LEN,
1210 output_len: int = RandomDataset.DEFAULT_OUTPUT_LEN,
1211 batchsize: int = 1,
1212 + max_loras: int | None = None,
1213 + lora_path: str | None = None,
1214 + lora_assignment: str = "random",
1215 limit_mm_per_prompt: dict[str, int] = DEFAULT_LIMIT_MM_PER_PROMPT,
1216 base_items_per_request: int = DEFAULT_BASE_ITEMS_PER_REQUEST,
1217 num_mm_items_range_ratio: float = DEFAULT_NUM_MM_ITEMS_RANGE_RATIO,
@@ -1251,6 +1256,7 @@ class RandomMultiModalDataset(RandomDataset):
1256 # We want to exclude placeholder tokens and all
1257 # tokens that indicate start/end of image as it
1258 # may break prompt replacement logic.
1259 + assert hasattr(tokenizer, "added_tokens_decoder")
1260 prohibited_tokens = list(
1261 tok_id
1262 for tok_id, token in tokenizer.added_tokens_decoder.items()
@@ -1376,6 +1382,7 @@ class ShareGPTDataset(BenchmarkDataset):
1382 lora_assignment: str = "random",
1383 **kwargs,
1384 ) -> list[SampleRequest]:
1385 + assert self.data is not None, "Dataset must be loaded before sampling"
1386 samples: list[SampleRequest] = []
1387 ind = 0
1388 for entry in self.data:
@@ -1454,7 +1461,7 @@ class TimedTrace(BenchmarkDataset):
1461 f'label_output_length: "{self.label_output_length}", '
1462 f'label_hash_ids: "{self.label_hash_ids}"'
1463 )
1457 - self._expanded_generated_prompts = {}
1464 + self._expanded_generated_prompts: dict[str, Any] = {}
1465 self.load_data()
1466
1467 def load_data(self) -> None:
@@ -1529,10 +1536,12 @@ class TimedTrace(BenchmarkDataset):
1536 tokenizer: TokenizerLike,
1537 num_requests: int,
1538 request_id_prefix: str = "",
1539 + no_oversample: bool = False,
1540 **kwargs,
1533 - ) -> list:
1534 - samples: list = []
1541 + ) -> list[SampleRequest]:
1542 + samples: list[SampleRequest] = []
1543 assert tokenizer is not None, "Tokenizer must be provided, now is Null"
1544 + assert self.data is not None, "Data must be loaded before sampling"
1545
1546 for ind, entry in enumerate(self.data):
1547 if len(samples) >= num_requests:
@@ -2139,12 +2148,12 @@ def get_samples(args, tokenizer: TokenizerLike) -> list[SampleRequest]:
2148 )
2149
2150 elif args.dataset_name == "sonnet":
2142 - dataset = SonnetDataset(
2151 + sonnet_dataset = SonnetDataset(
2152 dataset_path=args.dataset_path, disable_shuffle=args.disable_shuffle
2153 )
2154 # For the "sonnet" dataset, formatting depends on the backend.
2155 if args.backend == "openai-chat":
2147 - input_requests = dataset.sample(
2156 + input_requests = sonnet_dataset.sample(
2157 num_requests=args.num_prompts,
2158 input_len=args.sonnet_input_len,
2159 output_len=args.sonnet_output_len,
@@ -2155,10 +2164,13 @@ def get_samples(args, tokenizer: TokenizerLike) -> list[SampleRequest]:
2164 no_oversample=args.no_oversample,
2165 )
2166 else:
2158 - assert tokenizer.chat_template or tokenizer.default_chat_template, (
2159 - "Tokenizer/model must have chat template for sonnet dataset."
2160 - )
2161 - input_requests = dataset.sample(
2167 + assert (
2168 + hasattr(tokenizer, "chat_template") and tokenizer.chat_template
2169 + ) or (
2170 + hasattr(tokenizer, "default_chat_template")
2171 + and tokenizer.default_chat_template
2172 + ), "Tokenizer/model must have chat template for sonnet dataset."
2173 + input_requests = sonnet_dataset.sample(
2174 num_requests=args.num_prompts,
2175 input_len=args.sonnet_input_len,
2176 output_len=args.sonnet_output_len,
@@ -2173,6 +2185,7 @@ def get_samples(args, tokenizer: TokenizerLike) -> list[SampleRequest]:
2185 # all following datasets are implemented from the
2186 # HuggingFaceDataset base class
2187 hf_kwargs = {}
2188 + dataset_class: type[BenchmarkDataset]
2189 if (
2190 args.dataset_path in VisionArenaDataset.SUPPORTED_DATASET_PATHS
2191 or args.hf_name in VisionArenaDataset.SUPPORTED_DATASET_PATHS
@@ -2548,23 +2561,23 @@ class CustomDataset(BenchmarkDataset):
2561
2562 if tokenizer is None:
2563 new_output_len = 1
2564 + elif output_len is None or output_len == -1:
2565 + # check that the request has an 'output_tokens' field
2566 + if "output_tokens" not in item:
2567 + raise ValueError(
2568 + "If no output length is provided the "
2569 + "custom dataset must contain an 'output_tokens' field."
2570 + )
2571 + # Use number of output tokens from the request data
2572 + try:
2573 + new_output_len = int(item["output_tokens"])
2574 + except (ValueError, TypeError) as e:
2575 + raise ValueError(
2576 + f"Invalid value for 'output_tokens' in custom dataset: "
2577 + f"'{item['output_tokens']}'. Must be an integer."
2578 + ) from e
2579 else:
2580 new_output_len = output_len
2553 - if output_len is None or output_len == -1:
2554 - # check that the request has an 'output_tokens' field
2555 - if "output_tokens" not in item:
2556 - raise ValueError(
2557 - "If no output length is provided the "
2558 - "custom dataset must contain an 'output_tokens' field."
2559 - )
2560 - # Use number of output tokens from the request data
2561 - try:
2562 - new_output_len = int(item["output_tokens"])
2563 - except (ValueError, TypeError) as e:
2564 - raise ValueError(
2565 - f"Invalid value for 'output_tokens' in custom dataset: "
2566 - f"'{item['output_tokens']}'. Must be an integer."
2567 - ) from e
2581
2582 if tokenizer is None:
2583 prompt_len = 1
@@ -2797,11 +2810,15 @@ class CustomImageDataset(CustomDataset):
2810 self,
2811 tokenizer: TokenizerLike,
2812 num_requests: int,
2813 + request_id_prefix: str = "",
2814 + no_oversample: bool = False,
2815 + lora_path: str | None = None,
2816 + max_loras: int | None = None,
2817 output_len: int | None = None,
2818 enable_multimodal_chat: bool = False,
2819 + skip_chat_template: bool = False,
2820 + chat_template_kwargs: dict | None = None,
2821 ensure_client_side_data: bool = False,
2803 - request_id_prefix: str = "",
2804 - no_oversample: bool = False,
2822 **kwargs,
2823 ) -> list[SampleRequest]:
2824 # load all data if needed
@@ -2814,7 +2831,7 @@ class CustomImageDataset(CustomDataset):
2831 num_requests,
2832 )
2833
2817 - sampled_requests = []
2834 + sampled_requests: list[SampleRequest] = []
2835 for i, item in enumerate(self.data):
2836 if len(sampled_requests) >= num_requests:
2837 break
@@ -2837,7 +2854,7 @@ class CustomImageDataset(CustomDataset):
2854 SampleRequest(
2855 prompt=prompt,
2856 prompt_len=prompt_len,
2840 - expected_output_len=output_len,
2857 + expected_output_len=output_len or 0,
2858 multi_modal_data=None,
2859 request_id=request_id_prefix + str(i),
2860 )
@@ -2863,7 +2880,7 @@ class CustomImageDataset(CustomDataset):
2880 SampleRequest(
2881 prompt=prompt,
2882 prompt_len=prompt_len,
2866 - expected_output_len=output_len,
2883 + expected_output_len=output_len or 0,
2884 multi_modal_data=mm_content,
2885 request_id=request_id_prefix + str(i),
2886 )
@@ -2891,17 +2908,20 @@ class CustomAudioDataset(CustomDataset):
2908 self,
2909 tokenizer: TokenizerLike,
2910 num_requests: int,
2894 - output_len: int | None = None,
2911 request_id_prefix: str = "",
2912 no_oversample: bool = False,
2897 - skip_chat_template: bool = False,
2913 + lora_path: str | None = None,
2914 + max_loras: int | None = None,
2915 + output_len: int | None = None,
2916 enable_multimodal_chat: bool = False,
2917 + skip_chat_template: bool = False,
2918 + chat_template_kwargs: dict | None = None,
2919 **kwargs,
2920 ) -> list[SampleRequest]:
2921 self.num_available_samples = len(self.data)
2922 if num_requests <= 0:
2923 num_requests = self.num_available_samples
2904 - sampled_requests = []
2924 + sampled_requests: list[SampleRequest] = []
2925 for i, item in enumerate(self.data):
2926 if len(sampled_requests) >= num_requests:
2927 break
@@ -2950,7 +2970,6 @@ class CustomAudioDataset(CustomDataset):
2970 prompt_len = (
2971 len(tokenizer(prompt).input_ids) if isinstance(prompt, str) else 1
2972 )
2953 - new_output_len = output_len
2973 if output_len is None or output_len == -1:
2974 if "output_tokens" not in item:
2975 raise ValueError(
@@ -2958,6 +2977,8 @@ class CustomAudioDataset(CustomDataset):
2977 "custom dataset must contain an 'output_tokens' field."
2978 )
2979 new_output_len = int(item["output_tokens"])
2980 + else:
2981 + new_output_len = output_len
2982 sampled_requests.append(
2983 SampleRequest(
2984 prompt=prompt,
@@ -3015,10 +3036,30 @@ class SpecBench(CustomDataset):
3036
3037 def sample(
3038 self,
3039 + tokenizer: TokenizerLike,
3040 + num_requests: int,
3041 + request_id_prefix: str = "",
3042 + no_oversample: bool = False,
3043 + lora_path: str | None = None,
3044 + max_loras: int | None = None,
3045 + output_len: int | None = None,
3046 + enable_multimodal_chat: bool = False,
3047 + skip_chat_template: bool = False,
3048 + chat_template_kwargs: dict | None = None,
3049 **kwargs,
3050 ) -> list[SampleRequest]:
3051 # leverage CustomDataset sample
3052 return super().sample(
3053 + tokenizer=tokenizer,
3054 + num_requests=num_requests,
3055 + request_id_prefix=request_id_prefix,
3056 + no_oversample=no_oversample,
3057 + lora_path=lora_path,
3058 + max_loras=max_loras,
3059 + output_len=output_len,
3060 + enable_multimodal_chat=enable_multimodal_chat,
3061 + skip_chat_template=skip_chat_template,
3062 + chat_template_kwargs=chat_template_kwargs,
3063 **kwargs,
3064 )
3065
@@ -3067,16 +3108,21 @@ class SonnetDataset(BenchmarkDataset):
3108 return_prompt_formatted: bool = False,
3109 **kwargs,
3110 ) -> list[SampleRequest]:
3111 + poem_lines = self.data
3112 + assert poem_lines is not None
3113 # Calculate average token length for a poem line.
3071 - tokenized_lines = [tokenizer(line).input_ids for line in self.data]
3114 + tokenized_lines = [tokenizer(line).input_ids for line in poem_lines]
3115 avg_len = sum(len(tokens) for tokens in tokenized_lines) / len(tokenized_lines)
3116
3117 # Build the base prompt.
3118 base_prompt = "Pick as many lines as you can from these poem lines:\n"
3119 base_msg = [{"role": "user", "content": base_prompt}]
3120 base_fmt = tokenizer.apply_chat_template(
3078 - base_msg, add_generation_prompt=True, tokenize=False
3121 + base_msg, # type: ignore[arg-type]
3122 + add_generation_prompt=True,
3123 + tokenize=False,
3124 )
3125 + assert isinstance(base_fmt, str)
3126 base_offset = len(tokenizer(base_fmt).input_ids)
3127 if input_len <= base_offset:
3128 raise ValueError(
@@ -3085,26 +3131,31 @@ class SonnetDataset(BenchmarkDataset):
3131 )
3132
3133 # Determine how many poem lines to use.
3088 - num_input_lines = round((input_len - base_offset) / avg_len)
3134 + num_input_lines = max(round((input_len - base_offset) / avg_len), 1)
3135 num_prefix_lines = max(round((prefix_len - base_offset) / avg_len), 0)
3090 - prefix_lines = self.data[:num_prefix_lines]
3136 + prefix_lines = poem_lines[:num_prefix_lines]
3137
3138 samples: list[SampleRequest] = []
3139 ind = 0
3140 while len(samples) < num_requests:
3141 extra_lines = random.choices(
3096 - self.data, k=num_input_lines - num_prefix_lines
3142 + poem_lines, k=num_input_lines - num_prefix_lines
3143 )
3144 prompt = f"{base_prompt}{''.join(prefix_lines + extra_lines)}"
3145 msg = [{"role": "user", "content": prompt}]
3146 prompt_formatted = tokenizer.apply_chat_template(
3101 - msg, add_generation_prompt=True, tokenize=False
3147 + msg, # type: ignore[arg-type]
3148 + add_generation_prompt=True,
3149 + tokenize=False,
3150 )
3151 + assert isinstance(prompt_formatted, str)
3152 prompt_len = len(tokenizer(prompt_formatted).input_ids)
3153 if prompt_len <= input_len:
3154 samples.append(
3155 SampleRequest(
3107 - prompt=prompt_formatted if return_prompt_formatted else prompt,
3156 + prompt=(
3157 + prompt_formatted if return_prompt_formatted else prompt
3158 + ),
3159 prompt_len=prompt_len,
3160 expected_output_len=output_len,
3161 request_id=request_id_prefix + str(ind),
@@ -3145,6 +3196,7 @@ class BurstGPTDataset(BenchmarkDataset):
3196 self.data = gpt4_df
3197
3198 def _sample_loaded_data(self, num_requests: int) -> list:
3199 + assert self.data is not None, "Dataset must be loaded before sampling"
3200 if num_requests <= len(self.data):
3201 data = self.data.sample(n=num_requests, random_state=self.random_seed)
3202 else:
@@ -3258,6 +3310,7 @@ class ConversationDataset(HuggingFaceDataset):
3310 enable_multimodal_chat: bool = False,
3311 **kwargs,
3312 ) -> list[SampleRequest]:
3313 + assert self.data is not None, "Dataset must be loaded before sampling"
3314 # Filter examples with at least 2 conversations
3315 filtered_data = self.data.filter(lambda x: len(x["conversations"]) >= 2)
3316 sampled_requests: list[SampleRequest] = []
@@ -3318,6 +3371,7 @@ class MultiModalConversationDataset(HuggingFaceDataset):
3371 enable_multimodal_chat: bool = False,
3372 **kwargs,
3373 ) -> list[SampleRequest]:
3374 + assert self.data is not None, "Dataset must be loaded before sampling"
3375 # Filter examples with at least 2 conversations
3376 filtered_data = self.data.filter(lambda x: len(x["conversations"]) >= 2)
3377 sampled_requests: list[SampleRequest] = []
@@ -3393,7 +3447,8 @@ class VisionArenaDataset(HuggingFaceDataset):
3447
3448 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3449
3396 - sampled_requests = []
3450 + assert self.data is not None, "Dataset must be loaded before sampling"
3451 + sampled_requests: list[SampleRequest] = []
3452 for i, item in enumerate(self.data):
3453 if len(sampled_requests) >= num_requests:
3454 break
@@ -3465,7 +3520,8 @@ class MMVUDataset(HuggingFaceDataset):
3520
3521 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3522
3468 - sampled_requests = []
3523 + assert self.data is not None, "Dataset must be loaded before sampling"
3524 + sampled_requests: list[SampleRequest] = []
3525 for i, item in enumerate(self.data):
3526 if len(sampled_requests) >= num_requests:
3527 break
@@ -3531,18 +3587,21 @@ class InstructCoderDataset(HuggingFaceDataset):
3587 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3588 sampled_requests: list[SampleRequest] = []
3589 for i, prompt in enumerate(self.sample_prompts(n=num_requests)):
3590 + prompt_text = prompt
3591 # apply template
3592 if not skip_chat_template:
3536 - prompt = tokenizer.apply_chat_template(
3593 + prompt_text_result = tokenizer.apply_chat_template(
3594 [{"role": "user", "content": prompt}],
3595 add_generation_prompt=True,
3596 tokenize=False,
3597 )
3598 + assert isinstance(prompt_text_result, str)
3599 + prompt_text = prompt_text_result
3600
3542 - prompt_len = len(tokenizer(prompt).input_ids)
3601 + prompt_len = len(tokenizer(prompt_text).input_ids)
3602 sampled_requests.append(
3603 SampleRequest(
3545 - prompt=prompt,
3604 + prompt=prompt_text,
3605 prompt_len=prompt_len,
3606 expected_output_len=output_len,
3607 request_id=request_id_prefix + str(i),
@@ -3554,6 +3613,7 @@ class InstructCoderDataset(HuggingFaceDataset):
3613 return sampled_requests
3614
3615 def sample_prompts(self, n: int) -> Iterator[str]:
3616 + assert self.data is not None, "Dataset must be loaded before sampling"
3617 for item in self.data.take(n):
3618 prompt = (
3619 f"{item['input']}\n\n{item['instruction']} Just output "
@@ -3594,6 +3654,7 @@ class MTBenchDataset(HuggingFaceDataset):
3654 **kwargs,
3655 ) -> list[SampleRequest]:
3656 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3657 + assert self.data is not None, "Dataset must be loaded before sampling"
3658 sampled_requests: list[SampleRequest] = []
3659
3660 for i, item in enumerate(self.data):
@@ -3654,7 +3715,8 @@ class HumanEvalDataset(HuggingFaceDataset):
3715 **kwargs,
3716 ) -> list[SampleRequest]:
3717 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3657 - sampled_requests = []
3718 + sampled_requests: list[SampleRequest] = []
3719 + assert self.data is not None, "Data must be loaded before sampling"
3720
3721 for i, item in enumerate(self.data):
3722 if len(sampled_requests) >= num_requests:
@@ -3714,7 +3776,8 @@ class GSM8KDataset(HuggingFaceDataset):
3776 **kwargs,
3777 ) -> list[SampleRequest]:
3778 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3717 - sampled_requests = []
3779 + sampled_requests: list[SampleRequest] = []
3780 + assert self.data is not None, "Data must be loaded before sampling"
3781
3782 for i, item in enumerate(self.data):
3783 if len(sampled_requests) >= num_requests:
@@ -3772,17 +3835,18 @@ class BlazeditDataset(HuggingFaceDataset):
3835 self,
3836 tokenizer: TokenizerLike,
3837 num_requests: int,
3775 - output_len: int | None = None,
3776 - skip_chat_template: bool = False,
3838 request_id_prefix: str = "",
3839 no_oversample: bool = False,
3840 + output_len: int | None = None,
3841 + skip_chat_template: bool = False,
3842 min_distance: float = 0.0,
3843 max_distance: float = 1.0,
3844 **kwargs,
3845 ) -> list[SampleRequest]:
3846 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
3784 - sampled_requests = []
3847 + sampled_requests: list[SampleRequest] = []
3848
3849 + assert self.data is not None, "Dataset must be loaded before sampling"
3850 for i, item in enumerate(self.data):
3851 if len(sampled_requests) >= num_requests:
3852 break
@@ -3810,11 +3874,13 @@ Please generate the new code file in the "New file" section below.""" # noqa: E
3874
3875 # apply template
3876 if not skip_chat_template:
3813 - prompt = tokenizer.apply_chat_template(
3877 + prompt_result = tokenizer.apply_chat_template(
3878 [{"role": "user", "content": prompt}],
3879 add_generation_prompt=True,
3880 tokenize=False,
3881 )
3882 + assert isinstance(prompt_result, str)
3883 + prompt = prompt_result
3884
3885 prompt_len = len(tokenizer(prompt).input_ids)
3886
@@ -3858,6 +3924,7 @@ class AIMODataset(HuggingFaceDataset):
3924 output_len: int | None = None,
3925 **kwargs,
3926 ) -> list[SampleRequest]:
3927 + assert self.data is not None, "Dataset must be loaded before sampling"
3928 sampled_requests: list[SampleRequest] = []
3929 ind = 0
3930 dynamic_output = output_len is None
@@ -3970,6 +4037,7 @@ class NextEditPredictionDataset(HuggingFaceDataset):
4037 formatting_prompt_func = self.MAPPING_PROMPT_FUNCS.get(self.hf_name)
4038 if formatting_prompt_func is None:
4039 raise ValueError(f"Unsupported dataset path: {self.hf_name}")
4040 + assert self.data is not None, "Dataset must be loaded before sampling"
4041 samples = []
4042 for i, sample in enumerate(self.data):
4043 sample = formatting_prompt_func(sample)
@@ -4061,12 +4129,14 @@ class ASRDataset(HuggingFaceDataset):
4129 def _disable_audio_decode(self) -> None:
4130 from datasets import Audio
4131
4132 + assert self.data is not None, "Dataset must be loaded before sampling"
4133 self.data = self.data.cast_column("audio", Audio(decode=False))
4134
4135 def _materialize_local_audio_column(self) -> None:
4136 local_path_root = Path(
4137 hf_api().snapshot_download(self.hf_name, repo_type="dataset")
4138 )
4139 + assert self.data is not None, "Dataset must be loaded before sampling"
4140 self.data = self.data.map(
4141 lambda item: {
4142 "audio": str(local_path_root / item["url"]),
@@ -4090,15 +4160,19 @@ class ASRDataset(HuggingFaceDataset):
4160 else:
4161 prompt = ""
4162 prompt_len = len(tokenizer(prompt).input_ids)
4163 + assert self.data is not None, "Dataset must be loaded before sampling"
4164 sampled_requests: list[SampleRequest] = []
4165 ind = 0
4166 skipped = 0
4096 - asr_min_audio_len_sec = kwargs.get("asr_min_audio_len_sec")
4097 - asr_max_audio_len_sec = kwargs.get("asr_max_audio_len_sec")
4167 + asr_min_audio_len_sec: float = float(kwargs.get("asr_min_audio_len_sec") or 0.0)
4168 + asr_max_audio_len_sec: float = float(
4169 + kwargs.get("asr_max_audio_len_sec") or float("inf")
4170 + )
4171 durations = []
4172 for item in self.data:
4173 if len(sampled_requests) >= num_requests:
4174 break
4175 + mm_content: dict[str, Any]
4176 audio = item["audio"]
4177 if (
4178 isinstance(audio, dict)
@@ -4203,8 +4277,7 @@ class MLPerfDataset(HuggingFaceDataset):
4277 output_len: int | None = None,
4278 **kwargs,
4279 ) -> list[SampleRequest]:
4206 - # Force dynamic output length based on reference completion.
4207 - dynamic_output = output_len is None
4280 + assert self.data is not None, "Dataset must be loaded before sampling"
4281 sampled_requests: list[SampleRequest] = []
4282 ind = 0
4283
@@ -4222,15 +4295,18 @@ class MLPerfDataset(HuggingFaceDataset):
4295 {"role": "user", "content": question},
4296 ]
4297 prompt_formatted = tokenizer.apply_chat_template(
4225 - messages, add_generation_prompt=True, tokenize=False
4298 + messages, # type: ignore[arg-type]
4299 + add_generation_prompt=True,
4300 + tokenize=False,
4301 )
4302 + assert isinstance(prompt_formatted, str)
4303 prompt_len = len(tokenizer(prompt_formatted).input_ids)
4304
4305 # Determine output length from reference answer tokens.
4306 ref_out_len = len(
4307 tokenizer(reference_answer, add_special_tokens=False).input_ids
4308 )
4233 - expected_output_len = ref_out_len if dynamic_output else output_len
4309 + expected_output_len = ref_out_len if output_len is None else output_len
4310
4311 # Validate sequence lengths.
4312 if not is_valid_sequence(prompt_len, expected_output_len):
@@ -4371,6 +4447,7 @@ class MMStarDataset(HuggingFaceDataset):
4447 ) -> list[SampleRequest]:
4448 # If --hf-output-len is not set, use the default output length.
4449 output_len = output_len if output_len is not None else self.DEFAULT_OUTPUT_LEN
4450 + assert self.data is not None, "Dataset must be loaded before sampling"
4451 sampled_requests: list[SampleRequest] = []
4452
4453 for ind, item in enumerate(self.data):
@@ -4603,7 +4680,7 @@ class BFCLDataset(HuggingFaceDataset):
4680 exc_info=True,
4681 )
4682 rendered = None
4606 - if rendered is not None:
4683 + if rendered is not None and isinstance(rendered, str):
4684 prompt_len = len(tokenizer(rendered).input_ids)
4685 else:
4686 text = "\n".join(m.get("content", "") for m in messages)
vllm/benchmarks/latency.py
+6 -4
@@ -13,8 +13,9 @@ from tqdm import tqdm
13
14 from vllm.benchmarks.lib.utils import convert_to_pytorch_benchmark_format, write_to_json
15 from vllm.engine.arg_utils import EngineArgs
16 -from vllm.inputs import PromptType
16 +from vllm.inputs import TextPrompt, TokensPrompt
17 from vllm.sampling_params import BeamSearchParams
18 +from vllm.utils.argparse_utils import FlexibleArgumentParser
19
20
21 def save_to_pytorch_benchmark_format(
@@ -30,7 +31,7 @@ def save_to_pytorch_benchmark_format(
31 write_to_json(pt_file, pt_records)
32
33
33 -def add_cli_args(parser: argparse.ArgumentParser):
34 +def add_cli_args(parser: FlexibleArgumentParser):
35 parser.add_argument("--input-len", type=int, default=32)
36 parser.add_argument("--output-len", type=int, default=128)
37 parser.add_argument("--batch-size", type=int, default=8)
@@ -103,8 +104,9 @@ def main(args: argparse.Namespace):
104 dummy_prompt_token_ids = np.random.randint(
105 10000, size=(args.batch_size, args.input_len)
106 )
106 - dummy_prompts: list[PromptType] = [
107 - {"prompt_token_ids": batch} for batch in dummy_prompt_token_ids.tolist()
107 + dummy_prompts: list[TokensPrompt | TextPrompt] = [
108 + TokensPrompt(prompt_token_ids=batch)
109 + for batch in dummy_prompt_token_ids.tolist()
110 ]
111
112 def llm_generate():
vllm/benchmarks/lib/endpoint_request_func.py
+24 -20
@@ -75,7 +75,7 @@ class RequestFuncInput:
75 logprobs: int | None = None
76 extra_headers: dict | None = None
77 extra_body: dict | None = None
78 - multi_modal_content: dict | list[dict] | None = None
78 + multi_modal_content: dict[str, Any] | list[dict[str, Any]] | None = None
79 ignore_eos: bool = False
80 language: str | None = None
81 request_id: str | None = None
@@ -272,13 +272,13 @@ def _get_chat_content(
272 request_func_input: RequestFuncInput,
273 mm_position: Literal["first", "last"] = "last",
274 ) -> list[dict[str, Any]]:
275 - mm_contents = []
275 + mm_contents: list[dict[str, Any]] = []
276 if request_func_input.multi_modal_content:
277 mm_content = request_func_input.multi_modal_content
278 if isinstance(mm_content, list):
279 - mm_contents.extend(request_func_input.multi_modal_content)
279 + mm_contents.extend(mm_content)
280 elif isinstance(mm_content, dict):
281 - mm_contents.append(request_func_input.multi_modal_content)
281 + mm_contents.append(mm_content)
282 else:
283 raise TypeError(
284 "multi_modal_content must be a dict or list[dict] for openai-chat"
@@ -293,10 +293,11 @@ def _get_chat_content(
293 for item in prompt
294 )
295 ):
296 + prompt_dicts: list[dict[str, Any]] = prompt # type: ignore[assignment]
297 if mm_position == "first":
297 - return mm_contents + prompt
298 + return mm_contents + prompt_dicts
299
299 - return prompt + mm_contents
300 + return prompt_dicts + mm_contents
301
302 text_contents = [{"type": "text", "text": prompt}]
303
@@ -307,15 +308,15 @@ def _get_chat_content(
308
309
310 def _is_chat_messages(prompt: Any) -> bool:
310 - return (
311 - isinstance(prompt, list)
312 - and prompt
313 - and all(
314 - isinstance(item, dict)
315 - and isinstance(item.get("role"), str)
316 - and isinstance(item.get("content"), (str, list))
317 - for item in prompt
318 - )
311 + if not isinstance(prompt, list):
312 + return False
313 + if not prompt:
314 + return False
315 + return all(
316 + isinstance(item, dict)
317 + and isinstance(item.get("role"), str)
318 + and isinstance(item.get("content"), (str, list))
319 + for item in prompt
320 )
321
322
@@ -325,7 +326,7 @@ def _get_chat_messages(
326 ) -> list[dict[str, Any]]:
327 prompt = request_func_input.prompt
328 if _is_chat_messages(prompt):
328 - return prompt
329 + return prompt # type: ignore[return-value]
330
331 return [
332 {
@@ -385,8 +386,8 @@ async def async_request_openai_chat_completions(
386 if not chunk_bytes:
387 continue
388
388 - messages = handler.add_chunk(chunk_bytes)
389 - for message in messages:
389 + message_strings = handler.add_chunk(chunk_bytes)
390 + for message in message_strings:
391 # NOTE: SSE comments (often used as pings) start with
392 # a colon. These are not JSON data payload and should
393 # be skipped.
@@ -790,7 +791,7 @@ async def async_request_infinity_embeddings(
791 api_url = request_func_input.api_url
792 _validate_api_url(api_url, "Infinity Embeddings API", "embeddings")
793
793 - payload = {
794 + payload: dict[str, Any] = {
795 "model": request_func_input.model_name
796 if request_func_input.model_name
797 else request_func_input.model,
@@ -849,7 +850,10 @@ async def async_request_vllm_pooling(
850 "truncate_prompt_tokens": -1,
851 }
852
852 - payload = payload | request_func_input.prompt
853 + if isinstance(request_func_input.prompt, dict):
854 + payload = payload | request_func_input.prompt
855 + else:
856 + payload["input"] = request_func_input.prompt
857
858 _update_payload_common(payload, request_func_input)
859
vllm/benchmarks/lib/utils.py
+2 -2
@@ -15,7 +15,7 @@ def extract_field(
15 if field_name in extra_info:
16 return extra_info[field_name]
17
18 - v = args
18 + v: Any = args
19 # For example, args.compilation_config.mode
20 for nested_field in field_name.split("."):
21 if not hasattr(v, nested_field):
@@ -43,7 +43,7 @@ def convert_to_pytorch_benchmark_format(
43 on metric per record
44 https://github.com/pytorch/pytorch/wiki/How-to-integrate-with-PyTorch-OSS-benchmark-database
45 """
46 - records = []
46 + records: list[Any] = []
47 if not os.environ.get("SAVE_TO_PYTORCH_BENCHMARK_FORMAT", False):
48 return records
49
vllm/benchmarks/mm_processor.py
+3 -2
@@ -28,6 +28,7 @@ from vllm.benchmarks.datasets import (
28 )
29 from vllm.benchmarks.throughput import get_requests
30 from vllm.engine.arg_utils import EngineArgs
31 +from vllm.utils.argparse_utils import FlexibleArgumentParser
32 from vllm.utils.gc_utils import freeze_gc_heap
33 from vllm.utils.import_utils import PlaceholderModule
34
@@ -369,7 +370,7 @@ def benchmark_multimodal_processor(
370 return benchmark_result
371
372
372 -def add_cli_args(parser: argparse.ArgumentParser) -> None:
373 +def add_cli_args(parser: FlexibleArgumentParser) -> None:
374 """Add CLI arguments for the multimodal processor benchmark."""
375 from vllm.engine.arg_utils import EngineArgs
376
@@ -532,7 +533,7 @@ def main(args: argparse.Namespace) -> None:
533
534
535 if __name__ == "__main__":
535 - parser = argparse.ArgumentParser(description="Benchmark mm processor latency")
536 + parser = FlexibleArgumentParser(description="Benchmark mm processor latency")
537 add_cli_args(parser)
538 args = parser.parse_args()
539 main(args)
vllm/benchmarks/serve.py
+37 -14
@@ -30,7 +30,7 @@ import ssl
30 import time
31 import uuid
32 import warnings
33 -from collections.abc import AsyncGenerator, Iterable
33 +from collections.abc import AsyncGenerator, Iterable, Iterator
34 from dataclasses import dataclass, replace
35 from datetime import datetime
36 from enum import Enum
@@ -52,6 +52,7 @@ from vllm.benchmarks.lib.endpoint_request_func import (
52 from vllm.benchmarks.lib.ready_checker import wait_for_endpoint
53 from vllm.benchmarks.lib.utils import convert_to_pytorch_benchmark_format, write_to_json
54 from vllm.tokenizers import TokenizerLike, get_tokenizer
55 +from vllm.utils.argparse_utils import FlexibleArgumentParser
56 from vllm.utils.gc_utils import freeze_gc_heap
57 from vllm.utils.network_utils import join_host_port
58
@@ -129,6 +130,7 @@ async def _align_prompts_to_server_tokenizer(
130 )
131
132 async def _fix_one(req: SampleRequest) -> SampleRequest:
133 + assert isinstance(req.prompt, str)
134 tokens = await _tokenize(req.prompt)
135 if len(tokens) <= req.prompt_len:
136 return req
@@ -360,7 +362,7 @@ class EmbedBenchmarkMetrics:
362 mean_e2el_ms: float
363 std_e2el_ms: float
364 median_e2el_ms: float
363 - percentiles_e2el_ms: float
365 + percentiles_e2el_ms: list[tuple[float, float]]
366
367
368 def _get_current_request_rate(
@@ -433,8 +435,8 @@ async def get_request(
435 assert total_requests > 0, "No requests provided."
436
437 # Precompute delays among requests to minimize request send laggings
436 - request_rates = []
437 - delay_ts = []
438 + request_rates: list[float] = []
439 + delay_ts: list[float] = []
440
441 # if the traces have timing info then:
442 if not self_timed:
@@ -484,7 +486,10 @@ async def get_request(
486 else:
487 for request_index, request in enumerate(input_requests):
488 # this is cumulative running ts, from which sleep is calculated later
487 - delay_ts.append(request.timestamp)
489 + if request.timestamp is not None:
490 + delay_ts.append(request.timestamp)
491 + else:
492 + delay_ts.append(0.0)
493 # TODO: there is no notion of RPS here, may be we can calculate
494 # from the trace.
495 request_rates.append(0.0)
@@ -599,7 +604,7 @@ def calculate_metrics(
604 )
605 actual_output_lens.append(output_len)
606 total_input += outputs[i].prompt_len
602 - tpot = 0
607 + tpot = 0.0
608 if output_len > 1:
609 latency_minus_ttft = outputs[i].latency - outputs[i].ttft
610 tpot = latency_minus_ttft / (output_len - 1)
@@ -894,11 +899,12 @@ async def benchmark(
899
900 print("Starting main benchmark run...")
901
902 + lora_modules_iter: Iterator[str] | None = None
903 if lora_modules:
904 lora_modules_list = list(lora_modules)
905 if lora_assignment == "round-robin":
906 # Deterministic round-robin assignment across requests.
901 - lora_modules = iter(
907 + lora_modules_iter = iter(
908 [
909 lora_modules_list[i % len(lora_modules_list)]
910 for i in range(len(input_requests))
@@ -906,7 +912,7 @@ async def benchmark(
912 )
913 else:
914 # For each input request, choose a LoRA module at random.
909 - lora_modules = iter(
915 + lora_modules_iter = iter(
916 [random.choice(lora_modules_list) for _ in range(len(input_requests))]
917 )
918
@@ -1004,19 +1010,23 @@ async def benchmark(
1010 )
1011 per_request_extra_body = _merge_overrides(extra_body, request.request_overrides)
1012 req_model_id, req_model_name = model_id, model_name
1007 - if lora_modules:
1008 - req_lora_module = next(lora_modules)
1013 + if lora_modules_iter:
1014 + req_lora_module = next(lora_modules_iter)
1015 req_model_id, req_model_name = req_lora_module, req_lora_module
1016
1017 + mm_content_typed: dict[str, Any] | list[dict[str, Any]] | None = None
1018 + if isinstance(mm_content, (dict, list)):
1019 + mm_content_typed = mm_content
1020 +
1021 request_func_input = RequestFuncInput(
1022 model=req_model_id,
1023 model_name=req_model_name,
1024 prompt=prompt,
1025 api_url=api_url,
1026 prompt_len=prompt_len,
1017 - output_len=output_len,
1027 + output_len=output_len or 0,
1028 logprobs=logprobs,
1019 - multi_modal_content=mm_content,
1029 + multi_modal_content=mm_content_typed,
1030 ignore_eos=ignore_eos,
1031 extra_headers=extra_headers,
1032 extra_body=per_request_extra_body,
@@ -1107,6 +1117,8 @@ async def benchmark(
1117 "committed_per_step": delta_committed / denoising_steps,
1118 }
1119
1120 + metrics: BenchmarkMetrics | EmbedBenchmarkMetrics
1121 + actual_output_lens: list[int] | int
1122 if task_type == TaskType.GENERATION:
1123 metrics, actual_output_lens = calculate_metrics(
1124 input_requests=input_requests,
@@ -1140,7 +1152,7 @@ async def benchmark(
1152 "Request throughput (req/s):", metrics.request_throughput
1153 )
1154 )
1143 - if goodput_config_dict:
1155 + if goodput_config_dict and isinstance(metrics, BenchmarkMetrics):
1156 print(
1157 "{:<40} {:<10.2f}".format(
1158 "Request goodput (req/s):", metrics.request_goodput
@@ -1177,6 +1189,7 @@ async def benchmark(
1189 )
1190 )
1191
1192 + result: dict[str, Any]
1193 if isinstance(metrics, BenchmarkMetrics):
1194 result = {
1195 "duration": benchmark_duration,
@@ -1462,7 +1475,7 @@ def compute_result_filename(
1475 return file_name
1476
1477
1465 -def add_cli_args(parser: argparse.ArgumentParser):
1478 +def add_cli_args(parser: FlexibleArgumentParser):
1479 add_dataset_parser(parser)
1480 parser.add_argument(
1481 "--label",
@@ -2023,6 +2036,7 @@ async def main_async(args: argparse.Namespace) -> dict[str, Any]:
2036 args.self_timed = False
2037
2038 # Load the dataset.
2039 + assert tokenizer is not None, "Tokenizer must be initialized before loading dataset"
2040 input_requests = get_samples(args, tokenizer)
2041
2042 if args.dataset_name in ("random", "prefix_repetition"):
@@ -2154,6 +2168,9 @@ async def main_async(args: argparse.Namespace) -> dict[str, Any]:
2168
2169 # Generate timeline plot if requested
2170 if args.plot_timeline:
2171 + assert file_name is not None, (
2172 + "file_name must be set when plot_timeline is enabled"
2173 + )
2174 try:
2175 from vllm.benchmarks.plot import generate_timeline_plot
2176
@@ -2200,6 +2217,9 @@ async def main_async(args: argparse.Namespace) -> dict[str, Any]:
2217
2218 # Generate dataset statistics plot if requested
2219 if args.plot_dataset_stats:
2220 + assert file_name is not None, (
2221 + "file_name must be set when plot_dataset_stats is enabled"
2222 + )
2223 try:
2224 from vllm.benchmarks.plot import generate_dataset_stats_plot
2225
@@ -2249,6 +2269,9 @@ async def main_async(args: argparse.Namespace) -> dict[str, Any]:
2269
2270 # Save to file
2271 if args.save_result or args.append_result:
2272 + assert file_name is not None, (
2273 + "file_name must be set when save_result or append_result is enabled"
2274 + )
2275 with open(
2276 file_name, mode="a+" if args.append_result else "w", encoding="utf-8"
2277 ) as outfile:
vllm/benchmarks/startup.py
+3 -2
@@ -26,6 +26,7 @@ from vllm.benchmarks.lib.utils import (
26 write_to_json,
27 )
28 from vllm.engine.arg_utils import EngineArgs
29 +from vllm.utils.argparse_utils import FlexibleArgumentParser
30
31 PERCENTAGES = [10, 25, 50, 75, 90, 99]
32
@@ -190,7 +191,7 @@ def save_to_pytorch_benchmark_format(
191 write_to_json(f"{base_name}.{m.key}.pytorch.json", records)
192
193
193 -def add_cli_args(parser: argparse.ArgumentParser):
194 +def add_cli_args(parser: FlexibleArgumentParser):
195 parser.add_argument(
196 "--num-iters-cold",
197 type=int,
@@ -234,7 +235,7 @@ def main(args: argparse.Namespace):
235 """
236
237 # Create a queue for inter-process communication
237 - result_queue = multiprocessing.Queue()
238 + result_queue: multiprocessing.Queue[Any] = multiprocessing.Queue()
239 process = multiprocessing.Process(
240 target=run_startup_in_subprocess,
241 args=(
vllm/benchmarks/sweep/cli.py
+2 -1
@@ -3,6 +3,7 @@
3 import argparse
4
5 from vllm.entrypoints.serve.utils.api_utils import VLLM_SUBCMD_PARSER_EPILOG
6 +from vllm.utils.argparse_utils import FlexibleArgumentParser
7
8 from .plot import SweepPlotArgs
9 from .plot import main as plot_main
@@ -24,7 +25,7 @@ SUBCOMMANDS = (
25 )
26
27
27 -def add_cli_args(parser: argparse.ArgumentParser):
28 +def add_cli_args(parser: FlexibleArgumentParser):
29 subparsers = parser.add_subparsers(required=True, dest="sweep_type")
30
31 for cmd, entrypoint in SUBCOMMANDS:
vllm/benchmarks/sweep/param_sweep.py
+2 -3
@@ -2,7 +2,6 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import json
4 import os
5 -from typing import Any
5
6
7 class ParameterSweep(list["ParameterSweepItem"]):
@@ -62,8 +61,8 @@ class ParameterSweepItem(dict[str, object]):
61
62 return cls(record)
63
65 - def __or__(self, other: dict[str, Any]):
66 - return type(self)(super().__or__(other))
64 + def __or__(self, other: dict[str, object], /) -> "ParameterSweepItem": # type: ignore[override]
65 + return ParameterSweepItem(super().__or__(other))
66
67 @property
68 def name(self) -> str:
vllm/benchmarks/sweep/plot.py
+6 -3
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, ClassVar
12
13 from typing_extensions import Self, override
14
15 +from vllm.utils.argparse_utils import FlexibleArgumentParser
16 from vllm.utils.collection_utils import full_groupby
17 from vllm.utils.import_utils import PlaceholderModule
18
@@ -51,6 +52,7 @@ class PlotFilterBase(ABC):
52 class PlotEqualTo(PlotFilterBase):
53 @override
54 def apply(self, df: "pd.DataFrame") -> "pd.DataFrame":
55 + target: float | str
56 try:
57 target = float(self.target)
58 except ValueError:
@@ -63,6 +65,7 @@ class PlotEqualTo(PlotFilterBase):
65 class PlotNotEqualTo(PlotFilterBase):
66 @override
67 def apply(self, df: "pd.DataFrame") -> "pd.DataFrame":
68 + target: float | str
69 try:
70 target = float(self.target)
71 except ValueError:
@@ -182,7 +185,7 @@ def _convert_inf_nan_strings(data: list[dict[str, object]]) -> list[dict[str, ob
185 """
186 converted_data = []
187 for record in data:
185 - converted_record = {}
188 + converted_record: dict[str, object] = {}
189 for key, value in record.items():
190 if isinstance(value, str):
191 if value in ["inf", "-inf", "nan"]:
@@ -531,7 +534,7 @@ class SweepPlotArgs:
534 )
535
536 @classmethod
534 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
537 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
538 parser.add_argument(
539 "EXPERIMENT_DIR",
540 type=str,
@@ -682,7 +685,7 @@ def main(args: argparse.Namespace):
685
686
687 if __name__ == "__main__":
685 - parser = argparse.ArgumentParser(description=SweepPlotArgs.parser_help)
688 + parser = FlexibleArgumentParser(description=SweepPlotArgs.parser_help)
689 SweepPlotArgs.add_cli_args(parser)
690
691 main(parser.parse_args())
vllm/benchmarks/sweep/plot_pareto.py
+3 -2
@@ -8,6 +8,7 @@ from functools import partial
8 from pathlib import Path
9 from typing import TYPE_CHECKING, ClassVar
10
11 +from vllm.utils.argparse_utils import FlexibleArgumentParser
12 from vllm.utils.collection_utils import full_groupby
13 from vllm.utils.import_utils import PlaceholderModule
14
@@ -342,7 +343,7 @@ class SweepPlotParetoArgs:
343 )
344
345 @classmethod
345 - def add_cli_args(cls, parser: argparse.ArgumentParser):
346 + def add_cli_args(cls, parser: FlexibleArgumentParser):
347 parser.add_argument(
348 "EXPERIMENT_DIR",
349 type=str,
@@ -394,7 +395,7 @@ def main(args: argparse.Namespace):
395
396
397 if __name__ == "__main__":
397 - parser = argparse.ArgumentParser(description=SweepPlotParetoArgs.parser_help)
398 + parser = FlexibleArgumentParser(description=SweepPlotParetoArgs.parser_help)
399 SweepPlotParetoArgs.add_cli_args(parser)
400
401 main(parser.parse_args())
vllm/benchmarks/sweep/serve.py
+3 -2
@@ -10,6 +10,7 @@ from datetime import datetime
10 from pathlib import Path
11 from typing import ClassVar
12
13 +from vllm.utils.argparse_utils import FlexibleArgumentParser
14 from vllm.utils.import_utils import PlaceholderModule
15
16 from .param_sweep import ParameterSweep, ParameterSweepItem
@@ -368,7 +369,7 @@ class SweepServeArgs:
369 )
370
371 @classmethod
371 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
372 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
373 parser.add_argument(
374 "--serve-cmd",
375 type=str,
@@ -531,7 +532,7 @@ def main(args: argparse.Namespace):
532
533
534 if __name__ == "__main__":
534 - parser = argparse.ArgumentParser(description=SweepServeArgs.parser_help)
535 + parser = FlexibleArgumentParser(description=SweepServeArgs.parser_help)
536 SweepServeArgs.add_cli_args(parser)
537
538 main(parser.parse_args())
vllm/benchmarks/sweep/serve_workload.py
+3 -2
@@ -10,6 +10,7 @@ import numpy as np
10 from typing_extensions import assert_never
11
12 from vllm.benchmarks.datasets import DEFAULT_NUM_PROMPTS
13 +from vllm.utils.argparse_utils import FlexibleArgumentParser
14 from vllm.utils.import_utils import PlaceholderModule
15
16 from .param_sweep import ParameterSweep, ParameterSweepItem
@@ -273,7 +274,7 @@ class SweepServeWorkloadArgs(SweepServeArgs):
274 )
275
276 @classmethod
276 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
277 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
278 parser = super().add_cli_args(parser)
279
280 workload_group = parser.add_argument_group("workload options")
@@ -322,7 +323,7 @@ def main(args: argparse.Namespace):
323
324
325 if __name__ == "__main__":
325 - parser = argparse.ArgumentParser(description=SweepServeWorkloadArgs.parser_help)
326 + parser = FlexibleArgumentParser(description=SweepServeWorkloadArgs.parser_help)
327 SweepServeWorkloadArgs.add_cli_args(parser)
328
329 main(parser.parse_args())
vllm/benchmarks/sweep/startup.py
+2 -2
@@ -317,7 +317,7 @@ class SweepStartupArgs:
317 )
318
319 @classmethod
320 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
320 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
321 parser.add_argument(
322 "--startup-cmd",
323 type=str,
@@ -438,6 +438,6 @@ def main(args: argparse.Namespace):
438
439
440 if __name__ == "__main__":
441 - parser = argparse.ArgumentParser(description=SweepStartupArgs.parser_help)
441 + parser = FlexibleArgumentParser(description=SweepStartupArgs.parser_help)
442 SweepStartupArgs.add_cli_args(parser)
443 main(parser.parse_args())
vllm/benchmarks/throughput.py
+44 -17
@@ -18,6 +18,7 @@ from transformers import AutoModelForCausalLM, PreTrainedTokenizerBase
18 from vllm.benchmarks.datasets import (
19 AIMODataset,
20 ASRDataset,
21 + BenchmarkDataset,
22 BurstGPTDataset,
23 ConversationDataset,
24 InstructCoderDataset,
@@ -41,6 +42,7 @@ from vllm.outputs import RequestOutput
42 from vllm.platforms import current_platform
43 from vllm.sampling_params import BeamSearchParams
44 from vllm.tokenizers import TokenizerLike, get_tokenizer
45 +from vllm.utils.argparse_utils import FlexibleArgumentParser
46 from vllm.utils.async_utils import merge_async_iterators
47
48
@@ -102,13 +104,15 @@ def _run_vllm_requests(
104
105 prompts: list[TextPrompt | TokensPrompt] = []
106 sampling_params: list[SamplingParams] = []
105 - lora_requests: list[LoRARequest] | None = [] if enable_lora else None
107 + lora_requests: list[LoRARequest | None] | None = [] if enable_lora else None
108 for request in requests:
107 - prompt = (
108 - TokensPrompt(prompt_token_ids=request.prompt["prompt_token_ids"])
109 - if "prompt_token_ids" in request.prompt
110 - else TextPrompt(prompt=request.prompt)
111 - )
109 + if isinstance(request.prompt, dict) and "prompt_token_ids" in request.prompt:
110 + prompt_token_ids = request.prompt["prompt_token_ids"]
111 + assert isinstance(prompt_token_ids, list)
112 + prompt = TokensPrompt(prompt_token_ids=prompt_token_ids)
113 + else:
114 + assert isinstance(request.prompt, str)
115 + prompt = TextPrompt(prompt=request.prompt)
116 if request.multi_modal_data:
117 assert isinstance(request.multi_modal_data, dict)
118 prompt["multi_modal_data"] = request.multi_modal_data
@@ -159,7 +163,20 @@ def _run_vllm_requests(
163 end = time.perf_counter()
164 else:
165 assert lora_requests is None, "BeamSearch API does not support LoRA"
162 - beam_prompts = [request.prompt for request in requests]
166 + beam_prompts: list[TextPrompt | TokensPrompt] = []
167 + for request in requests:
168 + if isinstance(request.prompt, str):
169 + beam_prompts.append(TextPrompt(prompt=request.prompt))
170 + elif (
171 + isinstance(request.prompt, dict)
172 + and "prompt_token_ids" in request.prompt
173 + ):
174 + token_ids = request.prompt["prompt_token_ids"]
175 + assert isinstance(token_ids, list)
176 + beam_prompts.append(TokensPrompt(prompt_token_ids=token_ids))
177 + else:
178 + # Fallback: convert to string
179 + beam_prompts.append(TextPrompt(prompt=str(request.prompt)))
180 # output_len should be the same for all requests.
181 output_len = requests[0].expected_output_len
182 for request in requests:
@@ -268,7 +285,7 @@ def _run_vllm_chat_requests(
285 llm.wake_up(tags=["scheduling"])
286 outputs = llm.wait_for_completion(output_type=RequestOutput, use_tqdm=True)
287 else:
271 - outputs = llm.chat(prompts, sampling_params, use_tqdm=True)
288 + outputs = llm.chat(prompts, sampling_params, use_tqdm=True) # type: ignore[arg-type]
289
290 if do_profile:
291 llm.stop_profile()
@@ -339,11 +356,13 @@ async def _run_vllm_async_requests(
356 sampling_params: list[SamplingParams] = []
357 lora_requests: list[LoRARequest | None] = []
358 for request in requests:
342 - prompt = (
343 - TokensPrompt(prompt_token_ids=request.prompt["prompt_token_ids"])
344 - if "prompt_token_ids" in request.prompt
345 - else TextPrompt(prompt=request.prompt)
346 - )
359 + if isinstance(request.prompt, dict) and "prompt_token_ids" in request.prompt:
360 + prompt_token_ids = request.prompt["prompt_token_ids"]
361 + assert isinstance(prompt_token_ids, list)
362 + prompt = TokensPrompt(prompt_token_ids=prompt_token_ids)
363 + else:
364 + assert isinstance(request.prompt, str)
365 + prompt = TextPrompt(prompt=request.prompt)
366
367 if request.multi_modal_data:
368 assert isinstance(request.multi_modal_data, dict)
@@ -366,9 +385,11 @@ async def _run_vllm_async_requests(
385 start = time.perf_counter()
386 if do_profile:
387 await llm.start_profile()
369 - for i, (prompt, sp, lr) in enumerate(zip(prompts, sampling_params, lora_requests)):
388 + for i, (prompt_item, sp, lr) in enumerate(
389 + zip(prompts, sampling_params, lora_requests)
390 + ):
391 generator = llm.generate(
371 - prompt, sp, lora_request=lr, request_id=f"{request_id_prefix}{i}"
392 + prompt_item, sp, lora_request=lr, request_id=f"{request_id_prefix}{i}"
393 )
394 generators.append(generator)
395 all_gens = merge_async_iterators(*generators)
@@ -440,6 +461,7 @@ def _run_hf_requests(
461 prompt_len = requests[i].prompt_len
462 output_len = requests[i].expected_output_len
463 # Add the prompt to the batch.
464 + assert isinstance(prompt, str), "Prompt must be a string for HF backend"
465 batch.append(prompt)
466 max_prompt_len = max(max_prompt_len, prompt_len)
467 max_output_len = max(max_output_len, output_len)
@@ -500,6 +522,7 @@ def save_to_pytorch_benchmark_format(
522
523 def get_requests(args, tokenizer):
524 # Common parameters for all dataset types.
525 + dataset_cls: type[BenchmarkDataset]
526 common_kwargs = {
527 "dataset_path": args.dataset_path,
528 "random_seed": args.seed,
@@ -875,7 +898,7 @@ def validate_args(args):
898 )
899
900
878 -def add_cli_args(parser: argparse.ArgumentParser):
901 +def add_cli_args(parser: FlexibleArgumentParser):
902 parser.add_argument(
903 "--backend",
904 type=str,
@@ -1178,7 +1201,11 @@ def main(args: argparse.Namespace):
1201 total_prompt_tokens += (
1202 len(ro.prompt_token_ids) if ro.prompt_token_ids else 0
1203 )
1181 - total_output_tokens += sum(len(o.token_ids) for o in ro.outputs if o)
1204 + total_output_tokens += sum(
1205 + len(o.token_ids)
1206 + for o in ro.outputs
1207 + if o is not None and o.token_ids is not None
1208 + )
1209 total_num_tokens = total_prompt_tokens + total_output_tokens
1210 else:
1211 total_num_tokens = sum(r.prompt_len + r.expected_output_len for r in requests)
vllm/entrypoints/cli/benchmark/base.py
+2 -1
@@ -3,6 +3,7 @@
3 import argparse
4
5 from vllm.entrypoints.cli.types import CLISubcommand
6 +from vllm.utils.argparse_utils import FlexibleArgumentParser
7
8
9 class BenchmarkSubcommandBase(CLISubcommand):
@@ -11,7 +12,7 @@ class BenchmarkSubcommandBase(CLISubcommand):
12 help: str
13
14 @classmethod
14 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
15 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
16 """Add the CLI arguments to the parser."""
17 raise NotImplementedError
18
vllm/entrypoints/cli/benchmark/latency.py
+2 -1
@@ -4,6 +4,7 @@ import argparse
4
5 from vllm.benchmarks.latency import add_cli_args, main
6 from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
7 +from vllm.utils.argparse_utils import FlexibleArgumentParser
8
9
10 class BenchmarkLatencySubcommand(BenchmarkSubcommandBase):
@@ -13,7 +14,7 @@ class BenchmarkLatencySubcommand(BenchmarkSubcommandBase):
14 help = "Benchmark the latency of a single batch of requests."
15
16 @classmethod
16 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
17 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
18 add_cli_args(parser)
19
20 @staticmethod
vllm/entrypoints/cli/benchmark/mm_processor.py
+2 -1
@@ -4,6 +4,7 @@ import argparse
4
5 from vllm.benchmarks.mm_processor import add_cli_args, main
6 from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
7 +from vllm.utils.argparse_utils import FlexibleArgumentParser
8
9
10 class BenchmarkMMProcessorSubcommand(BenchmarkSubcommandBase):
@@ -13,7 +14,7 @@ class BenchmarkMMProcessorSubcommand(BenchmarkSubcommandBase):
14 help = "Benchmark multimodal processor latency across different configurations."
15
16 @classmethod
16 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
17 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
18 add_cli_args(parser)
19
20 @staticmethod
vllm/entrypoints/cli/benchmark/serve.py
+2 -1
@@ -4,6 +4,7 @@ import argparse
4
5 from vllm.benchmarks.serve import add_cli_args, main
6 from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
7 +from vllm.utils.argparse_utils import FlexibleArgumentParser
8
9
10 class BenchmarkServingSubcommand(BenchmarkSubcommandBase):
@@ -13,7 +14,7 @@ class BenchmarkServingSubcommand(BenchmarkSubcommandBase):
14 help = "Benchmark the online serving throughput."
15
16 @classmethod
16 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
17 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
18 add_cli_args(parser)
19
20 @staticmethod
vllm/entrypoints/cli/benchmark/startup.py
+2 -1
@@ -4,6 +4,7 @@ import argparse
4
5 from vllm.benchmarks.startup import add_cli_args, main
6 from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
7 +from vllm.utils.argparse_utils import FlexibleArgumentParser
8
9
10 class BenchmarkStartupSubcommand(BenchmarkSubcommandBase):
@@ -13,7 +14,7 @@ class BenchmarkStartupSubcommand(BenchmarkSubcommandBase):
14 help = "Benchmark the startup time of vLLM models."
15
16 @classmethod
16 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
17 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
18 add_cli_args(parser)
19
20 @staticmethod
vllm/entrypoints/cli/benchmark/sweep.py
+2 -1
@@ -4,6 +4,7 @@ import argparse
4
5 from vllm.benchmarks.sweep.cli import add_cli_args, main
6 from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
7 +from vllm.utils.argparse_utils import FlexibleArgumentParser
8
9
10 class BenchmarkSweepSubcommand(BenchmarkSubcommandBase):
@@ -13,7 +14,7 @@ class BenchmarkSweepSubcommand(BenchmarkSubcommandBase):
14 help = "Benchmark for a parameter sweep."
15
16 @classmethod
16 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
17 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
18 add_cli_args(parser)
19
20 @staticmethod
vllm/entrypoints/cli/benchmark/throughput.py
+2 -1
@@ -4,6 +4,7 @@ import argparse
4
5 from vllm.benchmarks.throughput import add_cli_args, main
6 from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
7 +from vllm.utils.argparse_utils import FlexibleArgumentParser
8
9
10 class BenchmarkThroughputSubcommand(BenchmarkSubcommandBase):
@@ -13,7 +14,7 @@ class BenchmarkThroughputSubcommand(BenchmarkSubcommandBase):
14 help = "Benchmark offline inference throughput."
15
16 @classmethod
16 - def add_cli_args(cls, parser: argparse.ArgumentParser) -> None:
17 + def add_cli_args(cls, parser: FlexibleArgumentParser) -> None:
18 add_cli_args(parser)
19
20 @staticmethod