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 15, 02:15 UTC
[Model] New model support for microsoft/Phi-4-mini-flash-reasoning (#20702)
Signed-off-by: Congcong Chen <congcongchen@microsoft.com>
Congcong Chen committed
Jul 12, 2025 at 06:02 UTC
2c11a738b35e6e65731c5ad7774581ca2c33dc6a
22 files changed
+1869
-41
csrc/mamba/mamba_ssm/selective_scan_fwd.cu
+25
-24
@@ -312,19 +312,20 @@ void selective_scan_fwd_launch(SSMParamsBase ¶ms, cudaStream_t stream) {
312
// kIsVariableB, kIsVariableC and kHasZ are all set to True to reduce binary size
313
constexpr bool kIsVariableB = true;
314
constexpr bool kIsVariableC = true;
315
- constexpr bool kHasZ = true;
315
BOOL_SWITCH(params.seqlen % (kNThreads * kNItems) == 0, kIsEvenLen, [&] {
317
- BOOL_SWITCH(params.query_start_loc_ptr != nullptr , kVarlen, [&] {
318
- using Ktraits = Selective_Scan_fwd_kernel_traits<kNThreads, kNItems, kNRows, kIsEvenLen, kIsVariableB, kIsVariableC, kHasZ, kVarlen, input_t, weight_t>;
319
- constexpr int kSmemSize = Ktraits::kSmemSize + kNRows * MAX_DSTATE * sizeof(typename Ktraits::scan_t);
320
- dim3 grid(params.batch, params.dim / kNRows);
321
- auto kernel = &selective_scan_fwd_kernel<Ktraits>;
322
- if (kSmemSize >= 48 * 1024) {
323
- C10_CUDA_CHECK(cudaFuncSetAttribute(
324
- (void *) kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, kSmemSize));
325
- }
326
- kernel<<<grid, Ktraits::kNThreads, kSmemSize, stream>>>(params);
327
- C10_CUDA_KERNEL_LAUNCH_CHECK();
316
+ BOOL_SWITCH(params.z_ptr != nullptr , kHasZ, [&] {
317
+ BOOL_SWITCH(params.query_start_loc_ptr != nullptr , kVarlen, [&] {
318
+ using Ktraits = Selective_Scan_fwd_kernel_traits<kNThreads, kNItems, kNRows, kIsEvenLen, kIsVariableB, kIsVariableC, kHasZ, kVarlen, input_t, weight_t>;
319
+ constexpr int kSmemSize = Ktraits::kSmemSize + kNRows * MAX_DSTATE * sizeof(typename Ktraits::scan_t);
320
+ dim3 grid(params.batch, params.dim / kNRows);
321
+ auto kernel = &selective_scan_fwd_kernel<Ktraits>;
322
+ if (kSmemSize >= 48 * 1024) {
323
+ C10_CUDA_CHECK(cudaFuncSetAttribute(
324
+ kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, kSmemSize));
325
+ }
326
+ kernel<<<grid, Ktraits::kNThreads, kSmemSize, stream>>>(params);
327
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
328
+ });
329
});
330
});
331
}
@@ -612,19 +613,20 @@ void selective_scan_fwd(const torch::Tensor &u, const torch::Tensor &delta,
613
614
at::Tensor z, out_z;
615
const bool has_z = z_.has_value();
615
- TORCH_CHECK(has_z, "has_z = False is disabled in favor of reduced binary size")
616
- z = z_.value();
617
- TORCH_CHECK(z.scalar_type() == input_type);
618
- TORCH_CHECK(z.is_cuda());
619
- TORCH_CHECK(z.stride(-1) == 1 || z.size(-1) == 1);
620
- if (varlen){
621
- CHECK_SHAPE(z, dim, seqlen);
622
- } else {
623
- CHECK_SHAPE(z, batch_size, dim, seqlen);
616
+ if (has_z) {
617
+ z = z_.value();
618
+ TORCH_CHECK(z.scalar_type() == input_type);
619
+ TORCH_CHECK(z.is_cuda());
620
+ TORCH_CHECK(z.stride(-1) == 1 || z.size(-1) == 1);
621
+ if (varlen){
622
+ CHECK_SHAPE(z, dim, seqlen);
623
+ } else {
624
+ CHECK_SHAPE(z, batch_size, dim, seqlen);
625
+ }
626
+
627
+ out_z = z;
628
}
629
626
- out_z = z;
627
-
630
// Right now u has BHL layout and delta has HBL layout, and we want out to have HBL layout
631
at::Tensor out = delta;
632
TORCH_CHECK(ssm_states.scalar_type() == input_type);
@@ -653,4 +655,3 @@ void selective_scan_fwd(const torch::Tensor &u, const torch::Tensor &delta,
655
selective_scan_fwd_cuda<input_t, weight_t>(params, stream);
656
});
657
}
656
-
docs/models/supported_models.md
+1
@@ -374,6 +374,7 @@ Specified using `--task generate`.
374
| `Phi3ForCausalLM` | Phi-4, Phi-3 | `microsoft/Phi-4-mini-instruct`, `microsoft/Phi-4`, `microsoft/Phi-3-mini-4k-instruct`, `microsoft/Phi-3-mini-128k-instruct`, `microsoft/Phi-3-medium-128k-instruct`, etc. | ✅︎ | ✅︎ | ✅︎ |
375
| `Phi3SmallForCausalLM` | Phi-3-Small | `microsoft/Phi-3-small-8k-instruct`, `microsoft/Phi-3-small-128k-instruct`, etc. | | ✅︎ | ✅︎ |
376
| `PhiMoEForCausalLM` | Phi-3.5-MoE | `microsoft/Phi-3.5-MoE-instruct`, etc. | ✅︎ | ✅︎ | ✅︎ |
377
+| `Phi4FlashForCausalLM` | Phi-4-mini-flash-reasoning | `microsoft/microsoft/Phi-4-mini-instruct`, etc. | | | |
378
| `PersimmonForCausalLM` | Persimmon | `adept/persimmon-8b-base`, `adept/persimmon-8b-chat`, etc. | | ✅︎ | ✅︎ |
379
| `Plamo2ForCausalLM` | PLaMo2 | `pfnet/plamo-2-1b`, `pfnet/plamo-2-8b`, etc. | | | |
380
| `QWenLMHeadModel` | Qwen | `Qwen/Qwen-7B`, `Qwen/Qwen-7B-Chat`, etc. | ✅︎ | ✅︎ | ✅︎ |
tests/models/registry.py
+4
@@ -248,6 +248,10 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
248
"Phi3SmallForCausalLM": _HfExamplesInfo("microsoft/Phi-3-small-8k-instruct",
249
trust_remote_code=True,
250
v0_only=True),
251
+ "Phi4FlashForCausalLM": _HfExamplesInfo("microsoft/Phi-4-mini-flash-reasoning", # noqa: E501
252
+ trust_remote_code=True,
253
+ v0_only=True,
254
+ max_model_len=10240),
255
"PhiMoEForCausalLM": _HfExamplesInfo("microsoft/Phi-3.5-MoE-instruct",
256
trust_remote_code=True),
257
"Plamo2ForCausalLM": _HfExamplesInfo("pfnet/plamo-2-1b",
tests/models/test_initialization.py
+3
@@ -103,6 +103,9 @@ def test_can_initialize(model_arch: str, monkeypatch: pytest.MonkeyPatch):
103
_initialize_kv_caches_v1), monkeypatch.context() as m):
104
if model_info.v0_only:
105
m.setenv("VLLM_USE_V1", "0")
106
+ if model_arch == "Phi4FlashForCausalLM":
107
+ # Phi4FlashForCausalLM only supports DIFFERENTIAL_FLASH_ATTN backend
108
+ m.setenv("VLLM_ATTENTION_BACKEND", "DIFFERENTIAL_FLASH_ATTN")
109
LLM(
110
model_info.default,
111
tokenizer=model_info.tokenizer,
tests/test_utils.py
+25
@@ -458,6 +458,31 @@ def test_bind_kv_cache():
458
assert ctx['layers.2.self_attn'].kv_cache[0] is kv_cache[2]
459
assert ctx['layers.3.self_attn'].kv_cache[0] is kv_cache[3]
460
461
+def test_bind_kv_cache_kv_sharing():
462
+ from vllm.attention import Attention
463
+
464
+ ctx = {
465
+ 'layers.0.self_attn': Attention(32, 128, 0.1),
466
+ 'layers.1.self_attn': Attention(32, 128, 0.1),
467
+ 'layers.2.self_attn': Attention(32, 128, 0.1),
468
+ 'layers.3.self_attn': Attention(32, 128, 0.1),
469
+ }
470
+ kv_cache = [
471
+ torch.zeros((1, )),
472
+ torch.zeros((1, )),
473
+ torch.zeros((1, )),
474
+ torch.zeros((1, )),
475
+ ]
476
+ shared_kv_cache_layers = {
477
+ 'layers.2.self_attn': 'layers.1.self_attn',
478
+ 'layers.3.self_attn': 'layers.0.self_attn'
479
+ }
480
+ bind_kv_cache(ctx, [kv_cache], shared_kv_cache_layers)
481
+ assert ctx['layers.0.self_attn'].kv_cache[0] is kv_cache[0]
482
+ assert ctx['layers.1.self_attn'].kv_cache[0] is kv_cache[1]
483
+ assert ctx['layers.2.self_attn'].kv_cache[0] is kv_cache[1]
484
+ assert ctx['layers.3.self_attn'].kv_cache[0] is kv_cache[0]
485
+
486
def test_bind_kv_cache_non_attention():
487
from vllm.attention import Attention
488
vllm/attention/backends/blocksparse_attn.py
+2
-1
@@ -308,7 +308,8 @@ class BlocksparseFlashAttentionImpl(AttentionImpl):
308
kv_sharing_target_layer_name: Optional[str] = None,
309
) -> None:
310
if kv_sharing_target_layer_name is not None:
311
- raise NotImplementedError("KV sharing is not supported in V0.")
311
+ raise NotImplementedError("KV sharing is not supported in V0 "
312
+ "BLOCK_SPARSE_FLASH_ATTN Backend.")
313
assert blocksparse_params is not None
314
assert alibi_slopes is None, ValueError(
315
"Alibi not support for blocksparse flash attention.")
vllm/attention/backends/differential_flash_attn.py
new
+1000
@@ -0,0 +1,1000 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""" An implementation of https://arxiv.org/pdf/2410.05258 """
4
+from collections import defaultdict
5
+from dataclasses import dataclass
6
+from itertools import accumulate
7
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type
8
+
9
+import torch
10
+from einops import rearrange
11
+
12
+from vllm import _custom_ops as ops
13
+# yapf conflicts with isort for this block
14
+# yapf: disable
15
+from vllm.attention.backends.abstract import (AttentionBackend, AttentionImpl,
16
+ AttentionLayer,
17
+ AttentionMetadata,
18
+ AttentionMetadataBuilder,
19
+ AttentionType,
20
+ is_quantized_kv_cache)
21
+from vllm.attention.backends.flash_attn import FlashAttentionBackend
22
+# yapf: enable
23
+from vllm.attention.backends.utils import (PAD_SLOT_ID, CommonAttentionState,
24
+ compute_slot_mapping,
25
+ compute_slot_mapping_start_idx,
26
+ is_all_cross_attn_metadata_set,
27
+ is_all_encoder_attn_metadata_set,
28
+ is_block_tables_empty)
29
+from vllm.attention.utils.fa_utils import (flash_attn_supports_fp8,
30
+ get_flash_attn_version)
31
+from vllm.logger import init_logger
32
+from vllm.multimodal import MultiModalPlaceholderMap
33
+from vllm.utils import async_tensor_h2d, make_tensor_with_pad
34
+from vllm.vllm_flash_attn import (flash_attn_varlen_func,
35
+ flash_attn_with_kvcache)
36
+
37
+if TYPE_CHECKING:
38
+ from vllm.worker.model_runner import (ModelInputForGPUBuilder,
39
+ ModelInputForGPUWithSamplingMetadata)
40
+
41
+logger = init_logger(__name__)
42
+
43
+
44
+class DifferentialFlashAttentionBackend(AttentionBackend):
45
+ accept_output_buffer = False
46
+
47
+ @staticmethod
48
+ def get_supported_head_sizes() -> List[int]:
49
+ return [32, 64, 96, 128, 160, 192, 224, 256]
50
+
51
+ @staticmethod
52
+ def get_kv_cache_shape(
53
+ num_blocks: int,
54
+ block_size: int,
55
+ num_kv_heads: int,
56
+ head_size: int,
57
+ ) -> Tuple[int, ...]:
58
+ if block_size % 16 != 0:
59
+ raise ValueError("Block size must be a multiple of 16.")
60
+ assert num_kv_heads % 2 == 0, "num_kv_heads must be divisible by 2"
61
+ return (2, 2, num_blocks, block_size, num_kv_heads // 2, head_size)
62
+
63
+ @staticmethod
64
+ def get_name() -> str:
65
+ return "DIFFERENTIAL_FLASH_ATTN"
66
+
67
+ @staticmethod
68
+ def get_impl_cls() -> Type["DifferentialFlashAttentionImpl"]:
69
+ return DifferentialFlashAttentionImpl
70
+
71
+ @staticmethod
72
+ def get_metadata_cls() -> Type["DifferentialFlashAttentionMetadata"]:
73
+ return DifferentialFlashAttentionMetadata
74
+
75
+ @staticmethod
76
+ def get_builder_cls() -> Type["DifferentialFlashAttentionMetadataBuilder"]:
77
+ return DifferentialFlashAttentionMetadataBuilder
78
+
79
+ @staticmethod
80
+ def get_state_cls() -> Type["CommonAttentionState"]:
81
+ return CommonAttentionState
82
+
83
+ @staticmethod
84
+ def swap_blocks(
85
+ src_kv_cache: torch.Tensor,
86
+ dst_kv_cache: torch.Tensor,
87
+ src_to_dst: torch.Tensor,
88
+ ) -> None:
89
+ src_key_cache = src_kv_cache[0]
90
+ dst_key_cache = dst_kv_cache[0]
91
+ ops.swap_blocks(src_key_cache, dst_key_cache, src_to_dst)
92
+ src_value_cache = src_kv_cache[1]
93
+ dst_value_cache = dst_kv_cache[1]
94
+ ops.swap_blocks(src_value_cache, dst_value_cache, src_to_dst)
95
+
96
+ @staticmethod
97
+ def copy_blocks(
98
+ kv_caches: List[torch.Tensor],
99
+ src_to_dists: torch.Tensor,
100
+ ) -> None:
101
+ key_caches = [kv_cache[0] for kv_cache in kv_caches]
102
+ value_caches = [kv_cache[1] for kv_cache in kv_caches]
103
+
104
+ ops.copy_blocks(key_caches, value_caches, src_to_dists)
105
+
106
+
107
+@dataclass
108
+class DifferentialFlashAttentionMetadata(AttentionMetadata):
109
+ """Metadata for FlashAttentionBackend.
110
+
111
+ NOTE: Any python object stored here is not updated when it is
112
+ cuda-graph replayed. If you have values that need to be changed
113
+ dynamically, it should be stored in tensor. The tensor has to be
114
+ updated from `CUDAGraphRunner.forward` API.
115
+ """
116
+ # (batch_size,). The sequence length per sequence. Sequence length means
117
+ # the computed tokens + new tokens None if it is a decoding.
118
+ seq_lens: Optional[List[int]]
119
+ # seq_lens stored as a tensor.
120
+ seq_lens_tensor: Optional[torch.Tensor]
121
+
122
+ # NOTE(sang): Definition of context_len, query_len, and seq_len.
123
+ # |---------- N-1 iteration --------|
124
+ # |---------------- N iteration ---------------------|
125
+ # |- tokenA -|......................|-- newTokens ---|
126
+ # |---------- context_len ----------|
127
+ # |-------------------- seq_len ---------------------|
128
+ # |-- query_len ---|
129
+
130
+ # Maximum sequence length among prefill batch. 0 if there are decoding
131
+ # requests only.
132
+ max_prefill_seq_len: int
133
+ # Maximum sequence length among decode batch. 0 if there are prefill
134
+ # requests only.
135
+ max_decode_seq_len: int
136
+ # (batch_size,) A tensor of context lengths (tokens that are computed
137
+ # so far).
138
+ context_lens_tensor: Optional[torch.Tensor]
139
+
140
+ # (batch_size, max_blocks_per_seq).
141
+ # Block addresses per sequence. (Seq id -> list of physical block)
142
+ # E.g., [0, 1, 2] means tokens are stored in 0th, 1st, and 2nd blocks
143
+ # in the kv cache. Each block can contain up to block_size tokens.
144
+ # 2nd dimensions are padded up to max_blocks_per_seq if it is cuda-graph
145
+ # captured.
146
+ block_tables: Optional[torch.Tensor]
147
+
148
+ # Whether or not if cuda graph is enabled.
149
+ # Cuda-graph is currently enabled for decoding only.
150
+ # TODO(woosuk): Move `use_cuda_graph` out since it's unrelated to attention.
151
+
152
+ use_cuda_graph: bool
153
+
154
+ # Maximum query length in the batch.
155
+ max_query_len: Optional[int] = None
156
+
157
+ # Max number of query tokens among request in the batch.
158
+ max_decode_query_len: Optional[int] = None
159
+
160
+ # (batch_size + 1,). The cumulative subquery lengths of the sequences in
161
+ # the batch, used to index into subquery. E.g., if the subquery length
162
+ # is [4, 6], it is [0, 4, 10].
163
+ query_start_loc: Optional[torch.Tensor] = None
164
+ # (batch_size + 1,). The cumulative sequence lengths of the sequences in
165
+ # the batch, used to index into sequence. E.g., if the sequence length is
166
+ # [4, 6], it is [0, 4, 10].
167
+ seq_start_loc: Optional[torch.Tensor] = None
168
+
169
+ _cached_prefill_metadata: Optional[
170
+ "DifferentialFlashAttentionMetadata"] = None
171
+ _cached_decode_metadata: Optional[
172
+ "DifferentialFlashAttentionMetadata"] = None
173
+
174
+ # Begin encoder attn & enc/dec cross-attn fields...
175
+
176
+ # Encoder sequence lengths representation
177
+ encoder_seq_lens: Optional[List[int]] = None
178
+ encoder_seq_lens_tensor: Optional[torch.Tensor] = None
179
+ # (batch_size + 1,). The cumulative sequence lengths of the sequences in
180
+ # the batch, used to index into sequence. E.g., if the sequence length is
181
+ # [4, 6], it is [0, 4, 10].
182
+ encoder_seq_start_loc: Optional[torch.Tensor] = None
183
+ # Maximum sequence length among encoder sequences
184
+ max_encoder_seq_len: Optional[int] = None
185
+ # Number of tokens input to encoder
186
+ num_encoder_tokens: Optional[int] = None
187
+
188
+ # Cross-attention memory-mapping data structures: slot mapping
189
+ # and block tables
190
+ cross_slot_mapping: Optional[torch.Tensor] = None
191
+ cross_block_tables: Optional[torch.Tensor] = None
192
+
193
+ # Cross-layer shared attention block tables
194
+ cross_layer_shared_block_tables: Optional[torch.Tensor] = None
195
+
196
+ @property
197
+ def is_all_encoder_attn_metadata_set(self):
198
+ '''
199
+ All attention metadata required for encoder attention is set.
200
+ '''
201
+ return is_all_encoder_attn_metadata_set(self)
202
+
203
+ @property
204
+ def is_all_cross_attn_metadata_set(self):
205
+ '''
206
+ All attention metadata required for enc/dec cross-attention is set.
207
+
208
+ Superset of encoder attention required metadata.
209
+ '''
210
+ return is_all_cross_attn_metadata_set(self)
211
+
212
+ @property
213
+ def prefill_metadata(
214
+ self) -> Optional["DifferentialFlashAttentionMetadata"]:
215
+ if self.num_prefills == 0:
216
+ return None
217
+
218
+ if self._cached_prefill_metadata is not None:
219
+ return self._cached_prefill_metadata
220
+
221
+ assert ((self.seq_lens is not None)
222
+ or (self.encoder_seq_lens is not None))
223
+ assert ((self.seq_lens_tensor is not None)
224
+ or (self.encoder_seq_lens_tensor is not None))
225
+
226
+ # Compute some attn_metadata fields which default to None
227
+ query_start_loc = (None if self.query_start_loc is None else
228
+ self.query_start_loc[:self.num_prefills + 1])
229
+ slot_mapping = (None if self.slot_mapping is None else
230
+ self.slot_mapping[:self.num_prefill_tokens])
231
+ seq_lens = (None if self.seq_lens is None else
232
+ self.seq_lens[:self.num_prefills])
233
+ seq_lens_tensor = (None if self.seq_lens_tensor is None else
234
+ self.seq_lens_tensor[:self.num_prefills])
235
+ seq_start_loc = (None if self.seq_start_loc is None else
236
+ self.seq_start_loc[:self.num_prefills + 1])
237
+ context_lens_tensor = (None if self.context_lens_tensor is None else
238
+ self.context_lens_tensor[:self.num_prefills])
239
+ block_tables = (None if self.block_tables is None else
240
+ self.block_tables[:self.num_prefills])
241
+ cross_layer_shared_block_tables = (
242
+ None if self.cross_layer_shared_block_tables is None else
243
+ self.cross_layer_shared_block_tables[:self.num_prefills])
244
+
245
+ self._cached_prefill_metadata = DifferentialFlashAttentionMetadata(
246
+ num_prefills=self.num_prefills,
247
+ num_prefill_tokens=self.num_prefill_tokens,
248
+ num_decode_tokens=0,
249
+ slot_mapping=slot_mapping,
250
+ multi_modal_placeholder_index_maps=self.
251
+ multi_modal_placeholder_index_maps,
252
+ enable_kv_scales_calculation=self.enable_kv_scales_calculation,
253
+ seq_lens=seq_lens,
254
+ seq_lens_tensor=seq_lens_tensor,
255
+ max_query_len=self.max_query_len,
256
+ max_prefill_seq_len=self.max_prefill_seq_len,
257
+ max_decode_query_len=0,
258
+ max_decode_seq_len=0,
259
+ query_start_loc=query_start_loc,
260
+ seq_start_loc=seq_start_loc,
261
+ context_lens_tensor=context_lens_tensor,
262
+ block_tables=block_tables,
263
+ cross_layer_shared_block_tables=cross_layer_shared_block_tables,
264
+ use_cuda_graph=False,
265
+ # Begin encoder & cross attn fields below...
266
+ encoder_seq_lens=self.encoder_seq_lens,
267
+ encoder_seq_lens_tensor=self.encoder_seq_lens_tensor,
268
+ encoder_seq_start_loc=self.encoder_seq_start_loc,
269
+ max_encoder_seq_len=self.max_encoder_seq_len,
270
+ cross_slot_mapping=self.cross_slot_mapping,
271
+ cross_block_tables=self.cross_block_tables)
272
+ return self._cached_prefill_metadata
273
+
274
+ @property
275
+ def decode_metadata(
276
+ self) -> Optional["DifferentialFlashAttentionMetadata"]:
277
+ if self.num_decode_tokens == 0:
278
+ return None
279
+
280
+ if self._cached_decode_metadata is not None:
281
+ return self._cached_decode_metadata
282
+ assert ((self.seq_lens_tensor is not None)
283
+ or (self.encoder_seq_lens_tensor is not None))
284
+
285
+ # Compute some attn_metadata fields which default to None
286
+ slot_mapping = (None if self.slot_mapping is None else
287
+ self.slot_mapping[self.num_prefill_tokens:])
288
+ seq_lens_tensor = (None if self.seq_lens_tensor is None else
289
+ self.seq_lens_tensor[self.num_prefills:])
290
+ block_tables = (None if self.block_tables is None else
291
+ self.block_tables[self.num_prefills:])
292
+ cross_layer_shared_block_tables = (
293
+ None if self.cross_layer_shared_block_tables is None else
294
+ self.cross_layer_shared_block_tables[self.num_prefills:])
295
+ self._cached_decode_metadata = DifferentialFlashAttentionMetadata(
296
+ num_prefills=0,
297
+ num_prefill_tokens=0,
298
+ num_decode_tokens=self.num_decode_tokens,
299
+ slot_mapping=slot_mapping,
300
+ multi_modal_placeholder_index_maps=None,
301
+ enable_kv_scales_calculation=True,
302
+ seq_lens=None,
303
+ seq_lens_tensor=seq_lens_tensor,
304
+ max_decode_query_len=self.max_decode_query_len,
305
+ max_query_len=self.max_query_len,
306
+ max_prefill_seq_len=0,
307
+ max_decode_seq_len=self.max_decode_seq_len,
308
+ # Batch may be composed of prefill|decodes, adjust query start
309
+ # indices to refer to the start of decodes. E.g.
310
+ # in tokens:[3 prefills|6 decodes], query_start_loc=[3,9] => [0,6].
311
+ query_start_loc=(self.query_start_loc[self.num_prefills:] -
312
+ self.query_start_loc[self.num_prefills])
313
+ if self.query_start_loc is not None else None,
314
+ seq_start_loc=self.seq_start_loc[self.num_prefills:]
315
+ if self.seq_start_loc is not None else None,
316
+ context_lens_tensor=None,
317
+ block_tables=block_tables,
318
+ cross_layer_shared_block_tables=cross_layer_shared_block_tables,
319
+ use_cuda_graph=self.use_cuda_graph,
320
+ # Begin encoder & cross attn fields below...
321
+ encoder_seq_lens=self.encoder_seq_lens,
322
+ encoder_seq_lens_tensor=self.encoder_seq_lens_tensor,
323
+ encoder_seq_start_loc=self.encoder_seq_start_loc,
324
+ max_encoder_seq_len=self.max_encoder_seq_len,
325
+ cross_slot_mapping=self.cross_slot_mapping,
326
+ cross_block_tables=self.cross_block_tables)
327
+ return self._cached_decode_metadata
328
+
329
+ def advance_step(self,
330
+ model_input: "ModelInputForGPUWithSamplingMetadata",
331
+ sampled_token_ids: Optional[torch.Tensor],
332
+ block_size: int,
333
+ num_seqs: int,
334
+ num_queries: int,
335
+ turn_prefills_into_decodes: bool = False):
336
+ """
337
+ Update metadata in-place to advance one decode step.
338
+ """
339
+ # When using cudagraph, the num_seqs is padded to the next captured
340
+ # batch sized, but num_queries tracks the actual number of requests in
341
+ # the batch. For --enforce-eager mode, num_seqs == num_queries
342
+ if num_seqs != num_queries:
343
+ assert num_seqs > num_queries
344
+ assert self.use_cuda_graph
345
+
346
+ if turn_prefills_into_decodes:
347
+ # When Multi-Step is enabled with Chunked-Prefill, prefills and
348
+ # decodes are scheduled together. In the first step, all the
349
+ # prefills turn into decodes. This update reflects that
350
+ # conversion.
351
+ assert self.num_decode_tokens + self.num_prefills == num_seqs
352
+ self.num_decode_tokens += self.num_prefills
353
+ self.num_prefills = 0
354
+ self.num_prefill_tokens = 0
355
+ self.max_prefill_seq_len = 0
356
+ self.max_query_len = 1
357
+
358
+ self.slot_mapping = self.slot_mapping[:num_seqs]
359
+ else:
360
+ assert self.seq_lens is not None
361
+ assert self.max_decode_seq_len == max(self.seq_lens)
362
+
363
+ assert self.num_prefills == 0
364
+ assert self.num_prefill_tokens == 0
365
+ assert self.num_decode_tokens == num_seqs
366
+ assert self.slot_mapping.shape == (num_seqs, )
367
+
368
+ assert self.seq_lens is not None
369
+ assert len(self.seq_lens) == num_seqs
370
+ assert self.seq_lens_tensor is not None
371
+ assert self.seq_lens_tensor.shape == (num_seqs, )
372
+ assert self.max_query_len == 1
373
+ assert self.max_prefill_seq_len == 0
374
+
375
+ assert self.query_start_loc is not None
376
+ assert self.query_start_loc.shape == (num_queries + 1, )
377
+ assert self.seq_start_loc is not None
378
+ assert self.seq_start_loc.shape == (num_seqs + 1, )
379
+
380
+ assert self.context_lens_tensor is not None
381
+ assert self.context_lens_tensor.shape == (num_queries, )
382
+
383
+ assert self.block_tables is not None
384
+ assert self.block_tables.shape[0] == num_seqs
385
+
386
+ # Update query lengths. Note that we update only queries and not seqs,
387
+ # since tensors may be padded due to captured cuda graph batch size
388
+ for i in range(num_queries):
389
+ self.seq_lens[i] += 1
390
+ self.max_decode_seq_len = max(self.seq_lens)
391
+
392
+ ops.advance_step_flashattn(num_seqs=num_seqs,
393
+ num_queries=num_queries,
394
+ block_size=block_size,
395
+ input_tokens=model_input.input_tokens,
396
+ sampled_token_ids=sampled_token_ids,
397
+ input_positions=model_input.input_positions,
398
+ seq_lens=self.seq_lens_tensor,
399
+ slot_mapping=self.slot_mapping,
400
+ block_tables=self.block_tables)
401
+
402
+
403
+class DifferentialFlashAttentionMetadataBuilder(
404
+ AttentionMetadataBuilder[DifferentialFlashAttentionMetadata]):
405
+
406
+ def __init__(self, input_builder: "ModelInputForGPUBuilder"):
407
+ self.input_builder = input_builder
408
+ self.runner = input_builder.runner
409
+ self.sliding_window = input_builder.sliding_window
410
+ self.block_size = input_builder.block_size
411
+
412
+ def prepare(self):
413
+ self.slot_mapping: List[int] = []
414
+ self.prefill_seq_lens: List[int] = []
415
+ self.context_lens: List[int] = []
416
+ self.block_tables: List[List[int]] = []
417
+ self.cross_layer_shared_block_tables: List[List[int]] = []
418
+ self.curr_seq_lens: List[int] = []
419
+ self.multimodal_placeholder_maps: Dict[
420
+ str,
421
+ MultiModalPlaceholderMap] = defaultdict(MultiModalPlaceholderMap)
422
+ self.num_prefills = 0
423
+ self.num_prefill_tokens = 0
424
+ self.num_decode_tokens = 0
425
+ self.has_prefix_cache_hit = False
426
+
427
+ def _add_seq_group(
428
+ self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
429
+ chunked_prefill_enabled: bool, prefix_cache_hit: bool):
430
+ """Add a sequence group to the metadata. Specifically update/append
431
+ 1. context length.
432
+ 2. block table.
433
+ 3. slot mapping.
434
+ """
435
+ # TODO: add support for chunked prefill and prefix caching.
436
+ assert not chunked_prefill_enabled, \
437
+ "chunked prefill is not supported for now"
438
+ assert not prefix_cache_hit, "prefix caching is not supported for now"
439
+
440
+ is_prompt = inter_data.is_prompt
441
+ block_tables = inter_data.block_tables
442
+
443
+ for (seq_id, token_len, seq_len, curr_seq_len, query_len, context_len,
444
+ curr_sliding_window_block) in zip(
445
+ inter_data.seq_ids, [len(t) for t in inter_data.input_tokens],
446
+ inter_data.orig_seq_lens, inter_data.seq_lens,
447
+ inter_data.query_lens, inter_data.context_lens,
448
+ inter_data.curr_sliding_window_blocks):
449
+ self.context_lens.append(context_len)
450
+
451
+ if is_prompt:
452
+ mm_maps = inter_data.multi_modal_placeholder_maps
453
+ if mm_maps:
454
+ for modality, placeholders in mm_maps.items():
455
+ self.multimodal_placeholder_maps[modality].extend(
456
+ placeholders)
457
+
458
+ self.num_prefills += 1
459
+ self.num_prefill_tokens += token_len
460
+ self.prefill_seq_lens.append(seq_len)
461
+ else:
462
+ self.num_decode_tokens += query_len
463
+ self.curr_seq_lens.append(curr_seq_len)
464
+
465
+ # Compute block table.
466
+ # TODO(sang): Combine chunked prefill and prefix caching by
467
+ # only allowing multiple of block_size chunk size.
468
+ # NOTE: This only works for oooooooxxx style attention.
469
+ block_table = []
470
+ if prefix_cache_hit:
471
+ # NOTE(woosuk): For flash-attn, the block table should
472
+ # include the entries for the incoming prefill tokens.
473
+ block_table = block_tables[seq_id]
474
+ elif ((chunked_prefill_enabled or not is_prompt)
475
+ and block_tables is not None):
476
+ if curr_sliding_window_block == 0:
477
+ block_table = block_tables[seq_id]
478
+ else:
479
+ block_table = block_tables[seq_id][
480
+ -curr_sliding_window_block:]
481
+ self.block_tables.append(block_table)
482
+
483
+ cross_layer_shared_block_table = []
484
+ if prefix_cache_hit:
485
+ cross_layer_shared_block_table = block_tables[seq_id]
486
+ elif block_tables is not None:
487
+ if curr_sliding_window_block == 0:
488
+ cross_layer_shared_block_table = block_tables[seq_id]
489
+ else:
490
+ cross_layer_shared_block_table = block_tables[seq_id][
491
+ -curr_sliding_window_block:]
492
+ self.cross_layer_shared_block_tables.append(
493
+ cross_layer_shared_block_table)
494
+
495
+ # Compute slot mapping.
496
+ is_profile_run = is_block_tables_empty(block_tables)
497
+ start_idx = compute_slot_mapping_start_idx(is_prompt, query_len,
498
+ context_len,
499
+ self.sliding_window)
500
+ compute_slot_mapping(is_profile_run, self.slot_mapping, seq_id,
501
+ seq_len, context_len, start_idx,
502
+ self.block_size, inter_data.block_tables)
503
+
504
+ def _get_graph_runner_block_tables(self, num_seqs: int,
505
+ block_tables: List[List[int]],
506
+ graph_block_tables) -> torch.Tensor:
507
+ # The shape of graph_block_tables is
508
+ # [max batch size, max context len // block size].
509
+ # max_batch_size, max_blocks = self.runner.graph_block_tables.shape
510
+ max_batch_size, max_blocks = graph_block_tables.shape
511
+ assert max_batch_size >= num_seqs
512
+
513
+ # graph_block_tables = self.runner.graph_block_tables[:num_seqs]
514
+ graph_block_tables = graph_block_tables[:num_seqs]
515
+ for i, block_table in enumerate(block_tables):
516
+ if block_table:
517
+ num_blocks = len(block_table)
518
+ if num_blocks <= max_blocks:
519
+ graph_block_tables[i, :num_blocks] = block_table
520
+ else:
521
+ # It may be possible to have more blocks allocated due
522
+ # to lookahead slots of multi-step, however, they are
523
+ # not used anyway, so can be safely ignored.
524
+ graph_block_tables[
525
+ i, :max_blocks] = block_table[:max_blocks]
526
+
527
+ return torch.from_numpy(graph_block_tables).to(
528
+ device=self.runner.device, non_blocking=True)
529
+
530
+ def build(self, seq_lens: List[int], query_lens: List[int],
531
+ cuda_graph_pad_size: int, batch_size: int):
532
+ """Build attention metadata with on-device tensors.
533
+
534
+ Args:
535
+ seq_lens: The maybe padded sequence lengths of the input sequences.
536
+ query_lens: The query lengths of the input sequences.
537
+ cuda_graph_pad_size: The padding size for cuda graph.
538
+ -1 if cuda graph is not used.
539
+ batch_size: The maybe padded batch size.
540
+ """
541
+ prefix_cache_hit = any([
542
+ inter_data.prefix_cache_hit
543
+ for inter_data in self.input_builder.inter_data_list
544
+ ])
545
+ for inter_data in self.input_builder.inter_data_list:
546
+ self._add_seq_group(inter_data,
547
+ self.input_builder.chunked_prefill_enabled,
548
+ prefix_cache_hit)
549
+
550
+ device = self.runner.device
551
+ use_captured_graph = cuda_graph_pad_size != -1
552
+
553
+ max_query_len = max(query_lens)
554
+ decode_query_lens = query_lens[self.num_prefills:]
555
+ if len(decode_query_lens) > 0:
556
+ max_decode_query_len = max(decode_query_lens)
557
+ else:
558
+ max_decode_query_len = 1
559
+ max_prefill_seq_len = max(self.prefill_seq_lens, default=0)
560
+ max_decode_seq_len = max(self.curr_seq_lens, default=0)
561
+ num_decode_tokens = self.num_decode_tokens
562
+ query_start_loc = list(accumulate(query_lens, initial=0))
563
+ seq_start_loc = list(accumulate(seq_lens, initial=0))
564
+
565
+ num_seqs = len(seq_lens)
566
+ if use_captured_graph:
567
+ self.slot_mapping.extend([PAD_SLOT_ID] * cuda_graph_pad_size)
568
+ self.block_tables.extend([] * cuda_graph_pad_size)
569
+
570
+ self.cross_layer_shared_block_tables.extend([] *
571
+ cuda_graph_pad_size)
572
+
573
+ num_decode_tokens = batch_size - self.num_prefill_tokens
574
+ block_tables = self._get_graph_runner_block_tables(
575
+ num_seqs, self.block_tables, self.runner.graph_block_tables)
576
+ cross_layer_shared_block_tables = \
577
+ self._get_graph_runner_block_tables(
578
+ num_seqs, self.cross_layer_shared_block_tables,
579
+ self.runner.cross_layer_shared_graph_block_tables)
580
+ else:
581
+ block_tables = make_tensor_with_pad(
582
+ self.block_tables,
583
+ pad=0,
584
+ dtype=torch.int,
585
+ device=device,
586
+ )
587
+ cross_layer_shared_block_tables = make_tensor_with_pad(
588
+ self.cross_layer_shared_block_tables,
589
+ pad=0,
590
+ dtype=torch.int,
591
+ device=device,
592
+ )
593
+ assert max_query_len > 0, ("query_lens: {}".format(query_lens))
594
+
595
+ assert device is not None
596
+ context_lens_tensor = async_tensor_h2d(self.context_lens, torch.int,
597
+ device, self.runner.pin_memory)
598
+ seq_lens_tensor = async_tensor_h2d(seq_lens, torch.int, device,
599
+ self.runner.pin_memory)
600
+ slot_mapping_tensor = async_tensor_h2d(self.slot_mapping, torch.long,
601
+ device, self.runner.pin_memory)
602
+ query_start_loc_tensor = async_tensor_h2d(query_start_loc, torch.int32,
603
+ device,
604
+ self.runner.pin_memory)
605
+ seq_start_loc_tensor = async_tensor_h2d(seq_start_loc, torch.int32,
606
+ device, self.runner.pin_memory)
607
+ placeholder_index_maps = {
608
+ modality: placeholder_map.index_map()
609
+ for modality, placeholder_map in
610
+ self.multimodal_placeholder_maps.items()
611
+ }
612
+
613
+ return DifferentialFlashAttentionMetadata(
614
+ num_prefills=self.num_prefills,
615
+ slot_mapping=slot_mapping_tensor,
616
+ num_prefill_tokens=self.num_prefill_tokens,
617
+ num_decode_tokens=num_decode_tokens,
618
+ seq_lens=seq_lens,
619
+ multi_modal_placeholder_index_maps=placeholder_index_maps,
620
+ enable_kv_scales_calculation=True,
621
+ seq_lens_tensor=seq_lens_tensor,
622
+ max_query_len=max_query_len,
623
+ max_decode_query_len=max_decode_query_len,
624
+ max_prefill_seq_len=max_prefill_seq_len,
625
+ max_decode_seq_len=max_decode_seq_len,
626
+ query_start_loc=query_start_loc_tensor,
627
+ seq_start_loc=seq_start_loc_tensor,
628
+ context_lens_tensor=context_lens_tensor,
629
+ block_tables=block_tables,
630
+ cross_layer_shared_block_tables=cross_layer_shared_block_tables,
631
+ use_cuda_graph=use_captured_graph,
632
+ )
633
+
634
+
635
+class DifferentialFlashAttentionImpl(AttentionImpl):
636
+ """
637
+ If the input tensors contain prompt tokens, the layout is as follows:
638
+ |<--------------- num_prefill_tokens ----------------->|
639
+ |<--prefill_0-->|<--prefill_1-->|...|<--prefill_N-1--->|
640
+
641
+ Otherwise, the layout is as follows:
642
+ |<----------------- num_decode_tokens ------------------>|
643
+ |<--decode_0-->|..........|<--decode_M-1-->|<--padding-->|
644
+
645
+ Generation tokens can contain padding when cuda-graph is used.
646
+ Currently, prompt tokens don't contain any padding.
647
+
648
+ The prompts might have different lengths, while the generation tokens
649
+ always have length 1.
650
+
651
+ If chunked prefill is enabled, prefill tokens and decode tokens can be
652
+ batched together in a flattened 1D query.
653
+
654
+ |<----- num_prefill_tokens ---->|<------- num_decode_tokens --------->|
655
+ |<-prefill_0->|...|<-prefill_N-1->|<--decode_0-->|...|<--decode_M-1-->|
656
+
657
+ Currently, cuda graph is disabled for chunked prefill, meaning there's no
658
+ padding between prefill and decode tokens.
659
+ """
660
+
661
+ def __init__(
662
+ self,
663
+ num_heads: int,
664
+ head_size: int,
665
+ scale: float,
666
+ num_kv_heads: int,
667
+ alibi_slopes: Optional[List[float]],
668
+ sliding_window: Optional[int],
669
+ kv_cache_dtype: str,
670
+ blocksparse_params: Optional[Dict[str, Any]] = None,
671
+ logits_soft_cap: Optional[float] = None,
672
+ attn_type: str = AttentionType.DECODER,
673
+ kv_sharing_target_layer_name: Optional[str] = None,
674
+ use_irope: bool = False,
675
+ differential_flash_attention_config: Optional[Dict[str, Any]] = None,
676
+ ) -> None:
677
+ if differential_flash_attention_config is None:
678
+ differential_flash_attention_config = {}
679
+ self.differential_flash_attention_config = \
680
+ differential_flash_attention_config
681
+ self.used_shared_kv_cache = kv_sharing_target_layer_name is not None
682
+ self.kv_sharing_target_layer_name = kv_sharing_target_layer_name
683
+ if blocksparse_params is not None:
684
+ raise ValueError(
685
+ "FlashAttention does not support block-sparse attention.")
686
+ if use_irope:
687
+ logger.warning(
688
+ "Using irope in V0 is not supported yet, it will fall back "
689
+ "to global attention for long context.")
690
+ self.num_heads = num_heads
691
+ self.head_size = head_size
692
+ self.scale = float(scale)
693
+ self.num_kv_heads = num_kv_heads
694
+ if alibi_slopes is not None:
695
+ alibi_slopes = torch.tensor(alibi_slopes, dtype=torch.float32)
696
+ self.alibi_slopes = alibi_slopes
697
+ self.sliding_window = ((sliding_window - 1,
698
+ 0) if sliding_window is not None else (-1, -1))
699
+ self.kv_cache_dtype = kv_cache_dtype
700
+ self.vllm_flash_attn_version = get_flash_attn_version(
701
+ requires_alibi=self.alibi_slopes is not None)
702
+ if is_quantized_kv_cache(self.kv_cache_dtype) and (
703
+ not self.kv_cache_dtype.startswith("fp8")
704
+ or not flash_attn_supports_fp8()):
705
+ raise NotImplementedError(
706
+ f"FlashAttention does not support {self.kv_cache_dtype} "
707
+ "kv-cache on this device "
708
+ f"(FA supports fp8 = {flash_attn_supports_fp8()}).")
709
+ if logits_soft_cap is None:
710
+ # In flash-attn, setting logits_soft_cap as 0 means no soft cap.
711
+ logits_soft_cap = 0
712
+ self.logits_soft_cap = logits_soft_cap
713
+
714
+ assert self.num_heads % self.num_kv_heads == 0
715
+ self.num_queries_per_kv = self.num_heads // self.num_kv_heads
716
+
717
+ support_head_sizes = FlashAttentionBackend.get_supported_head_sizes()
718
+ if head_size not in support_head_sizes:
719
+ raise ValueError(
720
+ f"Head size {head_size} is not supported by FlashAttention. "
721
+ f"Supported head sizes are: {support_head_sizes}.")
722
+ self.attn_type = attn_type
723
+
724
+ self.lambda_full = None
725
+ self.subln = self.differential_flash_attention_config["subln"]
726
+
727
+ def split_heads(self, x):
728
+ # split by num_heads, the stripe pattern is friendly to tensor parallel.
729
+ x = rearrange(x, "... (H two) D -> ... H two D", two=2)
730
+ x1 = x[..., 0, :]
731
+ x2 = x[..., 1, :]
732
+ return x1.contiguous(), x2.contiguous()
733
+
734
+ def split_kv_cache(self, x):
735
+ # split by num_heads, the stripe pattern is friendly to tensor parallel.
736
+ if x.numel() == 0:
737
+ return torch.empty(0), torch.empty(0)
738
+
739
+ x1, x2 = x[0], x[1]
740
+ return x1, x2
741
+
742
+ def populate_kv_cache(self, layer: AttentionLayer, key: torch.Tensor,
743
+ value: torch.Tensor, kv_cache: torch.Tensor,
744
+ attn_metadata: DifferentialFlashAttentionMetadata):
745
+ if kv_cache.numel() > 0 and key is not None and value is not None:
746
+ updated_slot_mapping = attn_metadata.slot_mapping
747
+ torch.ops._C_cache_ops.reshape_and_cache_flash(
748
+ key,
749
+ value,
750
+ kv_cache[0],
751
+ kv_cache[1],
752
+ updated_slot_mapping.flatten(),
753
+ self.kv_cache_dtype,
754
+ layer._k_scale,
755
+ layer._v_scale,
756
+ )
757
+
758
+ def forward_generate_kv_cache(
759
+ self, query: torch.Tensor, key: Optional[torch.Tensor],
760
+ value: Optional[torch.Tensor], k_cache: torch.Tensor,
761
+ v_cache: torch.Tensor,
762
+ attn_metadata: DifferentialFlashAttentionMetadata) -> torch.Tensor:
763
+
764
+ head_size = self.head_size
765
+ num_heads = self.num_heads // 2
766
+ num_kv_heads = self.num_kv_heads // 2
767
+
768
+ query = query.view(-1, num_heads, head_size)
769
+ if key is not None:
770
+ assert value is not None
771
+ key = key.view(-1, num_kv_heads, head_size)
772
+ value = value.view(-1, num_kv_heads, head_size)
773
+ else:
774
+ assert value is None
775
+
776
+ num_prefill_tokens = attn_metadata.num_prefill_tokens
777
+ num_decode_tokens = attn_metadata.num_decode_tokens
778
+ assert key.shape[
779
+ 0] == num_prefill_tokens + num_decode_tokens, "key shape mismatch"
780
+ assert value.shape[
781
+ 0] == num_prefill_tokens + num_decode_tokens, "value shape mismatch"
782
+
783
+ output = torch.empty_like(query)
784
+ # Query for decode. KV is not needed because it is already cached.
785
+ decode_query = query[num_prefill_tokens:]
786
+ # QKV for prefill.
787
+ query = query[:num_prefill_tokens]
788
+ if key is not None and value is not None:
789
+ key = key[:num_prefill_tokens]
790
+ value = value[:num_prefill_tokens]
791
+
792
+ assert query.shape[0] == num_prefill_tokens, "query shape mismatch"
793
+ assert decode_query.shape[
794
+ 0] == num_decode_tokens, "decode query shape mismatch"
795
+
796
+ if prefill_meta := attn_metadata.prefill_metadata:
797
+ # Prompt run.
798
+ if k_cache.numel() == 0 \
799
+ or prefill_meta.block_tables is None \
800
+ or prefill_meta.block_tables.numel() == 0:
801
+ # normal attention
802
+ prefill_output = flash_attn_varlen_func(
803
+ q=query,
804
+ k=key,
805
+ v=value,
806
+ cu_seqlens_q=prefill_meta.seq_start_loc,
807
+ cu_seqlens_k=prefill_meta.seq_start_loc,
808
+ max_seqlen_q=prefill_meta.max_prefill_seq_len,
809
+ max_seqlen_k=prefill_meta.max_prefill_seq_len,
810
+ softmax_scale=self.scale,
811
+ causal=True,
812
+ window_size=self.sliding_window,
813
+ alibi_slopes=self.alibi_slopes,
814
+ softcap=self.logits_soft_cap,
815
+ )
816
+ assert prefill_output.shape == output[:
817
+ num_prefill_tokens].shape
818
+ output[:num_prefill_tokens] = prefill_output
819
+ else:
820
+ raise Exception("prefix caching not supported")
821
+
822
+ if decode_meta := attn_metadata.decode_metadata:
823
+ block_tables_arg = decode_meta.block_tables
824
+ try:
825
+ output[num_prefill_tokens:] = flash_attn_with_kvcache(
826
+ q=decode_query.unsqueeze(1),
827
+ k_cache=k_cache,
828
+ v_cache=v_cache,
829
+ block_table=block_tables_arg,
830
+ cache_seqlens=decode_meta.seq_lens_tensor,
831
+ softmax_scale=self.scale,
832
+ causal=True,
833
+ window_size=self.sliding_window,
834
+ alibi_slopes=self.alibi_slopes,
835
+ softcap=self.logits_soft_cap,
836
+ ).squeeze(1)
837
+ except Exception as e:
838
+ logger.error("Error in PagedAttention.forward_decode: %s",
839
+ str(e))
840
+ raise e
841
+
842
+ # Reshape the output tensor.
843
+ return output.view(-1, num_heads, head_size)
844
+
845
+ def forward_with_kv_cache_only(
846
+ self,
847
+ query: torch.Tensor,
848
+ k_cache: torch.Tensor,
849
+ v_cache: torch.Tensor,
850
+ attn_metadata: DifferentialFlashAttentionMetadata,
851
+ ):
852
+ if not attn_metadata.decode_metadata:
853
+ block_tables_arg = attn_metadata.cross_layer_shared_block_tables
854
+ else:
855
+ block_tables_arg = attn_metadata.block_tables
856
+
857
+ output = flash_attn_with_kvcache(
858
+ q=query.unsqueeze(1),
859
+ k_cache=k_cache,
860
+ v_cache=v_cache,
861
+ block_table=block_tables_arg,
862
+ cache_seqlens=attn_metadata.seq_lens_tensor,
863
+ softmax_scale=self.scale,
864
+ causal=True,
865
+ window_size=self.sliding_window,
866
+ alibi_slopes=self.alibi_slopes,
867
+ softcap=self.logits_soft_cap,
868
+ ).squeeze(1)
869
+ return output
870
+
871
+ def forward(
872
+ self,
873
+ layer: AttentionLayer,
874
+ q: torch.Tensor,
875
+ k: torch.Tensor,
876
+ v: torch.Tensor,
877
+ kv_cache: torch.Tensor,
878
+ attn_metadata: DifferentialFlashAttentionMetadata,
879
+ output: Optional[torch.Tensor] = None,
880
+ output_scale: Optional[torch.Tensor] = None,
881
+ ) -> torch.Tensor:
882
+ """Forward pass with FlashAttention.
883
+
884
+ Args:
885
+ query: shape = [num_tokens, num_heads, head_size]
886
+ key: shape = [num_tokens, num_kv_heads, head_size]
887
+ value: shape = [num_tokens, num_kv_heads, head_size]
888
+ output: shape = [num_tokens, num_heads, head_size]
889
+ kv_cache = [2, num_blocks, block_size, num_kv_heads, head_size]
890
+ NOTE: kv_cache will be an empty tensor with shape [0]
891
+ for profiling run.
892
+ attn_metadata: Metadata for attention.
893
+ NOTE: It in-place updates the output tensor.
894
+ NOTE: FP8 quantization, flash-attn expect the size of
895
+ {q,k,v}_descale to be (num_sequences, num_kv_heads).
896
+ We use torch's .expand() to avoid duplicating values
897
+ """
898
+ if self.lambda_full is None:
899
+ self.lambda_init = self.differential_flash_attention_config[
900
+ "lambda_init"]
901
+ lambda_q1 = self.differential_flash_attention_config["lambda_q1"]
902
+ lambda_k1 = self.differential_flash_attention_config["lambda_k1"]
903
+ lambda_q2 = self.differential_flash_attention_config["lambda_q2"]
904
+ lambda_k2 = self.differential_flash_attention_config["lambda_k2"]
905
+ lambda_1 = torch.exp(
906
+ torch.sum(lambda_q1 * lambda_k1, dim=-1).float()).type_as(q)
907
+ lambda_2 = torch.exp(
908
+ torch.sum(lambda_q2 * lambda_k2, dim=-1).float()).type_as(q)
909
+ self.lambda_full = lambda_1 - lambda_2 + self.lambda_init
910
+
911
+ if not self.used_shared_kv_cache: # need to generate kv-cache
912
+ q = q.view(-1, self.num_heads, self.head_size)
913
+ k = k.view(-1, self.num_kv_heads, self.head_size)
914
+ v = v.view(-1, self.num_kv_heads, self.head_size)
915
+
916
+ q1, q2 = self.split_heads(q)
917
+ k1, k2 = self.split_heads(k)
918
+ v1, v2 = self.split_heads(v)
919
+
920
+ # kv_cache shape is (2, 2, num_blocks, block_size, num_kv_heads // 2, head_size) # noqa: E501
921
+ # Split by half along the first dimension.
922
+ kv_cache1, kv_cache2 = self.split_kv_cache(kv_cache)
923
+ assert kv_cache1.is_contiguous(), "kv_cache1 is not contiguous"
924
+ assert kv_cache2.is_contiguous(), "kv_cache2 is not contiguous"
925
+
926
+ if kv_cache1.numel() != 0:
927
+ self.populate_kv_cache(layer, k1, v1, kv_cache1, attn_metadata)
928
+ self.populate_kv_cache(layer, k2, v2, kv_cache2, attn_metadata)
929
+
930
+ key_cache1, value_cache1 = self.split_kv_cache(kv_cache1)
931
+ key_cache2, value_cache2 = self.split_kv_cache(kv_cache2)
932
+ else:
933
+ key_cache1, value_cache1 = torch.empty(0), torch.empty(0)
934
+ key_cache2, value_cache2 = torch.empty(0), torch.empty(0)
935
+ attn11 = self.forward_generate_kv_cache(q1, k1, v1, key_cache1,
936
+ value_cache1,
937
+ attn_metadata)
938
+ attn12 = self.forward_generate_kv_cache(q1, k1, v2, key_cache1,
939
+ value_cache2,
940
+ attn_metadata)
941
+ attn11 = attn11.view(q1.shape)
942
+ attn12 = attn12.view(q1.shape)
943
+ attn1 = torch.cat([attn11, attn12], dim=-1)
944
+
945
+ attn21 = self.forward_generate_kv_cache(q2, k2, v1, key_cache2,
946
+ value_cache1,
947
+ attn_metadata)
948
+ attn22 = self.forward_generate_kv_cache(q2, k2, v2, key_cache2,
949
+ value_cache2,
950
+ attn_metadata)
951
+ attn21 = attn21.view(q2.shape)
952
+ attn22 = attn22.view(q2.shape)
953
+ attn2 = torch.cat([attn21, attn22], dim=-1)
954
+
955
+ attn = attn1 - self.lambda_full * attn2
956
+ # attn shape (-1, self.num_heads // 2, 2 * self.head_dim)
957
+ attn = self.subln(attn)
958
+ attn = attn * (1 - self.lambda_init)
959
+ # reshape back to 2 * num_head
960
+ attn_output = rearrange(attn,
961
+ "... H (two D) -> ... (H two) D",
962
+ two=2)
963
+
964
+ else: # re-use the kv cache, full attention
965
+ q = q.view(-1, self.num_heads, self.head_size)
966
+ q1, q2 = self.split_heads(q)
967
+ # kv_cache shape is (2, num_blocks, block_size, num_kv_heads, head_size) # noqa: E501
968
+ kv_cache1, kv_cache2 = self.split_kv_cache(kv_cache)
969
+ key_cache1, value_cache1 = kv_cache1[0], kv_cache1[1]
970
+ key_cache2, value_cache2 = kv_cache2[0], kv_cache2[1]
971
+
972
+ attn11 = self.forward_with_kv_cache_only(q1, key_cache1,
973
+ value_cache1,
974
+ attn_metadata)
975
+ attn12 = self.forward_with_kv_cache_only(q1, key_cache1,
976
+ value_cache2,
977
+ attn_metadata)
978
+ attn11 = attn11.view(q1.shape)
979
+ attn12 = attn12.view(q1.shape)
980
+ attn1 = torch.cat([attn11, attn12], dim=-1)
981
+
982
+ attn21 = self.forward_with_kv_cache_only(q2, key_cache2,
983
+ value_cache1,
984
+ attn_metadata)
985
+ attn22 = self.forward_with_kv_cache_only(q2, key_cache2,
986
+ value_cache2,
987
+ attn_metadata)
988
+ attn21 = attn21.view(q2.shape)
989
+ attn22 = attn22.view(q2.shape)
990
+ attn2 = torch.cat([attn21, attn22], dim=-1)
991
+
992
+ attn = attn1 - self.lambda_full * attn2
993
+ attn = self.subln(attn)
994
+ attn = attn * (1 - self.lambda_init)
995
+ # reshape back to 2 * num_head
996
+ attn_output = rearrange(attn,
997
+ "... H (two D) -> ... (H two) D",
998
+ two=2)
999
+ attn_output = attn_output.view(-1, self.num_heads * self.head_size)
1000
+ return attn_output
vllm/attention/backends/dual_chunk_flash_attn.py
+2
-1
@@ -295,7 +295,8 @@ class DualChunkFlashAttentionImpl(FlashAttentionImpl):
295
dual_chunk_attention_config: Optional[Dict[str, Any]] = None,
296
) -> None:
297
if kv_sharing_target_layer_name is not None:
298
- raise NotImplementedError("KV sharing is not supported in V0.")
298
+ raise NotImplementedError("KV sharing is not supported in V0 "
299
+ "DUAL_CHUNK_FLASH_ATTN backend.")
300
self.num_heads = num_heads
301
self.head_size = head_size
302
self.scale = float(scale)
vllm/attention/backends/flash_attn.py
+2
-1
@@ -622,7 +622,8 @@ class FlashAttentionImpl(AttentionImpl):
622
use_irope: bool = False,
623
) -> None:
624
if kv_sharing_target_layer_name is not None:
625
- raise NotImplementedError("KV sharing is not supported in V0.")
625
+ raise NotImplementedError("KV sharing is not supported in V0 "
626
+ "FLASH_ATTN backend.")
627
if blocksparse_params is not None:
628
raise ValueError(
629
"FlashAttention does not support block-sparse attention.")
vllm/attention/backends/flashinfer.py
+2
-1
@@ -1006,7 +1006,8 @@ class FlashInferImpl(AttentionImpl):
1006
use_irope: bool = False,
1007
) -> None:
1008
if kv_sharing_target_layer_name is not None:
1009
- raise NotImplementedError("KV sharing is not supported in V0.")
1009
+ raise NotImplementedError("KV sharing is not supported in V0 "
1010
+ "FLASHINFER backend.")
1011
if use_irope:
1012
logger.warning_once(
1013
"Using irope in FlashInfer is not supported yet, it will fall"
vllm/attention/backends/hpu_attn.py
+2
-1
@@ -115,7 +115,8 @@ class HPUAttentionImpl(AttentionImpl, torch.nn.Module):
115
) -> None:
116
super(AttentionImpl, self).__init__()
117
if kv_sharing_target_layer_name is not None:
118
- raise NotImplementedError("KV sharing is not supported in V0.")
118
+ raise NotImplementedError("KV sharing is not supported in V0 "
119
+ "HPU_ATTN backend.")
120
if use_irope:
121
logger.warning_once(
122
"Using irope in HPU is not supported yet, it will fall back "
vllm/attention/backends/rocm_flash_attn.py
+2
-1
@@ -501,7 +501,8 @@ class ROCmFlashAttentionImpl(AttentionImpl):
501
use_irope: bool = False,
502
) -> None:
503
if kv_sharing_target_layer_name is not None:
504
- raise NotImplementedError("KV sharing is not supported in V0.")
504
+ raise NotImplementedError("KV sharing is not supported in V0 "
505
+ "ROCM_FLASH backend.")
506
if use_irope:
507
logger.warning_once(
508
"Using irope in ROCm Flash Attention is not supported yet, it "
vllm/attention/backends/xformers.py
+2
-1
@@ -394,7 +394,8 @@ class XFormersImpl(AttentionImpl[XFormersMetadata]):
394
use_irope: bool = False,
395
) -> None:
396
if kv_sharing_target_layer_name is not None:
397
- raise NotImplementedError("KV sharing is not supported in V0.")
397
+ raise NotImplementedError("KV sharing is not supported in V0 "
398
+ "XFORMERS backend.")
399
if blocksparse_params is not None:
400
raise ValueError(
401
"XFormers does not support block-sparse attention.")
vllm/attention/layer.py
-4
@@ -160,10 +160,6 @@ class Attention(nn.Module):
160
self.attn_type = attn_type
161
162
if kv_sharing_target_layer_name is not None:
163
- if not envs.VLLM_USE_V1:
164
- raise NotImplementedError(
165
- "Cross-layer KV sharing is not supported in V0.")
166
-
163
validate_kv_sharing_target(
164
prefix,
165
kv_sharing_target_layer_name,
vllm/model_executor/layers/logits_processor.py
+2
-1
@@ -59,11 +59,12 @@ class LogitsProcessor(nn.Module):
59
hidden_states: torch.Tensor,
60
sampling_metadata: Optional[SamplingMetadata] = None,
61
embedding_bias: Optional[torch.Tensor] = None,
62
+ prune_hidden_states: bool = True,
63
) -> Optional[torch.Tensor]:
64
if self.logits_as_input:
65
logits = hidden_states
66
else:
66
- if sampling_metadata is not None:
67
+ if sampling_metadata is not None and prune_hidden_states:
68
hidden_states = _prune_hidden_states(hidden_states,
69
sampling_metadata)
70
vllm/model_executor/models/phi4flash.py
new
+746
@@ -0,0 +1,746 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import math
4
+from collections.abc import Iterable
5
+from typing import Optional, Union
6
+
7
+import torch
8
+import torch.nn as nn
9
+from transformers.activations import ACT2FN
10
+
11
+import vllm.envs as envs
12
+from vllm.attention import Attention, AttentionMetadata, AttentionType
13
+from vllm.attention.selector import _Backend
14
+from vllm.config import CacheConfig, VllmConfig
15
+from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
16
+from vllm.forward_context import ForwardContext, get_forward_context
17
+from vllm.logger import init_logger
18
+from vllm.model_executor.layers.linear import (ColumnParallelLinear,
19
+ MergedColumnParallelLinear,
20
+ RowParallelLinear)
21
+from vllm.model_executor.layers.logits_processor import LogitsProcessor
22
+from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
23
+ causal_conv1d_fn, causal_conv1d_update)
24
+from vllm.model_executor.layers.mamba.ops.mamba_ssm import (
25
+ selective_scan_fn, selective_state_update)
26
+from vllm.model_executor.layers.sampler import SamplerOutput, get_sampler
27
+from vllm.model_executor.layers.vocab_parallel_embedding import (
28
+ DEFAULT_VOCAB_PADDING_SIZE, ParallelLMHead, VocabParallelEmbedding)
29
+from vllm.model_executor.models.interfaces import (HasInnerState, IsHybrid,
30
+ SupportsV0Only)
31
+from vllm.model_executor.models.mamba_cache import (MambaCacheManager,
32
+ MambaCacheParams)
33
+from vllm.model_executor.sampling_metadata import SamplingMetadata
34
+from vllm.sequence import IntermediateTensors
35
+
36
+from .utils import make_layers, maybe_prefix
37
+
38
+logger = init_logger(__name__)
39
+
40
+
41
+class SwiGLUActivation(nn.Module):
42
+
43
+ def forward(self, x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor:
44
+ return x1 * nn.functional.silu(x2)
45
+
46
+
47
+class SambaYMLP(nn.Module):
48
+ """Gated Linear Unit.
49
+
50
+ Reference:
51
+ Language Modeling with Gated Convolutional Networks.
52
+ https://arxiv.org/pdf/1612.08083v3.pdf.
53
+
54
+ """
55
+
56
+ def __init__(self, config):
57
+ super().__init__()
58
+
59
+ self.config = config
60
+ self.fc1 = nn.Linear(config.hidden_size,
61
+ 2 * config.intermediate_size,
62
+ bias=False)
63
+ self.fc2 = nn.Linear(config.intermediate_size,
64
+ config.hidden_size,
65
+ bias=False)
66
+
67
+ self.activation_fn = ACT2FN[config.hidden_act]
68
+
69
+ def forward(self, hidden_states):
70
+ y = self.fc1(hidden_states)
71
+ gate, y = y.chunk(2, dim=-1)
72
+ y = y * self.activation_fn(gate)
73
+ return self.fc2(y)
74
+
75
+
76
+def get_virtual_engine():
77
+ forward_context: ForwardContext = get_forward_context()
78
+ return forward_context.virtual_engine
79
+
80
+
81
+class SambaYAttention(nn.Module):
82
+
83
+ def __init__(self,
84
+ config,
85
+ layer_idx: Optional[int] = None,
86
+ yoco_cross: bool = False,
87
+ cache_config: Optional[CacheConfig] = None,
88
+ prefix: str = ""):
89
+ super().__init__()
90
+ if layer_idx is None:
91
+ logger.warning_once(
92
+ f"Instantiating {self.__class__.__name__} without passing "
93
+ "a `layer_idx` is not recommended and will lead to errors "
94
+ "during the forward call if caching is used. Please make "
95
+ "sure to provide a `layer_idx` when creating this class.")
96
+ self.hidden_size = config.hidden_size
97
+ self.num_heads = config.num_attention_heads
98
+ self.head_dim = self.hidden_size // self.num_heads
99
+ self.num_key_value_heads = config.num_key_value_heads
100
+ self.yoco_cross = yoco_cross
101
+
102
+ if (self.head_dim * self.num_heads) != self.hidden_size:
103
+ raise ValueError("hidden_size must be divisible by num_heads "
104
+ f"(got `hidden_size`: {self.hidden_size} and "
105
+ f"`num_heads`: {self.num_heads}).")
106
+
107
+ op_size = self.num_heads * self.head_dim + 2 * (
108
+ self.num_key_value_heads * self.head_dim)
109
+ self.out_proj = nn.Linear(self.num_heads * self.head_dim,
110
+ self.hidden_size,
111
+ bias=True)
112
+ if yoco_cross:
113
+ self.Wqkv = nn.Linear(self.hidden_size,
114
+ self.num_heads * self.head_dim,
115
+ bias=True)
116
+ else:
117
+ self.Wqkv = nn.Linear(self.hidden_size, op_size, bias=True)
118
+
119
+ # disable sliding window for the second half of the model
120
+ sliding_window = config.interleaved_sliding_window[layer_idx]
121
+ if layer_idx >= config.num_hidden_layers // 2:
122
+ assert sliding_window is None, \
123
+ "sliding_window must be none for the second decoder"
124
+ else:
125
+ assert sliding_window is not None, \
126
+ "sliding_window must be set for the first decoder"
127
+
128
+ assert self.num_heads % 2 == 0, 'num_heads should be even'
129
+ assert self.num_key_value_heads % 2 == 0, 'num_heads should be even'
130
+
131
+ self.lambda_init = self.lambda_init_fn(layer_idx)
132
+ self.lambda_q1 = nn.Parameter(
133
+ torch.zeros(self.head_dim, dtype=torch.float32).normal_(mean=0,
134
+ std=0.1))
135
+ self.lambda_k1 = nn.Parameter(
136
+ torch.zeros(self.head_dim, dtype=torch.float32).normal_(mean=0,
137
+ std=0.1))
138
+ self.lambda_q2 = nn.Parameter(
139
+ torch.zeros(self.head_dim, dtype=torch.float32).normal_(mean=0,
140
+ std=0.1))
141
+ self.lambda_k2 = nn.Parameter(
142
+ torch.zeros(self.head_dim, dtype=torch.float32).normal_(mean=0,
143
+ std=0.1))
144
+ self.subln = nn.RMSNorm(2 * self.head_dim,
145
+ eps=1e-5,
146
+ elementwise_affine=True)
147
+
148
+ params = {
149
+ 'differential_flash_attention_config': {
150
+ 'lambda_init': self.lambda_init,
151
+ 'lambda_q1': self.lambda_q1,
152
+ 'lambda_k1': self.lambda_k1,
153
+ 'lambda_q2': self.lambda_q2,
154
+ 'lambda_k2': self.lambda_k2,
155
+ "subln": self.subln,
156
+ }
157
+ }
158
+
159
+ if yoco_cross:
160
+ kv_shared_layer_index = config.num_hidden_layers // 2 + 1
161
+ kv_sharing_target_layer_name = \
162
+ f"model.layers.{kv_shared_layer_index}.self_attn.attn"
163
+ else:
164
+ kv_sharing_target_layer_name = None
165
+
166
+ self.attn = Attention(
167
+ self.num_heads,
168
+ self.head_dim,
169
+ self.head_dim**-0.5,
170
+ num_kv_heads=self.num_key_value_heads,
171
+ cache_config=cache_config,
172
+ per_layer_sliding_window=sliding_window,
173
+ prefix=f"{prefix}.attn",
174
+ attn_type=AttentionType.DECODER,
175
+ kv_sharing_target_layer_name=kv_sharing_target_layer_name,
176
+ **params)
177
+ assert self.attn.backend == _Backend.DIFFERENTIAL_FLASH_ATTN,\
178
+ "DIFFERENTIAL_FLASH_ATTN required"
179
+
180
+ def lambda_init_fn(self, depth):
181
+ return 0.8 - 0.6 * math.exp(-0.3 * depth)
182
+
183
+ def forward(
184
+ self,
185
+ hidden_states: torch.Tensor,
186
+ ):
187
+
188
+ if not self.yoco_cross: # need to generate kv-cache
189
+ qkv = self.Wqkv(hidden_states)
190
+ q, k, v = qkv.split([
191
+ self.hidden_size, self.num_key_value_heads * self.head_dim,
192
+ self.num_key_value_heads * self.head_dim
193
+ ],
194
+ dim=-1)
195
+ attn_output = self.attn(q, k, v)
196
+ else: # re-use the kv cache, full attention
197
+ q = self.Wqkv(hidden_states)
198
+ attn_output = self.attn(q, None, None)
199
+ attn_output = attn_output.view(-1, self.num_heads * self.head_dim)
200
+ return self.out_proj(attn_output)
201
+
202
+
203
+class Phi4Mamba(nn.Module):
204
+
205
+ def __init__(
206
+ self,
207
+ d_model,
208
+ d_state=16,
209
+ d_conv=4,
210
+ expand=2,
211
+ dt_rank="auto",
212
+ dt_min=0.001,
213
+ dt_max=0.1,
214
+ dt_init="random", # difference
215
+ dt_scale=1.0, # difference
216
+ dt_init_floor=1e-4,
217
+ conv_bias=True,
218
+ bias=False,
219
+ use_fast_path=True, # Fused kernel options
220
+ layer_idx=None,
221
+ device=None,
222
+ dtype=None,
223
+ yoco_cross=False,
224
+ yoco_kv=False,
225
+ ):
226
+ factory_kwargs = {"params_dtype": dtype} # difference
227
+ super().__init__()
228
+ self.yoco_cross = yoco_cross
229
+ self.yoco_kv = yoco_kv
230
+ self.d_model = d_model
231
+ self.d_state = d_state
232
+ self.d_conv = d_conv
233
+ self.expand = expand
234
+ self.d_inner = int(self.expand * self.d_model)
235
+ self.dt_rank = math.ceil(self.d_model /
236
+ 16) if dt_rank == "auto" else dt_rank
237
+ self.use_fast_path = use_fast_path
238
+ self.layer_idx = layer_idx
239
+ self.swiGluActivation = SwiGLUActivation()
240
+ if self.yoco_cross:
241
+ self.in_proj = MergedColumnParallelLinear(self.d_model,
242
+ [self.d_inner],
243
+ bias=bias,
244
+ **factory_kwargs)
245
+ self.out_proj = RowParallelLinear(self.d_inner,
246
+ self.d_model,
247
+ bias=bias,
248
+ **factory_kwargs)
249
+ return
250
+ self.conv1d = ColumnParallelLinear(
251
+ input_size=d_conv,
252
+ output_size=self.d_inner,
253
+ bias=conv_bias,
254
+ params_dtype=dtype,
255
+ )
256
+ # unsqueeze to fit conv1d weights shape into the linear weights shape.
257
+ # Can't do this in `weight_loader` since it already exists in
258
+ # `ColumnParallelLinear` and `set_weight_attrs`
259
+ # doesn't allow to override it
260
+ self.conv1d.weight.data = self.conv1d.weight.data.unsqueeze(1)
261
+
262
+ self.in_proj = MergedColumnParallelLinear(
263
+ self.d_model,
264
+ [self.d_inner] * 2,
265
+ bias=bias,
266
+ params_dtype=dtype,
267
+ )
268
+
269
+ # selective projection used to make dt, B and C input dependent
270
+ self.x_proj = RowParallelLinear(
271
+ self.d_inner,
272
+ self.dt_rank + self.d_state * 2,
273
+ bias=False,
274
+ params_dtype=dtype,
275
+ )
276
+
277
+ # time step projection (discretization) -
278
+ # In the forward we need to apply dt_proj without the bias,
279
+ # as the bias is added in the selective scan kernel.
280
+ self.dt_proj = ColumnParallelLinear(
281
+ self.dt_rank,
282
+ self.d_inner,
283
+ bias=True,
284
+ skip_bias_add=True,
285
+ params_dtype=dtype,
286
+ )
287
+
288
+ # # D "skip" parameter
289
+ # self.D = nn.Parameter(torch.ones(self.d_inner)) # Keep in fp32
290
+ self.A = nn.Parameter(
291
+ torch.empty(
292
+ self.d_inner,
293
+ self.d_state,
294
+ dtype=torch.float32,
295
+ ))
296
+ self.D = nn.Parameter(torch.ones(self.d_inner, dtype=torch.float32))
297
+
298
+ self.out_proj = RowParallelLinear(
299
+ self.d_inner,
300
+ self.d_model,
301
+ bias=bias,
302
+ input_is_parallel=True,
303
+ params_dtype=dtype,
304
+ )
305
+ self.activation = "silu"
306
+
307
+ def forward(self,
308
+ hidden_states: torch.Tensor,
309
+ attn_metadata: AttentionMetadata,
310
+ mamba_cache_params: MambaCacheParams,
311
+ yoco_key_values=None) -> torch.Tensor:
312
+
313
+ if self.yoco_cross:
314
+ out = self.in_proj(hidden_states)[0]
315
+ out = self.swiGluActivation(yoco_key_values, out)
316
+ out = self.out_proj(out)
317
+ return out[0], yoco_key_values
318
+
319
+ # 1. Gated MLP's linear projection
320
+ # projected_states = self.in_proj(hidden_states)[0].transpose(-2, -1)
321
+ projected_states = self.in_proj(
322
+ hidden_states.to(self.in_proj.weight.dtype))[0].transpose(-2, -1)
323
+ hidden_states, gate = projected_states.chunk(2, dim=-2)
324
+
325
+ # 2. Convolution sequence transformation
326
+ conv_weights = self.conv1d.weight.view(self.conv1d.weight.size(0),
327
+ self.conv1d.weight.size(2))
328
+
329
+ if attn_metadata.query_start_loc is not None \
330
+ and attn_metadata.context_lens_tensor is not None:
331
+ # |---------- N-1 iteration --------|
332
+ # |---------------- N iteration ---------------------|
333
+ # |- tokenA -|......................|-- newTokens ---|
334
+ # |---------- context_len ----------|
335
+ # |-------------------- seq_len ---------------------|
336
+ # |-- query_len ---|
337
+ hidden_states = causal_conv1d_fn(
338
+ hidden_states,
339
+ conv_weights,
340
+ self.conv1d.bias,
341
+ activation=self.activation,
342
+ conv_states=mamba_cache_params.conv_state,
343
+ has_initial_state=attn_metadata.context_lens_tensor > 0,
344
+ cache_indices=mamba_cache_params.state_indices_tensor,
345
+ query_start_loc=attn_metadata.query_start_loc)
346
+ else:
347
+ hidden_states = causal_conv1d_update(
348
+ hidden_states.transpose(0, 1),
349
+ mamba_cache_params.conv_state,
350
+ conv_weights,
351
+ self.conv1d.bias,
352
+ self.activation,
353
+ conv_state_indices=mamba_cache_params.state_indices_tensor)
354
+ hidden_states = hidden_states.transpose(0, 1)
355
+
356
+ # 3. State Space Model sequence transformation
357
+ # 3.a. input varying initialization of time_step, B and C
358
+ ssm_parameters = self.x_proj(hidden_states.transpose(-2, -1))[0]
359
+
360
+ time_step, B, C = torch.split(
361
+ ssm_parameters,
362
+ [self.dt_rank, self.d_state, self.d_state],
363
+ dim=-1,
364
+ )
365
+
366
+ # Note that Jamba normalizes B, C, and time_step here but Mamba doesn't.
367
+
368
+ discrete_time_step = self.dt_proj(time_step)[0].transpose(-2, -1)
369
+ # 3.c perform the recurrence y ← SSM(A, B, C)(x)
370
+ time_proj_bias = (self.dt_proj.bias.float() if hasattr(
371
+ self.dt_proj, "bias") else None)
372
+
373
+ if attn_metadata.query_start_loc is not None \
374
+ and attn_metadata.context_lens_tensor is not None:
375
+ scan_outputs = selective_scan_fn(
376
+ hidden_states,
377
+ mamba_cache_params.ssm_state,
378
+ discrete_time_step,
379
+ self.A,
380
+ B.transpose(-2, -1),
381
+ C.transpose(-2, -1),
382
+ self.D.float(),
383
+ # z,
384
+ None if self.yoco_kv else gate,
385
+ time_proj_bias,
386
+ delta_softplus=True,
387
+ cache_indices=mamba_cache_params.state_indices_tensor,
388
+ has_initial_state=attn_metadata.context_lens_tensor > 0,
389
+ query_start_loc=attn_metadata.query_start_loc)
390
+ else:
391
+ scan_outputs = selective_state_update(
392
+ mamba_cache_params.ssm_state,
393
+ hidden_states.transpose(0, 1),
394
+ discrete_time_step.transpose(0, 1),
395
+ self.A,
396
+ B,
397
+ C,
398
+ self.D,
399
+ # z
400
+ # gate.transpose(0, 1),
401
+ None if self.yoco_kv else gate.transpose(0, 1),
402
+ time_proj_bias,
403
+ dt_softplus=True,
404
+ state_batch_indices=mamba_cache_params.state_indices_tensor)
405
+ scan_outputs = scan_outputs.transpose(0, 1)
406
+
407
+ # 4. Final linear projection
408
+ if self.yoco_kv:
409
+ # gate = gate.transpose(-1,-2).contiguous()
410
+ yoco_key_values = scan_outputs.transpose(-2, -1)
411
+ scan_outputs = self.swiGluActivation(scan_outputs, gate)
412
+
413
+ contextualized_states = self.out_proj(scan_outputs.transpose(-2,
414
+ -1))[0]
415
+
416
+ return contextualized_states, yoco_key_values
417
+
418
+
419
+class SambaYDecoderLayer(nn.Module):
420
+
421
+ def __init__(
422
+ self,
423
+ config,
424
+ layer_idx,
425
+ cache_config,
426
+ prefix: str = "",
427
+ ) -> None:
428
+ super().__init__()
429
+
430
+ self.config = config
431
+ self.layer_idx = layer_idx
432
+
433
+ self.mlp = SambaYMLP(config)
434
+ self.input_layernorm = nn.LayerNorm(config.hidden_size,
435
+ eps=config.layer_norm_eps)
436
+
437
+ self.yoco_mb = False
438
+ self.yoco_cross = False
439
+ if layer_idx >= config.num_hidden_layers // 2:
440
+ self.yoco_mb = True
441
+ self.yoco_cross = (layer_idx
442
+ >= (config.num_hidden_layers // 2 + 2))
443
+ self.use_mamba = config.mb_per_layer > 0 and \
444
+ layer_idx % config.mb_per_layer == 0
445
+ if self.use_mamba:
446
+ factory_kwargs = {"dtype": None}
447
+ self.attn = Phi4Mamba(config.hidden_size,
448
+ layer_idx=layer_idx,
449
+ yoco_cross=self.yoco_cross,
450
+ yoco_kv=self.yoco_mb,
451
+ **factory_kwargs)
452
+ else:
453
+ self.attn = SambaYAttention(config,
454
+ layer_idx=layer_idx,
455
+ yoco_cross=self.yoco_cross,
456
+ cache_config=cache_config,
457
+ prefix=f"{prefix}.self_attn")
458
+ self.post_attention_layernorm = nn.LayerNorm(config.hidden_size,
459
+ eps=config.layer_norm_eps)
460
+
461
+ def forward(
462
+ self,
463
+ hidden_states: torch.Tensor,
464
+ positions: torch.Tensor,
465
+ attn_metadata: AttentionMetadata,
466
+ mamba_cache_params: MambaCacheParams,
467
+ ssm_output: Optional[torch.LongTensor] = None,
468
+ ) -> Union[torch.Tensor, IntermediateTensors]:
469
+ if self.use_mamba:
470
+ assert mamba_cache_params is not None
471
+ else:
472
+ assert mamba_cache_params is None
473
+
474
+ residual = hidden_states
475
+ hidden_states = self.input_layernorm(
476
+ hidden_states.to(dtype=self.input_layernorm.weight.dtype))
477
+
478
+ if self.use_mamba:
479
+ attn_outputs, ssm_output = self.attn(hidden_states,
480
+ attn_metadata,
481
+ mamba_cache_params,
482
+ yoco_key_values=ssm_output)
483
+ residual = residual.to(torch.float32)
484
+ else:
485
+ attn_outputs = self.attn(hidden_states, )
486
+ hidden_states = residual + attn_outputs
487
+ residual = hidden_states
488
+ hidden_states = self.post_attention_layernorm(
489
+ hidden_states.to(dtype=self.post_attention_layernorm.weight.dtype))
490
+ hidden_states = self.mlp(hidden_states)
491
+ hidden_states = residual + hidden_states
492
+
493
+ return hidden_states, ssm_output
494
+
495
+
496
+class SambaYModel(nn.Module):
497
+
498
+ def __init__(self,
499
+ config,
500
+ cache_config=None,
501
+ quant_config=None,
502
+ lora_config=None,
503
+ prefix: str = "") -> None:
504
+ super().__init__()
505
+ self.config = config
506
+ self.vocab_size = config.vocab_size
507
+ self.embed_tokens = VocabParallelEmbedding(
508
+ self.vocab_size,
509
+ config.hidden_size,
510
+ org_num_embeddings=config.vocab_size,
511
+ )
512
+
513
+ # Pipeline parallel is not supported since the second half of
514
+ # the layers share the kv cache.
515
+ if get_pp_group().world_size != 1:
516
+ raise ValueError("Pipeline Parallel not supported")
517
+
518
+ self.start_layer, self.end_layer, self.layers = make_layers(
519
+ config.num_hidden_layers,
520
+ lambda prefix: SambaYDecoderLayer(config,
521
+ int(prefix.split('.')[-1]),
522
+ cache_config,
523
+ prefix=prefix),
524
+ prefix=f"{prefix}.layers")
525
+ self.final_layernorm = nn.LayerNorm(config.hidden_size,
526
+ eps=config.layer_norm_eps)
527
+
528
+ def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
529
+ return self.embed_tokens(input_ids)
530
+
531
+ def forward(
532
+ self,
533
+ input_ids: Optional[torch.Tensor],
534
+ positions: torch.Tensor,
535
+ attn_metadata: AttentionMetadata,
536
+ mamba_cache_params: MambaCacheParams,
537
+ intermediate_tensors: Optional[IntermediateTensors] = None,
538
+ inputs_embeds: Optional[torch.Tensor] = None,
539
+ ) -> Union[torch.Tensor, IntermediateTensors]:
540
+
541
+ if get_pp_group().is_first_rank:
542
+ if inputs_embeds is not None:
543
+ hidden_states = inputs_embeds
544
+ else:
545
+ hidden_states = self.get_input_embeddings(input_ids)
546
+ else:
547
+ assert intermediate_tensors is not None
548
+ hidden_states = intermediate_tensors["hidden_states"]
549
+
550
+ mamba_state_idx = 0
551
+ ssm_output = None
552
+ for i in range(self.start_layer, self.end_layer):
553
+ layer = self.layers[i]
554
+ if i == self.config.num_hidden_layers // 2 + 2:
555
+ # profile run
556
+ kv_cache_idx = self.config.num_hidden_layers // 2 + 1
557
+ cache_layer = self.layers[kv_cache_idx]
558
+ kv_cache = cache_layer.attn.attn.kv_cache
559
+ if kv_cache[0].numel() == 0:
560
+ break
561
+
562
+ # Starting from this layer, we do not need to calculate
563
+ # the kv cache since we reuse the kv cache from last layer.
564
+ # If in prefill phase, we can <s>prune></s> truncate
565
+ # the hidden state to save computation cost.
566
+ if attn_metadata.prefill_metadata and not envs.VLLM_USE_V1:
567
+ selected_token_indices = torch.cumsum(
568
+ attn_metadata.seq_lens_tensor, dim=0) - 1
569
+ hidden_states = hidden_states.index_select(
570
+ 0, selected_token_indices)
571
+ ssm_output = ssm_output.index_select(
572
+ 0, selected_token_indices)
573
+
574
+ if layer.use_mamba:
575
+ if i < self.config.num_hidden_layers // 2 or \
576
+ not layer.yoco_cross:
577
+ mamba_cache = mamba_cache_params.at_layer_idx(
578
+ mamba_state_idx)
579
+ mamba_state_idx += 1
580
+ else:
581
+ mamba_cache = mamba_cache_params.at_layer_idx(
582
+ mamba_state_idx - 1)
583
+
584
+ hidden_states, ssm_output = layer(hidden_states,
585
+ positions,
586
+ attn_metadata,
587
+ mamba_cache,
588
+ ssm_output=ssm_output)
589
+ else:
590
+ hidden_states, ssm_output = layer(
591
+ hidden_states,
592
+ positions,
593
+ attn_metadata,
594
+ None, # mamba_cache_params
595
+ ssm_output=ssm_output)
596
+
597
+ hidden_states = self.final_layernorm(
598
+ hidden_states.to(dtype=self.final_layernorm.weight.dtype))
599
+ return hidden_states
600
+
601
+
602
+class Phi4FlashForCausalLM(nn.Module, HasInnerState, IsHybrid, SupportsV0Only):
603
+
604
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
605
+ config = vllm_config.model_config.hf_config
606
+ cache_config = vllm_config.cache_config
607
+ lora_config = vllm_config.lora_config
608
+ quant_config = vllm_config.quant_config
609
+ scheduler_config = vllm_config.scheduler_config
610
+ self.compilation_config = vllm_config.compilation_config
611
+ self.vllm_config = vllm_config
612
+ # Prefix caching and chunked prefill is not supported for this model.
613
+ assert not cache_config.enable_prefix_caching, \
614
+ "Phi4flash currently does not support prefix caching"
615
+ assert not scheduler_config.chunked_prefill_enabled, \
616
+ "Phi4Flash currently does not support prefix caching"
617
+ super().__init__()
618
+ self.config = config
619
+ self.model_config = vllm_config.model_config
620
+ self.scheduler_config = scheduler_config
621
+ self.model = SambaYModel(config,
622
+ cache_config=cache_config,
623
+ prefix=maybe_prefix(prefix, "model"))
624
+ self.unpadded_vocab_size = config.vocab_size
625
+ if lora_config:
626
+ self.unpadded_vocab_size += lora_config.lora_extra_vocab_size
627
+ self.lm_head = ParallelLMHead(
628
+ self.unpadded_vocab_size,
629
+ config.hidden_size,
630
+ org_num_embeddings=config.vocab_size,
631
+ padding_size=(
632
+ DEFAULT_VOCAB_PADDING_SIZE
633
+ # We need bigger padding if using lora for kernel
634
+ # compatibility
635
+ if not lora_config else lora_config.lora_vocab_padding_size),
636
+ quant_config=quant_config,
637
+ )
638
+ self.embedding_bias = None
639
+ # Used to track and store by the Mamba cache between steps.
640
+ self.mamba_cache: Optional[MambaCacheManager] = None
641
+ self.logits_processor = LogitsProcessor(self.unpadded_vocab_size,
642
+ config.vocab_size,
643
+ logits_as_input=False)
644
+ self.sampler = get_sampler()
645
+
646
+ def forward(
647
+ self,
648
+ input_ids: torch.Tensor,
649
+ positions: torch.Tensor,
650
+ intermediate_tensors: Optional[IntermediateTensors] = None,
651
+ inputs_embeds: Optional[torch.Tensor] = None,
652
+ **kwargs,
653
+ ) -> Union[torch.Tensor, IntermediateTensors]:
654
+ if self.mamba_cache is None:
655
+ num_mamba_layers = self.config.num_hidden_layers \
656
+ // 2 // self.config.mb_per_layer + 1
657
+ self.mamba_cache = MambaCacheManager(
658
+ self.vllm_config, self.lm_head.weight.dtype, num_mamba_layers,
659
+ *self._get_mamba_cache_shape())
660
+ mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
661
+
662
+ attn_metadata = get_forward_context().attn_metadata
663
+ # input_ids and hidden_states isn't a one-to-one mapping in prefill
664
+ # stage due to YOCO optimization.
665
+ hidden_states = self.model(input_ids, positions, attn_metadata,
666
+ mamba_cache_params, intermediate_tensors,
667
+ inputs_embeds)
668
+ return hidden_states
669
+
670
+ def _get_mamba_cache_shape(
671
+ self
672
+ ) -> tuple[Optional[tuple[int, int]], Optional[tuple[int, int]]]:
673
+ world_size = get_tensor_model_parallel_world_size()
674
+ hidden_size = self.config.hidden_size
675
+ mamba_expand = self.config.mamba_expand # 2
676
+ mamba_d_conv = self.config.mamba_d_conv # 4
677
+ mamba_d_state = self.config.mamba_d_state # 16
678
+ conv_state_shape = (
679
+ mamba_expand * hidden_size // world_size,
680
+ mamba_d_conv - 1,
681
+ )
682
+ temporal_state_shape = (
683
+ mamba_expand * hidden_size // world_size,
684
+ mamba_d_state,
685
+ )
686
+ return conv_state_shape, temporal_state_shape
687
+
688
+ def copy_inputs_before_cuda_graphs(self, input_buffers, **kwargs):
689
+ return self.mamba_cache.copy_inputs_before_cuda_graphs(
690
+ input_buffers, **kwargs)
691
+
692
+ def get_seqlen_agnostic_capture_inputs(self, batch_size: int):
693
+ return self.mamba_cache.get_seqlen_agnostic_capture_inputs(batch_size)
694
+
695
+ def compute_logits(
696
+ self,
697
+ hidden_states: torch.Tensor,
698
+ sampling_metadata: SamplingMetadata,
699
+ ) -> Optional[torch.Tensor]:
700
+ # If the shape is the same, it means that we have already
701
+ # prune hidden states manually.
702
+ prune_hidden_states = hidden_states.size(
703
+ 0) != sampling_metadata.selected_token_indices.size(0)
704
+ processed_logits = self.logits_processor(
705
+ self.lm_head,
706
+ hidden_states,
707
+ sampling_metadata,
708
+ self.embedding_bias,
709
+ prune_hidden_states=prune_hidden_states)
710
+ return processed_logits
711
+
712
+ def sample(
713
+ self,
714
+ logits: torch.Tensor,
715
+ sampling_metadata: SamplingMetadata,
716
+ ) -> Optional[SamplerOutput]:
717
+ next_tokens = self.sampler(logits, sampling_metadata)
718
+ return next_tokens
719
+
720
+ def load_weights(
721
+ self,
722
+ weights: Iterable[tuple[str, torch.Tensor]],
723
+ ):
724
+ weights = {name: weight for name, weight in weights}
725
+ adjusted_weights = {}
726
+ for name, weight in weights.items():
727
+ if "A_log" in name:
728
+ name = name.replace("A_log", "A")
729
+ weight = -torch.exp(weight.float())
730
+ if "inner_cross_attn." in name:
731
+ name = name.replace("inner_cross_attn.", "")
732
+ adjusted_weights[name] = weight
733
+ adjusted_weights["lm_head.weight"] = weights[
734
+ "model.embed_tokens.weight"]
735
+ loaded_params: set[str] = set()
736
+ for name, param in self.named_parameters():
737
+ weight = adjusted_weights.get(name)
738
+ if weight is not None and weight.shape != param.shape:
739
+ logger.warning("Shape mismatch: %s %s %s", name, weight.shape,
740
+ param.shape)
741
+ loaded_params.add(name)
742
+ missing_keys, unexpected_keys = self.load_state_dict(adjusted_weights,
743
+ strict=False)
744
+ assert len(unexpected_keys) == 0, f"Unexpected keys: {unexpected_keys}"
745
+ assert len(missing_keys) == 0, f"Missing keys: {missing_keys}"
746
+ return loaded_params
vllm/model_executor/models/registry.py
+1
@@ -110,6 +110,7 @@ _TEXT_GENERATION_MODELS = {
110
"Phi3ForCausalLM": ("phi3", "Phi3ForCausalLM"),
111
"Phi3SmallForCausalLM": ("phi3_small", "Phi3SmallForCausalLM"),
112
"PhiMoEForCausalLM": ("phimoe", "PhiMoEForCausalLM"),
113
+ "Phi4FlashForCausalLM": ("phi4flash", "Phi4FlashForCausalLM"),
114
"Plamo2ForCausalLM": ("plamo2", "Plamo2ForCausalLM"),
115
"QWenLMHeadModel": ("qwen", "QWenLMHeadModel"),
116
"Qwen2ForCausalLM": ("qwen2", "Qwen2ForCausalLM"),
vllm/platforms/cuda.py
+4
@@ -316,6 +316,10 @@ class CudaPlatformBase(Platform):
316
logger.info("Using DualChunkFlashAttention backend.")
317
return ("vllm.attention.backends.dual_chunk_flash_attn."
318
"DualChunkFlashAttentionBackend")
319
+ elif selected_backend == _Backend.DIFFERENTIAL_FLASH_ATTN:
320
+ logger.info("Using DifferentialFlashAttention backend.")
321
+ return ("vllm.attention.backends.differential_flash_attn."
322
+ "DifferentialFlashAttentionBackend")
323
elif selected_backend == _Backend.FLASH_ATTN:
324
pass
325
elif selected_backend:
vllm/platforms/interface.py
+1
@@ -60,6 +60,7 @@ class _Backend(enum.Enum):
60
IPEX = enum.auto()
61
BLOCK_SPARSE_FLASH_ATTN = enum.auto()
62
DUAL_CHUNK_FLASH_ATTN = enum.auto()
63
+ DIFFERENTIAL_FLASH_ATTN = enum.auto()
64
NO_ATTENTION = enum.auto()
65
FLEX_ATTENTION = enum.auto()
66
vllm/utils/__init__.py
+15
-3
@@ -2888,8 +2888,9 @@ def get_mp_context():
2888
2889
2890
def bind_kv_cache(
2891
- ctx: dict[str, Any],
2892
- kv_cache: list[list[torch.Tensor]], # [virtual_engine][layer_index]
2891
+ ctx: dict[str, Any],
2892
+ kv_cache: list[list[torch.Tensor]], # [virtual_engine][layer_index]
2893
+ shared_kv_cache_layers: Optional[dict[str, str]] = None
2894
) -> None:
2895
# Bind the kv_cache tensor to Attention modules, similar to
2896
# ctx[layer_name].kv_cache[ve]=kv_cache[ve][extract_layer_index(layer_name)]
@@ -2901,12 +2902,17 @@ def bind_kv_cache(
2902
# attention of the same layer (e.g., bart's decoder.layers.1.self_attn
2903
# and decoder.layers.1.encoder_attn) is mapped to the same kv cache
2904
# tensor
2905
+ # 5. Some models have attention layers that share kv cache with previous
2906
+ # layers, this is specified through shared_kv_cache_layers
2907
+ if shared_kv_cache_layers is None:
2908
+ shared_kv_cache_layers = {}
2909
from vllm.attention import AttentionType
2910
from vllm.model_executor.models.utils import extract_layer_index
2911
layer_need_kv_cache = [
2912
layer_name for layer_name in ctx
2913
if (hasattr(ctx[layer_name], 'attn_type') and ctx[layer_name].attn_type
2909
- in (AttentionType.DECODER, AttentionType.ENCODER_DECODER))
2914
+ in (AttentionType.DECODER, AttentionType.ENCODER_DECODER)) \
2915
+ and ctx[layer_name].kv_sharing_target_layer_name is None
2916
]
2917
layer_index_sorted = sorted(
2918
set(
@@ -2919,6 +2925,12 @@ def bind_kv_cache(
2925
assert len(forward_ctx.kv_cache) == len(kv_cache)
2926
for ve, ve_kv_cache in enumerate(kv_cache):
2927
forward_ctx.kv_cache[ve] = ve_kv_cache[kv_cache_idx]
2928
+ if shared_kv_cache_layers is not None:
2929
+ for layer_name, target_layer_name in shared_kv_cache_layers.items():
2930
+ assert extract_layer_index(target_layer_name) < \
2931
+ extract_layer_index(layer_name), \
2932
+ "v0 doesn't support interleaving kv sharing"
2933
+ ctx[layer_name].kv_cache = ctx[target_layer_name].kv_cache
2934
2935
2936
def run_method(obj: Any, method: Union[str, bytes, Callable], args: tuple[Any],
vllm/worker/model_runner.py
+4
@@ -1112,6 +1112,10 @@ class GPUModelRunnerBase(ModelRunnerBase[TModelInputForGPU]):
1112
(self.max_batchsize_to_capture, self.get_max_block_per_batch()),
1113
dtype=np.int32)
1114
1115
+ self.cross_layer_shared_graph_block_tables = np.zeros(
1116
+ (self.max_batchsize_to_capture, self.get_max_block_per_batch()),
1117
+ dtype=np.int32)
1118
+
1119
# Attention-free but stateful models like Mamba need a placeholder attn
1120
# backend, as the attention metadata is needed to manage internal state.
1121
# However we must bypass attention selection altogether for some models
vllm/worker/worker.py
+24
-2
@@ -9,7 +9,8 @@ import torch
9
import torch.distributed
10
11
import vllm.envs as envs
12
-from vllm.config import VllmConfig
12
+from vllm.attention.layer import Attention
13
+from vllm.config import VllmConfig, get_layers_from_vllm_config
14
from vllm.device_allocator.cumem import CuMemAllocator
15
from vllm.distributed import (ensure_model_parallel_initialized,
16
init_distributed_environment,
@@ -345,8 +346,29 @@ class Worker(LocalOrDistributedWorkerBase):
346
self.cache_engine[ve].gpu_cache
347
for ve in range(self.parallel_config.pipeline_parallel_size)
348
]
349
+
350
+ # Layer pairings for cross-layer KV sharing.
351
+ # If an Attention layer `layer_name` is in the keys of this dict, it
352
+ # means this layer will perform attention using the keys and values
353
+ # from the KV cache of `shared_kv_cache_layers[layer_name]`.
354
+ shared_kv_cache_layers: dict[str, str] = {}
355
+
356
+ attn_layers = get_layers_from_vllm_config(self.vllm_config, Attention)
357
+
358
+ for layer_name, attn_module in attn_layers.items():
359
+ if (kv_tgt_layer :=
360
+ attn_module.kv_sharing_target_layer_name) is not None:
361
+ # The layer doesn't need its own KV cache and will use that of
362
+ # the target layer. We skip creating a KVCacheSpec for it, so
363
+ # that KV cache management logic will act as this layer does
364
+ # not exist, and doesn't allocate KV cache for the layer. This
365
+ # enables the memory saving of cross-layer kv sharing, allowing
366
+ # a given amount of memory to accommodate longer context lengths
367
+ # or enable more requests to be processed simultaneously.
368
+ shared_kv_cache_layers[layer_name] = kv_tgt_layer
369
+
370
bind_kv_cache(self.compilation_config.static_forward_context,
349
- self.gpu_cache)
371
+ self.gpu_cache, shared_kv_cache_layers)
372
373
def _warm_up_model(self) -> None:
374
# warm up sizes that are not in cudagraph capture sizes,