main
py 372 lines 11.1 KB
Raw
1 """Schema and utilities for inputs to the engine client (`LLMEngine`/`AsyncLLM`)."""
2
3 # SPDX-License-Identifier: Apache-2.0
4 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
5 from collections.abc import Mapping, Sequence
6 from typing import TYPE_CHECKING, Literal, TypeAlias
7
8 from typing_extensions import NotRequired, TypedDict, assert_never
9
10 from vllm.exceptions import VLLMValidationError
11
12 if TYPE_CHECKING:
13 import torch
14
15 from vllm.multimodal.inputs import MultiModalKwargsOptionalItems, PlaceholderRange
16
17
18 class _InputOptions(TypedDict):
19 """Additional options available to all
20 [`SingletonInput`][vllm.inputs.engine.SingletonInput] types.
21 """
22
23 arrival_time: NotRequired[float]
24 """The time when the input was received (before rendering)."""
25
26 cache_salt: NotRequired[str]
27 """Optional cache salt to be used for prefix caching."""
28
29
30 class TokensInput(_InputOptions):
31 """Represents token-based input to the engine."""
32
33 type: Literal["token"]
34 """The type of input."""
35
36 prompt_token_ids: list[int]
37 """The token IDs of the prompt."""
38
39 prompt: NotRequired[str]
40 """The prompt text corresponding to the token IDs, if available."""
41
42 prompt_token_offsets: NotRequired[list[tuple[int, int]] | None]
43 """Char-level (start, end) offsets per token, propagated from the
44 renderer's TokensPrompt when offsets were computed."""
45
46
47 def tokens_input(
48 prompt_token_ids: list[int],
49 *,
50 prompt: str | None = None,
51 cache_salt: str | None = None,
52 ) -> TokensInput:
53 """Construct [`TokensInput`][vllm.inputs.engine.TokensInput]
54 from optional values.
55 """
56 inputs = TokensInput(type="token", prompt_token_ids=prompt_token_ids)
57
58 if prompt is not None:
59 inputs["prompt"] = prompt
60 if cache_salt is not None:
61 inputs["cache_salt"] = cache_salt
62
63 return inputs
64
65
66 class EmbedsInput(_InputOptions):
67 """Represents embeddings-based input to the engine."""
68
69 type: Literal["embeds"]
70 """The type of input."""
71
72 prompt_embeds: "torch.Tensor"
73 """The embeddings of the prompt."""
74
75 prompt: NotRequired[str]
76 """The prompt text corresponding to the token IDs, if available."""
77
78 prompt_token_ids: NotRequired[list[int]]
79 """Token IDs of the rendered prompt. Only set for mixed-mode inputs
80 (chat completion with `prompt_embeds` content parts). When present,
81 `is_token_ids` MUST also be present and have the same length.
82 For pure-embeds inputs this field is absent."""
83
84 is_token_ids: NotRequired[list[bool]]
85 """Per-position mask for mixed-mode inputs. `True` means the position
86 is a real token ID (use the model's embedding layer); `False` means
87 the position uses a pre-computed embedding row from `prompt_embeds`.
88 Length MUST equal `len(prompt_token_ids)`.
89 For pure-embeds inputs this field is absent."""
90
91
92 def embeds_input(
93 prompt_embeds: "torch.Tensor",
94 *,
95 prompt: str | None = None,
96 cache_salt: str | None = None,
97 prompt_token_ids: list[int] | None = None,
98 is_token_ids: list[bool] | None = None,
99 ) -> EmbedsInput:
100 """Construct [`EmbedsInput`][vllm.inputs.engine.EmbedsInput]
101 from optional values.
102 """
103 inputs = EmbedsInput(type="embeds", prompt_embeds=prompt_embeds)
104
105 if prompt is not None:
106 inputs["prompt"] = prompt
107 if cache_salt is not None:
108 inputs["cache_salt"] = cache_salt
109 if prompt_token_ids is not None:
110 inputs["prompt_token_ids"] = prompt_token_ids
111 if is_token_ids is not None:
112 inputs["is_token_ids"] = is_token_ids
113
114 return inputs
115
116
117 MultiModalHashes: TypeAlias = Mapping[str, list[str]]
118 """
119 A dictionary containing per-item hashes for each modality.
120 """
121
122
123 MultiModalPlaceholders: TypeAlias = Mapping[str, Sequence["PlaceholderRange"]]
124 """
125 A dictionary containing per-item placeholder ranges for each modality.
126 """
127
128
129 class MultiModalInput(_InputOptions):
130 """Represents multi-modal input to the engine."""
131
132 type: Literal["multimodal"]
133 """The type of input."""
134
135 prompt_token_ids: list[int]
136 """The processed token IDs which includes placeholder tokens."""
137
138 prompt: NotRequired[str]
139 """The prompt text corresponding to the token IDs, if available."""
140
141 mm_kwargs: "MultiModalKwargsOptionalItems"
142 """Keyword arguments to be directly passed to the model after batching."""
143
144 mm_hashes: MultiModalHashes
145 """The hashes of the multi-modal data."""
146
147 mm_placeholders: MultiModalPlaceholders
148 """
149 For each modality, information about the placeholder tokens in
150 `prompt_token_ids`.
151 """
152
153
154 def mm_input(
155 prompt_token_ids: list[int],
156 mm_kwargs: "MultiModalKwargsOptionalItems",
157 mm_hashes: MultiModalHashes,
158 mm_placeholders: MultiModalPlaceholders,
159 *,
160 prompt: str | None = None,
161 cache_salt: str | None = None,
162 ) -> MultiModalInput:
163 inputs = MultiModalInput(
164 type="multimodal",
165 prompt_token_ids=prompt_token_ids,
166 mm_kwargs=mm_kwargs,
167 mm_hashes=mm_hashes,
168 mm_placeholders=mm_placeholders,
169 )
170
171 if prompt is not None:
172 inputs["prompt"] = prompt
173 if cache_salt is not None:
174 inputs["cache_salt"] = cache_salt
175
176 return inputs
177
178
179 class MultiModalEncDecInput(MultiModalInput):
180 """Represents multi-modal input to the engine for encoder-decoder models.
181
182 Note:
183 Even text-only encoder-decoder models are currently implemented
184 as multi-modal models for convenience.
185 (Example: https://github.com/vllm-project/bart-plugin)
186
187 """
188
189 encoder_prompt_token_ids: list[int]
190 """The processed token IDs of the encoder prompt."""
191
192 encoder_prompt: NotRequired[str]
193 """The prompt text corresponding to the encoder token IDs, if available."""
194
195
196 def mm_enc_dec_input(
197 encoder_inputs: MultiModalInput,
198 decoder_prompt_token_ids: list[int],
199 *,
200 decoder_prompt: str | None = None,
201 ) -> MultiModalEncDecInput:
202 inputs = MultiModalEncDecInput(
203 type="multimodal",
204 prompt_token_ids=decoder_prompt_token_ids,
205 encoder_prompt_token_ids=encoder_inputs["prompt_token_ids"],
206 mm_kwargs=encoder_inputs["mm_kwargs"],
207 mm_hashes=encoder_inputs["mm_hashes"],
208 mm_placeholders=encoder_inputs["mm_placeholders"],
209 )
210
211 if decoder_prompt is not None:
212 inputs["prompt"] = decoder_prompt
213 if "prompt" in encoder_inputs:
214 inputs["encoder_prompt"] = encoder_inputs["prompt"]
215 if "cache_salt" in encoder_inputs:
216 inputs["cache_salt"] = encoder_inputs["cache_salt"]
217
218 return inputs
219
220
221 DecoderOnlyEngineInput: TypeAlias = TokensInput | EmbedsInput | MultiModalInput
222 """
223 A rendered [`DecoderOnlyPrompt`][vllm.inputs.llm.DecoderOnlyPrompt]
224 which can be passed to `LLMEngine.add_request` or `AsyncLLM.add_request`.
225 """
226
227
228 EncoderInput: TypeAlias = TokensInput | MultiModalEncDecInput
229 """
230 A rendered [`EncoderPrompt`][vllm.inputs.llm.EncoderPrompt]
231 which can be passed to `LLMEngine.add_request` or `AsyncLLM.add_request`.
232 """
233
234
235 DecoderEngineInput: TypeAlias = TokensInput | MultiModalInput
236 """
237 A rendered [`DecoderPrompt`][vllm.inputs.llm.DecoderPrompt]
238 which can be passed to `LLMEngine.add_request` or `AsyncLLM.add_request`.
239 """
240
241
242 class EncoderDecoderInput(TypedDict):
243 """A rendered [`EncoderDecoderPrompt`][vllm.inputs.llm.EncoderDecoderPrompt]
244 which can be passed to `LLMEngine.add_request` or `AsyncLLM.add_request`.
245 """
246
247 type: Literal["enc_dec"]
248
249 encoder_prompt: EncoderInput
250 """The inputs for the encoder portion."""
251
252 decoder_prompt: DecoderEngineInput
253 """The inputs for the decoder portion."""
254
255 arrival_time: NotRequired[float]
256 """The time when the input was received (before rendering)."""
257
258
259 SingletonInput: TypeAlias = DecoderOnlyEngineInput | MultiModalEncDecInput
260 """
261 A rendered [`SingletonPrompt`][vllm.inputs.llm.SingletonPrompt]
262 which can be passed to `LLMEngine.add_request` or `AsyncLLM.add_request`.
263 """
264
265
266 EngineInput: TypeAlias = DecoderOnlyEngineInput | EncoderDecoderInput
267 """
268 A rendered [`PromptType`][vllm.inputs.llm.PromptType]
269 which can be passed to `LLMEngine.add_request` or `AsyncLLM.add_request`.
270 """
271
272
273 def _validate_enc_input(enc_input: SingletonInput) -> EncoderInput:
274 if enc_input["type"] == "embeds":
275 raise VLLMValidationError(
276 "Embedding inputs are not supported for encoder-decoder models"
277 )
278
279 if (
280 enc_input["type"] == "multimodal"
281 and "encoder_prompt_token_ids" not in enc_input
282 ):
283 raise RuntimeError(
284 "You should register an encoder-decoder multi-modal processor "
285 "for encoder-decoder models."
286 )
287
288 return enc_input # type: ignore[return-value]
289
290
291 def _validate_dec_input(dec_input: SingletonInput) -> DecoderEngineInput:
292 if dec_input["type"] == "embeds":
293 raise VLLMValidationError(
294 "Embedding inputs are not supported for encoder-decoder models"
295 )
296
297 return dec_input
298
299
300 def _prepare_decoder_input_ids_for_generation(
301 decoder_input_ids: list[int],
302 decoder_start_token_id: int,
303 ) -> list[int]:
304 """Prepare `decoder_input_ids` for generation with encoder-decoder models,
305 according to `GenerationMixin._prepare_decoder_input_ids_for_generation()`.
306
307 Source:
308 https://github.com/huggingface/transformers/blob/v5.1.0/src/transformers/generation/utils.py
309 """
310 if len(decoder_input_ids) == 0 or decoder_input_ids[0] != decoder_start_token_id:
311 decoder_input_ids = [decoder_start_token_id] + decoder_input_ids
312
313 return decoder_input_ids
314
315
316 def build_enc_dec_input(
317 encoder_input: SingletonInput,
318 decoder_input: SingletonInput | None,
319 decoder_start_token_id: int,
320 skip_decoder_start_token: bool = False,
321 ) -> EncoderDecoderInput:
322 enc_input = _validate_enc_input(encoder_input)
323
324 if decoder_input is None:
325 dec_input: DecoderEngineInput = enc_input
326 else:
327 dec_input = _validate_dec_input(decoder_input)
328
329 enc_input_new: EncoderInput
330 dec_input_new: DecoderEngineInput
331
332 if enc_input["type"] == "multimodal":
333 enc_input_new = tokens_input(
334 enc_input["encoder_prompt_token_ids"],
335 prompt=enc_input.get("encoder_prompt"),
336 )
337 dec_input_new = mm_input(
338 prompt_token_ids=dec_input["prompt_token_ids"],
339 prompt=dec_input.get("prompt"),
340 mm_kwargs=enc_input["mm_kwargs"],
341 mm_hashes=enc_input["mm_hashes"],
342 mm_placeholders=enc_input["mm_placeholders"],
343 )
344 elif enc_input["type"] == "token":
345 enc_input_new = tokens_input(prompt_token_ids=[])
346 dec_input_new = dec_input
347 else:
348 assert_never(enc_input)
349
350 if not skip_decoder_start_token:
351 dec_input_new["prompt_token_ids"] = _prepare_decoder_input_ids_for_generation(
352 dec_input_new["prompt_token_ids"],
353 decoder_start_token_id,
354 )
355
356 if cache_salt := enc_input.get("cache_salt"):
357 dec_input_new["cache_salt"] = cache_salt
358
359 return EncoderDecoderInput(
360 type="enc_dec",
361 encoder_prompt=enc_input_new,
362 decoder_prompt=dec_input_new,
363 )
364
365
366 def split_enc_dec_input(
367 inputs: EngineInput,
368 ) -> tuple[SingletonInput | None, SingletonInput]:
369 if inputs["type"] == "enc_dec":
370 return inputs["encoder_prompt"], inputs["decoder_prompt"]
371
372 return None, inputs