214 lines
6.2 KiB
Python
214 lines
6.2 KiB
Python
"""Benchmark random dataset request generation without sending HTTP requests.
|
|
|
|
This script exercises the same ``get_requests()`` path used by ``evalscope perf``
|
|
and measures only local request construction. It is intended for comparing
|
|
serial generation (``--num-workers 1``) with multiprocessing generation for the
|
|
``random`` dataset.
|
|
|
|
Example:
|
|
python examples/perf/benchmark_random_request_generation.py \
|
|
--tokenizer-path Qwen/Qwen2.5-0.5B-Instruct \
|
|
--numbers 128 512 \
|
|
--prompt-lengths 2048 8192 \
|
|
--workers 1 2 4 8 0
|
|
"""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import time
|
|
from dataclasses import dataclass
|
|
from typing import Any, Dict, List
|
|
|
|
from evalscope.perf.arguments import Arguments
|
|
from evalscope.perf.benchmark import get_requests
|
|
from evalscope.perf.plugin.datasets.random_dataset import RandomDatasetPlugin
|
|
from evalscope.perf.utils.worker_util import resolve_dataset_generation_workers
|
|
|
|
_BENCHMARK_URL = 'http://127.0.0.1:8000/v1/chat/completions'
|
|
|
|
|
|
class _NoopApiPlugin:
|
|
"""Build request dictionaries without touching the network."""
|
|
|
|
def build_request(self, messages: Any) -> Dict[str, Any]:
|
|
return {'messages': messages}
|
|
|
|
|
|
@dataclass
|
|
class _BenchmarkResult:
|
|
number: int
|
|
prompt_length: int
|
|
configured_workers: int
|
|
resolved_workers: int
|
|
repeat: int
|
|
seconds: float
|
|
requests_per_second: float
|
|
|
|
|
|
def _parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description='Benchmark random request generation throughput.')
|
|
parser.add_argument(
|
|
'--tokenizer-path',
|
|
type=str,
|
|
default='Qwen/Qwen2.5-0.5B-Instruct',
|
|
help='Tokenizer path or model id used by the random dataset.',
|
|
)
|
|
parser.add_argument(
|
|
'--numbers',
|
|
type=int,
|
|
nargs='+',
|
|
default=[128, 512],
|
|
help='Request counts to benchmark.',
|
|
)
|
|
parser.add_argument(
|
|
'--prompt-lengths',
|
|
type=int,
|
|
nargs='+',
|
|
default=[2048, 8192],
|
|
help='Fixed prompt lengths to benchmark.',
|
|
)
|
|
parser.add_argument(
|
|
'--workers',
|
|
type=int,
|
|
nargs='+',
|
|
default=[1, 2, 4, 8, 0],
|
|
help='Configured --num-workers values. Use 0 for auto.',
|
|
)
|
|
parser.add_argument(
|
|
'--repeat',
|
|
type=int,
|
|
default=1,
|
|
help='Number of repeats per case.',
|
|
)
|
|
parser.add_argument(
|
|
'--apply-chat-template',
|
|
action='store_true',
|
|
default=False,
|
|
help='Generate chat-template-shaped messages instead of raw text prompts.',
|
|
)
|
|
parser.add_argument(
|
|
'--tokenize-prompt',
|
|
action='store_true',
|
|
default=False,
|
|
help='Benchmark token-id prompt generation for the completions endpoint.',
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def _make_arguments(
|
|
tokenizer_path: str,
|
|
number: int,
|
|
prompt_length: int,
|
|
workers: int,
|
|
apply_chat_template: bool,
|
|
tokenize_prompt: bool,
|
|
) -> Arguments:
|
|
return Arguments(
|
|
model='request-generation-benchmark',
|
|
url=_BENCHMARK_URL,
|
|
dataset='random',
|
|
tokenizer_path=tokenizer_path,
|
|
number=number,
|
|
parallel=1,
|
|
num_workers=workers,
|
|
min_prompt_length=prompt_length,
|
|
max_prompt_length=prompt_length,
|
|
max_tokens=1,
|
|
apply_chat_template=apply_chat_template,
|
|
tokenize_prompt=tokenize_prompt,
|
|
)
|
|
|
|
|
|
def _random_dataset_supports_parallel(args: Arguments) -> bool:
|
|
plugin = object.__new__(RandomDatasetPlugin)
|
|
plugin.query_parameters = args
|
|
plugin.number = args.total_count
|
|
return plugin.supports_parallel_message_generation(args.total_count)
|
|
|
|
|
|
async def _consume_requests(args: Arguments) -> int:
|
|
count = 0
|
|
async for _request, _is_warmup in get_requests(args, _NoopApiPlugin()):
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def _run_case(
|
|
tokenizer_path: str,
|
|
number: int,
|
|
prompt_length: int,
|
|
workers: int,
|
|
repeat: int,
|
|
apply_chat_template: bool,
|
|
tokenize_prompt: bool,
|
|
) -> _BenchmarkResult:
|
|
args = _make_arguments(
|
|
tokenizer_path=tokenizer_path,
|
|
number=number,
|
|
prompt_length=prompt_length,
|
|
workers=workers,
|
|
apply_chat_template=apply_chat_template,
|
|
tokenize_prompt=tokenize_prompt,
|
|
)
|
|
resolved_workers = resolve_dataset_generation_workers(
|
|
args=args,
|
|
total_count=args.total_count,
|
|
supports_parallel_generation=_random_dataset_supports_parallel(args),
|
|
)
|
|
|
|
start = time.perf_counter()
|
|
count = asyncio.run(_consume_requests(args))
|
|
seconds = time.perf_counter() - start
|
|
if count != args.total_count:
|
|
raise RuntimeError(f'Expected {args.total_count} requests, got {count}.')
|
|
|
|
return _BenchmarkResult(
|
|
number=number,
|
|
prompt_length=prompt_length,
|
|
configured_workers=workers,
|
|
resolved_workers=resolved_workers,
|
|
repeat=repeat,
|
|
seconds=seconds,
|
|
requests_per_second=count / seconds,
|
|
)
|
|
|
|
|
|
def _run_matrix(args: argparse.Namespace) -> List[_BenchmarkResult]:
|
|
results = []
|
|
for number in args.numbers:
|
|
for prompt_length in args.prompt_lengths:
|
|
for workers in args.workers:
|
|
for repeat_index in range(args.repeat):
|
|
results.append(
|
|
_run_case(
|
|
tokenizer_path=args.tokenizer_path,
|
|
number=number,
|
|
prompt_length=prompt_length,
|
|
workers=workers,
|
|
repeat=repeat_index + 1,
|
|
apply_chat_template=args.apply_chat_template,
|
|
tokenize_prompt=args.tokenize_prompt,
|
|
)
|
|
)
|
|
return results
|
|
|
|
|
|
def _print_results(results: List[_BenchmarkResult]) -> None:
|
|
print('number,prompt_length,configured_workers,resolved_workers,repeat,seconds,requests_per_second')
|
|
for result in results:
|
|
print(
|
|
f'{result.number},{result.prompt_length},{result.configured_workers},'
|
|
f'{result.resolved_workers},{result.repeat},{result.seconds:.3f},'
|
|
f'{result.requests_per_second:.2f}'
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
args = _parse_args()
|
|
results = _run_matrix(args)
|
|
_print_results(results)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|