| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | from dataclasses import dataclass, field |
| 4 | from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast |
| 5 | |
| 6 | from vllm.exceptions import VLLMValidationError |
| 7 | from vllm.inputs import EmbedsPrompt, TextPrompt, TokensPrompt |
| 8 | from vllm.logger import init_logger |
| 9 | from vllm.multimodal.media.connector import merge_media_io_kwargs |
| 10 | from vllm.tokenizers import TokenizerLike |
| 11 | from vllm.utils.import_utils import LazyLoader |
| 12 | |
| 13 | if TYPE_CHECKING: |
| 14 | import torch |
| 15 | |
| 16 | from vllm.entrypoints.chat_utils import ChatTemplateContentFormatOption |
| 17 | else: |
| 18 | torch = LazyLoader("torch", globals(), "torch") |
| 19 | |
| 20 | ChatTemplateContentFormatOption = object |
| 21 | |
| 22 | logger = init_logger(__name__) |
| 23 | |
| 24 | |
| 25 | _S = TypeVar("_S", list[int], "torch.Tensor") |
| 26 | |
| 27 | # Prompt keys whose entry i describes token i of the prompt, so they must be |
| 28 | # reduced with the same slice as the prompt tokens themselves. Anything added |
| 29 | # here is truncated by `TokenizeParams.apply_post_tokenization`. |
| 30 | _PARALLEL_TO_PROMPT_TOKENS = ( |
| 31 | "prompt_token_offsets", |
| 32 | "prompt_is_token_ids", |
| 33 | ) |
| 34 | |
| 35 | |
| 36 | def merge_kwargs( |
| 37 | defaults: dict[str, Any] | None, |
| 38 | overrides: dict[str, Any] | None, |
| 39 | /, |
| 40 | *, |
| 41 | unset_values: tuple[object, ...] = (None, "auto"), |
| 42 | ) -> dict[str, Any]: |
| 43 | if defaults is None: |
| 44 | defaults = {} |
| 45 | if overrides is None: |
| 46 | overrides = {} |
| 47 | |
| 48 | return defaults | {k: v for k, v in overrides.items() if v not in unset_values} |
| 49 | |
| 50 | |
| 51 | def recursively_merge_kwargs( |
| 52 | defaults: dict[str, Any] | None, |
| 53 | overrides: dict[str, Any] | None, |
| 54 | /, |
| 55 | *, |
| 56 | unset_values: tuple[object, ...] = (None, "auto"), |
| 57 | ) -> dict[str, Any]: |
| 58 | if defaults is None: |
| 59 | defaults = {} |
| 60 | if overrides is None: |
| 61 | overrides = {} |
| 62 | |
| 63 | merged = dict(defaults) |
| 64 | |
| 65 | for k, v in overrides.items(): |
| 66 | if v in unset_values: |
| 67 | continue |
| 68 | |
| 69 | if k in merged and isinstance(merged[k], dict) and isinstance(v, dict): |
| 70 | merged[k] = recursively_merge_kwargs( |
| 71 | merged[k], v, unset_values=unset_values |
| 72 | ) |
| 73 | else: |
| 74 | merged[k] = v |
| 75 | |
| 76 | return merged |
| 77 | |
| 78 | |
| 79 | @dataclass(frozen=True) |
| 80 | class ChatParams: |
| 81 | """Configuration to control how to parse chat messages.""" |
| 82 | |
| 83 | chat_template: str | None = None |
| 84 | """The chat template to apply.""" |
| 85 | |
| 86 | chat_template_content_format: "ChatTemplateContentFormatOption" = "auto" |
| 87 | """The format of the chat template.""" |
| 88 | |
| 89 | chat_template_kwargs: dict[str, Any] = field(default_factory=dict) |
| 90 | """The kwargs to pass to the chat template.""" |
| 91 | |
| 92 | media_io_kwargs: dict[str, dict[str, Any]] | None = None |
| 93 | """Per-modality kwargs for media I/O (loading/decoding images, videos, etc.).""" |
| 94 | |
| 95 | mm_processor_kwargs: dict[str, Any] | None = None |
| 96 | """The kwargs to pass to the multi-modal processor.""" |
| 97 | |
| 98 | tool_choice: Any | None = None |
| 99 | """Request-level tool choice for renderers that need API metadata.""" |
| 100 | |
| 101 | response_format: Any | None = None |
| 102 | """Request-level response format for renderers that need API metadata.""" |
| 103 | |
| 104 | def with_defaults( |
| 105 | self, |
| 106 | default_chat_template_kwargs: dict[str, Any] | None = None, |
| 107 | default_media_io_kwargs: dict[str, dict[str, Any]] | None = None, |
| 108 | default_mm_processor_kwargs: dict[str, Any] | None = None, |
| 109 | ): |
| 110 | if ( |
| 111 | not default_chat_template_kwargs |
| 112 | and not default_media_io_kwargs |
| 113 | and not default_mm_processor_kwargs |
| 114 | ): |
| 115 | return self |
| 116 | |
| 117 | return ChatParams( |
| 118 | chat_template=self.chat_template, |
| 119 | chat_template_content_format=self.chat_template_content_format, |
| 120 | chat_template_kwargs=merge_kwargs( |
| 121 | default_chat_template_kwargs, |
| 122 | self.chat_template_kwargs, |
| 123 | ), |
| 124 | media_io_kwargs=merge_media_io_kwargs( |
| 125 | default_media_io_kwargs, |
| 126 | self.media_io_kwargs, |
| 127 | ), |
| 128 | mm_processor_kwargs=recursively_merge_kwargs( |
| 129 | default_mm_processor_kwargs, |
| 130 | self.mm_processor_kwargs, |
| 131 | ), |
| 132 | tool_choice=self.tool_choice, |
| 133 | response_format=self.response_format, |
| 134 | ) |
| 135 | |
| 136 | def get_apply_chat_template_kwargs(self) -> dict[str, Any]: |
| 137 | """The arguments to pass to `tokenizer.apply_chat_template`.""" |
| 138 | return merge_kwargs( |
| 139 | self.chat_template_kwargs, |
| 140 | dict(chat_template=self.chat_template, return_dict=False), |
| 141 | ) |
| 142 | |
| 143 | |
| 144 | @dataclass(frozen=True) |
| 145 | class TokenizeParams: |
| 146 | """Configuration to control how prompts are tokenized.""" |
| 147 | |
| 148 | max_total_tokens: int | None |
| 149 | """ |
| 150 | Maximum allowed number of input + output tokens. |
| 151 | |
| 152 | Usually, this refers to the model's context length. |
| 153 | """ |
| 154 | |
| 155 | max_output_tokens: int = 0 |
| 156 | """Maximum requested number of output tokens.""" |
| 157 | |
| 158 | pad_prompt_tokens: int | None = None |
| 159 | """ |
| 160 | Number of tokens to pad to: |
| 161 | - `None` means no padding. |
| 162 | - `-1` maps to `max_input_tokens`. |
| 163 | """ |
| 164 | |
| 165 | truncate_prompt_tokens: int | None = None |
| 166 | """ |
| 167 | Number of tokens to keep: |
| 168 | - `None` means no truncation. |
| 169 | - `-1` maps to `max_input_tokens`. |
| 170 | """ |
| 171 | |
| 172 | truncation_side: Literal["left", "right"] | None = None |
| 173 | """ |
| 174 | Which side to truncate from when ``truncate_prompt_tokens`` is active: |
| 175 | - ``"right"`` keeps the first N tokens (truncate from the end). |
| 176 | - ``"left"`` keeps the last N tokens (truncate from the start). |
| 177 | - ``None`` falls back to the tokenizer default. |
| 178 | """ |
| 179 | |
| 180 | do_lower_case: bool = False |
| 181 | """Whether to normalize text to lower case before tokenization.""" |
| 182 | |
| 183 | add_special_tokens: bool = True |
| 184 | """Whether to add special tokens.""" |
| 185 | |
| 186 | return_token_offsets: bool = False |
| 187 | """If true, request char-level (start, end) offsets per token. Honored |
| 188 | only for Fast (Rust-backed) tokenizers with text input and no multimodal |
| 189 | data; otherwise silently ignored.""" |
| 190 | |
| 191 | needs_detokenization: bool = False |
| 192 | """ |
| 193 | Whether the tokenized prompt needs to contain the original text. |
| 194 | |
| 195 | Not to be confused with `SamplingParams.detokenize` which deals |
| 196 | with the output generated by the model. |
| 197 | """ |
| 198 | |
| 199 | max_total_tokens_param: str = "max_total_tokens" |
| 200 | """Override this to edit the message for validation errors.""" |
| 201 | |
| 202 | max_output_tokens_param: str = "max_output_tokens" |
| 203 | """Override this to edit the message for validation errors.""" |
| 204 | |
| 205 | truncate_prompt_tokens_param: str = "truncate_prompt_tokens" |
| 206 | """Override this to edit the message for validation errors.""" |
| 207 | |
| 208 | @property |
| 209 | def max_input_tokens(self) -> int | None: |
| 210 | """Maximum allowed number of input tokens.""" |
| 211 | if self.max_total_tokens is None: |
| 212 | return None |
| 213 | |
| 214 | return self.max_total_tokens - self.max_output_tokens |
| 215 | |
| 216 | def __post_init__(self) -> None: |
| 217 | max_total_tokens = self.max_total_tokens |
| 218 | max_output_tokens = self.max_output_tokens |
| 219 | max_input_tokens = self.max_input_tokens |
| 220 | truncate_prompt_tokens = self.truncate_prompt_tokens |
| 221 | |
| 222 | if self.truncation_side not in (None, "left", "right"): |
| 223 | raise VLLMValidationError( |
| 224 | "`truncation_side` must be either 'left' or 'right'.", |
| 225 | parameter="truncation_side", |
| 226 | value=self.truncation_side, |
| 227 | ) |
| 228 | |
| 229 | if ( |
| 230 | max_output_tokens is not None |
| 231 | and max_total_tokens is not None |
| 232 | and max_output_tokens > max_total_tokens |
| 233 | ): |
| 234 | raise VLLMValidationError( |
| 235 | f"{self.max_output_tokens_param}={max_output_tokens} " |
| 236 | f"cannot be greater than " |
| 237 | f"{self.max_total_tokens_param}={max_total_tokens}. " |
| 238 | f"Please request fewer output tokens.", |
| 239 | parameter=self.max_output_tokens_param, |
| 240 | value=max_output_tokens, |
| 241 | ) |
| 242 | |
| 243 | if ( |
| 244 | max_input_tokens is not None |
| 245 | and truncate_prompt_tokens is not None |
| 246 | and truncate_prompt_tokens > max_input_tokens |
| 247 | ): |
| 248 | raise VLLMValidationError( |
| 249 | f"{self.truncate_prompt_tokens_param}={truncate_prompt_tokens} " |
| 250 | f"cannot be greater than {self.max_total_tokens_param} - " |
| 251 | f"{self.max_output_tokens_param} = {max_input_tokens}. " |
| 252 | f"Please request a smaller truncation size.", |
| 253 | parameter=self.truncate_prompt_tokens_param, |
| 254 | value=truncate_prompt_tokens, |
| 255 | ) |
| 256 | |
| 257 | def with_kwargs(self, **tokenization_kwargs: Any): |
| 258 | max_length = tokenization_kwargs.pop("max_length", self.max_input_tokens) |
| 259 | pad_prompt_tokens = tokenization_kwargs.pop( |
| 260 | "pad_prompt_tokens", self.pad_prompt_tokens |
| 261 | ) |
| 262 | truncate_prompt_tokens = tokenization_kwargs.pop( |
| 263 | "truncate_prompt_tokens", self.truncate_prompt_tokens |
| 264 | ) |
| 265 | truncation_side = tokenization_kwargs.pop( |
| 266 | "truncation_side", self.truncation_side |
| 267 | ) |
| 268 | do_lower_case = tokenization_kwargs.pop("do_lower_case", self.do_lower_case) |
| 269 | add_special_tokens = tokenization_kwargs.pop( |
| 270 | "add_special_tokens", self.add_special_tokens |
| 271 | ) |
| 272 | needs_detokenization = tokenization_kwargs.pop( |
| 273 | "needs_detokenization", self.needs_detokenization |
| 274 | ) |
| 275 | |
| 276 | # https://huggingface.co/docs/transformers/en/pad_truncation |
| 277 | if padding := tokenization_kwargs.pop("padding", None): |
| 278 | if padding == "max_length": |
| 279 | pad_prompt_tokens = max_length |
| 280 | elif padding in (False, "do_not_pad"): |
| 281 | pad_prompt_tokens = None |
| 282 | else: |
| 283 | # To emit the below warning |
| 284 | tokenization_kwargs["padding"] = padding |
| 285 | |
| 286 | if truncation := tokenization_kwargs.pop("truncation", None): |
| 287 | if truncation in (True, "longest_first"): |
| 288 | truncate_prompt_tokens = max_length |
| 289 | elif truncation in (False, "do_not_truncate"): |
| 290 | truncate_prompt_tokens = None |
| 291 | else: |
| 292 | # To emit the below warning |
| 293 | tokenization_kwargs["truncation"] = truncation |
| 294 | |
| 295 | if tokenization_kwargs: |
| 296 | logger.warning( |
| 297 | "The following tokenization arguments are not supported " |
| 298 | "by vLLM Renderer and will be ignored: %s", |
| 299 | tokenization_kwargs, |
| 300 | ) |
| 301 | |
| 302 | max_total_tokens = self.max_total_tokens |
| 303 | |
| 304 | return TokenizeParams( |
| 305 | max_total_tokens=max_total_tokens, |
| 306 | max_output_tokens=( |
| 307 | 0 |
| 308 | if max_total_tokens is None or max_length is None |
| 309 | else max_total_tokens - max_length |
| 310 | ), |
| 311 | pad_prompt_tokens=pad_prompt_tokens, |
| 312 | truncate_prompt_tokens=truncate_prompt_tokens, |
| 313 | truncation_side=truncation_side, |
| 314 | do_lower_case=do_lower_case, |
| 315 | add_special_tokens=add_special_tokens, |
| 316 | needs_detokenization=needs_detokenization, |
| 317 | ) |
| 318 | |
| 319 | def get_encode_kwargs(self) -> dict[str, Any]: |
| 320 | """The arguments to pass to `tokenizer.encode`.""" |
| 321 | max_length = self.truncate_prompt_tokens |
| 322 | if max_length is not None and max_length < 0: |
| 323 | max_length = self.max_input_tokens |
| 324 | elif max_length is None and self.max_input_tokens is not None: |
| 325 | # This prevents tokenization from taking up more resources than necessary |
| 326 | # while still failing `self._token_len_check` as expected by users |
| 327 | max_length = self.max_input_tokens + 1 |
| 328 | |
| 329 | # Explicit truncation-side overrides require the full token sequence |
| 330 | # so we can slice from the requested side in _token_truncation. |
| 331 | # Disable tokenizer-level truncation because its default side may |
| 332 | # differ from the requested side. The defense against unbounded |
| 333 | # tokenization lives in _text_len_check (character-level pre-trim). |
| 334 | if self.truncation_side is not None and self.truncate_prompt_tokens is not None: |
| 335 | return dict( |
| 336 | truncation=False, |
| 337 | add_special_tokens=self.add_special_tokens, |
| 338 | ) |
| 339 | |
| 340 | return dict( |
| 341 | truncation=max_length is not None, |
| 342 | max_length=max_length, |
| 343 | add_special_tokens=self.add_special_tokens, |
| 344 | ) |
| 345 | |
| 346 | def _text_len_check(self, tokenizer: TokenizerLike | None, text: str) -> str: |
| 347 | """Apply length checks to prompt text if necessary.""" |
| 348 | max_input_tokens = self.max_input_tokens |
| 349 | if max_input_tokens is None or tokenizer is None: |
| 350 | return text |
| 351 | |
| 352 | max_input_chars = max_input_tokens * tokenizer.max_chars_per_token |
| 353 | |
| 354 | if self.truncate_prompt_tokens is None: |
| 355 | if len(text) > max_input_chars: |
| 356 | raise VLLMValidationError( |
| 357 | f"This model's maximum context length is " |
| 358 | f"{self.max_total_tokens} tokens. However, you requested " |
| 359 | f"{self.max_output_tokens} output tokens and your prompt " |
| 360 | f"contains {len(text)} characters (more than " |
| 361 | f"{max_input_chars} characters, which is the upper bound " |
| 362 | f"for {max_input_tokens} input tokens). " |
| 363 | f"Please reduce the length of the input prompt or the " |
| 364 | f"number of requested output tokens.", |
| 365 | parameter="input_text", |
| 366 | value=len(text), |
| 367 | ) |
| 368 | elif self.truncation_side is not None and len(text) > max_input_chars: |
| 369 | if self.truncation_side == "left": |
| 370 | text = text[-max_input_chars:] |
| 371 | else: |
| 372 | text = text[:max_input_chars] |
| 373 | |
| 374 | return text |
| 375 | |
| 376 | def _get_text_truncation_offset( |
| 377 | self, tokenizer: TokenizerLike | None, text: str |
| 378 | ) -> int: |
| 379 | """Return the number of source characters removed from the left. |
| 380 | |
| 381 | ``_text_len_check`` pre-truncates long text before tokenization when |
| 382 | an explicit truncation side is requested. Fast-tokenizer offsets are |
| 383 | then relative to that shortened string, so callers need this prefix |
| 384 | length to map them back to the original prompt. |
| 385 | """ |
| 386 | max_input_tokens = self.max_input_tokens |
| 387 | if ( |
| 388 | max_input_tokens is None |
| 389 | or tokenizer is None |
| 390 | or self.truncate_prompt_tokens is None |
| 391 | or self.truncation_side != "left" |
| 392 | ): |
| 393 | return 0 |
| 394 | |
| 395 | max_input_chars = max_input_tokens * tokenizer.max_chars_per_token |
| 396 | if max_input_chars <= 0: |
| 397 | return 0 |
| 398 | |
| 399 | return max(len(text) - max_input_chars, 0) |
| 400 | |
| 401 | def _text_lowercase(self, tokenizer: TokenizerLike | None, text: str) -> str: |
| 402 | """Apply lowercase to prompt text if necessary.""" |
| 403 | return text.lower() if self.do_lower_case else text |
| 404 | |
| 405 | def _validate_text(self, tokenizer: TokenizerLike | None, text: str) -> str: |
| 406 | """Apply all validators to prompt text.""" |
| 407 | for validator in ( |
| 408 | self._text_len_check, |
| 409 | self._text_lowercase, |
| 410 | ): |
| 411 | text = validator(tokenizer, text) |
| 412 | |
| 413 | return text |
| 414 | |
| 415 | def apply_pre_tokenization( |
| 416 | self, |
| 417 | tokenizer: TokenizerLike | None, |
| 418 | prompt: TextPrompt, |
| 419 | ) -> TextPrompt: |
| 420 | """Ensure that the prompt meets the requirements set out by this config. |
| 421 | If that is not possible, raise a `VLLMValidationError`. |
| 422 | |
| 423 | This method is run before tokenization occurs. |
| 424 | """ |
| 425 | prompt["prompt"] = self._validate_text(tokenizer, prompt["prompt"]) |
| 426 | |
| 427 | return prompt |
| 428 | |
| 429 | def _token_padding(self, tokenizer: TokenizerLike | None, tokens: _S) -> _S: |
| 430 | """Apply padding to prompt tokens if necessary.""" |
| 431 | pad_length = self.pad_prompt_tokens |
| 432 | if pad_length is not None and pad_length < 0: |
| 433 | pad_length = self.max_input_tokens |
| 434 | |
| 435 | if pad_length is None or pad_length <= len(tokens): |
| 436 | return tokens |
| 437 | |
| 438 | if tokenizer is None: |
| 439 | raise VLLMValidationError( |
| 440 | "Cannot pad tokens when `skip_tokenizer_init=True`", |
| 441 | parameter="pad_prompt_tokens", |
| 442 | ) |
| 443 | if not isinstance(tokens, list): |
| 444 | raise VLLMValidationError( |
| 445 | "Cannot pad tokens for embedding inputs", |
| 446 | parameter="pad_prompt_tokens", |
| 447 | ) |
| 448 | |
| 449 | return tokens + [tokenizer.pad_token_id] * (pad_length - len(tokens)) |
| 450 | |
| 451 | def _truncation_slice( |
| 452 | self, tokenizer: TokenizerLike | None, length: int |
| 453 | ) -> slice | None: |
| 454 | """The slice truncation applies to a sequence of `length` tokens. |
| 455 | |
| 456 | `None` means no truncation. Anything parallel to the prompt tokens |
| 457 | must be reduced with this same slice to stay aligned with them; list |
| 458 | such keys in `_PARALLEL_TO_PROMPT_TOKENS`. |
| 459 | """ |
| 460 | max_length = self.truncate_prompt_tokens |
| 461 | if max_length is not None and max_length < 0: |
| 462 | max_length = self.max_input_tokens |
| 463 | |
| 464 | if max_length is None or max_length >= length: |
| 465 | return None |
| 466 | if max_length == 0: |
| 467 | return slice(0, 0) |
| 468 | |
| 469 | side = self.truncation_side or ( |
| 470 | tokenizer.truncation_side if tokenizer is not None else None |
| 471 | ) |
| 472 | if side == "left": |
| 473 | return slice(-max_length, None) |
| 474 | |
| 475 | return slice(0, max_length) |
| 476 | |
| 477 | def _token_truncation(self, tokenizer: TokenizerLike | None, tokens: _S) -> _S: |
| 478 | """Apply truncation to prompt tokens if necessary.""" |
| 479 | truncation = self._truncation_slice(tokenizer, len(tokens)) |
| 480 | if truncation is None: |
| 481 | return tokens |
| 482 | |
| 483 | return tokens[truncation] |
| 484 | |
| 485 | def _token_len_check(self, tokenizer: TokenizerLike | None, tokens: _S) -> _S: |
| 486 | """Apply length checks to prompt tokens if necessary.""" |
| 487 | max_input_tokens = self.max_input_tokens |
| 488 | if max_input_tokens is None: |
| 489 | return tokens |
| 490 | |
| 491 | if len(tokens) > max_input_tokens: |
| 492 | token_count = len(tokens) |
| 493 | # The tokenizer may have truncated the prompt to |
| 494 | # max_input_tokens + 1 (see get_encode_kwargs), so the |
| 495 | # actual prompt length could be larger. |
| 496 | qualifier = "at least " if token_count == max_input_tokens + 1 else "" |
| 497 | total = token_count + self.max_output_tokens |
| 498 | raise VLLMValidationError( |
| 499 | f"This model's maximum context length is " |
| 500 | f"{self.max_total_tokens} tokens. However, you requested " |
| 501 | f"{self.max_output_tokens} output tokens and your prompt " |
| 502 | f"contains {qualifier}{token_count} input tokens, " |
| 503 | f"for a total of {qualifier}{total} tokens. " |
| 504 | f"Please reduce the length of the input prompt or the " |
| 505 | f"number of requested output tokens.", |
| 506 | parameter="input_tokens", |
| 507 | value=token_count, |
| 508 | ) |
| 509 | |
| 510 | return tokens |
| 511 | |
| 512 | def _validate_tokens(self, tokenizer: TokenizerLike | None, tokens: _S) -> _S: |
| 513 | """Apply all validators to a token sequence.""" |
| 514 | # Truncation runs before padding, matching the Transformers pipeline |
| 515 | # these parameters are named after. Padding first would let a |
| 516 | # subsequent left-side truncation keep only the pad tokens it just |
| 517 | # appended, discarding the prompt entirely. |
| 518 | for validator in ( |
| 519 | self._token_truncation, |
| 520 | self._token_padding, |
| 521 | self._token_len_check, |
| 522 | ): |
| 523 | tokens = validator(tokenizer, tokens) |
| 524 | |
| 525 | return tokens |
| 526 | |
| 527 | def apply_post_tokenization( |
| 528 | self, |
| 529 | tokenizer: TokenizerLike | None, |
| 530 | prompt: TokensPrompt | EmbedsPrompt, |
| 531 | ) -> TokensPrompt | EmbedsPrompt: |
| 532 | """Ensure that the prompt meets the requirements set out by this config. |
| 533 | If that is not possible, raise a `VLLMValidationError`. |
| 534 | |
| 535 | This method is run after tokenization occurs. |
| 536 | """ |
| 537 | if "prompt_token_ids" in prompt: |
| 538 | prompt["prompt_token_ids"] = self._validate_tokens( # type: ignore[typeddict-unknown-key] |
| 539 | tokenizer, |
| 540 | prompt["prompt_token_ids"], # type: ignore[typeddict-item] |
| 541 | ) |
| 542 | if "prompt_embeds" in prompt: |
| 543 | prompt["prompt_embeds"] = self._validate_tokens( # type: ignore[typeddict-unknown-key] |
| 544 | tokenizer, |
| 545 | prompt["prompt_embeds"], # type: ignore[typeddict-item] |
| 546 | ) |
| 547 | prompt_dict = cast(dict, prompt) |
| 548 | for key in _PARALLEL_TO_PROMPT_TOKENS: |
| 549 | parallel = prompt_dict.get(key) |
| 550 | if parallel is None: |
| 551 | continue |
| 552 | truncation = self._truncation_slice(tokenizer, len(parallel)) |
| 553 | if truncation is not None: |
| 554 | prompt_dict[key] = parallel[truncation] |
| 555 | |
| 556 | return prompt |