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 15, 16:00 UTC
[Model][MiniMaxText01] Support MiniMaxText01 model inference (#13454)
Signed-off-by: qscqesze <475517977@qq.com> Co-authored-by: qingjun <qingjun@minimaxi.com> Co-authored-by: qscqesze <475517977@qq.com>
Gerald committed
Apr 2, 2025 at 04:23 UTC
9ef98d527ee1b943afd39d9dcad0ffe089ab241f
11 files changed
+2440
-130
docs/source/models/supported_models.md
+5
@@ -503,6 +503,11 @@ See [this page](#generative-models) for more information on how to use generativ
503
* `xverse/XVERSE-7B-Chat`, `xverse/XVERSE-13B-Chat`, `xverse/XVERSE-65B-Chat`, etc.
504
* ✅︎
505
* ✅︎
506
+- * `MiniMaxText01ForCausalLM`
507
+ * MiniMax-Text
508
+ * `MiniMaxAI/MiniMax-Text-01`, etc.
509
+ *
510
+ * ✅︎
511
- * `Zamba2ForCausalLM`
512
* Zamba2
513
* `Zyphra/Zamba2-7B-instruct`, `Zyphra/Zamba2-2.7B-instruct`, `Zyphra/Zamba2-1.2B-instruct`, etc.
tests/kernels/test_lightning_attn.py
new
+286
@@ -0,0 +1,286 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+
3
+import pytest
4
+import torch
5
+
6
+from vllm.model_executor.layers.lightning_attn import (
7
+ linear_decode_forward_triton)
8
+from vllm.platforms import current_platform
9
+
10
+NUM_HEADS = [4, 8]
11
+HEAD_SIZES = [64]
12
+BATCH_SIZES = [1, 2]
13
+SEQ_LENGTHS = [16]
14
+DTYPES = [torch.float32]
15
+
16
+
17
+def reference_lightning_attention(q, k, v, ed, block_size, kv_history):
18
+ """Reference implementation of lightning attention core algorithm
19
+
20
+ The difference from the main implementation is that this processes
21
+ each step sequentially, instead of using parallelized triton kernels
22
+ """
23
+ B, H, S, D = q.shape
24
+ E = v.shape[-1]
25
+ dtype = q.dtype
26
+ output = torch.zeros((B, H, S, E), dtype=dtype, device=q.device)
27
+
28
+ # Use clone() to ensure an independent copy
29
+ if kv_history is None:
30
+ kv_cache = torch.zeros((B, H, D, E), dtype=dtype, device=q.device)
31
+ else:
32
+ kv_cache = kv_history.clone()
33
+
34
+ # More efficient implementation
35
+ # Convert decay factors to matrix form
36
+ if ed.dim() == 1:
37
+ decay = torch.exp(-ed).view(1, -1, 1, 1)
38
+ else:
39
+ decay = torch.exp(-ed)
40
+
41
+ for b in range(B):
42
+ for step in range(S):
43
+ # Process all heads at once for this position
44
+ q_bs = q[b, :, step] # [H, D]
45
+ k_bs = k[b, :, step] # [H, D]
46
+ v_bs = v[b, :, step] # [H, E]
47
+
48
+ # Calculate KV outer products for all heads
49
+ for h in range(H):
50
+ # Calculate KV outer product
51
+ kv_outer = torch.outer(k_bs[h], v_bs[h])
52
+
53
+ # Update KV cache with decay
54
+ # Note: Using the same order as in the Triton kernel
55
+ kv_cache[b, h] = decay[0, h, 0, 0] * kv_cache[b, h] + kv_outer
56
+
57
+ # Calculate attention output
58
+ output[b, h, step] = torch.matmul(q_bs[h], kv_cache[b, h])
59
+
60
+ # Match the shape returned by the actual implementation
61
+ # The actual implementation returns a tensor of shape [B, H, 2, D, E]
62
+ # where dimension 2 contains both KV and KV history
63
+ kv_reshaped = kv_cache.unsqueeze(2) # [B, H, 1, D, E]
64
+ final_kv_cache = torch.cat([kv_reshaped, kv_reshaped],
65
+ dim=2) # [B, H, 2, D, E]
66
+
67
+ return output, final_kv_cache
68
+
69
+
70
+def reference_linear_decode(q, k, v, kv_caches, slope_rate, slot_idx):
71
+ """Reference implementation: linear attention decode function"""
72
+ B, H, _, D = q.shape
73
+ output = torch.zeros(B, H * D, dtype=q.dtype, device=q.device)
74
+
75
+ # Calculate decay factors once (more efficient)
76
+ decay = torch.exp(-slope_rate).view(-1, 1, 1) # [H, 1, 1]
77
+
78
+ # Process each batch
79
+ for b in range(B):
80
+ slot_id = slot_idx[b].item()
81
+
82
+ # Skip padding positions
83
+ if slot_id == -1:
84
+ continue
85
+
86
+ # Process all heads at once for this batch
87
+ q_b = q[b, :, 0] # [H, D]
88
+ k_b = k[b, :, 0] # [H, D]
89
+ v_b = v[b, :, 0] # [H, D]
90
+
91
+ # Process each attention head
92
+ for h in range(H):
93
+ # Get current query, key and value
94
+ q_bh = q_b[h]
95
+ k_bh = k_b[h]
96
+ v_bh = v_b[h]
97
+
98
+ # Get cache
99
+ kv_cache_old = kv_caches[b, h]
100
+
101
+ # Calculate new key-value outer product
102
+ kv_outer = torch.outer(k_bh, v_bh)
103
+
104
+ # Apply decay and update cache
105
+ kv_new = kv_outer + decay[h, 0, 0] * kv_cache_old
106
+
107
+ # Calculate output
108
+ out_h = torch.matmul(q_bh, kv_new)
109
+
110
+ # Update output and cache
111
+ output[b, h * D:(h + 1) * D] = out_h
112
+ kv_caches[b, h] = kv_new
113
+
114
+ return output
115
+
116
+
117
+@pytest.mark.parametrize("batch_size", BATCH_SIZES)
118
+@pytest.mark.parametrize("num_heads", NUM_HEADS)
119
+@pytest.mark.parametrize("head_size", HEAD_SIZES)
120
+@pytest.mark.parametrize("dtype", DTYPES)
121
+@torch.inference_mode()
122
+def test_linear_decode_forward_triton(
123
+ batch_size: int,
124
+ num_heads: int,
125
+ head_size: int,
126
+ dtype: torch.dtype,
127
+):
128
+ torch.set_default_device("cuda")
129
+ torch.manual_seed(42)
130
+ torch.cuda.manual_seed_all(42)
131
+ current_platform.seed_everything(42)
132
+ base = 0.01
133
+ q = base * torch.randn(batch_size, num_heads, 1, head_size, dtype=dtype)
134
+ k = base * torch.randn(batch_size, num_heads, 1, head_size, dtype=dtype)
135
+ v = base * torch.randn(batch_size, num_heads, 1, head_size, dtype=dtype)
136
+
137
+ kv_caches = base * torch.randn(batch_size,
138
+ num_heads,
139
+ head_size,
140
+ head_size,
141
+ dtype=dtype,
142
+ device="cuda")
143
+
144
+ kv_caches_copy = kv_caches.clone()
145
+
146
+ slope_rate = torch.zeros(num_heads, device="cuda")
147
+ for h in range(num_heads):
148
+ slope_rate[h] = 0.1 * (h + 1)
149
+
150
+ slot_idx = torch.arange(batch_size, device="cuda")
151
+
152
+ triton_output = linear_decode_forward_triton(q, k, v, kv_caches,
153
+ slope_rate, slot_idx)
154
+
155
+ reference_output = reference_linear_decode(q, k, v, kv_caches_copy,
156
+ slope_rate, slot_idx)
157
+ torch.testing.assert_close(triton_output,
158
+ reference_output,
159
+ rtol=1e-1,
160
+ atol=1e-1)
161
+ torch.testing.assert_close(kv_caches, kv_caches_copy, rtol=1e-1, atol=1e-1)
162
+
163
+ assert triton_output.shape == (batch_size, num_heads * head_size)
164
+
165
+
166
+@pytest.mark.parametrize("num_heads", NUM_HEADS)
167
+@pytest.mark.parametrize("head_size", HEAD_SIZES)
168
+@pytest.mark.parametrize("dtype", DTYPES)
169
+@torch.inference_mode()
170
+def test_linear_decode_forward_triton_with_padding(
171
+ num_heads: int,
172
+ head_size: int,
173
+ dtype: torch.dtype,
174
+):
175
+ torch.set_default_device("cuda")
176
+ torch.manual_seed(42)
177
+ torch.cuda.manual_seed_all(42)
178
+ current_platform.seed_everything(42)
179
+
180
+ batch_size = 4
181
+ base = 0.01
182
+ q = base * torch.randn(batch_size, num_heads, 1, head_size, dtype=dtype)
183
+ k = base * torch.randn(batch_size, num_heads, 1, head_size, dtype=dtype)
184
+ v = base * torch.randn(batch_size, num_heads, 1, head_size, dtype=dtype)
185
+
186
+ kv_caches = base * torch.randn(batch_size,
187
+ num_heads,
188
+ head_size,
189
+ head_size,
190
+ dtype=dtype,
191
+ device="cuda")
192
+
193
+ kv_caches_copy = kv_caches.clone()
194
+
195
+ slope_rate = torch.zeros(num_heads, device="cuda")
196
+ for h in range(num_heads):
197
+ slope_rate[h] = 0.1 * (h + 1)
198
+
199
+ slot_idx = torch.tensor([0, 1, -1, 2], device="cuda")
200
+
201
+ triton_output = linear_decode_forward_triton(q, k, v, kv_caches,
202
+ slope_rate, slot_idx)
203
+
204
+ reference_output = reference_linear_decode(q, k, v, kv_caches_copy,
205
+ slope_rate, slot_idx)
206
+
207
+ padding_mask = (slot_idx
208
+ != -1).unsqueeze(1).expand(-1, num_heads * head_size)
209
+
210
+ triton_masked = triton_output[padding_mask]
211
+ reference_masked = reference_output[padding_mask]
212
+
213
+ atol, rtol = 1.5e-1, 1.5e-1
214
+
215
+ valid_indices = slot_idx != -1
216
+
217
+ for i in range(batch_size):
218
+ if valid_indices[i] > 0:
219
+ torch.testing.assert_close(kv_caches[i],
220
+ kv_caches_copy[i],
221
+ rtol=rtol,
222
+ atol=atol)
223
+
224
+ torch.testing.assert_close(triton_masked,
225
+ reference_masked,
226
+ rtol=rtol,
227
+ atol=atol)
228
+
229
+ assert triton_output.shape == (batch_size, num_heads * head_size)
230
+
231
+
232
+@pytest.mark.parametrize("batch_size", BATCH_SIZES)
233
+@pytest.mark.parametrize("num_heads", NUM_HEADS)
234
+@pytest.mark.parametrize("head_size", HEAD_SIZES)
235
+@pytest.mark.parametrize("seq_len", SEQ_LENGTHS)
236
+@pytest.mark.parametrize("dtype", DTYPES)
237
+@torch.inference_mode()
238
+def test_lightning_attention_reference(
239
+ batch_size: int,
240
+ num_heads: int,
241
+ head_size: int,
242
+ seq_len: int,
243
+ dtype: torch.dtype,
244
+):
245
+ torch.set_default_device("cuda")
246
+ torch.manual_seed(42)
247
+ torch.cuda.manual_seed_all(42)
248
+ current_platform.seed_everything(42)
249
+
250
+ base = 0.01
251
+ q = base * torch.randn(
252
+ batch_size, num_heads, seq_len, head_size, dtype=dtype)
253
+ k = base * torch.randn(
254
+ batch_size, num_heads, seq_len, head_size, dtype=dtype)
255
+ v = base * torch.randn(
256
+ batch_size, num_heads, seq_len, head_size, dtype=dtype)
257
+
258
+ ed = torch.zeros(num_heads, device="cuda")
259
+ for h in range(num_heads):
260
+ ed[h] = 0.1 * (h + 1)
261
+
262
+ kv_history = base * torch.randn(batch_size,
263
+ num_heads,
264
+ head_size,
265
+ head_size,
266
+ dtype=dtype,
267
+ device="cuda")
268
+
269
+ kv_history_clone = kv_history.clone()
270
+
271
+ ref_output, ref_kv_cache = reference_lightning_attention(
272
+ q, k, v, ed, 256, kv_history)
273
+
274
+ from vllm.model_executor.layers.lightning_attn import lightning_attention
275
+ actual_output, actual_kv_cache = lightning_attention(
276
+ q, k, v, ed, 256, kv_history_clone)
277
+
278
+ atol, rtol = 1.5e-1, 1.5e-1
279
+ torch.testing.assert_close(ref_output, actual_output, rtol=rtol, atol=atol)
280
+ torch.testing.assert_close(ref_kv_cache,
281
+ actual_kv_cache,
282
+ rtol=rtol,
283
+ atol=atol)
284
+
285
+ assert ref_output.shape == (batch_size, num_heads, seq_len, head_size)
286
+ assert ref_kv_cache.shape == actual_kv_cache.shape
tests/models/registry.py
+2
@@ -176,6 +176,8 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
176
trust_remote_code=True),
177
"MiniCPM3ForCausalLM": _HfExamplesInfo("openbmb/MiniCPM3-4B",
178
trust_remote_code=True),
179
+ "MiniMaxText01ForCausalLM": _HfExamplesInfo("MiniMaxAI/MiniMax-Text-01",
180
+ trust_remote_code=True),
181
"MistralForCausalLM": _HfExamplesInfo("mistralai/Mistral-7B-Instruct-v0.1"),
182
"MixtralForCausalLM": _HfExamplesInfo("mistralai/Mixtral-8x7B-Instruct-v0.1"), # noqa: E501
183
"QuantMixtralForCausalLM": _HfExamplesInfo("mistral-community/Mixtral-8x22B-v0.1-AWQ"), # noqa: E501
vllm/config.py
+25
-17
@@ -971,26 +971,34 @@ class ModelConfig:
971
return sum(not bc.attention.no_op
972
for bc in block_configs[start:end])
973
else:
974
- # Hybrid model
974
+ # Hybrid model Jamba
975
layers_block_type_value = getattr(self.hf_config,
976
"layers_block_type", None)
977
- if layers_block_type_value is None:
978
- raise ValueError("The model is an hybrid without a "
979
- "layers_block_type in the hf_config, "
980
- "cannot determine the num of "
981
- f"{block_type.value} layers")
982
-
983
- if hasattr(self.hf_text_config,
984
- "model_type") and (self.hf_text_config.model_type
985
- == "zamba2"):
986
- if attn_block_type:
987
- return sum(t == "hybrid"
988
- for t in layers_block_type_value[start:end])
989
- else:
990
- return self.get_num_layers(parallel_config)
977
+ if layers_block_type_value is not None:
978
+ if hasattr(self.hf_text_config,
979
+ "model_type") and (self.hf_text_config.model_type
980
+ == "zamba2"):
981
+ if attn_block_type:
982
+ return sum(t == "hybrid"
983
+ for t in layers_block_type_value[start:end])
984
+ else:
985
+ return self.get_num_layers(parallel_config)
986
+ return sum(t == block_type.value
987
+ for t in layers_block_type_value[start:end])
988
+
989
+ # Hybrid model Minimax
990
+ attn_type_list = getattr(self.hf_config, "attn_type_list", None)
991
+ if attn_type_list:
992
+ return sum(t == 1 for t in attn_type_list[start:end])
993
+
994
+ if layers_block_type_value is None and attn_type_list is None:
995
+ raise ValueError(
996
+ "The model is an hybrid without a"
997
+ "layers_block_type or an attn_type_list in the hf_config,"
998
+ "cannot determine the num of "
999
+ f"{block_type.value} layers")
1000
992
- return sum(t == block_type.value
993
- for t in layers_block_type_value[start:end])
1001
+ return sum(t == 1 for t in attn_type_list[start:end])
1002
1003
def get_multimodal_config(self) -> "MultiModalConfig":
1004
"""
vllm/engine/async_llm_engine.py
+5
-2
@@ -303,8 +303,11 @@ class _AsyncLLMEngine(LLMEngine):
303
ctx.seq_group_metadata_list = seq_group_metadata_list
304
ctx.scheduler_outputs = scheduler_outputs
305
306
- finished_requests_ids = self.scheduler[
307
- virtual_engine].get_and_reset_finished_requests_ids()
306
+ if not scheduler_outputs.is_empty():
307
+ # this will cause mamba_cache/minimax_cache failed
308
+ # to release finished_requests_ids of the last steps
309
+ finished_requests_ids = self.scheduler[
310
+ virtual_engine].get_and_reset_finished_requests_ids()
311
312
# Maybe switch from async mode to sync mode
313
if not allow_async_output_proc and len(ctx.output_queue) > 0:
vllm/model_executor/layers/lightning_attn.py
new
+651
@@ -0,0 +1,651 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+import torch
3
+import triton
4
+import triton.language as tl
5
+from einops import rearrange
6
+
7
+
8
+@triton.jit
9
+def _fwd_diag_kernel(Q, K, V, Out, S, b: tl.constexpr, h: tl.constexpr, n,
10
+ d: tl.constexpr, e: tl.constexpr, BLOCK: tl.constexpr,
11
+ NUM_BLOCK, CBLOCK: tl.constexpr):
12
+ # This kernel computes the diagonal blocks of the attention matrix
13
+ # Each diagonal block represents attention
14
+ # where queries attend to keys in the same block
15
+ off = tl.program_id(0)
16
+ off_bh = off // NUM_BLOCK # batch-head index
17
+ off_block = off % NUM_BLOCK # block index within the sequence
18
+ off_cblock = tl.program_id(1) # sub-block index within a block
19
+
20
+ off_h = off_bh % h # head index
21
+
22
+ # Calculate base offsets for the current batch and head
23
+ qk_offset = off_bh * n * d
24
+ v_offset = off_bh * n * e
25
+ o_offset = off_bh * n * e
26
+
27
+ # Calculate offsets for the current block
28
+ block_offset = off_block * BLOCK
29
+ qk_block_offset = block_offset * d
30
+ v_block_offset = block_offset * e
31
+ o_block_offset = block_offset * e
32
+
33
+ # Calculate offsets for the current sub-block
34
+ cblock_offset = off_cblock * CBLOCK
35
+ q_cblock_offset = cblock_offset * d
36
+ o_cblock_offset = cblock_offset * e
37
+
38
+ # Calculate pointers to the query, key, value, and output tensors
39
+ Q_block_ptr = (Q + qk_offset + qk_block_offset + q_cblock_offset +
40
+ tl.arange(0, CBLOCK)[:, None] * d +
41
+ tl.arange(0, d)[None, :])
42
+ K_trans_block_ptr = (K + qk_offset + qk_block_offset +
43
+ tl.arange(0, CBLOCK)[None, :] * d +
44
+ tl.arange(0, d)[:, None])
45
+ V_block_ptr = (V + v_offset + v_block_offset +
46
+ tl.arange(0, CBLOCK)[:, None] * e +
47
+ tl.arange(0, e)[None, :])
48
+ O_block_ptr = (Out + o_offset + o_block_offset + o_cblock_offset +
49
+ tl.arange(0, CBLOCK)[:, None] * e +
50
+ tl.arange(0, e)[None, :])
51
+
52
+ # Load the decay rate for the current head
53
+ S_block_ptr = S + off_h
54
+ s = tl.load(S_block_ptr)
55
+
56
+ i = off_cblock
57
+ q_index = tl.arange(0, CBLOCK) + i * CBLOCK
58
+
59
+ # Load query values
60
+ q = tl.load(Q_block_ptr,
61
+ mask=block_offset + q_index[:, None] < n,
62
+ other=0.0).to(tl.float32)
63
+
64
+ # Initialize output accumulator
65
+ qkv = tl.zeros([CBLOCK, e], dtype=tl.float32)
66
+
67
+ # Process all sub-blocks up to and
68
+ # including the current one (causal attention)
69
+ for j in range(i + 1):
70
+ kv_index = tl.arange(0, CBLOCK) + j * CBLOCK
71
+ diff = q_index[:, None] - kv_index[None, :]
72
+ s_index = s * diff
73
+ # Apply causal mask: only attend to positions before the current one
74
+ s_index = tl.where(diff >= 0, -s_index, float("-inf"))
75
+ decay = tl.exp(s_index)
76
+
77
+ # Load key and value
78
+ k_trans = tl.load(
79
+ K_trans_block_ptr,
80
+ mask=block_offset + kv_index[None, :] < n,
81
+ other=0.0,
82
+ ).to(tl.float32)
83
+ v = tl.load(
84
+ V_block_ptr,
85
+ mask=block_offset + kv_index[:, None] < n,
86
+ other=0.0,
87
+ ).to(tl.float32)
88
+
89
+ # Compute attention scores and apply decay
90
+ qk = tl.dot(q, k_trans) * decay
91
+
92
+ # Compute weighted values and accumulate
93
+ qkv += tl.dot(qk, v)
94
+
95
+ # Move to the next sub-block
96
+ K_trans_block_ptr += CBLOCK * d
97
+ V_block_ptr += CBLOCK * e
98
+
99
+ # Store the result
100
+ tl.store(
101
+ O_block_ptr,
102
+ qkv.to(O_block_ptr.dtype.element_ty),
103
+ mask=block_offset + q_index[:, None] < n,
104
+ )
105
+
106
+
107
+@triton.jit
108
+def _fwd_kv_parallel(
109
+ K,
110
+ V,
111
+ K_decay,
112
+ KV,
113
+ b: tl.constexpr,
114
+ h: tl.constexpr,
115
+ n,
116
+ d: tl.constexpr,
117
+ e: tl.constexpr,
118
+ BLOCK: tl.constexpr,
119
+ NUM_BLOCK,
120
+ D_FBLOCK: tl.constexpr,
121
+ E_FBLOCK: tl.constexpr,
122
+ NUM_FBLOCK: tl.constexpr,
123
+ CBLOCK: tl.constexpr,
124
+ NUM_CBLOCK: tl.constexpr,
125
+):
126
+ # This kernel computes the key-value outer
127
+ # products for each block in parallel
128
+ off_bh = tl.program_id(0) # batch-head index
129
+ off_block = tl.program_id(1) # block index
130
+
131
+ off_h = off_bh % h # head index
132
+
133
+ block_offset = off_block * BLOCK
134
+
135
+ # Calculate offsets for the current block
136
+ k_block_offset = block_offset * d
137
+ v_block_offset = block_offset * e
138
+ kv_block_offset = off_block * d * e
139
+
140
+ # Calculate base offsets for the current batch and head
141
+ k_offset = off_bh * n * d
142
+ v_offset = off_bh * n * e
143
+ kv_offset = off_bh * NUM_BLOCK * d * e
144
+
145
+ # Calculate pointers to the key, value, and key-value tensors
146
+ K_trans_block_ptr = (K + k_offset + k_block_offset +
147
+ tl.arange(0, CBLOCK)[None, :] * d +
148
+ tl.arange(0, D_FBLOCK)[:, None])
149
+ V_block_ptr = (V + v_offset + v_block_offset +
150
+ tl.arange(0, CBLOCK)[:, None] * e +
151
+ tl.arange(0, E_FBLOCK)[None, :])
152
+ KV_block_ptr = (KV + kv_offset + kv_block_offset +
153
+ tl.arange(0, D_FBLOCK)[:, None] * e +
154
+ tl.arange(0, E_FBLOCK)[None, :])
155
+
156
+ # Load the decay factors for the current head and block
157
+ k_decay_ptr = (K_decay + off_h * BLOCK + tl.arange(0, CBLOCK)[None, :])
158
+
159
+ kv_index = tl.arange(0, CBLOCK)
160
+
161
+ # Initialize the key-value outer product accumulator
162
+ kv = tl.zeros([D_FBLOCK, E_FBLOCK], dtype=tl.float32)
163
+
164
+ # Handle the last block which might be smaller than BLOCK
165
+ if off_block == NUM_BLOCK - 1:
166
+ split_n = n - (NUM_BLOCK - 1) * BLOCK
167
+ else:
168
+ split_n = BLOCK
169
+ left_shift = tl.cdiv(split_n, CBLOCK) * CBLOCK - split_n
170
+ num_blocks = min(tl.cdiv(split_n, CBLOCK), NUM_CBLOCK)
171
+ k_decay_ptr += (NUM_CBLOCK - num_blocks) * CBLOCK
172
+
173
+ # Process all sub-blocks in the current block
174
+ for j in range(num_blocks):
175
+ left_bound = (1 - j) * left_shift
176
+ # Load key and value, handling boundary conditions
177
+ k_trans = tl.load(K_trans_block_ptr - left_shift * d,
178
+ mask=kv_index[None, :] >= left_bound,
179
+ other=0.0)
180
+ v = tl.load(V_block_ptr - left_shift * e,
181
+ mask=kv_index[:, None] >= left_bound,
182
+ other=0.0)
183
+
184
+ # Load decay factor and compute weighted key-value outer product
185
+ k_decay = tl.load(k_decay_ptr)
186
+ kv += tl.dot(k_trans * k_decay, v)
187
+
188
+ # Move to the next sub-block
189
+ K_trans_block_ptr += CBLOCK * d
190
+ V_block_ptr += CBLOCK * e
191
+ k_decay_ptr += CBLOCK
192
+
193
+ # Store the result
194
+ tl.store(KV_block_ptr, kv.to(KV_block_ptr.dtype.element_ty))
195
+
196
+
197
+@triton.jit
198
+def _fwd_kv_reduce(S, KV, KV_HISTORY, b: tl.constexpr, h: tl.constexpr, n,
199
+ d: tl.constexpr, e: tl.constexpr, BLOCK: tl.constexpr,
200
+ NUM_BLOCK, D_FBLOCK: tl.constexpr, E_FBLOCK: tl.constexpr):
201
+ # This kernel reduces the key-value outer products
202
+ # across blocks and updates the KV history
203
+ off_bh = tl.program_id(0) # batch-head index
204
+ off_h = off_bh % h # head index
205
+
206
+ kv_offset = off_bh * NUM_BLOCK * d * e
207
+
208
+ # Calculate pointer to the key-value tensor
209
+ KV_block_ptr = (KV + kv_offset + tl.arange(0, D_FBLOCK)[:, None] * e +
210
+ tl.arange(0, E_FBLOCK)[None, :])
211
+
212
+ # Load the decay rate for the current head
213
+ s_ptrs = S + off_h
214
+ s = tl.load(s_ptrs)
215
+
216
+ # Calculate pointer to the key-value history tensor
217
+ kv_history_offset = off_bh * d * e
218
+ KV_HISTORY_block_ptr = (KV_HISTORY + kv_history_offset +
219
+ tl.arange(0, D_FBLOCK)[:, None] * e +
220
+ tl.arange(0, E_FBLOCK)[None, :])
221
+
222
+ # Load the previous key-value history
223
+ kv_pre = tl.load(KV_HISTORY_block_ptr).to(tl.float32)
224
+
225
+ # Process all blocks in reverse order to compute the prefix sum
226
+ for i in range(NUM_BLOCK):
227
+ block_size = min(n - i * BLOCK, BLOCK)
228
+ # Compute decay factor for the current block
229
+ block_decay = tl.exp(-s.to(tl.float32) * block_size)
230
+
231
+ # Load the current key-value outer product
232
+ kv_cur = tl.load(KV_block_ptr).to(tl.float32)
233
+ # Store the previous key-value history to the current block
234
+ tl.store(KV_block_ptr, kv_pre.to(KV_block_ptr.dtype.element_ty))
235
+
236
+ # Update the key-value history with the current block
237
+ kv_pre = block_decay * kv_pre + kv_cur
238
+ KV_block_ptr += d * e
239
+
240
+ # Store the updated key-value history
241
+ tl.store(KV_HISTORY_block_ptr, kv_pre)
242
+
243
+
244
+@triton.jit
245
+def _fwd_none_diag_kernel(
246
+ Q,
247
+ Out,
248
+ S,
249
+ KV,
250
+ b: tl.constexpr,
251
+ h: tl.constexpr,
252
+ n,
253
+ d: tl.constexpr,
254
+ e: tl.constexpr,
255
+ BLOCK: tl.constexpr,
256
+ NUM_BLOCK,
257
+ E_FBLOCK: tl.constexpr,
258
+ CBLOCK: tl.constexpr,
259
+ NUM_CBLOCK: tl.constexpr,
260
+):
261
+ # This kernel computes the non-diagonal blocks of the attention matrix
262
+ # Each non-diagonal block represents attention
263
+ # where queries attend to keys in different blocks
264
+ off_bh = tl.program_id(0) # batch-head index
265
+ off_h = off_bh % h # head index
266
+
267
+ off_nc = tl.program_id(1)
268
+ off_n = off_nc // NUM_CBLOCK # block index
269
+ off_c = off_nc % NUM_CBLOCK # sub-block index
270
+ off_e = tl.program_id(2) # output feature block index
271
+
272
+ n_offset = off_n * BLOCK
273
+ c_offset = off_c * CBLOCK
274
+ e_offset = off_e * E_FBLOCK
275
+ block_offset = n_offset + c_offset
276
+
277
+ # Calculate offsets for the current batch, head, and block
278
+ q_offset = off_bh * n * d + (n_offset + c_offset) * d
279
+ o_offset = off_bh * n * e + (n_offset + c_offset) * e + e_offset
280
+ kv_offset = off_bh * NUM_BLOCK * d * e + off_n * d * e + e_offset
281
+
282
+ # Calculate pointers to the query, output, and key-value tensors
283
+ Q_block_ptr = (Q + q_offset + tl.arange(0, CBLOCK)[:, None] * d +
284
+ tl.arange(0, d)[None, :])
285
+ O_block_ptr = (Out + o_offset + tl.arange(0, CBLOCK)[:, None] * e +
286
+ tl.arange(0, E_FBLOCK)[None, :])
287
+ KV_block_ptr = (KV + kv_offset + tl.arange(0, d)[:, None] * e +
288
+ tl.arange(0, E_FBLOCK)[None, :])
289
+
290
+ # Load the decay rate for the current head
291
+ S_block_ptr = S + off_h
292
+ s = tl.load(S_block_ptr)
293
+
294
+ c_array = tl.arange(0, CBLOCK)
295
+
296
+ # Load the key-value outer product for the current block
297
+ kv = tl.load(KV_block_ptr).to(tl.float32)
298
+ q_index = block_offset + tl.arange(0, CBLOCK)
299
+
300
+ # Load query values
301
+ q = tl.load(Q_block_ptr, mask=q_index[:, None] < n,
302
+ other=0.).to(tl.float32)
303
+
304
+ # Compute decay factors for the current sub-block
305
+ q_decay = tl.exp(-s.to(tl.float32) * (off_c * CBLOCK + c_array[:, None]))
306
+
307
+ # Compute non-diagonal attention output
308
+ qkv_none_diag = tl.dot(q, kv) * q_decay
309
+
310
+ # Load diagonal attention output (computed by _fwd_diag_kernel)
311
+ qkv_diag = tl.load(O_block_ptr, mask=q_index[:, None] < n,
312
+ other=0.).to(tl.float32)
313
+
314
+ # Combine diagonal and non-diagonal attention outputs
315
+ qkv = qkv_diag + qkv_none_diag
316
+
317
+ # Store the result
318
+ tl.store(O_block_ptr,
319
+ qkv.to(O_block_ptr.dtype.element_ty),
320
+ mask=q_index[:, None] < n)
321
+
322
+
323
+class _attention(torch.autograd.Function):
324
+
325
+ @staticmethod
326
+ def forward(ctx, q, k, v, s, kv_history):
327
+ # Forward pass of the lightning attention algorithm
328
+ q = q.contiguous()
329
+ k = k.contiguous()
330
+ v = v.contiguous()
331
+ s = s.contiguous()
332
+
333
+ # Check CUDA compute capability
334
+ capability = torch.cuda.get_device_capability()
335
+ if capability[0] < 8:
336
+ raise RuntimeError("Flash attention currently only supported",
337
+ "for compute capability >= 80")
338
+
339
+ # Get input dimensions
340
+ b, h, n, d = q.shape
341
+ e = v.shape[-1]
342
+
343
+ # Initialize output tensor
344
+ o = torch.empty((b, h, n, e), dtype=q.dtype, device=q.device)
345
+
346
+ # Set block sizes
347
+ BLOCK = 256
348
+ NUM_BLOCK = triton.cdiv(n, BLOCK)
349
+
350
+ CBLOCK = 32
351
+ NUM_CBLOCK = BLOCK // CBLOCK
352
+ assert BLOCK % CBLOCK == 0, "BLOCK must be a multiple of CBLOCK"
353
+
354
+ # Compute decay factors for keys
355
+ array = torch.arange(0, BLOCK, device=q.device) + 1
356
+ k_decay = torch.exp(-s * (BLOCK - array.reshape(1, -1)))
357
+
358
+ # Step 1: Compute diagonal blocks of attention
359
+ grid = (b * h * NUM_BLOCK, NUM_CBLOCK)
360
+ _fwd_diag_kernel[grid](q,
361
+ k,
362
+ v,
363
+ o,
364
+ s,
365
+ b,
366
+ h,
367
+ n,
368
+ d,
369
+ e,
370
+ BLOCK=BLOCK,
371
+ NUM_BLOCK=NUM_BLOCK,
372
+ CBLOCK=CBLOCK)
373
+
374
+ # Set feature block sizes
375
+ NUM_FBLOCK = 1
376
+ D_FBLOCK = d // NUM_FBLOCK
377
+ assert d % NUM_FBLOCK == 0
378
+ E_FBLOCK = e // NUM_FBLOCK
379
+ assert e % NUM_FBLOCK == 0
380
+
381
+ CBLOCK = 64
382
+ NUM_CBLOCK = BLOCK // CBLOCK
383
+ assert BLOCK % CBLOCK == 0, "BLOCK must be a multiple of CBLOCK"
384
+
385
+ # Step 2: Compute key-value outer products for each block in parallel
386
+ kv = torch.empty((b, h, NUM_BLOCK, d, e),
387
+ dtype=torch.float32,
388
+ device=q.device)
389
+ grid = (b * h, NUM_BLOCK)
390
+ _fwd_kv_parallel[grid](
391
+ k,
392
+ v,
393
+ k_decay,
394
+ kv,
395
+ b,
396
+ h,
397
+ n,
398
+ d,
399
+ e,
400
+ BLOCK=BLOCK,
401
+ NUM_BLOCK=NUM_BLOCK,
402
+ D_FBLOCK=D_FBLOCK,
403
+ E_FBLOCK=E_FBLOCK,
404
+ NUM_FBLOCK=NUM_FBLOCK,
405
+ CBLOCK=CBLOCK,
406
+ NUM_CBLOCK=NUM_CBLOCK,
407
+ )
408
+
409
+ # Step 3: Reduce key-value outer products
410
+ # across blocks and update KV history
411
+ grid = (b * h, NUM_FBLOCK)
412
+ _fwd_kv_reduce[grid](s,
413
+ kv,
414
+ kv_history,
415
+ b,
416
+ h,
417
+ n,
418
+ d,
419
+ e,
420
+ BLOCK=BLOCK,
421
+ NUM_BLOCK=NUM_BLOCK,
422
+ D_FBLOCK=D_FBLOCK,
423
+ E_FBLOCK=E_FBLOCK)
424
+
425
+ # Step 4: Compute non-diagonal blocks of attention
426
+ grid = (b * h, NUM_BLOCK * NUM_CBLOCK)
427
+ _fwd_none_diag_kernel[grid](
428
+ q,
429
+ o,
430
+ s,
431
+ kv,
432
+ b,
433
+ h,
434
+ n,
435
+ d,
436
+ e,
437
+ BLOCK=BLOCK,
438
+ NUM_BLOCK=NUM_BLOCK,
439
+ E_FBLOCK=E_FBLOCK,
440
+ CBLOCK=CBLOCK,
441
+ NUM_CBLOCK=NUM_CBLOCK,
442
+ )
443
+
444
+ # Save tensors for backward pass
445
+ ctx.save_for_backward(q, k, v, s, kv)
446
+ ctx.BLOCK = BLOCK
447
+
448
+ return o, torch.cat([kv, kv_history.unsqueeze(2)], dim=2)
449
+
450
+
451
+# Apply the lightning attention function
452
+lightning_attention_ = _attention.apply
453
+
454
+
455
+def lightning_attention(q, k, v, ed, block_size=256, kv_history=None):
456
+ """
457
+ Apply lightning attention algorithm
458
+ to compute attention efficiently.
459
+
460
+ Args:
461
+ q: Query tensor of shape [batch, heads, seq_len, dim]
462
+ k: Key tensor of shape [batch, heads, seq_len, dim]
463
+ v: Value tensor of shape [batch, heads, seq_len, dim_v]
464
+ ed: Decay rate tensor of shape [heads]
465
+ block_size: Size of blocks for block-sparse attention
466
+ kv_history: Optional key-value history from previous computations
467
+
468
+ Returns:
469
+ output: Attention output
470
+ kv: Updated key-value history
471
+ """
472
+ d = q.shape[-1]
473
+ e = v.shape[-1]
474
+
475
+ if ed.dim() == 1:
476
+ ed = ed.view(1, -1, 1, 1)
477
+
478
+ # Split the computation into chunks for better parallelism
479
+ m = 128 if d >= 128 else 64
480
+ assert d % m == 0, f"Dimension d ({d}) must be divisible by m ({m})"
481
+ arr = [m * i for i in range(d // m + 1)]
482
+ if arr[-1] != d:
483
+ arr.append(d)
484
+ n = len(arr)
485
+ output = 0
486
+
487
+ # Initialize or clone key-value history
488
+ if kv_history is None:
489
+ kv_history = torch.zeros((q.shape[0], q.shape[1], d, e),
490
+ dtype=torch.float32,
491
+ device=q.device)
492
+ else:
493
+ kv_history = kv_history.clone().contiguous()
494
+
495
+ # Process each chunk and accumulate results
496
+ for i in range(n - 1):
497
+ s = arr[i]
498
+ e = arr[i + 1]
499
+ q1 = q[..., s:e]
500
+ k1 = k[..., s:e]
501
+ o, kv = lightning_attention_(q1, k1, v, ed, kv_history)
502
+ output = output + o
503
+ return output, kv
504
+
505
+
506
+@triton.jit
507
+def _linear_attn_decode_kernel(
508
+ q_ptr,
509
+ k_ptr,
510
+ v_ptr,
511
+ kv_cache_ptr,
512
+ slope_rate,
513
+ slot_idx,
514
+ output_ptr,
515
+ D: tl.constexpr,
516
+ qkv_b_stride,
517
+ qkv_h_stride,
518
+ cache_b_stride,
519
+ cache_h_stride,
520
+ cache_d0_stride,
521
+ cache_d1_stride,
522
+ BLOCK_SIZE: tl.constexpr,
523
+):
524
+ """
525
+ Kernel for linear attention decoding with KV cache.
526
+
527
+ This kernel computes attention for a single token using the KV cache.
528
+ """
529
+ pid_b = tl.program_id(0) # batch index
530
+ pid_h = tl.program_id(1) # head index
531
+ pid_d = tl.program_id(2) # dimension block index
532
+
533
+ # Load slot index for the current batch
534
+ slot_id = tl.load(slot_idx + pid_b)
535
+
536
+ # Skip if slot_id is -1 (padding)
537
+ if slot_id == -1:
538
+ return
539
+
540
+ batch_id = pid_b
541
+ head_id = pid_h
542
+
543
+ # Load decay rate for the current head
544
+ ratio = tl.load(slope_rate + pid_h)
545
+
546
+ # Calculate offsets for dimensions
547
+ qk_d_offsets = tl.arange(0, D)
548
+ v_d_offsets = tl.arange(0, BLOCK_SIZE) + pid_d * BLOCK_SIZE
549
+ cache_d_offsets = qk_d_offsets[:, None] * cache_d0_stride + v_d_offsets[
550
+ None, :] * cache_d1_stride
551
+
552
+ # Calculate offsets for the current batch and head
553
+ q_offset = batch_id * qkv_b_stride + head_id * qkv_h_stride
554
+ k_offset = batch_id * qkv_b_stride + head_id * qkv_h_stride
555
+ v_offset = batch_id * qkv_b_stride + head_id * qkv_h_stride
556
+
557
+ cache_offset = slot_id * cache_b_stride + head_id * cache_h_stride
558
+
559
+ # Create masks for loading tensors
560
+ qk_mask = qk_d_offsets < D
561
+ v_mask = v_d_offsets < D
562
+
563
+ # Load query, key, and value tensors
564
+ q = tl.load(q_ptr + q_offset + qk_d_offsets, mask=qk_mask, other=0.0)
565
+ k = tl.load(k_ptr + k_offset + qk_d_offsets, mask=qk_mask, other=0.0)
566
+ v = tl.load(v_ptr + v_offset + v_d_offsets, mask=v_mask, other=0.0)
567
+
568
+ # Compute key-value outer product
569
+ kv_outer = k[:, None] * v[None, :]
570
+ kv_mask = qk_mask[:, None] & v_mask[None, :]
571
+
572
+ # Apply decay to previous KV cache
573
+ ratio = tl.exp(-ratio)
574
+ kv_ptr = kv_cache_ptr + cache_offset + cache_d_offsets
575
+ kv_cache_old = tl.load(kv_ptr, mask=kv_mask, other=0.0)
576
+ kv_outer = kv_outer + ratio * kv_cache_old
577
+
578
+ # Compute attention output
579
+ output = q[:, None].to(tl.float32) * kv_outer
580
+ output = tl.sum(output, axis=0)
581
+
582
+ # Update KV cache and store output
583
+ tl.store(kv_ptr, kv_outer, mask=kv_mask)
584
+ tl.store(output_ptr + q_offset + v_d_offsets, output, mask=v_mask)
585
+
586
+
587
+def linear_decode_forward_triton(
588
+ q: torch.Tensor,
589
+ k: torch.Tensor,
590
+ v: torch.Tensor,
591
+ kv_caches: torch.Tensor,
592
+ slope_rate: torch.Tensor,
593
+ slot_idx: torch.Tensor,
594
+ BLOCK_SIZE: int = 32,
595
+) -> torch.Tensor:
596
+ """
597
+ Perform linear attention decoding using Triton kernels.
598
+
599
+ Args:
600
+ q: Query tensor of shape [B, H, 1, D]
601
+ k: Key tensor of shape [B, H, 1, D]
602
+ v: Value tensor of shape [B, H, 1, D]
603
+ kv_caches: Key-value cache tensor
604
+ slope_rate: Decay rate tensor
605
+ slot_idx: Slot indices for batches
606
+ BLOCK_SIZE: Size of blocks for processing
607
+
608
+ Returns:
609
+ output: Attention output tensor
610
+ """
611
+ B, H, _, D = q.shape
612
+ assert k.shape == (B, H, 1, D)
613
+ assert v.shape == (B, H, 1, D)
614
+
615
+ # Initialize output tensor
616
+ output = torch.empty_like(q)
617
+
618
+ # Set grid dimensions for the kernel
619
+ grid = (B, H, D // BLOCK_SIZE)
620
+
621
+ # Calculate strides for tensors
622
+ qkv_b_stride = q.stride(0)
623
+ qkv_h_stride = q.stride(1)
624
+
625
+ cache_b_stride = kv_caches.stride(0)
626
+ cache_h_stride = kv_caches.stride(1)
627
+ cache_d0_stride = kv_caches.stride(2)
628
+ cache_d1_stride = kv_caches.stride(3)
629
+
630
+ # Launch the kernel
631
+ _linear_attn_decode_kernel[grid](
632
+ q,
633
+ k,
634
+ v,
635
+ kv_caches,
636
+ slope_rate,
637
+ slot_idx,
638
+ output,
639
+ D,
640
+ qkv_b_stride,
641
+ qkv_h_stride,
642
+ cache_b_stride,
643
+ cache_h_stride,
644
+ cache_d0_stride,
645
+ cache_d1_stride,
646
+ BLOCK_SIZE=BLOCK_SIZE,
647
+ )
648
+
649
+ # Reshape output and return
650
+ output = rearrange(output, "b h n d -> b n (h d)")
651
+ return output.squeeze(1).contiguous()
vllm/model_executor/models/constant_size_cache.py
new
+136
@@ -0,0 +1,136 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+from abc import ABC, abstractmethod
3
+from typing import Any, Dict, List, Tuple
4
+
5
+import torch
6
+
7
+from vllm.attention.backends.utils import PAD_SLOT_ID
8
+
9
+
10
+class ConstantSizeCache(ABC):
11
+ """
12
+ Abstract base class for managing constant size caches
13
+ like Mamba and Minimax.
14
+ """
15
+
16
+ def __init__(self, max_batch_size: int):
17
+ # Maps between the request id and a dict that maps between the seq_id
18
+ # and its index inside the cache
19
+ self.cache_indices_mapping: Dict[str, Dict[int, int]] = {}
20
+ self.free_cache_indices = list(range(max_batch_size))
21
+
22
+ @property
23
+ @abstractmethod
24
+ def cache(self) -> Any:
25
+ """Return the underlying cache tensor(s)"""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def _copy_cache(self, from_index: int, to_index: int):
30
+ """Copy cache data from one index to another"""
31
+ pass
32
+
33
+ def current_run_tensors(self, **kwargs) -> Tuple:
34
+ """
35
+ Return the tensors for the current run's conv and ssm state.
36
+ """
37
+ if "seqlen_agnostic_capture_inputs" not in kwargs:
38
+ # We get here only on Prefill/Eager mode runs
39
+ request_ids_to_seq_ids = kwargs["request_ids_to_seq_ids"]
40
+ finished_requests_ids = kwargs["finished_requests_ids"]
41
+
42
+ self._release_finished_requests(finished_requests_ids)
43
+ state_indices = self._prepare_current_run_cache(
44
+ request_ids_to_seq_ids, finished_requests_ids)
45
+
46
+ state_indices_tensor = torch.as_tensor(state_indices,
47
+ dtype=torch.int32,
48
+ device="cuda")
49
+ cache_tensors = self.cache
50
+ else:
51
+ # CUDA graph capturing runs
52
+ cache_tensors, state_indices_tensor = kwargs[
53
+ "seqlen_agnostic_capture_inputs"]
54
+
55
+ return (cache_tensors, state_indices_tensor)
56
+
57
+ def copy_inputs_before_cuda_graphs(self, input_buffers, **kwargs):
58
+ """
59
+ Copy the relevant state_indices into the CUDA graph input buffer
60
+ """
61
+ assert all(
62
+ key in kwargs
63
+ for key in ["request_ids_to_seq_ids", "finished_requests_ids"])
64
+ finished_requests_ids = kwargs["finished_requests_ids"]
65
+ request_ids_to_seq_ids = kwargs["request_ids_to_seq_ids"]
66
+ assert "seqlen_agnostic_capture_inputs" in input_buffers
67
+ _, input_state_indices_buffer = input_buffers[
68
+ "seqlen_agnostic_capture_inputs"]
69
+
70
+ self._release_finished_requests(finished_requests_ids)
71
+ state_indices = self._prepare_current_run_cache(
72
+ request_ids_to_seq_ids, finished_requests_ids)
73
+ cuda_graph_pad_len = input_state_indices_buffer.shape[0] - len(
74
+ state_indices)
75
+ state_indices.extend([PAD_SLOT_ID] * cuda_graph_pad_len)
76
+
77
+ input_state_indices_buffer.copy_(
78
+ torch.as_tensor(state_indices, dtype=torch.int32, device="cuda"))
79
+
80
+ def get_seqlen_agnostic_capture_inputs(self, batch_size: int):
81
+ """
82
+ Provide the CUDA graph capture runs with a buffer in adjusted size.
83
+ The buffer is used to maintain the Cache during the CUDA graph replay
84
+ runs.
85
+ """
86
+ state_indices_tensor = torch.as_tensor([PAD_SLOT_ID] * batch_size,
87
+ dtype=torch.int32,
88
+ device="cuda")
89
+ return (self.cache, state_indices_tensor)
90
+
91
+ def _assign_seq_id_to_cache_index(self, cur_rid: str, seq_id: int,
92
+ finished_requests_ids) -> int:
93
+ """
94
+ Assign (req_id,seq_id) pair to a `destination_index` index, if
95
+ already occupied, move the occupying index to a free index.
96
+ """
97
+ if cur_rid in finished_requests_ids:
98
+ # set as pad, do not allocate destination index
99
+ return PAD_SLOT_ID
100
+ elif cur_rid not in self.cache_indices_mapping:
101
+ destination_index = self.free_cache_indices.pop()
102
+ self.cache_indices_mapping[cur_rid] = {seq_id: destination_index}
103
+ return destination_index
104
+ elif seq_id not in (seq_ids2indices :=
105
+ self.cache_indices_mapping[cur_rid]):
106
+ # parallel sampling , where n > 1, assume prefill have
107
+ # already happened, so we copy the
108
+ # existing cache into the siblings seq_ids caches
109
+ index_exists = next(iter(seq_ids2indices.values()))
110
+ # case of decoding n>1, copy prefill cache to decoding indices
111
+ destination_index = self.free_cache_indices.pop()
112
+ self._copy_cache(from_index=index_exists,
113
+ to_index=destination_index)
114
+ self.cache_indices_mapping[cur_rid][seq_id] = destination_index
115
+ return destination_index
116
+ else:
117
+ return self.cache_indices_mapping[cur_rid][seq_id]
118
+
119
+ def _prepare_current_run_cache(
120
+ self, request_ids_to_seq_ids: Dict[str, list[int]],
121
+ finished_requests_ids: List[str]) -> List[int]:
122
+ return [
123
+ self._assign_seq_id_to_cache_index(req_id, seq_id,
124
+ finished_requests_ids)
125
+ for req_id, seq_ids in request_ids_to_seq_ids.items()
126
+ for seq_id in seq_ids
127
+ ]
128
+
129
+ def _release_finished_requests(self,
130
+ finished_seq_groups_req_ids: List[str]):
131
+ for req_id in finished_seq_groups_req_ids:
132
+ if req_id in self.cache_indices_mapping:
133
+ for seq_id in self.cache_indices_mapping[req_id]:
134
+ self.free_cache_indices.append(
135
+ self.cache_indices_mapping[req_id][seq_id])
136
+ self.cache_indices_mapping.pop(req_id)
vllm/model_executor/models/mamba_cache.py
+21
-111
@@ -1,12 +1,13 @@
1
# SPDX-License-Identifier: Apache-2.0
2
3
from dataclasses import dataclass
4
-from typing import Dict, List, Tuple
4
+from typing import Tuple
5
6
import torch
7
8
from vllm.attention.backends.utils import PAD_SLOT_ID
9
from vllm.config import VllmConfig
10
+from vllm.model_executor.models.constant_size_cache import ConstantSizeCache
11
12
13
@dataclass
@@ -21,7 +22,7 @@ class MambaCacheParams:
22
self.state_indices_tensor)
23
24
24
-class MambaCacheManager:
25
+class MambaCacheManager(ConstantSizeCache):
26
27
def __init__(self, vllm_config: VllmConfig, dtype: torch.dtype,
28
num_mamba_layers: int, conv_state_shape: Tuple[int, int],
@@ -32,6 +33,9 @@ class MambaCacheManager:
33
if not vllm_config.model_config.enforce_eager:
34
max_batch_size = vllm_config.pad_for_cudagraph(max_batch_size)
35
36
+ # Initialize parent class
37
+ super().__init__(max_batch_size)
38
+
39
conv_state = torch.empty(size=(num_mamba_layers, max_batch_size) +
40
conv_state_shape,
41
dtype=dtype,
@@ -41,126 +45,32 @@ class MambaCacheManager:
45
dtype=dtype,
46
device="cuda")
47
44
- self.mamba_cache = (conv_state, temporal_state)
48
+ self._mamba_cache = (conv_state, temporal_state)
49
+
50
+ @property
51
+ def cache(self):
52
+ return self._mamba_cache
53
46
- # Maps between the request id and a dict that maps between the seq_id
47
- # and its index inside the self.mamba_cache
48
- self.mamba_cache_indices_mapping: Dict[str, Dict[int, int]] = {}
49
- self.free_cache_indices = list(range(max_batch_size))
54
+ def _copy_cache(self, from_index: int, to_index: int):
55
+ for cache_t in self.cache:
56
+ cache_t[:, to_index].copy_(cache_t[:, from_index],
57
+ non_blocking=True)
58
59
def current_run_tensors(self, **kwargs) -> MambaCacheParams:
60
"""
61
Return the tensors for the current run's conv and ssm state.
62
"""
55
- if "seqlen_agnostic_capture_inputs" not in kwargs:
56
- # We get here only on Prefill/Eager mode runs
57
- request_ids_to_seq_ids = kwargs["request_ids_to_seq_ids"]
58
- finished_requests_ids = kwargs["finished_requests_ids"]
59
-
60
- self._release_finished_requests(finished_requests_ids)
61
- state_indices = self._prepare_current_run_mamba_cache(
62
- request_ids_to_seq_ids, finished_requests_ids)
63
-
64
- state_indices_tensor = torch.as_tensor(state_indices,
65
- dtype=torch.int32,
66
- device="cuda")
67
- mamba_cache_tensors = self.mamba_cache
68
-
69
- else:
70
- # CUDA graph capturing runs
71
- (mamba_cache_tensors,
72
- state_indices_tensor) = kwargs["seqlen_agnostic_capture_inputs"]
73
-
74
- return MambaCacheParams(mamba_cache_tensors[0], mamba_cache_tensors[1],
63
+ cache_tensors, state_indices_tensor = super().current_run_tensors(
64
+ **kwargs)
65
+ return MambaCacheParams(cache_tensors[0], cache_tensors[1],
66
state_indices_tensor)
67
77
- def copy_inputs_before_cuda_graphs(self, input_buffers, **kwargs):
78
- """
79
- Copy the relevant state_indices into the CUDA graph input buffer
80
- """
81
- assert all(
82
- key in kwargs
83
- for key in ["request_ids_to_seq_ids", "finished_requests_ids"])
84
- finished_requests_ids = kwargs["finished_requests_ids"]
85
- request_ids_to_seq_ids = kwargs["request_ids_to_seq_ids"]
86
- assert "seqlen_agnostic_capture_inputs" in input_buffers
87
- _, input_state_indices_buffer = input_buffers[
88
- "seqlen_agnostic_capture_inputs"]
89
-
90
- self._release_finished_requests(finished_requests_ids)
91
- state_indices = self._prepare_current_run_mamba_cache(
92
- request_ids_to_seq_ids, finished_requests_ids)
93
- cuda_graph_pad_len = input_state_indices_buffer.shape[0] - len(
94
- state_indices)
95
- state_indices.extend([PAD_SLOT_ID] * cuda_graph_pad_len)
96
-
97
- input_state_indices_buffer.copy_(
98
- torch.as_tensor(state_indices, dtype=torch.int32, device="cuda"))
99
-
68
def get_seqlen_agnostic_capture_inputs(self, batch_size: int):
69
"""
70
Provide the CUDA graph capture runs with a buffer in adjusted size.
71
The buffer is used to maintain the Mamba Cache during the CUDA graph
72
replay runs.
73
"""
106
- state_indices_tensor = torch.as_tensor([PAD_SLOT_ID] * batch_size,
107
- dtype=torch.int32,
108
- device="cuda")
109
- return (self.mamba_cache, state_indices_tensor)
110
-
111
- def _copy_mamba_cache(self, from_index: int, to_index: int):
112
- assert len(self.mamba_cache) > 0
113
- for cache_t in self.mamba_cache:
114
- cache_t[:, to_index].copy_(cache_t[:, from_index],
115
- non_blocking=True)
116
-
117
- def _assign_seq_id_to_cache_index(self, cur_rid: str, seq_id: int,
118
- finished_requests_ids) -> int:
119
- """
120
- Assign (req_id,seq_id) pair to a `destination_index` index, if
121
- already occupied, move the occupying index to a free index.
122
- """
123
- if cur_rid in finished_requests_ids:
124
- # set as pad, do not allocate destination index
125
- return PAD_SLOT_ID
126
- elif cur_rid not in self.mamba_cache_indices_mapping:
127
- destination_index = self.free_cache_indices.pop()
128
- self.mamba_cache_indices_mapping[cur_rid] = {
129
- seq_id: destination_index
130
- }
131
- return destination_index
132
- elif seq_id not in (seq_ids2indices :=
133
- self.mamba_cache_indices_mapping[cur_rid]):
134
- # parallel sampling , where n > 1, assume prefill have
135
- # already happened, so we copy the
136
- # existing cache into the siblings seq_ids caches
137
- index_exists = next(iter(seq_ids2indices.values()))
138
- # case of decoding n>1, copy prefill cache to decoding indices
139
- destination_index = self.free_cache_indices.pop()
140
- self._copy_mamba_cache(from_index=index_exists,
141
- to_index=destination_index)
142
- self.mamba_cache_indices_mapping[cur_rid][
143
- seq_id] = destination_index
144
- return destination_index
145
- else:
146
- # already exists
147
- return self.mamba_cache_indices_mapping[cur_rid][seq_id]
148
-
149
- def _prepare_current_run_mamba_cache(
150
- self, request_ids_to_seq_ids: Dict[str, list[int]],
151
- finished_requests_ids: List[str]) -> List[int]:
152
- return [
153
- self._assign_seq_id_to_cache_index(req_id, seq_id,
154
- finished_requests_ids)
155
- for req_id, seq_ids in request_ids_to_seq_ids.items()
156
- for seq_id in seq_ids
157
- ]
158
-
159
- def _release_finished_requests(self,
160
- finished_seq_groups_req_ids: List[str]):
161
- for req_id in finished_seq_groups_req_ids:
162
- if req_id in self.mamba_cache_indices_mapping:
163
- for seq_id in self.mamba_cache_indices_mapping[req_id]:
164
- self.free_cache_indices.append(
165
- self.mamba_cache_indices_mapping[req_id][seq_id])
166
- self.mamba_cache_indices_mapping.pop(req_id)
74
+ return self._mamba_cache, torch.as_tensor([PAD_SLOT_ID] * batch_size,
75
+ dtype=torch.int32,
76
+ device="cuda")
vllm/model_executor/models/minimax_cache.py
new
+35
@@ -0,0 +1,35 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+from dataclasses import dataclass
3
+
4
+import torch
5
+
6
+from vllm.model_executor.models.constant_size_cache import ConstantSizeCache
7
+
8
+
9
+@dataclass
10
+class MinimaxCacheParams:
11
+ minimax_cache: torch.Tensor = torch.Tensor()
12
+ state_indices_tensor: torch.Tensor = torch.Tensor()
13
+
14
+ def at_layer_idx(self, layer_idx):
15
+ return MinimaxCacheParams(self.minimax_cache[layer_idx, ...],
16
+ self.state_indices_tensor)
17
+
18
+
19
+class MinimaxCacheManager(ConstantSizeCache):
20
+
21
+ def __init__(self, dtype, cache_shape):
22
+ super().__init__(cache_shape[1]) # max_batch_size is cache_shape[1]
23
+ self._minimax_cache = torch.empty(size=cache_shape,
24
+ dtype=dtype,
25
+ device="cuda")
26
+
27
+ @property
28
+ def cache(self):
29
+ return self._minimax_cache
30
+
31
+ def _copy_cache(self, from_index: int, to_index: int):
32
+ assert len(self.cache) > 0
33
+ for cache_t in self.cache:
34
+ cache_t[:, to_index].copy_(cache_t[:, from_index],
35
+ non_blocking=True)
vllm/model_executor/models/minimax_text_01.py
new
+1273
@@ -0,0 +1,1273 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+"""Inference-only MiniMaxText01 model."""
3
+import copy
4
+import math
5
+import re
6
+from typing import Dict, Iterable, List, Optional, Tuple, Union
7
+
8
+import torch
9
+import torch.distributed
10
+import torch.nn.functional as F
11
+from einops import rearrange
12
+from torch import nn
13
+from transformers.configuration_utils import PretrainedConfig
14
+
15
+from vllm.attention import Attention, AttentionMetadata
16
+from vllm.config import CacheConfig, VllmConfig
17
+from vllm.distributed.communication_op import tensor_model_parallel_all_reduce
18
+from vllm.distributed.parallel_state import (
19
+ get_pp_group, get_tensor_model_parallel_rank,
20
+ get_tensor_model_parallel_world_size)
21
+from vllm.forward_context import get_forward_context
22
+from vllm.model_executor.custom_op import CustomOp
23
+from vllm.model_executor.layers.activation import SiluAndMul
24
+from vllm.model_executor.layers.fused_moe import FusedMoE
25
+from vllm.model_executor.layers.layernorm import RMSNorm
26
+from vllm.model_executor.layers.lightning_attn import (
27
+ lightning_attention, linear_decode_forward_triton)
28
+from vllm.model_executor.layers.linear import (ColumnParallelLinear,
29
+ MergedColumnParallelLinear,
30
+ QKVParallelLinear,
31
+ ReplicatedLinear,
32
+ RowParallelLinear)
33
+from vllm.model_executor.layers.logits_processor import LogitsProcessor
34
+from vllm.model_executor.layers.quantization.base_config import (
35
+ QuantizationConfig)
36
+from vllm.model_executor.layers.sampler import Sampler
37
+from vllm.model_executor.layers.vocab_parallel_embedding import (
38
+ DEFAULT_VOCAB_PADDING_SIZE, ParallelLMHead, VocabParallelEmbedding)
39
+from vllm.model_executor.model_loader.weight_utils import default_weight_loader
40
+from vllm.model_executor.models.utils import maybe_prefix
41
+from vllm.model_executor.sampling_metadata import SamplingMetadata
42
+from vllm.sequence import IntermediateTensors
43
+
44
+from .interfaces import HasInnerState, IsHybrid, SupportsV0Only
45
+from .minimax_cache import MinimaxCacheManager, MinimaxCacheParams
46
+from .utils import PPMissingLayer, is_pp_missing_parameter, make_layers
47
+
48
+
49
+def replace_weight_name(name: str,
50
+ key: str = None,
51
+ to: str = None,
52
+ count: int = None,
53
+ prefix: str = None) -> str:
54
+ name = name.replace(key, to) if count is None else \
55
+ name.replace(key, to, count)
56
+ return name
57
+
58
+
59
+def weight_loader_with_alias(alias: str):
60
+
61
+ def wrapper(func: callable):
62
+
63
+ def inner_func(param: torch.Tensor,
64
+ loaded_weight: torch.Tensor,
65
+ *args,
66
+ prefix: str = None,
67
+ **kwargs):
68
+ value = func(param, loaded_weight, *args, **kwargs)
69
+ return value
70
+
71
+ return inner_func
72
+
73
+ return wrapper
74
+
75
+
76
+class MiniMaxText01RMSNormTP(CustomOp):
77
+ name = "MiniMaxText01RMSNormTP"
78
+
79
+ def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
80
+ super().__init__()
81
+ self.tp_world = get_tensor_model_parallel_world_size()
82
+ self.tp_rank = get_tensor_model_parallel_rank()
83
+ self.weight = nn.Parameter(torch.ones(int(hidden_size /
84
+ self.tp_world)))
85
+
86
+ self.weight.weight_loader = self.weight_loader
87
+ self.variance_epsilon = eps
88
+ return
89
+
90
+ @staticmethod
91
+ def weight_loader(
92
+ param: nn.Parameter,
93
+ loaded_weight: torch.Tensor,
94
+ ) -> None:
95
+ tp_world = get_tensor_model_parallel_world_size()
96
+ tp_rank = get_tensor_model_parallel_rank()
97
+
98
+ shard_size = loaded_weight.shape[0] // tp_world
99
+ shard = slice(tp_rank * shard_size, (tp_rank + 1) * shard_size)
100
+ param.data.copy_(loaded_weight[shard])
101
+ return
102
+
103
+ def _forward(
104
+ self,
105
+ x: torch.Tensor,
106
+ ) -> torch.Tensor:
107
+ orig_dtype = x.dtype
108
+ x = x.to(torch.float32)
109
+ variance = x.pow(2).mean(dim=-1, keepdim=True, dtype=torch.float32)
110
+ if self.tp_world > 1:
111
+ variance = tensor_model_parallel_all_reduce(
112
+ variance) / self.tp_world
113
+ x = x * torch.rsqrt(variance + self.variance_epsilon)
114
+ x = x.to(orig_dtype) * self.weight
115
+ return x
116
+
117
+ def forward(
118
+ self,
119
+ x: torch.Tensor,
120
+ residual: Optional[torch.Tensor] = None,
121
+ ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
122
+ assert residual is None, "RMSNorm does not support residual connection."
123
+ return self._forward(x)
124
+
125
+
126
+class MiniMaxText01RotaryEmbedding(CustomOp):
127
+ name = "MiniMaxText01RotaryEmbedding"
128
+
129
+ def __init__(
130
+ self,
131
+ head_size: int,
132
+ rotary_dim: int,
133
+ max_position: int,
134
+ base: int,
135
+ is_neox_style: bool,
136
+ cache_dtype: torch.dtype,
137
+ ) -> None:
138
+ super().__init__()
139
+ self.head_size = head_size
140
+ self.rotary_dim = rotary_dim
141
+ self.max_position_embeddings = max_position
142
+ self.base = base
143
+ self.is_neox_style = is_neox_style
144
+ self.cache_dtype = cache_dtype
145
+ cache = self._compute_cos_sin_cache().to(cache_dtype)
146
+ self.register_buffer("cos_sin_cache", cache, persistent=False)
147
+
148
+ def _compute_inv_freq(
149
+ self,
150
+ base: Union[int, float],
151
+ ) -> torch.Tensor:
152
+ """Compute the inverse frequency."""
153
+ inv_freq = 1.0 / (base**(torch.arange(
154
+ 0, self.rotary_dim, 2, dtype=torch.float) / self.rotary_dim))
155
+ return inv_freq
156
+
157
+ def _compute_cos_sin_cache(self) -> torch.Tensor:
158
+ """Compute the cos and sin cache."""
159
+ inv_freq = self._compute_inv_freq(self.base)
160
+ t = torch.arange(self.max_position_embeddings, dtype=torch.float)
161
+ freqs = torch.einsum("i,j -> ij", t, inv_freq)
162
+ cos = freqs.cos()
163
+ sin = freqs.sin()
164
+ cache = torch.cat((cos, sin), dim=-1)
165
+ return cache
166
+
167
+ def forward(
168
+ self,
169
+ positions: torch.Tensor,
170
+ query: torch.Tensor,
171
+ key: torch.Tensor,
172
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
173
+ from vllm import _custom_ops as ops
174
+ self.cos_sin_cache = self.cos_sin_cache.to(positions.device)
175
+ query_cast = query.to(self.cache_dtype)
176
+ key_cast = key.to(self.cache_dtype)
177
+ ops.rotary_embedding(positions, query_cast, key_cast, self.head_size,
178
+ self.cos_sin_cache, self.is_neox_style)
179
+ query = query_cast.to(query.dtype)
180
+ key = key_cast.to(key.dtype)
181
+ return query, key
182
+
183
+
184
+class MiniMaxText01MLP(nn.Module):
185
+
186
+ def __init__(
187
+ self,
188
+ hidden_size: int,
189
+ intermediate_size: int,
190
+ quant_config: Optional[QuantizationConfig] = None,
191
+ layer_idx: int = None,
192
+ prefix: str = "mlp",
193
+ ) -> None:
194
+ super().__init__()
195
+ self.layer_idx = layer_idx
196
+
197
+ self.gate_up_proj = MergedColumnParallelLinear(
198
+ hidden_size,
199
+ [intermediate_size] * 2,
200
+ bias=False,
201
+ quant_config=quant_config,
202
+ prefix=f"{prefix}.gate_up_proj",
203
+ )
204
+ self.down_proj = RowParallelLinear(
205
+ intermediate_size,
206
+ hidden_size,
207
+ bias=False,
208
+ quant_config=quant_config,
209
+ prefix=f"{prefix}.down_proj",
210
+ )
211
+ self.act_fn = SiluAndMul()
212
+ return
213
+
214
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
215
+
216
+ gate_up, _ = self.gate_up_proj(x)
217
+ x = self.act_fn(gate_up)
218
+ x, _ = self.down_proj(x)
219
+ return x
220
+
221
+
222
+class MiniMaxText01MoE(nn.Module):
223
+
224
+ def __init__(
225
+ self,
226
+ num_experts: int,
227
+ top_k: int,
228
+ hidden_size: int,
229
+ intermediate_size: int,
230
+ params_dtype: Optional[torch.dtype] = None,
231
+ layer_idx: int = None,
232
+ quant_config: Optional[QuantizationConfig] = None,
233
+ prefix: str = "moe",
234
+ ) -> None:
235
+ super().__init__()
236
+
237
+ self.layer_idx = layer_idx
238
+ self.tp_size = get_tensor_model_parallel_world_size()
239
+ self.num_total_experts = num_experts
240
+ self.top_k = top_k
241
+ self.hidden_size = hidden_size
242
+ self.intermediate_size = intermediate_size // self.tp_size
243
+ self.quant_config = quant_config
244
+
245
+ if params_dtype is None:
246
+ params_dtype = torch.get_default_dtype()
247
+ self.params_dtype = params_dtype
248
+
249
+ self.gate = ReplicatedLinear(
250
+ self.hidden_size,
251
+ self.num_total_experts,
252
+ bias=False,
253
+ params_dtype=torch.float32,
254
+ quant_config=None,
255
+ prefix=f"{prefix}.gate",
256
+ )
257
+ self.gate.weight.weight_loader = MiniMaxText01MoE.gate_weight_loader
258
+
259
+ self.experts = FusedMoE(
260
+ num_experts=self.num_total_experts,
261
+ top_k=self.top_k,
262
+ hidden_size=self.hidden_size,
263
+ intermediate_size=self.intermediate_size * self.tp_size,
264
+ params_dtype=self.params_dtype,
265
+ reduce_results=True,
266
+ renormalize=True,
267
+ quant_config=self.quant_config,
268
+ tp_size=self.tp_size,
269
+ prefix=f"{prefix}.experts",
270
+ )
271
+ return
272
+
273
+ @staticmethod
274
+ def gate_weight_loader(param: nn.Parameter,
275
+ loaded_weight: torch.Tensor) -> None:
276
+ assert param.size() == loaded_weight.size()
277
+ param.data.copy_(loaded_weight.to(torch.float32))
278
+ return
279
+
280
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
281
+ num_tokens, hidden_size = hidden_states.shape
282
+ hidden_states = hidden_states.view(-1, self.hidden_size)
283
+ router_logits_fp32, _ = self.gate(hidden_states.to(torch.float32))
284
+ final_hidden_states = self.experts(
285
+ hidden_states, router_logits_fp32.to(hidden_states.dtype))
286
+ final_hidden = final_hidden_states.view(num_tokens, hidden_size)
287
+ return final_hidden
288
+
289
+
290
+class MiniMaxText01LinearKernel:
291
+
292
+ @staticmethod
293
+ def jit_linear_forward_prefix(q: torch.Tensor,
294
+ k: torch.Tensor,
295
+ v: torch.Tensor,
296
+ kv_caches: torch.Tensor,
297
+ slope_rate: torch.Tensor,
298
+ block_size: int,
299
+ layer_idx: int = None,
300
+ **kwargs) -> torch.Tensor:
301
+
302
+ slope_rate = slope_rate.to(torch.float32)
303
+ should_pad_dim = q.dim() == 3
304
+ if should_pad_dim:
305
+ q = q.unsqueeze(0)
306
+ k = k.unsqueeze(0)
307
+ v = v.unsqueeze(0)
308
+ b, h, n, d = q.shape
309
+ e = d
310
+ kv_history = kv_caches.reshape(1, h, d, e).contiguous()
311
+ output, kv_history = lightning_attention(q,
312
+ k,
313
+ v,
314
+ slope_rate,
315
+ block_size=block_size,
316
+ kv_history=kv_history)
317
+ kv_caches.copy_(kv_history[:, :, -1, :, :].reshape(h, d, e))
318
+ assert output.shape[0] == 1, "batch size must be 1"
319
+ return rearrange(output.squeeze(0), "h n d -> n (h d)")
320
+
321
+
322
+class MiniMaxText01LinearAttention(nn.Module):
323
+
324
+ def __init__(
325
+ self,
326
+ hidden_size: int,
327
+ hidden_inner_size: int,
328
+ num_heads: int,
329
+ head_dim: int,
330
+ max_position: int,
331
+ block_size: int,
332
+ num_hidden_layer: int,
333
+ quant_config: Optional[QuantizationConfig] = None,
334
+ layer_idx: int = 0,
335
+ linear_layer_idx: int = 0,
336
+ prefix: str = "linear_attn",
337
+ ) -> None:
338
+ super().__init__()
339
+
340
+ self.layer_idx = layer_idx
341
+ self.BLOCK = block_size
342
+ self.hidden_size = hidden_size
343
+ self.num_heads = num_heads
344
+ self.head_dim = head_dim
345
+ self.total_num_heads = num_heads
346
+ self.hidden_inner_size = hidden_inner_size
347
+ self.tp_size = get_tensor_model_parallel_world_size()
348
+ self.tp_rank = get_tensor_model_parallel_rank()
349
+
350
+ assert self.total_num_heads % self.tp_size == 0
351
+ self.tp_heads = self.total_num_heads // self.tp_size
352
+ self.qkv_size = self.num_heads * self.head_dim
353
+ self.tp_hidden = self.head_dim * self.tp_heads
354
+
355
+ self.qkv_proj = ColumnParallelLinear(
356
+ hidden_size,
357
+ self.hidden_inner_size * 3,
358
+ bias=False,
359
+ quant_config=quant_config,
360
+ prefix=f"{prefix}.qkv_proj",
361
+ )
362
+ self.output_gate = ColumnParallelLinear(
363
+ hidden_size,
364
+ self.hidden_inner_size,
365
+ bias=False,
366
+ quant_config=quant_config,
367
+ prefix=f"{prefix}.output_gate",
368
+ )
369
+ self.out_proj = RowParallelLinear(
370
+ self.hidden_inner_size,
371
+ hidden_size,
372
+ bias=False,
373
+ quant_config=quant_config,
374
+ prefix=f"{prefix}.out_proj",
375
+ )
376
+ self.norm = MiniMaxText01RMSNormTP(
377
+ self.hidden_inner_size,
378
+ eps=1e-5,
379
+ )
380
+
381
+ slope_rate = MiniMaxText01LinearAttention._build_slope_tensor(
382
+ self.num_heads)
383
+ if num_hidden_layer <= 1:
384
+ self.slope_rate = slope_rate * (1 + 1e-5)
385
+ else:
386
+ self.slope_rate = slope_rate * (1 - layer_idx /
387
+ (num_hidden_layer - 1) + 1e-5)
388
+ self.tp_slope = self.slope_rate[self.tp_rank *
389
+ self.tp_heads:(self.tp_rank + 1) *
390
+ self.tp_heads].contiguous()
391
+
392
+ @staticmethod
393
+ def weight_direct_load(param: torch.Tensor,
394
+ loaded_weight: torch.Tensor) -> None:
395
+ assert param.size() == loaded_weight.size()
396
+ param.data.copy_(loaded_weight)
397
+ return
398
+
399
+ @staticmethod
400
+ def _build_slope_tensor(n_attention_heads: int):
401
+
402
+ def get_slopes(n):
403
+
404
+ def get_slopes_power_of_2(n):
405
+ start = 2**(-(2**-(math.log2(n) - 3)))
406
+ ratio = start
407
+ return [start * ratio**i for i in range(n)]
408
+
409
+ if math.log2(n).is_integer():
410
+ return get_slopes_power_of_2(n)
411
+ else:
412
+ closest_power_of_2 = 2**math.floor(math.log2(n))
413
+ return (get_slopes_power_of_2(closest_power_of_2) + get_slopes(
414
+ 2 * closest_power_of_2)[0::2][:n - closest_power_of_2])
415
+
416
+ slopes = torch.tensor(get_slopes(n_attention_heads),
417
+ dtype=torch.float32).reshape(
418
+ n_attention_heads, 1, 1)
419
+ return slopes
420
+
421
+ def _prefill_and_mix_infer(self, q, k, v, kv_cache, state_indices_tensor,
422
+ attn_metadata):
423
+ hidden = []
424
+ for _prefill_idx in range(getattr(attn_metadata, "num_prefills", 0)):
425
+ _start = attn_metadata.query_start_loc[_prefill_idx]
426
+ _end = attn_metadata.query_start_loc[_prefill_idx + 1]
427
+ slot_id = state_indices_tensor[_prefill_idx]
428
+ qs = q[_start:_end].transpose(0, 1).contiguous()
429
+ ks = k[_start:_end].transpose(0, 1).contiguous()
430
+ vs = v[_start:_end].transpose(0, 1).contiguous()
431
+ slot_id = state_indices_tensor[_prefill_idx]
432
+ slice_layer_cache = kv_cache[slot_id, ...]
433
+
434
+ out_slice = MiniMaxText01LinearKernel.jit_linear_forward_prefix(
435
+ qs,
436
+ ks,
437
+ vs,
438
+ slice_layer_cache,
439
+ self.tp_slope,
440
+ self.BLOCK,
441
+ layer_idx=self.layer_idx)
442
+ hidden.append(out_slice.contiguous())
443
+ if attn_metadata.num_decode_tokens > 0:
444
+ hidden.append(
445
+ self._decode_infer(q, k, v, kv_cache, state_indices_tensor,
446
+ attn_metadata))
447
+ hidden = torch.concat(hidden, dim=0).contiguous()
448
+ return hidden
449
+
450
+ def _decode_infer(self, q, k, v, kv_cache, state_indices_tensor,
451
+ attn_metadata):
452
+ q = q[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
453
+ k = k[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
454
+ v = v[attn_metadata.num_prefill_tokens:].unsqueeze(2).contiguous()
455
+ slot_id = state_indices_tensor[getattr(attn_metadata, "num_prefills", 0
456
+ ):]
457
+ hidden = linear_decode_forward_triton(q, k, v, kv_cache, self.tp_slope,
458
+ slot_id, 32)
459
+ return hidden
460
+
461
+ def forward(self, hidden_states: torch.Tensor, positions: torch.Tensor,
462
+ kv_caches: MinimaxCacheParams, **kwargs) -> torch.Tensor:
463
+ qkv, _ = self.qkv_proj(hidden_states)
464
+ qkv32 = qkv.to(torch.float32)
465
+ qkvact = torch.nn.functional.silu(qkv32)
466
+ qkvact = qkvact.view((qkv.shape[0], self.tp_heads, -1))
467
+ q, k, v = torch.split(qkvact, [self.head_dim] * 3, dim=-1)
468
+ forward_context = get_forward_context()
469
+ attn_metadata = forward_context.attn_metadata
470
+ kv_cache = kv_caches.minimax_cache
471
+ state_indices_tensor = kv_caches.state_indices_tensor
472
+
473
+ decode_only = getattr(attn_metadata, "num_prefills", 0) == 0
474
+ if not decode_only:
475
+ hidden = self._prefill_and_mix_infer(q, k, v, kv_cache,
476
+ state_indices_tensor,
477
+ attn_metadata)
478
+ else:
479
+ hidden = self._decode_infer(q, k, v, kv_cache,
480
+ state_indices_tensor, attn_metadata)
481
+
482
+ hidden = self.norm._forward(hidden)
483
+ gate, _ = self.output_gate(hidden_states)
484
+ hidden = F.sigmoid(gate) * hidden
485
+ hidden = hidden.to(hidden_states.dtype)
486
+ hidden, _ = self.out_proj(hidden)
487
+ return hidden
488
+
489
+
490
+class MiniMaxText01Attention(nn.Module):
491
+
492
+ def __init__(
493
+ self,
494
+ hidden_size: int,
495
+ num_heads: int,
496
+ head_dim: int,
497
+ num_kv_heads: int,
498
+ rotary_dim: int,
499
+ max_position: int = 4096 * 32,
500
+ rope_theta: float = 10000,
501
+ sliding_window: Optional[int] = None,
502
+ quant_config: Optional[QuantizationConfig] = None,
503
+ layer_idx: int = None,
504
+ cache_config: Optional[CacheConfig] = None,
505
+ prefix: str = "mha",
506
+ ) -> None:
507
+ super().__init__()
508
+ self.layer_idx = layer_idx
509
+
510
+ self.hidden_size = hidden_size
511
+ tp_size = get_tensor_model_parallel_world_size()
512
+ self.total_num_heads = num_heads
513
+ assert self.total_num_heads % tp_size == 0
514
+ self.num_heads = self.total_num_heads // tp_size
515
+ self.total_num_kv_heads = num_kv_heads
516
+ if self.total_num_kv_heads >= tp_size:
517
+ assert self.total_num_kv_heads % tp_size == 0
518
+ else:
519
+ assert tp_size % self.total_num_kv_heads == 0
520
+ self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
521
+ self.head_dim = head_dim
522
+
523
+ self.q_size = self.num_heads * self.head_dim
524
+ self.kv_size = self.num_kv_heads * self.head_dim
525
+ self.scaling = self.head_dim**-0.5
526
+ self.rope_theta = rope_theta
527
+ self.sliding_window = sliding_window
528
+
529
+ self.qkv_proj = QKVParallelLinear(
530
+ hidden_size,
531
+ self.head_dim,
532
+ self.total_num_heads,
533
+ self.total_num_kv_heads,
534
+ bias=False,
535
+ quant_config=quant_config,
536
+ prefix=f"{prefix}.qkv_proj",
537
+ )
538
+ self.o_proj = RowParallelLinear(
539
+ self.total_num_heads * self.head_dim,
540
+ hidden_size,
541
+ bias=False,
542
+ quant_config=quant_config,
543
+ prefix=f"{prefix}.o_proj",
544
+ )
545
+ self.attn = Attention(
546
+ self.num_heads,
547
+ self.head_dim,
548
+ self.scaling,
549
+ num_kv_heads=self.num_kv_heads,
550
+ cache_config=cache_config,
551
+ quant_config=quant_config,
552
+ prefix=f"{prefix}.attn",
553
+ )
554
+ return
555
+
556
+ def forward(self, hidden_states: torch.Tensor, positions: torch.Tensor,
557
+ **kwargs) -> torch.Tensor:
558
+ forward_context = get_forward_context()
559
+ attn_metadata = forward_context.attn_metadata
560
+ qkv, _ = self.qkv_proj(hidden_states)
561
+ q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
562
+ q, k = attn_metadata.rotary_emb(positions, q, k)
563
+ attn_output = self.attn(q, k, v)
564
+ output, _ = self.o_proj(attn_output)
565
+ return output
566
+
567
+
568
+class MiniMaxText01DecoderLayer(nn.Module):
569
+
570
+ def __init__(
571
+ self,
572
+ config: PretrainedConfig,
573
+ cache_config: Optional[CacheConfig] = None,
574
+ quant_config: Optional[QuantizationConfig] = None,
575
+ expert_num: int = 1,
576
+ layer_id: int = None,
577
+ linear_layer_id: Optional[int] = None,
578
+ prefix: str = "decoder",
579
+ ) -> None:
580
+ self._ilayer = layer_id
581
+ self._irank = get_tensor_model_parallel_rank()
582
+ super().__init__()
583
+
584
+ self.hidden_size = config.hidden_size
585
+ self.expert_num = expert_num
586
+
587
+ rope_theta = getattr(config, "rope_theta", 10000)
588
+
589
+ head_dim = getattr(config, "head_dim",
590
+ config.hidden_size // config.num_attention_heads)
591
+ if hasattr(config, "max_model_len") and isinstance(
592
+ config.max_model_len, int):
593
+ max_position_embeddings = min(config.max_position_embeddings,
594
+ config.max_model_len)
595
+ if config.attention_type == 0:
596
+ use_headxdim = True
597
+ hidden_inner = (head_dim * config.num_attention_heads
598
+ if use_headxdim else config.hidden_size)
599
+ self.self_attn = MiniMaxText01LinearAttention(
600
+ hidden_size=self.hidden_size,
601
+ hidden_inner_size=hidden_inner,
602
+ num_heads=config.num_attention_heads,
603
+ head_dim=head_dim,
604
+ max_position=max_position_embeddings,
605
+ block_size=config.block if hasattr(config, "block") else 256,
606
+ num_hidden_layer=config.num_hidden_layers,
607
+ quant_config=quant_config,
608
+ layer_idx=self._ilayer,
609
+ linear_layer_idx=linear_layer_id,
610
+ prefix=prefix)
611
+ elif config.attention_type == 1:
612
+ self.self_attn = MiniMaxText01Attention(
613
+ hidden_size=self.hidden_size,
614
+ num_heads=config.num_attention_heads,
615
+ head_dim=head_dim,
616
+ rotary_dim=config.rotary_dim
617
+ if hasattr(config, "rotary_dim") else head_dim,
618
+ num_kv_heads=config.num_key_value_heads,
619
+ max_position=max_position_embeddings,
620
+ rope_theta=rope_theta,
621
+ sliding_window=config.sliding_window,
622
+ quant_config=quant_config,
623
+ layer_idx=self._ilayer,
624
+ cache_config=cache_config,
625
+ prefix=prefix)
626
+ else:
627
+ raise ValueError(
628
+ f"Unsupported attention type: {self.config.attention_type}")
629
+
630
+ if expert_num == 1:
631
+ self.mlp = MiniMaxText01MLP(
632
+ hidden_size=self.hidden_size,
633
+ intermediate_size=config.intermediate_size,
634
+ quant_config=quant_config,
635
+ layer_idx=self._ilayer,
636
+ prefix=prefix)
637
+ else:
638
+ self.block_sparse_moe = MiniMaxText01MoE(
639
+ num_experts=expert_num,
640
+ top_k=config.num_experts_per_tok,
641
+ hidden_size=config.hidden_size,
642
+ intermediate_size=config.intermediate_size,
643
+ layer_idx=self._ilayer,
644
+ quant_config=quant_config,
645
+ prefix=prefix)
646
+
647
+ self.input_layernorm = RMSNorm(config.hidden_size,
648
+ eps=config.rms_norm_eps)
649
+ self.post_attention_layernorm = RMSNorm(config.hidden_size,
650
+ eps=config.rms_norm_eps)
651
+ if config.attention_type == 0:
652
+ self.layernorm_attention_alpha = getattr(
653
+ config, 'layernorm_linear_attention_alpha', 1)
654
+ self.layernorm_attention_beta = getattr(
655
+ config, 'layernorm_linear_attention_beta', 1)
656
+ else:
657
+ self.layernorm_attention_alpha = getattr(
658
+ config, 'layernorm_full_attention_alpha', 1)
659
+ self.layernorm_attention_beta = getattr(
660
+ config, 'layernorm_full_attention_beta', 1)
661
+ self.layernorm_mlp_alpha = getattr(config, 'layernorm_mlp_alpha', 1)
662
+ self.layernorm_mlp_beta = getattr(config, 'layernorm_mlp_beta', 1)
663
+ self.postnorm = getattr(config, 'postnorm', False)
664
+ self.shared_moe = False
665
+
666
+ shared_intermediate = getattr(config, 'shared_intermediate_size', 0)
667
+ if shared_intermediate > 0:
668
+ self.shared_moe = True
669
+ self.shared_mlp = MiniMaxText01MLP(
670
+ hidden_size=self.hidden_size,
671
+ intermediate_size=shared_intermediate,
672
+ quant_config=quant_config,
673
+ layer_idx=self._ilayer,
674
+ prefix=prefix)
675
+ self.coefficient = ReplicatedLinear(
676
+ self.hidden_size,
677
+ 1,
678
+ bias=False,
679
+ quant_config=quant_config,
680
+ params_dtype=torch.float32,
681
+ )
682
+ self.coefficient.weight.weight_loader = (
683
+ self.shared_moe_coefficient_loader)
684
+ self.shared_moe_mode = getattr(config, 'shared_moe_mode',
685
+ 'softmax')
686
+ return
687
+
688
+ def forward(self,
689
+ hidden_states: torch.Tensor,
690
+ positions: torch.Tensor,
691
+ kv_caches: Union[List[Dict], Optional[torch.Tensor]],
692
+ attn_metadata: AttentionMetadata,
693
+ residual: Optional[torch.Tensor],
694
+ is_warmup: bool = False,
695
+ **kwargs) -> Tuple[torch.Tensor, torch.Tensor]:
696
+
697
+ forward_context = get_forward_context()
698
+ attn_metadata = forward_context.attn_metadata
699
+ layernorm_input = hidden_states
700
+ layernorm_output = self.input_layernorm(layernorm_input)
701
+ residual = layernorm_output if self.postnorm else layernorm_input
702
+ self_attention_output = self.self_attn(
703
+ hidden_states=layernorm_output,
704
+ positions=positions,
705
+ kv_caches=kv_caches,
706
+ attn_metadata=attn_metadata,
707
+ )
708
+
709
+ residual = residual * self.layernorm_attention_alpha
710
+ self_attention_output = (self_attention_output *
711
+ self.layernorm_attention_beta)
712
+
713
+ layernorm_input = residual + self_attention_output
714
+ layernorm_output = self.post_attention_layernorm(layernorm_input)
715
+ residual = layernorm_output if self.postnorm else layernorm_input
716
+
717
+ if self.expert_num == 1:
718
+ hidden_states = self.mlp(layernorm_output)
719
+ else:
720
+ moe_hidden_states = self.block_sparse_moe(
721
+ copy.deepcopy(layernorm_output))
722
+ if self.shared_moe:
723
+ before_moe_dtype = layernorm_output.dtype
724
+ moe_hidden_fp32 = moe_hidden_states.to(torch.float32)
725
+ output_mlp = self.shared_mlp(layernorm_output).to(
726
+ torch.float32)
727
+
728
+ coef, _ = self.coefficient(layernorm_output.to(torch.float32))
729
+
730
+ if self.shared_moe_mode == 'softmax':
731
+ coef = torch.nn.functional.softmax(coef, dim=-1)
732
+ hidden_states = moe_hidden_fp32 * (
733
+ 1 - coef) + output_mlp * coef
734
+ elif self.shared_moe_mode == 'sigmoid':
735
+ coef = torch.nn.functional.sigmoid(coef)
736
+ hidden_states = moe_hidden_fp32 * (
737
+ 1 - coef) + output_mlp * coef
738
+
739
+ hidden_states = hidden_states.to(before_moe_dtype)
740
+ else:
741
+ hidden_states = moe_hidden_states
742
+
743
+ residual = residual * self.layernorm_mlp_alpha
744
+ hidden_states = hidden_states * self.layernorm_mlp_beta
745
+
746
+ hidden_states = residual + hidden_states
747
+
748
+ return hidden_states, None
749
+
750
+ @staticmethod
751
+ def shared_moe_coefficient_loader(param: torch.Tensor,
752
+ loaded_weight: torch.Tensor) -> None:
753
+ assert param.size() == loaded_weight.size()
754
+
755
+ param.data.copy_(loaded_weight.to(torch.float32))
756
+ return
757
+
758
+
759
+class MiniMaxText01Model(nn.Module):
760
+
761
+ def __init__(
762
+ self,
763
+ config: PretrainedConfig,
764
+ quant_config: Optional[QuantizationConfig] = None,
765
+ cache_config: Optional[CacheConfig] = None,
766
+ scheduler_config=None,
767
+ prefix: str = "",
768
+ ) -> None:
769
+ super().__init__()
770
+
771
+ self.padding_idx = config.pad_token_id
772
+ self.vocab_size = config.vocab_size
773
+
774
+ self.decoder_attention_types = getattr(
775
+ config, "attn_type_list", False) or getattr(
776
+ config, "decoder_attention_types", False)
777
+ if not self.decoder_attention_types:
778
+ self.decoder_attention_types = [1] * config.num_hidden_layers
779
+ self.num_layers = config.num_hidden_layers
780
+
781
+ self._layer_barrier = False
782
+ if get_pp_group().is_first_rank:
783
+ self.embed_tokens = VocabParallelEmbedding(
784
+ self.vocab_size,
785
+ config.hidden_size,
786
+ org_num_embeddings=self.vocab_size,
787
+ )
788
+ else:
789
+ self.embed_tokens = PPMissingLayer()
790
+
791
+ def layer_fn(prefix):
792
+ layer_idx = int(prefix.split('.')[-1])
793
+ layer_config = config
794
+ layer_config.attention_type = self.decoder_attention_types[
795
+ layer_idx]
796
+ layer_config.layer_idx = layer_idx
797
+
798
+ decoder_kwargs = {
799
+ "quant_config": quant_config,
800
+ "layer_id": layer_idx,
801
+ "cache_config": cache_config
802
+ }
803
+
804
+ if layer_config.attention_type == 0:
805
+ decoder_kwargs["linear_layer_id"] = sum(
806
+ 1 for i in range(layer_idx)
807
+ if self.decoder_attention_types[i] == 0)
808
+ else:
809
+ decoder_kwargs["linear_layer_id"] = None
810
+
811
+ if hasattr(config, "num_local_experts") and isinstance(
812
+ config.num_local_experts, list):
813
+ decoder_kwargs["expert_num"] = config.num_local_experts[
814
+ layer_idx]
815
+ elif hasattr(config, "num_local_experts") and isinstance(
816
+ config.num_local_experts, int):
817
+ decoder_kwargs["expert_num"] = config.num_local_experts
818
+ else:
819
+ decoder_kwargs["expert_num"] = 1
820
+
821
+ return MiniMaxText01DecoderLayer(layer_config,
822
+ **decoder_kwargs,
823
+ prefix=prefix)
824
+
825
+ self.start_layer, self.end_layer, self.layers = make_layers(
826
+ config.num_hidden_layers, layer_fn, prefix=f"{prefix}.layers")
827
+
828
+ linear_layer_nums = sum(1 for i in range(config.num_hidden_layers)
829
+ if self.decoder_attention_types[i] == 0)
830
+ max_slots_number = scheduler_config.max_num_seqs
831
+ self.cache_shape = (linear_layer_nums, max_slots_number,
832
+ config.num_attention_heads //
833
+ get_tensor_model_parallel_world_size(),
834
+ config.head_dim, config.head_dim)
835
+ _dummy = torch.zeros(1)
836
+ self._dtype = _dummy.dtype
837
+ del _dummy
838
+
839
+ self.minimax_cache = MinimaxCacheManager(dtype=self._dtype,
840
+ cache_shape=self.cache_shape)
841
+
842
+ rope_theta = getattr(config, "rope_theta", 10000)
843
+ head_dim = getattr(config, "head_dim",
844
+ config.hidden_size // config.num_attention_heads)
845
+ if hasattr(config, "max_model_len") and isinstance(
846
+ config.max_model_len, int):
847
+ max_position_embeddings = min(config.max_position_embeddings,
848
+ config.max_model_len)
849
+ self.rotary_emb = MiniMaxText01RotaryEmbedding(
850
+ head_dim,
851
+ rotary_dim=config.rotary_dim
852
+ if hasattr(config, "rotary_dim") else head_dim,
853
+ max_position=max_position_embeddings,
854
+ base=int(rope_theta),
855
+ is_neox_style=True,
856
+ cache_dtype=torch.float32,
857
+ )
858
+
859
+ norm_kwargs = {}
860
+ if hasattr(config, "rms_norm_eps"):
861
+ norm_kwargs["eps"] = config.rms_norm_eps
862
+ if get_pp_group().is_last_rank:
863
+ self.norm = RMSNorm(config.hidden_size, **norm_kwargs)
864
+ else:
865
+ self.norm = PPMissingLayer()
866
+ self.embed_scale = 1.0
867
+ return
868
+
869
+ def _clear_prefill_cache(self, attn_metadata,
870
+ minimax_cache_tensors: torch.Tensor, **kwargs):
871
+ seq_to_slot_maps = {}
872
+ seq_id_map = sum(list(kwargs["request_ids_to_seq_ids"].values()), [])
873
+ for _, seq_to_slot_map in (
874
+ self.minimax_cache.cache_indices_mapping.items()):
875
+ seq_to_slot_maps.update(seq_to_slot_map)
876
+
877
+ slots_to_clear = []
878
+ for _prefill_id in range(getattr(attn_metadata, "num_prefills", 0)):
879
+ seq_id = seq_id_map[_prefill_id]
880
+ if attn_metadata.context_lens_tensor[
881
+ _prefill_id] == 0 and seq_id in seq_to_slot_maps:
882
+ slots_to_clear.append(seq_to_slot_maps[seq_id])
883
+
884
+ if slots_to_clear:
885
+ slots_tensor = torch.tensor(slots_to_clear,
886
+ device=minimax_cache_tensors.device,
887
+ dtype=torch.long)
888
+ minimax_cache_tensors[:, slots_tensor, ...] = 0
889
+
890
+ def forward(self,
891
+ input_ids: Optional[torch.Tensor],
892
+ positions: torch.Tensor,
893
+ kv_caches: List[torch.Tensor],
894
+ intermediate_tensors=None,
895
+ inputs_embeds: Optional[torch.Tensor] = None,
896
+ **kwargs) -> torch.Tensor:
897
+ forward_context = get_forward_context()
898
+ attn_metadata = forward_context.attn_metadata
899
+ if attn_metadata is None:
900
+ return None
901
+ if "request_ids_to_seq_ids" not in kwargs:
902
+ kwargs["request_ids_to_seq_ids"] = {}
903
+ if "finished_requests_ids" not in kwargs:
904
+ kwargs["finished_requests_ids"] = []
905
+ (
906
+ minimax_cache_tensors,
907
+ state_indices_tensor,
908
+ ) = self.minimax_cache.current_run_tensors(**kwargs)
909
+ if getattr(attn_metadata, "num_prefills", 0) > 0:
910
+ self._clear_prefill_cache(attn_metadata, minimax_cache_tensors,
911
+ **kwargs)
912
+
913
+ minimax_cache_params = MinimaxCacheParams(minimax_cache_tensors,
914
+ state_indices_tensor)
915
+ if get_pp_group().is_first_rank:
916
+ if inputs_embeds is None:
917
+ hidden_states = self.embed_scale * self.embed_tokens(input_ids)
918
+ else:
919
+ hidden_states = inputs_embeds
920
+ residual = None
921
+ else:
922
+ assert intermediate_tensors is not None
923
+ hidden_states = intermediate_tensors["hidden_states"]
924
+ residual = intermediate_tensors["residual"]
925
+
926
+ kv_cache_index = 0
927
+ minimax_cache_index = 0
928
+ attn_metadata.rotary_emb = self.rotary_emb
929
+ for i in range(self.start_layer, self.end_layer):
930
+ layer = self.layers[i]
931
+ _caches = None
932
+ if isinstance(layer.self_attn, MiniMaxText01Attention):
933
+ _caches = kv_caches[kv_cache_index]
934
+ kv_cache_index += 1
935
+ if isinstance(layer.self_attn, MiniMaxText01LinearAttention):
936
+ current_state_layer = minimax_cache_index
937
+ _caches = minimax_cache_params.at_layer_idx(
938
+ current_state_layer)
939
+ minimax_cache_index += 1
940
+ hidden_states, residual = layer(
941
+ hidden_states=hidden_states,
942
+ positions=positions,
943
+ kv_caches=_caches,
944
+ attn_metadata=attn_metadata,
945
+ residual=residual,
946
+ )
947
+ if not get_pp_group().is_last_rank:
948
+ return IntermediateTensors({
949
+ "hidden_states": hidden_states,
950
+ "residual": residual
951
+ })
952
+ if residual is not None:
953
+ hidden_states, _ = self.norm(hidden_states, residual)
954
+ else:
955
+ hidden_states = self.norm(hidden_states)
956
+
957
+ return hidden_states
958
+
959
+
960
+class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid,
961
+ SupportsV0Only):
962
+
963
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
964
+
965
+ super().__init__()
966
+ config = vllm_config.model_config.hf_config
967
+ quant_config = vllm_config.quant_config
968
+ lora_config = vllm_config.lora_config
969
+ self.config = config
970
+ self.lora_config = lora_config
971
+
972
+ if not hasattr(config, "sliding_window"):
973
+ config.sliding_window = None
974
+
975
+ self.CONCAT_FFN = True
976
+
977
+ self.unpadded_vocab_size = self.config.vocab_size
978
+ if hasattr(vllm_config.model_config, "max_model_len"):
979
+ self.config.max_model_len = vllm_config.model_config.max_model_len
980
+ self.model = MiniMaxText01Model(
981
+ self.config,
982
+ quant_config,
983
+ cache_config=vllm_config.cache_config,
984
+ scheduler_config=vllm_config.scheduler_config,
985
+ prefix=maybe_prefix(prefix, "model"))
986
+ if get_pp_group().is_last_rank:
987
+ self.lm_head = ParallelLMHead(
988
+ self.unpadded_vocab_size,
989
+ self.config.hidden_size,
990
+ org_num_embeddings=self.config.vocab_size,
991
+ padding_size=DEFAULT_VOCAB_PADDING_SIZE,
992
+ )
993
+
994
+ self.logits_processor = LogitsProcessor(self.unpadded_vocab_size,
995
+ self.config.vocab_size)
996
+
997
+ self.sampler = Sampler()
998
+ else:
999
+ self.lm_head = PPMissingLayer()
1000
+
1001
+ flash_layer_count = sum(1 for attn_type in self.config.attn_type_list
1002
+ if attn_type == 1)
1003
+ self.kv_cache = [torch.tensor([]) for _ in range(flash_layer_count)]
1004
+ return
1005
+
1006
+ def copy_inputs_before_cuda_graphs(self, input_buffers, **kwargs):
1007
+ return self.model.minimax_cache.copy_inputs_before_cuda_graphs(
1008
+ input_buffers, **kwargs)
1009
+
1010
+ def get_seqlen_agnostic_capture_inputs(self, batch_size: int):
1011
+ return self.model.minimax_cache.get_seqlen_agnostic_capture_inputs(
1012
+ batch_size)
1013
+
1014
+ def forward(self,
1015
+ input_ids: torch.Tensor,
1016
+ positions: torch.Tensor,
1017
+ intermediate_tensors: Optional[IntermediateTensors] = None,
1018
+ inputs_embeds: Optional[torch.Tensor] = None,
1019
+ **kwargs) -> torch.Tensor:
1020
+ hidden_states = self.model(input_ids, positions, self.kv_cache,
1021
+ intermediate_tensors, inputs_embeds,
1022
+ **kwargs)
1023
+
1024
+ return hidden_states
1025
+
1026
+ def compute_logits(self, hidden_states: torch.Tensor,
1027
+ sampling_metadata: SamplingMetadata) -> torch.Tensor:
1028
+ logits = self.logits_processor(self.lm_head, hidden_states,
1029
+ sampling_metadata)
1030
+
1031
+ return logits
1032
+
1033
+ def sample(
1034
+ self,
1035
+ logits: torch.Tensor,
1036
+ sampling_metadata: SamplingMetadata,
1037
+ ):
1038
+
1039
+ next_tokens = self.sampler(logits, sampling_metadata)
1040
+
1041
+ return next_tokens
1042
+
1043
+ def make_empty_intermediate_tensors(
1044
+ self, batch_size: int, dtype: torch.dtype,
1045
+ device: torch.device) -> IntermediateTensors:
1046
+ return IntermediateTensors({
1047
+ "hidden_states":
1048
+ torch.zeros((batch_size, self.config.hidden_size),
1049
+ dtype=dtype,
1050
+ device=device),
1051
+ "residual":
1052
+ torch.zeros((batch_size, self.config.hidden_size),
1053
+ dtype=dtype,
1054
+ device=device),
1055
+ })
1056
+
1057
+ def load_weights(self, weights: Iterable[Tuple[str,
1058
+ torch.Tensor]]) -> None:
1059
+ params_dict = dict(self.named_parameters())
1060
+
1061
+ def which_layer(name: str) -> int:
1062
+ if "layers" in name:
1063
+ after_layer = name.split("layers")[-1]
1064
+ return int(after_layer.split(".")[1])
1065
+ return None
1066
+
1067
+ def is_linear_attn_layer(layer_idx: int) -> bool:
1068
+ if layer_idx is None or not hasattr(self.config, "attn_type_list"):
1069
+ return False
1070
+ return self.config.attn_type_list[layer_idx] == 0
1071
+
1072
+ def is_moe_weight(name: str) -> bool:
1073
+ return "block_sparse_moe" in name and not name.endswith(".bias")
1074
+
1075
+ def get_expert_id(param_name):
1076
+ pattern = r'model\.layers\.\d+\.block_sparse_moe\.experts\.(\d+)\.'
1077
+ match = re.search(pattern, param_name)
1078
+ if match:
1079
+ return match.group(1)
1080
+ return None
1081
+
1082
+ def load_sparse_moe_weight(name: str, loaded_weight: torch.Tensor,
1083
+ self) -> None:
1084
+ if isinstance(self.config.num_local_experts, list):
1085
+ expert_params_mapping = [
1086
+ ("w13_weight"
1087
+ if weight_name in ["w1", "w3"] else "w2_weight",
1088
+ f"experts.{expert_id}.{weight_name}.weight", expert_id)
1089
+ for expert_id in range(max(self.config.num_local_experts))
1090
+ for weight_name in ["w1", "w2", "w3"]
1091
+ ]
1092
+ else:
1093
+ expert_params_mapping = [
1094
+ ("w13_scale" if weight_name in ["w1", "w3"] else
1095
+ "w2_scale", f"{expert_id}.{weight_name}.weight_scale",
1096
+ expert_id, weight_name)
1097
+ for expert_id in range(self.config.num_local_experts)
1098
+ for weight_name in ["w1", "w2", "w3"]
1099
+ ] + [("w13_weight" if weight_name in ["w1", "w3"] else
1100
+ "w2_weight", f"{expert_id}.{weight_name}.weight",
1101
+ expert_id, weight_name)
1102
+ for expert_id in range(self.config.num_local_experts)
1103
+ for weight_name in ["w1", "w2", "w3"]]
1104
+ for (param_name, weight_name, expert_id,
1105
+ shard_id) in expert_params_mapping:
1106
+ name_expert_id = get_expert_id(name)
1107
+ if name_expert_id is not None and int(name_expert_id) != int(
1108
+ expert_id):
1109
+ continue
1110
+ if weight_name not in name:
1111
+ continue
1112
+ name = name.replace(weight_name, param_name)
1113
+ if is_pp_missing_parameter(name, self):
1114
+ return
1115
+ param = params_dict[name]
1116
+ weight_loader = param.weight_loader
1117
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1118
+ weight_loader(param,
1119
+ loaded_weight,
1120
+ weight_name,
1121
+ expert_id=expert_id,
1122
+ shard_id=shard_id)
1123
+ break
1124
+ else:
1125
+ if is_pp_missing_parameter(name, self):
1126
+ return
1127
+ param = params_dict[name]
1128
+ weight_loader = getattr(param, "weight_loader",
1129
+ default_weight_loader)
1130
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1131
+ weight_loader(param, loaded_weight)
1132
+ return
1133
+
1134
+ def is_shared_mlp_weight(name: str) -> bool:
1135
+ return "shared_mlp" in name and not name.endswith(".bias")
1136
+
1137
+ def load_shared_mlp_weight(name: str, loaded_weight: torch.Tensor,
1138
+ self) -> None:
1139
+ if not self.CONCAT_FFN:
1140
+ if "gate_proj" in name:
1141
+ name = name.replace("gate_proj", "w1", 1)
1142
+ elif "up_proj" in name:
1143
+ name = name.replace("up_proj", "w3", 1)
1144
+ elif "down_proj" in name:
1145
+ name = name.replace("down_proj", "w2", 1)
1146
+ else:
1147
+ if "gate_proj" in name:
1148
+ name = name.replace("gate_proj", "gate_up_proj", 1)
1149
+ loaded_shard_id = 0
1150
+ elif "up_proj" in name:
1151
+ name = name.replace("up_proj", "gate_up_proj", 1)
1152
+ loaded_shard_id = 1
1153
+ if is_pp_missing_parameter(name, self):
1154
+ return
1155
+ param = params_dict[name]
1156
+ weight_loader = getattr(param, "weight_loader",
1157
+ default_weight_loader)
1158
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1159
+ if not self.CONCAT_FFN:
1160
+ weight_loader(param, loaded_weight)
1161
+ else:
1162
+ if "gate_up_proj" in name:
1163
+ weight_loader(param, loaded_weight, loaded_shard_id)
1164
+ elif "down_proj" in name:
1165
+ weight_loader(param, loaded_weight)
1166
+ else:
1167
+ raise AssertionError(
1168
+ "MLP weight not in [gate_up_proj, down_proj]")
1169
+ return
1170
+
1171
+ def is_mha_weight(name: str) -> bool:
1172
+ return "self_attn" in name and not name.endswith(".bias")
1173
+
1174
+ def load_linear_attn_weight(name: str, loaded_weight: torch.Tensor,
1175
+ self) -> None:
1176
+ if is_pp_missing_parameter(name, self):
1177
+ return
1178
+ param = params_dict[name]
1179
+
1180
+ weight_loader = getattr(
1181
+ param, "weight_loader",
1182
+ MiniMaxText01LinearAttention.weight_direct_load)
1183
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1184
+ weight_loader(param, loaded_weight)
1185
+ return
1186
+
1187
+ def load_flash_attn_weight(name: str, loaded_weight: torch.Tensor,
1188
+ self) -> None:
1189
+
1190
+ flash_mha_params_mapping = [
1191
+ ("qkv_proj", "q_proj", "q"),
1192
+ ("qkv_proj", "k_proj", "k"),
1193
+ ("qkv_proj", "v_proj", "v"),
1194
+ ("gate_up_proj", "gate_proj", 0),
1195
+ ("gate_up_proj", "up_proj", 1),
1196
+ ]
1197
+ for (param_name, weight_name,
1198
+ shard_id) in flash_mha_params_mapping:
1199
+ if weight_name not in name:
1200
+ continue
1201
+ name = name.replace(weight_name, param_name)
1202
+ if is_pp_missing_parameter(name, self):
1203
+ return
1204
+ param = params_dict[name]
1205
+ weight_loader = getattr(param, "weight_loader",
1206
+ default_weight_loader)
1207
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1208
+ weight_loader(param, loaded_weight, shard_id)
1209
+ break
1210
+ else:
1211
+ if is_pp_missing_parameter(name, self):
1212
+ return
1213
+ param = params_dict[name]
1214
+
1215
+ weight_loader = getattr(param, "weight_loader",
1216
+ default_weight_loader)
1217
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1218
+ weight_loader(param, loaded_weight)
1219
+ return
1220
+
1221
+ def is_layer_norm_weight(name: str) -> bool:
1222
+ return "norm" in name and not name.endswith(
1223
+ ".bias") and name in params_dict
1224
+
1225
+ def load_layer_norm_weight(name: str, loaded_weight: torch.Tensor,
1226
+ self) -> None:
1227
+ if is_pp_missing_parameter(name, self):
1228
+ return
1229
+ param = params_dict[name]
1230
+ weight_loader = getattr(param, "weight_loader",
1231
+ default_weight_loader)
1232
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1233
+ weight_loader(param, loaded_weight)
1234
+ return
1235
+
1236
+ def load_basic_weight(name: str, loaded_weight: torch.Tensor,
1237
+ self) -> None:
1238
+ if is_pp_missing_parameter(name, self):
1239
+ return
1240
+ param = params_dict[name]
1241
+ weight_loader = getattr(param, "weight_loader",
1242
+ default_weight_loader)
1243
+ weight_loader = weight_loader_with_alias(name)(weight_loader)
1244
+ weight_loader(param, loaded_weight)
1245
+ return
1246
+
1247
+ for name, loaded_weight in weights:
1248
+ weight_at_layer = which_layer(name)
1249
+ if weight_at_layer and weight_at_layer >= len(
1250
+ self.config.attn_type_list):
1251
+ continue
1252
+
1253
+ if is_layer_norm_weight(name):
1254
+ load_layer_norm_weight(name, loaded_weight, self)
1255
+ continue
1256
+ if is_mha_weight(name):
1257
+ if is_linear_attn_layer(weight_at_layer):
1258
+ load_linear_attn_weight(name, loaded_weight, self)
1259
+ else:
1260
+ load_flash_attn_weight(name, loaded_weight, self)
1261
+ continue
1262
+ if is_moe_weight(name):
1263
+ load_sparse_moe_weight(name, loaded_weight, self)
1264
+ continue
1265
+ if is_shared_mlp_weight(name):
1266
+ load_shared_mlp_weight(name, loaded_weight, self)
1267
+ continue
1268
+
1269
+ if "rotary_emb.inv_freq" in name:
1270
+ continue
1271
+
1272
+ load_basic_weight(name, loaded_weight, self)
1273
+ return
vllm/model_executor/models/registry.py
+1
@@ -35,6 +35,7 @@ _TEXT_GENERATION_MODELS = {
35
"AquilaModel": ("llama", "LlamaForCausalLM"),
36
"AquilaForCausalLM": ("llama", "LlamaForCausalLM"), # AquilaChat2
37
"ArcticForCausalLM": ("arctic", "ArcticForCausalLM"),
38
+ "MiniMaxText01ForCausalLM": ("minimax_text_01", "MiniMaxText01ForCausalLM"),
39
# baichuan-7b, upper case 'C' in the class name
40
"BaiChuanForCausalLM": ("baichuan", "BaiChuanForCausalLM"),
41
# baichuan-13b, lower case 'c' in the class name