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
[V1] [Hybrid] Support Minimax-Text-01 in V1 (#22151)
Signed-off-by: Thomas Parnell <tpa@zurich.ibm.com>
Thomas Parnell committed
Aug 9, 2025 at 08:08 UTC
6ade99eafa373f5c88eb6b8956daa4c217aa7cda
5 files changed
+234
-42
vllm/model_executor/layers/lightning_attn.py
+1
-1
@@ -532,7 +532,7 @@ def _linear_attn_decode_kernel(
532
pid_d = tl.program_id(2) # dimension block index
533
534
# Load slot index for the current batch
535
- slot_id = tl.load(slot_idx + pid_b)
535
+ slot_id = tl.load(slot_idx + pid_b).to(tl.int64)
536
537
# Skip if slot_id is -1 (padding)
538
if slot_id == -1:
vllm/model_executor/layers/mamba/mamba_utils.py
+11
@@ -5,6 +5,17 @@ from vllm.distributed import divide
5
6
class MambaStateShapeCalculator:
7
8
+ @classmethod
9
+ def linear_attention_state_shape(
10
+ cls,
11
+ num_heads: int,
12
+ tp_size: int,
13
+ head_dim: int,
14
+ ) -> tuple[tuple[int, int, int], ...]:
15
+
16
+ state_shape = (num_heads // tp_size, head_dim, head_dim)
17
+ return (state_shape, )
18
+
19
@classmethod
20
def mamba1_state_shape(
21
cls,
vllm/model_executor/models/minimax_text_01.py
+152
-40
@@ -14,8 +14,9 @@ from einops import rearrange
14
from torch import nn
15
from transformers.configuration_utils import PretrainedConfig
16
17
+from vllm import envs
18
from vllm.attention import Attention, AttentionMetadata
18
-from vllm.config import CacheConfig, VllmConfig
19
+from vllm.config import CacheConfig, VllmConfig, get_current_vllm_config
20
from vllm.distributed.communication_op import tensor_model_parallel_all_reduce
21
from vllm.distributed.parallel_state import (
22
get_pp_group, get_tensor_model_parallel_rank,
@@ -33,6 +34,9 @@ from vllm.model_executor.layers.linear import (ColumnParallelLinear,
34
ReplicatedLinear,
35
RowParallelLinear)
36
from vllm.model_executor.layers.logits_processor import LogitsProcessor
37
+from vllm.model_executor.layers.mamba.abstract import MambaBase
38
+from vllm.model_executor.layers.mamba.mamba_utils import (
39
+ MambaStateShapeCalculator)
40
from vllm.model_executor.layers.quantization.base_config import (
41
QuantizationConfig)
42
from vllm.model_executor.layers.vocab_parallel_embedding import (
@@ -41,8 +45,9 @@ from vllm.model_executor.model_loader.weight_utils import default_weight_loader
45
from vllm.model_executor.models.utils import maybe_prefix
46
from vllm.model_executor.sampling_metadata import SamplingMetadata
47
from vllm.sequence import IntermediateTensors
48
+from vllm.v1.attention.backends.linear_attn import LinearAttentionMetadata
49
45
-from .interfaces import HasInnerState, IsHybrid, SupportsV0Only
50
+from .interfaces import HasInnerState, IsHybrid
51
from .minimax_cache import MinimaxCacheManager, MinimaxCacheParams
52
from .utils import PPMissingLayer, is_pp_missing_parameter, make_layers
53
@@ -327,7 +332,17 @@ class MiniMaxText01LinearKernel:
332
return rearrange(output.squeeze(0), "h n d -> n (h d)")
333
334
330
-class MiniMaxText01LinearAttention(nn.Module):
335
+class MiniMaxText01LinearAttention(nn.Module, MambaBase):
336
+
337
+ @property
338
+ def mamba_type(self) -> str:
339
+ return "linear_attention"
340
+
341
+ def get_state_shape(self) -> tuple[tuple[int, ...], tuple[int, ...]]:
342
+ return MambaStateShapeCalculator.linear_attention_state_shape(
343
+ num_heads=self.num_heads,
344
+ tp_size=self.tp_size,
345
+ head_dim=self.head_dim)
346
347
def __init__(
348
self,
@@ -359,6 +374,7 @@ class MiniMaxText01LinearAttention(nn.Module):
374
self.tp_heads = self.total_num_heads // self.tp_size
375
self.qkv_size = self.num_heads * self.head_dim
376
self.tp_hidden = self.head_dim * self.tp_heads
377
+ self.prefix = prefix
378
379
self.qkv_proj = ColumnParallelLinear(
380
hidden_size,
@@ -397,6 +413,12 @@ class MiniMaxText01LinearAttention(nn.Module):
413
self.tp_heads:(self.tp_rank + 1) *
414
self.tp_heads].contiguous()
415
416
+ if envs.VLLM_USE_V1:
417
+ compilation_config = get_current_vllm_config().compilation_config
418
+ if prefix in compilation_config.static_forward_context:
419
+ raise ValueError(f"Duplicate layer name: {prefix}")
420
+ compilation_config.static_forward_context[prefix] = self
421
+
422
@staticmethod
423
def weight_direct_load(param: torch.Tensor,
424
loaded_weight: torch.Tensor) -> None:
@@ -434,13 +456,14 @@ class MiniMaxText01LinearAttention(nn.Module):
456
break
457
if _prefill_idx >= len(state_indices_tensor):
458
break
437
- _start = attn_metadata.query_start_loc[_prefill_idx]
438
- _end = attn_metadata.query_start_loc[_prefill_idx + 1]
439
- slot_id = state_indices_tensor[_prefill_idx]
459
+ # prefills are packed at end of batch in V1
460
+ offset = attn_metadata.num_decode_tokens if envs.VLLM_USE_V1 else 0
461
+ _start = attn_metadata.query_start_loc[offset + _prefill_idx]
462
+ _end = attn_metadata.query_start_loc[offset + _prefill_idx + 1]
463
+ slot_id = state_indices_tensor[offset + _prefill_idx]
464
qs = q[_start:_end].transpose(0, 1).contiguous()
465
ks = k[_start:_end].transpose(0, 1).contiguous()
466
vs = v[_start:_end].transpose(0, 1).contiguous()
443
- slot_id = state_indices_tensor[_prefill_idx]
467
slice_layer_cache = kv_cache[slot_id, ...]
468
469
out_slice = MiniMaxText01LinearKernel.jit_linear_forward_prefix(
@@ -453,9 +476,13 @@ class MiniMaxText01LinearAttention(nn.Module):
476
layer_idx=self.layer_idx)
477
hidden.append(out_slice.contiguous())
478
if attn_metadata.num_decode_tokens > 0:
456
- hidden.append(
457
- self._decode_infer(q, k, v, kv_cache, state_indices_tensor,
458
- attn_metadata))
479
+ hidden_decode = self._decode_infer(q, k, v, kv_cache,
480
+ state_indices_tensor,
481
+ attn_metadata)
482
+ if envs.VLLM_USE_V1:
483
+ hidden.insert(0, hidden_decode)
484
+ else:
485
+ hidden.append(hidden_decode)
486
487
if not hidden:
488
return torch.empty((0, q.size(-1)), device=q.device, dtype=q.dtype)
@@ -465,11 +492,17 @@ class MiniMaxText01LinearAttention(nn.Module):
492
493
def _decode_infer(self, q, k, v, kv_cache, state_indices_tensor,
494
attn_metadata):
468
- q = q[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
469
- k = k[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
470
- v = v[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
471
- slot_id = state_indices_tensor[getattr(attn_metadata, "num_prefills", 0
472
- ):]
495
+ if not envs.VLLM_USE_V1:
496
+ q = q[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
497
+ k = k[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
498
+ v = v[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
499
+ num_prefills = getattr(attn_metadata, "num_prefills", 0)
500
+ slot_id = state_indices_tensor[num_prefills:]
501
+ else:
502
+ q = q[:attn_metadata.num_decode_tokens].unsqueeze(2).contiguous()
503
+ k = k[:attn_metadata.num_decode_tokens].unsqueeze(2).contiguous()
504
+ v = v[:attn_metadata.num_decode_tokens].unsqueeze(2).contiguous()
505
+ slot_id = state_indices_tensor[:attn_metadata.num_decodes]
506
hidden = linear_decode_forward_triton(q, k, v, kv_cache, self.tp_slope,
507
slot_id, 32)
508
return hidden
@@ -483,17 +516,49 @@ class MiniMaxText01LinearAttention(nn.Module):
516
q, k, v = torch.split(qkvact, [self.head_dim] * 3, dim=-1)
517
forward_context = get_forward_context()
518
attn_metadata = forward_context.attn_metadata
486
- kv_cache = kv_caches.minimax_cache
487
- state_indices_tensor = kv_caches.state_indices_tensor
519
+ if envs.VLLM_USE_V1:
520
+ if attn_metadata is not None:
521
+ assert isinstance(attn_metadata, dict)
522
+ attn_metadata = attn_metadata[self.prefix]
523
+ assert isinstance(attn_metadata, LinearAttentionMetadata)
524
+ kv_cache = self.kv_cache[forward_context.virtual_engine][0]
525
+ state_indices_tensor = attn_metadata.state_indices_tensor
526
+
527
+ num_prefills = getattr(attn_metadata, "num_prefills", 0)
528
+ if num_prefills > 0:
529
+ num_decode_tokens = getattr(attn_metadata,
530
+ "num_decode_tokens", 0)
531
+ for prefill_idx in range(num_prefills):
532
+ q_start = attn_metadata.query_start_loc[
533
+ num_decode_tokens + prefill_idx]
534
+ q_end = attn_metadata.query_start_loc[num_decode_tokens
535
+ + prefill_idx +
536
+ 1]
537
+ query_len = q_end - q_start
538
+ context_len = attn_metadata.seq_lens[
539
+ num_decode_tokens + prefill_idx] - query_len
540
+ if context_len == 0:
541
+ block_to_clear = state_indices_tensor[
542
+ num_decode_tokens + prefill_idx]
543
+ kv_cache[block_to_clear, ...] = 0
544
+ else:
545
+ kv_cache = kv_caches.minimax_cache
546
+ state_indices_tensor = kv_caches.state_indices_tensor
547
548
decode_only = getattr(attn_metadata, "num_prefills", 0) == 0
490
- if not decode_only:
491
- hidden = self._prefill_and_mix_infer(q, k, v, kv_cache,
492
- state_indices_tensor,
493
- attn_metadata)
549
+ if attn_metadata is None:
550
+ hidden = torch.empty((q.shape[0], q.shape[1] * q.shape[2]),
551
+ device=q.device,
552
+ dtype=q.dtype)
553
else:
495
- hidden = self._decode_infer(q, k, v, kv_cache,
496
- state_indices_tensor, attn_metadata)
554
+ if not decode_only:
555
+ hidden = self._prefill_and_mix_infer(q, k, v, kv_cache,
556
+ state_indices_tensor,
557
+ attn_metadata)
558
+ else:
559
+ hidden = self._decode_infer(q, k, v, kv_cache,
560
+ state_indices_tensor,
561
+ attn_metadata)
562
563
hidden = self.norm._forward(hidden)
564
gate, _ = self.output_gate(hidden_states)
@@ -541,6 +606,7 @@ class MiniMaxText01Attention(nn.Module):
606
self.scaling = self.head_dim**-0.5
607
self.rope_theta = rope_theta
608
self.sliding_window = sliding_window
609
+ self.prefix = prefix
610
611
self.qkv_proj = QKVParallelLinear(
612
hidden_size,
@@ -575,7 +641,12 @@ class MiniMaxText01Attention(nn.Module):
641
attn_metadata = forward_context.attn_metadata
642
qkv, _ = self.qkv_proj(hidden_states)
643
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
578
- q, k = attn_metadata.rotary_emb(positions, q, k)
644
+ if envs.VLLM_USE_V1:
645
+ if attn_metadata is not None:
646
+ q, k = attn_metadata[f"{self.prefix}.attn"].rotary_emb(
647
+ positions, q, k)
648
+ else:
649
+ q, k = attn_metadata.rotary_emb(positions, q, k)
650
attn_output = self.attn(q, k, v)
651
output, _ = self.o_proj(attn_output)
652
return output
@@ -595,6 +666,7 @@ class MiniMaxText01DecoderLayer(nn.Module):
666
) -> None:
667
self._ilayer = layer_id
668
self._irank = get_tensor_model_parallel_rank()
669
+ self.prefix = prefix
670
super().__init__()
671
672
self.hidden_size = config.hidden_size
@@ -876,8 +948,9 @@ class MiniMaxText01Model(nn.Module):
948
self._dtype = _dummy.dtype
949
del _dummy
950
879
- self.minimax_cache = MinimaxCacheManager(dtype=torch.float32,
880
- cache_shape=self.cache_shape)
951
+ if not envs.VLLM_USE_V1:
952
+ self.minimax_cache = MinimaxCacheManager(
953
+ dtype=torch.float32, cache_shape=self.cache_shape)
954
955
rope_theta = getattr(config, "rope_theta", 10000)
956
head_dim = getattr(config, "head_dim", None)
@@ -944,23 +1017,27 @@ class MiniMaxText01Model(nn.Module):
1017
**kwargs) -> Union[torch.Tensor, IntermediateTensors]:
1018
forward_context = get_forward_context()
1019
attn_metadata = forward_context.attn_metadata
947
- if attn_metadata is None:
1020
+ if not envs.VLLM_USE_V1 and attn_metadata is None:
1021
return None
1022
if "request_ids_to_seq_ids" not in kwargs:
1023
kwargs["request_ids_to_seq_ids"] = {}
1024
if "finished_requests_ids" not in kwargs:
1025
kwargs["finished_requests_ids"] = []
1026
954
- (
955
- minimax_cache_tensors,
956
- state_indices_tensor,
957
- ) = self.minimax_cache.current_run_tensors(**kwargs)
958
- if getattr(attn_metadata, "num_prefills", 0) > 0:
959
- self._clear_prefill_cache(attn_metadata, minimax_cache_tensors,
960
- **kwargs)
1027
+ if not envs.VLLM_USE_V1:
1028
+ (
1029
+ minimax_cache_tensors,
1030
+ state_indices_tensor,
1031
+ ) = self.minimax_cache.current_run_tensors(**kwargs)
1032
+ if getattr(attn_metadata, "num_prefills", 0) > 0:
1033
+ self._clear_prefill_cache(attn_metadata, minimax_cache_tensors,
1034
+ **kwargs)
1035
+
1036
+ minimax_cache_params = MinimaxCacheParams(minimax_cache_tensors,
1037
+ state_indices_tensor)
1038
+ else:
1039
+ minimax_cache_params = None
1040
962
- minimax_cache_params = MinimaxCacheParams(minimax_cache_tensors,
963
- state_indices_tensor)
1041
if get_pp_group().is_first_rank:
1042
if inputs_embeds is None:
1043
hidden_states = self.embed_scale * self.embed_tokens(input_ids)
@@ -973,11 +1050,22 @@ class MiniMaxText01Model(nn.Module):
1050
residual = intermediate_tensors["residual"]
1051
1052
minimax_cache_index = 0
976
- attn_metadata.rotary_emb = self.rotary_emb
1053
+
1054
for i in range(self.start_layer, self.end_layer):
1055
layer = self.layers[i]
1056
+ if attn_metadata is not None:
1057
+ # TODO (tdoublep): this whole thing with the rotary_emb is
1058
+ # weird. we shouldn't be passing it via attn_metadata imo.
1059
+ if envs.VLLM_USE_V1:
1060
+ if isinstance(layer.self_attn, MiniMaxText01Attention):
1061
+ attn_metadata[layer.prefix +
1062
+ ".attn"].rotary_emb = self.rotary_emb
1063
+ else:
1064
+ attn_metadata.rotary_emb = self.rotary_emb
1065
+
1066
_caches = None
980
- if isinstance(layer.self_attn, MiniMaxText01LinearAttention):
1067
+ if not envs.VLLM_USE_V1 and isinstance(
1068
+ layer.self_attn, MiniMaxText01LinearAttention):
1069
current_state_layer = minimax_cache_index
1070
_caches = minimax_cache_params.at_layer_idx(
1071
current_state_layer)
@@ -1002,8 +1090,7 @@ class MiniMaxText01Model(nn.Module):
1090
return hidden_states
1091
1092
1005
-class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid,
1006
- SupportsV0Only):
1093
+class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid):
1094
1095
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
1096
@@ -1321,3 +1408,28 @@ class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid,
1408
1409
load_basic_weight(name, loaded_weight, self)
1410
return loaded_params
1411
+
1412
+ @classmethod
1413
+ def get_mamba_state_shape_from_config(
1414
+ cls,
1415
+ vllm_config: "VllmConfig",
1416
+ use_v1: bool = True,
1417
+ ) -> tuple[tuple[int, ...], ...]:
1418
+ """Calculate shape for MiniMaxText01LinearAttention cache.
1419
+
1420
+ Args:
1421
+ vllm_config: vLLM config
1422
+ use_v1: Get shapes for V1 (or V0)
1423
+
1424
+ Returns:
1425
+ Tuple containing:
1426
+ - state_shape: Shape of the cache
1427
+ """
1428
+ parallel_config = vllm_config.parallel_config
1429
+ hf_config = vllm_config.model_config.hf_config
1430
+
1431
+ return MambaStateShapeCalculator.linear_attention_state_shape(
1432
+ num_heads=hf_config.num_attention_heads,
1433
+ tp_size=parallel_config.tensor_parallel_size,
1434
+ head_dim=hf_config.head_dim,
1435
+ )
vllm/v1/attention/backends/linear_attn.py
new
+67
@@ -0,0 +1,67 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from dataclasses import dataclass
4
+from typing import ClassVar
5
+
6
+import torch
7
+
8
+from vllm.attention.backends.abstract import AttentionBackend
9
+from vllm.config import VllmConfig
10
+from vllm.v1.attention.backends.utils import (AttentionMetadataBuilder,
11
+ CommonAttentionMetadata,
12
+ split_decodes_and_prefills)
13
+from vllm.v1.kv_cache_interface import AttentionSpec, MambaSpec
14
+
15
+
16
+class LinearAttentionBackend(AttentionBackend):
17
+
18
+ @staticmethod
19
+ def get_builder_cls() -> type["LinearAttentionMetadataBuilder"]:
20
+ return LinearAttentionMetadataBuilder
21
+
22
+
23
+@dataclass
24
+class LinearAttentionMetadata:
25
+ num_prefills: int
26
+ num_prefill_tokens: int
27
+ num_decodes: int
28
+ num_decode_tokens: int
29
+ query_start_loc: torch.Tensor
30
+ seq_lens: torch.Tensor
31
+
32
+ state_indices_tensor: torch.Tensor # shape: [batch,]
33
+
34
+
35
+class LinearAttentionMetadataBuilder(
36
+ AttentionMetadataBuilder[LinearAttentionMetadata]):
37
+
38
+ reorder_batch_threshold: ClassVar[int] = 1
39
+
40
+ def __init__(self, kv_cache_spec: AttentionSpec, layer_names: list[str],
41
+ vllm_config: VllmConfig, device: torch.device):
42
+ assert isinstance(kv_cache_spec, MambaSpec)
43
+ self.kv_cache_spec = kv_cache_spec
44
+
45
+ def build(self,
46
+ common_prefix_len: int,
47
+ common_attn_metadata: CommonAttentionMetadata,
48
+ fast_build: bool = False) -> LinearAttentionMetadata:
49
+ query_start_loc = common_attn_metadata.query_start_loc
50
+ seq_lens = common_attn_metadata.seq_lens
51
+
52
+ state_indices_tensor = common_attn_metadata.block_table_tensor[:, 0]
53
+
54
+ num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
55
+ split_decodes_and_prefills(common_attn_metadata,
56
+ decode_threshold=1))
57
+
58
+ attn_metadata = LinearAttentionMetadata(
59
+ num_prefills=num_prefills,
60
+ num_prefill_tokens=num_prefill_tokens,
61
+ num_decodes=num_decodes,
62
+ num_decode_tokens=num_decode_tokens,
63
+ query_start_loc=query_start_loc,
64
+ seq_lens=seq_lens,
65
+ state_indices_tensor=state_indices_tensor,
66
+ )
67
+ return attn_metadata
vllm/v1/attention/backends/mamba_selectors.py
+3
-1
@@ -1,6 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
from vllm.attention.backends.abstract import AttentionBackend
4
+from vllm.v1.attention.backends.linear_attn import LinearAttentionBackend
5
from vllm.v1.attention.backends.mamba1_attn import Mamba1AttentionBackend
6
from vllm.v1.attention.backends.mamba_attn import Mamba2AttentionBackend
7
@@ -8,9 +9,10 @@ from vllm.v1.attention.backends.mamba_attn import Mamba2AttentionBackend
9
def get_mamba_attn_backend(mamba_type: str) -> type[AttentionBackend]:
10
if mamba_type == "mamba1":
11
return Mamba1AttentionBackend
11
-
12
if mamba_type == "mamba2":
13
return Mamba2AttentionBackend
14
+ if mamba_type == "linear_attention":
15
+ return LinearAttentionBackend
16
17
raise NotImplementedError(f"Mamba Attention type {mamba_type} is not "
18
"supported yet.")