processor.py 7.79 KB
Newer Older
1
2
3
import time
from typing import Any, Dict, Mapping, Optional, Tuple, Union

4
from vllm.config import CacheConfig, LoRAConfig, ModelConfig
5
6
7
from vllm.inputs import (INPUT_REGISTRY, InputRegistry, ProcessorInputs,
                         PromptType, SingletonInputsAdapter)
from vllm.inputs.parse import is_encoder_decoder_inputs
8
9
from vllm.inputs.preprocess import InputPreprocessor
from vllm.lora.request import LoRARequest
10
11
from vllm.multimodal import (MULTIMODAL_REGISTRY, MultiModalKwargs,
                             MultiModalRegistry)
12
13
14
15
from vllm.pooling_params import PoolingParams
from vllm.prompt_adapter.request import PromptAdapterRequest
from vllm.sampling_params import SamplingParams
from vllm.transformers_utils.config import try_get_generation_config
16
from vllm.transformers_utils.tokenizer_group import BaseTokenizerGroup
17
from vllm.v1.engine import DetokenizerRequest, EngineCoreRequest
18
from vllm.v1.engine.mm_input_mapper import MMHasher, MMInputMapperClient
19
20
21
22
23
24
25


class Processor:

    def __init__(
        self,
        model_config: ModelConfig,
26
        cache_config: CacheConfig,
27
        lora_config: Optional[LoRAConfig],
28
        tokenizer: BaseTokenizerGroup,
29
        input_registry: InputRegistry = INPUT_REGISTRY,
30
        mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
31
32
33
34
35
36
37
38
39
    ):

        self.model_config = model_config
        self.lora_config = lora_config
        self.tokenizer = tokenizer

        self.generation_config_fields = _load_generation_config_dict(
            model_config)
        self.input_preprocessor = InputPreprocessor(model_config,
40
41
                                                    self.tokenizer,
                                                    mm_registry)
42
43
44
        self.input_processor = input_registry.create_input_processor(
            model_config)

45
        # Multi-modal (huggingface) input mapper
46
47
48
        self.mm_input_mapper_client = MMInputMapperClient(model_config)

        # Multi-modal hasher (for images)
49
        self.use_hash = (not model_config.disable_mm_preprocessor_cache) or \
50
51
            cache_config.enable_prefix_caching
        self.mm_hasher = MMHasher()
52

53
54
55
56
57
58
59
60
    # TODO: run in an ThreadpoolExecutor or BackgroundProcess.
    # This ideally should releases the GIL, so we should not block the
    # asyncio loop while this is running.
    def process_inputs(
        self,
        request_id: str,
        prompt: PromptType,
        params: Union[SamplingParams, PoolingParams],
61
        arrival_time: Optional[float] = None,
62
63
64
65
66
67
        lora_request: Optional[LoRARequest] = None,
        trace_headers: Optional[Mapping[str, str]] = None,
        prompt_adapter_request: Optional[PromptAdapterRequest] = None,
        priority: int = 0,
    ) -> Tuple[DetokenizerRequest, EngineCoreRequest]:

68
        # TODO(woosuk): Support pooling models.
69
70
71
72
73
74
75
76
77
78
79
        # TODO(woosuk): Check max_logprobs
        # TODO(woosuk): Support encoder-decoder models.

        if lora_request is not None and not self.lora_config:
            raise ValueError(f"Got lora_request {lora_request} but LoRA is "
                             "not enabled!")
        if arrival_time is None:
            arrival_time = time.time()
        assert priority == 0, "vLLM V1 does not support priority at the moment."
        assert trace_headers is None, "vLLM V1 does not support tracing yet."

80
81
        # Compute MM hashes (if enabled)
        mm_hashes = None
82
        if self.use_hash:
83
            mm_hashes = self.mm_hasher.hash_prompt(prompt)
84

85
86
87
88
89
90
91
92
93
94
95
        # Process inputs.
        preprocessed_inputs = self.input_preprocessor.preprocess(
            prompt,
            request_id=request_id,
            lora_request=lora_request,
            prompt_adapter_request=prompt_adapter_request,
        )
        processed_inputs = self.input_processor(preprocessed_inputs)
        self._validate_model_inputs(processed_inputs)
        eos_token_id = self.input_preprocessor.get_eos_token_id(lora_request)

