| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Regression test for --skip-tokenizer-init with --dataset-name custom. |
| 4 | |
| 5 | Before the fix (introduced by #39896), running: |
| 6 | |
| 7 | vllm bench serve \ |
| 8 | --backend vllm-pooling \ |
| 9 | --dataset-name custom \ |
| 10 | --dataset-path <path> \ |
| 11 | --model ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL-Sen1Floods11 \ |
| 12 | --endpoint /pooling \ |
| 13 | --skip-tokenizer-init \ |
| 14 | ... |
| 15 | |
| 16 | raised immediately with: |
| 17 | |
| 18 | AssertionError: Tokenizer must be initialized before loading dataset |
| 19 | |
| 20 | even though CustomDataset.sample() already handles tokenizer=None. |
| 21 | This test exercises main_async() directly so it catches any regression |
| 22 | re-introduced at the serve.py level, not just inside get_samples(). |
| 23 | """ |
| 24 | |
| 25 | import argparse |
| 26 | import asyncio |
| 27 | import json |
| 28 | from pathlib import Path |
| 29 | from unittest.mock import AsyncMock, patch |
| 30 | |
| 31 | import pytest |
| 32 | |
| 33 | import vllm.benchmarks.serve as serve_module |
| 34 | |
| 35 | # Exact prompt payload from the failing benchmark run against a |
| 36 | # Prithvi-EO-2.0 pooling endpoint (URL-in / base64-out format). |
| 37 | _PRITHVI_PROMPT = { |
| 38 | "data": { |
| 39 | "data": "https://huggingface.co/christian-pinto/Prithvi-EO-2.0-300M-TL-VLLM/resolve/main/India_900498_S2Hand.tif", |
| 40 | "data_format": "url", |
| 41 | "out_data_format": "b64_json", |
| 42 | "indices": [1, 2, 3, 8, 11, 12], |
| 43 | }, |
| 44 | "priority": 0, |
| 45 | "softmax": False, |
| 46 | } |
| 47 | |
| 48 | |
| 49 | def _write_dataset(path: Path) -> None: |
| 50 | path.write_text(json.dumps({"prompt": _PRITHVI_PROMPT}) + "\n") |
| 51 | |
| 52 | |
| 53 | def _args(dataset_path: str) -> argparse.Namespace: |
| 54 | """Reproduce the argparse.Namespace that serve.py builds from the |
| 55 | failing command, including skip_tokenizer_init=True.""" |
| 56 | return argparse.Namespace( |
| 57 | # dataset |
| 58 | dataset_name="custom", |
| 59 | dataset_path=dataset_path, |
| 60 | disable_shuffle=False, |
| 61 | num_prompts=1, |
| 62 | custom_output_len=256, |
| 63 | skip_chat_template=True, |
| 64 | chat_template_kwargs=None, |
| 65 | no_oversample=False, |
| 66 | seed=0, |
| 67 | request_id_prefix="bench-", |
| 68 | # model / tokenizer |
| 69 | model="ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL-Sen1Floods11", |
| 70 | served_model_name=None, |
| 71 | tokenizer=None, |
| 72 | tokenizer_mode="auto", |
| 73 | trust_remote_code=False, |
| 74 | skip_tokenizer_init=True, # <-- the flag under test |
| 75 | # backend / endpoint |
| 76 | backend="vllm-pooling", |
| 77 | base_url="http://127.0.0.1:8000", |
| 78 | host="127.0.0.1", |
| 79 | port=8000, |
| 80 | endpoint="/pooling", |
| 81 | header=None, |
| 82 | insecure=False, |
| 83 | # traffic |
| 84 | request_rate=16.0, |
| 85 | burstiness=1.0, |
| 86 | max_concurrency=None, |
| 87 | probe_request_rate=0.0, |
| 88 | # misc serve args that main_async reads before reaching get_samples |
| 89 | plot_timeline=False, |
| 90 | plot_dataset_stats=False, |
| 91 | self_timed=None, |
| 92 | metadata=None, |
| 93 | label=None, |
| 94 | logprobs=None, |
| 95 | use_beam_search=False, |
| 96 | ignore_eos=False, |
| 97 | goodput=None, |
| 98 | percentile_metrics="ttft,tpot,itl,e2el", |
| 99 | metric_percentiles="25,50,75,99", |
| 100 | save_result=False, |
| 101 | append_result=False, |
| 102 | result_dir=".", |
| 103 | result_filename=None, |
| 104 | num_warmups=0, |
| 105 | profile=False, |
| 106 | disable_tqdm=True, |
| 107 | lora_modules=None, |
| 108 | lora_assignment="random", |
| 109 | ramp_up_strategy=None, |
| 110 | ramp_up_start_rps=None, |
| 111 | ramp_up_end_rps=None, |
| 112 | ready_check_timeout_sec=0, |
| 113 | extra_body=None, |
| 114 | top_p=None, |
| 115 | top_k=None, |
| 116 | min_p=None, |
| 117 | temperature=None, |
| 118 | frequency_penalty=None, |
| 119 | presence_penalty=None, |
| 120 | repetition_penalty=None, |
| 121 | save_detailed=False, |
| 122 | input_len=None, |
| 123 | output_len=None, |
| 124 | ) |
| 125 | |
| 126 | |
| 127 | @pytest.mark.benchmark |
| 128 | def test_main_async_skip_tokenizer_init_does_not_raise(tmp_path: Path) -> None: |
| 129 | """main_async must not raise AssertionError when skip_tokenizer_init=True |
| 130 | and dataset_name='custom'. |
| 131 | |
| 132 | On main (before the fix) this test fails with: |
| 133 | AssertionError: Tokenizer must be initialized before loading dataset |
| 134 | """ |
| 135 | dataset_path = tmp_path / "dataset_url_input_india.jsonl" |
| 136 | _write_dataset(dataset_path) |
| 137 | |
| 138 | args = _args(str(dataset_path)) |
| 139 | |
| 140 | # Patch benchmark() so we never make real HTTP requests — the regression |
| 141 | # triggers before benchmark() is ever called, so this just keeps the test |
| 142 | # fast and hermetic. |
| 143 | mock_result = { |
| 144 | "completed": 1, |
| 145 | "failed": 0, |
| 146 | "total_input_tokens": 1, |
| 147 | "total_output_tokens": 1, |
| 148 | "request_throughput": 1.0, |
| 149 | "output_throughput": 1.0, |
| 150 | "total_token_throughput": 1.0, |
| 151 | "input_lens": [], |
| 152 | "output_lens": [], |
| 153 | "ttfts": [], |
| 154 | "itls": [], |
| 155 | "generated_texts": [], |
| 156 | "errors": [], |
| 157 | "duration": 1.0, |
| 158 | } |
| 159 | with patch.object( |
| 160 | serve_module, "benchmark", new=AsyncMock(return_value=mock_result) |
| 161 | ): |
| 162 | # Must NOT raise AssertionError |
| 163 | asyncio.run(serve_module.main_async(args)) |