A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 14, 05:30 UTC
[Misc] Clean up cruft from previous FlashMLA sparse implementation (#26125)
Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com>
Lucas Wilkinson committed
Oct 7, 2025 at 22:09 UTC
f80e7866c096c478021e04911fcdaedbd3d69930
9 files changed
+79
-140
tests/kernels/attention/test_attention_selector.py
+2
-2
@@ -165,10 +165,10 @@ def test_env(
165
pytest.skip("FlashMLA only supports block_size 64")
166
else:
167
from vllm.v1.attention.backends.mla.flashmla import (
168
- is_flashmla_supported,
168
+ is_flashmla_dense_supported,
169
)
170
171
- is_supported, _ = is_flashmla_supported()
171
+ is_supported, _ = is_flashmla_dense_supported()
172
if not is_supported:
173
pytest.skip("FlashMLA not supported on this platform")
174
else:
tests/kernels/attention/test_flashmla.py
+6
-4
@@ -10,7 +10,7 @@ import torch
10
from vllm.attention.ops.flashmla import (
11
flash_mla_with_kvcache,
12
get_mla_metadata,
13
- is_flashmla_supported,
13
+ is_flashmla_dense_supported,
14
)
15
from vllm.triton_utils import triton
16
@@ -27,13 +27,15 @@ def cal_diff(
27
28
29
FLASH_MLA_UNSUPPORTED_REASON = (
30
- is_flashmla_supported()[1]
31
- if not is_flashmla_supported()[0]
30
+ is_flashmla_dense_supported()[1]
31
+ if not is_flashmla_dense_supported()[0]
32
else "FlashMLA is supported"
33
)
34
35
36
-@pytest.mark.skipif(not is_flashmla_supported()[0], reason=FLASH_MLA_UNSUPPORTED_REASON)
36
+@pytest.mark.skipif(
37
+ not is_flashmla_dense_supported()[0], reason=FLASH_MLA_UNSUPPORTED_REASON
38
+)
39
@pytest.mark.parametrize("b", [128])
40
@pytest.mark.parametrize("s_q", [1, 2])
41
@pytest.mark.parametrize("mean_sk", [4096, 8192, 16384])
tests/kernels/attention/test_flashmla_sparse.py
+9
-16
@@ -4,19 +4,12 @@ import pytest
4
import torch
5
6
7
-def _cuda_sm90_available() -> bool:
8
- if not torch.cuda.is_available():
9
- return False
10
- major, _ = torch.cuda.get_device_capability()
11
- return major == 9
12
-
13
-
7
def test_sparse_flashmla_metadata_smoke():
8
import vllm.attention.ops.flashmla as fm
9
17
- ok, reason = fm.is_flashmla_supported()
18
- if not ok or not _cuda_sm90_available():
19
- pytest.skip(reason or "SM90 not available")
10
+ ok, reason = fm.is_flashmla_sparse_supported()
11
+ if not ok:
12
+ pytest.skip(reason)
13
14
device = torch.device("cuda")
15
batch_size = 1
@@ -43,9 +36,9 @@ def test_sparse_flashmla_metadata_smoke():
36
def test_sparse_flashmla_decode_smoke():
37
import vllm.attention.ops.flashmla as fm
38
46
- ok, reason = fm.is_flashmla_supported()
47
- if not ok or not _cuda_sm90_available():
48
- pytest.skip(reason or "SM90 not available")
39
+ ok, reason = fm.is_flashmla_sparse_supported()
40
+ if not ok:
41
+ pytest.skip(reason)
42
43
device = torch.device("cuda")
44
batch_size = 1
@@ -106,9 +99,9 @@ def test_sparse_flashmla_decode_smoke():
99
def test_sparse_flashmla_prefill_smoke():
100
import vllm.attention.ops.flashmla as fm
101
109
- ok, reason = fm.is_flashmla_supported()
110
- if not ok or not _cuda_sm90_available():
111
- pytest.skip(reason or "SM90 not available")
102
+ ok, reason = fm.is_flashmla_sparse_supported()
103
+ if not ok:
104
+ pytest.skip(reason)
105
106
device = torch.device("cuda")
107
s_q = 1
tests/v1/attention/test_sparse_mla_backends.py
+17
-66
@@ -24,12 +24,7 @@ from vllm import _custom_ops as ops
24
from vllm.attention.ops import flashmla
25
from vllm.model_executor.layers.linear import ColumnParallelLinear
26
from vllm.utils import cdiv
27
-from vllm.v1.attention.backends.mla.flashmla_sparse import (
28
- FlashMLASparseBackend,
29
- FlashMLASparseDecodeAndContextMetadata,
30
- FlashMLASparseImpl,
31
- FlashMLASparseMetadata,
32
-)
27
+from vllm.v1.attention.backends.mla.flashmla_sparse import FlashMLASparseBackend
28
from vllm.v1.attention.backends.mla.indexer import split_prefill_chunks
29
30
SPARSE_BACKEND_BATCH_SPECS = {
@@ -116,59 +111,6 @@ def _quantize_dequantize_fp8_ds_mla(
111
return dequant_kv_c, dequant_k_pe
112
113
119
-def test_sparse_backend_metadata_registration():
120
- backend = FlashMLASparseBackend
121
-
122
- assert backend.get_name() == "FLASHMLA_SPARSE_VLLM_V1"
123
- assert backend.get_metadata_cls() is FlashMLASparseMetadata
124
- assert backend.get_impl_cls() is FlashMLASparseImpl
125
-
126
- dtype_list = backend.get_supported_dtypes()
127
- assert torch.bfloat16 in dtype_list
128
-
129
- shape = backend.get_kv_cache_shape(
130
- num_blocks=2, block_size=64, num_kv_heads=1, head_size=576
131
- )
132
- assert shape == (2, 64, 576)
133
-
134
-
135
-def test_sparse_decode_metadata_filters_prefill_indices():
136
- prefill_context_lengths = torch.tensor([4, 2], dtype=torch.int32)
137
- metadata = FlashMLASparseDecodeAndContextMetadata(
138
- scheduler_metadata=torch.tensor([[0]], dtype=torch.int32),
139
- num_splits=torch.tensor([1, 1], dtype=torch.int32),
140
- cache_lens=torch.tensor([10, 12], dtype=torch.int32),
141
- prefill_context_lengths=prefill_context_lengths,
142
- )
143
-
144
- indices = torch.tensor([[0, 3, 5], [1, 2, 4]], dtype=torch.int32)
145
-
146
- context_indices, new_token_indices = metadata.filter_prefill_indices(indices)
147
-
148
- expected_context = torch.tensor([[-1, -1, 5], [-1, -1, 4]], dtype=torch.int32)
149
- expected_new_tokens = torch.tensor([[-1, -1, 1], [-1, 0, 2]], dtype=torch.int32)
150
-
151
- assert torch.equal(context_indices, expected_context)
152
- assert torch.equal(new_token_indices, expected_new_tokens)
153
-
154
-
155
-def test_sparse_impl_zero_fills_when_metadata_missing():
156
- impl = FlashMLASparseImpl.__new__(FlashMLASparseImpl)
157
- dummy_layer = object()
158
- q = torch.zeros((2, 1, 3))
159
- k_c = torch.zeros((2, 3))
160
- k_pe = torch.zeros((2, 1, 1))
161
- kv_cache = torch.zeros((1, 1, 1))
162
- output = torch.ones((2, 4))
163
-
164
- result = FlashMLASparseImpl.forward(
165
- impl, dummy_layer, q, k_c, k_pe, kv_cache, attn_metadata=None, output=output
166
- )
167
-
168
- assert result is output
169
- assert torch.all(result == 0)
170
-
171
-
114
@pytest.mark.parametrize("batch_name", list(SPARSE_BACKEND_BATCH_SPECS.keys()))
115
@pytest.mark.parametrize("kv_cache_dtype", ["fp8_ds_mla", "auto"])
116
def test_sparse_backend_decode_correctness(dist_init, batch_name, kv_cache_dtype):
@@ -198,11 +140,12 @@ def test_sparse_backend_decode_correctness(dist_init, batch_name, kv_cache_dtype
140
max_model_len=max_seqlen,
141
num_gpu_blocks=max(2048, cdiv(total_cache_tokens, block_size) + 1),
142
block_size=block_size,
143
+ hf_config_override={
144
+ "index_topk": topk_tokens,
145
+ "attn_module_list_cfg": [{"topk_tokens": topk_tokens}],
146
+ },
147
)
148
model_config = vllm_config.model_config
203
- model_config.hf_config = SimpleNamespace(
204
- attn_module_list_cfg=[{"topk_tokens": topk_tokens}]
205
- )
149
model_config.hf_text_config = SimpleNamespace(
150
q_lora_rank=None,
151
kv_lora_rank=kv_lora_rank,
@@ -301,6 +244,7 @@ def test_sparse_backend_decode_correctness(dist_init, batch_name, kv_cache_dtype
244
sdpa_reference = torch.cat(reference_outputs, dim=0)
245
246
vllm_config.cache_config.cache_dtype = kv_cache_dtype
247
+ vllm_config.model_config.hf_config.index_topk = topk_tokens
248
249
common_attn_metadata = create_common_attn_metadata(
250
batch_spec,
@@ -352,7 +296,7 @@ def test_sparse_backend_decode_correctness(dist_init, batch_name, kv_cache_dtype
296
debug_indices = debug_indices.expand(metadata.num_actual_tokens, -1).clone()
297
mock_indexer = SimpleNamespace(topk_indices_buffer=debug_indices)
298
355
- ok, reason = flashmla.is_flashmla_supported()
299
+ ok, reason = flashmla.is_flashmla_sparse_supported()
300
if not ok:
301
pytest.skip(reason)
302
@@ -397,9 +341,16 @@ def test_sparse_backend_decode_correctness(dist_init, batch_name, kv_cache_dtype
341
metadata.num_actual_tokens, num_heads * v_head_dim, dtype=dtype, device=device
342
)
343
400
- backend_output = impl.forward(
401
- layer, query_vllm, kv_c_vllm, k_pe_vllm, kv_cache, metadata, output=out_buffer
402
- )
344
+ with torch.inference_mode():
345
+ backend_output = impl.forward(
346
+ layer,
347
+ query_vllm,
348
+ kv_c_vllm,
349
+ k_pe_vllm,
350
+ kv_cache,
351
+ metadata,
352
+ output=out_buffer,
353
+ )
354
355
assert backend_output.shape == sdpa_reference.shape
356
assert backend_output.dtype == sdpa_reference.dtype
tests/v1/attention/utils.py
+4
@@ -181,6 +181,7 @@ def create_vllm_config(
181
max_num_batched_tokens: int = 8192,
182
enable_chunked_prefill: bool = True,
183
add_mock_model_methods: bool = True,
184
+ hf_config_override: Optional[dict] = None,
185
) -> VllmConfig:
186
"""Create a VllmConfig for testing with reasonable defaults."""
187
@@ -235,6 +236,9 @@ def create_vllm_config(
236
lambda self, i: 1.0 / model_config.get_head_size() ** 0.5, model_config
237
)
238
239
+ if hf_config_override:
240
+ model_config.hf_config.update(hf_config_override)
241
+
242
return VllmConfig(
243
model_config=model_config,
244
cache_config=cache_config,
vllm/attention/ops/flashmla.py
+35
-9
@@ -31,21 +31,47 @@ else:
31
_flashmla_extension_C_AVAILABLE = False
32
33
34
-def is_flashmla_supported() -> tuple[bool, Optional[str]]:
34
+def _is_flashmla_available() -> tuple[bool, Optional[str]]:
35
+ if not _flashmla_C_AVAILABLE:
36
+ return (
37
+ False,
38
+ "vllm._flashmla_C is not available, likely was not "
39
+ "compiled due to insufficient nvcc version or a supported arch "
40
+ "was not in the list of target arches to compile for.",
41
+ )
42
+ if not _flashmla_extension_C_AVAILABLE:
43
+ return (
44
+ False,
45
+ "vllm._flashmla_extension_C is not available, likely "
46
+ "was not compiled due to a build error.",
47
+ )
48
+
49
+ return True, None
50
+
51
+
52
+def is_flashmla_dense_supported() -> tuple[bool, Optional[str]]:
53
"""
54
Return: is_supported_flag, unsupported_reason (optional).
55
"""
38
- if not current_platform.is_cuda():
39
- return False, "FlashMLA is only supported on CUDA devices."
56
+ is_availble, maybe_reason = _is_flashmla_available()
57
+ if not is_availble:
58
+ return False, maybe_reason
59
if current_platform.get_device_capability()[0] != 9:
41
- return False, "FlashMLA is only supported on Hopper devices."
42
- if not _flashmla_C_AVAILABLE:
60
+ return False, "FlashMLA Dense is only supported on Hopper devices."
61
+ return True, None
62
+
63
+
64
+def is_flashmla_sparse_supported() -> tuple[bool, Optional[str]]:
65
+ """
66
+ Return: is_supported_flag, unsupported_reason (optional).
67
+ """
68
+ is_availble, maybe_reason = _is_flashmla_available()
69
+ if not is_availble:
70
+ return False, maybe_reason
71
+ if current_platform.get_device_capability()[0] not in (9, 10):
72
return (
73
False,
45
- "vllm._flashmla_C is not available, likely was not "
46
- "compiled due to insufficient nvcc version or a supported arch "
47
- "(only sm90a currently) was not in the list of target arches to "
48
- "compile for.",
74
+ "FlashMLA Sparse is only supported on Hopper and Blackwell devices.",
75
)
76
return True, None
77
vllm/platforms/cuda.py
+4
-4
@@ -146,11 +146,11 @@ class CudaPlatformBase(Platform):
146
use_cutlass_mla = envs.VLLM_ATTENTION_BACKEND == "CUTLASS_MLA"
147
use_flashinfer_mla = envs.VLLM_ATTENTION_BACKEND == "FLASHINFER_MLA"
148
149
- from vllm.attention.ops.flashmla import is_flashmla_supported
149
+ from vllm.attention.ops.flashmla import is_flashmla_dense_supported
150
151
if (
152
use_flashmla
153
- and is_flashmla_supported()[0]
153
+ and is_flashmla_dense_supported()[0]
154
and cache_config.block_size != 64
155
):
156
cache_config.block_size = 64
@@ -256,7 +256,7 @@ class CudaPlatformBase(Platform):
256
"Set VLLM_USE_V1=1 to enable them."
257
)
258
259
- from vllm.attention.ops.flashmla import is_flashmla_supported
259
+ from vllm.attention.ops.flashmla import is_flashmla_dense_supported
260
from vllm.attention.utils.fa_utils import flash_attn_supports_mla
261
262
if use_sparse:
@@ -277,7 +277,7 @@ class CudaPlatformBase(Platform):
277
and block_size in [32, 64]
278
)
279
use_flashmla = selected_backend == _Backend.FLASHMLA or (
280
- selected_backend is None and is_flashmla_supported()[0]
280
+ selected_backend is None and is_flashmla_dense_supported()[0]
281
)
282
use_flashattn = selected_backend == _Backend.FLASH_ATTN_MLA or (
283
selected_backend is None and flash_attn_supports_mla()
vllm/v1/attention/backends/mla/flashmla.py
+2
-2
@@ -10,7 +10,7 @@ from vllm.attention.backends.abstract import AttentionLayer, AttentionType
10
from vllm.attention.ops.flashmla import (
11
flash_mla_with_kvcache,
12
get_mla_metadata,
13
- is_flashmla_supported,
13
+ is_flashmla_dense_supported,
14
)
15
from vllm.config import VllmConfig
16
from vllm.logger import init_logger
@@ -177,7 +177,7 @@ class FlashMLAImpl(MLACommonImpl[FlashMLAMetadata]):
177
**mla_args,
178
)
179
180
- is_supported, reason = is_flashmla_supported()
180
+ is_supported, reason = is_flashmla_dense_supported()
181
assert is_supported, reason
182
183
unsupported_features = [alibi_slopes, sliding_window, logits_soft_cap]
vllm/v1/attention/backends/mla/flashmla_sparse.py
-37
@@ -1,6 +1,5 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-import math
3
from dataclasses import dataclass
4
from typing import TYPE_CHECKING, ClassVar, Optional
5
@@ -51,12 +50,6 @@ structured as:
50
"""
51
52
54
-def _lse2_to_lse(lse_base2: torch.Tensor) -> torch.Tensor:
55
- # Convert base-2 LSE to natural-log LSE
56
- # Keep FP32 for numerical stability during the merge.
57
- return lse_base2.to(torch.float32) * math.log(2.0)
58
-
59
-
53
class FlashMLASparseBackend(AttentionBackend):
54
accept_output_buffer: bool = True
55
@@ -100,36 +93,6 @@ class FlashMLASparseBackend(AttentionBackend):
93
return [576]
94
95
103
-@dataclass
104
-class MLASparsePrefillMetadata:
105
- # NOTE(Chen): not call it "FlashMLASparsePrefillMetadata" because
106
- # the kernel is not from flashmla
107
- block_table: torch.Tensor
108
- has_context: bool = False
109
- context_lens: Optional[torch.Tensor] = None
110
-
111
-
112
-@dataclass
113
-class FlashMLASparseDecodeAndContextMetadata:
114
- scheduler_metadata: torch.Tensor = None
115
- num_splits: torch.Tensor = None
116
- cache_lens: torch.Tensor = None
117
- prefill_context_lengths: Optional[torch.Tensor] = None
118
- prefill_new_k_start_locs: Optional[torch.Tensor] = None
119
- dummy_block_table: torch.Tensor = None
120
-
121
- def filter_prefill_indices(
122
- self, indices: torch.Tensor
123
- ) -> tuple[torch.Tensor, torch.Tensor]:
124
- assert self.prefill_context_lengths is not None
125
- prefill_context_lengths = self.prefill_context_lengths.unsqueeze(-1)
126
- context_indices = torch.where(indices < prefill_context_lengths, indices, -1)
127
- new_token_indices = torch.where(
128
- indices >= prefill_context_lengths, indices - prefill_context_lengths, -1
129
- )
130
- return context_indices, new_token_indices
131
-
132
-
96
@dataclass
97
class FlashMLASparseMetadata:
98
num_reqs: int