[Kernel] Resubmit PR 48666 - Gemma4 FP8 KV FA4 head dim 512 backend selection (#53175)
Signed-off-by: Jhao-Ting Chen <jhaotingc@nvidia.com>
Jhao-Ting Chen committed
Sep 23, 2026 at 20:29 UTC
4fb767ff0a0e0aae9dd2720c47245d0202136dfd
47 files changed
+241
-103
tests/kernels/attention/test_attention_selector.py
+2
-6
@@ -848,7 +848,7 @@ def hopper_selection():
848
def test_hopper_mm_prefix_selects_triton_flash_attn(
849
use_mm_prefix, flash_attn_version, hopper_selection
850
):
851
- """Hopper selects the composite only when its causal route resolves FA4."""
851
+ """Hopper selects the composite when its causal route resolves FA3 or FA4."""
852
from vllm.engine.arg_utils import EngineArgs
853
854
config = EngineArgs(
@@ -860,11 +860,7 @@ def test_hopper_mm_prefix_selects_triton_flash_attn(
860
backend = get_attn_backend(
861
256, torch.bfloat16, None, use_mm_prefix=use_mm_prefix
862
)
863
- fa4_resolved = config.attention_config.flash_attn_version == 4
864
- if use_mm_prefix:
865
- expected = "TRITON_FLASH_ATTN" if fa4_resolved else "TRITON_ATTN"
866
- else:
867
- expected = "FLASH_ATTN"
863
+ expected = "TRITON_FLASH_ATTN" if use_mm_prefix else "FLASH_ATTN"
864
assert backend.get_name() == expected
865
866
tests/v1/attention/test_backend_per_kind.py
+1
-1
@@ -29,7 +29,7 @@ from vllm.v1.kv_cache_interface import KVCacheSpecKind
29
def test_largest_kernel_block_within(supported_sizes, expected):
30
class Backend:
31
@staticmethod
32
- def get_supported_kernel_block_sizes():
32
+ def get_supported_kernel_block_sizes(kv_cache_spec=None):
33
return supported_sizes
34
35
assert _largest_kernel_block_within(Backend, 1024, 1024 * 1536, 2048) == expected
vllm/model_executor/layers/attention/attention.py
+14
-5
@@ -8,7 +8,10 @@ import torch.nn as nn
8
9
import vllm.envs as envs
10
from vllm.compilation.breakable_cudagraph import eager_break_during_capture
11
-from vllm.config import CacheConfig, get_current_vllm_config
11
+from vllm.config import (
12
+ CacheConfig,
13
+ get_current_vllm_config,
14
+)
15
from vllm.config.vllm import VllmConfig
16
from vllm.forward_context import ForwardContext, get_forward_context
17
from vllm.logger import init_logger
@@ -100,6 +103,7 @@ def _largest_kernel_block_within(
103
per_token_bytes: int,
104
page_budget: int,
105
fallback: int,
106
+ kv_cache_spec: KVCacheSpec | None = None,
107
) -> int:
108
"""Largest supported kernel block size whose page fits in ``page_budget``.
109
@@ -111,7 +115,7 @@ def _largest_kernel_block_within(
115
"""
116
from vllm.v1.attention.backend import MultipleOf
117
114
- sizes = attn_backend.get_supported_kernel_block_sizes()
118
+ sizes = attn_backend.get_supported_kernel_block_sizes(kv_cache_spec)
119
max_block_size = page_budget // per_token_bytes
120
candidates = [s for s in sizes if isinstance(s, int)]
121
candidates.extend(
@@ -632,7 +636,7 @@ class Attention(nn.Module, AttentionLayerBase):
636
# ``unify`` scales it up by an integer ratio.
637
shared_page = vllm_config.cache_config.skip_page_size_padded
638
# The backend owns its packing
635
- sw_per_token = self.attn_backend.customize_spec(
639
+ kv_cache_spec = self.attn_backend.customize_spec(
640
SlidingWindowSpec(
641
block_size=1,
642
num_kv_heads=self.num_kv_heads,
@@ -642,10 +646,15 @@ class Attention(nn.Module, AttentionLayerBase):
646
kv_quant_mode=quant_mode,
647
sliding_window=self.sliding_window,
648
)
645
- ).real_page_size_bytes
649
+ )
650
+ sw_per_token = kv_cache_spec.real_page_size_bytes
651
page_budget = shared_page or sw_per_token * block_size
652
sw_block_size = _largest_kernel_block_within(
648
- self.attn_backend, sw_per_token, page_budget, block_size
653
+ self.attn_backend,
654
+ sw_per_token,
655
+ page_budget,
656
+ block_size,
657
+ kv_cache_spec,
658
)
659
return SlidingWindowSpec(
660
block_size=sw_block_size,
vllm/model_executor/models/config.py
+26
-12
@@ -212,12 +212,15 @@ class Gemma4Config(VerifyAndUpdateConfig):
212
"""Configure attention for heterogeneous head dimensions.
213
214
Gemma4 uses different head dimensions for sliding window vs full attention
215
- layers. The default FA3 on Hopper cannot handle head_dim > 256, which causes
216
- mixed backend selection and numerical divergence.
217
-
218
- When FA4 is available we force it for ALL layers, giving a uniform kernel path
219
- and avoiding the mixed FA3+FA4 penalty. When FA4 is not available we fall back
220
- to Triton.
215
+ layers. The default FA3 on Hopper cannot handle head_dim > 256.
216
+
217
+ On SM90 with FP8 KV cache, use FA3 for supported layers and let the generic
218
+ FlashAttention selector upgrade larger head dimensions to FA4.
219
+ The multimodal-prefix composite routes image masks to Triton and causal
220
+ requests to this per-layer FA3/FA4 selection. For other configurations,
221
+ force FA4 for all layers to avoid the mixed
222
+ FA3+FA4 penalty.
223
+ When FA4 is not available, fall back to Triton.
224
"""
225
model_config = vllm_config.model_config
226
arch_config = model_config.model_arch_config
@@ -230,6 +233,7 @@ class Gemma4Config(VerifyAndUpdateConfig):
233
if len(set(head_dims.values())) <= 1:
234
return
235
236
+ from vllm.platforms import current_platform
237
from vllm.v1.attention.backends.fa_utils import is_fa_version_supported
238
from vllm.v1.attention.backends.registry import AttentionBackendEnum
239
@@ -241,12 +245,22 @@ class Gemma4Config(VerifyAndUpdateConfig):
245
and vllm_config.attention_config.backend
246
in (None, AttentionBackendEnum.FLASH_ATTN)
247
):
244
- vllm_config.attention_config.flash_attn_version = 4
245
- logger.info(
246
- "Gemma4 model has heterogeneous head dimensions %s. Using FA4 for "
247
- "all layers to avoid mixed FA3/FA4 penalty.",
248
- head_dims,
249
- )
248
+ use_per_layer_fa = current_platform.is_device_capability_family(
249
+ 90
250
+ ) and vllm_config.cache_config.cache_dtype.startswith("fp8")
251
+ if use_per_layer_fa:
252
+ logger.info(
253
+ "Gemma4 model has heterogeneous head dimensions %s. Using "
254
+ "per-layer FA3/FA4 selection for FP8 KV cache on SM90.",
255
+ head_dims,
256
+ )
257
+ else:
258
+ vllm_config.attention_config.flash_attn_version = 4
259
+ logger.info(
260
+ "Gemma4 model has heterogeneous head dimensions %s. Using FA4 "
261
+ "for all layers to avoid mixed FA3/FA4 penalty.",
262
+ head_dims,
263
+ )
264
elif vllm_config.attention_config.backend is None:
265
vllm_config.attention_config.backend = AttentionBackendEnum.TRITON_ATTN
266
logger.info(
vllm/models/deepseek_v4/compressor.py
+1
-1
@@ -66,7 +66,7 @@ class CompressorBackend(AttentionBackend):
66
return "CompressorBackend"
67
68
@staticmethod
69
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
69
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
70
return [MultipleOf(1)]
71
72
@classmethod
vllm/models/deepseek_v4/nvidia/flashinfer_sparse.py
+1
-1
@@ -111,7 +111,7 @@ class DeepseekV4FlashInferMLASparseBackend(DeepseekV4SparseMLABackend):
111
]
112
113
@staticmethod
114
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
114
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
115
return [256]
116
117
@staticmethod
vllm/models/deepseek_v4/sparse_mla.py
+1
-1
@@ -57,7 +57,7 @@ class DeepseekV4SparseMLABackend(AttentionBackend):
57
]
58
59
@staticmethod
60
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
60
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
61
return [256]
62
63
@staticmethod
vllm/models/deepseek_v41/compressor.py
+1
-1
@@ -37,7 +37,7 @@ class CompressorBackend(AttentionBackend):
37
return "CompressorBackend"
38
39
@staticmethod
40
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
40
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
41
return [MultipleOf(1)]
42
43
@classmethod
vllm/models/deepseek_v41/nvidia/flashinfer_sparse.py
+1
-1
@@ -113,7 +113,7 @@ class DeepseekV4FlashInferMLASparseBackend(DeepseekV4SparseMLABackend):
113
]
114
115
@staticmethod
116
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
116
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
117
return [128]
118
119
@staticmethod
vllm/models/deepseek_v41/sparse_mla.py
+2
-2
@@ -86,7 +86,7 @@ class DeepseekV4SparseMLABackend(AttentionBackend):
86
]
87
88
@staticmethod
89
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
89
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
90
return [64 if current_platform.is_device_capability_family(90) else 128]
91
92
@staticmethod
@@ -255,7 +255,7 @@ class FlashMLAMegaAttnBackend(DeepseekV4FlashMLABackend):
255
return "FLASHMLA_MEGA_ATTN_DSV41"
256
257
@staticmethod
258
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
258
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
259
return [128]
260
261
@classmethod
vllm/models/minimax_m3/common/indexer.py
+1
-1
@@ -91,7 +91,7 @@ class MiniMaxM3IndexerBackend(AttentionBackend):
91
return [128]
92
93
@staticmethod
94
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
94
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
95
return [128]
96
97
@classmethod
vllm/models/minimax_m3/common/sparse_attention.py
+1
-1
@@ -159,7 +159,7 @@ class MiniMaxM3SparseBackend(AttentionBackend):
159
return [128]
160
161
@staticmethod
162
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
162
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
163
# Page size == sparse block size (one sparse block per KV page).
164
return [128]
165
vllm/models/qwen4_exp/amd/qsa.py
+1
-1
@@ -75,7 +75,7 @@ class Qwen4ExpQSAFlashAttentionBackend(FlashAttentionBackend):
75
return "QWEN4_EXP_QSA_TRITON"
76
77
@staticmethod
78
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
78
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
79
# QSA consumes manager pages directly and does not use FA4 paged attention.
80
return [MultipleOf(16)]
81
vllm/models/qwen4_exp/nvidia/qsa.py
+1
-1
@@ -103,7 +103,7 @@ class Qwen4ExpQSAFlashAttentionBackend(FlashAttentionBackend):
103
return "QWEN4_EXP_QSA_TRITON"
104
105
@staticmethod
106
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
106
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
107
# QSA consumes manager pages directly and does not use FA4 paged attention.
108
return [MultipleOf(16)]
109
vllm/v1/attention/backend.py
+3
-1
@@ -69,7 +69,9 @@ class AttentionBackend(ABC):
69
forward_includes_kv_cache_update: bool = True
70
71
@staticmethod
72
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
72
+ def get_supported_kernel_block_sizes(
73
+ kv_cache_spec: "KVCacheSpec | None" = None,
74
+ ) -> list[int | MultipleOf]:
75
return [MultipleOf(1)]
76
77
@staticmethod
vllm/v1/attention/backends/b12x.py
+1
-1
@@ -183,7 +183,7 @@ class B12xPagedAttentionBackend(AttentionBackend):
183
return B12xPagedMetadataBuilder
184
185
@staticmethod
186
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
186
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
187
return list(_B12X_SUPPORTED_PAGE_SIZES)
188
189
@classmethod
vllm/v1/attention/backends/composite.py
+11
-5
@@ -116,14 +116,16 @@ class MMPrefixAttentionRouting(CompositeAttentionRouting):
116
117
118
def _intersect_kernel_block_sizes(
119
- first: type[AttentionBackend], second: type[AttentionBackend]
119
+ first: type[AttentionBackend],
120
+ second: type[AttentionBackend],
121
+ kv_cache_spec: KVCacheSpec | None = None,
122
):
123
from vllm.v1.attention.backend import MultipleOf
124
125
result: list[int | MultipleOf] = []
126
seen: set[tuple[type, int]] = set()
125
- for lhs in first.get_supported_kernel_block_sizes():
126
- for rhs in second.get_supported_kernel_block_sizes():
127
+ for lhs in first.get_supported_kernel_block_sizes(kv_cache_spec):
128
+ for rhs in second.get_supported_kernel_block_sizes(kv_cache_spec):
129
candidate: int | MultipleOf | None = None
130
if isinstance(lhs, MultipleOf) and isinstance(rhs, MultipleOf):
131
candidate = MultipleOf(math.lcm(lhs.base, rhs.base))
@@ -378,7 +380,9 @@ def create_composite_attention_backend(
380
return CompositeAttentionMetadataBuilder
381
382
@staticmethod
381
- def get_supported_kernel_block_sizes():
383
+ def get_supported_kernel_block_sizes(
384
+ kv_cache_spec: KVCacheSpec | None = None,
385
+ ):
386
if kernel_block_sizes:
387
return [
388
size
@@ -388,7 +392,9 @@ def create_composite_attention_backend(
392
for backend in (general_backend, causal_backend)
393
)
394
]
391
- return _intersect_kernel_block_sizes(general_backend, causal_backend)
395
+ return _intersect_kernel_block_sizes(
396
+ general_backend, causal_backend, kv_cache_spec
397
+ )
398
399
@classmethod
400
def supports_block_size(cls, block_size):
vllm/v1/attention/backends/cpu_attn.py
+1
-1
@@ -57,7 +57,7 @@ class CPUAttentionBackend(AttentionBackend):
57
]
58
59
@staticmethod
60
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
60
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
61
return [MultipleOf(32)]
62
63
@classmethod
vllm/v1/attention/backends/fa_utils.py
+5
-1
@@ -303,7 +303,11 @@ def flash_attn_supports_kv_cache_dtype(
303
kv_cache_block_size=kv_cache_block_size,
304
supports_fa4_hd256=supports_fa4_hd256,
305
)
306
- return (fa_version == 3 and current_platform.is_device_capability_family(90)) or (
306
+ is_sm90 = current_platform.is_device_capability_family(90)
307
+ sm90_fp8_kv_supported = fa_version == 3 or (
308
+ fa_version == 4 and head_size in (None, 512)
309
+ )
310
+ return (sm90_fp8_kv_supported and is_sm90) or (
311
fa_version == 4 and current_platform.is_device_capability_family(100)
312
)
313
vllm/v1/attention/backends/flash_attn.py
+81
-16
@@ -291,18 +291,56 @@ class FlashAttentionBackend(AttentionBackend):
291
]
292
head_size_v: int | None = None
293
294
+ @staticmethod
295
+ def _get_sm90_fa4_fp8_kv_block_size(
296
+ kv_cache_spec: KVCacheSpec | None = None,
297
+ ) -> int | None:
298
+ if kv_cache_spec is None:
299
+ vllm_config = get_current_vllm_config_or_none()
300
+ if vllm_config is None or vllm_config.model_config is None:
301
+ return None
302
+ head_size = vllm_config.model_config.get_head_size()
303
+ uses_fp8_kv = vllm_config.cache_config.cache_dtype in (
304
+ "fp8",
305
+ "fp8_e4m3",
306
+ )
307
+ else:
308
+ if not isinstance(kv_cache_spec, AttentionSpec):
309
+ return None
310
+ head_size = kv_cache_spec.head_size
311
+ uses_fp8_kv = kv_cache_spec.kv_quant_mode == KVQuantMode.FP8_PER_TENSOR
312
+
313
+ if (
314
+ current_platform.is_device_capability_family(90)
315
+ and uses_fp8_kv
316
+ and head_size == 512
317
+ and get_flash_attn_version(head_size=head_size) == 4
318
+ ):
319
+ # The SM90 FP8-KV-dequant kernel uses a 64-token TMA tile/page.
320
+ return 64
321
+ return None
322
+
323
@classmethod
295
- def _get_fa4_hd256_block_size(cls) -> int | None:
296
- vllm_config = get_current_vllm_config_or_none()
297
- if vllm_config is None or vllm_config.model_config is None:
298
- return None
324
+ def _get_fa4_hd256_block_size(
325
+ cls, kv_cache_spec: KVCacheSpec | None = None
326
+ ) -> int | None:
327
+ if kv_cache_spec is None:
328
+ vllm_config = get_current_vllm_config_or_none()
329
+ if vllm_config is None or vllm_config.model_config is None:
330
+ return None
331
+ head_size = vllm_config.model_config.get_head_size()
332
+ head_size_v = cls.head_size_v
333
+ else:
334
+ if not isinstance(kv_cache_spec, AttentionSpec):
335
+ return None
336
+ head_size = kv_cache_spec.head_size
337
+ head_size_v = kv_cache_spec.head_size_v
338
300
- head_size = vllm_config.model_config.get_head_size()
339
if (
302
- uses_fa4_hd256_kernel(head_size, cls.head_size_v)
340
+ uses_fa4_hd256_kernel(head_size, head_size_v)
341
and get_flash_attn_version(
342
head_size=head_size,
305
- head_size_v=cls.head_size_v,
343
+ head_size_v=head_size_v,
344
supports_fa4_hd256=True,
345
)
346
== 4
@@ -311,8 +349,12 @@ class FlashAttentionBackend(AttentionBackend):
349
return None
350
351
@classmethod
314
- def get_supported_kernel_block_sizes(cls) -> list[int | MultipleOf]:
315
- if block_size := cls._get_fa4_hd256_block_size():
352
+ def get_supported_kernel_block_sizes(
353
+ cls, kv_cache_spec: KVCacheSpec | None = None
354
+ ) -> list[int | MultipleOf]:
355
+ if block_size := cls._get_sm90_fa4_fp8_kv_block_size(kv_cache_spec):
356
+ return [block_size]
357
+ if block_size := cls._get_fa4_hd256_block_size(kv_cache_spec):
358
# Sliding-window specs select the smallest advertised size.
359
return [block_size]
360
return [MultipleOf(16)]
@@ -321,6 +363,8 @@ class FlashAttentionBackend(AttentionBackend):
363
364
@classmethod
365
def get_preferred_block_size(cls, default_block_size: int) -> int:
366
+ if block_size := cls._get_sm90_fa4_fp8_kv_block_size():
367
+ return max(default_block_size, block_size)
368
if block_size := cls._get_fa4_hd256_block_size():
369
return max(default_block_size, block_size)
370
if current_platform.is_xpu():
@@ -419,6 +463,13 @@ class FlashAttentionBackend(AttentionBackend):
463
) -> str | None:
464
if has_sink and device_capability < DeviceCapability(9, 0):
465
return "sink not supported on compute capability < 9.0"
466
+ if (
467
+ use_mm_prefix
468
+ and kv_cache_dtype is not None
469
+ and is_quantized_kv_cache(kv_cache_dtype)
470
+ and device_capability == DeviceCapability(9, 0)
471
+ ):
472
+ return "SM90 FP8 KV with mm_prefix requires Triton"
473
if (
474
kv_cache_dtype is not None
475
and is_quantized_kv_cache(kv_cache_dtype)
@@ -431,7 +482,10 @@ class FlashAttentionBackend(AttentionBackend):
482
supports_fa4_hd256=True,
483
)
484
):
434
- return "FP8 KV cache requires FA3 on SM90 or FA4 on SM100"
485
+ return (
486
+ "FP8 KV cache requires FA3 on SM90, FA4 with head_size=512 "
487
+ "on SM90, or FA4 on SM100"
488
+ )
489
if (
490
use_mm_prefix
491
and get_flash_attn_version(
@@ -514,15 +568,16 @@ def _get_sliding_window_configs(
568
) -> set[tuple[int, int] | None]:
569
"""Get the set of all sliding window configs used in the model.
570
517
- Only inspects FlashAttentionImpl layers. Other backends (e.g.
518
- TurboQuant, MLA) use their own metadata builders and are skipped.
571
+ Only inspects FlashAttentionImpl layers, including implementations wrapped
572
+ by a composite backend. Other backends (e.g. TurboQuant, MLA) use their own
573
+ metadata builders and are skipped.
574
"""
575
sliding_window_configs: set[tuple[int, int] | None] = set()
576
layers = get_layers_from_vllm_config(vllm_config, Attention)
577
for layer in layers.values():
523
- if not isinstance(layer.impl, FlashAttentionImpl):
524
- continue
525
- sliding_window_configs.add(layer.impl.sliding_window)
578
+ for impl in layer.impl.get_impl_variants():
579
+ if isinstance(impl, FlashAttentionImpl):
580
+ sliding_window_configs.add(impl.sliding_window)
581
return sliding_window_configs
582
583
@@ -1145,7 +1200,17 @@ class FlashAttentionImpl(AttentionImpl):
1200
"heads in the layer"
1201
)
1202
1148
- self.supports_quant_query_input = flash_attn_supports_quant_query_input()
1203
+ # FA4's SM90 FP8-KV path consumes native FP16/BF16 Q and dequantizes
1204
+ # FP8 K/V in-kernel. Other FA4 paths (notably SM100) still require Q,
1205
+ # K, and V to have the same FP8 dtype.
1206
+ uses_sm90_fa4_fp8_kv_dequant = (
1207
+ self.vllm_flash_attn_version == 4
1208
+ and current_platform.is_device_capability_family(90)
1209
+ and self.kv_cache_dtype in ("fp8", "fp8_e4m3")
1210
+ )
1211
+ self.supports_quant_query_input = flash_attn_supports_quant_query_input() and (
1212
+ not uses_sm90_fa4_fp8_kv_dequant
1213
+ )
1214
1215
dcp_a2a = (
1216
vllm_config is not None
vllm/v1/attention/backends/flashinfer.py
+1
-1
@@ -423,7 +423,7 @@ class FlashInferBackend(AttentionBackend):
423
]
424
425
@staticmethod
426
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
426
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
427
# Page sizes >= 128 only run on the trtllm-gen dynamic kernel (GQA/MQA
428
# on Blackwell); advertise them only when usable so selection never
429
# picks a large kernel block we cannot serve.
vllm/v1/attention/backends/flex_attention.py
+1
-1
@@ -154,7 +154,7 @@ class FlexAttentionBackend(AttentionBackend):
154
return []
155
156
@staticmethod
157
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
157
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
158
return [MultipleOf(16)]
159
160
vllm/v1/attention/backends/hpc_attn.py
+1
-1
@@ -288,7 +288,7 @@ class HpcAttentionBackend(AttentionBackend):
288
forward_includes_kv_cache_update: bool = True
289
290
@staticmethod
291
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
291
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
292
return [64]
293
294
@staticmethod
vllm/v1/attention/backends/mla/amx_mla.py
+1
-1
@@ -130,7 +130,7 @@ class AMXMLABackend(MLACommonBackend):
130
return [576]
131
132
@staticmethod
133
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
133
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
134
return [MultipleOf(32)]
135
136
@classmethod
vllm/v1/attention/backends/mla/cpu_mla.py
+1
-1
@@ -64,7 +64,7 @@ class CPUMLABackend(MLACommonBackend):
64
return [576]
65
66
@staticmethod
67
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
67
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
68
return [16]
69
70
@classmethod
vllm/v1/attention/backends/mla/cutlass_mla.py
+1
-1
@@ -46,7 +46,7 @@ class CutlassMLABackend(MLACommonBackend):
46
]
47
48
@staticmethod
49
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
49
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
50
return [128]
51
52
@staticmethod
vllm/v1/attention/backends/mla/flashattn_mla.py
+1
-1
@@ -49,7 +49,7 @@ class FlashAttnMLABackend(MLACommonBackend):
49
]
50
51
@staticmethod
52
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
52
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
53
return [MultipleOf(16)]
54
55
@staticmethod
vllm/v1/attention/backends/mla/flashattn_mla_sparse.py
+1
-1
@@ -40,7 +40,7 @@ class FlashAttnMLASparseBackend(AttentionBackend):
40
]
41
42
@staticmethod
43
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
43
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
44
return [64]
45
46
@staticmethod
vllm/v1/attention/backends/mla/flashinfer_mla.py
+1
-1
@@ -201,7 +201,7 @@ class FlashInferMLABackend(MLACommonBackend):
201
]
202
203
@staticmethod
204
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
204
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
205
return [32, 64]
206
207
@staticmethod
vllm/v1/attention/backends/mla/flashinfer_mla_sparse.py
+2
-2
@@ -83,7 +83,7 @@ class FlashInferMLASparseTRTLLMBackend(_FlashInferMLASparseBackendBase):
83
]
84
85
@staticmethod
86
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
86
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
87
return [32, 64]
88
89
@staticmethod
@@ -172,7 +172,7 @@ class FlashInferMLASparseSM120Backend(_FlashInferMLASparseBackendBase):
172
return "FLASHINFER_MLA_SPARSE_SM120"
173
174
@staticmethod
175
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
175
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
176
return [64, 256]
177
178
@staticmethod
vllm/v1/attention/backends/mla/flashinfer_mla_sparse_sm90.py
+1
-1
@@ -74,7 +74,7 @@ class FlashInferMLASparseSM90Backend(AttentionBackend):
74
]
75
76
@staticmethod
77
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
77
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
78
return [MultipleOf(64)]
79
80
@staticmethod
vllm/v1/attention/backends/mla/flashmla.py
+1
-1
@@ -55,7 +55,7 @@ class FlashMLABackend(MLACommonBackend):
55
]
56
57
@staticmethod
58
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
58
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
59
return [64]
60
61
@staticmethod
vllm/v1/attention/backends/mla/flashmla_sparse.py
+1
-1
@@ -139,7 +139,7 @@ class FlashMLASparseBackend(AttentionBackend):
139
]
140
141
@staticmethod
142
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
142
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
143
return [64]
144
145
@staticmethod
vllm/v1/attention/backends/mla/indexer.py
+4
-4
@@ -200,7 +200,7 @@ class DeepseekV32IndexerBackend(AttentionBackend):
200
return "DEEPSEEK_V32_INDEXER"
201
202
@staticmethod
203
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
203
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
204
return [1, MultipleOf(16)] if current_platform.is_rocm() else [64]
205
206
@classmethod
@@ -228,7 +228,7 @@ class KpoolTailBackend(DeepseekV32IndexerBackend):
228
return []
229
230
@staticmethod
231
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
231
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
232
return [MultipleOf(1)]
233
234
@staticmethod
@@ -257,7 +257,7 @@ class DeepseekV4IndexerBackend(DeepseekV32IndexerBackend):
257
return (KVCacheLayout.BLHNC, KVCacheLayout.BLNHC)
258
259
@staticmethod
260
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
260
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
261
# Block sizes count uncompressed tokens: C4 indexer pages hold 64 rows.
262
return [256]
263
@@ -274,7 +274,7 @@ class DeepseekV41IndexerBackend(DeepseekV4IndexerBackend):
274
return "DEEPSEEK_V41_INDEXER"
275
276
@staticmethod
277
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
277
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
278
return [64 if current_platform.is_device_capability_family(90) else 128]
279
280
vllm/v1/attention/backends/mla/rocm_aiter_mla.py
+1
-1
@@ -282,7 +282,7 @@ class AiterMLABackend(MLACommonBackend):
282
return []
283
284
@staticmethod
285
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
285
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
286
# The aiter MLA decode kernel always operates with page_size=1
287
# internally (the wrapper flattens kv_buffer via .view(-1, 1, 1, H)).
288
# We support any kernel_block_size by expanding block-level indices
vllm/v1/attention/backends/mla/rocm_aiter_mla_sparse.py
+1
-1
@@ -287,7 +287,7 @@ class ROCMAiterMLASparseBackend(AttentionBackend):
287
]
288
289
@staticmethod
290
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
290
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
291
return [1, MultipleOf(16)]
292
293
@staticmethod
vllm/v1/attention/backends/mla/sparse_swa.py
+1
-1
@@ -148,7 +148,7 @@ class DeepseekSparseSWABackend(AttentionBackend):
148
return "DEEPSEEK_SPARSE_SWA"
149
150
@staticmethod
151
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
151
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
152
return [MultipleOf(32)]
153
154
@classmethod
vllm/v1/attention/backends/mla/tokenspeed_mla.py
+1
-1
@@ -87,7 +87,7 @@ class TokenspeedMLABackend(MLACommonBackend):
87
]
88
89
@staticmethod
90
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
90
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
91
return [32, 64]
92
93
@staticmethod
vllm/v1/attention/backends/mla/triton_mla.py
+1
-1
@@ -107,7 +107,7 @@ class TritonMLABackend(MLACommonBackend):
107
return []
108
109
@staticmethod
110
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
110
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
111
return [MultipleOf(16)]
112
113
@classmethod
vllm/v1/attention/backends/rocm_aiter_fa.py
+1
-1
@@ -852,7 +852,7 @@ class AiterFlashAttentionBackend(AttentionBackend):
852
return attn_type in (AttentionType.DECODER,)
853
854
@staticmethod
855
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
855
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
856
if not rocm_aiter_ops.is_shuffle_kv_cache_enabled():
857
return [16, 32]
858
# Only gluon serves 128-token pages; the pa_fwd_asm/ll4mi decode
vllm/v1/attention/backends/rocm_aiter_unified_attn.py
+1
-1
@@ -64,7 +64,7 @@ class RocmAiterUnifiedAttentionBackend(RocmAttentionBackend):
64
]
65
66
@staticmethod
67
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
67
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
68
return [MultipleOf(16)]
69
70
@classmethod
vllm/v1/attention/backends/rocm_attn.py
+1
-1
@@ -176,7 +176,7 @@ class RocmAttentionBackend(AttentionBackend):
176
]
177
178
@staticmethod
179
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
179
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
180
# ROCM paged attention native C++ kernel only supports block sizes 16 and 32
181
# due to shared memory (LDS) constraints on AMD GPUs.
182
# See csrc/rocm/attention.cu CALL_CUSTOM_LAUNCHER_BLK macro.
vllm/v1/attention/backends/triton_attn.py
+1
-1
@@ -304,7 +304,7 @@ class TritonAttentionBackend(AttentionBackend):
304
]
305
306
@staticmethod
307
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
307
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
308
return [MultipleOf(16)]
309
310
@classmethod
vllm/v1/attention/backends/triton_flash_attn.py
+10
-12
@@ -15,7 +15,7 @@ from vllm.v1.attention.backends.flash_attn import FlashAttentionBackend
15
from vllm.v1.attention.backends.triton_attn import TritonAttentionBackend
16
17
18
-class _FA4Backend(FlashAttentionBackend):
18
+class _FABackend(FlashAttentionBackend):
19
@classmethod
20
def supports_combination(
21
cls,
@@ -42,22 +42,20 @@ class _FA4Backend(FlashAttentionBackend):
42
)
43
if reason is not None:
44
return reason
45
- if (
46
- get_flash_attn_version(
47
- head_size=head_size,
48
- has_sinks=has_sink,
49
- kv_cache_block_size=block_size,
50
- supports_fa4_hd256=True,
51
- )
52
- != 4
53
- ):
54
- return "causal route requires FlashAttention v4"
45
+ fa_version = get_flash_attn_version(
46
+ head_size=head_size,
47
+ has_sinks=has_sink,
48
+ kv_cache_block_size=block_size,
49
+ supports_fa4_hd256=True,
50
+ )
51
+ if fa_version not in (3, 4):
52
+ return "causal route requires FlashAttention v3 or v4"
53
return None
54
55
56
TritonFlashAttentionBackend = create_composite_attention_backend(
57
TritonAttentionBackend,
60
- _FA4Backend,
58
+ _FABackend,
59
name="TritonFlashAttentionBackend",
60
backend_name="TRITON_FLASH_ATTN",
61
module=__name__,
vllm/v1/attention/backends/turboquant_attn.py
+1
-1
@@ -161,7 +161,7 @@ class TurboQuantAttentionBackend(AttentionBackend):
161
return (KVCacheLayout.LBNHC,)
162
163
@staticmethod
164
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
164
+ def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
165
return [16, 32, 64, 128]
166
167
@classmethod
vllm/v1/worker/gpu/spec_decode/gemma4/speculator.py
+37
-2
@@ -24,6 +24,27 @@ from vllm.v1.worker.gpu.spec_decode.autoregressive.speculator import (
24
logger = init_logger(__name__)
25
26
27
+def _copy_target_kv_scales(attn: nn.Module, target_attn: nn.Module) -> None:
28
+ """Copy target KV scales while preserving their tensor representation.
29
+
30
+ Default attention scales are scalar buffers, while some quantization
31
+ methods replace them with length-one or per-head parameters. Preserve the
32
+ draft layer's registration type so the shared KV cache is interpreted with
33
+ the target's values and shapes without aliasing target parameters.
34
+ """
35
+ for scale_name in ("_k_scale", "_v_scale"):
36
+ draft_scale = getattr(attn, scale_name)
37
+ target_scale = getattr(target_attn, scale_name)
38
+ scale = target_scale.detach().clone()
39
+ if isinstance(draft_scale, nn.Parameter):
40
+ scale = nn.Parameter(scale, requires_grad=False)
41
+ setattr(attn, scale_name, scale)
42
+ for scale_name in ("_k_scale_float", "_v_scale_float"):
43
+ setattr(attn, scale_name, getattr(target_attn, scale_name))
44
+ for scale_name in ("_k_scale_cpu", "_v_scale_cpu"):
45
+ getattr(attn, scale_name).copy_(getattr(target_attn, scale_name))
46
+
47
+
48
class Gemma4Speculator(AutoRegressiveSpeculator):
49
@property
50
def advance_draft_positions(self) -> bool:
@@ -77,7 +98,7 @@ class Gemma4Speculator(AutoRegressiveSpeculator):
98
model: nn.Module,
99
target_attn_layer_names: set[str],
100
) -> None:
80
- """Wire draft layers to share KV with the target model.
101
+ """Wire draft layers to share KV and KV scales with the target model.
102
103
Each draft decoder layer is mapped to the last non-KV-shared
104
target layer of the same attention type (sliding or full).
@@ -92,7 +113,10 @@ class Gemma4Speculator(AutoRegressiveSpeculator):
113
return
114
115
target_num_kv_shared = getattr(target_text_config, "num_kv_shared_layers", 0)
95
- num_non_shared = len(target_layer_types) - target_num_kv_shared
116
+ target_num_layers = getattr(
117
+ target_text_config, "num_hidden_layers", len(target_layer_types)
118
+ )
119
+ num_non_shared = target_num_layers - target_num_kv_shared
120
type_to_target_indices: dict[str, list[int]] = defaultdict(list)
121
for idx, lt in enumerate(target_layer_types[:num_non_shared]):
122
type_to_target_indices[lt].append(idx)
@@ -128,6 +152,17 @@ class Gemma4Speculator(AutoRegressiveSpeculator):
152
target_idx = candidates[-1]
153
target_layer_name = f"{target_prefix}.{target_idx}.self_attn.attn"
154
attn.kv_sharing_target_layer_name = target_layer_name
155
+
156
+ # KV-cache sharing aliases the cache tensor during allocation, but
157
+ # the quantization scales live on the Attention modules themselves.
158
+ # The BF16 draft model has no quantization config, so its K/V scales
159
+ # otherwise remain at the default 1.0 while it reads the target's
160
+ # calibrated FP8 cache.
161
+ target_attn = self.vllm_config.compilation_config.static_forward_context[
162
+ target_layer_name
163
+ ]
164
+ _copy_target_kv_scales(attn, target_attn)
165
+
166
logger.info(
167
"Gemma4 MTP: draft layer %d (%s) -> %s",
168
draft_idx,
vllm/vllm_flash_attn/flash_attn_interface.py
+10
-1
@@ -434,6 +434,14 @@ def flash_attn_varlen_func(
434
435
from vllm.vllm_flash_attn.cute.interface import _flash_attn_fwd
436
437
+ # SM90 FA4 fp8-KV path: fp8 e4m3 paged K/V dequantized and the K/V descale folded
438
+ # in-kernel; accepts bf16/fp16 Q and writes O in its native dtype (no Q cast, no
439
+ # output copy). Only the (batch, num_kv_heads) f32 K/V descales are forwarded.
440
+ fa4_fp8_kv_dequant = (
441
+ k.dtype == torch.float8_e4m3fn
442
+ and torch.cuda.get_device_capability()[0] == 9
443
+ )
444
+
445
out, softmax_lse, _, _ = _flash_attn_fwd(
446
q,
447
k,
@@ -458,10 +466,11 @@ def flash_attn_varlen_func(
466
block_sparse_tensors=block_sparse_tensors,
467
aux_tensors=aux_tensors,
468
aux_tensor_leading_dims=aux_tensor_leading_dims,
461
- q_descale=q_descale,
469
+ q_descale=None if fa4_fp8_kv_dequant else q_descale,
470
k_descale=k_descale,
471
v_descale=v_descale,
472
output_scale=output_scale,
473
+ fp8_kv_dequant=fa4_fp8_kv_dequant,
474
)
475
else:
476
raise ValueError(f"Unsupported FA version: {fa_version}")