@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 8c3e199998
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

Revert gemma3n fast prefill changes (#23897)

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

Yong Hoon Shin committed Aug 29, 2025 at 12:16 UTC 8c3e199998cc5b1225328f2de01a7443fbb4f3cd
3 files changed +67 -355
tests/v1/e2e/test_kv_sharing_fast_prefill.py
+1
@@ -64,6 +64,7 @@ def cleanup(llm: LLM, compilation_config: CompilationConfig):
64
65 @fork_new_process_for_each_test
66 @pytest.mark.parametrize("enforce_eager", [True])
67 +@pytest.mark.skip(reason="Disable until Gemma3n supports fast prefill")
68 def test_kv_sharing_fast_prefill(
69 monkeypatch: pytest.MonkeyPatch,
70 enforce_eager: bool,
vllm/model_executor/models/gemma3n.py
+65 -354
@@ -23,11 +23,9 @@ from torch import nn
23 from transformers.models.gemma3n.configuration_gemma3n import Gemma3nTextConfig
24
25 from vllm.attention import Attention
26 -from vllm.compilation.backends import set_model_tag
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
30 -from vllm.forward_context import get_forward_context
29 from vllm.logger import init_logger
30 from vllm.model_executor.layers.activation import (_ACTIVATION_REGISTRY,
31 GeluAndMul,
@@ -47,7 +45,6 @@ from vllm.model_executor.model_loader.weight_utils import (
45 default_weight_loader, maybe_remap_kv_scale_name)
46 from vllm.model_executor.sampling_metadata import SamplingMetadata
47 from vllm.sequence import IntermediateTensors
50 -from vllm.v1.attention.backends.utils import KVSharingFastPrefillMetadata
48
49 from .interfaces import SupportsQuant
50 from .utils import (AutoWeightsLoader, extract_layer_index,
@@ -536,178 +533,7 @@ class Gemma3nDecoderLayer(nn.Module):
533 return corrected_predictions
534
535
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 -
547 - def __init__(
548 - self,
549 - *,
550 - vllm_config: VllmConfig,
551 - prefix: str = "",
552 - decoder_layers: list[Gemma3nDecoderLayer],
553 - layer_idx_start: int,
554 - per_layer_model_projection: ColumnParallelLinear,
555 - embed_scale_per_layer: torch.Tensor,
556 - embed_tokens_per_layer: VocabParallelEmbedding,
557 - per_layer_projection_norm: RMSNorm,
558 - per_layer_input_scale: torch.Tensor,
559 - altup_projections: nn.ModuleList,
560 - eps: torch.Tensor,
561 - embed_tokens: VocabParallelEmbedding,
562 - embed_scale: torch.Tensor,
563 - ):
564 - super().__init__()
565 - self.decoder_layers = decoder_layers
566 - self.layer_idx_start = layer_idx_start
567 - self.per_layer_model_projection = per_layer_model_projection
568 - self.config = vllm_config.model_config.hf_config
569 - self.embed_scale_per_layer = embed_scale_per_layer
570 - self.embed_tokens_per_layer = embed_tokens_per_layer
571 - self.per_layer_projection_norm = per_layer_projection_norm
572 - self.per_layer_input_scale = per_layer_input_scale
573 - self.altup_projections = altup_projections
574 - self.eps = eps
575 - self.embed_tokens = embed_tokens
576 - self.embed_scale = embed_scale
577 -
578 - def get_per_layer_input_embeddings(
579 - self, input_ids: torch.Tensor) -> torch.Tensor:
580 - # Deal with the fact that vocab_size_per_layer_input < vocab_size
581 - # which causes us to have some out of vocab tokens by setting
582 - # those token ids to 0. This matches the HF implementation.
583 - per_layer_inputs_mask = torch.logical_and(
584 - input_ids >= 0, input_ids < self.config.vocab_size_per_layer_input)
585 - per_layer_inputs_tokens = torch.where(per_layer_inputs_mask, input_ids,
586 - torch.zeros_like(input_ids))
587 - return self.embed_tokens_per_layer(
588 - per_layer_inputs_tokens) * self.embed_scale_per_layer
589 -
590 - def get_per_layer_inputs(
591 - self,
592 - hidden_states_0: torch.Tensor,
593 - per_layer_inputs: Optional[torch.Tensor],
594 - ) -> torch.Tensor:
595 - per_layer_projection = self.per_layer_model_projection(hidden_states_0)
596 - per_layer_projection = per_layer_projection.reshape(
597 - *hidden_states_0.shape[:-1],
598 - self.config.num_hidden_layers,
599 - self.config.hidden_size_per_layer_input,
600 - )
601 - per_layer_projection = self.per_layer_projection_norm(
602 - per_layer_projection)
603 - if per_layer_inputs is not None:
604 - # Profiling run does not compute per_layer_inputs
605 - per_layer_inputs = per_layer_projection + per_layer_inputs
606 - per_layer_inputs *= self.per_layer_input_scale
607 - else:
608 - per_layer_inputs = per_layer_projection
609 - return per_layer_inputs
610 -
611 - def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
612 - return self.embed_tokens(input_ids) * self.embed_scale
613 -
614 - def altup_embed(self, hidden_states_0: torch.Tensor) -> torch.Tensor:
615 - # Altup embed.
616 - hidden_states = [hidden_states_0] * self.config.altup_num_inputs
617 - target_magnitude = torch.mean(hidden_states_0**2, dim=-1,
618 - keepdim=True)**0.5
619 - for i in range(1, self.config.altup_num_inputs):
620 - hidden_states[i] = self.altup_projections[i - 1](hidden_states[i])
621 - new_magnitude = torch.mean(hidden_states[i]**2,
622 - dim=-1,
623 - keepdim=True)**0.5
624 - hidden_states[i] *= target_magnitude / torch.maximum(
625 - new_magnitude, self.eps)
626 - hidden_states = torch.stack(hidden_states, dim=-1)
627 - return hidden_states
628 -
629 - def forward(
630 - self,
631 - input_ids: torch.Tensor,
632 - positions: torch.Tensor,
633 - inputs_embeds: Optional[torch.Tensor] = None,
634 - per_layer_inputs: Optional[torch.Tensor] = None,
635 - **kwargs,
636 - ) -> tuple[torch.Tensor, torch.Tensor]:
637 - if inputs_embeds is not None:
638 - hidden_states_0 = inputs_embeds
639 - else:
640 - hidden_states_0 = self.get_input_embeddings(input_ids)
641 -
642 - adjusted_per_layer_inputs = self.get_per_layer_inputs(
643 - hidden_states_0, per_layer_inputs)
644 - hidden_states = self.altup_embed(hidden_states_0)
645 -
646 - # [altnum_inputs, num_tokens, hidden_size]
647 - hidden_states = hidden_states.permute(2, 0, 1)
648 -
649 - for idx, layer in enumerate(self.decoder_layers):
650 - layer_idx = idx + self.layer_idx_start
651 - # [altup_num_inputs, num_tokens, hidden_size]
652 - hidden_states = layer(
653 - positions=positions,
654 - hidden_states=hidden_states,
655 - per_layer_input=adjusted_per_layer_inputs[:, layer_idx, :],
656 - **kwargs,
657 - )
658 -
659 - # [num_tokens, hidden_size, altnum_inputs]
660 - hidden_states = hidden_states.permute(1, 2, 0)
661 -
662 - return hidden_states, adjusted_per_layer_inputs
663 -
664 -
665 -# This enables torch.compile if --kv-sharing-fast-prefill passed
666 -@support_torch_compile(enable_if=lambda vllm_config: vllm_config.cache_config.
667 - kv_sharing_fast_prefill)
668 -class Gemma3nCrossDecoder(nn.Module):
669 - """
670 - Cross-decoder layers
671 - """
672 -
673 - def __init__(
674 - self,
675 - *,
676 - vllm_config: VllmConfig,
677 - prefix: str = "",
678 - decoder_layers: list[Gemma3nDecoderLayer],
679 - layer_idx_start: int,
680 - ):
681 - super().__init__()
682 - self.decoder_layers = decoder_layers
683 - self.layer_idx_start = layer_idx_start
684 -
685 - def forward(
686 - self,
687 - positions: torch.Tensor,
688 - hidden_states: torch.Tensor,
689 - per_layer_inputs: torch.Tensor,
690 - **kwargs,
691 - ) -> torch.Tensor:
692 - # [altnum_inputs, num_tokens, hidden_size]
693 - hidden_states = hidden_states.permute(2, 0, 1)
694 - for idx, layer in enumerate(self.decoder_layers):
695 - layer_idx = idx + self.layer_idx_start
696 - # [altup_num_inputs, num_tokens, hidden_size]
697 - hidden_states = layer(
698 - positions=positions,
699 - hidden_states=hidden_states,
700 - per_layer_input=per_layer_inputs[:, layer_idx, :],
701 - **kwargs,
702 - )
703 - # [num_tokens, hidden_size, altnum_inputs]
704 - hidden_states = hidden_states.permute(1, 2, 0)
705 - return hidden_states
706 -
707 -
708 -# This disables torch.compile if --kv-sharing-fast-prefill passed
709 -@support_torch_compile(enable_if=lambda vllm_config: not vllm_config.
710 - cache_config.kv_sharing_fast_prefill)
536 +@support_torch_compile
537 class Gemma3nTextModel(nn.Module, SupportsQuant):
538
539 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
@@ -717,6 +543,7 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
543 quant_config = vllm_config.quant_config
544 self.config = config
545 self.quant_config = quant_config
546 +
547 self.embed_tokens = VocabParallelEmbedding(
548 config.vocab_size,
549 config.hidden_size,
@@ -786,211 +613,95 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
613 lambda prefix: Gemma3nDecoderLayer(
614 config, cache_config, quant_config, prefix=prefix),
615 prefix=f"{prefix}.layers")
789 -
790 - self.eps = torch.tensor(torch.finfo().min)
791 -
792 - first_kv_shared_layer_idx = (config.num_hidden_layers -
793 - config.num_kv_shared_layers)
794 - # Layer idx 0-19 are self-decoder layers in You Only Cache Once (YOCO)
795 - with set_model_tag("self_decoder"):
796 - self.self_decoder = Gemma3nSelfDecoder(
797 - vllm_config=vllm_config,
798 - prefix=f"{prefix}.self_decoder",
799 - decoder_layers=self.layers[:first_kv_shared_layer_idx],
800 - layer_idx_start=0,
801 - per_layer_model_projection=self.per_layer_model_projection,
802 - embed_scale_per_layer=self.embed_scale_per_layer,
803 - embed_tokens_per_layer=self.embed_tokens_per_layer,
804 - per_layer_projection_norm=self.per_layer_projection_norm,
805 - per_layer_input_scale=self.per_layer_input_scale,
806 - altup_projections=self.altup_projections,
807 - eps=self.eps,
808 - embed_tokens=self.embed_tokens,
809 - embed_scale=self.embed_scale,
810 - )
811 - # Layer idx 20-30 are cross-decoder layers in YOCO
812 - with set_model_tag("cross_decoder"):
813 - self.cross_decoder = Gemma3nCrossDecoder(
814 - vllm_config=vllm_config,
815 - prefix=f"{prefix}.cross_decoder",
816 - decoder_layers=self.layers[first_kv_shared_layer_idx:],
817 - layer_idx_start=first_kv_shared_layer_idx,
818 - )
819 -
616 self.norm = RMSNorm(
617 config.hidden_size,
618 eps=config.rms_norm_eps,
619 )
824 -
825 - self.fast_prefill_enabled = cache_config.kv_sharing_fast_prefill
826 -
827 - if self.fast_prefill_enabled:
828 - # Allocate static buffers for CUDAGraph
829 - # TODO(sarckk): Extract this functionality to interface
830 - max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens
831 - device = next(self.parameters()).device
832 - self.positions = torch.zeros(max_num_tokens,
833 - dtype=torch.int64,
834 - device=device)
835 - self.hidden_states = torch.zeros(
836 - (max_num_tokens, config.hidden_size,
837 - self.config.altup_num_inputs),
838 - dtype=self.embed_tokens.weight.dtype,
839 - device=device,
840 - )
841 - self.per_layer_inputs = torch.zeros(
842 - (max_num_tokens, self.config.num_hidden_layers,
843 - self.config.hidden_size_per_layer_input),
844 - dtype=self.embed_tokens.weight.dtype,
845 - device=device,
846 - )
620 + self.eps = torch.tensor(torch.finfo().min)
621
622 def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
849 - return self.self_decoder.get_input_embeddings(input_ids)
623 + return self.embed_tokens(input_ids) * self.embed_scale
624 +
625 + def get_per_layer_input_embeddings(
626 + self, input_ids: torch.Tensor) -> torch.Tensor:
627 + # Deal with the fact that vocab_size_per_layer_input < vocab_size
628 + # which causes us to have some out of vocab tokens by setting
629 + # those token ids to 0. This matches the HF implementation.
630 + per_layer_inputs_mask = torch.logical_and(
631 + input_ids >= 0, input_ids < self.config.vocab_size_per_layer_input)
632 + per_layer_inputs_tokens = torch.where(per_layer_inputs_mask, input_ids,
633 + torch.zeros_like(input_ids))
634 + return self.embed_tokens_per_layer(
635 + per_layer_inputs_tokens) * self.embed_scale_per_layer
636
851 - def fast_prefill_forward(
637 + def forward(
638 self,
853 - input_ids: torch.Tensor,
639 + input_ids: Optional[torch.Tensor],
640 positions: torch.Tensor,
641 + per_layer_inputs: torch.Tensor,
642 + intermediate_tensors: Optional[IntermediateTensors] = None,
643 inputs_embeds: Optional[torch.Tensor] = None,
856 - per_layer_inputs: Optional[torch.Tensor] = None,
644 **kwargs,
858 - ) -> torch.Tensor:
859 - logits_indices_padded, num_logits_indices = None, None
860 - attn_metadata = get_forward_context().attn_metadata
861 -
862 - # attn_metadata is None during dummy runs
863 - if (self.fast_prefill_enabled and attn_metadata is not None):
864 - assert isinstance(attn_metadata, dict)
865 - # Last layer is a KV sharing layer
866 - layer_attn_metadata = attn_metadata[
867 - self.layers[-1].self_attn.attn.layer_name]
868 - if (isinstance(layer_attn_metadata, KVSharingFastPrefillMetadata)):
869 - logits_indices_padded = (
870 - layer_attn_metadata.logits_indices_padded)
871 - num_logits_indices = layer_attn_metadata.num_logits_indices
872 -
873 - # Copy inputs for cudagraph
874 - batch_size = positions.size(0)
875 - self.positions[:batch_size].copy_(positions)
876 - self_decoder_hidden_states, per_layer_inputs_adjusted = \
877 - self.self_decoder(
878 - input_ids=input_ids,
879 - positions=self.positions[:batch_size],
880 - inputs_embeds=inputs_embeds,
881 - per_layer_inputs=per_layer_inputs,
882 - **kwargs,
883 - )
884 -
885 - if logits_indices_padded is None:
886 - logits_indices_padded = torch.arange(
887 - positions.size(0),
888 - dtype=positions.dtype,
889 - device=positions.device,
890 - )
645 + ) -> Union[torch.Tensor, IntermediateTensors]:
646 + if inputs_embeds is not None:
647 + hidden_states_0 = inputs_embeds
648 + else:
649 + hidden_states_0 = self.get_input_embeddings(input_ids)
650
892 - # NOTE(sarckk): There is currently a bug caused by
893 - # vLLM converting output of last piecewise CUDA graph
894 - # to weakref, causing memory to be prematurely freed
895 - # when there are multiple compilation units
896 - # Keep .clone() until fix in
897 - # https://github.com/vllm-project/vllm/pull/22282
898 - hidden_states = self_decoder_hidden_states.clone()
899 -
900 - # Copy inputs for cudagraph
901 - num_padded_logits_indices = logits_indices_padded.size(0)
902 - self.positions[:num_padded_logits_indices].copy_(
903 - positions[logits_indices_padded])
904 - self.hidden_states[:num_padded_logits_indices].copy_(
905 - self_decoder_hidden_states[logits_indices_padded])
906 - self.per_layer_inputs[:num_padded_logits_indices].copy_(
907 - per_layer_inputs_adjusted[logits_indices_padded])
908 - cross_decoder_hidden_states = self.cross_decoder(
909 - positions=self.positions[:num_padded_logits_indices],
910 - hidden_states=self.hidden_states[:num_padded_logits_indices],
911 - per_layer_inputs=self.per_layer_inputs[:num_padded_logits_indices],
912 - **kwargs,
651 + per_layer_projection = self.per_layer_model_projection(hidden_states_0)
652 + per_layer_projection = per_layer_projection.reshape(
653 + *hidden_states_0.shape[:-1],
654 + self.config.num_hidden_layers,
655 + self.config.hidden_size_per_layer_input,
656 )
657 + per_layer_projection = self.per_layer_projection_norm(
658 + per_layer_projection)
659
915 - if num_logits_indices is not None:
916 - assert num_logits_indices > 0
917 - # Merge cross-decoder and self-decoder hidden states
918 - hidden_states[logits_indices_padded[:num_logits_indices]] = (
919 - cross_decoder_hidden_states[:num_logits_indices])
660 + if per_layer_inputs is not None:
661 + # Profiling run does not compute per_layer_inputs
662 + per_layer_inputs = per_layer_projection + per_layer_inputs
663 + per_layer_inputs *= self.per_layer_input_scale
664 else:
921 - hidden_states = cross_decoder_hidden_states
665 + per_layer_inputs = per_layer_projection
666
923 - return hidden_states
667 + # Altup embed.
668 + hidden_states = [hidden_states_0] * self.config.altup_num_inputs
669 + target_magnitude = torch.mean(hidden_states_0**2, dim=-1,
670 + keepdim=True)**0.5
671 + for i in range(1, self.config.altup_num_inputs):
672 + hidden_states[i] = self.altup_projections[i - 1](hidden_states[i])
673 + new_magnitude = torch.mean(hidden_states[i]**2,
674 + dim=-1,
675 + keepdim=True)**0.5
676 + hidden_states[i] *= target_magnitude / torch.maximum(
677 + new_magnitude, self.eps)
678 + hidden_states = torch.stack(hidden_states, dim=0)
679
925 - def normal_forward(
926 - self,
927 - input_ids: torch.Tensor,
928 - positions: torch.Tensor,
929 - inputs_embeds: Optional[torch.Tensor] = None,
930 - per_layer_inputs: Optional[torch.Tensor] = None,
931 - **kwargs,
932 - ) -> torch.Tensor:
933 - hidden_states, per_layer_inputs = self.self_decoder(
934 - input_ids=input_ids,
935 - positions=positions,
936 - inputs_embeds=inputs_embeds,
937 - per_layer_inputs=per_layer_inputs,
938 - **kwargs,
939 - )
940 - hidden_states = self.cross_decoder(
941 - positions=positions,
942 - hidden_states=hidden_states,
943 - per_layer_inputs=per_layer_inputs,
944 - **kwargs,
945 - )
946 - return hidden_states
680 + # Transformer blocks.
681 + for layer_idx, layer in enumerate(self.layers):
682 + # [altup_num_inputs, num_tokens, hidden_size]
683 + hidden_states = layer(
684 + positions=positions,
685 + hidden_states=hidden_states,
686 + per_layer_input=per_layer_inputs[:, layer_idx, :],
687 + **kwargs,
688 + )
689
948 - def altup_unembed(
949 - self,
950 - hidden_states: torch.Tensor,
951 - ) -> torch.Tensor:
690 # Altup unembed.
953 - target_magnitude = torch.mean(hidden_states[..., 0]**2,
691 + target_magnitude = torch.mean(hidden_states[0]**2,
692 dim=-1,
693 keepdim=True)**0.5
694 for i in range(1, self.config.altup_num_inputs):
957 - hidden_states[..., i] = self.altup_unembed_projections[i - 1](
958 - hidden_states[..., i])
959 - new_magnitude = torch.mean(hidden_states[..., i]**2,
695 + hidden_states[i] = self.altup_unembed_projections[i - 1](
696 + hidden_states[i])
697 + new_magnitude = torch.mean(hidden_states[i]**2,
698 dim=-1,
699 keepdim=True)**0.5
962 - hidden_states[..., i] *= target_magnitude / torch.maximum(
700 + hidden_states[i] *= target_magnitude / torch.maximum(
701 new_magnitude, self.eps)
964 - # [num_tokens,hidden_size, altup_num_inputs] -> [num_tokens,hidden_size]
965 - hidden_states = torch.mean(hidden_states, dim=-1)
966 - return hidden_states
702 + # [altup_num_inputs,num_tokens,hidden_size] -> [num_tokens,hidden_size]
703 + hidden_states = torch.mean(hidden_states, dim=0)
704
968 - def forward(
969 - self,
970 - input_ids: Optional[torch.Tensor],
971 - positions: torch.Tensor,
972 - per_layer_inputs: Optional[torch.Tensor] = None,
973 - intermediate_tensors: Optional[IntermediateTensors] = None,
974 - inputs_embeds: Optional[torch.Tensor] = None,
975 - **kwargs,
976 - ) -> Union[torch.Tensor, IntermediateTensors]:
977 - if self.fast_prefill_enabled:
978 - hidden_states = self.fast_prefill_forward(
979 - input_ids,
980 - positions,
981 - inputs_embeds,
982 - per_layer_inputs,
983 - **kwargs,
984 - )
985 - else:
986 - hidden_states = self.normal_forward(
987 - input_ids,
988 - positions,
989 - inputs_embeds,
990 - per_layer_inputs,
991 - **kwargs,
992 - )
993 - hidden_states = self.altup_unembed(hidden_states)
705 return self.norm(hidden_states)
706
707 def load_weights(self, weights: Iterable[tuple[str,
vllm/model_executor/models/gemma3n_mm.py
+1 -1
@@ -620,7 +620,7 @@ class Gemma3nForConditionalGeneration(nn.Module, SupportsMultiModal):
620 # NOTE (NickLucche) Each pass needs tokens to compute PLE so we cache
621 # them here, as the model forward has only access to the input_embeds.
622 if input_ids is not None:
623 - per_layer_inputs = self.language_model.model.self_decoder.get_per_layer_input_embeddings(
623 + per_layer_inputs = self.language_model.model.get_per_layer_input_embeddings(
624 input_ids)
625 per_layer_inputs = per_layer_inputs.reshape(
626 -1, self.config.text_config.num_hidden_layers,