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

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

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

Synced Nov 14, 05:30 UTC

[Model] support new model ovis2.5 (#23084)

Signed-off-by: myselvess <244285088@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>

myselvess committed Aug 19, 2025 at 21:12 UTC b87cb97a53bcff92a90308528b3f313e43aff102
12 files changed +1787 -1
docs/models/supported_models.md
+1
@@ -641,6 +641,7 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
641 | `MolmoForCausalLM` | Molmo | T + I<sup>+</sup> | `allenai/Molmo-7B-D-0924`, `allenai/Molmo-7B-O-0924`, etc. | ✅︎ | ✅︎ | ✅︎ |
642 | `NVLM_D_Model` | NVLM-D 1.0 | T + I<sup>+</sup> | `nvidia/NVLM-D-72B`, etc. | | ✅︎ | ✅︎ |
643 | `Ovis` | Ovis2, Ovis1.6 | T + I<sup>+</sup> | `AIDC-AI/Ovis2-1B`, `AIDC-AI/Ovis1.6-Llama3.2-3B`, etc. | | ✅︎ | ✅︎ |
644 +| `Ovis2_5` | Ovis2.5 | T + I<sup>+</sup> + V | `AIDC-AI/Ovis2.5-9B`, etc. | | | ✅︎ |
645 | `PaliGemmaForConditionalGeneration` | PaliGemma, PaliGemma 2 | T + I<sup>E</sup> | `google/paligemma-3b-pt-224`, `google/paligemma-3b-mix-224`, `google/paligemma2-3b-ft-docci-448`, etc. | | ✅︎ | ⚠️ |
646 | `Phi3VForCausalLM` | Phi-3-Vision, Phi-3.5-Vision | T + I<sup>E+</sup> | `microsoft/Phi-3-vision-128k-instruct`, `microsoft/Phi-3.5-vision-instruct`, etc. | | ✅︎ | ✅︎ |
647 | `Phi4MMForCausalLM` | Phi-4-multimodal | T + I<sup>+</sup> / T + A<sup>+</sup> / I<sup>+</sup> + A<sup>+</sup> | `microsoft/Phi-4-multimodal-instruct`, etc. | ✅︎ | ✅︎ | ✅︎ |
examples/offline_inference/vision_language.py
+33
@@ -1105,6 +1105,38 @@ def run_ovis(questions: list[str], modality: str) -> ModelRequestData:
1105 )
1106
1107
1108 +# Ovis2_5
1109 +def run_ovis2_5(questions: list[str], modality: str) -> ModelRequestData:
1110 + model_name = "AIDC-AI/Ovis2.5-2B"
1111 +
1112 + engine_args = EngineArgs(
1113 + model=model_name,
1114 + max_model_len=4096,
1115 + max_num_seqs=2,
1116 + trust_remote_code=True,
1117 + dtype="half",
1118 + limit_mm_per_prompt={modality: 1},
1119 + )
1120 + if modality == "image":
1121 + placeholder = "<image>"
1122 + elif modality == "video":
1123 + placeholder = "<video>"
1124 +
1125 + tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
1126 + messages = [
1127 + [{"role": "user", "content": f"{placeholder}\n{question}"}]
1128 + for question in questions
1129 + ]
1130 + prompts = tokenizer.apply_chat_template(
1131 + messages, tokenize=False, add_generation_prompt=True
1132 + )
1133 +
1134 + return ModelRequestData(
1135 + engine_args=engine_args,
1136 + prompts=prompts,
1137 + )
1138 +
1139 +
1140 # PaliGemma
1141 def run_paligemma(questions: list[str], modality: str) -> ModelRequestData:
1142 assert modality == "image"
@@ -1579,6 +1611,7 @@ model_example_map = {
1611 "nemotron_vl": run_nemotron_vl,
1612 "NVLM_D": run_nvlm_d,
1613 "ovis": run_ovis,
1614 + "ovis2_5": run_ovis2_5,
1615 "paligemma": run_paligemma,
1616 "paligemma2": run_paligemma2,
1617 "phi3_v": run_phi3v,
examples/offline_inference/vision_language_multi_image.py
+31
@@ -680,6 +680,36 @@ def load_ovis(question: str, image_urls: list[str]) -> ModelRequestData:
680 )
681
682
683 +# ovis2_5
684 +def load_ovis2_5(question: str, image_urls: list[str]) -> ModelRequestData:
685 + model_name = "AIDC-AI/Ovis2.5-2B"
686 +
687 + engine_args = EngineArgs(
688 + model=model_name,
689 + max_model_len=8192,
690 + max_num_seqs=2,
691 + trust_remote_code=True,
692 + dtype="half",
693 + limit_mm_per_prompt={"image": len(image_urls)},
694 + )
695 +
696 + placeholders = "\n".join(
697 + f"Image-{i}: <image>\n" for i, _ in enumerate(image_urls, start=1)
698 + )
699 + messages = [{"role": "user", "content": f"{placeholders}\n{question}"}]
700 +
701 + tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
702 + prompt = tokenizer.apply_chat_template(
703 + messages, tokenize=False, add_generation_prompt=True
704 + )
705 +
706 + return ModelRequestData(
707 + engine_args=engine_args,
708 + prompt=prompt,
709 + image_data=[fetch_image(url) for url in image_urls],
710 + )
711 +
712 +
713 def load_pixtral_hf(question: str, image_urls: list[str]) -> ModelRequestData:
714 model_name = "mistral-community/pixtral-12b"
715
@@ -1155,6 +1185,7 @@ model_example_map = {
1185 "mllama": load_mllama,
1186 "NVLM_D": load_nvlm_d,
1187 "ovis": load_ovis,
1188 + "ovis2_5": load_ovis2_5,
1189 "phi3_v": load_phi3v,
1190 "phi4_mm": load_phi4mm,
1191 "phi4_multimodal": load_phi4_multimodal,
tests/models/multimodal/generation/test_common.py
+21
@@ -11,6 +11,7 @@ from pathlib import PosixPath
11 import pytest
12 from transformers import (AutoModel, AutoModelForImageTextToText,
13 AutoModelForTextToWaveform, AutoModelForVision2Seq)
14 +from transformers.utils import is_flash_attn_2_available
15
16 from vllm.platforms import current_platform
17 from vllm.utils import identity
@@ -621,6 +622,26 @@ VLM_TEST_SETTINGS = {
622 hf_model_kwargs={"llm_attn_implementation": "sdpa"},
623 patch_hf_runner=model_utils.ovis_patch_hf_runner,
624 ),
625 + "ovis2_5": VLMTestInfo(
626 + models=["AIDC-AI/Ovis2.5-2B"],
627 + test_type=(
628 + VLMTestType.IMAGE,
629 + VLMTestType.MULTI_IMAGE,
630 + VLMTestType.VIDEO
631 + ),
632 + prompt_formatter=lambda img_prompt: f"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n{img_prompt}<|im_end|>\n<|im_start|>assistant\n", # noqa: E501
633 + img_idx_to_prompt=lambda idx: "<image>\n", # noqa: E501
634 + video_idx_to_prompt=lambda idx: "<video>\n",
635 + max_model_len=4096,
636 + max_num_seqs=2,
637 + dtype="half",
638 + num_logprobs=10,
639 + patch_hf_runner=model_utils.ovis2_5_patch_hf_runner,
640 + marks=[pytest.mark.skipif(
641 + not is_flash_attn_2_available(),
642 + reason="HF model needs `flash_attn` installed"
643 + )],
644 + ),
645 "phi3v": VLMTestInfo(
646 models=["microsoft/Phi-3.5-vision-instruct"],
647 test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
tests/models/multimodal/generation/vlm_utils/model_utils.py
+58
@@ -10,6 +10,7 @@ from typing import Optional, Union
10
11 import numpy as np
12 import numpy.typing as npt
13 +import PIL.Image
14 import pytest
15 import regex as re
16 import torch
@@ -810,6 +811,63 @@ def ovis_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
811 return hf_model
812
813
814 +def ovis2_5_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
815 + """Patches and returns an instance of the HfRunner to use for Ovis2."""
816 + hf_model.model.get_output_embeddings = lambda: \
817 + hf_model.model.llm.get_output_embeddings()
818 +
819 + def processor(*args, text="", images=None, videos=None, **kwargs):
820 + if images is None:
821 + images = []
822 + else:
823 + images = [images] if isinstance(images, Image) else images
824 + if videos is None:
825 + videos = []
826 + else:
827 + videos = [videos] if isinstance(videos, np.ndarray) else videos
828 + videos = [[PIL.Image.fromarray(frame) for frame in vid]
829 + for vid in videos]
830 +
831 + prompt_start_and_end = {
832 + "qwen2": ("<|im_start|>user\n", "<|im_end|>\n"),
833 + "llama":
834 + ("<|start_header_id|>user<|end_header_id|>\n\n", "<|eot_id|>"),
835 + "gemma2": ("<start_of_turn>user\n", "<end_of_turn>\n"),
836 + }
837 + for start, end in prompt_start_and_end.values():
838 + if start in text and end in text:
839 + text = text.split(start)[1].split(end)[0]
840 + break
841 +
842 + images_message = [{"type": "image", "image": img} for img in images]
843 + videos_message = [{"type": "video", "video": vid} for vid in videos]
844 +
845 + messages = [{
846 + "role":
847 + "user",
848 + "content": [
849 + *images_message,
850 + *videos_message,
851 + {
852 + "type": "text",
853 + "text": text
854 + },
855 + ],
856 + }]
857 +
858 + input_ids, pixel_values, grid_thws = hf_model.model.preprocess_inputs(
859 + messages=messages, enable_thinking=True)
860 + inputs = {
861 + "inputs": input_ids,
862 + "pixel_values": pixel_values,
863 + "grid_thws": grid_thws,
864 + }
865 + return BatchFeature(data=inputs, tensor_type="pt")
866 +
867 + hf_model.processor = processor
868 + return hf_model
869 +
870 +
871 def qwen2_5_omni_patch_hf_runner(hf_model: HfRunner) -> HfRunner:
872 """Patches and returns an instance of the HfRunner for Qwen2.5-Omni."""
873 thinker = hf_model.model.thinker
tests/models/multimodal/processing/test_common.py
+2
@@ -162,6 +162,7 @@ def _test_processing_correctness(
162 _ADD_SPECIAL_TOKENS_OVERRIDES = {
163 "mllama": False,
164 "ovis": False,
165 + "ovis2_5": False,
166 "paligemma": False,
167 "ultravox": False,
168 "whisper": False,
@@ -301,6 +302,7 @@ def _test_processing_correctness_one(
302 "AIDC-AI/Ovis1.6-Gemma2-9B",
303 "AIDC-AI/Ovis1.6-Llama3.2-3B",
304 "AIDC-AI/Ovis2-1B",
305 + "AIDC-AI/Ovis2.5-2B",
306 "google/paligemma-3b-mix-224",
307 "google/paligemma2-3b-ft-docci-448",
308 "microsoft/Phi-3.5-vision-instruct",
tests/models/registry.py
+3
@@ -464,6 +464,9 @@ _MULTIMODAL_EXAMPLE_MODELS = {
464 transformers_version_reason="HF model is not compatible", # noqa: E501
465 extras={"1.6-llama": "AIDC-AI/Ovis1.6-Llama3.2-3B",
466 "1.6-gemma": "AIDC-AI/Ovis1.6-Gemma2-9B"}), # noqa: E501
467 + "Ovis2_5": _HfExamplesInfo("AIDC-AI/Ovis2.5-2B", trust_remote_code=True,
468 + max_transformers_version="4.53",
469 + transformers_version_reason="HF model is not compatible"), # noqa: E501
470 "PaliGemmaForConditionalGeneration": _HfExamplesInfo("google/paligemma-3b-mix-224", # noqa: E501
471 extras={"v2": "google/paligemma2-3b-ft-docci-448"}), # noqa: E501
472 "Phi3VForCausalLM": _HfExamplesInfo("microsoft/Phi-3-vision-128k-instruct",
vllm/model_executor/models/ovis2_5.py new
+570
@@ -0,0 +1,570 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +""" PyTorch Ovis model."""
4 +from collections.abc import Iterable, Mapping
5 +from functools import partial
6 +from typing import Optional, Union
7 +
8 +import torch
9 +import torch.nn as nn
10 +from transformers import BaseImageProcessor, BatchFeature, PretrainedConfig
11 +
12 +from vllm.config import VllmConfig
13 +from vllm.model_executor.layers.linear import ReplicatedLinear
14 +from vllm.model_executor.layers.quantization.base_config import (
15 + QuantizationConfig)
16 +from vllm.model_executor.models.ovis import (OvisImagePatchInputs,
17 + VisualEmbedding)
18 +from vllm.model_executor.models.siglip2navit import Siglip2NavitModel
19 +from vllm.model_executor.models.utils import (AutoWeightsLoader, flatten_bn,
20 + init_vllm_registered_model,
21 + maybe_prefix)
22 +from vllm.model_executor.sampling_metadata import SamplingMetadata
23 +from vllm.multimodal import MULTIMODAL_REGISTRY
24 +from vllm.multimodal.inputs import (MultiModalDataDict, MultiModalFieldConfig,
25 + MultiModalKwargsItems)
26 +from vllm.multimodal.parse import ImageSize, MultiModalDataItems
27 +from vllm.multimodal.processing import (BaseMultiModalProcessor,
28 + BaseProcessingInfo, PromptReplacement)
29 +from vllm.multimodal.profiling import BaseDummyInputsBuilder
30 +from vllm.sequence import IntermediateTensors
31 +from vllm.transformers_utils.processors.ovis2_5 import Ovis2_5Processor
32 +
33 +from .interfaces import MultiModalEmbeddings, SupportsMultiModal
34 +
35 +IMAGE_TOKEN = "<image>"
36 +VIDEO_TOKEN = "<video>"
37 +INDICATOR_IDS = [-301, -302, -303, -304]
38 +
39 +IMAGE_PAD_TOKEN_MAP = {
40 + "gemma2": "<unused0>",
41 + "llama": "<|reserved_special_token_0|>",
42 + "qwen2": "<|image_pad|>",
43 + "qwen3": "<|image_pad|>",
44 +}
45 +IMAGE_PAD_TOKEN_ID_MAP = {
46 + "gemma2": 7,
47 + "llama": 128002,
48 + "qwen2": 151655,
49 + "qwen3": 151655,
50 +}
51 +
52 +
53 +def _ovis2_5_field_config():
54 + return dict(pixel_values=MultiModalFieldConfig.batched("image"),
55 + grids=MultiModalFieldConfig.batched("image"),
56 + indicator_tokens=MultiModalFieldConfig.batched("image"),
57 + video_pixel_values=MultiModalFieldConfig.batched("video"),
58 + video_indicator_tokens=MultiModalFieldConfig.batched("video"),
59 + video_grids=MultiModalFieldConfig.batched("video"))
60 +
61 +
62 +class VisualTokenizer(torch.nn.Module):
63 + """
64 + VIT
65 + """
66 +
67 + def __init__(
68 + self,
69 + config: PretrainedConfig,
70 + visual_vocab_size: int,
71 + quant_config: Optional[QuantizationConfig] = None,
72 + prefix: str = "",
73 + ):
74 + super().__init__()
75 + self.config = config
76 + self.vit = self._init_backbone(
77 + config=config,
78 + quant_config=quant_config,
79 + prefix=f"{prefix}.vit",
80 + )
81 + # reserved tokens for INDICATOR_IDS
82 + head_dim = visual_vocab_size - len(INDICATOR_IDS)
83 + self.head = torch.nn.Sequential(
84 + ReplicatedLinear(
85 + self.config.hidden_size * self.config.hidden_stride**2,
86 + head_dim,
87 + bias=False,
88 + return_bias=False,
89 + ), torch.nn.LayerNorm(head_dim))
90 +
91 + def _init_backbone(
92 + self,
93 + config: PretrainedConfig,
94 + quant_config: Optional[QuantizationConfig] = None,
95 + prefix: str = "",
96 + ):
97 + model_type = config.model_type
98 + if model_type == "siglip2_navit":
99 + return Siglip2NavitModel(config=config, )
100 + raise ValueError(
101 + f"Unsupported visual tokenizer model_type: {model_type}")
102 +
103 + @property
104 + def dtype(self):
105 + return next(self.head.parameters()).dtype
106 +
107 + @property
108 + def device(self):
109 + return next(self.head.parameters()).device
110 +
111 + def tokenize(self, logits):
112 + tokens = torch.softmax(logits, dim=-1,
113 + dtype=torch.float32).to(logits.dtype)
114 + return tokens
115 +
116 + def encode(self, pixel_values, grid_thws):
117 + features = self.vit(pixel_values,
118 + grid_thws,
119 + output_hidden_states=True,
120 + return_dict=True)
121 + # refer to qwen2.5-vl patchmerger
122 + seq_len, _ = features.shape
123 + features = features.reshape(seq_len // (self.config.hidden_stride**2),
124 + -1)
125 +
126 + return features
127 +
128 + def forward(self, pixel_values, grid_thws) -> torch.Tensor:
129 + features = self.encode(pixel_values, grid_thws)
130 + logits = self.head(features)
131 + tokens = self.tokenize(logits)
132 + # tokens' shape is [#Token, VocabSize-4],
133 + # so padding with [#Token, 4], after which,
134 + # tokens' shape should become [#Token, VocabSize];
135 + tokens = torch.nn.functional.pad(
136 + tokens,
137 + (0, len(INDICATOR_IDS)),
138 + mode="constant",
139 + value=0,
140 + )
141 + return tokens
142 +
143 +
144 +class Ovis2_5ProcessingInfo(BaseProcessingInfo):
145 +
146 + def get_hf_config(self):
147 + return self.ctx.get_hf_config()
148 +
149 + def get_hf_processor(self, **kwargs):
150 + vit_config = self.get_hf_config().vit_config
151 + return self.ctx.get_hf_processor(
152 + Ovis2_5Processor,
153 + image_pad_token=self.get_image_pad_token(),
154 + patch_size=vit_config.patch_size,
155 + hidden_stride=vit_config.hidden_stride,
156 + temporal_patch_size=vit_config.temporal_patch_size,
157 + )
158 +
159 + def get_image_pad_token(self) -> str:
160 + hf_text_config = self.get_hf_config().get_text_config()
161 + text_model_type = hf_text_config.model_type
162 + return IMAGE_PAD_TOKEN_MAP.get(text_model_type)
163 +
164 + def get_image_processor(self) -> BaseImageProcessor:
165 + return self.get_hf_processor().image_processor # type: ignore
166 +
167 + def get_supported_mm_limits(self) -> Mapping[str, Optional[int]]:
168 + return {"image": None, "video": 1}
169 +
170 + def get_image_size_with_most_features(self) -> ImageSize:
171 + # NOTE(myselvess): max_pixels 1792 * 1792 hardcoded in original code
172 + # TODO(myselvess): Be adjusted based on the max_pixels
173 + return ImageSize(width=1792, height=1792)
174 +
175 + def get_num_image_tokens(
176 + self,
177 + *,
178 + image_width: int,
179 + image_height: int,
180 + num_frames: int = 1,
181 + ) -> tuple[ImageSize, int]:
182 + hf_config = self.get_hf_config()
183 + vit_config = hf_config.vit_config
184 + patch_size = vit_config.patch_size
185 + temporal_patch_size = vit_config.temporal_patch_size
186 + # NOTE: Frames are padded to be divisible by `temporal_patch_size`
187 + # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/qwen2_vl/image_processing_qwen2_vl.py#L294
188 + padded_num_frames = num_frames + (-num_frames % temporal_patch_size)
189 + grid_t = max(padded_num_frames // temporal_patch_size, 1)
190 + grid_h = image_height // patch_size
191 + grid_w = image_width // patch_size
192 + num_patches = grid_t * grid_h * grid_w
193 + num_vision_tokens = num_patches
194 + return num_vision_tokens
195 +
196 + def get_max_image_tokens(self) -> int:
197 + target_width, target_height = self.get_image_size_with_most_features()
198 + return self.get_num_image_tokens(image_width=target_width,
199 + image_height=target_height)
200 +
201 + def _get_max_video_frames(self, max_tokens: int) -> int:
202 + target_width, target_height = self.get_image_size_with_most_features()
203 + num_frames = 0
204 + while True:
205 + next_num_frames = num_frames + 1
206 + next_max_tokens = self.get_num_video_tokens(
207 + image_width=target_width,
208 + image_height=target_height,
209 + num_frames=next_num_frames,
210 + image_processor=None,
211 + )
212 + if next_max_tokens > max_tokens:
213 + break
214 + num_frames = next_num_frames
215 + return num_frames
216 +
217 + def get_num_frames_with_most_features(
218 + self,
219 + seq_len: int,
220 + mm_counts: Mapping[str, int],
221 + ) -> int:
222 + max_images = mm_counts.get("image", 0)
223 + max_videos = mm_counts.get("video", 0)
224 + max_image_tokens = self.get_max_image_tokens() * max_images
225 + max_total_frames = self._get_max_video_frames(seq_len -
226 + max_image_tokens)
227 + max_frames_per_video = max_total_frames // max(max_videos, 1)
228 + return max(max_frames_per_video, 1)
229 +
230 + def get_num_video_tokens(
231 + self,
232 + *,
233 + image_width: int,
234 + image_height: int,
235 + num_frames: int,
236 + image_processor: Optional[BaseImageProcessor],
237 + ) -> int:
238 + num_video_tokens = self.get_num_image_tokens(image_width=image_width,
239 + image_height=image_height,
240 + num_frames=num_frames)
241 + return num_video_tokens
242 +
243 + def get_max_video_tokens(
244 + self,
245 + seq_len: int,
246 + mm_counts: Mapping[str, int],
247 + ) -> int:
248 + target_width, target_height = self.get_image_size_with_most_features()
249 + return self.get_num_video_tokens(
250 + image_width=target_width,
251 + image_height=target_height,
252 + num_frames=self.get_num_frames_with_most_features(
253 + seq_len, mm_counts),
254 + image_processor=None,
255 + )
256 +
257 +
258 +class Ovis2_5DummyInputsBuilder(BaseDummyInputsBuilder[Ovis2_5ProcessingInfo]):
259 +
260 + def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
261 + num_images = mm_counts.get("image", 0)
262 + num_videos = mm_counts.get("video", 0)
263 + return IMAGE_TOKEN * num_images + VIDEO_TOKEN * num_videos
264 +
265 + def get_dummy_mm_data(
266 + self,
267 + seq_len: int,
268 + mm_counts: Mapping[str, int],
269 + ) -> MultiModalDataDict:
270 + num_images = mm_counts.get("image", 0)
271 + num_videos = mm_counts.get("video", 0)
272 +
273 + target_width, target_height = \
274 + self.info.get_image_size_with_most_features()
275 + target_num_frames = \
276 + self.info.get_num_frames_with_most_features(seq_len, mm_counts)
277 + mm_data = {
278 + "image":
279 + self._get_dummy_images(width=target_width,
280 + height=target_height,
281 + num_images=num_images),
282 + "video":
283 + self._get_dummy_videos(
284 + width=target_width,
285 + height=target_height,
286 + num_frames=target_num_frames,
287 + num_videos=num_videos,
288 + )
289 + }
290 + return mm_data
291 +
292 +
293 +class Ovis2_5MultiModalProcessor(BaseMultiModalProcessor[Ovis2_5ProcessingInfo]
294 + ):
295 +
296 + def visual_indicators_to_visual_tokens(
297 + self,
298 + visual_indicators: list[int],
299 + ) -> list[int]:
300 + """
301 + Filter image indicators placeholders and convert them to corresponding
302 + tokens in visual tokenizer.
303 + """
304 + hf_config = self.info.get_hf_config()
305 + vte_vocab_size = hf_config.visual_vocab_size
306 + return [
307 + vte_vocab_size - len(INDICATOR_IDS) + abs(x + 300) - 1
308 + for x in visual_indicators if x < -300
309 + ]
310 +
311 + def _call_hf_processor(
312 + self,
313 + prompt: str,
314 + mm_data: Mapping[str, object],
315 + mm_kwargs: Mapping[str, object],
316 + tok_kwargs: Mapping[str, object],
317 + ) -> BatchFeature:
318 + if not mm_data:
319 + # Avoid warning from HF logger for text-only input
320 + tokenizer = self.info.get_tokenizer()
321 + prompt_ids = tokenizer.encode(prompt, add_special_tokens=False)
322 + return BatchFeature(dict(input_ids=[prompt_ids]), tensor_type="pt")
323 +
324 + processed_outputs = super()._call_hf_processor(
325 + prompt=prompt,
326 + mm_data=mm_data,
327 + mm_kwargs=mm_kwargs,
328 + tok_kwargs=tok_kwargs,
329 + )
330 + hf_processor = self.info.get_hf_processor()
331 +
332 + if "videos" in mm_data:
333 + visual_indicators = [
334 + hf_processor.construct_visual_indicators((1, 1, 1), True)
335 + for grid in processed_outputs["video_grids"]
336 + ]
337 + indicator_tokens = [
338 + self.visual_indicators_to_visual_tokens(indicator)
339 + for indicator in visual_indicators
340 + ]
341 + processed_outputs["video_indicator_tokens"] = indicator_tokens
342 + if "images" in mm_data:
343 + visual_indicators = [
344 + hf_processor.construct_visual_indicators((1, 1, 1), False)
345 + for grid in processed_outputs["grids"]
346 + ]
347 + indicator_tokens = [
348 + self.visual_indicators_to_visual_tokens(indicator)
349 + for indicator in visual_indicators
350 + ]
351 +
352 + processed_outputs["indicator_tokens"] = indicator_tokens
353 + return processed_outputs
354 +
355 + def _apply_hf_processor_tokens_only(
356 + self,
357 + prompt_tokens: list[int],
358 + ) -> list[int]:
359 +
360 + return prompt_tokens
361 +
362 + def _get_mm_fields_config(
363 + self,
364 + hf_inputs: BatchFeature,
365 + hf_processor_mm_kwargs: Mapping[str, object],
366 + ) -> Mapping[str, MultiModalFieldConfig]:
367 + return _ovis2_5_field_config()
368 +
369 + def _get_prompt_updates(
370 + self,
371 + mm_items: MultiModalDataItems,
372 + hf_processor_mm_kwargs: Mapping[str, object],
373 + out_mm_kwargs: MultiModalKwargsItems,
374 + ) -> list[PromptReplacement]:
375 +
376 + def get_replacement_ovis(item_idx, modality: str):
377 + if modality == "image":
378 + out_item = out_mm_kwargs["image"][item_idx]
379 + grid = out_item["grids"].data
380 + elif modality == "video":
381 + out_item = out_mm_kwargs["video"][item_idx]
382 + grid = out_item["video_grids"].data
383 + hf_processor = self.info.get_hf_processor()
384 + return hf_processor.construct_visual_placeholders(grid[0], )
385 +
386 + return [
387 + PromptReplacement(
388 + modality=modality,
389 + target=IMAGE_TOKEN if modality == "image" else VIDEO_TOKEN,
390 + replacement=partial(get_replacement_ovis, modality=modality),
391 + ) for modality in ("image", "video")
392 + ]
393 +
394 +
395 +@MULTIMODAL_REGISTRY.register_processor(Ovis2_5MultiModalProcessor,
396 + info=Ovis2_5ProcessingInfo,
397 + dummy_inputs=Ovis2_5DummyInputsBuilder)
398 +class Ovis2_5(nn.Module, SupportsMultiModal):
399 +
400 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
401 + super().__init__()
402 + config = vllm_config.model_config.hf_config
403 + quant_config = vllm_config.quant_config
404 +
405 + self.config: PretrainedConfig = config
406 + self.llm = init_vllm_registered_model(
407 + vllm_config=vllm_config.with_hf_config(config.text_config),
408 + prefix=maybe_prefix(prefix, "llm"),
409 + )
410 +
411 + self.visual_tokenizer = VisualTokenizer(
412 + config=config.vit_config,
413 + visual_vocab_size=config.visual_vocab_size,
414 + quant_config=quant_config,
415 + prefix=f"{prefix}.visual_tokenizer",
416 + )
417 +
418 + self.vte = VisualEmbedding(config.visual_vocab_size,
419 + config.hidden_size)
420 +
421 + text_model_type = self.config.get_text_config().model_type
422 + self.image_pad_token_id = IMAGE_PAD_TOKEN_ID_MAP[text_model_type]
423 +
424 + # TODO(Isotr0py): PP support
425 + # self.make_empty_intermediate_tensors = (
426 + # self.language_model.make_empty_intermediate_tensors)
427 +
428 + def _parse_and_validate_visual_input(
429 + self, is_video,
430 + **kwargs: object) -> Optional[OvisImagePatchInputs]:
431 + if is_video:
432 + pixel_values = kwargs.pop("video_pixel_values", None)
433 + indicator_tokens = kwargs.pop("video_indicator_tokens", None)
434 + grids = kwargs.pop("video_grids", None)
435 + else:
436 + pixel_values = kwargs.pop("pixel_values", None)
437 + indicator_tokens = kwargs.pop("indicator_tokens", None)
438 + grids = kwargs.pop("grids", None)
439 + if pixel_values is None and indicator_tokens is None:
440 + return None
441 +
442 + if pixel_values is not None and indicator_tokens is not None:
443 + if not isinstance(pixel_values, (torch.Tensor, list)):
444 + raise ValueError("Incorrect type of pixel values. "
445 + f"Got type: {type(pixel_values)}")
446 +
447 + if not isinstance(indicator_tokens, (torch.Tensor, list)):
448 + raise ValueError("Incorrect type of indicator_tokens. "
449 + f"Got type: {type(indicator_tokens)}")
450 +
451 + return OvisImagePatchInputs(
452 + type="image_patches",
453 + flat_data=flatten_bn(flatten_bn(pixel_values), concat=True),
454 + patches_per_image=[
455 + x.shape[0] // (self.config.vit_config.hidden_stride**2)
456 + for x in flatten_bn(pixel_values)
457 + ],
458 + indicator_tokens=flatten_bn(flatten_bn(indicator_tokens),
459 + concat=True),
460 + grids=flatten_bn(flatten_bn(grids), concat=True),
461 + )
462 +
463 + raise AssertionError("This line should be unreachable.")
464 +
465 + def _process_image_input(
466 + self, image_input: OvisImagePatchInputs) -> MultiModalEmbeddings:
467 + image_patches_flat = image_input["flat_data"]
468 + patches_per_image = image_input["patches_per_image"]
469 + indicator_tokens = image_input["indicator_tokens"]
470 + grid_thws = image_input["grids"]
471 +
472 + indicator_per_image = list(
473 + map(lambda x: 2 if x > 1 else x + 2, patches_per_image))
474 +
475 + target_dtype = self.visual_tokenizer.dtype
476 + visual_tokens = self.visual_tokenizer(
477 + image_patches_flat.to(target_dtype), grid_thws)
478 +
479 + visual_embeds = self.vte(visual_tokens) # 1:1 numeric eq.
480 + indicator_embeds = self.vte(indicator_tokens)
481 +
482 + visual_embeds_per_image = visual_embeds.split(patches_per_image, dim=0)
483 + indicator_embeds_per_image = indicator_embeds.split(
484 + indicator_per_image)
485 +
486 + vision_embeddings = []
487 + for indicator, visual in zip(indicator_embeds_per_image,
488 + visual_embeds_per_image):
489 + vision_embeddings_per_image = []
490 + visual = visual.unsqueeze(0)
491 + for i in range(visual.shape[0]):
492 + vision_embeddings_per_image.append(
493 + torch.cat([indicator[i:i + 1], visual[i]], dim=0))
494 + vision_embeddings_per_image.append(indicator[i + 1:])
495 + vision_embeddings.append(
496 + torch.cat(vision_embeddings_per_image, dim=0))
497 + return tuple(vision_embeddings)
498 +
499 + def get_multimodal_embeddings(
500 + self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
501 + embeddings = []
502 +
503 + # NOTE: _parse_and_validate_visual_input has side-effects and pops
504 + # keys from kwargs. We process images first, then videos.
505 + image_input = self._parse_and_validate_visual_input(False, **kwargs)
506 + if image_input:
507 + embeddings.extend(self._process_image_input(image_input))
508 +
509 + video_input = self._parse_and_validate_visual_input(True, **kwargs)
510 + if video_input:
511 + embeddings.extend(self._process_image_input(video_input))
512 +
513 + return tuple(embeddings) if embeddings else None
514 +
515 + def get_input_embeddings(
516 + self,
517 + input_ids: torch.Tensor,
518 + multimodal_embeddings: Optional[MultiModalEmbeddings] = None,
519 + ) -> torch.Tensor:
520 + inputs_embeds = self.llm.get_input_embeddings(input_ids)
521 + if multimodal_embeddings is not None:
522 + tmp = torch.concat(multimodal_embeddings, dim=0)
523 + inputs_embeds[input_ids == self.image_pad_token_id] = tmp
524 + return inputs_embeds
525 +
526 + def forward(
527 + self,
528 + input_ids: torch.Tensor,
529 + positions: torch.Tensor,
530 + intermediate_tensors: Optional[IntermediateTensors] = None,
531 + inputs_embeds: Optional[torch.Tensor] = None,
532 + **kwargs: object,
533 + ) -> Union[torch.Tensor, IntermediateTensors]:
534 + if intermediate_tensors is not None:
535 + inputs_embeds = None
536 +
537 + # NOTE: In v1, inputs_embeds is always generated at model runner, this
538 + # condition is for v0 compatibility.
539 + elif inputs_embeds is None:
540 + vision_embeddings = self.get_multimodal_embeddings(**kwargs)
541 +
542 + inputs_embeds = self.get_input_embeddings(input_ids,
543 + vision_embeddings)
544 + input_ids = None
545 +
546 + # up until here we have a inputs_embeds 100% numerical identity
547 + # between the OG HF Transformers implementation and ours
548 + hidden_states = self.llm(
549 + input_ids=input_ids,
550 + positions=positions,
551 + intermediate_tensors=intermediate_tensors,
552 + inputs_embeds=inputs_embeds,
553 + )
554 + return hidden_states
555 +
556 + def compute_logits(
557 + self,
558 + hidden_states: torch.Tensor,
559 + sampling_metadata: SamplingMetadata,
560 + ) -> Optional[torch.Tensor]:
561 + logits = self.llm.compute_logits(hidden_states, sampling_metadata)
562 + return logits
563 +
564 + def load_weights(self, weights: Iterable[tuple[str,
565 + torch.Tensor]]) -> set[str]:
566 + loader = AutoWeightsLoader(self)
567 + return loader.load_weights(weights)
568 +
569 + def get_language_model(self) -> torch.nn.Module:
570 + return self.llm
\ No newline at end of file
vllm/model_executor/models/registry.py
+1
@@ -231,6 +231,7 @@ _MULTIMODAL_MODELS = {
231 "MolmoForCausalLM": ("molmo", "MolmoForCausalLM"),
232 "NVLM_D": ("nvlm_d", "NVLM_D_Model"),
233 "Ovis": ("ovis", "Ovis"),
234 + "Ovis2_5": ("ovis2_5", "Ovis2_5"),
235 "PaliGemmaForConditionalGeneration": ("paligemma", "PaliGemmaForConditionalGeneration"), # noqa: E501
236 "Phi3VForCausalLM": ("phi3v", "Phi3VForCausalLM"),
237 "Phi4MMForCausalLM": ("phi4mm", "Phi4MMForCausalLM"),
vllm/model_executor/models/siglip2navit.py new
+607
@@ -0,0 +1,607 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Implementation of SiglipVisionModel intended to be only used
4 +within a vision language model."""
5 +
6 +from typing import Optional, Union
7 +
8 +import torch
9 +from einops import rearrange, repeat
10 +from torch import nn
11 +from torch.nn import functional as F
12 +from transformers.activations import ACT2FN
13 +from transformers.configuration_utils import PretrainedConfig
14 +from transformers.modeling_outputs import BaseModelOutputWithNoAttention
15 +
16 +from vllm.platforms import _Backend
17 +
18 +from .vision import get_vit_attn_backend
19 +
20 +
21 +class VisionRotaryEmbedding(nn.Module):
22 +
23 + def __init__(self, dim: int, theta: float = 10000.0) -> None:
24 + super().__init__()
25 + inv_freq = 1.0 / (theta
26 + **(torch.arange(0, dim, 2, dtype=torch.float) / dim))
27 + self.register_buffer("inv_freq", inv_freq, persistent=False)
28 +
29 + def forward(self, seqlen: int) -> torch.Tensor:
30 + seq = torch.arange(seqlen,
31 + device=self.inv_freq.device,
32 + dtype=self.inv_freq.dtype)
33 + freqs = torch.outer(seq, self.inv_freq)
34 + return freqs
35 +
36 +
37 +class Siglip2VisionEmbeddings(nn.Module):
38 +
39 + def __init__(self, config: PretrainedConfig):
40 + super().__init__()
41 + self.config = config
42 + self.embed_dim = config.hidden_size
43 + self.patch_size = config.patch_size
44 + self.image_size = config.image_size
45 + self.num_patches = config.num_patches
46 + self.preserve_original_pe = config.preserve_original_pe
47 + self.hidden_stride = config.hidden_stride
48 +
49 + # siglip2 naflex
50 + if self.num_patches > 0:
51 + self.patch_embedding = nn.Linear(
52 + in_features=config.num_channels * self.patch_size *
53 + self.patch_size,
54 + out_features=self.embed_dim,
55 + )
56 + if self.preserve_original_pe:
57 + self.position_embedding_size = int(self.num_patches**0.5)
58 + self.position_embedding = nn.Embedding(self.num_patches,
59 + self.embed_dim)
60 +
61 + else:
62 + self.patch_embedding = nn.Conv2d(
63 + in_channels=config.num_channels,
64 + out_channels=self.embed_dim,
65 + kernel_size=self.patch_size,
66 + stride=self.patch_size,
67 + padding="valid",
68 + )
69 + if self.preserve_original_pe:
70 + self.num_patches = (self.image_size // self.patch_size)**2
71 + self.position_embedding_size = (self.image_size //
72 + self.patch_size)
73 + self.position_embedding = nn.Embedding(self.num_patches,
74 + self.embed_dim)
75 +
76 + def forward(self,
77 + pixel_values: torch.FloatTensor,
78 + grid_thws: Optional[torch.LongTensor] = None) -> torch.Tensor:
79 + """
80 + Args:
81 + pixel_values (`torch.FloatTensor`):
82 + Pixel values of shape (
83 + num_patches,
84 + num_channels * temporal_patch_size * patch_size * patch_size
85 + )
86 + grid_thws: (`torch.LongTensor`):
87 + grid shape (num_patches, 3)
88 + """
89 +
90 + # Apply patch embeddings to already patchified pixel values
91 + target_dtype = self.patch_embedding.weight.dtype
92 + if isinstance(self.patch_embedding, nn.Linear):
93 + patch_embeds = self.patch_embedding(
94 + pixel_values.to(dtype=target_dtype))
95 + elif isinstance(self.patch_embedding, nn.Conv2d):
96 + pixel_values = pixel_values.view(
97 + -1, self.config.num_channels * self.config.temporal_patch_size,
98 + self.patch_size, self.patch_size)
99 + patch_embeds = self.patch_embedding(
100 + pixel_values.to(dtype=target_dtype))
101 + patch_embeds = patch_embeds.reshape(-1, self.embed_dim)
102 +
103 + if self.preserve_original_pe:
104 + assert grid_thws is not None
105 + pos_embed_new = torch.zeros_like(patch_embeds)
106 + positional_embeddings = self.position_embedding.weight.reshape(
107 + self.position_embedding_size, self.position_embedding_size,
108 + -1).unsqueeze(0).permute(0, 3, 1, 2)
109 + cnt = 0
110 + for t, h, w in grid_thws:
111 + volume = t * h * w
112 + pe = F.interpolate(positional_embeddings,
113 + size=(h, w),
114 + mode='bicubic',
115 + align_corners=False)
116 + pe = pe.permute(0, 2, 3, 1).reshape(1, h * w, -1)
117 + pe = pe[0].repeat(t, 1)
118 + pe = pe.reshape(t, h // self.hidden_stride, self.hidden_stride,
119 + w // self.hidden_stride, self.hidden_stride,
120 + -1)
121 + pe = pe.permute(0, 1, 3, 2, 4, 5).reshape(volume, -1)
122 + pos_embed_new[cnt:cnt + volume] = pe
123 + cnt += volume
124 + patch_embeds = patch_embeds + pos_embed_new
125 +
126 + return patch_embeds
127 +
128 +
129 +# copy from flash_attn/layers/rotary.py
130 +def rotate_half(x, interleaved=False):
131 + if not interleaved:
132 + x1, x2 = x.chunk(2, dim=-1)
133 + return torch.cat((-x2, x1), dim=-1)
134 + else:
135 + x1, x2 = x[..., ::2], x[..., 1::2]
136 + return rearrange(torch.stack((-x2, x1), dim=-1),
137 + "... d two -> ... (d two)",
138 + two=2)
139 +
140 +
141 +def apply_rotary_emb_torch(x, cos, sin, interleaved=False):
142 + """
143 + x: (batch_size, seqlen, nheads, headdim)
144 + cos, sin: (seqlen, rotary_dim / 2) or (batch_size, seqlen, rotary_dim / 2)
145 + """
146 + ro_dim = cos.shape[-1] * 2
147 + assert ro_dim <= x.shape[-1]
148 + cos = repeat(
149 + cos,
150 + "... d -> ... 1 (2 d)" if not interleaved else "... d -> ... 1 (d 2)")
151 + sin = repeat(
152 + sin,
153 + "... d -> ... 1 (2 d)" if not interleaved else "... d -> ... 1 (d 2)")
154 + return torch.cat(
155 + [
156 + x[..., :ro_dim] * cos +
157 + rotate_half(x[..., :ro_dim], interleaved) * sin, x[..., ro_dim:]
158 + ],
159 + dim=-1,
160 + )
161 +
162 +
163 +def apply_rotary_pos_emb(
164 + q: torch.Tensor,
165 + k: torch.Tensor,
166 + cos: torch.Tensor,
167 + sin: torch.Tensor,
168 + is_flash_attn_backend: bool = False,
169 +) -> tuple[torch.Tensor, torch.Tensor]:
170 + cos = cos.chunk(2, dim=-1)[0].contiguous()
171 + sin = sin.chunk(2, dim=-1)[0].contiguous()
172 + if is_flash_attn_backend:
173 + from flash_attn.layers.rotary import apply_rotary_emb
174 + apply_rotary_emb_func = apply_rotary_emb
175 + else:
176 + apply_rotary_emb_func = apply_rotary_emb_torch
177 + q_embed = apply_rotary_emb_func(q.float(), cos.float(),
178 + sin.float()).type_as(q)
179 + k_embed = apply_rotary_emb_func(k.float(), cos.float(),
180 + sin.float()).type_as(k)
181 + return q_embed, k_embed
182 +
183 +
184 +class Siglip2Attention(nn.Module):
185 + """Multi-headed attention from 'Attention Is All You Need' paper"""
186 +
187 + def __init__(self, config):
188 + super().__init__()
189 + self.config = config
190 + self.embed_dim = config.hidden_size
191 + self.num_heads = config.num_attention_heads
192 + self.head_dim = self.embed_dim // self.num_heads
193 + if self.head_dim * self.num_heads != self.embed_dim:
194 + raise ValueError(
195 + f"embed_dim must be divisible by num_heads "
196 + f"(got `embed_dim`: {self.embed_dim} and `num_heads`:"
197 + f" {self.num_heads}).")
198 + self.scale = self.head_dim**-0.5
199 + self.dropout = config.attention_dropout
200 + self.is_causal = False
201 +
202 + self.k_proj = nn.Linear(self.embed_dim, self.embed_dim)
203 + self.v_proj = nn.Linear(self.embed_dim, self.embed_dim)
204 + self.q_proj = nn.Linear(self.embed_dim, self.embed_dim)
205 + self.out_proj = nn.Linear(self.embed_dim, self.embed_dim)
206 +
207 + self.use_rope = config.use_rope
208 +
209 + # Detect attention implementation.
210 + self.attn_backend: _Backend = get_vit_attn_backend(support_fa=True)
211 + if self.attn_backend not in {
212 + _Backend.FLASH_ATTN, _Backend.TORCH_SDPA,
213 + _Backend.ROCM_AITER_FA
214 + }:
215 + self.attn_backend = _Backend.TORCH_SDPA
216 + self.is_flash_attn_backend = self.attn_backend in {
217 + _Backend.FLASH_ATTN, _Backend.ROCM_AITER_FA
218 + }
219 +
220 + def forward(
221 + self,
222 + hidden_states: torch.Tensor,
223 + cu_seqlens: torch.Tensor,
224 + position_embeddings: Optional[tuple[torch.Tensor,
225 + torch.Tensor]] = None,
226 + ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
227 + """Input shape: Batch x Time x Channel"""
228 +
229 + seq_length, embed_dim = hidden_states.shape
230 +
231 + queries = self.q_proj(hidden_states)
232 + keys = self.k_proj(hidden_states)
233 + values = self.v_proj(hidden_states)
234 +
235 + queries = queries.view(seq_length, self.num_heads, self.head_dim)
236 + keys = keys.view(seq_length, self.num_heads, self.head_dim)
237 + values = values.view(seq_length, self.num_heads, self.head_dim)
238 +
239 + if self.use_rope:
240 + cos, sin = position_embeddings
241 + queries, keys = apply_rotary_pos_emb(queries.unsqueeze(0),
242 + keys.unsqueeze(0), cos, sin,
243 + self.is_flash_attn_backend)
244 + queries = queries.squeeze(0)
245 + keys = keys.squeeze(0)
246 +
247 + max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item()
248 + if self.is_flash_attn_backend:
249 + if self.attn_backend == _Backend.ROCM_AITER_FA:
250 + from aiter import flash_attn_varlen_func
251 + else:
252 + from flash_attn import flash_attn_varlen_func
253 + attn_output = flash_attn_varlen_func(
254 + queries, keys, values, cu_seqlens, cu_seqlens, max_seqlen,
255 + max_seqlen).reshape(seq_length, -1)
256 + elif self.attn_backend == _Backend.TORCH_SDPA:
257 + # Execute attention entry by entry for speed & less VRAM.
258 + batch_size = cu_seqlens.shape[0] - 1
259 + outputs = []
260 + cu = cu_seqlens.tolist()
261 + for i in range(batch_size):
262 + start_idx = cu[i]
263 + end_idx = cu[i + 1]
264 +
265 + # Each sequence is processed independently.
266 + q_i = queries[start_idx:end_idx].unsqueeze(0)
267 + k_i = keys[start_idx:end_idx].unsqueeze(0)
268 + v_i = values[start_idx:end_idx].unsqueeze(0)
269 +
270 + # (1, seq_len, num_heads, head_dim) ->
271 + # (1, num_heads, seq_len, head_dim)
272 + q_i, k_i, v_i = [x.transpose(1, 2) for x in (q_i, k_i, v_i)]
273 +
274 + output_i = F.scaled_dot_product_attention(q_i,
275 + k_i,
276 + v_i,
277 + dropout_p=0.0)
278 + # (1, num_heads, seq_len, head_dim) -> (seq_len, embed_dim)
279 + output_i = output_i.transpose(1, 2).reshape(-1, self.embed_dim)
280 + outputs.append(output_i)
281 +
282 + attn_output = torch.cat(outputs, dim=0)
283 + attn_output = self.out_proj(attn_output)
284 + return attn_output
285 +
286 +
287 +class Siglip2MLP(nn.Module):
288 +
289 + def __init__(self, config):
290 + super().__init__()
291 + self.config = config
292 + self.activation_fn = ACT2FN[config.hidden_act]
293 + self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
294 + self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
295 +
296 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
297 + hidden_states = self.fc1(hidden_states)
298 + hidden_states = self.activation_fn(hidden_states)
299 + hidden_states = self.fc2(hidden_states)
300 + return hidden_states
301 +
302 +
303 +class Siglip2EncoderLayer(nn.Module):
304 +
305 + def __init__(self, config: PretrainedConfig):
306 + super().__init__()
307 + self.embed_dim = config.hidden_size
308 + self.layer_norm1 = nn.LayerNorm(self.embed_dim,
309 + eps=config.layer_norm_eps)
310 + self.self_attn = Siglip2Attention(config)
311 + self.layer_norm2 = nn.LayerNorm(self.embed_dim,
312 + eps=config.layer_norm_eps)
313 + self.mlp = Siglip2MLP(config)
314 +
315 + def forward(self, hidden_states: torch.Tensor, cu_seqlens: torch.Tensor,
316 + position_embeddings: torch.Tensor) -> tuple[torch.FloatTensor]:
317 + """
318 + Args:
319 + hidden_states (`torch.FloatTensor`):
320 + Input to the layer of shape `(batch, seq_len, embed_dim)`.
321 + output_attentions (`bool`, *optional*, defaults to `False`):
322 + Whether or not to return the attentions tensors of all
323 + attention layers. See `attentions` under
324 + returned tensors for more detail.
325 + """
326 + residual = hidden_states
327 +
328 + hidden_states = self.layer_norm1(hidden_states)
329 + hidden_states = self.self_attn(hidden_states=hidden_states,
330 + cu_seqlens=cu_seqlens,
331 + position_embeddings=position_embeddings)
332 + hidden_states = residual + hidden_states
333 +
334 + residual = hidden_states
335 + hidden_states = self.layer_norm2(hidden_states)
336 + hidden_states = self.mlp(hidden_states)
337 + hidden_states = residual + hidden_states
338 + return hidden_states
339 +
340 +
341 +class Siglip2Encoder(nn.Module):
342 + """
343 + Transformer encoder consisting of `config.num_hidden_layers`
344 + self attention layers. Each layer is a [`Siglip2EncoderLayer`].
345 +
346 + Args:
347 + config: PretrainedConfig
348 + """
349 +
350 + def __init__(self, config: PretrainedConfig):
351 + super().__init__()
352 + self.config = config
353 + self.layers = nn.ModuleList([
354 + Siglip2EncoderLayer(config)
355 + for _ in range(config.num_hidden_layers)
356 + ])
357 + self.gradient_checkpointing = False
358 +
359 + self.rotary_pos_emb = VisionRotaryEmbedding(
360 + config.hidden_size // config.num_attention_heads // 2)
361 + self.patch_size = config.patch_size
362 + self.hidden_stride = config.hidden_stride
363 + self.window_size = config.window_size
364 + self.spatial_merge_unit = config.hidden_stride * config.hidden_stride
365 + if config.fullatt_block_indexes is None:
366 + self.fullatt_block_indexes = None
367 + else:
368 + self.fullatt_block_indexes = [
369 + int(i) for i in config.fullatt_block_indexes.split('|')
370 + ]
371 +
372 + # copied from qwen2.5_vl
373 + def rot_pos_emb(self, grid_thw):
374 + pos_ids = []
375 + for t, h, w in grid_thw:
376 + hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
377 + hpos_ids = hpos_ids.reshape(
378 + h // self.hidden_stride,
379 + self.hidden_stride,
380 + w // self.hidden_stride,
381 + self.hidden_stride,
382 + )
383 + hpos_ids = hpos_ids.permute(0, 2, 1, 3)
384 + hpos_ids = hpos_ids.flatten()
385 +
386 + wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
387 + wpos_ids = wpos_ids.reshape(
388 + h // self.hidden_stride,
389 + self.hidden_stride,
390 + w // self.hidden_stride,
391 + self.hidden_stride,
392 + )
393 + wpos_ids = wpos_ids.permute(0, 2, 1, 3)
394 + wpos_ids = wpos_ids.flatten()
395 + pos_ids.append(
396 + torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
397 + pos_ids = torch.cat(pos_ids, dim=0)
398 + max_grid_size = grid_thw[:, 1:].max()
399 + rotary_pos_emb_full = self.rotary_pos_emb(max_grid_size)
400 + rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
401 + return rotary_pos_emb
402 +
403 + def get_window_index(self, grid_thw):
404 + window_index: list = []
405 + cu_window_seqlens: list = [0]
406 + window_index_id = 0
407 + # patch (after merge) number in each window
408 + vit_merger_window_size = (self.window_size // self.hidden_stride //
409 + self.patch_size)
410 +
411 + for grid_t, grid_h, grid_w in grid_thw:
412 + llm_grid_h, llm_grid_w = (
413 + grid_h // self.hidden_stride, # number of patch after merge
414 + grid_w // self.hidden_stride,
415 + )
416 + index = torch.arange(grid_t * llm_grid_h * llm_grid_w).reshape(
417 + grid_t, llm_grid_h, llm_grid_w)
418 + pad_h = vit_merger_window_size - llm_grid_h % vit_merger_window_size
419 + pad_w = vit_merger_window_size - llm_grid_w % vit_merger_window_size
420 + num_windows_h = (llm_grid_h + pad_h) // vit_merger_window_size
421 + num_windows_w = (llm_grid_w + pad_w) // vit_merger_window_size
422 + index_padded = F.pad(index, (0, pad_w, 0, pad_h), "constant", -100)
423 + index_padded = index_padded.reshape(
424 + grid_t,
425 + num_windows_h,
426 + vit_merger_window_size,
427 + num_windows_w,
428 + vit_merger_window_size,
429 + )
430 + index_padded = index_padded.permute(0, 1, 3, 2, 4).reshape(
431 + grid_t,
432 + num_windows_h * num_windows_w,
433 + vit_merger_window_size,
434 + vit_merger_window_size,
435 + )
436 + seqlens = (index_padded != -100).sum([2, 3]).reshape(-1)
437 + index_padded = index_padded.reshape(-1)
438 + index_new = index_padded[index_padded != -100]
439 + window_index.append(index_new + window_index_id)
440 + cu_seqlens_tmp = seqlens.cumsum(
441 + 0) * self.spatial_merge_unit + cu_window_seqlens[-1]
442 + cu_window_seqlens.extend(cu_seqlens_tmp.tolist())
443 + window_index_id += (grid_t * llm_grid_h * llm_grid_w).item()
444 + window_index = torch.cat(window_index, dim=0)
445 +
446 + return window_index, cu_window_seqlens
447 +
448 + # Ignore copy
449 + def forward(
450 + self,
451 + inputs_embeds,
452 + grid_thws: torch.Tensor,
453 + output_hidden_states: bool = False,
454 + ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, ...]]]:
455 + r"""
456 + Args:
457 + inputs_embeds (`torch.FloatTensor` of shape
458 + `(batch_size, sequence_length, hidden_size)`):
459 + Optionally, instead of passing `input_ids` you can choose to
460 + directly pass an embedded representation. This is useful if
461 + you want more control over how to convert `input_ids` indices
462 + into associated vectors than the model's internal embedding
463 + lookup matrix.
464 + grid_thws (`torch.LongTensor`):
465 + grid shape (num_patches, 3)
466 + output_hidden_states (`bool`, *optional*):
467 + Whether or not to return the hidden states of all layers. See
468 + `hidden_states` under returned tensors for more detail.
469 + return_dict (`bool`, *optional*):
470 + Whether or not to return a [`~utils.ModelOutput`] instead of
471 + a plain tuple.
472 + """
473 + rotary_pos_emb = self.rot_pos_emb(grid_thws)
474 + window_index, cu_window_seqlens = self.get_window_index(grid_thws)
475 + cu_window_seqlens = torch.tensor(
476 + cu_window_seqlens,
477 + device=inputs_embeds.device,
478 + dtype=grid_thws.dtype if torch.jit.is_tracing() else torch.int32,
479 + )
480 + cu_window_seqlens = torch.unique_consecutive(cu_window_seqlens)
481 +
482 + seq_len, _ = inputs_embeds.size()
483 + inputs_embeds = inputs_embeds.reshape(
484 + seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
485 + inputs_embeds = inputs_embeds[window_index, :, :]
486 + inputs_embeds = inputs_embeds.reshape(seq_len, -1)
487 + rotary_pos_emb = rotary_pos_emb.reshape(
488 + seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
489 + rotary_pos_emb = rotary_pos_emb[window_index, :, :]
490 + rotary_pos_emb = rotary_pos_emb.reshape(seq_len, -1)
491 + emb = torch.cat((rotary_pos_emb, rotary_pos_emb), dim=-1)
492 + position_embeddings = (emb.cos(), emb.sin())
493 +
494 + cu_seqlens = torch.repeat_interleave(
495 + grid_thws[:, 1] * grid_thws[:, 2], grid_thws[:, 0]
496 + ).cumsum(
497 + dim=0,
498 + # Select dtype based on the following factors:
499 + # - FA2 requires that cu_seqlens_q must have dtype int32
500 + # - torch.onnx.export requires that cu_seqlens_q must have
501 + # same dtype as grid_thw
502 + # See https://github.com/huggingface/transformers/pull/34852
503 + # for more information
504 + dtype=grid_thws.dtype if torch.jit.is_tracing() else torch.int32,
505 + )
506 + cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0)
507 +
508 + reverse_indices = torch.argsort(window_index)
509 + encoder_states = () if output_hidden_states else None
510 +
511 + hidden_states = inputs_embeds
512 + for index, block in enumerate(self.layers):
513 + if (not self.fullatt_block_indexes
514 + or index in self.fullatt_block_indexes):
515 + cu_seqlens_tmp = cu_seqlens
516 + else:
517 + cu_seqlens_tmp = cu_window_seqlens
518 + hidden_states = block(hidden_states, cu_seqlens_tmp,
519 + position_embeddings)
520 + if output_hidden_states:
521 + hidden_states_ = hidden_states.reshape(
522 + seq_len // self.spatial_merge_unit,
523 + self.spatial_merge_unit, -1)
524 + encoder_states += (hidden_states_[reverse_indices, :].reshape(
525 + seq_len, -1), )
526 + # tokens = self.post_trunk_norm(tokens)
527 + hidden_states = hidden_states.reshape(
528 + seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
529 + hidden_states = hidden_states[reverse_indices, :].reshape(seq_len, -1)
530 +
531 + return hidden_states, encoder_states
532 +
533 +
534 +class Siglip2VisionTransformer(nn.Module):
535 +
536 + def __init__(self, config: PretrainedConfig):
537 + super().__init__()
538 + self.config = config
539 + embed_dim = config.hidden_size
540 +
541 + self.embeddings = Siglip2VisionEmbeddings(config)
542 + self.encoder = Siglip2Encoder(config)
543 + self.post_layernorm = nn.LayerNorm(embed_dim,
544 + eps=config.layer_norm_eps)
545 + self._use_flash_attention_2 = \
546 + (config._attn_implementation == "flash_attention_2")
547 +
548 + def forward(
549 + self,
550 + pixel_values: torch.FloatTensor,
551 + grid_thws: torch.LongTensor,
552 + output_hidden_states: Optional[bool] = True,
553 + return_dict: Optional[bool] = True,
554 + ) -> Union[
555 + tuple[torch.Tensor],
556 + tuple[torch.Tensor, tuple[torch.Tensor, ...]],
557 + BaseModelOutputWithNoAttention,
558 + ]:
559 + r"""
560 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`):
561 + Tensor containing the spatial dimensions (height, width)
562 + of the input images.
563 + """
564 + hidden_states = self.embeddings(pixel_values, grid_thws)
565 +
566 + last_hidden_state, hidden_states = self.encoder(
567 + hidden_states, grid_thws, output_hidden_states)
568 + last_hidden_state = self.post_layernorm(last_hidden_state)
569 +
570 + if not return_dict:
571 + output = (last_hidden_state, )
572 + output += (hidden_states, ) if output_hidden_states else ()
573 + return output
574 +
575 + return last_hidden_state
576 +
577 +
578 +class Siglip2NavitModel(torch.nn.Module):
579 +
580 + def __init__(self, config: PretrainedConfig):
581 + super().__init__()
582 +
583 + self.vision_model = Siglip2VisionTransformer(config)
584 +
585 + def forward(
586 + self,
587 + pixel_values: torch.FloatTensor,
588 + grid_thws: torch.LongTensor,
589 + output_hidden_states: Optional[bool] = None,
590 + return_dict: Optional[bool] = None,
591 + ) -> Union[
592 + tuple[torch.Tensor],
593 + tuple[torch.Tensor, tuple[torch.Tensor, ...]],
594 + BaseModelOutputWithNoAttention,
595 + ]:
596 +
597 + if output_hidden_states is None:
598 + output_hidden_states = self.config.output_hidden_states
599 + if return_dict is None:
600 + return_dict = self.config.use_return_dict
601 +
602 + return self.vision_model(
603 + pixel_values=pixel_values,
604 + grid_thws=grid_thws,
605 + output_hidden_states=output_hidden_states,
606 + return_dict=return_dict,
607 + )
vllm/transformers_utils/processors/__init__.py
+2 -1
@@ -11,5 +11,6 @@ reasons:
11 from vllm.transformers_utils.processors.deepseek_vl2 import (
12 DeepseekVLV2Processor)
13 from vllm.transformers_utils.processors.ovis import OvisProcessor
14 +from vllm.transformers_utils.processors.ovis2_5 import Ovis2_5Processor
15
15 -__all__ = ["DeepseekVLV2Processor", "OvisProcessor"]
16 +__all__ = ["DeepseekVLV2Processor", "OvisProcessor", "Ovis2_5Processor"]
vllm/transformers_utils/processors/ovis2_5.py new
+458
@@ -0,0 +1,458 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +import math
4 +from functools import cached_property
5 +from typing import Optional, Union
6 +
7 +import numpy as np
8 +import PIL
9 +import torch
10 +from transformers import AutoProcessor, BatchFeature
11 +from transformers.image_utils import ImageInput
12 +from transformers.processing_utils import (ProcessingKwargs, ProcessorMixin,
13 + Unpack)
14 +from transformers.tokenization_utils_base import PreTokenizedInput, TextInput
15 +
16 +__all__ = ['Ovis2_5Processor']
17 +IMAGE_TOKEN = "<image>"
18 +VIDEO_TOKEN = "<video>"
19 +MIN_PIXELS = 448 * 448
20 +MAX_PIXELS = 1792 * 1792
21 +
22 +
23 +class Ovis2_5ProcessorKwargs(ProcessingKwargs,
24 + total=False): # type: ignore[call-arg]
25 + _defaults = {
26 + "text_kwargs": {
27 + "padding": False,
28 + },
29 + "images_kwargs": {
30 + 'convert_to_rgb': True,
31 + 'min_pixels': MIN_PIXELS,
32 + 'max_pixels': MAX_PIXELS,
33 + },
34 + "videos_kwargs": {
35 + 'convert_to_rgb': True,
36 + 'min_pixels': MIN_PIXELS,
37 + 'max_pixels': MAX_PIXELS,
38 + }
39 + }
40 +
41 +
42 +class Ovis2_5Processor(ProcessorMixin):
43 + r"""
44 + Constructs a Ovis processor which wraps a Ovis image processor
45 + and a Qwen2 tokenizer into a single processor.
46 + [`OvisProcessor`] offers all the functionalities of
47 + [`Qwen2VLImageProcessor`] and [`Qwen2TokenizerFast`].
48 + See the [`~OvisProcessor.__call__`] and [`~OvisProcessor.decode`]
49 + for more information.
50 + Args:
51 + image_processor ([`Qwen2VLImageProcessor`], *optional*):
52 + The image processor is a required input.
53 + tokenizer ([`Qwen2TokenizerFast`], *optional*):
54 + The tokenizer is a required input.
55 + chat_template (`str`, *optional*): A Jinja template which will
56 + be used to convert lists of messages in a chat into
57 + a tokenizable string.
58 + """
59 +
60 + attributes = ["image_processor", "tokenizer"]
61 + valid_kwargs = ["chat_template", "image_pad_token"]
62 +
63 + image_processor_class = "AutoImageProcessor"
64 + tokenizer_class = "AutoTokenizer"
65 +
66 + def __init__(
67 + self,
68 + image_processor=None,
69 + tokenizer=None,
70 + chat_template=None,
71 + image_pad_token=None,
72 + patch_size=16,
73 + hidden_stride=2,
74 + temporal_patch_size=1,
75 + **kwargs,
76 + ):
77 + self.image_token = IMAGE_TOKEN
78 + self.video_token = VIDEO_TOKEN
79 + self.image_pad_token = "<|image_pad|>"
80 +
81 + self.patch_size = patch_size
82 + self.hidden_stride = hidden_stride
83 + self.temporal_patch_size = temporal_patch_size
84 + super().__init__(image_processor,
85 + tokenizer,
86 + chat_template=chat_template)
87 +
88 + @cached_property
89 + def extra_special_tokens(self):
90 + image_pad_token_id = self.tokenizer.get_vocab()[self.image_pad_token]
91 + extra_special_tokens = {
92 + "image_token": -200,
93 + "video_token": -201,
94 + "visual_atom": -300,
95 + "image_start": -301,
96 + "image_end": -302,
97 + "video_start": -303,
98 + "video_end": -304,
99 + 'image_pad': image_pad_token_id,
100 + }
101 + return extra_special_tokens
102 +
103 + def __call__(
104 + self,
105 + images: ImageInput = None,
106 + videos: Union[np.ndarray, list[ImageInput]] = None,
107 + text: Union[TextInput, PreTokenizedInput, list[TextInput],
108 + list[PreTokenizedInput]] = None,
109 + **kwargs: Unpack[Ovis2_5ProcessorKwargs],
110 + ) -> BatchFeature:
111 + """
112 + Main method to prepare for the model one or several sequences(s)
113 + and image(s). This method forwards the `text`and `kwargs` arguments
114 + to Qwen2TokenizerFast's [`~Qwen2TokenizerFast.__call__`] if `text`
115 + is not `None` to encode the text. To prepare the vision inputs,
116 + this method forwards the `vision_infos` and `kwrags` arguments to
117 + Qwen2VLImageProcessor's [`~Qwen2VLImageProcessor.__call__`]
118 + if `vision_infos` is not `None`.
119 + Args:
120 + images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`,
121 + `list[PIL.Image.Image]`, `list[np.ndarray]`,
122 + `list[torch.Tensor]`):
123 + The image or batch of images to be prepared.
124 + Each image can be a PIL image, NumPy array or PyTorch
125 + tensor. Both channels-first and channels-last formats
126 + are supported.
127 + text (`str`, `list[str]`, `list[list[str]]`):
128 + The sequence or batch of sequences to be encoded.
129 + Each sequence can be a string or a list of strings
130 + (pretokenized string). If the sequences are provided as
131 + list of strings (pretokenized), you must set
132 + `is_split_into_words=True` (to lift the ambiguity with
133 + a batch of sequences).
134 + videos (`np.ndarray`, `torch.Tensor`, `list[np.ndarray]`,
135 + `list[torch.Tensor]`):
136 + The image or batch of videos to be prepared. Each video
137 + can be a 4D NumPy array or PyTorch tensor, or a nested
138 + list of 3D frames. Both channels-first and channels-last
139 + formats are supported.
140 + return_tensors (`str` or [`~utils.TensorType`], *optional*):
141 + If set, will return tensors of a particular framework.
142 + Acceptable values are:
143 + - `'tf'`: Return TensorFlow `tf.constant` objects.
144 + - `'pt'`: Return PyTorch `torch.Tensor` objects.
145 + - `'np'`: Return NumPy `np.ndarray` objects.
146 + - `'jax'`: Return JAX `jnp.ndarray` objects.
147 + Returns:
148 + [`BatchFeature`]: A [`BatchFeature`] with the following fields:
149 + - **input_ids** -- list of token ids to be fed to a model.
150 + Returned when `text` is not `None`.
151 + - **attention_mask** -- list of indices specifying which tokens
152 + should be attended to by the model (when
153 + `return_attention_mask=True` or if *"attention_mask"*
154 + is in `self.model_input_names` and if `text` is not `None`).
155 + - **pixel_values** -- Pixel values to be fed to a model.
156 + Returned when `images` is not `None`.
157 + - **pixel_values_videos** -- Pixel values of videos to be fed to
158 + a model. Returned when `videos` is not `None`.
159 + - **image_grid_thw** -- list of image 3D grid in LLM. Returned
160 + when `images` is not `None`.
161 + - **video_grid_thw** -- list of video 3D grid in LLM. Returned
162 + when `videos` is not `None`.
163 + - **second_per_grid_ts** -- list of video seconds per time grid.
164 + Returned when `videos` is not `None`.
165 + """
166 + output_kwargs = self._merge_kwargs(
167 + Ovis2_5ProcessorKwargs,
168 + tokenizer_init_kwargs=self.tokenizer.init_kwargs,
169 + **kwargs,
170 + )
171 + # Process all images first
172 + visual_features = {}
173 + output = BatchFeature()
174 + if images is not None:
175 + processed_images = []
176 + image_placeholders_list = []
177 + grids = []
178 + # Process each image
179 + for image in images if isinstance(images, list) else [images]:
180 + pixel_values, image_placeholders, grid = (
181 + self.preprocess_multidata(
182 + images=image, **output_kwargs["images_kwargs"]))
183 + processed_images.append(pixel_values)
184 + image_placeholders_list.append(image_placeholders)
185 + grids.append(grid)
186 +
187 + # assign all processed images
188 + if processed_images:
189 + visual_features["image_placeholders"] = image_placeholders_list
190 + output["pixel_values"] = processed_images
191 + output["grids"] = grids
192 +
193 + if videos is not None:
194 + processed_videos = []
195 + videos_placeholders_list = []
196 + grids = []
197 + # Process each video
198 + for video in videos if isinstance(videos, list) else [videos]:
199 + pixel_values, video_placeholders, grid = (
200 + self.preprocess_multidata(
201 + video=video, **output_kwargs["videos_kwargs"]))
202 + processed_videos.append(pixel_values)
203 + videos_placeholders_list.append(video_placeholders)
204 + grids.append(grid)
205 + # assign all processed videos
206 + if processed_videos:
207 + visual_features[
208 + "video_placeholders"] = videos_placeholders_list
209 + output["video_pixel_values"] = processed_videos
210 + output["video_grids"] = grids
211 +
212 + # Process text input
213 + if text is not None:
214 + if not isinstance(text, list):
215 + text = [text]
216 + tokenized_batched_text = self._tokenize_with_visual_symbol(text)
217 + image_token_id = self.get_token_value("image_token")
218 + video_token_id = self.get_token_value("video_token")
219 + replaced_ids_list = []
220 + image_idx = 0
221 + video_idx = 0
222 + for ids_tensor in tokenized_batched_text:
223 + has_image_tokens = (image_token_id in ids_tensor
224 + and "image_placeholders" in visual_features
225 + and image_idx < len(
226 + visual_features["image_placeholders"]))
227 + has_video_tokens = (video_token_id in ids_tensor
228 + and "video_placeholders" in visual_features
229 + and video_idx < len(
230 + visual_features["video_placeholders"]))
231 + if has_image_tokens or has_video_tokens:
232 + # Convert to list for easier manipulation
233 + ids_list = ids_tensor.tolist()
234 + new_ids = []
235 +
236 + # Replace placeholders
237 + for token_id in ids_list:
238 + if token_id == image_token_id:
239 + new_ids.extend(
240 + visual_features["image_placeholders"]
241 + [image_idx])
242 + image_idx += 1
243 + elif token_id == video_token_id:
244 + new_ids.extend(
245 + visual_features["video_placeholders"]
246 + [video_idx])
247 + video_idx += 1
248 + else:
249 + new_ids.append(token_id)
250 + # Convert back to tensor
251 + ids_tensor = torch.tensor(new_ids, dtype=torch.long)
252 + replaced_ids_list.append(ids_tensor)
253 + if replaced_ids_list:
254 + replaced_and_tokenized_ids = torch.stack(replaced_ids_list)
255 + else:
256 + replaced_and_tokenized_ids = torch.tensor([], dtype=torch.long)
257 + output["input_ids"] = replaced_and_tokenized_ids
258 +
259 + return output
260 + # If only images were provided
261 + return BatchFeature(data=visual_features)
262 +
263 + def _tokenize_with_visual_symbol(self,
264 + text_list: list[str]) -> torch.LongTensor:
265 + batch_token_ids = []
266 + for text in text_list:
267 + token_ids = []
268 + video_token_id = self.get_token_value("video_token")
269 + image_token_id = self.get_token_value("image_token")
270 + video_split_texts = text.split(self.video_token)
271 +
272 + for j, video_segment in enumerate(video_split_texts):
273 + image_split_texts = video_segment.split(self.image_token)
274 + text_chunks = [
275 + self.tokenizer(chunk, add_special_tokens=False).input_ids
276 + for chunk in image_split_texts
277 + ]
278 + segment_tokens = []
279 + for i, chunk in enumerate(text_chunks):
280 + segment_tokens.extend(chunk)
281 + if i < len(text_chunks) - 1:
282 + segment_tokens.append(image_token_id)
283 + token_ids.extend(segment_tokens)
284 + if j < len(video_split_texts) - 1:
285 + token_ids.append(video_token_id)
286 +
287 + batch_token_ids.append(token_ids)
288 + return torch.tensor(batch_token_ids, dtype=torch.long)
289 +
290 + # Copied from qwen2_vl
291 + def smart_resize(self,
292 + height: int,
293 + width: int,
294 + factor: int = 28,
295 + min_pixels: int = MIN_PIXELS,
296 + max_pixels: int = MAX_PIXELS):
297 + """Rescales the image so that the following conditions are met:
298 + 1. Both dimensions (height and width) are divisible by 'factor'.
299 + 2. The total number of pixels is within the range
300 + ['min_pixels', 'max_pixels'].
301 + 3. The aspect ratio of the image is maintained as closely as possible.
302 + """
303 + if height < factor or width < factor:
304 + print(f"height:{height} or width:{width} must be "
305 + f"larger than factor:{factor}")
306 + if height < width:
307 + width = round(factor / height * width)
308 + height = factor
309 + else:
310 + height = round(factor / width * height)
311 + width = factor
312 +
313 + elif max(height, width) / min(height, width) > 200:
314 + print(f"absolute aspect ratio must be smaller than 200, "
315 + f"got {max(height, width) / min(height, width)}")
316 + if height > width:
317 + height = 200 * width
318 + else:
319 + width = 200 * height
320 +
321 + h_bar = round(height / factor) * factor
322 + w_bar = round(width / factor) * factor
323 + if h_bar * w_bar > max_pixels:
324 + beta = math.sqrt((height * width) / max_pixels)
325 + h_bar = math.floor(height / beta / factor) * factor
326 + w_bar = math.floor(width / beta / factor) * factor
327 + elif h_bar * w_bar < min_pixels:
328 + beta = math.sqrt(min_pixels / (height * width))
329 + h_bar = math.ceil(height * beta / factor) * factor
330 + w_bar = math.ceil(width * beta / factor) * factor
331 + return h_bar, w_bar
332 +
333 + def get_token_value(self, tok):
334 + return self.extra_special_tokens[tok]
335 +
336 + def construct_visual_indicators(self, grid, is_video: bool = False):
337 + if is_video:
338 + start_token = self.get_token_value('video_start')
339 + end_token = self.get_token_value('video_end')
340 + else:
341 + start_token = self.get_token_value('image_start')
342 + end_token = self.get_token_value('image_end')
343 +
344 + image_placeholders = [start_token, self.get_token_value('visual_atom')]
345 + if grid[0] * grid[1] > 1:
346 + for r in range(grid[0]):
347 + for c in range(grid[1]):
348 + image_placeholders.append(
349 + self.get_token_value('visual_atom'))
350 +
351 + image_placeholders.append(end_token)
352 + return image_placeholders
353 +
354 + def construct_visual_placeholders(self, grid, is_video: bool = False):
355 + visual_placeholders = self.construct_visual_indicators((1, 1),
356 + is_video)
357 +
358 + image_atom_token_id = self.get_token_value('visual_atom')
359 + # Extract the padding token ID from tokenizer
360 + image_padding_token_id = self.get_token_value('image_pad')
361 +
362 + num_image_atoms = grid[0] * grid[1] * grid[2]
363 + num_image_atoms //= self.hidden_stride**2
364 + num_image_atoms //= self.temporal_patch_size
365 +
366 + # Create a new list with padding tokens inserted
367 + padded_placeholder_tokens = []
368 + for token in visual_placeholders:
369 + if token == image_atom_token_id:
370 + padded_placeholder_tokens.extend([image_padding_token_id] *
371 + num_image_atoms)
372 + else:
373 + padded_placeholder_tokens.append(image_padding_token_id)
374 + return padded_placeholder_tokens
375 +
376 + def preprocess_multidata(
377 + self,
378 + images: Optional[Union[PIL.Image.Image, list[PIL.Image.Image]]] = None,
379 + video: Optional[Union[list[PIL.Image.Image], np.ndarray]] = None,
380 + convert_to_rgb: Optional[bool] = True,
381 + min_pixels: int = MIN_PIXELS,
382 + max_pixels: int = MAX_PIXELS,
383 + return_tensors: Optional[str] = 'pt',
384 + ):
385 + is_video = False
386 + if images is not None:
387 + if not isinstance(images, list):
388 + images = [images]
389 + elif video is not None:
390 + is_video = True
391 + # type of vidoe in dummy_mm_data is np.ndarray
392 + if isinstance(video, np.ndarray):
393 + images = []
394 + for i in range(video.shape[0]):
395 + image = PIL.Image.fromarray(video[i].astype(np.uint8))
396 + images.append(image)
397 + elif isinstance(video, list):
398 + images = video
399 + min_pixels = min(max_pixels if max_pixels is not None else MAX_PIXELS,
400 + min_pixels if min_pixels is not None else MIN_PIXELS)
401 + images = [
402 + image.convert("RGB")
403 + if convert_to_rgb and image.mode != 'RGB' else image
404 + for image in images
405 + ]
406 +
407 + width, height = images[0].size
408 + resized_height, resized_width = height, width
409 + processed_images = []
410 + for image in images:
411 + resized_height, resized_width = self.smart_resize(
412 + height,
413 + width,
414 + factor=self.patch_size * self.hidden_stride,
415 + min_pixels=min_pixels,
416 + max_pixels=max_pixels,
417 + )
418 + new_size = dict(height=resized_height, width=resized_width)
419 + image_pt = self.image_processor.preprocess(
420 + image, size=new_size, return_tensors="np")['pixel_values'][0]
421 +
422 + processed_images.append(image_pt)
423 +
424 + patches = np.array(processed_images)
425 + if patches.shape[0] % self.temporal_patch_size != 0:
426 + num_to_pad = self.temporal_patch_size - (patches.shape[0] %
427 + self.temporal_patch_size)
428 + repeats = np.repeat(patches[-1][np.newaxis], num_to_pad, axis=0)
429 + patches = np.concatenate([patches, repeats], axis=0)
430 + channel = patches.shape[1]
431 + grid_t = patches.shape[0] // self.temporal_patch_size
432 + grid_h = resized_height // self.patch_size
433 + grid_w = resized_width // self.patch_size
434 +
435 + patches = patches.reshape(
436 + grid_t,
437 + self.temporal_patch_size,
438 + channel,
439 + grid_h // self.hidden_stride,
440 + self.hidden_stride,
441 + self.patch_size,
442 + grid_w // self.hidden_stride,
443 + self.hidden_stride,
444 + self.patch_size,
445 + )
446 + patches = patches.transpose(0, 3, 6, 4, 7, 2, 1, 5, 8)
447 + flatten_patches = patches.reshape(
448 + grid_t * grid_h * grid_w, channel * self.temporal_patch_size *
449 + self.patch_size * self.patch_size)
450 +
451 + visual_placeholders = self.construct_visual_placeholders(
452 + [grid_t, grid_h, grid_w], is_video)
453 + return torch.tensor(
454 + flatten_patches), visual_placeholders, torch.tensor(
455 + [[grid_t, grid_h, grid_w]])
456 +
457 +
458 +AutoProcessor.register("Ovis2_5Processor", Ovis2_5Processor)