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, 01:00 UTC
[Model] Support VLMs with transformers backend (#20543)
Signed-off-by: raushan <raushan@huggingface.co> Signed-off-by: Isotr0py <2037008807@qq.com> Signed-off-by: Isotr0py <mozf@mail2.sysu.edu.cn> Co-authored-by: Isotr0py <2037008807@qq.com> Co-authored-by: Isotr0py <mozf@mail2.sysu.edu.cn> Co-authored-by: Cyrus Leung <cyrus.tl.leung@gmail.com>
Raushan Turganbay committed
Jul 20, 2025 at 15:25 UTC
9499e26e2ae18826bcda99ae7e0883268cde03db
7 files changed
+625
-87
docs/models/supported_models.md
+6
-3
@@ -18,7 +18,7 @@ These models are what we list in [supported-text-models][supported-text-models]
18
19
### Transformers
20
21
-vLLM also supports model implementations that are available in Transformers. This does not currently work for all models, but most decoder language models are supported, and vision language model support is planned!
21
+vLLM also supports model implementations that are available in Transformers. This does not currently work for all models, but most decoder language models and common vision language models are supported! Vision-language models currently accept only image inputs, and require setting `--disable_mm_preprocessor_cache` when running. Support for video inputs and caching of multi-modal preprocessors will be added in future releases.
22
23
To check if the modeling backend is Transformers, you can simply do this:
24
@@ -28,7 +28,7 @@ llm = LLM(model=..., task="generate") # Name or path of your model
28
llm.apply_model(lambda model: print(type(model)))
29
```
30
31
-If it is `TransformersForCausalLM` then it means it's based on Transformers!
31
+If it is `TransformersForCausalLM` or `TransformersForMultimodalLM` then it means it's based on Transformers!
32
33
!!! tip
34
You can force the use of `TransformersForCausalLM` by setting `model_impl="transformers"` for [offline-inference](../serving/offline_inference.md) or `--model-impl transformers` for the [openai-compatible-server](../serving/openai_compatible_server.md).
@@ -36,6 +36,9 @@ If it is `TransformersForCausalLM` then it means it's based on Transformers!
36
!!! note
37
vLLM may not fully optimise the Transformers implementation so you may see degraded performance if comparing a native model to a Transformers model in vLLM.
38
39
+!!! note
40
+ In case of vision language models if you are loading with `dtype="auto"`, vLLM loads the whole model with config's `dtype` if it exists. In contrast the native Transformers will respect the `dtype` attribute of each backbone in the model. That might cause a slight difference in performance.
41
+
42
#### Custom models
43
44
If a model is neither supported natively by vLLM or Transformers, it can still be used in vLLM!
@@ -99,7 +102,7 @@ Here is what happens in the background when this model is loaded:
102
103
1. The config is loaded.
104
2. `MyModel` Python class is loaded from the `auto_map` in config, and we check that the model `is_backend_compatible()`.
102
-3. `MyModel` is loaded into `TransformersForCausalLM` (see <gh-file:vllm/model_executor/models/transformers.py>) which sets `self.config._attn_implementation = "vllm"` so that vLLM's attention layer is used.
105
+3. `MyModel` is loaded into `TransformersForCausalLM` or `TransformersForMultimodalLM` (see <gh-file:vllm/model_executor/models/transformers.py>) which sets `self.config._attn_implementation = "vllm"` so that vLLM's attention layer is used.
106
107
That's it!
108
tests/models/multimodal/generation/test_common.py
+75
@@ -35,6 +35,8 @@ if current_platform.is_rocm():
35
REQUIRES_V0_MODELS = [
36
# V1 Test: not enough KV cache space in C1.
37
"fuyu",
38
+ # V1 Test: Deadlock issue when processing mm_inputs
39
+ "llava-onevision-transformers",
40
]
41
42
# yapf: disable
@@ -170,6 +172,79 @@ VLM_TEST_SETTINGS = {
172
hf_output_post_proc=model_utils.ultravox_trunc_hf_output,
173
marks=[pytest.mark.core_model, pytest.mark.cpu_model],
174
),
175
+ #### Transformers fallback to test
176
+ ## To reduce test burden, we only test batching arbitrary image size
177
+ # Dynamic image length and number of patches
178
+ "llava-onevision-transformers": VLMTestInfo(
179
+ models=["llava-hf/llava-onevision-qwen2-0.5b-ov-hf"],
180
+ test_type=VLMTestType.IMAGE,
181
+ prompt_formatter=lambda vid_prompt: f"<|im_start|>user\n{vid_prompt}<|im_end|>\n<|im_start|>assistant\n", # noqa: E501
182
+ max_model_len=16384,
183
+ hf_model_kwargs=model_utils.llava_onevision_hf_model_kwargs("llava-hf/llava-onevision-qwen2-0.5b-ov-hf"), # noqa: E501
184
+ auto_cls=AutoModelForImageTextToText,
185
+ vllm_output_post_proc=model_utils.llava_onevision_vllm_to_hf_output,
186
+ image_size_factors=[(0.25, 0.5, 1.0)],
187
+ vllm_runner_kwargs={
188
+ "model_impl": "transformers",
189
+ "disable_mm_preprocessor_cache": True,
190
+ "enable_prefix_caching": False,
191
+ },
192
+ marks=[pytest.mark.core_model],
193
+ ),
194
+ # FIXME(Isotr0py): Enable this test after
195
+ # https://github.com/huggingface/transformers/pull/39470 released
196
+ # "idefics3-transformers": VLMTestInfo(
197
+ # models=["HuggingFaceTB/SmolVLM-256M-Instruct"],
198
+ # test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
199
+ # prompt_formatter=lambda img_prompt:f"<|begin_of_text|>User:{img_prompt}<end_of_utterance>\nAssistant:", # noqa: E501
200
+ # img_idx_to_prompt=lambda idx: "<image>",
201
+ # max_model_len=8192,
202
+ # max_num_seqs=2,
203
+ # auto_cls=AutoModelForImageTextToText,
204
+ # hf_output_post_proc=model_utils.idefics3_trunc_hf_output,
205
+ # image_size_factors=[(0.25, 0.5, 1.0)],
206
+ # vllm_runner_kwargs={
207
+ # "model_impl": "transformers",
208
+ # "disable_mm_preprocessor_cache": True,
209
+ # "enable_prefix_caching": False,
210
+ # },
211
+ # marks=[pytest.mark.core_model],
212
+ # ),
213
+ # Pixel values from processor are not 4D or 5D arrays
214
+ "qwen2_5_vl-transformers": VLMTestInfo(
215
+ models=["Qwen/Qwen2.5-VL-3B-Instruct"],
216
+ test_type=VLMTestType.IMAGE,
217
+ prompt_formatter=lambda img_prompt: f"<|im_start|>User\n{img_prompt}<|im_end|>\n<|im_start|>assistant\n", # noqa: E501
218
+ img_idx_to_prompt=lambda idx: "<|vision_start|><|image_pad|><|vision_end|>", # noqa: E501
219
+ max_model_len=4096,
220
+ max_num_seqs=2,
221
+ auto_cls=AutoModelForImageTextToText,
222
+ vllm_output_post_proc=model_utils.qwen2_vllm_to_hf_output,
223
+ image_size_factors=[(0.25, 0.2, 0.15)],
224
+ vllm_runner_kwargs={
225
+ "model_impl": "transformers",
226
+ "disable_mm_preprocessor_cache": True,
227
+ "enable_prefix_caching": False,
228
+ },
229
+ marks=[large_gpu_mark(min_gb=32)],
230
+ ),
231
+ # Check "auto" with fallback to transformers
232
+ "internvl-transformers": VLMTestInfo(
233
+ models=["OpenGVLab/InternVL3-1B-hf"],
234
+ test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
235
+ prompt_formatter=lambda img_prompt: f"<|im_start|>User\n{img_prompt}<|im_end|>\n<|im_start|>Assistant\n", # noqa: E501
236
+ img_idx_to_prompt=lambda idx: "<IMG_CONTEXT>",
237
+ max_model_len=4096,
238
+ use_tokenizer_eos=True,
239
+ image_size_factors=[(0.25, 0.5, 1.0)],
240
+ vllm_runner_kwargs={
241
+ "model_impl": "auto",
242
+ "disable_mm_preprocessor_cache": True,
243
+ "enable_prefix_caching": False,
244
+ },
245
+ auto_cls=AutoModelForImageTextToText,
246
+ marks=[pytest.mark.core_model],
247
+ ),
248
#### Extended model tests
249
"aria": VLMTestInfo(
250
models=["rhymes-ai/Aria"],
tests/models/registry.py
+1
@@ -499,6 +499,7 @@ _SPECULATIVE_DECODING_EXAMPLE_MODELS = {
499
500
_TRANSFORMERS_MODELS = {
501
"TransformersForCausalLM": _HfExamplesInfo("ArthurZ/Ilama-3.2-1B", trust_remote_code=True), # noqa: E501
502
+ "TransformersForMultimodalLM": _HfExamplesInfo("OpenGVLab/InternVL3-1B-hf"),
503
}
504
505
_EXAMPLE_MODELS = {
vllm/config.py
+30
-9
@@ -562,6 +562,10 @@ class ModelConfig:
562
563
self.task = "embed"
564
565
+ model_info, arch = self.registry.inspect_model_cls(self.architectures)
566
+ self._model_info = model_info
567
+ self._architecture = arch
568
+
569
all_supported_tasks = self._get_supported_tasks(self.task)
570
logger.debug("Tasks supported by runner type: %s", all_supported_tasks)
571
supported_runner_types = self._get_supported_runner_types(
@@ -587,10 +591,6 @@ class ModelConfig:
591
else:
592
self.truncation_side = "right"
593
590
- model_info, arch = self.registry.inspect_model_cls(self.architectures)
591
- self._model_info = model_info
592
- self._architecture = arch
593
-
594
self.pooler_config = self._init_pooler_config()
595
596
self.dtype = _get_and_verify_dtype(
@@ -674,6 +674,16 @@ class ModelConfig:
674
"max_model_len must be an integer after __post_init__.")
675
return self
676
677
+ def _get_transformers_backend_cls(self) -> str:
678
+ """Determine which Transformers backend class will be used if
679
+ `model_impl` is set to `transformers` or `auto`."""
680
+ if self.hf_config != self.hf_text_config:
681
+ # If 'hf_text_config' is the same as 'hf_config'. If not, it is
682
+ # probably a composite config, i.e. multimodal
683
+ return "TransformersForMultimodalLM"
684
+ else:
685
+ return "TransformersForCausalLM"
686
+
687
@property
688
def registry(self):
689
return me_models.ModelRegistry
@@ -681,7 +691,19 @@ class ModelConfig:
691
@property
692
def architectures(self) -> list[str]:
693
# architectures in the model config.
684
- return getattr(self.hf_config, "architectures", [])
694
+ architectures = getattr(self.hf_config, "architectures", [])
695
+ # The registry assumes that it can always inspect the vLLM model class
696
+ # for a given architecture. This assumption breaks down for the
697
+ # Transformers backend, which may use a different class depending on
698
+ # the model type. To work around this, we add the correct Transformers
699
+ # backend class to the architectures list. We must do this here because
700
+ # we need access to the `hf_config` to determine the backend class.
701
+ transformers_backend_cls = self._get_transformers_backend_cls()
702
+ if (self.model_impl != ModelImpl.VLLM.value
703
+ and all(arch != transformers_backend_cls
704
+ for arch in architectures)):
705
+ architectures.append(transformers_backend_cls)
706
+ return architectures
707
708
@property
709
def architecture(self) -> str:
@@ -827,10 +849,9 @@ class ModelConfig:
849
("EmbeddingModel", "embed"),
850
("RewardModel", "reward"),
851
]
830
- _, arch = self.registry.inspect_model_cls(architectures)
852
853
for suffix, pref_task in suffix_to_preferred_task:
833
- if arch.endswith(suffix):
854
+ if self.architecture.endswith(suffix):
855
return pref_task
856
857
return "embed"
@@ -944,10 +965,10 @@ class ModelConfig:
965
("EmbeddingModel", "pooling"),
966
("RewardModel", "pooling"),
967
]
947
- _, arch = self.registry.inspect_model_cls(self.architectures)
968
969
for suffix, pref_runner in suffix_to_preferred_runner:
950
- if arch.endswith(suffix) and pref_runner in supported_runner_types:
970
+ if self.architecture.endswith(
971
+ suffix) and pref_runner in supported_runner_types:
972
return pref_runner
973
974
if "generate" in supported_runner_types:
vllm/model_executor/model_loader/utils.py
+26
-23
@@ -25,6 +25,7 @@ from vllm.model_executor.models.adapters import (as_embedding_model,
25
as_reward_model,
26
as_seq_cls_model)
27
from vllm.model_executor.models.interfaces import SupportsQuant
28
+from vllm.model_executor.models.registry import _TRANSFORMERS_MODELS
29
from vllm.utils import is_pin_memory_available
30
31
logger = init_logger(__name__)
@@ -169,9 +170,22 @@ def device_loading_context(module: torch.nn.Module,
170
171
def resolve_transformers_arch(model_config: ModelConfig,
172
architectures: list[str]):
173
+ if model_config.model_impl == ModelImpl.VLLM:
174
+ raise ValueError(
175
+ "Attempting to resolve architecture from the Transformers library "
176
+ "but the model implementation is set to vLLM. This should never "
177
+ "happen.")
178
+
179
for i, arch in enumerate(architectures):
173
- if arch == "TransformersForCausalLM":
180
+ if arch in _TRANSFORMERS_MODELS:
181
continue
182
+
183
+ if model_config.model_impl == ModelImpl.AUTO:
184
+ logger.warning(
185
+ "%s has no vLLM implementation, falling back to Transformers "
186
+ "implementation. Some features may not be supported and "
187
+ "performance may not be optimal.", arch)
188
+
189
auto_map: dict[str, str] = getattr(model_config.hf_config, "auto_map",
190
None) or dict()
191
# Make sure that config class is always initialized before model class,
@@ -199,25 +213,13 @@ def resolve_transformers_arch(model_config: ModelConfig,
213
"not present in the model config's 'auto_map' (relevant "
214
"if the model is custom).")
215
model_module = auto_modules["AutoModel"]
202
- # TODO(Isotr0py): Further clean up these raises.
203
- # perhaps handled them in _ModelRegistry._raise_for_unsupported?
204
- if model_config.model_impl == ModelImpl.TRANSFORMERS:
205
- if not model_module.is_backend_compatible():
206
- raise ValueError(
207
- f"The Transformers implementation of {arch} is not "
208
- "compatible with vLLM.")
209
- architectures[i] = "TransformersForCausalLM"
210
- if model_config.model_impl == ModelImpl.AUTO:
211
- if not model_module.is_backend_compatible():
212
- raise ValueError(
213
- f"{arch} has no vLLM implementation and the Transformers "
214
- "implementation is not compatible with vLLM. Try setting "
215
- "VLLM_USE_V1=0.")
216
- logger.warning(
217
- "%s has no vLLM implementation, falling back to Transformers "
218
- "implementation. Some features may not be supported and "
219
- "performance may not be optimal.", arch)
220
- architectures[i] = "TransformersForCausalLM"
216
+
217
+ if not model_module.is_backend_compatible():
218
+ raise ValueError(
219
+ f"The Transformers implementation of '{arch}' is not "
220
+ "compatible with vLLM.")
221
+
222
+ architectures[i] = model_config._get_transformers_backend_cls()
223
return architectures
224
225
@@ -237,8 +239,9 @@ def get_model_architecture(
239
]
240
241
vllm_supported_archs = ModelRegistry.get_supported_archs()
240
- vllm_not_supported = not any(arch in vllm_supported_archs
241
- for arch in architectures)
242
+ is_supported = lambda arch: (arch in vllm_supported_archs and arch not in
243
+ _TRANSFORMERS_MODELS)
244
+ vllm_not_supported = not any(is_supported(arch) for arch in architectures)
245
246
if vllm_not_supported:
247
# try automatic conversion in adapters.py
@@ -259,7 +262,7 @@ def get_model_architecture(
262
break
263
264
if (model_config.model_impl == ModelImpl.TRANSFORMERS or
262
- model_config.model_impl != ModelImpl.VLLM and vllm_not_supported):
265
+ model_config.model_impl == ModelImpl.AUTO and vllm_not_supported):
266
architectures = resolve_transformers_arch(model_config, architectures)
267
logger.debug_once("Resolve transformers arch %s", str(architectures))
268
elif (model_config.quantization is not None
vllm/model_executor/models/registry.py
+9
-3
@@ -253,6 +253,7 @@ _SPECULATIVE_DECODING_MODELS = {
253
}
254
255
_TRANSFORMERS_MODELS = {
256
+ "TransformersForMultimodalLM": ("transformers", "TransformersForMultimodalLM"), # noqa: E501
257
"TransformersForCausalLM": ("transformers", "TransformersForCausalLM"),
258
}
259
# yapf: enable
@@ -504,9 +505,14 @@ class _ModelRegistry:
505
if causal_lm_arch in self.models:
506
normalized_arch.append(arch)
507
507
- # make sure Transformers backend is put at the last as a fallback
508
- if len(normalized_arch) != len(architectures):
509
- normalized_arch.append("TransformersForCausalLM")
508
+ # NOTE(Isotr0py): Be careful of architectures' order!
509
+ # Make sure Transformers backend architecture is at the end of the
510
+ # list, otherwise pooling models automatic conversion will fail!
511
+ for arch in normalized_arch:
512
+ if arch.startswith("TransformersFor"):
513
+ normalized_arch.remove(arch)
514
+ normalized_arch.append(arch)
515
+
516
return normalized_arch
517
518
def inspect_model_cls(
vllm/model_executor/models/transformers.py
+478
-49
@@ -15,8 +15,8 @@
15
# See the License for the specific language governing permissions and
16
# limitations under the License.
17
"""Wrapper around `transformers` models"""
18
-from collections.abc import Iterable
19
-from contextlib import nullcontext
18
+from collections.abc import Iterable, Mapping
19
+from contextlib import contextmanager, nullcontext
20
from typing import Literal, Optional, Union
21
22
import regex as re
@@ -41,11 +41,21 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
41
ParallelLMHead, VocabParallelEmbedding)
42
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
43
from vllm.model_executor.sampling_metadata import SamplingMetadata
44
+from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalKwargs
45
+from vllm.multimodal.inputs import (MultiModalDataDict, MultiModalFieldConfig,
46
+ MultiModalInputs, PlaceholderRange)
47
+from vllm.multimodal.parse import ImageProcessorItems, MultiModalDataItems
48
+from vllm.multimodal.processing import (BaseMultiModalProcessor,
49
+ BaseProcessingInfo)
50
+from vllm.multimodal.profiling import BaseDummyInputsBuilder
51
from vllm.sequence import IntermediateTensors
52
+from vllm.transformers_utils.processor import cached_get_processor
53
+from vllm.utils import is_list_of
54
46
-from .interfaces import SupportsLoRA, SupportsPP, SupportsQuant
55
+from .interfaces import (SupportsLoRA, SupportsMultiModal, SupportsPP,
56
+ SupportsQuant)
57
from .utils import (AutoWeightsLoader, PPMissingLayer, WeightsMapper,
48
- is_pp_missing_parameter,
58
+ flatten_bn, is_pp_missing_parameter,
59
make_empty_intermediate_tensors_factory, maybe_prefix)
60
61
logger = init_logger(__name__)
@@ -112,6 +122,269 @@ def replace_linear_class(
122
)
123
124
125
+# Copied from `accelerate`
126
+@contextmanager
127
+def init_on_device_without_buffers(device: torch.device):
128
+ """
129
+ A context manager under which models are initialized with all
130
+ parameters on the specified device. However buffers are not
131
+ initialized on specified device.
132
+
133
+ Args:
134
+ device (`torch.device`):
135
+ Device to initialize all parameters on.
136
+ """
137
+
138
+ old_register_parameter = nn.Module.register_parameter
139
+
140
+ def register_empty_parameter(module, name, param):
141
+ old_register_parameter(module, name, param)
142
+ if param is not None:
143
+ param_cls = type(module._parameters[name])
144
+ kwargs = module._parameters[name].__dict__
145
+ kwargs["requires_grad"] = param.requires_grad
146
+ module._parameters[name] = param_cls(
147
+ module._parameters[name].to(device), **kwargs)
148
+
149
+ tensor_constructors_to_patch = {}
150
+
151
+ def patch_tensor_constructor(fn):
152
+
153
+ def wrapper(*args, **kwargs):
154
+ kwargs["device"] = device
155
+ return fn(*args, **kwargs)
156
+
157
+ return wrapper
158
+
159
+ try:
160
+ nn.Module.register_parameter = register_empty_parameter
161
+ for torch_function_name in tensor_constructors_to_patch:
162
+ setattr(
163
+ torch, torch_function_name,
164
+ patch_tensor_constructor(getattr(torch, torch_function_name)))
165
+ yield
166
+ finally:
167
+ nn.Module.register_parameter = old_register_parameter
168
+ for torch_function_name, old_torch_function in (
169
+ tensor_constructors_to_patch.items()):
170
+ setattr(torch, torch_function_name, old_torch_function)
171
+
172
+
173
+class MultiModalProcessingInfo(BaseProcessingInfo):
174
+
175
+ def get_hf_config(self):
176
+ return self.ctx.model_config.hf_config
177
+
178
+ def get_supported_mm_limits(self):
179
+ return {"image": None}
180
+
181
+ def get_mm_max_tokens_per_item(self, seq_len, mm_counts):
182
+ return {"image": self.get_max_image_tokens()}
183
+
184
+ def get_max_image_tokens(self) -> int:
185
+ width, height = self.get_max_image_size()
186
+ processor = self.get_hf_processor()
187
+ mm_processor_kwargs = self.ctx.model_config.mm_processor_kwargs or {}
188
+ mm_tokens = processor._get_num_multimodal_tokens(
189
+ image_sizes=([height, width], ), **mm_processor_kwargs)
190
+ image_tokens = mm_tokens["num_image_tokens"][0]
191
+ return image_tokens
192
+
193
+ def get_hf_processor(self):
194
+ processor = cached_get_processor(self.ctx.model_config.model)
195
+ return processor
196
+
197
+ def get_max_image_size(self):
198
+ return 10_000, 10_000 # hardcode for arbitrary very large size
199
+
200
+
201
+class MultiModalDummyInputsBuilder(
202
+ BaseDummyInputsBuilder[MultiModalProcessingInfo]):
203
+
204
+ def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
205
+ num_images = mm_counts.get("image", 0)
206
+
207
+ processor = self.info.get_hf_processor()
208
+ if "gemma3" in processor.__class__.__name__.lower():
209
+ image_token = processor.boi_token
210
+ else:
211
+ image_token = getattr(processor, "image_token", "")
212
+ return image_token * num_images
213
+
214
+ def get_dummy_mm_data(
215
+ self,
216
+ seq_len: int,
217
+ mm_counts: Mapping[str, int],
218
+ ) -> MultiModalDataDict:
219
+ num_images = mm_counts.get("image", 0)
220
+
221
+ target_width, target_height = self.info.get_max_image_size()
222
+
223
+ return {
224
+ "image":
225
+ self._get_dummy_images(width=target_width,
226
+ height=target_height,
227
+ num_images=num_images),
228
+ }
229
+
230
+
231
+class MultiModalProcessor(BaseMultiModalProcessor[MultiModalProcessingInfo]):
232
+
233
+ def _get_prompt_updates(
234
+ self,
235
+ mm_items: MultiModalDataItems,
236
+ hf_processor_mm_kwargs: Mapping[str, object],
237
+ out_mm_kwargs: MultiModalKwargs,
238
+ ):
239
+ """
240
+ Given the original multi-modal items for this modality
241
+ and HF-processed data, output the updates to perform.
242
+
243
+ The information returned by this method is used to update token inputs
244
+ which bypass the HF processor. It is also used to update the output of
245
+ HF processor if the HF process does not apply prompt updates to text
246
+ inputs.
247
+
248
+ Moreover, this information is critical to determine the token positions
249
+ in order to construct :class:`~vllm-multimodal.input.PlaceholderRange`
250
+ for each multi-modal item.
251
+ """
252
+ return None
253
+
254
+ def _get_mm_fields_config(
255
+ self,
256
+ hf_inputs,
257
+ hf_processor_mm_kwargs,
258
+ num_image_patches: torch.Tensor = None,
259
+ ):
260
+ # HF Processors always return a mask but vLLM doesn't need it
261
+ hf_inputs.pop("attention_mask", None)
262
+ mm_fields = {
263
+ key: MultiModalFieldConfig.flat_from_sizes("image",
264
+ num_image_patches)
265
+ for key in hf_inputs
266
+ }
267
+ mm_fields["image_embeds"] = MultiModalFieldConfig.flat_from_sizes(
268
+ "image", num_image_patches)
269
+ mm_fields["num_image_patches"] = MultiModalFieldConfig.batched("image")
270
+ return mm_fields
271
+
272
+ def _apply_hf_processor_text_mm(
273
+ self,
274
+ prompt_text: str,
275
+ mm_items: MultiModalDataItems,
276
+ hf_processor_mm_kwargs: Mapping[str, object],
277
+ tokenization_kwargs: Mapping[str, object],
278
+ ):
279
+ """
280
+ Apply the HF processor on the prompt text and multi-modal data
281
+ together.
282
+
283
+ In addition, return whether prompt replacements have been applied.
284
+ """
285
+ processor_data, passthrough_data = self._get_hf_mm_data(mm_items)
286
+ processor_data["return_mm_token_type_ids"] = True
287
+
288
+ processed_data = self._call_hf_processor(
289
+ prompt=prompt_text,
290
+ mm_data=processor_data,
291
+ mm_kwargs=hf_processor_mm_kwargs,
292
+ tok_kwargs=tokenization_kwargs,
293
+ )
294
+ processed_data.update(passthrough_data)
295
+
296
+ prompt_ids, = processed_data.pop("input_ids").tolist()
297
+ mm_token_type_ids = processed_data.pop(
298
+ "mm_token_type_ids"
299
+ ) if "mm_token_type_ids" in processed_data else processed_data.pop(
300
+ "token_type_ids") # for gemma3 only
301
+
302
+ return prompt_ids, processed_data, mm_token_type_ids
303
+
304
+ def apply(
305
+ self,
306
+ prompt: Union[str, list[int]],
307
+ mm_data: MultiModalDataDict,
308
+ hf_processor_mm_kwargs: Mapping[str, object],
309
+ tokenization_kwargs: Optional[Mapping[str, object]] = None,
310
+ return_mm_hashes: bool = False,
311
+ ) -> MultiModalInputs:
312
+ """
313
+ Process multi-modal inputs to be used in vLLM.
314
+
315
+ Apply HF Processor on prompt text and multi-modal data together,
316
+ outputting token IDs and processed tensors.
317
+ """
318
+ if return_mm_hashes:
319
+ raise ValueError(
320
+ "TransformersForMultimodalLM doesn't support mm hashing yet! "
321
+ "Probably you didn't set `disable_mm_preprocessor_cache=True`")
322
+
323
+ if tokenization_kwargs is None:
324
+ tokenization_kwargs = {}
325
+
326
+ mm_items = self._to_mm_items(mm_data)
327
+ hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
328
+
329
+ (prompt_ids, processed_data,
330
+ mm_token_type_ids) = self._apply_hf_processor_text_mm(
331
+ prompt_text=prompt,
332
+ mm_items=mm_items,
333
+ hf_processor_mm_kwargs=hf_processor_mm_kwargs,
334
+ tokenization_kwargs=tokenization_kwargs,
335
+ )
336
+
337
+ # HF processor will return `mm_token_type_ids` from which
338
+ # we can infer mm_placeholders. Until then hardcode to make code run
339
+ # Below tested on Llava. Prompts and `mm_token_type_ids` are always bs=1
340
+ mm_positions = torch.where(mm_token_type_ids == 1)[1]
341
+ images = mm_items.get_items("image", ImageProcessorItems)
342
+ mm_processor_kwargs = (self.info.ctx.model_config.mm_processor_kwargs
343
+ or {})
344
+ image_sizes = []
345
+ for item_idx in range(len(images)):
346
+ image_size = images.get_image_size(item_idx)
347
+ image_sizes.append((image_size.height, image_size.width))
348
+
349
+ mm_tokens_per_modality = hf_processor._get_num_multimodal_tokens(
350
+ image_sizes=image_sizes, **mm_processor_kwargs)
351
+
352
+ mm_placeholders = {}
353
+ split_sizes = mm_tokens_per_modality["num_image_tokens"]
354
+ if split_sizes:
355
+ chunked_mm_positions = torch.split(mm_positions, split_sizes)
356
+ mm_tokens = torch.tensor(prompt_ids)[mm_token_type_ids[0].bool()]
357
+ chunked_mm_tokens = torch.split(mm_tokens, split_sizes)
358
+ ranges = [
359
+ PlaceholderRange(
360
+ offset=positions[0].item(),
361
+ length=positions.shape[0],
362
+ is_embed=(mm_tokens == hf_processor.image_token_id).bool())
363
+ for positions, mm_tokens in zip(chunked_mm_positions,
364
+ chunked_mm_tokens)
365
+ ]
366
+ mm_placeholders = {"image": ranges}
367
+
368
+ num_image_patches = torch.tensor(
369
+ mm_tokens_per_modality["num_image_patches"]
370
+ ) if "num_image_patches" in mm_tokens_per_modality else None
371
+ processed_data['num_image_patches'] = num_image_patches
372
+ mm_kwargs = MultiModalKwargs.from_hf_inputs(
373
+ processed_data,
374
+ self._get_mm_fields_config(processed_data, hf_processor_mm_kwargs,
375
+ num_image_patches),
376
+ )
377
+
378
+ return MultiModalInputs(
379
+ type="multimodal",
380
+ prompt=prompt,
381
+ prompt_token_ids=prompt_ids,
382
+ mm_kwargs=mm_kwargs,
383
+ mm_hashes=None,
384
+ mm_placeholders=mm_placeholders,
385
+ )
386
+
387
+
388
class ConfigOverride:
389
"""Context manager to temporarily override config attributes."""
390
@@ -153,6 +426,7 @@ class TransformersModel(nn.Module):
426
quant_config: QuantizationConfig = vllm_config.quant_config
427
428
self.config = config
429
+ self.text_config = config.get_text_config()
430
self.cache_config = cache_config
431
self.device_config = device_config
432
self.model_config = model_config
@@ -173,14 +447,16 @@ class TransformersModel(nn.Module):
447
config_override = ConfigOverride(
448
config, sliding_window=config.interleaved_sliding_window)
449
176
- # Use meta device to delay allocating GPU tensors
177
- with torch.device("meta"), config_override:
450
+ # Set correct attn and init on "meta" to delay allocating GPU tensors
451
+ # TODO: @raushan, use the public `model.set_attn_implementation()`
452
+ # method after v4.54.0 is released
453
+ self.text_config._attn_implementation = "vllm"
454
+ with init_on_device_without_buffers("meta"), config_override:
455
# FIXME(Isotr0py): We need to refactor this part in the future to
456
# avoid registering an extra model layer, otherwise we will need a
457
# weights mapper to rename weights.
458
self.model: PreTrainedModel = AutoModel.from_config(
459
config,
183
- attn_implementation="vllm",
460
torch_dtype=model_config.dtype,
461
trust_remote_code=model_config.trust_remote_code,
462
)
@@ -189,27 +465,25 @@ class TransformersModel(nn.Module):
465
self.tensor_parallel()
466
467
# Input embeddings
468
+ text_config = config.get_text_config()
469
if not isinstance(self.model.get_input_embeddings(), PPMissingLayer):
470
self.model.set_input_embeddings(
471
VocabParallelEmbedding(
195
- config.vocab_size,
196
- config.hidden_size,
197
- org_num_embeddings=config.vocab_size,
472
+ text_config.vocab_size,
473
+ text_config.hidden_size,
474
+ org_num_embeddings=text_config.vocab_size,
475
quant_config=quant_config,
476
))
477
478
# Attention layers
479
self.attention_instances = self.create_attention_instances()
480
204
- # Initialize buffers (e.g. rotary embedding inverse frequency)
205
- self.init_buffers(self.model)
206
-
481
# Initialize any parameters that have not had their modules replaced
482
self.init_parameters(self.model)
483
484
self.make_empty_intermediate_tensors = (
485
make_empty_intermediate_tensors_factory(["hidden_states"],
212
- config.hidden_size))
486
+ text_config.hidden_size))
487
488
def pipeline_parallel(self):
489
"""
@@ -240,14 +514,15 @@ class TransformersModel(nn.Module):
514
515
# Layers before module list
516
for name in pp_plan[:module_list_idx]:
243
- if self.pp_group.is_first_rank or (self.config.tie_word_embeddings
244
- and self.pp_group.is_last_rank):
517
+ if self.pp_group.is_first_rank or (
518
+ self.text_config.tie_word_embeddings
519
+ and self.pp_group.is_last_rank):
520
continue
521
setattr(self.model, name, PPMissingLayer())
522
523
# Module list
249
- start_layer, end_layer = get_pp_indices(self.config.num_hidden_layers,
250
- self.pp_rank, self.pp_size)
524
+ start_layer, end_layer = get_pp_indices(
525
+ self.text_config.num_hidden_layers, self.pp_rank, self.pp_size)
526
layers_name = pp_plan[module_list_idx]
527
layers = getattr(self.model, layers_name)
528
for i in range(len(layers)):
@@ -298,7 +573,7 @@ class TransformersModel(nn.Module):
573
self.parallel_config)
574
head_size = self.model_config.get_head_size()
575
num_kv_heads = self.model_config.get_num_kv_heads(self.parallel_config)
301
- start, end = get_pp_indices(self.config.num_hidden_layers,
576
+ start, end = get_pp_indices(self.text_config.num_hidden_layers,
577
self.pp_rank, self.pp_size)
578
579
attention_instances = {}
@@ -323,35 +598,6 @@ class TransformersModel(nn.Module):
598
prefix=f"{i}.attn")
599
return attention_instances
600
326
- def init_buffers(self, module: nn.Module):
327
- """
328
- If a `buffer` is on the `meta` device, then its parent
329
- `module` is the original module created by:
330
-
331
- ```python
332
- with torch.device("meta"):
333
- self.model: PreTrainedModel = AutoModel.from_config(...)
334
- ```
335
-
336
- This means that:
337
- - `type(module)` is a class from `transformers`
338
- - This class is constructed using a `PretrainedConfig`
339
- """
340
- for name, buffer in module.named_buffers(recurse=False):
341
- if buffer.device == torch.device("meta"):
342
- if module == self.model:
343
- logger.warning(
344
- "To initialize buffers correctly, we instantiate the "
345
- "parent module and and extract the value of the "
346
- "buffer from it. In this case, the parent module is "
347
- "the base model. Instantiating the entire model here "
348
- "risks GPU OOM. Could this buffer be moved to a child "
349
- "module?")
350
- new_buffer = getattr(type(module)(self.config), name)
351
- setattr(module, name, new_buffer)
352
- for child in module.children():
353
- self.init_buffers(child)
354
-
601
def init_parameters(self, module: nn.Module):
602
"""
603
If a `parameter` is on the `meta` device, then its parent
@@ -366,6 +612,7 @@ class TransformersModel(nn.Module):
612
if param.device == torch.device("meta"):
613
new_param = nn.Parameter(
614
torch.empty_like(param.data,
615
+ dtype=self.model_config.dtype,
616
device=self.device_config.device))
617
setattr(module, name, new_param)
618
for child in module.children():
@@ -391,11 +638,16 @@ class TransformersModel(nn.Module):
638
if inputs_embeds is not None:
639
inputs_embeds = inputs_embeds[None, ...]
640
641
+ if self.model_config.uses_mrope:
642
+ position_ids = positions[:, None]
643
+ else:
644
+ position_ids = positions[None, ...]
645
+
646
hidden_states = self.model(
647
input_ids=input_ids,
648
inputs_embeds=inputs_embeds,
649
use_cache=False,
398
- position_ids=positions[None, ...],
650
+ position_ids=position_ids,
651
attention_instances=self.attention_instances,
652
return_dict=False)[0][0, ...] # we remove batch dimension for now
653
@@ -507,3 +759,180 @@ class TransformersForCausalLM(nn.Module, SupportsQuant, SupportsLoRA,
759
if self.config.tie_word_embeddings else None),
760
)
761
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
762
+
763
+
764
+@MULTIMODAL_REGISTRY.register_processor(
765
+ MultiModalProcessor,
766
+ info=MultiModalProcessingInfo,
767
+ dummy_inputs=MultiModalDummyInputsBuilder)
768
+class TransformersForMultimodalLM(nn.Module, SupportsQuant, SupportsLoRA,
769
+ SupportsPP, SupportsMultiModal):
770
+ embedding_padding_modules = ["lm_head"]
771
+ embedding_modules = ["embed_tokens"]
772
+
773
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
774
+ super().__init__()
775
+ config: PretrainedConfig = vllm_config.model_config.hf_config
776
+ quant_config: QuantizationConfig = vllm_config.quant_config
777
+
778
+ self.config = config
779
+ self.dtype = vllm_config.model_config.dtype
780
+
781
+ self.model = TransformersModel(vllm_config=vllm_config, prefix=prefix)
782
+ text_config = config.get_text_config()
783
+
784
+ if get_pp_group().is_last_rank:
785
+ self.unpadded_vocab_size = text_config.vocab_size
786
+ self.lm_head = ParallelLMHead(
787
+ text_config.vocab_size,
788
+ text_config.hidden_size,
789
+ quant_config=quant_config,
790
+ prefix=maybe_prefix(prefix, "lm_head"),
791
+ )
792
+ if text_config.tie_word_embeddings:
793
+ self.lm_head = self.lm_head.tie_weights(
794
+ self.model.get_input_embeddings())
795
+
796
+ logit_scale = getattr(config, "logit_scale", 1.0)
797
+ self.logits_processor = LogitsProcessor(self.unpadded_vocab_size,
798
+ text_config.vocab_size,
799
+ logit_scale)
800
+ else:
801
+ self.lm_head = PPMissingLayer()
802
+
803
+ self.make_empty_intermediate_tensors = (
804
+ self.model.make_empty_intermediate_tensors)
805
+
806
+ @property
807
+ def hf_to_vllm_mapper(self):
808
+ # Backwards compatibility for prev released models
809
+ # State dicts back then had different formats
810
+ # and cannot be loaded with `AutoModel` mapping
811
+ # as is
812
+ prefix_mapper = {
813
+ "language_model.model": "model.language_model",
814
+ "text_model.model": "model.text_model",
815
+ "vision_tower": "model.vision_tower",
816
+ "vqmodel": "model.vqmodel",
817
+ "vision_model": "model.vision_model",
818
+ "vision_embed_tokens": "model.vision_embed_tokens",
819
+ "image_newline": "model.image_newline",
820
+ "multi_modal_projector": "model.multi_modal_projector",
821
+ "text_model.lm_head": "lm_head",
822
+ "language_model.lm_head": "lm_head",
823
+ }
824
+ # Don't change the order for QwenVL
825
+ if 'Qwen2' in self.config.__class__.__name__:
826
+ prefix_mapper["model"] = "model.language_model"
827
+ prefix_mapper["visual"] = "model.visual"
828
+
829
+ return WeightsMapper(orig_to_new_prefix=prefix_mapper, )
830
+
831
+ def forward(
832
+ self,
833
+ input_ids: Optional[torch.Tensor],
834
+ positions: torch.Tensor,
835
+ intermediate_tensors: Optional[IntermediateTensors] = None,
836
+ inputs_embeds: Optional[torch.Tensor] = None,
837
+ **kwargs: object,
838
+ ) -> Union[torch.Tensor, IntermediateTensors]:
839
+ # NOTE: In v1, inputs_embeds is always generated at model runner from
840
+ # `get_multimodal_embeddings` and `get_input_embeddings`, this
841
+ # condition is only for v0 compatibility.
842
+ if inputs_embeds is None:
843
+ multimodal_embeds = self.get_multimodal_embeddings(**kwargs)
844
+ if multimodal_embeds is not None:
845
+ inputs_embeds = self.get_input_embeddings(
846
+ input_ids, multimodal_embeds)
847
+ input_ids = None
848
+
849
+ model_output = self.model(input_ids, positions, intermediate_tensors,
850
+ inputs_embeds)
851
+ return model_output
852
+
853
+ def compute_logits(
854
+ self,
855
+ hidden_states: torch.Tensor,
856
+ sampling_metadata: SamplingMetadata,
857
+ ) -> Optional[torch.Tensor]:
858
+ logits = self.logits_processor(self.lm_head, hidden_states,
859
+ sampling_metadata)
860
+ return logits
861
+
862
+ def load_weights(self, weights: Iterable[tuple[str,
863
+ torch.Tensor]]) -> set[str]:
864
+ loader = AutoWeightsLoader(
865
+ self,
866
+ skip_prefixes=([
867
+ "lm_head."
868
+ ] if self.config.get_text_config().tie_word_embeddings else None),
869
+ )
870
+ return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
871
+
872
+ def get_multimodal_embeddings(self, **kwargs):
873
+ pixel_values = kwargs.pop("pixel_values", None)
874
+ pixel_values = pixel_values if pixel_values is not None else kwargs.pop(
875
+ "image_patches", None)
876
+ image_embeds = kwargs.pop("image_embeds", None)
877
+
878
+ if image_embeds is not None:
879
+ return image_embeds
880
+
881
+ if pixel_values is None and image_embeds is None:
882
+ return None
883
+
884
+ num_image_patches = kwargs.pop("num_image_patches")
885
+ if pixel_values is not None:
886
+ if isinstance(pixel_values, torch.Tensor):
887
+ pixel_values = flatten_bn(pixel_values).to(self.dtype)
888
+ elif is_list_of(pixel_values, torch.Tensor):
889
+ pixel_values = flatten_bn(flatten_bn(pixel_values),
890
+ concat=True).to(self.dtype)
891
+ else:
892
+ raise ValueError(
893
+ f"Unsupported pixel_values type {type(pixel_values)}. "
894
+ "Expected `torch.Tensor` or list of `torch.Tensor`.")
895
+
896
+ if isinstance(num_image_patches, list):
897
+ num_image_patches = torch.cat(num_image_patches)
898
+
899
+ vision_embeddings = self.model.model.get_image_features(
900
+ pixel_values,
901
+ **{
902
+ k: v.flatten(0, 1)
903
+ for k, v in kwargs.items()
904
+ },
905
+ )
906
+
907
+ if isinstance(vision_embeddings, torch.Tensor):
908
+ if vision_embeddings.ndim == 2:
909
+ vision_embeddings = vision_embeddings.unsqueeze(0)
910
+
911
+ # Embeddings have to be 2D tensors of length `num_images`
912
+ # but transformers returns concat tensors if each patch
913
+ # is of different size. We split it back to make vLLM happy
914
+ vision_embeddings = torch.split(
915
+ vision_embeddings,
916
+ num_image_patches.flatten().tolist())
917
+ vision_embeddings = [
918
+ embed.flatten(start_dim=0, end_dim=-2)
919
+ for embed in vision_embeddings
920
+ ]
921
+
922
+ return vision_embeddings
923
+
924
+ def get_input_embeddings(
925
+ self,
926
+ input_ids: torch.Tensor,
927
+ multimodal_embeddings=None,
928
+ ) -> torch.Tensor:
929
+ inputs_embeds = self.model.model.get_input_embeddings()(input_ids)
930
+ if (multimodal_embeddings is not None
931
+ and len(multimodal_embeddings) != 0):
932
+ mask = (input_ids == self.config.image_token_id)
933
+ mask = mask.unsqueeze(-1).expand_as(inputs_embeds)
934
+ multimodal_embeddings = torch.cat(multimodal_embeddings)
935
+
936
+ inputs_embeds = inputs_embeds.masked_scatter(
937
+ mask, multimodal_embeddings)
938
+ return inputs_embeds