main
py 2,130 lines 83 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Attention layer with FlashAttention."""
4
5 import copy
6 import functools
7 from dataclasses import dataclass
8 from typing import Any, ClassVar
9
10 import numpy as np
11 import torch
12
13 from vllm.model_executor.layers.attention import Attention
14 from vllm.model_executor.warmup.jit_warmup import VllmJitKernel, WarmupChoices
15 from vllm.platforms import current_platform
16 from vllm.utils.torch_utils import (
17 PIN_MEMORY,
18 canonicalize_singleton_dim_strides,
19 is_quantized_kv_cache,
20 )
21 from vllm.v1.attention.backend import (
22 AttentionBackend,
23 AttentionImpl,
24 AttentionType,
25 MultipleOf,
26 )
27 from vllm.v1.attention.backends.fa_utils import (
28 FA4_HD256_PAGE_SIZE,
29 flash_attn_supports_kv_cache_dtype,
30 flash_attn_supports_quant_query_input,
31 get_flash_attn_version,
32 is_fa_version_supported,
33 is_flash_attn_varlen_func_available,
34 uses_fa4_hd256_kernel,
35 )
36 from vllm.v1.attention.backends.utils import (
37 fill_mm_prefix_query_ranges,
38 get_dcp_local_seq_lens,
39 get_num_attention_heads_from_layers,
40 )
41 from vllm.v1.attention.ops.dcp import (
42 cp_lse_ag_out_rs,
43 dcp_a2a_lse_reduce,
44 )
45 from vllm.v1.attention.ops.merge_attn_states import merge_attn_states
46 from vllm.v1.worker.workspace import current_workspace_manager
47
48 if is_flash_attn_varlen_func_available():
49 from vllm.v1.attention.backends.fa_utils import (
50 compile_flash_attn_varlen_func_from_specs,
51 flash_attn_supports_sinks,
52 flash_attn_varlen_func,
53 get_scheduler_metadata,
54 reshape_and_cache_flash,
55 )
56 import vllm.envs as envs
57 from vllm.config import (
58 VllmConfig,
59 get_current_vllm_config_or_none,
60 get_layers_from_vllm_config,
61 )
62 from vllm.config.cache import CacheDType
63 from vllm.distributed.parallel_state import (
64 get_dcp_group,
65 get_dcp_world_size_and_rank,
66 )
67 from vllm.logger import init_logger
68 from vllm.platforms.interface import DeviceCapability
69 from vllm.utils.math_utils import cdiv, round_up
70 from vllm.v1.attention.backend import (
71 AttentionCGSupport,
72 AttentionMetadataBuilder,
73 CommonAttentionMetadata,
74 )
75 from vllm.v1.kv_cache_interface import (
76 AttentionSpec,
77 FullAttentionSpec,
78 KVCacheSpec,
79 KVQuantMode,
80 SlidingWindowSpec,
81 )
82 from vllm.v1.worker.cp_utils import (
83 run_split_fa2_dcp_context_attention,
84 should_skip_dcp_context_attention,
85 should_split_fa2_dcp_context_attention,
86 split_dcp_context_queries,
87 )
88
89 logger = init_logger(__name__)
90
91 FA4_DENSE_FLOAT_DTYPES = (torch.bfloat16, torch.float16)
92 FA4_DENSE_Q_TILE = 128
93 FA4_DENSE_NUM_BLOCKS = 256
94 FA4_DENSE_MAX_SEQLEN_K = 8192
95
96
97 class FA4DenseAttentionKernel(VllmJitKernel["FA4DenseAttentionKernel.CompileKey"]):
98 """Warm paged, causal FA4 on Blackwell and Hopper."""
99
100 @dataclass(frozen=True)
101 class CompileKey:
102 q_stage: int
103 dtype: torch.dtype
104 qhead_per_kvhead: int
105 head_dim: int
106 page_size: int
107 has_window_left: bool
108 has_window_right: bool
109 softcap: float
110 is_single_batch: bool
111 num_splits: int
112
113 @staticmethod
114 def kernel(*args: Any, **kwargs: Any) -> Any:
115 assert flash_attn_varlen_func is not None
116 return flash_attn_varlen_func(*args, **kwargs)
117
118 def dispatch( # type: ignore[override]
119 self,
120 *,
121 q_stage: int,
122 dtype: torch.dtype,
123 num_qo_heads: int,
124 num_kv_heads: int,
125 head_dim: int,
126 page_size: int,
127 window_size: tuple[int, int],
128 softcap: float,
129 is_single_batch: bool = True,
130 num_splits: int = 1,
131 ) -> CompileKey:
132 return self.CompileKey(
133 q_stage=q_stage,
134 dtype=dtype,
135 qhead_per_kvhead=num_qo_heads // num_kv_heads,
136 head_dim=head_dim,
137 page_size=page_size,
138 has_window_left=window_size[0] >= 0,
139 has_window_right=window_size[1] >= 0,
140 softcap=softcap,
141 is_single_batch=is_single_batch,
142 num_splits=num_splits,
143 )
144
145 def get_warmup_keys(
146 self,
147 *,
148 vllm_config: VllmConfig,
149 kv_cache_spec: AttentionSpec,
150 num_qo_heads: int,
151 ) -> list[CompileKey]:
152 capability = current_platform.get_device_capability()
153 major = capability.major if capability is not None else None
154 if (
155 vllm_config.parallel_config.decode_context_parallel_size != 1
156 or vllm_config.model_config.rswa_window is not None
157 or kv_cache_spec.kv_quant_mode != KVQuantMode.NONE
158 or kv_cache_spec.dtype not in FA4_DENSE_FLOAT_DTYPES
159 or not isinstance(kv_cache_spec, (FullAttentionSpec, SlidingWindowSpec))
160 ):
161 return []
162 head_dim = kv_cache_spec.head_size
163 page_size = kv_cache_spec.block_size
164 sliding_window = kv_cache_spec.sliding_window
165 window_size = (
166 (sliding_window - 1, 0) if sliding_window is not None else (-1, -1)
167 )
168 softcap = float(
169 getattr(
170 vllm_config.model_config.hf_text_config, "attn_logit_softcapping", 0
171 )
172 or 0
173 )
174 if (
175 major in (10, 11)
176 and uses_fa4_hd256_kernel(head_dim)
177 and page_size == FA4_HD256_PAGE_SIZE
178 ):
179 q_stages = WarmupChoices(1, 2)
180 single_batches = WarmupChoices(True, False)
181 split_counts = WarmupChoices(1)
182 elif (
183 major == 9 and head_dim == 512 and window_size == (-1, -1) and softcap == 0
184 ):
185 # SM90 forward has split/non-split variants; its transitive
186 # combine specializes at 32/64/128/256 splits.
187 q_stages = WarmupChoices(1)
188 single_batches = WarmupChoices(False)
189 split_counts = WarmupChoices(1, 32, 64, 128, 256)
190 else:
191 return []
192
193 return self._trace_dispatch(self.dispatch)(
194 q_stage=q_stages,
195 is_single_batch=single_batches,
196 num_splits=split_counts,
197 dtype=vllm_config.model_config.dtype,
198 num_qo_heads=num_qo_heads,
199 num_kv_heads=kv_cache_spec.num_kv_heads,
200 head_dim=head_dim,
201 page_size=page_size,
202 window_size=WarmupChoices(window_size),
203 softcap=softcap,
204 )
205
206 def compile(self, compile_key: CompileKey) -> None:
207 assert compile_flash_attn_varlen_func_from_specs is not None
208 max_seqlen_q = FA4_DENSE_Q_TILE + 1 if compile_key.q_stage == 2 else 1
209 batch_size = 1 if compile_key.is_single_batch else 2
210 kv_shape = (
211 FA4_DENSE_NUM_BLOCKS,
212 compile_key.page_size,
213 1,
214 compile_key.head_dim,
215 )
216 kv_stride = (
217 2 * compile_key.page_size * compile_key.head_dim,
218 2 * compile_key.head_dim,
219 2 * compile_key.head_dim,
220 1,
221 )
222 compile_flash_attn_varlen_func_from_specs(
223 q_shape=(
224 batch_size * max_seqlen_q,
225 compile_key.qhead_per_kvhead,
226 compile_key.head_dim,
227 ),
228 k_shape=kv_shape,
229 v_shape=kv_shape,
230 k_stride=kv_stride,
231 v_stride=kv_stride,
232 q_dtype=compile_key.dtype,
233 cu_seqlens_q_shape=(batch_size + 1,),
234 seqused_k_shape=(batch_size,),
235 page_table_shape=(
236 batch_size,
237 FA4_DENSE_MAX_SEQLEN_K // compile_key.page_size,
238 ),
239 max_seqlen_q=max_seqlen_q,
240 max_seqlen_k=FA4_DENSE_MAX_SEQLEN_K,
241 causal=True,
242 window_size=[
243 1 if compile_key.has_window_left else -1,
244 1 if compile_key.has_window_right else -1,
245 ],
246 softcap=compile_key.softcap,
247 num_splits=2 if compile_key.num_splits > 1 else 1,
248 fa_version=4,
249 return_softmax_lse=False,
250 )
251 if compile_key.head_dim == 512 and compile_key.num_splits > 1:
252 from torch._subclasses.fake_tensor import FakeTensorMode
253
254 from vllm.vllm_flash_attn.cute.interface import _flash_attn_fwd_combine
255
256 # FA4's forward compile-only entry returns before its transitive
257 # SplitKV combine. Call the native combine entry in FakeTensorMode:
258 # it populates the native cache without launching the kernel.
259 with FakeTensorMode():
260 _flash_attn_fwd_combine(
261 torch.empty(
262 (compile_key.num_splits, 2, 1, 512),
263 dtype=torch.float32,
264 device="cuda",
265 ),
266 torch.empty(
267 (compile_key.num_splits, 2, 1),
268 dtype=torch.float32,
269 device="cuda",
270 ),
271 torch.empty((2, 1, 512), dtype=compile_key.dtype, device="cuda"),
272 cu_seqlens=torch.empty((3,), dtype=torch.int32, device="cuda"),
273 )
274
275 def __call__(
276 self,
277 *,
278 q: torch.Tensor,
279 k: torch.Tensor,
280 v: torch.Tensor,
281 **kwargs: Any,
282 ) -> Any:
283 return self.kernel(q=q, k=k, v=v, **kwargs)
284
285
286 class FlashAttentionBackend(AttentionBackend):
287 supported_dtypes: ClassVar[list[torch.dtype]] = [torch.float16, torch.bfloat16]
288 supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [
289 "auto",
290 "float16",
291 "bfloat16",
292 "fp8",
293 "fp8_e4m3",
294 ]
295 head_size_v: int | None = None
296
297 @staticmethod
298 def _get_sm90_fa4_fp8_kv_block_size(
299 kv_cache_spec: KVCacheSpec | None = None,
300 ) -> int | None:
301 if kv_cache_spec is None:
302 vllm_config = get_current_vllm_config_or_none()
303 if vllm_config is None or vllm_config.model_config is None:
304 return None
305 head_size = vllm_config.model_config.get_head_size()
306 uses_fp8_kv = vllm_config.cache_config.cache_dtype in (
307 "fp8",
308 "fp8_e4m3",
309 )
310 else:
311 if not isinstance(kv_cache_spec, AttentionSpec):
312 return None
313 head_size = kv_cache_spec.head_size
314 uses_fp8_kv = kv_cache_spec.kv_quant_mode == KVQuantMode.FP8_PER_TENSOR
315
316 if (
317 current_platform.is_device_capability_family(90)
318 and uses_fp8_kv
319 and head_size == 512
320 and get_flash_attn_version(head_size=head_size) == 4
321 ):
322 # The SM90 FP8-KV-dequant kernel uses a 64-token TMA tile/page.
323 return 64
324 return None
325
326 @classmethod
327 def _get_fa4_hd256_block_size(
328 cls, kv_cache_spec: KVCacheSpec | None = None
329 ) -> int | None:
330 if kv_cache_spec is None:
331 vllm_config = get_current_vllm_config_or_none()
332 if vllm_config is None or vllm_config.model_config is None:
333 return None
334 head_size = vllm_config.model_config.get_head_size()
335 head_size_v = cls.head_size_v
336 else:
337 if not isinstance(kv_cache_spec, AttentionSpec):
338 return None
339 head_size = kv_cache_spec.head_size
340 head_size_v = kv_cache_spec.head_size_v
341
342 if (
343 uses_fa4_hd256_kernel(head_size, head_size_v)
344 and get_flash_attn_version(
345 head_size=head_size,
346 head_size_v=head_size_v,
347 supports_fa4_hd256=True,
348 )
349 == 4
350 ):
351 return FA4_HD256_PAGE_SIZE
352 return None
353
354 @classmethod
355 def get_supported_kernel_block_sizes(
356 cls, kv_cache_spec: KVCacheSpec | None = None
357 ) -> list[int | MultipleOf]:
358 if block_size := cls._get_sm90_fa4_fp8_kv_block_size(kv_cache_spec):
359 return [block_size]
360 if block_size := cls._get_fa4_hd256_block_size(kv_cache_spec):
361 # Sliding-window specs select the smallest advertised size.
362 return [block_size]
363 return [MultipleOf(16)]
364
365 forward_includes_kv_cache_update: bool = False
366
367 @classmethod
368 def get_preferred_block_size(cls, default_block_size: int) -> int:
369 if block_size := cls._get_sm90_fa4_fp8_kv_block_size():
370 return max(default_block_size, block_size)
371 if block_size := cls._get_fa4_hd256_block_size():
372 return max(default_block_size, block_size)
373 if current_platform.is_xpu():
374 return max(default_block_size, 64)
375 return super().get_preferred_block_size(default_block_size)
376
377 @staticmethod
378 def get_name() -> str:
379 return "FLASH_ATTN"
380
381 @classmethod
382 def supports_rswa(cls) -> bool:
383 return True
384
385 @classmethod
386 def supports_sliding_window(cls) -> bool:
387 return True
388
389 @classmethod
390 def supports_batch_invariance(cls) -> bool:
391 return True
392
393 @classmethod
394 def supports_non_causal(cls) -> bool:
395 return True
396
397 @classmethod
398 def supports_attn_type(cls, attn_type: str) -> bool:
399 """FlashAttention supports all attention types."""
400 return attn_type in (
401 AttentionType.DECODER,
402 AttentionType.ENCODER,
403 AttentionType.ENCODER_ONLY,
404 AttentionType.ENCODER_DECODER,
405 )
406
407 @classmethod
408 def supports_per_head_quant_scales(cls) -> bool:
409 fa_version = get_flash_attn_version()
410 return fa_version is not None and fa_version >= 3
411
412 @staticmethod
413 def get_impl_cls() -> type["FlashAttentionImpl"]:
414 return FlashAttentionImpl
415
416 @staticmethod
417 def get_builder_cls() -> type["FlashAttentionMetadataBuilder"]:
418 return FlashAttentionMetadataBuilder
419
420 @classmethod
421 def supports_head_size(cls, head_size: int) -> bool:
422 if head_size % 8 != 0:
423 return False
424 if head_size <= 256:
425 return True
426 if is_fa_version_supported(4):
427 return head_size <= 512
428 return False
429
430 @classmethod
431 def supports_kv_cache_dtype(cls, kv_cache_dtype: CacheDType | None) -> bool:
432 if kv_cache_dtype is None:
433 return True
434 if kv_cache_dtype not in cls.supported_kv_cache_dtypes:
435 return False
436 if is_quantized_kv_cache(kv_cache_dtype):
437 return flash_attn_supports_kv_cache_dtype(kv_cache_dtype)
438 return True
439
440 @classmethod
441 def supports_mm_prefix(cls) -> bool:
442 return is_fa_version_supported(4)
443
444 @classmethod
445 def supports_sink(cls) -> bool:
446 if not is_flash_attn_varlen_func_available():
447 return False
448 return flash_attn_supports_sinks()
449
450 @classmethod
451 def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
452 return capability >= DeviceCapability(8, 0)
453
454 @classmethod
455 def supports_combination(
456 cls,
457 head_size: int,
458 dtype: torch.dtype,
459 kv_cache_dtype: CacheDType | None,
460 block_size: int | None,
461 use_mla: bool,
462 has_sink: bool,
463 use_sparse: bool,
464 use_mm_prefix: bool,
465 device_capability: DeviceCapability,
466 ) -> str | None:
467 if has_sink and device_capability < DeviceCapability(9, 0):
468 return "sink not supported on compute capability < 9.0"
469 if (
470 use_mm_prefix
471 and kv_cache_dtype is not None
472 and is_quantized_kv_cache(kv_cache_dtype)
473 and device_capability == DeviceCapability(9, 0)
474 ):
475 return "SM90 FP8 KV with mm_prefix requires Triton"
476 if (
477 kv_cache_dtype is not None
478 and is_quantized_kv_cache(kv_cache_dtype)
479 and not flash_attn_supports_kv_cache_dtype(
480 kv_cache_dtype,
481 head_size=head_size,
482 head_size_v=head_size,
483 has_sinks=has_sink,
484 kv_cache_block_size=block_size,
485 supports_fa4_hd256=True,
486 )
487 ):
488 return (
489 "FP8 KV cache requires FA3 on SM90, FA4 with head_size=512 "
490 "on SM90, or FA4 on SM100"
491 )
492 if (
493 use_mm_prefix
494 and get_flash_attn_version(
495 head_size=head_size,
496 has_sinks=has_sink,
497 kv_cache_block_size=block_size,
498 supports_fa4_hd256=True,
499 )
500 != 4
501 ):
502 return (
503 "mm_prefix (PrefixLM bidirectional attention) requires "
504 "FlashAttention v4, which does not resolve for this "
505 "head_size"
506 )
507 return None
508
509
510 @dataclass
511 class FlashAttentionMetadata:
512 # NOTE(sang): Definition of context_len, query_len, and seq_len.
513 # |---------- N-1 iteration --------|
514 # |---------------- N iteration ---------------------|
515 # |- tokenA -|......................|-- newTokens ---|
516 # |---------- context_len ----------|
517 # |-------------------- seq_len ---------------------|
518 # |-- query_len ---|
519
520 num_actual_tokens: int # Number of tokens excluding padding.
521 max_query_len: int
522 query_start_loc: torch.Tensor
523 max_seq_len: int
524 seq_lens: torch.Tensor
525 block_table: torch.Tensor
526 slot_mapping: torch.Tensor
527
528 # For cascade attention.
529 use_cascade: bool
530 common_prefix_len: int
531 cu_prefix_query_lens: torch.Tensor | None
532 prefix_kv_lens: torch.Tensor | None
533 suffix_kv_lens: torch.Tensor | None
534
535 # For GQA DCP
536 max_dcp_context_kv_len: int | None = None
537 dcp_context_kv_lens: torch.Tensor | None = None
538
539 # Split counts for FA2 DCP context attention. num_prefill_* tracks
540 # context-bearing extend rows; pure prefills do not attend to DCP context.
541 num_decode_reqs: int = 0
542 num_prefill_reqs: int = 0
543 num_decode_tokens: int = 0
544 num_prefill_tokens: int = 0
545
546 # Optional aot scheduling
547 scheduler_metadata: torch.Tensor | None = None
548 prefix_scheduler_metadata: torch.Tensor | None = None
549 max_num_splits: int = 0
550
551 causal: bool | torch.Tensor = True
552
553 # PrefixLM bidirectional range containing each scheduled query token.
554 # Shape: (num_actual_tokens, 2) int32, absolute [start, end] bounds;
555 # (-1, -1) for query tokens outside every multimodal range.
556 mm_prefix_query_range_tensor: torch.Tensor | None = None
557
558 # Reference Sliding Window Attention (R-SWA) fields.
559 # rswa_prefix_lens: per-request prompt lengths [num_reqs], int32, CUDA.
560 # rswa_window: sliding window size (scalar int, for logic checks).
561 # rswa_window_tensor: [1] int32 CUDA tensor — pre-allocated in build() so
562 # no CPU→CUDA copy is needed inside forward() during CUDA graph capture.
563 # Only populated when the model uses R-SWA (Unlimited-OCR).
564 rswa_prefix_lens: torch.Tensor | None = None
565 rswa_window: int | None = None
566 rswa_window_tensor: torch.Tensor | None = None
567
568
569 def _get_sliding_window_configs(
570 vllm_config: VllmConfig,
571 ) -> set[tuple[int, int] | None]:
572 """Get the set of all sliding window configs used in the model.
573
574 Only inspects FlashAttentionImpl layers, including implementations wrapped
575 by a composite backend. Other backends (e.g. TurboQuant, MLA) use their own
576 metadata builders and are skipped.
577 """
578 sliding_window_configs: set[tuple[int, int] | None] = set()
579 layers = get_layers_from_vllm_config(vllm_config, Attention)
580 for layer in layers.values():
581 for impl in layer.impl.get_impl_variants():
582 if isinstance(impl, FlashAttentionImpl):
583 sliding_window_configs.add(impl.sliding_window)
584 return sliding_window_configs
585
586
587 def _maybe_symmetrize_window(
588 window: tuple[int, int] | None,
589 causal: bool | torch.Tensor,
590 ) -> tuple[int, int] | None:
591 """Make a causal sliding window ``(w, 0)`` symmetric ``(w, w)`` when attention
592 is non-causal, so bidirectional queries attend in both directions. Leaves
593 full-attention ``(-1, -1)`` and already-symmetric windows untouched.
594 """
595 non_causal = isinstance(causal, torch.Tensor) or causal is False
596 if window is not None and window[0] >= 0 and window[1] == 0 and non_causal:
597 return (window[0], window[0])
598 return window
599
600
601 class FlashAttentionMetadataBuilder(AttentionMetadataBuilder[FlashAttentionMetadata]):
602 # FA3:
603 # Supports full cudagraphs for all cases.
604 #
605 # FA2:
606 # For FA2, a graph is captured with max_query_len=1, (which is what we
607 # capture by default for num_tokens <= max_num_seqs when there is no
608 # spec-decode) then these graphs will not work for mixed prefill-decode
609 # (unlike FA3). This is due to special max_query_len=1 packed-GQA handling
610 # in FA2.
611 # In summary if we are running with spec decodes the graphs would
612 # work for mixed prefill-decode and uniform-decode. But for non-spec decodes
613 # the graphs would not work for mixed prefill-decode; sorta the inverse
614 # of UNIFORM_SINGLE_TOKEN_DECODE.
615 # There's probably a better way to describe this using `AttentionCGSupport`
616 # but for now just set it to `UNIFORM_BATCH` to get use to drop down
617 # to FULL_AND_PIECEWISE.
618 # TODO(luka, lucas): audit FA2 as part of:
619 # https://github.com/vllm-project/vllm/issues/22945
620 _cudagraph_support = (
621 AttentionCGSupport.ALWAYS
622 if get_flash_attn_version() == 3
623 else AttentionCGSupport.UNIFORM_BATCH
624 )
625 supports_update_block_table: bool = True
626
627 @classmethod
628 def get_cudagraph_support(
629 cls,
630 vllm_config: "VllmConfig",
631 kv_cache_spec: "KVCacheSpec",
632 ) -> AttentionCGSupport:
633 return cls._cudagraph_support
634
635 def _get_scheduler_metadata(
636 self,
637 *,
638 aot_schedule: bool,
639 batch_size: int,
640 cu_query_lens: torch.Tensor,
641 max_query_len: int,
642 seqlens: torch.Tensor,
643 max_seq_len: int,
644 causal: bool | torch.Tensor,
645 max_num_splits: int,
646 ) -> torch.Tensor | None:
647 if not aot_schedule:
648 return None
649
650 cache_dtype = self.cache_config.cache_dtype
651 if is_quantized_kv_cache(cache_dtype):
652 qkv_dtype = current_platform.fp8_dtype()
653 else:
654 qkv_dtype = self.kv_cache_dtype
655 return get_scheduler_metadata(
656 batch_size=batch_size,
657 max_seqlen_q=max_query_len,
658 max_seqlen_k=max_seq_len,
659 num_heads_q=self.num_heads_q * self.dcp_world_size,
660 num_heads_kv=self.num_heads_kv,
661 headdim=self.headdim,
662 cache_seqlens=seqlens,
663 qkv_dtype=qkv_dtype,
664 cu_seqlens_q=cu_query_lens,
665 page_size=self.block_size,
666 causal=causal,
667 window_size=_maybe_symmetrize_window(self.aot_sliding_window, causal),
668 num_splits=max_num_splits,
669 )
670
671 def _store_scheduler_metadata(
672 self, scheduler_metadata: torch.Tensor | None
673 ) -> torch.Tensor | None:
674 if self.use_full_cuda_graph and scheduler_metadata is not None:
675 n = scheduler_metadata.shape[0]
676 assert self.scheduler_metadata is not None
677 self.scheduler_metadata[:n] = scheduler_metadata
678 # NOTE(woosuk): We should zero out the rest of the scheduler
679 # metadata to guarantee the correctness. Otherwise, some thread
680 # blocks may use the invalid scheduler metadata and overwrite the
681 # output buffer.
682 self.scheduler_metadata[n:] = 0
683 return self.scheduler_metadata[:n]
684 return scheduler_metadata
685
686 def __init__(
687 self,
688 kv_cache_spec: AttentionSpec,
689 layer_names: list[str],
690 vllm_config: VllmConfig,
691 device: torch.device,
692 ):
693 super().__init__(kv_cache_spec, layer_names, vllm_config, device)
694 self.model_config = vllm_config.model_config
695 self.parallel_config = vllm_config.parallel_config
696 self.cache_config = vllm_config.cache_config
697 self.compilation_config = vllm_config.compilation_config
698 self.attention_config = vllm_config.attention_config
699
700 self.num_heads_q = get_num_attention_heads_from_layers(
701 vllm_config, layer_names
702 ) or self.model_config.get_num_attention_heads(self.parallel_config)
703 self.num_heads_kv = kv_cache_spec.num_kv_heads
704 self.kv_cache_dtype = kv_cache_spec.dtype
705 self.headdim = kv_cache_spec.head_size
706 self.block_size = kv_cache_spec.block_size
707
708 self.max_num_splits = 0 # No upper bound on the number of splits.
709 self.aot_schedule = get_flash_attn_version() == 3
710 head_size_v = getattr(kv_cache_spec, "head_size_v", None)
711 fa_version = get_flash_attn_version(
712 head_size=self.headdim,
713 head_size_v=head_size_v,
714 kv_cache_block_size=self.block_size,
715 supports_fa4_hd256=True,
716 )
717 self.fa4_hd256 = fa_version == 4 and uses_fa4_hd256_kernel(
718 self.headdim, head_size_v
719 )
720
721 if (
722 vllm_config.kernel_config.enable_jit_warmup
723 and self.model_config.hf_config.model_type in ("gemma4", "gemma4_unified")
724 and fa_version == 4
725 ):
726 _FA4_DENSE_ATTENTION_KERNEL.register_warmup(
727 vllm_config=vllm_config,
728 kv_cache_spec=kv_cache_spec,
729 num_qo_heads=self.num_heads_q,
730 )
731
732 self.dcp_world_size, self.dcp_rank = get_dcp_world_size_and_rank(
733 kv_cache_spec.dcp_sharded
734 )
735
736 # Fused draft decode reuses the captured metadata object across draft
737 # steps. For DCP, build-time host-side decisions such as
738 # skip_dcp_context_attention() can change the metadata shape/control
739 # path (for example max_dcp_context_kv_len), and those Python-side
740 # fields are not refreshed in-place between graph replays. Keep the
741 # fused path disabled until DCP gets a full replay-safe refresh model.
742 self.supports_draft_decode_metadata_update = self.dcp_world_size == 1
743
744 self.cp_kv_cache_interleave_size = (
745 self.parallel_config.cp_kv_cache_interleave_size
746 )
747
748 self.use_full_cuda_graph = (
749 self.compilation_config.cudagraph_mode.has_full_cudagraphs()
750 )
751 self.max_cudagraph_size = self.compilation_config.max_cudagraph_capture_size
752
753 if self.use_full_cuda_graph and self.aot_schedule:
754 # FA3 scheduler_metadata size: 1 + round_up(batch_size, 4) * 4
755 # The +1 is for the tile_count_semaphore (synchronization).
756 # The 4 slots per batch element (num_prepare_batch_vectors) are:
757 # prepare_varlen + dynamic_split + sort_batches + head_swizzle
758 # See: https://github.com/vllm-project/flash-attention/blob/5824e6e/hopper/flash_api.cpp#L664-L671 # noqa: E501
759 max_batch_size = max(
760 vllm_config.scheduler_config.max_num_seqs,
761 self.max_cudagraph_size or 0,
762 )
763 self.scheduler_metadata = torch.zeros(
764 1 + round_up(max_batch_size, 4) * 4,
765 dtype=torch.int32,
766 device=self.device,
767 )
768 # When using cuda graph, we need to set the upper bound of the
769 # number of splits so that large enough intermediate buffers are
770 # pre-allocated during capture.
771 self.max_num_splits = (
772 self.attention_config.flash_attn_max_num_splits_for_cuda_graph
773 )
774
775 if self.dcp_world_size > 1:
776 max_num_reqs = vllm_config.scheduler_config.max_num_seqs
777 self._dcp_context_kv_lens = torch.zeros(
778 max_num_reqs,
779 dtype=torch.int32,
780 device=self.device,
781 )
782
783 # Sliding window size to be used with the AOT scheduler will be
784 # populated on first build() call.
785 self.aot_sliding_window: tuple[int, int] | None = None
786
787 # R-SWA: persistent CUDA-graph-safe buffers owned by this builder.
788 self.rswa_window: int | None = self.model_config.rswa_window
789 self.persistent_rswa_prefix_lens: torch.Tensor | None = None
790 self.persistent_rswa_window_tensor: torch.Tensor | None = None
791 if self.rswa_window is not None:
792 max_num_reqs = vllm_config.scheduler_config.max_num_seqs
793 self.persistent_rswa_prefix_lens = torch.zeros(
794 max_num_reqs, dtype=torch.int32, device=self.device
795 )
796 self.persistent_rswa_window_tensor = torch.tensor(
797 [self.rswa_window], dtype=torch.int32, device=self.device
798 )
799
800 # mm_prefix: persistent staging + device buffers owned by this builder,
801 # sized by scheduled query tokens so build() never allocates.
802 self.mm_prefix_query_ranges_cpu: torch.Tensor | None = None
803 self.mm_prefix_query_ranges_np: np.ndarray | None = None
804 self.mm_prefix_query_ranges_gpu: torch.Tensor | None = None
805 if self.model_config.is_mm_prefix_lm:
806 max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens
807 self.mm_prefix_query_ranges_cpu = torch.empty(
808 (max_num_tokens, 2), dtype=torch.int32, pin_memory=PIN_MEMORY
809 )
810 self.mm_prefix_query_ranges_np = self.mm_prefix_query_ranges_cpu.numpy()
811 self.mm_prefix_query_ranges_gpu = torch.empty(
812 (max_num_tokens, 2), dtype=torch.int32, device=self.device
813 )
814
815 def build(
816 self,
817 common_prefix_len: int,
818 common_attn_metadata: CommonAttentionMetadata,
819 fast_build: bool = False,
820 ) -> FlashAttentionMetadata:
821 """fast_build disables AOT scheduling, used when there will be few
822 iterations i.e. spec-decode
823 """
824 num_reqs = common_attn_metadata.num_reqs
825 num_actual_tokens = common_attn_metadata.num_actual_tokens
826 max_query_len = common_attn_metadata.max_query_len
827 max_seq_len = common_attn_metadata.max_seq_len
828 query_start_loc = common_attn_metadata.query_start_loc
829 seq_lens = common_attn_metadata.seq_lens
830 block_table_tensor = common_attn_metadata.block_table_tensor
831 slot_mapping = common_attn_metadata.slot_mapping
832 causal = common_attn_metadata.causal
833
834 # Disable AOT schedule for spec-decode proposer (not worth the overhead)
835 # and for batch invariance (schedule varies with max_seqlen_q/k).
836 aot_schedule = (
837 self.aot_schedule and not fast_build and not envs.VLLM_BATCH_INVARIANT
838 )
839
840 if self.aot_sliding_window is None:
841 self.aot_sliding_window = (-1, -1)
842 # For the AOT scheduler we need the sliding window value to be
843 # constant for all layers to. We have to populate this on the first
844 # build() call so the layers are constructed (cannot populate)
845 # in __init__.
846 if aot_schedule:
847 sliding_window_configs = _get_sliding_window_configs(self.vllm_config)
848 if len(sliding_window_configs) == 1:
849 sliding_window_config = sliding_window_configs.pop()
850 if sliding_window_config is not None:
851 self.aot_sliding_window = sliding_window_config
852 elif len(sliding_window_configs) > 1:
853 self.aot_schedule = False
854 aot_schedule = False
855
856 max_num_splits = 0 # 0 means use FA3's heuristics, not CG compatible
857 if (
858 self.use_full_cuda_graph
859 and self.max_cudagraph_size is not None
860 and num_actual_tokens <= self.max_cudagraph_size
861 ):
862 # NOTE(woosuk): Setting num_splits > 1 may increase the memory
863 # usage, because the intermediate buffers of size [num_splits,
864 # num_heads, num_tokens, head_size] are allocated. Therefore,
865 # we only set num_splits when using cuda graphs.
866 max_num_splits = self.max_num_splits
867
868 if envs.VLLM_BATCH_INVARIANT:
869 max_num_splits = 1
870
871 use_cascade = common_prefix_len > 0
872 max_dcp_context_kv_len = 0
873 dcp_context_kv_lens = None
874 num_decode_reqs = 0
875 num_prefill_reqs = 0
876 num_decode_tokens = 0
877 num_prefill_tokens = 0
878
879 cu_prefix_query_lens = None
880 prefix_kv_lens = None
881 suffix_kv_lens = None
882 prefix_scheduler_metadata = None
883
884 if self.dcp_world_size > 1:
885 query_lens = query_start_loc[1:] - query_start_loc[:-1]
886 context_kv_lens = seq_lens - query_lens
887 local_context_kv_lens = get_dcp_local_seq_lens(
888 context_kv_lens,
889 self.dcp_world_size,
890 self.dcp_rank,
891 self.cp_kv_cache_interleave_size,
892 )
893 self._dcp_context_kv_lens[:num_reqs] = local_context_kv_lens
894 self._dcp_context_kv_lens[num_reqs:] = 0
895 dcp_context_kv_lens = self._dcp_context_kv_lens[:num_reqs]
896
897 skip_dcp_context_attention = False
898 if common_attn_metadata.seq_lens_cpu_upper_bound is not None:
899 query_lens_cpu = (
900 common_attn_metadata.query_start_loc_cpu[1 : num_reqs + 1]
901 - common_attn_metadata.query_start_loc_cpu[:num_reqs]
902 )
903 context_kv_lens_cpu = (
904 common_attn_metadata.seq_lens_cpu_upper_bound[:num_reqs]
905 - query_lens_cpu
906 )
907 skip_dcp_context_attention = should_skip_dcp_context_attention(
908 context_kv_lens_cpu
909 )
910
911 if max_query_len > 1:
912 (
913 num_decode_reqs,
914 num_prefill_reqs,
915 num_decode_tokens,
916 num_prefill_tokens,
917 ) = split_dcp_context_queries(
918 common_attn_metadata.query_start_loc_cpu,
919 common_attn_metadata.seq_lens_cpu_upper_bound,
920 max_query_len,
921 num_actual_tokens,
922 )
923
924 # After DCP distribution, the maximum number of tokens for any rank is
925 # ceil(L / (N * I)) * I, where L is max_seq_len, N is dcp_world_size,
926 # and I is cp_kv_cache_interleave_size.
927 # This eliminates GPU->CPU sync while minimizing workspace over-allocation.
928 if skip_dcp_context_attention:
929 max_dcp_context_kv_len = 0
930 scheduler_metadata = None
931 else:
932 num_partitions = self.dcp_world_size * self.cp_kv_cache_interleave_size
933 max_dcp_context_kv_len = (
934 (max_seq_len + num_partitions - 1) // num_partitions
935 ) * self.cp_kv_cache_interleave_size
936
937 scheduler_metadata = self._get_scheduler_metadata(
938 aot_schedule=aot_schedule,
939 batch_size=num_reqs,
940 cu_query_lens=query_start_loc,
941 max_query_len=max_query_len,
942 seqlens=dcp_context_kv_lens,
943 max_seq_len=max_dcp_context_kv_len,
944 causal=False,
945 max_num_splits=max_num_splits,
946 )
947 elif use_cascade:
948 cu_prefix_query_lens = torch.tensor(
949 [0, num_actual_tokens], dtype=torch.int32, device=self.device
950 )
951 prefix_kv_lens = torch.tensor(
952 [common_prefix_len], dtype=torch.int32, device=self.device
953 )
954 # Use GPU tensor directly - no CPU sync needed
955 suffix_kv_lens = seq_lens[:num_reqs] - common_prefix_len
956 prefix_scheduler_metadata = self._get_scheduler_metadata(
957 aot_schedule=aot_schedule,
958 batch_size=1,
959 cu_query_lens=cu_prefix_query_lens,
960 max_query_len=num_actual_tokens,
961 seqlens=prefix_kv_lens,
962 max_seq_len=common_prefix_len,
963 causal=False,
964 max_num_splits=max_num_splits,
965 )
966 scheduler_metadata = self._get_scheduler_metadata(
967 aot_schedule=aot_schedule,
968 batch_size=num_reqs,
969 cu_query_lens=query_start_loc,
970 max_query_len=max_query_len,
971 seqlens=suffix_kv_lens,
972 max_seq_len=max_seq_len - common_prefix_len,
973 causal=True,
974 max_num_splits=max_num_splits,
975 )
976 else:
977 scheduler_metadata = self._get_scheduler_metadata(
978 aot_schedule=aot_schedule,
979 batch_size=num_reqs,
980 cu_query_lens=query_start_loc,
981 max_query_len=max_query_len,
982 seqlens=seq_lens,
983 max_seq_len=max_seq_len,
984 causal=causal,
985 max_num_splits=max_num_splits,
986 )
987 scheduler_metadata = self._store_scheduler_metadata(scheduler_metadata)
988
989 if isinstance(causal, torch.Tensor) and causal.dtype != torch.int32:
990 raise ValueError(
991 f"Per-request causal tensor must be int32, got {causal.dtype}. "
992 "Casting here would allocate a fresh tensor each build() and "
993 "break FULL CUDA graph replay; allocate the buffer as int32."
994 )
995
996 attn_metadata = FlashAttentionMetadata(
997 num_actual_tokens=num_actual_tokens,
998 max_query_len=max_query_len,
999 query_start_loc=query_start_loc,
1000 max_seq_len=max_seq_len,
1001 seq_lens=seq_lens,
1002 block_table=block_table_tensor,
1003 slot_mapping=slot_mapping,
1004 max_dcp_context_kv_len=max_dcp_context_kv_len,
1005 dcp_context_kv_lens=dcp_context_kv_lens,
1006 num_decode_reqs=num_decode_reqs,
1007 num_prefill_reqs=num_prefill_reqs,
1008 num_decode_tokens=num_decode_tokens,
1009 num_prefill_tokens=num_prefill_tokens,
1010 use_cascade=use_cascade,
1011 common_prefix_len=common_prefix_len,
1012 scheduler_metadata=scheduler_metadata,
1013 cu_prefix_query_lens=cu_prefix_query_lens,
1014 prefix_kv_lens=prefix_kv_lens,
1015 suffix_kv_lens=suffix_kv_lens,
1016 prefix_scheduler_metadata=prefix_scheduler_metadata,
1017 max_num_splits=max_num_splits,
1018 causal=causal,
1019 )
1020
1021 # Compute mm_prefix range tensor if the batch contains
1022 # multimodal tokens with bidirectional ranges. Built for every FA
1023 # group; Gemma4 nulls the field for its non-sliding layers.
1024 mm_ranges = common_attn_metadata.mm_req_doc_ranges
1025 if mm_ranges is not None and self.mm_prefix_query_ranges_np is not None:
1026 # The upper bound is exact for prefill rows, which is where
1027 # mm_prefix ranges live; decode rows only ever get an optimistic
1028 # (larger) context, moving them further past every range.
1029 assert common_attn_metadata.seq_lens_cpu_upper_bound is not None, (
1030 "mm_prefix requires seq_lens_cpu_upper_bound"
1031 )
1032 num_mm_tokens = fill_mm_prefix_query_ranges(
1033 self.mm_prefix_query_ranges_np,
1034 mm_ranges,
1035 common_attn_metadata.query_start_loc_cpu,
1036 common_attn_metadata.seq_lens_cpu_upper_bound,
1037 )
1038 if num_mm_tokens > 0:
1039 assert self.mm_prefix_query_ranges_cpu is not None
1040 assert self.mm_prefix_query_ranges_gpu is not None
1041 mm_query_ranges = self.mm_prefix_query_ranges_gpu[:num_mm_tokens]
1042 mm_query_ranges.copy_(
1043 self.mm_prefix_query_ranges_cpu[:num_mm_tokens],
1044 non_blocking=True,
1045 )
1046 attn_metadata.mm_prefix_query_range_tensor = mm_query_ranges
1047
1048 # R-SWA: copy prefix lengths into persistent buffers (outside the
1049 # compiled region) so forward() never allocates during CUDA graph
1050 # capture. rswa_window is a static model config scalar read here.
1051 if (
1052 self.rswa_window is not None
1053 and common_attn_metadata.rswa_prefix_lens is not None
1054 ):
1055 assert self.persistent_rswa_prefix_lens is not None
1056 assert self.persistent_rswa_window_tensor is not None
1057 src = common_attn_metadata.rswa_prefix_lens
1058 rswa_prefix_lens = self.persistent_rswa_prefix_lens[:num_reqs]
1059 rswa_prefix_lens.copy_(src[:num_reqs], non_blocking=True)
1060 attn_metadata.rswa_prefix_lens = rswa_prefix_lens
1061 attn_metadata.rswa_window = self.rswa_window
1062 attn_metadata.rswa_window_tensor = self.persistent_rswa_window_tensor
1063
1064 return attn_metadata
1065
1066 def update_block_table(
1067 self,
1068 metadata: FlashAttentionMetadata,
1069 blk_table: torch.Tensor,
1070 slot_mapping: torch.Tensor,
1071 ) -> FlashAttentionMetadata:
1072 new_metadata = copy.copy(metadata)
1073 new_metadata.block_table = blk_table
1074 new_metadata.slot_mapping = slot_mapping
1075 return new_metadata
1076
1077 def update_draft_decode_metadata(self, metadata: FlashAttentionMetadata) -> None:
1078 if metadata.scheduler_metadata is None:
1079 return
1080
1081 num_reqs = metadata.num_decode_reqs or metadata.seq_lens.shape[0]
1082
1083 assert self.dcp_world_size == 1
1084 assert not metadata.use_cascade
1085
1086 scheduler_metadata = self._get_scheduler_metadata(
1087 aot_schedule=True,
1088 batch_size=num_reqs,
1089 cu_query_lens=metadata.query_start_loc,
1090 max_query_len=metadata.max_query_len,
1091 seqlens=metadata.seq_lens,
1092 max_seq_len=metadata.max_seq_len,
1093 causal=metadata.causal,
1094 max_num_splits=metadata.max_num_splits,
1095 )
1096
1097 metadata.scheduler_metadata = self._store_scheduler_metadata(scheduler_metadata)
1098
1099 def use_cascade_attention(self, *args, **kwargs) -> bool:
1100 if self.fa4_hd256:
1101 # Cascade may use a non-page-aligned prefix length.
1102 return False
1103 return use_cascade_attention(*args, **kwargs)
1104
1105
1106 class FlashAttentionImpl(AttentionImpl):
1107 can_return_lse_for_decode: bool = True
1108 supports_dcp: bool = True
1109
1110 def __init__(
1111 self,
1112 num_heads: int,
1113 head_size: int,
1114 scale: float,
1115 num_kv_heads: int,
1116 alibi_slopes: list[float] | None,
1117 sliding_window: int | None,
1118 kv_cache_dtype: str,
1119 logits_soft_cap: float | None = None,
1120 attn_type: AttentionType = AttentionType.DECODER,
1121 kv_sharing_target_layer_name: str | None = None,
1122 sinks: torch.Tensor | None = None,
1123 ) -> None:
1124 self.num_heads = num_heads
1125 self.head_size = head_size
1126 self.scale = float(scale)
1127 self.num_kv_heads = num_kv_heads
1128 if alibi_slopes is not None:
1129 alibi_slopes = torch.tensor(alibi_slopes, dtype=torch.float32)
1130 self.alibi_slopes = alibi_slopes
1131 if sliding_window is None:
1132 self.sliding_window = (-1, -1)
1133 elif attn_type == AttentionType.ENCODER_ONLY:
1134 self.sliding_window = (sliding_window - 1, sliding_window - 1)
1135 else:
1136 self.sliding_window = (sliding_window - 1, 0)
1137 self.kv_cache_dtype = kv_cache_dtype
1138 if logits_soft_cap is None:
1139 # In flash-attn, setting logits_soft_cap as 0 means no soft cap.
1140 logits_soft_cap = 0
1141 self.logits_soft_cap = logits_soft_cap
1142 self.kv_sharing_target_layer_name = kv_sharing_target_layer_name
1143
1144 self.num_queries_per_kv = self.num_heads // self.num_kv_heads
1145
1146 self.attn_type = attn_type
1147 vllm_config = get_current_vllm_config_or_none()
1148 uses_kv_cache = attn_type not in (
1149 AttentionType.ENCODER,
1150 AttentionType.ENCODER_ONLY,
1151 )
1152 # The final KV cache block size is unavailable during construction.
1153 self.vllm_flash_attn_version = get_flash_attn_version(
1154 requires_alibi=alibi_slopes is not None,
1155 head_size=head_size,
1156 has_sinks=sinks is not None,
1157 requires_softcap=bool(self.logits_soft_cap),
1158 supports_fa4_hd256=True,
1159 )
1160 self.fa4_hd256 = self.vllm_flash_attn_version == 4 and uses_fa4_hd256_kernel(
1161 head_size
1162 )
1163 if self.fa4_hd256 and not uses_kv_cache and sliding_window is not None:
1164 # The hd256 kernel requires seqused_k for local attention.
1165 logger.warning_once(
1166 "FA4's Blackwell head_size=256 kernel does not support local "
1167 "attention on encoder inputs, defaulting to FA version 2."
1168 )
1169 self.vllm_flash_attn_version = 2
1170 self.fa4_hd256 = False
1171 logger.info_once(
1172 "Using FlashAttention version %s",
1173 self.vllm_flash_attn_version,
1174 )
1175 # Cache the batch invariant result for use in forward passes
1176 self.batch_invariant_enabled = envs.VLLM_BATCH_INVARIANT
1177
1178 if is_quantized_kv_cache(
1179 self.kv_cache_dtype
1180 ) and not flash_attn_supports_kv_cache_dtype(
1181 self.kv_cache_dtype,
1182 requires_alibi=alibi_slopes is not None,
1183 head_size=head_size,
1184 head_size_v=head_size,
1185 has_sinks=sinks is not None,
1186 requires_softcap=bool(self.logits_soft_cap),
1187 supports_fa4_hd256=True,
1188 ):
1189 raise NotImplementedError(
1190 f"FlashAttention does not support {self.kv_cache_dtype}"
1191 " kv-cache on this device."
1192 )
1193
1194 self.sinks = sinks
1195 if self.sinks is not None:
1196 assert flash_attn_supports_sinks(), (
1197 "Sinks are only supported in FlashAttention 3"
1198 )
1199 assert self.sinks.shape[0] == num_heads, (
1200 "Sinks must have the same number of heads as the number of "
1201 "heads in the layer"
1202 )
1203
1204 # FA4's SM90 FP8-KV path consumes native FP16/BF16 Q and dequantizes
1205 # FP8 K/V in-kernel. Other FA4 paths (notably SM100) still require Q,
1206 # K, and V to have the same FP8 dtype.
1207 uses_sm90_fa4_fp8_kv_dequant = (
1208 self.vllm_flash_attn_version == 4
1209 and current_platform.is_device_capability_family(90)
1210 and self.kv_cache_dtype in ("fp8", "fp8_e4m3")
1211 )
1212 self.supports_quant_query_input = flash_attn_supports_quant_query_input() and (
1213 not uses_sm90_fa4_fp8_kv_dequant
1214 )
1215
1216 dcp_a2a = (
1217 vllm_config is not None
1218 and vllm_config.parallel_config.decode_context_parallel_size > 1
1219 and vllm_config.parallel_config.dcp_comm_backend == "a2a"
1220 )
1221 self.dcp_combine = dcp_a2a_lse_reduce if dcp_a2a else cp_lse_ag_out_rs
1222
1223 self._dcp_dtype: torch.dtype | None = None
1224 self._dcp_max_num_tokens: int = 0
1225 if vllm_config is not None and self.dcp_world_size > 1:
1226 self._dcp_dtype = vllm_config.model_config.dtype
1227 self._dcp_max_num_tokens = (
1228 vllm_config.scheduler_config.max_num_batched_tokens
1229 )
1230
1231 def forward(
1232 self,
1233 layer: torch.nn.Module,
1234 query: torch.Tensor,
1235 key: torch.Tensor,
1236 value: torch.Tensor,
1237 kv_cache: torch.Tensor,
1238 attn_metadata: FlashAttentionMetadata,
1239 output: torch.Tensor,
1240 output_scale: torch.Tensor | None = None,
1241 output_block_scale: torch.Tensor | None = None,
1242 ) -> torch.Tensor:
1243 """Forward pass with FlashAttention.
1244
1245 Args:
1246 layer: The attention layer, providing the q/k/v quantization scales.
1247 query: shape = [num_tokens, num_heads, head_size]
1248 key: shape = [num_tokens, num_kv_heads, head_size]
1249 value: shape = [num_tokens, num_kv_heads, head_size]
1250 kv_cache: shape =
1251 [num_blocks, num_kv_heads, block_size, 2 * head_size]
1252 attn_metadata: Metadata for attention.
1253 output: Tensor that the attention result is written into.
1254 output_scale: Scale for fused output quantization; not supported
1255 by this backend.
1256 output_block_scale: Block scale for fused output quantization;
1257 not supported by this backend.
1258
1259 Returns:
1260 shape = [num_tokens, num_heads * head_size]
1261 NOTE: FP8 quantization, flash-attn expect the size of
1262 {q,k,v}_descale to be (num_sequences, num_kv_heads).
1263 We use torch's .expand() to avoid duplicating values
1264
1265 """
1266 assert self.vllm_flash_attn_version is not None, (
1267 "FlashAttention version not detected."
1268 )
1269
1270 if output_scale is not None or output_block_scale is not None:
1271 raise NotImplementedError(
1272 "fused output quantization is not yet supported for FlashAttentionImpl"
1273 )
1274
1275 if attn_metadata is None:
1276 # Profiling run.
1277 return output.fill_(0)
1278
1279 attn_type = self.attn_type
1280
1281 # IMPORTANT!
1282 # NOTE(woosuk): With piece-wise CUDA graphs, this method is executed in
1283 # eager-mode PyTorch. Thus, we need to be careful about any CPU overhead
1284 # in this method. For example, `view` and `slice` (or `[:n]`) operations
1285 # are surprisingly slow even in the case they do not invoke any GPU ops.
1286 # Minimize the PyTorch ops in this method as much as possible.
1287 # Whenever making a change in this method, please benchmark the
1288 # performance to make sure it does not introduce any overhead.
1289
1290 num_actual_tokens = attn_metadata.num_actual_tokens
1291
1292 # Handle encoder attention differently - no KV cache needed
1293 if attn_type in (AttentionType.ENCODER_ONLY, AttentionType.ENCODER):
1294 # For encoder attention,
1295 # we use direct Q, K, V tensors without caching
1296 return self._forward_encoder_attention(
1297 query[:num_actual_tokens],
1298 key[:num_actual_tokens],
1299 value[:num_actual_tokens],
1300 output[:num_actual_tokens],
1301 attn_metadata,
1302 layer,
1303 )
1304
1305 # (B, H, N, 2*D) -> ((B, N, H, D), (B, N, H, D))
1306 key_cache, value_cache = kv_cache.transpose(1, 2).split(self.head_size, dim=-1)
1307 # Fix degenerate strides on size-1 dims (e.g. num_kv_heads=1 with TP).
1308 # FA3/4 on H100+ uses TMA, which requires ≥16-byte stride alignment.
1309 # See vllm.utils.torch_utils.canonicalize_singleton_dim_strides.
1310 fixed_k = canonicalize_singleton_dim_strides(key_cache)
1311 fixed_v = canonicalize_singleton_dim_strides(value_cache)
1312 if fixed_k is not key_cache or fixed_v is not value_cache:
1313 logger.debug(
1314 "Canonicalized degenerate KV cache strides (FlashAttention): "
1315 "shape=%s, key strides before=%s after=%s, "
1316 "value strides before=%s after=%s",
1317 key_cache.shape,
1318 key_cache.stride(),
1319 fixed_k.stride(),
1320 value_cache.stride(),
1321 fixed_v.stride(),
1322 )
1323 key_cache, value_cache = fixed_k, fixed_v
1324
1325 if is_quantized_kv_cache(self.kv_cache_dtype):
1326 # queries are quantized in the attention layer
1327 key_cache = key_cache.view(current_platform.fp8_dtype())
1328 value_cache = value_cache.view(current_platform.fp8_dtype())
1329
1330 if not attn_metadata.use_cascade:
1331 cu_seqlens_q = attn_metadata.query_start_loc
1332 seqused_k = attn_metadata.seq_lens
1333 max_seqlen_q = attn_metadata.max_query_len
1334 max_seqlen_k = attn_metadata.max_seq_len
1335 block_table = attn_metadata.block_table
1336 scheduler_metadata = attn_metadata.scheduler_metadata
1337
1338 descale_shape = (cu_seqlens_q.shape[0] - 1, self.num_kv_heads)
1339
1340 q_descale = (
1341 layer._q_scale.expand(descale_shape)
1342 if self.supports_quant_query_input
1343 else None
1344 )
1345 k_descale = layer._k_scale.expand(descale_shape)
1346 v_descale = layer._v_scale.expand(descale_shape)
1347
1348 if self.dcp_world_size > 1:
1349 self._forward_with_dcp(
1350 query[:num_actual_tokens],
1351 key[:num_actual_tokens],
1352 value[:num_actual_tokens],
1353 key_cache,
1354 value_cache,
1355 output[:num_actual_tokens],
1356 attn_metadata,
1357 q_descale=q_descale,
1358 k_descale=k_descale,
1359 v_descale=v_descale,
1360 )
1361 return output
1362 else:
1363 causal = attn_metadata.causal
1364 is_dynamic_causal = isinstance(causal, torch.Tensor)
1365
1366 # The layer's own window wins over the group's: one KV cache
1367 # group can hold both windowed and global layers (e.g. Gemma-3
1368 # with the hybrid KV cache manager disabled), and the group spec
1369 # cannot describe both.
1370 window = _maybe_symmetrize_window(self.sliding_window, causal)
1371 sliding_window_size: list[int] | None = (
1372 list(window) if window is not None else None
1373 )
1374
1375 mm_prefix_query_ranges = attn_metadata.mm_prefix_query_range_tensor
1376 mm_mask_mod = None
1377 mm_aux = None
1378 if (
1379 mm_prefix_query_ranges is not None
1380 and not is_dynamic_causal
1381 and causal is True
1382 and self.vllm_flash_attn_version == 4
1383 ):
1384 # Triton convention: 1 + window_size[0]. Global layers store
1385 # (-1, -1) → sw stays None.
1386 layer_window = self.sliding_window
1387 sw_val = (
1388 1 + layer_window[0]
1389 if layer_window is not None and layer_window[0] >= 0
1390 else None
1391 )
1392 # Gemma4: also clamp the bidirectional block to the
1393 # sliding window when the layer opts in
1394 # (mm_prefix_clamp_sliding_window flag from PR #47217).
1395 mm_clamp_sw = 0
1396 if (
1397 getattr(layer, "mm_prefix_clamp_sliding_window", False)
1398 and sw_val is not None
1399 ):
1400 mm_clamp_sw = sw_val
1401 mm_mask_mod = _make_mm_prefix_mask_mod(
1402 sliding_window=mm_clamp_sw,
1403 sliding_window_left=sw_val,
1404 )
1405 mm_aux = [mm_prefix_query_ranges, attn_metadata.query_start_loc]
1406 # mm_prefix is (causal ∧ window) ∨ bidirectional-range —
1407 # not ⊆ causal. FA #155 stopped auto-clearing causal/local
1408 # when mask_mod is set, so the caller must disable them or
1409 # the built-in causal path shorts out / clips the mask_mod.
1410 causal = False
1411 sliding_window_size = None
1412
1413 # R-SWA: use CuTE-DSL mask_mod on FA4 for exact token-level
1414 # mask without block-size approximation. The mask_mod encodes
1415 # "causal AND (kv < prefix_len OR q - kv < rswa_window)", which
1416 # supersedes any FA-layer sliding_window_size parameter.
1417 rswa_mask_mod_fn = None
1418 rswa_aux = None
1419 if (
1420 attn_metadata.rswa_prefix_lens is not None
1421 and self.vllm_flash_attn_version == 4
1422 and not is_dynamic_causal
1423 ):
1424 rswa_mask_mod_fn = _make_rswa_mask_mod()
1425 rswa_aux = [
1426 attn_metadata.rswa_prefix_lens.to(torch.int32),
1427 attn_metadata.rswa_window_tensor, # pre-allocated CUDA tensor
1428 ]
1429 # mask_mod fully expresses R-SWA; disable FA's own window.
1430 sliding_window_size = None
1431
1432 dynamic_causal = None
1433 if isinstance(causal, torch.Tensor):
1434 if self.vllm_flash_attn_version != 4:
1435 raise NotImplementedError(
1436 "Per-sequence causal requires FA4. Current version: "
1437 f"FA{self.vllm_flash_attn_version}"
1438 )
1439 dynamic_causal = causal
1440 has_window = (
1441 sliding_window_size is not None and sliding_window_size[1] >= 0
1442 )
1443 causal = not has_window
1444
1445 num_splits = attn_metadata.max_num_splits
1446 if self.fa4_hd256:
1447 # hd256 requires page-aligned lengths, exact-width block
1448 # tables, and no SplitKV.
1449 num_pages = cdiv(max_seqlen_k, FA4_HD256_PAGE_SIZE)
1450 max_seqlen_k = num_pages * FA4_HD256_PAGE_SIZE
1451 block_table = block_table[:, :num_pages]
1452 num_splits = 1
1453
1454 _FA4_DENSE_ATTENTION_KERNEL(
1455 q=query[:num_actual_tokens],
1456 k=key_cache,
1457 v=value_cache,
1458 out=output[:num_actual_tokens],
1459 cu_seqlens_q=cu_seqlens_q,
1460 max_seqlen_q=max_seqlen_q,
1461 seqused_k=seqused_k,
1462 max_seqlen_k=max_seqlen_k,
1463 softmax_scale=self.scale,
1464 causal=causal,
1465 alibi_slopes=self.alibi_slopes,
1466 window_size=sliding_window_size,
1467 block_table=block_table,
1468 softcap=self.logits_soft_cap,
1469 scheduler_metadata=scheduler_metadata,
1470 fa_version=self.vllm_flash_attn_version,
1471 q_descale=q_descale,
1472 k_descale=k_descale,
1473 v_descale=v_descale,
1474 dynamic_causal=dynamic_causal,
1475 num_splits=num_splits,
1476 s_aux=self.sinks,
1477 mask_mod=rswa_mask_mod_fn or mm_mask_mod,
1478 aux_tensors=rswa_aux or mm_aux,
1479 )
1480 return output
1481
1482 # Cascade attention (rare case).
1483 cascade_attention(
1484 output[:num_actual_tokens],
1485 query[:num_actual_tokens],
1486 key_cache,
1487 value_cache,
1488 cu_query_lens=attn_metadata.query_start_loc,
1489 max_query_len=attn_metadata.max_query_len,
1490 cu_prefix_query_lens=attn_metadata.cu_prefix_query_lens,
1491 prefix_kv_lens=attn_metadata.prefix_kv_lens,
1492 suffix_kv_lens=attn_metadata.suffix_kv_lens,
1493 max_kv_len=attn_metadata.max_seq_len,
1494 softmax_scale=self.scale,
1495 alibi_slopes=self.alibi_slopes,
1496 sliding_window=self.sliding_window,
1497 logits_soft_cap=self.logits_soft_cap,
1498 block_table=attn_metadata.block_table,
1499 common_prefix_len=attn_metadata.common_prefix_len,
1500 max_num_splits=attn_metadata.max_num_splits,
1501 fa_version=self.vllm_flash_attn_version,
1502 prefix_scheduler_metadata=attn_metadata.prefix_scheduler_metadata,
1503 suffix_scheduler_metadata=attn_metadata.scheduler_metadata,
1504 q_descale=layer._q_scale,
1505 k_descale=layer._k_scale,
1506 v_descale=layer._v_scale,
1507 s_aux=self.sinks,
1508 )
1509 return output
1510
1511 def do_kv_cache_update(
1512 self,
1513 layer: torch.nn.Module,
1514 key: torch.Tensor,
1515 value: torch.Tensor,
1516 kv_cache: torch.Tensor,
1517 slot_mapping: torch.Tensor,
1518 ) -> None:
1519 if self.attn_type in (AttentionType.ENCODER_ONLY, AttentionType.ENCODER):
1520 # For encoder attention,
1521 # we use direct Q, K, V tensors without caching
1522 return
1523
1524 # Scatter write into the KV cache using slot_mapping indices.
1525 # No TMA kernel is invoked here, so stride canonicalization is not needed.
1526 # (B, H, N, 2*D) -> ((B, N, H, D), (B, N, H, D))
1527 key_cache, value_cache = kv_cache.transpose(1, 2).split(self.head_size, dim=-1)
1528
1529 # Reshape the input keys and values and store them in the cache.
1530 # Skip this if sharing KV cache with an earlier attention layer.
1531 # NOTE(woosuk): Here, key and value are padded while slot_mapping is
1532 # not padded. However, we don't need to do key[:num_actual_tokens]
1533 # and value[:num_actual_tokens] because the reshape_and_cache_flash
1534 # op uses the slot_mapping's shape to determine the number of
1535 # actual tokens.
1536 reshape_and_cache_flash(
1537 key,
1538 value,
1539 key_cache,
1540 value_cache,
1541 slot_mapping,
1542 self.kv_cache_dtype,
1543 layer._k_scale,
1544 layer._v_scale,
1545 )
1546
1547 def _forward_with_dcp(
1548 self,
1549 query: torch.Tensor,
1550 key: torch.Tensor,
1551 value: torch.Tensor,
1552 key_cache: torch.Tensor,
1553 value_cache: torch.Tensor,
1554 output: torch.Tensor,
1555 attn_metadata: FlashAttentionMetadata,
1556 q_descale: torch.Tensor | None = None,
1557 k_descale: torch.Tensor | None = None,
1558 v_descale: torch.Tensor | None = None,
1559 ) -> torch.Tensor:
1560 assert self.vllm_flash_attn_version is not None, (
1561 "FlashAttention version not detected."
1562 )
1563
1564 cu_seqlens_q = attn_metadata.query_start_loc
1565 max_seqlen_q = attn_metadata.max_query_len
1566 block_table = attn_metadata.block_table
1567
1568 query = query.contiguous()
1569 if attn_metadata.max_dcp_context_kv_len == 0:
1570 flash_attn_varlen_func(
1571 q=query,
1572 k=key,
1573 v=value,
1574 out=output,
1575 cu_seqlens_q=cu_seqlens_q,
1576 max_seqlen_q=max_seqlen_q,
1577 cu_seqlens_k=cu_seqlens_q,
1578 max_seqlen_k=max_seqlen_q,
1579 softmax_scale=self.scale,
1580 causal=attn_metadata.causal,
1581 alibi_slopes=self.alibi_slopes,
1582 window_size=list(self.sliding_window)
1583 if self.sliding_window is not None
1584 else None,
1585 softcap=self.logits_soft_cap,
1586 return_softmax_lse=True,
1587 fa_version=self.vllm_flash_attn_version,
1588 q_descale=q_descale,
1589 k_descale=k_descale,
1590 v_descale=v_descale,
1591 num_splits=attn_metadata.max_num_splits,
1592 )
1593 return output
1594
1595 query_across_dcp = get_dcp_group().all_gather(query, dim=1)
1596 sliding_window_size = (
1597 list(self.sliding_window) if self.sliding_window is not None else None
1598 )
1599 n = query_across_dcp.shape[0]
1600 num_reqs = cu_seqlens_q.shape[0] - 1
1601 num_decodes = attn_metadata.num_decode_reqs
1602 num_context_prefills = attn_metadata.num_prefill_reqs
1603 num_decode_tokens = attn_metadata.num_decode_tokens
1604 num_context_prefill_tokens = attn_metadata.num_prefill_tokens
1605 split_dcp_context = should_split_fa2_dcp_context_attention(
1606 self.vllm_flash_attn_version,
1607 max_seqlen_q,
1608 num_reqs,
1609 num_decodes,
1610 num_context_prefills,
1611 )
1612 dcp_context_out_tokens = max(n, self._dcp_max_num_tokens)
1613 dcp_context_out_spec = (
1614 (
1615 dcp_context_out_tokens,
1616 self.num_heads * self.dcp_world_size,
1617 self.head_size,
1618 ),
1619 self._dcp_dtype,
1620 )
1621 (dcp_context_out_workspace,) = current_workspace_manager().get_simultaneous(
1622 dcp_context_out_spec,
1623 )
1624 dcp_context_out = dcp_context_out_workspace[:n]
1625
1626 if split_dcp_context:
1627 # TODO: Remove this DCP + FA2 mixed decode/prefill workaround once
1628 # FA4 supports this Qwen3.5 shape.
1629 assert attn_metadata.dcp_context_kv_lens is not None
1630 assert attn_metadata.max_dcp_context_kv_len is not None
1631 assert self.vllm_flash_attn_version is not None
1632 context_attn_out, context_lse = run_split_fa2_dcp_context_attention(
1633 flash_attn_varlen_func,
1634 query_across_dcp,
1635 key_cache,
1636 value_cache,
1637 dcp_context_out,
1638 cu_seqlens_q,
1639 max_seqlen_q,
1640 attn_metadata.dcp_context_kv_lens,
1641 attn_metadata.max_dcp_context_kv_len,
1642 self.scale,
1643 self.alibi_slopes,
1644 sliding_window_size,
1645 block_table,
1646 self.logits_soft_cap,
1647 self.vllm_flash_attn_version,
1648 q_descale,
1649 k_descale,
1650 v_descale,
1651 attn_metadata.max_num_splits,
1652 self.num_heads,
1653 self.dcp_world_size,
1654 num_decodes,
1655 num_context_prefills,
1656 num_decode_tokens,
1657 num_context_prefill_tokens,
1658 )
1659 else:
1660 context_attn_out, context_lse = flash_attn_varlen_func(
1661 q=query_across_dcp,
1662 k=key_cache,
1663 v=value_cache,
1664 out=dcp_context_out,
1665 cu_seqlens_q=cu_seqlens_q,
1666 max_seqlen_q=max_seqlen_q,
1667 seqused_k=attn_metadata.dcp_context_kv_lens,
1668 max_seqlen_k=attn_metadata.max_dcp_context_kv_len,
1669 softmax_scale=self.scale,
1670 causal=False,
1671 alibi_slopes=self.alibi_slopes,
1672 window_size=sliding_window_size,
1673 block_table=block_table,
1674 softcap=self.logits_soft_cap,
1675 return_softmax_lse=True,
1676 scheduler_metadata=attn_metadata.scheduler_metadata,
1677 fa_version=self.vllm_flash_attn_version,
1678 q_descale=q_descale,
1679 k_descale=k_descale,
1680 v_descale=v_descale,
1681 num_splits=attn_metadata.max_num_splits,
1682 )
1683 # FA returns LSE in shape [ H, B ] but DCP combine wants [ B, H ]
1684 context_attn_out_cor, context_lse_cor = self.dcp_combine(
1685 context_attn_out,
1686 context_lse.transpose(0, 1),
1687 get_dcp_group(),
1688 return_lse=True,
1689 )
1690 context_lse_cor = context_lse_cor.transpose(0, 1).contiguous()
1691
1692 query_attn_out, query_lse = flash_attn_varlen_func(
1693 q=query,
1694 k=key,
1695 v=value,
1696 out=output,
1697 cu_seqlens_q=cu_seqlens_q,
1698 max_seqlen_q=max_seqlen_q,
1699 cu_seqlens_k=cu_seqlens_q,
1700 max_seqlen_k=max_seqlen_q,
1701 softmax_scale=self.scale,
1702 causal=attn_metadata.causal,
1703 alibi_slopes=self.alibi_slopes,
1704 window_size=sliding_window_size,
1705 softcap=self.logits_soft_cap,
1706 return_softmax_lse=True,
1707 fa_version=self.vllm_flash_attn_version,
1708 q_descale=q_descale,
1709 k_descale=k_descale,
1710 v_descale=v_descale,
1711 num_splits=attn_metadata.max_num_splits,
1712 )
1713 assert context_attn_out_cor.shape == query_attn_out.shape
1714 assert context_lse_cor.shape == query_lse.shape
1715 merge_attn_states(
1716 output,
1717 context_attn_out_cor,
1718 context_lse_cor,
1719 query_attn_out,
1720 query_lse,
1721 )
1722
1723 def _forward_encoder_attention(
1724 self,
1725 query: torch.Tensor,
1726 key: torch.Tensor,
1727 value: torch.Tensor,
1728 output: torch.Tensor,
1729 attn_metadata: FlashAttentionMetadata,
1730 layer: torch.nn.Module,
1731 ) -> torch.Tensor:
1732 """Forward pass for encoder attention without KV cache.
1733
1734 Args:
1735 query: shape = [num_encoder_tokens, num_heads, head_size]
1736 key: shape = [num_encoder_tokens, num_kv_heads, head_size]
1737 value: shape = [num_encoder_tokens, num_kv_heads, head_size]
1738 output: shape = [num_encoder_tokens, num_heads, head_size]
1739 attn_metadata: Encoder attention metadata
1740 layer: The attention layer
1741
1742 """
1743 assert self.vllm_flash_attn_version is not None, (
1744 "FlashAttention version not detected."
1745 )
1746
1747 # For encoder attention, process FP8 quantization if needed
1748 if is_quantized_kv_cache(self.kv_cache_dtype):
1749 raise NotImplementedError(
1750 "quantization is not supported for encoder attention"
1751 )
1752
1753 # Use encoder-specific metadata for sequence information
1754 cu_seqlens_q = attn_metadata.query_start_loc
1755 cu_seqlens_k = attn_metadata.query_start_loc
1756 max_seqlen_q = attn_metadata.max_query_len
1757 max_seqlen_k = attn_metadata.max_query_len
1758
1759 descale_shape = (
1760 cu_seqlens_q.shape[0] - 1, # type: ignore[union-attr]
1761 self.num_kv_heads,
1762 )
1763
1764 # Call flash attention directly on Q, K, V tensors
1765 sliding_window_size = (
1766 list(self.sliding_window) if self.sliding_window is not None else None
1767 )
1768 flash_attn_varlen_func(
1769 q=query,
1770 k=key,
1771 v=value,
1772 out=output,
1773 cu_seqlens_q=cu_seqlens_q,
1774 cu_seqlens_k=cu_seqlens_k,
1775 max_seqlen_q=max_seqlen_q,
1776 max_seqlen_k=max_seqlen_k,
1777 softmax_scale=self.scale,
1778 causal=False, # Encoder attention is bidirectional
1779 alibi_slopes=self.alibi_slopes,
1780 window_size=sliding_window_size,
1781 softcap=self.logits_soft_cap,
1782 fa_version=self.vllm_flash_attn_version,
1783 q_descale=layer._q_scale.expand(descale_shape) # type: ignore[operator]
1784 if self.supports_quant_query_input
1785 else None,
1786 k_descale=layer._k_scale.expand(descale_shape), # type: ignore[operator]
1787 v_descale=layer._v_scale.expand(descale_shape), # type: ignore[operator]
1788 # The hd256 kernel does not support SplitKV.
1789 num_splits=1 if self.batch_invariant_enabled or self.fa4_hd256 else 0,
1790 s_aux=self.sinks,
1791 )
1792
1793 return output
1794
1795
1796 @functools.cache
1797 def _make_mm_prefix_mask_mod(
1798 sliding_window: int = 0,
1799 sliding_window_left: int | None = None,
1800 ):
1801 """Build a CuTE-DSL mask_mod implementing
1802 ``(causal AND sliding_window) OR mm_prefix``.
1803
1804 Cached so identical ``(sliding_window, sliding_window_left)`` reuse the
1805 same function object. FA4's ``hash_callable`` mixes ``repr()`` of closure
1806 cells into the compile key; the nested ``_load_q_range`` would otherwise
1807 get a new address each call and force a full JIT recompile every forward.
1808
1809 The FA4 kernel passes *local* ``q_idx`` (0-based within the current
1810 prefill chunk) while ``kv_idx`` is absolute (0-based over the full
1811 KV cache). We recover the absolute Q position via
1812 ``q_abs = q_idx + seqlen_k - seqlen_q`` (the context-length offset)
1813 so that causal, sliding-window, and mm_prefix range comparisons all
1814 use consistent absolute positions. This matches the Triton
1815 reference path (``compute_kv_seq_mask``).
1816
1817 ``aux_tensors[0]`` holds the absolute ``[start, end]`` bounds of the
1818 mm_prefix range containing each scheduled query token (``(-1, -1)`` when
1819 none), and ``aux_tensors[1]`` is ``cu_seqlens_q``, used to turn the local
1820 ``q_idx`` into a packed row index. Because mm_prefix ranges never overlap,
1821 ``r_start <= kv_idx <= r_end`` is exactly "query and key share a range", so
1822 no key-side lookup is needed and ``kv_idx`` is never used as an index.
1823 The ``(-1, -1)`` sentinel falls out for free: ``kv_idx <= -1`` is false for
1824 every valid key.
1825
1826 ``sliding_window_left`` enforces the sliding window on the causal
1827 term (None = full causal, no window). ``sliding_window`` clamps the
1828 bidirectional block to the window (0 = unclamped; >0 = Gemma4 local
1829 layers via ``mm_prefix_clamp_sliding_window``).
1830 """
1831 import cutlass
1832 import cutlass.cute as cute
1833 from cutlass import Int32 # type: ignore[attr-defined]
1834
1835 from vllm.vllm_flash_attn.cute.utils import ( # type: ignore[import-untyped]
1836 scalar_to_ssa,
1837 ssa_to_scalar,
1838 )
1839
1840 @cute.jit
1841 def _load_q_range(q_idx, seqlen_info, aux_tensors, batch_idx):
1842 """Load the mm_prefix range bounds for this query row.
1843
1844 Both loads depend only on the query index, so they hoist out of the
1845 unrolled per-element mask loop. ``ssa_to_scalar`` reads lane 0, so one
1846 call must not span query rows (hence the ``__vec_size__`` pin below).
1847 Clamping keeps ``token_idx`` in bounds for partial tiles and padded
1848 (``seqlen_q == 0``) rows; neither produces output.
1849 """
1850 q_ranges = aux_tensors[0]
1851 cu_seqlens_q = aux_tensors[1]
1852 b = batch_idx[0]
1853 q_local = cutlass.min(ssa_to_scalar(q_idx), seqlen_info.seqlen_q - Int32(1))
1854 token_idx = cutlass.max(cu_seqlens_q[b] + q_local, Int32(0))
1855 return (
1856 scalar_to_ssa(q_ranges[token_idx, 0], Int32),
1857 scalar_to_ssa(q_ranges[token_idx, 1], Int32),
1858 )
1859
1860 if sliding_window_left is not None:
1861
1862 @cute.jit
1863 def mm_prefix_mask_mod(
1864 batch_idx: cute.TensorSSA,
1865 head_idx: cute.TensorSSA,
1866 q_idx: cute.TensorSSA,
1867 kv_idx: cute.TensorSSA,
1868 seqlen_info,
1869 aux_tensors,
1870 ):
1871 ctx_off = scalar_to_ssa(seqlen_info.seqlen_k - seqlen_info.seqlen_q, Int32)
1872 q_abs = q_idx + ctx_off
1873 sw = scalar_to_ssa(Int32(sliding_window_left), Int32)
1874 keep = (kv_idx <= q_abs) & ((q_abs - kv_idx) < sw)
1875 r_start, r_end = _load_q_range(q_idx, seqlen_info, aux_tensors, batch_idx)
1876 mm = (kv_idx >= r_start) & (kv_idx <= r_end)
1877 if sliding_window > 0:
1878 mm = mm & ((q_abs - kv_idx) < sw)
1879 keep = keep | mm
1880 return keep
1881
1882 else:
1883
1884 @cute.jit
1885 def mm_prefix_mask_mod(
1886 batch_idx: cute.TensorSSA,
1887 head_idx: cute.TensorSSA,
1888 q_idx: cute.TensorSSA,
1889 kv_idx: cute.TensorSSA,
1890 seqlen_info,
1891 aux_tensors,
1892 ):
1893 ctx_off = scalar_to_ssa(seqlen_info.seqlen_k - seqlen_info.seqlen_q, Int32)
1894 q_abs = q_idx + ctx_off
1895 keep = kv_idx <= q_abs
1896 r_start, r_end = _load_q_range(q_idx, seqlen_info, aux_tensors, batch_idx)
1897 keep = keep | ((kv_idx >= r_start) & (kv_idx <= r_end))
1898 return keep
1899
1900 mm_prefix_mask_mod.use_fast_sampling = True
1901 mm_prefix_mask_mod.__vec_size__ = 1 # _load_q_range takes lane 0 of q_idx
1902 return mm_prefix_mask_mod
1903
1904
1905 def _make_rswa_mask_mod():
1906 """Build a CuTE-DSL mask_mod for Reference Sliding Window Attention (R-SWA).
1907
1908 FA4 varlen + paged-KV convention (verified from cute/mask.py apply_mask):
1909 q_idx = LOCAL query-token offset (0 .. seqlen_q - 1) within this sequence.
1910 kv_idx = LOCAL KV-token position (0 .. seqlen_k - 1) within this sequence.
1911
1912 To recover the ABSOLUTE token position (needed for causal and the sliding
1913 window distance), use the standard offset:
1914 abs_q = q_idx + (seqlen_k - seqlen_q)
1915
1916 R-SWA keep condition:
1917 abs_q >= kv_idx (causal: KV at or before the query)
1918 AND (kv_idx < prefix_len (global prefix is always visible)
1919 OR abs_q - kv_idx < window) (generated tokens: sliding window)
1920
1921 aux_tensors[0]: prefix_lens [num_reqs] int32 — per-request prefill length.
1922 aux_tensors[1]: rswa_window [1] int32 — decode sliding window size.
1923
1924 use_fast_sampling=True lets FA4 skip fully-masked KV blocks (gap blocks)
1925 without loading their data.
1926 """
1927 import cutlass.cute as cute
1928 from cutlass import Int32 # type: ignore[attr-defined]
1929
1930 from vllm.vllm_flash_attn.cute.utils import ( # type: ignore[import-untyped]
1931 scalar_to_ssa,
1932 )
1933
1934 @cute.jit
1935 def rswa_mask_mod(
1936 batch_idx: cute.TensorSSA,
1937 head_idx: cute.TensorSSA,
1938 q_idx: cute.TensorSSA,
1939 kv_idx: cute.TensorSSA,
1940 seqlen_info,
1941 aux_tensors,
1942 ):
1943 b = batch_idx[0]
1944 prefix_len = scalar_to_ssa(aux_tensors[0][b], Int32)
1945 window = scalar_to_ssa(aux_tensors[1][0], Int32)
1946 # Convert local q offset to absolute token position.
1947 offset = scalar_to_ssa(seqlen_info.seqlen_k - seqlen_info.seqlen_q, Int32)
1948 abs_q = q_idx + offset
1949 causal = kv_idx <= abs_q
1950 in_prefix = kv_idx < prefix_len
1951 in_window = (abs_q - kv_idx) < window
1952 return causal & (in_prefix | in_window)
1953
1954 rswa_mask_mod.use_fast_sampling = True
1955 return rswa_mask_mod
1956
1957
1958 def use_cascade_attention(
1959 common_prefix_len: int,
1960 query_lens: np.ndarray,
1961 num_query_heads: int,
1962 num_kv_heads: int,
1963 use_alibi: bool,
1964 use_sliding_window: bool,
1965 use_local_attention: bool,
1966 num_sms: int,
1967 dcp_world_size: int,
1968 ) -> bool:
1969 """Decide whether to use cascade attention.
1970
1971 This function 1) checks whether cascade attention is supported with the
1972 given configuration, and 2) heuristically decides whether using cascade
1973 attention can improve performance.
1974 """
1975 # Too short common prefix. Probably not worth using cascade attention.
1976 # We use an arbitrary threshold of 256 tokens. TODO: Tune this threshold.
1977 # NOTE(woosuk): This is the common case. We should return False as soon as
1978 # possible to avoid any unnecessary computation.
1979 if common_prefix_len < 256:
1980 return False
1981 # Cascade attention is currently not supported with these variants.
1982 if use_alibi or use_sliding_window or use_local_attention:
1983 return False
1984 # Too few queries. Probably not worth using cascade attention.
1985 # We use an arbitrary threshold of 8 queries. TODO: Tune this threshold.
1986 num_reqs = len(query_lens)
1987 if num_reqs < 8:
1988 return False
1989 # disable cascade attention for DCP
1990 if dcp_world_size > 1:
1991 return False
1992
1993 # Heuristics to decide whether using cascade attention is beneficial.
1994 # 1. When FlashDecoding is not used for normal attention, cascade attention
1995 # is likely to be faster since it saves memory bandwidth.
1996 num_queries_per_kv = num_query_heads // num_kv_heads
1997 # The criteria for using FlashDecoding can be found in the following link:
1998 # https://github.com/vllm-project/flash-attention/blob/96266b1111111f3d11aabefaf3bacbab6a89d03c/csrc/flash_attn/flash_api.cpp#L535
1999 use_flash_decoding = (
2000 num_queries_per_kv > 1
2001 and not use_sliding_window
2002 and not use_alibi
2003 and np.all(query_lens == 1)
2004 )
2005 if not use_flash_decoding:
2006 # Use cascade attention.
2007 return True
2008
2009 # 2. When FlashDecoding is used for normal attention, it is not clear
2010 # whether cascade attention is beneficial, because FlashDecoding can
2011 # launch more CTAs than cascade attention.
2012 # We use a simple performance model to compare the two methods.
2013 # NOTE(woosuk): The performance model is very rough and may not be
2014 # accurate.
2015 num_tokens = num_reqs
2016 # NOTE(woosuk): These are default tile sizes. flash-attn might use
2017 # different tile sizes (e.g., 64 or 256) depending on the configuration.
2018 q_tile_size = 128
2019 kv_tile_size = 128
2020 num_prefix_tiles = cdiv(common_prefix_len, kv_tile_size)
2021
2022 cascade_ctas = num_query_heads * cdiv(num_tokens, q_tile_size)
2023 cascade_waves = cdiv(cascade_ctas, num_sms)
2024 cascade_time = cascade_waves * num_prefix_tiles
2025
2026 flash_decoding_ctas = (
2027 num_reqs * num_kv_heads * cdiv(num_queries_per_kv, q_tile_size)
2028 )
2029 flash_decoding_ctas *= num_prefix_tiles
2030 flash_decoding_time = cdiv(flash_decoding_ctas, num_sms)
2031
2032 # Use cascade attention if it is faster than FlashDecoding.
2033 return cascade_time < flash_decoding_time
2034
2035
2036 def cascade_attention(
2037 output: torch.Tensor,
2038 query: torch.Tensor,
2039 key_cache: torch.Tensor,
2040 value_cache: torch.Tensor,
2041 cu_query_lens: torch.Tensor,
2042 max_query_len: int,
2043 cu_prefix_query_lens: torch.Tensor,
2044 prefix_kv_lens: torch.Tensor,
2045 suffix_kv_lens: torch.Tensor,
2046 max_kv_len: int,
2047 softmax_scale: float,
2048 alibi_slopes: torch.Tensor | None,
2049 sliding_window: tuple[int, int],
2050 logits_soft_cap: float,
2051 block_table: torch.Tensor,
2052 common_prefix_len: int,
2053 max_num_splits: int,
2054 fa_version: int,
2055 prefix_scheduler_metadata: torch.Tensor | None = None,
2056 suffix_scheduler_metadata: torch.Tensor | None = None,
2057 q_descale: torch.Tensor | None = None,
2058 k_descale: torch.Tensor | None = None,
2059 v_descale: torch.Tensor | None = None,
2060 s_aux: torch.Tensor | None = None,
2061 ) -> torch.Tensor:
2062 assert alibi_slopes is None, "Cascade attention does not support ALiBi."
2063 # TODO: Support sliding window.
2064 assert sliding_window == (-1, -1), (
2065 "Cascade attention does not support sliding window."
2066 )
2067
2068 num_tokens = query.shape[0]
2069 block_size = key_cache.shape[-3]
2070 assert common_prefix_len % block_size == 0
2071 num_common_kv_blocks = common_prefix_len // block_size
2072 assert num_common_kv_blocks > 0
2073 descale_shape = (cu_prefix_query_lens.shape[0] - 1, key_cache.shape[-2])
2074
2075 # Process shared prefix.
2076 prefix_output, prefix_lse = flash_attn_varlen_func(
2077 q=query,
2078 k=key_cache,
2079 v=value_cache,
2080 cu_seqlens_q=cu_prefix_query_lens,
2081 seqused_k=prefix_kv_lens,
2082 max_seqlen_q=num_tokens,
2083 max_seqlen_k=common_prefix_len,
2084 softmax_scale=softmax_scale,
2085 causal=False,
2086 window_size=list(sliding_window),
2087 block_table=block_table[:1],
2088 softcap=logits_soft_cap,
2089 return_softmax_lse=True,
2090 scheduler_metadata=prefix_scheduler_metadata,
2091 fa_version=fa_version,
2092 q_descale=q_descale.expand(descale_shape) if q_descale is not None else None,
2093 k_descale=k_descale.expand(descale_shape) if k_descale is not None else None,
2094 v_descale=v_descale.expand(descale_shape) if v_descale is not None else None,
2095 # s_aux is incorporated into prefix_lse inside the GPU kernel,
2096 # enabling its effect during the final attention merge.
2097 s_aux=s_aux,
2098 num_splits=1 if envs.VLLM_BATCH_INVARIANT else max_num_splits,
2099 )
2100
2101 descale_shape = (cu_query_lens.shape[0] - 1, key_cache.shape[-2])
2102
2103 # Process suffix per query.
2104 suffix_output, suffix_lse = flash_attn_varlen_func(
2105 q=query,
2106 k=key_cache,
2107 v=value_cache,
2108 cu_seqlens_q=cu_query_lens,
2109 seqused_k=suffix_kv_lens,
2110 max_seqlen_q=max_query_len,
2111 max_seqlen_k=max_kv_len - common_prefix_len,
2112 softmax_scale=softmax_scale,
2113 causal=True,
2114 window_size=list(sliding_window),
2115 block_table=block_table[:, num_common_kv_blocks:],
2116 softcap=logits_soft_cap,
2117 return_softmax_lse=True,
2118 scheduler_metadata=suffix_scheduler_metadata,
2119 fa_version=fa_version,
2120 q_descale=q_descale.expand(descale_shape) if q_descale is not None else None,
2121 k_descale=k_descale.expand(descale_shape) if k_descale is not None else None,
2122 v_descale=v_descale.expand(descale_shape) if v_descale is not None else None,
2123 num_splits=1 if envs.VLLM_BATCH_INVARIANT else max_num_splits,
2124 )
2125
2126 # Merge prefix and suffix outputs, and store the result in output.
2127 merge_attn_states(output, prefix_output, prefix_lse, suffix_output, suffix_lse)
2128
2129
2130 _FA4_DENSE_ATTENTION_KERNEL = FA4DenseAttentionKernel()