[Refactor] Remove InputPreprocessor (#53064)

Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>

Cyrus Leung committed Aug 20, 2026 at 16:39 UTC 4666a8ba9ed5d271bf751cf1384cab2d2dcc0cfa
8 files changed +29 -367
.buildkite/test-amd.yaml
-2
@@ -172,7 +172,6 @@ steps:
172 source_file_dependencies:
173 - vllm/
174 - tests/test_envs.py
175 - - tests/test_inputs.py
175 - tests/test_outputs.py
176 - tests/test_pooling_params.py
177 - tests/test_ray_env.py
@@ -189,7 +188,6 @@ steps:
188 commands:
189 - python3 standalone_tests/lazy_imports.py
190 - pytest -v -s test_envs.py
192 - - pytest -v -s test_inputs.py
191 - pytest -v -s test_outputs.py
192 - pytest -v -s test_pooling_params.py
193 - pytest -v -s test_ray_env.py
.buildkite/test_areas/misc.yaml
-2
@@ -358,7 +358,6 @@ steps:
358 - vllm/utils/
359 - vllm/v1/
360 - tests/test_envs.py
361 - - tests/test_inputs.py
361 - tests/test_outputs.py
362 - tests/test_pooling_params.py
363 - tests/test_ray_env.py
@@ -376,7 +375,6 @@ steps:
375 commands:
376 - python3 standalone_tests/lazy_imports.py
377 - pytest -v -s test_envs.py
379 - - pytest -v -s test_inputs.py
378 - pytest -v -s test_outputs.py
379 - pytest -v -s test_pooling_params.py
380 - pytest -v -s test_ray_env.py
.github/CODEOWNERS
-1
@@ -107,7 +107,6 @@
107 /tests/multimodal @DarkLight1337 @ywang96 @NickLucche @shen-shanshan
108 /tests/multimodal/media @Isotr0py
109 /tests/quantization @mgoin @robertgshaw2-redhat @yewentao256 @pavanimajety @zyongye @AndreasKaratzas
110 -/tests/test_inputs.py @DarkLight1337 @ywang96
110 /tests/entrypoints/llm/test_struct_output_generate.py @mgoin @russellb @aarnphm
111 /tests/v1/structured_output @mgoin @russellb @aarnphm
112 /tests/v1/core @WoosukKwon @robertgshaw2-redhat @njhill @ywang96 @alexm-redhat @heheda12345 @ApostaC @orozery @ivanium
tests/renderers/test_process_multi_modal_uuids.py
+1 -2
@@ -54,11 +54,10 @@ def test_text_only_model_mm_data_maps_to_bad_request():
54
55 with pytest.raises(ValueError, match="text-only") as exc_info:
56 renderer._process_multimodal(
57 - prompt="What is in this image?",
57 + prompt=[1],
58 mm_data={"image": [cherry_pil_image]},
59 mm_uuids=None,
60 mm_processor_kwargs=None,
61 - tokenization_kwargs=None,
61 )
62
63 error_response = create_error_response(exc_info.value)
tests/test_inputs.py deleted
-31
@@ -1,31 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import pytest
5 -
6 -from vllm.config import ModelConfig, VllmConfig
7 -from vllm.inputs.preprocess import InputPreprocessor
8 -
9 -pytestmark = pytest.mark.cpu_test
10 -
11 -
12 -@pytest.mark.parametrize("model_id", ["facebook/chameleon-7b"])
13 -@pytest.mark.parametrize("prompt", ["", {"prompt_token_ids": []}])
14 -@pytest.mark.skip(
15 - reason=(
16 - "Applying huggingface processor on text inputs results in "
17 - "significant performance regression for multimodal models. "
18 - "See https://github.com/vllm-project/vllm/issues/26320"
19 - )
20 -)
21 -def test_preprocessor_always_mm_code_path(model_id, prompt):
22 - model_config = ModelConfig(model=model_id)
23 - vllm_config = VllmConfig(model_config=model_config)
24 - input_preprocessor = InputPreprocessor(vllm_config)
25 -
26 - # HF processor adds sep token
27 - tokenizer = input_preprocessor.get_tokenizer()
28 - sep_token_id = tokenizer.vocab[tokenizer.sep_token]
29 -
30 - processed_inputs = input_preprocessor.preprocess(prompt)
31 - assert sep_token_id in processed_inputs["prompt_token_ids"]
vllm/inputs/preprocess.py deleted
-291
@@ -1,291 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from collections.abc import Mapping
5 -from typing import Any, overload
6 -
7 -from typing_extensions import assert_never
8 -
9 -from vllm.config import VllmConfig
10 -from vllm.inputs import build_enc_dec_input
11 -from vllm.logger import init_logger
12 -from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
13 -from vllm.renderers import BaseRenderer, renderer_from_config
14 -from vllm.renderers.inputs import (
15 - DecoderDictPrompt,
16 - DecoderOnlyDictPrompt,
17 - EncoderDecoderDictPrompt,
18 - EncoderDictPrompt,
19 - SingletonDictPrompt,
20 -)
21 -from vllm.renderers.inputs.preprocess import parse_dec_only_prompt, parse_enc_dec_prompt
22 -from vllm.tokenizers import TokenizerLike
23 -
24 -from .engine import (
25 - DecoderEngineInput,
26 - DecoderOnlyEngineInput,
27 - EmbedsInput,
28 - EncoderDecoderInput,
29 - EncoderInput,
30 - EngineInput,
31 - MultiModalInput,
32 - SingletonInput,
33 - TokensInput,
34 - tokens_input,
35 -)
36 -from .llm import (
37 - EmbedsPrompt,
38 - MultiModalDataDict,
39 - MultiModalUUIDDict,
40 - PromptType,
41 - TextPrompt,
42 - TokensPrompt,
43 -)
44 -
45 -logger = init_logger(__name__)
46 -
47 -
48 -class InputPreprocessor:
49 - def __init__(
50 - self,
51 - vllm_config: VllmConfig,
52 - renderer: BaseRenderer | None = None,
53 - mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
54 - ) -> None:
55 - super().__init__()
56 -
57 - self.model_config = vllm_config.model_config
58 - self.renderer = renderer or renderer_from_config(vllm_config)
59 - self.mm_registry = mm_registry
60 -
61 - @property
62 - def tokenizer(self) -> TokenizerLike | None:
63 - return self.renderer.tokenizer
64 -
65 - def get_tokenizer(self) -> TokenizerLike:
66 - return self.renderer.get_tokenizer()
67 -
68 - def _tokenize_prompt(
69 - self,
70 - prompt: str,
71 - tokenization_kwargs: dict[str, Any] | None = None,
72 - ) -> list[int]:
73 - """
74 - Apply the model's tokenizer to a text prompt, returning the
75 - corresponding token IDs.
76 - """
77 - renderer = self.renderer
78 -
79 - tok_params = renderer.default_cmpl_tok_params.with_kwargs(
80 - **(tokenization_kwargs or {})
81 - )
82 -
83 - tok_prompt = renderer._tokenize_singleton_prompt(
84 - TextPrompt(prompt=prompt),
85 - tok_params,
86 - )
87 -
88 - return tok_prompt["prompt_token_ids"]
89 -
90 - def _process_multimodal(
91 - self,
92 - prompt: str | list[int],
93 - mm_data: MultiModalDataDict,
94 - mm_processor_kwargs: Mapping[str, object] | None = None,
95 - tokenization_kwargs: dict[str, Any] | None = None,
96 - *,
97 - mm_uuids: MultiModalUUIDDict | None = None,
98 - ) -> MultiModalInput:
99 - """
100 - Apply the model's multi-modal processor to a multi-modal prompt,
101 - returning the corresponding token IDs and metadata.
102 - """
103 - return self.renderer._process_multimodal(
104 - prompt,
105 - mm_data,
106 - mm_uuids=mm_uuids,
107 - mm_processor_kwargs=mm_processor_kwargs,
108 - tokenization_kwargs=tokenization_kwargs,
109 - )
110 -
111 - def _process_embeds(
112 - self,
113 - parsed_content: EmbedsPrompt,
114 - ) -> EmbedsInput:
115 - return self.renderer._process_embeds(parsed_content)
116 -
117 - def _truncate_inputs(
118 - self, inputs: list[int], tokenization_kwargs: dict[str, Any] | None = None
119 - ) -> list[int]:
120 - renderer = self.renderer
121 -
122 - tok_params = renderer.default_cmpl_tok_params.with_kwargs(
123 - **(tokenization_kwargs or {})
124 - )
125 -
126 - tok_prompt = renderer._tokenize_singleton_prompt(
127 - TokensPrompt(prompt_token_ids=inputs),
128 - tok_params,
129 - )
130 -
131 - return tok_prompt["prompt_token_ids"]
132 -
133 - def _process_tokens(
134 - self,
135 - parsed_content: TokensPrompt,
136 - tokenization_kwargs: dict[str, Any] | None = None,
137 - ) -> TokensInput | MultiModalInput:
138 - prompt_token_ids = self._truncate_inputs(
139 - parsed_content["prompt_token_ids"], tokenization_kwargs
140 - )
141 -
142 - inputs: TokensInput | MultiModalInput
143 - if multi_modal_data := parsed_content.get("multi_modal_data"):
144 - inputs = self._process_multimodal(
145 - prompt_token_ids,
146 - multi_modal_data,
147 - parsed_content.get("mm_processor_kwargs"),
148 - tokenization_kwargs=tokenization_kwargs,
149 - mm_uuids=parsed_content.get("multi_modal_uuids"),
150 - )
151 - else:
152 - inputs = tokens_input(prompt_token_ids)
153 -
154 - if prompt_text := parsed_content.get("prompt"):
155 - inputs["prompt"] = prompt_text
156 - if cache_salt := parsed_content.get("cache_salt"):
157 - inputs["cache_salt"] = cache_salt
158 -
159 - return inputs
160 -
161 - def _process_text(
162 - self,
163 - parsed_content: TextPrompt,
164 - tokenization_kwargs: dict[str, Any] | None = None,
165 - ) -> TokensInput | MultiModalInput:
166 - prompt_text = parsed_content["prompt"]
167 -
168 - inputs: TokensInput | MultiModalInput
169 - if multi_modal_data := parsed_content.get("multi_modal_data"):
170 - inputs = self._process_multimodal(
171 - prompt_text,
172 - multi_modal_data,
173 - parsed_content.get("mm_processor_kwargs") or {},
174 - tokenization_kwargs=tokenization_kwargs,
175 - )
176 - else:
177 - prompt_token_ids = self._tokenize_prompt(
178 - prompt_text,
179 - tokenization_kwargs=tokenization_kwargs,
180 - )
181 - inputs = tokens_input(prompt_token_ids)
182 -
183 - inputs["prompt"] = prompt_text
184 -
185 - if cache_salt := parsed_content.get("cache_salt"):
186 - inputs["cache_salt"] = cache_salt
187 -
188 - return inputs
189 -
190 - @overload
191 - def _prompt_to_llm_inputs(
192 - self,
193 - prompt: EncoderDictPrompt,
194 - tokenization_kwargs: dict[str, Any] | None = None,
195 - ) -> EncoderInput: ...
196 -
197 - @overload
198 - def _prompt_to_llm_inputs( # type: ignore[misc]
199 - self,
200 - prompt: DecoderDictPrompt,
201 - tokenization_kwargs: dict[str, Any] | None = None,
202 - ) -> DecoderEngineInput: ...
203 -
204 - @overload
205 - def _prompt_to_llm_inputs( # type: ignore[misc]
206 - self,
207 - prompt: DecoderOnlyDictPrompt,
208 - tokenization_kwargs: dict[str, Any] | None = None,
209 - ) -> DecoderOnlyEngineInput: ...
210 -
211 - def _prompt_to_llm_inputs(
212 - self,
213 - prompt: SingletonDictPrompt,
214 - tokenization_kwargs: dict[str, Any] | None = None,
215 - ) -> SingletonInput:
216 - if "prompt_embeds" in prompt:
217 - return self._process_embeds(prompt) # type: ignore[arg-type]
218 -
219 - if "prompt_token_ids" in prompt:
220 - return self._process_tokens(prompt) # type: ignore[arg-type]
221 -
222 - if "prompt" in prompt:
223 - return self._process_text(
224 - prompt, # type: ignore[arg-type]
225 - tokenization_kwargs=tokenization_kwargs,
226 - )
227 -
228 - assert_never(prompt) # type: ignore[arg-type]
229 -
230 - def _process_encoder_decoder_prompt(
231 - self,
232 - prompt: EncoderDecoderDictPrompt,
233 - tokenization_kwargs: dict[str, Any] | None = None,
234 - ) -> EncoderDecoderInput:
235 - encoder_prompt = prompt["encoder_prompt"]
236 - decoder_prompt = prompt["decoder_prompt"]
237 -
238 - skip_decoder_start_token = False
239 - if self.renderer.mm_processor is not None:
240 - from vllm.multimodal.processing import EncDecMultiModalProcessor
241 -
242 - if isinstance(self.renderer.mm_processor, EncDecMultiModalProcessor):
243 - skip_decoder_start_token = (
244 - self.renderer.mm_processor.skip_decoder_start_token
245 - )
246 -
247 - return build_enc_dec_input(
248 - encoder_input=self._prompt_to_llm_inputs(
249 - encoder_prompt,
250 - tokenization_kwargs=tokenization_kwargs,
251 - ),
252 - decoder_input=(
253 - None
254 - if decoder_prompt is None
255 - else self._prompt_to_llm_inputs(
256 - decoder_prompt,
257 - tokenization_kwargs=tokenization_kwargs,
258 - )
259 - ),
260 - decoder_start_token_id=self.renderer.get_dec_start_token_id(),
261 - skip_decoder_start_token=skip_decoder_start_token,
262 - )
263 -
264 - def _process_decoder_only_prompt(
265 - self,
266 - prompt: DecoderOnlyDictPrompt,
267 - tokenization_kwargs: dict[str, Any] | None = None,
268 - ) -> DecoderOnlyEngineInput:
269 - return self._prompt_to_llm_inputs(
270 - prompt,
271 - tokenization_kwargs=tokenization_kwargs,
272 - )
273 -
274 - def preprocess(
275 - self,
276 - prompt: PromptType,
277 - tokenization_kwargs: dict[str, Any] | None = None,
278 - ) -> EngineInput:
279 - """Preprocess the input prompt."""
280 - if self.model_config.is_encoder_decoder:
281 - # Encoder-decoder model requires special mapping of
282 - # input prompts to encoder & decoder.
283 - return self._process_encoder_decoder_prompt(
284 - parse_enc_dec_prompt(prompt),
285 - tokenization_kwargs,
286 - )
287 -
288 - return self._process_decoder_only_prompt(
289 - parse_dec_only_prompt(prompt),
290 - tokenization_kwargs=tokenization_kwargs,
291 - )
vllm/renderers/base.py
+1 -6
@@ -725,14 +725,12 @@ class BaseRenderer(ABC, Generic[_T]):
725
726 return mm_uuid_items
727
728 - # TODO: Remove str and tokenization_kwargs after deprecating InputPreprocessor
728 def _process_multimodal(
729 self,
731 - prompt: list[int] | str,
730 + prompt: list[int],
731 mm_data: MultiModalDataDict,
732 mm_uuids: MultiModalUUIDDict | None,
733 mm_processor_kwargs: Mapping[str, object] | None,
735 - tokenization_kwargs: dict[str, Any] | None,
734 *,
735 skip_mm_cache: bool = False,
736 ) -> "MultiModalInput":
@@ -755,7 +753,6 @@ class BaseRenderer(ABC, Generic[_T]):
753 mm_data_items,
754 mm_uuid_items,
755 hf_processor_mm_kwargs=mm_processor_kwargs or {},
758 - tokenization_kwargs=tokenization_kwargs or {},
756 )
757 mm_timing_ctx = self._mm_timing_registry.get(mm_req_id)
758
@@ -783,7 +780,6 @@ class BaseRenderer(ABC, Generic[_T]):
780 prompt_token_ids,
781 multi_modal_data,
782 mm_processor_kwargs=prompt.get("mm_processor_kwargs"),
786 - tokenization_kwargs=None, # Tokenization already done in Step 2
783 mm_uuids=prompt.get("multi_modal_uuids"),
784 skip_mm_cache=skip_mm_cache,
785 )
@@ -846,7 +842,6 @@ class BaseRenderer(ABC, Generic[_T]):
842 prompt_token_ids,
843 multi_modal_data,
844 mm_processor_kwargs=prompt.get("mm_processor_kwargs"),
849 - tokenization_kwargs=None,
845 mm_uuids=prompt.get("multi_modal_uuids"),
846 skip_mm_cache=skip_mm_cache,
847 )
vllm/v1/engine/input_processor.py
+27 -32
@@ -14,7 +14,6 @@ from vllm.inputs import (
14 SingletonInput,
15 split_enc_dec_input,
16 )
17 -from vllm.inputs.preprocess import InputPreprocessor
17 from vllm.logger import init_logger
18 from vllm.lora.request import LoRARequest
19 from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
@@ -24,6 +23,7 @@ from vllm.multimodal.utils import argsort_mm_positions
23 from vllm.platforms import current_platform
24 from vllm.pooling_params import PoolingParams
25 from vllm.renderers import BaseRenderer, renderer_from_config
26 +from vllm.renderers.inputs.preprocess import parse_model_prompt
27 from vllm.sampling_params import SamplingParams
28 from vllm.tasks import GENERATION_TASKS, POOLING_TASKS, SupportedTask
29 from vllm.tokenizers import TokenizerLike
@@ -67,12 +67,6 @@ class InputProcessor:
67 )
68 mm_budget.reset_cache() # Not used anymore
69
70 - self.input_preprocessor = InputPreprocessor(
71 - vllm_config,
72 - renderer=renderer,
73 - mm_registry=mm_registry,
74 - )
75 -
70 # Raw-prompt preprocessing (tokenization and multimodal processing)
71 # is blocking, so async callers should run it on the renderer's
72 # thread pool to keep their event loop responsive.
@@ -313,44 +307,45 @@ class InputProcessor:
307 )
308
309 if isinstance(prompt, dict) and "type" in prompt:
316 - if tokenization_kwargs:
317 - logger.warning_once(
318 - "Passing tokenization_kwargs to InputProcessor is deprecated "
319 - "and will be removed in v0.18. You should instead pass "
320 - "them to Renderer.render_cmpl() or Renderer.render_chat()."
321 - )
322 -
310 if arrival_time is None:
311 arrival_time = prompt.get("arrival_time", time.time()) # type: ignore[assignment]
312
326 - processed_inputs: EngineInput = prompt # type: ignore[assignment]
313 + engine_input: EngineInput = prompt # type: ignore[assignment]
314 else:
315 logger.warning_once(
316 "Passing raw prompts to InputProcessor is deprecated "
330 - "and will be removed in v0.18. You should instead pass "
317 + "and will be removed in the future. You should instead pass "
318 "the outputs of Renderer.render_cmpl() or Renderer.render_chat()."
319 )
320
321 if arrival_time is None:
322 arrival_time = time.time()
323
337 - processed_inputs = self.input_preprocessor.preprocess(
338 - prompt,
339 - tokenization_kwargs=tokenization_kwargs,
324 + renderer = self.renderer
325 + model_config = self.model_config
326 +
327 + parsed_prompt = parse_model_prompt(model_config, prompt)
328 + tok_params = renderer.default_cmpl_tok_params.with_kwargs(
329 + **(tokenization_kwargs or {})
330 + )
331 +
332 + (engine_input,) = renderer.render_cmpl(
333 + [parsed_prompt],
334 + tok_params,
335 )
336
342 - current_platform.validate_request(processed_inputs, params)
337 + current_platform.validate_request(engine_input, params)
338
344 - encoder_inputs, decoder_inputs = split_enc_dec_input(processed_inputs)
345 - self._validate_model_inputs(encoder_inputs, decoder_inputs)
339 + encoder_input, decoder_input = split_enc_dec_input(engine_input)
340 + self._validate_model_inputs(encoder_input, decoder_input)
341
342 # Mypy can be conservative for TypedDict unions; normalize access.
348 - if decoder_inputs["type"] == "embeds":
349 - prompt_embeds = decoder_inputs["prompt_embeds"]
350 - prompt_token_ids = decoder_inputs.get("prompt_token_ids")
351 - prompt_is_token_ids = decoder_inputs.get("is_token_ids")
343 + if decoder_input["type"] == "embeds":
344 + prompt_embeds = decoder_input["prompt_embeds"]
345 + prompt_token_ids = decoder_input.get("prompt_token_ids")
346 + prompt_is_token_ids = decoder_input.get("is_token_ids")
347 else:
353 - prompt_token_ids = decoder_inputs["prompt_token_ids"]
348 + prompt_token_ids = decoder_input["prompt_token_ids"]
349 prompt_embeds = None
350 prompt_is_token_ids = None
351
@@ -385,10 +380,10 @@ class InputProcessor:
380 # Multimodal related.
381 mm_features: list[MultiModalFeatureSpec] | None = None
382
388 - if decoder_inputs["type"] == "multimodal":
389 - decoder_mm_inputs = decoder_inputs["mm_kwargs"]
390 - decoder_mm_positions = decoder_inputs["mm_placeholders"]
391 - decoder_mm_hashes = decoder_inputs["mm_hashes"]
383 + if decoder_input["type"] == "multimodal":
384 + decoder_mm_inputs = decoder_input["mm_kwargs"]
385 + decoder_mm_positions = decoder_input["mm_placeholders"]
386 + decoder_mm_hashes = decoder_input["mm_hashes"]
387
388 if not all(
389 isinstance(leaf, str) for leaf in json_iter_leaves(decoder_mm_hashes)
@@ -430,7 +425,7 @@ class InputProcessor:
425 pooling_params=pooling_params,
426 arrival_time=arrival_time,
427 lora_request=lora_request,
433 - cache_salt=decoder_inputs.get("cache_salt"),
428 + cache_salt=decoder_input.get("cache_salt"),
429 priority=priority,
430 data_parallel_rank=data_parallel_rank,
431 trace_headers=trace_headers,