@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 58fab50d82
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Frontend] Require flag for loading text and image embeds (#27204)

Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk> Co-authored-by: DarkLight1337 <tlleungac@connect.ust.hk>

Russell Bryant committed Oct 22, 2025 at 11:52 UTC 58fab50d82838d5014f4a14d991fdb9352c9c84b
25 files changed +203 -64
docs/features/multimodal_inputs.md
+25 -4
@@ -359,13 +359,19 @@ Full example: [examples/offline_inference/audio_language.py](../../examples/offl
359 To input pre-computed embeddings belonging to a data type (i.e. image, video, or audio) directly to the language model,
360 pass a tensor of shape `(num_items, feature_size, hidden_size of LM)` to the corresponding field of the multi-modal dictionary.
361
362 +You must enable this feature via `enable_mm_embeds=True`.
363 +
364 +!!! warning
365 + The vLLM engine may crash if incorrect shape of embeddings is passed.
366 + Only enable this flag for trusted users!
367 +
368 ??? code
369
370 ```python
371 from vllm import LLM
372
373 # Inference with image embeddings as input
368 - llm = LLM(model="llava-hf/llava-1.5-7b-hf")
374 + llm = LLM(model="llava-hf/llava-1.5-7b-hf", enable_mm_embeds=True)
375
376 # Refer to the HuggingFace repo for the correct format to use
377 prompt = "USER: <image>\nWhat is the content of this image?\nASSISTANT:"
@@ -397,7 +403,11 @@ For Qwen2-VL and MiniCPM-V, we accept additional parameters alongside the embedd
403 image_embeds = torch.load(...)
404
405 # Qwen2-VL
400 - llm = LLM("Qwen/Qwen2-VL-2B-Instruct", limit_mm_per_prompt={"image": 4})
406 + llm = LLM(
407 + "Qwen/Qwen2-VL-2B-Instruct",
408 + limit_mm_per_prompt={"image": 4},
409 + enable_mm_embeds=True,
410 + )
411 mm_data = {
412 "image": {
413 "image_embeds": image_embeds,
@@ -407,7 +417,12 @@ For Qwen2-VL and MiniCPM-V, we accept additional parameters alongside the embedd
417 }
418
419 # MiniCPM-V
410 - llm = LLM("openbmb/MiniCPM-V-2_6", trust_remote_code=True, limit_mm_per_prompt={"image": 4})
420 + llm = LLM(
421 + "openbmb/MiniCPM-V-2_6",
422 + trust_remote_code=True,
423 + limit_mm_per_prompt={"image": 4},
424 + enable_mm_embeds=True,
425 + )
426 mm_data = {
427 "image": {
428 "image_embeds": image_embeds,
@@ -732,7 +747,13 @@ Full example: [examples/online_serving/openai_chat_completion_client_for_multimo
747 ### Embedding Inputs
748
749 To input pre-computed embeddings belonging to a data type (i.e. image, video, or audio) directly to the language model,
735 -pass a tensor of shape to the corresponding field of the multi-modal dictionary.
750 +pass a tensor of shape `(num_items, feature_size, hidden_size of LM)` to the corresponding field of the multi-modal dictionary.
751 +
752 +You must enable this feature via the `--enable-mm-embeds` flag in `vllm serve`.
753 +
754 +!!! warning
755 + The vLLM engine may crash if incorrect shape of embeddings is passed.
756 + Only enable this flag for trusted users!
757
758 #### Image Embedding Inputs
759
docs/features/prompt_embeds.md
+5 -1
@@ -20,12 +20,16 @@ You can pass prompt embeddings from Hugging Face Transformers models to the `'p
20
21 ## Online Serving
22
23 -Our OpenAI-compatible server accepts prompt embeddings inputs via the [Completions API](https://platform.openai.com/docs/api-reference/completions). Prompt embeddings inputs are added via a new `'prompt_embeds'` key in the JSON package.
23 +Our OpenAI-compatible server accepts prompt embeddings inputs via the [Completions API](https://platform.openai.com/docs/api-reference/completions). Prompt embeddings inputs are added via a new `'prompt_embeds'` key in the JSON package and are enabled by the `--enable-prompt-embeds` flag in `vllm serve`.
24
25 When a mixture of `'prompt_embeds'` and `'prompt'` inputs are provided in a single request, the prompt embeds are always returned first.
26
27 Prompt embeddings are passed in as base64 encoded torch tensors.
28
29 +!!! warning
30 + The vLLM engine may crash if incorrect shape of embeddings is passed.
31 + Only enable this flag for trusted users!
32 +
33 ### Transformers Inputs via OpenAI Client
34
35 First, launch the OpenAI-compatible server:
examples/offline_inference/prithvi_geospatial_mae.py
+1
@@ -49,6 +49,7 @@ class PrithviMAE:
49 dtype="float16",
50 enforce_eager=True,
51 model_impl="terratorch",
52 + enable_mm_embeds=True,
53 )
54
55 def run(self, input_data, location_coords):
examples/offline_inference/prithvi_geospatial_mae_io_processor.py
+1
@@ -38,6 +38,7 @@ def main():
38 max_num_seqs=32,
39 io_processor_plugin="prithvi_to_tiff",
40 model_impl="terratorch",
41 + enable_mm_embeds=True,
42 )
43
44 pooling_params = PoolingParams(task="token_classify", activation=False)
examples/online_serving/prithvi_geospatial_mae.py
+1
@@ -19,6 +19,7 @@ import requests
19 # --task embed --trust-remote-code
20 # --skip-tokenizer-init --enforce-eager
21 # --io-processor-plugin prithvi_to_tiff
22 +# --enable-mm-embeds
23
24
25 def main():
tests/entrypoints/llm/test_prompt_validation.py
+16 -1
@@ -2,6 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import pytest
5 +import torch
6
7 from vllm import LLM
8
@@ -12,8 +13,22 @@ def test_empty_prompt():
13 llm.generate([""])
14
15
15 -@pytest.mark.skip_v1
16 def test_out_of_vocab_token():
17 llm = LLM(model="openai-community/gpt2", enforce_eager=True)
18 with pytest.raises(ValueError, match="out of vocabulary"):
19 llm.generate({"prompt_token_ids": [999999]})
20 +
21 +
22 +def test_require_mm_embeds():
23 + llm = LLM(
24 + model="llava-hf/llava-1.5-7b-hf",
25 + enforce_eager=True,
26 + enable_mm_embeds=False,
27 + )
28 + with pytest.raises(ValueError, match="--enable-mm-embeds"):
29 + llm.generate(
30 + {
31 + "prompt": "<image>",
32 + "multi_modal_data": {"image": torch.empty(1, 1, 1)},
33 + }
34 + )
tests/entrypoints/openai/test_completion_with_prompt_embeds.py
+13
@@ -292,3 +292,16 @@ async def test_prompt_logprobs_raises_error(
292 temperature=0.0,
293 extra_body={"prompt_embeds": encoded_embeds, "prompt_logprobs": True},
294 )
295 +
296 +
297 +@pytest.mark.asyncio
298 +async def test_empty_prompt_embeds(
299 + client_with_prompt_embeds: openai.AsyncOpenAI,
300 +) -> None:
301 + await client_with_prompt_embeds.completions.create(
302 + model=MODEL_NAME,
303 + prompt="Hello",
304 + max_tokens=5,
305 + temperature=0.0,
306 + extra_body={"prompt_embeds": []},
307 + )
tests/entrypoints/openai/test_prompt_validation.py
+27 -2
@@ -2,6 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import io
5 +from unittest.mock import Mock
6
7 # imports for structured outputs tests
8 import openai
@@ -10,7 +11,8 @@ import pytest
11 import regex as re
12 import torch
13
13 -from vllm.entrypoints.renderer import BaseRenderer
14 +from vllm.config import ModelConfig
15 +from vllm.entrypoints.renderer import CompletionRenderer
16
17 from ...utils import RemoteOpenAIServer
18
@@ -59,6 +61,10 @@ async def test_out_of_vocab_token_ids():
61 def test_load_prompt_embeds(
62 dtype: torch.dtype, layout: torch.layout, seq_len: int, hidden_size: int
63 ):
64 + model_config = Mock(spec=ModelConfig)
65 + model_config.enable_prompt_embeds = True
66 + renderer = CompletionRenderer(model_config, tokenizer=None)
67 +
68 # construct arbitrary tensors of various dtypes, layouts, and sizes.
69 # We need to check against different layouts to make sure that if a user
70 # uses sparse tensors to reduce the transmission size of prompt embeddings,
@@ -83,7 +89,7 @@ def test_load_prompt_embeds(
89 buffer.seek(0)
90 encoded_tensor = pybase64.b64encode(buffer.getvalue())
91
86 - loaded_prompt_embeds = BaseRenderer.load_prompt_embeds(encoded_tensor)
92 + loaded_prompt_embeds = renderer.load_prompt_embeds(encoded_tensor)
93 assert len(loaded_prompt_embeds) == 1
94 loaded_tensor = loaded_prompt_embeds[0]["prompt_embeds"]
95 assert loaded_tensor.device.type == "cpu"
@@ -91,3 +97,22 @@ def test_load_prompt_embeds(
97 torch.testing.assert_close(
98 loaded_tensor, tensor.to("cpu").to_dense(), equal_nan=True
99 )
100 +
101 +
102 +@pytest.mark.parametrize("dtype", [torch.float32])
103 +@pytest.mark.parametrize("seq_len", [2])
104 +@pytest.mark.parametrize("hidden_size", [2])
105 +def test_disable_prompt_embeds(dtype: torch.dtype, seq_len: int, hidden_size: int):
106 + model_config = Mock(spec=ModelConfig)
107 + model_config.enable_prompt_embeds = False
108 + renderer = CompletionRenderer(model_config, tokenizer=None)
109 +
110 + tensor = torch.randn((seq_len, hidden_size), dtype=dtype)
111 +
112 + buffer = io.BytesIO()
113 + torch.save(tensor, buffer)
114 + buffer.seek(0)
115 + encoded_tensor = pybase64.b64encode(buffer.getvalue())
116 +
117 + with pytest.raises(ValueError, match="--enable-prompt-embeds"):
118 + renderer.load_prompt_embeds(encoded_tensor)
tests/entrypoints/openai/test_vision_embeds.py renamed
+29 -31
@@ -15,30 +15,7 @@ MODEL_NAME = "ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL-Sen1Floods11"
15 DTYPE = "float16"
16
17
18 -@pytest.fixture(scope="module")
19 -def server():
20 - args = [
21 - "--runner",
22 - "pooling",
23 - # use half precision for speed and memory savings in CI environment
24 - "--dtype",
25 - DTYPE,
26 - "--enforce-eager",
27 - "--trust-remote-code",
28 - "--skip-tokenizer-init",
29 - "--max-num-seqs",
30 - "32",
31 - "--model-impl",
32 - "terratorch",
33 - ]
34 -
35 - with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
36 - yield remote_server
37 -
38 -
39 -@pytest.mark.asyncio
40 -@pytest.mark.parametrize("model_name", [MODEL_NAME])
41 -async def test_single_request(server: RemoteOpenAIServer, model_name: str):
18 +def _terratorch_dummy_inputs(model_name: str):
19 pixel_values = torch.full((6, 512, 512), 1.0, dtype=torch.float16)
20 location_coords = torch.full((1, 2), 1.0, dtype=torch.float16)
21
@@ -54,7 +31,7 @@ async def test_single_request(server: RemoteOpenAIServer, model_name: str):
31 binary_data = buffer_coord.read()
32 base64_coord_embedding = base64.b64encode(binary_data).decode("utf-8")
33
57 - prompt = {
34 + return {
35 "model": model_name,
36 "additional_data": {"prompt_token_ids": [1]},
37 "encoding_format": "base64",
@@ -74,12 +51,33 @@ async def test_single_request(server: RemoteOpenAIServer, model_name: str):
51 ],
52 }
53
77 - # test single pooling
78 - response = requests.post(server.url_for("pooling"), json=prompt)
79 - response.raise_for_status()
54
81 - output = response.json()["data"][0]["data"]
55 +@pytest.mark.parametrize("model_name", [MODEL_NAME])
56 +async def test_single_request(model_name: str):
57 + args = [
58 + "--runner",
59 + "pooling",
60 + # use half precision for speed and memory savings in CI environment
61 + "--dtype",
62 + DTYPE,
63 + "--enforce-eager",
64 + "--trust-remote-code",
65 + "--max-num-seqs",
66 + "32",
67 + "--model-impl",
68 + "terratorch",
69 + "--skip-tokenizer-init",
70 + "--enable-mm-embeds",
71 + ]
72 +
73 + with RemoteOpenAIServer(MODEL_NAME, args) as server:
74 + prompt = _terratorch_dummy_inputs(model_name)
75 +
76 + # test single pooling
77 + response = requests.post(server.url_for("pooling"), json=prompt)
78 + response.raise_for_status()
79
83 - np_response = np.frombuffer(base64.b64decode(output), dtype=np.float32)
80 + output = response.json()["data"][0]["data"]
81
85 - assert len(np_response) == 524288
82 + np_response = np.frombuffer(base64.b64decode(output), dtype=np.float32)
83 + assert len(np_response) == 524288
tests/entrypoints/test_chat_utils.py
+17 -4
@@ -73,6 +73,19 @@ def phi3v_model_config_mm_interleaved():
73 )
74
75
76 +@pytest.fixture(scope="function")
77 +def phi3v_model_config_image_embeds():
78 + return ModelConfig(
79 + PHI3V_MODEL_ID,
80 + runner="generate",
81 + trust_remote_code=True,
82 + limit_mm_per_prompt={
83 + "image": 2,
84 + },
85 + enable_mm_embeds=True,
86 + )
87 +
88 +
89 @pytest.fixture(scope="module")
90 def phi3v_tokenizer():
91 return get_tokenizer(PHI3V_MODEL_ID)
@@ -799,7 +812,7 @@ def test_parse_chat_messages_empty_pil_image_with_uuid(
812
813
814 def test_parse_chat_messages_empty_image_embeds_with_uuid(
802 - phi3v_model_config,
815 + phi3v_model_config_image_embeds,
816 phi3v_tokenizer,
817 ):
818 uuid = "abcd"
@@ -813,7 +826,7 @@ def test_parse_chat_messages_empty_image_embeds_with_uuid(
826 ],
827 }
828 ],
816 - phi3v_model_config,
829 + phi3v_model_config_image_embeds,
830 phi3v_tokenizer,
831 content_format="string",
832 )
@@ -832,7 +845,7 @@ def test_parse_chat_messages_empty_image_embeds_with_uuid(
845
846 @pytest.mark.asyncio
847 async def test_parse_chat_messages_empty_image_embeds_with_uuid_async(
835 - phi3v_model_config,
848 + phi3v_model_config_image_embeds,
849 phi3v_tokenizer,
850 ):
851 uuid = "abcd"
@@ -846,7 +859,7 @@ async def test_parse_chat_messages_empty_image_embeds_with_uuid_async(
859 ],
860 }
861 ],
849 - phi3v_model_config,
862 + phi3v_model_config_image_embeds,
863 phi3v_tokenizer,
864 content_format="string",
865 )
tests/entrypoints/test_renderer.py
+1
@@ -17,6 +17,7 @@ from vllm.inputs.data import is_embeds_prompt
17 class MockModelConfig:
18 max_model_len: int = 100
19 encoder_config: dict | None = None
20 + enable_prompt_embeds: bool = True
21
22
23 class MockTokenizerResult:
tests/models/multimodal/generation/test_common.py
+1 -2
@@ -109,8 +109,7 @@ VLM_TEST_SETTINGS = {
109 limit_mm_per_prompt={"image": 4},
110 )
111 ],
112 - # TODO: Revert to "auto" when CPU backend can use torch > 2.6
113 - dtype="bfloat16" if current_platform.is_cpu() else "auto",
112 + vllm_runner_kwargs={"enable_mm_embeds": True},
113 marks=[pytest.mark.core_model, pytest.mark.cpu_model],
114 ),
115 "qwen2_5_vl": VLMTestInfo(
tests/models/multimodal/generation/test_qwen2_vl.py
+1
@@ -292,6 +292,7 @@ def run_embedding_input_test(
292 tensor_parallel_size=tensor_parallel_size,
293 distributed_executor_backend=distributed_executor_backend,
294 default_torch_num_threads=1,
295 + enable_mm_embeds=True,
296 ) as vllm_model:
297 outputs_per_case_for_original_input = [
298 vllm_model.generate_greedy_logprobs(
tests/models/multimodal/pooling/test_prithvi_mae.py
+1
@@ -34,6 +34,7 @@ def _run_test(
34 dtype="half",
35 enforce_eager=True,
36 skip_tokenizer_init=True,
37 + enable_mm_embeds=True,
38 # Limit the maximum number of sequences to avoid the
39 # test going OOM during the warmup run
40 max_num_seqs=32,
tests/models/test_initialization.py
+6
@@ -104,6 +104,11 @@ def can_initialize(
104 m.setenv("VLLM_ATTENTION_BACKEND", "TRITON_ATTN")
105 if model_arch == "WhisperForConditionalGeneration":
106 m.setenv("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
107 +
108 + extra_args = {}
109 + if model_arch in ("PrithviGeoSpatialMAE", "Terratorch"):
110 + extra_args["enable_mm_embeds"] = True
111 +
112 LLM(
113 model_info.default,
114 tokenizer=model_info.tokenizer,
@@ -128,6 +133,7 @@ def can_initialize(
133 else "vllm",
134 hf_overrides=hf_overrides_fn,
135 max_num_seqs=model_info.max_num_seqs,
136 + **extra_args,
137 )
138
139
tests/models/test_terratorch.py
+1
@@ -32,6 +32,7 @@ def test_inference(
32 dtype="half",
33 enforce_eager=True,
34 skip_tokenizer_init=True,
35 + enable_mm_embeds=True,
36 # Limit the maximum number of sequences to avoid the
37 # test going OOM during the warmup run
38 max_num_seqs=32,
tests/plugins_tests/test_io_processor_plugins.py
+1
@@ -38,6 +38,7 @@ def server():
38 "prithvi_to_tiff",
39 "--model-impl",
40 "terratorch",
41 + "--enable-mm-embeds",
42 ]
43
44 with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
tests/v1/entrypoints/openai/test_completion.py
-15
@@ -6,7 +6,6 @@ import openai # use the official client for correctness check
6 import pytest
7 import pytest_asyncio
8 import regex as re
9 -import requests
9 from openai import BadRequestError
10
11 from tests.utils import RemoteOpenAIServer
@@ -686,17 +685,3 @@ async def test_invalid_grammar(client: openai.AsyncOpenAI, model_name: str):
685 "structured_outputs": {"grammar": invalid_simplified_sql_grammar}
686 },
687 )
689 -
690 -
691 -@pytest.mark.asyncio
692 -async def test_completion_with_empty_prompt_embeds(client: openai.AsyncOpenAI) -> None:
693 - """Test completion with empty prompt embeds."""
694 - payload: dict[str, object] = {"prompt": "Hello", "prompt_embeds": []}
695 - headers: dict[str, str] = {"Content-Type": "application/json"}
696 - # base_url = http://localhost:8000/v1/completions
697 - response = requests.post(
698 - f"{client.base_url}completions", headers=headers, json=payload
699 - )
700 - assert response.status_code == 200, (
701 - f"Expected status code 200, got {response.status_code}. "
702 - )
tests/v1/entrypoints/openai/test_completion_with_image_embeds.py
+1
@@ -32,6 +32,7 @@ def default_image_embeds_server_args() -> list[str]:
32 "--enforce-eager",
33 "--limit-mm-per-prompt",
34 json.dumps({"image": MAXIMUM_IMAGES}),
35 + "--enable-mm-embeds",
36 ]
37
38
vllm/config/model.py
+7 -2
@@ -232,8 +232,10 @@ class ModelConfig:
232 output will contain token ids."""
233 enable_prompt_embeds: bool = False
234 """If `True`, enables passing text embeddings as inputs via the
235 - `prompt_embeds` key. Note that enabling this will double the time required
236 - for graph compilation."""
235 + `prompt_embeds` key.
236 +
237 + WARNING: The vLLM engine may crash if incorrect shape of embeddings is passed.
238 + Only enable this flag for trusted users!"""
239 served_model_name: str | list[str] | None = None
240 """The model name(s) used in the API. If multiple names are provided, the
241 server will respond to any of the provided names. The model name in the
@@ -303,6 +305,7 @@ class ModelConfig:
305 """Configuration for multimodal model. If `None`, this will be inferred
306 from the architecture of `self.model`."""
307 limit_mm_per_prompt: InitVar[dict[str, int | dict[str, int]] | None] = None
308 + enable_mm_embeds: InitVar[bool | None] = None
309 media_io_kwargs: InitVar[dict[str, dict[str, Any]] | None] = None
310 mm_processor_kwargs: InitVar[dict[str, Any] | None] = None
311 mm_processor_cache_gb: InitVar[float | None] = None
@@ -421,6 +424,7 @@ class ModelConfig:
424 self,
425 # Multimodal config init vars
426 limit_mm_per_prompt: dict[str, int] | None,
427 + enable_mm_embeds: bool | None,
428 media_io_kwargs: dict[str, dict[str, Any]] | None,
429 mm_processor_kwargs: dict[str, Any] | None,
430 mm_processor_cache_gb: float | None,
@@ -731,6 +735,7 @@ class ModelConfig:
735
736 mm_config_kwargs = dict(
737 limit_per_prompt=limit_mm_per_prompt,
738 + enable_mm_embeds=enable_mm_embeds,
739 media_io_kwargs=media_io_kwargs,
740 mm_processor_kwargs=mm_processor_kwargs,
741 mm_processor_cache_gb=mm_processor_cache_gb,
vllm/config/multimodal.py
+8
@@ -75,6 +75,14 @@ class MultiModalConfig:
75 {"image": 16, "video": {"count": 1, "num_frames": 32, "width": 512,
76 "height": 512}}
77 """
78 + enable_mm_embeds: bool = False
79 + """If `True`, enables passing multimodal embeddings:
80 + for `LLM` class, this refers to tensor inputs under `multi_modal_data`;
81 + for the OpenAI-compatible server, this refers to chat messages with content
82 + `"type": "*_embeds"`.
83 +
84 + WARNING: The vLLM engine may crash if incorrect shape of embeddings is passed.
85 + Only enable this flag for trusted users!"""
86 media_io_kwargs: dict[str, dict[str, Any]] = Field(default_factory=dict)
87 """Additional args passed to process media inputs, keyed by modalities.
88 For example, to set num_frames for video, set
vllm/engine/arg_utils.py
+5
@@ -438,6 +438,7 @@ class EngineArgs:
438 limit_mm_per_prompt: dict[str, int | dict[str, int]] = get_field(
439 MultiModalConfig, "limit_per_prompt"
440 )
441 + enable_mm_embeds: bool = MultiModalConfig.enable_mm_embeds
442 interleave_mm_strings: bool = MultiModalConfig.interleave_mm_strings
443 media_io_kwargs: dict[str, dict[str, Any]] = get_field(
444 MultiModalConfig, "media_io_kwargs"
@@ -896,6 +897,9 @@ class EngineArgs:
897 multimodal_group.add_argument(
898 "--limit-mm-per-prompt", **multimodal_kwargs["limit_per_prompt"]
899 )
900 + multimodal_group.add_argument(
901 + "--enable-mm-embeds", **multimodal_kwargs["enable_mm_embeds"]
902 + )
903 multimodal_group.add_argument(
904 "--media-io-kwargs", **multimodal_kwargs["media_io_kwargs"]
905 )
@@ -1159,6 +1163,7 @@ class EngineArgs:
1163 enable_prompt_embeds=self.enable_prompt_embeds,
1164 served_model_name=self.served_model_name,
1165 limit_mm_per_prompt=self.limit_mm_per_prompt,
1166 + enable_mm_embeds=self.enable_mm_embeds,
1167 interleave_mm_strings=self.interleave_mm_strings,
1168 media_io_kwargs=self.media_io_kwargs,
1169 skip_mm_profiling=self.skip_mm_profiling,
vllm/entrypoints/chat_utils.py
+20
@@ -811,6 +811,10 @@ class MultiModalContentParser(BaseMultiModalContentParser):
811 allowed_media_domains=tracker.allowed_media_domains,
812 )
813
814 + @property
815 + def model_config(self) -> ModelConfig:
816 + return self._tracker.model_config
817 +
818 def parse_image(self, image_url: str | None, uuid: str | None = None) -> None:
819 image = self._connector.fetch_image(image_url) if image_url else None
820
@@ -822,6 +826,12 @@ class MultiModalContentParser(BaseMultiModalContentParser):
826 image_embeds: str | dict[str, str] | None,
827 uuid: str | None = None,
828 ) -> None:
829 + mm_config = self.model_config.get_multimodal_config()
830 + if not mm_config.enable_mm_embeds:
831 + raise ValueError(
832 + "You must set `--enable-mm-embeds` to input `image_embeds`"
833 + )
834 +
835 if isinstance(image_embeds, dict):
836 embeds = {
837 k: self._connector.fetch_image_embedding(v)
@@ -886,6 +896,10 @@ class AsyncMultiModalContentParser(BaseMultiModalContentParser):
896 allowed_media_domains=tracker.allowed_media_domains,
897 )
898
899 + @property
900 + def model_config(self) -> ModelConfig:
901 + return self._tracker.model_config
902 +
903 def parse_image(self, image_url: str | None, uuid: str | None = None) -> None:
904 image_coro = self._connector.fetch_image_async(image_url) if image_url else None
905
@@ -897,6 +911,12 @@ class AsyncMultiModalContentParser(BaseMultiModalContentParser):
911 image_embeds: str | dict[str, str] | None,
912 uuid: str | None = None,
913 ) -> None:
914 + mm_config = self.model_config.get_multimodal_config()
915 + if not mm_config.enable_mm_embeds:
916 + raise ValueError(
917 + "You must set `--enable-mm-embeds` to input `image_embeds`"
918 + )
919 +
920 future: asyncio.Future[str | dict[str, str] | None] = asyncio.Future()
921
922 if isinstance(image_embeds, dict):
vllm/entrypoints/renderer.py
+5 -2
@@ -156,14 +156,17 @@ class BaseRenderer(ABC):
156 """
157 raise NotImplementedError
158
159 - @classmethod
159 def load_prompt_embeds(
161 - cls,
160 + self,
161 prompt_embeds: bytes | list[bytes],
162 truncate_prompt_tokens: Annotated[int, Field(ge=0)] | None = None,
163 cache_salt: str | None = None,
164 ) -> list[EngineEmbedsPrompt]:
165 """Load and validate base64-encoded embeddings into prompt objects."""
166 + if not self.model_config.enable_prompt_embeds:
167 + raise ValueError(
168 + "You must set `--enable-prompt-embeds` to input `prompt_embeds`."
169 + )
170
171 def _load_and_validate_embed(embed: bytes) -> EngineEmbedsPrompt:
172 tensor = torch.load(
vllm/multimodal/processing.py
+10
@@ -1308,6 +1308,16 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1308 [`_get_hf_mm_data`][vllm.multimodal.processing.BaseMultiModalProcessor._get_hf_mm_data].
1309 """
1310 mm_items = self.data_parser.parse_mm_data(mm_data)
1311 +
1312 + mm_config = self.info.ctx.model_config.get_multimodal_config()
1313 + if not mm_config.enable_mm_embeds:
1314 + for modality, items in mm_items.items():
1315 + if isinstance(items, (EmbeddingItems, DictEmbeddingItems)):
1316 + raise ValueError(
1317 + f"You must set `--enable-mm-embeds` to input "
1318 + f"`{modality}_embeds`"
1319 + )
1320 +
1321 for modality, items in mm_items.items():
1322 self.validate_num_items(modality, len(items))
1323