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
[Doc] ruff format some Python examples (#26767)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Oct 14, 2025 at 18:21 UTC
ef9676a1f1af444d35160794b79c85594c517a6c
20 files changed
+341
-290
docs/configuration/conserving_memory.md
+23
-21
@@ -11,8 +11,7 @@ The following code splits the model across 2 GPUs.
11
```python
12
from vllm import LLM
13
14
-llm = LLM(model="ibm-granite/granite-3.1-8b-instruct",
15
- tensor_parallel_size=2)
14
+llm = LLM(model="ibm-granite/granite-3.1-8b-instruct", tensor_parallel_size=2)
15
```
16
17
!!! warning
@@ -43,9 +42,7 @@ and the maximum batch size (`max_num_seqs` option).
42
```python
43
from vllm import LLM
44
46
-llm = LLM(model="adept/fuyu-8b",
47
- max_model_len=2048,
48
- max_num_seqs=2)
45
+llm = LLM(model="adept/fuyu-8b", max_model_len=2048, max_num_seqs=2)
46
```
47
48
## Reduce CUDA Graphs
@@ -78,8 +75,7 @@ You can disable graph capturing completely via the `enforce_eager` flag:
75
```python
76
from vllm import LLM
77
81
-llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct",
82
- enforce_eager=True)
78
+llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct", enforce_eager=True)
79
```
80
81
## Adjust cache size
@@ -97,8 +93,10 @@ You can allow a smaller number of multi-modal items per prompt to reduce the mem
93
from vllm import LLM
94
95
# Accept up to 3 images and 1 video per prompt
100
-llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct",
101
- limit_mm_per_prompt={"image": 3, "video": 1})
96
+llm = LLM(
97
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
98
+ limit_mm_per_prompt={"image": 3, "video": 1},
99
+)
100
```
101
102
You can go a step further and disable unused modalities completely by setting its limit to zero.
@@ -108,8 +106,10 @@ For example, if your application only accepts image input, there is no need to a
106
from vllm import LLM
107
108
# Accept any number of images but no videos
111
-llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct",
112
- limit_mm_per_prompt={"video": 0})
109
+llm = LLM(
110
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
111
+ limit_mm_per_prompt={"video": 0},
112
+)
113
```
114
115
You can even run a multi-modal model for text-only inference:
@@ -118,8 +118,10 @@ You can even run a multi-modal model for text-only inference:
118
from vllm import LLM
119
120
# Don't accept images. Just text.
121
-llm = LLM(model="google/gemma-3-27b-it",
122
- limit_mm_per_prompt={"image": 0})
121
+llm = LLM(
122
+ model="google/gemma-3-27b-it",
123
+ limit_mm_per_prompt={"image": 0},
124
+)
125
```
126
127
### Configurable options
@@ -173,14 +175,14 @@ Here are some examples:
175
from vllm import LLM
176
177
# Available for Qwen2-VL series models
176
-llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct",
177
- mm_processor_kwargs={
178
- "max_pixels": 768 * 768, # Default is 1280 * 28 * 28
179
- })
178
+llm = LLM(
179
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
180
+ mm_processor_kwargs={"max_pixels": 768 * 768}, # Default is 1280 * 28 * 28
181
+)
182
183
# Available for InternVL series models
182
-llm = LLM(model="OpenGVLab/InternVL2-2B",
183
- mm_processor_kwargs={
184
- "max_dynamic_patch": 4, # Default is 12
185
- })
184
+llm = LLM(
185
+ model="OpenGVLab/InternVL2-2B",
186
+ mm_processor_kwargs={"max_dynamic_patch": 4}, # Default is 12
187
+)
188
```
docs/configuration/optimization.md
+15
-9
@@ -100,7 +100,7 @@ from vllm import LLM
100
llm = LLM(
101
model="meta-llama/Llama-3.3-70B-Instruct,
102
tensor_parallel_size=4,
103
- pipeline_parallel_size=2
103
+ pipeline_parallel_size=2,
104
)
105
```
106
@@ -257,18 +257,24 @@ Examples:
257
258
```python
259
# Use a larger cache
260
-llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct",
261
- mm_processor_cache_gb=8)
260
+llm = LLM(
261
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
262
+ mm_processor_cache_gb=8,
263
+)
264
265
# Use a shared-memory based IPC cache
264
-llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct",
265
- tensor_parallel_size=2,
266
- mm_processor_cache_type="shm",
267
- mm_processor_cache_gb=8)
266
+llm = LLM(
267
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
268
+ tensor_parallel_size=2,
269
+ mm_processor_cache_type="shm",
270
+ mm_processor_cache_gb=8,
271
+)
272
273
# Disable the cache
270
-llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct",
271
- mm_processor_cache_gb=0)
274
+llm = LLM(
275
+ model="Qwen/Qwen2.5-VL-3B-Instruct",
276
+ mm_processor_cache_gb=0,
277
+)
278
```
279
280
### Cache Placement
docs/contributing/model/basic.md
+2
-2
@@ -73,8 +73,8 @@ def forward(
73
self,
74
input_ids: torch.Tensor,
75
positions: torch.Tensor,
76
- intermediate_tensors: Optional[IntermediateTensors] = None,
77
- inputs_embeds: Optional[torch.Tensor] = None,
76
+ intermediate_tensors: IntermediateTensors | None = None,
77
+ inputs_embeds: torch.Tensor | None = None,
78
) -> torch.Tensor:
79
...
80
```
docs/contributing/model/multimodal.md
+23
-19
@@ -16,7 +16,7 @@ Further update the model as follows:
16
...
17
18
@classmethod
19
- def get_placeholder_str(cls, modality: str, i: int) -> Optional[str]:
19
+ def get_placeholder_str(cls, modality: str, i: int) -> str | None:
20
if modality.startswith("image"):
21
return "<image>"
22
@@ -45,14 +45,14 @@ Further update the model as follows:
45
...
46
47
def _process_image_input(self, image_input: YourModelImageInputs) -> torch.Tensor:
48
-
48
assert self.vision_encoder is not None
49
image_features = self.vision_encoder(image_input)
50
return self.multi_modal_projector(image_features)
51
52
def get_multimodal_embeddings(
54
- self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
55
-
53
+ self,
54
+ **kwargs: object,
55
+ ) -> MultiModalEmbeddings | None:
56
# Validate the multimodal input keyword arguments
57
image_input = self._parse_and_validate_image_input(**kwargs)
58
if image_input is None:
@@ -110,7 +110,7 @@ to return the maximum number of input items for each modality supported by the m
110
For example, if the model supports any number of images but only one video per prompt:
111
112
```python
113
-def get_supported_mm_limits(self) -> Mapping[str, Optional[int]]:
113
+def get_supported_mm_limits(self) -> Mapping[str, int | None]:
114
return {"image": None, "video": 1}
115
```
116
@@ -258,7 +258,7 @@ Assuming that the memory usage increases with the number of tokens, the dummy in
258
self,
259
seq_len: int,
260
mm_counts: Mapping[str, int],
261
- mm_options: Optional[Mapping[str, BaseDummyOptions]] = None,
261
+ mm_options: Mapping[str, BaseDummyOptions] | None = None,
262
) -> MultiModalDataDict:
263
num_images = mm_counts.get("image", 0)
264
@@ -421,8 +421,10 @@ Assuming that the memory usage increases with the number of tokens, the dummy in
421
```python
422
def get_image_size_with_most_features(self) -> ImageSize:
423
image_processor = self.get_image_processor()
424
- return ImageSize(width=image_processor.size["width"],
425
- height=image_processor.size["height"])
424
+ return ImageSize(
425
+ width=image_processor.size["width"],
426
+ height=image_processor.size["height"],
427
+ )
428
```
429
430
Fuyu does not expect image placeholders in the inputs to HF processor, so
@@ -452,10 +454,12 @@ Assuming that the memory usage increases with the number of tokens, the dummy in
454
455
return {
456
"image":
455
- self._get_dummy_images(width=target_width,
456
- height=target_height,
457
- num_images=num_images,
458
- overrides=image_overrides)
457
+ self._get_dummy_images(
458
+ width=target_width,
459
+ height=target_height,
460
+ num_images=num_images,
461
+ overrides=image_overrides,
462
+ )
463
}
464
```
465
@@ -744,8 +748,7 @@ Each [PromptUpdate][vllm.multimodal.processing.PromptUpdate] instance specifies
748
image_width=image_size.width,
749
image_height=image_size.height,
750
)
747
- image_tokens = ([_IMAGE_TOKEN_ID] * ncols +
748
- [_NEWLINE_TOKEN_ID]) * nrows
751
+ image_tokens = ([_IMAGE_TOKEN_ID] * ncols + [_NEWLINE_TOKEN_ID]) * nrows
752
753
return PromptUpdateDetails.select_token_id(
754
image_tokens + [bos_token_id],
@@ -781,8 +784,7 @@ Each [PromptUpdate][vllm.multimodal.processing.PromptUpdate] instance specifies
784
image_width=image_size.width,
785
image_height=image_size.height,
786
)
784
- image_tokens = ([_IMAGE_TOKEN_ID] * ncols +
785
- [_NEWLINE_TOKEN_ID]) * nrows
787
+ image_tokens = ([_IMAGE_TOKEN_ID] * ncols + [_NEWLINE_TOKEN_ID]) * nrows
788
789
return PromptUpdateDetails.select_token_id(
790
image_tokens + [bos_token_id],
@@ -810,9 +812,11 @@ to register them to the multi-modal registry:
812
from vllm.model_executor.models.interfaces import SupportsMultiModal
813
+ from vllm.multimodal import MULTIMODAL_REGISTRY
814
813
-+ @MULTIMODAL_REGISTRY.register_processor(YourMultiModalProcessor,
814
-+ info=YourProcessingInfo,
815
-+ dummy_inputs=YourDummyInputsBuilder)
815
++ @MULTIMODAL_REGISTRY.register_processor(
816
++ YourMultiModalProcessor,
817
++ info=YourProcessingInfo,
818
++ dummy_inputs=YourDummyInputsBuilder,
819
++ )
820
class YourModelForImage2Seq(nn.Module, SupportsMultiModal):
821
```
822
docs/contributing/model/registration.md
+1
-1
@@ -42,7 +42,7 @@ def register():
42
43
ModelRegistry.register_model(
44
"YourModelForCausalLM",
45
- "your_code:YourModelForCausalLM"
45
+ "your_code:YourModelForCausalLM",
46
)
47
```
48
docs/contributing/model/transcription.md
+11
-1
@@ -15,6 +15,7 @@ Declare supported languages and capabilities:
15
- Set `supports_transcription_only=True` if the model should not serve text generation (eg Whisper).
16
17
??? code "supported_languages and supports_transcription_only"
18
+
19
```python
20
from typing import ClassVar, Mapping, Literal
21
import numpy as np
@@ -43,6 +44,7 @@ Provide an ASR configuration via [get_speech_to_text_config][vllm.model_executor
44
This is for controlling general behavior of the API when serving your model:
45
46
??? code "get_speech_to_text_config()"
47
+
48
```python
49
class YourASRModel(nn.Module, SupportsTranscription):
50
...
@@ -71,6 +73,7 @@ Implement the prompt construction via [get_generation_prompt][vllm.model_executo
73
Return a dict containing `multi_modal_data` with the audio, and either a `prompt` string or `prompt_token_ids`:
74
75
??? code "get_generation_prompt()"
76
+
77
```python
78
class YourASRModel(nn.Module, SupportsTranscription):
79
...
@@ -107,6 +110,7 @@ Return a dict containing `multi_modal_data` with the audio, and either a `prompt
110
Return a dict with separate `encoder_prompt` and `decoder_prompt` entries:
111
112
??? code "get_generation_prompt()"
113
+
114
```python
115
class YourASRModel(nn.Module, SupportsTranscription):
116
...
@@ -148,12 +152,16 @@ Language validation via [validate_language][vllm.model_executor.models.interface
152
If your model requires a language and you want a default, override this method (see Whisper):
153
154
??? code "validate_language()"
155
+
156
```python
157
@classmethod
158
def validate_language(cls, language: str | None) -> str | None:
159
if language is None:
160
logger.warning(
156
- "Defaulting to language='en'. If you wish to transcribe audio in a different language, pass the `language` field.")
161
+ "Defaulting to language='en'. If you wish to transcribe "
162
+ "audio in a different language, pass the `language` field "
163
+ "in the TranscriptionRequest."
164
+ )
165
language = "en"
166
return super().validate_language(language)
167
```
@@ -165,6 +173,7 @@ Token accounting for streaming via [get_num_audio_tokens][vllm.model_executor.mo
173
Provide a fast duration→token estimate to improve streaming usage statistics:
174
175
??? code "get_num_audio_tokens()"
176
+
177
```python
178
class YourASRModel(nn.Module, SupportsTranscription):
179
...
@@ -191,6 +200,7 @@ The API server takes care of basic audio I/O and optional chunking before buildi
200
Relevant server logic:
201
202
??? code "_preprocess_speech_to_text()"
203
+
204
```python
205
# vllm/entrypoints/openai/speech_to_text.py
206
async def _preprocess_speech_to_text(...):
docs/deployment/frameworks/cerebrium.md
+2
-2
@@ -63,7 +63,7 @@ If successful, you should be returned a CURL command that you can call inference
63
64
??? console "Command"
65
66
- ```python
66
+ ```bash
67
curl -X POST https://api.cortex.cerebrium.ai/v4/p-xxxxxx/vllm/run \
68
-H 'Content-Type: application/json' \
69
-H 'Authorization: <JWT TOKEN>' \
@@ -81,7 +81,7 @@ You should get a response like:
81
82
??? console "Response"
83
84
- ```python
84
+ ```json
85
{
86
"run_id": "52911756-3066-9ae8-bcc9-d9129d1bd262",
87
"result": {
docs/deployment/frameworks/dstack.md
+2
-2
@@ -83,7 +83,7 @@ After the provisioning, you can interact with the model by using the OpenAI SDK:
83
84
client = OpenAI(
85
base_url="https://gateway.<gateway domain>",
86
- api_key="<YOUR-DSTACK-SERVER-ACCESS-TOKEN>"
86
+ api_key="<YOUR-DSTACK-SERVER-ACCESS-TOKEN>",
87
)
88
89
completion = client.chat.completions.create(
@@ -93,7 +93,7 @@ After the provisioning, you can interact with the model by using the OpenAI SDK:
93
"role": "user",
94
"content": "Compose a poem that explains the concept of recursion in programming.",
95
}
96
- ]
96
+ ],
97
)
98
99
print(completion.choices[0].message.content)
docs/deployment/frameworks/haystack.md
+1
-1
@@ -34,7 +34,7 @@ pip install vllm haystack-ai
34
api_key=Secret.from_token("VLLM-PLACEHOLDER-API-KEY"),
35
model="mistralai/Mistral-7B-Instruct-v0.1",
36
api_base_url="http://{your-vLLM-host-ip}:{your-vLLM-host-port}/v1",
37
- generation_kwargs = {"max_tokens": 512}
37
+ generation_kwargs={"max_tokens": 512},
38
)
39
40
response = generator.run(
docs/deployment/frameworks/hf_inference_endpoints.md
+18
-18
@@ -32,28 +32,28 @@ This is the easiest way to get started with vLLM on Hugging Face Inference Endpo
32
import os
33
34
client = OpenAI(
35
- base_url = DEPLOYMENT_URL,
36
- api_key = os.environ["HF_TOKEN"] # https://huggingface.co/settings/tokens
35
+ base_url=DEPLOYMENT_URL,
36
+ api_key=os.environ["HF_TOKEN"], # https://huggingface.co/settings/tokens
37
)
38
39
chat_completion = client.chat.completions.create(
40
- model = "HuggingFaceTB/SmolLM3-3B",
41
- messages = [
40
+ model="HuggingFaceTB/SmolLM3-3B",
41
+ messages=[
42
{
43
"role": "user",
44
"content": [
45
{
46
"type": "text",
47
- "text": "Give me a brief explanation of gravity in simple terms."
47
+ "text": "Give me a brief explanation of gravity in simple terms.",
48
}
49
- ]
49
+ ],
50
}
51
],
52
- stream = True
52
+ stream=True,
53
)
54
55
for message in chat_completion:
56
- print(message.choices[0].delta.content, end = "")
56
+ print(message.choices[0].delta.content, end="")
57
```
58
59
!!! note
@@ -86,34 +86,34 @@ This method applies to models with the [`transformers` library tag](https://hugg
86
import os
87
88
client = OpenAI(
89
- base_url = DEPLOYMENT_URL,
90
- api_key = os.environ["HF_TOKEN"] # https://huggingface.co/settings/tokens
89
+ base_url=DEPLOYMENT_URL,
90
+ api_key=os.environ["HF_TOKEN"], # https://huggingface.co/settings/tokens
91
)
92
93
chat_completion = client.chat.completions.create(
94
- model = "ibm-granite/granite-docling-258M",
95
- messages = [
94
+ model="ibm-granite/granite-docling-258M",
95
+ messages=[
96
{
97
"role": "user",
98
"content": [
99
{
100
"type": "image_url",
101
"image_url": {
102
- "url": "https://huggingface.co/ibm-granite/granite-docling-258M/resolve/main/assets/new_arxiv.png"
103
- }
102
+ "url": "https://huggingface.co/ibm-granite/granite-docling-258M/resolve/main/assets/new_arxiv.png",
103
+ },
104
},
105
{
106
"type": "text",
107
- "text": "Convert this page to docling."
108
- }
107
+ "text": "Convert this page to docling.",
108
+ },
109
]
110
}
111
],
112
- stream = True
112
+ stream=True,
113
)
114
115
for message in chat_completion:
116
- print(message.choices[0].delta.content, end = "")
116
+ print(message.choices[0].delta.content, end="")
117
```
118
119
!!! note
docs/deployment/frameworks/litellm.md
+7
-6
@@ -36,15 +36,16 @@ pip install vllm litellm
36
```python
37
import litellm
38
39
- messages = [{ "content": "Hello, how are you?","role": "user"}]
39
+ messages = [{"content": "Hello, how are you?", "role": "user"}]
40
41
# hosted_vllm is prefix key word and necessary
42
response = litellm.completion(
43
- model="hosted_vllm/qwen/Qwen1.5-0.5B-Chat", # pass the vllm model name
44
- messages=messages,
45
- api_base="http://{your-vllm-server-host}:{your-vllm-server-port}/v1",
46
- temperature=0.2,
47
- max_tokens=80)
43
+ model="hosted_vllm/qwen/Qwen1.5-0.5B-Chat", # pass the vllm model name
44
+ messages=messages,
45
+ api_base="http://{your-vllm-server-host}:{your-vllm-server-port}/v1",
46
+ temperature=0.2,
47
+ max_tokens=80,
48
+ )
49
50
print(response)
51
```
docs/deployment/frameworks/retrieval_augmented_generation.md
+2
-2
@@ -40,7 +40,7 @@ pip install -U vllm \
40
41
1. Run the script
42
43
- ```python
43
+ ```bash
44
python retrieval_augmented_generation_with_langchain.py
45
```
46
@@ -78,6 +78,6 @@ pip install vllm \
78
79
1. Run the script:
80
81
- ```python
81
+ ```bash
82
python retrieval_augmented_generation_with_llamaindex.py
83
```
docs/design/cuda_graphs.md
+9
-7
@@ -106,9 +106,11 @@ The dispatch code looks like:
106
batch_descriptor=BatchDescriptor(num_tokens=num_input_tokens, uniform_decode=...)
107
runtime_mode, batch_descriptor = cudagraphdispatcher.dispatch(batch_descriptor)
108
# execution
109
-with set_forward_context(...,
110
- cudagraph_runtime_mode=runtime_mode,
111
- batch_descriptor=batch_descriptor):
109
+with set_forward_context(
110
+ ...,
111
+ cudagraph_runtime_mode=runtime_mode,
112
+ batch_descriptor=batch_descriptor,
113
+):
114
output = self.model(...)
115
```
116
@@ -202,10 +204,10 @@ from vllm.config import CUDAGraphMode
204
205
compilation_config = {"level": 3, "cudagraph_mode": "FULL_AND_PIECEWISE"}
206
model = vllm.LLM(
205
- model="meta-llama/Llama-3.1-8B-Instruct",
206
- dtype='auto',
207
- compilation_config = compilation_config,
208
- )
207
+ model="meta-llama/Llama-3.1-8B-Instruct",
208
+ dtype="auto",
209
+ compilation_config=compilation_config,
210
+)
211
sampling_params = vllm.SamplingParams(
212
temperature=0, # greedy decoding
213
max_tokens=1024,
docs/design/io_processor_plugins.md
+15
-12
@@ -9,8 +9,8 @@ When performing an inference with IO Processor plugins, the prompt type is defin
9
IO Processor plugins implement the `IOProcessor` interface (<gh-file:vllm/plugins/io_processors/interface.py>):
10
11
```python
12
-IOProcessorInput = TypeVar('IOProcessorInput')
13
-IOProcessorOutput = TypeVar('IOProcessorOutput')
12
+IOProcessorInput = TypeVar("IOProcessorInput")
13
+IOProcessorOutput = TypeVar("IOProcessorOutput")
14
15
class IOProcessor(ABC, Generic[IOProcessorInput, IOProcessorOutput]):
16
@@ -21,30 +21,32 @@ class IOProcessor(ABC, Generic[IOProcessorInput, IOProcessorOutput]):
21
def pre_process(
22
self,
23
prompt: IOProcessorInput,
24
- request_id: Optional[str] = None,
24
+ request_id: str | None = None,
25
**kwargs,
26
- ) -> Union[PromptType, Sequence[PromptType]]:
26
+ ) -> PromptType | Sequence[PromptType]:
27
raise NotImplementedError
28
29
async def pre_process_async(
30
self,
31
prompt: IOProcessorInput,
32
- request_id: Optional[str] = None,
32
+ request_id: str | None = None,
33
**kwargs,
34
- ) -> Union[PromptType, Sequence[PromptType]]:
34
+ ) -> PromptType | Sequence[PromptType]:
35
return self.pre_process(prompt, request_id, **kwargs)
36
37
@abstractmethod
38
- def post_process(self,
39
- model_output: Sequence[PoolingRequestOutput],
40
- request_id: Optional[str] = None,
41
- **kwargs) -> IOProcessorOutput:
38
+ def post_process(
39
+ self,
40
+ model_output: Sequence[PoolingRequestOutput],
41
+ request_id: str | None = None,
42
+ **kwargs,
43
+ ) -> IOProcessorOutput:
44
raise NotImplementedError
45
46
async def post_process_async(
47
self,
48
model_output: AsyncGenerator[tuple[int, PoolingRequestOutput]],
47
- request_id: Optional[str] = None,
49
+ request_id: str | None = None,
50
**kwargs,
51
) -> IOProcessorOutput:
52
collected_output = [item async for i, item in model_output]
@@ -56,7 +58,8 @@ class IOProcessor(ABC, Generic[IOProcessorInput, IOProcessorOutput]):
58
59
@abstractmethod
60
def output_to_response(
59
- self, plugin_output: IOProcessorOutput) -> IOProcessorResponse:
61
+ self, plugin_output: IOProcessorOutput
62
+ ) -> IOProcessorResponse:
63
raise NotImplementedError
64
```
65
docs/design/metrics.md
+7
-5
@@ -478,15 +478,17 @@ us with:
478
479
```python
480
if seq_group.is_finished():
481
- if (seq_group.metrics.first_scheduled_time is not None and
482
- seq_group.metrics.first_token_time is not None):
481
+ if (
482
+ seq_group.metrics.first_scheduled_time is not None
483
+ and seq_group.metrics.first_token_time is not None
484
+ ):
485
time_queue_requests.append(
486
seq_group.metrics.first_scheduled_time -
485
- seq_group.metrics.arrival_time)
487
+ seq_group.metrics.arrival_time
488
+ )
489
...
490
if seq_group.metrics.time_in_queue is not None:
488
- time_in_queue_requests.append(
489
- seq_group.metrics.time_in_queue)
491
+ time_in_queue_requests.append(seq_group.metrics.time_in_queue)
492
```
493
494
This seems duplicative, and one of them should be removed. The latter
docs/design/prefix_caching.md
+2
-2
@@ -112,8 +112,8 @@ class KVCacheBlock:
112
ref_cnt: int
113
114
# The pointers to form a doubly linked list for the free queue.
115
- prev_free_block: Optional["KVCacheBlock"] = None
116
- next_free_block: Optional["KVCacheBlock"] = None
115
+ prev_free_block: "KVCacheBlock | None" = None
116
+ next_free_block: "KVCacheBlock | None" = None
117
```
118
119
There are two design points to highlight:
docs/features/lora.md
+4
-7
@@ -32,7 +32,7 @@ the third parameter is the path to the LoRA adapter.
32
sampling_params = SamplingParams(
33
temperature=0,
34
max_tokens=256,
35
- stop=["[/assistant]"]
35
+ stop=["[/assistant]"],
36
)
37
38
prompts = [
@@ -43,7 +43,7 @@ the third parameter is the path to the LoRA adapter.
43
outputs = llm.generate(
44
prompts,
45
sampling_params,
46
- lora_request=LoRARequest("sql_adapter", 1, sql_lora_path)
46
+ lora_request=LoRARequest("sql_adapter", 1, sql_lora_path),
47
)
48
```
49
@@ -197,7 +197,7 @@ Alternatively, follow these example steps to implement your own plugin:
197
lora_request = LoRARequest(
198
lora_name=lora_name,
199
lora_path=local_path,
200
- lora_int_id=abs(hash(lora_name))
200
+ lora_int_id=abs(hash(lora_name)),
201
)
202
return lora_request
203
```
@@ -296,10 +296,7 @@ To this end, we allow registration of default multimodal LoRAs to handle this au
296
if has_audio:
297
question = f"<|audio|>{question}"
298
chat = [
299
- {
300
- "role": "user",
301
- "content": question
302
- }
299
+ {"role": "user", "content": question},
300
]
301
return tokenizer.apply_chat_template(chat, tokenize=False)
302
docs/features/multimodal_inputs.md
+147
-130
@@ -154,9 +154,7 @@ To substitute multiple images inside the same text prompt, you can pass in a lis
154
155
outputs = llm.generate({
156
"prompt": prompt,
157
- "multi_modal_data": {
158
- "image": [image1, image2]
159
- },
157
+ "multi_modal_data": {"image": [image1, image2]},
158
})
159
160
for o in outputs:
@@ -183,21 +181,24 @@ conversation = [
181
{"role": "assistant", "content": "Hello! How can I assist you today?"},
182
{
183
"role": "user",
186
- "content": [{
187
- "type": "image_url",
188
- "image_url": {
189
- "url": image_url
190
- }
191
- },{
192
- "type": "image_pil",
193
- "image_pil": image_pil
194
- }, {
195
- "type": "image_embeds",
196
- "image_embeds": image_embeds
197
- }, {
198
- "type": "text",
199
- "text": "What's in these images?"
200
- }],
184
+ "content": [
185
+ {
186
+ "type": "image_url",
187
+ "image_url": {"url": image_url},
188
+ },
189
+ {
190
+ "type": "image_pil",
191
+ "image_pil": image_pil,
192
+ },
193
+ {
194
+ "type": "image_embeds",
195
+ "image_embeds": image_embeds,
196
+ },
197
+ {
198
+ "type": "text",
199
+ "text": "What's in these images?",
200
+ },
201
+ ],
202
},
203
]
204
@@ -224,7 +225,10 @@ Multi-image input can be extended to perform video captioning. We show this with
225
message = {
226
"role": "user",
227
"content": [
227
- {"type": "text", "text": "Describe this set of frames. Consider the frames to be a part of the same video."},
228
+ {
229
+ "type": "text",
230
+ "text": "Describe this set of frames. Consider the frames to be a part of the same video.",
231
+ },
232
],
233
}
234
for i in range(len(video_frames)):
@@ -255,13 +259,13 @@ When loading RGBA images (images with transparency), vLLM converts them to RGB f
259
# Custom black background for dark theme
260
llm = LLM(
261
model="llava-hf/llava-1.5-7b-hf",
258
- media_io_kwargs={"image": {"rgba_background_color": [0, 0, 0]}}
262
+ media_io_kwargs={"image": {"rgba_background_color": [0, 0, 0]}},
263
)
264
265
# Custom brand color background (e.g., blue)
266
llm = LLM(
267
model="llava-hf/llava-1.5-7b-hf",
264
- media_io_kwargs={"image": {"rgba_background_color": [0, 0, 255]}}
268
+ media_io_kwargs={"image": {"rgba_background_color": [0, 0, 255]}},
269
)
270
```
271
@@ -294,20 +298,23 @@ Instead of NumPy arrays, you can also pass `'torch.Tensor'` instances, as shown
298
limit_mm_per_prompt={"video": 1},
299
)
300
297
- sampling_params = SamplingParams(
298
- max_tokens=1024,
299
- )
301
+ sampling_params = SamplingParams(max_tokens=1024)
302
303
video_messages = [
302
- {"role": "system", "content": "You are a helpful assistant."},
303
- {"role": "user", "content": [
304
+ {
305
+ "role": "system",
306
+ "content": "You are a helpful assistant.",
307
+ },
308
+ {
309
+ "role": "user",
310
+ "content": [
311
{"type": "text", "text": "describe this video."},
312
{
313
"type": "video",
314
"video": video_path,
315
"total_pixels": 20480 * 28 * 28,
309
- "min_pixels": 16 * 28 * 28
310
- }
316
+ "min_pixels": 16 * 28 * 28,
317
+ },
318
]
319
},
320
]
@@ -465,21 +472,24 @@ Then, you can use the OpenAI client as follows:
472
473
chat_response = client.chat.completions.create(
474
model="microsoft/Phi-3.5-vision-instruct",
468
- messages=[{
469
- "role": "user",
470
- "content": [
471
- # NOTE: The prompt formatting with the image token `<image>` is not needed
472
- # since the prompt will be processed automatically by the API server.
473
- {"type": "text", "text": "What’s in this image?"},
474
- {
475
- "type": "image_url",
476
- "image_url": {
477
- url": image_url
475
+ messages=[
476
+ {
477
+ "role": "user",
478
+ "content": [
479
+ # NOTE: The prompt formatting with the image token `<image>` is not needed
480
+ # since the prompt will be processed automatically by the API server.
481
+ {
482
+ "type": "text",
483
+ "text": "What’s in this image?",
484
},
479
- "uuid": image_url # Optional
480
- },
481
- ],
482
- }],
485
+ {
486
+ "type": "image_url",
487
+ "image_url": {"url": image_url},
488
+ "uuid": image_url, # Optional
489
+ },
490
+ ],
491
+ }
492
+ ],
493
)
494
print("Chat completion output:", chat_response.choices[0].message.content)
495
@@ -489,26 +499,27 @@ Then, you can use the OpenAI client as follows:
499
500
chat_response = client.chat.completions.create(
501
model="microsoft/Phi-3.5-vision-instruct",
492
- messages=[{
493
- "role": "user",
494
- "content": [
495
- {"type": "text", "text": "What are the animals in these images?"},
496
- {
497
- "type": "image_url",
498
- "image_url": {
499
- "url": image_url_duck
502
+ messages=[
503
+ {
504
+ "role": "user",
505
+ "content": [
506
+ {
507
+ "type": "text",
508
+ "text": "What are the animals in these images?",
509
},
501
- "uuid": image_url_duck # Optional
502
- },
503
- {
504
- "type": "image_url",
505
- "image_url": {
506
- "url": image_url_lion
510
+ {
511
+ "type": "image_url",
512
+ "image_url": {"url": image_url_duck},
513
+ "uuid": image_url_duck, # Optional
514
},
508
- "uuid": image_url_lion # Optional
509
- },
510
- ],
511
- }],
515
+ {
516
+ "type": "image_url",
517
+ "image_url": {"url": image_url_lion},
518
+ "uuid": image_url_lion, # Optional
519
+ },
520
+ ],
521
+ }
522
+ ],
523
)
524
print("Chat completion output:", chat_response.choices[0].message.content)
525
```
@@ -560,23 +571,22 @@ Then, you can use the OpenAI client as follows:
571
572
## Use video url in the payload
573
chat_completion_from_url = client.chat.completions.create(
563
- messages=[{
564
- "role":
565
- "user",
566
- "content": [
567
- {
568
- "type": "text",
569
- "text": "What's in this video?"
570
- },
571
- {
572
- "type": "video_url",
573
- "video_url": {
574
- "url": video_url
574
+ messages=[
575
+ {
576
+ "role": "user",
577
+ "content": [
578
+ {
579
+ "type": "text",
580
+ "text": "What's in this video?",
581
},
576
- "uuid": video_url # Optional
577
- },
578
- ],
579
- }],
582
+ {
583
+ "type": "video_url",
584
+ "video_url": {"url": video_url},
585
+ "uuid": video_url, # Optional
586
+ },
587
+ ],
588
+ }
589
+ ],
590
model=model,
591
max_completion_tokens=64,
592
)
@@ -652,23 +662,25 @@ Then, you can use the OpenAI client as follows:
662
audio_base64 = encode_base64_content_from_url(audio_url)
663
664
chat_completion_from_base64 = client.chat.completions.create(
655
- messages=[{
656
- "role": "user",
657
- "content": [
658
- {
659
- "type": "text",
660
- "text": "What's in this audio?"
661
- },
662
- {
663
- "type": "input_audio",
664
- "input_audio": {
665
- "data": audio_base64,
666
- "format": "wav"
665
+ messages=[
666
+ {
667
+ "role": "user",
668
+ "content": [
669
+ {
670
+ "type": "text",
671
+ "text": "What's in this audio?",
672
},
668
- "uuid": audio_url # Optional
669
- },
670
- ],
671
- }],
673
+ {
674
+ "type": "input_audio",
675
+ "input_audio": {
676
+ "data": audio_base64,
677
+ "format": "wav",
678
+ },
679
+ "uuid": audio_url, # Optional
680
+ },
681
+ ],
682
+ },
683
+ ],
684
model=model,
685
max_completion_tokens=64,
686
)
@@ -683,22 +695,22 @@ Alternatively, you can pass `audio_url`, which is the audio counterpart of `imag
695
696
```python
697
chat_completion_from_url = client.chat.completions.create(
686
- messages=[{
687
- "role": "user",
688
- "content": [
689
- {
690
- "type": "text",
691
- "text": "What's in this audio?"
692
- },
693
- {
694
- "type": "audio_url",
695
- "audio_url": {
696
- "url": audio_url
698
+ messages=[
699
+ {
700
+ "role": "user",
701
+ "content": [
702
+ {
703
+ "type": "text",
704
+ "text": "What's in this audio?",
705
},
698
- "uuid": audio_url # Optional
699
- },
700
- ],
701
- }],
706
+ {
707
+ "type": "audio_url",
708
+ "audio_url": {"url": audio_url},
709
+ "uuid": audio_url, # Optional
710
+ },
711
+ ],
712
+ }
713
+ ],
714
model=model,
715
max_completion_tokens=64,
716
)
@@ -747,43 +759,48 @@ The following example demonstrates how to pass image embeddings to the OpenAI se
759
760
# Basic usage - this is equivalent to the LLaVA example for offline inference
761
model = "llava-hf/llava-1.5-7b-hf"
750
- embeds = {
762
+ embeds = {
763
"type": "image_embeds",
764
"image_embeds": f"{base64_image_embedding}",
753
- "uuid": image_url # Optional
765
+ "uuid": image_url, # Optional
766
}
767
768
# Pass additional parameters (available to Qwen2-VL and MiniCPM-V)
769
model = "Qwen/Qwen2-VL-2B-Instruct"
758
- embeds = {
770
+ embeds = {
771
"type": "image_embeds",
772
"image_embeds": {
761
- "image_embeds": f"{base64_image_embedding}" , # Required
762
- "image_grid_thw": f"{base64_image_grid_thw}" # Required by Qwen/Qwen2-VL-2B-Instruct
773
+ "image_embeds": f"{base64_image_embedding}", # Required
774
+ "image_grid_thw": f"{base64_image_grid_thw}", # Required by Qwen/Qwen2-VL-2B-Instruct
775
},
764
- "uuid": image_url # Optional
776
+ "uuid": image_url, # Optional
777
}
778
model = "openbmb/MiniCPM-V-2_6"
767
- embeds = {
779
+ embeds = {
780
"type": "image_embeds",
781
"image_embeds": {
770
- "image_embeds": f"{base64_image_embedding}" , # Required
771
- "image_sizes": f"{base64_image_sizes}" # Required by openbmb/MiniCPM-V-2_6
782
+ "image_embeds": f"{base64_image_embedding}", # Required
783
+ "image_sizes": f"{base64_image_sizes}", # Required by openbmb/MiniCPM-V-2_6
784
},
773
- "uuid": image_url # Optional
785
+ "uuid": image_url, # Optional
786
}
787
chat_completion = client.chat.completions.create(
788
messages=[
777
- {"role": "system", "content": "You are a helpful assistant."},
778
- {"role": "user", "content": [
789
{
780
- "type": "text",
781
- "text": "What's in this image?",
790
+ "role": "system",
791
+ "content": "You are a helpful assistant.",
792
},
783
- embeds,
784
- ],
785
- },
786
- ],
793
+ {
794
+ "role": "user",
795
+ "content": [
796
+ {
797
+ "type": "text",
798
+ "text": "What's in this image?",
799
+ },
800
+ embeds,
801
+ ],
802
+ },
803
+ ],
804
model=model,
805
)
806
```
@@ -802,22 +819,22 @@ For Online Serving, you can also skip sending media if you expect cache hits wit
819
{
820
"type": "image_embeds",
821
"image_embeds": None,
805
- "uuid": image_uuid
822
+ "uuid": image_uuid,
823
},
824
825
# input_audio:
826
{
827
"type": "input_audio",
828
"input_audio": None,
812
- "uuid": audio_uuid
829
+ "uuid": audio_uuid,
830
},
831
832
# PIL Image:
833
{
834
"type": "image_pil",
818
- "image_pil": None
819
- "uuid": image_uuid
820
- }
835
+ "image_pil": None,
836
+ "uuid": image_uuid,
837
+ },
838
839
```
840
docs/features/reasoning_outputs.md
+31
-25
@@ -117,9 +117,11 @@ OpenAI Python client library does not officially support `reasoning_content` att
117
# For granite, add: `extra_body={"chat_template_kwargs": {"thinking": True}}`
118
# For Qwen3 series, if you want to disable thinking in reasoning mode, add:
119
# extra_body={"chat_template_kwargs": {"enable_thinking": False}}
120
- stream = client.chat.completions.create(model=model,
121
- messages=messages,
122
- stream=True)
120
+ stream = client.chat.completions.create(
121
+ model=model,
122
+ messages=messages,
123
+ stream=True,
124
+ )
125
126
print("client: Start streaming chat completions...")
127
printed_reasoning_content = False
@@ -159,27 +161,29 @@ The reasoning content is also available when both tool calling and the reasoning
161
162
client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
163
162
- tools = [{
163
- "type": "function",
164
- "function": {
165
- "name": "get_weather",
166
- "description": "Get the current weather in a given location",
167
- "parameters": {
168
- "type": "object",
169
- "properties": {
170
- "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
171
- "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
172
- },
173
- "required": ["location", "unit"]
174
- }
164
+ tools = [
165
+ {
166
+ "type": "function",
167
+ "function": {
168
+ "name": "get_weather",
169
+ "description": "Get the current weather in a given location",
170
+ "parameters": {
171
+ "type": "object",
172
+ "properties": {
173
+ "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
174
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
175
+ },
176
+ "required": ["location", "unit"],
177
+ }
178
+ },
179
}
176
- }]
180
+ ]
181
182
response = client.chat.completions.create(
183
model=client.models.list().data[0].id,
184
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
185
tools=tools,
182
- tool_choice="auto"
186
+ tool_choice="auto",
187
)
188
189
print(response)
@@ -225,7 +229,7 @@ You can add a new `ReasoningParser` similar to <gh-file:vllm/reasoning/deepseek_
229
previous_token_ids: Sequence[int],
230
current_token_ids: Sequence[int],
231
delta_token_ids: Sequence[int],
228
- ) -> Union[DeltaMessage, None]:
232
+ ) -> DeltaMessage | None:
233
"""
234
Instance method that should be implemented for extracting reasoning
235
from an incomplete response; for use when handling reasoning calls and
@@ -235,8 +239,10 @@ You can add a new `ReasoningParser` similar to <gh-file:vllm/reasoning/deepseek_
239
"""
240
241
def extract_reasoning_content(
238
- self, model_output: str, request: ChatCompletionRequest
239
- ) -> tuple[Optional[str], Optional[str]]:
242
+ self,
243
+ model_output: str,
244
+ request: ChatCompletionRequest | ResponsesRequest,
245
+ ) -> tuple[str | None, str | None]:
246
"""
247
Extract reasoning content from a complete model-generated string.
248
@@ -274,10 +280,10 @@ Additionally, to enable structured output, you'll need to create a new `Reasoner
280
281
@classmethod
282
def from_tokenizer(cls, tokenizer: PreTrainedTokenizer) -> Reasoner:
277
- return cls(start_token_id=tokenizer.encode(
278
- "<think>", add_special_tokens=False)[0],
279
- end_token_id=tokenizer.encode("</think>",
280
- add_special_tokens=False)[0])
283
+ return cls(
284
+ start_token_id=tokenizer.encode("<think>", add_special_tokens=False)[0],
285
+ end_token_id=tokenizer.encode("</think>", add_special_tokens=False)[0],
286
+ )
287
288
def is_reasoning_end(self, input_ids: list[int]) -> bool:
289
return self.end_token_id in input_ids
docs/features/tool_calling.md
+19
-18
@@ -27,27 +27,29 @@ Next, make a request that triggers the model to use the available tools:
27
return f"Getting the weather for {location} in {unit}..."
28
tool_functions = {"get_weather": get_weather}
29
30
- tools = [{
31
- "type": "function",
32
- "function": {
33
- "name": "get_weather",
34
- "description": "Get the current weather in a given location",
35
- "parameters": {
36
- "type": "object",
37
- "properties": {
38
- "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
39
- "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
30
+ tools = [
31
+ {
32
+ "type": "function",
33
+ "function": {
34
+ "name": "get_weather",
35
+ "description": "Get the current weather in a given location",
36
+ "parameters": {
37
+ "type": "object",
38
+ "properties": {
39
+ "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
40
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
41
+ },
42
+ "required": ["location", "unit"],
43
},
41
- "required": ["location", "unit"]
42
- }
43
- }
44
- }]
44
+ },
45
+ },
46
+ ]
47
48
response = client.chat.completions.create(
49
model=client.models.list().data[0].id,
50
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
51
tools=tools,
50
- tool_choice="auto"
52
+ tool_choice="auto",
53
)
54
55
tool_call = response.choices[0].message.tool_calls[0].function
@@ -402,8 +404,7 @@ Here is a summary of a plugin file:
404
405
# adjust request. e.g.: set skip special tokens
406
# to False for tool call output.
405
- def adjust_request(
406
- self, request: ChatCompletionRequest) -> ChatCompletionRequest:
407
+ def adjust_request(self, request: ChatCompletionRequest) -> ChatCompletionRequest:
408
return request
409
410
# implement the tool call parse for stream call
@@ -416,7 +417,7 @@ Here is a summary of a plugin file:
417
current_token_ids: Sequence[int],
418
delta_token_ids: Sequence[int],
419
request: ChatCompletionRequest,
419
- ) -> Union[DeltaMessage, None]:
420
+ ) -> DeltaMessage | None:
421
return delta
422
423
# implement the tool parse for non-stream call