A high-throughput and memory-efficient inference and serving engine for LLMs
[Kernel] Support GLM-5 dimensions for TRT-LLM ragged MLA prefill (#43525)
Signed-off-by: Mohammad Miadh Angkad <176301910+mmangkad@users.noreply.github.com> Signed-off-by: Matthew Bonanni <mbonanni@redhat.com> Co-authored-by: Matthew Bonanni <mbonanni@redhat.com>
Mohammad Miadh Angkad committed
Jun 17, 2026 at 04:49 UTC
9d4dc4ca2fefae1a19648d7989f1dffb351e1b29
10 files changed
+200
-77
docs/design/attention_backends.md
+3
-3
@@ -214,9 +214,9 @@ hardware and configuration.
214
| Backend | Description | Dtypes | Compute Cap. | Notes |
215
| ------- | ----------- | ------ | ------------ | ----- |
216
| `FLASH_ATTN`‡ | FlashAttention varlen (FA2/FA3/FA4) | fp16, bf16 | Any | FA4 on SM100+, FA3 on SM90, FA2 otherwise |
217
-| `TRTLLM_RAGGED` | TensorRT-LLM ragged attention | fp16, bf16 | 10.x | DeepSeek R1 dims only |
218
-| `FLASHINFER` | FlashInfer CUTLASS backend | fp16, bf16 | 10.x | DeepSeek R1 dims only |
219
-| `TOKENSPEED_MLA` | | fp16, bf16 | 10.x | DeepSeek R1 dims only |
217
+| `TRTLLM_RAGGED` | TensorRT-LLM ragged attention | fp16, bf16 | 10.x | (qk_nope_head_dim=128, qk_rope_head_dim=64, v_head_dim=128) or (qk_nope_head_dim=192, qk_rope_head_dim=64, v_head_dim=256) only |
218
+| `FLASHINFER` | FlashInfer CUTLASS backend | fp16, bf16 | 10.x | (qk_nope_head_dim=128, qk_rope_head_dim=64, v_head_dim=128) only |
219
+| `TOKENSPEED_MLA` | | fp16, bf16 | 10.x | (qk_nope_head_dim=128, qk_rope_head_dim=64, v_head_dim=128) only |
220
221
> **‡** Automatic selection tries FlashAttention first. On Blackwell
222
> (SM100), the fallback order is TRT-LLM Ragged, FlashInfer, then
tests/v1/attention/test_mla_backends.py
+10
-2
@@ -765,7 +765,8 @@ def test_backend_correctness(
765
if not backends_to_test:
766
pytest.skip(f"No backends support kv_cache_dtype={kv_cache_dtype}")
767
768
- # Skip prefill backends that can't satisfy capability/deps/R1 constraints.
768
+ # Skip prefill backends that can't satisfy capability/deps/dimension constraints.
769
+ from vllm.v1.attention.backends.mla.prefill.base import MLADimensions
770
from vllm.v1.attention.backends.mla.prefill.selector import (
771
MLAPrefillSelectorConfig,
772
)
@@ -773,7 +774,14 @@ def test_backend_correctness(
774
try:
775
prefill_invalid_reasons = prefill_backend.get_class().validate_configuration(
776
current_platform.get_device_capability(),
776
- MLAPrefillSelectorConfig(dtype=torch.bfloat16, is_r1_compatible=True),
777
+ MLAPrefillSelectorConfig(
778
+ dtype=torch.bfloat16,
779
+ mla_dimensions=MLADimensions(
780
+ qk_nope_head_dim=128,
781
+ qk_rope_head_dim=64,
782
+ v_head_dim=128,
783
+ ),
784
+ ),
785
)
786
except ImportError:
787
prefill_invalid_reasons = ["ImportError"]
tests/v1/attention/test_mla_prefill_registry.py
-2
@@ -16,7 +16,6 @@ class CustomMLAPrefillBackend(MLAPrefillBackend):
16
"""Mock custom MLA prefill backend for testing."""
17
18
supported_dtypes = [torch.bfloat16, torch.float16]
19
- requires_r1_mla_dimensions = False
19
20
@staticmethod
21
def get_name() -> str:
@@ -83,7 +82,6 @@ def test_register_custom_backend_as_decorator():
82
@register_mla_prefill_backend(MLAPrefillBackendEnum.CUSTOM)
83
class DecoratedPrefillBackend(MLAPrefillBackend):
84
supported_dtypes = [torch.bfloat16]
86
- requires_r1_mla_dimensions = False
85
86
@staticmethod
87
def get_name() -> str:
tests/v1/attention/test_mla_prefill_selector.py
+44
-25
@@ -9,12 +9,12 @@ import torch
9
10
from vllm.config import AttentionConfig, ModelConfig, VllmConfig
11
from vllm.platforms.interface import DeviceCapability
12
+from vllm.v1.attention.backends.mla.prefill.base import MLADimensions
13
from vllm.v1.attention.backends.mla.prefill.registry import MLAPrefillBackendEnum
14
from vllm.v1.attention.backends.mla.prefill.selector import (
15
MLAPrefillSelectorConfig,
16
_auto_select_mla_prefill_backend,
17
get_mla_prefill_backend,
17
- is_deepseek_r1_mla_compatible,
18
)
19
20
@@ -149,11 +149,14 @@ class TestAutoSelectMLAPrefillBackend:
149
"""Tests for fallback and error paths in auto-selection."""
150
151
def test_blackwell_falls_back_to_trtllm(self):
152
- vllm_config = _make_vllm_config()
152
capability = DeviceCapability(major=10, minor=0)
153
selector_config = MLAPrefillSelectorConfig(
154
dtype=torch.bfloat16,
156
- is_r1_compatible=is_deepseek_r1_mla_compatible(vllm_config),
155
+ mla_dimensions=MLADimensions(
156
+ qk_nope_head_dim=128,
157
+ qk_rope_head_dim=64,
158
+ v_head_dim=128,
159
+ ),
160
)
161
162
try:
@@ -177,11 +180,14 @@ class TestAutoSelectMLAPrefillBackend:
180
assert backend.get_name() == "TRTLLM_RAGGED"
181
182
def test_all_fail_raises_error(self):
180
- vllm_config = _make_vllm_config()
183
capability = DeviceCapability(major=10, minor=0)
184
selector_config = MLAPrefillSelectorConfig(
185
dtype=torch.bfloat16,
184
- is_r1_compatible=is_deepseek_r1_mla_compatible(vllm_config),
186
+ mla_dimensions=MLADimensions(
187
+ qk_nope_head_dim=128,
188
+ qk_rope_head_dim=64,
189
+ v_head_dim=128,
190
+ ),
191
)
192
193
def mock_get_class(backend_enum): # noqa: ARG001
@@ -201,28 +207,26 @@ class TestAutoSelectMLAPrefillBackend:
207
class TestBackendValidation:
208
"""Tests for backend validation logic."""
209
204
- def test_r1_dimension_requirement(self):
210
+ def test_backend_supported_dimension_validation(self):
211
try:
212
from vllm.v1.attention.backends.mla.prefill.flashinfer import (
213
FlashInferPrefillBackend,
214
)
215
+ from vllm.v1.attention.backends.mla.prefill.trtllm_ragged import (
216
+ TrtllmRaggedPrefillBackend,
217
+ )
218
except ImportError:
210
- pytest.skip("FlashInfer prefill backend not available")
219
+ pytest.skip("MLA prefill backend not available")
220
return
221
213
- assert FlashInferPrefillBackend.requires_r1_mla_dimensions is True
214
-
215
- vllm_config = _make_vllm_config(
216
- model_config=_make_mock_model_config(
217
- qk_nope_head_dim=128,
218
- qk_rope_head_dim=64,
219
- v_head_dim=128,
220
- )
221
- )
222
capability = DeviceCapability(major=10, minor=0)
223
selector_config = MLAPrefillSelectorConfig(
224
dtype=torch.bfloat16,
225
- is_r1_compatible=is_deepseek_r1_mla_compatible(vllm_config),
225
+ mla_dimensions=MLADimensions(
226
+ qk_nope_head_dim=128,
227
+ qk_rope_head_dim=64,
228
+ v_head_dim=128,
229
+ ),
230
)
231
232
with patch.object(FlashInferPrefillBackend, "is_available", return_value=True):
@@ -232,16 +236,13 @@ class TestBackendValidation:
236
)
237
assert len(invalid_reasons) == 0
238
235
- vllm_config_invalid = _make_vllm_config(
236
- model_config=_make_mock_model_config(
239
+ selector_config_invalid = MLAPrefillSelectorConfig(
240
+ dtype=torch.bfloat16,
241
+ mla_dimensions=MLADimensions(
242
qk_nope_head_dim=64,
243
qk_rope_head_dim=64,
244
v_head_dim=128,
240
- )
241
- )
242
- selector_config_invalid = MLAPrefillSelectorConfig(
243
- dtype=torch.bfloat16,
244
- is_r1_compatible=is_deepseek_r1_mla_compatible(vllm_config_invalid),
245
+ ),
246
)
247
248
with patch.object(FlashInferPrefillBackend, "is_available", return_value=True):
@@ -250,7 +251,25 @@ class TestBackendValidation:
251
selector_config_invalid,
252
)
253
assert len(invalid_reasons) == 1
253
- assert "DeepSeek R1 MLA dimensions" in invalid_reasons[0]
254
+ assert "supported MLA dimensions" in invalid_reasons[0]
255
+
256
+ selector_config_glm5 = MLAPrefillSelectorConfig(
257
+ dtype=torch.bfloat16,
258
+ mla_dimensions=MLADimensions(
259
+ qk_nope_head_dim=192,
260
+ qk_rope_head_dim=64,
261
+ v_head_dim=256,
262
+ ),
263
+ )
264
+
265
+ with patch.object(
266
+ TrtllmRaggedPrefillBackend, "is_available", return_value=True
267
+ ):
268
+ invalid_reasons = TrtllmRaggedPrefillBackend.validate_configuration(
269
+ capability,
270
+ selector_config_glm5,
271
+ )
272
+ assert invalid_reasons == []
273
274
275
class TestMLAPrefillBackendParsing:
tools/pre_commit/generate_attention_backend_docs.py
+55
-11
@@ -384,6 +384,49 @@ def parse_mla_prefill_priorities() -> dict[str, list[str]]:
384
return priorities
385
386
387
+def parse_mla_dimensions_call(node: ast.AST) -> str | None:
388
+ """Parse an MLADimensions(...) call into a compact display string."""
389
+ if not isinstance(node, ast.Call):
390
+ return None
391
+
392
+ func = node.func
393
+ if not isinstance(func, ast.Name) or func.id != "MLADimensions":
394
+ return None
395
+
396
+ dimensions: dict[str, int] = {}
397
+ for keyword in node.keywords:
398
+ if (
399
+ keyword.arg is not None
400
+ and isinstance(keyword.value, ast.Constant)
401
+ and isinstance(keyword.value.value, int)
402
+ ):
403
+ dimensions[keyword.arg] = keyword.value.value
404
+
405
+ qk_nope_head_dim = dimensions.get("qk_nope_head_dim")
406
+ qk_rope_head_dim = dimensions.get("qk_rope_head_dim")
407
+ v_head_dim = dimensions.get("v_head_dim")
408
+ if qk_nope_head_dim is None or qk_rope_head_dim is None or v_head_dim is None:
409
+ return None
410
+
411
+ return (
412
+ f"(qk_nope_head_dim={qk_nope_head_dim}, "
413
+ f"qk_rope_head_dim={qk_rope_head_dim}, v_head_dim={v_head_dim})"
414
+ )
415
+
416
+
417
+def parse_supported_mla_dimensions(node: ast.AST | None) -> list[str]:
418
+ """Parse a supported_mla_dimensions class variable."""
419
+ if not isinstance(node, ast.List):
420
+ return []
421
+
422
+ supported_dimensions = []
423
+ for element in node.elts:
424
+ dimensions = parse_mla_dimensions_call(element)
425
+ if dimensions is not None:
426
+ supported_dimensions.append(dimensions)
427
+ return supported_dimensions
428
+
429
+
430
def parse_mla_prefill_backend_file(class_path: str) -> dict[str, Any] | None:
431
"""Parse a single MLA prefill backend file to extract its properties.
432
@@ -409,20 +452,20 @@ def parse_mla_prefill_backend_file(class_path: str) -> dict[str, Any] | None:
452
453
info: dict[str, Any] = {
454
"compute_capability": "Any",
412
- "requires_r1_dims": False,
455
+ "supported_mla_dimensions": [],
456
"dtypes": "fp16, bf16", # Default from base class
457
}
458
459
# Parse class variables
460
for item in class_node.body:
418
- if isinstance(item, ast.Assign):
419
- for target in item.targets:
420
- if (
421
- isinstance(target, ast.Name)
422
- and target.id == "requires_r1_mla_dimensions"
423
- and isinstance(item.value, ast.Constant)
424
- ):
425
- info["requires_r1_dims"] = item.value.value
461
+ if (
462
+ isinstance(item, ast.AnnAssign)
463
+ and isinstance(item.target, ast.Name)
464
+ and item.target.id == "supported_mla_dimensions"
465
+ ):
466
+ info["supported_mla_dimensions"] = parse_supported_mla_dimensions(
467
+ item.value
468
+ )
469
470
# Parse supported_dtypes class variable
471
if (
@@ -515,8 +558,9 @@ def parse_mla_prefill_backends() -> list[dict[str, Any]]:
558
marker = "‡"
559
560
notes = ""
518
- if backend_info.get("requires_r1_dims"):
519
- notes = "DeepSeek R1 dims only"
561
+ supported_mla_dimensions = backend_info.get("supported_mla_dimensions", [])
562
+ if supported_mla_dimensions:
563
+ notes = " or ".join(supported_mla_dimensions) + " only"
564
elif backend_name == "FLASH_ATTN":
565
notes = "FA4 on SM100+, FA3 on SM90, FA2 otherwise"
566
vllm/v1/attention/backends/mla/prefill/base.py
+23
-4
@@ -3,6 +3,7 @@
3
"""Abstract base class for MLA prefill backends."""
4
5
from abc import ABC, abstractmethod
6
+from dataclasses import dataclass
7
from typing import TYPE_CHECKING, ClassVar
8
9
import torch
@@ -19,6 +20,20 @@ if TYPE_CHECKING:
20
)
21
22
23
+@dataclass(frozen=True, kw_only=True)
24
+class MLADimensions:
25
+ qk_nope_head_dim: int
26
+ qk_rope_head_dim: int
27
+ v_head_dim: int
28
+
29
+ def __str__(self) -> str:
30
+ return (
31
+ f"(qk_nope_head_dim={self.qk_nope_head_dim}, "
32
+ f"qk_rope_head_dim={self.qk_rope_head_dim}, "
33
+ f"v_head_dim={self.v_head_dim})"
34
+ )
35
+
36
+
37
class MLAPrefillBackend(ABC):
38
"""Abstract base class for MLA prefill backends."""
39
@@ -26,7 +41,7 @@ class MLAPrefillBackend(ABC):
41
torch.float16,
42
torch.bfloat16,
43
]
29
- requires_r1_mla_dimensions: ClassVar[bool] = False
44
+ supported_mla_dimensions: ClassVar[list[MLADimensions]] = []
45
46
@staticmethod
47
@abstractmethod
@@ -71,10 +86,14 @@ class MLAPrefillBackend(ABC):
86
if not cls.is_available():
87
invalid_reasons.append("required dependencies not available")
88
74
- if cls.requires_r1_mla_dimensions and not selector_config.is_r1_compatible:
89
+ if (
90
+ cls.supported_mla_dimensions
91
+ and selector_config.mla_dimensions not in cls.supported_mla_dimensions
92
+ ):
93
+ supported = ", ".join(str(dims) for dims in cls.supported_mla_dimensions)
94
invalid_reasons.append(
76
- "model does not have DeepSeek R1 MLA dimensions "
77
- "(qk_nope_head_dim=128, qk_rope_head_dim=64, v_head_dim=128)"
95
+ "Model does not have supported MLA dimensions "
96
+ f"(got {selector_config.mla_dimensions}; supported: {supported})"
97
)
98
99
return invalid_reasons
vllm/v1/attention/backends/mla/prefill/flashinfer.py
+12
-3
@@ -2,12 +2,15 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""FlashInfer backend for MLA prefill."""
4
5
-from typing import TYPE_CHECKING
5
+from typing import TYPE_CHECKING, ClassVar
6
7
import torch
8
9
import vllm.envs as envs
10
-from vllm.v1.attention.backends.mla.prefill.base import MLAPrefillBackend
10
+from vllm.v1.attention.backends.mla.prefill.base import (
11
+ MLADimensions,
12
+ MLAPrefillBackend,
13
+)
14
from vllm.v1.attention.backends.utils import (
15
PerLayerParameters,
16
get_per_layer_parameters,
@@ -33,7 +36,13 @@ _DEFAULT_NUM_CHUNKS = 32
36
class FlashInferPrefillBackend(MLAPrefillBackend):
37
"""FlashInfer backend for MLA prefill."""
38
36
- requires_r1_mla_dimensions = True
39
+ supported_mla_dimensions: ClassVar[list[MLADimensions]] = [
40
+ MLADimensions(
41
+ qk_nope_head_dim=128,
42
+ qk_rope_head_dim=64,
43
+ v_head_dim=128,
44
+ ),
45
+ ]
46
47
@staticmethod
48
def get_name() -> str:
vllm/v1/attention/backends/mla/prefill/selector.py
+24
-21
@@ -13,6 +13,7 @@ import torch
13
14
from vllm.logger import init_logger
15
from vllm.platforms.interface import DeviceCapability
16
+from vllm.v1.attention.backends.mla.prefill.base import MLADimensions
17
from vllm.v1.attention.backends.mla.prefill.registry import MLAPrefillBackendEnum
18
19
if TYPE_CHECKING:
@@ -31,24 +32,17 @@ class MLAPrefillSelectorConfig(NamedTuple):
32
"""
33
34
dtype: torch.dtype
34
- is_r1_compatible: bool
35
-
36
-
37
-def is_deepseek_r1_mla_compatible(vllm_config: "VllmConfig") -> bool:
38
- """Check if model has DeepSeek R1 compatible MLA dimensions.
35
+ mla_dimensions: MLADimensions = MLADimensions(
36
+ qk_nope_head_dim=0,
37
+ qk_rope_head_dim=0,
38
+ v_head_dim=0,
39
+ )
40
40
- DeepSeek R1 MLA dimensions are:
41
- - qk_nope_head_dim = 128
42
- - qk_rope_head_dim = 64
43
- - v_head_dim = 128
44
- """
45
- if vllm_config.model_config is None:
46
- return False
47
- hf_text_config = vllm_config.model_config.hf_text_config
48
- qk_nope_head_dim = getattr(hf_text_config, "qk_nope_head_dim", 1)
49
- qk_rope_head_dim = getattr(hf_text_config, "qk_rope_head_dim", 1)
50
- v_head_dim = getattr(hf_text_config, "v_head_dim", 1)
51
- return qk_nope_head_dim == 128 and qk_rope_head_dim == 64 and v_head_dim == 128
41
+ def __repr__(self):
42
+ return (
43
+ f"MLAPrefillSelectorConfig(dtype={self.dtype}, "
44
+ f"mla_dimensions={self.mla_dimensions})"
45
+ )
46
47
48
def _get_mla_prefill_backend_priorities(
@@ -101,10 +95,19 @@ def get_mla_prefill_backend(
95
96
attention_config = vllm_config.attention_config
97
104
- selector_config = MLAPrefillSelectorConfig(
105
- dtype=vllm_config.model_config.dtype,
106
- is_r1_compatible=is_deepseek_r1_mla_compatible(vllm_config),
107
- )
98
+ model_config = vllm_config.model_config
99
+ if model_config is None:
100
+ selector_config = MLAPrefillSelectorConfig(dtype=torch.get_default_dtype())
101
+ else:
102
+ hf_text_config = model_config.hf_text_config
103
+ selector_config = MLAPrefillSelectorConfig(
104
+ dtype=model_config.dtype,
105
+ mla_dimensions=MLADimensions(
106
+ qk_nope_head_dim=getattr(hf_text_config, "qk_nope_head_dim", 0),
107
+ qk_rope_head_dim=getattr(hf_text_config, "qk_rope_head_dim", 0),
108
+ v_head_dim=getattr(hf_text_config, "v_head_dim", 0),
109
+ ),
110
+ )
111
112
if attention_config.mla_prefill_backend is not None:
113
selected_backend = attention_config.mla_prefill_backend
vllm/v1/attention/backends/mla/prefill/tokenspeed_mla.py
+12
-3
@@ -2,11 +2,14 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""TokenSpeed CuTe DSL backend for MLA prefill."""
4
5
-from typing import TYPE_CHECKING
5
+from typing import TYPE_CHECKING, ClassVar
6
7
import torch
8
9
-from vllm.v1.attention.backends.mla.prefill.base import MLAPrefillBackend
9
+from vllm.v1.attention.backends.mla.prefill.base import (
10
+ MLADimensions,
11
+ MLAPrefillBackend,
12
+)
13
14
if TYPE_CHECKING:
15
from vllm.config import VllmConfig
@@ -19,7 +22,13 @@ if TYPE_CHECKING:
22
class TokenspeedMLAPrefillBackend(MLAPrefillBackend):
23
"""TokenSpeed CuTe DSL backend for MLA prefill."""
24
22
- requires_r1_mla_dimensions = True
25
+ supported_mla_dimensions: ClassVar[list[MLADimensions]] = [
26
+ MLADimensions(
27
+ qk_nope_head_dim=128,
28
+ qk_rope_head_dim=64,
29
+ v_head_dim=128,
30
+ ),
31
+ ]
32
33
@staticmethod
34
def get_name() -> str:
vllm/v1/attention/backends/mla/prefill/trtllm_ragged.py
+17
-3
@@ -2,12 +2,15 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""TRT-LLM Ragged backend for MLA prefill."""
4
5
-from typing import TYPE_CHECKING
5
+from typing import TYPE_CHECKING, ClassVar
6
7
import torch
8
9
import vllm.envs as envs
10
-from vllm.v1.attention.backends.mla.prefill.base import MLAPrefillBackend
10
+from vllm.v1.attention.backends.mla.prefill.base import (
11
+ MLADimensions,
12
+ MLAPrefillBackend,
13
+)
14
from vllm.v1.worker.workspace import current_workspace_manager
15
16
if TYPE_CHECKING:
@@ -21,7 +24,18 @@ if TYPE_CHECKING:
24
class TrtllmRaggedPrefillBackend(MLAPrefillBackend):
25
"""TRT-LLM Ragged backend for MLA prefill."""
26
24
- requires_r1_mla_dimensions = True
27
+ supported_mla_dimensions: ClassVar[list[MLADimensions]] = [
28
+ MLADimensions(
29
+ qk_nope_head_dim=128,
30
+ qk_rope_head_dim=64,
31
+ v_head_dim=128,
32
+ ),
33
+ MLADimensions(
34
+ qk_nope_head_dim=192,
35
+ qk_rope_head_dim=64,
36
+ v_head_dim=256,
37
+ ),
38
+ ]
39
40
@staticmethod
41
def get_name() -> str: