[Frontend] Parallelize preprocessing within the same request for pooling models online serving. (#49153)
Signed-off-by: wang.yuqi <yuqi.wang@daocloud.io>
wang.yuqi committed
Jul 22, 2026 at 18:56 UTC
61c9ef986a807aa3b9c6ccd25bb223b8f4116ac7
17 files changed
+530
-812
tests/entrypoints/pooling/embed/test_io_processor.py
+23
-231
@@ -9,8 +9,6 @@ from pydantic import TypeAdapter, ValidationError
9
from vllm import PoolingParams
10
from vllm.entrypoints.pooling.embed.io_processor import EmbedIOProcessor
11
from vllm.entrypoints.pooling.embed.protocol import (
12
- CohereEmbedContent,
13
- CohereEmbedInput,
12
CohereEmbedRequest,
13
EmbeddingBatchChatInputRequest,
14
EmbeddingBatchChatRequest,
@@ -19,7 +17,10 @@ from vllm.entrypoints.pooling.embed.protocol import (
17
EmbeddingCompletionRequest,
18
EmbeddingRequest,
19
)
22
-from vllm.entrypoints.pooling.typing import PoolingServeContext
20
+from vllm.entrypoints.pooling.typing import (
21
+ PoolingEngineInput,
22
+ PoolingServeContext,
23
+)
24
from vllm.outputs import PoolingOutput, PoolingRequestOutput
25
26
@@ -410,6 +411,7 @@ class TestChunkedEmbeddingProcessing:
411
def _make_handler(cls):
412
handler = object.__new__(EmbedIOProcessor)
413
handler.model_config = cls._FakeModelConfig()
414
+ handler.enable_chunked_processing = True
415
return handler
416
417
@staticmethod
@@ -421,15 +423,29 @@ class TestChunkedEmbeddingProcessing:
423
}
424
)
425
assert isinstance(request, EmbeddingCompletionRequest)
426
+ pooling_params = PoolingParams()
427
return PoolingServeContext(
428
request=request,
426
- pooling_params=PoolingParams(),
429
+ pooling_params=pooling_params,
430
model_name="test",
431
request_id="embd-client-prompt-999-chunk-888",
432
engine_inputs=[
430
- {"prompt_token_ids": [0, 1, 2, 3, 4]},
431
- {"prompt_token_ids": [10, 11]},
433
+ PoolingEngineInput(
434
+ prompts={"prompt_token_ids": [0, 1, 2, 3, 4]},
435
+ params=pooling_params,
436
+ lora_requests=None,
437
+ priorities=0,
438
+ ),
439
+ PoolingEngineInput(
440
+ prompts={"prompt_token_ids": [10, 11]},
441
+ params=pooling_params,
442
+ lora_requests=None,
443
+ priorities=0,
444
+ ),
445
],
446
+ lora_request=None,
447
+ priorities=0,
448
+ prompt_extras=None,
449
)
450
451
@staticmethod
@@ -450,7 +466,7 @@ class TestChunkedEmbeddingProcessing:
466
handler = self._make_handler()
467
ctx = self._make_context()
468
453
- handler._pre_process_chunked(ctx)
469
+ handler.maybe_pre_process_chunked(ctx)
470
471
assert ctx.prompt_request_ids == [
472
"embd-client-prompt-999-chunk-888-prompt-0-chunk-0",
@@ -488,227 +504,3 @@ class TestChunkedEmbeddingProcessing:
504
ctx.final_res_batch[1].outputs.data,
505
torch.tensor([9.0, 9.0]),
506
)
491
-
492
-
493
-class TestPreProcessCohereOnline:
494
- """Unit tests for EmbedIOProcessor._pre_process_cohere_online."""
495
-
496
- @staticmethod
497
- def _make_context(**request_kwargs) -> PoolingServeContext[CohereEmbedRequest]:
498
- return PoolingServeContext(
499
- request=CohereEmbedRequest(model="test", **request_kwargs),
500
- pooling_params=PoolingParams(),
501
- model_name="test",
502
- request_id="embd-test",
503
- )
504
-
505
- @staticmethod
506
- def _make_handler():
507
- handler = object.__new__(EmbedIOProcessor)
508
- handler._validate_input_type = lambda _input_type: None
509
- return handler
510
-
511
- def test_text_only_without_task_prefix_uses_completion_path(self):
512
- handler = self._make_handler()
513
- ctx = self._make_context(texts=["hello"])
514
- calls: list[tuple[str, object]] = []
515
-
516
- def preprocess_cmpl_online(request, prompt_input, prompt_embeds):
517
- calls.append(("completion", prompt_input))
518
- return ["completion"]
519
-
520
- handler._get_task_instruction_prefix = lambda _input_type: None
521
- handler._has_chat_template = lambda: False
522
- handler._preprocess_cmpl_online = preprocess_cmpl_online
523
- handler._batch_render_chat = lambda *_args, **_kwargs: pytest.fail(
524
- "text-only request should not require chat rendering"
525
- )
526
-
527
- handler._pre_process_cohere_online(ctx)
528
-
529
- assert ctx.engine_inputs == ["completion"]
530
- assert calls == [("completion", ["hello"])]
531
-
532
- def test_text_only_falls_back_to_prefixed_completion_without_template(self):
533
- handler = self._make_handler()
534
- ctx = self._make_context(texts=["hello"], input_type="query")
535
- calls: list[tuple[str, object]] = []
536
-
537
- def preprocess_cmpl(request, prompt_input, prompt_embeds):
538
- calls.append(("completion", prompt_input))
539
- return ["fallback"]
540
-
541
- handler._get_task_instruction_prefix = lambda _input_type: "query: "
542
- handler._has_chat_template = lambda: False
543
- handler._batch_render_chat = lambda *_args, **_kwargs: pytest.fail(
544
- "chat rendering should be skipped without a template"
545
- )
546
- handler._preprocess_cmpl_online = preprocess_cmpl
547
-
548
- handler._pre_process_cohere_online(ctx)
549
-
550
- assert ctx.engine_inputs == ["fallback"]
551
- assert calls == [("completion", ["query: hello"])]
552
-
553
- def test_text_only_with_template_uses_chat_path(self):
554
- handler = self._make_handler()
555
- ctx = self._make_context(texts=["hello"], input_type="query")
556
- calls: list[tuple[str, object]] = []
557
-
558
- def batch_render_chat(
559
- request,
560
- all_messages,
561
- truncate_prompt_tokens,
562
- truncation_side,
563
- ):
564
- calls.append(
565
- (
566
- "chat",
567
- {
568
- "request": request,
569
- "all_messages": all_messages,
570
- "truncate_prompt_tokens": truncate_prompt_tokens,
571
- "truncation_side": truncation_side,
572
- },
573
- )
574
- )
575
- return ["chat"]
576
-
577
- handler._get_task_instruction_prefix = lambda _input_type: "query: "
578
- handler._has_chat_template = lambda: True
579
- handler._batch_render_chat = batch_render_chat
580
- handler._preprocess_cmpl_online = lambda *_args, **_kwargs: pytest.fail(
581
- "completion path should be skipped when a template exists"
582
- )
583
-
584
- handler._pre_process_cohere_online(ctx)
585
-
586
- assert ctx.engine_inputs == ["chat"]
587
- assert calls == [
588
- (
589
- "chat",
590
- {
591
- "request": ctx.request,
592
- "all_messages": [
593
- handler._mixed_input_to_messages(
594
- CohereEmbedInput(
595
- content=[CohereEmbedContent(type="text", text="hello")]
596
- ),
597
- task_prefix="query: ",
598
- )
599
- ],
600
- "truncate_prompt_tokens": -1,
601
- "truncation_side": None,
602
- },
603
- )
604
- ]
605
-
606
-
607
-class TestPreProcessOpenAIEmbeddingChatOnline:
608
- """Unit tests for OpenAI embedding chat preprocessing."""
609
-
610
- class _FakeModelConfig:
611
- max_model_len = 128
612
- encoder_config: dict[str, object] = {}
613
- pooler_config = None
614
- multimodal_config = None
615
- is_encoder_decoder = False
616
-
617
- class _FakeRenderer:
618
- tokenizer = object()
619
-
620
- def __init__(self):
621
- self.calls = []
622
-
623
- def render_chat(
624
- self,
625
- all_messages,
626
- chat_params,
627
- tok_params,
628
- prompt_extras=None,
629
- ):
630
- self.calls.append(
631
- {
632
- "all_messages": all_messages,
633
- "chat_params": chat_params,
634
- "tok_params": tok_params,
635
- "prompt_extras": prompt_extras,
636
- }
637
- )
638
- return all_messages, [
639
- {"prompt_token_ids": [index]} for index, _ in enumerate(all_messages)
640
- ]
641
-
642
- @classmethod
643
- def _make_handler(cls, renderer):
644
- handler = object.__new__(EmbedIOProcessor)
645
- handler.renderer = renderer
646
- handler.model_config = cls._FakeModelConfig()
647
- handler.chat_template = "template"
648
- handler.chat_template_content_format = "auto"
649
- handler.trust_request_chat_template = False
650
- handler.enable_chunked_processing = False
651
- return handler
652
-
653
- @staticmethod
654
- def _make_context(
655
- request: (
656
- EmbeddingChatRequest
657
- | EmbeddingBatchChatRequest
658
- | EmbeddingChatInputRequest
659
- | EmbeddingBatchChatInputRequest
660
- ),
661
- ) -> PoolingServeContext[
662
- EmbeddingChatRequest
663
- | EmbeddingBatchChatRequest
664
- | EmbeddingChatInputRequest
665
- | EmbeddingBatchChatInputRequest
666
- ]:
667
- return PoolingServeContext(
668
- request=request,
669
- pooling_params=PoolingParams(),
670
- model_name="test",
671
- request_id="embd-test",
672
- )
673
-
674
- def test_chat_template_kwargs_forwarded_for_batched_input_messages(self):
675
- request = TypeAdapter(EmbeddingRequest).validate_python(
676
- {
677
- "model": "test",
678
- "input": [
679
- [{"role": "user", "content": "hello"}],
680
- [{"role": "user", "content": "goodbye"}],
681
- ],
682
- "add_generation_prompt": True,
683
- "chat_template_kwargs": {"instruction": "Represent the query: "},
684
- "mm_processor_kwargs": {"max_pixels": 1},
685
- "cache_salt": "salt",
686
- }
687
- )
688
- assert isinstance(request, EmbeddingBatchChatInputRequest)
689
-
690
- renderer = self._FakeRenderer()
691
- handler = self._make_handler(renderer)
692
- ctx = self._make_context(request)
693
-
694
- handler.pre_process_online(ctx)
695
-
696
- assert ctx.engine_inputs == [
697
- {"prompt_token_ids": [0]},
698
- {"prompt_token_ids": [1]},
699
- ]
700
- assert len(renderer.calls) == 1
701
-
702
- call = renderer.calls[0]
703
- assert call["all_messages"] == request.messages
704
- assert call["prompt_extras"] == {
705
- "mm_processor_kwargs": {"max_pixels": 1},
706
- "cache_salt": "salt",
707
- }
708
-
709
- chat_template_kwargs = call["chat_params"].chat_template_kwargs
710
- assert chat_template_kwargs["instruction"] == "Represent the query: "
711
- assert chat_template_kwargs["add_generation_prompt"] is True
712
- assert chat_template_kwargs["continue_final_message"] is False
713
- assert "tools" not in chat_template_kwargs
714
- assert chat_template_kwargs["tokenize"] is False
tests/entrypoints/pooling/scoring/test_cross_encoder_offline.py
-43
@@ -2,7 +2,6 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import weakref
5
-from types import SimpleNamespace
5
6
import pytest
7
import torch
@@ -10,10 +9,7 @@ import torch
9
from tests.models.utils import softmax
10
from vllm import LLM, PoolingParams
11
from vllm.distributed import cleanup_dist_env_and_memory
13
-from vllm.entrypoints.pooling.scoring.io_processor import CrossEncoderIOProcessor
14
-from vllm.entrypoints.pooling.scoring.typing import ScoringData
12
from vllm.platforms import current_platform
16
-from vllm.renderers import TokenizeParams
13
14
MODEL_NAME = "tomaarsen/Qwen3-Reranker-0.6B-seq-cls"
15
PROMPT = "The chef prepared a delicious meal."
@@ -145,45 +141,6 @@ def test_max_tokens_per_doc(llm: LLM):
141
assert with_limit_tokens < no_limit_tokens
142
143
148
-def test_token_type_ids_follow_post_tokenization():
149
- processor = object.__new__(CrossEncoderIOProcessor)
150
- processor.tokenizer = SimpleNamespace(truncation_side="right", pad_token_id=-1)
151
- processor.renderer = SimpleNamespace(process_for_engine=lambda prompt, _: prompt)
152
- processor.model_config = None
153
- processor.get_score_prompt = lambda **_: (
154
- "",
155
- {
156
- "prompt_token_ids": list(range(32)),
157
- "token_type_ids": [0] * 16 + [1] * 16,
158
- },
159
- )
160
-
161
- engine_inputs, pooling_params = processor._pre_process(
162
- ScoringData(data_1=["query"], data_2=["document"]),
163
- TokenizeParams(
164
- max_total_tokens=None,
165
- truncate_prompt_tokens=16,
166
- truncation_side="left",
167
- ),
168
- PoolingParams(task="classify", extra_kwargs={"cache_salt": "salt"}),
169
- )
170
-
171
- assert engine_inputs[0]["prompt_token_ids"] == list(range(16, 32))
172
- assert pooling_params[0].extra_kwargs == {
173
- "cache_salt": "salt",
174
- "compressed_token_type_ids": 0,
175
- }
176
-
177
- engine_inputs, pooling_params = processor._pre_process(
178
- ScoringData(data_1=["query"], data_2=["document"]),
179
- TokenizeParams(max_total_tokens=None, pad_prompt_tokens=40),
180
- PoolingParams(task="classify"),
181
- )
182
-
183
- assert engine_inputs[0]["prompt_token_ids"] == list(range(32)) + [-1] * 8
184
- assert pooling_params[0].extra_kwargs == {"compressed_token_type_ids": 16}
185
-
186
-
144
def test_pooling_params(llm: LLM):
145
def get_outputs(use_activation):
146
outputs = llm.score(
tests/entrypoints/serve/lora/test_serving_models.py
+8
-6
@@ -161,7 +161,9 @@ def _make_pooling_serving(lora_name: str) -> _ConcretePoolingServing:
161
return serving
162
163
164
-def _make_pooling_ctx(model_name: str) -> PoolingServeContext:
164
+def _make_pooling_ctx(
165
+ model_name: str, serving: PoolingBaseServing
166
+) -> PoolingServeContext:
167
mock_request = MagicMock()
168
mock_request.model = model_name
169
return PoolingServeContext(
@@ -169,6 +171,9 @@ def _make_pooling_ctx(model_name: str) -> PoolingServeContext:
171
model_name=MODEL_NAME,
172
request_id="test-id",
173
pooling_params=PoolingParams(),
174
+ lora_request=serving._maybe_get_adapters(mock_request),
175
+ priorities=0,
176
+ prompt_extras=None,
177
)
178
179
@@ -176,9 +181,7 @@ def test_pooling_maybe_get_adapters_lora_name_sets_lora_request():
181
"""LoRA adapter name must populate ctx.lora_request without raising."""
182
lora_name = "bot-embed-lora"
183
serving = _make_pooling_serving(lora_name)
179
- ctx = _make_pooling_ctx(lora_name)
180
-
181
- ctx.lora_request = serving._maybe_get_adapters(ctx.request)
184
+ ctx = _make_pooling_ctx(lora_name, serving)
185
186
assert ctx.lora_request is not None
187
assert ctx.lora_request.lora_name == lora_name
@@ -187,7 +190,6 @@ def test_pooling_maybe_get_adapters_lora_name_sets_lora_request():
190
def test_pooling_maybe_get_adapters_unknown_model_raises():
191
"""An unrecognised model name must still raise VLLMNotFoundError."""
192
serving = _make_pooling_serving("some-lora")
190
- ctx = _make_pooling_ctx("unknown-model")
193
194
with pytest.raises(VLLMNotFoundError):
193
- serving._maybe_get_adapters(ctx.request)
195
+ _make_pooling_ctx("unknown-model", serving)
vllm/entrypoints/pooling/base/io_processor.py
+96
-100
@@ -2,6 +2,7 @@
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 (
@@ -10,21 +11,17 @@ from vllm import (
11
)
12
from vllm.config import VllmConfig
13
from vllm.entrypoints.chat_utils import (
13
- ChatCompletionMessageParam,
14
ChatTemplateConfig,
15
- ChatTemplateContentFormatOption,
16
- ConversationMessage,
15
)
18
-from vllm.entrypoints.serve.engine.typing import RendererChatRequest, RendererRequest
19
-from vllm.inputs import EngineInput, SingletonPrompt
16
from vllm.lora.request import LoRARequest
17
from vllm.renderers import BaseRenderer, merge_kwargs
18
from vllm.renderers.inputs.preprocess import parse_model_prompt, prompt_to_seq
23
-from vllm.tool_parsers import ToolParser
19
+from vllm.utils.async_utils import make_async
20
from vllm.utils.mistral import is_mistral_tokenizer
21
22
from ..typing import (
27
- ALLOfflineInputsContext,
23
+ AnyOfflineInputsContext,
24
+ AnyRenderParam,
25
EncodeChatRenderParams,
26
EncodeCMPLRenderParams,
27
OfflineEncodeInputsContext,
@@ -66,14 +63,25 @@ class PoolingIOProcessor:
63
chat_template_config.trust_request_chat_template
64
)
65
66
+ self.template_kwargs = None
67
+ self.tool_dicts = None
68
+
69
+ # Shared thread pool executor for preprocessing
70
+ self._executor: Executor = self.renderer._executor
71
+ self.render_async = make_async(self.render, executor=self._executor)
72
+
73
#######################################
74
# online APIs
75
76
def create_pooling_params(self, request):
77
return request.to_pooling_params()
78
75
- def pre_process_online(self, ctx: PoolingServeContext):
79
+ def get_request_factory_online(
80
+ self, ctx: PoolingServeContext
81
+ ) -> Sequence[AnyRenderParam]:
82
request = ctx.request
83
+ renderer = self.renderer
84
+ requests: Sequence[AnyRenderParam]
85
86
if isinstance(request, PoolingChatLikeRequest):
87
self._validate_chat_template(
@@ -81,24 +89,87 @@ class PoolingIOProcessor:
89
chat_template_kwargs=request.chat_template_kwargs,
90
trust_request_chat_template=self.trust_request_chat_template,
91
)
84
- _, engine_inputs = self._preprocess_chat_online(
85
- request,
86
- request.messages,
87
- default_template=self.chat_template,
88
- default_template_content_format=self.chat_template_content_format,
89
- default_template_kwargs=None,
92
+
93
+ num_requests = 1
94
+ default_template_kwargs = merge_kwargs(
95
+ self.template_kwargs,
96
+ dict(
97
+ tools=self.tool_dicts,
98
+ tokenize=is_mistral_tokenizer(renderer.tokenizer),
99
+ ),
100
+ )
101
+
102
+ mm_config = self.model_config.multimodal_config
103
+ tok_params = request.build_tok_params(self.model_config)
104
+ chat_params = request.build_chat_params(
105
+ self.chat_template, self.chat_template_content_format
106
+ ).with_defaults(
107
+ default_template_kwargs,
108
+ default_media_io_kwargs=(
109
+ mm_config.media_io_kwargs if mm_config else None
110
+ ),
111
+ )
112
+
113
+ params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
114
+ seq_lora_requests = self._lora_request_to_seq(
115
+ ctx.lora_request, num_requests
116
)
117
+ seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
118
+
119
+ requests = [
120
+ EncodeChatRenderParams(
121
+ conversations=request.messages,
122
+ chat_params=chat_params,
123
+ tok_params=tok_params,
124
+ prompt_extras=ctx.prompt_extras,
125
+ skip_mm_cache=False,
126
+ params=params_seq[i],
127
+ lora_requests=seq_lora_requests[i],
128
+ priorities=seq_priority[i],
129
+ )
130
+ for i in range(num_requests)
131
+ ]
132
+
133
+ return requests
134
+
135
elif isinstance(request, PoolingCompletionLikeRequest):
92
- engine_inputs = self._preprocess_cmpl_online(
93
- request,
94
- prompt_input=request.input,
95
- prompt_embeds=None,
136
+ model_config = self.model_config
137
+ prompts_seq = prompt_to_seq(request.input)
138
+ num_requests = len(prompts_seq)
139
+
140
+ parsed_prompts = [
141
+ (
142
+ prompt
143
+ if isinstance(prompt, bytes)
144
+ else parse_model_prompt(model_config, prompt)
145
+ )
146
+ for prompt in prompts_seq
147
+ ]
148
+ tok_params = request.build_tok_params(model_config)
149
+
150
+ params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
151
+ seq_lora_requests = self._lora_request_to_seq(
152
+ ctx.lora_request, num_requests
153
)
154
+ seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
155
+
156
+ requests = [
157
+ EncodeCMPLRenderParams(
158
+ prompts=parsed_prompts[i],
159
+ tok_params=tok_params,
160
+ prompt_extras=ctx.prompt_extras,
161
+ skip_mm_cache=False,
162
+ params=params_seq[i],
163
+ lora_requests=seq_lora_requests[i],
164
+ priorities=seq_priority[i],
165
+ )
166
+ for i in range(num_requests)
167
+ ]
168
+
169
+ return requests
170
else:
171
raise ValueError(f"Invalid {self.name} request type")
172
100
- ctx.engine_inputs = engine_inputs
101
-
173
def post_process_online(
174
self,
175
ctx: PoolingServeContext,
@@ -109,7 +180,7 @@ class PoolingIOProcessor:
180
# offline APIs
181
182
def get_request_factory_offline(
112
- self, ctx: ALLOfflineInputsContext
183
+ self, ctx: AnyOfflineInputsContext
184
) -> tuple[RequestFactory, int]:
185
assert isinstance(ctx, OfflineEncodeInputsContext)
186
@@ -209,84 +280,6 @@ class PoolingIOProcessor:
280
priorities=render_params["priorities"],
281
)
282
212
- def _preprocess_cmpl_online(
213
- self,
214
- request: RendererRequest,
215
- prompt_input: str | list[str] | list[int] | list[list[int]] | None,
216
- prompt_embeds: bytes | list[bytes] | None,
217
- ) -> list[EngineInput]:
218
- renderer = self.renderer
219
- model_config = self.model_config
220
-
221
- prompts = list[SingletonPrompt | bytes]()
222
- if prompt_embeds is not None: # embeds take higher priority
223
- prompts.extend(prompt_to_seq(prompt_embeds))
224
- if prompt_input is not None:
225
- prompts.extend(prompt_to_seq(prompt_input))
226
-
227
- parsed_prompts = [
228
- (
229
- prompt
230
- if isinstance(prompt, bytes)
231
- else parse_model_prompt(model_config, prompt)
232
- )
233
- for prompt in prompts
234
- ]
235
- tok_params = request.build_tok_params(model_config)
236
-
237
- return renderer.render_cmpl(
238
- parsed_prompts,
239
- tok_params,
240
- prompt_extras={
241
- k: v
242
- for k in ("mm_processor_kwargs", "cache_salt")
243
- if (v := getattr(request, k, None)) is not None
244
- },
245
- )
246
-
247
- def _preprocess_chat_online(
248
- self,
249
- request: RendererChatRequest,
250
- messages: list[ChatCompletionMessageParam],
251
- default_template: str | None,
252
- default_template_content_format: ChatTemplateContentFormatOption,
253
- default_template_kwargs: dict[str, Any] | None,
254
- tool_dicts: list[dict[str, Any]] | None = None,
255
- tool_parser: type[ToolParser] | None = None,
256
- ) -> tuple[list[ConversationMessage], list[EngineInput]]:
257
- renderer = self.renderer
258
-
259
- default_template_kwargs = merge_kwargs(
260
- default_template_kwargs,
261
- dict(
262
- tools=tool_dicts,
263
- tokenize=is_mistral_tokenizer(renderer.tokenizer),
264
- ),
265
- )
266
-
267
- mm_config = self.model_config.multimodal_config
268
-
269
- tok_params = request.build_tok_params(self.model_config)
270
- chat_params = request.build_chat_params(
271
- default_template, default_template_content_format
272
- ).with_defaults(
273
- default_template_kwargs,
274
- default_media_io_kwargs=(mm_config.media_io_kwargs if mm_config else None),
275
- )
276
-
277
- (conversation,), (engine_input,) = renderer.render_chat(
278
- [messages],
279
- chat_params,
280
- tok_params,
281
- prompt_extras={
282
- k: v
283
- for k in ("mm_processor_kwargs", "cache_salt")
284
- if (v := getattr(request, k, None)) is not None
285
- },
286
- )
287
-
288
- return conversation, [engine_input]
289
-
283
def _validate_chat_template(
284
self,
285
request_chat_template: str | None,
@@ -341,10 +334,13 @@ class PoolingIOProcessor:
334
335
def _priority_to_seq(
336
self,
344
- priority: Sequence[int] | None,
337
+ priority: int | Sequence[int] | None,
338
num_requests: int,
339
) -> Sequence[int]:
340
if priority is not None:
341
+ if isinstance(priority, int):
342
+ return [priority] * num_requests
343
+
344
if len(priority) != num_requests:
345
raise ValueError(
346
f"The lengths of prompts ({num_requests}) "
vllm/entrypoints/pooling/base/serving.py
+31
-29
@@ -1,6 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-
3
+import asyncio
4
from abc import ABC, abstractmethod
5
from collections.abc import AsyncGenerator, Mapping
6
from concurrent.futures import Executor
@@ -63,9 +63,6 @@ class PoolingBaseServing(ABC, BaseServing):
63
64
# Shared thread pool executor for preprocessing and postprocessing.
65
self._executor: Executor = self.renderer._executor
66
- self._preprocessing_async = make_async(
67
- self._preprocessing, executor=self._executor
68
- )
66
self._postprocessing_async = make_async(
67
self._postprocessing, executor=self._executor
68
)
@@ -77,7 +74,7 @@ class PoolingBaseServing(ABC, BaseServing):
74
) -> Response:
75
io_processor = self.get_io_processor(request)
76
ctx = await self._init_ctx(io_processor, request, raw_request)
80
- await self._preprocessing_async(io_processor, ctx)
77
+ await self._preprocessing(io_processor, ctx)
78
await self._prepare_generators(ctx)
79
await self._collect_batch(ctx)
80
return await self._postprocessing_async(io_processor, ctx)
@@ -86,11 +83,17 @@ class PoolingBaseServing(ABC, BaseServing):
83
def get_io_processor(self, request: AnyPoolingRequest) -> PoolingIOProcessor:
84
raise NotImplementedError
85
89
- @torch.inference_mode()
90
- def _preprocessing(
86
+ async def _preprocessing(
87
self, io_processor: PoolingIOProcessor, ctx: PoolingServeContext
88
):
93
- return io_processor.pre_process_online(ctx)
89
+ requests = io_processor.get_request_factory_online(ctx)
90
+
91
+ if len(requests) == 0:
92
+ raise ValueError("You must pass at least one prompt")
93
+
94
+ ctx.engine_inputs = await asyncio.gather(
95
+ *[io_processor.render_async(request) for request in requests]
96
+ )
97
98
@torch.inference_mode()
99
def _postprocessing(
@@ -110,16 +113,26 @@ class PoolingBaseServing(ABC, BaseServing):
113
await self._check_model(request)
114
115
pooling_params = io_processor.create_pooling_params(request)
116
+ lora_request = self._maybe_get_adapters(request)
117
+ priorities = getattr(request, "priority", 0)
118
+ prompt_extras = {
119
+ k: v
120
+ for k in ("mm_processor_kwargs", "cache_salt", "chat_template_kwargs")
121
+ if (v := getattr(request, k, None)) is not None
122
+ }
123
+
124
ctx = PoolingServeContext(
125
request=request,
126
raw_request=raw_request,
127
model_name=model_name,
128
pooling_params=pooling_params,
129
request_id=request_id,
130
+ lora_request=lora_request,
131
+ priorities=priorities,
132
+ prompt_extras=prompt_extras,
133
)
134
135
self._validate_request(ctx)
122
- ctx.lora_request = self._maybe_get_adapters(ctx.request)
136
return ctx
137
138
async def _prepare_generators(
@@ -127,7 +140,7 @@ class PoolingBaseServing(ABC, BaseServing):
140
ctx: PoolingServeContext,
141
):
142
if ctx.engine_inputs is None:
130
- raise ValueError("Engine prompts not available")
143
+ raise ValueError("Engine inputs not available")
144
145
generators: list[AsyncGenerator[PoolingRequestOutput, None]] = []
146
@@ -139,12 +152,7 @@ class PoolingBaseServing(ABC, BaseServing):
152
153
assert ctx.pooling_params is not None
154
pooling_params = ctx.pooling_params
142
-
143
- if isinstance(pooling_params, list):
144
- for params in pooling_params:
145
- params.verify(self.model_config)
146
- else:
147
- pooling_params.verify(self.model_config)
155
+ pooling_params.verify(self.model_config)
156
157
for i, engine_input in enumerate(ctx.engine_inputs):
158
prompt_request_id = (
@@ -153,26 +161,20 @@ class PoolingBaseServing(ABC, BaseServing):
161
else ctx.prompt_request_ids[i]
162
)
163
156
- params = (
157
- pooling_params[i]
158
- if isinstance(pooling_params, list)
159
- else pooling_params
160
- )
161
-
164
self._log_inputs(
165
prompt_request_id,
164
- engine_input,
165
- params=params,
166
+ engine_input["prompts"],
167
+ params=engine_input["params"],
168
lora_request=ctx.lora_request,
169
)
170
171
generator = self.engine_client.encode(
170
- engine_input,
171
- params,
172
- prompt_request_id,
173
- lora_request=ctx.lora_request,
172
+ request_id=prompt_request_id,
173
+ prompt=engine_input["prompts"],
174
+ pooling_params=engine_input["params"],
175
+ lora_request=engine_input["lora_requests"],
176
+ priority=engine_input["priorities"],
177
trace_headers=trace_headers,
175
- priority=getattr(ctx.request, "priority", 0),
178
)
179
180
generators.append(generator)
vllm/entrypoints/pooling/classify/api_router.py
+5
-5
@@ -16,8 +16,11 @@ from .serving import ServingClassification
16
router = APIRouter()
17
18
19
-def classify(request: Request) -> ServingClassification | None:
20
- return request.app.state.serving_classification
19
+def classify(request: Request) -> ServingClassification:
20
+ handler = getattr(request.app.state, "serving_classification", None)
21
+ if handler is None:
22
+ raise NotImplementedError("The model does not support Classification API")
23
+ return handler
24
25
26
@router.post("/classify", dependencies=[Depends(validate_json_request)])
@@ -27,7 +30,4 @@ async def create_classify(
30
request: ClassificationRequest, raw_request: Request
31
) -> Response:
32
handler = classify(raw_request)
30
- if handler is None:
31
- raise NotImplementedError("The model does not support Classification API")
32
-
33
return await handler(request, raw_request)
vllm/entrypoints/pooling/embed/api_router.py
+5
-8
@@ -18,8 +18,11 @@ from .serving import ServingEmbedding
18
router = APIRouter()
19
20
21
-def embedding(request: Request) -> ServingEmbedding | None:
22
- return request.app.state.serving_embedding
21
+def embedding(request: Request) -> ServingEmbedding:
22
+ handler = getattr(request.app.state, "serving_embedding", None)
23
+ if handler is None:
24
+ raise NotImplementedError("The model does not support Embeddings API")
25
+ return handler
26
27
28
@router.post(
@@ -37,9 +40,6 @@ async def create_embedding(
40
raw_request: Request,
41
):
42
handler = embedding(raw_request)
40
- if handler is None:
41
- raise NotImplementedError("The model does not support Embeddings API")
42
-
43
return await handler(request, raw_request)
44
45
@@ -58,7 +58,4 @@ async def create_cohere_embedding(
58
raw_request: Request,
59
):
60
handler = embedding(raw_request)
61
- if handler is None:
62
- raise NotImplementedError("The model does not support Embeddings API")
63
-
61
return await handler(request, raw_request)
vllm/entrypoints/pooling/embed/io_processor.py
+125
-131
@@ -17,7 +17,7 @@ from vllm.entrypoints.chat_utils import (
17
ChatCompletionMessageParam,
18
CustomChatCompletionMessageParam,
19
)
20
-from vllm.inputs import EngineInput, tokens_input
20
+from vllm.inputs import tokens_input
21
from vllm.logger import init_logger
22
from vllm.outputs import PoolingOutput, PoolingRequestOutput
23
from vllm.renderers import merge_kwargs
@@ -28,11 +28,14 @@ from vllm.utils.mistral import is_mistral_tokenizer
28
from ..base.io_processor import PoolingIOProcessor
29
from ..scoring.io_processor import JinaRankingIOProcessorMixin
30
from ..typing import (
31
- ALLOfflineInputsContext,
31
+ AnyOfflineInputsContext,
32
+ AnyRenderParam,
33
ChunkedEmbeddingMetadata,
34
+ EncodeChatRenderParams,
35
OfflineEncodeInputsContext,
36
PoolingChatLikeRequest,
37
PoolingCompletionLikeRequest,
38
+ PoolingEngineInput,
39
PoolingServeContext,
40
RequestFactory,
41
)
@@ -76,9 +79,11 @@ class EmbedIOProcessor(PoolingIOProcessor):
79
list(self.task_instructions.keys()),
80
)
81
79
- def pre_process_online(self, ctx: PoolingServeContext):
82
+ def get_request_factory_online(
83
+ self, ctx: PoolingServeContext
84
+ ) -> Sequence[AnyRenderParam]:
85
if isinstance(ctx.request, CohereEmbedRequest):
81
- self._pre_process_cohere_online(ctx)
86
+ requests = self._get_request_factory_cohere_online(ctx)
87
elif isinstance(
88
ctx.request,
89
(
@@ -88,12 +93,11 @@ class EmbedIOProcessor(PoolingIOProcessor):
93
EmbeddingBatchChatInputRequest,
94
),
95
):
91
- self._pre_process_openai_chat_online(ctx)
96
+ requests = self._get_request_factory_chat_input_online(ctx)
97
else:
93
- super().pre_process_online(ctx)
98
+ requests = super().get_request_factory_online(ctx)
99
95
- if self.enable_chunked_processing:
96
- self._pre_process_chunked(ctx)
100
+ return requests
101
102
def post_process_online(
103
self,
@@ -114,18 +118,21 @@ class EmbedIOProcessor(PoolingIOProcessor):
118
# PTAL: examples/pooling/embed/openai_embedding_long_text
119
#################################################################
120
117
- def _pre_process_chunked(self, ctx: PoolingServeContext) -> None:
121
+ def maybe_pre_process_chunked(self, ctx: PoolingServeContext) -> None:
122
+ if not self.enable_chunked_processing:
123
+ return None
124
+
125
if ctx.engine_inputs is None:
126
raise ValueError("Engine prompts not available")
127
128
ctx.original_engine_inputs = ctx.engine_inputs
129
request_id = ctx.request_id
130
max_model_len = self.model_config.max_model_len
124
- chunked_engine_inputs: list[EngineInput] = []
131
+ chunked_engine_inputs: list[PoolingEngineInput] = []
132
prompt_request_ids: list[str] = []
133
chunked_embedding_metadata: list[ChunkedEmbeddingMetadata] = []
134
for prompt_idx, engine_input in enumerate(ctx.engine_inputs):
128
- token_ids = engine_input.get("prompt_token_ids", None)
135
+ token_ids = engine_input["prompts"].get("prompt_token_ids", None)
136
if token_ids is None:
137
raise NotImplementedError(
138
"Long Text Embedding with Chunked Processing does "
@@ -138,7 +145,12 @@ class EmbedIOProcessor(PoolingIOProcessor):
145
chunk_list(prompt_token_ids, max_model_len)
146
):
147
chunked_engine_inputs.append(
141
- tokens_input(prompt_token_ids=chunk_tokens)
148
+ PoolingEngineInput(
149
+ prompts=tokens_input(prompt_token_ids=chunk_tokens),
150
+ params=engine_input["params"],
151
+ lora_requests=engine_input["lora_requests"],
152
+ priorities=engine_input["priorities"],
153
+ )
154
)
155
prompt_request_ids.append(
156
f"{request_id}-prompt-{prompt_idx}-chunk-{chunk_idx}"
@@ -234,7 +246,7 @@ class EmbedIOProcessor(PoolingIOProcessor):
246
247
# Get original prompt token IDs for this prompt
248
original_prompt = original_engine_inputs[prompt_idx]
237
- token_ids = original_prompt.get("prompt_token_ids", None)
249
+ token_ids = original_prompt["prompts"].get("prompt_token_ids", None)
250
if token_ids is None:
251
raise NotImplementedError(
252
"Long Text Embedding with Chunked Processing does "
@@ -262,6 +274,68 @@ class EmbedIOProcessor(PoolingIOProcessor):
274
275
return None
276
277
+ #################################################################
278
+ # Chat input Request Preprocessing & Postprocessing
279
+ #################################################################
280
+
281
+ def _get_request_factory_chat_input_online(
282
+ self,
283
+ ctx: PoolingServeContext,
284
+ ) -> Sequence[AnyRenderParam]:
285
+ request = ctx.request
286
+ renderer = self.renderer
287
+
288
+ self._validate_chat_template(
289
+ request_chat_template=request.chat_template,
290
+ chat_template_kwargs=request.chat_template_kwargs,
291
+ trust_request_chat_template=self.trust_request_chat_template,
292
+ )
293
+
294
+ if isinstance(
295
+ request, (EmbeddingBatchChatRequest, EmbeddingBatchChatInputRequest)
296
+ ):
297
+ all_messages = request.messages
298
+ else:
299
+ all_messages = [request.messages]
300
+ num_requests = len(all_messages)
301
+
302
+ default_template_kwargs = merge_kwargs(
303
+ self.template_kwargs,
304
+ dict(
305
+ tools=self.tool_dicts,
306
+ tokenize=is_mistral_tokenizer(renderer.tokenizer),
307
+ ),
308
+ )
309
+
310
+ mm_config = self.model_config.multimodal_config
311
+ tok_params = request.build_tok_params(self.model_config)
312
+ chat_params = request.build_chat_params(
313
+ self.chat_template, self.chat_template_content_format
314
+ ).with_defaults(
315
+ default_template_kwargs,
316
+ default_media_io_kwargs=(mm_config.media_io_kwargs if mm_config else None),
317
+ )
318
+
319
+ params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
320
+ seq_lora_requests = self._lora_request_to_seq(ctx.lora_request, num_requests)
321
+ seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
322
+
323
+ requests = [
324
+ EncodeChatRenderParams(
325
+ conversations=all_messages[i],
326
+ chat_params=chat_params,
327
+ tok_params=tok_params,
328
+ prompt_extras=ctx.prompt_extras,
329
+ skip_mm_cache=False,
330
+ params=params_seq[i],
331
+ lora_requests=seq_lora_requests[i],
332
+ priorities=seq_priority[i],
333
+ )
334
+ for i in range(num_requests)
335
+ ]
336
+
337
+ return requests
338
+
339
#################################################################
340
# Cohere Request Preprocessing & Postprocessing
341
#################################################################
@@ -378,71 +452,9 @@ class EmbedIOProcessor(PoolingIOProcessor):
452
)
453
return super().create_pooling_params(request)
454
381
- def _pre_process_openai_chat_online(
382
- self,
383
- ctx: PoolingServeContext[
384
- EmbeddingChatRequest
385
- | EmbeddingBatchChatRequest
386
- | EmbeddingChatInputRequest
387
- | EmbeddingBatchChatInputRequest
388
- ],
389
- ) -> None:
390
- request = ctx.request
391
- self._validate_chat_template(
392
- request_chat_template=request.chat_template,
393
- chat_template_kwargs=request.chat_template_kwargs,
394
- trust_request_chat_template=self.trust_request_chat_template,
395
- )
396
-
397
- if isinstance(
398
- request, (EmbeddingBatchChatRequest, EmbeddingBatchChatInputRequest)
399
- ):
400
- all_messages = request.messages
401
- else:
402
- all_messages = [request.messages]
403
- ctx.engine_inputs = self._batch_render_openai_chat(request, all_messages)
404
-
405
- def _batch_render_openai_chat(
406
- self,
407
- request: (
408
- EmbeddingChatRequest
409
- | EmbeddingBatchChatRequest
410
- | EmbeddingChatInputRequest
411
- | EmbeddingBatchChatInputRequest
412
- ),
413
- all_messages: Sequence[list[ChatCompletionMessageParam]],
414
- ) -> list[EngineInput]:
415
- renderer = self.renderer
416
- mm_config = self.model_config.multimodal_config
417
-
418
- tok_params = request.build_tok_params(self.model_config)
419
- chat_params = request.build_chat_params(
420
- self.chat_template,
421
- self.chat_template_content_format,
422
- ).with_defaults(
423
- merge_kwargs(
424
- None,
425
- dict(
426
- tools=None,
427
- tokenize=is_mistral_tokenizer(renderer.tokenizer),
428
- ),
429
- ),
430
- default_media_io_kwargs=(mm_config.media_io_kwargs if mm_config else None),
431
- )
432
-
433
- _, engine_inputs = renderer.render_chat(
434
- all_messages,
435
- chat_params,
436
- tok_params,
437
- prompt_extras={
438
- k: v
439
- for k in ("mm_processor_kwargs", "cache_salt")
440
- if (v := getattr(request, k, None)) is not None
441
- },
442
- )
443
- return engine_inputs
444
-
445
- def _pre_process_cohere_online(self, ctx: PoolingServeContext) -> None:
455
+ def _get_request_factory_cohere_online(
456
+ self, ctx: PoolingServeContext
457
+ ) -> Sequence[AnyRenderParam]:
458
"""Convert a ``CohereEmbedRequest`` into engine prompts.
459
460
If a model has a chat template the task instruction are rendered
@@ -479,13 +491,12 @@ class EmbedIOProcessor(PoolingIOProcessor):
491
task_prefix = self._get_task_instruction_prefix(input_type)
492
493
if task_prefix is None:
482
- ctx.engine_inputs = self._preprocess_cohere_text_completion(
483
- request,
494
+ return self._get_request_factory_cohere_text_completion(
495
+ ctx,
496
texts,
497
truncate_prompt_tokens,
498
truncation_side,
499
)
488
- return
500
501
all_messages = [
502
self._mixed_input_to_messages(
@@ -497,27 +508,30 @@ class EmbedIOProcessor(PoolingIOProcessor):
508
for text in texts
509
]
510
if self._has_chat_template():
500
- ctx.engine_inputs = self._batch_render_chat(
501
- request,
511
+ return self._batch_render_chat(
512
+ ctx,
513
all_messages,
514
truncate_prompt_tokens,
515
truncation_side,
516
)
517
else:
507
- ctx.engine_inputs = self._preprocess_cohere_text_completion(
508
- request,
518
+ return self._get_request_factory_cohere_text_completion(
519
+ ctx,
520
self._apply_task_instruction(texts, input_type),
521
truncate_prompt_tokens,
522
truncation_side,
523
)
513
- return
524
525
task_prefix = self._get_task_instruction_prefix(input_type)
526
all_messages = [
527
self._mixed_input_to_messages(inp, task_prefix=task_prefix) for inp in input
528
]
519
- ctx.engine_inputs = self._batch_render_chat(
520
- request, all_messages, truncate_prompt_tokens, truncation_side
529
+
530
+ return self._batch_render_chat(
531
+ ctx,
532
+ all_messages,
533
+ truncate_prompt_tokens,
534
+ truncation_side,
535
)
536
537
def _has_chat_template(self) -> bool:
@@ -531,13 +545,14 @@ class EmbedIOProcessor(PoolingIOProcessor):
545
is not None
546
)
547
534
- def _preprocess_cohere_text_completion(
548
+ def _get_request_factory_cohere_text_completion(
549
self,
536
- request: CohereEmbedRequest,
550
+ ctx: PoolingServeContext,
551
texts: list[str],
552
truncate_prompt_tokens: int | None,
553
truncation_side: Literal["left", "right"] | None,
540
- ) -> list[EngineInput]:
554
+ ) -> Sequence[AnyRenderParam]:
555
+ request = ctx.request
556
proxy = EmbeddingCompletionRequest(
557
model=request.model,
558
input=texts,
@@ -546,50 +561,32 @@ class EmbedIOProcessor(PoolingIOProcessor):
561
truncate_prompt_tokens=truncate_prompt_tokens,
562
truncation_side=truncation_side,
563
)
549
- return self._preprocess_cmpl_online(
550
- proxy, prompt_input=proxy.input, prompt_embeds=None
551
- )
564
+ ctx.request = proxy
565
+ requests = super().get_request_factory_online(ctx)
566
+ ctx.request = request
567
+ return requests
568
569
def _batch_render_chat(
570
self,
555
- request: CohereEmbedRequest,
571
+ ctx: PoolingServeContext,
572
all_messages: Sequence[list[ChatCompletionMessageParam]],
573
truncate_prompt_tokens: int | None,
574
truncation_side: Literal["left", "right"] | None,
559
- ) -> list[EngineInput]:
575
+ ) -> Sequence[AnyRenderParam]:
576
"""Batch-render multiple conversations through the chat template."""
561
- if not all_messages:
562
- return []
563
-
564
- proxy = EmbeddingChatRequest(
577
+ request = ctx.request
578
+ proxy = EmbeddingBatchChatRequest(
579
model=request.model,
566
- messages=list(all_messages[0]),
580
+ messages=all_messages,
581
dimensions=request.output_dimension,
582
encoding_format="float",
583
truncate_prompt_tokens=truncate_prompt_tokens,
584
truncation_side=truncation_side,
585
)
572
-
573
- renderer = self.renderer
574
- mm_config = self.model_config.multimodal_config
575
-
576
- tok_params = proxy.build_tok_params(self.model_config)
577
- chat_params = proxy.build_chat_params(
578
- self.chat_template,
579
- self.chat_template_content_format,
580
- ).with_defaults(
581
- merge_kwargs(
582
- None,
583
- dict(
584
- tools=None,
585
- tokenize=is_mistral_tokenizer(renderer.tokenizer),
586
- ),
587
- ),
588
- default_media_io_kwargs=(mm_config.media_io_kwargs if mm_config else None),
589
- )
590
-
591
- _, engine_inputs = renderer.render_chat(all_messages, chat_params, tok_params)
592
- return engine_inputs
586
+ ctx.request = proxy
587
+ requests = self._get_request_factory_chat_input_online(ctx)
588
+ ctx.request = request
589
+ return requests
590
591
def _validate_input_type(self, input_type: str | None) -> None:
592
"""Raise if *input_type* is not supported by this model."""
@@ -640,7 +637,9 @@ class TokenEmbedIOProcessor(PoolingIOProcessor):
637
class JinaRankingTokenEmbedIOProcessor(
638
TokenEmbedIOProcessor, JinaRankingIOProcessorMixin
639
):
643
- def pre_process_online(self, ctx: PoolingServeContext):
640
+ def get_request_factory_online(
641
+ self, ctx: PoolingServeContext
642
+ ) -> Sequence[AnyRenderParam]:
643
request = ctx.request
644
if isinstance(request, PoolingCompletionLikeRequest):
645
prompts = request.input
@@ -655,20 +654,15 @@ class JinaRankingTokenEmbedIOProcessor(
654
query=text_prompts[-1], docs=text_prompts[:-1]
655
)
656
658
- engine_inputs = self._preprocess_cmpl_online(
659
- request,
660
- prompt_input=prompt_input,
661
- prompt_embeds=None,
662
- )
657
+ request.input = prompt_input
658
+ return super().get_request_factory_online(ctx)
659
elif isinstance(request, PoolingChatLikeRequest):
660
raise ValueError("The JinaForRanking does not support chat Request.")
661
else:
662
raise ValueError(f"Invalid {self.name} request type")
663
668
- ctx.engine_inputs = engine_inputs
669
-
664
def get_request_factory_offline(
671
- self, ctx: ALLOfflineInputsContext
665
+ self, ctx: AnyOfflineInputsContext
666
) -> tuple[RequestFactory, int]:
667
assert isinstance(ctx, OfflineEncodeInputsContext)
668
if not isinstance(ctx.prompts, Sequence) or len(ctx.prompts) < 2:
vllm/entrypoints/pooling/embed/protocol.py
+6
-6
@@ -93,9 +93,9 @@ class EmbeddingBatchChatRequest(
93
``messages`` instead of introducing a separate batch-specific field.
94
"""
95
96
- messages: list[Annotated[list[ChatCompletionMessageParam], Field(min_length=1)]] = (
97
- Field(..., min_length=1)
98
- )
96
+ messages: Sequence[
97
+ Annotated[list[ChatCompletionMessageParam], Field(min_length=1)]
98
+ ] = Field(..., min_length=1)
99
100
def to_pooling_params(self):
101
return PoolingParams(
@@ -133,9 +133,9 @@ class EmbeddingChatInputRequest(
133
class EmbeddingBatchChatInputRequest(EmbeddingBatchChatRequest):
134
"""OpenAI embeddings request with batched chat conversations in ``input``."""
135
136
- input: list[Annotated[list[ChatCompletionMessageParam], Field(min_length=1)]] = (
137
- Field(..., min_length=1)
138
- )
136
+ input: Sequence[
137
+ Annotated[list[ChatCompletionMessageParam], Field(min_length=1)]
138
+ ] = Field(..., min_length=1)
139
140
@model_validator(mode="before")
141
@classmethod
vllm/entrypoints/pooling/embed/serving.py
+7
@@ -9,6 +9,7 @@ from vllm.logger import init_logger
9
from vllm.outputs import PoolingRequestOutput
10
from vllm.utils.serial_utils import EmbedDType, Endianness
11
12
+from ..base.io_processor import PoolingIOProcessor
13
from ..base.serving import PoolingServing
14
from ..typing import PoolingServeContext
15
from ..utils import (
@@ -53,6 +54,12 @@ class ServingEmbedding(PoolingServing):
54
def init_io_processor(self, *args, **kwargs) -> EmbedIOProcessor:
55
return EmbedIOProcessor(*args, **kwargs)
56
57
+ async def _preprocessing(
58
+ self, io_processor: PoolingIOProcessor, ctx: PoolingServeContext
59
+ ):
60
+ await super()._preprocessing(io_processor, ctx)
61
+ self.io_processor.maybe_pre_process_chunked(ctx)
62
+
63
def _build_response(
64
self,
65
ctx: PoolingServeContext,
vllm/entrypoints/pooling/offline.py
+5
-2
@@ -25,7 +25,7 @@ from .factories import init_pooling_io_processors
25
from .scoring.io_processor import ScoringIOProcessor
26
from .scoring.typing import ScoreInput
27
from .typing import (
28
- ALLOfflineInputsContext,
28
+ AnyOfflineInputsContext,
29
OfflineEncodeInputsContext,
30
OfflineOutputsContext,
31
OfflinePluginInputsContext,
@@ -104,7 +104,7 @@ class PoolingOfflineMixin(OfflineInferenceMixin):
104
105
io_processor = self.pooling_io_processors[pooling_task]
106
107
- ctx: ALLOfflineInputsContext
107
+ ctx: AnyOfflineInputsContext
108
if isinstance(prompts, dict) and "data" in prompts:
109
ctx = OfflinePluginInputsContext(
110
pooling_task=pooling_task,
@@ -399,6 +399,9 @@ class PoolingOfflineMixin(OfflineInferenceMixin):
399
num_requests: int,
400
use_tqdm: bool | Callable[..., tqdm] = True,
401
):
402
+ if num_requests == 0:
403
+ raise ValueError("You must pass at least one prompt")
404
+
405
# Keeping max_num_seqs * 2 requests in the core can already saturate the core.
406
# Therefore, keep most requests waiting outside the core.
407
max_requests_in_core = (
vllm/entrypoints/pooling/pooling/api_router.py
+5
-5
@@ -17,8 +17,11 @@ from .serving import ServingPooling
17
router = APIRouter()
18
19
20
-def pooling(request: Request) -> ServingPooling | None:
21
- return request.app.state.serving_pooling
20
+def pooling(request: Request) -> ServingPooling:
21
+ handler = getattr(request.app.state, "serving_pooling", None)
22
+ if handler is None:
23
+ raise NotImplementedError("The model does not support Pooling API")
24
+ return handler
25
26
27
@router.post(
@@ -33,7 +36,4 @@ def pooling(request: Request) -> ServingPooling | None:
36
@load_aware_call
37
async def create_pooling(request: PoolingRequest, raw_request: Request):
38
handler = pooling(raw_request)
36
- if handler is None:
37
- raise NotImplementedError("The model does not support Pooling API")
38
-
39
return await handler(request, raw_request)
vllm/entrypoints/pooling/pooling/io_processor.py
+26
-13
@@ -10,7 +10,9 @@ from vllm.renderers.inputs.preprocess import parse_model_prompt, prompt_to_seq
10
11
from ..base.io_processor import PoolingIOProcessor
12
from ..typing import (
13
- ALLOfflineInputsContext,
13
+ AnyOfflineInputsContext,
14
+ AnyRenderParam,
15
+ EncodeCMPLRenderParams,
16
OfflineEncodeInputsContext,
17
OfflineOutputsContext,
18
OfflinePluginInputsContext,
@@ -49,7 +51,9 @@ class PluginWithIOProcessorPlugins(PoolingIOProcessor):
51
#######################################
52
# online APIs
53
52
- def pre_process_online(self, ctx: PoolingServeContext):
54
+ def get_request_factory_online(
55
+ self, ctx: PoolingServeContext
56
+ ) -> Sequence[AnyRenderParam]:
57
assert isinstance(ctx.request, IOProcessorRequest)
58
59
validated_prompt = self.io_processor.parse_data(ctx.request.data)
@@ -66,24 +70,33 @@ class PluginWithIOProcessorPlugins(PoolingIOProcessor):
70
)
71
for prompt in prompt_to_seq(raw_prompts)
72
]
73
+ num_requests = len(parsed_prompts)
74
75
tok_params = ctx.request.build_tok_params(self.model_config)
76
72
- ctx.engine_inputs = self.renderer.render_cmpl(
73
- parsed_prompts,
74
- tok_params,
75
- prompt_extras={
76
- k: v
77
- for k in ("mm_processor_kwargs", "cache_salt")
78
- if (v := getattr(ctx.request, k, None)) is not None
79
- },
80
- )
81
-
77
pooling_params = self.io_processor.merge_pooling_params()
78
if pooling_params.task is None:
79
pooling_params.task = "plugin"
80
ctx.pooling_params = pooling_params
81
82
+ params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
83
+ seq_lora_requests = self._lora_request_to_seq(ctx.lora_request, num_requests)
84
+ seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
85
+
86
+ requests = [
87
+ EncodeCMPLRenderParams(
88
+ prompts=parsed_prompts[i],
89
+ tok_params=tok_params,
90
+ prompt_extras=ctx.prompt_extras,
91
+ skip_mm_cache=False,
92
+ params=params_seq[i],
93
+ lora_requests=seq_lora_requests[i],
94
+ priorities=seq_priority[i],
95
+ )
96
+ for i in range(num_requests)
97
+ ]
98
+ return requests
99
+
100
def post_process_online(
101
self,
102
ctx: PoolingServeContext,
@@ -114,7 +127,7 @@ class PluginWithIOProcessorPlugins(PoolingIOProcessor):
127
# offline APIs
128
129
def get_request_factory_offline(
117
- self, ctx: ALLOfflineInputsContext
130
+ self, ctx: AnyOfflineInputsContext
131
) -> tuple[RequestFactory, int]:
132
assert isinstance(ctx, OfflinePluginInputsContext)
133
assert isinstance(ctx.prompts, dict) and "data" in ctx.prompts
vllm/entrypoints/pooling/scoring/api_router.py
+10
-10
@@ -20,12 +20,18 @@ router = APIRouter()
20
logger = init_logger(__name__)
21
22
23
-def score(request: Request) -> ServingScores | None:
24
- return request.app.state.serving_scores
23
+def score(request: Request) -> ServingScores:
24
+ handler = getattr(request.app.state, "serving_scores", None)
25
+ if handler is None:
26
+ raise NotImplementedError("The model does not support Score API")
27
+ return handler
28
29
27
-def rerank(request: Request) -> ServingScores | None:
28
- return request.app.state.serving_scores
30
+def rerank(request: Request) -> ServingScores:
31
+ handler = getattr(request.app.state, "serving_scores", None)
32
+ if handler is None:
33
+ raise NotImplementedError("The model does not support Rerank (Score) API")
34
+ return handler
35
36
37
@router.post(
@@ -40,9 +46,6 @@ def rerank(request: Request) -> ServingScores | None:
46
@load_aware_call
47
async def create_score(request: ScoreRequest, raw_request: Request):
48
handler = score(raw_request)
43
- if handler is None:
44
- raise NotImplementedError("The model does not support Score API")
45
-
49
return await handler(request, raw_request)
50
51
@@ -77,9 +80,6 @@ async def create_score_v1(request: ScoreRequest, raw_request: Request):
80
@load_aware_call
81
async def do_rerank(request: RerankRequest, raw_request: Request):
82
handler = rerank(raw_request)
80
- if handler is None:
81
- raise NotImplementedError("The model does not support Rerank (Score) API")
82
-
83
return await handler(request, raw_request)
84
85
vllm/entrypoints/pooling/scoring/io_processor.py
+147
-192
@@ -6,8 +6,7 @@ from typing import Any, TypeAlias, cast
6
7
import torch.nn.functional as F
8
9
-from vllm import PoolingParams, PoolingRequestOutput, PromptType, TokensPrompt
10
-from vllm.inputs import EngineInput
9
+from vllm import PoolingParams, PoolingRequestOutput, TokensPrompt
10
from vllm.renderers import TokenizeParams
11
from vllm.renderers.hf import safe_apply_chat_template
12
from vllm.renderers.inputs.preprocess import (
@@ -20,8 +19,11 @@ from vllm.utils.mistral import is_mistral_tokenizer
19
20
from ...chat_utils import ChatTemplateResolutionError
21
from ..base.io_processor import PoolingIOProcessor
22
+from ..pooling.protocol import PoolingCompletionRequest
23
from ..typing import (
24
- ALLOfflineInputsContext,
24
+ AnyOfflineInputsContext,
25
+ AnyPoolingRequest,
26
+ AnyRenderParam,
27
EncodeChatRenderParams,
28
EncodeCMPLRenderParams,
29
OfflineEncodeInputsContext,
@@ -167,17 +169,7 @@ class ScoringIOProcessor(PoolingIOProcessor):
169
)
170
return scoring_data
171
170
-
171
-class BiEncoderIOProcessor(ScoringIOProcessor):
172
- name = "bi-encoder"
173
- pooling_task: PoolingTask = "embed"
174
-
175
- #######################################
176
- # online APIs
177
-
178
- def pre_process_online(self, ctx: ScoringServeContext):
179
- request = ctx.request
180
-
172
+ def valid_inputs_online(self, request: AnyPoolingRequest):
173
if isinstance(request, ScoreRequest):
174
data_1 = request.data_1
175
data_2 = request.data_2
@@ -185,9 +177,24 @@ class BiEncoderIOProcessor(ScoringIOProcessor):
177
data_1 = request.query
178
data_2 = request.documents
179
else:
188
- raise ValueError(f"Invalid {self.name} request type")
180
+ raise ValueError(f"Invalid {request.__class__.__name__} request type")
181
182
scoring_data = self.valid_inputs(data_1, data_2)
183
+ return scoring_data
184
+
185
+
186
+class BiEncoderIOProcessor(ScoringIOProcessor):
187
+ name = "bi-encoder"
188
+ pooling_task: PoolingTask = "embed"
189
+
190
+ #######################################
191
+ # online APIs
192
+
193
+ def get_request_factory_online(
194
+ self, ctx: PoolingServeContext
195
+ ) -> Sequence[AnyRenderParam]:
196
+ request = ctx.request
197
+ scoring_data = self.valid_inputs_online(request)
198
199
max_tokens_per_query, max_tokens_per_doc = self._get_token_limits(
200
request=request
@@ -197,19 +204,37 @@ class BiEncoderIOProcessor(ScoringIOProcessor):
204
scoring_data, max_tokens_per_query, max_tokens_per_doc
205
)
206
200
- tok_params = request.build_tok_params(self.model_config)
201
- engine_inputs = self._pre_process(
202
- scoring_data,
203
- tok_params,
204
- prompt_extras={
205
- k: v
206
- for k in ("mm_processor_kwargs", "cache_salt", "chat_template_kwargs")
207
- if (v := getattr(request, k, None)) is not None
208
- },
207
+ data_1 = score_data_to_prompts(scoring_data.data_1, "query", self.model_config)
208
+ data_2 = score_data_to_prompts(
209
+ scoring_data.data_2, "document", self.model_config
210
)
211
+ prompts = data_1 + data_2
212
+ ctx.n_queries = len(data_1)
213
211
- ctx.engine_inputs = engine_inputs
212
- ctx.n_queries = len(scoring_data.data_1)
214
+ prompts_seq = prompt_to_seq(prompts)
215
+ parsed_prompts = [
216
+ parse_model_prompt(self.model_config, prompt) for prompt in prompts_seq
217
+ ]
218
+ num_requests = len(parsed_prompts)
219
+
220
+ tok_params = request.build_tok_params(self.model_config)
221
+ params_seq = self._params_to_seq(ctx.pooling_params, num_requests)
222
+ seq_lora_requests = self._lora_request_to_seq(ctx.lora_request, num_requests)
223
+ seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
224
+
225
+ requests = [
226
+ EncodeCMPLRenderParams(
227
+ prompts=parsed_prompts[i],
228
+ tok_params=tok_params,
229
+ prompt_extras=ctx.prompt_extras,
230
+ skip_mm_cache=False,
231
+ params=params_seq[i],
232
+ lora_requests=seq_lora_requests[i],
233
+ priorities=seq_priority[i],
234
+ )
235
+ for i in range(num_requests)
236
+ ]
237
+ return requests
238
239
def post_process_online(
240
self,
@@ -226,7 +251,7 @@ class BiEncoderIOProcessor(ScoringIOProcessor):
251
# offline APIs
252
253
def get_request_factory_offline(
229
- self, ctx: ALLOfflineInputsContext
254
+ self, ctx: AnyOfflineInputsContext
255
) -> tuple[RequestFactory, int]:
256
assert isinstance(ctx, OfflineScoringInputsContext)
257
@@ -267,41 +292,6 @@ class BiEncoderIOProcessor(ScoringIOProcessor):
292
#######################################
293
# helpers
294
270
- def _pre_process(
271
- self,
272
- scoring_data: ScoringData,
273
- tok_params: TokenizeParams,
274
- prompt_extras: dict[str, Any] | None = None,
275
- ) -> Sequence[EngineInput]:
276
- data_1 = score_data_to_prompts(scoring_data.data_1, "query", self.model_config)
277
- data_2 = score_data_to_prompts(
278
- scoring_data.data_2, "document", self.model_config
279
- )
280
-
281
- return self._preprocess_cmpl_offline(
282
- prompts=data_1 + data_2, tok_params=tok_params, prompt_extras=prompt_extras
283
- )
284
-
285
- def _preprocess_cmpl_offline(
286
- self,
287
- prompts: PromptType | Sequence[PromptType],
288
- tok_params: TokenizeParams,
289
- prompt_extras: dict[str, Any] | None = None,
290
- ) -> Sequence[EngineInput]:
291
- prompts = prompt_to_seq(prompts)
292
- parsed_prompts = [
293
- (
294
- prompt
295
- if isinstance(prompt, bytes)
296
- else parse_model_prompt(self.model_config, prompt)
297
- )
298
- for prompt in prompts
299
- ]
300
-
301
- return self.renderer.render_cmpl(
302
- parsed_prompts, tok_params, prompt_extras=prompt_extras
303
- )
304
-
295
def _post_process(self, outputs: list[PoolingRequestOutput], n_queries: int):
296
emb_data_1 = outputs[:n_queries]
297
emb_data_2 = outputs[n_queries:]
@@ -432,49 +422,50 @@ class CrossEncoderIOProcessor(ScoringIOProcessor):
422
#######################################
423
# online APIs
424
435
- def pre_process_online(self, ctx: ScoringServeContext):
425
+ def get_request_factory_online(
426
+ self, ctx: PoolingServeContext
427
+ ) -> Sequence[AnyRenderParam]:
428
request = ctx.request
429
+ scoring_data = self.valid_inputs_online(request)
430
+ data_1 = scoring_data.data_1
431
+ data_2 = scoring_data.data_2
432
+ num_requests = len(data_2)
433
438
- if isinstance(request, ScoreRequest):
439
- data_1 = request.data_1
440
- data_2 = request.data_2
441
- elif isinstance(request, RerankRequest):
442
- data_1 = request.query
443
- data_2 = request.documents
444
- else:
445
- raise ValueError(f"Invalid {self.name} request type")
446
-
447
- scoring_data = self.valid_inputs(data_1, data_2)
434
+ if len(data_1) == 1:
435
+ data_1 = data_1 * num_requests
436
437
max_tokens_per_query, max_tokens_per_doc = self._get_token_limits(
438
request=request
439
)
440
441
tok_params = request.build_tok_params(self.model_config)
454
- pooling_params = self.create_pooling_params(request)
455
-
456
- engine_inputs, pooling_params_list = self._pre_process(
457
- scoring_data,
458
- tok_params,
459
- pooling_params,
460
- chat_template=self.chat_template,
461
- max_tokens_per_query=max_tokens_per_query,
462
- max_tokens_per_doc=max_tokens_per_doc,
463
- prompt_extras={
464
- k: v
465
- for k in ("mm_processor_kwargs", "cache_salt", "chat_template_kwargs")
466
- if (v := getattr(request, k, None)) is not None
467
- },
468
- )
442
+ seq_lora_requests = self._lora_request_to_seq(ctx.lora_request, num_requests)
443
+ seq_priority = self._priority_to_seq(ctx.priorities, num_requests)
444
+
445
+ requests = [
446
+ ScoringRenderParams(
447
+ data_1=data_1[i],
448
+ data_2=data_2[i],
449
+ chat_template=self.chat_template,
450
+ max_tokens_per_query=max_tokens_per_query,
451
+ max_tokens_per_doc=max_tokens_per_doc,
452
+ tok_params=tok_params,
453
+ prompt_extras=ctx.prompt_extras,
454
+ skip_mm_cache=False,
455
+ params=ctx.pooling_params,
456
+ lora_requests=seq_lora_requests[i],
457
+ priorities=seq_priority[i],
458
+ )
459
+ for i in range(num_requests)
460
+ ]
461
470
- ctx.engine_inputs = engine_inputs
471
- ctx.pooling_params = pooling_params_list
462
+ return requests
463
464
#######################################
465
# offline APIs
466
467
def get_request_factory_offline(
477
- self, ctx: ALLOfflineInputsContext
468
+ self, ctx: AnyOfflineInputsContext
469
) -> tuple[RequestFactory, int]:
470
assert isinstance(ctx, OfflineScoringInputsContext)
471
@@ -485,10 +476,12 @@ class CrossEncoderIOProcessor(ScoringIOProcessor):
476
if len(data_1) == 1:
477
data_1 = data_1 * num_requests
478
479
+ max_tokens_per_query, max_tokens_per_doc = self._get_token_limits(
480
+ pooling_params=ctx.pooling_params
481
+ )
482
tok_params = self.renderer.default_cmpl_tok_params.with_kwargs(
483
**(ctx.tokenization_kwargs or {})
484
)
491
-
485
prompt_extras = ctx.pooling_params.extra_kwargs
486
487
seq_lora_requests = self._lora_request_to_seq(ctx.lora_request, num_requests)
@@ -500,6 +493,8 @@ class CrossEncoderIOProcessor(ScoringIOProcessor):
493
data_1=data_1[i],
494
data_2=data_2[i],
495
chat_template=ctx.chat_template,
496
+ max_tokens_per_query=max_tokens_per_query,
497
+ max_tokens_per_doc=max_tokens_per_doc,
498
tok_params=tok_params,
499
prompt_extras=prompt_extras,
500
skip_mm_cache=False,
@@ -531,17 +526,13 @@ class CrossEncoderIOProcessor(ScoringIOProcessor):
526
params = render_params["params"]
527
prompt_extras = render_params["prompt_extras"]
528
534
- max_tokens_per_query, max_tokens_per_doc = self._get_token_limits(
535
- pooling_params=params
536
- )
537
-
529
_, engine_prompt = self.get_score_prompt(
530
data_1=render_params["data_1"],
531
data_2=render_params["data_2"],
532
encode_kwargs=tok_params.get_encode_kwargs(),
533
chat_template=render_params["chat_template"],
543
- max_tokens_per_query=max_tokens_per_query,
544
- max_tokens_per_doc=max_tokens_per_doc,
534
+ max_tokens_per_query=render_params["max_tokens_per_query"],
535
+ max_tokens_per_doc=render_params["max_tokens_per_doc"],
536
chat_template_kwargs=prompt_extras.get("chat_template_kwargs")
537
if prompt_extras
538
else None,
@@ -551,29 +542,16 @@ class CrossEncoderIOProcessor(ScoringIOProcessor):
542
543
if token_type_ids := engine_prompt.pop("token_type_ids", None):
544
params = params.clone()
554
- compressed = compress_token_type_ids(token_type_ids)
555
- params.extra_kwargs = {"compressed_token_type_ids": compressed}
556
-
557
- engine_input = self.renderer.process_for_engine(engine_prompt, arrival_time)
558
-
559
- return PoolingEngineInput(
560
- prompts=engine_input,
561
- params=params,
562
- lora_requests=render_params["lora_requests"],
563
- priorities=render_params["priorities"],
564
- )
545
+ compressed = compress_token_type_ids(
546
+ _apply_post_tokenization_to_token_type_ids(
547
+ self.tokenizer, tok_params, token_type_ids
548
+ )
549
+ )
550
+ params.extra_kwargs = {
551
+ **(params.extra_kwargs or {}),
552
+ "compressed_token_type_ids": compressed,
553
+ }
554
566
- def _pre_process(
567
- self,
568
- scoring_data: ScoringData,
569
- tok_params: TokenizeParams,
570
- pooling_params: PoolingParams | None,
571
- chat_template: str | None = None,
572
- max_tokens_per_query: int = 0,
573
- max_tokens_per_doc: int = 0,
574
- prompt_extras: dict[str, Any] | None = None,
575
- ) -> tuple[Sequence[EngineInput], list[PoolingParams]]:
576
- arrival_time = time.time()
555
engine_prompt_extras = (
556
{
557
k: v
@@ -584,55 +562,18 @@ class CrossEncoderIOProcessor(ScoringIOProcessor):
562
else None
563
)
564
587
- data_1 = scoring_data.data_1
588
- data_2 = scoring_data.data_2
565
+ if engine_prompt_extras:
566
+ target_prompt = extract_target_prompt(self.model_config, engine_prompt)
567
+ target_prompt.update(engine_prompt_extras)
568
590
- if len(data_1) == 1:
591
- data_1 = data_1 * len(data_2)
592
-
593
- if pooling_params is None:
594
- pooling_params = PoolingParams(task="classify")
595
-
596
- pooling_params_list = list[PoolingParams]()
597
- engine_inputs = list[EngineInput]()
598
- for q, d in zip(data_1, data_2):
599
- _, engine_prompt = self.get_score_prompt(
600
- data_1=q,
601
- data_2=d,
602
- encode_kwargs=tok_params.get_encode_kwargs(),
603
- chat_template=chat_template,
604
- max_tokens_per_query=max_tokens_per_query,
605
- max_tokens_per_doc=max_tokens_per_doc,
606
- chat_template_kwargs=prompt_extras.get("chat_template_kwargs")
607
- if prompt_extras
608
- else None,
609
- )
610
-
611
- token_type_ids = engine_prompt.pop("token_type_ids", None)
612
- tok_params.apply_post_tokenization(self.tokenizer, engine_prompt)
613
-
614
- if token_type_ids is not None:
615
- params = pooling_params.clone()
616
- compressed = compress_token_type_ids(
617
- _apply_post_tokenization_to_token_type_ids(
618
- self.tokenizer, tok_params, token_type_ids
619
- )
620
- )
621
- params.extra_kwargs = {
622
- **(params.extra_kwargs or {}),
623
- "compressed_token_type_ids": compressed,
624
- }
625
- pooling_params_list.append(params)
626
- else:
627
- pooling_params_list.append(pooling_params)
569
+ engine_input = self.renderer.process_for_engine(engine_prompt, arrival_time)
570
629
- if engine_prompt_extras:
630
- target_prompt = extract_target_prompt(self.model_config, engine_prompt)
631
- target_prompt.update(engine_prompt_extras)
632
- engine_inputs.append(
633
- self.renderer.process_for_engine(engine_prompt, arrival_time)
634
- )
635
- return engine_inputs, pooling_params_list
571
+ return PoolingEngineInput(
572
+ prompts=engine_input,
573
+ params=params,
574
+ lora_requests=render_params["lora_requests"],
575
+ priorities=render_params["priorities"],
576
+ )
577
578
def get_score_prompt(
579
self,
@@ -837,16 +778,27 @@ class JinaRankingIOProcessor(LateInteractionIOProcessor, JinaRankingIOProcessorM
778
name = "jina-reranking-scoring"
779
pooling_task: PoolingTask = "token_embed"
780
840
- def get_request_factory_offline(
841
- self, ctx: ALLOfflineInputsContext
842
- ) -> tuple[RequestFactory, int]:
843
- assert isinstance(ctx, OfflineScoringInputsContext)
781
+ def get_request_factory_online(
782
+ self, ctx: PoolingServeContext
783
+ ) -> Sequence[AnyRenderParam]:
784
+ request = ctx.request
785
+ ctx.n_queries = 1
786
845
- scoring_data = ctx.scoring_data
846
- prompt_extras = ctx.pooling_params.extra_kwargs
787
+ prompt_extras = ctx.prompt_extras
788
+ scoring_data = self.valid_inputs_online(request)
789
+
790
+ max_tokens_per_query, max_tokens_per_doc = self._get_token_limits(
791
+ request=request
792
+ )
793
+
794
+ if max_tokens_per_query > 0 or max_tokens_per_doc > 0:
795
+ scoring_data = self._truncate_scoring_data(
796
+ scoring_data, max_tokens_per_query, max_tokens_per_doc
797
+ )
798
799
queries = self.ensure_str(scoring_data.data_1)
800
docs = self.ensure_str(scoring_data.data_2)
801
+
802
chat_template_kwargs = (
803
prompt_extras.get("chat_template_kwargs") if prompt_extras else None
804
)
@@ -868,24 +820,19 @@ class JinaRankingIOProcessor(LateInteractionIOProcessor, JinaRankingIOProcessorM
820
for q, d in zip(queries, docs)
821
]
822
871
- return PoolingIOProcessor.get_request_factory_offline(
872
- self,
873
- OfflineEncodeInputsContext(
874
- pooling_task=self.pooling_task,
875
- prompts=prompts,
876
- tokenization_kwargs=ctx.tokenization_kwargs,
877
- pooling_params=ctx.pooling_params,
878
- lora_request=ctx.lora_request,
879
- priorities=ctx.priorities,
880
- ),
881
- )
823
+ ctx.request = PoolingCompletionRequest(task="token_embed", input=prompts)
824
+ requests = PoolingIOProcessor.get_request_factory_online(self, ctx)
825
+ ctx.request = request
826
+ return requests
827
+
828
+ def get_request_factory_offline(
829
+ self, ctx: AnyOfflineInputsContext
830
+ ) -> tuple[RequestFactory, int]:
831
+ assert isinstance(ctx, OfflineScoringInputsContext)
832
+
833
+ scoring_data = ctx.scoring_data
834
+ prompt_extras = ctx.pooling_params.extra_kwargs
835
883
- def _pre_process(
884
- self,
885
- scoring_data: ScoringData,
886
- tok_params: TokenizeParams,
887
- prompt_extras: dict[str, Any] | None = None,
888
- ) -> Sequence[EngineInput]:
836
queries = self.ensure_str(scoring_data.data_1)
837
docs = self.ensure_str(scoring_data.data_2)
838
chat_template_kwargs = (
@@ -909,8 +856,16 @@ class JinaRankingIOProcessor(LateInteractionIOProcessor, JinaRankingIOProcessorM
856
for q, d in zip(queries, docs)
857
]
858
912
- return self._preprocess_cmpl_offline(
913
- prompts=prompts, tok_params=tok_params, prompt_extras=prompt_extras
859
+ return PoolingIOProcessor.get_request_factory_offline(
860
+ self,
861
+ OfflineEncodeInputsContext(
862
+ pooling_task=self.pooling_task,
863
+ prompts=prompts,
864
+ tokenization_kwargs=ctx.tokenization_kwargs,
865
+ pooling_params=ctx.pooling_params,
866
+ lora_request=ctx.lora_request,
867
+ priorities=ctx.priorities,
868
+ ),
869
)
870
871
def _post_process(self, outputs: list[PoolingRequestOutput], n_queries: int):
vllm/entrypoints/pooling/scoring/serving.py
+13
-19
@@ -190,7 +190,7 @@ class ServingScores(PoolingServing):
190
191
async def flash_late_interaction(self, *args, **kwargs) -> Response:
192
ctx = await self._init_ctx(self.io_processor, *args, **kwargs)
193
- await self._preprocessing_async(self.io_processor, ctx)
193
+ await self._preprocessing(self.io_processor, ctx)
194
195
# stage 1: encode queries and cache token embeddings on workers.
196
await self._flash_late_interaction_encode_queries(ctx)
@@ -211,7 +211,6 @@ class ServingScores(PoolingServing):
211
query_keys = [f"{ctx.request_id}-query-{i}" for i in range(n_queries)]
212
query_uses = [n_docs if n_queries == 1 else 1] * n_queries
213
214
- query_pooling_params_list = []
214
for i in range(n_queries):
215
pooling_params = ctx.pooling_params.clone()
216
pooling_params.late_interaction_params = (
@@ -220,23 +219,21 @@ class ServingScores(PoolingServing):
219
query_uses=query_uses[i],
220
)
221
)
223
- query_pooling_params_list.append(pooling_params)
222
+ query_engine_inputs[i]["params"] = pooling_params
223
225
- assert (
226
- n_queries
227
- == len(query_pooling_params_list)
228
- == len(query_engine_inputs)
229
- == len(query_keys)
230
- )
224
+ assert n_queries == len(query_engine_inputs) == len(query_keys)
225
226
query_ctx = ScoringServeContext(
227
request=ctx.request,
228
raw_request=ctx.raw_request,
229
model_name=ctx.model_name,
230
request_id=ctx.request_id,
237
- pooling_params=query_pooling_params_list,
231
+ pooling_params=ctx.pooling_params,
232
prompt_request_ids=query_keys,
233
engine_inputs=query_engine_inputs,
234
+ lora_request=ctx.lora_request,
235
+ priorities=ctx.priorities,
236
+ prompt_extras=ctx.prompt_extras,
237
)
238
239
await self._prepare_generators(query_ctx)
@@ -255,30 +252,27 @@ class ServingScores(PoolingServing):
252
query_keys = [f"{ctx.request_id}-query-{i}" for i in range(n_queries)]
253
doc_keys = [f"{ctx.request_id}-doc-{i}" for i in range(n_docs)]
254
258
- doc_pooling_params_list = []
255
for i in range(n_docs):
256
query_idx = 0 if n_queries == 1 else i
257
pooling_params = ctx.pooling_params.clone()
258
pooling_params.late_interaction_params = build_late_interaction_doc_params(
259
query_key=query_keys[query_idx]
260
)
265
- doc_pooling_params_list.append(pooling_params)
261
+ doc_engine_inputs[i]["params"] = pooling_params
262
267
- assert (
268
- n_docs
269
- == len(doc_pooling_params_list)
270
- == len(doc_engine_inputs)
271
- == len(doc_keys)
272
- )
263
+ assert n_docs == len(doc_engine_inputs) == len(doc_keys)
264
265
doc_ctx = ScoringServeContext(
266
request=ctx.request,
267
raw_request=ctx.raw_request,
268
model_name=ctx.model_name,
269
request_id=ctx.request_id,
279
- pooling_params=doc_pooling_params_list,
270
+ pooling_params=ctx.pooling_params,
271
prompt_request_ids=doc_keys,
272
engine_inputs=doc_engine_inputs,
273
+ lora_request=ctx.lora_request,
274
+ priorities=ctx.priorities,
275
+ prompt_extras=ctx.prompt_extras,
276
)
277
278
await self._prepare_generators(doc_ctx)
vllm/entrypoints/pooling/typing.py
+18
-12
@@ -88,10 +88,13 @@ class PoolingServeContext(Generic[PoolingRequestT]):
88
raw_request: Request | None = None
89
model_name: str
90
request_id: str
91
- pooling_params: PoolingParams | list[PoolingParams]
91
+ pooling_params: PoolingParams
92
+ lora_request: LoRARequest | None
93
+ priorities: int | Sequence[int] | None
94
+ prompt_extras: dict[str, Any] | None
95
+
96
created_time: int = field(default_factory=lambda: int(time.time()))
93
- lora_request: LoRARequest | None = None
94
- engine_inputs: Sequence[EngineInput] | None = None
97
+ engine_inputs: Sequence["PoolingEngineInput"] | None = None
98
prompt_request_ids: list[str] | None = None
99
100
result_generator: AsyncGenerator[tuple[int, PoolingRequestOutput], None] | None = (
@@ -100,7 +103,7 @@ class PoolingServeContext(Generic[PoolingRequestT]):
103
final_res_batch: list[PoolingRequestOutput] = field(default_factory=list)
104
105
## for Long Text Embedding with Chunked Processing
103
- original_engine_inputs: Sequence[EngineInput] | None = None
106
+ original_engine_inputs: Sequence["PoolingEngineInput"] | None = None
107
chunked_embedding_metadata: list[ChunkedEmbeddingMetadata] | None = None
108
109
## for bi-encoder & late-interaction
@@ -118,7 +121,7 @@ class OfflineInputsContext:
121
pooling_task: PoolingTask
122
tokenization_kwargs: dict[str, Any] | None
123
lora_request: Sequence[LoRARequest | None] | None
121
- priorities: Sequence[int] | None
124
+ priorities: int | Sequence[int] | None
125
126
127
@dataclass
@@ -140,7 +143,7 @@ class OfflinePluginInputsContext(OfflineInputsContext):
143
pooling_params: PoolingParams | Sequence[PoolingParams] | None
144
145
143
-ALLOfflineInputsContext: TypeAlias = (
146
+AnyOfflineInputsContext: TypeAlias = (
147
OfflineEncodeInputsContext
148
| OfflineScoringInputsContext
149
| OfflinePluginInputsContext
@@ -178,6 +181,15 @@ class ScoringRenderParams(RenderParams):
181
data_1: ScoreData
182
data_2: ScoreData
183
chat_template: str | None
184
+ max_tokens_per_query: int
185
+ max_tokens_per_doc: int
186
+
187
+
188
+AnyRenderParam: TypeAlias = (
189
+ EncodeCMPLRenderParams | EncodeChatRenderParams | ScoringRenderParams
190
+)
191
+RequestGenerator: TypeAlias = Generator[AnyRenderParam]
192
+RequestFactory: TypeAlias = Callable[[], RequestGenerator]
193
194
195
class PoolingEngineInput(TypedDict):
@@ -185,9 +197,3 @@ class PoolingEngineInput(TypedDict):
197
params: PoolingParams
198
lora_requests: LoRARequest | None
199
priorities: int
188
-
189
-
190
-RequestGenerator: TypeAlias = Generator[
191
- EncodeCMPLRenderParams | EncodeChatRenderParams | ScoringRenderParams
192
-]
193
-RequestFactory: TypeAlias = Callable[[], RequestGenerator]