main
py 271 lines 8.2 KB
Raw
1 """Schemas and utilities for preprocessing inputs."""
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, NamedTuple, TypeAlias, TypedDict, overload
7
8 from vllm.inputs import (
9 EmbedsPrompt,
10 EngineInput,
11 ExplicitEncoderDecoderPrompt,
12 PromptType,
13 SingletonPrompt,
14 TextPrompt,
15 TokensPrompt,
16 )
17 from vllm.utils import length_from_prompt_token_ids_or_embeds
18 from vllm.utils.collection_utils import is_list_of
19
20 if TYPE_CHECKING:
21 import torch
22
23 from vllm.config import ModelConfig
24 from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
25
26
27 @overload
28 def prompt_to_seq(
29 prompt_or_prompts: SingletonPrompt | bytes | Sequence[SingletonPrompt | bytes],
30 ) -> Sequence[SingletonPrompt]: ...
31
32
33 @overload
34 def prompt_to_seq( # type: ignore[misc]
35 prompt_or_prompts: ExplicitEncoderDecoderPrompt
36 | Sequence[ExplicitEncoderDecoderPrompt],
37 ) -> Sequence[ExplicitEncoderDecoderPrompt]: ...
38
39
40 @overload
41 def prompt_to_seq( # type: ignore[misc]
42 prompt_or_prompts: PromptType | Sequence[PromptType],
43 ) -> Sequence[PromptType]: ...
44
45
46 def prompt_to_seq(
47 prompt_or_prompts: PromptType | bytes | Sequence[PromptType | bytes],
48 ) -> Sequence[PromptType]:
49 if isinstance(prompt_or_prompts, (dict, str, bytes)) or (
50 len(prompt_or_prompts) > 0 and is_list_of(prompt_or_prompts, int)
51 ):
52 return [prompt_or_prompts] # type: ignore[list-item]
53
54 return prompt_or_prompts # type: ignore[return-value]
55
56
57 def conversation_to_seq(
58 conversation_or_conversations: list["ChatCompletionMessageParam"]
59 | Sequence[list["ChatCompletionMessageParam"]],
60 ) -> Sequence[list["ChatCompletionMessageParam"]]:
61 if len(conversation_or_conversations) > 0 and is_list_of(
62 conversation_or_conversations, dict
63 ):
64 return [conversation_or_conversations] # type: ignore[list-item]
65
66 return conversation_or_conversations # type: ignore[return-value]
67
68
69 DecoderOnlyDictPrompt: TypeAlias = TextPrompt | TokensPrompt | EmbedsPrompt
70 """
71 A [`DecoderOnlyPrompt`][vllm.inputs.llm.DecoderOnlyPrompt]
72 that has been standardized into a dictionary.
73 """
74
75
76 EncoderDictPrompt: TypeAlias = TextPrompt | TokensPrompt
77 """
78 A [`EncoderPrompt`][vllm.inputs.llm.EncoderPrompt]
79 that has been standardized into a dictionary.
80 """
81
82
83 DecoderDictPrompt: TypeAlias = TextPrompt | TokensPrompt
84 """
85 A [`DecoderPrompt`][vllm.inputs.llm.DecoderPrompt]
86 that has been standardized into a dictionary.
87 """
88
89
90 class EncoderDecoderDictPrompt(TypedDict):
91 """A [`EncoderDecoderPrompt`][vllm.inputs.llm.EncoderDecoderPrompt]
92 that has been standardized into a dictionary.
93 """
94
95 encoder_prompt: EncoderDictPrompt
96
97 decoder_prompt: DecoderDictPrompt | None
98
99
100 SingletonDictPrompt: TypeAlias = (
101 DecoderOnlyDictPrompt | EncoderDictPrompt | DecoderDictPrompt
102 )
103 """
104 A [`SingletonPrompt`][vllm.inputs.llm.SingletonPrompt]
105 that has been standardized into a dictionary.
106 """
107
108
109 DictPrompt: TypeAlias = DecoderOnlyDictPrompt | EncoderDecoderDictPrompt
110 """
111 A [`PromptType`][vllm.inputs.llm.PromptType]
112 that has been standardized into a dictionary.
113 """
114
115
116 def _validate_prompt_dict(prompt: Mapping[str, object]) -> None:
117 """Reject malformed dict prompts before renderer tokenization."""
118 if (
119 "prompt" not in prompt
120 or "prompt_token_ids" in prompt
121 or "prompt_embeds" in prompt
122 ):
123 return
124
125 if not isinstance(prompt["prompt"], str):
126 raise TypeError("Prompt text should be a string")
127
128
129 def parse_dec_only_prompt(prompt: PromptType | object) -> DecoderOnlyDictPrompt:
130 """Parse a prompt for a decoder-only model and normalize it to a dictionary."""
131 if isinstance(prompt, str):
132 return TextPrompt(prompt=prompt)
133
134 if isinstance(prompt, list):
135 if not is_list_of(prompt, int):
136 raise TypeError("Token prompt should be a list of integers")
137
138 return TokensPrompt(prompt_token_ids=prompt)
139
140 if isinstance(prompt, dict):
141 if "encoder_prompt" in prompt:
142 raise TypeError("Cannot pass encoder-decoder prompt to decoder-only models")
143
144 _validate_prompt_dict(prompt)
145
146 if (
147 "prompt" in prompt
148 or "prompt_token_ids" in prompt
149 or "prompt_embeds" in prompt
150 ):
151 return prompt # type: ignore[return-value]
152
153 raise TypeError("Prompt dictionary must contain text, tokens, or embeddings")
154
155 raise TypeError("Prompt should be a string, list of tokens, or dictionary")
156
157
158 def _parse_enc_prompt(prompt: PromptType | object) -> EncoderDictPrompt:
159 if isinstance(prompt, str):
160 return TextPrompt(prompt=prompt)
161
162 if isinstance(prompt, list):
163 if not is_list_of(prompt, int):
164 raise TypeError("Token prompt should be a list of integers")
165
166 return TokensPrompt(prompt_token_ids=prompt)
167
168 if isinstance(prompt, dict):
169 _validate_prompt_dict(prompt)
170
171 if "prompt_embeds" in prompt:
172 raise TypeError("Cannot pass embeddings prompt to encoder-decoder models")
173
174 if "prompt" in prompt or "prompt_token_ids" in prompt:
175 return prompt # type: ignore[return-value]
176
177 raise TypeError("Prompt dictionary must contain text or tokens")
178
179 raise TypeError("Prompt should be a string, list of tokens, or dictionary")
180
181
182 def _parse_dec_prompt(prompt: PromptType | object) -> DecoderDictPrompt:
183 if isinstance(prompt, str):
184 return TextPrompt(prompt=prompt)
185
186 if isinstance(prompt, list):
187 if not is_list_of(prompt, int):
188 raise TypeError("Token prompt should be a list of integers")
189
190 return TokensPrompt(prompt_token_ids=prompt)
191
192 if isinstance(prompt, dict):
193 _validate_prompt_dict(prompt)
194
195 if "prompt_embeds" in prompt:
196 raise TypeError("Cannot pass embeddings prompt to encoder-decoder models")
197
198 if (
199 "multi_modal_data" in prompt
200 or "mm_processor_kwargs" in prompt
201 or "multi_modal_uuids" in prompt
202 ):
203 raise TypeError("Cannot pass multi-modal inputs to decoder prompt")
204
205 if "prompt" in prompt or "prompt_token_ids" in prompt:
206 return prompt # type: ignore[return-value]
207
208 raise TypeError("Prompt dictionary must contain text or tokens")
209
210 raise TypeError("Prompt should be a string, list of tokens, or dictionary")
211
212
213 def parse_enc_dec_prompt(prompt: PromptType | object) -> EncoderDecoderDictPrompt:
214 """Parse a prompt for an encoder-decoder model and normalize it to a dictionary."""
215 if isinstance(prompt, dict) and "encoder_prompt" in prompt:
216 enc_prompt = prompt["encoder_prompt"] # type: ignore[typeddict-item]
217 dec_prompt = prompt["decoder_prompt"] # type: ignore[typeddict-item]
218 else:
219 enc_prompt = prompt
220 dec_prompt = None
221
222 return EncoderDecoderDictPrompt(
223 encoder_prompt=_parse_enc_prompt(enc_prompt),
224 decoder_prompt=None if dec_prompt is None else _parse_dec_prompt(dec_prompt),
225 )
226
227
228 def parse_model_prompt(model_config: "ModelConfig", prompt: object):
229 if model_config.is_encoder_decoder:
230 return parse_enc_dec_prompt(prompt)
231
232 return parse_dec_only_prompt(prompt)
233
234
235 class PromptComponents(NamedTuple):
236 text: str | None = None
237 token_ids: list[int] | None = None
238 embeds: "torch.Tensor | None" = None
239
240
241 def extract_target_prompt(model_config: "ModelConfig", prompt: object):
242 return (
243 parse_enc_dec_prompt(prompt)["encoder_prompt"]
244 if model_config.is_encoder_decoder
245 else parse_dec_only_prompt(prompt)
246 )
247
248
249 def extract_prompt_components(
250 model_config: "ModelConfig",
251 prompt: PromptType | EngineInput,
252 ) -> PromptComponents:
253 target_prompt = extract_target_prompt(model_config, prompt)
254
255 return PromptComponents(
256 text=target_prompt.get("prompt"),
257 token_ids=target_prompt.get("prompt_token_ids"),
258 embeds=target_prompt.get("prompt_embeds"),
259 )
260
261
262 def extract_prompt_len(
263 model_config: "ModelConfig",
264 prompt: PromptType | EngineInput,
265 ):
266 target_prompt = extract_target_prompt(model_config, prompt)
267
268 return length_from_prompt_token_ids_or_embeds(
269 target_prompt.get("prompt_token_ids"),
270 target_prompt.get("prompt_embeds"),
271 )