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
[CI/Build] Improve Tensor Schema tests speed by avoid engine core initialization (#23357)
Signed-off-by: Isotr0py <mozf@mail2.sysu.edu.cn>
Isotr0py committed
Sep 1, 2025 at 13:52 UTC
ff0e59d83a287c3d749b02e8b6a43f31bc856753
7 files changed
+157
-115
.buildkite/test-pipeline.yaml
+1
-2
@@ -566,8 +566,7 @@ steps:
566
- tests/models/multimodal
567
commands:
568
- pip install git+https://github.com/TIGER-AI-Lab/Mantis.git
569
- - pytest -v -s models/multimodal/processing --ignore models/multimodal/processing/test_tensor_schema.py
570
- - pytest -v -s models/multimodal/processing/test_tensor_schema.py
569
+ - pytest -v -s models/multimodal/processing
570
571
- label: Multi-Modal Models Test (Standard)
572
mirror_hardwares: [amdexperimental]
tests/models/multimodal/processing/test_tensor_schema.py
+52
-80
@@ -1,30 +1,31 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import tempfile
4
from collections.abc import Iterable
5
+from contextlib import contextmanager
6
from functools import partial
7
from typing import Any, Union
6
-from unittest.mock import patch
8
9
import numpy as np
10
import pytest
11
+import torch.nn as nn
12
from mistral_common.protocol.instruct.messages import (ImageChunk, TextChunk,
13
UserMessage)
14
from mistral_common.protocol.instruct.request import ChatCompletionRequest
15
from PIL import Image
16
15
-from vllm.config import ModelConfig
16
-from vllm.engine.llm_engine import LLMEngine as V0LLMEngine
17
+from vllm.config import ModelConfig, VllmConfig, set_current_vllm_config
18
+from vllm.distributed import (cleanup_dist_env_and_memory,
19
+ init_distributed_environment,
20
+ initialize_model_parallel)
21
from vllm.inputs import InputProcessingContext
18
-from vllm.multimodal import (MULTIMODAL_REGISTRY, BatchedTensorInputs,
19
- MultiModalKwargs)
22
+from vllm.model_executor.model_loader.utils import set_default_torch_dtype
23
+from vllm.multimodal import MULTIMODAL_REGISTRY, BatchedTensorInputs
24
from vllm.multimodal.processing import BaseMultiModalProcessor
25
from vllm.multimodal.utils import group_mm_kwargs_by_modality
26
from vllm.transformers_utils.tokenizer import cached_tokenizer_from_config
23
-from vllm.utils import GiB_bytes, is_list_of, set_default_torch_num_threads
24
-from vllm.v1.core.kv_cache_utils import get_kv_cache_config
25
-from vllm.v1.engine.core import EngineCore as V1EngineCore
27
+from vllm.utils import is_list_of
28
27
-from ....conftest import VllmRunner
29
from ...registry import _MULTIMODAL_EXAMPLE_MODELS, HF_EXAMPLE_MODELS
30
from ...utils import dummy_hf_overrides
31
@@ -137,6 +138,27 @@ def create_batched_mm_kwargs(
138
return group_mm_kwargs_by_modality(items)
139
140
141
+@contextmanager
142
+def initialize_dummy_model(model_cls: nn.Module, model_config: ModelConfig):
143
+ temp_file = tempfile.mkstemp()[1]
144
+ init_distributed_environment(
145
+ world_size=1,
146
+ rank=0,
147
+ distributed_init_method=f"file://{temp_file}",
148
+ local_rank=0,
149
+ backend="nccl",
150
+ )
151
+ initialize_model_parallel(tensor_model_parallel_size=1)
152
+ vllm_config = VllmConfig(model_config=model_config)
153
+ with set_current_vllm_config(vllm_config=vllm_config):
154
+ with set_default_torch_dtype(model_config.dtype):
155
+ model = model_cls(vllm_config=vllm_config)
156
+ yield model
157
+
158
+ del model
159
+ cleanup_dist_env_and_memory()
160
+
161
+
162
def get_model_id_to_test(
163
model_arch_list: Iterable[str]) -> list[tuple[str, str]]:
164
filtered_results = []
@@ -155,8 +177,7 @@ def get_model_id_to_test(
177
@pytest.mark.parametrize(
178
"model_arch, model_id",
179
get_model_id_to_test(_MULTIMODAL_EXAMPLE_MODELS.keys()))
158
-def test_model_tensor_schema(model_arch: str, model_id: str,
159
- vllm_runner: type[VllmRunner], monkeypatch):
180
+def test_model_tensor_schema(model_arch: str, model_id: str):
181
if model_arch in ARCH_TO_SKIP:
182
pytest.skip(f"Skipping {model_arch} due to {ARCH_TO_SKIP[model_arch]}")
183
if model_id in REPO_ID_TO_SKIP:
@@ -177,14 +198,20 @@ def test_model_tensor_schema(model_arch: str, model_id: str,
198
tokenizer_mode=model_info.tokenizer_mode,
199
revision=model_info.revision,
200
trust_remote_code=model_info.trust_remote_code,
180
- hf_overrides=model_info.hf_overrides,
201
+ hf_overrides=hf_overrides_fn,
202
)
203
model_cls = MULTIMODAL_REGISTRY._get_model_cls(model_config)
204
factories = MULTIMODAL_REGISTRY._processor_factories[model_cls]
205
185
- if not any(
186
- hasattr(model_cls, f"_parse_and_validate_{m}_input")
187
- for m in ["image", "video", "audio"]):
206
+ inputs_parse_methods = []
207
+ for attr_name in dir(model_cls):
208
+ attr = getattr(model_cls, attr_name)
209
+ if hasattr(attr, "__annotations__"):
210
+ return_type = attr.__annotations__.get("return", None)
211
+ if return_type is not None and "Input" in str(return_type):
212
+ inputs_parse_methods.append(attr_name)
213
+
214
+ if not any(inputs_parse_methods):
215
pytest.skip(f"{model_arch} does not support tensor schema validation.")
216
217
ctx = InputProcessingContext(
@@ -197,68 +224,13 @@ def test_model_tensor_schema(model_arch: str, model_id: str,
224
modality: 3 if limit is None else limit
225
for modality, limit in supported_mm_limits.items()
226
}
200
-
201
- # Avoid calling model.forward()
202
- def _initialize_kv_caches_v0(self) -> None:
203
- self.cache_config.num_gpu_blocks = 0
204
- self.cache_config.num_cpu_blocks = 0
205
-
206
- def _initialize_kv_caches_v1(self, vllm_config):
207
- kv_cache_specs = self.model_executor.get_kv_cache_specs()
208
- scheduler_kv_cache_config = get_kv_cache_config(
209
- vllm_config,
210
- kv_cache_specs[0],
211
- 10 * GiB_bytes,
212
- )
213
-
214
- # gpu_blocks (> 0), cpu_blocks, scheduler_kv_cache_config
215
- return 1, 0, scheduler_kv_cache_config
216
-
217
- with (patch.object(V0LLMEngine, "_initialize_kv_caches",
218
- _initialize_kv_caches_v0),
219
- patch.object(V1EngineCore, "_initialize_kv_caches",
220
- _initialize_kv_caches_v1), monkeypatch.context() as m):
221
- m.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1")
222
- if model_info.v0_only:
223
- m.setenv("VLLM_USE_V1", "0")
224
-
225
- # TODO(Isotr0py): Can we avoid initializing engine?
226
- with (
227
- set_default_torch_num_threads(1),
228
- vllm_runner(
229
- model_id,
230
- tokenizer_name=model_info.tokenizer,
231
- tokenizer_mode=model_info.tokenizer_mode,
232
- revision=model_info.revision,
233
- trust_remote_code=model_info.trust_remote_code,
234
- max_model_len=model_info.max_model_len,
235
- load_format="dummy",
236
- hf_overrides=hf_overrides_fn,
237
- limit_mm_per_prompt=limit_mm_per_prompt,
238
- enforce_eager=True,
239
- ) as vllm_model,
240
- ):
241
- model_config = vllm_model.llm.llm_engine.model_config
242
- llm_engine = vllm_model.llm.llm_engine
243
-
244
- if hasattr(llm_engine, "processor"):
245
- # v1 processor
246
- mm_registry = llm_engine.processor.mm_registry
247
- else:
248
- # v0 input_preprocessor
249
- mm_registry = llm_engine.input_preprocessor.mm_registry
250
-
251
- processor = mm_registry.create_processor(model_config)
252
-
253
- def validate_model_input(model, modality: str,
254
- mm_kwargs: MultiModalKwargs):
255
- method_name = f"_parse_and_validate_{modality}_input"
256
- if hasattr(model, method_name):
257
- getattr(model, method_name)(**mm_kwargs)
258
-
259
- for modality, _, mm_kwargs in create_batched_mm_kwargs(
260
- model_config, processor):
261
- valid_func = partial(validate_model_input,
262
- modality=modality,
263
- mm_kwargs=mm_kwargs)
264
- vllm_model.apply_model(valid_func)
227
+ model_config.get_multimodal_config().limit_per_prompt = limit_mm_per_prompt
228
+ processor = factories.build_processor(ctx, cache=None)
229
+
230
+ with initialize_dummy_model(model_cls, model_config) as model:
231
+ for modality, _, mm_kwargs in create_batched_mm_kwargs(
232
+ model_config, processor):
233
+ for method_name in inputs_parse_methods:
234
+ print(f"Testing `{method_name}` with modality={modality} "
235
+ f"and mm_kwargs{list(mm_kwargs.keys())}")
236
+ getattr(model, method_name)(modality=modality, **mm_kwargs)
vllm/model_executor/models/granite_speech.py
+1
-1
@@ -549,7 +549,7 @@ class GraniteSpeechForConditionalGeneration(
549
550
raise ValueError("Only audio modality is supported")
551
552
- def __init__(self, *, vllm_config: VllmConfig, prefix: str):
552
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
553
super().__init__()
554
config = vllm_config.model_config.hf_config
555
quant_config = vllm_config.quant_config
vllm/model_executor/models/mllama.py
+2
-1
@@ -1371,7 +1371,8 @@ class MllamaForConditionalGeneration(nn.Module, SupportsMultiModal,
1371
output_tensor[i, :t.size(0)] = t
1372
return output_tensor
1373
1374
- def _parse_and_validate_image_input(self, **kwargs: object):
1374
+ def _parse_and_validate_image_input(
1375
+ self, **kwargs: object) -> Optional[MllamaImagePixelInputs]:
1376
# tensor with the same shape will be batched together by
1377
# MultiModalKwargs.batch, so pixel_values here can be:
1378
# - list[torch.Tensor]:
vllm/model_executor/models/ovis.py
+1
-1
@@ -209,7 +209,7 @@ class OvisImagePatchInputs(TypedDict):
209
`(batch_size * num_patches, patch_size_x * patch_size_y * num_channels)`
210
"""
211
212
- inducator_tokens: torch.Tensor
212
+ indicator_tokens: torch.Tensor
213
"""
214
Shape:
215
`(batch_size * (num_patches + 1))`
vllm/model_executor/models/ovis2_5.py
+98
-28
@@ -3,7 +3,7 @@
3
""" PyTorch Ovis model."""
4
from collections.abc import Iterable, Mapping
5
from functools import partial
6
-from typing import Optional, Union
6
+from typing import Literal, Optional, TypedDict, Union
7
8
import torch
9
import torch.nn as nn
@@ -50,6 +50,27 @@ IMAGE_PAD_TOKEN_ID_MAP = {
50
}
51
52
53
+class OvisVideoPatchInputs(TypedDict):
54
+ type: Literal["video_patches"]
55
+ flat_data: torch.Tensor
56
+ """
57
+ Shape:
58
+ `(batch_size * num_patches, patch_size_x * patch_size_y * num_channels)`
59
+ """
60
+
61
+ indicator_tokens: torch.Tensor
62
+ """
63
+ Shape:
64
+ `(batch_size * (num_patches + 1))`
65
+ """
66
+
67
+ patches_per_image: list[int]
68
+ """
69
+ List of number of total patches for each frame in the video.
70
+ This is used to restore the first two dimensions of `flat_data`.
71
+ """
72
+
73
+
74
def _ovis2_5_field_config():
75
return dict(pixel_values=MultiModalFieldConfig.batched("image"),
76
grids=MultiModalFieldConfig.batched("image"),
@@ -429,17 +450,11 @@ class Ovis2_5(nn.Module, SupportsMultiModal, SupportsPP):
450
self.make_empty_intermediate_tensors = (
451
self.get_language_model().make_empty_intermediate_tensors)
452
432
- def _parse_and_validate_visual_input(
433
- self, is_video,
434
- **kwargs: object) -> Optional[OvisImagePatchInputs]:
435
- if is_video:
436
- pixel_values = kwargs.pop("video_pixel_values", None)
437
- indicator_tokens = kwargs.pop("video_indicator_tokens", None)
438
- grids = kwargs.pop("video_grids", None)
439
- else:
440
- pixel_values = kwargs.pop("pixel_values", None)
441
- indicator_tokens = kwargs.pop("indicator_tokens", None)
442
- grids = kwargs.pop("grids", None)
453
+ def _parse_and_validate_image_input(
454
+ self, **kwargs: object) -> Optional[OvisImagePatchInputs]:
455
+ pixel_values = kwargs.pop("pixel_values", None)
456
+ indicator_tokens = kwargs.pop("indicator_tokens", None)
457
+ grids = kwargs.pop("grids", None)
458
if pixel_values is None and indicator_tokens is None:
459
return None
460
@@ -466,8 +481,40 @@ class Ovis2_5(nn.Module, SupportsMultiModal, SupportsPP):
481
482
raise AssertionError("This line should be unreachable.")
483
484
+ def _parse_and_validate_video_input(
485
+ self, **kwargs: object) -> Optional[OvisImagePatchInputs]:
486
+ pixel_values = kwargs.pop("video_pixel_values", None)
487
+ indicator_tokens = kwargs.pop("video_indicator_tokens", None)
488
+ grids = kwargs.pop("video_grids", None)
489
+ if pixel_values is None and indicator_tokens is None:
490
+ return None
491
+
492
+ if pixel_values is not None and indicator_tokens is not None:
493
+ if not isinstance(pixel_values, (torch.Tensor, list)):
494
+ raise ValueError("Incorrect type of pixel values. "
495
+ f"Got type: {type(pixel_values)}")
496
+
497
+ if not isinstance(indicator_tokens, (torch.Tensor, list)):
498
+ raise ValueError("Incorrect type of indicator_tokens. "
499
+ f"Got type: {type(indicator_tokens)}")
500
+
501
+ return OvisVideoPatchInputs(
502
+ type="video_patches",
503
+ flat_data=flatten_bn(flatten_bn(pixel_values), concat=True),
504
+ patches_per_image=[
505
+ x.shape[0] // (self.config.vit_config.hidden_stride**2)
506
+ for x in flatten_bn(pixel_values)
507
+ ],
508
+ indicator_tokens=flatten_bn(flatten_bn(indicator_tokens),
509
+ concat=True),
510
+ grids=flatten_bn(flatten_bn(grids), concat=True),
511
+ )
512
+
513
+ raise AssertionError("This line should be unreachable.")
514
+
515
def _process_image_input(
470
- self, image_input: OvisImagePatchInputs) -> MultiModalEmbeddings:
516
+ self, image_input: Union[OvisImagePatchInputs, OvisVideoPatchInputs]
517
+ ) -> MultiModalEmbeddings:
518
image_patches_flat = image_input["flat_data"]
519
patches_per_image = image_input["patches_per_image"]
520
indicator_tokens = image_input["indicator_tokens"]
@@ -500,21 +547,44 @@ class Ovis2_5(nn.Module, SupportsMultiModal, SupportsPP):
547
torch.cat(vision_embeddings_per_image, dim=0))
548
return tuple(vision_embeddings)
549
503
- def get_multimodal_embeddings(
504
- self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
505
- embeddings = []
506
-
507
- # NOTE: _parse_and_validate_visual_input has side-effects and pops
508
- # keys from kwargs. We process images first, then videos.
509
- image_input = self._parse_and_validate_visual_input(False, **kwargs)
510
- if image_input:
511
- embeddings.extend(self._process_image_input(image_input))
512
-
513
- video_input = self._parse_and_validate_visual_input(True, **kwargs)
514
- if video_input:
515
- embeddings.extend(self._process_image_input(video_input))
516
-
517
- return tuple(embeddings) if embeddings else None
550
+ def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
551
+ modalities = {}
552
+
553
+ # Preserve the order of modalities if there are multiple of them
554
+ # from the order of kwargs.
555
+ for input_key in kwargs:
556
+ if input_key in ("pixel_values", "indicator_tokens",
557
+ "grids") and "images" not in modalities:
558
+ modalities["images"] = self._parse_and_validate_image_input(
559
+ **kwargs)
560
+ if input_key in ("video_pixel_values", "video_indicator_tokens",
561
+ "video_grids") and "videos" not in modalities:
562
+ modalities["videos"] = self._parse_and_validate_video_input(
563
+ **kwargs)
564
+
565
+ return modalities
566
+
567
+ def get_multimodal_embeddings(self,
568
+ **kwargs: object) -> MultiModalEmbeddings:
569
+
570
+ modalities = self._parse_and_validate_multimodal_inputs(**kwargs)
571
+ if not modalities:
572
+ return []
573
+
574
+ multimodal_embeddings: tuple[torch.Tensor, ...] = ()
575
+ # NOTE: It is important to iterate over the keys in this dictionary
576
+ # to preserve the order of the modalities.
577
+ for modality in modalities:
578
+ if modality == "images":
579
+ image_input = modalities["images"]
580
+ vision_embeddings = self._process_image_input(image_input)
581
+ multimodal_embeddings += vision_embeddings
582
+ if modality == "videos":
583
+ video_input = modalities["videos"]
584
+ video_embeddings = self._process_image_input(video_input)
585
+ multimodal_embeddings += video_embeddings
586
+
587
+ return multimodal_embeddings
588
589
def get_input_embeddings(
590
self,
vllm/model_executor/models/phi4mm.py
+2
-2
@@ -1031,8 +1031,8 @@ class Phi4MMForCausalLM(nn.Module, SupportsLoRA, SupportsMultiModal):
1031
]
1032
return audio_embeds
1033
1034
- def _parse_and_validate_image_input(self,
1035
- **kwargs: object) -> Optional[dict]:
1034
+ def _parse_and_validate_image_input(
1035
+ self, **kwargs: object) -> Optional[Phi4MMImagePixelInputs]:
1036
input_image_embeds: NestedTensors = kwargs.get("input_image_embeds")
1037
if input_image_embeds is None:
1038
return None