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
[V1] Enable prefill optimization for Gemma3n (#22628)
Signed-off-by: Yong Hoon Shin <yhshin@meta.com>
Yong Hoon Shin committed
Aug 28, 2025 at 14:54 UTC
cb293f6a790d555d6d7ced872118ff029bd828e8
9 files changed
+583
-228
tests/v1/e2e/test_kv_sharing_fast_prefill.py
-57
@@ -2,7 +2,6 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import random
5
-from typing import Optional, Union
5
6
import pytest
7
import torch
@@ -10,12 +9,6 @@ import torch
9
from vllm import LLM, SamplingParams
10
from vllm.config import CompilationConfig, CompilationLevel
11
from vllm.distributed import cleanup_dist_env_and_memory
13
-from vllm.forward_context import get_forward_context
14
-from vllm.model_executor.models.gemma3n_mm import (
15
- Gemma3nForConditionalGeneration)
16
-from vllm.model_executor.models.registry import ModelRegistry
17
-from vllm.model_executor.models.utils import extract_layer_index
18
-from vllm.sequence import IntermediateTensors
12
13
from ...utils import fork_new_process_for_each_test
14
@@ -23,54 +16,6 @@ from ...utils import fork_new_process_for_each_test
16
SEED = 42
17
18
26
-class TestGemma3nForConditionalGeneration(Gemma3nForConditionalGeneration):
27
-
28
- def forward(
29
- self,
30
- input_ids: torch.Tensor,
31
- positions: torch.Tensor,
32
- intermediate_tensors: Optional[IntermediateTensors] = None,
33
- inputs_embeds: Optional[torch.Tensor] = None,
34
- **kwargs,
35
- ) -> Union[torch.Tensor, IntermediateTensors]:
36
- hidden_states = super().forward(input_ids, positions,
37
- intermediate_tensors, inputs_embeds,
38
- **kwargs)
39
- attn_metadata = get_forward_context().attn_metadata
40
- # attn_metadata is None during dummy runs
41
- if (attn_metadata is not None
42
- and self.language_model.cache_config.kv_sharing_fast_prefill):
43
- assert isinstance(attn_metadata, dict) # true in V1
44
- # Gemma3n-E2B has 30 layers, with last 20 layers being
45
- # cross-decoder layers. Check attention metadata is correct
46
- for layer_name, metadata in attn_metadata.items():
47
- layer_idx = extract_layer_index(layer_name)
48
- if layer_idx >= 20:
49
- assert hasattr(metadata, 'logits_indices_padded')
50
- assert hasattr(metadata, 'num_logits_indices')
51
- else:
52
- assert not hasattr(metadata, 'logits_indices_padded')
53
- assert not hasattr(metadata, 'num_logits_indices')
54
-
55
- # Last layer will be a KV sharing layer
56
- layer_attn_metadata = attn_metadata[
57
- self.language_model.model.layers[-1].self_attn.attn.layer_name]
58
- logits_indices_padded = (layer_attn_metadata.logits_indices_padded)
59
- assert logits_indices_padded is not None
60
- num_logits_indices = layer_attn_metadata.num_logits_indices
61
- assert num_logits_indices > 0
62
- # Reset hidden states to random values and
63
- # only set logits at logits_indices to valid values
64
- # Because logits_indices are the only positions that are used
65
- # for output token sampling, this still produces same outputs
66
- logits_hs = hidden_states[logits_indices_padded]
67
- hidden_states = torch.randn_like(hidden_states)
68
- gen_indices = logits_indices_padded[:num_logits_indices]
69
- hidden_states[gen_indices] = logits_hs[:num_logits_indices]
70
-
71
- return hidden_states
72
-
73
-
19
@pytest.fixture
20
def test_prompts():
21
"""
@@ -124,8 +69,6 @@ def test_kv_sharing_fast_prefill(
69
enforce_eager: bool,
70
test_prompts: list[str],
71
):
127
- ModelRegistry.register_model("Gemma3nForConditionalGeneration",
128
- TestGemma3nForConditionalGeneration)
72
sampling_params = SamplingParams(temperature=0.0, max_tokens=100)
73
compilation_config = CompilationConfig(
74
# This allows vLLM compilation backend to handle allocating and
vllm/config/cache.py
+7
-5
@@ -145,12 +145,19 @@ class CacheConfig:
145
146
self._verify_cache_dtype()
147
self._verify_prefix_caching()
148
+ self._verify_kv_sharing_fast_prefill()
149
150
def metrics_info(self):
151
# convert cache_config to dict(key: str, value: str) for prometheus
152
# metrics info
153
return {key: str(value) for key, value in self.__dict__.items()}
154
155
+ def _verify_kv_sharing_fast_prefill(self) -> None:
156
+ if self.kv_sharing_fast_prefill and not envs.VLLM_USE_V1:
157
+ raise NotImplementedError(
158
+ "Fast prefill optimization for KV sharing is not supported "
159
+ "in V0 currently.")
160
+
161
@model_validator(mode='after')
162
def _verify_args(self) -> Self:
163
if self.cpu_offload_gb < 0:
@@ -162,11 +169,6 @@ class CacheConfig:
169
"GPU memory utilization must be less than 1.0. Got "
170
f"{self.gpu_memory_utilization}.")
171
165
- if self.kv_sharing_fast_prefill:
166
- logger.warning_once(
167
- "--kv-sharing-fast-prefill is currently work in progress "
168
- "and not functional yet (i.e. no prefill savings)")
169
-
172
return self
173
174
def _verify_cache_dtype(self) -> None:
vllm/model_executor/models/gemma3n.py
+354
-65
@@ -23,9 +23,11 @@ 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
27
from vllm.compilation.decorators import support_torch_compile
28
from vllm.config import CacheConfig, VllmConfig
29
from vllm.distributed import get_tensor_model_parallel_world_size
30
+from vllm.forward_context import get_forward_context
31
from vllm.logger import init_logger
32
from vllm.model_executor.layers.activation import (_ACTIVATION_REGISTRY,
33
GeluAndMul,
@@ -45,6 +47,7 @@ from vllm.model_executor.model_loader.weight_utils import (
47
default_weight_loader, maybe_remap_kv_scale_name)
48
from vllm.model_executor.sampling_metadata import SamplingMetadata
49
from vllm.sequence import IntermediateTensors
50
+from vllm.v1.attention.backends.utils import KVSharingFastPrefillMetadata
51
52
from .interfaces import SupportsQuant
53
from .utils import (AutoWeightsLoader, extract_layer_index,
@@ -533,7 +536,178 @@ class Gemma3nDecoderLayer(nn.Module):
536
return corrected_predictions
537
538
536
-@support_torch_compile
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)
711
class Gemma3nTextModel(nn.Module, SupportsQuant):
712
713
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
@@ -543,7 +717,6 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
717
quant_config = vllm_config.quant_config
718
self.config = config
719
self.quant_config = quant_config
546
-
720
self.embed_tokens = VocabParallelEmbedding(
721
config.vocab_size,
722
config.hidden_size,
@@ -613,95 +786,211 @@ class Gemma3nTextModel(nn.Module, SupportsQuant):
786
lambda prefix: Gemma3nDecoderLayer(
787
config, cache_config, quant_config, prefix=prefix),
788
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
+
820
self.norm = RMSNorm(
821
config.hidden_size,
822
eps=config.rms_norm_eps,
823
)
620
- self.eps = torch.tensor(torch.finfo().min)
824
622
- def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
623
- return self.embed_tokens(input_ids) * self.embed_scale
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
+ )
847
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
848
+ def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
849
+ return self.self_decoder.get_input_embeddings(input_ids)
850
637
- def forward(
851
+ def fast_prefill_forward(
852
self,
639
- input_ids: Optional[torch.Tensor],
853
+ input_ids: torch.Tensor,
854
positions: torch.Tensor,
641
- per_layer_inputs: torch.Tensor,
642
- intermediate_tensors: Optional[IntermediateTensors] = None,
855
inputs_embeds: Optional[torch.Tensor] = None,
856
+ per_layer_inputs: Optional[torch.Tensor] = None,
857
**kwargs,
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)
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
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,
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
+ )
891
+
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,
913
)
657
- per_layer_projection = self.per_layer_projection_norm(
658
- per_layer_projection)
914
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
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])
920
else:
665
- per_layer_inputs = per_layer_projection
921
+ hidden_states = cross_decoder_hidden_states
922
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)
923
+ return hidden_states
924
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
- )
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
947
948
+ def altup_unembed(
949
+ self,
950
+ hidden_states: torch.Tensor,
951
+ ) -> torch.Tensor:
952
# Altup unembed.
691
- target_magnitude = torch.mean(hidden_states[0]**2,
953
+ target_magnitude = torch.mean(hidden_states[..., 0]**2,
954
dim=-1,
955
keepdim=True)**0.5
956
for i in range(1, self.config.altup_num_inputs):
695
- hidden_states[i] = self.altup_unembed_projections[i - 1](
696
- hidden_states[i])
697
- new_magnitude = torch.mean(hidden_states[i]**2,
957
+ hidden_states[..., i] = self.altup_unembed_projections[i - 1](
958
+ hidden_states[..., i])
959
+ new_magnitude = torch.mean(hidden_states[..., i]**2,
960
dim=-1,
961
keepdim=True)**0.5
700
- hidden_states[i] *= target_magnitude / torch.maximum(
962
+ hidden_states[..., i] *= target_magnitude / torch.maximum(
963
new_magnitude, self.eps)
702
- # [altup_num_inputs,num_tokens,hidden_size] -> [num_tokens,hidden_size]
703
- hidden_states = torch.mean(hidden_states, dim=0)
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
967
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)
994
return self.norm(hidden_states)
995
996
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.get_per_layer_input_embeddings(
623
+ per_layer_inputs = self.language_model.model.self_decoder.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,
vllm/v1/attention/backends/utils.py
+126
-13
@@ -4,11 +4,13 @@ import abc
4
import enum
5
import functools
6
from abc import abstractmethod
7
-from dataclasses import dataclass, make_dataclass
8
-from typing import TYPE_CHECKING, Any, ClassVar, Generic, Optional, TypeVar
7
+from dataclasses import dataclass, fields, make_dataclass
8
+from typing import (TYPE_CHECKING, Any, ClassVar, Generic, Optional, Protocol,
9
+ TypeVar)
10
11
import numpy as np
12
import torch
13
+from typing_extensions import runtime_checkable
14
15
from vllm.config import VllmConfig, get_layers_from_vllm_config
16
from vllm.utils import cdiv
@@ -19,7 +21,8 @@ if TYPE_CHECKING:
21
from vllm.v1.worker.gpu_input_batch import InputBatch
22
23
import vllm.envs as envs
22
-from vllm.attention.backends.abstract import AttentionBackend
24
+from vllm.attention.backends.abstract import (AttentionBackend,
25
+ AttentionMetadata)
26
from vllm.attention.layer import Attention
27
from vllm.distributed.kv_transfer.kv_connector.utils import (
28
get_kv_connector_cache_layout)
@@ -65,6 +68,10 @@ class CommonAttentionMetadata:
68
69
causal: bool = True
70
71
+ # Needed by FastPrefillAttentionBuilder
72
+ logits_indices_padded: Optional[torch.Tensor] = None
73
+ num_logits_indices: Optional[int] = None
74
+
75
76
@dataclass
77
class UbatchSlice:
@@ -542,6 +549,69 @@ def make_local_attention_virtual_batches(
549
)
550
551
552
+def make_kv_sharing_fast_prefill_common_attn_metadata(
553
+ common_attn_metadata: CommonAttentionMetadata,
554
+) -> CommonAttentionMetadata:
555
+ if common_attn_metadata.max_query_len == 1:
556
+ # All requests are decode (assume 1 token for now)
557
+ # Skip computing fast prefill path
558
+ return common_attn_metadata
559
+
560
+ assert common_attn_metadata.logits_indices_padded is not None
561
+ assert common_attn_metadata.num_logits_indices is not None
562
+
563
+ logits_indices_padded = common_attn_metadata.logits_indices_padded
564
+ num_logits_indices = common_attn_metadata.num_logits_indices
565
+ # Get rid of CUDAGraph padding, if any
566
+ logits_indices = logits_indices_padded[:num_logits_indices]
567
+ num_reqs = common_attn_metadata.num_reqs
568
+ query_start_loc = common_attn_metadata.query_start_loc
569
+ seq_lens = common_attn_metadata.seq_lens
570
+ # Example inputs
571
+ # num_reqs: 3
572
+ # generation_indices: [14, 18, 19, 27]
573
+ # query_start_loc: [0, 15, 20, 28]
574
+ # seq_lens: [41, 31, 40]
575
+
576
+ # Find how many decode indices belong to each request
577
+ # request_ids: [0, 1, 1, 2]
578
+ request_ids = torch.bucketize(logits_indices,
579
+ query_start_loc[1:],
580
+ right=True)
581
+
582
+ # Figure out how many tokens are in each request
583
+ # num_decode_tokens: [1, 2, 1]
584
+ num_decode_tokens = torch.bincount(request_ids, minlength=num_reqs)
585
+
586
+ # Calculate new query_start_loc with tokens in generation_indices
587
+ # decode_query_start_loc: [0, 1, 3, 4]
588
+ decode_query_start_loc = torch.empty(num_reqs + 1,
589
+ device=query_start_loc.device,
590
+ dtype=query_start_loc.dtype)
591
+
592
+ decode_query_start_loc[0] = 0
593
+ decode_query_start_loc[1:] = torch.cumsum(num_decode_tokens, dim=0)
594
+ decode_max_query_len = int(num_decode_tokens.max().item())
595
+ total_num_decode_tokens = int(num_decode_tokens.sum().item())
596
+
597
+ common_attn_metadata = CommonAttentionMetadata(
598
+ query_start_loc=decode_query_start_loc,
599
+ query_start_loc_cpu=decode_query_start_loc.to("cpu",
600
+ non_blocking=True),
601
+ seq_lens=seq_lens,
602
+ seq_lens_cpu=seq_lens.to("cpu", non_blocking=True),
603
+ num_computed_tokens_cpu=common_attn_metadata.num_computed_tokens_cpu,
604
+ num_reqs=num_reqs,
605
+ num_actual_tokens=total_num_decode_tokens,
606
+ max_query_len=decode_max_query_len,
607
+ max_seq_len=common_attn_metadata.max_seq_len,
608
+ block_table_tensor=common_attn_metadata.block_table_tensor,
609
+ slot_mapping=common_attn_metadata.slot_mapping,
610
+ causal=True,
611
+ )
612
+ return common_attn_metadata
613
+
614
+
615
def subclass_attention_backend(
616
name_prefix: str, attention_backend_cls: type[AttentionBackend],
617
builder_cls: type[AttentionMetadataBuilder[M]]
@@ -679,13 +749,56 @@ def subclass_attention_metadata(
749
return Wrapped
750
751
682
-def make_kv_sharing_fast_prefill_attention_metadata(
683
- metadata_cls: Any, ) -> Any:
684
- """
685
- Return a new subclass of `metadata_cls` for fast prefill
686
- """
687
- return subclass_attention_metadata(
688
- name_prefix="KVSharingFastPrefill",
689
- metadata_cls=metadata_cls,
690
- fields=KV_SHARING_FAST_PREFILL_METADATA_FIELDS,
691
- )
752
+@runtime_checkable
753
+class KVSharingFastPrefillMetadata(Protocol):
754
+ logits_indices_padded: torch.Tensor
755
+ num_logits_indices: int
756
+
757
+
758
+def create_fast_prefill_custom_backend(
759
+ prefix: str,
760
+ underlying_attn_backend: AttentionBackend,
761
+) -> type[AttentionBackend]:
762
+
763
+ underlying_builder = underlying_attn_backend.get_builder_cls()
764
+
765
+ class FastPrefillAttentionBuilder(underlying_builder): # type: ignore
766
+
767
+ def build(self,
768
+ common_prefix_len: int,
769
+ common_attn_metadata: CommonAttentionMetadata,
770
+ fast_build: bool = False) -> AttentionMetadata:
771
+ new_common_attn_metadata =\
772
+ make_kv_sharing_fast_prefill_common_attn_metadata(common_attn_metadata)
773
+ metadata = super().build(common_prefix_len,
774
+ new_common_attn_metadata, fast_build)
775
+
776
+ class KVSharingFastPrefillAttentionMetadata(
777
+ metadata.__class__, # type: ignore
778
+ KVSharingFastPrefillMetadata):
779
+
780
+ def __init__(self, metadata, common_attn_metadata):
781
+ # Shallow copy all fields in metadata cls
782
+ for field in fields(metadata.__class__):
783
+ setattr(self, field.name,
784
+ getattr(metadata, field.name))
785
+
786
+ # Set additional fields that will be used in model code
787
+ assert (common_attn_metadata.logits_indices_padded
788
+ is not None
789
+ and common_attn_metadata.num_logits_indices
790
+ is not None)
791
+ self.logits_indices_padded = \
792
+ common_attn_metadata.logits_indices_padded
793
+ self.num_logits_indices = \
794
+ common_attn_metadata.num_logits_indices
795
+
796
+ return KVSharingFastPrefillAttentionMetadata(
797
+ metadata, common_attn_metadata)
798
+
799
+ attn_backend = subclass_attention_backend(
800
+ name_prefix=prefix,
801
+ attention_backend_cls=underlying_attn_backend,
802
+ builder_cls=FastPrefillAttentionBuilder)
803
+
804
+ return attn_backend
vllm/v1/engine/async_llm.py
+7
@@ -335,6 +335,13 @@ class AsyncLLM(EngineClient):
335
returning the RequestOutput back to the caller.
336
"""
337
338
+ if (self.vllm_config.cache_config.kv_sharing_fast_prefill
339
+ and sampling_params.prompt_logprobs):
340
+ raise ValueError(
341
+ "--kv-sharing-fast-prefill produces incorrect logprobs for "
342
+ "prompt tokens, please disable it when the requests need "
343
+ "prompt logprobs")
344
+
345
try:
346
# We start the output_handler on the first call to generate() so
347
# we can call __init__ before the event loop, which enables us
vllm/v1/worker/gpu_model_runner.py
+53
-43
@@ -1,7 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-import dataclasses
4
import gc
5
import itertools
6
import time
@@ -58,7 +57,7 @@ from vllm.utils import (STR_DTYPE_TO_TORCH_DTYPE, DeviceMemoryProfiler,
57
supports_dynamo)
58
from vllm.v1.attention.backends.utils import (
59
AttentionCGSupport, AttentionMetadataBuilder, CommonAttentionMetadata,
61
- make_kv_sharing_fast_prefill_attention_metadata,
60
+ create_fast_prefill_custom_backend,
61
reorder_batch_to_split_decodes_and_prefills)
62
from vllm.v1.cudagraph_dispatcher import CudagraphDispatcher
63
from vllm.v1.kv_cache_interface import (AttentionSpec,
@@ -84,9 +83,10 @@ from vllm.v1.worker.kv_connector_model_runner_mixin import (
83
KVConnectorModelRunnerMixin, KVConnectorOutput)
84
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
85
87
-from .utils import (AttentionGroup, MultiModalBudget, bind_kv_cache,
88
- gather_mm_placeholders, initialize_kv_cache_for_kv_sharing,
89
- sanity_check_mm_encoder_outputs, scatter_mm_placeholders)
86
+from .utils import (AttentionGroup, MultiModalBudget,
87
+ add_kv_sharing_layers_to_kv_cache_groups, bind_kv_cache,
88
+ gather_mm_placeholders, sanity_check_mm_encoder_outputs,
89
+ scatter_mm_placeholders)
90
91
if TYPE_CHECKING:
92
import xgrammar as xgr
@@ -860,6 +860,8 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
860
max_seq_len=max_seq_len,
861
block_table_tensor=blk_table_tensor,
862
slot_mapping=slot_mapping,
863
+ logits_indices_padded=logits_indices_padded,
864
+ num_logits_indices=logits_indices.size(0),
865
causal=True,
866
)
867
@@ -884,28 +886,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
886
common_attn_metadata=common_attn_metadata,
887
))
888
887
- fast_prefill_metadata = attn_metadata_i
888
- if (self.cache_config.kv_sharing_fast_prefill
889
- and self.kv_sharing_fast_prefill_eligible_layers):
890
- # Dynamically create a a dataclass type that inherits
891
- # from attention metadata type but includes additional
892
- # fields logits_indices_padded and num_logits_indices
893
- # which are required for prefill truncation
894
- fast_prefill_metadata_type = (
895
- make_kv_sharing_fast_prefill_attention_metadata(
896
- metadata_cls=type(attn_metadata_i), ))
897
- fast_prefill_metadata = fast_prefill_metadata_type(
898
- **dataclasses.asdict(attn_metadata_i),
899
- logits_indices_padded=logits_indices_padded,
900
- num_logits_indices=logits_indices.size(0),
901
- )
902
-
889
for layer_name in attn_group.layer_names:
904
- if (self.cache_config.kv_sharing_fast_prefill
905
- and layer_name
906
- in self.kv_sharing_fast_prefill_eligible_layers):
907
- attn_metadata[layer_name] = fast_prefill_metadata
908
- continue
890
attn_metadata[layer_name] = attn_metadata_i
891
892
# Hot-Swap lora model
@@ -1484,6 +1465,12 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1465
return self.kv_connector_no_forward(scheduler_output,
1466
self.vllm_config)
1467
1468
+ if self.cache_config.kv_sharing_fast_prefill:
1469
+ assert not self.input_batch.num_prompt_logprobs, (
1470
+ "--kv-sharing-fast-prefill produces incorrect logprobs for "
1471
+ "prompt tokens, tokens, please disable it when the requests "
1472
+ "need prompt logprobs")
1473
+
1474
# Prepare the decoder inputs.
1475
(attn_metadata, logits_indices, spec_decode_metadata,
1476
num_scheduled_tokens_np, spec_decode_common_attn_metadata,
@@ -2742,6 +2729,13 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2729
# layer.
2730
for layer_name in layer_names:
2731
attn_backend = layers[layer_name].get_attn_backend()
2732
+
2733
+ if layer_name in self.kv_sharing_fast_prefill_eligible_layers:
2734
+ attn_backend = create_fast_prefill_custom_backend(
2735
+ "FastPrefill",
2736
+ attn_backend,
2737
+ )
2738
+
2739
key = attn_backend.full_cls_name()
2740
attn_backends[key] = attn_backend
2741
attn_backend_layers[key].append(layer_name)
@@ -3074,20 +3068,40 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3068
kv_caches = self._reshape_kv_cache_tensors(kv_cache_config,
3069
kv_cache_raw_tensors)
3070
3077
- # Setup `kv_cache_config` and `kv_caches` for models
3078
- # with cross-layer KV sharing
3079
- if self.shared_kv_cache_layers:
3080
- initialize_kv_cache_for_kv_sharing(
3081
- self.shared_kv_cache_layers,
3082
- kv_cache_config.kv_cache_groups,
3083
- kv_caches,
3084
- self.attn_groups,
3085
- self.runner_only_attn_layers,
3086
- )
3071
+ # Set up cross-layer KV cache sharing
3072
+ for layer_name, target_layer_name in self.shared_kv_cache_layers.items(
3073
+ ):
3074
+ logger.debug("%s reuses KV cache of %s", layer_name,
3075
+ target_layer_name)
3076
+ kv_caches[layer_name] = kv_caches[target_layer_name]
3077
+
3078
+ bind_kv_cache(kv_caches,
3079
+ self.compilation_config.static_forward_context,
3080
+ self.kv_caches)
3081
+ return kv_caches
3082
+
3083
+ def maybe_add_kv_sharing_layers_to_kv_cache_groups(
3084
+ self, kv_cache_config: KVCacheConfig) -> None:
3085
+ """
3086
+ Add layers that re-use KV cache to KV cache group of its target layer.
3087
+ Mapping of KV cache tensors happens in `initialize_kv_cache_tensors()`
3088
+ """
3089
+ if not self.shared_kv_cache_layers:
3090
+ # No cross-layer KV sharing, return
3091
+ return
3092
+
3093
+ add_kv_sharing_layers_to_kv_cache_groups(
3094
+ self.shared_kv_cache_layers,
3095
+ kv_cache_config.kv_cache_groups,
3096
+ self.runner_only_attn_layers,
3097
+ )
3098
+
3099
+ if self.cache_config.kv_sharing_fast_prefill:
3100
+ # In You Only Cache Once (https://arxiv.org/abs/2405.05254) or other
3101
+ # similar KV sharing setups, only the layers that generate KV caches
3102
+ # are involved in the prefill phase, enabling prefill to early exit.
3103
attn_layers = get_layers_from_vllm_config(self.vllm_config,
3104
Attention)
3089
- # Iterate in reversed order and add layers that re-use KV cache
3090
- # e.g. in YOCO-like KV sharing setups (e.g. Gemma3n)
3105
for layer_name in reversed(attn_layers):
3106
if layer_name in self.shared_kv_cache_layers:
3107
self.kv_sharing_fast_prefill_eligible_layers.add(
@@ -3095,11 +3109,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3109
else:
3110
break
3111
3098
- bind_kv_cache(kv_caches,
3099
- self.compilation_config.static_forward_context,
3100
- self.kv_caches)
3101
- return kv_caches
3102
-
3112
def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
3113
"""
3114
Initialize KV cache based on `kv_cache_config`.
@@ -3111,6 +3120,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3120
self.kv_cache_config = kv_cache_config
3121
self.may_reinitialize_input_batch(kv_cache_config)
3122
self.may_add_encoder_only_layers_to_kv_cache_config()
3123
+ self.maybe_add_kv_sharing_layers_to_kv_cache_groups(kv_cache_config)
3124
self.initialize_attn_backend(kv_cache_config)
3125
kv_caches = self.initialize_kv_cache_tensors(kv_cache_config)
3126
vllm/v1/worker/tpu_model_runner.py
+28
-11
@@ -55,9 +55,8 @@ from vllm.v1.worker.kv_connector_model_runner_mixin import (
55
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
56
from vllm.v1.worker.tpu_input_batch import CachedRequestState, InputBatch
57
58
-from .utils import (MultiModalBudget, bind_kv_cache,
59
- initialize_kv_cache_for_kv_sharing,
60
- sanity_check_mm_encoder_outputs)
58
+from .utils import (MultiModalBudget, add_kv_sharing_layers_to_kv_cache_groups,
59
+ bind_kv_cache, sanity_check_mm_encoder_outputs)
60
61
if TYPE_CHECKING:
62
from vllm.v1.core.sched.output import SchedulerOutput
@@ -1599,6 +1598,30 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1598
self.encoder_cache.clear()
1599
gc.collect()
1600
1601
+ def maybe_setup_cross_layer_kv_sharing(
1602
+ self,
1603
+ kv_caches: dict[str, torch.Tensor],
1604
+ kv_cache_config: KVCacheConfig,
1605
+ ) -> None:
1606
+ """
1607
+ Add layers that re-use KV cache to KV cache group of its target layer.
1608
+ Mapping of KV cache tensors happens in `initialize_kv_cache_tensors()`
1609
+ """
1610
+ if not self.shared_kv_cache_layers:
1611
+ # No cross-layer KV sharing, return
1612
+ return
1613
+
1614
+ add_kv_sharing_layers_to_kv_cache_groups(
1615
+ self.shared_kv_cache_layers,
1616
+ kv_cache_config.kv_cache_groups,
1617
+ )
1618
+
1619
+ for layer_name, target_layer_name in self.shared_kv_cache_layers.items(
1620
+ ):
1621
+ logger.debug("%s reuses KV cache of %s", layer_name,
1622
+ target_layer_name)
1623
+ kv_caches[layer_name] = kv_caches[target_layer_name]
1624
+
1625
def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
1626
"""
1627
Initialize KV cache based on `kv_cache_config`.
@@ -1664,14 +1687,8 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1687
else:
1688
raise NotImplementedError
1689
1667
- # Setup `kv_cache_config` and `kv_caches` for models
1668
- # with cross-layer KV sharing
1669
- if self.shared_kv_cache_layers:
1670
- initialize_kv_cache_for_kv_sharing(
1671
- self.shared_kv_cache_layers,
1672
- kv_cache_config.kv_cache_groups,
1673
- kv_caches,
1674
- )
1690
+ # Set up cross-layer KV cache sharing if needed
1691
+ self.maybe_setup_cross_layer_kv_sharing(kv_caches, kv_cache_config)
1692
1693
bind_kv_cache(
1694
kv_caches,
vllm/v1/worker/utils.py
+7
-33
@@ -203,12 +203,9 @@ def gather_mm_placeholders(
203
return placeholders[is_embed]
204
205
206
-def initialize_kv_cache_for_kv_sharing(
206
+def add_kv_sharing_layers_to_kv_cache_groups(
207
shared_kv_cache_layers: dict[str, str],
208
kv_cache_groups: list[KVCacheGroupSpec],
209
- kv_caches: dict[str, torch.Tensor],
210
- # Optional for now to avoid breaking TPU
211
- attn_groups: Optional[list[list[AttentionGroup]]] = None,
209
runner_only_attn_layers: Optional[set[str]] = None,
210
) -> None:
211
"""
@@ -223,38 +220,15 @@ def initialize_kv_cache_for_kv_sharing(
220
means this layer will perform attention using the keys and values
221
from the KV cache of `shared_kv_cache_layers[layer_name]`.
222
kv_cache_groups: The KV cache groups of the model.
226
- kv_caches: The allocated kv_caches with layer names as keys.
227
- Note that layers in shared_kv_cache_layers.keys() are not
228
- originally included as it only contains layers which have its own
229
- KV cache allocation.
230
- attn_groups: Optional list of attention groups. Layers in the same KV
231
- cache group may be placed in different attention groups if they
232
- have different attention backends. Currently only provided by
233
- GPU model runner.
223
"""
235
- # mapping from layer name to tuple of (kv_cache_group_idx, attn_group_idx)
236
- layer_to_attn_group_idx: dict[str, tuple[int, int]] = {}
237
- if attn_groups:
238
- for kv_cache_group_idx, kv_attn_groups in enumerate(attn_groups):
239
- for attn_group_idx, attn_group in enumerate(kv_attn_groups):
240
- for layer_name in attn_group.layer_names:
241
- layer_to_attn_group_idx[layer_name] = (kv_cache_group_idx,
242
- attn_group_idx)
243
- else:
244
- for kv_cache_group_idx, kv_cache_group in enumerate(kv_cache_groups):
245
- for layer_name in kv_cache_group.layer_names:
246
- # attn group idx default to 0 if not provided
247
- layer_to_attn_group_idx[layer_name] = (kv_cache_group_idx, 0)
224
+ layer_to_kv_cache_group: dict[str, KVCacheGroupSpec] = {}
225
+ for kv_cache_group in kv_cache_groups:
226
+ for layer_name in kv_cache_group.layer_names:
227
+ layer_to_kv_cache_group[layer_name] = kv_cache_group
228
229
for layer_name, target_layer_name in shared_kv_cache_layers.items():
250
- kv_caches[layer_name] = kv_caches[target_layer_name]
251
- kv_cache_group_idx = layer_to_attn_group_idx[target_layer_name][0]
252
- kv_cache_groups[kv_cache_group_idx].layer_names.append(layer_name)
253
-
254
- if attn_groups:
255
- attn_group_idx = layer_to_attn_group_idx[target_layer_name][1]
256
- attn_groups[kv_cache_group_idx][attn_group_idx].layer_names.append(
257
- layer_name)
230
+ tgt_kv_cache_group = layer_to_kv_cache_group[target_layer_name]
231
+ tgt_kv_cache_group.layer_names.append(layer_name)
232
233
if runner_only_attn_layers is not None:
234
runner_only_attn_layers.add(layer_name)