@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 77d906995c
Fork 0

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 14, 05:30 UTC

[KV sharing] Re-land Gemma3n model changes from #22628 (#24357)

Signed-off-by: Yong Hoon Shin <yhshin@meta.com>

Yong Hoon Shin committed Sep 23, 2025 at 19:25 UTC 77d906995c464fd24fc5b3effad29b65bcf65798
1 file changed +344 -58
vllm/model_executor/models/gemma3n.py
+344 -58
@@ -26,6 +26,7 @@ from vllm.attention import Attention
26 from vllm.compilation.decorators import support_torch_compile
27 from vllm.config import CacheConfig, VllmConfig
28 from vllm.distributed import get_tensor_model_parallel_world_size
29 +from vllm.forward_context import get_forward_context
30 from vllm.logger import init_logger
31 from vllm.model_executor.layers.activation import (_ACTIVATION_REGISTRY,
32 GeluAndMul,
@@ -44,6 +45,7 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
45 from vllm.model_executor.model_loader.weight_utils import (
46 default_weight_loader, maybe_remap_kv_scale_name)
47 from vllm.sequence import IntermediateTensors
48 +from vllm.v1.attention.backends.utils import KVSharingFastPrefillMetadata
49
50 from .interfaces import SupportsQuant
51 from .utils import (AutoWeightsLoader, extract_layer_index,
@@ -51,6 +53,8 @@ from .utils import (AutoWeightsLoader, extract_layer_index,
53
54 logger = init_logger(__name__)
55
56 +EPS = torch.tensor(torch.finfo().min)
57 +
58
59 class Gemma3nAltUp(nn.Module):
60 """Alternating updates (Altup)
@@ -532,16 +536,29 @@ class Gemma3nDecoderLayer(nn.Module):
536 return corrected_predictions
537
538
535 -@support_torch_compile
536 -class Gemma3nTextModel(nn.Module, SupportsQuant):
539 +# This enables torch.compile if --kv-sharing-fast-prefill passed
540 +@support_torch_compile(enable_if=lambda vllm_config: vllm_config.cache_config.
541 + kv_sharing_fast_prefill)
542 +class Gemma3nSelfDecoder(nn.Module):
543 + """
544 + Includes altup embedding and self decoder layers
545 + """
546
538 - def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
547 + def __init__(
548 + self,
549 + *,
550 + vllm_config: VllmConfig,
551 + prefix: str = "",
552 + decoder_layers: list[Gemma3nDecoderLayer],
553 + layer_idx_start: int,
554 + ):
555 super().__init__()
556 + self.decoder_layers = decoder_layers
557 + self.layer_idx_start = layer_idx_start
558 +
559 config = vllm_config.model_config.hf_config
541 - cache_config = vllm_config.cache_config
542 - quant_config = vllm_config.quant_config
560 self.config = config
544 - self.quant_config = quant_config
561 + quant_config = vllm_config.quant_config
562
563 self.embed_tokens = VocabParallelEmbedding(
564 config.vocab_size,
@@ -594,32 +611,6 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
611 prefix=f"{prefix}.altup_projections.{idx-1}",
612 ) for idx in range(1, self.config.altup_num_inputs)
613 ])
597 - self.altup_unembed_projections = nn.ModuleList([
598 - ColumnParallelLinear(
599 - config.hidden_size,
600 - config.hidden_size,
601 - bias=False,
602 - gather_output=True,
603 - return_bias=False,
604 - quant_config=quant_config,
605 - prefix=f"{prefix}.altup_unembed_projections.{idx-1}",
606 - ) for idx in range(1, self.config.altup_num_inputs)
607 - ])
608 -
609 - # Transformer blocks.
610 - self.start_layer, self.end_layer, self.layers = make_layers(
611 - config.num_hidden_layers,
612 - lambda prefix: Gemma3nDecoderLayer(
613 - config, cache_config, quant_config, prefix=prefix),
614 - prefix=f"{prefix}.layers")
615 - self.norm = RMSNorm(
616 - config.hidden_size,
617 - eps=config.rms_norm_eps,
618 - )
619 - self.eps = torch.tensor(torch.finfo().min)
620 -
621 - def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
622 - return self.embed_tokens(input_ids) * self.embed_scale
614
615 def get_per_layer_input_embeddings(
616 self, input_ids: torch.Tensor) -> torch.Tensor:
@@ -633,20 +624,11 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
624 return self.embed_tokens_per_layer(
625 per_layer_inputs_tokens) * self.embed_scale_per_layer
626
636 - def forward(
627 + def get_per_layer_inputs(
628 self,
638 - input_ids: Optional[torch.Tensor],
639 - positions: torch.Tensor,
640 - per_layer_inputs: torch.Tensor,
641 - intermediate_tensors: Optional[IntermediateTensors] = None,
642 - inputs_embeds: Optional[torch.Tensor] = None,
643 - **kwargs,
644 - ) -> Union[torch.Tensor, IntermediateTensors]:
645 - if inputs_embeds is not None:
646 - hidden_states_0 = inputs_embeds
647 - else:
648 - hidden_states_0 = self.get_input_embeddings(input_ids)
649 -
629 + hidden_states_0: torch.Tensor,
630 + per_layer_inputs: Optional[torch.Tensor],
631 + ) -> torch.Tensor:
632 per_layer_projection = self.per_layer_model_projection(hidden_states_0)
633 per_layer_projection = per_layer_projection.reshape(
634 *hidden_states_0.shape[:-1],
@@ -655,14 +637,18 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
637 )
638 per_layer_projection = self.per_layer_projection_norm(
639 per_layer_projection)
658 -
640 if per_layer_inputs is not None:
641 # Profiling run does not compute per_layer_inputs
642 per_layer_inputs = per_layer_projection + per_layer_inputs
643 per_layer_inputs *= self.per_layer_input_scale
644 else:
645 per_layer_inputs = per_layer_projection
646 + return per_layer_inputs
647
648 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
649 + return self.embed_tokens(input_ids) * self.embed_scale
650 +
651 + def altup_embed(self, hidden_states_0: torch.Tensor) -> torch.Tensor:
652 # Altup embed.
653 hidden_states = [hidden_states_0] * self.config.altup_num_inputs
654 target_magnitude = torch.mean(hidden_states_0**2, dim=-1,
@@ -673,11 +659,77 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
659 dim=-1,
660 keepdim=True)**0.5
661 hidden_states[i] *= target_magnitude / torch.maximum(
676 - new_magnitude, self.eps)
677 - hidden_states = torch.stack(hidden_states, dim=0)
662 + new_magnitude, EPS)
663 + hidden_states = torch.stack(hidden_states, dim=-1)
664 + return hidden_states
665 +
666 + def forward(
667 + self,
668 + input_ids: torch.Tensor,
669 + positions: torch.Tensor,
670 + inputs_embeds: Optional[torch.Tensor] = None,
671 + per_layer_inputs: Optional[torch.Tensor] = None,
672 + **kwargs,
673 + ) -> tuple[torch.Tensor, torch.Tensor]:
674 + if inputs_embeds is not None:
675 + hidden_states_0 = inputs_embeds
676 + else:
677 + hidden_states_0 = self.get_input_embeddings(input_ids)
678 +
679 + adjusted_per_layer_inputs = self.get_per_layer_inputs(
680 + hidden_states_0, per_layer_inputs)
681 + hidden_states = self.altup_embed(hidden_states_0)
682 +
683 + # [altnum_inputs, num_tokens, hidden_size]
684 + hidden_states = hidden_states.permute(2, 0, 1)
685 +
686 + for idx, layer in enumerate(self.decoder_layers):
687 + layer_idx = idx + self.layer_idx_start
688 + # [altup_num_inputs, num_tokens, hidden_size]
689 + hidden_states = layer(
690 + positions=positions,
691 + hidden_states=hidden_states,
692 + per_layer_input=adjusted_per_layer_inputs[:, layer_idx, :],
693 + **kwargs,
694 + )
695 +
696 + # [num_tokens, hidden_size, altnum_inputs]
697 + hidden_states = hidden_states.permute(1, 2, 0)
698 +
699 + return hidden_states, adjusted_per_layer_inputs
700 +
701 +
702 +# This enables torch.compile if --kv-sharing-fast-prefill passed
703 +@support_torch_compile(enable_if=lambda vllm_config: vllm_config.cache_config.
704 + kv_sharing_fast_prefill)
705 +class Gemma3nCrossDecoder(nn.Module):
706 + """
707 + Cross-decoder layers
708 + """
709 +
710 + def __init__(
711 + self,
712 + *,
713 + vllm_config: VllmConfig,
714 + prefix: str = "",
715 + decoder_layers: list[Gemma3nDecoderLayer],
716 + layer_idx_start: int,
717 + ):
718 + super().__init__()
719 + self.decoder_layers = decoder_layers
720 + self.layer_idx_start = layer_idx_start
721
679 - # Transformer blocks.
680 - for layer_idx, layer in enumerate(self.layers):
722 + def forward(
723 + self,
724 + positions: torch.Tensor,
725 + hidden_states: torch.Tensor,
726 + per_layer_inputs: torch.Tensor,
727 + **kwargs,
728 + ) -> torch.Tensor:
729 + # [altnum_inputs, num_tokens, hidden_size]
730 + hidden_states = hidden_states.permute(2, 0, 1)
731 + for idx, layer in enumerate(self.decoder_layers):
732 + layer_idx = idx + self.layer_idx_start
733 # [altup_num_inputs, num_tokens, hidden_size]
734 hidden_states = layer(
735 positions=positions,
@@ -685,22 +737,249 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
737 per_layer_input=per_layer_inputs[:, layer_idx, :],
738 **kwargs,
739 )
740 + # [num_tokens, hidden_size, altnum_inputs]
741 + hidden_states = hidden_states.permute(1, 2, 0)
742 + return hidden_states
743 +
744 +
745 +# This disables torch.compile if --kv-sharing-fast-prefill passed
746 +@support_torch_compile(enable_if=lambda vllm_config: not vllm_config.
747 + cache_config.kv_sharing_fast_prefill)
748 +class Gemma3nTextModel(nn.Module, SupportsQuant):
749 +
750 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
751 + super().__init__()
752 + config = vllm_config.model_config.hf_config
753 + cache_config = vllm_config.cache_config
754 + quant_config = vllm_config.quant_config
755 + self.config = config
756 + self.quant_config = quant_config
757 +
758 + self.altup_unembed_projections = nn.ModuleList([
759 + ColumnParallelLinear(
760 + config.hidden_size,
761 + config.hidden_size,
762 + bias=False,
763 + gather_output=True,
764 + return_bias=False,
765 + quant_config=quant_config,
766 + prefix=f"{prefix}.altup_unembed_projections.{idx-1}",
767 + ) for idx in range(1, self.config.altup_num_inputs)
768 + ])
769 +
770 + # Allocate config.num_kv_shared_layers layers for self-decoder
771 + self.start_layer, self.end_layer, self.layers = make_layers(
772 + config.num_hidden_layers,
773 + lambda prefix: Gemma3nDecoderLayer(
774 + config, cache_config, quant_config, prefix=prefix),
775 + prefix=f"{prefix}.layers")
776 +
777 + first_kv_shared_layer_idx = (config.num_hidden_layers -
778 + config.num_kv_shared_layers)
779 +
780 + # NOTE(sarckk): importing this top level seems to cause issues
781 + # during running of tests.
782 + from vllm.compilation.backends import set_model_tag
783 +
784 + # Layer idx 0-19 are self-decoder layers in You Only Cache Once (YOCO)
785 + with set_model_tag("self_decoder"):
786 + self.self_decoder = Gemma3nSelfDecoder(
787 + vllm_config=vllm_config,
788 + prefix=f"{prefix}.self_decoder",
789 + decoder_layers=self.layers[:first_kv_shared_layer_idx],
790 + layer_idx_start=0,
791 + )
792 + # Layer idx 20-30 are cross-decoder layers in YOCO
793 + with set_model_tag("cross_decoder"):
794 + self.cross_decoder = Gemma3nCrossDecoder(
795 + vllm_config=vllm_config,
796 + prefix=f"{prefix}.cross_decoder",
797 + decoder_layers=self.layers[first_kv_shared_layer_idx:],
798 + layer_idx_start=first_kv_shared_layer_idx,
799 + )
800 +
801 + self.norm = RMSNorm(
802 + config.hidden_size,
803 + eps=config.rms_norm_eps,
804 + )
805 +
806 + self.fast_prefill_enabled = cache_config.kv_sharing_fast_prefill
807 +
808 + if self.fast_prefill_enabled:
809 + # Allocate static buffers for CUDAGraph
810 + # TODO(sarckk): Extract this functionality to interface
811 + max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens
812 + device = next(self.parameters()).device
813 + self.positions = torch.zeros(max_num_tokens,
814 + dtype=torch.int64,
815 + device=device)
816 + self.hidden_states = torch.zeros(
817 + (max_num_tokens, config.hidden_size,
818 + self.config.altup_num_inputs),
819 + dtype=self.embed_tokens.weight.dtype,
820 + device=device,
821 + )
822 + self.per_layer_inputs = torch.zeros(
823 + (max_num_tokens, self.config.num_hidden_layers,
824 + self.config.hidden_size_per_layer_input),
825 + dtype=self.embed_tokens.weight.dtype,
826 + device=device,
827 + )
828 +
829 + @property
830 + def embed_tokens(self):
831 + return self.self_decoder.embed_tokens
832 +
833 + def get_per_layer_input_embeddings(
834 + self, input_ids: torch.Tensor) -> torch.Tensor:
835 + return self.self_decoder.get_per_layer_input_embeddings(input_ids)
836 +
837 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
838 + return self.self_decoder.get_input_embeddings(input_ids)
839 +
840 + def fast_prefill_forward(
841 + self,
842 + input_ids: torch.Tensor,
843 + positions: torch.Tensor,
844 + inputs_embeds: Optional[torch.Tensor] = None,
845 + per_layer_inputs: Optional[torch.Tensor] = None,
846 + **kwargs,
847 + ) -> torch.Tensor:
848 + logits_indices_padded, num_logits_indices = None, None
849 + attn_metadata = get_forward_context().attn_metadata
850 +
851 + # attn_metadata is None during dummy runs
852 + if (self.fast_prefill_enabled and attn_metadata is not None):
853 + assert isinstance(attn_metadata, dict)
854 + # Last layer is a KV sharing layer
855 + layer_attn_metadata = attn_metadata[
856 + self.layers[-1].self_attn.attn.layer_name]
857 + if (isinstance(layer_attn_metadata, KVSharingFastPrefillMetadata)):
858 + logits_indices_padded = (
859 + layer_attn_metadata.logits_indices_padded)
860 + num_logits_indices = layer_attn_metadata.num_logits_indices
861 +
862 + # Copy inputs for cudagraph
863 + batch_size = positions.size(0)
864 + self.positions[:batch_size].copy_(positions)
865 + self_decoder_hidden_states, per_layer_inputs_adjusted = \
866 + self.self_decoder(
867 + input_ids=input_ids,
868 + positions=self.positions[:batch_size],
869 + inputs_embeds=inputs_embeds,
870 + per_layer_inputs=per_layer_inputs,
871 + **kwargs,
872 + )
873 +
874 + if logits_indices_padded is None:
875 + logits_indices_padded = torch.arange(
876 + positions.size(0),
877 + dtype=positions.dtype,
878 + device=positions.device,
879 + )
880 +
881 + # NOTE(sarckk): There is currently a bug caused by
882 + # vLLM converting output of last piecewise CUDA graph
883 + # to weakref, causing memory to be prematurely freed
884 + # when there are multiple compilation units
885 + # Keep .clone() until fix in
886 + # https://github.com/vllm-project/vllm/pull/22282
887 + hidden_states = self_decoder_hidden_states.clone()
888 +
889 + # Copy inputs for cudagraph
890 + num_padded_logits_indices = logits_indices_padded.size(0)
891 + self.positions[:num_padded_logits_indices].copy_(
892 + positions[logits_indices_padded])
893 + self.hidden_states[:num_padded_logits_indices].copy_(
894 + self_decoder_hidden_states[logits_indices_padded])
895 + self.per_layer_inputs[:num_padded_logits_indices].copy_(
896 + per_layer_inputs_adjusted[logits_indices_padded])
897 + cross_decoder_hidden_states = self.cross_decoder(
898 + positions=self.positions[:num_padded_logits_indices],
899 + hidden_states=self.hidden_states[:num_padded_logits_indices],
900 + per_layer_inputs=self.per_layer_inputs[:num_padded_logits_indices],
901 + **kwargs,
902 + )
903 +
904 + if num_logits_indices is not None:
905 + assert num_logits_indices > 0
906 + # Merge cross-decoder and self-decoder hidden states
907 + hidden_states[logits_indices_padded[:num_logits_indices]] = (
908 + cross_decoder_hidden_states[:num_logits_indices])
909 + else:
910 + hidden_states = cross_decoder_hidden_states
911 +
912 + return hidden_states
913 +
914 + def normal_forward(
915 + self,
916 + input_ids: torch.Tensor,
917 + positions: torch.Tensor,
918 + inputs_embeds: Optional[torch.Tensor] = None,
919 + per_layer_inputs: Optional[torch.Tensor] = None,
920 + **kwargs,
921 + ) -> torch.Tensor:
922 + hidden_states, per_layer_inputs = self.self_decoder(
923 + input_ids=input_ids,
924 + positions=positions,
925 + inputs_embeds=inputs_embeds,
926 + per_layer_inputs=per_layer_inputs,
927 + **kwargs,
928 + )
929 + hidden_states = self.cross_decoder(
930 + positions=positions,
931 + hidden_states=hidden_states,
932 + per_layer_inputs=per_layer_inputs,
933 + **kwargs,
934 + )
935 + return hidden_states
936
937 + def altup_unembed(
938 + self,
939 + hidden_states: torch.Tensor,
940 + ) -> torch.Tensor:
941 # Altup unembed.
690 - target_magnitude = torch.mean(hidden_states[0]**2,
942 + target_magnitude = torch.mean(hidden_states[..., 0]**2,
943 dim=-1,
944 keepdim=True)**0.5
945 for i in range(1, self.config.altup_num_inputs):
694 - hidden_states[i] = self.altup_unembed_projections[i - 1](
695 - hidden_states[i])
696 - new_magnitude = torch.mean(hidden_states[i]**2,
946 + hidden_states[..., i] = self.altup_unembed_projections[i - 1](
947 + hidden_states[..., i])
948 + new_magnitude = torch.mean(hidden_states[..., i]**2,
949 dim=-1,
950 keepdim=True)**0.5
699 - hidden_states[i] *= target_magnitude / torch.maximum(
700 - new_magnitude, self.eps)
701 - # [altup_num_inputs,num_tokens,hidden_size] -> [num_tokens,hidden_size]
702 - hidden_states = torch.mean(hidden_states, dim=0)
951 + hidden_states[..., i] *= target_magnitude / torch.maximum(
952 + new_magnitude, EPS)
953 + # [num_tokens,hidden_size, altup_num_inputs] -> [num_tokens,hidden_size]
954 + hidden_states = torch.mean(hidden_states, dim=-1)
955 + return hidden_states
956
957 + def forward(
958 + self,
959 + input_ids: Optional[torch.Tensor],
960 + positions: torch.Tensor,
961 + per_layer_inputs: Optional[torch.Tensor] = None,
962 + intermediate_tensors: Optional[IntermediateTensors] = None,
963 + inputs_embeds: Optional[torch.Tensor] = None,
964 + **kwargs,
965 + ) -> Union[torch.Tensor, IntermediateTensors]:
966 + if self.fast_prefill_enabled:
967 + hidden_states = self.fast_prefill_forward(
968 + input_ids,
969 + positions,
970 + inputs_embeds,
971 + per_layer_inputs,
972 + **kwargs,
973 + )
974 + else:
975 + hidden_states = self.normal_forward(
976 + input_ids,
977 + positions,
978 + inputs_embeds,
979 + per_layer_inputs,
980 + **kwargs,
981 + )
982 + hidden_states = self.altup_unembed(hidden_states)
983 return self.norm(hidden_states)
984
985 def load_weights(self, weights: Iterable[tuple[str,
@@ -716,6 +995,13 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
995 params_dict = dict(self.named_parameters())
996 loaded_params: set[str] = set()
997 for name, loaded_weight in weights:
998 + # decoder layer weights, altup_unembed_projections and rmsnorm
999 + # are initialized in text model, others are in self decoder
1000 + if (not name.startswith('layers')
1001 + and not name.startswith('altup_unembed_projections')
1002 + and not name.startswith('norm')):
1003 + name = f"self_decoder.{name}"
1004 +
1005 if (self.quant_config is not None and
1006 (scale_name := self.quant_config.get_cache_scale(name))):
1007 # Loading kv cache scales for compressed-tensors quantization