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 8, 05:00 UTC
[Renderer] Move InputPreprocessor into Renderer (2/2) (#34560)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Feb 17, 2026 at 21:29 UTC
574fe75245fdadfa61b9c00b24dc84177540e3a5
32 files changed
+983
-1053
tests/entrypoints/llm/test_chat.py
+6
-9
@@ -195,18 +195,15 @@ def test_chat_batch_failure_cleanup(llm_for_failure_test):
195
valid_msg = [{"role": "user", "content": "Hello"}]
196
long_text = "This is a very long text to test the error " * 50
197
invalid_msg = [{"role": "user", "content": long_text}]
198
- batch_1 = [
199
- valid_msg,
200
- valid_msg,
201
- invalid_msg,
202
- ]
203
- batch_2 = [
204
- valid_msg,
205
- valid_msg,
206
- ]
198
+
199
+ batch_1 = [valid_msg, valid_msg, invalid_msg]
200
+ batch_2 = [valid_msg, valid_msg]
201
sampling_params = SamplingParams(temperature=0, max_tokens=10)
202
+
203
with pytest.raises(ValueError, match="context length is only"):
204
llm.chat(batch_1, sampling_params=sampling_params)
205
+ assert llm.llm_engine.get_num_unfinished_requests() == 0
206
+
207
outputs_2 = llm.chat(batch_2, sampling_params=sampling_params)
208
assert len(outputs_2) == len(batch_2)
209
assert llm.llm_engine.get_num_unfinished_requests() == 0
tests/models/multimodal/processing/test_common.py
+3
-2
@@ -489,8 +489,9 @@ def _assert_inputs_equal(
489
if ignore_mm_keys is None:
490
ignore_mm_keys = set()
491
492
- a_rest = {k: v for k, v in a.items() if k != "mm_kwargs"}
493
- b_rest = {k: v for k, v in b.items() if k != "mm_kwargs"}
492
+ ignore_prompt_keys = ("prompt", "mm_kwargs")
493
+ a_rest = {k: v for k, v in a.items() if k not in ignore_prompt_keys}
494
+ b_rest = {k: v for k, v in b.items() if k not in ignore_prompt_keys}
495
496
assert a_rest == b_rest, msg
497
tests/renderers/test_process_multi_modal_uuids.py
new
+165
@@ -0,0 +1,165 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+import pytest
5
+
6
+from vllm.assets.image import ImageAsset
7
+from vllm.assets.video import VideoAsset
8
+from vllm.config import CacheConfig, ModelConfig, VllmConfig
9
+from vllm.renderers.hf import HfRenderer
10
+from vllm.tokenizers.registry import tokenizer_args_from_config
11
+
12
+cherry_pil_image = ImageAsset("cherry_blossom").pil_image
13
+stop_pil_image = ImageAsset("stop_sign").pil_image
14
+baby_reading_np_ndarrays = VideoAsset("baby_reading").np_ndarrays
15
+
16
+
17
+def _build_renderer(
18
+ *, mm_cache_gb: float = 4.0, enable_prefix_caching: bool = True
19
+) -> HfRenderer:
20
+ model_config = ModelConfig(
21
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
22
+ max_model_len=128,
23
+ mm_processor_cache_gb=mm_cache_gb,
24
+ )
25
+
26
+ vllm_config = VllmConfig(
27
+ model_config=model_config,
28
+ cache_config=CacheConfig(enable_prefix_caching=enable_prefix_caching),
29
+ )
30
+
31
+ _, tokenizer_name, _, kwargs = tokenizer_args_from_config(model_config)
32
+
33
+ return HfRenderer.from_config(
34
+ vllm_config,
35
+ tokenizer_kwargs={**kwargs, "tokenizer_name": tokenizer_name},
36
+ )
37
+
38
+
39
+def test_multi_modal_uuids_length_mismatch_raises():
40
+ renderer = _build_renderer()
41
+
42
+ mm_data = {"image": [cherry_pil_image, stop_pil_image]}
43
+
44
+ # Mismatch: 2 items but only 1 uuid provided
45
+ mm_uuids = {"image": ["hash_cherry"]}
46
+
47
+ mm_processor = renderer.get_mm_processor()
48
+ mm_items = mm_processor.info.parse_mm_data(mm_data)
49
+
50
+ with pytest.raises(ValueError, match="must have same length as"):
51
+ renderer._process_mm_uuids(mm_data, mm_items, mm_uuids, "req-1")
52
+
53
+
54
+def test_multi_modal_uuids_missing_modality_raises():
55
+ renderer = _build_renderer()
56
+
57
+ mm_data = {
58
+ "image": [cherry_pil_image],
59
+ "video": None,
60
+ }
61
+
62
+ # Only image uuids provided; video missing should raise
63
+ mm_uuids = {"image": ["hash_cherry"]}
64
+
65
+ mm_processor = renderer.get_mm_processor()
66
+ mm_items = mm_processor.info.parse_mm_data(mm_data)
67
+
68
+ with pytest.raises(ValueError, match="is empty but .* is missing"):
69
+ renderer._process_mm_uuids(mm_data, mm_items, mm_uuids, "req-2")
70
+
71
+
72
+@pytest.mark.parametrize(
73
+ "mm_cache_gb, enable_prefix_caching",
74
+ [
75
+ (4.0, True), # default behavior
76
+ (4.0, False), # prefix caching disabled
77
+ (0.0, True), # processor cache disabled
78
+ ],
79
+)
80
+def test_multi_modal_uuids_accepts_none_and_passes_through(
81
+ monkeypatch, mm_cache_gb: float, enable_prefix_caching: bool
82
+):
83
+ renderer = _build_renderer(
84
+ mm_cache_gb=mm_cache_gb,
85
+ enable_prefix_caching=enable_prefix_caching,
86
+ )
87
+
88
+ mm_data = {
89
+ "image": [cherry_pil_image, stop_pil_image],
90
+ "video": baby_reading_np_ndarrays,
91
+ }
92
+
93
+ # Use a consistent two-image scenario across all configurations
94
+ mm_uuids = {"image": [None, "hash_stop"], "video": None}
95
+
96
+ mm_processor = renderer.get_mm_processor()
97
+ mm_items = mm_processor.info.parse_mm_data(mm_data)
98
+ processed_mm_uuids = renderer._process_mm_uuids(
99
+ mm_data, mm_items, mm_uuids, "req-3"
100
+ )
101
+
102
+ assert processed_mm_uuids == mm_uuids
103
+
104
+
105
+@pytest.mark.parametrize(
106
+ "mm_cache_gb, enable_prefix_caching",
107
+ [
108
+ (4.0, True), # default behavior
109
+ (4.0, False), # prefix caching disabled
110
+ (0.0, True), # processor cache disabled
111
+ ],
112
+)
113
+def test_multi_modal_uuids_accepts_empty(
114
+ monkeypatch, mm_cache_gb: float, enable_prefix_caching: bool
115
+):
116
+ renderer = _build_renderer(
117
+ mm_cache_gb=mm_cache_gb,
118
+ enable_prefix_caching=enable_prefix_caching,
119
+ )
120
+
121
+ # While None means cached multi-modal input requiring UUIDs
122
+ # an empty list means no multi-modal input
123
+ mm_data = {"image": [], "video": []} # type: ignore[var-annotated]
124
+ mm_uuids = {"image": [], "video": None} # type: ignore[var-annotated]
125
+
126
+ mm_processor = renderer.get_mm_processor()
127
+ mm_items = mm_processor.info.parse_mm_data(mm_data)
128
+ processed_mm_uuids = renderer._process_mm_uuids(
129
+ mm_data, mm_items, mm_uuids, "req-4"
130
+ )
131
+
132
+ assert processed_mm_uuids == mm_uuids
133
+
134
+
135
+def test_multi_modal_uuids_ignored_when_caching_disabled(monkeypatch):
136
+ # When both processor cache is 0 and prefix caching disabled, the
137
+ # processor builds overrides from request id instead of using user UUIDs.
138
+ renderer = _build_renderer(mm_cache_gb=0.0, enable_prefix_caching=False)
139
+
140
+ request_id = "req-42"
141
+ mm_data = {
142
+ "image": [cherry_pil_image, stop_pil_image],
143
+ "video": baby_reading_np_ndarrays,
144
+ }
145
+ mm_uuids = {"image": ["hash_cherry", "hash_stop"], "video": ["hash_video"]}
146
+
147
+ mm_processor = renderer.get_mm_processor()
148
+ mm_items = mm_processor.info.parse_mm_data(mm_data)
149
+ processed_mm_uuids = renderer._process_mm_uuids(
150
+ mm_data, mm_items, mm_uuids, request_id
151
+ )
152
+
153
+ # Expect request-id-based overrides are passed through
154
+ assert set(mm_uuids.keys()) == {"image", "video"}
155
+ assert len(mm_uuids["image"]) == 2
156
+ assert len(mm_uuids["video"]) == 1
157
+ assert processed_mm_uuids["image"][0].startswith(
158
+ f"{request_id}-image-"
159
+ ) and processed_mm_uuids["image"][0].endswith("-0")
160
+ assert processed_mm_uuids["image"][1].startswith(
161
+ f"{request_id}-image-"
162
+ ) and processed_mm_uuids["image"][1].endswith("-1")
163
+ assert processed_mm_uuids["video"][0].startswith(
164
+ f"{request_id}-video-"
165
+ ) and processed_mm_uuids["video"][0].endswith("-0")
tests/samplers/test_beam_search.py
-2
@@ -20,7 +20,6 @@ MM_BEAM_WIDTHS = [2]
20
MODELS = ["TinyLlama/TinyLlama-1.1B-Chat-v1.0"]
21
22
23
-@pytest.mark.skip_v1 # V1 engine does not yet support beam search
23
@pytest.mark.parametrize("model", MODELS)
24
@pytest.mark.parametrize("dtype", ["half"])
25
@pytest.mark.parametrize("max_tokens", MAX_TOKENS)
@@ -62,7 +61,6 @@ def test_beam_search_single_input(
61
)
62
63
65
-@pytest.mark.skip_v1 # V1 engine does not yet support beam search
64
@pytest.mark.parametrize("model", MODELS)
65
@pytest.mark.parametrize("dtype", ["half"])
66
@pytest.mark.parametrize("max_tokens", MAX_TOKENS)
tests/v1/engine/test_process_multi_modal_uuids.py
deleted
-174
@@ -1,174 +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.assets.image import ImageAsset
7
-from vllm.assets.video import VideoAsset
8
-from vllm.config import CacheConfig, ModelConfig, VllmConfig
9
-from vllm.multimodal import MultiModalUUIDDict
10
-from vllm.sampling_params import SamplingParams
11
-from vllm.v1.engine.input_processor import InputProcessor
12
-
13
-cherry_pil_image = ImageAsset("cherry_blossom").pil_image
14
-stop_pil_image = ImageAsset("stop_sign").pil_image
15
-baby_reading_np_ndarrays = VideoAsset("baby_reading").np_ndarrays
16
-
17
-
18
-def _build_input_processor(
19
- *, mm_cache_gb: float = 4.0, enable_prefix_caching: bool = True
20
-) -> InputProcessor:
21
- model_config = ModelConfig(
22
- model="Qwen/Qwen2.5-VL-3B-Instruct",
23
- max_model_len=128,
24
- mm_processor_cache_gb=mm_cache_gb,
25
- )
26
-
27
- vllm_config = VllmConfig(
28
- model_config=model_config,
29
- cache_config=CacheConfig(enable_prefix_caching=enable_prefix_caching),
30
- )
31
-
32
- return InputProcessor(vllm_config)
33
-
34
-
35
-def test_multi_modal_uuids_length_mismatch_raises():
36
- input_processor = _build_input_processor()
37
-
38
- prompt = {
39
- "prompt": "USER: <image>\nDescribe\nASSISTANT:",
40
- "multi_modal_data": {"image": [cherry_pil_image, stop_pil_image]},
41
- # Mismatch: 2 items but only 1 uuid provided
42
- "multi_modal_uuids": {"image": ["hash_cherry"]},
43
- }
44
-
45
- with pytest.raises(ValueError, match="must have same length as"):
46
- input_processor.process_inputs(
47
- request_id="req-1",
48
- prompt=prompt, # type: ignore[arg-type]
49
- params=SamplingParams(),
50
- )
51
-
52
-
53
-def test_multi_modal_uuids_missing_modality_raises():
54
- input_processor = _build_input_processor()
55
-
56
- prompt = {
57
- "prompt": "USER: <image><video>\nDescribe\nASSISTANT:",
58
- # Two modalities provided in data
59
- "multi_modal_data": {
60
- "image": [cherry_pil_image],
61
- "video": None,
62
- },
63
- # Only image uuids provided; video missing should raise
64
- "multi_modal_uuids": {"image": ["hash_cherry"]},
65
- }
66
-
67
- with pytest.raises(ValueError, match="is empty but .* is missing"):
68
- input_processor.process_inputs(
69
- request_id="req-2",
70
- prompt=prompt, # type: ignore[arg-type]
71
- params=SamplingParams(),
72
- )
73
-
74
-
75
-@pytest.mark.parametrize(
76
- "mm_cache_gb, enable_prefix_caching",
77
- [
78
- (4.0, True), # default behavior
79
- (4.0, False), # prefix caching disabled
80
- (0.0, True), # processor cache disabled
81
- ],
82
-)
83
-def test_multi_modal_uuids_accepts_none_and_passes_through(
84
- monkeypatch, mm_cache_gb: float, enable_prefix_caching: bool
85
-):
86
- input_processor = _build_input_processor(
87
- mm_cache_gb=mm_cache_gb,
88
- enable_prefix_caching=enable_prefix_caching,
89
- )
90
-
91
- # Capture the overrides passed to InputPreprocessor.preprocess
92
- captured: dict[str, object] = {}
93
-
94
- def fake_preprocess(
95
- prompt, *, tokenization_kwargs=None, lora_request=None, mm_uuids=None
96
- ):
97
- captured["mm_uuids"] = mm_uuids
98
- # Minimal processed inputs for decoder-only flow
99
- return {"type": "token", "prompt_token_ids": [1]}
100
-
101
- # Monkeypatch only the bound preprocess method on this instance
102
- monkeypatch.setattr(
103
- input_processor.input_preprocessor, "preprocess", fake_preprocess, raising=True
104
- )
105
-
106
- # Use a consistent two-image scenario across all configurations
107
- mm_uuids = {"image": [None, "hash_stop"], "video": None}
108
- prompt = {
109
- "prompt": "USER: <image><image>\nTwo images\nASSISTANT:",
110
- "multi_modal_data": {
111
- "image": [cherry_pil_image, stop_pil_image],
112
- "video": baby_reading_np_ndarrays,
113
- },
114
- "multi_modal_uuids": mm_uuids,
115
- }
116
-
117
- input_processor.process_inputs(
118
- request_id="req-3",
119
- prompt=prompt, # type: ignore[arg-type]
120
- params=SamplingParams(),
121
- )
122
-
123
- assert captured["mm_uuids"] == mm_uuids
124
-
125
-
126
-def test_multi_modal_uuids_ignored_when_caching_disabled(monkeypatch):
127
- # When both processor cache is 0 and prefix caching disabled, the
128
- # processor builds overrides from request id instead of using user UUIDs.
129
- input_processor = _build_input_processor(
130
- mm_cache_gb=0.0, enable_prefix_caching=False
131
- )
132
-
133
- captured: dict[str, MultiModalUUIDDict] = {}
134
-
135
- def fake_preprocess(
136
- prompt, *, tokenization_kwargs=None, lora_request=None, mm_uuids=None
137
- ):
138
- captured["mm_uuids"] = mm_uuids
139
- return {"type": "token", "prompt_token_ids": [1]}
140
-
141
- monkeypatch.setattr(
142
- input_processor.input_preprocessor, "preprocess", fake_preprocess, raising=True
143
- )
144
-
145
- request_id = "req-42"
146
- mm_uuids = {"image": ["hash_cherry", "hash_stop"], "video": ["hash_video"]}
147
- prompt = {
148
- "prompt": "USER: <image><image><video>\nDescribe\nASSISTANT:",
149
- "multi_modal_data": {
150
- "image": [cherry_pil_image, stop_pil_image],
151
- "video": [baby_reading_np_ndarrays],
152
- },
153
- "multi_modal_uuids": mm_uuids,
154
- }
155
-
156
- input_processor.process_inputs(
157
- request_id=request_id,
158
- prompt=prompt, # type: ignore[arg-type]
159
- params=SamplingParams(),
160
- )
161
-
162
- # Expect request-id-based overrides are passed through
163
- assert set(mm_uuids.keys()) == {"image", "video"}
164
- assert len(mm_uuids["image"]) == 2
165
- assert len(mm_uuids["video"]) == 1
166
- assert captured["mm_uuids"]["image"][0].startswith(
167
- f"{request_id}-image-"
168
- ) and captured["mm_uuids"]["image"][0].endswith("-0")
169
- assert captured["mm_uuids"]["image"][1].startswith(
170
- f"{request_id}-image-"
171
- ) and captured["mm_uuids"]["image"][1].endswith("-1")
172
- assert captured["mm_uuids"]["video"][0].startswith(
173
- f"{request_id}-video-"
174
- ) and captured["mm_uuids"]["video"][0].endswith("-0")
vllm/beam_search.py
+29
-8
@@ -2,13 +2,11 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
from dataclasses import dataclass
5
-from typing import TYPE_CHECKING, Any
5
6
+from vllm.inputs import TokenInputs, token_inputs
7
from vllm.logprobs import Logprob
8
from vllm.lora.request import LoRARequest
9
-
10
-if TYPE_CHECKING:
11
- from vllm.multimodal import MultiModalDataDict
9
+from vllm.multimodal.inputs import MultiModalInputs, mm_inputs
10
11
12
@dataclass
@@ -19,6 +17,8 @@ class BeamSearchSequence:
17
about to be returned to the user.
18
"""
19
20
+ orig_prompt: TokenInputs | MultiModalInputs
21
+
22
# The tokens include the prompt.
23
tokens: list[int]
24
logprobs: list[dict[int, Logprob]]
@@ -27,8 +27,28 @@ class BeamSearchSequence:
27
text: str | None = None
28
finish_reason: str | None = None
29
stop_reason: int | str | None = None
30
- multi_modal_data: "MultiModalDataDict | None" = None
31
- mm_processor_kwargs: dict[str, Any] | None = None
30
+
31
+ def get_prompt(self):
32
+ prompt = self.orig_prompt
33
+
34
+ prompt_text = prompt.get("prompt")
35
+ cache_salt = prompt.get("cache_salt")
36
+
37
+ if prompt["type"] == "token":
38
+ return token_inputs(
39
+ self.tokens,
40
+ prompt=prompt_text,
41
+ cache_salt=cache_salt,
42
+ )
43
+
44
+ return mm_inputs(
45
+ prompt_token_ids=self.tokens,
46
+ mm_kwargs=prompt["mm_kwargs"],
47
+ mm_hashes=prompt["mm_hashes"],
48
+ mm_placeholders=prompt["mm_placeholders"],
49
+ prompt=prompt_text,
50
+ cache_salt=cache_salt,
51
+ )
52
53
54
@dataclass
@@ -44,14 +64,15 @@ class BeamSearchOutput:
64
class BeamSearchInstance:
65
def __init__(
66
self,
47
- prompt_tokens: list[int],
67
+ prompt: TokenInputs | MultiModalInputs,
68
lora_request: LoRARequest | None = None,
69
logprobs: list[dict[int, Logprob]] | None = None,
70
**kwargs,
71
):
72
self.beams: list[BeamSearchSequence] = [
73
BeamSearchSequence(
54
- tokens=prompt_tokens,
74
+ orig_prompt=prompt,
75
+ tokens=prompt["prompt_token_ids"],
76
logprobs=[] if logprobs is None else list(logprobs),
77
lora_request=lora_request,
78
**kwargs,
vllm/engine/protocol.py
+6
-6
@@ -11,13 +11,12 @@ from vllm.distributed.weight_transfer.base import (
11
WeightTransferInitRequest,
12
WeightTransferUpdateRequest,
13
)
14
-from vllm.inputs.data import PromptType
14
+from vllm.inputs.data import ProcessorInputs, PromptType
15
from vllm.lora.request import LoRARequest
16
from vllm.outputs import PoolingRequestOutput, RequestOutput
17
from vllm.plugins.io_processors import IOProcessor
18
from vllm.pooling_params import PoolingParams
19
from vllm.renderers import BaseRenderer
20
-from vllm.renderers.inputs import DictPrompt, TokPrompt
20
from vllm.sampling_params import SamplingParams
21
from vllm.tasks import SupportedTask
22
from vllm.v1.engine import EngineCoreRequest
@@ -35,7 +34,7 @@ class StreamingInput:
34
where inputs are provided via an async generator.
35
"""
36
38
- prompt: PromptType
37
+ prompt: ProcessorInputs
38
sampling_params: SamplingParams | None = None
39
40
@@ -69,8 +68,7 @@ class EngineClient(ABC):
68
self,
69
prompt: EngineCoreRequest
70
| PromptType
72
- | DictPrompt
73
- | TokPrompt
71
+ | ProcessorInputs
72
| AsyncGenerator[StreamingInput, None],
73
sampling_params: SamplingParams,
74
request_id: str,
@@ -81,6 +79,7 @@ class EngineClient(ABC):
79
trace_headers: Mapping[str, str] | None = None,
80
priority: int = 0,
81
data_parallel_rank: int | None = None,
82
+ reasoning_ended: bool | None = None,
83
) -> AsyncGenerator[RequestOutput, None]:
84
"""Generate outputs for a request."""
85
...
@@ -88,13 +87,14 @@ class EngineClient(ABC):
87
@abstractmethod
88
def encode(
89
self,
91
- prompt: PromptType | DictPrompt | TokPrompt,
90
+ prompt: PromptType | ProcessorInputs,
91
pooling_params: PoolingParams,
92
request_id: str,
93
lora_request: LoRARequest | None = None,
94
trace_headers: Mapping[str, str] | None = None,
95
priority: int = 0,
96
tokenization_kwargs: dict[str, Any] | None = None,
97
+ reasoning_ended: bool | None = None,
98
) -> AsyncGenerator[PoolingRequestOutput, None]:
99
"""Generate outputs for a request from a pooling model."""
100
...
vllm/entrypoints/llm.py
+203
-221
@@ -3,8 +3,8 @@
3
4
import itertools
5
import warnings
6
-from collections.abc import Callable, Sequence
7
-from typing import TYPE_CHECKING, Any, cast
6
+from collections.abc import Callable, Iterable, Sequence
7
+from typing import TYPE_CHECKING, Any
8
9
import cloudpickle
10
import torch.nn as nn
@@ -55,6 +55,7 @@ from vllm.entrypoints.pooling.score.utils import (
55
from vllm.entrypoints.utils import log_non_default_args
56
from vllm.inputs.data import (
57
DataPrompt,
58
+ ProcessorInputs,
59
PromptType,
60
SingletonPrompt,
61
TextPrompt,
@@ -73,10 +74,8 @@ from vllm.outputs import (
74
from vllm.platforms import current_platform
75
from vllm.pooling_params import PoolingParams
76
from vllm.renderers import ChatParams, merge_kwargs
76
-from vllm.renderers.inputs import DictPrompt, TokPrompt
77
from vllm.renderers.inputs.preprocess import (
78
conversation_to_seq,
79
- extract_prompt_components,
79
parse_model_prompt,
80
prompt_to_seq,
81
)
@@ -86,6 +85,7 @@ from vllm.tokenizers import TokenizerLike
85
from vllm.tokenizers.mistral import MistralTokenizer
86
from vllm.usage.usage_lib import UsageContext
87
from vllm.utils.counter import Counter
88
+from vllm.utils.tqdm_utils import maybe_tqdm
89
from vllm.v1.engine.llm_engine import LLMEngine
90
from vllm.v1.sample.logits_processor import LogitsProcessor
91
@@ -400,7 +400,7 @@ class LLM:
400
sampling_params: SamplingParams | Sequence[SamplingParams] | None = None,
401
*,
402
use_tqdm: bool | Callable[..., tqdm] = True,
403
- lora_request: list[LoRARequest] | LoRARequest | None = None,
403
+ lora_request: Sequence[LoRARequest] | LoRARequest | None = None,
404
priority: list[int] | None = None,
405
tokenization_kwargs: dict[str, Any] | None = None,
406
) -> list[RequestOutput]:
@@ -462,7 +462,7 @@ class LLM:
462
self,
463
prompts: PromptType | Sequence[PromptType],
464
sampling_params: SamplingParams | Sequence[SamplingParams] | None = None,
465
- lora_request: list[LoRARequest] | LoRARequest | None = None,
465
+ lora_request: Sequence[LoRARequest] | LoRARequest | None = None,
466
priority: list[int] | None = None,
467
use_tqdm: bool | Callable[..., tqdm] = True,
468
tokenization_kwargs: dict[str, Any] | None = None,
@@ -495,34 +495,32 @@ class LLM:
495
# Use the same preprocessing as _run_completion
496
seq_prompts = prompt_to_seq(prompts)
497
seq_params = self._params_to_seq(sampling_params, len(seq_prompts))
498
-
499
- if any(param.truncate_prompt_tokens is not None for param in seq_params):
500
- engine_prompts: Sequence[DictPrompt | TokPrompt] = [
501
- engine_prompt
502
- for prompt, param in zip(seq_prompts, seq_params)
503
- for engine_prompt in self._preprocess_cmpl(
504
- [prompt],
505
- tokenization_kwargs=merge_kwargs(
506
- tokenization_kwargs,
507
- dict(truncate_prompt_tokens=param.truncate_prompt_tokens),
498
+ seq_lora_requests = self._lora_request_to_seq(lora_request, len(seq_prompts))
499
+ seq_tok_kwargs = [
500
+ merge_kwargs(
501
+ tokenization_kwargs,
502
+ dict(truncate_prompt_tokens=param.truncate_prompt_tokens),
503
+ )
504
+ for param in seq_params
505
+ ]
506
+ seq_priority = self._priority_to_seq(priority, len(prompts))
507
+
508
+ request_ids = self._render_and_add_requests(
509
+ prompts=(
510
+ self._preprocess_cmpl_one(prompt, tok_kwargs)
511
+ for prompt, tok_kwargs in zip(
512
+ maybe_tqdm(
513
+ seq_prompts,
514
+ use_tqdm=use_tqdm,
515
+ desc="Rendering prompts",
516
),
517
+ seq_tok_kwargs,
518
)
510
- ]
511
- else:
512
- engine_prompts = self._preprocess_cmpl(
513
- seq_prompts,
514
- tokenization_kwargs=tokenization_kwargs,
515
- )
516
-
517
- request_ids = self._validate_and_add_requests(
518
- prompts=engine_prompts,
519
- params=seq_params,
520
- use_tqdm=use_tqdm,
521
- lora_request=self._get_modality_specific_lora_reqs(
522
- engine_prompts, lora_request
519
),
520
+ params=seq_params,
521
+ lora_requests=seq_lora_requests,
522
tokenization_kwargs=tokenization_kwargs,
525
- priority=priority,
523
+ priorities=seq_priority,
524
)
525
526
return request_ids
@@ -545,53 +543,41 @@ class LLM:
543
outputs = self._run_engine(use_tqdm=use_tqdm)
544
return self.engine_class.validate_outputs(outputs, RequestOutput)
545
548
- def _get_modality_specific_lora_reqs(
546
+ def _resolve_lora_reqs(
547
self,
550
- prompts: Sequence[DictPrompt | TokPrompt],
551
- lora_request: list[LoRARequest] | LoRARequest | None,
548
+ prompts: Sequence[ProcessorInputs],
549
+ lora_request: Sequence[LoRARequest | None] | LoRARequest | None,
550
):
553
- # Grab the lora config off the vllm config on the engine,
554
- # since this is the same for both v0 & v1.
551
lora_config = self.llm_engine.vllm_config.lora_config
552
+ seq_lora_requests = self._lora_request_to_seq(lora_request, len(prompts))
553
557
- # If there's no lora config / default_mm_loras, or the model
558
- # isn't multimodal, leave the lora as is.
554
if (
555
lora_config is None
556
or not self.model_config.is_multimodal_model
557
or (lora_config and lora_config.default_mm_loras is None)
558
):
564
- return lora_request
565
-
566
- optional_loras = (
567
- [lora_request] * len(prompts)
568
- if not isinstance(lora_request, Sequence)
569
- else lora_request
570
- )
559
+ return seq_lora_requests
560
561
return [
562
self._resolve_single_prompt_mm_lora(
563
prompt,
575
- opt_lora_req,
564
+ lora_req,
565
lora_config.default_mm_loras,
566
)
578
- for prompt, opt_lora_req in zip(prompts, optional_loras)
567
+ for prompt, lora_req in zip(prompts, seq_lora_requests)
568
]
569
570
def _resolve_single_prompt_mm_lora(
571
self,
583
- prompt: DictPrompt | TokPrompt,
572
+ prompt: ProcessorInputs,
573
lora_request: LoRARequest | None,
574
default_mm_loras: dict[str, str] | None,
575
):
587
- if not default_mm_loras or not (
588
- mm_data := prompt.get("multi_modal_data") or {}
589
- ):
576
+ if not default_mm_loras or prompt["type"] != "multimodal":
577
return lora_request
578
592
- intersection = set(
593
- mm_data.keys() # type: ignore
594
- ).intersection(default_mm_loras.keys())
579
+ prompt_modalities = prompt["mm_placeholders"].keys()
580
+ intersection = set(prompt_modalities).intersection(default_mm_loras.keys())
581
if not intersection:
582
return lora_request
583
if len(intersection) > 1:
@@ -674,22 +660,6 @@ class LLM:
660
"""
661
return self.llm_engine.apply_model(func)
662
677
- def _get_beam_search_lora_requests(
678
- self,
679
- lora_request: list[LoRARequest] | LoRARequest | None,
680
- prompts: list[TokensPrompt | TextPrompt],
681
- ) -> list[LoRARequest | None]:
682
- """Get the optional lora request corresponding to each prompt."""
683
- if isinstance(lora_request, Sequence) and len(lora_request) != len(prompts):
684
- raise ValueError(
685
- "Lora request list should be the same length as the prompts"
686
- )
687
-
688
- if lora_request is None or isinstance(lora_request, LoRARequest):
689
- return [lora_request] * len(prompts)
690
-
691
- raise TypeError(f"Invalid lora_request type {type(lora_request)}")
692
-
663
def beam_search(
664
self,
665
prompts: list[TokensPrompt | TextPrompt],
@@ -718,13 +688,12 @@ class LLM:
688
ignore_eos = params.ignore_eos
689
length_penalty = params.length_penalty
690
721
- lora_requests = self._get_beam_search_lora_requests(lora_request, prompts)
691
+ tokenizer = self.renderer.get_tokenizer()
692
+ eos_token_id = tokenizer.eos_token_id
693
+ sort_beams_key = create_sort_beams_key_function(eos_token_id, length_penalty)
694
723
- tokenizer = self.get_tokenizer()
724
- sort_beams_key = create_sort_beams_key_function(
725
- tokenizer.eos_token_id,
726
- length_penalty,
727
- )
695
+ engine_prompts = self._preprocess_cmpl(prompts)
696
+ lora_requests = self._lora_request_to_seq(lora_request, len(engine_prompts))
697
698
if use_tqdm and concurrency_limit is not None:
699
logger.warning(
@@ -734,21 +703,12 @@ class LLM:
703
use_tqdm = False
704
705
if concurrency_limit is None:
737
- concurrency_limit = len(prompts)
738
-
739
- def create_tokens_prompt_from_beam(beam: BeamSearchSequence) -> TokensPrompt:
740
- token_prompt_kwargs: TokensPrompt = {"prompt_token_ids": beam.tokens}
741
- if beam.multi_modal_data is not None:
742
- token_prompt_kwargs["multi_modal_data"] = beam.multi_modal_data
743
-
744
- if beam.mm_processor_kwargs is not None:
745
- token_prompt_kwargs["mm_processor_kwargs"] = beam.mm_processor_kwargs
746
- return TokensPrompt(**token_prompt_kwargs)
706
+ concurrency_limit = len(engine_prompts)
707
708
# generate 2 * beam_width candidates at each step
709
# following the huggingface transformers implementation
710
# at https://github.com/huggingface/transformers/blob/e15687fffe5c9d20598a19aeab721ae0a7580f8a/src/transformers/generation/beam_search.py#L534 # noqa
751
- beam_search_params = SamplingParams(
711
+ sampling_params = SamplingParams(
712
logprobs=2 * beam_width,
713
max_tokens=1,
714
temperature=temperature,
@@ -756,30 +716,25 @@ class LLM:
716
)
717
instances: list[BeamSearchInstance] = []
718
759
- for lora_req, prompt in zip(lora_requests, prompts):
760
- # Add multimodal processor kwargs & data
761
- mm_kwargs = {}
762
- if "multi_modal_data" in prompt:
763
- mm_kwargs["multi_modal_data"] = prompt["multi_modal_data"]
764
- if "mm_processor_kwargs" in prompt:
765
- mm_kwargs["mm_processor_kwargs"] = prompt["mm_processor_kwargs"]
766
-
767
- if "prompt_token_ids" in prompt:
768
- prompt = cast(TokensPrompt, prompt) # Needed for mypy
769
- prompt_tokens = prompt["prompt_token_ids"]
770
- else:
771
- prompt_tokens = tokenizer.encode(prompt["prompt"])
719
+ for lora_req, prompt in zip(lora_requests, engine_prompts):
720
+ if prompt["type"] == "embeds":
721
+ raise NotImplementedError(
722
+ "Embedding prompt not supported for beam search"
723
+ )
724
+ if prompt["type"] == "enc_dec":
725
+ raise NotImplementedError(
726
+ "Encoder-decoder prompt not supported for beam search"
727
+ )
728
729
instances.append(
730
BeamSearchInstance(
775
- prompt_tokens,
731
+ prompt,
732
lora_request=lora_req,
733
logprobs=None,
778
- **mm_kwargs,
734
),
735
)
736
782
- for prompt_start in range(0, len(prompts), concurrency_limit):
737
+ for prompt_start in range(0, len(instances), concurrency_limit):
738
instances_batch = instances[prompt_start : prompt_start + concurrency_limit]
739
740
token_iter = range(max_tokens)
@@ -808,22 +763,15 @@ class LLM:
763
if len(all_beams) == 0:
764
break
765
811
- # create corresponding batch entries for prompt & optional lora
812
- prompts_batch, lora_req_batch = zip(
813
- *[
814
- (create_tokens_prompt_from_beam(beam), beam.lora_request)
815
- for beam in all_beams
816
- ]
817
- )
818
-
766
# only runs for one step
767
# we don't need to use tqdm here
821
- output = self.generate(
822
- prompts_batch,
823
- sampling_params=beam_search_params,
768
+ raw_output = self._render_and_run_requests(
769
+ prompts=(beam.get_prompt() for beam in all_beams),
770
+ params=self._params_to_seq(sampling_params, len(all_beams)),
771
+ lora_requests=[beam.lora_request for beam in all_beams],
772
use_tqdm=False,
825
- lora_request=lora_req_batch,
773
)
774
+ output = self.engine_class.validate_outputs(raw_output, RequestOutput)
775
776
for (start, end), instance in zip(
777
instance_start_and_end, instances_batch
@@ -841,19 +789,15 @@ class LLM:
789
logprobs = result.outputs[0].logprobs[0]
790
for token_id, logprob_obj in logprobs.items():
791
new_beam = BeamSearchSequence(
792
+ current_beam.orig_prompt,
793
tokens=current_beam.tokens + [token_id],
794
logprobs=current_beam.logprobs + [logprobs],
795
lora_request=current_beam.lora_request,
796
cum_logprob=current_beam.cum_logprob
797
+ logprob_obj.logprob,
849
- multi_modal_data=current_beam.multi_modal_data,
850
- mm_processor_kwargs=current_beam.mm_processor_kwargs,
798
)
799
853
- if (
854
- token_id == tokenizer.eos_token_id
855
- and not ignore_eos
856
- ):
800
+ if token_id == eos_token_id and not ignore_eos:
801
instance.completed.append(new_beam)
802
else:
803
instance_new_beams.append(new_beam)
@@ -872,6 +816,7 @@ class LLM:
816
817
for beam in best_beams:
818
beam.text = tokenizer.decode(beam.tokens)
819
+
820
outputs.append(BeamSearchOutput(sequences=best_beams))
821
822
return outputs
@@ -880,7 +825,7 @@ class LLM:
825
self,
826
prompts: Sequence[PromptType],
827
tokenization_kwargs: dict[str, Any] | None = None,
883
- ) -> Sequence[DictPrompt | TokPrompt]:
828
+ ) -> Sequence[ProcessorInputs]:
829
"""
830
Convert prompt inputs from LLM APIs (other than [LLM.chat][]) into
831
a format that can be passed to `_add_request`.
@@ -888,8 +833,7 @@ class LLM:
833
Refer to [LLM.generate][] for a complete description of the arguments.
834
835
Returns:
891
- A list of `TokPrompt` objects containing the tokenized prompt
892
- after chat template interpolation, and the raw multi-modal inputs.
836
+ A list of `ProcessorInputs` objects ready to be passed into LLMEngine.
837
"""
838
renderer = self.renderer
839
model_config = self.model_config
@@ -903,6 +847,14 @@ class LLM:
847
848
return renderer.render_cmpl(parsed_prompts, tok_params)
849
850
+ def _preprocess_cmpl_one(
851
+ self,
852
+ prompt: PromptType,
853
+ tokenization_kwargs: dict[str, Any] | None = None,
854
+ ) -> ProcessorInputs:
855
+ (engine_prompt,) = self._preprocess_cmpl([prompt], tokenization_kwargs)
856
+ return engine_prompt
857
+
858
def _preprocess_chat(
859
self,
860
conversations: Sequence[list[ChatCompletionMessageParam]],
@@ -914,7 +866,7 @@ class LLM:
866
tools: list[dict[str, Any]] | None = None,
867
tokenization_kwargs: dict[str, Any] | None = None,
868
mm_processor_kwargs: dict[str, Any] | None = None,
917
- ) -> Sequence[TokPrompt]:
869
+ ) -> Sequence[ProcessorInputs]:
870
"""
871
Convert a list of conversations into prompts so that they can then
872
be used as input for other LLM APIs.
@@ -922,8 +874,7 @@ class LLM:
874
Refer to [LLM.chat][] for a complete description of the arguments.
875
876
Returns:
925
- A list of `TokPrompt` objects containing the tokenized prompt
926
- after chat template interpolation, and the raw multi-modal inputs.
877
+ A list of `ProcessorInputs` objects ready to be passed into LLMEngine.
878
"""
879
renderer = self.renderer
880
@@ -953,13 +904,39 @@ class LLM:
904
905
return engine_prompts
906
907
+ def _preprocess_chat_one(
908
+ self,
909
+ conversation: list[ChatCompletionMessageParam],
910
+ chat_template: str | None = None,
911
+ chat_template_content_format: ChatTemplateContentFormatOption = "auto",
912
+ chat_template_kwargs: dict[str, Any] | None = None,
913
+ add_generation_prompt: bool = True,
914
+ continue_final_message: bool = False,
915
+ tools: list[dict[str, Any]] | None = None,
916
+ tokenization_kwargs: dict[str, Any] | None = None,
917
+ mm_processor_kwargs: dict[str, Any] | None = None,
918
+ ) -> ProcessorInputs:
919
+ (engine_prompt,) = self._preprocess_chat(
920
+ [conversation],
921
+ chat_template=chat_template,
922
+ chat_template_content_format=chat_template_content_format,
923
+ chat_template_kwargs=chat_template_kwargs,
924
+ add_generation_prompt=add_generation_prompt,
925
+ continue_final_message=continue_final_message,
926
+ tools=tools,
927
+ tokenization_kwargs=tokenization_kwargs,
928
+ mm_processor_kwargs=mm_processor_kwargs,
929
+ )
930
+
931
+ return engine_prompt
932
+
933
def chat(
934
self,
935
messages: list[ChatCompletionMessageParam]
936
| Sequence[list[ChatCompletionMessageParam]],
937
sampling_params: SamplingParams | Sequence[SamplingParams] | None = None,
938
use_tqdm: bool | Callable[..., tqdm] = True,
962
- lora_request: LoRARequest | None = None,
939
+ lora_request: Sequence[LoRARequest] | LoRARequest | None = None,
940
chat_template: str | None = None,
941
chat_template_content_format: ChatTemplateContentFormatOption = "auto",
942
add_generation_prompt: bool = True,
@@ -1805,47 +1782,41 @@ class LLM:
1782
| Sequence[SamplingParams | PoolingParams],
1783
*,
1784
use_tqdm: bool | Callable[..., tqdm] = True,
1808
- lora_request: list[LoRARequest] | LoRARequest | None = None,
1785
+ lora_request: Sequence[LoRARequest] | LoRARequest | None = None,
1786
priority: list[int] | None = None,
1787
tokenization_kwargs: dict[str, Any] | None = None,
1788
):
1789
seq_prompts = prompt_to_seq(prompts)
1790
seq_params = self._params_to_seq(params, len(seq_prompts))
1814
-
1815
- if any(param.truncate_prompt_tokens is not None for param in seq_params):
1816
- # TODO: Remove this after deprecating `param.truncate_prompt_tokens`
1817
- # Then, move the code from the `else` block to the top and let
1818
- # `self._preprocess_cmpl` handle prompt normalization
1819
- engine_prompts: Sequence[DictPrompt | TokPrompt] = [
1820
- engine_prompt
1821
- for prompt, param in zip(seq_prompts, seq_params)
1822
- for engine_prompt in self._preprocess_cmpl(
1823
- [prompt],
1824
- tokenization_kwargs=merge_kwargs(
1825
- tokenization_kwargs,
1826
- dict(truncate_prompt_tokens=param.truncate_prompt_tokens),
1791
+ seq_lora_requests = self._lora_request_to_seq(lora_request, len(seq_prompts))
1792
+ seq_tok_kwargs = [
1793
+ merge_kwargs(
1794
+ tokenization_kwargs,
1795
+ dict(truncate_prompt_tokens=param.truncate_prompt_tokens),
1796
+ )
1797
+ for param in seq_params
1798
+ ]
1799
+ seq_priority = self._priority_to_seq(priority, len(prompts))
1800
+
1801
+ return self._render_and_run_requests(
1802
+ prompts=(
1803
+ self._preprocess_cmpl_one(prompt, tok_kwargs)
1804
+ for prompt, tok_kwargs in zip(
1805
+ maybe_tqdm(
1806
+ seq_prompts,
1807
+ use_tqdm=use_tqdm,
1808
+ desc="Rendering prompts",
1809
),
1810
+ seq_tok_kwargs,
1811
)
1829
- ]
1830
- else:
1831
- engine_prompts = self._preprocess_cmpl(
1832
- seq_prompts,
1833
- tokenization_kwargs=tokenization_kwargs,
1834
- )
1835
-
1836
- self._validate_and_add_requests(
1837
- prompts=engine_prompts,
1812
+ ),
1813
params=seq_params,
1814
use_tqdm=use_tqdm,
1840
- lora_request=self._get_modality_specific_lora_reqs(
1841
- engine_prompts, lora_request
1842
- ),
1815
+ lora_requests=seq_lora_requests,
1816
tokenization_kwargs=tokenization_kwargs,
1844
- priority=priority,
1817
+ priorities=seq_priority,
1818
)
1819
1847
- return self._run_engine(use_tqdm=use_tqdm)
1848
-
1820
def _run_chat(
1821
self,
1822
messages: list[ChatCompletionMessageParam]
@@ -1855,7 +1826,7 @@ class LLM:
1826
| Sequence[SamplingParams | PoolingParams],
1827
*,
1828
use_tqdm: bool | Callable[..., tqdm] = True,
1858
- lora_request: LoRARequest | None = None,
1829
+ lora_request: Sequence[LoRARequest] | LoRARequest | None = None,
1830
chat_template: str | None = None,
1831
chat_template_content_format: ChatTemplateContentFormatOption = "auto",
1832
add_generation_prompt: bool = True,
@@ -1865,68 +1836,94 @@ class LLM:
1836
tokenization_kwargs: dict[str, Any] | None = None,
1837
mm_processor_kwargs: dict[str, Any] | None = None,
1838
):
1868
- engine_prompts = self._preprocess_chat(
1869
- conversation_to_seq(messages),
1870
- chat_template=chat_template,
1871
- chat_template_content_format=chat_template_content_format,
1872
- chat_template_kwargs=chat_template_kwargs,
1873
- add_generation_prompt=add_generation_prompt,
1874
- continue_final_message=continue_final_message,
1875
- tools=tools,
1839
+ seq_convs = conversation_to_seq(messages)
1840
+ seq_params = self._params_to_seq(params, len(seq_convs))
1841
+ seq_lora_requests = self._lora_request_to_seq(lora_request, len(seq_convs))
1842
+ seq_tok_kwargs = [
1843
+ merge_kwargs(
1844
+ tokenization_kwargs,
1845
+ dict(truncate_prompt_tokens=param.truncate_prompt_tokens),
1846
+ )
1847
+ for param in seq_params
1848
+ ]
1849
+
1850
+ return self._render_and_run_requests(
1851
+ prompts=(
1852
+ self._preprocess_chat_one(
1853
+ conversation,
1854
+ chat_template=chat_template,
1855
+ chat_template_content_format=chat_template_content_format,
1856
+ chat_template_kwargs=chat_template_kwargs,
1857
+ add_generation_prompt=add_generation_prompt,
1858
+ continue_final_message=continue_final_message,
1859
+ tools=tools,
1860
+ tokenization_kwargs=tok_kwargs,
1861
+ mm_processor_kwargs=mm_processor_kwargs,
1862
+ )
1863
+ for conversation, tok_kwargs in zip(
1864
+ maybe_tqdm(
1865
+ seq_convs,
1866
+ use_tqdm=use_tqdm,
1867
+ desc="Rendering conversations",
1868
+ ),
1869
+ seq_tok_kwargs,
1870
+ )
1871
+ ),
1872
+ params=seq_params,
1873
+ lora_requests=seq_lora_requests,
1874
+ use_tqdm=use_tqdm,
1875
tokenization_kwargs=tokenization_kwargs,
1877
- mm_processor_kwargs=mm_processor_kwargs,
1876
)
1877
1880
- self._validate_and_add_requests(
1881
- prompts=engine_prompts,
1878
+ def _render_and_run_requests(
1879
+ self,
1880
+ prompts: Iterable[ProcessorInputs],
1881
+ params: Sequence[SamplingParams | PoolingParams],
1882
+ *,
1883
+ lora_requests: Sequence[LoRARequest | None] | None = None,
1884
+ tokenization_kwargs: dict[str, Any] | None = None,
1885
+ priorities: Sequence[int] | None = None,
1886
+ use_tqdm: bool | Callable[..., tqdm] = True,
1887
+ ):
1888
+ if isinstance(prompts, (list, tuple)):
1889
+ logger.warning_once(
1890
+ "Rendering all prompts before adding them to the engine "
1891
+ "is less efficient than performing both on the same prompt "
1892
+ "before processing the next prompt. You should instead pass "
1893
+ "a generator that renders one prompt per iteration, as that allows "
1894
+ "engine execution to begin for the first prompt while processing "
1895
+ "the next prompt."
1896
+ )
1897
+
1898
+ self._render_and_add_requests(
1899
+ prompts=prompts,
1900
params=params,
1883
- use_tqdm=use_tqdm,
1884
- lora_request=self._get_modality_specific_lora_reqs(
1885
- engine_prompts, lora_request
1886
- ),
1901
+ lora_requests=lora_requests,
1902
tokenization_kwargs=tokenization_kwargs,
1903
+ priorities=priorities,
1904
)
1905
1906
return self._run_engine(use_tqdm=use_tqdm)
1907
1892
- def _validate_and_add_requests(
1908
+ def _render_and_add_requests(
1909
self,
1894
- prompts: Sequence[DictPrompt | TokPrompt],
1895
- params: SamplingParams
1896
- | PoolingParams
1897
- | Sequence[SamplingParams | PoolingParams],
1910
+ prompts: Iterable[ProcessorInputs],
1911
+ params: Sequence[SamplingParams | PoolingParams],
1912
*,
1899
- use_tqdm: bool | Callable[..., tqdm] = True,
1900
- lora_request: Sequence[LoRARequest | None] | LoRARequest | None,
1913
+ lora_requests: Sequence[LoRARequest | None] | None = None,
1914
tokenization_kwargs: dict[str, Any] | None = None,
1902
- priority: list[int] | None = None,
1915
+ priorities: Sequence[int] | None = None,
1916
) -> list[str]:
1904
- num_requests = len(prompts)
1905
- seq_params = self._params_to_seq(params, num_requests)
1906
- seq_lora_requests = self._lora_request_to_seq(lora_request, num_requests)
1907
- seq_priority = self._priority_to_seq(priority, num_requests)
1908
-
1909
- for sp in seq_params:
1910
- if isinstance(sp, SamplingParams):
1911
- # We only care about the final output
1912
- sp.output_kind = RequestOutputKind.FINAL_ONLY
1913
-
1914
- # Add requests to the engine.
1915
- it = prompts
1916
- if use_tqdm:
1917
- tqdm_func = use_tqdm if callable(use_tqdm) else tqdm
1918
- it = tqdm_func(it, desc="Adding requests")
1919
-
1917
added_request_ids: list[str] = []
1918
1919
try:
1923
- for i, prompt in enumerate(it):
1920
+ for i, prompt in enumerate(prompts):
1921
request_id = self._add_request(
1922
prompt,
1926
- seq_params[i],
1927
- lora_request=seq_lora_requests[i],
1923
+ params[i],
1924
+ lora_request=None if lora_requests is None else lora_requests[i],
1925
tokenization_kwargs=tokenization_kwargs,
1929
- priority=seq_priority[i],
1926
+ priority=0 if priorities is None else priorities[i],
1927
)
1928
added_request_ids.append(request_id)
1929
except Exception as e:
@@ -1938,13 +1935,16 @@ class LLM:
1935
1936
def _add_request(
1937
self,
1941
- prompt: PromptType | DictPrompt | TokPrompt,
1938
+ prompt: ProcessorInputs,
1939
params: SamplingParams | PoolingParams,
1940
lora_request: LoRARequest | None = None,
1941
tokenization_kwargs: dict[str, Any] | None = None,
1942
priority: int = 0,
1943
) -> str:
1947
- prompt_text, _, _ = extract_prompt_components(self.model_config, prompt)
1944
+ if isinstance(params, SamplingParams):
1945
+ # We only care about the final output
1946
+ params.output_kind = RequestOutputKind.FINAL_ONLY
1947
+
1948
request_id = str(next(self.request_counter))
1949
1950
if params.truncate_prompt_tokens is not None:
@@ -1962,32 +1962,14 @@ class LLM:
1962
dict(truncate_prompt_tokens=params.truncate_prompt_tokens),
1963
)
1964
1965
- renderer = self.renderer
1966
- tok_params = renderer.default_cmpl_tok_params.with_kwargs(
1967
- **(tokenization_kwargs or {})
1968
- )
1969
-
1970
- tokenization_kwargs = tok_params.get_encode_kwargs()
1971
- engine_request = self.input_processor.process_inputs(
1965
+ return self.llm_engine.add_request(
1966
request_id,
1967
prompt,
1968
params,
1969
lora_request=lora_request,
1970
tokenization_kwargs=tokenization_kwargs,
1971
priority=priority,
1978
- supported_tasks=self.supported_tasks,
1979
- )
1980
-
1981
- self.llm_engine.add_request(
1982
- request_id,
1983
- engine_request,
1984
- params,
1985
- lora_request=lora_request,
1986
- tokenization_kwargs=tokenization_kwargs,
1987
- priority=priority,
1988
- prompt_text=prompt_text,
1972
)
1990
- return engine_request.request_id
1973
1974
def _run_engine(
1975
self,
vllm/entrypoints/openai/chat_completion/serving.py
+12
-25
@@ -67,13 +67,12 @@ from vllm.entrypoints.openai.parser.harmony_utils import (
67
)
68
from vllm.entrypoints.openai.utils import maybe_filter_parallel_tool_calls
69
from vllm.entrypoints.utils import get_max_tokens, should_include_usage
70
-from vllm.inputs.data import TokensPrompt
70
+from vllm.inputs.data import ProcessorInputs, TokensPrompt
71
from vllm.logger import init_logger
72
from vllm.logprobs import Logprob
73
from vllm.outputs import CompletionOutput, RequestOutput
74
from vllm.parser import ParserManager
75
from vllm.reasoning import ReasoningParser
76
-from vllm.renderers.inputs import TokPrompt
76
from vllm.sampling_params import BeamSearchParams, SamplingParams
77
from vllm.tokenizers import TokenizerLike
78
from vllm.tokenizers.mistral import (
@@ -221,7 +220,7 @@ class OpenAIServingChat(OpenAIServing):
220
async def render_chat_request(
221
self,
222
request: ChatCompletionRequest,
224
- ) -> tuple[list[ConversationMessage], list[TokPrompt]] | ErrorResponse:
223
+ ) -> tuple[list[ConversationMessage], list[ProcessorInputs]] | ErrorResponse:
224
"""
225
render chat request by validating and preprocessing inputs.
226
@@ -380,7 +379,9 @@ class OpenAIServingChat(OpenAIServing):
379
generators: list[AsyncGenerator[RequestOutput, None]] = []
380
try:
381
for i, engine_prompt in enumerate(engine_prompts):
383
- prompt_text = self._extract_prompt_text(engine_prompt)
382
+ prompt_token_ids = self._extract_prompt_components(
383
+ engine_prompt
384
+ ).token_ids
385
386
# If we are creating sub requests for multiple prompts, ensure that they
387
# have unique request ids.
@@ -431,35 +432,21 @@ class OpenAIServingChat(OpenAIServing):
432
trace_headers=trace_headers,
433
)
434
else:
434
- tok_params = request.build_tok_params(self.model_config)
435
- tokenization_kwargs = tok_params.get_encode_kwargs()
436
-
437
- engine_request = self.input_processor.process_inputs(
438
- sub_request_id,
439
- engine_prompt,
440
- sampling_params,
441
- lora_request=lora_request,
442
- tokenization_kwargs=tokenization_kwargs,
443
- trace_headers=trace_headers,
444
- priority=request.priority,
445
- data_parallel_rank=data_parallel_rank,
435
+ reasoning_ended = (
436
+ reasoning_parser.is_reasoning_end(prompt_token_ids or [])
437
+ if reasoning_parser
438
+ else None
439
)
447
- reasoning_ended = None
448
- if reasoning_parser:
449
- reasoning_ended = reasoning_parser.is_reasoning_end(
450
- engine_request.prompt_token_ids or [] # type: ignore[attr-defined]
451
- )
452
- engine_request.reasoning_ended = reasoning_ended
440
+
441
generator = self.engine_client.generate(
454
- engine_request,
442
+ engine_prompt,
443
sampling_params,
444
sub_request_id,
445
lora_request=lora_request,
446
trace_headers=trace_headers,
447
priority=request.priority,
460
- prompt_text=prompt_text,
461
- tokenization_kwargs=tokenization_kwargs,
448
data_parallel_rank=data_parallel_rank,
449
+ reasoning_ended=reasoning_ended,
450
)
451
452
generators.append(generator)
vllm/entrypoints/openai/completion/serving.py
+4
-22
@@ -34,10 +34,10 @@ from vllm.entrypoints.openai.engine.serving import (
34
from vllm.entrypoints.openai.models.serving import OpenAIServingModels
35
from vllm.entrypoints.utils import get_max_tokens, should_include_usage
36
from vllm.exceptions import VLLMValidationError
37
+from vllm.inputs.data import ProcessorInputs
38
from vllm.logger import init_logger
39
from vllm.logprobs import Logprob
40
from vllm.outputs import RequestOutput
40
-from vllm.renderers.inputs import TokPrompt
41
from vllm.sampling_params import BeamSearchParams, SamplingParams
42
from vllm.tokenizers import TokenizerLike
43
from vllm.utils.async_utils import merge_async_iterators
@@ -80,7 +80,7 @@ class OpenAIServingCompletion(OpenAIServing):
80
async def render_completion_request(
81
self,
82
request: CompletionRequest,
83
- ) -> list[TokPrompt] | ErrorResponse:
83
+ ) -> list[ProcessorInputs] | ErrorResponse:
84
"""
85
render completion request by validating and preprocessing inputs.
86
@@ -163,8 +163,6 @@ class OpenAIServingCompletion(OpenAIServing):
163
generators: list[AsyncGenerator[RequestOutput, None]] = []
164
try:
165
for i, engine_prompt in enumerate(engine_prompts):
166
- prompt_text = self._extract_prompt_text(engine_prompt)
167
-
166
max_tokens = get_max_tokens(
167
max_model_len,
168
request.max_tokens,
@@ -208,29 +206,13 @@ class OpenAIServingCompletion(OpenAIServing):
206
trace_headers=trace_headers,
207
)
208
else:
211
- tok_params = request.build_tok_params(self.model_config)
212
- tokenization_kwargs = tok_params.get_encode_kwargs()
213
-
214
- engine_request = self.input_processor.process_inputs(
215
- request_id_item,
216
- engine_prompt,
217
- sampling_params,
218
- lora_request=lora_request,
219
- tokenization_kwargs=tokenization_kwargs,
220
- trace_headers=trace_headers,
221
- priority=request.priority,
222
- data_parallel_rank=data_parallel_rank,
223
- )
224
-
209
generator = self.engine_client.generate(
226
- engine_request,
210
+ engine_prompt,
211
sampling_params,
212
request_id_item,
213
lora_request=lora_request,
214
trace_headers=trace_headers,
215
priority=request.priority,
232
- prompt_text=prompt_text,
233
- tokenization_kwargs=tokenization_kwargs,
216
data_parallel_rank=data_parallel_rank,
217
)
218
@@ -312,7 +294,7 @@ class OpenAIServingCompletion(OpenAIServing):
294
async def completion_stream_generator(
295
self,
296
request: CompletionRequest,
315
- engine_prompts: list[TokPrompt],
297
+ engine_prompts: list[ProcessorInputs],
298
result_generator: AsyncIterator[tuple[int, RequestOutput]],
299
request_id: str,
300
created_time: int,
vllm/entrypoints/openai/engine/serving.py
+41
-87
@@ -96,15 +96,19 @@ from vllm.entrypoints.serve.tokenize.protocol import (
96
)
97
from vllm.entrypoints.utils import get_max_tokens, sanitize_message
98
from vllm.exceptions import VLLMValidationError
99
-from vllm.inputs.data import PromptType, SingletonPrompt, TokensPrompt
99
+from vllm.inputs.data import (
100
+ ProcessorInputs,
101
+ PromptType,
102
+ SingletonPrompt,
103
+ TokensPrompt,
104
+ token_inputs,
105
+)
106
from vllm.logger import init_logger
107
from vllm.logprobs import Logprob, PromptLogprobs
108
from vllm.lora.request import LoRARequest
103
-from vllm.multimodal import MultiModalDataDict
109
from vllm.outputs import CompletionOutput, PoolingRequestOutput, RequestOutput
110
from vllm.pooling_params import PoolingParams
111
from vllm.renderers import ChatParams, TokenizeParams, merge_kwargs
107
-from vllm.renderers.inputs import TokPrompt
112
from vllm.renderers.inputs.preprocess import (
113
extract_prompt_components,
114
extract_prompt_len,
@@ -206,7 +210,7 @@ class ServeContext(Generic[RequestT]):
210
request_id: str
211
created_time: int = field(default_factory=lambda: int(time.time()))
212
lora_request: LoRARequest | None = None
209
- engine_prompts: list[TokPrompt] | None = None
213
+ engine_prompts: list[ProcessorInputs] | None = None
214
215
result_generator: AsyncGenerator[tuple[int, PoolingRequestOutput], None] | None = (
216
None
@@ -249,7 +253,7 @@ class OpenAIServing:
253
254
async def beam_search(
255
self,
252
- prompt: TokPrompt,
256
+ prompt: ProcessorInputs,
257
request_id: str,
258
params: BeamSearchParams,
259
lora_request: LoRARequest | None = None,
@@ -262,86 +266,53 @@ class OpenAIServing:
266
length_penalty = params.length_penalty
267
include_stop_str_in_output = params.include_stop_str_in_output
268
265
- input_processor = self.input_processor
266
- tokenizer = input_processor.tokenizer
267
- if tokenizer is None:
268
- raise VLLMValidationError(
269
- "You cannot use beam search when `skip_tokenizer_init=True`",
270
- parameter="skip_tokenizer_init",
271
- value=True,
272
- )
273
-
274
- eos_token_id: int = tokenizer.eos_token_id # type: ignore
275
-
276
- if isinstance(prompt, dict) and "encoder_prompt" in prompt:
277
- raise NotImplementedError("Encoder-decoder prompt not supported")
278
-
279
- prompt_text: str | None = prompt.get("prompt") # type: ignore
280
- prompt_token_ids: list[int] = prompt.get("prompt_token_ids", []) # type: ignore
281
- multi_modal_data: MultiModalDataDict | None = prompt.get("multi_modal_data") # type: ignore
282
-
283
- mm_processor_kwargs: dict[str, Any] | None = None
269
+ tokenizer = self.renderer.get_tokenizer()
270
+ eos_token_id = tokenizer.eos_token_id
271
+ sort_beams_key = create_sort_beams_key_function(eos_token_id, length_penalty)
272
285
- # This is a workaround to fix multimodal beam search; this is a
286
- # bandaid fix for 2 small problems:
287
- # 1. Multi_modal_data on the processed_inputs currently resolves to
288
- # `None`.
289
- # 2. preprocessing above expands the multimodal placeholders. However,
290
- # this happens again in generation, so the double expansion causes
291
- # a mismatch.
292
- # TODO - would be ideal to handle this more gracefully.
273
+ if prompt["type"] == "embeds":
274
+ raise NotImplementedError("Embedding prompt not supported for beam search")
275
+ if prompt["type"] == "enc_dec":
276
+ raise NotImplementedError(
277
+ "Encoder-decoder prompt not supported for beam search"
278
+ )
279
280
+ prompt_text = prompt.get("prompt")
281
+ prompt_token_ids = prompt["prompt_token_ids"]
282
tokenized_length = len(prompt_token_ids)
283
296
- sort_beams_key = create_sort_beams_key_function(eos_token_id, length_penalty)
297
-
284
logprobs_num = 2 * beam_width
299
- beam_search_params = SamplingParams(
285
+ sampling_params = SamplingParams(
286
logprobs=logprobs_num,
287
max_tokens=1,
288
temperature=temperature,
289
)
290
all_beams = [
291
BeamSearchSequence(
292
+ orig_prompt=prompt,
293
tokens=prompt_token_ids,
294
cum_logprob=0,
295
logprobs=[],
309
- multi_modal_data=multi_modal_data,
310
- mm_processor_kwargs=mm_processor_kwargs,
296
lora_request=lora_request,
297
)
298
]
299
completed = []
300
301
for _ in range(max_tokens):
317
- prompts_batch, lora_req_batch = zip(
318
- *[
319
- (
320
- TokensPrompt(
321
- prompt_token_ids=beam.tokens,
322
- multi_modal_data=beam.multi_modal_data,
323
- mm_processor_kwargs=beam.mm_processor_kwargs,
324
- ),
325
- beam.lora_request,
326
- )
327
- for beam in all_beams
328
- ]
329
- )
330
-
302
tasks = []
303
request_id_batch = f"{request_id}-{random_uuid()}"
304
334
- for i, (individual_prompt, lora_req) in enumerate(
335
- zip(prompts_batch, lora_req_batch)
336
- ):
305
+ for i, beam in enumerate(all_beams):
306
+ prompt_item = beam.get_prompt()
307
+ lora_request_item = beam.lora_request
308
request_id_item = f"{request_id_batch}-beam-{i}"
309
task = asyncio.create_task(
310
collect_from_async_generator(
311
self.engine_client.generate(
341
- individual_prompt,
342
- beam_search_params,
312
+ prompt_item,
313
+ sampling_params,
314
request_id_item,
344
- lora_request=lora_req,
315
+ lora_request=lora_request_item,
316
trace_headers=trace_headers,
317
)
318
)
@@ -406,6 +377,7 @@ class OpenAIServing:
377
logprobs_entry = result.outputs[0].logprobs[0]
378
completed.append(
379
BeamSearchSequence(
380
+ orig_prompt=prompt,
381
tokens=current_beam.tokens + [eos_token_id]
382
if include_stop_str_in_output
383
else current_beam.tokens,
@@ -433,12 +405,11 @@ class OpenAIServing:
405
logprobs_entry = result.outputs[0].logprobs[0]
406
new_beams.append(
407
BeamSearchSequence(
408
+ orig_prompt=prompt,
409
tokens=current_beam.tokens + [token_id],
410
logprobs=current_beam.logprobs + [logprobs_entry],
411
lora_request=current_beam.lora_request,
412
cum_logprob=float(all_beams_logprob[idx]),
440
- multi_modal_data=current_beam.multi_modal_data,
441
- mm_processor_kwargs=current_beam.mm_processor_kwargs,
413
)
414
)
415
@@ -958,7 +929,7 @@ class OpenAIServing:
929
request: RendererRequest,
930
prompt_input: str | list[str] | list[int] | list[list[int]] | None,
931
prompt_embeds: bytes | list[bytes] | None,
961
- ) -> list[TokPrompt]:
932
+ ) -> list[ProcessorInputs]:
933
prompts = list[SingletonPrompt | bytes]()
934
if prompt_embeds is not None: # embeds take higher priority
935
prompts.extend(prompt_to_seq(prompt_embeds))
@@ -971,7 +942,7 @@ class OpenAIServing:
942
self,
943
request: RendererRequest,
944
prompts: Sequence[PromptType | bytes],
974
- ) -> list[TokPrompt]:
945
+ ) -> list[ProcessorInputs]:
946
renderer = self.renderer
947
model_config = self.model_config
948
@@ -1004,7 +975,7 @@ class OpenAIServing:
975
default_template_kwargs: dict[str, Any] | None,
976
tool_dicts: list[dict[str, Any]] | None = None,
977
tool_parser: Callable[[TokenizerLike], ToolParser] | None = None,
1007
- ) -> tuple[list[ConversationMessage], list[TokPrompt]]:
978
+ ) -> tuple[list[ConversationMessage], list[ProcessorInputs]]:
979
from vllm.tokenizers.mistral import MistralTokenizer
980
981
renderer = self.renderer
@@ -1052,13 +1023,13 @@ class OpenAIServing:
1023
1024
return conversation, [engine_prompt]
1025
1055
- def _extract_prompt_components(self, prompt: object):
1026
+ def _extract_prompt_components(self, prompt: PromptType | ProcessorInputs):
1027
return extract_prompt_components(self.model_config, prompt)
1028
1058
- def _extract_prompt_text(self, prompt: object):
1029
+ def _extract_prompt_text(self, prompt: ProcessorInputs):
1030
return self._extract_prompt_components(prompt).text
1031
1061
- def _extract_prompt_len(self, prompt: object):
1032
+ def _extract_prompt_len(self, prompt: ProcessorInputs):
1033
return extract_prompt_len(self.model_config, prompt)
1034
1035
async def _render_next_turn(
@@ -1088,16 +1059,14 @@ class OpenAIServing:
1059
async def _generate_with_builtin_tools(
1060
self,
1061
request_id: str,
1091
- engine_prompt: TokPrompt,
1062
+ engine_prompt: ProcessorInputs,
1063
sampling_params: SamplingParams,
1093
- tok_params: TokenizeParams,
1064
context: ConversationContext,
1065
lora_request: LoRARequest | None = None,
1066
priority: int = 0,
1067
trace_headers: Mapping[str, str] | None = None,
1068
):
1069
max_model_len = self.model_config.max_model_len
1100
- prompt_text = self._extract_prompt_text(engine_prompt)
1070
1071
orig_priority = priority
1072
sub_request = 0
@@ -1112,26 +1081,13 @@ class OpenAIServing:
1081
lora_request=lora_request,
1082
)
1083
1115
- tokenization_kwargs = tok_params.get_encode_kwargs()
1116
- engine_request = self.input_processor.process_inputs(
1117
- sub_request_id,
1118
- engine_prompt,
1119
- sampling_params,
1120
- lora_request=lora_request,
1121
- tokenization_kwargs=tokenization_kwargs,
1122
- trace_headers=trace_headers,
1123
- priority=priority,
1124
- )
1125
-
1084
generator = self.engine_client.generate(
1127
- engine_request,
1085
+ engine_prompt,
1086
sampling_params,
1087
sub_request_id,
1088
lora_request=lora_request,
1089
trace_headers=trace_headers,
1090
priority=priority,
1133
- prompt_text=prompt_text,
1134
- tokenization_kwargs=tokenization_kwargs,
1091
)
1092
1093
async for res in generator:
@@ -1154,11 +1110,11 @@ class OpenAIServing:
1110
# Render the next prompt token ids and update sampling_params.
1111
if isinstance(context, (HarmonyContext, StreamingHarmonyContext)):
1112
token_ids = context.render_for_completion()
1157
- engine_prompt = TokensPrompt(prompt_token_ids=token_ids)
1113
+ engine_prompt = token_inputs(token_ids)
1114
1115
sampling_params.max_tokens = max_model_len - len(token_ids)
1116
elif isinstance(context, ParsableContext):
1161
- engine_prompts = await self._render_next_turn(
1117
+ (engine_prompt,) = await self._render_next_turn(
1118
context.request,
1119
context.parser.response_messages,
1120
context.tool_dicts,
@@ -1166,8 +1122,6 @@ class OpenAIServing:
1122
context.chat_template,
1123
context.chat_template_content_format,
1124
)
1169
- engine_prompt = engine_prompts[0]
1170
- prompt_text = self._extract_prompt_text(engine_prompt)
1125
1126
sampling_params.max_tokens = get_max_tokens(
1127
max_model_len,
@@ -1184,7 +1138,7 @@ class OpenAIServing:
1138
def _log_inputs(
1139
self,
1140
request_id: str,
1187
- inputs: PromptType | TokPrompt,
1141
+ inputs: PromptType | ProcessorInputs,
1142
params: SamplingParams | PoolingParams | BeamSearchParams | None,
1143
lora_request: LoRARequest | None,
1144
) -> None:
vllm/entrypoints/openai/realtime/serving.py
+8
-2
@@ -15,6 +15,7 @@ from vllm.entrypoints.openai.models.serving import OpenAIServingModels
15
from vllm.inputs.data import PromptType
16
from vllm.logger import init_logger
17
from vllm.model_executor.models.interfaces import SupportsRealtime
18
+from vllm.renderers.inputs.preprocess import parse_model_prompt
19
20
logger = init_logger(__name__)
21
@@ -70,15 +71,20 @@ class OpenAIServingRealtime(OpenAIServing):
71
Yields:
72
StreamingInput objects containing audio prompts for the engine
73
"""
74
+ model_config = self.model_config
75
+ renderer = self.renderer
76
77
# mypy is being stupid
78
# TODO(Patrick) - fix this
79
stream_input_iter = cast(
80
AsyncGenerator[PromptType, None],
81
self.model_cls.buffer_realtime_audio(
79
- audio_stream, input_stream, self.model_config
82
+ audio_stream, input_stream, model_config
83
),
84
)
85
86
async for prompt in stream_input_iter:
84
- yield StreamingInput(prompt=prompt)
87
+ parsed_prompt = parse_model_prompt(model_config, prompt)
88
+ (engine_prompt,) = await renderer.render_cmpl_async([parsed_prompt])
89
+
90
+ yield StreamingInput(prompt=engine_prompt)
vllm/entrypoints/openai/responses/context.py
+2
-2
@@ -9,7 +9,7 @@ from abc import ABC, abstractmethod
9
from collections.abc import Callable
10
from contextlib import AsyncExitStack
11
from dataclasses import replace
12
-from typing import TYPE_CHECKING, Union
12
+from typing import TYPE_CHECKING, Final, Union
13
14
from openai.types.responses.response_function_tool_call_output_item import (
15
ResponseFunctionToolCallOutputItem,
@@ -304,7 +304,7 @@ class ParsableContext(ConversationContext):
304
305
self.tool_dicts = construct_tool_dicts(request.tools, request.tool_choice)
306
self.chat_template = chat_template
307
- self.chat_template_content_format = chat_template_content_format
307
+ self.chat_template_content_format: Final = chat_template_content_format
308
309
self.input_messages: list[ResponseRawMessageAndToken] = []
310
self.output_messages: list[ResponseRawMessageAndToken] = []
vllm/entrypoints/openai/responses/serving.py
+3
-6
@@ -116,13 +116,12 @@ from vllm.entrypoints.openai.responses.utils import (
116
)
117
from vllm.entrypoints.utils import get_max_tokens
118
from vllm.exceptions import VLLMValidationError
119
-from vllm.inputs.data import TokensPrompt
119
+from vllm.inputs.data import ProcessorInputs, token_inputs
120
from vllm.logger import init_logger
121
from vllm.logprobs import Logprob as SampleLogprob
122
from vllm.logprobs import SampleLogprobs
123
from vllm.outputs import CompletionOutput
124
from vllm.parser import ParserManager
125
-from vllm.renderers.inputs import TokPrompt
125
from vllm.sampling_params import SamplingParams, StructuredOutputsParams
126
from vllm.tokenizers import TokenizerLike
127
from vllm.utils import random_uuid
@@ -298,7 +297,7 @@ class OpenAIServingResponses(OpenAIServing):
297
298
def _validate_generator_input(
299
self,
301
- engine_prompt: TokPrompt,
300
+ engine_prompt: ProcessorInputs,
301
) -> ErrorResponse | None:
302
"""Add validations to the input to the generator here."""
303
prompt_len = self._extract_prompt_len(engine_prompt)
@@ -458,7 +457,6 @@ class OpenAIServingResponses(OpenAIServing):
457
sampling_params = request.to_sampling_params(
458
default_max_tokens, self.default_sampling_params
459
)
461
- tok_params = request.build_tok_params(self.model_config)
460
461
trace_headers = (
462
None
@@ -512,7 +510,6 @@ class OpenAIServingResponses(OpenAIServing):
510
request_id=request.request_id,
511
engine_prompt=engine_prompt,
512
sampling_params=sampling_params,
515
- tok_params=tok_params,
513
context=context,
514
lora_request=lora_request,
515
priority=request.priority,
@@ -647,7 +644,7 @@ class OpenAIServingResponses(OpenAIServing):
644
645
messages = self._construct_input_messages_with_harmony(request, prev_response)
646
prompt_token_ids = render_for_completion(messages)
650
- engine_prompt = TokensPrompt(prompt_token_ids=prompt_token_ids)
647
+ engine_prompt = token_inputs(prompt_token_ids)
648
649
# Add cache_salt if provided in the request
650
if request.cache_salt is not None:
vllm/entrypoints/openai/speech_to_text/speech_to_text.py
+49
-56
@@ -36,14 +36,15 @@ from vllm.entrypoints.openai.speech_to_text.protocol import (
36
TranslationSegment,
37
TranslationStreamResponse,
38
)
39
+from vllm.entrypoints.utils import get_max_tokens
40
from vllm.exceptions import VLLMValidationError
40
-from vllm.inputs.data import PromptType
41
+from vllm.inputs import ProcessorInputs
42
from vllm.logger import init_logger
43
from vllm.logprobs import FlatLogprobs, Logprob
44
from vllm.model_executor.models import SupportsTranscription, supports_transcription
45
from vllm.outputs import RequestOutput
45
-from vllm.renderers.inputs import EncoderDecoderDictPrompt
46
-from vllm.renderers.inputs.preprocess import parse_enc_dec_prompt
46
+from vllm.renderers.inputs import DictPrompt, EncoderDecoderDictPrompt
47
+from vllm.renderers.inputs.preprocess import parse_enc_dec_prompt, parse_model_prompt
48
from vllm.tokenizers import get_tokenizer
49
from vllm.utils.import_utils import PlaceholderModule
50
@@ -202,8 +203,6 @@ class OpenAISpeechToText(OpenAIServing):
203
return
204
205
try:
205
- from vllm.sampling_params import SamplingParams
206
-
206
warmup_start = time.perf_counter()
207
logger.info("Warming up multimodal input processor...")
208
@@ -221,21 +220,11 @@ class OpenAISpeechToText(OpenAIServing):
220
request_prompt="",
221
to_language=None,
222
)
224
-
225
- # Create minimal sampling params
226
- dummy_params = SamplingParams(
227
- max_tokens=1,
228
- temperature=0.0,
229
- skip_clone=True, # Internal warmup, safe to skip clone
230
- )
223
+ parsed_prompt = parse_model_prompt(self.model_config, dummy_prompt)
224
225
# Process the dummy input through the input processor
226
# This will trigger all the multimodal processing initialization
234
- _ = self.input_processor.process_inputs(
235
- request_id="warmup",
236
- prompt=dummy_prompt,
237
- params=dummy_params,
238
- )
227
+ _ = self.renderer.render_cmpl([parsed_prompt])
228
229
warmup_elapsed = time.perf_counter() - warmup_start
230
logger.info("Input processor warmup completed in %.2fs", warmup_elapsed)
@@ -257,7 +246,7 @@ class OpenAISpeechToText(OpenAIServing):
246
self,
247
request: SpeechToTextRequest,
248
audio_data: bytes,
260
- ) -> tuple[list[PromptType], float]:
249
+ ) -> tuple[list[ProcessorInputs], float]:
250
# Validate request
251
language = self.model_cls.validate_language(request.language)
252
# Skip to_language validation to avoid extra logging for Whisper.
@@ -285,7 +274,7 @@ class OpenAISpeechToText(OpenAIServing):
274
and duration > self.asr_config.max_audio_clip_s
275
)
276
chunks = [y] if not do_split_audio else self._split_audio(y, int(sr))
288
- prompts = []
277
+ parsed_prompts: list[DictPrompt] = []
278
for chunk in chunks:
279
# The model has control over the construction, as long as it
280
# returns a valid PromptType.
@@ -298,12 +287,19 @@ class OpenAISpeechToText(OpenAIServing):
287
request_prompt=request.prompt,
288
to_language=to_language,
289
)
290
+
291
+ parsed_prompt: DictPrompt
292
if request.response_format == "verbose_json":
302
- prompt = self._preprocess_verbose_prompt(parse_enc_dec_prompt(prompt))
293
+ parsed_prompt = parse_enc_dec_prompt(prompt)
294
+ parsed_prompt = self._preprocess_verbose_prompt(parsed_prompt)
295
+ else:
296
+ parsed_prompt = parse_model_prompt(self.model_config, prompt)
297
+
298
+ parsed_prompts.append(parsed_prompt)
299
304
- prompts.append(prompt)
300
+ engine_prompts = await self.renderer.render_cmpl_async(parsed_prompts)
301
306
- return prompts, duration
302
+ return engine_prompts, duration
303
304
def _preprocess_verbose_prompt(self, prompt: EncoderDecoderDictPrompt):
305
dec_prompt = prompt["decoder_prompt"]
@@ -436,7 +432,7 @@ class OpenAISpeechToText(OpenAIServing):
432
try:
433
lora_request = self._maybe_get_adapters(request)
434
439
- prompts, duration_s = await self._preprocess_speech_to_text(
435
+ engine_prompts, duration_s = await self._preprocess_speech_to_text(
436
request=request,
437
audio_data=audio_data,
438
)
@@ -445,57 +441,54 @@ class OpenAISpeechToText(OpenAIServing):
441
logger.exception("Error in preprocessing prompt inputs")
442
return self.create_error_response(e)
443
444
+ # Schedule the request and get the result generator.
445
+ max_model_len = self.model_config.max_model_len
446
list_result_generator: list[AsyncGenerator[RequestOutput, None]] | None = None
447
try:
448
# Unlike most decoder-only models, whisper generation length is not
449
# constrained by the size of the input audio, which is mapped to a
450
# fixed-size log-mel-spectogram. Still, allow for fewer tokens to be
451
# generated by respecting the extra completion tokens arg.
454
- if request.max_completion_tokens is None:
455
- default_max_tokens = self.model_config.max_model_len
456
- else:
457
- default_max_tokens = min(
458
- self.model_config.max_model_len, request.max_completion_tokens
459
- )
452
+ max_tokens = get_max_tokens(
453
+ max_model_len,
454
+ request.max_completion_tokens,
455
+ 0,
456
+ self.default_sampling_params,
457
+ )
458
+
459
sampling_params = request.to_sampling_params(
461
- default_max_tokens, self.default_sampling_params
460
+ max_tokens,
461
+ self.default_sampling_params,
462
)
463
if request.response_format == "verbose_json":
464
sampling_params.logprobs = 1
465
466
- self._log_inputs(
467
- request_id,
468
- # It will not display special tokens like <|startoftranscript|>
469
- request.prompt,
470
- params=sampling_params,
471
- lora_request=lora_request,
472
- )
473
-
474
- trace_headers = (
475
- None
476
- if raw_request is None
477
- else await self._get_trace_headers(raw_request.headers)
478
- )
479
-
466
list_result_generator = []
481
- for i, prompt in enumerate(prompts):
467
+ for i, engine_prompt in enumerate(engine_prompts):
468
request_id_item = f"{request_id}_{i}"
483
- engine_request = self.input_processor.process_inputs(
469
+
470
+ self._log_inputs(
471
request_id_item,
485
- prompt,
472
+ engine_prompt,
473
+ params=sampling_params,
474
+ lora_request=lora_request,
475
+ )
476
+
477
+ trace_headers = (
478
+ None
479
+ if raw_request is None
480
+ else await self._get_trace_headers(raw_request.headers)
481
+ )
482
+
483
+ generator = self.engine_client.generate(
484
+ engine_prompt,
485
sampling_params,
486
+ request_id_item,
487
lora_request=lora_request,
488
trace_headers=trace_headers,
489
- priority=0,
490
- )
491
- list_result_generator.append(
492
- self.engine_client.generate(
493
- engine_request,
494
- sampling_params,
495
- request_id_item,
496
- lora_request=lora_request,
497
- )
489
)
490
+
491
+ list_result_generator.append(generator)
492
except ValueError as e:
493
return self.create_error_response(e)
494
vllm/entrypoints/pooling/embed/serving.py
+3
-12
@@ -28,11 +28,10 @@ from vllm.entrypoints.pooling.utils import (
28
encode_pooling_output_base64,
29
encode_pooling_output_float,
30
)
31
-from vllm.inputs.data import TokensPrompt
31
+from vllm.inputs.data import ProcessorInputs, TokensPrompt, token_inputs
32
from vllm.logger import init_logger
33
from vllm.outputs import PoolingOutput, PoolingRequestOutput
34
from vllm.pooling_params import PoolingParams
35
-from vllm.renderers.inputs import TokPrompt
35
from vllm.utils.async_utils import merge_async_iterators
36
from vllm.utils.collection_utils import chunk_list
37
from vllm.utils.serial_utils import EmbedDType, Endianness
@@ -256,7 +255,7 @@ class OpenAIServingEmbedding(OpenAIServing):
255
chunk_request_id = f"{ctx.request_id}-prompt-{prompt_idx}-chunk-{chunk_idx}"
256
257
# Create engine prompt for this chunk
259
- chunk_engine_prompt = TokensPrompt(prompt_token_ids=chunk_tokens)
258
+ chunk_engine_prompt = token_inputs(chunk_tokens)
259
260
# Log the chunk
261
self._log_inputs(
@@ -266,16 +265,12 @@ class OpenAIServingEmbedding(OpenAIServing):
265
lora_request=ctx.lora_request,
266
)
267
269
- tok_params = ctx.request.build_tok_params(self.model_config)
270
- tokenization_kwargs = tok_params.get_encode_kwargs()
271
-
268
# Create generator for this chunk and wrap it to return indices
269
original_generator = self.engine_client.encode(
270
chunk_engine_prompt,
271
pooling_params,
272
chunk_request_id,
273
lora_request=ctx.lora_request,
278
- tokenization_kwargs=tokenization_kwargs,
274
trace_headers=trace_headers,
275
priority=ctx.request.priority,
276
)
@@ -362,7 +357,7 @@ class OpenAIServingEmbedding(OpenAIServing):
357
async def _create_single_prompt_generator(
358
self,
359
ctx: EmbeddingServeContext,
365
- engine_prompt: TokPrompt,
360
+ engine_prompt: ProcessorInputs,
361
pooling_params: PoolingParams,
362
trace_headers: Mapping[str, str] | None,
363
prompt_index: int,
@@ -377,16 +372,12 @@ class OpenAIServingEmbedding(OpenAIServing):
372
lora_request=ctx.lora_request,
373
)
374
380
- tok_params = ctx.request.build_tok_params(self.model_config)
381
- tokenization_kwargs = tok_params.get_encode_kwargs()
382
-
375
# Return the original generator without wrapping
376
return self.engine_client.encode(
377
engine_prompt,
378
pooling_params,
379
request_id_item,
380
lora_request=ctx.lora_request,
389
- tokenization_kwargs=tokenization_kwargs,
381
trace_headers=trace_headers,
382
priority=ctx.request.priority,
383
)
vllm/entrypoints/pooling/pooling/serving.py
+2
-7
@@ -33,10 +33,9 @@ from vllm.entrypoints.pooling.utils import (
33
encode_pooling_output_base64,
34
encode_pooling_output_float,
35
)
36
-from vllm.inputs import PromptType
36
+from vllm.inputs import ProcessorInputs
37
from vllm.logger import init_logger
38
from vllm.outputs import PoolingRequestOutput
39
-from vllm.renderers.inputs import TokPrompt
39
from vllm.renderers.inputs.preprocess import prompt_to_seq
40
from vllm.utils.async_utils import merge_async_iterators
41
from vllm.utils.serial_utils import EmbedDType, EncodingFormat, Endianness
@@ -93,7 +92,7 @@ class OpenAIServingPooling(OpenAIServing):
92
"dimensions is currently not supported"
93
)
94
96
- engine_prompts: Sequence[PromptType | TokPrompt]
95
+ engine_prompts: Sequence[ProcessorInputs]
96
if use_io_processor := isinstance(request, IOProcessorRequest):
97
if self.io_processor is None:
98
raise ValueError(
@@ -152,9 +151,6 @@ class OpenAIServingPooling(OpenAIServing):
151
else:
152
pooling_params = request.to_pooling_params() # type: ignore
153
155
- tok_params = request.build_tok_params(self.model_config)
156
- tokenization_kwargs = tok_params.get_encode_kwargs()
157
-
154
for i, engine_prompt in enumerate(engine_prompts):
155
request_id_item = f"{request_id}-{i}"
156
@@ -176,7 +172,6 @@ class OpenAIServingPooling(OpenAIServing):
172
pooling_params,
173
request_id_item,
174
lora_request=lora_request,
179
- tokenization_kwargs=tokenization_kwargs,
175
trace_headers=trace_headers,
176
priority=request.priority,
177
)
vllm/entrypoints/pooling/score/serving.py
+13
-7
@@ -35,7 +35,7 @@ from vllm.entrypoints.pooling.score.utils import (
35
get_score_prompt,
36
validate_score_input,
37
)
38
-from vllm.inputs.data import TokensPrompt
38
+from vllm.inputs.data import ProcessorInputs, TokensPrompt, token_inputs
39
from vllm.logger import init_logger
40
from vllm.lora.request import LoRARequest
41
from vllm.outputs import PoolingRequestOutput, ScoringRequestOutput
@@ -108,12 +108,15 @@ class ServingScores(OpenAIServing):
108
*(encode_async(t, **tokenization_kwargs) for t in input_texts)
109
)
110
111
- engine_prompts: list[TokensPrompt] = []
111
+ engine_prompts: list[ProcessorInputs] = []
112
for tok_result, input_text in zip(tokenized_prompts, input_texts):
113
text_token_prompt = self._validate_input(request, tok_result, input_text)
114
115
engine_prompts.append(
116
- TokensPrompt(prompt_token_ids=text_token_prompt["prompt_token_ids"])
116
+ token_inputs(
117
+ text_token_prompt["prompt_token_ids"],
118
+ prompt=input_text,
119
+ )
120
)
121
122
# Schedule the request and get the result generator.
@@ -125,7 +128,7 @@ class ServingScores(OpenAIServing):
128
129
self._log_inputs(
130
request_id_item,
128
- input_texts[i],
131
+ engine_prompt,
132
params=pooling_params,
133
lora_request=lora_request,
134
)
@@ -207,12 +210,15 @@ class ServingScores(OpenAIServing):
210
*(encode_async(t, **tokenization_kwargs) for t in input_texts)
211
)
212
210
- engine_prompts: list[TokensPrompt] = []
213
+ engine_prompts: list[ProcessorInputs] = []
214
for tok_result, input_text in zip(tokenized_prompts, input_texts):
215
text_token_prompt = self._validate_input(request, tok_result, input_text)
216
217
engine_prompts.append(
215
- TokensPrompt(prompt_token_ids=text_token_prompt["prompt_token_ids"])
218
+ token_inputs(
219
+ text_token_prompt["prompt_token_ids"],
220
+ prompt=input_text,
221
+ )
222
)
223
224
# Schedule the request and get the result generator.
@@ -225,7 +231,7 @@ class ServingScores(OpenAIServing):
231
232
self._log_inputs(
233
request_id_item,
228
- input_texts[i],
234
+ engine_prompt,
235
params=pooling_params,
236
lora_request=lora_request,
237
)
vllm/entrypoints/serve/disagg/serving.py
+2
-17
@@ -29,7 +29,6 @@ from vllm.entrypoints.serve.disagg.protocol import (
29
GenerateResponse,
30
GenerateResponseChoice,
31
)
32
-from vllm.inputs.data import TokensPrompt
32
from vllm.logger import init_logger
33
from vllm.logprobs import Logprob
34
from vllm.outputs import RequestOutput
@@ -116,7 +115,7 @@ class ServingTokens(OpenAIServing):
115
116
self._log_inputs(
117
request_id,
119
- TokensPrompt(prompt_token_ids=request.token_ids),
118
+ engine_prompt,
119
params=sampling_params,
120
lora_request=lora_request,
121
)
@@ -127,27 +126,13 @@ class ServingTokens(OpenAIServing):
126
else await self._get_trace_headers(raw_request.headers)
127
)
128
130
- tok_params = request.build_tok_params(self.model_config)
131
- tokenization_kwargs = tok_params.get_encode_kwargs()
132
-
133
- engine_request = self.input_processor.process_inputs(
134
- request_id,
135
- engine_prompt,
136
- sampling_params,
137
- lora_request=lora_request,
138
- tokenization_kwargs=tokenization_kwargs,
139
- trace_headers=trace_headers,
140
- priority=request.priority,
141
- )
142
-
129
result_generator = self.engine_client.generate(
144
- engine_request,
130
+ engine_prompt,
131
sampling_params,
132
request_id,
133
lora_request=lora_request,
134
trace_headers=trace_headers,
135
priority=request.priority,
150
- tokenization_kwargs=tokenization_kwargs,
136
)
137
138
except ValueError as e:
vllm/entrypoints/serve/tokenize/serving.py
+2
-2
@@ -20,7 +20,7 @@ from vllm.entrypoints.serve.tokenize.protocol import (
20
TokenizeResponse,
21
TokenizerInfoResponse,
22
)
23
-from vllm.inputs import TokensPrompt
23
+from vllm.inputs import TokensPrompt, token_inputs
24
from vllm.logger import init_logger
25
from vllm.tokenizers import TokenizerLike
26
@@ -135,7 +135,7 @@ class OpenAIServingTokenization(OpenAIServing):
135
136
self._log_inputs(
137
request_id,
138
- TokensPrompt(prompt_token_ids=request.tokens),
138
+ token_inputs(request.tokens),
139
params=None,
140
lora_request=lora_request,
141
)
vllm/inputs/data.py
+6
@@ -187,6 +187,9 @@ class _InputOptions(TypedDict):
187
Additional options available to all input types.
188
"""
189
190
+ arrival_time: NotRequired[float]
191
+ """The time when the input was received (before rendering)."""
192
+
193
cache_salt: NotRequired[str]
194
"""Optional cache salt to be used for prefix caching."""
195
@@ -300,6 +303,9 @@ class EncoderDecoderInputs(TypedDict):
303
decoder_prompt: DecoderInputs
304
"""The inputs for the decoder portion."""
305
306
+ arrival_time: NotRequired[float]
307
+ """The time when the input was received (before rendering)."""
308
+
309
310
ProcessorInputs: TypeAlias = DecoderOnlyInputs | EncoderDecoderInputs
311
"""
vllm/inputs/preprocess.py
+15
-85
@@ -19,11 +19,9 @@ from vllm.renderers import BaseRenderer, renderer_from_config
19
from vllm.renderers.inputs import (
20
DecoderDictPrompt,
21
DecoderOnlyDictPrompt,
22
- DictPrompt,
22
EncoderDecoderDictPrompt,
23
EncoderDictPrompt,
24
SingletonDictPrompt,
26
- TokPrompt,
25
)
26
from vllm.renderers.inputs.preprocess import parse_dec_only_prompt, parse_enc_dec_prompt
27
from vllm.tokenizers import TokenizerLike
@@ -41,7 +39,6 @@ from .data import (
39
TextPrompt,
40
TokenInputs,
41
TokensPrompt,
44
- embeds_inputs,
42
token_inputs,
43
)
44
@@ -83,7 +80,7 @@ class InputPreprocessor:
80
**(tokenization_kwargs or {})
81
)
82
86
- tok_prompt = renderer.tokenize_prompt(
83
+ tok_prompt = renderer._tokenize_singleton_prompt(
84
TextPrompt(prompt=prompt),
85
tok_params,
86
)
@@ -103,17 +100,10 @@ class InputPreprocessor:
100
Apply the model's multi-modal processor to a multi-modal prompt,
101
returning the corresponding token IDs and metadata.
102
"""
106
- mm_processor = self.renderer.get_mm_processor()
107
-
108
- if mm_processor_kwargs is None:
109
- mm_processor_kwargs = {}
110
-
111
- mm_items = mm_processor.info.parse_mm_data(mm_data)
112
-
113
- return mm_processor.apply(
103
+ return self.renderer._process_multimodal(
104
prompt,
115
- mm_items,
116
- hf_processor_mm_kwargs=mm_processor_kwargs,
105
+ mm_data,
106
+ mm_processor_kwargs=mm_processor_kwargs,
107
tokenization_kwargs=tokenization_kwargs,
108
mm_uuids=mm_uuids,
109
)
@@ -122,31 +112,7 @@ class InputPreprocessor:
112
self,
113
parsed_content: EmbedsPrompt,
114
) -> EmbedsInputs:
125
- if not self.model_config.enable_prompt_embeds:
126
- raise ValueError(
127
- "You must set `--enable-prompt-embeds` to input `prompt_embeds`."
128
- )
129
-
130
- prompt_embeds = parsed_content["prompt_embeds"]
131
-
132
- # prompt_embeds must be (seq_len, hidden_size), but if the user
133
- # passes in a batch of size 1, i.e. (1, seq_len, hidden_size),
134
- # we can unambiguously process the intent by squeezing the batch
135
- # dimension.
136
- if prompt_embeds.ndim == 3:
137
- prompt_embeds = prompt_embeds.squeeze(dim=0)
138
-
139
- if prompt_embeds.ndim != 2:
140
- raise ValueError("prompt_embeds must be of shape (seq_len, hidden_size).")
141
-
142
- # Tensors must be on CPU for serialization between processes
143
- # in the MsgpackEncoder. Casting to CPU here ensures that there is no
144
- # hidden device transfer in the critical path of generation.
145
- prompt_embeds = prompt_embeds.cpu()
146
-
147
- return embeds_inputs(
148
- prompt_embeds=prompt_embeds, cache_salt=parsed_content.get("cache_salt")
149
- )
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
@@ -157,7 +123,7 @@ class InputPreprocessor:
123
**(tokenization_kwargs or {})
124
)
125
160
- tok_prompt = renderer.tokenize_prompt(
126
+ tok_prompt = renderer._tokenize_singleton_prompt(
127
TokensPrompt(prompt_token_ids=inputs),
128
tok_params,
129
)
@@ -168,8 +134,6 @@ class InputPreprocessor:
134
self,
135
parsed_content: TokensPrompt,
136
tokenization_kwargs: dict[str, Any] | None = None,
171
- *,
172
- mm_uuids: MultiModalUUIDDict | None = None,
137
) -> TokenInputs | MultiModalInputs:
138
prompt_token_ids = self._truncate_inputs(
139
parsed_content["prompt_token_ids"], tokenization_kwargs
@@ -182,11 +146,13 @@ class InputPreprocessor:
146
multi_modal_data,
147
parsed_content.get("mm_processor_kwargs") or {},
148
tokenization_kwargs=tokenization_kwargs,
185
- mm_uuids=mm_uuids,
149
+ mm_uuids=parsed_content.get("multi_modal_uuids"),
150
)
151
else:
152
inputs = token_inputs(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
@@ -196,8 +162,6 @@ class InputPreprocessor:
162
self,
163
parsed_content: TextPrompt,
164
tokenization_kwargs: dict[str, Any] | None = None,
199
- *,
200
- mm_uuids: MultiModalUUIDDict | None = None,
165
) -> TokenInputs | MultiModalInputs:
166
prompt_text = parsed_content["prompt"]
167
@@ -208,7 +172,6 @@ class InputPreprocessor:
172
multi_modal_data,
173
parsed_content.get("mm_processor_kwargs") or {},
174
tokenization_kwargs=tokenization_kwargs,
211
- mm_uuids=mm_uuids,
175
)
176
else:
177
prompt_token_ids = self._tokenize_prompt(
@@ -217,6 +180,8 @@ class InputPreprocessor:
180
)
181
inputs = token_inputs(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
@@ -227,8 +192,6 @@ class InputPreprocessor:
192
self,
193
prompt: EncoderDictPrompt,
194
tokenization_kwargs: dict[str, Any] | None = None,
230
- *,
231
- mm_uuids: MultiModalUUIDDict | None = None,
195
) -> EncoderInputs: ...
196
197
@overload
@@ -236,8 +199,6 @@ class InputPreprocessor:
199
self,
200
prompt: DecoderDictPrompt,
201
tokenization_kwargs: dict[str, Any] | None = None,
239
- *,
240
- mm_uuids: MultiModalUUIDDict | None = None,
202
) -> DecoderInputs: ...
203
204
@overload
@@ -245,16 +206,12 @@ class InputPreprocessor:
206
self,
207
prompt: DecoderOnlyDictPrompt,
208
tokenization_kwargs: dict[str, Any] | None = None,
248
- *,
249
- mm_uuids: MultiModalUUIDDict | None = None,
209
) -> DecoderOnlyInputs: ...
210
211
def _prompt_to_llm_inputs(
212
self,
213
prompt: SingletonDictPrompt,
214
tokenization_kwargs: dict[str, Any] | None = None,
256
- *,
257
- mm_uuids: MultiModalUUIDDict | None = None,
215
) -> SingletonInputs:
216
"""
217
Extract the singleton inputs from a prompt.
@@ -271,16 +228,12 @@ class InputPreprocessor:
228
return self._process_embeds(prompt) # type: ignore[arg-type]
229
230
if "prompt_token_ids" in prompt:
274
- return self._process_tokens(
275
- prompt, # type: ignore[arg-type]
276
- mm_uuids=mm_uuids,
277
- )
231
+ return self._process_tokens(prompt) # type: ignore[arg-type]
232
233
if "prompt" in prompt:
234
return self._process_text(
235
prompt, # type: ignore[arg-type]
236
tokenization_kwargs=tokenization_kwargs,
283
- mm_uuids=mm_uuids,
237
)
238
239
assert_never(prompt) # type: ignore[arg-type]
@@ -289,8 +242,6 @@ class InputPreprocessor:
242
self,
243
prompt: EncoderDecoderDictPrompt,
244
tokenization_kwargs: dict[str, Any] | None = None,
292
- *,
293
- mm_uuids: MultiModalUUIDDict | None = None,
245
) -> EncoderDecoderInputs:
246
"""
247
For encoder/decoder models only:
@@ -314,7 +265,6 @@ class InputPreprocessor:
265
encoder_inputs=self._prompt_to_llm_inputs(
266
encoder_prompt,
267
tokenization_kwargs=tokenization_kwargs,
317
- mm_uuids=mm_uuids,
268
),
269
decoder_inputs=(
270
None
@@ -331,8 +281,6 @@ class InputPreprocessor:
281
self,
282
prompt: DecoderOnlyDictPrompt,
283
tokenization_kwargs: dict[str, Any] | None = None,
334
- *,
335
- mm_uuids: MultiModalUUIDDict | None = None,
284
) -> DecoderOnlyInputs:
285
"""
286
For decoder-only models:
@@ -350,41 +298,23 @@ class InputPreprocessor:
298
return self._prompt_to_llm_inputs(
299
prompt,
300
tokenization_kwargs=tokenization_kwargs,
353
- mm_uuids=mm_uuids,
301
)
302
356
- def _preprocess(
303
+ def preprocess(
304
self,
358
- prompt: PromptType | DictPrompt | TokPrompt,
305
+ prompt: PromptType,
306
tokenization_kwargs: dict[str, Any] | None = None,
360
- *,
361
- mm_uuids: MultiModalUUIDDict | None = None,
307
) -> ProcessorInputs:
308
+ """Preprocess the input prompt."""
309
if self.model_config.is_encoder_decoder:
310
# Encoder-decoder model requires special mapping of
311
# input prompts to encoder & decoder.
312
return self._process_encoder_decoder_prompt(
313
parse_enc_dec_prompt(prompt),
314
tokenization_kwargs,
369
- mm_uuids=mm_uuids,
315
)
316
317
return self._process_decoder_only_prompt(
318
parse_dec_only_prompt(prompt),
319
tokenization_kwargs=tokenization_kwargs,
375
- mm_uuids=mm_uuids,
320
)
377
-
378
- def preprocess(
379
- self,
380
- prompt: PromptType | DictPrompt | TokPrompt,
381
- tokenization_kwargs: dict[str, Any] | None = None,
382
- *,
383
- mm_uuids: MultiModalUUIDDict | None = None,
384
- ) -> ProcessorInputs:
385
- """Preprocess the input prompt."""
386
- res = self._preprocess(prompt, tokenization_kwargs, mm_uuids=mm_uuids)
387
-
388
- self.renderer.update_mm_cache_stats()
389
-
390
- return res
vllm/model_executor/models/funasr.py
+3
-15
@@ -48,7 +48,6 @@ from vllm.multimodal.processing import (
48
BaseProcessingInfo,
49
PromptReplacement,
50
PromptUpdate,
51
- PromptUpdateDetails,
51
)
52
from vllm.transformers_utils.processor import cached_processor_from_config
53
from vllm.transformers_utils.processors.funasr_processor import FunASRFeatureExtractor
@@ -810,13 +809,7 @@ class FunASRMultiModalProcessor(BaseMultiModalProcessor[FunASRProcessingInfo]):
809
out_mm_kwargs: MultiModalKwargsItems,
810
) -> Sequence[PromptUpdate]:
811
processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
813
- tokenizer = self.info.get_tokenizer()
814
- vocab = tokenizer.get_vocab()
815
-
816
- # Use getattr with default to be compatible with transformers<4.48
817
- audio_token = getattr(processor, "audio_token", "<|AUDIO|>")
818
-
819
- audio_token_id = vocab[audio_token]
812
+ audio_token_id = processor.audio_token_id
813
814
out_mm_data = out_mm_kwargs.get_data()
815
@@ -836,17 +829,12 @@ class FunASRMultiModalProcessor(BaseMultiModalProcessor[FunASRProcessingInfo]):
829
assert len(audio_embeds.shape) == 2, "audio_embeds must be a 2D tensor"
830
num_features = audio_embeds.shape[0]
831
839
- audio_tokens = [audio_token_id] * num_features
840
-
841
- return PromptUpdateDetails.select_token_id(
842
- audio_tokens,
843
- embed_token_id=audio_token_id,
844
- )
832
+ return [audio_token_id] * num_features
833
834
return [
835
PromptReplacement(
836
modality="audio",
849
- target=audio_token,
837
+ target=[audio_token_id],
838
replacement=get_replacement_qwen2_audio,
839
)
840
]
vllm/model_executor/models/qwen2_audio.py
+6
-20
@@ -59,7 +59,6 @@ from vllm.multimodal.processing import (
59
BaseProcessingInfo,
60
PromptReplacement,
61
PromptUpdate,
62
- PromptUpdateDetails,
62
)
63
from vllm.sequence import IntermediateTensors
64
from vllm.utils.tensor_schema import TensorSchema, TensorShape
@@ -187,8 +186,10 @@ class Qwen2AudioDummyInputsBuilder(BaseDummyInputsBuilder[Qwen2AudioProcessingIn
186
187
hf_processor = self.info.get_hf_processor()
188
audio_token = hf_processor.audio_token
189
+ audio_bos_token = hf_processor.audio_bos_token
190
+ audio_eos_token = hf_processor.audio_eos_token
191
191
- return audio_token * num_audios
192
+ return (audio_bos_token + audio_token + audio_eos_token) * num_audios
193
194
def get_dummy_mm_data(
195
self,
@@ -262,17 +263,7 @@ class Qwen2AudioMultiModalProcessor(BaseMultiModalProcessor[Qwen2AudioProcessing
263
out_mm_kwargs: MultiModalKwargsItems,
264
) -> Sequence[PromptUpdate]:
265
processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
265
- tokenizer = self.info.get_tokenizer()
266
- vocab = tokenizer.get_vocab()
267
-
268
- # Use getattr with default to be compatible with transformers<4.48
269
- audio_token = getattr(processor, "audio_token", "<|AUDIO|>")
270
- audio_bos_token = getattr(processor, "audio_bos_token", "<|audio_bos|>")
271
- audio_eos_token = getattr(processor, "audio_eos_token", "<|audio_eos|>")
272
-
273
- audio_token_id = vocab[audio_token]
274
- audio_bos_id = vocab[audio_bos_token]
275
- audio_eos_id = vocab[audio_eos_token]
266
+ audio_token_id = processor.audio_token_id
267
268
out_mm_data = out_mm_kwargs.get_data()
269
feature_attention_mask = out_mm_data.get("feature_attention_mask")
@@ -303,17 +294,12 @@ class Qwen2AudioMultiModalProcessor(BaseMultiModalProcessor[Qwen2AudioProcessing
294
"to be represented inside the model"
295
)
296
306
- audio_tokens = [audio_token_id] * num_features
307
-
308
- return PromptUpdateDetails.select_token_id(
309
- [audio_bos_id] + audio_tokens + [audio_eos_id],
310
- embed_token_id=audio_token_id,
311
- )
297
+ return [audio_token_id] * num_features
298
299
return [
300
PromptReplacement(
301
modality="audio",
316
- target=audio_token,
302
+ target=[audio_token_id],
303
replacement=get_replacement_qwen2_audio,
304
)
305
]
vllm/multimodal/processing/processor.py
+3
@@ -1843,15 +1843,18 @@ class EncDecMultiModalProcessor(BaseMultiModalProcessor[_I]):
1843
tokenizer = self.info.get_tokenizer()
1844
decoder_prompt_raw = self.create_decoder_prompt(prompt, mm_items)
1845
if isinstance(decoder_prompt_raw, str):
1846
+ decoder_prompt_text = decoder_prompt_raw
1847
decoder_prompt_ids = tokenizer.encode(
1848
decoder_prompt_raw, add_special_tokens=False
1849
)
1850
else:
1851
+ decoder_prompt_text = None
1852
decoder_prompt_ids = decoder_prompt_raw
1853
1854
return mm_enc_dec_inputs(
1855
encoder_inputs,
1856
decoder_prompt_ids,
1857
+ decoder_prompt=decoder_prompt_text,
1858
)
1859
1860
def apply(
vllm/platforms/interface.py
+1
-2
@@ -19,7 +19,6 @@ if TYPE_CHECKING:
19
from vllm.config import VllmConfig
20
from vllm.inputs import ProcessorInputs, PromptType
21
from vllm.pooling_params import PoolingParams
22
- from vllm.renderers.inputs import DictPrompt, TokPrompt
22
from vllm.sampling_params import SamplingParams
23
from vllm.utils.argparse_utils import FlexibleArgumentParser
24
from vllm.v1.attention.selector import AttentionSelectorConfig
@@ -569,7 +568,7 @@ class Platform:
568
@classmethod
569
def validate_request(
570
cls,
572
- prompt: "PromptType | DictPrompt | TokPrompt",
571
+ prompt: "PromptType | ProcessorInputs",
572
params: "SamplingParams | PoolingParams",
573
processed_inputs: "ProcessorInputs",
574
) -> None:
vllm/renderers/base.py
+311
-79
@@ -1,17 +1,29 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import asyncio
4
+import time
5
from abc import ABC, abstractmethod
5
-from collections.abc import Sequence
6
+from collections.abc import Mapping, Sequence
7
from functools import cached_property
8
from typing import TYPE_CHECKING, Any, Generic, overload
9
10
from typing_extensions import TypeVar
11
11
-from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
12
+from vllm.inputs import (
13
+ EmbedsInputs,
14
+ EmbedsPrompt,
15
+ EncoderDecoderInputs,
16
+ ProcessorInputs,
17
+ SingletonInputs,
18
+ TextPrompt,
19
+ TokenInputs,
20
+ TokensPrompt,
21
+)
22
+from vllm.inputs.data import build_enc_dec_inputs, embeds_inputs, token_inputs
23
from vllm.logger import init_logger
24
from vllm.tokenizers import TokenizerLike
25
from vllm.utils.async_utils import AsyncMicrobatchTokenizer
26
+from vllm.utils.counter import AtomicCounter
27
from vllm.utils.torch_utils import set_default_torch_num_threads
28
from vllm.v1.metrics.stats import MultiModalCacheStats
29
@@ -20,6 +32,8 @@ from .inputs import (
32
DictPrompt,
33
EncoderDecoderDictPrompt,
34
EncoderDecoderTokPrompt,
35
+ SingletonDictPrompt,
36
+ SingletonTokPrompt,
37
TokPrompt,
38
)
39
from .inputs.preprocess import extract_target_prompt
@@ -32,6 +46,12 @@ if TYPE_CHECKING:
46
ConversationMessage,
47
)
48
from vllm.multimodal.cache import BaseMultiModalProcessorCache
49
+ from vllm.multimodal.inputs import (
50
+ MultiModalDataDict,
51
+ MultiModalInputs,
52
+ MultiModalUUIDDict,
53
+ )
54
+ from vllm.multimodal.parse import MultiModalDataItems
55
from vllm.multimodal.processing import BaseMultiModalProcessor
56
57
logger = init_logger(__name__)
@@ -79,6 +99,10 @@ class BaseRenderer(ABC, Generic[_T]):
99
if mm_processor_cache:
100
self._mm_cache_stats = MultiModalCacheStats()
101
102
+ # This is used to generate internal request ID for MM processing
103
+ # It has no relation to the request ID for engine core
104
+ self._mm_req_counter = AtomicCounter()
105
+
106
def get_tokenizer(self) -> _T:
107
tokenizer = self.tokenizer
108
if tokenizer is None:
@@ -284,17 +308,79 @@ class BaseRenderer(ABC, Generic[_T]):
308
309
return prompt
310
311
+ @overload
312
+ def _tokenize_singleton_prompt(
313
+ self,
314
+ prompt: TextPrompt | TokensPrompt,
315
+ params: TokenizeParams,
316
+ ) -> TokensPrompt: ...
317
+
318
+ @overload
319
+ def _tokenize_singleton_prompt( # type: ignore[misc]
320
+ self,
321
+ prompt: EmbedsPrompt,
322
+ params: TokenizeParams,
323
+ ) -> EmbedsPrompt: ...
324
+
325
+ def _tokenize_singleton_prompt(
326
+ self,
327
+ prompt: SingletonDictPrompt,
328
+ params: TokenizeParams,
329
+ ) -> SingletonTokPrompt:
330
+ if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
331
+ prompt = params.apply_pre_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
332
+ prompt = self._tokenize_prompt(prompt, params)
333
+
334
+ if params.needs_detokenization and "prompt" not in prompt:
335
+ if "prompt_token_ids" not in prompt:
336
+ raise RuntimeError("Cannot run detokenization on embeddings")
337
+
338
+ prompt = self._detokenize_prompt(prompt) # type: ignore[arg-type]
339
+
340
+ return params.apply_post_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
341
+
342
+ @overload
343
+ async def _tokenize_singleton_prompt_async(
344
+ self,
345
+ prompt: TextPrompt | TokensPrompt,
346
+ params: TokenizeParams,
347
+ ) -> TokensPrompt: ...
348
+
349
+ @overload
350
+ async def _tokenize_singleton_prompt_async( # type: ignore[misc]
351
+ self,
352
+ prompt: EmbedsPrompt,
353
+ params: TokenizeParams,
354
+ ) -> EmbedsPrompt: ...
355
+
356
+ async def _tokenize_singleton_prompt_async(
357
+ self,
358
+ prompt: SingletonDictPrompt,
359
+ params: TokenizeParams,
360
+ ) -> SingletonTokPrompt:
361
+ if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
362
+ prompt = params.apply_pre_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
363
+ prompt = await self._tokenize_prompt_async(prompt, params)
364
+
365
+ if params.needs_detokenization and "prompt" not in prompt:
366
+ if "prompt_token_ids" not in prompt:
367
+ raise RuntimeError("Cannot run detokenization on embeddings")
368
+
369
+ prompt = await self._detokenize_prompt_async(prompt) # type: ignore[arg-type]
370
+
371
+ return params.apply_post_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
372
+
373
def _tokenize_enc_dec_prompt(
374
self,
375
prompt: EncoderDecoderDictPrompt,
376
params: TokenizeParams,
377
) -> EncoderDecoderTokPrompt:
378
enc_prompt, dec_prompt = (
293
- self.tokenize_prompt(prompt["encoder_prompt"], params),
379
+ self._tokenize_singleton_prompt(prompt["encoder_prompt"], params),
380
(
381
None
382
if prompt["decoder_prompt"] is None
297
- else self.tokenize_prompt(prompt["decoder_prompt"], params)
383
+ else self._tokenize_singleton_prompt(prompt["decoder_prompt"], params)
384
),
385
)
386
@@ -309,11 +395,13 @@ class BaseRenderer(ABC, Generic[_T]):
395
params: TokenizeParams,
396
) -> EncoderDecoderTokPrompt:
397
enc_prompt, dec_prompt = await asyncio.gather(
312
- self.tokenize_prompt_async(prompt["encoder_prompt"], params),
398
+ self._tokenize_singleton_prompt_async(prompt["encoder_prompt"], params),
399
(
400
asyncio.sleep(0)
401
if prompt["decoder_prompt"] is None
316
- else self.tokenize_prompt_async(prompt["decoder_prompt"], params)
402
+ else self._tokenize_singleton_prompt_async(
403
+ prompt["decoder_prompt"], params
404
+ )
405
),
406
)
407
@@ -322,27 +410,6 @@ class BaseRenderer(ABC, Generic[_T]):
410
decoder_prompt=dec_prompt,
411
)
412
325
- @overload
326
- def tokenize_prompt(
327
- self,
328
- prompt: TextPrompt | TokensPrompt,
329
- params: TokenizeParams,
330
- ) -> TokensPrompt: ...
331
-
332
- @overload
333
- def tokenize_prompt( # type: ignore[misc]
334
- self,
335
- prompt: EmbedsPrompt,
336
- params: TokenizeParams,
337
- ) -> EmbedsPrompt: ...
338
-
339
- @overload
340
- def tokenize_prompt( # type: ignore[misc]
341
- self,
342
- prompt: EncoderDecoderDictPrompt,
343
- params: TokenizeParams,
344
- ) -> EncoderDecoderTokPrompt: ...
345
-
413
def tokenize_prompt(
414
self,
415
prompt: DictPrompt,
@@ -351,17 +418,7 @@ class BaseRenderer(ABC, Generic[_T]):
418
if "encoder_prompt" in prompt:
419
return self._tokenize_enc_dec_prompt(prompt, params) # type: ignore[arg-type]
420
354
- if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
355
- prompt = params.apply_pre_tokenization(self.tokenizer, prompt)
356
- prompt = self._tokenize_prompt(prompt, params)
357
-
358
- if params.needs_detokenization and "prompt" not in prompt:
359
- if "prompt_token_ids" not in prompt:
360
- raise RuntimeError("Cannot run detokenization on embeddings")
361
-
362
- prompt = self._detokenize_prompt(prompt) # type: ignore[arg-type]
363
-
364
- return params.apply_post_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
421
+ return self._tokenize_singleton_prompt(prompt, params)
422
423
def tokenize_prompts(
424
self,
@@ -370,27 +427,6 @@ class BaseRenderer(ABC, Generic[_T]):
427
) -> list[TokPrompt]:
428
return [self.tokenize_prompt(prompt, params) for prompt in prompts]
429
373
- @overload
374
- async def tokenize_prompt_async(
375
- self,
376
- prompt: TextPrompt | TokensPrompt,
377
- params: TokenizeParams,
378
- ) -> TokensPrompt: ...
379
-
380
- @overload
381
- async def tokenize_prompt_async( # type: ignore[misc]
382
- self,
383
- prompt: EmbedsPrompt,
384
- params: TokenizeParams,
385
- ) -> EmbedsPrompt: ...
386
-
387
- @overload
388
- async def tokenize_prompt_async( # type: ignore[misc]
389
- self,
390
- prompt: EncoderDecoderDictPrompt,
391
- params: TokenizeParams,
392
- ) -> EncoderDecoderTokPrompt: ...
393
-
430
async def tokenize_prompt_async(
431
self,
432
prompt: DictPrompt,
@@ -399,17 +435,7 @@ class BaseRenderer(ABC, Generic[_T]):
435
if "encoder_prompt" in prompt:
436
return await self._tokenize_enc_dec_prompt_async(prompt, params) # type: ignore[arg-type]
437
402
- if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
403
- prompt = params.apply_pre_tokenization(self.tokenizer, prompt)
404
- prompt = await self._tokenize_prompt_async(prompt, params)
405
-
406
- if params.needs_detokenization and "prompt" not in prompt:
407
- if "prompt_token_ids" not in prompt:
408
- raise RuntimeError("Cannot run detokenization on embeddings")
409
-
410
- prompt = await self._detokenize_prompt_async(prompt) # type: ignore[arg-type]
411
-
412
- return params.apply_post_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
438
+ return await self._tokenize_singleton_prompt_async(prompt, params)
439
440
async def tokenize_prompts_async(
441
self,
@@ -423,7 +449,7 @@ class BaseRenderer(ABC, Generic[_T]):
449
# Step 3: Add extra keys to the prompts
450
def _apply_prompt_extras(
451
self,
426
- prompts: Sequence[DictPrompt | TokPrompt],
452
+ prompts: Sequence[TokPrompt],
453
prompt_extras: dict[str, Any] | None,
454
):
455
if not prompt_extras:
@@ -433,6 +459,200 @@ class BaseRenderer(ABC, Generic[_T]):
459
target_prompt = extract_target_prompt(self.model_config, prompt)
460
target_prompt.update(prompt_extras) # type: ignore[arg-type]
461
462
+ # Step 4: Convert to engine inputs
463
+ def _validate_mm_uuids(
464
+ self,
465
+ mm_data: "MultiModalDataDict",
466
+ mm_items: "MultiModalDataItems",
467
+ mm_uuids: "MultiModalUUIDDict | None",
468
+ ) -> None:
469
+ if mm_uuids is None:
470
+ mm_uuids = {}
471
+
472
+ # NOTE: Keys corresponding to `None` in `mm_data` don't appear in `mm_items`
473
+ modalities = mm_data.keys() | mm_uuids.keys()
474
+
475
+ for modality in modalities:
476
+ data_items = mm_items.get(modality) or list[Any]()
477
+
478
+ uuid_items = mm_uuids.get(modality) or list[str | None]()
479
+ if isinstance(uuid_items, str):
480
+ uuid_items = [uuid_items]
481
+
482
+ if len(data_items) > 0:
483
+ if len(uuid_items) > 0 and len(data_items) != len(uuid_items):
484
+ raise ValueError(
485
+ f"If given, multi_modal_uuids[{modality!r}] must have "
486
+ f"same length as multi_modal_data[{modality!r}], but "
487
+ f"got {len(uuid_items)} vs {len(data_items)}."
488
+ )
489
+
490
+ for i, item in enumerate(data_items):
491
+ if item is None:
492
+ if not uuid_items:
493
+ raise ValueError(
494
+ f"multi_modal_data[{modality!r}][{i}] is empty but "
495
+ f"multi_modal_uuids[{modality!r}] is missing."
496
+ )
497
+
498
+ if uuid_items[i] is None:
499
+ raise ValueError(
500
+ f"multi_modal_data[{modality!r}][{i}] is empty but "
501
+ f"multi_modal_uuids[{modality!r}][{i}] is missing."
502
+ )
503
+
504
+ def _process_mm_uuids(
505
+ self,
506
+ mm_data: "MultiModalDataDict",
507
+ mm_items: "MultiModalDataItems",
508
+ mm_uuids: "MultiModalUUIDDict | None",
509
+ mm_req_id: str,
510
+ ):
511
+ model_config = self.model_config
512
+
513
+ # NOTE: When users explicitly turn off BOTH prefix caching and input
514
+ # processing caching, no multimodal features or embeddings will be
515
+ # reused across requests, therefore identifying multimodal data items
516
+ # by their content is no longer necessary, and we create uuids with
517
+ # `<mm_req_id>-<modality>-<index>`, overriding even user-provided ones.
518
+ if (
519
+ model_config.multimodal_config
520
+ and model_config.multimodal_config.mm_processor_cache_gb == 0
521
+ and not self.config.cache_config.enable_prefix_caching
522
+ ):
523
+ mm_uuids = {
524
+ modality: [f"{mm_req_id}-{modality}-{i}" for i in range(data_count)]
525
+ for modality, data_count in mm_items.get_all_counts().items()
526
+ }
527
+
528
+ self._validate_mm_uuids(mm_data, mm_items, mm_uuids)
529
+
530
+ return mm_uuids
531
+
532
+ # TODO: Remove str and tokenization_kwargs after deprecating InputPreprocessor
533
+ def _process_multimodal(
534
+ self,
535
+ prompt: list[int] | str,
536
+ mm_data: "MultiModalDataDict",
537
+ mm_processor_kwargs: Mapping[str, object] | None,
538
+ tokenization_kwargs: dict[str, Any] | None,
539
+ mm_uuids: "MultiModalUUIDDict | None",
540
+ ) -> "MultiModalInputs":
541
+ from vllm.multimodal.processing.context import set_request_id
542
+
543
+ mm_req_id = f"renderer-mm-{self._mm_req_counter.inc(1)}"
544
+
545
+ mm_processor = self.get_mm_processor()
546
+
547
+ mm_items = mm_processor.info.parse_mm_data(mm_data)
548
+ mm_uuids = self._process_mm_uuids(mm_data, mm_items, mm_uuids, mm_req_id)
549
+
550
+ with set_request_id(mm_req_id), set_default_torch_num_threads():
551
+ mm_inputs = mm_processor.apply(
552
+ prompt,
553
+ mm_items,
554
+ hf_processor_mm_kwargs=mm_processor_kwargs or {},
555
+ tokenization_kwargs=tokenization_kwargs,
556
+ mm_uuids=mm_uuids,
557
+ )
558
+
559
+ self.update_mm_cache_stats()
560
+
561
+ return mm_inputs
562
+
563
+ def _process_tokens(
564
+ self,
565
+ prompt: TokensPrompt,
566
+ ) -> "TokenInputs | MultiModalInputs":
567
+ prompt_token_ids = prompt["prompt_token_ids"]
568
+
569
+ inputs: TokenInputs | MultiModalInputs
570
+ if multi_modal_data := prompt.get("multi_modal_data"):
571
+ inputs = self._process_multimodal(
572
+ prompt_token_ids,
573
+ multi_modal_data,
574
+ mm_processor_kwargs=prompt.get("mm_processor_kwargs"),
575
+ tokenization_kwargs=None, # Tokenization already done in Step 2
576
+ mm_uuids=prompt.get("multi_modal_uuids"),
577
+ )
578
+ else:
579
+ inputs = token_inputs(prompt_token_ids)
580
+
581
+ if prompt_text := prompt.get("prompt"):
582
+ inputs["prompt"] = prompt_text
583
+ if cache_salt := prompt.get("cache_salt"):
584
+ inputs["cache_salt"] = cache_salt
585
+
586
+ return inputs
587
+
588
+ def _process_embeds(
589
+ self,
590
+ prompt: EmbedsPrompt,
591
+ ) -> EmbedsInputs:
592
+ if not self.model_config.enable_prompt_embeds:
593
+ raise ValueError(
594
+ "You must set `--enable-prompt-embeds` to input `prompt_embeds`."
595
+ )
596
+
597
+ prompt_embeds = prompt["prompt_embeds"]
598
+
599
+ # prompt_embeds must be (seq_len, hidden_size), but if the user
600
+ # passes in a batch of size 1, i.e. (1, seq_len, hidden_size),
601
+ # we can unambiguously process the intent by squeezing the batch
602
+ # dimension.
603
+ if prompt_embeds.ndim == 3:
604
+ prompt_embeds = prompt_embeds.squeeze(dim=0)
605
+
606
+ if prompt_embeds.ndim != 2:
607
+ raise ValueError("prompt_embeds must be of shape (seq_len, hidden_size).")
608
+
609
+ # Tensors must be on CPU for serialization between processes
610
+ # in the MsgpackEncoder. Casting to CPU here ensures that there is no
611
+ # hidden device transfer in the critical path of generation.
612
+ prompt_embeds = prompt_embeds.cpu()
613
+
614
+ return embeds_inputs(
615
+ prompt_embeds=prompt_embeds,
616
+ cache_salt=prompt.get("cache_salt"),
617
+ )
618
+
619
+ def _process_singleton(
620
+ self,
621
+ prompt: SingletonTokPrompt,
622
+ ) -> SingletonInputs:
623
+ if "prompt_embeds" in prompt:
624
+ return self._process_embeds(prompt) # type: ignore[arg-type]
625
+
626
+ return self._process_tokens(prompt) # type: ignore[arg-type]
627
+
628
+ def _process_enc_dec(
629
+ self,
630
+ prompt: EncoderDecoderTokPrompt,
631
+ ) -> EncoderDecoderInputs:
632
+ enc_prompt = prompt["encoder_prompt"]
633
+ dec_prompt = prompt["decoder_prompt"]
634
+
635
+ return build_enc_dec_inputs(
636
+ encoder_inputs=self._process_singleton(enc_prompt),
637
+ decoder_inputs=(
638
+ None if dec_prompt is None else self._process_singleton(dec_prompt)
639
+ ),
640
+ decoder_start_token_id=self.get_dec_start_token_id(),
641
+ )
642
+
643
+ def process_for_engine(
644
+ self, prompt: TokPrompt, arrival_time: float
645
+ ) -> ProcessorInputs:
646
+ engine_prompt: ProcessorInputs
647
+ if "encoder_prompt" in prompt:
648
+ engine_prompt = self._process_enc_dec(prompt) # type: ignore[arg-type]
649
+ else:
650
+ engine_prompt = self._process_singleton(prompt)
651
+
652
+ engine_prompt["arrival_time"] = arrival_time
653
+
654
+ return engine_prompt
655
+
656
# Top-level methods
657
def render_cmpl(
658
self,
@@ -441,6 +661,8 @@ class BaseRenderer(ABC, Generic[_T]):
661
*,
662
prompt_extras: dict[str, Any] | None = None,
663
):
664
+ arrival_time = time.time()
665
+
666
if tok_params is None:
667
tok_params = self.default_cmpl_tok_params
668
@@ -449,8 +671,7 @@ class BaseRenderer(ABC, Generic[_T]):
671
672
self._apply_prompt_extras(tok_prompts, prompt_extras)
673
452
- # TODO: Apply multi-modal processor
453
- return tok_prompts
674
+ return [self.process_for_engine(prompt, arrival_time) for prompt in tok_prompts]
675
676
async def render_cmpl_async(
677
self,
@@ -459,6 +680,8 @@ class BaseRenderer(ABC, Generic[_T]):
680
*,
681
prompt_extras: dict[str, Any] | None = None,
682
):
683
+ arrival_time = time.time()
684
+
685
if tok_params is None:
686
tok_params = self.default_cmpl_tok_params
687
@@ -467,8 +690,7 @@ class BaseRenderer(ABC, Generic[_T]):
690
691
self._apply_prompt_extras(tok_prompts, prompt_extras)
692
470
- # TODO: Apply multi-modal processor
471
- return tok_prompts
693
+ return [self.process_for_engine(prompt, arrival_time) for prompt in tok_prompts]
694
695
def render_chat(
696
self,
@@ -478,6 +700,8 @@ class BaseRenderer(ABC, Generic[_T]):
700
*,
701
prompt_extras: dict[str, Any] | None = None,
702
):
703
+ arrival_time = time.time()
704
+
705
if tok_params is None:
706
tok_params = self.default_chat_tok_params
707
@@ -496,8 +720,11 @@ class BaseRenderer(ABC, Generic[_T]):
720
721
self._apply_prompt_extras(tok_prompts, prompt_extras)
722
499
- # TODO: Apply multi-modal processor
500
- return out_conversations, tok_prompts
723
+ eng_prompts = [
724
+ self.process_for_engine(prompt, arrival_time) for prompt in tok_prompts
725
+ ]
726
+
727
+ return out_conversations, eng_prompts
728
729
async def render_chat_async(
730
self,
@@ -507,6 +734,8 @@ class BaseRenderer(ABC, Generic[_T]):
734
*,
735
prompt_extras: dict[str, Any] | None = None,
736
):
737
+ arrival_time = time.time()
738
+
739
if tok_params is None:
740
tok_params = self.default_chat_tok_params
741
@@ -525,5 +754,8 @@ class BaseRenderer(ABC, Generic[_T]):
754
755
self._apply_prompt_extras(tok_prompts, prompt_extras)
756
528
- # TODO: Apply multi-modal processor
529
- return out_conversations, tok_prompts
757
+ eng_prompts = [
758
+ self.process_for_engine(prompt, arrival_time) for prompt in tok_prompts
759
+ ]
760
+
761
+ return out_conversations, eng_prompts
vllm/renderers/inputs/preprocess.py
+13
-10
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, NamedTuple, TypeAlias, TypedDict, overload
10
from vllm.inputs import (
11
EmbedsPrompt,
12
ExplicitEncoderDecoderPrompt,
13
+ ProcessorInputs,
14
PromptType,
15
SingletonPrompt,
16
TextPrompt,
@@ -115,7 +116,7 @@ that has been standardized into a dictionary.
116
"""
117
118
118
-def parse_dec_only_prompt(prompt: object) -> DecoderOnlyDictPrompt:
119
+def parse_dec_only_prompt(prompt: PromptType | object) -> DecoderOnlyDictPrompt:
120
"""
121
Parse a prompt for a decoder-only model and normalize it to a dictionary.
122
"""
@@ -144,7 +145,7 @@ def parse_dec_only_prompt(prompt: object) -> DecoderOnlyDictPrompt:
145
raise TypeError("Prompt should be a string, list of tokens, or dictionary")
146
147
147
-def _parse_enc_prompt(prompt: object) -> EncoderDictPrompt:
148
+def _parse_enc_prompt(prompt: PromptType | object) -> EncoderDictPrompt:
149
if isinstance(prompt, str):
150
return TextPrompt(prompt=prompt)
151
@@ -166,7 +167,7 @@ def _parse_enc_prompt(prompt: object) -> EncoderDictPrompt:
167
raise TypeError("Prompt should be a string, list of tokens, or dictionary")
168
169
169
-def _parse_dec_prompt(prompt: object) -> DecoderDictPrompt:
170
+def _parse_dec_prompt(prompt: PromptType | object) -> DecoderDictPrompt:
171
if isinstance(prompt, str):
172
return TextPrompt(prompt=prompt)
173
@@ -195,13 +196,13 @@ def _parse_dec_prompt(prompt: object) -> DecoderDictPrompt:
196
raise TypeError("Prompt should be a string, list of tokens, or dictionary")
197
198
198
-def parse_enc_dec_prompt(prompt: object) -> EncoderDecoderDictPrompt:
199
+def parse_enc_dec_prompt(prompt: PromptType | object) -> EncoderDecoderDictPrompt:
200
"""
201
Parse a prompt for an encoder-decoder model and normalize it to a dictionary.
202
"""
203
if isinstance(prompt, dict) and "encoder_prompt" in prompt:
203
- enc_prompt: object = prompt["encoder_prompt"] # type: ignore[typeddict-item]
204
- dec_prompt: object | None = prompt["decoder_prompt"] # type: ignore[typeddict-item]
204
+ enc_prompt = prompt["encoder_prompt"] # type: ignore[typeddict-item]
205
+ dec_prompt = prompt["decoder_prompt"] # type: ignore[typeddict-item]
206
else:
207
enc_prompt = prompt
208
dec_prompt = None
@@ -235,21 +236,23 @@ def extract_target_prompt(model_config: "ModelConfig", prompt: object):
236
237
def extract_prompt_components(
238
model_config: "ModelConfig",
238
- prompt: object,
239
+ prompt: PromptType | ProcessorInputs,
240
) -> PromptComponents:
241
target_prompt = extract_target_prompt(model_config, prompt)
242
243
return PromptComponents(
244
text=target_prompt.get("prompt"),
244
- token_ids=target_prompt.get("prompt_token_ids"), # type: ignore[arg-type]
245
+ token_ids=target_prompt.get("prompt_token_ids"),
246
embeds=target_prompt.get("prompt_embeds"),
247
)
248
249
249
-def extract_prompt_len(model_config: "ModelConfig", prompt: object):
250
+def extract_prompt_len(
251
+ model_config: "ModelConfig", prompt: PromptType | ProcessorInputs
252
+):
253
target_prompt = extract_target_prompt(model_config, prompt)
254
255
return length_from_prompt_token_ids_or_embeds(
253
- target_prompt.get("prompt_token_ids"), # type: ignore[arg-type]
256
+ target_prompt.get("prompt_token_ids"),
257
target_prompt.get("prompt_embeds"),
258
)
vllm/utils/tqdm_utils.py
new
+39
@@ -0,0 +1,39 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from collections.abc import Callable, Iterable, Sequence
4
+from typing import Any, TypeVar, overload
5
+
6
+from tqdm.auto import tqdm
7
+
8
+_T = TypeVar("_T", bound=Iterable)
9
+
10
+
11
+@overload
12
+def maybe_tqdm(
13
+ it: Sequence[_T],
14
+ *,
15
+ use_tqdm: bool | Callable[..., tqdm],
16
+ **tqdm_kwargs: Any,
17
+) -> Sequence[_T]: ...
18
+
19
+
20
+@overload
21
+def maybe_tqdm(
22
+ it: Iterable[_T],
23
+ *,
24
+ use_tqdm: bool | Callable[..., tqdm],
25
+ **tqdm_kwargs: Any,
26
+) -> Iterable[_T]: ...
27
+
28
+
29
+def maybe_tqdm(
30
+ it: Iterable[_T],
31
+ *,
32
+ use_tqdm: bool | Callable[..., tqdm],
33
+ **tqdm_kwargs: Any,
34
+) -> Iterable[_T]:
35
+ if not use_tqdm:
36
+ return it
37
+
38
+ tqdm_func = use_tqdm if callable(use_tqdm) else tqdm
39
+ return tqdm_func(it, **tqdm_kwargs)
vllm/v1/engine/async_llm.py
+15
-11
@@ -20,7 +20,7 @@ from vllm.distributed.weight_transfer.base import (
20
)
21
from vllm.engine.arg_utils import AsyncEngineArgs
22
from vllm.engine.protocol import EngineClient, StreamingInput
23
-from vllm.inputs import PromptType
23
+from vllm.inputs import ProcessorInputs, PromptType
24
from vllm.logger import init_logger
25
from vllm.lora.request import LoRARequest
26
from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
@@ -28,7 +28,6 @@ from vllm.outputs import STREAM_FINISHED, PoolingRequestOutput, RequestOutput
28
from vllm.plugins.io_processors import get_io_processor
29
from vllm.pooling_params import PoolingParams
30
from vllm.renderers import merge_kwargs, renderer_from_config
31
-from vllm.renderers.inputs import DictPrompt, TokPrompt
31
from vllm.renderers.inputs.preprocess import extract_prompt_components
32
from vllm.sampling_params import RequestOutputKind, SamplingParams
33
from vllm.tasks import SupportedTask
@@ -290,8 +289,7 @@ class AsyncLLM(EngineClient):
289
request_id: str,
290
prompt: EngineCoreRequest
291
| PromptType
293
- | DictPrompt
294
- | TokPrompt
292
+ | ProcessorInputs
293
| AsyncGenerator[StreamingInput, None],
294
params: SamplingParams | PoolingParams,
295
arrival_time: float | None = None,
@@ -301,6 +299,7 @@ class AsyncLLM(EngineClient):
299
priority: int = 0,
300
data_parallel_rank: int | None = None,
301
prompt_text: str | None = None,
302
+ reasoning_ended: bool | None = None,
303
) -> RequestOutputCollector:
304
"""Add new request to the AsyncLLM."""
305
@@ -336,6 +335,9 @@ class AsyncLLM(EngineClient):
335
)
336
337
if isinstance(prompt, AsyncGenerator):
338
+ if reasoning_ended is not None:
339
+ raise NotImplementedError
340
+
341
# Streaming input case.
342
return await self._add_streaming_input_request(
343
request_id,
@@ -359,10 +361,6 @@ class AsyncLLM(EngineClient):
361
"latter will be used, and the former will be ignored."
362
)
363
else:
362
- if prompt_text is not None:
363
- raise ValueError(
364
- "should only provide prompt_text with EngineCoreRequest"
365
- )
364
request = self.input_processor.process_inputs(
365
request_id,
366
prompt,
@@ -377,6 +375,9 @@ class AsyncLLM(EngineClient):
375
)
376
prompt_text, _, _ = extract_prompt_components(self.model_config, prompt)
377
378
+ if reasoning_ended is not None:
379
+ request.reasoning_ended = reasoning_ended
380
+
381
self.input_processor.assign_request_id(request)
382
383
# We start the output_handler on the first call to add_request() so
@@ -536,8 +537,7 @@ class AsyncLLM(EngineClient):
537
self,
538
prompt: EngineCoreRequest
539
| PromptType
539
- | DictPrompt
540
- | TokPrompt
540
+ | ProcessorInputs
541
| AsyncGenerator[StreamingInput, None],
542
sampling_params: SamplingParams,
543
request_id: str,
@@ -548,6 +548,7 @@ class AsyncLLM(EngineClient):
548
trace_headers: Mapping[str, str] | None = None,
549
priority: int = 0,
550
data_parallel_rank: int | None = None,
551
+ reasoning_ended: bool | None = None,
552
) -> AsyncGenerator[RequestOutput, None]:
553
"""
554
Main function called by the API server to kick off a request
@@ -576,6 +577,7 @@ class AsyncLLM(EngineClient):
577
priority=priority,
578
data_parallel_rank=data_parallel_rank,
579
prompt_text=prompt_text,
580
+ reasoning_ended=reasoning_ended,
581
)
582
583
# The output_handler task pushes items into the queue.
@@ -770,13 +772,14 @@ class AsyncLLM(EngineClient):
772
773
async def encode(
774
self,
773
- prompt: PromptType | DictPrompt | TokPrompt,
775
+ prompt: PromptType | ProcessorInputs,
776
pooling_params: PoolingParams,
777
request_id: str,
778
lora_request: LoRARequest | None = None,
779
trace_headers: Mapping[str, str] | None = None,
780
priority: int = 0,
781
tokenization_kwargs: dict[str, Any] | None = None,
782
+ reasoning_ended: bool | None = None,
783
) -> AsyncGenerator[PoolingRequestOutput, None]:
784
"""
785
Main function called by the API server to kick off a request
@@ -802,6 +805,7 @@ class AsyncLLM(EngineClient):
805
tokenization_kwargs=tokenization_kwargs,
806
trace_headers=trace_headers,
807
priority=priority,
808
+ reasoning_ended=reasoning_ended,
809
)
810
811
# The output_handler task pushes items into the queue.
vllm/v1/engine/input_processor.py
+10
-158
@@ -3,7 +3,7 @@
3
4
import time
5
from collections.abc import Mapping
6
-from typing import Any, Literal, cast
6
+from typing import Any, Literal
7
8
import vllm.envs as envs
9
from vllm.config import VllmConfig
@@ -11,7 +11,6 @@ from vllm.inputs.data import (
11
ProcessorInputs,
12
PromptType,
13
SingletonInputs,
14
- SingletonPrompt,
14
)
15
from vllm.inputs.parse import split_enc_dec_inputs
16
from vllm.inputs.preprocess import InputPreprocessor
@@ -20,22 +19,16 @@ from vllm.lora.request import LoRARequest
19
from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
20
from vllm.multimodal.encoder_budget import MultiModalBudget
21
from vllm.multimodal.inputs import (
23
- MultiModalDataDict,
22
MultiModalFeatureSpec,
25
- MultiModalUUIDDict,
23
)
27
-from vllm.multimodal.parse import ModalityDataItems, MultiModalDataItems
28
-from vllm.multimodal.processing.context import set_request_id
24
from vllm.multimodal.utils import argsort_mm_positions
25
from vllm.pooling_params import PoolingParams
26
from vllm.renderers import BaseRenderer, renderer_from_config
32
-from vllm.renderers.inputs import DictPrompt, TokPrompt
27
from vllm.sampling_params import SamplingParams
28
from vllm.tasks import POOLING_TASKS, SupportedTask
29
from vllm.tokenizers import TokenizerLike
30
from vllm.utils import length_from_prompt_token_ids_or_embeds, random_uuid
31
from vllm.utils.jsontree import json_iter_leaves
38
-from vllm.utils.torch_utils import set_default_torch_num_threads
32
from vllm.v1.engine import EngineCoreRequest
33
34
logger = init_logger(__name__)
@@ -133,81 +126,6 @@ class InputProcessor:
126
f"but got {type(params).__name__}"
127
)
128
136
- def _parse_mm_items(self, mm_data: MultiModalDataDict) -> MultiModalDataItems:
137
- mm_processor = self.renderer.get_mm_processor()
138
- return mm_processor.info.parse_mm_data(mm_data)
139
-
140
- def _validate_singleton_mm_uuids(self, prompt: SingletonPrompt) -> None:
141
- if not isinstance(prompt, dict):
142
- return
143
-
144
- mm_data = cast(MultiModalDataDict, prompt.get("multi_modal_data") or {})
145
- mm_uuids = cast(MultiModalUUIDDict, prompt.get("multi_modal_uuids") or {})
146
- if not mm_data and not mm_uuids:
147
- return
148
-
149
- mm_data_parsed = self._parse_mm_items(
150
- {k: v for k, v in mm_data.items() if v is not None}
151
- )
152
- mm_uuids_parsed = {
153
- k: [v] if isinstance(v, str) else v
154
- for k, v in mm_uuids.items()
155
- if v is not None
156
- }
157
-
158
- # NOTE: Include the keys corresponding to `None`
159
- modalities = mm_data.keys() | mm_uuids.keys()
160
-
161
- for modality in modalities:
162
- data_items = cast(
163
- ModalityDataItems | list[Any], mm_data_parsed.get(modality, [])
164
- )
165
- uuid_items = cast(list[str | None], mm_uuids_parsed.get(modality, []))
166
-
167
- if len(data_items) > 0:
168
- if len(uuid_items) > 0 and len(data_items) != len(uuid_items):
169
- raise ValueError(
170
- f"If given, multi_modal_uuids[{modality!r}] must have "
171
- f"same length as multi_modal_data[{modality!r}], but "
172
- f"got {len(uuid_items)} vs {len(data_items)}."
173
- )
174
-
175
- for i, item in enumerate(data_items):
176
- if item is None:
177
- if not uuid_items:
178
- raise ValueError(
179
- f"multi_modal_data[{modality!r}][{i}] is empty but "
180
- f"multi_modal_uuids[{modality!r}] is missing."
181
- )
182
-
183
- if uuid_items[i] is None:
184
- raise ValueError(
185
- f"multi_modal_data[{modality!r}][{i}] is empty but "
186
- f"multi_modal_uuids[{modality!r}][{i}] is missing."
187
- )
188
- else:
189
- if len(uuid_items) == 0:
190
- raise ValueError(
191
- f"multi_modal_data[{modality!r}] is empty but "
192
- f"multi_modal_uuids[{modality!r}] is missing."
193
- )
194
-
195
- def _validate_mm_uuids(self, prompt: PromptType | DictPrompt | TokPrompt) -> None:
196
- """
197
- Validate that user-provided multi_modal_uuids align with
198
- multi_modal_data in the incoming request prompt(s).
199
- Only checks lengths; `None` entries are allowed and will be
200
- auto-hashed downstream.
201
- """
202
-
203
- if isinstance(prompt, dict) and "encoder_prompt" in prompt:
204
- self._validate_singleton_mm_uuids(prompt["encoder_prompt"]) # type: ignore[typeddict-item]
205
-
206
- if (dec_prompt := prompt["decoder_prompt"]) is not None: # type: ignore[typeddict-item]
207
- self._validate_singleton_mm_uuids(dec_prompt)
208
- else:
209
- self._validate_singleton_mm_uuids(prompt)
210
-
129
def _validate_lora(self, lora_request: LoRARequest | None) -> None:
130
if lora_request is None:
131
return
@@ -227,47 +145,6 @@ class InputProcessor:
145
"[lora_path]` to use the LoRA tokenizer."
146
)
147
230
- def _extract_singleton_mm_data(
231
- self, prompt: SingletonPrompt
232
- ) -> MultiModalDataDict | None:
233
- if not isinstance(prompt, dict):
234
- return None
235
-
236
- return prompt.get("multi_modal_data")
237
-
238
- def _extract_mm_data(
239
- self, prompt: PromptType | DictPrompt | TokPrompt
240
- ) -> MultiModalDataDict | None:
241
- if isinstance(prompt, dict) and "encoder_prompt" in prompt:
242
- return self._extract_singleton_mm_data(prompt["encoder_prompt"]) # type: ignore[typeddict-item]
243
- else:
244
- return self._extract_singleton_mm_data(prompt)
245
-
246
- def _maybe_build_mm_uuids(
247
- self,
248
- request_id: str,
249
- prompt: PromptType | DictPrompt | TokPrompt,
250
- ) -> MultiModalUUIDDict | None:
251
- """Build per-item multimodal hash overrides when enabled. In this case,
252
- multimodal data items are identified by their request id, modality and
253
- index rather than their content.
254
-
255
- Returns a dictionary of modality -> list[str] of overrides, or None if
256
- disabled or no multimodal data is present.
257
- """
258
- mm_data = self._extract_mm_data(prompt)
259
- if not mm_data:
260
- return None
261
-
262
- mm_items = self._parse_mm_items(
263
- {k: v for k, v in mm_data.items() if v is not None}
264
- )
265
-
266
- return {
267
- modality: [f"{request_id}-{modality}-{i}" for i in range(data_count)]
268
- for modality, data_count in mm_items.get_all_counts().items()
269
- }
270
-
148
def _get_mm_identifier(
149
self,
150
mm_hash: str,
@@ -309,7 +186,7 @@ class InputProcessor:
186
def process_inputs(
187
self,
188
request_id: str,
312
- prompt: PromptType | DictPrompt | TokPrompt,
189
+ prompt: PromptType | ProcessorInputs,
190
params: SamplingParams | PoolingParams,
191
arrival_time: float | None = None,
192
lora_request: LoRARequest | None = None,
@@ -333,43 +210,18 @@ class InputProcessor:
210
f"is out of range [0, {num_ranks})."
211
)
212
336
- if arrival_time is None:
337
- arrival_time = time.time()
213
+ if isinstance(prompt, dict) and "type" in prompt:
214
+ if arrival_time is None:
215
+ arrival_time = prompt.get("arrival_time", time.time()) # type: ignore[assignment]
216
339
- # Optionally generate multimodal hash overrides to avoid hashing
340
- # multimodal data items by their content as their identifiers.
341
-
342
- # NOTE: when users explicitly turn off BOTH prefix caching and input
343
- # processing caching, no multimodal features or embeddings will be
344
- # reused across requests, therefore identifying multimodal data items
345
- # by their content is no longer necessary, and we create uuids with
346
- # request id-modality-index as multimodal hash overrides.
347
- if (
348
- self.model_config.multimodal_config
349
- and self.model_config.multimodal_config.mm_processor_cache_gb == 0
350
- and not self.cache_config.enable_prefix_caching
351
- ):
352
- mm_uuids = self._maybe_build_mm_uuids(request_id, prompt)
217
+ processed_inputs: ProcessorInputs = prompt # type: ignore[assignment]
218
else:
354
- # Otherwise, use user-provided uuids as multimodal hash overrides
355
- # if provided.
356
- self._validate_mm_uuids(prompt)
357
- if isinstance(prompt, dict):
358
- mm_uuids = cast(
359
- MultiModalUUIDDict | None, prompt.get("multi_modal_uuids")
360
- )
361
- else:
362
- mm_uuids = None
363
-
364
- # Process inputs, which includes:
365
- # 1. Tokenize text prompt, with LoRA request if one exists.
366
- # 2. For multimodal models with a merged preprocessor, preprocess
367
- # multimodal data and expand prompt token ids accordingly.
368
- with set_request_id(request_id), set_default_torch_num_threads():
369
- processed_inputs: ProcessorInputs = self.input_preprocessor.preprocess(
219
+ if arrival_time is None:
220
+ arrival_time = time.time()
221
+
222
+ processed_inputs = self.input_preprocessor.preprocess(
223
prompt,
224
tokenization_kwargs=tokenization_kwargs,
372
- mm_uuids=mm_uuids,
225
)
226
227
from vllm.platforms import current_platform
vllm/v1/engine/llm_engine.py
+8
-6
@@ -14,7 +14,7 @@ from vllm.config import ParallelConfig, VllmConfig
14
from vllm.distributed import stateless_destroy_torch_distributed_process_group
15
from vllm.distributed.parallel_state import get_dp_group
16
from vllm.engine.arg_utils import EngineArgs
17
-from vllm.inputs import PromptType
17
+from vllm.inputs import ProcessorInputs, PromptType
18
from vllm.logger import init_logger
19
from vllm.lora.request import LoRARequest
20
from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
@@ -22,7 +22,6 @@ from vllm.outputs import PoolingRequestOutput, RequestOutput
22
from vllm.plugins.io_processors import get_io_processor
23
from vllm.pooling_params import PoolingParams
24
from vllm.renderers import renderer_from_config
25
-from vllm.renderers.inputs import DictPrompt, TokPrompt
25
from vllm.renderers.inputs.preprocess import extract_prompt_components
26
from vllm.sampling_params import SamplingParams
27
from vllm.tasks import SupportedTask
@@ -220,7 +219,7 @@ class LLMEngine:
219
def add_request(
220
self,
221
request_id: str,
223
- prompt: EngineCoreRequest | PromptType | DictPrompt | TokPrompt,
222
+ prompt: EngineCoreRequest | PromptType | ProcessorInputs,
223
params: SamplingParams | PoolingParams,
224
arrival_time: float | None = None,
225
lora_request: LoRARequest | None = None,
@@ -228,7 +227,7 @@ class LLMEngine:
227
trace_headers: Mapping[str, str] | None = None,
228
priority: int = 0,
229
prompt_text: str | None = None,
231
- ) -> None:
230
+ ) -> str:
231
# Validate the request_id type.
232
if not isinstance(request_id, str):
233
raise TypeError(f"request_id must be a string, got {type(request_id)}")
@@ -243,7 +242,6 @@ class LLMEngine:
242
"latter will be used, and the former will be ignored."
243
)
244
else:
246
- assert prompt_text is None
245
request = self.input_processor.process_inputs(
246
request_id,
247
prompt,
@@ -259,6 +257,8 @@ class LLMEngine:
257
258
self.input_processor.assign_request_id(request)
259
260
+ req_id = request.request_id
261
+
262
# Use cloned params that may have been updated in process_inputs()
263
params = request.params
264
@@ -269,7 +269,7 @@ class LLMEngine:
269
self.output_processor.add_request(request, prompt_text, None, 0)
270
# Add the request to EngineCore.
271
self.engine_core.add_request(request)
272
- return
272
+ return req_id
273
274
# Fan out child requests (for n>1).
275
parent_req = ParentRequest(request)
@@ -286,6 +286,8 @@ class LLMEngine:
286
# Add the request to EngineCore.
287
self.engine_core.add_request(child_request)
288
289
+ return req_id
290
+
291
def step(self) -> list[RequestOutput | PoolingRequestOutput]:
292
if self.should_execute_dummy_batch:
293
self.should_execute_dummy_batch = False