main
py 698 lines 23.1 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/amalad/vllm/blob/nemotron_parse/vllm/model_executor/models/nemotron_parse.py
5 # that's based on https://huggingface.co/nvidia/NVIDIA-Nemotron-Parse-v1.1/blob/main/hf_nemotron_parse_modeling.py
6 #
7 # Bart classes based on old vLLM codebase:
8 # https://github.com/vllm-project/vllm/blob/v0.10.2/vllm/model_executor/models/bart.py
9
10 import math
11 from collections.abc import Iterable, Mapping, Sequence
12 from typing import Annotated, Literal
13
14 import torch
15 import torch.nn as nn
16 from einops import rearrange
17 from transformers import (
18 BartConfig,
19 BatchFeature,
20 PreTrainedConfig,
21 )
22
23 from vllm.config import CacheConfig, VllmConfig
24 from vllm.config.lora import LoRAConfig
25 from vllm.config.multimodal import MultiModalDummyOptions
26 from vllm.inputs import MultiModalDataDict
27 from vllm.logger import init_logger
28 from vllm.model_executor.layers.activation import get_act_fn
29 from vllm.model_executor.layers.linear import ColumnParallelLinear, RowParallelLinear
30 from vllm.model_executor.layers.logits_processor import LogitsProcessor
31 from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
32 from vllm.model_executor.layers.vocab_parallel_embedding import (
33 ParallelLMHead,
34 VocabParallelEmbedding,
35 )
36 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
37 from vllm.model_executor.models.interfaces import (
38 MultiModalEmbeddings,
39 SupportsMultiModal,
40 )
41 from vllm.model_executor.models.radio import RadioModel
42 from vllm.model_executor.models.whisper import WhisperAttention, WhisperCrossAttention
43 from vllm.multimodal import MULTIMODAL_REGISTRY
44 from vllm.multimodal.inputs import (
45 MultiModalFieldConfig,
46 MultiModalKwargsItems,
47 )
48 from vllm.multimodal.parse import MultiModalDataItems
49 from vllm.multimodal.processing import (
50 BaseDummyInputsBuilder,
51 BaseProcessingInfo,
52 EncDecMultiModalProcessor,
53 PromptReplacement,
54 PromptUpdate,
55 )
56 from vllm.renderers import TokenizeParams
57 from vllm.transformers_utils.configs.radio import RadioConfig
58 from vllm.utils.tensor_schema import TensorSchema, TensorShape
59 from vllm.v1.attention.backend import AttentionType
60
61 logger = init_logger(__name__)
62
63
64 class BartScaledWordEmbedding(VocabParallelEmbedding):
65 """This module overrides VocabParallelEmbedding's
66 forward by multiplying with embeddings scale.
67 """
68
69 def __init__(
70 self, num_embeddings: int, embedding_dim: int, embed_scale: float = 1.0
71 ):
72 super().__init__(num_embeddings, embedding_dim)
73 self.embed_scale = embed_scale
74
75 def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
76 return super().forward(input_ids) * self.embed_scale
77
78
79 class BartDecoderLayer(nn.Module):
80 def __init__(
81 self,
82 config: BartConfig,
83 cache_config: CacheConfig | None = None,
84 quant_config: QuantizationConfig | None = None,
85 prefix: str = "",
86 ):
87 super().__init__()
88 self.embed_dim = config.d_model
89
90 self.self_attn = WhisperAttention(
91 embed_dim=self.embed_dim,
92 num_heads=config.decoder_attention_heads,
93 attn_type=AttentionType.DECODER,
94 cache_config=cache_config,
95 quant_config=quant_config,
96 prefix=f"{prefix}.self_attn",
97 )
98 self.activation_fn = get_act_fn(config.activation_function)
99
100 self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim)
101 """
102 afeldman-nm: personally I would call this "cross-attention",
103 however I left the name as "encoder_attn" to maintain consistency
104 with the name of the pretrained weights.
105 """
106 self.encoder_attn = WhisperCrossAttention(
107 self.embed_dim,
108 config.decoder_attention_heads,
109 cache_config=cache_config,
110 quant_config=quant_config,
111 prefix=f"{prefix}.encoder_attn",
112 )
113 self.encoder_attn_layer_norm = nn.LayerNorm(self.embed_dim)
114
115 ffn_hidden_size = self.embed_dim
116 ffn_intermediate_size = config.encoder_ffn_dim
117 ffn_has_bias = True
118 self.fc1 = ColumnParallelLinear(
119 ffn_hidden_size,
120 ffn_intermediate_size,
121 bias=ffn_has_bias,
122 quant_config=quant_config,
123 prefix=f"{prefix}.fc1",
124 )
125 self.fc2 = RowParallelLinear(
126 ffn_intermediate_size,
127 ffn_hidden_size,
128 bias=ffn_has_bias,
129 quant_config=quant_config,
130 prefix=f"{prefix}.fc2",
131 )
132
133 self.final_layer_norm = nn.LayerNorm(self.embed_dim)
134
135 def forward(
136 self,
137 decoder_hidden_states: torch.Tensor,
138 encoder_hidden_states: torch.Tensor | None = None,
139 ) -> torch.Tensor:
140 r"""Args:
141 decoder_hidden_states: torch.Tensor of *decoder* input embeddings.
142 encoder_hidden_states: torch.Tensor of *encoder* input embeddings.
143
144 Returns:
145 Decoder layer output torch.Tensor
146
147 """
148 residual = decoder_hidden_states
149
150 # Self Attention
151 hidden_states = self.self_attn(hidden_states=decoder_hidden_states)
152
153 hidden_states = residual + hidden_states
154 hidden_states = self.self_attn_layer_norm(hidden_states)
155
156 # Cross-Attention Block
157
158 residual = hidden_states
159
160 hidden_states = self.encoder_attn(
161 hidden_states=hidden_states,
162 encoder_hidden_states=encoder_hidden_states,
163 )
164
165 hidden_states = residual + hidden_states
166 hidden_states = self.encoder_attn_layer_norm(hidden_states)
167
168 # Fully Connected
169 residual = hidden_states
170 fc1_out, _ = self.fc1(hidden_states)
171 hidden_states = self.activation_fn(fc1_out)
172
173 hidden_states, _ = self.fc2(hidden_states)
174
175 hidden_states = residual + hidden_states
176 hidden_states = self.final_layer_norm(hidden_states)
177
178 return hidden_states
179
180
181 class MBartDecoderLayer(BartDecoderLayer):
182 def forward(
183 self,
184 decoder_hidden_states: torch.Tensor,
185 encoder_hidden_states: torch.Tensor | None = None,
186 ) -> torch.Tensor:
187 residual = decoder_hidden_states
188 hidden_states = self.self_attn_layer_norm(decoder_hidden_states)
189
190 # Self Attention
191 hidden_states = self.self_attn(hidden_states=hidden_states)
192
193 hidden_states = residual + hidden_states
194
195 # Cross-Attention Block
196
197 residual = hidden_states
198 hidden_states = self.encoder_attn_layer_norm(hidden_states)
199
200 hidden_states = self.encoder_attn(
201 hidden_states=hidden_states,
202 encoder_hidden_states=encoder_hidden_states,
203 )
204
205 hidden_states = residual + hidden_states
206
207 # Fully Connected
208 residual = hidden_states
209 hidden_states = self.final_layer_norm(hidden_states)
210 fc1_out, _ = self.fc1(hidden_states)
211 hidden_states = self.activation_fn(fc1_out)
212
213 hidden_states, _ = self.fc2(hidden_states)
214
215 hidden_states = residual + hidden_states
216
217 return hidden_states
218
219
220 class MBartDecoderNoPos(nn.Module):
221 """Transformer decoder consisting of *config.decoder_layers* layers.
222 Each layer is a [`BartDecoderLayer`]
223
224 Args:
225 config: BartConfig
226 embed_tokens (nn.Embedding): output embedding
227
228 """
229
230 def __init__(
231 self,
232 config: BartConfig,
233 cache_config: CacheConfig | None = None,
234 quant_config: QuantizationConfig | None = None,
235 lora_config: LoRAConfig | None = None,
236 embed_tokens: nn.Embedding | None = None,
237 prefix: str = "",
238 ):
239 super().__init__()
240 self.cache_config = cache_config
241 self.quant_config = quant_config
242 self.lora_config = lora_config
243 embed_scale = math.sqrt(config.d_model) if config.scale_embedding else 1.0
244
245 self.embed_tokens = BartScaledWordEmbedding(
246 config.vocab_size, config.d_model, embed_scale=embed_scale
247 )
248
249 if embed_tokens is not None:
250 self.embed_tokens.weight = embed_tokens.weight
251
252 self.layers = nn.ModuleList(
253 [
254 MBartDecoderLayer(
255 config,
256 cache_config,
257 quant_config,
258 prefix=f"{prefix}.layers.{layer_idx}",
259 )
260 for layer_idx in range(config.decoder_layers)
261 ]
262 )
263
264 self.layernorm_embedding = nn.LayerNorm(config.d_model)
265 self.layer_norm = nn.LayerNorm(config.d_model)
266
267 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
268 return self.embed_tokens(input_ids)
269
270 def forward(
271 self,
272 decoder_input_ids: torch.Tensor | None,
273 *,
274 encoder_hidden_states: torch.Tensor | None,
275 inputs_embeds: torch.Tensor | None = None,
276 **kwargs,
277 ) -> torch.Tensor:
278 r"""Args:
279 decoder_input_ids: Indices of *decoder* input sequence tokens in the
280 vocabulary. Padding will be ignored by default should you provide it.
281 encoder_hidden_states: Tensor of encoder output embeddings
282 Returns:
283 Decoder output torch.Tensor
284
285 """
286 if inputs_embeds is None:
287 inputs_embeds = self.embed_tokens(decoder_input_ids)
288
289 hidden_states = self.layernorm_embedding(inputs_embeds)
290
291 # decoder layers
292
293 for decoder_layer in self.layers:
294 hidden_states = decoder_layer(
295 decoder_hidden_states=hidden_states,
296 encoder_hidden_states=encoder_hidden_states,
297 )
298
299 hidden_states = self.layer_norm(hidden_states)
300 return hidden_states
301
302 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
303 stacked_params_mapping = [
304 # (param_name, shard_name, shard_id)
305 (".self_attn.qkv_proj", ".self_attn.q_proj", "q"),
306 (".self_attn.qkv_proj", ".self_attn.k_proj", "k"),
307 (".self_attn.qkv_proj", ".self_attn.v_proj", "v"),
308 # MergedColumnParallelLinear uses integer indices (0, 1)
309 (".encoder_attn.kv_proj", ".encoder_attn.k_proj", 0),
310 (".encoder_attn.kv_proj", ".encoder_attn.v_proj", 1),
311 ]
312 params_dict = dict(self.named_parameters())
313 loaded_params: set[str] = set()
314 for name, loaded_weight in weights:
315 if name.startswith("embed_positions"):
316 continue
317
318 for param_name, weight_name, shard_id in stacked_params_mapping:
319 if weight_name not in name:
320 continue
321 name = name.replace(weight_name, param_name)
322 # Skip loading extra bias for GPTQ models.
323 if name.endswith(".bias") and name not in params_dict:
324 continue
325
326 param = params_dict[name]
327 weight_loader = param.weight_loader
328 weight_loader(param, loaded_weight, shard_id)
329 break
330 else:
331 # Skip loading extra bias for GPTQ models.
332 if name.endswith(".bias") and name not in params_dict:
333 continue
334
335 param = params_dict[name]
336 weight_loader = getattr(param, "weight_loader", default_weight_loader)
337 weight_loader(param, loaded_weight)
338 loaded_params.add(name)
339 return loaded_params
340
341
342 class NemotronParsePixelInputs(TensorSchema):
343 """Dimensions:
344 - b: Batch size
345 - c: Number of channels (3)
346 - h: Height
347 - w: Width
348 """
349
350 type: Literal["pixel_values"]
351 data: Annotated[torch.Tensor, TensorShape("b", 3, "h", "w")]
352
353
354 class NemotronParseProcessingInfo(BaseProcessingInfo):
355 def get_default_tok_params(self) -> TokenizeParams:
356 return super().get_default_tok_params().with_kwargs(add_special_tokens=False)
357
358 @property
359 def skip_prompt_length_check(self) -> bool:
360 return True # Because the encoder prompt is padded
361
362 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
363 return {"image": 1}
364
365 def get_num_image_tokens(self) -> int:
366 config = self.get_hf_config()
367 final_size = config.image_size
368 patch_size = config.encoder.patch_size
369
370 return (final_size[0] // patch_size) * ((final_size[1] // patch_size) // 4) + 1
371
372 def get_mm_max_tokens_per_item(
373 self,
374 seq_len: int,
375 mm_counts: Mapping[str, int],
376 ) -> Mapping[str, int] | None:
377 image_tokens = self.get_num_image_tokens()
378 return {"image": image_tokens}
379
380
381 class NemotronParseDummyInputsBuilder(
382 BaseDummyInputsBuilder[NemotronParseProcessingInfo]
383 ):
384 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
385 return ""
386
387 def get_dummy_mm_data(
388 self,
389 seq_len: int,
390 mm_counts: Mapping[str, int],
391 mm_options: MultiModalDummyOptions,
392 ) -> MultiModalDataDict:
393 num_images = mm_counts.get("image", 0)
394
395 target_width, target_height = self.info.get_hf_config().image_size
396
397 return {
398 "image": self._get_dummy_images(
399 width=target_width, height=target_height, num_images=num_images
400 )
401 }
402
403
404 class NemotronParseMultiModalProcessor(
405 EncDecMultiModalProcessor[NemotronParseProcessingInfo]
406 ):
407 def create_encoder_prompt(
408 self,
409 prompt: list[int],
410 mm_items: MultiModalDataItems,
411 ) -> list[int]:
412 return [0]
413
414 def _get_mm_fields_config(
415 self,
416 hf_inputs: BatchFeature,
417 hf_processor_mm_kwargs: Mapping[str, object],
418 ) -> Mapping[str, MultiModalFieldConfig]:
419 return dict(pixel_values=MultiModalFieldConfig.batched("image"))
420
421 def _get_prompt_updates(
422 self,
423 mm_items: MultiModalDataItems,
424 hf_processor_mm_kwargs: Mapping[str, object],
425 out_mm_kwargs: MultiModalKwargsItems,
426 ) -> Sequence[PromptUpdate]:
427 num_image_tokens = self.info.get_num_image_tokens()
428
429 return [
430 PromptReplacement(
431 modality="image",
432 target=[0],
433 replacement=[0] * num_image_tokens,
434 )
435 ]
436
437
438 class RadioWithNeck(nn.Module):
439 """Vision encoder using RADIO model with custom neck."""
440
441 def __init__(
442 self,
443 config: PreTrainedConfig,
444 quant_config: QuantizationConfig | None = None,
445 prefix: str = "",
446 ):
447 super().__init__()
448 self.config = config.encoder
449
450 self.model_encoder = self.get_vit_model_from_radio_config(
451 config, quant_config=quant_config
452 )
453
454 # Neck components
455 last_hidden_state = 1024
456 self.conv1 = nn.Conv1d(1280, last_hidden_state, 1)
457 self.layer_norm1 = nn.LayerNorm(
458 last_hidden_state, eps=1e-06, elementwise_affine=True
459 )
460 self.conv2 = nn.Conv2d(
461 last_hidden_state,
462 last_hidden_state,
463 kernel_size=(1, 4),
464 stride=(1, 4),
465 padding=0,
466 bias=False,
467 )
468 self.layer_norm2 = nn.LayerNorm(
469 last_hidden_state, eps=1e-06, elementwise_affine=True
470 )
471 self.sum_proj = ColumnParallelLinear(
472 3840,
473 last_hidden_state,
474 quant_config=quant_config,
475 prefix=f"{prefix}.sum_proj",
476 )
477 self.layer_norm3 = nn.LayerNorm(
478 last_hidden_state, eps=1e-06, elementwise_affine=True
479 )
480
481 def get_vit_model_from_radio_config(
482 self,
483 hf_config: PreTrainedConfig,
484 quant_config: QuantizationConfig | None = None,
485 ) -> RadioModel:
486 hf_config_vision = hf_config.encoder
487 model_name = hf_config_vision.args.get("model")
488 if model_name is None:
489 raise ValueError(f"Unsupported vit model type: {model_name}")
490
491 radio_config = RadioConfig(
492 model_name=model_name,
493 image_size=hf_config.image_size,
494 **hf_config_vision.args,
495 )
496
497 return RadioModel(config=radio_config, quant_config=quant_config)
498
499 def forward(self, pixel_values: torch.Tensor, **kwargs) -> torch.Tensor:
500 summary, feature = self.model_encoder(pixel_values)
501
502 output = self.conv1(feature.permute(0, 2, 1)).permute(0, 2, 1)
503 output = self.layer_norm1(output)
504
505 patch_size = self.config.patch_size
506 output = rearrange(
507 output,
508 "b (h w) d -> b d h w",
509 h=pixel_values.shape[-2] // patch_size,
510 w=pixel_values.shape[-1] // patch_size,
511 )
512
513 output = self.conv2(output)
514 output = rearrange(output, "b d h w -> b (h w) d")
515 output = self.layer_norm2(output)
516 summary = self.layer_norm3(self.sum_proj(summary)[0])
517 output = torch.cat((output, summary.unsqueeze(1)), dim=1)
518
519 return output
520
521 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
522 model_encoder_weights = []
523 adaptor_dict = {
524 name: param
525 for name, param in dict(self.named_parameters()).items()
526 if not name.startswith("model_encoder")
527 }
528 for name, w in weights:
529 if name.startswith("model_encoder"):
530 model_encoder_weights.append((".".join(name.split(".")[1:]), w))
531 else:
532 param = adaptor_dict[name]
533 with torch.no_grad():
534 default_weight_loader(param, w)
535
536 self.model_encoder.load_weights(model_encoder_weights)
537
538
539 @MULTIMODAL_REGISTRY.register_processor(
540 NemotronParseMultiModalProcessor,
541 info=NemotronParseProcessingInfo,
542 dummy_inputs=NemotronParseDummyInputsBuilder,
543 )
544 class NemotronParseForConditionalGeneration(nn.Module, SupportsMultiModal):
545 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
546 super().__init__()
547 config = vllm_config.model_config.hf_config
548
549 self.config = config
550 self.vision_config = config.encoder
551 cache_config = vllm_config.cache_config
552 quant_config = vllm_config.quant_config
553
554 with self._mark_tower_model(vllm_config, "image"):
555 self.encoder = RadioWithNeck(
556 config=config, quant_config=quant_config, prefix=f"{prefix}.encoder"
557 )
558
559 with self._mark_language_model(vllm_config):
560 self.decoder = MBartDecoderNoPos(
561 config.decoder,
562 cache_config=cache_config,
563 quant_config=quant_config,
564 prefix=f"{prefix}.decoder",
565 )
566
567 self.vocab_size = config.decoder.vocab_size
568 self.lm_head = ParallelLMHead(
569 config.decoder.vocab_size, config.decoder.d_model, quant_config=quant_config
570 )
571 # Some checkpoints (e.g. compact exports) tie the output head to the
572 # decoder's input embeddings instead of materializing a separate
573 # lm_head.weight tensor.
574 tie_word_embeddings = bool(
575 getattr(config, "tie_word_embeddings", False)
576 or getattr(config.decoder, "tie_word_embeddings", False)
577 )
578 if tie_word_embeddings:
579 self.lm_head = self.lm_head.tie_weights(self.decoder.embed_tokens)
580 self.logits_processor = LogitsProcessor(
581 self.vocab_size, config.decoder.vocab_size
582 )
583
584 @classmethod
585 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
586 if modality.startswith("image"):
587 return None
588
589 raise ValueError("Only image modality is supported")
590
591 def _parse_and_validate_image_input(
592 self, **kwargs: object
593 ) -> NemotronParsePixelInputs | None:
594 pixel_values = kwargs.pop("pixel_values", None)
595 image_embeds = kwargs.pop("image_embeds", None)
596
597 if pixel_values is None and image_embeds is None:
598 return None
599
600 if pixel_values is not None and image_embeds is not None:
601 raise ValueError("Both pixel values and image embeds are provided.")
602
603 if pixel_values is not None:
604 h, w = self.config.image_size
605 return NemotronParsePixelInputs(
606 type="pixel_values",
607 data=pixel_values,
608 resolve_bindings={
609 "h": h,
610 "w": w,
611 },
612 )
613
614 if image_embeds is not None:
615 raise NotImplementedError
616
617 raise AssertionError("This line should be unreachable.")
618
619 def _process_image_input(
620 self, image_input: NemotronParsePixelInputs
621 ) -> torch.Tensor:
622 assert image_input["type"] == "pixel_values"
623 pixel_values = image_input["data"]
624 dtype = next(self.encoder.parameters()).dtype
625 pixel_values = pixel_values.to(dtype)
626 return self.encoder(pixel_values)
627
628 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings | None:
629 image_input = self._parse_and_validate_image_input(**kwargs)
630 if image_input is None:
631 return None
632 vision_embeddings = self._process_image_input(image_input)
633 return vision_embeddings
634
635 def forward(
636 self,
637 input_ids: torch.Tensor | None,
638 positions: torch.Tensor,
639 encoder_outputs: list[torch.Tensor] | None = None,
640 **kwargs,
641 ) -> torch.Tensor:
642 r"""Args:
643 input_ids: torch.Tensor of *decoder* input token ids.
644 positions: torch.Tensor of *decoder* position indices.
645 encoder_outputs: List of encoder output tensors (vision embeddings).
646 During profiling, this may be None or empty.
647
648 Returns:
649 Output torch.Tensor
650
651 """
652 inputs_embeds = None
653 if encoder_outputs:
654 inputs_embeds = torch.cat(encoder_outputs, dim=0)
655 hidden_states = self.decoder(
656 decoder_input_ids=input_ids, encoder_hidden_states=inputs_embeds
657 )
658 return hidden_states
659
660 def compute_logits(
661 self,
662 hidden_states: torch.Tensor,
663 ) -> torch.Tensor | None:
664 return self.logits_processor(self.lm_head, hidden_states)
665
666 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
667 lm_head_dict = dict(self.lm_head.named_parameters())
668
669 def is_encoder(name: str) -> bool:
670 return name.startswith("encoder")
671
672 def is_decoder(name: str) -> bool:
673 return name.startswith("decoder")
674
675 def is_lm_head(name: str):
676 return name.startswith("lm_head")
677
678 # Separate weights by component
679 encoder_weights = []
680 decoder_weights = []
681
682 for name, w in weights:
683 if is_encoder(name):
684 encoder_weights.append((".".join(name.split(".")[1:]), w))
685 elif is_decoder(name):
686 decoder_weights.append((".".join(name.split(".")[1:]), w))
687 elif is_lm_head(name):
688 trimmed_name = ".".join(name.split(".")[1:])
689 param = lm_head_dict[trimmed_name]
690 with torch.no_grad():
691 default_weight_loader(param, w)
692 else:
693 logger.info("Found unexpected weight: %s", name)
694
695 # Load encoder weights
696 self.encoder.load_weights(encoder_weights)
697 # Load decoder weights
698 self.decoder.load_weights(decoder_weights)