main
py 407 lines 12.9 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections.abc import Iterable, Mapping, Sequence
4 from typing import Annotated, Literal, TypeAlias
5
6 import torch
7 from torch import nn
8 from transformers import BatchFeature, PaliGemmaConfig
9
10 from vllm.config import VllmConfig
11 from vllm.config.multimodal import MultiModalDummyOptions
12 from vllm.inputs import MultiModalDataDict, MultiModalInput
13 from vllm.logger import init_logger
14 from vllm.multimodal import MULTIMODAL_REGISTRY
15 from vllm.multimodal.inputs import (
16 MultiModalFieldConfig,
17 MultiModalKwargsItem,
18 MultiModalKwargsItems,
19 )
20 from vllm.multimodal.parse import (
21 ImageEmbeddingItems,
22 ImageProcessorItems,
23 MultiModalDataItems,
24 )
25 from vllm.multimodal.processing import (
26 BaseDummyInputsBuilder,
27 BaseMultiModalProcessor,
28 BaseProcessingInfo,
29 ProcessorInputs,
30 PromptIndexTargets,
31 PromptInsertion,
32 PromptUpdate,
33 PromptUpdateDetails,
34 TimingContext,
35 )
36 from vllm.renderers import TokenizeParams
37 from vllm.sequence import IntermediateTensors
38 from vllm.utils.tensor_schema import TensorSchema, TensorShape
39
40 from .interfaces import (
41 MultiModalEmbeddings,
42 SupportsLoRA,
43 SupportsMultiModal,
44 SupportsPP,
45 )
46 from .module_mapping import MultiModelKeys
47 from .siglip import SiglipVisionModel
48 from .utils import (
49 AutoWeightsLoader,
50 WeightsMapper,
51 init_vllm_registered_model,
52 maybe_prefix,
53 )
54 from .vision import get_vision_encoder_info
55
56 logger = init_logger(__name__)
57
58
59 class PaliGemmaImagePixelInputs(TensorSchema):
60 """Dimensions:
61 - bn: Batch size * number of images
62 - c: Number of channels (3)
63 - h: Height
64 - w: Width
65 """
66
67 type: Literal["pixel_values"] = "pixel_values"
68 data: Annotated[torch.Tensor, TensorShape("bn", 3, "h", "w")]
69
70
71 class PaliGemmaImageEmbeddingInputs(TensorSchema):
72 """Dimensions:
73 - bn: Batch size * number of images
74 - ifs: Image feature size
75 - hs: Hidden size (must match language model backbone)
76 """
77
78 type: Literal["image_embeds"] = "image_embeds"
79 data: Annotated[torch.Tensor, TensorShape("bn", "ifs", "hs")]
80
81
82 PaliGemmaImageInputs: TypeAlias = (
83 PaliGemmaImagePixelInputs | PaliGemmaImageEmbeddingInputs
84 )
85
86
87 class PaliGemmaMultiModalProjector(nn.Module):
88 def __init__(self, vision_hidden_size: int, projection_dim: int):
89 super().__init__()
90
91 self.linear = nn.Linear(vision_hidden_size, projection_dim, bias=True)
92
93 def forward(self, image_features: torch.Tensor) -> torch.Tensor:
94 hidden_states = self.linear(image_features)
95 return hidden_states
96
97
98 class PaliGemmaProcessingInfo(BaseProcessingInfo):
99 def get_hf_config(self):
100 return self.ctx.get_hf_config(PaliGemmaConfig)
101
102 def get_vision_encoder_info(self):
103 return get_vision_encoder_info(self.get_hf_config())
104
105 def get_default_tok_params(self) -> TokenizeParams:
106 return super().get_default_tok_params().with_kwargs(add_special_tokens=False)
107
108 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
109 return {"image": 1}
110
111 def get_num_image_tokens(
112 self,
113 *,
114 image_width: int,
115 image_height: int,
116 ) -> int:
117 vision_encoder_info = self.get_vision_encoder_info()
118
119 return vision_encoder_info.get_num_image_tokens(
120 image_width=image_width,
121 image_height=image_height,
122 )
123
124
125 class PaliGemmaDummyInputsBuilder(BaseDummyInputsBuilder[PaliGemmaProcessingInfo]):
126 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
127 return ""
128
129 def get_dummy_mm_data(
130 self,
131 seq_len: int,
132 mm_counts: Mapping[str, int],
133 mm_options: MultiModalDummyOptions,
134 ) -> MultiModalDataDict:
135 hf_config = self.info.get_hf_config()
136 vision_config = hf_config.vision_config
137 max_image_size = vision_config.image_size
138
139 return {
140 "image": self._get_dummy_images(
141 width=max_image_size,
142 height=max_image_size,
143 num_images=mm_counts.get("image", 0),
144 overrides=mm_options.get("image"),
145 )
146 }
147
148
149 class PaliGemmaMultiModalProcessor(BaseMultiModalProcessor[PaliGemmaProcessingInfo]):
150 def _get_mm_fields_config(
151 self,
152 hf_inputs: BatchFeature,
153 hf_processor_mm_kwargs: Mapping[str, object],
154 ) -> Mapping[str, MultiModalFieldConfig]:
155 return dict(pixel_values=MultiModalFieldConfig.batched("image"))
156
157 def _get_prompt_updates(
158 self,
159 mm_items: MultiModalDataItems,
160 hf_processor_mm_kwargs: Mapping[str, object],
161 out_mm_kwargs: MultiModalKwargsItems,
162 ) -> Sequence[PromptUpdate]:
163 hf_config = self.info.get_hf_config()
164 image_token_id = hf_config.image_token_index
165
166 tokenizer = self.info.get_tokenizer()
167
168 bos_token_id = tokenizer.bos_token_id
169 assert isinstance(bos_token_id, int)
170
171 def get_insertion(item_idx: int):
172 images = mm_items.get_items(
173 "image", (ImageEmbeddingItems, ImageProcessorItems)
174 )
175
176 if isinstance(images, ImageEmbeddingItems):
177 num_image_tokens = images.get_feature_size(item_idx)
178 else:
179 assert isinstance(images, ImageProcessorItems)
180 image_size = images.get_image_size(item_idx)
181 num_image_tokens = self.info.get_num_image_tokens(
182 image_width=image_size.width,
183 image_height=image_size.height,
184 )
185
186 image_tokens = [image_token_id] * num_image_tokens
187
188 return PromptUpdateDetails.select_token_id(
189 image_tokens + [bos_token_id],
190 embed_token_id=image_token_id,
191 )
192
193 # Paligemma 1 and 2 have different tokenizer.add_bos_token
194 # Insert <image>*n + <bos> after <bos> for Paligemma 1
195 # Insert <image>*n + <bos> for Paligemma 2
196 add_bos_token = getattr(tokenizer, "add_bos_token", None)
197 assert isinstance(add_bos_token, bool)
198 return [
199 PromptInsertion(
200 modality="image",
201 target=PromptIndexTargets.prefix(
202 [bos_token_id] if add_bos_token else []
203 ),
204 insertion=get_insertion,
205 )
206 ]
207
208 def apply(
209 self,
210 inputs: ProcessorInputs,
211 timing_ctx: TimingContext,
212 ) -> MultiModalInput:
213 mm_inputs = super().apply(inputs, timing_ctx)
214 prompt_token_ids = mm_inputs["prompt_token_ids"]
215
216 tokenizer = self.info.get_tokenizer()
217 vocab = tokenizer.get_vocab()
218 newline_token_id = vocab["\n"]
219
220 # Force to add newline at the end of prompt for paligemma's format
221 # This step can NOT be replacemented by current PromptUpdate methods
222 if len(prompt_token_ids) and prompt_token_ids[-1] != newline_token_id:
223 prompt_token_ids.append(newline_token_id)
224 mm_inputs["prompt_token_ids"] = prompt_token_ids
225
226 return mm_inputs
227
228
229 @MULTIMODAL_REGISTRY.register_processor(
230 PaliGemmaMultiModalProcessor,
231 info=PaliGemmaProcessingInfo,
232 dummy_inputs=PaliGemmaDummyInputsBuilder,
233 )
234 class PaliGemmaForConditionalGeneration(
235 nn.Module, SupportsLoRA, SupportsMultiModal, SupportsPP
236 ):
237 packed_modules_mapping = {
238 "qkv_proj": [
239 "q_proj",
240 "k_proj",
241 "v_proj",
242 ],
243 "gate_up_proj": [
244 "gate_proj",
245 "up_proj",
246 ],
247 }
248
249 hf_to_vllm_mapper = WeightsMapper(
250 orig_to_new_prefix={
251 # mapping for new names in checkpoint saved after transformers v4.52
252 "model.language_model.": "language_model.model.",
253 "model.vision_tower.": "vision_tower.",
254 "model.multi_modal_projector.": "multi_modal_projector.",
255 "lm_head.": "language_model.lm_head.",
256 }
257 )
258
259 supports_tower_connector_lora = True
260
261 @classmethod
262 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
263 if modality.startswith("image"):
264 return None
265
266 raise ValueError("Only image modality is supported")
267
268 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
269 super().__init__()
270 config = vllm_config.model_config.hf_config
271 quant_config = vllm_config.quant_config
272 multimodal_config = vllm_config.model_config.multimodal_config
273 self.config = config
274 self.multimodal_config = multimodal_config
275 self.quant_config = quant_config
276
277 with self._mark_tower_model(vllm_config, "image"):
278 self.vision_tower = SiglipVisionModel(
279 config.vision_config,
280 quant_config,
281 prefix=maybe_prefix(prefix, "vision_tower"),
282 )
283 self.multi_modal_projector = PaliGemmaMultiModalProjector(
284 vision_hidden_size=config.vision_config.hidden_size,
285 projection_dim=config.vision_config.projection_dim,
286 )
287
288 if config.text_config.model_type == "gemma":
289 config.text_config.architectures = ["GemmaForCausalLM"]
290 else:
291 config.text_config.architectures = ["Gemma2ForCausalLM"]
292
293 with self._mark_language_model(vllm_config):
294 self.language_model = init_vllm_registered_model(
295 vllm_config=vllm_config,
296 hf_config=config.text_config,
297 prefix=maybe_prefix(prefix, "language_model"),
298 )
299
300 logit_scale = getattr(config, "logit_scale", 1.0)
301 self.language_model.logits_processor.scale *= logit_scale
302
303 self.make_empty_intermediate_tensors = (
304 self.language_model.make_empty_intermediate_tensors
305 )
306
307 def _parse_and_validate_image_input(
308 self, **kwargs: object
309 ) -> PaliGemmaImageInputs | None:
310 pixel_values = kwargs.pop("pixel_values", None)
311 image_embeds = kwargs.pop("image_embeds", None)
312
313 if pixel_values is None and image_embeds is None:
314 return None
315
316 if pixel_values is not None:
317 h = w = self.config.vision_config.image_size
318
319 return PaliGemmaImagePixelInputs(
320 type="pixel_values",
321 data=pixel_values,
322 resolve_bindings={"h": h, "w": w},
323 )
324
325 if image_embeds is not None:
326 return PaliGemmaImageEmbeddingInputs(
327 type="image_embeds",
328 data=image_embeds,
329 )
330
331 raise AssertionError("This line should be unreachable.")
332
333 def _image_pixels_to_features(
334 self,
335 vision_tower: SiglipVisionModel,
336 pixel_values: torch.Tensor,
337 ) -> torch.Tensor:
338 target_dtype = vision_tower.get_input_embeddings().weight.dtype
339 image_features = vision_tower(pixel_values.to(dtype=target_dtype))
340
341 return image_features
342
343 def _process_image_input(
344 self,
345 image_input: PaliGemmaImageInputs,
346 ) -> torch.Tensor:
347 if image_input["type"] == "image_embeds":
348 return image_input["data"]
349
350 pixel_values = image_input["data"]
351 image_features = self._image_pixels_to_features(
352 self.vision_tower,
353 pixel_values,
354 )
355
356 return self.multi_modal_projector(image_features)
357
358 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
359 image_input = self._parse_and_validate_image_input(**kwargs)
360 if image_input is None:
361 return []
362 vision_embeddings = self._process_image_input(image_input)
363 return vision_embeddings
364
365 def forward(
366 self,
367 input_ids: torch.Tensor | None,
368 positions: torch.Tensor,
369 intermediate_tensors: IntermediateTensors | None = None,
370 inputs_embeds: torch.Tensor | None = None,
371 **kwargs: object,
372 ) -> IntermediateTensors:
373 if intermediate_tensors is not None:
374 inputs_embeds = None
375
376 hidden_states = self.language_model.model(
377 input_ids, positions, intermediate_tensors, inputs_embeds=inputs_embeds
378 )
379
380 return hidden_states
381
382 def compute_logits(
383 self,
384 hidden_states: torch.Tensor,
385 ) -> torch.Tensor | None:
386 return self.language_model.compute_logits(hidden_states)
387
388 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
389 loader = AutoWeightsLoader(self)
390 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
391
392 def get_mm_mapping(self) -> MultiModelKeys:
393 return MultiModelKeys.from_string_field(
394 language_model="language_model",
395 connector="multi_modal_projector",
396 tower_model="vision_tower",
397 )
398
399 def get_mm_lora_token_counts(
400 self,
401 *,
402 modality: str,
403 mm_kwargs: MultiModalKwargsItem | None,
404 num_mm_embeds: int,
405 ) -> tuple[int, int | None]:
406 del modality, mm_kwargs
407 return num_mm_embeds, num_mm_embeds