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 5, 21:00 UTC
[Bugfix][Model] Fix PixtralForConditionalGeneration LoRA (#36963)
Signed-off-by: Jee Jee Li <pandaleefree@gmail.com> Co-authored-by: Roger Wang <hey@rogerw.io>
Jee Jee Li committed
Mar 30, 2026 at 14:59 UTC
ac30a8311ec286f85ddd1b8f8773ccfbfcf8ba08
1 file changed
+183
-52
vllm/model_executor/models/pixtral.py
+183
-52
@@ -8,7 +8,6 @@ from typing import Annotated, Literal
8
9
import torch
10
import torch.nn as nn
11
-import torch.nn.functional as F
11
from mistral_common.protocol.instruct.chunk import ImageChunk, TextChunk
12
from mistral_common.protocol.instruct.messages import UserMessage
13
from mistral_common.protocol.instruct.request import ChatCompletionRequest
@@ -26,16 +25,18 @@ from vllm.config import VllmConfig
25
from vllm.config.multimodal import BaseDummyOptions
26
from vllm.distributed import divide, get_tensor_model_parallel_world_size
27
from vllm.inputs import MultiModalDataDict
29
-from vllm.model_executor.layers.activation import get_act_and_mul_fn
28
+from vllm.model_executor.layers.activation import SiluAndMul, get_act_and_mul_fn
29
from vllm.model_executor.layers.conv import Conv2dLayer
30
from vllm.model_executor.layers.layernorm import RMSNorm
31
from vllm.model_executor.layers.linear import (
32
MergedColumnParallelLinear,
33
QKVParallelLinear,
34
+ ReplicatedLinear,
35
RowParallelLinear,
36
)
37
from vllm.model_executor.layers.quantization import QuantizationConfig
38
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
39
+from vllm.model_executor.models.utils import WeightsMapper
40
from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalKwargsItems
41
from vllm.multimodal.inputs import (
42
MultiModalFieldConfig,
@@ -293,6 +294,23 @@ class PixtralMultiModalProcessor(BaseMultiModalProcessor[PixtralProcessingInfo])
294
class PixtralForConditionalGeneration(
295
nn.Module, SupportsLoRA, SupportsEagle3, SupportsMultiModal, SupportsPP
296
):
297
+ hf_to_vllm_mapper = WeightsMapper(
298
+ orig_to_new_prefix={
299
+ "model.language_model.": "language_model.model.",
300
+ "model.vision_tower.": "vision_encoder.",
301
+ "model.multi_modal_projector.": "vision_language_adapter.",
302
+ },
303
+ orig_to_new_substr={
304
+ ".linear_1.": ".w_in.",
305
+ ".linear_2.": ".w_out.",
306
+ },
307
+ )
308
+
309
+ packed_modules_mapping = {
310
+ "qkv_proj": ["q_proj", "k_proj", "v_proj"],
311
+ "gate_up_proj": ["gate_proj", "up_proj"],
312
+ }
313
+
314
@classmethod
315
def get_placeholder_str(cls, modality: str, i: int) -> str | None:
316
if modality.startswith("image"):
@@ -325,7 +343,10 @@ class PixtralForConditionalGeneration(
343
)
344
345
with self._mark_tower_model(vllm_config, "image"):
328
- self.vision_encoder = VisionTransformer(self.vision_args)
346
+ self.vision_encoder = VisionTransformer(
347
+ self.vision_args,
348
+ prefix=maybe_prefix(prefix, "vision_encoder"),
349
+ )
350
self.pre_mm_projector_norm = (
351
RMSNorm(self.vision_args.hidden_size, eps=1e-5)
352
if self.vision_args.add_pre_mm_projector_layer_norm
@@ -435,6 +456,15 @@ class PixtralForConditionalGeneration(
456
return self.language_model.get_eagle3_aux_hidden_state_layers()
457
458
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
459
+ _vision_encoder_stacked_params = [
460
+ # (param_name, shard_name, shard_id)
461
+ (".qkv_proj", ".q_proj", "q"),
462
+ (".qkv_proj", ".k_proj", "k"),
463
+ (".qkv_proj", ".v_proj", "v"),
464
+ (".gate_up_proj", ".gate_proj", 0),
465
+ (".gate_up_proj", ".up_proj", 1),
466
+ ]
467
+
468
def is_vision_encoder_weights(weight: tuple[str, torch.Tensor]):
469
return weight[0].startswith(("vision_encoder", "vision_tower"))
470
@@ -449,7 +479,6 @@ class PixtralForConditionalGeneration(
479
def is_pre_mm_projector_norm(weight: tuple[str, torch.Tensor]):
480
return weight[0].startswith("pre_mm_projector_norm")
481
452
- # Get references to parameters for direct loading
482
vision_encoder_dict = (
483
dict(self.vision_encoder.named_parameters())
484
if self.vision_encoder is not None
@@ -472,29 +501,41 @@ class PixtralForConditionalGeneration(
501
)
502
503
def llm_weights_generator():
475
- # Single pass over weights
504
for name, w in weights:
505
if is_vision_encoder_weights((name, w)):
506
if _is_layer_none_or_staged(self.vision_encoder):
507
continue
480
- # Load vision encoder weights directly
508
trimmed_name = ".".join(name.split(".")[1:])
482
- param = vision_encoder_dict.get(trimmed_name)
483
- if param is not None:
484
- with torch.no_grad():
485
- default_weight_loader(param, w)
509
+ for (
510
+ param_name,
511
+ weight_name,
512
+ shard_id,
513
+ ) in _vision_encoder_stacked_params:
514
+ if weight_name in trimmed_name:
515
+ trimmed_name = trimmed_name.replace(weight_name, param_name)
516
+ param = vision_encoder_dict[trimmed_name]
517
+ weight_loader = param.weight_loader
518
+ weight_loader(param, w, shard_id)
519
+ break
520
+ else:
521
+ param = vision_encoder_dict.get(trimmed_name)
522
+ if param is not None:
523
+ weight_loader = getattr(
524
+ param, "weight_loader", default_weight_loader
525
+ )
526
+ weight_loader(param, w)
527
elif is_patch_merger((name, w)):
528
if _is_layer_none_or_staged(self.patch_merger):
529
continue
489
- # Load vision patch merger weights directly
530
trimmed_name = ".".join(name.split(".")[1:])
531
param = patch_merger_dict[trimmed_name]
492
- with torch.no_grad():
493
- default_weight_loader(param, w)
532
+ weight_loader = getattr(
533
+ param, "weight_loader", default_weight_loader
534
+ )
535
+ weight_loader(param, w)
536
elif is_pre_mm_projector_norm((name, w)):
537
if _is_layer_none_or_staged(self.pre_mm_projector_norm):
538
continue
497
- # Load vision pre_mm_projector_norm weights directly
539
trimmed_name = ".".join(name.split(".")[1:])
540
param = pre_mm_projector_norm_dict[trimmed_name]
541
with torch.no_grad():
@@ -502,26 +543,23 @@ class PixtralForConditionalGeneration(
543
elif is_vision_lang_adapter_weights((name, w)):
544
if _is_layer_none_or_staged(self.vision_language_adapter):
545
continue
505
- # Load vision-language adapter weights directly
546
trimmed_name = ".".join(name.split(".")[1:])
547
param = vision_lang_adapter_dict.get(trimmed_name)
548
if param is not None:
509
- with torch.no_grad():
510
- default_weight_loader(param, w)
549
+ weight_loader = getattr(
550
+ param, "weight_loader", default_weight_loader
551
+ )
552
+ weight_loader(param, w)
553
else:
512
- # LLM weights: yield them to be loaded
513
- # by language_model.load_weights
514
- # Strip "language_model." prefix if present (HF sharded format)
554
name = name.removeprefix("language_model.")
555
yield (name, w)
556
518
- # Now we call the language model load with the generator
557
self.language_model.load_weights(llm_weights_generator())
558
559
def get_mm_mapping(self) -> MultiModelKeys:
560
return MultiModelKeys.from_string_field(
523
- language_model="language_model",
524
- connector="vision_language_adapter",
561
+ language_model="language_model.",
562
+ connector="vision_language_adapter.",
563
tower_model="vision_encoder",
564
)
565
@@ -614,29 +652,78 @@ def apply_rotary_emb_vit(
652
653
654
class FeedForward(nn.Module):
617
- def __init__(self, args: VisionEncoderArgs):
655
+ def __init__(
656
+ self,
657
+ hidden_size: int,
658
+ intermediate_size: int,
659
+ quant_config: QuantizationConfig | None = None,
660
+ bias: bool = False,
661
+ prefix: str = "",
662
+ reduce_results: bool = True,
663
+ disable_tp: bool = False,
664
+ ) -> None:
665
super().__init__()
619
- assert args.intermediate_size is not None
620
- self.w1 = nn.Linear(args.hidden_size, args.intermediate_size, bias=False)
621
- self.w2 = nn.Linear(args.intermediate_size, args.hidden_size, bias=False)
622
- self.w3 = nn.Linear(args.hidden_size, args.intermediate_size, bias=False)
666
624
- def forward(self, x: torch.Tensor) -> torch.Tensor:
625
- return self.w2(F.silu(self.w1(x)) * self.w3(x))
667
+ self.gate_up_proj = MergedColumnParallelLinear(
668
+ input_size=hidden_size,
669
+ output_sizes=[intermediate_size] * 2,
670
+ bias=bias,
671
+ quant_config=quant_config,
672
+ disable_tp=disable_tp,
673
+ prefix=f"{prefix}.w13",
674
+ )
675
+ self.down_proj = RowParallelLinear(
676
+ input_size=intermediate_size,
677
+ output_size=hidden_size,
678
+ bias=bias,
679
+ quant_config=quant_config,
680
+ reduce_results=reduce_results,
681
+ disable_tp=disable_tp,
682
+ prefix=f"{prefix}.w2",
683
+ )
684
+
685
+ self.act_fn = SiluAndMul()
686
+
687
+ def forward(self, x):
688
+ x, _ = self.gate_up_proj(x)
689
+ x = self.act_fn(x)
690
+ x, _ = self.down_proj(x)
691
+ return x
692
693
694
class Attention(nn.Module):
629
- def __init__(self, args: VisionEncoderArgs):
695
+ def __init__(
696
+ self,
697
+ args: VisionEncoderArgs,
698
+ quant_config: QuantizationConfig | None = None,
699
+ prefix: str = "",
700
+ disable_tp: bool = False,
701
+ ):
702
super().__init__()
703
self.args = args
704
assert not args.hidden_size % args.num_attention_heads
633
- self.n_heads = args.num_attention_heads
705
self.head_dim = args.hidden_size // args.num_attention_heads
706
636
- self.wq = nn.Linear(args.hidden_size, args.hidden_size, bias=False)
637
- self.wk = nn.Linear(args.hidden_size, args.hidden_size, bias=False)
638
- self.wv = nn.Linear(args.hidden_size, args.hidden_size, bias=False)
639
- self.wo = nn.Linear(args.hidden_size, args.hidden_size, bias=False)
707
+ self.qkv_proj = QKVParallelLinear(
708
+ hidden_size=args.hidden_size,
709
+ head_size=self.head_dim,
710
+ total_num_heads=args.num_attention_heads,
711
+ bias=False,
712
+ quant_config=quant_config,
713
+ prefix=f"{prefix}.wqkv",
714
+ disable_tp=disable_tp,
715
+ )
716
+ self.o_proj = RowParallelLinear(
717
+ input_size=args.hidden_size,
718
+ output_size=args.hidden_size,
719
+ bias=False,
720
+ quant_config=quant_config,
721
+ prefix=f"{prefix}.wo",
722
+ disable_tp=disable_tp,
723
+ )
724
+
725
+ tp_size = 1 if disable_tp else get_tensor_model_parallel_world_size()
726
+ self.n_heads = divide(args.num_attention_heads, tp_size)
727
728
def forward(
729
self,
@@ -646,7 +733,8 @@ class Attention(nn.Module):
733
) -> torch.Tensor:
734
batch, patches, _ = x.shape
735
649
- q, k, v = self.wq(x), self.wk(x), self.wv(x)
736
+ qkv, _ = self.qkv_proj(x)
737
+ q, k, v = qkv.chunk(3, dim=-1)
738
q = q.reshape(batch, patches, self.n_heads, self.head_dim)
739
k = k.reshape(batch, patches, self.n_heads, self.head_dim)
740
v = v.reshape(batch, patches, self.n_heads, self.head_dim)
@@ -663,14 +751,32 @@ class Attention(nn.Module):
751
out = out.transpose(1, 2)
752
753
out = out.reshape(batch, patches, self.n_heads * self.head_dim)
666
- return self.wo(out)
754
+ out, _ = self.o_proj(out)
755
+ return out
756
757
758
class TransformerBlock(nn.Module):
670
- def __init__(self, args: VisionEncoderArgs):
759
+ def __init__(
760
+ self,
761
+ args: VisionEncoderArgs,
762
+ quant_config: QuantizationConfig | None = None,
763
+ prefix: str = "",
764
+ disable_tp: bool = False,
765
+ ):
766
super().__init__()
672
- self.attention = Attention(args)
673
- self.feed_forward = FeedForward(args)
767
+ self.attention = Attention(
768
+ args,
769
+ quant_config=quant_config,
770
+ prefix=f"{prefix}.attention",
771
+ disable_tp=disable_tp,
772
+ )
773
+ self.feed_forward = FeedForward(
774
+ args.hidden_size,
775
+ args.intermediate_size,
776
+ quant_config=quant_config,
777
+ prefix=f"{prefix}.feed_forward",
778
+ disable_tp=disable_tp,
779
+ )
780
self.attention_norm = RMSNorm(args.hidden_size, eps=1e-5)
781
self.ffn_norm = RMSNorm(args.hidden_size, eps=1e-5)
782
@@ -690,11 +796,24 @@ class TransformerBlock(nn.Module):
796
797
798
class Transformer(nn.Module):
693
- def __init__(self, args: VisionEncoderArgs):
799
+ def __init__(
800
+ self,
801
+ args: VisionEncoderArgs,
802
+ quant_config: QuantizationConfig | None = None,
803
+ prefix: str = "",
804
+ disable_tp: bool = False,
805
+ ):
806
super().__init__()
807
self.layers = torch.nn.ModuleList()
696
- for _ in range(args.num_hidden_layers):
697
- self.layers.append(TransformerBlock(args))
808
+ for idx in range(args.num_hidden_layers):
809
+ self.layers.append(
810
+ TransformerBlock(
811
+ args,
812
+ quant_config=quant_config,
813
+ prefix=f"{prefix}.layers.{idx}",
814
+ disable_tp=disable_tp,
815
+ )
816
+ )
817
818
def forward(
819
self,
@@ -727,9 +846,15 @@ def position_meshgrid(
846
847
848
class VisionTransformer(nn.Module):
730
- def __init__(self, args: VisionEncoderArgs):
849
+ def __init__(
850
+ self,
851
+ args: VisionEncoderArgs,
852
+ quant_config: QuantizationConfig | None = None,
853
+ prefix: str = "",
854
+ ):
855
super().__init__()
856
self.args = args
857
+ disable_tp = is_vit_use_data_parallel()
858
self.patch_conv = Conv2dLayer(
859
in_channels=args.num_channels,
860
out_channels=args.hidden_size,
@@ -738,7 +863,12 @@ class VisionTransformer(nn.Module):
863
bias=False,
864
)
865
self.ln_pre = RMSNorm(args.hidden_size, eps=1e-5)
741
- self.transformer = Transformer(args)
866
+ self.transformer = Transformer(
867
+ args,
868
+ quant_config=quant_config,
869
+ prefix=f"{prefix}.transformer",
870
+ disable_tp=disable_tp,
871
+ )
872
873
head_dim = self.args.hidden_size // self.args.num_attention_heads
874
assert head_dim % 2 == 0, "ROPE requires even head_dim"
@@ -822,13 +952,16 @@ class VisionLanguageAdapter(nn.Module):
952
def __init__(self, args: VisionEncoderArgs, dim: int):
953
super().__init__()
954
assert isinstance(args, VisionEncoderArgs)
825
- self.w_in = nn.Linear(
955
+ self.w_in = ReplicatedLinear(
956
args.hidden_size,
957
dim,
958
bias=args.adapter_bias,
959
+ return_bias=False,
960
)
961
self.gelu = nn.GELU()
831
- self.w_out = nn.Linear(dim, dim, bias=args.adapter_bias)
962
+ self.w_out = ReplicatedLinear(
963
+ dim, dim, bias=args.adapter_bias, return_bias=False
964
+ )
965
966
def forward(self, x: torch.Tensor) -> torch.Tensor:
967
return self.w_out(self.gelu(self.w_in(x)))
@@ -852,10 +985,8 @@ class PatchMerger(nn.Module):
985
self.spatial_merge_size = spatial_merge_size
986
self.mlp_input_dim = mlp_input_dim
987
855
- self.merging_layer = nn.Linear(
856
- mlp_input_dim,
857
- vision_encoder_dim,
858
- bias=use_mlp_bias,
988
+ self.merging_layer = ReplicatedLinear(
989
+ mlp_input_dim, vision_encoder_dim, bias=use_mlp_bias, return_bias=False
990
)
991
992
def forward(