96
97
98
99
100
101
102
103
104
105
106
107
108
        if is_encoder_decoder_inputs(processed_inputs):
            decoder_inputs = SingletonInputsAdapter(
                processed_inputs["decoder"])
            encoder_inputs = SingletonInputsAdapter(
                processed_inputs["encoder"])
        else:
            decoder_inputs = SingletonInputsAdapter(processed_inputs)
            encoder_inputs = None

        # TODO: Impl encoder-decoder
        if encoder_inputs is not None:
            raise NotImplementedError

109
110
111
112
113
114
        assert isinstance(params, SamplingParams)
        # TODO: can we avoid cloning here in multiproc case
        sampling_params = params.clone()
        sampling_params.update_from_generation_config(
            self.generation_config_fields, eos_token_id)

115
116
117
118
119
120
121
122
        # For merged preprocessor, mm_data is already mm_inputs
        precomputed_mm_inputs = None
        if isinstance(decoder_inputs.multi_modal_data, MultiModalKwargs):
            precomputed_mm_inputs = [decoder_inputs.multi_modal_data]

        # Apply MM mapper
        mm_inputs = None
        if len(decoder_inputs.multi_modal_data) > 0:
123
            mm_inputs = self.mm_input_mapper_client.process_inputs(
124
125
                decoder_inputs.multi_modal_data, mm_hashes,
                decoder_inputs.mm_processor_kwargs, precomputed_mm_inputs)
126

127
128
        # Make Request for Detokenizer.
        detokenizer_request = DetokenizerRequest(
129
130
131
            request_id,
            decoder_inputs.prompt,
            decoder_inputs.prompt_token_ids,
132
133
            sampling_params.skip_special_tokens,
            sampling_params.spaces_between_special_tokens,
134
135
136
137
            sampling_params.output_kind,
            sampling_params.stop,
            sampling_params.include_stop_str_in_output,
        )
138
139
140

        # Make Request for EngineCore.
        engine_core_request = EngineCoreRequest(
141
142
143
            request_id,
            decoder_inputs.prompt,
            decoder_inputs.prompt_token_ids,
144
            mm_inputs,
145
            mm_hashes,
146
147
148
149
150
151
            decoder_inputs.multi_modal_placeholders,
            sampling_params,
            eos_token_id,
            arrival_time,
            lora_request,
        )
152
153
154

        return detokenizer_request, engine_core_request

155
156
157
158
159
160
161
162
163
164
165
    def _validate_model_inputs(self, inputs: ProcessorInputs):
        if is_encoder_decoder_inputs(inputs):
            # For encoder-decoder multimodal models, the max_prompt_len
            # restricts the decoder prompt length
            prompt_inputs = inputs["decoder" if self.model_config.
                                   is_multimodal_model else "encoder"]
        else:
            prompt_inputs = inputs

        prompt_ids = SingletonInputsAdapter(prompt_inputs).prompt_token_ids

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
        if prompt_ids is None or len(prompt_ids) == 0:
            raise ValueError("Prompt cannot be empty")

        if self.model_config.is_multimodal_model:
            max_prompt_len = self.model_config.max_model_len

            if len(prompt_ids) > max_prompt_len:
                raise ValueError(
                    f"The prompt (total length {len(prompt_ids)}) is too long "
                    f"to fit into the model (context length {max_prompt_len}). "
                    "Make sure that `max_model_len` is no smaller than the "
                    "number of text tokens plus multimodal tokens. For image "
                    "inputs, the number of image tokens depends on the number "
                    "of images, and possibly their aspect ratios as well.")

181
182
183
184
            # TODO: Find out how many placeholder tokens are there so we can
            # check that chunked prefill does not truncate them
            # max_batch_len = self.scheduler_config.max_num_batched_tokens

185
186
187
188
189
190
191
192
193
194
195
196

def _load_generation_config_dict(model_config: ModelConfig) -> Dict[str, Any]:
    config = try_get_generation_config(
        model_config.model,
        trust_remote_code=model_config.trust_remote_code,
        revision=model_config.revision,
    )

    if config is None:
        return {}

    return config.to_diff_dict()