main
py 550 lines 19.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # adapted from https://github.com/huggingface/transformers/blob/v4.39.3/src/transformers/models/ovis/modeling_ovis.py
5 # Copyright 2023 The vLLM team.
6 # Copyright 2023 HuggingFace Inc. team. All rights reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License");
9 # you may not use this file except in compliance with the License.
10 # You may obtain a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
19 """PyTorch Ovis model."""
20
21 import math
22 from collections.abc import Iterable, Mapping
23 from typing import Annotated, Literal
24
25 import torch
26 import torch.nn as nn
27 from torch import Tensor
28 from torch.nn.functional import gumbel_softmax, pad, softmax
29 from transformers import BatchFeature, PreTrainedConfig
30
31 from vllm.config import VllmConfig
32 from vllm.config.multimodal import MultiModalDummyOptions
33 from vllm.inputs import MultiModalDataDict
34 from vllm.model_executor.layers.linear import ReplicatedLinear
35 from vllm.model_executor.layers.quantization import QuantizationConfig
36 from vllm.model_executor.models.aimv2 import AIMv2Model
37 from vllm.model_executor.models.siglip import SiglipVisionModel
38 from vllm.model_executor.models.utils import (
39 AutoWeightsLoader,
40 flatten_bn,
41 init_vllm_registered_model,
42 maybe_prefix,
43 )
44 from vllm.multimodal import MULTIMODAL_REGISTRY
45 from vllm.multimodal.inputs import (
46 MultiModalFieldConfig,
47 MultiModalKwargsItems,
48 )
49 from vllm.multimodal.parse import ImageSize, MultiModalDataItems
50 from vllm.multimodal.processing import (
51 BaseDummyInputsBuilder,
52 BaseMultiModalProcessor,
53 BaseProcessingInfo,
54 PromptReplacement,
55 cached_encode,
56 )
57 from vllm.renderers import TokenizeParams
58 from vllm.sequence import IntermediateTensors
59 from vllm.transformers_utils.processors.ovis import OvisProcessor
60 from vllm.utils.tensor_schema import TensorSchema, TensorShape
61
62 from .interfaces import (
63 MultiModalEmbeddings,
64 SupportsMultiModal,
65 SupportsPP,
66 supports_pp,
67 )
68
69 # Cannot find the following number from hf config.
70 IMAGE_TOKEN = "<image>"
71 IMAGE_INDICATOR_IDS = [-301, -302, -303, -304, -305]
72
73 IMAGE_PAD_TOKEN_MAP = {
74 "gemma2": "<unused0>",
75 "llama": "<|reserved_special_token_0|>",
76 "qwen2": "<|image_pad|>",
77 }
78 IMAGE_PAD_TOKEN_ID_MAP = {
79 "gemma2": 7,
80 "llama": 128002,
81 "qwen2": 151655,
82 }
83
84
85 def st_argmax(y_soft: torch.Tensor, dim: int): # straight-through softmax
86 index = y_soft.argmax(dim, keepdim=True)
87 return torch.zeros_like(
88 y_soft,
89 memory_format=torch.legacy_contiguous_format,
90 ).scatter_(dim, index, 1.0)
91
92
93 class VisualTokenizer(torch.nn.Module):
94 def __init__(
95 self,
96 config: PreTrainedConfig,
97 quant_config: QuantizationConfig | None = None,
98 prefix: str = "",
99 ):
100 super().__init__()
101 self.config = config
102 self.backbone = self._init_backbone(
103 config=config,
104 quant_config=quant_config,
105 prefix=maybe_prefix(prefix, "backbone"),
106 )
107 # reserved tokens for IMAGE_INDICATORS
108 head_dim = config.vocab_size - len(IMAGE_INDICATOR_IDS)
109 self.head = torch.nn.Sequential(
110 ReplicatedLinear(
111 config.backbone_config.hidden_size
112 * config.hidden_stride
113 * config.hidden_stride,
114 head_dim,
115 bias=False,
116 return_bias=False,
117 ),
118 torch.nn.LayerNorm(head_dim),
119 )
120
121 def _init_backbone(
122 self,
123 config: PreTrainedConfig,
124 quant_config: QuantizationConfig | None = None,
125 prefix: str = "",
126 ) -> nn.Module:
127 model_type = config.backbone_config.model_type
128 if model_type == "aimv2":
129 # No post rms_norm in Ovis2's AIMv2 ViT.
130 return AIMv2Model(
131 config=config.backbone_config,
132 quant_config=quant_config,
133 require_post_norm=False,
134 prefix=prefix,
135 )
136 elif model_type == "siglip_vision_model":
137 return SiglipVisionModel(
138 config=config.backbone_config,
139 quant_config=quant_config,
140 prefix=prefix,
141 )
142 raise ValueError(f"Unsupported visual tokenizer model_type: {model_type}")
143
144 @property
145 def dtype(self) -> torch.dtype:
146 return next(self.head.parameters()).dtype
147
148 @property
149 def device(self) -> torch.device:
150 return next(self.head.parameters()).device
151
152 def tokenize(self, logits: torch.Tensor) -> torch.Tensor:
153 if self.config.tokenize_function == "softmax":
154 tokens = softmax(logits, dim=-1)
155 elif self.config.tokenize_function == "gumbel_argmax":
156 tokens = gumbel_softmax(logits, tau=self.config.tau, hard=True)
157 elif self.config.tokenize_function == "st_argmax":
158 tokens = st_argmax(logits, dim=-1)
159 else:
160 raise ValueError(
161 "Invalid `max_type`, expected softmax or gumbel_argmax "
162 f"or st_argmax, but got {self.config.tokenize_function}"
163 )
164 return tokens
165
166 def encode(self, pixel_values: torch.Tensor) -> torch.Tensor:
167 features = self.backbone(pixel_values)
168 if self.config.drop_cls_token:
169 features = features[:, 1:, :]
170
171 # merge number of `hidden_stride * hidden_stride` hidden states together
172 # to reduce token sequence length
173 # e.g., for hidden_stride=2, this leads to a token length reduction:
174 # 1024 -> 256 for aimv2
175 if self.config.hidden_stride > 1:
176 # this `d` maybe different from the above `d`
177 n, L, d = features.shape
178 sqrt_l = int(L**0.5)
179 assert sqrt_l**2 == L, (
180 "The token sequence length should be a perfect square."
181 )
182 features = features.reshape(n, sqrt_l, sqrt_l, d)
183 pl = (
184 self.config.hidden_stride - (sqrt_l % self.config.hidden_stride)
185 ) % self.config.hidden_stride
186 features = pad(features, (0, 0, 0, pl, 0, pl), "constant", 0)
187 sqrt_l += pl
188 features = features.reshape(
189 n,
190 sqrt_l // self.config.hidden_stride,
191 self.config.hidden_stride,
192 sqrt_l // self.config.hidden_stride,
193 self.config.hidden_stride,
194 d,
195 )
196 # [n, sqrt_l/hs, sqrt_l/hs, hs, hs, d]
197 features = features.permute(0, 1, 3, 2, 4, 5)
198 # [n, sqrt_l/hs, sqrt_l/hs, hs*hs*d]
199 features = features.flatten(3)
200 # [n, sqrt_l/hs*sqrt_l/hs, hs*hs*d]
201 features = features.reshape(
202 n, -1, self.config.hidden_stride * self.config.hidden_stride * d
203 )
204
205 return features
206
207 def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
208 """[BatchSize, ImageShape] -> [BatchSize, Token, VocabSize]."""
209 features = self.encode(pixel_values)
210 logits = self.head(features)
211 tokens = self.tokenize(logits)
212 # tokens' shape is [BatchSize, #Token, VocabSize-5], so padding with
213 # [BatchSize, #Token, 5], after which, tokens' shape should become
214 # [BatchSize, #Token, VocabSize]
215 tokens = torch.nn.functional.pad(
216 tokens,
217 (0, len(IMAGE_INDICATOR_IDS)),
218 mode="constant",
219 value=0,
220 )
221 return tokens
222
223
224 class OvisImagePatchInputs(TensorSchema):
225 """Dimensions:
226 - bnp: Batch size * number of images * number of patches
227 - h: Height of each patch
228 - w: Width of each patch
229 - patch_indicators: Batch size * (number of patches + 1)
230 - bn: Batch size * number of images
231 """
232
233 type: Literal["image_patches"]
234 flat_data: Annotated[torch.Tensor, TensorShape("bnp", 3, "h", "w")]
235 indicator_tokens: Annotated[torch.Tensor, TensorShape("patch_indicators")]
236 patches_per_image: Annotated[list[int], TensorShape("bn")]
237 # This is used to restore the first two dimensions of `flat_data`.
238
239
240 class VisualEmbedding(torch.nn.Embedding):
241 def __init__(self, *args, **kwargs):
242 super().__init__(*args, **kwargs)
243
244 def forward(self, visual_tokens: Tensor) -> Tensor:
245 if visual_tokens.dtype in [
246 torch.int8,
247 torch.int16,
248 torch.int32,
249 torch.int64,
250 torch.long,
251 ]:
252 return super().forward(visual_tokens)
253 return torch.matmul(visual_tokens, self.weight)
254
255 @property
256 def device(self):
257 return self.weight.device
258
259 @property
260 def dtype(self):
261 return self.weight.dtype
262
263
264 class OvisProcessingInfo(BaseProcessingInfo):
265 def get_hf_processor(self, **kwargs: object):
266 return self.ctx.get_hf_processor(
267 OvisProcessor,
268 image_pad_token=self.get_image_pad_token(),
269 image_segment_len=self.get_image_segment_len(),
270 **kwargs,
271 )
272
273 def get_default_tok_params(self) -> TokenizeParams:
274 return super().get_default_tok_params().with_kwargs(add_special_tokens=False)
275
276 def get_image_segment_len(self) -> int:
277 visual_tokenizer_config = self.get_hf_config().visual_tokenizer_config
278 image_size = visual_tokenizer_config.backbone_config.image_size
279 patch_size = visual_tokenizer_config.backbone_config.patch_size
280 hidden_stride = visual_tokenizer_config.hidden_stride
281 patch_grid_length = math.ceil(image_size / patch_size)
282 assert patch_grid_length % hidden_stride == 0, (
283 f"patch_grid_length {patch_grid_length} is not divisible by "
284 f"hidden_stride {hidden_stride}"
285 )
286 # minus 1 for presented image token
287 return (patch_grid_length // hidden_stride) ** 2 - 1
288
289 def get_image_pad_token(self) -> str | None:
290 hf_text_config = self.get_hf_config().get_text_config()
291 text_model_type = hf_text_config.model_type
292 return IMAGE_PAD_TOKEN_MAP.get(text_model_type)
293
294 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
295 return {"image": None}
296
297 def get_image_size_with_most_features(self) -> ImageSize:
298 height, width = self.get_hf_processor().get_image_size()
299 hs = self.get_hf_config().visual_tokenizer_config.hidden_stride
300 # NOTE(Isotr0py): 9 is `max_partition` hardcoded in original code
301 # https://huggingface.co/AIDC-AI/Ovis2-1B/blob/main/modeling_ovis.py#L96
302 return ImageSize(width=width * hs * 9, height=height * hs * 9)
303
304
305 class OvisDummyInputsBuilder(BaseDummyInputsBuilder[OvisProcessingInfo]):
306 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
307 num_images = mm_counts.get("image", 0)
308 return IMAGE_TOKEN * num_images
309
310 def get_dummy_mm_data(
311 self,
312 seq_len: int,
313 mm_counts: Mapping[str, int],
314 mm_options: MultiModalDummyOptions,
315 ) -> MultiModalDataDict:
316 target_width, target_height = self.info.get_image_size_with_most_features()
317
318 mm_data = {
319 "image": self._get_dummy_images(
320 width=target_width,
321 height=target_height,
322 num_images=mm_counts.get("image", 0),
323 overrides=mm_options.get("image"),
324 ),
325 }
326 return mm_data
327
328
329 class OvisMultiModalProcessor(BaseMultiModalProcessor[OvisProcessingInfo]):
330 def _get_hf_mm_text(self, mm_counts: Mapping[str, int]) -> str:
331 return self.dummy_inputs.get_dummy_text(mm_counts)
332
333 def image_indicators_to_visual_tokens(
334 self,
335 image_indicators: list[int],
336 ) -> list[int]:
337 """Filter image indicators placeholders and convert them to corresponding
338 tokens in visual tokenizer.
339 For example, [-301, -300, -302, -300, -303, -300, -304, -300, -305]
340 should return [vocab_size-1, vocab_size-2, ..., vocab_size-5]
341 """
342 hf_config = self.info.get_hf_config()
343 vte_vocab_size = hf_config.visual_tokenizer_config.vocab_size
344 # -300 is image_atom token, filter them out
345 return [vte_vocab_size + x + 300 for x in image_indicators if x < -300]
346
347 def _postprocess_hf_mm_data(
348 self,
349 mm_data: Mapping[str, object],
350 hf_processor_mm_kwargs: Mapping[str, object],
351 processed_data: BatchFeature,
352 ) -> BatchFeature:
353 if not mm_data:
354 return processed_data
355
356 hf_processor = self.info.get_hf_processor()
357 image_indicators = [
358 hf_processor.construct_image_indicators(grid)
359 for grid in processed_data["grids"]
360 ]
361 indicator_tokens = [
362 self.image_indicators_to_visual_tokens(indicator)
363 for indicator in image_indicators
364 ]
365 processed_data["indicator_tokens"] = torch.tensor(indicator_tokens)
366
367 return processed_data
368
369 def _get_mm_fields_config(
370 self,
371 hf_inputs: BatchFeature,
372 hf_processor_mm_kwargs: Mapping[str, object],
373 ) -> Mapping[str, MultiModalFieldConfig]:
374 return dict(
375 pixel_values=MultiModalFieldConfig.batched("image"),
376 grids=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
377 indicator_tokens=MultiModalFieldConfig.batched("image"),
378 )
379
380 def _get_prompt_updates(
381 self,
382 mm_items: MultiModalDataItems,
383 hf_processor_mm_kwargs: Mapping[str, object],
384 out_mm_kwargs: MultiModalKwargsItems,
385 ) -> list[PromptReplacement]:
386 tokenizer = self.info.get_tokenizer()
387
388 def get_replacement_ovis(item_idx: int):
389 out_item = out_mm_kwargs["image"][item_idx]
390 grid = out_item["grids"].data
391
392 hf_processor = self.info.get_hf_processor()
393 return hf_processor.construct_image_placeholders(grid)
394
395 return [
396 PromptReplacement(
397 modality="image",
398 target=cached_encode(tokenizer, IMAGE_TOKEN, add_special_tokens=False),
399 replacement=get_replacement_ovis,
400 ),
401 ]
402
403
404 @MULTIMODAL_REGISTRY.register_processor(
405 OvisMultiModalProcessor,
406 info=OvisProcessingInfo,
407 dummy_inputs=OvisDummyInputsBuilder,
408 )
409 class Ovis(nn.Module, SupportsMultiModal, SupportsPP):
410 @classmethod
411 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
412 if modality.startswith("image"):
413 return IMAGE_TOKEN
414
415 raise ValueError("Only image modality is supported")
416
417 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
418 super().__init__()
419 config = vllm_config.model_config.hf_config
420 quant_config = vllm_config.quant_config
421
422 self.config: PreTrainedConfig = config
423
424 with self._mark_language_model(vllm_config):
425 self.llm = init_vllm_registered_model(
426 vllm_config=vllm_config.with_hf_config(config.get_text_config()),
427 prefix=maybe_prefix(prefix, "llm"),
428 )
429
430 with self._mark_tower_model(vllm_config, "image"):
431 self.visual_tokenizer = VisualTokenizer(
432 config=config.visual_tokenizer_config,
433 quant_config=quant_config,
434 prefix=maybe_prefix(prefix, "visual_tokenizer"),
435 )
436 self.vte = VisualEmbedding(
437 self.config.visual_tokenizer_config.vocab_size, self.config.hidden_size
438 )
439
440 text_model_type = self.config.get_text_config().model_type
441 self.image_pad_token_id = IMAGE_PAD_TOKEN_ID_MAP[text_model_type]
442
443 language_model = self.get_language_model()
444 assert supports_pp(language_model)
445 self.make_empty_intermediate_tensors = (
446 language_model.make_empty_intermediate_tensors
447 )
448
449 def _parse_and_validate_image_input(
450 self, **kwargs: object
451 ) -> OvisImagePatchInputs | None:
452 pixel_values = kwargs.pop("pixel_values", None)
453 indicator_tokens = kwargs.pop("indicator_tokens", None)
454
455 if pixel_values is None and indicator_tokens is None:
456 return None
457
458 if pixel_values is not None and indicator_tokens is not None:
459 if not isinstance(pixel_values, (torch.Tensor, list)):
460 raise ValueError(
461 f"Incorrect type of pixel values. Got type: {type(pixel_values)}"
462 )
463
464 if not isinstance(indicator_tokens, (torch.Tensor, list)):
465 raise ValueError(
466 "Incorrect type of indicator_tokens. "
467 f"Got type: {type(pixel_values)}"
468 )
469
470 return OvisImagePatchInputs(
471 type="image_patches",
472 flat_data=flatten_bn(pixel_values, concat=True),
473 patches_per_image=[x.shape[0] for x in pixel_values],
474 indicator_tokens=flatten_bn(indicator_tokens, concat=True),
475 )
476
477 raise AssertionError("This line should be unreachable.")
478
479 def _process_image_input(
480 self, image_input: OvisImagePatchInputs
481 ) -> MultiModalEmbeddings:
482 image_patches_flat = image_input["flat_data"]
483 patches_per_image = image_input["patches_per_image"]
484 indicator_tokens = image_input["indicator_tokens"]
485
486 indicator_per_image = list(
487 map(lambda x: x + 1 if x > 1 else x + 2, patches_per_image)
488 )
489
490 target_dtype = self.visual_tokenizer.dtype
491 visual_tokens = self.visual_tokenizer(image_patches_flat.to(target_dtype))
492 visual_embeds = self.vte(visual_tokens) # 1:1 numeric eq.
493
494 indicator_embeds = self.vte(indicator_tokens)
495 indicator_embeds_per_image = indicator_embeds.split(indicator_per_image)
496
497 visual_embeds_per_image = visual_embeds.split(patches_per_image, dim=0)
498 vision_embeddings = []
499 for indicator, visual in zip(
500 indicator_embeds_per_image, visual_embeds_per_image
501 ):
502 vision_embeddings_per_image = []
503 for i in range(visual.shape[0]):
504 vision_embeddings_per_image.append(
505 torch.cat([indicator[i : i + 1], visual[i]], dim=0)
506 )
507 vision_embeddings_per_image.append(indicator[i + 1 :])
508 vision_embeddings.append(torch.cat(vision_embeddings_per_image, dim=0))
509
510 return tuple(vision_embeddings)
511
512 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
513 image_input = self._parse_and_validate_image_input(**kwargs)
514 if image_input is None:
515 return []
516
517 image_features = self._process_image_input(image_input)
518
519 return image_features
520
521 def forward(
522 self,
523 input_ids: torch.Tensor | None,
524 positions: torch.Tensor,
525 intermediate_tensors: IntermediateTensors | None = None,
526 inputs_embeds: torch.Tensor | None = None,
527 **kwargs: object,
528 ) -> torch.Tensor | IntermediateTensors:
529 if intermediate_tensors is not None:
530 inputs_embeds = None
531
532 # up until here we have an inputs_embeds 100% numerical identity
533 # between the OG HF Transformers implementation and ours
534 hidden_states = self.llm(
535 input_ids=input_ids,
536 positions=positions,
537 intermediate_tensors=intermediate_tensors,
538 inputs_embeds=inputs_embeds,
539 )
540 return hidden_states
541
542 def compute_logits(
543 self,
544 hidden_states: torch.Tensor,
545 ) -> torch.Tensor | None:
546 return self.llm.compute_logits(hidden_states)
547
548 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
549 loader = AutoWeightsLoader(self)
550 return loader.load_weights(weights)