# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project from collections.abc import Callable, Sequence from typing import Any from tqdm.auto import tqdm from vllm.entrypoints.chat_utils import ChatTemplateConfig from vllm.entrypoints.offline_utils import OfflineInferenceMixin from vllm.inputs import DataPrompt, PromptType from vllm.logger import init_logger from vllm.lora.request import LoRARequest from vllm.outputs import ( ClassificationRequestOutput, EmbeddingRequestOutput, PoolingRequestOutput, ScoringRequestOutput, ) from vllm.pooling_params import PoolingParams from vllm.tasks import SCORE_TYPE_MAP, PoolingTask, SupportedTask from .factories import init_pooling_io_processors from .scoring.io_processor import ScoringIOProcessor from .scoring.typing import ScoreInput from .typing import OfflineInputsContext, OfflineOutputsContext logger = init_logger(__name__) class PoolingOfflineMixin(OfflineInferenceMixin): """Offline inference for pooling models""" runner_type: str chat_template: str | None supported_tasks: tuple[SupportedTask, ...] def __init__(self): self.pooling_task = self.model_config.get_pooling_task(self.supported_tasks) if self.pooling_task is not None: logger.info("Supported pooling task: %s", self.pooling_task) self.chat_template_config = ChatTemplateConfig(chat_template=self.chat_template) self.pooling_io_processors = init_pooling_io_processors( supported_tasks=self.supported_tasks, vllm_config=self.llm_engine.vllm_config, renderer=self.renderer, chat_template_config=self.chat_template_config, ) def encode( self, prompts: PromptType | Sequence[PromptType] | DataPrompt, pooling_params: PoolingParams | Sequence[PoolingParams] | None = None, *, use_tqdm: bool | Callable[..., tqdm] = True, lora_request: list[LoRARequest] | LoRARequest | None = None, pooling_task: PoolingTask | None = None, tokenization_kwargs: dict[str, Any] | None = None, ) -> list[PoolingRequestOutput]: """Apply pooling to the hidden states corresponding to the input prompts. This class automatically batches the given prompts, considering the memory constraint. For the best performance, put all of your prompts into a single list and pass it to this method. Args: prompts: The prompts to the LLM. You may pass a sequence of prompts for batch inference. See [PromptType][vllm.inputs.PromptType] for more details about the format of each prompt. pooling_params: The pooling parameters for pooling. If None, we use the default pooling parameters. use_tqdm: If `True`, shows a tqdm progress bar. If a callable (e.g., `functools.partial(tqdm, leave=False)`), it is used to create the progress bar. If `False`, no progress bar is created. lora_request: LoRA request to use for generation, if any. pooling_task: Override the pooling task to use. tokenization_kwargs: Overrides for `tokenizer.encode`. Returns: A list of `PoolingRequestOutput` objects containing the pooled hidden states in the same order as the input prompts. """ if isinstance(prompts, dict) and "data" in prompts and pooling_task != "plugin": raise ValueError( "The 'data' field is only supported for the 'plugin' pooling task." ) self._verify_pooling_task(pooling_task) assert pooling_task is not None and pooling_task in self.pooling_io_processors io_processor = self.pooling_io_processors[pooling_task] if pooling_params is None: pooling_params = PoolingParams() ctx = OfflineInputsContext( prompts=prompts, pooling_params=pooling_params, tokenization_kwargs=tokenization_kwargs, ) engine_inputs = io_processor.pre_process_offline(ctx) n_inputs = len(engine_inputs) assert ctx.pooling_params is not None params_seq = self._params_to_seq(ctx.pooling_params, n_inputs) for param in params_seq: if param.task is None: param.task = pooling_task elif pooling_task == "plugin": # `plugin` task uses io_processor.parse_request to verify inputs. # We actually allow plugin to overwrite pooling_task. pass elif param.task != pooling_task: msg = f"You cannot overwrite {param.task=!r} with {pooling_task=!r}!" raise ValueError(msg) seq_lora_requests = self._lora_request_to_seq(lora_request, n_inputs) seq_priority = self._priority_to_seq(None, n_inputs) self._render_and_add_requests( prompts=engine_inputs, params=params_seq, lora_requests=seq_lora_requests, priorities=seq_priority, ) outputs = self._run_engine(use_tqdm=use_tqdm, output_type=PoolingRequestOutput) outputs = io_processor.post_process_offline( ctx=OfflineOutputsContext(outputs=outputs) ) return outputs def _verify_pooling_task(self, pooling_task: PoolingTask | None): if self.runner_type != "pooling": raise ValueError( "LLM.encode() is only supported for pooling models. " "Try passing `--runner pooling` to use the model as a " "pooling model." ) if pooling_task is None: raise ValueError( """ pooling_task required for `LLM.encode`. Please use one of the more specific methods or set the pooling_task when using `LLM.encode`: - For embeddings, use `LLM.embed(...)` or `pooling_task="embed"`. - For classification logits, use `LLM.classify(...)` or `pooling_task="classify"`. - For similarity scores, use `LLM.score(...)`. - For rewards, `pooling_task="classify"` or `pooling_task="token_classify"`. - For token classification, use `pooling_task="token_classify"`. - For multi-vector retrieval, use `pooling_task="token_embed"`. """ # noqa: E501 ) if ( pooling_task in ("embed", "token_embed") and pooling_task not in self.supported_tasks ): raise ValueError( "Embedding API is not supported by this model. " "Try converting the model using `--convert embed`." ) if ( pooling_task in ("classify", "token_classify") and pooling_task not in self.supported_tasks ): raise ValueError( "Classification API is not supported by this model. " "Try converting the model using `--convert classify`." ) # plugin task uses io_processor.parse_request to verify inputs if pooling_task != "plugin" and pooling_task != self.pooling_task: if pooling_task not in self.supported_tasks: raise ValueError( f"Unsupported task: {pooling_task!r} " f"Supported tasks: {self.supported_tasks}" ) else: raise ValueError( f"Try switching the model's pooling_task " f'via `PoolerConfig(task="{pooling_task}")`' ) if pooling_task == "plugin" and "plugin" not in self.pooling_io_processors: raise ValueError( "No IOProcessor plugin installed. Please refer " "to the documentation and to the " "'prithvi_geospatial_mae_io_processor' " "offline inference example for more details." ) def embed( self, prompts: PromptType | Sequence[PromptType], *, use_tqdm: bool | Callable[..., tqdm] = True, pooling_params: PoolingParams | Sequence[PoolingParams] | None = None, lora_request: list[LoRARequest] | LoRARequest | None = None, tokenization_kwargs: dict[str, Any] | None = None, ) -> list[EmbeddingRequestOutput]: """ Generate an embedding vector for each prompt. This class automatically batches the given prompts, considering the memory constraint. For the best performance, put all of your prompts into a single list and pass it to this method. Args: prompts: The prompts to the LLM. You may pass a sequence of prompts for batch inference. See [PromptType][vllm.inputs.PromptType] for more details about the format of each prompt. pooling_params: The pooling parameters for pooling. If None, we use the default pooling parameters. use_tqdm: If `True`, shows a tqdm progress bar. If a callable (e.g., `functools.partial(tqdm, leave=False)`), it is used to create the progress bar. If `False`, no progress bar is created. lora_request: LoRA request to use for generation, if any. tokenization_kwargs: Overrides for `tokenizer.encode`. Returns: A list of `EmbeddingRequestOutput` objects containing the embedding vectors in the same order as the input prompts. """ items = self.encode( prompts, use_tqdm=use_tqdm, pooling_params=pooling_params, lora_request=lora_request, pooling_task="embed", tokenization_kwargs=tokenization_kwargs, ) return [EmbeddingRequestOutput.from_base(item) for item in items] def classify( self, prompts: PromptType | Sequence[PromptType], *, pooling_params: PoolingParams | Sequence[PoolingParams] | None = None, use_tqdm: bool | Callable[..., tqdm] = True, lora_request: list[LoRARequest] | LoRARequest | None = None, tokenization_kwargs: dict[str, Any] | None = None, ) -> list[ClassificationRequestOutput]: """ Generate class logits for each prompt. This class automatically batches the given prompts, considering the memory constraint. For the best performance, put all of your prompts into a single list and pass it to this method. Args: prompts: The prompts to the LLM. You may pass a sequence of prompts for batch inference. See [PromptType][vllm.inputs.PromptType] for more details about the format of each prompt. pooling_params: The pooling parameters for pooling. If None, we use the default pooling parameters. use_tqdm: If `True`, shows a tqdm progress bar. If a callable (e.g., `functools.partial(tqdm, leave=False)`), it is used to create the progress bar. If `False`, no progress bar is created. lora_request: LoRA request to use for generation, if any. tokenization_kwargs: Overrides for `tokenizer.encode`. Returns: A list of `ClassificationRequestOutput` objects containing the embedding vectors in the same order as the input prompts. """ items = self.encode( prompts, use_tqdm=use_tqdm, pooling_params=pooling_params, lora_request=lora_request, pooling_task="classify", tokenization_kwargs=tokenization_kwargs, ) return [ClassificationRequestOutput.from_base(item) for item in items] def score( self, data_1: ScoreInput | list[ScoreInput], data_2: ScoreInput | list[ScoreInput], /, *, use_tqdm: bool | Callable[..., tqdm] = True, pooling_params: PoolingParams | None = None, lora_request: list[LoRARequest] | LoRARequest | None = None, tokenization_kwargs: dict[str, Any] | None = None, chat_template: str | None = None, ) -> list[ScoringRequestOutput]: """Generate similarity scores for all pairs `` or ``. The inputs can be `1 -> 1`, `1 -> N` or `N -> N`. In the `1 - N` case the `data_1` input will be replicated `N` times to pair with the `data_2` inputs. The input pairs are used to build a list of prompts for the cross encoder model. This class automatically batches the prompts, considering the memory constraint. For the best performance, put all of your inputs into a single list and pass it to this method. Supports both text and multi-modal data (images, etc.) when used with appropriate multi-modal models. For multi-modal inputs, ensure the prompt structure matches the model's expected input format. Args: data_1: Can be a single prompt, a list of prompts or `ScoreMultiModalParam`, which can contain either text or multi-modal data. When a list, it must have the same length as the `data_2` list. data_2: The data to pair with the query to form the input to the LLM. Can be text or multi-modal data. See [PromptType] [vllm.inputs.PromptType] for more details about the format of each prompt. pooling_params: The pooling parameters for pooling. If None, we use the default pooling parameters. use_tqdm: If `True`, shows a tqdm progress bar. If a callable (e.g., `functools.partial(tqdm, leave=False)`), it is used to create the progress bar. If `False`, no progress bar is created. lora_request: LoRA request to use for generation, if any. chat_template: The chat template to use for the scoring. If None, we use the model's default chat template. tokenization_kwargs: Overrides for `tokenizer.encode`. Returns: A list of `ScoringRequestOutput` objects containing the generated scores in the same order as the input prompts. """ if self.runner_type != "pooling": raise ValueError( "LLM.score() is only supported for pooling models. " "Try passing `--runner pooling` to use the model as a " "pooling model." ) score_type: str | None = SCORE_TYPE_MAP.get(self.pooling_task, None) # type: ignore[arg-type] if ( score_type == "cross-encoder" and getattr(self.model_config.hf_config, "num_labels", 0) != 1 ): raise ValueError("Scoring API is only enabled for num_labels == 1.") if score_type is None or score_type not in self.pooling_io_processors: raise ValueError("This model does not support the Scoring API.") io_processor = self.pooling_io_processors[score_type] assert isinstance(io_processor, ScoringIOProcessor) pooling_task = io_processor.pooling_task scoring_data = io_processor.valid_inputs(data_1, data_2) n_queries = len(scoring_data.data_1) if pooling_params is None: pooling_params = PoolingParams() ctx = OfflineInputsContext( prompts=scoring_data, pooling_params=pooling_params, tokenization_kwargs=tokenization_kwargs, chat_template=chat_template, n_queries=n_queries, ) engine_inputs = io_processor.pre_process_offline(ctx) n_inputs = len(engine_inputs) seq_lora_requests = self._lora_request_to_seq(lora_request, n_inputs) params_seq = self._params_to_seq(ctx.pooling_params, n_inputs) for param in params_seq: if param.task is None: param.task = pooling_task elif param.task != pooling_task: msg = f"You cannot overwrite {param.task=!r} with {pooling_task=!r}!" raise ValueError(msg) seq_priority = self._priority_to_seq(None, n_inputs) self._render_and_add_requests( prompts=engine_inputs, params=params_seq, lora_requests=seq_lora_requests, priorities=seq_priority, ) outputs = self._run_engine(use_tqdm=use_tqdm, output_type=PoolingRequestOutput) outputs = io_processor.post_process_offline( ctx=OfflineOutputsContext(outputs=outputs, n_queries=n_queries), ) return [ScoringRequestOutput.from_base(item) for item in outputs]