main
py 534 lines 17.8 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
5
6 import regex as re
7 import torch
8 import torch.nn as nn
9 from transformers import AriaConfig, AriaTextConfig, BatchFeature
10 from transformers.models.aria.modeling_aria import AriaCrossAttention
11 from transformers.models.aria.processing_aria import AriaProcessor
12
13 from vllm.config import VllmConfig
14 from vllm.config.multimodal import MultiModalDummyOptions
15 from vllm.inputs import MultiModalDataDict
16 from vllm.model_executor.layers.activation import get_act_fn
17 from vllm.model_executor.layers.fused_moe import FusedMoEFactory
18 from vllm.model_executor.layers.linear import ColumnParallelLinear, RowParallelLinear
19 from vllm.model_executor.layers.logits_processor import LogitsProcessor
20 from vllm.model_executor.layers.quantization import QuantizationConfig
21 from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
22 from vllm.multimodal import MULTIMODAL_REGISTRY
23 from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
24 from vllm.multimodal.parse import MultiModalDataItems
25 from vllm.multimodal.processing import (
26 BaseDummyInputsBuilder,
27 BaseMultiModalProcessor,
28 BaseProcessingInfo,
29 PromptReplacement,
30 PromptUpdate,
31 )
32 from vllm.sequence import IntermediateTensors
33 from vllm.utils.tensor_schema import TensorSchema, TensorShape
34
35 from .idefics2_vision_model import Idefics2VisionConfig
36 from .idefics2_vision_model import (
37 Idefics2VisionTransformer as Idefics3VisionTransformer,
38 )
39 from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsQuant
40 from .llama import LlamaDecoderLayer, LlamaMLP, LlamaModel
41 from .utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
42
43
44 class AriaImagePixelInputs(TensorSchema):
45 """Dimensions:
46 - b: Batch size
47 - n: Number of images
48 - c: Number of channels
49 - h: Height of each image
50 - w: Width of each image
51 """
52
53 type: Literal["pixel_values"]
54
55 pixel_values: Annotated[
56 torch.Tensor,
57 TensorShape("bn", 3, "h", "w"),
58 ]
59
60 pixel_mask: Annotated[
61 torch.Tensor | None,
62 TensorShape("bn", "h", "w"),
63 ]
64
65
66 class AriaVisionTransformer(Idefics3VisionTransformer, SupportsQuant):
67 packed_modules_mapping = {"qkv_proj": ["q_proj", "k_proj", "v_proj"]}
68
69 def __init__(
70 self,
71 config: Idefics2VisionConfig,
72 quant_config: QuantizationConfig | None = None,
73 prefix: str = "",
74 ) -> None:
75 super().__init__(config, quant_config=quant_config, prefix=prefix)
76 # Unlike Idefics3VisionTransformer which uses LayerNorm after the
77 # final layer, Aria omits this normalization, so we replace it with an
78 # Identity layer
79 self.post_layernorm = nn.Identity()
80
81 hf_to_vllm_mapper = WeightsMapper(
82 # NOTE: post_layernorm is not used in Aria.
83 orig_to_new_substr={"post_layernorm": None},
84 orig_to_new_stacked={
85 ".q_proj": (".qkv_proj", "q"),
86 ".k_proj": (".qkv_proj", "k"),
87 ".v_proj": (".qkv_proj", "v"),
88 },
89 )
90
91 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
92 loader = AutoWeightsLoader(self)
93 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
94
95
96 class AriaProjectorMLP(nn.Module):
97 def __init__(
98 self,
99 in_features: int,
100 hidden_features: int,
101 output_dim: int,
102 prefix: str = "",
103 ) -> None:
104 super().__init__()
105
106 self.linear_in = ColumnParallelLinear(
107 in_features, hidden_features, bias=False, prefix=f"{prefix}.linear_in"
108 )
109 self.linear_out = RowParallelLinear(
110 hidden_features, output_dim, bias=False, prefix=f"{prefix}.linear_out"
111 )
112 self.act = get_act_fn("gelu_new")
113
114 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
115 hidden_states, _ = self.linear_in(hidden_states)
116 hidden_states = self.act(hidden_states)
117 hidden_states, _ = self.linear_out(hidden_states)
118 return hidden_states
119
120
121 class AriaProjector(nn.Module):
122 """A projection module with one cross attention layer and one FFN layer, which
123 projects ViT's outputs into MoE's inputs.
124
125 Args:
126 config: [AriaConfig](https://huggingface.co/docs/transformers/main/model_doc/aria#transformers.AriaConfig)
127 containing projector configuration parameters.
128
129 Outputs:
130 A tensor with the shape of (batch_size, query_number, output_dim)
131
132 """
133
134 def __init__(self, config: AriaConfig, prefix: str = "") -> None:
135 super().__init__()
136
137 self.patch_to_query_dict = config.projector_patch_to_query_dict
138 self.in_features = config.vision_config.hidden_size
139 self.num_heads = config.vision_config.num_attention_heads
140 self.kv_dim = config.vision_config.hidden_size
141 self.hidden_features = config.text_config.hidden_size
142 self.output_dim = config.text_config.hidden_size
143
144 self.query = nn.Parameter(
145 torch.empty(
146 config.max_value_projector_patch_to_query_dict, self.in_features
147 )
148 )
149
150 self.cross_attn = AriaCrossAttention(config)
151
152 self.layer_norm = nn.LayerNorm(self.in_features)
153 self.feed_forward = AriaProjectorMLP(
154 self.in_features,
155 self.hidden_features,
156 self.output_dim,
157 prefix=f"{prefix}.feed_forward",
158 )
159
160 def forward(
161 self,
162 x: torch.Tensor,
163 attn_mask: torch.Tensor | None = None,
164 ) -> torch.Tensor:
165 batch_size, num_patches = x.shape[0], x.shape[1]
166
167 if num_patches not in self.patch_to_query_dict:
168 raise KeyError(
169 f"Number of patches {num_patches} not found in "
170 "patch_to_query_dict amongst possible values "
171 f"{self.patch_to_query_dict.keys()}."
172 )
173
174 query_num = self.patch_to_query_dict[num_patches]
175
176 queries = self.query[:query_num].unsqueeze(0).repeat(batch_size, 1, 1)
177
178 if attn_mask is not None:
179 attn_mask = attn_mask.repeat_interleave(self.num_heads, 0)
180 attn_mask = attn_mask.unsqueeze(1).expand(-1, queries.size(1), -1)
181
182 attention_out = self.cross_attn(x, queries, attn_mask=attn_mask)
183
184 out = self.feed_forward(self.layer_norm(attention_out))
185
186 return out
187
188
189 class AriaTextMoELayer(nn.Module):
190 """Mixture of Experts (MoE) Layer for the AriaMoE model.
191
192 This layer implements the MoE mechanism, which routes input tokens to
193 different experts based on a routing algorithm, processes them through the
194 experts, and then combines the outputs.
195 """
196
197 def __init__(
198 self,
199 config: AriaTextConfig,
200 quant_config: QuantizationConfig | None,
201 prefix: str = "",
202 ) -> None:
203 super().__init__()
204 self.config = config
205
206 self.router_weight = nn.Parameter(
207 torch.empty((self.config.moe_num_experts, self.config.hidden_size))
208 )
209
210 self.shared_experts = LlamaMLP(
211 config.hidden_size,
212 config.intermediate_size * config.moe_num_shared_experts,
213 "silu",
214 quant_config=quant_config,
215 bias=config.mlp_bias,
216 )
217
218 self.experts = FusedMoEFactory(
219 shared_experts=self.shared_experts,
220 num_experts=config.moe_num_experts,
221 top_k=config.moe_topk,
222 hidden_size=config.hidden_size,
223 intermediate_size=config.intermediate_size,
224 quant_config=quant_config,
225 prefix=f"{prefix}.experts",
226 is_fused_checkpoint_transposed=True,
227 )
228
229 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
230 """Forward pass of the MoE Layer.
231
232 Args:
233 hidden_states: Input tensor of shape
234 (batch_size, sequence_length, hidden_size).
235
236 Returns:
237 torch.Tensor: Output tensor after passing through the MoE layer.
238
239 """
240 router_output = torch.nn.functional.linear(hidden_states, self.router_weight)
241
242 return self.experts(hidden_states, router_output)
243
244
245 class AriaTextDecoderLayer(LlamaDecoderLayer):
246 """Custom Decoder Layer for the AriaMoE model which modifies the standard
247 `LlamaDecoderLayer` by replacing the traditional MLP with a Mixture of
248 Experts (MoE) Layer.
249 """
250
251 def __init__(self, vllm_config: VllmConfig, prefix: str = "") -> None:
252 super().__init__(vllm_config, prefix)
253
254 config = vllm_config.model_config.hf_config
255 quant_config = vllm_config.quant_config
256
257 self.mlp = AriaTextMoELayer(
258 config, quant_config=quant_config, prefix=f"{prefix}.mlp"
259 )
260
261
262 class AriaTextModel(LlamaModel, SupportsQuant):
263 """Custom LlamaModel for the AriaMoE model which modifies the standard
264 LlamaModel by replacing the `LlamaDecoderLayer` with `MoEDecoderLayer`.
265 """
266
267 packed_modules_mapping = {
268 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
269 "gate_up_proj": ["gate_proj", "up_proj"],
270 }
271
272 # The fused expert loader expects names without the .weight suffix.
273 hf_to_vllm_mapper = LlamaModel.hf_to_vllm_mapper | WeightsMapper(
274 orig_to_new_regex={
275 re.compile(r"experts\.fc1\.weight$"): "experts.gate_up_proj",
276 re.compile(r"experts\.fc2\.weight$"): "experts.down_proj",
277 },
278 # Quantization configs also use the expert module names.
279 orig_to_new_substr={
280 "experts.fc1": "experts.gate_up_proj",
281 "experts.fc2": "experts.down_proj",
282 },
283 )
284
285 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
286 super().__init__(
287 vllm_config=vllm_config, prefix=prefix, layer_type=AriaTextDecoderLayer
288 )
289
290
291 class AriaProcessingInfo(BaseProcessingInfo):
292 def get_hf_config(self):
293 return self.ctx.get_hf_config(AriaConfig)
294
295 def get_vision_config(self):
296 return self.get_hf_config().vision_config
297
298 def get_hf_processor(self, **kwargs: object):
299 return self.ctx.get_hf_processor(AriaProcessor, **kwargs)
300
301 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
302 return {"image": None}
303
304 def get_num_image_tokens(self) -> int:
305 hf_config = self.get_hf_config()
306 return max(hf_config.projector_patch_to_query_dict.values())
307
308
309 class AriaDummyInputsBuilder(BaseDummyInputsBuilder[AriaProcessingInfo]):
310 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
311 num_images = mm_counts.get("image", 0)
312
313 processor = self.info.get_hf_processor()
314 image_token = getattr(processor.tokenizer, "image_token", None)
315 assert isinstance(image_token, str)
316
317 return image_token * num_images
318
319 def get_dummy_mm_data(
320 self,
321 seq_len: int,
322 mm_counts: Mapping[str, int],
323 mm_options: MultiModalDummyOptions,
324 ) -> MultiModalDataDict:
325 vision_config = self.info.get_vision_config()
326
327 max_image_size = vision_config.image_size
328
329 return {
330 "image": self._get_dummy_images(
331 width=max_image_size,
332 height=max_image_size,
333 num_images=mm_counts.get("image", 0),
334 overrides=mm_options.get("image"),
335 )
336 }
337
338
339 class AriaMultiModalProcessor(BaseMultiModalProcessor[AriaProcessingInfo]):
340 def _get_mm_fields_config(
341 self,
342 hf_inputs: BatchFeature,
343 hf_processor_mm_kwargs: Mapping[str, object],
344 ) -> Mapping[str, MultiModalFieldConfig]:
345 return dict(
346 pixel_values=MultiModalFieldConfig.batched("image"),
347 pixel_mask=MultiModalFieldConfig.batched("image"),
348 )
349
350 def _get_prompt_updates(
351 self,
352 mm_items: MultiModalDataItems,
353 hf_processor_mm_kwargs: Mapping[str, object],
354 out_mm_kwargs: MultiModalKwargsItems,
355 ) -> Sequence[PromptUpdate]:
356 hf_config = self.info.get_hf_config()
357 image_token_id = hf_config.image_token_index
358
359 num_image_tokens = self.info.get_num_image_tokens()
360
361 return [
362 PromptReplacement(
363 modality="image",
364 target=[image_token_id],
365 replacement=[image_token_id] * num_image_tokens,
366 )
367 ]
368
369
370 @MULTIMODAL_REGISTRY.register_processor(
371 AriaMultiModalProcessor,
372 info=AriaProcessingInfo,
373 dummy_inputs=AriaDummyInputsBuilder,
374 )
375 class AriaForConditionalGeneration(nn.Module, SupportsMultiModal):
376 """Aria model for conditional generation tasks.
377
378 This model combines a vision tower, a multi-modal projector, and a language
379 model to perform tasks that involve both image and text inputs.
380 """
381
382 hf_to_vllm_mapper = WeightsMapper(
383 orig_to_new_prefix={
384 # mapping for new names in checkpoint saved after transformers v4.52
385 "model.language_model.": "language_model.model.",
386 "model.vision_tower.": "vision_tower.",
387 "model.multi_modal_projector.": "multi_modal_projector.",
388 # mapping for original checkpoint
389 "language_model.model": "language_model",
390 "language_model.lm_head": "lm_head",
391 },
392 orig_to_new_suffix={
393 "router.weight": "router_weight",
394 },
395 )
396
397 @classmethod
398 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
399 if modality.startswith("image"):
400 return "<|fim_prefix|><|img|><|fim_suffix|>"
401
402 raise ValueError("Only image modality is supported")
403
404 def __init__(
405 self,
406 vllm_config: VllmConfig,
407 prefix: str = "",
408 ):
409 super().__init__()
410 config = vllm_config.model_config.hf_config
411 quant_config = vllm_config.quant_config
412
413 self.config = config
414
415 with self._mark_tower_model(vllm_config, "image"):
416 self.vision_tower = AriaVisionTransformer(
417 config.vision_config,
418 quant_config=quant_config,
419 prefix=maybe_prefix(prefix, "vision_tower"),
420 )
421 self.multi_modal_projector = AriaProjector(
422 config, prefix=maybe_prefix(prefix, "multi_modal_projector")
423 )
424
425 with self._mark_language_model(vllm_config):
426 self.language_model = AriaTextModel(
427 vllm_config=vllm_config.with_hf_config(config.text_config),
428 prefix=maybe_prefix(prefix, "language_model.model"),
429 )
430
431 self.lm_head = ParallelLMHead(
432 config.text_config.vocab_size,
433 config.text_config.hidden_size,
434 quant_config=quant_config,
435 prefix=maybe_prefix(prefix, "lm_head"),
436 )
437
438 logit_scale = getattr(config, "logit_scale", 1.0)
439 self.logits_processor = LogitsProcessor(
440 config.text_config.vocab_size, scale=logit_scale
441 )
442
443 def _parse_and_validate_image_input(
444 self, **kwargs: object
445 ) -> AriaImagePixelInputs | None:
446 pixel_values = kwargs.pop("pixel_values", None)
447 pixel_mask = kwargs.pop("pixel_mask", None)
448
449 if pixel_values is None:
450 return None
451
452 return AriaImagePixelInputs(
453 type="pixel_values",
454 pixel_values=pixel_values,
455 pixel_mask=pixel_mask,
456 )
457
458 def _create_patch_attention_mask(
459 self,
460 pixel_mask: torch.Tensor | None,
461 ) -> torch.Tensor | None:
462 if pixel_mask is None:
463 return None
464
465 patches_subgrid = pixel_mask.unfold(
466 dimension=1,
467 size=self.vision_tower.config.patch_size,
468 step=self.vision_tower.config.patch_size,
469 ).unfold(
470 dimension=2,
471 size=self.vision_tower.config.patch_size,
472 step=self.vision_tower.config.patch_size,
473 )
474 return (patches_subgrid.sum(dim=(-1, -2)) > 0).bool()
475
476 def _process_image_input(
477 self, image_input: AriaImagePixelInputs
478 ) -> tuple[torch.Tensor, torch.Tensor]:
479 pixel_values = image_input["pixel_values"]
480 pixel_mask = image_input["pixel_mask"]
481
482 patch_attention_mask = self._create_patch_attention_mask(pixel_mask)
483
484 image_outputs = self.vision_tower(
485 pixel_values=pixel_values,
486 patch_attention_mask=patch_attention_mask,
487 )
488 image_attn_mask = None
489 if patch_attention_mask is not None:
490 flattened_mask = patch_attention_mask.flatten(1)
491 image_attn_mask = torch.logical_not(flattened_mask)
492
493 return self.multi_modal_projector(image_outputs, image_attn_mask)
494
495 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
496 image_input = self._parse_and_validate_image_input(**kwargs)
497 if image_input is None:
498 return []
499 multimodal_embeddings = self._process_image_input(image_input)
500 return multimodal_embeddings
501
502 def forward(
503 self,
504 input_ids: torch.Tensor | None,
505 positions: torch.Tensor,
506 intermediate_tensors: IntermediateTensors | None = None,
507 inputs_embeds: torch.Tensor | None = None,
508 **kwargs: object,
509 ) -> torch.Tensor | IntermediateTensors:
510 if intermediate_tensors is not None:
511 inputs_embeds = None
512
513 hidden_states = self.language_model(
514 input_ids,
515 positions,
516 intermediate_tensors,
517 inputs_embeds=inputs_embeds,
518 )
519
520 return hidden_states
521
522 def compute_logits(
523 self,
524 hidden_states: torch.Tensor,
525 ) -> torch.Tensor | None:
526 logits = self.logits_processor(self.lm_head, hidden_states)
527 return logits
528
529 def load_weights(
530 self,
531 weights: Iterable[tuple[str, torch.Tensor]],
532 ) -> set[str]:
533 loader = AutoWeightsLoader(self)
534 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)