main
py 356 lines 12 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from collections.abc import Sequence
5 from concurrent.futures import Executor
6 from typing import Any, Final, cast
7
8 from vllm import (
9 PoolingParams,
10 PoolingRequestOutput,
11 )
12 from vllm.config import VllmConfig
13 from vllm.entrypoints.chat_utils import (
14 ChatTemplateConfig,
15 )
16 from vllm.exceptions import VLLMValidationError
17 from vllm.lora.request import LoRARequest
18 from vllm.renderers import BaseRenderer, merge_kwargs
19 from vllm.renderers.inputs.preprocess import parse_model_prompt, prompt_to_seq
20 from vllm.utils.async_utils import make_async
21 from vllm.utils.mistral import is_mistral_tokenizer
22
23 from ..typing import (
24 AnyOfflineInputsContext,
25 AnyRenderParam,
26 EncodeChatRenderParams,
27 EncodeCMPLRenderParams,
28 OfflineEncodeInputsContext,
29 OfflineOutputsContext,
30 PoolingChatLikeRequest,
31 PoolingCompletionLikeRequest,
32 PoolingEngineInput,
33 PoolingServeContext,
34 RequestFactory,
35 RequestGenerator,
36 ScoringRenderParams,
37 )
38
39
40 class PoolingIOProcessor:
41 """Processor for handling preprocessing & postprocessing ops for pooling requests.
42
43 This class manages both online (serving) and offline (batch) processing of pooling
44 requests, handling chat and completion formats.
45 """
46
47 name: str
48
49 def __init__(
50 self,
51 vllm_config: VllmConfig,
52 renderer: BaseRenderer,
53 chat_template_config: ChatTemplateConfig,
54 ):
55 self.vllm_config = vllm_config
56 self.model_config = vllm_config.model_config
57 self.renderer = renderer
58
59 self.chat_template = chat_template_config.chat_template
60 self.chat_template_content_format: Final = (
61 chat_template_config.chat_template_content_format
62 )
63 self.trust_request_chat_template = (
64 chat_template_config.trust_request_chat_template
65 )
66
67 self.template_kwargs = None
68 self.tool_dicts = None
69
70 # Shared thread pool executor for preprocessing
71 self._executor: Executor = self.renderer._executor
72 self.render_async = make_async(self.render, executor=self._executor)
73
74 #######################################
75 # online APIs
76
77 def create_pooling_params(self, request):
78 return request.to_pooling_params()
79
80 def get_request_factory_online(
81 self, ctx: PoolingServeContext
82 ) -> Sequence[AnyRenderParam]:
83 request = ctx.request
84 renderer = self.renderer
85 requests: Sequence[AnyRenderParam]
86
87 if isinstance(request, PoolingChatLikeRequest):
88 self._validate_chat_template(
89 request_chat_template=request.chat_template,
90 chat_template_kwargs=request.chat_template_kwargs,
91 trust_request_chat_template=self.trust_request_chat_template,
92 )
93
94 num_requests = 1
95 default_template_kwargs = merge_kwargs(
96 self.template_kwargs,
97 dict(
98 tools=self.tool_dicts,
99 tokenize=is_mistral_tokenizer(renderer.tokenizer),
100 ),
101 )
102
103 mm_config = self.model_config.multimodal_config
104 tok_params = request.build_tok_params(self.model_config)
105 chat_params = request.build_chat_params(
106 self.chat_template, self.chat_template_content_format
107 ).with_defaults(
108 default_template_kwargs,
109 default_media_io_kwargs=(
110 mm_config.media_io_kwargs if mm_config else None
111 ),
112 )
113
114 params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
115 seq_lora_requests = self._lora_request_to_seq(
116 ctx.lora_request, num_requests
117 )
118 seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
119
120 requests = [
121 EncodeChatRenderParams(
122 conversations=request.messages,
123 chat_params=chat_params,
124 tok_params=tok_params,
125 prompt_extras=ctx.prompt_extras,
126 skip_mm_cache=False,
127 params=params_seq[i],
128 lora_requests=seq_lora_requests[i],
129 priorities=seq_priority[i],
130 )
131 for i in range(num_requests)
132 ]
133
134 return requests
135
136 elif isinstance(request, PoolingCompletionLikeRequest):
137 model_config = self.model_config
138 prompts_seq = prompt_to_seq(request.input)
139 num_requests = len(prompts_seq)
140
141 parsed_prompts = [
142 (
143 prompt
144 if isinstance(prompt, bytes)
145 else parse_model_prompt(model_config, prompt)
146 )
147 for prompt in prompts_seq
148 ]
149 tok_params = request.build_tok_params(model_config)
150
151 params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
152 seq_lora_requests = self._lora_request_to_seq(
153 ctx.lora_request, num_requests
154 )
155 seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
156
157 requests = [
158 EncodeCMPLRenderParams(
159 prompts=parsed_prompts[i],
160 tok_params=tok_params,
161 prompt_extras=ctx.prompt_extras,
162 skip_mm_cache=False,
163 params=params_seq[i],
164 lora_requests=seq_lora_requests[i],
165 priorities=seq_priority[i],
166 )
167 for i in range(num_requests)
168 ]
169
170 return requests
171 else:
172 raise ValueError(f"Invalid {self.name} request type")
173
174 def post_process_online(
175 self,
176 ctx: PoolingServeContext,
177 ):
178 pass
179
180 #######################################
181 # offline APIs
182
183 def get_request_factory_offline(
184 self, ctx: AnyOfflineInputsContext
185 ) -> tuple[RequestFactory, int]:
186 assert isinstance(ctx, OfflineEncodeInputsContext)
187
188 prompts_seq = prompt_to_seq(ctx.prompts)
189 num_requests = len(prompts_seq)
190 pooling_task = ctx.pooling_task
191
192 parsed_prompts = [
193 (
194 prompt
195 if isinstance(prompt, bytes)
196 else parse_model_prompt(self.model_config, prompt)
197 )
198 for prompt in prompts_seq
199 ]
200 tok_params = self.renderer.default_cmpl_tok_params.with_kwargs(
201 **(ctx.tokenization_kwargs or {})
202 )
203
204 pooling_params: PoolingParams | Sequence[PoolingParams]
205 if ctx.pooling_params is None:
206 pooling_params = PoolingParams()
207 else:
208 pooling_params = ctx.pooling_params
209
210 # Clone to avoid modifying the caller's pooling parameters.
211 params_seq = [
212 param.clone() for param in self._params_to_seq(pooling_params, num_requests)
213 ]
214
215 for param in params_seq:
216 if param.task is None:
217 param.task = pooling_task
218 elif pooling_task == "plugin":
219 # `plugin` task uses io_processor.parse_data to verify inputs.
220 # We actually allow plugin to overwrite pooling_task.
221 pass
222 elif param.task != pooling_task:
223 msg = f"You cannot overwrite {param.task=!r} with {pooling_task=!r}!"
224 raise VLLMValidationError(msg)
225
226 seq_lora_requests = self._lora_request_to_seq(ctx.lora_request, num_requests)
227 seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
228
229 def request_factory() -> RequestGenerator:
230 for i in range(num_requests):
231 yield EncodeCMPLRenderParams(
232 prompts=parsed_prompts[i],
233 tok_params=tok_params,
234 prompt_extras=None,
235 skip_mm_cache=False,
236 params=params_seq[i],
237 lora_requests=seq_lora_requests[i],
238 priorities=seq_priority[i],
239 )
240
241 return request_factory, num_requests
242
243 def post_process_offline(
244 self,
245 ctx: OfflineOutputsContext,
246 ) -> list[PoolingRequestOutput]:
247 return ctx.outputs
248
249 #######################################
250 # helpers
251
252 def render(
253 self,
254 render_params: EncodeCMPLRenderParams
255 | EncodeChatRenderParams
256 | ScoringRenderParams,
257 ) -> PoolingEngineInput:
258 if "conversations" in render_params:
259 render_params = cast(EncodeChatRenderParams, render_params)
260 (_,), engine_input = self.renderer.render_chat(
261 conversations=[render_params["conversations"]],
262 chat_params=render_params["chat_params"],
263 tok_params=render_params["tok_params"],
264 prompt_extras=render_params["prompt_extras"],
265 skip_mm_cache=render_params["skip_mm_cache"],
266 )
267 elif "prompts" in render_params:
268 render_params = cast(EncodeCMPLRenderParams, render_params)
269 engine_input = self.renderer.render_cmpl(
270 prompts=[render_params["prompts"]],
271 tok_params=render_params["tok_params"],
272 prompt_extras=render_params["prompt_extras"],
273 skip_mm_cache=render_params["skip_mm_cache"],
274 )
275 else:
276 raise ValueError(
277 f"Unsupported render_params type {render_params.__class__.__name__}"
278 )
279
280 return PoolingEngineInput(
281 prompts=engine_input[0],
282 params=render_params["params"],
283 lora_requests=render_params["lora_requests"],
284 priorities=render_params["priorities"],
285 )
286
287 def _validate_chat_template(
288 self,
289 request_chat_template: str | None,
290 chat_template_kwargs: dict[str, Any] | None,
291 trust_request_chat_template: bool,
292 ):
293 if not trust_request_chat_template and (
294 request_chat_template is not None
295 or (
296 chat_template_kwargs
297 and chat_template_kwargs.get("chat_template") is not None
298 )
299 ):
300 raise VLLMValidationError(
301 "Chat template is passed with request, but "
302 "--trust-request-chat-template is not set. "
303 "Refused request with untrusted chat template."
304 )
305 return None
306
307 def _params_to_seq(
308 self,
309 params: PoolingParams | Sequence[PoolingParams],
310 num_requests: int,
311 ) -> Sequence[PoolingParams]:
312 if isinstance(params, Sequence):
313 if len(params) != num_requests:
314 raise VLLMValidationError(
315 f"The lengths of prompts ({num_requests}) "
316 f"and params ({len(params)}) must be the same."
317 )
318
319 return params
320
321 return [params] * num_requests
322
323 def _lora_request_to_seq(
324 self,
325 lora_request: LoRARequest | None | Sequence[LoRARequest | None],
326 num_requests: int,
327 ) -> Sequence[LoRARequest | None]:
328 if isinstance(lora_request, Sequence):
329 if len(lora_request) != num_requests:
330 raise VLLMValidationError(
331 f"The lengths of prompts ({num_requests}) "
332 f"and lora_request ({len(lora_request)}) must be the same."
333 )
334
335 return lora_request
336
337 return [lora_request] * num_requests
338
339 def _priority_to_seq(
340 self,
341 priority: int | Sequence[int] | None,
342 num_requests: int,
343 ) -> Sequence[int]:
344 if priority is not None:
345 if isinstance(priority, int):
346 return [priority] * num_requests
347
348 if len(priority) != num_requests:
349 raise ValueError(
350 f"The lengths of prompts ({num_requests}) "
351 f"and priority ({len(priority)}) must be the same."
352 )
353
354 return priority
355
356 return [0] * num_requests