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 15, 13:30 UTC
Improve-mm-and-pooler-and-decoding-configs (#16789)
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Harry Mellor committed
Apr 18, 2025 at 06:13 UTC
e78587a64ccf236c8506c8b536965ac74560ab48
14 files changed
+84
-78
docs/source/models/supported_models.md
+1
-1
@@ -788,7 +788,7 @@ llm = LLM(
788
Online serving:
789
790
```bash
791
-vllm serve Qwen/Qwen2-VL-7B-Instruct --limit-mm-per-prompt image=4
791
+vllm serve Qwen/Qwen2-VL-7B-Instruct --limit-mm-per-prompt '{"image":4}'
792
```
793
794
**This is no longer required if you are using vLLM V1.**
docs/source/serving/multimodal_inputs.md
+1
-1
@@ -228,7 +228,7 @@ First, launch the OpenAI-compatible server:
228
229
```bash
230
vllm serve microsoft/Phi-3.5-vision-instruct --task generate \
231
- --trust-remote-code --max-model-len 4096 --limit-mm-per-prompt image=2
231
+ --trust-remote-code --max-model-len 4096 --limit-mm-per-prompt '{"image":2}'
232
```
233
234
Then, you can use the OpenAI client as follows:
examples/offline_inference/mistral-small.py
+2
-2
@@ -16,11 +16,11 @@ from vllm.sampling_params import SamplingParams
16
# # Mistral format
17
# vllm serve mistralai/Mistral-Small-3.1-24B-Instruct-2503 \
18
# --tokenizer-mode mistral --config-format mistral --load-format mistral \
19
-# --limit-mm-per-prompt 'image=4' --max-model-len 16384
19
+# --limit-mm-per-prompt '{"image":4}' --max-model-len 16384
20
#
21
# # HF format
22
# vllm serve mistralai/Mistral-Small-3.1-24B-Instruct-2503 \
23
-# --limit-mm-per-prompt 'image=4' --max-model-len 16384
23
+# --limit-mm-per-prompt '{"image":4}' --max-model-len 16384
24
# ```
25
#
26
# - Client:
examples/online_serving/openai_chat_completion_client_for_multimodal.py
+1
-1
@@ -9,7 +9,7 @@ vllm serve llava-hf/llava-1.5-7b-hf --chat-template template_llava.jinja
9
10
(multi-image inference with Phi-3.5-vision-instruct)
11
vllm serve microsoft/Phi-3.5-vision-instruct --task generate \
12
- --trust-remote-code --max-model-len 4096 --limit-mm-per-prompt image=2
12
+ --trust-remote-code --max-model-len 4096 --limit-mm-per-prompt '{"image":2}'
13
14
(audio inference with Ultravox)
15
vllm serve fixie-ai/ultravox-v0_5-llama-3_2-1b --max-model-len 4096
tests/engine/test_arg_utils.py
+4
@@ -24,6 +24,10 @@ from vllm.utils import FlexibleArgumentParser
24
}),
25
])
26
def test_limit_mm_per_prompt_parser(arg, expected):
27
+ """This functionality is deprecated and will be removed in the future.
28
+ This argument should be passed as JSON string instead.
29
+
30
+ TODO: Remove with nullable_kvs."""
31
parser = EngineArgs.add_cli_args(FlexibleArgumentParser())
32
if arg is None:
33
args = parser.parse_args([])
tests/entrypoints/openai/test_audio.py
+1
-1
@@ -27,7 +27,7 @@ def server():
27
"--enforce-eager",
28
"--trust-remote-code",
29
"--limit-mm-per-prompt",
30
- f"audio={MAXIMUM_AUDIOS}",
30
+ str({"audio": MAXIMUM_AUDIOS}),
31
]
32
33
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
tests/entrypoints/openai/test_video.py
+1
-1
@@ -31,7 +31,7 @@ def server():
31
"--enforce-eager",
32
"--trust-remote-code",
33
"--limit-mm-per-prompt",
34
- f"video={MAXIMUM_VIDEOS}",
34
+ str({"video": MAXIMUM_VIDEOS}),
35
]
36
37
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
tests/entrypoints/openai/test_vision.py
+1
-1
@@ -35,7 +35,7 @@ def server():
35
"--enforce-eager",
36
"--trust-remote-code",
37
"--limit-mm-per-prompt",
38
- f"image={MAXIMUM_IMAGES}",
38
+ str({"image": MAXIMUM_IMAGES}),
39
]
40
41
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
tests/entrypoints/openai/test_vision_embedding.py
+1
-1
@@ -37,7 +37,7 @@ def server():
37
"--enforce-eager",
38
"--trust-remote-code",
39
"--limit-mm-per-prompt",
40
- f"image={MAXIMUM_IMAGES}",
40
+ str({"image": MAXIMUM_IMAGES}),
41
"--chat-template",
42
str(vlm2vec_jinja_path),
43
]
tests/models/decoder_only/audio_language/test_ultravox.py
+3
-3
@@ -48,9 +48,9 @@ def audio(request):
48
])
49
def server(request, audio_assets):
50
args = [
51
- "--dtype=bfloat16", "--max-model-len=4096", "--enforce-eager",
52
- f"--limit-mm-per-prompt=audio={len(audio_assets)}",
53
- "--trust-remote-code"
51
+ "--dtype", "bfloat16", "--max-model-len", "4096", "--enforce-eager",
52
+ "--limit-mm-per-prompt",
53
+ str({"audio": len(audio_assets)}), "--trust-remote-code"
54
] + [
55
f"--{key.replace('_','-')}={value}"
56
for key, value in request.param.items()
vllm/config.py
+28
-21
@@ -17,7 +17,7 @@ from dataclasses import (MISSING, dataclass, field, fields, is_dataclass,
17
from importlib.util import find_spec
18
from pathlib import Path
19
from typing import (TYPE_CHECKING, Any, Callable, ClassVar, Final, Literal,
20
- Optional, Protocol, TypeVar, Union)
20
+ Optional, Protocol, TypeVar, Union, get_args)
21
22
import torch
23
from pydantic import BaseModel, Field, PrivateAttr
@@ -2725,6 +2725,7 @@ class PromptAdapterConfig:
2725
self.prompt_adapter_dtype)
2726
2727
2728
+@config
2729
@dataclass
2730
class MultiModalConfig:
2731
"""Controls the behavior of multimodal models."""
@@ -2732,6 +2733,8 @@ class MultiModalConfig:
2733
limit_per_prompt: Mapping[str, int] = field(default_factory=dict)
2734
"""
2735
The maximum number of input items allowed per prompt for each modality.
2736
+ This should be a JSON string that will be parsed into a dictionary.
2737
+ Defaults to 1 (V0) or 999 (V1) for each modality.
2738
"""
2739
2740
def compute_hash(self) -> str:
@@ -2753,24 +2756,20 @@ class MultiModalConfig:
2756
usedforsecurity=False).hexdigest()
2757
return hash_str
2758
2756
- def get_default_limit_per_prompt(self) -> int:
2757
- """
2758
- Return the default number of input items allowed per prompt
2759
- for any modality if not specified by the user.
2760
- """
2761
- return 999 if envs.VLLM_USE_V1 else 1
2762
-
2759
def get_limit_per_prompt(self, modality: str) -> int:
2760
"""
2761
Get the maximum number of input items allowed per prompt
2762
for the given modality.
2763
"""
2768
- default = self.get_default_limit_per_prompt()
2769
- return self.limit_per_prompt.get(modality, default)
2764
+ return self.limit_per_prompt.get(
2765
+ modality,
2766
+ 999 if envs.VLLM_USE_V1 else 1,
2767
+ )
2768
2769
# TODO: Add configs to init vision tower or not.
2770
2771
2772
+@config
2773
@dataclass
2774
class PoolerConfig:
2775
"""Controls the behavior of output pooling in pooling models."""
@@ -3095,15 +3094,28 @@ def get_served_model_name(model: str,
3094
return served_model_name
3095
3096
3097
+GuidedDecodingBackendV0 = Literal["auto", "outlines", "lm-format-enforcer",
3098
+ "xgrammar"]
3099
+GuidedDecodingBackendV1 = Literal["auto", "xgrammar", "guidance"]
3100
+
3101
+
3102
+@config
3103
@dataclass
3104
class DecodingConfig:
3100
- """Dataclass which contains the decoding strategy of the engine"""
3105
+ """Dataclass which contains the decoding strategy of the engine."""
3106
3102
- # Which guided decoding algo to use.
3103
- # 'outlines' / 'lm-format-enforcer' / 'xgrammar'
3104
- guided_decoding_backend: str = "auto" if envs.VLLM_USE_V1 else "xgrammar"
3107
+ guided_decoding_backend: Union[
3108
+ GuidedDecodingBackendV0,
3109
+ GuidedDecodingBackendV1] = "auto" if envs.VLLM_USE_V1 else "xgrammar"
3110
+ """Which engine will be used for guided decoding (JSON schema / regex etc)
3111
+ by default. With "auto", we will make opinionated choices based on request
3112
+ contents and what the backend libraries currently support, so the behavior
3113
+ is subject to change in each release."""
3114
3115
reasoning_backend: Optional[str] = None
3116
+ """Select the reasoning parser depending on the model that you're using.
3117
+ This is used to parse the reasoning content into OpenAI API format.
3118
+ Required for `--enable-reasoning`."""
3119
3120
def compute_hash(self) -> str:
3121
"""
@@ -3125,17 +3137,12 @@ class DecodingConfig:
3137
return hash_str
3138
3139
def __post_init__(self):
3128
- v0_valid_guided_backends = [
3129
- 'outlines', 'lm-format-enforcer', 'xgrammar', 'auto'
3130
- ]
3131
- v1_valid_guided_backends = ['xgrammar', 'guidance', 'auto']
3132
-
3140
backend = GuidedDecodingParams(
3141
backend=self.guided_decoding_backend).backend_name
3142
if envs.VLLM_USE_V1:
3136
- valid_guided_backends = v1_valid_guided_backends
3143
+ valid_guided_backends = get_args(GuidedDecodingBackendV1)
3144
else:
3138
- valid_guided_backends = v0_valid_guided_backends
3145
+ valid_guided_backends = get_args(GuidedDecodingBackendV0)
3146
if backend not in valid_guided_backends:
3147
raise ValueError(f"Invalid guided_decoding_backend '{backend}',"
3148
f" must be one of {valid_guided_backends}")
vllm/engine/arg_utils.py
+32
-40
@@ -20,11 +20,12 @@ from vllm.config import (CacheConfig, CompilationConfig, Config, ConfigFormat,
20
DecodingConfig, Device, DeviceConfig,
21
DistributedExecutorBackend, HfOverrides,
22
KVTransferConfig, LoadConfig, LoadFormat, LoRAConfig,
23
- ModelConfig, ModelImpl, ObservabilityConfig,
24
- ParallelConfig, PoolerConfig, PoolType,
25
- PromptAdapterConfig, SchedulerConfig, SchedulerPolicy,
26
- SpeculativeConfig, TaskOption, TokenizerPoolConfig,
27
- VllmConfig, get_attr_docs, get_field)
23
+ ModelConfig, ModelImpl, MultiModalConfig,
24
+ ObservabilityConfig, ParallelConfig, PoolerConfig,
25
+ PoolType, PromptAdapterConfig, SchedulerConfig,
26
+ SchedulerPolicy, SpeculativeConfig, TaskOption,
27
+ TokenizerPoolConfig, VllmConfig, get_attr_docs,
28
+ get_field)
29
from vllm.executor.executor_base import ExecutorBase
30
from vllm.logger import init_logger
31
from vllm.model_executor.layers.quantization import QUANTIZATION_METHODS
@@ -190,7 +191,8 @@ class EngineArgs:
191
TokenizerPoolConfig.pool_type
192
tokenizer_pool_extra_config: dict[str, Any] = \
193
get_field(TokenizerPoolConfig, "extra_config")
193
- limit_mm_per_prompt: Optional[Mapping[str, int]] = None
194
+ limit_mm_per_prompt: Mapping[str, int] = \
195
+ get_field(MultiModalConfig, "limit_per_prompt")
196
mm_processor_kwargs: Optional[Dict[str, Any]] = None
197
disable_mm_preprocessor_cache: bool = False
198
enable_lora: bool = False
@@ -252,7 +254,7 @@ class EngineArgs:
254
255
additional_config: Optional[Dict[str, Any]] = None
256
enable_reasoning: Optional[bool] = None
255
- reasoning_parser: Optional[str] = None
257
+ reasoning_parser: Optional[str] = DecodingConfig.reasoning_backend
258
use_tqdm_on_load: bool = LoadConfig.use_tqdm_on_load
259
260
def __post_init__(self):
@@ -478,18 +480,22 @@ class EngineArgs:
480
'Examples:\n'
481
'- 1k → 1000\n'
482
'- 1K → 1024\n')
481
- parser.add_argument(
483
+
484
+ # Guided decoding arguments
485
+ guided_decoding_kwargs = get_kwargs(DecodingConfig)
486
+ guided_decoding_group = parser.add_argument_group(
487
+ title="DecodingConfig",
488
+ description=DecodingConfig.__doc__,
489
+ )
490
+ guided_decoding_group.add_argument(
491
'--guided-decoding-backend',
483
- type=str,
484
- default=DecodingConfig.guided_decoding_backend,
485
- help='Which engine will be used for guided decoding'
486
- ' (JSON schema / regex etc) by default. Currently support '
487
- 'https://github.com/mlc-ai/xgrammar and '
488
- 'https://github.com/guidance-ai/llguidance.'
489
- 'Valid backend values are "xgrammar", "guidance", and "auto". '
490
- 'With "auto", we will make opinionated choices based on request '
491
- 'contents and what the backend libraries currently support, so '
492
- 'the behavior is subject to change in each release.')
492
+ **guided_decoding_kwargs["guided_decoding_backend"])
493
+ guided_decoding_group.add_argument(
494
+ "--reasoning-parser",
495
+ # This choices is a special case because it's not static
496
+ choices=list(ReasoningParserManager.reasoning_parsers),
497
+ **guided_decoding_kwargs["reasoning_backend"])
498
+
499
parser.add_argument(
500
'--logits-processor-pattern',
501
type=optional_str,
@@ -697,18 +703,14 @@ class EngineArgs:
703
**tokenizer_kwargs["extra_config"])
704
705
# Multimodal related configs
700
- parser.add_argument(
701
- '--limit-mm-per-prompt',
702
- type=nullable_kvs,
703
- default=EngineArgs.limit_mm_per_prompt,
704
- # The default value is given in
705
- # MultiModalConfig.get_default_limit_per_prompt
706
- help=('For each multimodal plugin, limit how many '
707
- 'input instances to allow for each prompt. '
708
- 'Expects a comma-separated list of items, '
709
- 'e.g.: `image=16,video=2` allows a maximum of 16 '
710
- 'images and 2 videos per prompt. Defaults to '
711
- '1 (V0) or 999 (V1) for each modality.'))
706
+ multimodal_kwargs = get_kwargs(MultiModalConfig)
707
+ multimodal_group = parser.add_argument_group(
708
+ title="MultiModalConfig",
709
+ description=MultiModalConfig.__doc__,
710
+ )
711
+ multimodal_group.add_argument('--limit-mm-per-prompt',
712
+ **multimodal_kwargs["limit_per_prompt"])
713
+
714
parser.add_argument(
715
'--mm-processor-kwargs',
716
default=None,
@@ -1018,16 +1020,6 @@ class EngineArgs:
1020
"If enabled, the model will be able to generate reasoning content."
1021
)
1022
1021
- parser.add_argument(
1022
- "--reasoning-parser",
1023
- type=str,
1024
- choices=list(ReasoningParserManager.reasoning_parsers),
1025
- default=None,
1026
- help=
1027
- "Select the reasoning parser depending on the model that you're "
1028
- "using. This is used to parse the reasoning content into OpenAI "
1029
- "API format. Required for ``--enable-reasoning``.")
1030
-
1023
parser.add_argument(
1024
"--disable-cascade-attn",
1025
action="store_true",
vllm/multimodal/processing.py
+4
-2
@@ -1,4 +1,5 @@
1
# SPDX-License-Identifier: Apache-2.0
2
+import json
3
import re
4
import sys
5
from abc import ABC, abstractmethod
@@ -1117,8 +1118,9 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1118
1119
if num_items > allowed_limit:
1120
raise ValueError(
1120
- f"You set or defaulted to {modality}={allowed_limit} "
1121
- f"in --limit-mm-per-prompt`, but passed {num_items} "
1121
+ "You set or defaulted to "
1122
+ f"'{json.dumps({modality: allowed_limit})}' in "
1123
+ f"`--limit-mm-per-prompt`, but passed {num_items} "
1124
f"{modality} items in the same prompt.")
1125
1126
return mm_items
vllm/multimodal/registry.py
+4
-3
@@ -1,6 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
3
import functools
4
+import json
5
from collections import UserDict
6
from collections.abc import Mapping, Sequence
7
from dataclasses import dataclass
@@ -194,9 +195,9 @@ class MultiModalRegistry:
195
max_items = self._limits_by_model[model_config][data_key]
196
if num_items > max_items:
197
raise ValueError(
197
- f"You set {data_key}={max_items} (or defaulted to 1) in "
198
- f"`--limit-mm-per-prompt`, but found {num_items} items "
199
- "in the same prompt.")
198
+ f"You set '{json.dumps({data_key: max_items})}' (or "
199
+ "defaulted to 1) in `--limit-mm-per-prompt`, but found "
200
+ f"{num_items} items in the same prompt.")
201
202
input_dict = plugin.map_input(model_config, data_value,
203
mm_processor_kwargs)