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, 17:45 UTC
[Refactor] Consolidate sequence normalization and enc-dec parsing (#33928)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Feb 6, 2026 at 23:43 UTC
cd8b405bd0c0f386ef7eedcb7d792a8bb72a8b65
38 files changed
+1256
-848
tests/entrypoints/openai/test_chat_error.py
+1
@@ -54,6 +54,7 @@ class MockModelConfig:
54
generation_config: str = "auto"
55
media_io_kwargs: dict[str, dict[str, Any]] = field(default_factory=dict)
56
skip_tokenizer_init = False
57
+ is_encoder_decoder: bool = False
58
59
def get_diff_sampling_param(self):
60
return self.diff_sampling_param or {}
tests/entrypoints/openai/test_completion_error.py
+1
@@ -53,6 +53,7 @@ class MockModelConfig:
53
generation_config: str = "auto"
54
media_io_kwargs: dict[str, dict[str, Any]] = field(default_factory=dict)
55
skip_tokenizer_init = False
56
+ is_encoder_decoder: bool = False
57
58
def get_diff_sampling_param(self):
59
return self.diff_sampling_param or {}
tests/entrypoints/openai/test_lora_resolvers.py
+1
@@ -52,6 +52,7 @@ class MockModelConfig:
52
encoder_config = None
53
generation_config: str = "auto"
54
skip_tokenizer_init: bool = False
55
+ is_encoder_decoder: bool = False
56
57
def get_diff_sampling_param(self):
58
return self.diff_sampling_param or {}
tests/entrypoints/openai/test_serving_chat.py
+1
@@ -529,6 +529,7 @@ class MockModelConfig:
529
generation_config: str = "auto"
530
media_io_kwargs: dict[str, dict[str, Any]] = field(default_factory=dict)
531
skip_tokenizer_init: bool = False
532
+ is_encoder_decoder: bool = False
533
534
def get_diff_sampling_param(self):
535
return self.diff_sampling_param or {}
tests/renderers/inputs/__init__.py
tests/renderers/inputs/test_preprocess.py
new
+41
@@ -0,0 +1,41 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from vllm.renderers.inputs.preprocess import prompt_to_seq
4
+
5
+
6
+def test_empty_input():
7
+ assert prompt_to_seq([]) == []
8
+ assert prompt_to_seq([[]]) == [[]]
9
+ assert prompt_to_seq([[], []]) == [[], []]
10
+
11
+
12
+def test_text_input():
13
+ assert prompt_to_seq("foo") == ["foo"]
14
+ assert prompt_to_seq(["foo"]) == ["foo"]
15
+ assert prompt_to_seq(["foo", "bar"]) == ["foo", "bar"]
16
+
17
+
18
+def test_token_input():
19
+ assert prompt_to_seq([1, 2]) == [[1, 2]]
20
+ assert prompt_to_seq([[1, 2]]) == [[1, 2]]
21
+ assert prompt_to_seq([[1, 2], [3, 4]]) == [[1, 2], [3, 4]]
22
+
23
+
24
+def test_text_token_input():
25
+ assert prompt_to_seq([[1, 2], "foo"]) == [[1, 2], "foo"]
26
+ assert prompt_to_seq(["foo", [1, 2]]) == ["foo", [1, 2]]
27
+
28
+
29
+def test_bytes_input():
30
+ assert prompt_to_seq(b"foo") == [b"foo"]
31
+ assert prompt_to_seq([b"foo"]) == [b"foo"]
32
+ assert prompt_to_seq([b"foo", b"bar"]) == [b"foo", b"bar"]
33
+
34
+
35
+def test_dict_input():
36
+ assert prompt_to_seq({"prompt": "foo"}) == [{"prompt": "foo"}]
37
+ assert prompt_to_seq([{"prompt": "foo"}]) == [{"prompt": "foo"}]
38
+ assert prompt_to_seq([{"prompt": "foo"}, {"prompt_token_ids": [1, 2]}]) == [
39
+ {"prompt": "foo"},
40
+ {"prompt_token_ids": [1, 2]},
41
+ ]
tests/renderers/test_completions.py
+90
-79
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import io
5
+from collections.abc import Sequence
6
from dataclasses import dataclass
7
from typing import Any
8
@@ -9,8 +10,11 @@ import pybase64
10
import pytest
11
import torch
12
13
+from vllm.config import ModelConfig
14
+from vllm.inputs import SingletonPrompt
15
from vllm.renderers import TokenizeParams
16
from vllm.renderers.hf import HfRenderer
17
+from vllm.renderers.inputs.preprocess import parse_model_prompt, prompt_to_seq
18
from vllm.tokenizers.registry import tokenizer_args_from_config
19
20
MODEL_NAME = "openai-community/gpt2"
@@ -33,6 +37,7 @@ class MockModelConfig:
37
encoder_config: dict[str, Any] | None = None
38
enable_prompt_embeds: bool = True
39
skip_tokenizer_init: bool = False
40
+ is_encoder_decoder: bool = False
41
42
43
@dataclass
@@ -80,65 +85,34 @@ def _build_renderer(
85
return renderer
86
87
83
-class TestValidatePrompt:
84
- STRING_INPUTS = [
85
- "",
86
- "foo",
87
- "foo bar",
88
- "foo baz bar",
89
- "foo bar qux baz",
90
- ]
91
-
92
- TOKEN_INPUTS = [
93
- [-1],
94
- [1],
95
- [1, 2],
96
- [1, 3, 4],
97
- [1, 2, 4, 3],
88
+def _preprocess_prompt(
89
+ mdoel_config: ModelConfig,
90
+ prompt_or_prompts: SingletonPrompt | bytes | Sequence[SingletonPrompt | bytes],
91
+):
92
+ return [
93
+ (
94
+ prompt
95
+ if isinstance(prompt, bytes)
96
+ else parse_model_prompt(mdoel_config, prompt)
97
+ )
98
+ for prompt in prompt_to_seq(prompt_or_prompts)
99
]
100
100
- INPUTS_SLICES = [
101
- slice(None, None, -1),
102
- slice(None, None, 2),
103
- slice(None, None, -2),
104
- ]
101
106
- # Test that a nested mixed-type list of lists raises a TypeError.
102
+class TestValidatePrompt:
103
def test_empty_input(self):
104
renderer = _build_renderer(MockModelConfig())
105
106
with pytest.raises(ValueError, match="at least one prompt"):
111
- renderer.render_completions([])
107
+ renderer.render_prompts(_preprocess_prompt(renderer.config, []))
108
109
def test_invalid_type(self):
110
renderer = _build_renderer(MockModelConfig())
111
116
- with pytest.raises(TypeError, match="string or an array of tokens"):
117
- renderer.render_completions([[1, 2], ["foo", "bar"]])
118
-
119
- @pytest.mark.parametrize("string_input", STRING_INPUTS)
120
- def test_string_consistent(self, string_input: str):
121
- renderer = _build_renderer(MockModelConfig())
122
-
123
- assert renderer.render_completions(string_input) == renderer.render_completions(
124
- [string_input]
125
- )
126
-
127
- @pytest.mark.parametrize("token_input", TOKEN_INPUTS)
128
- def test_token_consistent(self, token_input: list[int]):
129
- renderer = _build_renderer(MockModelConfig())
130
-
131
- assert renderer.render_completions(token_input) == renderer.render_completions(
132
- [token_input]
133
- )
134
-
135
- @pytest.mark.parametrize("inputs_slice", INPUTS_SLICES)
136
- def test_string_slice(self, inputs_slice: slice):
137
- renderer = _build_renderer(MockModelConfig())
138
-
139
- assert renderer.render_completions(self.STRING_INPUTS)[
140
- inputs_slice
141
- ] == renderer.render_completions(self.STRING_INPUTS[inputs_slice])
112
+ with pytest.raises(TypeError, match="should be a list of integers"):
113
+ renderer.render_prompts(
114
+ _preprocess_prompt(renderer.config, [[1, 2], ["foo", "bar"]]) # type: ignore[arg-type]
115
+ )
116
117
118
class TestRenderPrompt:
@@ -146,7 +120,7 @@ class TestRenderPrompt:
120
renderer = _build_renderer(MockModelConfig())
121
122
tokens = [101, 7592, 2088]
149
- prompts = renderer.render_completions(tokens)
123
+ prompts = renderer.render_prompts(_preprocess_prompt(renderer.config, tokens))
124
results = renderer.tokenize_prompts(
125
prompts,
126
TokenizeParams(max_total_tokens=100),
@@ -159,7 +133,9 @@ class TestRenderPrompt:
133
renderer = _build_renderer(MockModelConfig())
134
135
token_lists = [[101, 7592, 2088], [102, 1234, 5678, 9012], [103, 4567]]
162
- prompts = renderer.render_completions(token_lists)
136
+ prompts = renderer.render_prompts(
137
+ _preprocess_prompt(renderer.config, token_lists)
138
+ )
139
results = renderer.tokenize_prompts(
140
prompts,
141
TokenizeParams(max_total_tokens=100),
@@ -174,7 +150,9 @@ class TestRenderPrompt:
150
renderer = _build_renderer(MockModelConfig())
151
152
text_input = "x" * 10
177
- prompts = renderer.render_completions(text_input)
153
+ prompts = renderer.render_prompts(
154
+ _preprocess_prompt(renderer.config, text_input)
155
+ )
156
results = renderer.tokenize_prompts(
157
prompts,
158
TokenizeParams(max_total_tokens=100),
@@ -187,7 +165,9 @@ class TestRenderPrompt:
165
renderer = _build_renderer(MockModelConfig())
166
167
text_list_input = ["x" * 10, "x" * 12, "x" * 14]
190
- prompts = renderer.render_completions(text_list_input)
168
+ prompts = renderer.render_prompts(
169
+ _preprocess_prompt(renderer.config, text_list_input)
170
+ )
171
results = renderer.tokenize_prompts(
172
prompts,
173
TokenizeParams(max_total_tokens=100),
@@ -200,7 +180,9 @@ class TestRenderPrompt:
180
def test_zero_truncation(self):
181
renderer = _build_renderer(MockModelConfig())
182
203
- prompts = renderer.render_completions("x" * 200)
183
+ prompts = renderer.render_prompts(
184
+ _preprocess_prompt(renderer.config, "x" * 200)
185
+ )
186
results = renderer.tokenize_prompts(
187
prompts,
188
TokenizeParams(max_total_tokens=100, truncate_prompt_tokens=0),
@@ -212,7 +194,9 @@ class TestRenderPrompt:
194
def test_pos_truncation(self):
195
renderer = _build_renderer(MockModelConfig())
196
215
- prompts = renderer.render_completions("x" * 200)
197
+ prompts = renderer.render_prompts(
198
+ _preprocess_prompt(renderer.config, "x" * 200)
199
+ )
200
results = renderer.tokenize_prompts(
201
prompts,
202
TokenizeParams(max_total_tokens=100, truncate_prompt_tokens=50),
@@ -224,7 +208,9 @@ class TestRenderPrompt:
208
def test_neg_truncation(self):
209
renderer = _build_renderer(MockModelConfig())
210
227
- prompts = renderer.render_completions("x" * 200)
211
+ prompts = renderer.render_prompts(
212
+ _preprocess_prompt(renderer.config, "x" * 200)
213
+ )
214
results = renderer.tokenize_prompts(
215
prompts,
216
TokenizeParams(max_total_tokens=100, truncate_prompt_tokens=-1),
@@ -237,7 +223,9 @@ class TestRenderPrompt:
223
renderer = _build_renderer(MockModelConfig(), truncation_side="left")
224
225
long_tokens = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109] # 10 tokens
240
- prompts = renderer.render_completions(long_tokens)
226
+ prompts = renderer.render_prompts(
227
+ _preprocess_prompt(renderer.config, long_tokens)
228
+ )
229
results = renderer.tokenize_prompts(
230
prompts,
231
TokenizeParams(max_total_tokens=100, truncate_prompt_tokens=5),
@@ -251,7 +239,9 @@ class TestRenderPrompt:
239
renderer = _build_renderer(MockModelConfig(), truncation_side="right")
240
241
long_tokens = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109] # 10 tokens
254
- prompts = renderer.render_completions(long_tokens)
242
+ prompts = renderer.render_prompts(
243
+ _preprocess_prompt(renderer.config, long_tokens)
244
+ )
245
results = renderer.tokenize_prompts(
246
prompts,
247
TokenizeParams(max_total_tokens=100, truncate_prompt_tokens=5),
@@ -266,7 +256,9 @@ class TestRenderPrompt:
256
257
# Exceeds max_total_tokens and max_total_tokens * VLLM_MAX_CHARS_PER_TOKEN
258
long_tokens = "x" * 150
269
- prompts = renderer.render_completions(long_tokens)
259
+ prompts = renderer.render_prompts(
260
+ _preprocess_prompt(renderer.config, long_tokens)
261
+ )
262
263
with pytest.raises(
264
ValueError,
@@ -285,7 +277,9 @@ class TestRenderPrompt:
277
278
# Exceeds max_total_tokens but not max_total_tokens * VLLM_MAX_CHARS_PER_TOKEN
279
long_tokens = "x" * 150
288
- prompts = renderer.render_completions(long_tokens)
280
+ prompts = renderer.render_prompts(
281
+ _preprocess_prompt(renderer.config, long_tokens)
282
+ )
283
284
with pytest.raises(
285
ValueError,
@@ -304,7 +298,9 @@ class TestRenderPrompt:
298
renderer = _build_renderer(MockModelConfig())
299
300
long_tokens = list(range(150)) # Exceeds max_total_tokens=100
307
- prompts = renderer.render_completions(long_tokens)
301
+ prompts = renderer.render_prompts(
302
+ _preprocess_prompt(renderer.config, long_tokens)
303
+ )
304
305
with pytest.raises(
306
ValueError,
@@ -318,7 +314,9 @@ class TestRenderPrompt:
314
def test_no_tokenizer_for_text(self):
315
renderer = _build_renderer(MockModelConfig(skip_tokenizer_init=True))
316
321
- prompts = renderer.render_completions("Hello world")
317
+ prompts = renderer.render_prompts(
318
+ _preprocess_prompt(renderer.config, "Hello world")
319
+ )
320
321
with pytest.raises(ValueError, match="`skip_tokenizer_init=True`"):
322
renderer.tokenize_prompts(
@@ -330,7 +328,7 @@ class TestRenderPrompt:
328
renderer = _build_renderer(MockModelConfig())
329
330
tokens = [1, 2, 3, 4]
333
- prompts = renderer.render_completions(tokens)
331
+ prompts = renderer.render_prompts(_preprocess_prompt(renderer.config, tokens))
332
results = renderer.tokenize_prompts(
333
prompts,
334
TokenizeParams(
@@ -359,7 +357,9 @@ class TestRenderEmbedPrompt:
357
tensor_input = torch.randn(10, 768, dtype=torch.float32)
358
embed_bytes = self._create_test_embed_bytes(tensor_input)
359
362
- prompts = renderer.render_completions(prompt_embeds=embed_bytes)
360
+ prompts = renderer.render_prompts(
361
+ _preprocess_prompt(renderer.config, embed_bytes)
362
+ )
363
results = renderer.tokenize_prompts(
364
prompts,
365
TokenizeParams(max_total_tokens=100),
@@ -377,8 +377,11 @@ class TestRenderEmbedPrompt:
377
torch.randn(12, 512, dtype=torch.float32),
378
]
379
380
- prompts = renderer.render_completions(
381
- prompt_embeds=[self._create_test_embed_bytes(t) for t in tensor_inputs],
380
+ prompts = renderer.render_prompts(
381
+ _preprocess_prompt(
382
+ renderer.config,
383
+ [self._create_test_embed_bytes(t) for t in tensor_inputs],
384
+ )
385
)
386
results = renderer.tokenize_prompts(
387
prompts,
@@ -395,8 +398,10 @@ class TestRenderEmbedPrompt:
398
# Create tensor with more tokens than truncation limit
399
tensor_input = torch.randn(20, 768, dtype=torch.float32)
400
398
- prompts = renderer.render_completions(
399
- prompt_embeds=self._create_test_embed_bytes(tensor_input),
401
+ prompts = renderer.render_prompts(
402
+ _preprocess_prompt(
403
+ renderer.config, self._create_test_embed_bytes(tensor_input)
404
+ )
405
)
406
results = renderer.tokenize_prompts(
407
prompts,
@@ -420,8 +425,10 @@ class TestRenderEmbedPrompt:
425
for dtype in dtypes:
426
tensor_input = torch.randn(5, 256, dtype=dtype)
427
423
- prompts = renderer.render_completions(
424
- prompt_embeds=self._create_test_embed_bytes(tensor_input),
428
+ prompts = renderer.render_prompts(
429
+ _preprocess_prompt(
430
+ renderer.config, self._create_test_embed_bytes(tensor_input)
431
+ )
432
)
433
results = renderer.tokenize_prompts(
434
prompts,
@@ -437,8 +444,10 @@ class TestRenderEmbedPrompt:
444
# Test tensor with batch dimension gets squeezed
445
tensor_input = torch.randn(1, 10, 768, dtype=torch.float32)
446
440
- prompts = renderer.render_completions(
441
- prompt_embeds=self._create_test_embed_bytes(tensor_input),
447
+ prompts = renderer.render_prompts(
448
+ _preprocess_prompt(
449
+ renderer.config, self._create_test_embed_bytes(tensor_input)
450
+ )
451
)
452
results = renderer.tokenize_prompts(
453
prompts,
@@ -455,9 +464,11 @@ class TestRenderEmbedPrompt:
464
text_input = "Hello world"
465
tensor_input = torch.randn(5, 256, dtype=torch.float32)
466
458
- prompts = renderer.render_completions(
459
- text_input,
460
- prompt_embeds=self._create_test_embed_bytes(tensor_input),
467
+ prompts = renderer.render_prompts(
468
+ _preprocess_prompt(
469
+ renderer.config,
470
+ [text_input, self._create_test_embed_bytes(tensor_input)],
471
+ )
472
)
473
results = renderer.tokenize_prompts(
474
prompts,
@@ -465,8 +476,8 @@ class TestRenderEmbedPrompt:
476
)
477
478
assert len(results) == 2
468
- # First should be embed prompt
469
- assert torch.equal(results[0]["prompt_embeds"], tensor_input)
470
- # Second should be tokens prompt
471
- assert "prompt_token_ids" in results[1]
472
- assert len(results[1]["prompt_token_ids"]) == len(text_input)
479
+ # First should be tokens prompt
480
+ assert "prompt_token_ids" in results[0]
481
+ assert len(results[0]["prompt_token_ids"]) == len(text_input)
482
+ # Second should be embed prompt
483
+ assert torch.equal(results[1]["prompt_embeds"], tensor_input)
tests/renderers/test_mistral.py
+27
-2
@@ -3,16 +3,40 @@
3
4
import asyncio
5
import time
6
+from dataclasses import dataclass
7
+from typing import Any
8
from unittest.mock import Mock
9
10
import pytest
11
from mistral_common.tokens.tokenizers.base import SpecialTokenPolicy
12
11
-from vllm.config import ModelConfig
13
from vllm.renderers import ChatParams
14
from vllm.renderers.mistral import MistralRenderer, safe_apply_chat_template
15
from vllm.tokenizers.mistral import MistralTokenizer
16
17
+MODEL_NAME = "mistralai/Mistral-7B-Instruct-v0.3"
18
+
19
+
20
+@dataclass
21
+class MockHFConfig:
22
+ model_type: str = "any"
23
+
24
+
25
+@dataclass
26
+class MockModelConfig:
27
+ runner_type = "generate"
28
+ model: str = MODEL_NAME
29
+ tokenizer: str = MODEL_NAME
30
+ trust_remote_code: bool = False
31
+ max_model_len: int = 100
32
+ tokenizer_revision = None
33
+ tokenizer_mode = "mistral"
34
+ hf_config = MockHFConfig()
35
+ encoder_config: dict[str, Any] | None = None
36
+ enable_prompt_embeds: bool = True
37
+ skip_tokenizer_init: bool = False
38
+ is_encoder_decoder: bool = False
39
+
40
41
@pytest.mark.asyncio
42
async def test_async_mistral_tokenizer_does_not_block_event_loop():
@@ -23,9 +47,10 @@ async def test_async_mistral_tokenizer_does_not_block_event_loop():
47
time.sleep(2)
48
return expected_tokens
49
50
+ mock_model_config = MockModelConfig(skip_tokenizer_init=True)
51
mock_tokenizer = Mock(spec=MistralTokenizer)
52
mock_tokenizer.apply_chat_template = mocked_apply_chat_template
28
- mock_renderer = MistralRenderer(Mock(spec=ModelConfig), tokenizer_kwargs={})
53
+ mock_renderer = MistralRenderer(mock_model_config, tokenizer_kwargs={})
54
mock_renderer._tokenizer = mock_tokenizer
55
56
task = mock_renderer.render_messages_async([], ChatParams())
tests/test_inputs.py
+2
-41
@@ -4,52 +4,13 @@
4
import pytest
5
6
from vllm.config import ModelConfig
7
-from vllm.inputs import zip_enc_dec_prompts
7
from vllm.inputs.preprocess import InputPreprocessor
8
9
pytestmark = pytest.mark.cpu_test
10
11
13
-@pytest.mark.parametrize(
14
- "mm_processor_kwargs,expected_mm_kwargs",
15
- [
16
- (None, [{}, {}]),
17
- ({}, [{}, {}]),
18
- ({"foo": 100}, [{"foo": 100}, {"foo": 100}]),
19
- ([{"foo": 100}, {"bar": 200}], [{"foo": 100}, {"bar": 200}]),
20
- ],
21
-)
22
-def test_zip_enc_dec_prompts(mm_processor_kwargs, expected_mm_kwargs):
23
- """Test mm_processor_kwargs init for zipping enc/dec prompts."""
24
- encoder_prompts = ["An encoder prompt", "Another encoder prompt"]
25
- decoder_prompts = ["A decoder prompt", "Another decoder prompt"]
26
- zipped_prompts = zip_enc_dec_prompts(
27
- encoder_prompts, decoder_prompts, mm_processor_kwargs
28
- )
29
- assert len(zipped_prompts) == len(encoder_prompts) == len(decoder_prompts)
30
- for enc, dec, exp_kwargs, zipped in zip(
31
- encoder_prompts, decoder_prompts, expected_mm_kwargs, zipped_prompts
32
- ):
33
- assert isinstance(zipped, dict)
34
- assert len(zipped.keys()) == 3
35
- assert zipped["encoder_prompt"] == enc
36
- assert zipped["decoder_prompt"] == dec
37
- assert zipped["mm_processor_kwargs"] == exp_kwargs
38
-
39
-
40
-@pytest.mark.parametrize(
41
- "model_id",
42
- [
43
- "facebook/chameleon-7b",
44
- ],
45
-)
46
-@pytest.mark.parametrize(
47
- "prompt",
48
- [
49
- "",
50
- {"prompt_token_ids": []},
51
- ],
52
-)
12
+@pytest.mark.parametrize("model_id", ["facebook/chameleon-7b"])
13
+@pytest.mark.parametrize("prompt", ["", {"prompt_token_ids": []}])
14
@pytest.mark.skip(
15
reason=(
16
"Applying huggingface processor on text inputs results in "
vllm/engine/protocol.py
+7
-2
@@ -16,6 +16,7 @@ from vllm.outputs import PoolingRequestOutput, RequestOutput
16
from vllm.plugins.io_processors import IOProcessor
17
from vllm.pooling_params import PoolingParams
18
from vllm.renderers import BaseRenderer
19
+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
@@ -53,7 +54,11 @@ class EngineClient(ABC):
54
@abstractmethod
55
def generate(
56
self,
56
- prompt: EngineCoreRequest | PromptType | AsyncGenerator[StreamingInput, None],
57
+ prompt: EngineCoreRequest
58
+ | PromptType
59
+ | DictPrompt
60
+ | TokPrompt
61
+ | AsyncGenerator[StreamingInput, None],
62
sampling_params: SamplingParams,
63
request_id: str,
64
*,
@@ -70,7 +75,7 @@ class EngineClient(ABC):
75
@abstractmethod
76
def encode(
77
self,
73
- prompt: PromptType,
78
+ prompt: PromptType | DictPrompt | TokPrompt,
79
pooling_params: PoolingParams,
80
request_id: str,
81
lora_request: LoRARequest | None = None,
vllm/entrypoints/llm.py
+187
-149
@@ -4,7 +4,7 @@
4
import itertools
5
import warnings
6
from collections.abc import Callable, Sequence
7
-from typing import TYPE_CHECKING, Any, TypeAlias, cast
7
+from typing import TYPE_CHECKING, Any, cast
8
9
import cloudpickle
10
import torch.nn as nn
@@ -53,16 +53,13 @@ from vllm.entrypoints.pooling.score.utils import (
53
validate_score_input,
54
)
55
from vllm.entrypoints.utils import log_non_default_args
56
-from vllm.inputs import (
56
+from vllm.inputs.data import (
57
DataPrompt,
58
- EmbedsPrompt,
59
- ExplicitEncoderDecoderPrompt,
58
PromptType,
59
SingletonPrompt,
60
TextPrompt,
61
TokensPrompt,
62
)
65
-from vllm.inputs.parse import get_prompt_components, is_explicit_encoder_decoder_prompt
63
from vllm.logger import init_logger
64
from vllm.lora.request import LoRARequest
65
from vllm.model_executor.layers.quantization import QuantizationMethods
@@ -76,6 +73,13 @@ from vllm.outputs import (
73
from vllm.platforms import current_platform
74
from vllm.pooling_params import PoolingParams
75
from vllm.renderers import ChatParams, TokenizeParams, merge_kwargs
76
+from vllm.renderers.inputs import DictPrompt, SingletonDictPrompt, TokPrompt
77
+from vllm.renderers.inputs.preprocess import (
78
+ conversation_to_seq,
79
+ extract_prompt_components,
80
+ parse_model_prompt,
81
+ prompt_to_seq,
82
+)
83
from vllm.sampling_params import BeamSearchParams, RequestOutputKind, SamplingParams
84
from vllm.tasks import PoolingTask
85
from vllm.tokenizers import TokenizerLike
@@ -93,9 +97,6 @@ logger = init_logger(__name__)
97
98
_R = TypeVar("_R", default=Any)
99
96
-EnginePrompt: TypeAlias = TextPrompt | TokensPrompt | EmbedsPrompt
97
-EngineEncDecPrompt: TypeAlias = ExplicitEncoderDecoderPrompt[EnginePrompt, EnginePrompt]
98
-
100
101
class LLM:
102
"""An LLM for generating texts from given prompts and sampling parameters.
@@ -445,21 +446,20 @@ class LLM:
446
if sampling_params is None:
447
sampling_params = self.get_default_sampling_params()
448
448
- self._validate_and_add_requests(
449
+ outputs = self._run_completion(
450
prompts=prompts,
451
params=sampling_params,
452
use_tqdm=use_tqdm,
452
- lora_request=self._get_modality_specific_lora_reqs(prompts, lora_request),
453
+ lora_request=lora_request,
454
tokenization_kwargs=tokenization_kwargs,
455
priority=priority,
456
)
457
457
- outputs = self._run_engine(use_tqdm=use_tqdm)
458
return self.engine_class.validate_outputs(outputs, RequestOutput)
459
460
def _get_modality_specific_lora_reqs(
461
self,
462
- prompts: PromptType | Sequence[PromptType],
462
+ prompts: Sequence[DictPrompt | TokPrompt],
463
lora_request: list[LoRARequest] | LoRARequest | None,
464
):
465
# Grab the lora config off the vllm config on the engine,
@@ -475,9 +475,6 @@ class LLM:
475
):
476
return lora_request
477
478
- if not isinstance(prompts, Sequence) or isinstance(prompts, str):
479
- prompts = [prompts]
480
-
478
optional_loras = (
479
[lora_request] * len(prompts)
480
if not isinstance(lora_request, Sequence)
@@ -495,14 +492,12 @@ class LLM:
492
493
def _resolve_single_prompt_mm_lora(
494
self,
498
- prompt: PromptType,
495
+ prompt: DictPrompt | TokPrompt,
496
lora_request: LoRARequest | None,
497
default_mm_loras: dict[str, str] | None,
498
):
502
- if (
503
- not default_mm_loras
504
- or not isinstance(prompt, dict)
505
- or not (mm_data := prompt.get("multi_modal_data") or {})
499
+ if not default_mm_loras or not (
500
+ mm_data := prompt.get("multi_modal_data") or {}
501
):
502
return lora_request
503
@@ -806,61 +801,11 @@ class LLM:
801
add_special_tokens=not model_config.is_encoder_decoder,
802
).with_kwargs(tokenization_kwargs)
803
809
- def _normalize_prompts(
810
- self,
811
- prompts: PromptType | Sequence[PromptType],
812
- ) -> list[EnginePrompt | EngineEncDecPrompt]:
813
- if isinstance(prompts, str):
814
- prompts = TextPrompt(prompt=prompts)
815
-
816
- return prompts if isinstance(prompts, Sequence) else [prompts] # type: ignore[return-value]
817
-
818
- def _preprocess_cmpl_singleton(
819
- self,
820
- prompt: SingletonPrompt,
821
- tok_params: TokenizeParams,
822
- *,
823
- tokenize: bool,
824
- ) -> EnginePrompt:
825
- renderer = self.llm_engine.renderer
826
-
827
- if not isinstance(prompt, dict):
828
- prompt = renderer.render_completion(prompt)
829
-
830
- return renderer.tokenize_prompt(prompt, tok_params) if tokenize else prompt
831
-
832
- def _preprocess_cmpl_enc_dec(
833
- self,
834
- prompt: ExplicitEncoderDecoderPrompt,
835
- tok_params: TokenizeParams,
836
- ) -> EngineEncDecPrompt:
837
- enc_prompt = prompt["encoder_prompt"]
838
- dec_prompt = prompt["decoder_prompt"]
839
-
840
- return EngineEncDecPrompt(
841
- encoder_prompt=self._preprocess_cmpl_singleton(
842
- enc_prompt,
843
- tok_params,
844
- # TODO: Move multi-modal processor into tokenization
845
- tokenize=not self.model_config.is_multimodal_model,
846
- ),
847
- decoder_prompt=(
848
- None
849
- if dec_prompt is None
850
- else self._preprocess_cmpl_singleton(
851
- dec_prompt,
852
- tok_params,
853
- # TODO: Move multi-modal processor into tokenization
854
- tokenize=not self.model_config.is_multimodal_model,
855
- )
856
- ),
857
- )
858
-
804
def _preprocess_completion(
805
self,
861
- prompts: PromptType | Sequence[PromptType],
806
+ prompts: Sequence[PromptType],
807
tokenization_kwargs: dict[str, Any] | None = None,
863
- ) -> list[EnginePrompt | EngineEncDecPrompt]:
808
+ ) -> list[DictPrompt | TokPrompt]:
809
"""
810
Convert prompt inputs from LLM APIs (other than [LLM.chat][]) into
811
a format that can be passed to `_add_request`.
@@ -871,32 +816,26 @@ class LLM:
816
A list of `TokensPrompts` objects containing the tokenized prompt
817
after chat template interpolation, and the raw multi-modal inputs.
818
"""
819
+ renderer = self.llm_engine.renderer
820
+ model_config = self.model_config
821
+
822
tok_params = self._get_cmpl_tok_params(tokenization_kwargs)
823
876
- engine_prompts = list[EnginePrompt | EngineEncDecPrompt]()
877
- for prompt in self._normalize_prompts(prompts):
878
- if is_explicit_encoder_decoder_prompt(prompt):
879
- engine_prompts.append(self._preprocess_cmpl_enc_dec(prompt, tok_params))
880
- else:
881
- # Some MM models have non-default `add_special_tokens`
882
- # TODO: Move multi-modal processor into tokenization
883
- engine_prompts.append(
884
- self._preprocess_cmpl_singleton(
885
- prompt,
886
- tok_params,
887
- tokenize=not self.model_config.is_multimodal_model,
888
- )
889
- )
824
+ engine_prompts = list[DictPrompt | TokPrompt]()
825
+ for prompt in prompts:
826
+ parsed_prompt = parse_model_prompt(model_config, prompt)
827
+ in_prompt = renderer.render_prompt(parsed_prompt)
828
+
829
+ # Some MM models have non-default `add_special_tokens`
830
+ # TODO: Move multi-modal processor into tokenization
831
+ engine_prompts.append(
832
+ in_prompt
833
+ if model_config.is_multimodal_model
834
+ else renderer.tokenize_prompt(in_prompt, tok_params)
835
+ )
836
837
return engine_prompts
838
893
- def _normalize_conversations(
894
- self,
895
- conversations: list[ChatCompletionMessageParam]
896
- | list[list[ChatCompletionMessageParam]],
897
- ) -> list[list[ChatCompletionMessageParam]]:
898
- return conversations if is_list_of(conversations, list) else [conversations] # type: ignore[list-item,return-value]
899
-
839
def _get_chat_tok_params(self, tokenization_kwargs: dict[str, Any] | None):
840
model_config = self.model_config
841
encoder_config = model_config.encoder_config or {}
@@ -909,8 +848,7 @@ class LLM:
848
849
def _preprocess_chat(
850
self,
912
- conversations: list[ChatCompletionMessageParam]
913
- | list[list[ChatCompletionMessageParam]],
851
+ conversations: Sequence[list[ChatCompletionMessageParam]],
852
chat_template: str | None = None,
853
chat_template_content_format: ChatTemplateContentFormatOption = "auto",
854
chat_template_kwargs: dict[str, Any] | None = None,
@@ -919,7 +857,7 @@ class LLM:
857
tools: list[dict[str, Any]] | None = None,
858
tokenization_kwargs: dict[str, Any] | None = None,
859
mm_processor_kwargs: dict[str, Any] | None = None,
922
- ) -> list[EnginePrompt]:
860
+ ) -> list[DictPrompt | TokPrompt]:
861
"""
862
Convert a list of conversations into prompts so that they can then
863
be used as input for other LLM APIs.
@@ -947,11 +885,14 @@ class LLM:
885
)
886
tok_params = self._get_chat_tok_params(tokenization_kwargs)
887
950
- engine_prompts = list[EnginePrompt]()
951
- for conversation in self._normalize_conversations(conversations):
888
+ engine_prompts = list[DictPrompt | TokPrompt]()
889
+ for conversation in conversations:
890
_, in_prompt = renderer.render_messages(conversation, chat_params)
891
if mm_processor_kwargs is not None:
954
- in_prompt["mm_processor_kwargs"] = mm_processor_kwargs
892
+ target_prompt: SingletonDictPrompt = in_prompt.get( # type: ignore
893
+ "encoder_prompt", in_prompt
894
+ )
895
+ target_prompt["mm_processor_kwargs"] = mm_processor_kwargs # type: ignore
896
897
engine_prompts.append(renderer.tokenize_prompt(in_prompt, tok_params))
898
@@ -960,8 +901,8 @@ class LLM:
901
def chat(
902
self,
903
messages: list[ChatCompletionMessageParam]
963
- | list[list[ChatCompletionMessageParam]],
964
- sampling_params: SamplingParams | list[SamplingParams] | None = None,
904
+ | Sequence[list[ChatCompletionMessageParam]],
905
+ sampling_params: SamplingParams | Sequence[SamplingParams] | None = None,
906
use_tqdm: bool | Callable[..., tqdm] = True,
907
lora_request: LoRARequest | None = None,
908
chat_template: str | None = None,
@@ -984,7 +925,7 @@ class LLM:
925
to the OpenAI API.
926
927
Args:
987
- messages: A list of conversations or a single conversation.
928
+ messages: A sequence of conversations or a single conversation.
929
930
- Each conversation is represented as a list of messages.
931
- Each message is a dictionary with 'role' and 'content' keys.
@@ -1023,8 +964,23 @@ class LLM:
964
A list of `RequestOutput` objects containing the generated
965
responses in the same order as the input messages.
966
"""
1026
- prompts = self._preprocess_chat(
1027
- messages,
967
+ model_config = self.model_config
968
+ runner_type = model_config.runner_type
969
+ if runner_type != "generate":
970
+ raise ValueError(
971
+ "LLM.chat() is only supported for generative models. "
972
+ "Try passing `--runner generate` to use the model as a "
973
+ "generative model."
974
+ )
975
+
976
+ if sampling_params is None:
977
+ sampling_params = self.get_default_sampling_params()
978
+
979
+ outputs = self._run_chat(
980
+ messages=messages,
981
+ params=sampling_params,
982
+ use_tqdm=use_tqdm,
983
+ lora_request=lora_request,
984
chat_template=chat_template,
985
chat_template_content_format=chat_template_content_format,
986
chat_template_kwargs=chat_template_kwargs,
@@ -1035,13 +991,7 @@ class LLM:
991
mm_processor_kwargs=mm_processor_kwargs,
992
)
993
1038
- return self.generate(
1039
- prompts,
1040
- sampling_params=sampling_params,
1041
- use_tqdm=use_tqdm,
1042
- lora_request=lora_request,
1043
- tokenization_kwargs=tokenization_kwargs,
1044
- )
994
+ return self.engine_class.validate_outputs(outputs, RequestOutput)
995
996
def encode(
997
self,
@@ -1163,7 +1113,7 @@ class LLM:
1113
msg = f"You cannot overwrite {param.task=!r} with {pooling_task=!r}!"
1114
raise ValueError(msg)
1115
1166
- self._validate_and_add_requests(
1116
+ outputs = self._run_completion(
1117
prompts=prompts,
1118
params=pooling_params,
1119
use_tqdm=use_tqdm,
@@ -1171,8 +1121,6 @@ class LLM:
1121
tokenization_kwargs=tokenization_kwargs,
1122
)
1123
1174
- outputs = self._run_engine(use_tqdm=use_tqdm)
1175
-
1124
model_outputs = self.engine_class.validate_outputs(
1125
outputs, PoolingRequestOutput
1126
)
@@ -1523,14 +1471,13 @@ class LLM:
1471
1472
prompts.append(engine_prompt)
1473
1526
- self._validate_and_add_requests(
1474
+ outputs = self._run_completion(
1475
prompts=prompts,
1476
params=pooling_params_list,
1477
use_tqdm=use_tqdm,
1478
lora_request=lora_request,
1479
)
1480
1533
- outputs = self._run_engine(use_tqdm=use_tqdm)
1481
items = self.engine_class.validate_outputs(outputs, PoolingRequestOutput)
1482
1483
return [ScoringRequestOutput.from_base(item) for item in items]
@@ -1727,33 +1674,29 @@ class LLM:
1674
"""
1675
return self.llm_engine.get_metrics()
1676
1730
- def _validate_and_add_requests(
1677
+ def _params_to_seq(
1678
self,
1732
- prompts: PromptType | Sequence[PromptType],
1679
params: SamplingParams
1734
- | Sequence[SamplingParams]
1680
| PoolingParams
1736
- | Sequence[PoolingParams],
1737
- *,
1738
- use_tqdm: bool | Callable[..., tqdm] = True,
1739
- lora_request: Sequence[LoRARequest | None] | LoRARequest | None,
1740
- tokenization_kwargs: dict[str, Any] | None = None,
1741
- priority: list[int] | None = None,
1742
- ) -> None:
1743
- in_prompts = self._normalize_prompts(prompts)
1744
- num_requests = len(in_prompts)
1745
-
1681
+ | Sequence[SamplingParams | PoolingParams],
1682
+ num_requests: int,
1683
+ ) -> Sequence[SamplingParams | PoolingParams]:
1684
if isinstance(params, Sequence):
1685
if len(params) != num_requests:
1686
raise ValueError(
1687
f"The lengths of prompts ({params}) "
1750
- f"and lora_request ({len(params)}) must be the same."
1688
+ f"and params ({len(params)}) must be the same."
1689
)
1690
1753
- engine_params = params
1754
- else:
1755
- engine_params = [params] * num_requests
1691
+ return params
1692
1693
+ return [params] * num_requests
1694
+
1695
+ def _lora_request_to_seq(
1696
+ self,
1697
+ lora_request: LoRARequest | None | Sequence[LoRARequest | None],
1698
+ num_requests: int,
1699
+ ) -> Sequence[LoRARequest | None]:
1700
if isinstance(lora_request, Sequence):
1701
if len(lora_request) != num_requests:
1702
raise ValueError(
@@ -1761,28 +1704,50 @@ class LLM:
1704
f"and lora_request ({len(lora_request)}) must be the same."
1705
)
1706
1764
- engine_lora_requests: Sequence[LoRARequest | None] = lora_request
1765
- else:
1766
- engine_lora_requests = [lora_request] * num_requests
1707
+ return lora_request
1708
+
1709
+ return [lora_request] * num_requests
1710
1711
+ def _priority_to_seq(
1712
+ self,
1713
+ priority: list[int] | None,
1714
+ num_requests: int,
1715
+ ) -> Sequence[int]:
1716
if priority is not None:
1717
if len(priority) != num_requests:
1718
raise ValueError(
1719
f"The lengths of prompts ({num_requests}) "
1720
f"and priority ({len(priority)}) must be the same."
1721
)
1774
- else:
1775
- priority = [0] * num_requests
1722
1777
- if any(param.truncate_prompt_tokens is not None for param in engine_params):
1723
+ return priority
1724
+
1725
+ return [0] * num_requests
1726
+
1727
+ def _run_completion(
1728
+ self,
1729
+ prompts: PromptType | Sequence[PromptType],
1730
+ params: SamplingParams
1731
+ | PoolingParams
1732
+ | Sequence[SamplingParams | PoolingParams],
1733
+ *,
1734
+ use_tqdm: bool | Callable[..., tqdm] = True,
1735
+ lora_request: list[LoRARequest] | LoRARequest | None = None,
1736
+ priority: list[int] | None = None,
1737
+ tokenization_kwargs: dict[str, Any] | None = None,
1738
+ ):
1739
+ seq_prompts = prompt_to_seq(prompts)
1740
+ seq_params = self._params_to_seq(params, len(seq_prompts))
1741
+
1742
+ if any(param.truncate_prompt_tokens is not None for param in seq_params):
1743
# TODO: Remove this after deprecating `param.truncate_prompt_tokens`
1744
# Then, move the code from the `else` block to the top and let
1745
# `self._preprocess_completion` handle prompt normalization
1746
engine_prompts = [
1747
engine_prompt
1783
- for in_prompt, param in zip(in_prompts, engine_params)
1748
+ for prompt, param in zip(seq_prompts, seq_params)
1749
for engine_prompt in self._preprocess_completion(
1785
- [in_prompt],
1750
+ [prompt],
1751
tokenization_kwargs=merge_kwargs(
1752
tokenization_kwargs,
1753
dict(truncate_prompt_tokens=param.truncate_prompt_tokens),
@@ -1791,17 +1756,90 @@ class LLM:
1756
]
1757
else:
1758
engine_prompts = self._preprocess_completion(
1794
- in_prompts,
1759
+ seq_prompts,
1760
tokenization_kwargs=tokenization_kwargs,
1761
)
1762
1798
- for sp in engine_params:
1763
+ self._validate_and_add_requests(
1764
+ prompts=engine_prompts,
1765
+ params=seq_params,
1766
+ use_tqdm=use_tqdm,
1767
+ lora_request=self._get_modality_specific_lora_reqs(
1768
+ engine_prompts, lora_request
1769
+ ),
1770
+ tokenization_kwargs=tokenization_kwargs,
1771
+ priority=priority,
1772
+ )
1773
+
1774
+ return self._run_engine(use_tqdm=use_tqdm)
1775
+
1776
+ def _run_chat(
1777
+ self,
1778
+ messages: list[ChatCompletionMessageParam]
1779
+ | Sequence[list[ChatCompletionMessageParam]],
1780
+ params: SamplingParams
1781
+ | PoolingParams
1782
+ | Sequence[SamplingParams | PoolingParams],
1783
+ *,
1784
+ use_tqdm: bool | Callable[..., tqdm] = True,
1785
+ lora_request: LoRARequest | None = None,
1786
+ chat_template: str | None = None,
1787
+ chat_template_content_format: ChatTemplateContentFormatOption = "auto",
1788
+ add_generation_prompt: bool = True,
1789
+ continue_final_message: bool = False,
1790
+ tools: list[dict[str, Any]] | None = None,
1791
+ chat_template_kwargs: dict[str, Any] | None = None,
1792
+ tokenization_kwargs: dict[str, Any] | None = None,
1793
+ mm_processor_kwargs: dict[str, Any] | None = None,
1794
+ ):
1795
+ engine_prompts = self._preprocess_chat(
1796
+ conversation_to_seq(messages),
1797
+ chat_template=chat_template,
1798
+ chat_template_content_format=chat_template_content_format,
1799
+ chat_template_kwargs=chat_template_kwargs,
1800
+ add_generation_prompt=add_generation_prompt,
1801
+ continue_final_message=continue_final_message,
1802
+ tools=tools,
1803
+ tokenization_kwargs=tokenization_kwargs,
1804
+ mm_processor_kwargs=mm_processor_kwargs,
1805
+ )
1806
+
1807
+ self._validate_and_add_requests(
1808
+ prompts=engine_prompts,
1809
+ params=params,
1810
+ use_tqdm=use_tqdm,
1811
+ lora_request=self._get_modality_specific_lora_reqs(
1812
+ engine_prompts, lora_request
1813
+ ),
1814
+ tokenization_kwargs=tokenization_kwargs,
1815
+ )
1816
+
1817
+ return self._run_engine(use_tqdm=use_tqdm)
1818
+
1819
+ def _validate_and_add_requests(
1820
+ self,
1821
+ prompts: Sequence[DictPrompt | TokPrompt],
1822
+ params: SamplingParams
1823
+ | PoolingParams
1824
+ | Sequence[SamplingParams | PoolingParams],
1825
+ *,
1826
+ use_tqdm: bool | Callable[..., tqdm] = True,
1827
+ lora_request: Sequence[LoRARequest | None] | LoRARequest | None,
1828
+ tokenization_kwargs: dict[str, Any] | None = None,
1829
+ priority: list[int] | None = None,
1830
+ ) -> None:
1831
+ num_requests = len(prompts)
1832
+ seq_params = self._params_to_seq(params, num_requests)
1833
+ seq_lora_requests = self._lora_request_to_seq(lora_request, num_requests)
1834
+ seq_priority = self._priority_to_seq(priority, num_requests)
1835
+
1836
+ for sp in seq_params:
1837
if isinstance(sp, SamplingParams):
1838
# We only care about the final output
1839
sp.output_kind = RequestOutputKind.FINAL_ONLY
1840
1841
# Add requests to the engine.
1804
- it = engine_prompts
1842
+ it = prompts
1843
if use_tqdm:
1844
tqdm_func = use_tqdm if callable(use_tqdm) else tqdm
1845
it = tqdm_func(it, desc="Adding requests")
@@ -1812,10 +1850,10 @@ class LLM:
1850
for i, prompt in enumerate(it):
1851
request_id = self._add_request(
1852
prompt,
1815
- engine_params[i],
1816
- lora_request=engine_lora_requests[i],
1853
+ seq_params[i],
1854
+ lora_request=seq_lora_requests[i],
1855
tokenization_kwargs=tokenization_kwargs,
1818
- priority=priority[i],
1856
+ priority=seq_priority[i],
1857
)
1858
added_request_ids.append(request_id)
1859
except Exception as e:
@@ -1825,13 +1863,13 @@ class LLM:
1863
1864
def _add_request(
1865
self,
1828
- prompt: PromptType,
1866
+ prompt: PromptType | DictPrompt | TokPrompt,
1867
params: SamplingParams | PoolingParams,
1868
lora_request: LoRARequest | None = None,
1869
tokenization_kwargs: dict[str, Any] | None = None,
1870
priority: int = 0,
1871
) -> str:
1834
- prompt_text, _, _ = get_prompt_components(prompt)
1872
+ prompt_text, _, _ = extract_prompt_components(self.model_config, prompt)
1873
request_id = str(next(self.request_counter))
1874
1875
if params.truncate_prompt_tokens is not None:
vllm/entrypoints/openai/chat_completion/serving.py
+8
-10
@@ -67,12 +67,13 @@ 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 EmbedsPrompt, TokensPrompt
70
+from vllm.inputs.data import 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
77
from vllm.sampling_params import BeamSearchParams, SamplingParams
78
from vllm.tokenizers import TokenizerLike
79
from vllm.tokenizers.mistral import (
@@ -218,10 +219,7 @@ class OpenAIServingChat(OpenAIServing):
219
async def render_chat_request(
220
self,
221
request: ChatCompletionRequest,
221
- ) -> (
222
- tuple[list[ConversationMessage], list[TokensPrompt | EmbedsPrompt]]
223
- | ErrorResponse
224
- ):
222
+ ) -> tuple[list[ConversationMessage], list[TokPrompt]] | ErrorResponse:
223
"""
224
render chat request by validating and preprocessing inputs.
225
@@ -380,7 +378,7 @@ class OpenAIServingChat(OpenAIServing):
378
generators: list[AsyncGenerator[RequestOutput, None]] = []
379
try:
380
for i, engine_prompt in enumerate(engine_prompts):
383
- prompt_text = engine_prompt.get("prompt")
381
+ prompt_text = self._extract_prompt_text(engine_prompt)
382
383
# If we are creating sub requests for multiple prompts, ensure that they
384
# have unique request ids.
@@ -389,10 +387,10 @@ class OpenAIServingChat(OpenAIServing):
387
)
388
389
max_tokens = get_max_tokens(
392
- max_model_len=self.max_model_len,
393
- request=request,
394
- prompt=engine_prompt,
395
- default_sampling_params=self.default_sampling_params,
390
+ self.max_model_len,
391
+ request,
392
+ self._extract_prompt_len(engine_prompt),
393
+ self.default_sampling_params,
394
)
395
396
sampling_params: SamplingParams | BeamSearchParams
vllm/entrypoints/openai/completion/serving.py
+10
-10
@@ -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 EmbedsPrompt, TokensPrompt
37
from vllm.logger import init_logger
38
from vllm.logprobs import Logprob
39
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
@@ -78,7 +78,7 @@ class OpenAIServingCompletion(OpenAIServing):
78
async def render_completion_request(
79
self,
80
request: CompletionRequest,
81
- ) -> list[TokensPrompt | EmbedsPrompt] | ErrorResponse:
81
+ ) -> list[TokPrompt] | ErrorResponse:
82
"""
83
render completion request by validating and preprocessing inputs.
84
@@ -160,13 +160,13 @@ class OpenAIServingCompletion(OpenAIServing):
160
generators: list[AsyncGenerator[RequestOutput, None]] = []
161
try:
162
for i, engine_prompt in enumerate(engine_prompts):
163
- prompt_text = engine_prompt.get("prompt")
163
+ prompt_text = self._extract_prompt_text(engine_prompt)
164
165
max_tokens = get_max_tokens(
166
- max_model_len=self.max_model_len,
167
- request=request,
168
- prompt=engine_prompt,
169
- default_sampling_params=self.default_sampling_params,
166
+ self.max_model_len,
167
+ request,
168
+ self._extract_prompt_len(engine_prompt),
169
+ self.default_sampling_params,
170
)
171
172
sampling_params: SamplingParams | BeamSearchParams
@@ -277,7 +277,7 @@ class OpenAIServingCompletion(OpenAIServing):
277
# with the inputs token IDs
278
if final_res.prompt is None:
279
engine_prompt = engine_prompts[i]
280
- final_res.prompt = engine_prompt.get("prompt")
280
+ final_res.prompt = self._extract_prompt_text(engine_prompt)
281
282
final_res_batch_checked = cast(list[RequestOutput], final_res_batch)
283
@@ -313,7 +313,7 @@ class OpenAIServingCompletion(OpenAIServing):
313
async def completion_stream_generator(
314
self,
315
request: CompletionRequest,
316
- engine_prompts: list[TokensPrompt | EmbedsPrompt],
316
+ engine_prompts: list[TokPrompt],
317
result_generator: AsyncIterator[tuple[int, RequestOutput]],
318
request_id: str,
319
created_time: int,
@@ -347,7 +347,7 @@ class OpenAIServingCompletion(OpenAIServing):
347
prompt_text = res.prompt
348
if prompt_text is None:
349
engine_prompt = engine_prompts[prompt_idx]
350
- prompt_text = engine_prompt.get("prompt")
350
+ prompt_text = self._extract_prompt_text(engine_prompt)
351
352
# Prompt details are excluded from later streamed outputs
353
if prompt_token_ids is not None:
vllm/entrypoints/openai/engine/serving.py
+68
-41
@@ -96,11 +96,7 @@ 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 EmbedsPrompt, PromptType, TokensPrompt
100
-from vllm.inputs.parse import (
101
- get_prompt_components,
102
- is_explicit_encoder_decoder_prompt,
103
-)
99
+from vllm.inputs.data import PromptType, SingletonPrompt, TokensPrompt
100
from vllm.logger import init_logger
101
from vllm.logprobs import Logprob, PromptLogprobs
102
from vllm.lora.request import LoRARequest
@@ -108,6 +104,14 @@ from vllm.multimodal import MultiModalDataDict
104
from vllm.outputs import CompletionOutput, PoolingRequestOutput, RequestOutput
105
from vllm.pooling_params import PoolingParams
106
from vllm.renderers import ChatParams, TokenizeParams, merge_kwargs
107
+from vllm.renderers.inputs import TokPrompt
108
+from vllm.renderers.inputs.preprocess import (
109
+ SingletonDictPrompt,
110
+ extract_prompt_components,
111
+ extract_prompt_len,
112
+ parse_model_prompt,
113
+ prompt_to_seq,
114
+)
115
from vllm.sampling_params import BeamSearchParams, SamplingParams
116
from vllm.tokenizers import TokenizerLike
117
from vllm.tool_parsers import ToolParser
@@ -203,7 +207,7 @@ class ServeContext(Generic[RequestT]):
207
request_id: str
208
created_time: int = field(default_factory=lambda: int(time.time()))
209
lora_request: LoRARequest | None = None
206
- engine_prompts: list[TokensPrompt | EmbedsPrompt] | None = None
210
+ engine_prompts: list[TokPrompt] | None = None
211
212
result_generator: AsyncGenerator[tuple[int, PoolingRequestOutput], None] | None = (
213
None
@@ -247,7 +251,7 @@ class OpenAIServing:
251
252
async def beam_search(
253
self,
250
- prompt: PromptType,
254
+ prompt: TokPrompt,
255
request_id: str,
256
params: BeamSearchParams,
257
lora_request: LoRARequest | None = None,
@@ -271,20 +275,12 @@ class OpenAIServing:
275
276
eos_token_id: int = tokenizer.eos_token_id # type: ignore
277
274
- if is_explicit_encoder_decoder_prompt(prompt):
275
- raise NotImplementedError
278
+ if isinstance(prompt, dict) and "encoder_prompt" in prompt:
279
+ raise NotImplementedError("Encoder-decoder prompt not supported")
280
277
- prompt_text: str | None
278
- prompt_token_ids: list[int]
279
- multi_modal_data: MultiModalDataDict | None
280
- if isinstance(prompt, str):
281
- prompt_text = prompt
282
- prompt_token_ids = []
283
- multi_modal_data = None
284
- else:
285
- prompt_text = prompt.get("prompt") # type: ignore
286
- prompt_token_ids = prompt.get("prompt_token_ids", []) # type: ignore
287
- multi_modal_data = prompt.get("multi_modal_data") # type: ignore
281
+ prompt_text: str | None = prompt.get("prompt") # type: ignore
282
+ prompt_token_ids: list[int] = prompt.get("prompt_token_ids", []) # type: ignore
283
+ multi_modal_data: MultiModalDataDict | None = prompt.get("multi_modal_data") # type: ignore
284
285
mm_processor_kwargs: dict[str, Any] | None = None
286
@@ -963,22 +959,40 @@ class OpenAIServing:
959
request: RendererRequest,
960
prompt_input: str | list[str] | list[int] | list[list[int]] | None,
961
prompt_embeds: bytes | list[bytes] | None,
966
- ) -> list[TokensPrompt | EmbedsPrompt]:
962
+ ) -> list[TokPrompt]:
963
renderer = self.renderer
968
- tok_params = request.build_tok_params(self.model_config)
964
+ model_config = self.model_config
965
970
- in_prompts = await renderer.render_completions_async(
971
- prompt_input, prompt_embeds
972
- )
973
- engine_prompts = await renderer.tokenize_prompts_async(in_prompts, tok_params)
966
+ tok_params = request.build_tok_params(model_config)
967
+
968
+ prompts = list[SingletonPrompt | bytes]()
969
+ if prompt_embeds is not None: # embeds take higher priority
970
+ prompts.extend(prompt_to_seq(prompt_embeds))
971
+ if prompt_input is not None:
972
+ prompts.extend(prompt_to_seq(prompt_input))
973
+
974
+ parsed_prompts = [
975
+ (
976
+ prompt
977
+ if isinstance(prompt, bytes)
978
+ else parse_model_prompt(model_config, prompt)
979
+ )
980
+ for prompt in prompts
981
+ ]
982
+ in_prompts = await renderer.render_prompts_async(parsed_prompts)
983
984
extra_items = {
985
k: v
986
for k in ("mm_processor_kwargs", "cache_salt")
987
if (v := getattr(request, k, None)) is not None
988
}
980
- for prompt in engine_prompts:
981
- prompt.update(extra_items) # type: ignore
989
+ for in_prompt in in_prompts:
990
+ target_prompt: SingletonDictPrompt = in_prompt.get( # type: ignore
991
+ "encoder_prompt", in_prompt
992
+ )
993
+ target_prompt.update(extra_items) # type: ignore
994
+
995
+ engine_prompts = await renderer.tokenize_prompts_async(in_prompts, tok_params)
996
997
return engine_prompts
998
@@ -991,7 +1005,7 @@ class OpenAIServing:
1005
default_template_kwargs: dict[str, Any] | None,
1006
tool_dicts: list[dict[str, Any]] | None = None,
1007
tool_parser: Callable[[TokenizerLike], ToolParser] | None = None,
994
- ) -> tuple[list[ConversationMessage], list[TokensPrompt | EmbedsPrompt]]:
1008
+ ) -> tuple[list[ConversationMessage], list[TokPrompt]]:
1009
from vllm.tokenizers.mistral import MistralTokenizer
1010
1011
renderer = self.renderer
@@ -1009,17 +1023,21 @@ class OpenAIServing:
1023
default_template, default_template_content_format
1024
).with_defaults(default_template_kwargs)
1025
1012
- conversation, prompt = await renderer.render_messages_async(
1026
+ conversation, in_prompt = await renderer.render_messages_async(
1027
messages, chat_params
1028
)
1015
- engine_prompt = await renderer.tokenize_prompt_async(prompt, tok_params)
1029
+ target_prompt: SingletonDictPrompt = in_prompt.get( # type: ignore
1030
+ "encoder_prompt", in_prompt
1031
+ )
1032
1033
extra_items = {
1034
k: v
1035
for k in ("mm_processor_kwargs", "cache_salt")
1036
if (v := getattr(request, k, None)) is not None
1037
}
1022
- engine_prompt.update(extra_items) # type: ignore
1038
+ target_prompt.update(extra_items) # type: ignore
1039
+
1040
+ engine_prompt = await renderer.tokenize_prompt_async(target_prompt, tok_params)
1041
1042
# tool parsing is done only if a tool_parser has been set and if
1043
# tool_choice is not "none" (if tool_choice is "none" but a tool_parser
@@ -1040,6 +1058,15 @@ class OpenAIServing:
1058
1059
return conversation, [engine_prompt]
1060
1061
+ def _extract_prompt_components(self, prompt: object):
1062
+ return extract_prompt_components(self.model_config, prompt)
1063
+
1064
+ def _extract_prompt_text(self, prompt: object):
1065
+ return self._extract_prompt_components(prompt).text
1066
+
1067
+ def _extract_prompt_len(self, prompt: object):
1068
+ return extract_prompt_len(self.model_config, prompt)
1069
+
1070
async def _render_next_turn(
1071
self,
1072
request: ResponsesRequest,
@@ -1067,7 +1094,7 @@ class OpenAIServing:
1094
async def _generate_with_builtin_tools(
1095
self,
1096
request_id: str,
1070
- engine_prompt: TokensPrompt | EmbedsPrompt,
1097
+ engine_prompt: TokPrompt,
1098
sampling_params: SamplingParams,
1099
tok_params: TokenizeParams,
1100
context: ConversationContext,
@@ -1075,7 +1102,7 @@ class OpenAIServing:
1102
priority: int = 0,
1103
trace_headers: Mapping[str, str] | None = None,
1104
):
1078
- prompt_text = engine_prompt.get("prompt")
1105
+ prompt_text = self._extract_prompt_text(engine_prompt)
1106
1107
orig_priority = priority
1108
sub_request = 0
@@ -1145,12 +1172,12 @@ class OpenAIServing:
1172
context.chat_template_content_format,
1173
)
1174
engine_prompt = engine_prompts[0]
1148
- prompt_text = engine_prompt.get("prompt")
1175
+ prompt_text = self._extract_prompt_text(engine_prompt)
1176
1177
sampling_params.max_tokens = get_max_tokens(
1178
self.max_model_len,
1179
context.request,
1153
- engine_prompt,
1180
+ self._extract_prompt_len(engine_prompt),
1181
self.default_sampling_params, # type: ignore
1182
)
1183
@@ -1161,20 +1188,20 @@ class OpenAIServing:
1188
def _log_inputs(
1189
self,
1190
request_id: str,
1164
- inputs: PromptType,
1191
+ inputs: PromptType | TokPrompt,
1192
params: SamplingParams | PoolingParams | BeamSearchParams | None,
1193
lora_request: LoRARequest | None,
1194
) -> None:
1195
if self.request_logger is None:
1196
return
1197
1171
- prompt, prompt_token_ids, prompt_embeds = get_prompt_components(inputs)
1198
+ components = self._extract_prompt_components(inputs)
1199
1200
self.request_logger.log_inputs(
1201
request_id,
1175
- prompt,
1176
- prompt_token_ids,
1177
- prompt_embeds,
1202
+ components.text,
1203
+ components.token_ids,
1204
+ components.embeds,
1205
params=params,
1206
lora_request=lora_request,
1207
)
vllm/entrypoints/openai/responses/serving.py
+5
-5
@@ -116,13 +116,13 @@ 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 EmbedsPrompt, TokensPrompt
120
-from vllm.inputs.parse import get_prompt_len
119
+from vllm.inputs.data import TokensPrompt
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
126
from vllm.sampling_params import SamplingParams, StructuredOutputsParams
127
from vllm.tokenizers import TokenizerLike
128
from vllm.utils import random_uuid
@@ -292,10 +292,10 @@ class OpenAIServingResponses(OpenAIServing):
292
293
def _validate_generator_input(
294
self,
295
- engine_prompt: TokensPrompt | EmbedsPrompt,
295
+ engine_prompt: TokPrompt,
296
) -> ErrorResponse | None:
297
"""Add validations to the input to the generator here."""
298
- prompt_len = get_prompt_len(engine_prompt)
298
+ prompt_len = self._extract_prompt_len(engine_prompt)
299
if self.max_model_len <= prompt_len:
300
error_message = (
301
f"The engine prompt length {prompt_len} "
@@ -442,7 +442,7 @@ class OpenAIServingResponses(OpenAIServing):
442
default_max_tokens = get_max_tokens(
443
self.max_model_len,
444
request,
445
- engine_prompt,
445
+ self._extract_prompt_len(engine_prompt),
446
self.default_sampling_params,
447
)
448
vllm/entrypoints/openai/speech_to_text/speech_to_text.py
+13
-21
@@ -7,7 +7,7 @@ import time
7
import zlib
8
from collections.abc import AsyncGenerator, Callable
9
from functools import cached_property
10
-from typing import Literal, TypeAlias, TypeVar, cast
10
+from typing import Final, Literal, TypeAlias, TypeVar, cast
11
12
import numpy as np
13
from fastapi import Request
@@ -37,12 +37,13 @@ from vllm.entrypoints.openai.speech_to_text.protocol import (
37
TranslationStreamResponse,
38
)
39
from vllm.exceptions import VLLMValidationError
40
-from vllm.inputs.data import ExplicitEncoderDecoderPrompt, PromptType
41
-from vllm.inputs.parse import is_explicit_encoder_decoder_prompt
40
+from vllm.inputs.data import PromptType
41
from vllm.logger import init_logger
42
from vllm.logprobs import FlatLogprobs, Logprob
43
from vllm.model_executor.models import SupportsTranscription, supports_transcription
44
from vllm.outputs import RequestOutput
45
+from vllm.renderers.inputs import EncoderDecoderDictPrompt
46
+from vllm.renderers.inputs.preprocess import parse_enc_dec_prompt
47
from vllm.tokenizers import get_tokenizer
48
from vllm.utils.import_utils import PlaceholderModule
49
@@ -94,7 +95,7 @@ class OpenAISpeechToText(OpenAIServing):
95
)
96
97
self.default_sampling_params = self.model_config.get_diff_sampling_param()
97
- self.task_type = task_type
98
+ self.task_type: Final = task_type
99
100
self.asr_config = self.model_cls.get_speech_to_text_config(
101
self.model_config, task_type
@@ -298,35 +299,26 @@ class OpenAISpeechToText(OpenAIServing):
299
to_language=to_language,
300
)
301
if request.response_format == "verbose_json":
301
- if not is_explicit_encoder_decoder_prompt(prompt):
302
- raise VLLMValidationError(
303
- "Expected prompt to be an encoder-decoder prompt",
304
- parameter="prompt",
305
- value=type(prompt).__name__,
306
- )
307
-
308
- prompt = self._preprocess_verbose_prompt(prompt)
302
+ prompt = self._preprocess_verbose_prompt(parse_enc_dec_prompt(prompt))
303
304
prompts.append(prompt)
311
- return prompts, duration
305
313
- def _repl_verbose_text(self, text: str):
314
- return text.replace("<|notimestamps|>", "<|0.00|>")
306
+ return prompts, duration
307
316
- def _preprocess_verbose_prompt(self, prompt: ExplicitEncoderDecoderPrompt):
308
+ def _preprocess_verbose_prompt(self, prompt: EncoderDecoderDictPrompt):
309
dec_prompt = prompt["decoder_prompt"]
310
319
- if isinstance(dec_prompt, str):
320
- prompt["decoder_prompt"] = self._repl_verbose_text(dec_prompt)
321
- elif isinstance(dec_prompt, dict) and "prompt" in dec_prompt:
322
- dec_prompt["prompt"] = self._repl_verbose_text(dec_prompt["prompt"])
323
- else:
311
+ if not (isinstance(dec_prompt, dict) and "prompt" in dec_prompt):
312
raise VLLMValidationError(
313
"Expected decoder_prompt to contain text",
314
parameter="decoder_prompt",
315
value=type(dec_prompt).__name__,
316
)
317
318
+ dec_prompt["prompt"] = dec_prompt["prompt"].replace(
319
+ "<|notimestamps|>", "<|0.00|>"
320
+ )
321
+
322
return prompt
323
324
def _get_verbose_segments(
vllm/entrypoints/pooling/embed/serving.py
+3
-2
@@ -28,10 +28,11 @@ from vllm.entrypoints.pooling.utils import (
28
encode_pooling_output_base64,
29
encode_pooling_output_float,
30
)
31
-from vllm.inputs.data import EmbedsPrompt, TokensPrompt
31
+from vllm.inputs.data import TokensPrompt
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
36
from vllm.utils.async_utils import merge_async_iterators
37
from vllm.utils.collection_utils import chunk_list
38
from vllm.utils.serial_utils import EmbedDType, Endianness
@@ -369,7 +370,7 @@ class OpenAIServingEmbedding(OpenAIServing):
370
async def _create_single_prompt_generator(
371
self,
372
ctx: EmbeddingServeContext,
372
- engine_prompt: TokensPrompt | EmbedsPrompt,
373
+ engine_prompt: TokPrompt,
374
pooling_params: PoolingParams,
375
trace_headers: Mapping[str, str] | None,
376
prompt_index: int,
vllm/entrypoints/pooling/pooling/serving.py
+6
-6
@@ -33,8 +33,11 @@ from vllm.entrypoints.pooling.utils import (
33
encode_pooling_output_base64,
34
encode_pooling_output_float,
35
)
36
+from vllm.inputs import PromptType
37
from vllm.logger import init_logger
38
from vllm.outputs import PoolingRequestOutput
39
+from vllm.renderers.inputs import TokPrompt
40
+from vllm.renderers.inputs.preprocess import prompt_to_seq
41
from vllm.utils.async_utils import merge_async_iterators
42
from vllm.utils.serial_utils import EmbedDType, EncodingFormat, Endianness
43
@@ -91,6 +94,7 @@ class OpenAIServingPooling(OpenAIServing):
94
"dimensions is currently not supported"
95
)
96
97
+ engine_prompts: Sequence[PromptType | TokPrompt]
98
if is_io_processor_request:
99
if self.io_processor is None:
100
raise ValueError(
@@ -102,14 +106,10 @@ class OpenAIServingPooling(OpenAIServing):
106
107
validated_prompt = self.io_processor.parse_request(request)
108
105
- engine_prompts = await self.io_processor.pre_process_async(
109
+ raw_prompts = await self.io_processor.pre_process_async(
110
prompt=validated_prompt, request_id=request_id
111
)
108
- if not isinstance(engine_prompts, Sequence) or isinstance(
109
- engine_prompts, (str, bytes, bytearray)
110
- ):
111
- engine_prompts = [engine_prompts]
112
-
112
+ engine_prompts = prompt_to_seq(raw_prompts)
113
elif isinstance(request, PoolingChatRequest):
114
error_check_ret = self._validate_chat_template(
115
request_chat_template=request.chat_template,
vllm/entrypoints/utils.py
+1
-4
@@ -17,8 +17,6 @@ from starlette.background import BackgroundTask, BackgroundTasks
17
18
from vllm import envs
19
from vllm.engine.arg_utils import EngineArgs
20
-from vllm.inputs import EmbedsPrompt, TokensPrompt
21
-from vllm.inputs.parse import get_prompt_len
20
from vllm.logger import current_formatter_type, init_logger
21
from vllm.platforms import current_platform
22
from vllm.utils.argparse_utils import FlexibleArgumentParser
@@ -189,7 +187,7 @@ def cli_env_setup():
187
def get_max_tokens(
188
max_model_len: int,
189
request: "CompletionRequest | ChatCompletionRequest | ResponsesRequest",
192
- prompt: TokensPrompt | EmbedsPrompt,
190
+ input_length: int,
191
default_sampling_params: dict,
192
) -> int:
193
# NOTE: Avoid isinstance() for better efficiency
@@ -204,7 +202,6 @@ def get_max_tokens(
202
# CompletionRequest (also a fallback for ChatCompletionRequest)
203
max_tokens = getattr(request, "max_tokens", None)
204
207
- input_length = get_prompt_len(prompt)
205
default_max_tokens = max_model_len - input_length
206
max_output_tokens = current_platform.get_max_output_tokens(input_length)
207
vllm/inputs/__init__.py
-6
@@ -16,11 +16,8 @@ from .data import (
16
TextPrompt,
17
TokenInputs,
18
TokensPrompt,
19
- build_explicit_enc_dec_prompt,
19
embeds_inputs,
21
- to_enc_dec_tuple_list,
20
token_inputs,
23
- zip_enc_dec_prompts,
21
)
22
23
__all__ = [
@@ -39,8 +36,5 @@ __all__ = [
36
"EncoderDecoderInputs",
37
"ProcessorInputs",
38
"SingletonInputs",
42
- "build_explicit_enc_dec_prompt",
43
- "to_enc_dec_tuple_list",
44
- "zip_enc_dec_prompts",
39
"StreamingInput",
40
]
vllm/inputs/data.py
+133
-154
@@ -1,11 +1,10 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-from collections.abc import Iterable
3
from dataclasses import dataclass
5
-from typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias, cast
4
+from typing import TYPE_CHECKING, Any, Literal, TypeAlias
5
6
import torch
8
-from typing_extensions import NotRequired, TypedDict, TypeVar
7
+from typing_extensions import NotRequired, TypedDict
8
9
from vllm.sampling_params import SamplingParams
10
@@ -23,7 +22,13 @@ else:
22
MultiModalUUIDDict = object
23
24
26
-class _CommonKeys(TypedDict):
25
+# Inputs to LLM API
26
+class _PromptOptions(TypedDict):
27
+ """
28
+ Additional options available to all
29
+ [`SingletonPrompt`][vllm.inputs.data.SingletonPrompt].
30
+ """
31
+
32
multi_modal_data: NotRequired[MultiModalDataDict | None]
33
"""
34
Optional multi-modal data to pass to the model,
@@ -53,14 +58,14 @@ class _CommonKeys(TypedDict):
58
"""
59
60
56
-class TextPrompt(_CommonKeys):
61
+class TextPrompt(_PromptOptions):
62
"""Schema for a text prompt."""
63
64
prompt: str
65
"""The input text to be tokenized before passing to the model."""
66
67
63
-class TokensPrompt(_CommonKeys):
68
+class TokensPrompt(_PromptOptions):
69
"""Schema for a tokenized prompt."""
70
71
prompt_token_ids: list[int]
@@ -73,7 +78,7 @@ class TokensPrompt(_CommonKeys):
78
"""A list of token type IDs to pass to the cross encoder model."""
79
80
76
-class EmbedsPrompt(_CommonKeys):
81
+class EmbedsPrompt(_PromptOptions):
82
"""Schema for a prompt provided via token embeddings."""
83
84
prompt_embeds: torch.Tensor
@@ -83,93 +88,113 @@ class EmbedsPrompt(_CommonKeys):
88
"""The prompt text corresponding to the token embeddings, if available."""
89
90
86
-class DataPrompt(_CommonKeys):
87
- """Represents generic inputs handled by IO processor plugins."""
91
+DecoderOnlyPrompt: TypeAlias = (
92
+ str | TextPrompt | list[int] | TokensPrompt | EmbedsPrompt
93
+)
94
+"""
95
+Schema of a prompt for a decoder-only model:
96
89
- data: Any
90
- """The input data"""
97
+- A text prompt (string or [`TextPrompt`][vllm.inputs.data.TextPrompt])
98
+- A tokenized prompt (list of token IDs, or
99
+ [`TokensPrompt`][vllm.inputs.data.TokensPrompt])
100
+- An embeddings prompt ([`EmbedsPrompt`][vllm.inputs.data.EmbedsPrompt])
101
92
- data_format: str
93
- """The input data format"""
102
+For encoder-decoder models, passing a singleton prompt is shorthand for passing
103
+`ExplicitEncoderDecoderPrompt(encoder_prompt=prompt, decoder_prompt=None)`.
104
+"""
105
106
96
-SingletonPrompt: TypeAlias = str | TextPrompt | TokensPrompt | EmbedsPrompt
107
+EncoderPrompt: TypeAlias = str | TextPrompt | list[int] | TokensPrompt
108
+"""
109
+Schema of a prompt for the encoder part of a encoder-decoder model:
110
+
111
+- A text prompt (string or [`TextPrompt`][vllm.inputs.data.TextPrompt])
112
+- A tokenized prompt (list of token IDs, or
113
+ [`TokensPrompt`][vllm.inputs.data.TokensPrompt])
114
"""
98
-Set of possible schemas for a single prompt:
115
100
-- A text prompt ([`str`][] or [`TextPrompt`][vllm.inputs.data.TextPrompt])
101
-- A tokenized prompt ([`TokensPrompt`][vllm.inputs.data.TokensPrompt])
102
-- An embeddings prompt ([`EmbedsPrompt`][vllm.inputs.data.EmbedsPrompt])
116
104
-Note that "singleton" is as opposed to a data structure
105
-which encapsulates multiple prompts, i.e. of the sort
106
-which may be utilized for encoder/decoder models when
107
-the user desires to express both the encoder & decoder
108
-prompts explicitly, i.e.
109
-[`ExplicitEncoderDecoderPrompt`][vllm.inputs.data.ExplicitEncoderDecoderPrompt]
110
-
111
-A prompt of type [`SingletonPrompt`][vllm.inputs.data.SingletonPrompt] may be
112
-employed as (1) input to a decoder-only model, (2) input to
113
-the encoder of an encoder/decoder model, in the scenario
114
-where the decoder-prompt is not specified explicitly, or
115
-(3) as a member of a larger data structure encapsulating
116
-more than one prompt, i.e.
117
-[`ExplicitEncoderDecoderPrompt`][vllm.inputs.data.ExplicitEncoderDecoderPrompt]
117
+DecoderPrompt: TypeAlias = str | TextPrompt | list[int] | TokensPrompt
118
"""
119
+Schema of a prompt for the decoder part of an encoder-decoder model:
120
121
+- A text prompt (string or [`TextPrompt`][vllm.inputs.data.TextPrompt])
122
+- A tokenized prompt (list of token IDs, or
123
+ [`TokensPrompt`][vllm.inputs.data.TokensPrompt])
124
121
-_T1_co = TypeVar(
122
- "_T1_co", bound=SingletonPrompt, default=SingletonPrompt, covariant=True
123
-)
124
-_T2_co = TypeVar(
125
- "_T2_co", bound=SingletonPrompt, default=SingletonPrompt, covariant=True
126
-)
125
+Note:
126
+ Multi-modal inputs are not supported for decoder prompts.
127
+"""
128
129
129
-# TODO: Make fields ReadOnly once mypy supports it
130
-class ExplicitEncoderDecoderPrompt(TypedDict, Generic[_T1_co, _T2_co]):
130
+class ExplicitEncoderDecoderPrompt(TypedDict):
131
"""
132
- Represents an encoder/decoder model input prompt,
133
- comprising an explicit encoder prompt and a decoder prompt.
134
-
135
- The encoder and decoder prompts, respectively, may be formatted
136
- according to any of the
137
- [`SingletonPrompt`][vllm.inputs.data.SingletonPrompt] schemas,
138
- and are not required to have the same schema.
139
-
140
- Only the encoder prompt may have multi-modal data. mm_processor_kwargs
141
- should be at the top-level, and should not be set in the encoder/decoder
142
- prompts, since they are agnostic to the encoder/decoder.
143
-
144
- Note that an
145
- [`ExplicitEncoderDecoderPrompt`][vllm.inputs.data.ExplicitEncoderDecoderPrompt]
146
- may not be used as an input to a decoder-only model,
147
- and that the `encoder_prompt` and `decoder_prompt`
148
- fields of this data structure themselves must be
149
- [`SingletonPrompt`][vllm.inputs.data.SingletonPrompt] instances.
132
+ Schema for a pair of encoder and decoder singleton prompts.
133
+
134
+ Note:
135
+ This schema is not valid for decoder-only models.
136
"""
137
152
- encoder_prompt: _T1_co
138
+ encoder_prompt: EncoderPrompt
139
+ """The prompt for the encoder part of the model."""
140
154
- decoder_prompt: _T2_co | None
141
+ decoder_prompt: DecoderPrompt | None
142
+ """
143
+ The prompt for the decoder part of the model.
144
156
- mm_processor_kwargs: NotRequired[dict[str, Any]]
145
+ Passing `None` will cause the prompt to be inferred automatically.
146
+ """
147
148
159
-PromptType: TypeAlias = SingletonPrompt | ExplicitEncoderDecoderPrompt[Any, Any]
149
+EncoderDecoderPrompt: TypeAlias = EncoderPrompt | ExplicitEncoderDecoderPrompt
150
"""
161
-Set of possible schemas for an LLM input, including
162
-both decoder-only and encoder/decoder input types:
151
+Schema for a prompt for an encoder-decoder model.
152
164
-- A text prompt ([`str`][] or [`TextPrompt`][vllm.inputs.data.TextPrompt])
165
-- A tokenized prompt ([`TokensPrompt`][vllm.inputs.data.TokensPrompt])
166
-- An embeddings prompt ([`EmbedsPrompt`][vllm.inputs.data.EmbedsPrompt])
167
-- A single data structure containing both an encoder and a decoder prompt
168
- ([`ExplicitEncoderDecoderPrompt`][vllm.inputs.data.ExplicitEncoderDecoderPrompt])
153
+You can pass a singleton encoder prompt, in which case the decoder prompt is
154
+considered to be `None` (i.e., infer automatically).
155
+"""
156
+
157
+
158
+SingletonPrompt: TypeAlias = DecoderOnlyPrompt | EncoderPrompt | DecoderPrompt
159
+"""
160
+Schema for a single prompt. This is as opposed to a data structure
161
+which encapsulates multiple prompts, such as
162
+[`ExplicitEncoderDecoderPrompt`][vllm.inputs.data.ExplicitEncoderDecoderPrompt].
163
"""
164
165
172
-class TokenInputs(TypedDict):
166
+PromptType: TypeAlias = DecoderOnlyPrompt | EncoderDecoderPrompt
167
+"""
168
+Schema for any prompt, regardless of model type.
169
+
170
+This is the input format accepted by most [`LLM`][vllm.entrypoints.llm.LLM] APIs.
171
+"""
172
+
173
+
174
+class DataPrompt(_PromptOptions):
175
+ """
176
+ Represents generic inputs that are converted to
177
+ [`PromptType`][vllm.inputs.data.PromptType] by IO processor plugins.
178
+ """
179
+
180
+ data: Any
181
+ """The input data."""
182
+
183
+ data_format: str
184
+ """The input data format."""
185
+
186
+
187
+# Outputs of processor
188
+class _InputOptions(TypedDict):
189
+ """
190
+ Additional options available to all input types.
191
+ """
192
+
193
+ cache_salt: NotRequired[str]
194
+ """Optional cache salt to be used for prefix caching."""
195
+
196
+
197
+class TokenInputs(_InputOptions):
198
"""Represents token-based inputs."""
199
200
type: Literal["token"]
@@ -178,11 +203,6 @@ class TokenInputs(TypedDict):
203
prompt_token_ids: list[int]
204
"""The token IDs of the prompt."""
205
181
- cache_salt: NotRequired[str]
182
- """
183
- Optional cache salt to be used for prefix caching.
184
- """
185
-
206
207
def token_inputs(
208
prompt_token_ids: list[int],
@@ -198,7 +218,7 @@ def token_inputs(
218
return inputs
219
220
201
-class EmbedsInputs(TypedDict):
221
+class EmbedsInputs(_InputOptions):
222
"""Represents embeddings-based inputs."""
223
224
type: Literal["embeds"]
@@ -207,11 +227,6 @@ class EmbedsInputs(TypedDict):
227
prompt_embeds: torch.Tensor
228
"""The embeddings of the prompt."""
229
210
- cache_salt: NotRequired[str]
211
- """
212
- Optional cache salt to be used for prefix caching.
213
- """
214
-
230
231
def embeds_inputs(
232
prompt_embeds: torch.Tensor,
@@ -229,96 +244,60 @@ def embeds_inputs(
244
245
DecoderOnlyInputs: TypeAlias = TokenInputs | EmbedsInputs | MultiModalInputs
246
"""
232
-The inputs in [`LLMEngine`][vllm.engine.llm_engine.LLMEngine] before they are
233
-passed to the model executor.
234
-This specifies the data required for decoder-only models.
247
+A processed prompt from
248
+[`InputPreprocessor`][vllm.inputs.preprocess.InputPreprocessor]
249
+which can be passed to
250
+[`InputProcessor`][vllm.v1.engine.input_processor.InputProcessor]
251
+for decoder-only models.
252
+"""
253
+
254
+
255
+EncoderInputs: TypeAlias = TokenInputs | MultiModalEncDecInputs
256
+"""
257
+A processed encoder prompt from
258
+[`InputPreprocessor`][vllm.inputs.preprocess.InputPreprocessor]
259
+which can be passed to
260
+[`InputProcessor`][vllm.v1.engine.input_processor.InputProcessor]
261
+for encoder-decoder models.
262
+"""
263
+
264
+
265
+DecoderInputs: TypeAlias = TokenInputs | MultiModalInputs
266
+"""
267
+A processed decoder prompt from
268
+[`InputPreprocessor`][vllm.inputs.preprocess.InputPreprocessor]
269
+which can be passed to
270
+[`InputProcessor`][vllm.v1.engine.input_processor.InputProcessor]
271
+for encoder-decoder models.
272
"""
273
274
275
class EncoderDecoderInputs(TypedDict):
276
"""
240
- The inputs in [`LLMEngine`][vllm.engine.llm_engine.LLMEngine] before they
241
- are passed to the model executor.
242
-
243
- This specifies the required data for encoder-decoder models.
277
+ A processed pair of encoder and decoder singleton prompts.
278
+ [`InputPreprocessor`][vllm.inputs.preprocess.InputPreprocessor]
279
+ which can be passed to
280
+ [`InputProcessor`][vllm.v1.engine.input_processor.InputProcessor]
281
+ for encoder-decoder models.
282
"""
283
246
- encoder: TokenInputs | MultiModalEncDecInputs
284
+ encoder: EncoderInputs
285
"""The inputs for the encoder portion."""
286
249
- decoder: TokenInputs | MultiModalInputs
287
+ decoder: DecoderInputs
288
"""The inputs for the decoder portion."""
289
290
253
-SingletonInputs: TypeAlias = TokenInputs | EmbedsInputs | MultiModalInputs
254
-"""
255
-A processed [`SingletonPrompt`][vllm.inputs.data.SingletonPrompt] which can be
256
-passed to [`Sequence`][collections.abc.Sequence].
257
-"""
258
-
291
ProcessorInputs: TypeAlias = DecoderOnlyInputs | EncoderDecoderInputs
292
"""
261
-The outputs from [`vllm.inputs.preprocess.InputPreprocessor`][].
293
+A processed prompt from
294
+[`InputPreprocessor`][vllm.inputs.preprocess.InputPreprocessor]
295
+which can be passed to
296
+[`InputProcessor`][vllm.v1.engine.input_processor.InputProcessor].
297
"""
298
264
-_T1 = TypeVar("_T1", bound=SingletonPrompt, default=SingletonPrompt)
265
-_T2 = TypeVar("_T2", bound=SingletonPrompt, default=SingletonPrompt)
266
-
267
-
268
-def build_explicit_enc_dec_prompt(
269
- encoder_prompt: _T1,
270
- decoder_prompt: _T2 | None,
271
- mm_processor_kwargs: dict[str, Any] | None = None,
272
-) -> ExplicitEncoderDecoderPrompt[_T1, _T2]:
273
- if mm_processor_kwargs is None:
274
- mm_processor_kwargs = {}
275
- return ExplicitEncoderDecoderPrompt(
276
- encoder_prompt=encoder_prompt,
277
- decoder_prompt=decoder_prompt,
278
- mm_processor_kwargs=mm_processor_kwargs,
279
- )
280
-
281
-
282
-def zip_enc_dec_prompts(
283
- enc_prompts: Iterable[_T1],
284
- dec_prompts: Iterable[_T2 | None],
285
- mm_processor_kwargs: Iterable[dict[str, Any]] | dict[str, Any] | None = None,
286
-) -> list[ExplicitEncoderDecoderPrompt[_T1, _T2]]:
287
- """
288
- Zip encoder and decoder prompts together into a list of
289
- [`ExplicitEncoderDecoderPrompt`][vllm.inputs.data.ExplicitEncoderDecoderPrompt]
290
- instances.
299
292
- `mm_processor_kwargs` may also be provided; if a dict is passed, the same
293
- dictionary will be used for every encoder/decoder prompt. If an iterable is
294
- provided, it will be zipped with the encoder/decoder prompts.
295
- """
296
- if mm_processor_kwargs is None:
297
- mm_processor_kwargs = cast(dict[str, Any], {})
298
- if isinstance(mm_processor_kwargs, dict):
299
- return [
300
- build_explicit_enc_dec_prompt(
301
- encoder_prompt,
302
- decoder_prompt,
303
- cast(dict[str, Any], mm_processor_kwargs),
304
- )
305
- for (encoder_prompt, decoder_prompt) in zip(enc_prompts, dec_prompts)
306
- ]
307
- return [
308
- build_explicit_enc_dec_prompt(encoder_prompt, decoder_prompt, mm_proc_kwargs)
309
- for (encoder_prompt, decoder_prompt, mm_proc_kwargs) in zip(
310
- enc_prompts, dec_prompts, mm_processor_kwargs
311
- )
312
- ]
313
-
314
-
315
-def to_enc_dec_tuple_list(
316
- enc_dec_prompts: Iterable[ExplicitEncoderDecoderPrompt[_T1, _T2]],
317
-) -> list[tuple[_T1, _T2 | None]]:
318
- return [
319
- (enc_dec_prompt["encoder_prompt"], enc_dec_prompt["decoder_prompt"])
320
- for enc_dec_prompt in enc_dec_prompts
321
- ]
300
+SingletonInputs: TypeAlias = DecoderOnlyInputs | MultiModalEncDecInputs
301
302
303
@dataclass
vllm/inputs/parse.py
+1
-109
@@ -1,88 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-from typing import TYPE_CHECKING, Literal, NamedTuple, TypeAlias, TypedDict
3
5
-from typing_extensions import TypeIs
6
-
7
-from vllm.utils import length_from_prompt_token_ids_or_embeds
8
-
9
-from .data import (
10
- EmbedsPrompt,
11
- ExplicitEncoderDecoderPrompt,
12
- ProcessorInputs,
13
- PromptType,
14
- SingletonInputs,
15
- SingletonPrompt,
16
- TextPrompt,
17
- TokensPrompt,
18
-)
19
-
20
-if TYPE_CHECKING:
21
- import torch
22
-
23
-
24
-class ParsedStrPrompt(TypedDict):
25
- type: Literal["str"]
26
- content: str
27
-
28
-
29
-class ParsedTextPrompt(TypedDict):
30
- type: Literal["text"]
31
- content: TextPrompt
32
-
33
-
34
-class ParsedTokensPrompt(TypedDict):
35
- type: Literal["tokens"]
36
- content: TokensPrompt
37
-
38
-
39
-class ParsedEmbedsPrompt(TypedDict):
40
- type: Literal["embeds"]
41
- content: EmbedsPrompt
42
-
43
-
44
-ParsedSingletonPrompt: TypeAlias = (
45
- ParsedStrPrompt | ParsedTextPrompt | ParsedTokensPrompt | ParsedEmbedsPrompt
46
-)
47
-
48
-
49
-def parse_singleton_prompt(prompt: SingletonPrompt) -> ParsedSingletonPrompt:
50
- if isinstance(prompt, str):
51
- return ParsedStrPrompt(type="str", content=prompt)
52
- elif isinstance(prompt, dict):
53
- # Type ignores are because mypy does not correctly infer the TypedDicts
54
- # Pyright does succeed.
55
- if "prompt_embeds" in prompt:
56
- return ParsedEmbedsPrompt(type="embeds", content=prompt) # type: ignore[typeddict-item]
57
- elif "prompt_token_ids" in prompt:
58
- return ParsedTokensPrompt(type="tokens", content=prompt) # type: ignore[typeddict-item]
59
- elif "prompt" in prompt:
60
- return ParsedTextPrompt(type="text", content=prompt)
61
- raise TypeError(
62
- "inputs must be a string, TextPrompt, TokensPrompt, or EmbedsPrompt"
63
- )
64
-
65
-
66
-def is_explicit_encoder_decoder_prompt(
67
- prompt: PromptType,
68
-) -> TypeIs[ExplicitEncoderDecoderPrompt]:
69
- return isinstance(prompt, dict) and "encoder_prompt" in prompt
70
-
71
-
72
-def split_enc_dec_prompt(
73
- prompt: PromptType,
74
-) -> tuple[SingletonPrompt, SingletonPrompt | None]:
75
- if isinstance(prompt, str):
76
- return prompt, None
77
-
78
- if "encoder_prompt" in prompt and "decoder_prompt" in prompt:
79
- # NOTE: This passes pyright but not mypy
80
- return (
81
- prompt["encoder_prompt"], # type: ignore[typeddict-item]
82
- prompt["decoder_prompt"], # type: ignore[typeddict-item]
83
- )
84
-
85
- return prompt, None
4
+from .data import ProcessorInputs, SingletonInputs
5
6
7
def split_enc_dec_inputs(
@@ -96,30 +15,3 @@ def split_enc_dec_inputs(
15
)
16
17
return None, inputs
99
-
100
-
101
-class PromptComponents(NamedTuple):
102
- text: str | None = None
103
- token_ids: list[int] | None = None
104
- embeds: "torch.Tensor | None" = None
105
-
106
-
107
-def get_prompt_components(prompt: PromptType) -> PromptComponents:
108
- if isinstance(prompt, str):
109
- return PromptComponents(text=prompt)
110
-
111
- if encoder_prompt := prompt.get("encoder_prompt"):
112
- return get_prompt_components(encoder_prompt) # type: ignore[arg-type]
113
-
114
- return PromptComponents(
115
- text=prompt.get("prompt"), # type: ignore[arg-type]
116
- token_ids=prompt.get("prompt_token_ids"), # type: ignore[arg-type]
117
- embeds=prompt.get("prompt_embeds"),
118
- )
119
-
120
-
121
-def get_prompt_len(prompt: TokensPrompt | EmbedsPrompt):
122
- return length_from_prompt_token_ids_or_embeds(
123
- prompt.get("prompt_token_ids"), # type: ignore[arg-type]
124
- prompt.get("prompt_embeds"), # type: ignore[arg-type]
125
- )
vllm/inputs/preprocess.py
+66
-64
@@ -2,43 +2,51 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
from collections.abc import Mapping
5
-from typing import Any
5
+from typing import Any, overload
6
7
from typing_extensions import assert_never
8
9
from vllm.config import ModelConfig, ObservabilityConfig
10
-from vllm.inputs.parse import split_enc_dec_prompt
10
from vllm.logger import init_logger
11
from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry
12
from vllm.multimodal.cache import BaseMultiModalProcessorCache
13
from vllm.multimodal.inputs import (
14
MultiModalDataDict,
16
- MultiModalEncDecInputs,
15
MultiModalInputs,
16
MultiModalUUIDDict,
17
)
18
from vllm.multimodal.processing import BaseMultiModalProcessor
19
from vllm.renderers import renderer_from_config
20
+from vllm.renderers.inputs import (
21
+ DecoderDictPrompt,
22
+ DecoderOnlyDictPrompt,
23
+ DictPrompt,
24
+ EncoderDecoderDictPrompt,
25
+ EncoderDictPrompt,
26
+ SingletonDictPrompt,
27
+ TokPrompt,
28
+)
29
+from vllm.renderers.inputs.preprocess import parse_dec_only_prompt, parse_enc_dec_prompt
30
from vllm.tokenizers import TokenizerLike
31
from vllm.utils.jsontree import json_iter_leaves
32
from vllm.v1.metrics.stats import MultiModalCacheStats
33
34
from .data import (
35
+ DecoderInputs,
36
DecoderOnlyInputs,
37
EmbedsInputs,
38
EmbedsPrompt,
39
EncoderDecoderInputs,
40
+ EncoderInputs,
41
ProcessorInputs,
42
PromptType,
43
SingletonInputs,
34
- SingletonPrompt,
44
TextPrompt,
45
TokenInputs,
46
TokensPrompt,
47
embeds_inputs,
48
token_inputs,
49
)
41
-from .parse import is_explicit_encoder_decoder_prompt, parse_singleton_prompt
50
51
logger = init_logger(__name__)
52
@@ -328,9 +336,36 @@ class InputPreprocessor:
336
337
return inputs
338
339
+ @overload
340
def _prompt_to_llm_inputs(
341
self,
333
- prompt: SingletonPrompt,
342
+ prompt: EncoderDictPrompt,
343
+ tokenization_kwargs: dict[str, Any] | None = None,
344
+ *,
345
+ mm_uuids: MultiModalUUIDDict | None = None,
346
+ ) -> EncoderInputs: ...
347
+
348
+ @overload
349
+ def _prompt_to_llm_inputs( # type: ignore[misc]
350
+ self,
351
+ prompt: DecoderDictPrompt,
352
+ tokenization_kwargs: dict[str, Any] | None = None,
353
+ *,
354
+ mm_uuids: MultiModalUUIDDict | None = None,
355
+ ) -> DecoderInputs: ...
356
+
357
+ @overload
358
+ def _prompt_to_llm_inputs( # type: ignore[misc]
359
+ self,
360
+ prompt: DecoderOnlyDictPrompt,
361
+ tokenization_kwargs: dict[str, Any] | None = None,
362
+ *,
363
+ mm_uuids: MultiModalUUIDDict | None = None,
364
+ ) -> DecoderOnlyInputs: ...
365
+
366
+ def _prompt_to_llm_inputs(
367
+ self,
368
+ prompt: SingletonDictPrompt,
369
tokenization_kwargs: dict[str, Any] | None = None,
370
*,
371
mm_uuids: MultiModalUUIDDict | None = None,
@@ -346,34 +381,25 @@ class InputPreprocessor:
381
382
* [`SingletonInputs`][vllm.inputs.data.SingletonInputs] instance
383
"""
349
- parsed = parse_singleton_prompt(prompt)
384
+ if "prompt_embeds" in prompt:
385
+ return self._process_embeds(prompt) # type: ignore[arg-type]
386
351
- if parsed["type"] == "embeds":
352
- return self._process_embeds(parsed["content"])
353
- if parsed["type"] == "tokens":
387
+ if "prompt_token_ids" in prompt:
388
return self._process_tokens(
355
- parsed["content"],
389
+ prompt, # type: ignore[arg-type]
390
mm_uuids=mm_uuids,
391
)
358
- if parsed["type"] == "text":
359
- return self._process_text(
360
- parsed["content"],
361
- tokenization_kwargs=tokenization_kwargs,
362
- mm_uuids=mm_uuids,
363
- )
364
- if parsed["type"] == "str":
392
+
393
+ if "prompt" in prompt:
394
return self._process_text(
366
- TextPrompt(prompt=parsed["content"]),
395
+ prompt, # type: ignore[arg-type]
396
tokenization_kwargs=tokenization_kwargs,
397
mm_uuids=mm_uuids,
398
)
399
371
- assert_never(parsed)
400
+ assert_never(prompt) # type: ignore[arg-type]
401
373
- def _validate_enc_inputs(
374
- self,
375
- inputs: SingletonInputs,
376
- ) -> TokenInputs | MultiModalEncDecInputs:
402
+ def _validate_enc_inputs(self, inputs: SingletonInputs) -> EncoderInputs:
403
if inputs["type"] == "embeds":
404
raise ValueError(
405
"Embedding inputs are not supported for encoder-decoder models"
@@ -387,10 +413,7 @@ class InputPreprocessor:
413
414
return inputs # type: ignore[return-value]
415
390
- def _validate_dec_inputs(
391
- self,
392
- inputs: SingletonInputs,
393
- ) -> TokenInputs | MultiModalInputs:
416
+ def _validate_dec_inputs(self, inputs: SingletonInputs) -> DecoderInputs:
417
if inputs["type"] == "embeds":
418
raise ValueError(
419
"Embedding inputs are not supported for encoder-decoder models"
@@ -403,14 +426,15 @@ class InputPreprocessor:
426
encoder_inputs: SingletonInputs,
427
decoder_inputs: SingletonInputs | None = None,
428
) -> EncoderDecoderInputs:
406
- if decoder_inputs is None:
407
- decoder_inputs = encoder_inputs
408
-
429
enc_inputs = self._validate_enc_inputs(encoder_inputs)
410
- dec_inputs = self._validate_dec_inputs(decoder_inputs)
430
412
- enc_inputs_new: TokenInputs | MultiModalEncDecInputs
413
- dec_inputs_new: TokenInputs | MultiModalInputs
431
+ if decoder_inputs is None:
432
+ dec_inputs: DecoderInputs = enc_inputs # type: ignore[assignment]
433
+ else:
434
+ dec_inputs = self._validate_dec_inputs(decoder_inputs)
435
+
436
+ enc_inputs_new: EncoderInputs
437
+ dec_inputs_new: DecoderInputs
438
439
if enc_inputs["type"] == "multimodal":
440
enc_inputs_new = token_inputs(enc_inputs["encoder_prompt_token_ids"])
@@ -437,7 +461,7 @@ class InputPreprocessor:
461
462
def _process_encoder_decoder_prompt(
463
self,
440
- prompt: PromptType,
464
+ prompt: EncoderDecoderDictPrompt,
465
tokenization_kwargs: dict[str, Any] | None = None,
466
*,
467
mm_uuids: MultiModalUUIDDict | None = None,
@@ -448,24 +472,6 @@ class InputPreprocessor:
472
[`EncoderDecoderInputs`][vllm.inputs.data.EncoderDecoderInputs]
473
instance.
474
451
- There are two types of input prompts:
452
- singleton prompts which carry only the
453
- encoder prompt, and explicit encoder/decoder
454
- prompts which carry both the encoder and the
455
- decoder prompts as member variables.
456
-
457
- This function handles the following scenarios:
458
- * Singleton encoder prompt: extract encoder prompt
459
- token ids & infer default decoder prompt token ids
460
- * Explicit encoder/decoder prompt: extract encoder
461
- and decoder prompt token ids
462
-
463
- Note that for Explicit encoder/decoder prompts,
464
- each sub-prompt (encoder or decoder prompt) can
465
- have any possible singleton type; thus this
466
- method relies on helper functions to obtain
467
- token ids for the sub-prompts.
468
-
475
Arguments:
476
477
* prompt: an input prompt
@@ -475,7 +481,8 @@ class InputPreprocessor:
481
* [`EncoderDecoderInputs`][vllm.inputs.data.EncoderDecoderInputs]
482
instance
483
"""
478
- encoder_prompt, decoder_prompt = split_enc_dec_prompt(prompt)
484
+ encoder_prompt = prompt["encoder_prompt"]
485
+ decoder_prompt = prompt["decoder_prompt"]
486
487
return self._build_enc_dec_inputs(
488
encoder_inputs=self._prompt_to_llm_inputs(
@@ -495,7 +502,7 @@ class InputPreprocessor:
502
503
def _process_decoder_only_prompt(
504
self,
498
- prompt: SingletonPrompt,
505
+ prompt: DecoderOnlyDictPrompt,
506
tokenization_kwargs: dict[str, Any] | None = None,
507
*,
508
mm_uuids: MultiModalUUIDDict | None = None,
@@ -521,7 +528,7 @@ class InputPreprocessor:
528
529
def _preprocess(
530
self,
524
- prompt: PromptType,
531
+ prompt: PromptType | DictPrompt | TokPrompt,
532
tokenization_kwargs: dict[str, Any] | None = None,
533
*,
534
mm_uuids: MultiModalUUIDDict | None = None,
@@ -530,25 +537,20 @@ class InputPreprocessor:
537
# Encoder-decoder model requires special mapping of
538
# input prompts to encoder & decoder.
539
return self._process_encoder_decoder_prompt(
533
- prompt,
540
+ parse_enc_dec_prompt(prompt),
541
tokenization_kwargs,
542
mm_uuids=mm_uuids,
543
)
544
538
- if is_explicit_encoder_decoder_prompt(prompt):
539
- raise ValueError(
540
- "Cannot pass encoder-decoder prompt to decoder-only models"
541
- )
542
-
545
return self._process_decoder_only_prompt(
544
- prompt,
546
+ parse_dec_only_prompt(prompt),
547
tokenization_kwargs=tokenization_kwargs,
548
mm_uuids=mm_uuids,
549
)
550
551
def preprocess(
552
self,
551
- prompt: PromptType,
553
+ prompt: PromptType | DictPrompt | TokPrompt,
554
tokenization_kwargs: dict[str, Any] | None = None,
555
*,
556
mm_uuids: MultiModalUUIDDict | None = None,
vllm/multimodal/inputs.py
+6
-7
@@ -20,7 +20,7 @@ from typing import (
20
21
import numpy as np
22
from PIL.Image import Image
23
-from typing_extensions import NotRequired, TypeVar
23
+from typing_extensions import TypeVar
24
25
from vllm.utils.collection_utils import is_list_of
26
from vllm.utils.import_utils import LazyLoader
@@ -32,9 +32,13 @@ if TYPE_CHECKING:
32
import torch
33
import torch.types
34
from transformers.feature_extraction_utils import BatchFeature
35
+
36
+ from vllm.inputs.data import _InputOptions
37
else:
38
torch = LazyLoader("torch", globals(), "torch")
39
40
+ _InputOptions = dict
41
+
42
_T = TypeVar("_T")
43
44
HfImageItem: TypeAlias = Union["Image", np.ndarray, "torch.Tensor"]
@@ -1059,7 +1063,7 @@ A dictionary containing per-item placeholder ranges for each modality.
1063
"""
1064
1065
1062
-class MultiModalInputs(TypedDict):
1066
+class MultiModalInputs(_InputOptions):
1067
"""
1068
Represents the outputs of
1069
[`BaseMultiModalProcessor`][vllm.multimodal.processing.BaseMultiModalProcessor],
@@ -1084,11 +1088,6 @@ class MultiModalInputs(TypedDict):
1088
`prompt_token_ids`.
1089
"""
1090
1087
- cache_salt: NotRequired[str]
1088
- """
1089
- Optional cache salt to be used for prefix caching.
1090
- """
1091
-
1091
1092
class MultiModalEncDecInputs(MultiModalInputs):
1093
"""
vllm/platforms/interface.py
+2
-1
@@ -19,6 +19,7 @@ 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
23
from vllm.sampling_params import SamplingParams
24
from vllm.utils.argparse_utils import FlexibleArgumentParser
25
from vllm.v1.attention.selector import AttentionSelectorConfig
@@ -565,7 +566,7 @@ class Platform:
566
@classmethod
567
def validate_request(
568
cls,
568
- prompt: "PromptType",
569
+ prompt: "PromptType | DictPrompt | TokPrompt",
570
params: "SamplingParams | PoolingParams",
571
processed_inputs: "ProcessorInputs",
572
) -> None:
vllm/renderers/deepseek_v32.py
+6
-5
@@ -9,11 +9,12 @@ from vllm.entrypoints.chat_utils import (
9
parse_chat_messages,
10
parse_chat_messages_async,
11
)
12
-from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
12
from vllm.logger import init_logger
13
from vllm.tokenizers import cached_get_tokenizer
14
from vllm.tokenizers.deepseek_v32 import DeepseekV32Tokenizer
15
16
+from .inputs import DictPrompt
17
+from .inputs.preprocess import parse_dec_only_prompt
18
from .params import ChatParams
19
from .protocol import BaseRenderer
20
@@ -61,7 +62,7 @@ class DeepseekV32Renderer(BaseRenderer):
62
self,
63
messages: list[ChatCompletionMessageParam],
64
params: ChatParams,
64
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
65
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
66
tokenizer = self.get_tokenizer()
67
conversation, mm_data, mm_uuids = parse_chat_messages(
68
messages,
@@ -75,7 +76,7 @@ class DeepseekV32Renderer(BaseRenderer):
76
**params.get_apply_chat_template_kwargs(),
77
)
78
78
- prompt = self.render_completion(prompt_raw)
79
+ prompt = parse_dec_only_prompt(prompt_raw)
80
if mm_data is not None:
81
prompt["multi_modal_data"] = mm_data
82
if mm_uuids is not None:
@@ -87,7 +88,7 @@ class DeepseekV32Renderer(BaseRenderer):
88
self,
89
messages: list[ChatCompletionMessageParam],
90
params: ChatParams,
90
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
91
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
92
tokenizer = self.get_tokenizer()
93
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
94
messages,
@@ -101,7 +102,7 @@ class DeepseekV32Renderer(BaseRenderer):
102
**params.get_apply_chat_template_kwargs(),
103
)
104
104
- prompt = self.render_completion(prompt_raw)
105
+ prompt = parse_dec_only_prompt(prompt_raw)
106
if mm_data is not None:
107
prompt["multi_modal_data"] = mm_data
108
if mm_uuids is not None:
vllm/renderers/grok2.py
+6
-5
@@ -9,11 +9,12 @@ from vllm.entrypoints.chat_utils import (
9
parse_chat_messages,
10
parse_chat_messages_async,
11
)
12
-from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
12
from vllm.logger import init_logger
13
from vllm.tokenizers import cached_get_tokenizer
14
from vllm.tokenizers.grok2 import Grok2Tokenizer
15
16
+from .inputs import DictPrompt
17
+from .inputs.preprocess import parse_dec_only_prompt
18
from .params import ChatParams
19
from .protocol import BaseRenderer
20
@@ -61,7 +62,7 @@ class Grok2Renderer(BaseRenderer):
62
self,
63
messages: list[ChatCompletionMessageParam],
64
params: ChatParams,
64
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
65
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
66
tokenizer = self.get_tokenizer()
67
conversation, mm_data, mm_uuids = parse_chat_messages(
68
messages,
@@ -75,7 +76,7 @@ class Grok2Renderer(BaseRenderer):
76
**params.get_apply_chat_template_kwargs(),
77
)
78
78
- prompt = self.render_completion(prompt_raw)
79
+ prompt = parse_dec_only_prompt(prompt_raw)
80
if mm_data is not None:
81
prompt["multi_modal_data"] = mm_data
82
if mm_uuids is not None:
@@ -87,7 +88,7 @@ class Grok2Renderer(BaseRenderer):
88
self,
89
messages: list[ChatCompletionMessageParam],
90
params: ChatParams,
90
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
91
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
92
tokenizer = self.get_tokenizer()
93
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
94
messages,
@@ -101,7 +102,7 @@ class Grok2Renderer(BaseRenderer):
102
**params.get_apply_chat_template_kwargs(),
103
)
104
104
- prompt = self.render_completion(prompt_raw)
105
+ prompt = parse_dec_only_prompt(prompt_raw)
106
if mm_data is not None:
107
prompt["multi_modal_data"] = mm_data
108
if mm_uuids is not None:
vllm/renderers/hf.py
+6
-5
@@ -25,7 +25,6 @@ from vllm.entrypoints.chat_utils import (
25
parse_chat_messages,
26
parse_chat_messages_async,
27
)
28
-from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
28
from vllm.logger import init_logger
29
from vllm.tokenizers import cached_get_tokenizer
30
from vllm.tokenizers.hf import CachedHfTokenizer, HfTokenizer
@@ -33,6 +32,8 @@ from vllm.transformers_utils.chat_templates import get_chat_template_fallback_pa
32
from vllm.transformers_utils.processor import cached_get_processor
33
from vllm.utils.func_utils import supports_kw
34
35
+from .inputs import DictPrompt
36
+from .inputs.preprocess import parse_dec_only_prompt
37
from .params import ChatParams
38
from .protocol import BaseRenderer
39
@@ -632,7 +633,7 @@ class HfRenderer(BaseRenderer):
633
self,
634
messages: list[ChatCompletionMessageParam],
635
params: ChatParams,
635
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
636
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
637
model_config = self.config
638
tokenizer = self.get_tokenizer()
639
@@ -674,7 +675,7 @@ class HfRenderer(BaseRenderer):
675
video_placeholder,
676
)
677
677
- prompt = self.render_completion(prompt_raw)
678
+ prompt = parse_dec_only_prompt(prompt_raw)
679
if mm_data is not None:
680
prompt["multi_modal_data"] = mm_data
681
if mm_uuids is not None:
@@ -686,7 +687,7 @@ class HfRenderer(BaseRenderer):
687
self,
688
messages: list[ChatCompletionMessageParam],
689
params: ChatParams,
689
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
690
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
691
model_config = self.config
692
tokenizer = self.get_tokenizer()
693
@@ -726,7 +727,7 @@ class HfRenderer(BaseRenderer):
727
video_placeholder,
728
)
729
729
- prompt = self.render_completion(prompt_raw)
730
+ prompt = parse_dec_only_prompt(prompt_raw)
731
if mm_data is not None:
732
prompt["multi_modal_data"] = mm_data
733
if mm_uuids is not None:
vllm/renderers/inputs/__init__.py
new
+33
@@ -0,0 +1,33 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from .preprocess import (
4
+ DecoderDictPrompt,
5
+ DecoderOnlyDictPrompt,
6
+ DictPrompt,
7
+ EncoderDecoderDictPrompt,
8
+ EncoderDictPrompt,
9
+ SingletonDictPrompt,
10
+)
11
+from .tokenize import (
12
+ DecoderOnlyTokPrompt,
13
+ DecoderTokPrompt,
14
+ EncoderDecoderTokPrompt,
15
+ EncoderTokPrompt,
16
+ SingletonTokPrompt,
17
+ TokPrompt,
18
+)
19
+
20
+__all__ = [
21
+ "DecoderOnlyDictPrompt",
22
+ "EncoderDictPrompt",
23
+ "DecoderDictPrompt",
24
+ "EncoderDecoderDictPrompt",
25
+ "SingletonDictPrompt",
26
+ "DictPrompt",
27
+ "DecoderOnlyTokPrompt",
28
+ "EncoderTokPrompt",
29
+ "DecoderTokPrompt",
30
+ "EncoderDecoderTokPrompt",
31
+ "SingletonTokPrompt",
32
+ "TokPrompt",
33
+]
vllm/renderers/inputs/preprocess.py
new
+255
@@ -0,0 +1,255 @@
1
+"""
2
+Schemas and utilites for preprocessing inputs.
3
+"""
4
+
5
+# SPDX-License-Identifier: Apache-2.0
6
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
7
+from collections.abc import Sequence
8
+from typing import TYPE_CHECKING, NamedTuple, TypeAlias, TypedDict, overload
9
+
10
+from vllm.inputs import (
11
+ EmbedsPrompt,
12
+ ExplicitEncoderDecoderPrompt,
13
+ PromptType,
14
+ SingletonPrompt,
15
+ TextPrompt,
16
+ TokensPrompt,
17
+)
18
+from vllm.utils import length_from_prompt_token_ids_or_embeds
19
+from vllm.utils.collection_utils import is_list_of
20
+
21
+if TYPE_CHECKING:
22
+ import torch
23
+
24
+ from vllm.config import ModelConfig
25
+ from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
26
+
27
+
28
+@overload
29
+def prompt_to_seq(
30
+ prompt_or_prompts: SingletonPrompt | bytes | Sequence[SingletonPrompt | bytes],
31
+) -> Sequence[SingletonPrompt]: ...
32
+
33
+
34
+@overload
35
+def prompt_to_seq( # type: ignore[misc]
36
+ prompt_or_prompts: ExplicitEncoderDecoderPrompt
37
+ | Sequence[ExplicitEncoderDecoderPrompt],
38
+) -> Sequence[ExplicitEncoderDecoderPrompt]: ...
39
+
40
+
41
+@overload
42
+def prompt_to_seq( # type: ignore[misc]
43
+ prompt_or_prompts: PromptType | Sequence[PromptType],
44
+) -> Sequence[PromptType]: ...
45
+
46
+
47
+def prompt_to_seq(
48
+ prompt_or_prompts: PromptType | bytes | Sequence[PromptType | bytes],
49
+) -> Sequence[PromptType]:
50
+ if isinstance(prompt_or_prompts, (dict, str, bytes)) or (
51
+ len(prompt_or_prompts) > 0 and is_list_of(prompt_or_prompts, int)
52
+ ):
53
+ return [prompt_or_prompts] # type: ignore[list-item]
54
+
55
+ return prompt_or_prompts # type: ignore[return-value]
56
+
57
+
58
+def conversation_to_seq(
59
+ conversation_or_conversations: list["ChatCompletionMessageParam"]
60
+ | Sequence[list["ChatCompletionMessageParam"]],
61
+) -> Sequence[list["ChatCompletionMessageParam"]]:
62
+ if len(conversation_or_conversations) > 0 and is_list_of(
63
+ conversation_or_conversations, dict
64
+ ):
65
+ return [conversation_or_conversations] # type: ignore[list-item]
66
+
67
+ return conversation_or_conversations # type: ignore[return-value]
68
+
69
+
70
+DecoderOnlyDictPrompt: TypeAlias = TextPrompt | TokensPrompt | EmbedsPrompt
71
+"""
72
+A [`DecoderOnlyPrompt`][vllm.inputs.data.DecoderOnlyPrompt]
73
+that has been standardized into a dictionary.
74
+"""
75
+
76
+
77
+EncoderDictPrompt: TypeAlias = TextPrompt | TokensPrompt
78
+"""
79
+A [`EncoderPrompt`][vllm.inputs.data.EncoderPrompt]
80
+that has been standardized into a dictionary.
81
+"""
82
+
83
+
84
+DecoderDictPrompt: TypeAlias = TextPrompt | TokensPrompt
85
+"""
86
+A [`DecoderPrompt`][vllm.inputs.data.DecoderPrompt]
87
+that has been standardized into a dictionary.
88
+"""
89
+
90
+
91
+class EncoderDecoderDictPrompt(TypedDict):
92
+ """
93
+ A [`EncoderDecoderPrompt`][vllm.inputs.data.EncoderDecoderPrompt]
94
+ that has been standardized into a dictionary.
95
+ """
96
+
97
+ encoder_prompt: EncoderDictPrompt
98
+
99
+ decoder_prompt: DecoderDictPrompt | None
100
+
101
+
102
+SingletonDictPrompt: TypeAlias = (
103
+ DecoderOnlyDictPrompt | EncoderDictPrompt | DecoderDictPrompt
104
+)
105
+"""
106
+A [`SingletonPrompt`][vllm.inputs.data.SingletonPrompt]
107
+that has been standardized into a dictionary.
108
+"""
109
+
110
+
111
+DictPrompt: TypeAlias = DecoderOnlyDictPrompt | EncoderDecoderDictPrompt
112
+"""
113
+A [`PromptType`][vllm.inputs.data.PromptType]
114
+that has been standardized into a dictionary.
115
+"""
116
+
117
+
118
+def parse_dec_only_prompt(prompt: object) -> DecoderOnlyDictPrompt:
119
+ """
120
+ Parse a prompt for a decoder-only model and normalize it to a dictionary.
121
+ """
122
+ if isinstance(prompt, str):
123
+ return TextPrompt(prompt=prompt)
124
+
125
+ if isinstance(prompt, list):
126
+ if not is_list_of(prompt, int):
127
+ raise TypeError("Token prompt should be a list of integers")
128
+
129
+ return TokensPrompt(prompt_token_ids=prompt)
130
+
131
+ if isinstance(prompt, dict):
132
+ if "encoder_prompt" in prompt:
133
+ raise TypeError("Cannot pass encoder-decoder prompt to decoder-only models")
134
+
135
+ if (
136
+ "prompt" in prompt
137
+ or "prompt_token_ids" in prompt
138
+ or "prompt_embeds" in prompt
139
+ ):
140
+ return prompt # type: ignore[return-value]
141
+
142
+ raise TypeError("Prompt dictionary must contain text, tokens, or embeddings")
143
+
144
+ raise TypeError("Prompt should be a string, list of tokens, or dictionary")
145
+
146
+
147
+def _parse_enc_prompt(prompt: object) -> EncoderDictPrompt:
148
+ if isinstance(prompt, str):
149
+ return TextPrompt(prompt=prompt)
150
+
151
+ if isinstance(prompt, list):
152
+ if not is_list_of(prompt, int):
153
+ raise TypeError("Token prompt should be a list of integers")
154
+
155
+ return TokensPrompt(prompt_token_ids=prompt)
156
+
157
+ if isinstance(prompt, dict):
158
+ if "prompt_embeds" in prompt:
159
+ raise TypeError("Cannot pass embeddings prompt to encoder-decoder models")
160
+
161
+ if "prompt" in prompt or "prompt_token_ids" in prompt:
162
+ return prompt # type: ignore[return-value]
163
+
164
+ raise TypeError("Prompt dictionary must contain text or tokens")
165
+
166
+ raise TypeError("Prompt should be a string, list of tokens, or dictionary")
167
+
168
+
169
+def _parse_dec_prompt(prompt: object) -> DecoderDictPrompt:
170
+ if isinstance(prompt, str):
171
+ return TextPrompt(prompt=prompt)
172
+
173
+ if isinstance(prompt, list):
174
+ if not is_list_of(prompt, int):
175
+ raise TypeError("Token prompt should be a list of integers")
176
+
177
+ return TokensPrompt(prompt_token_ids=prompt)
178
+
179
+ if isinstance(prompt, dict):
180
+ if "prompt_embeds" in prompt:
181
+ raise TypeError("Cannot pass embeddings prompt to encoder-decoder models")
182
+
183
+ if (
184
+ "multi_modal_data" in prompt
185
+ or "mm_processor_kwargs" in prompt
186
+ or "multi_modal_uuids" in prompt
187
+ ):
188
+ raise TypeError("Cannot pass multi-modal inputs to decoder prompt")
189
+
190
+ if "prompt" in prompt or "prompt_token_ids" in prompt:
191
+ return prompt # type: ignore[return-value]
192
+
193
+ raise TypeError("Prompt dictionary must contain text or tokens")
194
+
195
+ raise TypeError("Prompt should be a string, list of tokens, or dictionary")
196
+
197
+
198
+def parse_enc_dec_prompt(prompt: object) -> EncoderDecoderDictPrompt:
199
+ """
200
+ Parse a prompt for an encoder-decoder model and normalize it to a dictionary.
201
+ """
202
+ 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]
205
+ else:
206
+ enc_prompt = prompt
207
+ dec_prompt = None
208
+
209
+ return EncoderDecoderDictPrompt(
210
+ encoder_prompt=_parse_enc_prompt(enc_prompt),
211
+ decoder_prompt=None if dec_prompt is None else _parse_dec_prompt(dec_prompt),
212
+ )
213
+
214
+
215
+def parse_model_prompt(model_config: "ModelConfig", prompt: object):
216
+ if model_config.is_encoder_decoder:
217
+ return parse_enc_dec_prompt(prompt)
218
+
219
+ return parse_dec_only_prompt(prompt)
220
+
221
+
222
+class PromptComponents(NamedTuple):
223
+ text: str | None = None
224
+ token_ids: list[int] | None = None
225
+ embeds: "torch.Tensor | None" = None
226
+
227
+
228
+def extract_prompt_components(
229
+ model_config: "ModelConfig",
230
+ prompt: object,
231
+) -> PromptComponents:
232
+ target_prompt = (
233
+ parse_enc_dec_prompt(prompt)["encoder_prompt"]
234
+ if model_config.is_encoder_decoder
235
+ else parse_dec_only_prompt(prompt)
236
+ )
237
+
238
+ return PromptComponents(
239
+ text=target_prompt.get("prompt"),
240
+ token_ids=target_prompt.get("prompt_token_ids"), # type: ignore[arg-type]
241
+ embeds=target_prompt.get("prompt_embeds"),
242
+ )
243
+
244
+
245
+def extract_prompt_len(model_config: "ModelConfig", prompt: object):
246
+ target_prompt = (
247
+ parse_enc_dec_prompt(prompt)["encoder_prompt"]
248
+ if model_config.is_encoder_decoder
249
+ else parse_dec_only_prompt(prompt)
250
+ )
251
+
252
+ return length_from_prompt_token_ids_or_embeds(
253
+ target_prompt.get("prompt_token_ids"), # type: ignore[arg-type]
254
+ target_prompt.get("prompt_embeds"),
255
+ )
vllm/renderers/inputs/tokenize.py
new
+57
@@ -0,0 +1,57 @@
1
+"""
2
+Schemas and utilites for tokenization inputs.
3
+"""
4
+
5
+# SPDX-License-Identifier: Apache-2.0
6
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
7
+from typing import TypeAlias, TypedDict
8
+
9
+from vllm.inputs import EmbedsPrompt, TokensPrompt
10
+
11
+DecoderOnlyTokPrompt: TypeAlias = TokensPrompt | EmbedsPrompt
12
+"""
13
+A [`DecoderOnlyDictPrompt`][vllm.renderers.inputs.preprocess.DecoderOnlyDictPrompt]
14
+that has been tokenized.
15
+"""
16
+
17
+
18
+EncoderTokPrompt: TypeAlias = TokensPrompt
19
+"""
20
+A [`EncoderDictPrompt`][vllm.renderers.inputs.preprocess.EncoderDictPrompt]
21
+that has been tokenized.
22
+"""
23
+
24
+
25
+DecoderTokPrompt: TypeAlias = TokensPrompt
26
+"""
27
+A [`DecoderDictPrompt`][vllm.renderers.inputs.preprocess.DecoderDictPrompt]
28
+that has been tokenized.
29
+"""
30
+
31
+
32
+class EncoderDecoderTokPrompt(TypedDict):
33
+ """
34
+ A
35
+ [`EncoderDecoderDictPrompt`][vllm.renderers.inputs.preprocess.EncoderDecoderDictPrompt]
36
+ that has been tokenized.
37
+ """
38
+
39
+ encoder_prompt: EncoderTokPrompt
40
+
41
+ decoder_prompt: DecoderTokPrompt | None
42
+
43
+
44
+SingletonTokPrompt: TypeAlias = (
45
+ DecoderOnlyTokPrompt | EncoderTokPrompt | DecoderTokPrompt
46
+)
47
+"""
48
+A [`SingletonDictPrompt`][vllm.renderers.inputs.preprocess.SingletonDictPrompt]
49
+that has been tokenized.
50
+"""
51
+
52
+
53
+TokPrompt: TypeAlias = DecoderOnlyTokPrompt | EncoderDecoderTokPrompt
54
+"""
55
+A [`DictPrompt`][vllm.renderers.inputs.preprocess.DictPrompt]
56
+that has been tokenized.
57
+"""
vllm/renderers/mistral.py
+6
-5
@@ -10,12 +10,13 @@ from vllm.entrypoints.chat_utils import (
10
parse_chat_messages,
11
parse_chat_messages_async,
12
)
13
-from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
13
from vllm.logger import init_logger
14
from vllm.tokenizers import cached_get_tokenizer
15
from vllm.tokenizers.mistral import MistralTokenizer
16
from vllm.utils.async_utils import make_async
17
18
+from .inputs import DictPrompt
19
+from .inputs.preprocess import parse_dec_only_prompt
20
from .params import ChatParams
21
from .protocol import BaseRenderer
22
@@ -95,7 +96,7 @@ class MistralRenderer(BaseRenderer):
96
self,
97
messages: list[ChatCompletionMessageParam],
98
params: ChatParams,
98
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
99
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
100
tokenizer = self.get_tokenizer()
101
conversation, mm_data, mm_uuids = parse_chat_messages(
102
messages,
@@ -109,7 +110,7 @@ class MistralRenderer(BaseRenderer):
110
**params.get_apply_chat_template_kwargs(),
111
)
112
112
- prompt = self.render_completion(prompt_raw)
113
+ prompt = parse_dec_only_prompt(prompt_raw)
114
if mm_data is not None:
115
prompt["multi_modal_data"] = mm_data
116
if mm_uuids is not None:
@@ -121,7 +122,7 @@ class MistralRenderer(BaseRenderer):
122
self,
123
messages: list[ChatCompletionMessageParam],
124
params: ChatParams,
124
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
125
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
126
tokenizer = self.get_tokenizer()
127
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
128
messages,
@@ -135,7 +136,7 @@ class MistralRenderer(BaseRenderer):
136
**params.get_apply_chat_template_kwargs(),
137
)
138
138
- prompt = self.render_completion(prompt_raw)
139
+ prompt = parse_dec_only_prompt(prompt_raw)
140
if mm_data is not None:
141
prompt["multi_modal_data"] = mm_data
142
if mm_uuids is not None:
vllm/renderers/protocol.py
+162
-79
@@ -2,14 +2,20 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import asyncio
4
from abc import ABC, abstractmethod
5
-from typing import TYPE_CHECKING, Any
5
+from collections.abc import Sequence
6
+from typing import TYPE_CHECKING, Any, overload
7
8
from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
9
from vllm.tokenizers import TokenizerLike
10
from vllm.utils.async_utils import AsyncMicrobatchTokenizer
10
-from vllm.utils.collection_utils import is_list_of
11
12
from .embed_utils import safe_load_prompt_embeds
13
+from .inputs import (
14
+ DictPrompt,
15
+ EncoderDecoderDictPrompt,
16
+ EncoderDecoderTokPrompt,
17
+ TokPrompt,
18
+)
19
from .params import ChatParams, TokenizeParams
20
21
if TYPE_CHECKING:
@@ -57,140 +63,217 @@ class BaseRenderer(ABC):
63
return self._async_tokenizer
64
65
# Step 1: Convert raw inputs to prompts
60
- def render_completion(
66
+ def render_prompt(
67
self,
62
- prompt_raw: str | list[int] | bytes,
63
- ) -> TextPrompt | TokensPrompt | EmbedsPrompt:
64
- error_msg = "Each prompt must be a string or an array of tokens"
68
+ prompt: DictPrompt | bytes,
69
+ ) -> DictPrompt:
70
+ if isinstance(prompt, bytes):
71
+ embeds = safe_load_prompt_embeds(self.config, prompt)
72
+ prompt = EmbedsPrompt(prompt_embeds=embeds)
73
66
- if isinstance(prompt_raw, str):
67
- return TextPrompt(prompt=prompt_raw)
74
+ return prompt
75
69
- if isinstance(prompt_raw, list):
70
- if not is_list_of(prompt_raw, int):
71
- raise TypeError(error_msg)
72
-
73
- return TokensPrompt(prompt_token_ids=prompt_raw)
74
-
75
- if isinstance(prompt_raw, bytes):
76
- embeds = safe_load_prompt_embeds(self.config, prompt_raw)
77
- return EmbedsPrompt(prompt_embeds=embeds)
78
-
79
- raise TypeError(error_msg)
80
-
81
- def render_completions(
76
+ def render_prompts(
77
self,
83
- prompt_input: str | list[str] | list[int] | list[list[int]] | None = None,
84
- prompt_embeds: bytes | list[bytes] | None = None,
85
- ) -> list[TextPrompt | TokensPrompt | EmbedsPrompt]:
86
- prompts_raw = list[str | list[int] | bytes]()
87
-
88
- if prompt_embeds is not None: # embeds take higher priority
89
- if isinstance(prompt_embeds, bytes):
90
- prompts_raw.append(prompt_embeds)
91
- else:
92
- prompts_raw.extend(prompt_embeds)
93
-
94
- if prompt_input is not None:
95
- if isinstance(prompt_input, str) or (
96
- len(prompt_input) > 0 and is_list_of(prompt_input, int)
97
- ):
98
- prompts_raw.append(prompt_input) # type: ignore[arg-type]
99
- else:
100
- prompts_raw.extend(prompt_input) # type: ignore[arg-type]
101
-
102
- if len(prompts_raw) == 0:
78
+ prompts: Sequence[DictPrompt | bytes],
79
+ ) -> list[DictPrompt]:
80
+ if len(prompts) == 0:
81
raise ValueError("You must pass at least one prompt")
82
105
- return [self.render_completion(prompt) for prompt in prompts_raw]
83
+ return [self.render_prompt(prompt) for prompt in prompts]
84
107
- async def render_completions_async(
85
+ async def render_prompts_async(
86
self,
109
- prompt_input: str | list[str] | list[int] | list[list[int]] | None = None,
110
- prompt_embeds: bytes | list[bytes] | None = None,
111
- ) -> list[TextPrompt | TokensPrompt | EmbedsPrompt]:
112
- return self.render_completions(prompt_input, prompt_embeds)
87
+ prompts: Sequence[DictPrompt | bytes],
88
+ ) -> list[DictPrompt]:
89
+ return self.render_prompts(prompts)
90
91
@abstractmethod
92
def render_messages(
93
self,
94
messages: list["ChatCompletionMessageParam"],
95
params: ChatParams,
119
- ) -> tuple[list["ConversationMessage"], TextPrompt | TokensPrompt | EmbedsPrompt]:
96
+ ) -> tuple[list["ConversationMessage"], DictPrompt]:
97
raise NotImplementedError
98
99
async def render_messages_async(
100
self,
101
messages: list["ChatCompletionMessageParam"],
102
params: ChatParams,
126
- ) -> tuple[list["ConversationMessage"], TextPrompt | TokensPrompt | EmbedsPrompt]:
103
+ ) -> tuple[list["ConversationMessage"], DictPrompt]:
104
return self.render_messages(messages, params)
105
106
# Step 2: Tokenize prompts if necessary
107
+ def _tokenize_prompt(
108
+ self,
109
+ prompt: TextPrompt,
110
+ params: TokenizeParams,
111
+ ) -> TokensPrompt:
112
+ tokenizer = self.get_tokenizer()
113
+ prompt_token_ids = tokenizer.encode(
114
+ prompt["prompt"],
115
+ **params.get_encode_kwargs(),
116
+ )
117
+
118
+ return TokensPrompt(prompt_token_ids=prompt_token_ids, **prompt)
119
+
120
+ async def _tokenize_prompt_async(
121
+ self,
122
+ prompt: TextPrompt,
123
+ params: TokenizeParams,
124
+ ) -> TokensPrompt:
125
+ tokenizer = self.get_async_tokenizer()
126
+ prompt_token_ids = await tokenizer.encode(
127
+ prompt["prompt"],
128
+ **params.get_encode_kwargs(),
129
+ )
130
+
131
+ return TokensPrompt(prompt_token_ids=prompt_token_ids, **prompt)
132
+
133
+ def _detokenize_prompt(self, prompt: TokensPrompt) -> TokensPrompt:
134
+ tokenizer = self.get_tokenizer()
135
+ prompt["prompt"] = tokenizer.decode(prompt["prompt_token_ids"])
136
+
137
+ return prompt
138
+
139
+ async def _detokenize_prompt_async(self, prompt: TokensPrompt) -> TokensPrompt:
140
+ tokenizer = self.get_async_tokenizer()
141
+ prompt["prompt"] = await tokenizer.decode(prompt["prompt_token_ids"])
142
+
143
+ return prompt
144
+
145
+ def _tokenize_enc_dec_prompt(
146
+ self,
147
+ prompt: EncoderDecoderDictPrompt,
148
+ params: TokenizeParams,
149
+ ) -> EncoderDecoderTokPrompt:
150
+ enc_prompt, dec_prompt = (
151
+ self.tokenize_prompt(prompt["encoder_prompt"], params),
152
+ (
153
+ None
154
+ if prompt["decoder_prompt"] is None
155
+ else self.tokenize_prompt(prompt["decoder_prompt"], params)
156
+ ),
157
+ )
158
+
159
+ return EncoderDecoderTokPrompt(
160
+ encoder_prompt=enc_prompt,
161
+ decoder_prompt=dec_prompt,
162
+ )
163
+
164
+ async def _tokenize_enc_dec_prompt_async(
165
+ self,
166
+ prompt: EncoderDecoderDictPrompt,
167
+ params: TokenizeParams,
168
+ ) -> EncoderDecoderTokPrompt:
169
+ enc_prompt, dec_prompt = await asyncio.gather(
170
+ self.tokenize_prompt_async(prompt["encoder_prompt"], params),
171
+ (
172
+ asyncio.sleep(0)
173
+ if prompt["decoder_prompt"] is None
174
+ else self.tokenize_prompt_async(prompt["decoder_prompt"], params)
175
+ ),
176
+ )
177
+
178
+ return EncoderDecoderTokPrompt(
179
+ encoder_prompt=enc_prompt,
180
+ decoder_prompt=dec_prompt,
181
+ )
182
+
183
+ @overload
184
def tokenize_prompt(
185
self,
132
- prompt: TextPrompt | TokensPrompt | EmbedsPrompt,
186
+ prompt: TextPrompt | TokensPrompt,
187
params: TokenizeParams,
134
- ) -> TokensPrompt | EmbedsPrompt:
135
- if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
136
- prompt = params.apply_pre_tokenization(self.tokenizer, prompt)
188
+ ) -> TokensPrompt: ...
189
138
- tokenizer = self.get_tokenizer()
139
- prompt_token_ids = tokenizer.encode(
140
- prompt["prompt"],
141
- **params.get_encode_kwargs(),
142
- )
190
+ @overload
191
+ def tokenize_prompt( # type: ignore[misc]
192
+ self,
193
+ prompt: EmbedsPrompt,
194
+ params: TokenizeParams,
195
+ ) -> EmbedsPrompt: ...
196
144
- prompt = TokensPrompt(prompt_token_ids=prompt_token_ids, **prompt)
197
+ @overload
198
+ def tokenize_prompt( # type: ignore[misc]
199
+ self,
200
+ prompt: EncoderDecoderDictPrompt,
201
+ params: TokenizeParams,
202
+ ) -> EncoderDecoderTokPrompt: ...
203
+
204
+ def tokenize_prompt(
205
+ self,
206
+ prompt: DictPrompt,
207
+ params: TokenizeParams,
208
+ ) -> TokPrompt:
209
+ if "encoder_prompt" in prompt:
210
+ return self._tokenize_enc_dec_prompt(prompt, params) # type: ignore[arg-type]
211
+
212
+ if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
213
+ prompt = params.apply_pre_tokenization(self.tokenizer, prompt)
214
+ prompt = self._tokenize_prompt(prompt, params)
215
216
if params.needs_detokenization and "prompt" not in prompt:
217
if "prompt_token_ids" not in prompt:
218
raise RuntimeError("Cannot run detokenization on embeddings")
219
150
- tokenizer = self.get_tokenizer()
151
- prompt_text = tokenizer.decode(prompt["prompt_token_ids"]) # type: ignore[typeddict-item]
152
- prompt["prompt"] = prompt_text # type: ignore[typeddict-unknown-key]
220
+ prompt = self._detokenize_prompt(prompt) # type: ignore[arg-type]
221
222
return params.apply_post_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
223
224
def tokenize_prompts(
225
self,
158
- prompts: list[TextPrompt | TokensPrompt | EmbedsPrompt],
226
+ prompts: Sequence[DictPrompt],
227
params: TokenizeParams,
160
- ) -> list[TokensPrompt | EmbedsPrompt]:
228
+ ) -> list[TokPrompt]:
229
return [self.tokenize_prompt(prompt, params) for prompt in prompts]
230
231
+ @overload
232
async def tokenize_prompt_async(
233
self,
165
- prompt: TextPrompt | TokensPrompt | EmbedsPrompt,
234
+ prompt: TextPrompt | TokensPrompt,
235
params: TokenizeParams,
167
- ) -> TokensPrompt | EmbedsPrompt:
168
- if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
169
- prompt = params.apply_pre_tokenization(self.tokenizer, prompt)
236
+ ) -> TokensPrompt: ...
237
+
238
+ @overload
239
+ async def tokenize_prompt_async( # type: ignore[misc]
240
+ self,
241
+ prompt: EmbedsPrompt,
242
+ params: TokenizeParams,
243
+ ) -> EmbedsPrompt: ...
244
+
245
+ @overload
246
+ async def tokenize_prompt_async( # type: ignore[misc]
247
+ self,
248
+ prompt: EncoderDecoderDictPrompt,
249
+ params: TokenizeParams,
250
+ ) -> EncoderDecoderTokPrompt: ...
251
171
- tokenizer = self.get_async_tokenizer()
172
- prompt_token_ids = await tokenizer.encode(
173
- prompt["prompt"],
174
- **params.get_encode_kwargs(),
175
- )
252
+ async def tokenize_prompt_async(
253
+ self,
254
+ prompt: DictPrompt,
255
+ params: TokenizeParams,
256
+ ) -> TokPrompt:
257
+ if "encoder_prompt" in prompt:
258
+ return await self._tokenize_enc_dec_prompt_async(prompt, params) # type: ignore[arg-type]
259
177
- prompt = TokensPrompt(prompt_token_ids=prompt_token_ids, **prompt)
260
+ if "prompt_token_ids" not in prompt and "prompt_embeds" not in prompt:
261
+ prompt = params.apply_pre_tokenization(self.tokenizer, prompt)
262
+ prompt = await self._tokenize_prompt_async(prompt, params)
263
264
if params.needs_detokenization and "prompt" not in prompt:
265
if "prompt_token_ids" not in prompt:
266
raise RuntimeError("Cannot run detokenization on embeddings")
267
183
- tokenizer = self.get_async_tokenizer()
184
- prompt_text = await tokenizer.decode(prompt["prompt_token_ids"]) # type: ignore[typeddict-item]
185
- prompt["prompt"] = prompt_text # type: ignore[typeddict-unknown-key]
268
+ prompt = await self._detokenize_prompt_async(prompt) # type: ignore[arg-type]
269
270
return params.apply_post_tokenization(self.tokenizer, prompt) # type: ignore[arg-type]
271
272
async def tokenize_prompts_async(
273
self,
191
- prompts: list[TextPrompt | TokensPrompt | EmbedsPrompt],
274
+ prompts: Sequence[DictPrompt],
275
params: TokenizeParams,
193
- ) -> list[TokensPrompt | EmbedsPrompt]:
276
+ ) -> list[TokPrompt]:
277
return await asyncio.gather(
278
*(self.tokenize_prompt_async(prompt, params) for prompt in prompts)
279
)
vllm/renderers/terratorch.py
+6
-5
@@ -9,10 +9,11 @@ from vllm.entrypoints.chat_utils import (
9
parse_chat_messages,
10
parse_chat_messages_async,
11
)
12
-from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt
12
from vllm.logger import init_logger
13
from vllm.tokenizers import TokenizerLike
14
15
+from .inputs import DictPrompt
16
+from .inputs.preprocess import parse_dec_only_prompt
17
from .params import ChatParams
18
from .protocol import BaseRenderer
19
@@ -45,7 +46,7 @@ class TerratorchRenderer(BaseRenderer):
46
self,
47
messages: list[ChatCompletionMessageParam],
48
params: ChatParams,
48
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
49
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
50
model_config = self.config
51
52
conversation, mm_data, mm_uuids = parse_chat_messages(
@@ -54,7 +55,7 @@ class TerratorchRenderer(BaseRenderer):
55
content_format="string",
56
)
57
57
- prompt = self.render_completion([1]) # Dummy token IDs
58
+ prompt = parse_dec_only_prompt([1]) # Dummy token IDs
59
if mm_data is not None:
60
prompt["multi_modal_data"] = mm_data
61
if mm_uuids is not None:
@@ -66,7 +67,7 @@ class TerratorchRenderer(BaseRenderer):
67
self,
68
messages: list[ChatCompletionMessageParam],
69
params: ChatParams,
69
- ) -> tuple[list[ConversationMessage], TextPrompt | TokensPrompt | EmbedsPrompt]:
70
+ ) -> tuple[list[ConversationMessage], DictPrompt]:
71
model_config = self.config
72
73
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
@@ -75,7 +76,7 @@ class TerratorchRenderer(BaseRenderer):
76
content_format="string",
77
)
78
78
- prompt = self.render_completion([1]) # Dummy token IDs
79
+ prompt = parse_dec_only_prompt([1]) # Dummy token IDs
80
if mm_data is not None:
81
prompt["multi_modal_data"] = mm_data
82
if mm_uuids is not None:
vllm/v1/engine/async_llm.py
+17
-6
@@ -28,6 +28,8 @@ 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 BaseRenderer, merge_kwargs
31
+from vllm.renderers.inputs import DictPrompt, TokPrompt
32
+from vllm.renderers.inputs.preprocess import extract_prompt_components
33
from vllm.sampling_params import RequestOutputKind, SamplingParams
34
from vllm.tasks import SupportedTask
35
from vllm.tokenizers import TokenizerLike
@@ -42,7 +44,6 @@ from vllm.v1.engine.exceptions import EngineDeadError, EngineGenerateError
44
from vllm.v1.engine.input_processor import InputProcessor
45
from vllm.v1.engine.output_processor import OutputProcessor, RequestOutputCollector
46
from vllm.v1.engine.parallel_sampling import ParentRequest
45
-from vllm.v1.engine.utils import get_prompt_text
47
from vllm.v1.executor import Executor
48
from vllm.v1.metrics.loggers import (
49
StatLoggerFactory,
@@ -284,7 +285,11 @@ class AsyncLLM(EngineClient):
285
async def add_request(
286
self,
287
request_id: str,
287
- prompt: EngineCoreRequest | PromptType | AsyncGenerator[StreamingInput, None],
288
+ prompt: EngineCoreRequest
289
+ | PromptType
290
+ | DictPrompt
291
+ | TokPrompt
292
+ | AsyncGenerator[StreamingInput, None],
293
params: SamplingParams | PoolingParams,
294
arrival_time: float | None = None,
295
lora_request: LoRARequest | None = None,
@@ -367,7 +372,7 @@ class AsyncLLM(EngineClient):
372
data_parallel_rank=data_parallel_rank,
373
supported_tasks=await self.get_supported_tasks(),
374
)
370
- prompt_text = get_prompt_text(prompt)
375
+ prompt_text, _, _ = extract_prompt_components(self.model_config, prompt)
376
377
self.input_processor.assign_request_id(request)
378
@@ -484,7 +489,9 @@ class AsyncLLM(EngineClient):
489
raise ValueError(
490
"prompt_embeds not supported for streaming inputs"
491
)
487
- prompt_text = get_prompt_text(input_chunk.prompt)
492
+ prompt_text, _, _ = extract_prompt_components(
493
+ self.model_config, input_chunk.prompt
494
+ )
495
await self._add_request(req, prompt_text, None, 0, queue)
496
except (asyncio.CancelledError, GeneratorExit):
497
cancelled = True
@@ -528,7 +535,11 @@ class AsyncLLM(EngineClient):
535
# re-multiplexed in the API server anyhow.
536
async def generate(
537
self,
531
- prompt: EngineCoreRequest | PromptType | AsyncGenerator[StreamingInput, None],
538
+ prompt: EngineCoreRequest
539
+ | PromptType
540
+ | DictPrompt
541
+ | TokPrompt
542
+ | AsyncGenerator[StreamingInput, None],
543
sampling_params: SamplingParams,
544
request_id: str,
545
*,
@@ -769,7 +780,7 @@ class AsyncLLM(EngineClient):
780
781
async def encode(
782
self,
772
- prompt: PromptType,
783
+ prompt: PromptType | DictPrompt | TokPrompt,
784
pooling_params: PoolingParams,
785
request_id: str,
786
lora_request: LoRARequest | None = None,
vllm/v1/engine/input_processor.py
+18
-16
@@ -7,14 +7,13 @@ from typing import Any, Literal, cast
7
8
from vllm.config import VllmConfig
9
from vllm.exceptions import VLLMValidationError
10
-from vllm.inputs import (
10
+from vllm.inputs.data import (
11
ProcessorInputs,
12
PromptType,
13
SingletonInputs,
14
SingletonPrompt,
15
- TextPrompt,
15
)
17
-from vllm.inputs.parse import is_explicit_encoder_decoder_prompt, split_enc_dec_inputs
16
+from vllm.inputs.parse import split_enc_dec_inputs
17
from vllm.inputs.preprocess import InputPreprocessor
18
from vllm.logger import init_logger
19
from vllm.lora.request import LoRARequest
@@ -30,6 +29,7 @@ from vllm.multimodal.processing.context import set_request_id
29
from vllm.multimodal.utils import argsort_mm_positions
30
from vllm.pooling_params import PoolingParams
31
from vllm.renderers import BaseRenderer
32
+from vllm.renderers.inputs import DictPrompt, TokPrompt
33
from vllm.sampling_params import _SAMPLING_EPS, SamplingParams
34
from vllm.tasks import POOLING_TASKS, SupportedTask
35
from vllm.tokenizers import TokenizerLike
@@ -243,8 +243,8 @@ class InputProcessor:
243
return mm_processor.info.parse_mm_data(mm_data)
244
245
def _validate_singleton_mm_uuids(self, prompt: SingletonPrompt) -> None:
246
- if isinstance(prompt, str):
247
- prompt = TextPrompt(prompt=prompt)
246
+ if not isinstance(prompt, dict):
247
+ return
248
249
mm_data = cast(MultiModalDataDict, prompt.get("multi_modal_data") or {})
250
mm_uuids = cast(MultiModalUUIDDict, prompt.get("multi_modal_uuids") or {})
@@ -297,7 +297,7 @@ class InputProcessor:
297
f"multi_modal_uuids[{modality!r}] is missing."
298
)
299
300
- def _validate_mm_uuids(self, prompt: PromptType) -> None:
300
+ def _validate_mm_uuids(self, prompt: PromptType | DictPrompt | TokPrompt) -> None:
301
"""
302
Validate that user-provided multi_modal_uuids align with
303
multi_modal_data in the incoming request prompt(s).
@@ -305,10 +305,10 @@ class InputProcessor:
305
auto-hashed downstream.
306
"""
307
308
- if is_explicit_encoder_decoder_prompt(prompt):
309
- self._validate_singleton_mm_uuids(prompt["encoder_prompt"])
308
+ if isinstance(prompt, dict) and "encoder_prompt" in prompt:
309
+ self._validate_singleton_mm_uuids(prompt["encoder_prompt"]) # type: ignore[typeddict-item]
310
311
- if (dec_prompt := prompt["decoder_prompt"]) is not None:
311
+ if (dec_prompt := prompt["decoder_prompt"]) is not None: # type: ignore[typeddict-item]
312
self._validate_singleton_mm_uuids(dec_prompt)
313
else:
314
self._validate_singleton_mm_uuids(prompt)
@@ -449,21 +449,23 @@ class InputProcessor:
449
def _extract_singleton_mm_data(
450
self, prompt: SingletonPrompt
451
) -> MultiModalDataDict | None:
452
- if isinstance(prompt, str):
452
+ if not isinstance(prompt, dict):
453
return None
454
455
- return prompt.get("multi_modal_data") # type: ignore[return-value]
455
+ return prompt.get("multi_modal_data")
456
457
- def _extract_mm_data(self, prompt: PromptType) -> MultiModalDataDict | None:
458
- if is_explicit_encoder_decoder_prompt(prompt):
459
- return self._extract_singleton_mm_data(prompt["encoder_prompt"])
457
+ def _extract_mm_data(
458
+ self, prompt: PromptType | DictPrompt | TokPrompt
459
+ ) -> MultiModalDataDict | None:
460
+ if isinstance(prompt, dict) and "encoder_prompt" in prompt:
461
+ return self._extract_singleton_mm_data(prompt["encoder_prompt"]) # type: ignore[typeddict-item]
462
else:
463
return self._extract_singleton_mm_data(prompt)
464
465
def _maybe_build_mm_uuids(
466
self,
467
request_id: str,
466
- prompt: PromptType,
468
+ prompt: PromptType | DictPrompt | TokPrompt,
469
) -> MultiModalUUIDDict | None:
470
"""Build per-item multimodal hash overrides when enabled. In this case,
471
multimodal data items are identified by their request id, modality and
@@ -519,7 +521,7 @@ class InputProcessor:
521
def process_inputs(
522
self,
523
request_id: str,
522
- prompt: PromptType,
524
+ prompt: PromptType | DictPrompt | TokPrompt,
525
params: SamplingParams | PoolingParams,
526
arrival_time: float | None = None,
527
lora_request: LoRARequest | None = None,
vllm/v1/engine/llm_engine.py
+4
-3
@@ -22,6 +22,8 @@ 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 BaseRenderer
25
+from vllm.renderers.inputs import DictPrompt, TokPrompt
26
+from vllm.renderers.inputs.preprocess import extract_prompt_components
27
from vllm.sampling_params import SamplingParams
28
from vllm.tasks import SupportedTask
29
from vllm.tokenizers import TokenizerLike
@@ -32,7 +34,6 @@ from vllm.v1.engine.core_client import EngineCoreClient
34
from vllm.v1.engine.input_processor import InputProcessor
35
from vllm.v1.engine.output_processor import OutputProcessor
36
from vllm.v1.engine.parallel_sampling import ParentRequest
35
-from vllm.v1.engine.utils import get_prompt_text
37
from vllm.v1.executor import Executor
38
from vllm.v1.metrics.loggers import StatLoggerFactory, StatLoggerManager
39
from vllm.v1.metrics.reader import Metric, get_metrics_snapshot
@@ -216,7 +217,7 @@ class LLMEngine:
217
def add_request(
218
self,
219
request_id: str,
219
- prompt: EngineCoreRequest | PromptType,
220
+ prompt: EngineCoreRequest | PromptType | DictPrompt | TokPrompt,
221
params: SamplingParams | PoolingParams,
222
arrival_time: float | None = None,
223
lora_request: LoRARequest | None = None,
@@ -251,7 +252,7 @@ class LLMEngine:
252
priority,
253
supported_tasks=self.get_supported_tasks(),
254
)
254
- prompt_text = get_prompt_text(prompt)
255
+ prompt_text, _, _ = extract_prompt_components(self.model_config, prompt)
256
257
self.input_processor.assign_request_id(request)
258
vllm/v1/engine/utils.py
-6
@@ -17,8 +17,6 @@ import zmq
17
18
from vllm import envs
19
from vllm.config import CacheConfig, ParallelConfig, VllmConfig
20
-from vllm.inputs import PromptType
21
-from vllm.inputs.parse import get_prompt_components
20
from vllm.logger import init_logger
21
from vllm.platforms import current_platform
22
from vllm.ray.ray_env import get_env_vars_to_copy
@@ -226,10 +224,6 @@ def get_device_indices(
224
return value
225
226
229
-def get_prompt_text(prompt: PromptType) -> str | None:
230
- return get_prompt_components(prompt)[0]
231
-
232
-
227
class CoreEngineActorManager:
228
"""
229
Utility class to handle creation, readiness, and shutdown