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
Add the support for the qwen3 next model (a hybrid attention model). (#24526)
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com> Co-authored-by: Jee Jee Li <pandaleefree@gmail.com>
Tao He committed
Sep 11, 2025 at 15:32 UTC
e93f4cc9e37484009f74e15d3111a1f335c532a5
29 files changed
+2476
-61
.yapfignore
+1
@@ -1 +1,2 @@
1
collect_env.py
2
+vllm/model_executor/layers/fla/ops/*.py
docs/models/supported_models.md
+1
@@ -403,6 +403,7 @@ th {
403
| `Qwen2MoeForCausalLM` | Qwen2MoE | `Qwen/Qwen1.5-MoE-A2.7B`, `Qwen/Qwen1.5-MoE-A2.7B-Chat`, etc. | ✅︎ | ✅︎ | ✅︎ |
404
| `Qwen3ForCausalLM` | Qwen3 | `Qwen/Qwen3-8B`, etc. | ✅︎ | ✅︎ | ✅︎ |
405
| `Qwen3MoeForCausalLM` | Qwen3MoE | `Qwen/Qwen3-30B-A3B`, etc. | ✅︎ | ✅︎ | ✅︎ |
406
+| `Qwen3NextForCausalLM` | Qwen3.5MoE | `Qwen/Qwen3-Next-80B-A3B-Instruct`, etc. | ✅︎ | ✅︎ | ✅︎ |
407
| `SeedOssForCausalLM` | SeedOss | `ByteDance-Seed/Seed-OSS-36B-Instruct`, etc. | ✅︎ | ✅︎ | ✅︎ |
408
| `StableLmForCausalLM` | StableLM | `stabilityai/stablelm-3b-4e1t`, `stabilityai/stablelm-base-alpha-7b-v2`, etc. | | | ✅︎ |
409
| `Starcoder2ForCausalLM` | Starcoder2 | `bigcode/starcoder2-3b`, `bigcode/starcoder2-7b`, `bigcode/starcoder2-15b`, etc. | | ✅︎ | ✅︎ |
pyproject.toml
+1
@@ -228,6 +228,7 @@ fo = "fo"
228
ba = "ba"
229
230
[tool.typos.type.py.extend-words]
231
+ba = "ba"
232
233
[tool.typos.type.cpp]
234
extend-glob = ["*.cu"]
tests/models/registry.py
+5
-1
@@ -326,6 +326,8 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
326
"Qwen2MoeForCausalLM": _HfExamplesInfo("Qwen/Qwen1.5-MoE-A2.7B-Chat"),
327
"Qwen3ForCausalLM": _HfExamplesInfo("Qwen/Qwen3-8B"),
328
"Qwen3MoeForCausalLM": _HfExamplesInfo("Qwen/Qwen3-30B-A3B"),
329
+ "Qwen3NextForCausalLM": _HfExamplesInfo("Qwen/Qwen3-Next-80B-A3B-Instruct",
330
+ min_transformers_version="4.56.2"),
331
"RWForCausalLM": _HfExamplesInfo("tiiuae/falcon-40b"),
332
"SeedOssForCausalLM": _HfExamplesInfo("ByteDance-Seed/Seed-OSS-36B-Instruct", # noqa: E501
333
trust_remote_code=True,
@@ -640,7 +642,9 @@ _SPECULATIVE_DECODING_EXAMPLE_MODELS = {
642
is_available_online=False),
643
"MiMoMTPModel": _HfExamplesInfo("XiaomiMiMo/MiMo-7B-RL",
644
trust_remote_code=True,
643
- speculative_model="XiaomiMiMo/MiMo-7B-RL")
645
+ speculative_model="XiaomiMiMo/MiMo-7B-RL"),
646
+ "Qwen3NextMTP": _HfExamplesInfo("Qwen/Qwen3-Next-80B-A3B-Instruct",
647
+ min_transformers_version="4.56.2"),
648
}
649
650
_TRANSFORMERS_BACKEND_MODELS = {
vllm/config/__init__.py
+46
-10
@@ -1508,7 +1508,8 @@ class ModelConfig:
1508
if (self.hf_text_config.model_type == "deepseek_mtp"
1509
or self.hf_config.model_type == "mimo_mtp"
1510
or self.hf_config.model_type == "glm4_moe_mtp"
1511
- or self.hf_config.model_type == "ernie_mtp"):
1511
+ or self.hf_config.model_type == "ernie_mtp"
1512
+ or self.hf_config.model_type == "qwen3_next_mtp"):
1513
total_num_hidden_layers = getattr(self.hf_text_config,
1514
"num_nextn_predict_layers", 0)
1515
else:
@@ -1571,15 +1572,28 @@ class ModelConfig:
1572
if attn_type_list:
1573
return sum(t == 1 for t in attn_type_list[start:end])
1574
1574
- if layers_block_type_value is None and attn_type_list is None:
1575
+ # Hybrid model Qwen3Next
1576
+ layer_types_value = getattr(self.hf_config, "layer_types", None)
1577
+ if layer_types_value is not None:
1578
+ if getattr(block_type, "value", block_type) == "attention":
1579
+ return sum(t == "full_attention"
1580
+ for t in layer_types_value[start:end])
1581
+ elif getattr(block_type, "value",
1582
+ block_type) == "linear_attention":
1583
+ return sum(t == "linear_attention"
1584
+ for t in layer_types_value[start:end])
1585
+ else:
1586
+ return sum(t == getattr(block_type, "value", block_type)
1587
+ for t in layer_types_value[start:end])
1588
+
1589
+ if (layers_block_type_value is None and attn_type_list is None
1590
+ and layer_types_value is None):
1591
raise ValueError(
1592
"The model is an hybrid without a"
1577
- "layers_block_type or an attn_type_list in the hf_config,"
1578
- "cannot determine the num of "
1593
+ "layers_block_type or an attn_type_list, or a layer_types "
1594
+ "in the hf_config, cannot determine the num of "
1595
f"{block_type.value} layers")
1596
1581
- return sum(t == 1 for t in attn_type_list[start:end])
1582
-
1597
def get_mamba_chunk_size(self) -> Optional[int]:
1598
"""
1599
Returns the mamba chunk size if it exists
@@ -1866,7 +1880,7 @@ class DeviceConfig:
1880
1881
SpeculativeMethod = Literal["ngram", "eagle", "eagle3", "medusa",
1882
"mlp_speculator", "draft_model", "deepseek_mtp",
1869
- "ernie_mtp"]
1883
+ "ernie_mtp", "qwen3_next_mtp"]
1884
1885
1886
@config
@@ -2007,7 +2021,15 @@ class SpeculativeConfig:
2021
"n_predict": n_predict,
2022
"architectures": ["ErnieMTPModel"]
2023
})
2010
- return hf_config
2024
+
2025
+ if hf_config.model_type == "qwen3_next":
2026
+ hf_config.model_type = "qwen3_next_mtp"
2027
+ if hf_config.model_type == "qwen3_next_mtp":
2028
+ n_predict = getattr(hf_config, "num_nextn_predict_layers", None)
2029
+ hf_config.update({
2030
+ "n_predict": n_predict,
2031
+ "architectures": ["Qwen3NextMTP"]
2032
+ })
2033
2034
return hf_config
2035
@@ -2028,9 +2050,13 @@ class SpeculativeConfig:
2050
(self.target_model_config.hf_text_config.model_type \
2051
== "deepseek_v3" or
2052
self.target_model_config.hf_text_config.model_type in
2031
- ("mimo","ernie4_5_moe")):
2053
+ ("mimo","ernie4_5_moe", "qwen3_next")):
2054
# use the draft model from the same model:
2055
self.model = self.target_model_config.model
2056
+ # Align the quantization of draft model for cases such as
2057
+ # --quantization fp8 with a bf16 checkpoint.
2058
+ if not self.quantization:
2059
+ self.quantization = self.target_model_config.quantization
2060
elif self.method in ("ngram", "[ngram]"):
2061
self.model = "ngram"
2062
else:
@@ -2140,6 +2166,15 @@ class SpeculativeConfig:
2166
"one layer. Might need some code changes " \
2167
"to support multiple layers."
2168
)
2169
+ elif (self.draft_model_config.hf_config.model_type ==
2170
+ "qwen3_next_mtp"):
2171
+ self.method = "qwen3_next_mtp"
2172
+ if self.num_speculative_tokens > 1:
2173
+ logger.warning(
2174
+ "All Qwen3Next MTP models only have " \
2175
+ "one layer. Might need some code changes " \
2176
+ "to support multiple layers."
2177
+ )
2178
else:
2179
self.method = "draft_model"
2180
raise NotImplementedError(
@@ -2355,7 +2390,8 @@ class SpeculativeConfig:
2390
return self.num_speculative_tokens
2391
2392
def use_eagle(self) -> bool:
2358
- return self.method in ("eagle", "eagle3", "deepseek_mtp", "ernie_mtp")
2393
+ return self.method in ("eagle", "eagle3", "deepseek_mtp", "ernie_mtp",
2394
+ "qwen3_next_mtp")
2395
2396
def __repr__(self) -> str:
2397
method = self.method
vllm/config/compilation.py
+1
@@ -341,6 +341,7 @@ class CompilationConfig:
341
"vllm.short_conv",
342
"vllm.linear_attention",
343
"vllm.plamo2_mamba_mixer",
344
+ "vllm.gdn_attention",
345
]
346
347
def compute_hash(self) -> str:
vllm/model_executor/layers/fla/ops/chunk_delta_h.py
+3
-2
@@ -14,7 +14,7 @@ import torch
14
from vllm.triton_utils import tl, triton
15
16
from .index import prepare_chunk_indices, prepare_chunk_offsets
17
-from .op import exp, safe_exp
17
+from .op import exp
18
from .utils import is_nvidia_hopper, use_cuda_graph
19
20
NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8, 16]
@@ -175,12 +175,13 @@ def chunk_gated_delta_rule_fwd_kernel_h_blockdim64(
175
boundary_check=(0, 1))
176
177
if USE_G:
178
+ m_t = (i_t * BT + tl.arange(0, BT)) < T
179
last_idx = min((i_t + 1) * BT, T) - 1
180
b_g_last = tl.load(g + bos * H + last_idx * H + i_h)
181
p_g = tl.make_block_ptr(g + bos * H + i_h, (T, ), (H, ),
182
(i_t * BT, ), (BT, ), (0, ))
183
b_g = tl.load(p_g, boundary_check=(0, ))
183
- b_v_new = b_v_new * safe_exp(b_g_last - b_g)[:, None]
184
+ b_v_new = b_v_new * tl.where(m_t, exp(b_g_last - b_g), 0)[:, None]
185
b_g_last = exp(b_g_last)
186
b_h1 = b_h1 * b_g_last
187
if K > 64:
vllm/model_executor/layers/fla/ops/chunk_o.py
+5
-4
@@ -16,7 +16,7 @@ import torch
16
from vllm.triton_utils import tl, triton
17
18
from .index import prepare_chunk_indices
19
-from .op import exp, safe_exp
19
+from .op import exp
20
from .utils import FLA_GDN_FIX_BT, check_shared_mem, is_nvidia_hopper
21
22
BKV_LIST = [64, 128] if check_shared_mem() else [32, 64]
@@ -112,10 +112,11 @@ def chunk_fwd_kernel_o(
112
p_g = tl.make_block_ptr(g, (T, ), (H, ), (i_t * BT, ), (BT, ), (0, ))
113
b_g = tl.load(p_g, boundary_check=(0, ))
114
b_o = b_o * exp(b_g)[:, None]
115
- b_A = b_A * safe_exp(b_g[:, None] - b_g[None, :])
115
+ b_A = b_A * exp(b_g[:, None] - b_g[None, :])
116
117
- o_i = tl.arange(0, BT)
118
- m_A = o_i[:, None] >= o_i[None, :]
117
+ o_t = i_t * BT + tl.arange(0, BT)
118
+ m_t = o_t < T
119
+ m_A = (o_t[:, None] >= o_t[None, :]) & (m_t[:, None] & m_t)
120
b_A = tl.where(m_A, b_A, 0)
121
122
p_v = tl.make_block_ptr(v, (T, V), (H * V, 1), (i_t * BT, i_v * BV),
vllm/model_executor/layers/fla/ops/chunk_scaled_dot_kkt.py
+6
-4
@@ -14,7 +14,7 @@ import torch
14
from vllm.triton_utils import tl, triton
15
16
from .index import prepare_chunk_indices
17
-from .op import safe_exp
17
+from .op import exp
18
19
20
@triton.heuristics({
@@ -56,7 +56,8 @@ def chunk_scaled_dot_kkt_fwd_kernel(
56
T = eos - bos
57
else:
58
bos, eos = i_b * T, i_b * T + T
59
- o_t = tl.arange(0, BT)
59
+ o_t = i_t * BT + tl.arange(0, BT)
60
+ m_t = o_t < T
61
62
p_beta = tl.make_block_ptr(beta + bos * H + i_h, (T, ), (H, ),
63
(i_t * BT, ), (BT, ), (0, ))
@@ -76,9 +77,10 @@ def chunk_scaled_dot_kkt_fwd_kernel(
77
(i_t * BT, ), (BT, ), (0, ))
78
b_g = tl.load(p_g, boundary_check=(0, ))
79
b_g_diff = b_g[:, None] - b_g[None, :]
79
- b_A = b_A * safe_exp(b_g_diff)
80
+ b_A = b_A * exp(b_g_diff)
81
81
- b_A = tl.where(o_t[:, None] > o_t[None, :], b_A, 0)
82
+ m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t)
83
+ b_A = tl.where(m_A, b_A, 0)
84
p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (T, BT), (BT * H, 1),
85
(i_t * BT, 0), (BT, BT), (1, 0))
86
tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1))
vllm/model_executor/layers/fla/ops/fused_recurrent.py
+2
-2
@@ -116,8 +116,8 @@ def fused_recurrent_gated_delta_rule_fwd_kernel(
116
b_g = tl.load(p_g).to(tl.float32)
117
118
if USE_QK_L2NORM_IN_KERNEL:
119
- b_q = b_q / (tl.sqrt(tl.sum(b_q * b_q)) + 1e-6)
120
- b_k = b_k / (tl.sqrt(tl.sum(b_k * b_k)) + 1e-6)
119
+ b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6)
120
+ b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6)
121
b_q = b_q * scale
122
# [BK, BV]
123
b_h *= exp(b_g)
vllm/model_executor/layers/fla/ops/l2norm.py
+1
-1
@@ -78,7 +78,7 @@ def l2norm_fwd_kernel2(X, Y, eps, M, N: tl.constexpr, MBLOCK: tl.constexpr):
78
row_idx = xoffset + tl.arange(0, MBLOCK)[:, None]
79
xmask = row_idx < M
80
rindex = tl.arange(0, N)[None, :]
81
- xs = tl.load(X + (rindex + N * row_idx), None).to(tl.float32)
81
+ xs = tl.load(X + (rindex + N * row_idx), xmask).to(tl.float32)
82
square = tl.broadcast_to(xs * xs, [MBLOCK, N])
83
square_sum = tl.sum(tl.where(xmask, square, 0), 1)[:, None]
84
rsqrt = tl.rsqrt(square_sum + eps)
vllm/model_executor/layers/fla/ops/op.py
-5
@@ -28,11 +28,6 @@ else:
28
log2 = tl.log2
29
30
31
-@triton.jit
32
-def safe_exp(x):
33
- return exp(tl.where(x <= 0, x, float('-inf')))
34
-
35
-
31
if not hasattr(tl, 'gather'):
32
33
@triton.jit
vllm/model_executor/layers/mamba/mamba_utils.py
+37
@@ -70,6 +70,15 @@ class MambaStateDtypeCalculator:
70
model_dtype)
71
return (conv_state_dtype, )
72
73
+ @classmethod
74
+ def gated_delta_net_state_dtype(
75
+ cls,
76
+ model_dtype: Union[ModelDType, torch.dtype],
77
+ mamba_cache_dtype: MambaDType,
78
+ ) -> tuple[torch.dtype, torch.dtype]:
79
+ state_dtype = get_kv_cache_torch_dtype(mamba_cache_dtype, model_dtype)
80
+ return (state_dtype, state_dtype)
81
+
82
83
class MambaStateShapeCalculator:
84
@@ -163,3 +172,31 @@ class MambaStateShapeCalculator:
172
173
# for n_groups == 1, this is exactly tp_size - n_groups
174
return tp_size - ngroups
175
+
176
+ @classmethod
177
+ def gated_delta_net_state_shape(
178
+ cls,
179
+ tp_world_size: int,
180
+ num_k_heads: int,
181
+ num_v_heads: int,
182
+ head_k_dim: int,
183
+ head_v_dim: int,
184
+ conv_kernel_size: int,
185
+ num_spec: int = 0,
186
+ use_v1: bool = True,
187
+ ):
188
+ conv_dim = (head_k_dim * num_k_heads * 2 + head_v_dim * num_v_heads)
189
+ conv_state_shape = (
190
+ divide(conv_dim, tp_world_size),
191
+ conv_kernel_size - 1 + num_spec,
192
+ )
193
+
194
+ # In V0, the conv_state shape was swapped during allocation in
195
+ # MambaCacheManager, but in V1 it needs to be determined here at the
196
+ # calculation level
197
+ if use_v1:
198
+ conv_state_shape = conv_state_shape[1], conv_state_shape[0]
199
+
200
+ temporal_state_shape = (divide(num_v_heads,
201
+ tp_world_size), head_k_dim, head_v_dim)
202
+ return conv_state_shape, temporal_state_shape
vllm/model_executor/layers/mamba/ops/causal_conv1d.py
+41
-7
@@ -464,7 +464,9 @@ def causal_conv1d_fn(
464
# 3. mapping from sequence x[idx] to a cache line at index as specified via cache_indices[idx]
465
# 4. computation can be skipped if cache_indices[idx] == pad_slot_id
466
num_cache_lines = conv_states.size(0)
467
- assert (num_cache_lines, dim, width - 1) == conv_states.shape
467
+ assert (num_cache_lines == conv_states.shape[0]
468
+ and dim == conv_states.shape[1]
469
+ and width - 1 <= conv_states.shape[2])
470
stride_istate_seq = conv_states.stride(0)
471
stride_istate_dim = conv_states.stride(1)
472
stride_istate_token = conv_states.stride(2)
@@ -623,6 +625,7 @@ def _causal_conv1d_update_kernel(
625
conv_state_ptr,
626
cache_seqlens_ptr, # circular buffer
627
conv_state_indices_ptr,
628
+ num_accepted_tokens_ptr,
629
o_ptr, # (batch, dim, seqlen)
630
# Matrix dimensions
631
batch: int,
@@ -639,6 +642,7 @@ def _causal_conv1d_update_kernel(
642
stride_conv_state_seq: tl.constexpr,
643
stride_conv_state_dim: tl.constexpr,
644
stride_conv_state_tok: tl.constexpr,
645
+ stride_state_indices: tl.constexpr,
646
stride_o_seq: tl.constexpr,
647
stride_o_dim: tl.constexpr,
648
stride_o_token: tl.constexpr,
@@ -649,6 +653,7 @@ def _causal_conv1d_update_kernel(
653
KERNEL_WIDTH: tl.constexpr,
654
SILU_ACTIVATION: tl.constexpr,
655
IS_CONTINUOUS_BATCHING: tl.constexpr,
656
+ IS_SPEC_DECODING: tl.constexpr,
657
NP2_STATELEN: tl.constexpr,
658
USE_PAD_SLOT: tl.constexpr,
659
BLOCK_N: tl.constexpr,
@@ -663,8 +668,9 @@ def _causal_conv1d_update_kernel(
668
669
if IS_CONTINUOUS_BATCHING:
670
# mask = idx_seq < batch
666
- conv_state_batch_coord = tl.load(conv_state_indices_ptr + idx_seq).to(
667
- tl.int64)
671
+ conv_state_batch_coord = tl.load(conv_state_indices_ptr +
672
+ idx_seq * stride_state_indices).to(
673
+ tl.int64)
674
else:
675
conv_state_batch_coord = idx_seq
676
if USE_PAD_SLOT: # noqa
@@ -672,13 +678,32 @@ def _causal_conv1d_update_kernel(
678
# not processing as this is not the actual sequence
679
return
680
681
+ if IS_SPEC_DECODING:
682
+ # The rolling of conv state:
683
+ #
684
+ # Before forward, the conv_state is:
685
+ # [history1, history2, ..., historyM].
686
+ #
687
+ # After forward, the conv_state becomes:
688
+ # [history2, ..., historyM, draft1, draft2, ..., draftN].
689
+ #
690
+ # After acceptance, it becomes:
691
+ #
692
+ # - accept 1 tokens: [history2, ..., historyM, draft1]
693
+ # - accept 2 tokens: [history3, ..., historyM, draft1, draft2]
694
+ # - and so on.
695
+ conv_state_token_offset = (tl.load(num_accepted_tokens_ptr + idx_seq) -
696
+ 1)
697
+ else:
698
+ conv_state_token_offset = 0
699
+
700
# STEP 1: READ init_state data
701
conv_states_base = (conv_state_ptr +
702
(conv_state_batch_coord * stride_conv_state_seq) +
703
(idx_feats * stride_conv_state_dim))
704
mask_w = idx_feats < dim
705
681
- prior_tokens = conv_states_base
706
+ prior_tokens = conv_states_base + conv_state_token_offset * stride_conv_state_tok
707
if KERNEL_WIDTH >= 2:
708
conv_states_ptrs = prior_tokens # [BLOCK_N]
709
col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
@@ -695,10 +720,14 @@ def _causal_conv1d_update_kernel(
720
# STEP 2: assume state_len > seqlen
721
idx_tokens = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
722
723
+ # The conv_state updates works in a sliding window manner,
724
+ # at each forward pass, the tokens are shift by 1, so we
725
+ # load since idx_tokens + 1.
726
conv_state_ptrs_source = (
727
conv_state_ptr + (conv_state_batch_coord * stride_conv_state_seq) +
728
+ conv_state_token_offset * stride_conv_state_tok +
729
(idx_feats * stride_conv_state_dim)[None, :] +
701
- ((idx_tokens + seqlen) * stride_conv_state_tok)[:, None]
730
+ ((idx_tokens + 1) * stride_conv_state_tok)[:, None]
731
) # [BLOCK_M, BLOCK_N]
732
mask = ((conv_state_batch_coord < num_cache_lines)
733
& ((idx_tokens + seqlen) < state_len)[:, None]
@@ -820,6 +849,7 @@ def causal_conv1d_update(
849
activation: Union[bool, str, None] = None,
850
cache_seqlens: Optional[torch.Tensor] = None,
851
conv_state_indices: Optional[torch.Tensor] = None,
852
+ num_accepted_tokens: Optional[torch.Tensor] = None,
853
pad_slot_id: int = PAD_SLOT_ID,
854
metadata=None,
855
validate_data=False,
@@ -890,10 +920,11 @@ def causal_conv1d_update(
920
) # X (batch, dim, seqlen)
921
922
stride_o_seq, stride_o_dim, stride_o_token = out.stride()
893
-
923
stride_istate_seq, stride_istate_dim, stride_istate_token = conv_state.stride(
924
)
896
- state_len = width - 1
925
+ stride_state_indices = conv_state_indices.stride(
926
+ 0) if conv_state_indices is not None else 0
927
+ state_len = width - 1 + (seqlen - 1) # effective state_len needed
928
np2_statelen = triton.next_power_of_2(state_len)
929
930
def grid(META):
@@ -910,6 +941,7 @@ def causal_conv1d_update(
941
conv_state,
942
cache_seqlens,
943
conv_state_indices,
944
+ num_accepted_tokens,
945
out,
946
# Matrix dimensions
947
batch,
@@ -926,6 +958,7 @@ def causal_conv1d_update(
958
stride_istate_seq,
959
stride_istate_dim,
960
stride_istate_token,
961
+ stride_state_indices,
962
stride_o_seq,
963
stride_o_dim,
964
stride_o_token,
@@ -936,6 +969,7 @@ def causal_conv1d_update(
969
KERNEL_WIDTH=width,
970
SILU_ACTIVATION=activation in ["silu", "swish"],
971
IS_CONTINUOUS_BATCHING=conv_state_indices is not None,
972
+ IS_SPEC_DECODING=num_accepted_tokens is not None,
973
NP2_STATELEN=np2_statelen,
974
USE_PAD_SLOT=pad_slot_id is not None,
975
BLOCK_N=256,
vllm/model_executor/models/config.py
+2
-1
@@ -312,7 +312,8 @@ class MambaModelConfig(VerifyAndUpdateConfig):
312
313
# TODO(tdoublep): remove as full cuda graph support is added
314
FCG_NOT_SUPPORTED_MODELS = [
315
- "Lfm2ForCausalLM", "MiniMaxText01ForCausalLM"
315
+ "Lfm2ForCausalLM",
316
+ "MiniMaxText01ForCausalLM",
317
]
318
319
if (model_config.architecture not in FCG_NOT_SUPPORTED_MODELS
vllm/model_executor/models/qwen3_next.py
new
+1294
@@ -0,0 +1,1294 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Inference-only Qwen3Next model."""
4
+from collections.abc import Iterable
5
+from typing import Optional
6
+
7
+import torch
8
+import torch.nn.functional as F
9
+from einops import rearrange
10
+from torch import nn
11
+from transformers.activations import ACT2FN
12
+
13
+from vllm import envs
14
+from vllm.attention import Attention, AttentionBackend, AttentionMetadata
15
+from vllm.compilation.decorators import support_torch_compile
16
+from vllm.config import (CacheConfig, ModelConfig, SpeculativeConfig,
17
+ VllmConfig, get_current_vllm_config)
18
+from vllm.distributed import (divide, get_ep_group, get_pp_group,
19
+ get_tensor_model_parallel_rank,
20
+ get_tensor_model_parallel_world_size)
21
+from vllm.forward_context import ForwardContext, get_forward_context
22
+from vllm.logger import init_logger
23
+from vllm.model_executor.layers.fla.ops import (
24
+ RMSNormGated, chunk_gated_delta_rule, fused_recurrent_gated_delta_rule)
25
+from vllm.model_executor.layers.fused_moe import FusedMoE
26
+# yapf conflicts with isort for this block
27
+# yapf: disable
28
+from vllm.model_executor.layers.layernorm import (
29
+ GemmaRMSNorm as Qwen3NextRMSNorm)
30
+# yapf: enable
31
+from vllm.model_executor.layers.linear import (ColumnParallelLinear,
32
+ MergedColumnParallelLinear,
33
+ QKVParallelLinear,
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_mixer2 import (
39
+ mamba_v2_sharded_weight_loader)
40
+from vllm.model_executor.layers.mamba.mamba_utils import (
41
+ MambaStateDtypeCalculator, MambaStateShapeCalculator)
42
+from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
43
+ causal_conv1d_fn, causal_conv1d_update)
44
+from vllm.model_executor.layers.quantization import QuantizationConfig
45
+from vllm.model_executor.layers.quantization.gptq import GPTQConfig
46
+from vllm.model_executor.layers.quantization.gptq_marlin import (
47
+ GPTQMarlinConfig)
48
+from vllm.model_executor.layers.rotary_embedding import get_rope
49
+from vllm.model_executor.layers.vocab_parallel_embedding import (
50
+ DEFAULT_VOCAB_PADDING_SIZE, ParallelLMHead, VocabParallelEmbedding)
51
+from vllm.model_executor.model_loader.weight_utils import (
52
+ default_weight_loader, sharded_weight_loader)
53
+from vllm.model_executor.models.mamba_cache import MambaCacheParams
54
+from vllm.model_executor.models.qwen2_moe import Qwen2MoeMLP as Qwen3NextMLP
55
+from vllm.model_executor.sampling_metadata import SamplingMetadata
56
+from vllm.model_executor.utils import set_weight_attrs
57
+from vllm.platforms import current_platform
58
+from vllm.sequence import IntermediateTensors
59
+from vllm.transformers_utils.configs import Qwen3NextConfig
60
+from vllm.triton_utils import tl, triton
61
+from vllm.utils import direct_register_custom_op
62
+from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
63
+
64
+from .interfaces import (HasInnerState, IsHybrid, MixtureOfExperts,
65
+ SupportsLoRA, SupportsPP)
66
+from .utils import (AutoWeightsLoader, PPMissingLayer, extract_layer_index,
67
+ is_pp_missing_parameter,
68
+ make_empty_intermediate_tensors_factory, make_layers,
69
+ maybe_prefix)
70
+
71
+logger = init_logger(__name__)
72
+
73
+KVCache = tuple[torch.Tensor, torch.Tensor]
74
+
75
+
76
+class Qwen3NextSparseMoeBlock(nn.Module):
77
+
78
+ def __init__(
79
+ self,
80
+ config: Qwen3NextConfig,
81
+ quant_config: Optional[QuantizationConfig] = None,
82
+ prefix: str = "",
83
+ enable_eplb: bool = False,
84
+ ):
85
+ super().__init__()
86
+ self.tp_size = get_tensor_model_parallel_world_size()
87
+
88
+ self.ep_group = get_ep_group().device_group
89
+ self.ep_rank = self.ep_group.rank()
90
+ self.ep_size = self.ep_group.size()
91
+ self.n_routed_experts = config.num_experts
92
+
93
+ if self.tp_size > config.num_experts:
94
+ raise ValueError(
95
+ f"Tensor parallel size {self.tp_size} is greater than "
96
+ f"the number of experts {config.num_experts}.")
97
+
98
+ # Load balancing settings.
99
+ vllm_config = get_current_vllm_config()
100
+ eplb_config = vllm_config.parallel_config.eplb_config
101
+ self.enable_eplb = enable_eplb
102
+
103
+ self.n_logical_experts = self.n_routed_experts
104
+ self.n_redundant_experts = eplb_config.num_redundant_experts
105
+ self.n_physical_experts = (self.n_logical_experts +
106
+ self.n_redundant_experts)
107
+ self.n_local_physical_experts = self.n_physical_experts // self.ep_size
108
+
109
+ self.physical_expert_start = (self.ep_rank *
110
+ self.n_local_physical_experts)
111
+ self.physical_expert_end = (self.physical_expert_start +
112
+ self.n_local_physical_experts)
113
+
114
+ self.experts = FusedMoE(num_experts=self.n_routed_experts,
115
+ top_k=config.num_experts_per_tok,
116
+ hidden_size=config.hidden_size,
117
+ intermediate_size=config.moe_intermediate_size,
118
+ reduce_results=False,
119
+ renormalize=config.norm_topk_prob,
120
+ quant_config=quant_config,
121
+ prefix=f"{prefix}.experts",
122
+ enable_eplb=self.enable_eplb,
123
+ num_redundant_experts=self.n_redundant_experts)
124
+
125
+ self.gate = ReplicatedLinear(
126
+ config.hidden_size,
127
+ config.num_experts,
128
+ bias=False,
129
+ quant_config=self._maybe_ignore_quant_config(quant_config),
130
+ prefix=f"{prefix}.gate")
131
+
132
+ if config.shared_expert_intermediate_size > 0:
133
+ self.shared_expert = Qwen3NextMLP(
134
+ hidden_size=config.hidden_size,
135
+ intermediate_size=config.shared_expert_intermediate_size,
136
+ hidden_act=config.hidden_act,
137
+ quant_config=quant_config,
138
+ reduce_results=self.experts.must_reduce_shared_expert_outputs(
139
+ ),
140
+ )
141
+ else:
142
+ self.shared_expert = None
143
+ self.shared_expert_gate = torch.nn.Linear(config.hidden_size,
144
+ 1,
145
+ bias=False)
146
+
147
+ def _maybe_ignore_quant_config(self, quant_config: QuantizationConfig):
148
+ # GPTQ configs do not have a list of ignored modules, however AutoGPTQ
149
+ # seems to avoid gate quantization.
150
+ # See: https://huggingface.co/Qwen/Qwen3-30B-A3B-GPTQ-Int4
151
+ if isinstance(quant_config, (GPTQConfig, GPTQMarlinConfig)):
152
+ return None
153
+ return quant_config
154
+
155
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
156
+ # NOTE: hidden_states can have either 1D or 2D shape.
157
+ orig_shape = hidden_states.shape
158
+ hidden_dim = hidden_states.shape[-1]
159
+ hidden_states = hidden_states.view(-1, hidden_dim)
160
+
161
+ shared_output = None
162
+ if self.shared_expert is not None:
163
+ shared_output = self.shared_expert(hidden_states)
164
+ if self.shared_expert_gate is not None:
165
+ shared_output = F.sigmoid(
166
+ self.shared_expert_gate(hidden_states)) * shared_output
167
+
168
+ # router_logits: (num_tokens, n_experts)
169
+ router_logits, _ = self.gate(hidden_states)
170
+ final_hidden_states = self.experts(hidden_states=hidden_states,
171
+ router_logits=router_logits)
172
+
173
+ if shared_output is not None:
174
+ final_hidden_states = final_hidden_states + shared_output
175
+ if self.tp_size > 1:
176
+ final_hidden_states = self.experts.maybe_all_reduce_tensor_model_parallel( # noqa E501
177
+ final_hidden_states)
178
+
179
+ return final_hidden_states.view(orig_shape)
180
+
181
+
182
+class Qwen3NextGatedDeltaNet(nn.Module, MambaBase):
183
+
184
+ @property
185
+ def mamba_type(self) -> str:
186
+ return "linear_attention"
187
+
188
+ def get_attn_backend(self) -> type["AttentionBackend"]:
189
+ from vllm.v1.attention.backends.gdn_attn import GDNAttentionBackend
190
+ return GDNAttentionBackend
191
+
192
+ def get_state_dtype(self) -> tuple[torch.dtype, torch.dtype]:
193
+ return MambaStateDtypeCalculator.gated_delta_net_state_dtype(
194
+ self.model_config.dtype, self.cache_config.mamba_cache_dtype)
195
+
196
+ def get_state_shape(self) -> tuple[tuple[int, ...], tuple[int, ...]]:
197
+ return MambaStateShapeCalculator.gated_delta_net_state_shape(
198
+ self.tp_size,
199
+ self.num_k_heads,
200
+ self.num_v_heads,
201
+ self.head_k_dim,
202
+ self.head_v_dim,
203
+ self.conv_kernel_size,
204
+ self.num_spec,
205
+ use_v1=True)
206
+
207
+ def __init__(
208
+ self,
209
+ config: Qwen3NextConfig,
210
+ model_config: Optional[ModelConfig] = None,
211
+ cache_config: Optional[CacheConfig] = None,
212
+ quant_config: Optional[QuantizationConfig] = None,
213
+ speculative_config: Optional[SpeculativeConfig] = None,
214
+ prefix: str = "",
215
+ ) -> None:
216
+ super().__init__()
217
+ self.tp_size = get_tensor_model_parallel_world_size()
218
+ self.tp_rank = get_tensor_model_parallel_rank()
219
+ self.hidden_size = config.hidden_size
220
+ self.num_v_heads = config.linear_num_value_heads
221
+ self.num_k_heads = config.linear_num_key_heads
222
+ self.head_k_dim = config.linear_key_head_dim
223
+ self.head_v_dim = config.linear_value_head_dim
224
+ self.key_dim = self.head_k_dim * self.num_k_heads
225
+ self.value_dim = self.head_v_dim * self.num_v_heads
226
+
227
+ self.conv_kernel_size = config.linear_conv_kernel_dim
228
+ self.layer_idx = extract_layer_index(prefix)
229
+ self.activation = config.hidden_act
230
+ self.act = ACT2FN[config.hidden_act]
231
+ self.layer_norm_epsilon = config.rms_norm_eps
232
+ self.prefix = prefix
233
+
234
+ self.config = config
235
+ self.model_config = model_config
236
+ self.cache_config = cache_config
237
+ self.quant_config = quant_config
238
+ self.speculative_config = speculative_config
239
+ self.num_spec = (self.speculative_config.num_speculative_tokens
240
+ if self.speculative_config else 0)
241
+
242
+ # QKV
243
+ self.conv_dim = self.key_dim * 2 + self.value_dim
244
+ self.conv1d = ColumnParallelLinear(
245
+ input_size=self.conv_kernel_size,
246
+ output_size=self.conv_dim,
247
+ bias=False,
248
+ prefix=f"{prefix}.conv1d",
249
+ )
250
+ self.conv1d.weight.data = self.conv1d.weight.data.unsqueeze(1)
251
+
252
+ # projection of the input hidden states
253
+ self.projection_size_qkvz = self.key_dim * 2 + self.value_dim * 2
254
+ self.projection_size_ba = self.num_v_heads * 2
255
+ self.in_proj = MergedColumnParallelLinear(
256
+ input_size=self.hidden_size,
257
+ output_sizes=[self.projection_size_qkvz, self.projection_size_ba],
258
+ bias=False,
259
+ quant_config=quant_config,
260
+ prefix=f"{prefix}.in_proj",
261
+ )
262
+
263
+ query_key_settings = (self.key_dim, 0, False)
264
+ value_settings = (self.value_dim, 0, False)
265
+
266
+ delattr(self.conv1d.weight, "weight_loader")
267
+ set_weight_attrs(
268
+ self.conv1d.weight, {
269
+ "weight_loader":
270
+ mamba_v2_sharded_weight_loader([
271
+ query_key_settings,
272
+ query_key_settings,
273
+ value_settings,
274
+ ], self.tp_size, self.tp_rank)
275
+ })
276
+
277
+ # selective projection used to make dt, B and C input dependant
278
+
279
+ # time step projection (discretization)
280
+ # instantiate once and copy inv_dt in init_weights of PretrainedModel
281
+ self.dt_bias = nn.Parameter(
282
+ torch.ones(self.num_v_heads // self.tp_size), )
283
+ self.A_log = nn.Parameter(
284
+ torch.empty(
285
+ divide(self.num_v_heads, self.tp_size),
286
+ dtype=torch.float32,
287
+ ))
288
+
289
+ set_weight_attrs(self.A_log,
290
+ {"weight_loader": sharded_weight_loader(0)})
291
+ set_weight_attrs(self.dt_bias,
292
+ {"weight_loader": sharded_weight_loader(0)})
293
+
294
+ self.norm = RMSNormGated(
295
+ self.head_v_dim,
296
+ eps=self.layer_norm_epsilon,
297
+ group_size=None,
298
+ norm_before_gate=True,
299
+ device=torch.cuda.current_device(),
300
+ dtype=config.torch_dtype,
301
+ )
302
+
303
+ self.out_proj = RowParallelLinear(self.value_dim,
304
+ self.hidden_size,
305
+ bias=False,
306
+ input_is_parallel=True,
307
+ quant_config=quant_config,
308
+ prefix=f"{prefix}.out_proj")
309
+
310
+ compilation_config = get_current_vllm_config().compilation_config
311
+ if prefix in compilation_config.static_forward_context:
312
+ raise ValueError(f"Duplicate layer name: {prefix}")
313
+ compilation_config.static_forward_context[prefix] = self
314
+
315
+ def fix_query_key_value_ordering(
316
+ self,
317
+ mixed_qkvz,
318
+ mixed_ba,
319
+ ):
320
+ """
321
+ Derives `query`, `key` and `value` tensors from `mixed_qkvzba`.
322
+ """
323
+ new_tensor_shape_qkvz = mixed_qkvz.size()[:-1] + (
324
+ self.num_k_heads // self.tp_size,
325
+ (self.head_k_dim + self.head_k_dim +
326
+ (self.head_v_dim + self.head_v_dim) * self.num_v_heads //
327
+ self.num_k_heads),
328
+ )
329
+ new_tensor_shape_ba = mixed_qkvz.size()[:-1] + (
330
+ self.num_k_heads // self.tp_size,
331
+ 2 * self.num_v_heads // self.num_k_heads,
332
+ )
333
+
334
+ mixed_qkvz = mixed_qkvz.view(*new_tensor_shape_qkvz)
335
+ mixed_ba = mixed_ba.view(*new_tensor_shape_ba)
336
+
337
+ split_arg_list_qkvz = [
338
+ self.head_k_dim,
339
+ self.head_k_dim,
340
+ (self.num_v_heads // self.num_k_heads * self.head_v_dim),
341
+ (self.num_v_heads // self.num_k_heads * self.head_v_dim),
342
+ ]
343
+ split_arg_list_ba = [
344
+ self.num_v_heads // self.num_k_heads,
345
+ self.num_v_heads // self.num_k_heads
346
+ ]
347
+
348
+ # [b, sq, ng, (hn + hn + np/ng * hn + np/ng + np/ng)]
349
+ # --> [b, sq, ng, hn], [b, sq, ng, hn], [b, sq, ng, np/ng * hn],
350
+ # [b, sq, ng, np/ng * hn], [b, sq, ng, np/ng], [b, sq, ng, np/ng]
351
+ (query, key, value, z) = torch.split(mixed_qkvz,
352
+ split_arg_list_qkvz,
353
+ dim=2)
354
+ (b, a) = torch.split(mixed_ba, split_arg_list_ba, dim=2)
355
+
356
+ # [b, sq, ng, np/ng * hn] -> [b, sq, np, hn]
357
+ value = value.reshape(value.size(0), -1, self.head_v_dim)
358
+ z = z.reshape(z.size(0), -1, self.head_v_dim)
359
+ b = b.reshape(b.size(0), self.num_v_heads // self.tp_size)
360
+ a = a.reshape(a.size(0), self.num_v_heads // self.tp_size)
361
+
362
+ return query, key, value, z, b, a
363
+
364
+ def rearrange_mixed_qkv(self, mixed_qkv):
365
+ if mixed_qkv is None:
366
+ return None, None, None
367
+ query, key, value = torch.split(
368
+ mixed_qkv,
369
+ [
370
+ self.key_dim // self.tp_size,
371
+ self.key_dim // self.tp_size,
372
+ self.value_dim // self.tp_size,
373
+ ],
374
+ dim=-1,
375
+ )
376
+ query, key = map(
377
+ lambda x: rearrange(x, 'l (h d) -> 1 l h d', d=self.head_k_dim),
378
+ (query, key))
379
+ value = rearrange(value, 'l (h d) -> 1 l h d', d=self.head_v_dim)
380
+ return query, key, value
381
+
382
+ def forward(
383
+ self,
384
+ hidden_states: torch.Tensor,
385
+ output: torch.Tensor,
386
+ cache_params: Optional[MambaCacheParams] = None,
387
+ ):
388
+ return torch.ops.vllm.gdn_attention(
389
+ hidden_states,
390
+ output,
391
+ self.prefix,
392
+ )
393
+
394
+ def _forward(
395
+ self,
396
+ hidden_states: torch.Tensor,
397
+ output: torch.Tensor,
398
+ ):
399
+ forward_context = get_forward_context()
400
+ attn_metadata: AttentionMetadata = forward_context.attn_metadata
401
+
402
+ if attn_metadata is None:
403
+ # V1 profile run
404
+ return
405
+
406
+ assert isinstance(attn_metadata, dict)
407
+ attn_metadata = attn_metadata[self.prefix]
408
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
409
+ has_initial_state = attn_metadata.has_initial_state
410
+ spec_query_start_loc = attn_metadata.spec_query_start_loc
411
+ non_spec_query_start_loc = attn_metadata.non_spec_query_start_loc
412
+ spec_sequence_masks = attn_metadata.spec_sequence_masks
413
+ spec_token_masks = attn_metadata.spec_token_masks
414
+ spec_state_indices_tensor = attn_metadata.spec_state_indices_tensor # noqa: E501
415
+ non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor # noqa: E501
416
+ self_kv_cache = self.kv_cache[forward_context.virtual_engine]
417
+ conv_state = self_kv_cache[0].transpose(-1, -2)
418
+ ssm_state = self_kv_cache[1]
419
+ num_actual_tokens = (attn_metadata.num_prefill_tokens +
420
+ attn_metadata.num_decode_tokens +
421
+ attn_metadata.num_spec_decode_tokens)
422
+ num_accepted_tokens = attn_metadata.num_accepted_tokens
423
+
424
+ # 1. Set up dimensions for reshapes later
425
+ projected_states, _ = self.in_proj(hidden_states[:num_actual_tokens])
426
+ if spec_token_masks is not None:
427
+ spec_token_masks = spec_token_masks[:num_actual_tokens]
428
+ projected_states_qkvz, projected_states_ba = torch.split(
429
+ projected_states,
430
+ [
431
+ self.projection_size_qkvz // self.tp_size,
432
+ self.projection_size_ba // self.tp_size
433
+ ],
434
+ dim=-1,
435
+ )
436
+ query, key, value, z, b, a = self.fix_query_key_value_ordering(
437
+ projected_states_qkvz, projected_states_ba)
438
+ query, key, value = map(lambda x: rearrange(x, 'l p d -> l (p d)'),
439
+ (query, key, value))
440
+ mixed_qkv = torch.cat((query, key, value), dim=-1)
441
+
442
+ # 2. Convolution sequence transformation
443
+ conv_weights = self.conv1d.weight.view(self.conv1d.weight.size(0),
444
+ self.conv1d.weight.size(2))
445
+
446
+ if spec_sequence_masks is not None:
447
+ if (attn_metadata.num_prefills == 0
448
+ and attn_metadata.num_decodes == 0):
449
+ mixed_qkv_spec = mixed_qkv
450
+ mixed_qkv_non_spec = None
451
+ else:
452
+ mixed_qkv_spec = mixed_qkv[spec_token_masks]
453
+ mixed_qkv_non_spec = mixed_qkv[~spec_token_masks]
454
+ else:
455
+ mixed_qkv_spec = None
456
+ mixed_qkv_non_spec = mixed_qkv
457
+
458
+ # 2.1: process the mutli-query part
459
+ if spec_sequence_masks is not None:
460
+ mixed_qkv_spec = mixed_qkv_spec.view(
461
+ attn_metadata.num_spec_decodes, -1, mixed_qkv_spec.size(-1))
462
+ mixed_qkv_spec = rearrange(mixed_qkv_spec, 'b l d -> b d l')
463
+ mixed_qkv_spec = causal_conv1d_update(
464
+ mixed_qkv_spec,
465
+ conv_state,
466
+ conv_weights,
467
+ self.conv1d.bias,
468
+ self.activation,
469
+ conv_state_indices=spec_state_indices_tensor[:, 0]
470
+ [:attn_metadata.num_spec_decodes],
471
+ num_accepted_tokens=num_accepted_tokens,
472
+ validate_data=False,
473
+ )
474
+ mixed_qkv_spec = rearrange(mixed_qkv_spec, 'b d l -> (b l) d')
475
+
476
+ # 2.2: process the remaining part
477
+ if attn_metadata.num_prefills > 0:
478
+ # - "cache_indices" updates the conv_state cache in positions
479
+ # pointed to by "mamba_cache_params.state_indices_tensor"
480
+ mixed_qkv_non_spec = causal_conv1d_fn(
481
+ mixed_qkv_non_spec.transpose(0, 1),
482
+ conv_weights,
483
+ self.conv1d.bias,
484
+ activation=self.activation,
485
+ conv_states=conv_state,
486
+ has_initial_state=has_initial_state,
487
+ cache_indices=non_spec_state_indices_tensor,
488
+ query_start_loc=non_spec_query_start_loc,
489
+ ).transpose(0, 1)
490
+ elif attn_metadata.num_decodes > 0:
491
+ mixed_qkv_non_spec = causal_conv1d_update(
492
+ mixed_qkv_non_spec,
493
+ conv_state,
494
+ conv_weights,
495
+ self.conv1d.bias,
496
+ self.activation,
497
+ conv_state_indices=non_spec_state_indices_tensor[:attn_metadata
498
+ .num_decodes],
499
+ validate_data=True,
500
+ )
501
+ else:
502
+ mixed_qkv_non_spec = None
503
+
504
+ query_spec, key_spec, value_spec = self.rearrange_mixed_qkv(
505
+ mixed_qkv_spec)
506
+ query_non_spec, key_non_spec, value_non_spec = self.rearrange_mixed_qkv(
507
+ mixed_qkv_non_spec)
508
+
509
+ beta = b.sigmoid()
510
+ # g = -self.A_log.float().exp() * F.softplus(a.float() + self.dt_bias)
511
+ g = fused_gdn_gating(self.A_log, a, self.dt_bias)
512
+ g, beta = map(lambda x: rearrange(x, 'l d -> 1 l d'), (g, beta))
513
+
514
+ if spec_sequence_masks is not None:
515
+ if (attn_metadata.num_prefills == 0
516
+ and attn_metadata.num_decodes == 0):
517
+ g_spec = g
518
+ beta_spec = beta
519
+ g_non_spec = None
520
+ beta_non_spec = None
521
+ else:
522
+ g_spec = g[:, spec_token_masks]
523
+ beta_spec = beta[:, spec_token_masks]
524
+ g_non_spec = g[:, ~spec_token_masks]
525
+ beta_non_spec = beta[:, ~spec_token_masks]
526
+ else:
527
+ g_spec = None
528
+ beta_spec = None
529
+ g_non_spec = g
530
+ beta_non_spec = beta
531
+
532
+ # 3. Recurrent attention
533
+
534
+ # 3.1: process the mutlti-query part
535
+ if spec_sequence_masks is not None:
536
+ core_attn_out_spec, last_recurrent_state = (
537
+ fused_recurrent_gated_delta_rule(
538
+ q=query_spec,
539
+ k=key_spec,
540
+ v=value_spec,
541
+ g=g_spec,
542
+ beta=beta_spec,
543
+ initial_state=ssm_state,
544
+ inplace_final_state=True,
545
+ cu_seqlens=spec_query_start_loc[:attn_metadata.
546
+ num_spec_decodes + 1],
547
+ ssm_state_indices=spec_state_indices_tensor,
548
+ num_accepted_tokens=num_accepted_tokens,
549
+ use_qk_l2norm_in_kernel=True,
550
+ ))
551
+ else:
552
+ core_attn_out_spec, last_recurrent_state = None, None
553
+
554
+ # 3.2: process the remaining part
555
+ if attn_metadata.num_prefills > 0:
556
+ initial_state = ssm_state[
557
+ non_spec_state_indices_tensor].contiguous()
558
+ initial_state[~has_initial_state, ...] = 0
559
+ (
560
+ core_attn_out_non_spec,
561
+ last_recurrent_state,
562
+ ) = chunk_gated_delta_rule(
563
+ q=query_non_spec,
564
+ k=key_non_spec,
565
+ v=value_non_spec,
566
+ g=g_non_spec,
567
+ beta=beta_non_spec,
568
+ initial_state=initial_state,
569
+ output_final_state=True,
570
+ cu_seqlens=non_spec_query_start_loc,
571
+ head_first=False,
572
+ use_qk_l2norm_in_kernel=True,
573
+ )
574
+ # Init cache
575
+ ssm_state[non_spec_state_indices_tensor] = last_recurrent_state.to(
576
+ ssm_state.dtype)
577
+ elif attn_metadata.num_decodes > 0:
578
+ core_attn_out_non_spec, last_recurrent_state = (
579
+ fused_recurrent_gated_delta_rule(
580
+ q=query_non_spec,
581
+ k=key_non_spec,
582
+ v=value_non_spec,
583
+ g=g_non_spec,
584
+ beta=beta_non_spec,
585
+ initial_state=ssm_state,
586
+ inplace_final_state=True,
587
+ cu_seqlens=non_spec_query_start_loc[:attn_metadata.
588
+ num_decodes + 1],
589
+ ssm_state_indices=non_spec_state_indices_tensor,
590
+ use_qk_l2norm_in_kernel=True,
591
+ ))
592
+ else:
593
+ core_attn_out_non_spec, last_recurrent_state = None, None
594
+
595
+ # Merge core attention output
596
+ if (spec_sequence_masks is not None
597
+ and core_attn_out_non_spec is not None):
598
+ core_attn_out = torch.empty(
599
+ (1, num_actual_tokens, *core_attn_out_spec.shape[2:]),
600
+ dtype=core_attn_out_non_spec.dtype,
601
+ device=core_attn_out_non_spec.device,
602
+ )
603
+ core_attn_out[:, spec_token_masks] = core_attn_out_spec
604
+ core_attn_out[:, ~spec_token_masks] = core_attn_out_non_spec
605
+ elif spec_sequence_masks is not None:
606
+ core_attn_out = core_attn_out_spec
607
+ else:
608
+ core_attn_out = core_attn_out_non_spec
609
+
610
+ z_shape_og = z.shape
611
+ # reshape input data into 2D tensor
612
+ core_attn_out = core_attn_out.reshape(-1, core_attn_out.shape[-1])
613
+ z = z.reshape(-1, z.shape[-1])
614
+ core_attn_out = self.norm(core_attn_out, z)
615
+ core_attn_out = core_attn_out.reshape(z_shape_og)
616
+ core_attn_out = rearrange(core_attn_out, '... h d -> ... (h d)')
617
+
618
+ output[:num_actual_tokens], _ = self.out_proj(core_attn_out)
619
+
620
+
621
+class Qwen3NextAttention(nn.Module):
622
+
623
+ def __init__(
624
+ self,
625
+ config: Qwen3NextConfig,
626
+ model_config: Optional[ModelConfig] = None,
627
+ cache_config: Optional[CacheConfig] = None,
628
+ quant_config: Optional[QuantizationConfig] = None,
629
+ prefix: str = "",
630
+ ) -> None:
631
+ super().__init__()
632
+ self.config = config
633
+ self.hidden_size = config.hidden_size
634
+ tp_size = get_tensor_model_parallel_world_size()
635
+ self.total_num_heads = config.num_attention_heads
636
+ assert self.total_num_heads % tp_size == 0
637
+ self.num_heads = self.total_num_heads // tp_size
638
+ self.total_num_kv_heads = config.num_key_value_heads
639
+ if self.total_num_kv_heads >= tp_size:
640
+ # Number of KV heads is greater than TP size, so we partition
641
+ # the KV heads across multiple tensor parallel GPUs.
642
+ assert self.total_num_kv_heads % tp_size == 0
643
+ else:
644
+ # Number of KV heads is less than TP size, so we replicate
645
+ # the KV heads across multiple tensor parallel GPUs.
646
+ assert tp_size % self.total_num_kv_heads == 0
647
+ self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
648
+ self.head_dim = config.head_dim or (self.hidden_size // self.num_heads)
649
+ self.q_size = self.num_heads * self.head_dim
650
+ self.kv_size = self.num_kv_heads * self.head_dim
651
+ self.scaling = self.head_dim**-0.5
652
+ self.dual_chunk_attention_config = getattr(
653
+ config, "dual_chunk_attention_config", None)
654
+ self.attn_output_gate = getattr(config, "attn_output_gate", True)
655
+
656
+ self.qkv_proj = QKVParallelLinear(
657
+ config.hidden_size,
658
+ self.head_dim,
659
+ self.total_num_heads * (1 + self.attn_output_gate),
660
+ self.total_num_kv_heads,
661
+ bias=getattr(config, "qkv_bias", False),
662
+ quant_config=quant_config,
663
+ prefix=f"{prefix}.qkv_proj",
664
+ )
665
+
666
+ self.o_proj = RowParallelLinear(
667
+ self.total_num_heads * self.head_dim,
668
+ config.hidden_size,
669
+ bias=False,
670
+ quant_config=quant_config,
671
+ prefix=f"{prefix}.o_proj",
672
+ )
673
+
674
+ self.rotary_emb = get_rope(
675
+ head_size=self.head_dim,
676
+ rotary_dim=self.head_dim,
677
+ max_position=config.max_position_embeddings,
678
+ base=config.rope_theta,
679
+ rope_scaling=config.rope_scaling,
680
+ partial_rotary_factor=config.partial_rotary_factor,
681
+ dual_chunk_attention_config=self.dual_chunk_attention_config,
682
+ )
683
+
684
+ self.attn = Attention(
685
+ self.num_heads,
686
+ self.head_dim,
687
+ self.scaling,
688
+ num_kv_heads=self.num_kv_heads,
689
+ cache_config=cache_config,
690
+ quant_config=quant_config,
691
+ prefix=f"{prefix}.attn",
692
+ **{
693
+ "layer_idx": extract_layer_index(prefix),
694
+ "dual_chunk_attention_config":
695
+ self.dual_chunk_attention_config,
696
+ } if self.dual_chunk_attention_config else {},
697
+ )
698
+
699
+ self.q_norm = Qwen3NextRMSNorm(self.head_dim, eps=config.rms_norm_eps)
700
+ self.k_norm = Qwen3NextRMSNorm(self.head_dim, eps=config.rms_norm_eps)
701
+
702
+ def forward(
703
+ self,
704
+ positions: torch.Tensor,
705
+ output: torch.Tensor,
706
+ hidden_states: torch.Tensor,
707
+ ):
708
+ qkv, _ = self.qkv_proj(hidden_states)
709
+
710
+ if self.attn_output_gate:
711
+ q_gate, k, v = qkv.split(
712
+ [self.q_size * 2, self.kv_size, self.kv_size], dim=-1)
713
+ orig_shape = q_gate.shape[:-1]
714
+ q_gate = q_gate.view(*orig_shape, self.num_heads, -1)
715
+ q, gate = torch.chunk(q_gate, 2, dim=-1)
716
+ q = q.reshape(*orig_shape, -1)
717
+ gate = gate.reshape(*orig_shape, -1)
718
+ else:
719
+ q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size],
720
+ dim=-1)
721
+
722
+ q = self.q_norm(q.view(-1, self.num_heads, self.head_dim)).view(
723
+ -1, self.num_heads * self.head_dim)
724
+ k = self.k_norm(k.view(-1, self.num_kv_heads, self.head_dim)).view(
725
+ -1, self.num_kv_heads * self.head_dim)
726
+
727
+ q, k = self.rotary_emb(positions, q, k)
728
+
729
+ attn_output = self.attn(q, k, v)
730
+
731
+ if self.attn_output_gate:
732
+ gate = torch.sigmoid(gate)
733
+ attn_output = attn_output * gate
734
+
735
+ output[:], _ = self.o_proj(attn_output)
736
+
737
+
738
+class Qwen3NextDecoderLayer(nn.Module):
739
+
740
+ def __init__(
741
+ self,
742
+ config: Qwen3NextConfig,
743
+ layer_type: str,
744
+ model_config: Optional[ModelConfig] = None,
745
+ cache_config: Optional[CacheConfig] = None,
746
+ quant_config: Optional[QuantizationConfig] = None,
747
+ speculative_config: Optional[SpeculativeConfig] = None,
748
+ prefix: str = "",
749
+ enable_eplb: bool = False,
750
+ ) -> None:
751
+ super().__init__()
752
+ self.config = config
753
+
754
+ self.layer_type = layer_type
755
+ self.layer_idx = extract_layer_index(prefix)
756
+
757
+ if self.layer_type == "linear_attention":
758
+ self.linear_attn = Qwen3NextGatedDeltaNet(
759
+ config,
760
+ model_config=model_config,
761
+ cache_config=cache_config,
762
+ quant_config=quant_config,
763
+ speculative_config=speculative_config,
764
+ prefix=f'{prefix}.linear_attn')
765
+ elif self.layer_type == "full_attention":
766
+ self.self_attn = Qwen3NextAttention(
767
+ config,
768
+ model_config=model_config,
769
+ cache_config=cache_config,
770
+ quant_config=quant_config,
771
+ prefix=f'{prefix}.self_attn',
772
+ )
773
+ else:
774
+ raise ValueError(f"Invalid layer_type {self.layer_type}")
775
+
776
+ mlp_only_layers = ([] if not hasattr(config, "mlp_only_layers") else
777
+ config.mlp_only_layers)
778
+ if (self.layer_idx not in mlp_only_layers) and (
779
+ config.num_experts > 0 and
780
+ (self.layer_idx + 1) % config.decoder_sparse_step == 0):
781
+ self.mlp = Qwen3NextSparseMoeBlock(
782
+ config=config,
783
+ quant_config=quant_config,
784
+ prefix=f"{prefix}.mlp",
785
+ enable_eplb=enable_eplb,
786
+ )
787
+ else:
788
+ self.mlp = Qwen3NextMLP(
789
+ hidden_size=config.hidden_size,
790
+ intermediate_size=config.intermediate_size,
791
+ hidden_act=config.hidden_act,
792
+ quant_config=quant_config,
793
+ )
794
+
795
+ self.input_layernorm = Qwen3NextRMSNorm(config.hidden_size,
796
+ eps=config.rms_norm_eps)
797
+ self.post_attention_layernorm = Qwen3NextRMSNorm(
798
+ config.hidden_size, eps=config.rms_norm_eps)
799
+
800
+ self.layer_scale = getattr(config, "layer_scale", False)
801
+ if self.layer_scale:
802
+ self.attn_layer_scale = torch.nn.Parameter(
803
+ torch.zeros(
804
+ 1,
805
+ 1,
806
+ self.config.hidden_size,
807
+ dtype=config.torch_dtype,
808
+ ), )
809
+ self.ffn_layer_scale = torch.nn.Parameter(
810
+ torch.zeros(
811
+ 1,
812
+ 1,
813
+ self.config.hidden_size,
814
+ dtype=config.torch_dtype,
815
+ ), )
816
+
817
+ def forward(
818
+ self,
819
+ hidden_states: torch.Tensor,
820
+ residual: Optional[torch.Tensor],
821
+ positions: torch.Tensor = None,
822
+ **kwargs: object,
823
+ ):
824
+ if residual is None:
825
+ residual = hidden_states
826
+ hidden_states = self.input_layernorm(hidden_states)
827
+ else:
828
+ hidden_states, residual = self.input_layernorm(
829
+ hidden_states, residual)
830
+
831
+ self_attention_output = torch.empty_like(hidden_states)
832
+ if self.layer_type == "linear_attention":
833
+ self.linear_attn(
834
+ hidden_states=hidden_states,
835
+ output=self_attention_output,
836
+ )
837
+ elif self.layer_type == "full_attention":
838
+ self.self_attn(
839
+ hidden_states=hidden_states,
840
+ output=self_attention_output,
841
+ positions=positions,
842
+ )
843
+ else:
844
+ raise ValueError("Invalid layer_type")
845
+ hidden_states = self_attention_output
846
+
847
+ if self.layer_scale:
848
+ if len(hidden_states.shape) == 2:
849
+ hidden_states = hidden_states * (
850
+ self.attn_layer_scale.to(hidden_states.dtype)[0] + 1)
851
+ else:
852
+ hidden_states = hidden_states * (
853
+ self.attn_layer_scale.to(hidden_states.dtype) + 1)
854
+
855
+ # Fully Connected
856
+ hidden_states, residual = self.post_attention_layernorm(
857
+ hidden_states, residual)
858
+ hidden_states = self.mlp(hidden_states)
859
+
860
+ if self.layer_scale:
861
+ if len(hidden_states.shape) == 2:
862
+ hidden_states = hidden_states * (
863
+ self.ffn_layer_scale.to(hidden_states.dtype)[0] + 1)
864
+ else:
865
+ assert len(hidden_states.shape) == len(
866
+ self.ffn_layer_scale.shape
867
+ ), f'shape must be the same {len(hidden_states.shape)}, {len(self.ffn_layer_scale.shape)}' # noqa: E501
868
+ hidden_states = hidden_states * (
869
+ self.ffn_layer_scale.to(hidden_states.dtype) + 1)
870
+
871
+ return hidden_states, residual
872
+
873
+
874
+@support_torch_compile
875
+class Qwen3NextModel(nn.Module):
876
+
877
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
878
+ super().__init__()
879
+
880
+ config: Qwen3NextConfig = vllm_config.model_config.hf_config
881
+ model_config = vllm_config.model_config
882
+ cache_config = vllm_config.cache_config
883
+ quant_config = vllm_config.quant_config
884
+ parallel_config = vllm_config.parallel_config
885
+ lora_config = vllm_config.lora_config
886
+ speculative_config = vllm_config.speculative_config
887
+ enable_eplb = parallel_config.enable_eplb
888
+ eplb_config = parallel_config.eplb_config
889
+ self.num_redundant_experts = eplb_config.num_redundant_experts
890
+
891
+ self.config = config
892
+ lora_vocab = ((lora_config.lora_extra_vocab_size *
893
+ (lora_config.max_loras or 1)) if lora_config else 0)
894
+ self.vocab_size = config.vocab_size + lora_vocab
895
+
896
+ self.embed_tokens = VocabParallelEmbedding(
897
+ self.vocab_size,
898
+ config.hidden_size,
899
+ org_num_embeddings=config.vocab_size,
900
+ )
901
+
902
+ def get_layer(prefix: str):
903
+ return Qwen3NextDecoderLayer(
904
+ config,
905
+ layer_type=config.layer_types[extract_layer_index(prefix)],
906
+ model_config=model_config,
907
+ cache_config=cache_config,
908
+ quant_config=quant_config,
909
+ speculative_config=speculative_config,
910
+ prefix=prefix,
911
+ enable_eplb=enable_eplb,
912
+ )
913
+
914
+ self.start_layer, self.end_layer, self.layers = make_layers(
915
+ config.num_hidden_layers, get_layer, prefix=f"{prefix}.layers")
916
+ self.make_empty_intermediate_tensors = (
917
+ make_empty_intermediate_tensors_factory(
918
+ ["hidden_states", "residual"], config.hidden_size))
919
+
920
+ self.norm = Qwen3NextRMSNorm(config.hidden_size,
921
+ eps=config.rms_norm_eps)
922
+
923
+ def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
924
+ return self.embed_tokens(input_ids)
925
+
926
+ def forward(
927
+ self,
928
+ input_ids: torch.Tensor,
929
+ positions: torch.Tensor,
930
+ intermediate_tensors: Optional[IntermediateTensors] = None,
931
+ inputs_embeds: Optional[torch.Tensor] = None,
932
+ ) -> torch.Tensor:
933
+ if get_pp_group().is_first_rank:
934
+ if inputs_embeds is not None:
935
+ hidden_states = inputs_embeds
936
+ else:
937
+ hidden_states = self.get_input_embeddings(input_ids)
938
+ residual = None
939
+ else:
940
+ assert intermediate_tensors is not None
941
+ hidden_states = intermediate_tensors["hidden_states"]
942
+ residual = intermediate_tensors["residual"]
943
+
944
+ for layer in self.layers:
945
+ hidden_states, residual = layer(
946
+ positions=positions,
947
+ hidden_states=hidden_states,
948
+ residual=residual,
949
+ )
950
+
951
+ if not get_pp_group().is_last_rank:
952
+ return IntermediateTensors({
953
+ "hidden_states": hidden_states,
954
+ "residual": residual
955
+ })
956
+ hidden_states, _ = self.norm(hidden_states, residual)
957
+ return hidden_states
958
+
959
+ def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
960
+ # Params for weights, fp8 weight scales, fp8 activation scales
961
+ # (param_name, weight_name, expert_id, shard_id)
962
+ return FusedMoE.make_expert_params_mapping(
963
+ ckpt_gate_proj_name="gate_proj",
964
+ ckpt_down_proj_name="down_proj",
965
+ ckpt_up_proj_name="up_proj",
966
+ num_experts=self.config.num_experts,
967
+ num_redundant_experts=self.num_redundant_experts)
968
+
969
+ def load_weights(self, weights: Iterable[tuple[str,
970
+ torch.Tensor]]) -> set[str]:
971
+ stacked_params_mapping = [
972
+ # (param_name, shard_name, shard_id)
973
+ ("qkv_proj", "q_proj", "q"),
974
+ ("qkv_proj", "k_proj", "k"),
975
+ ("qkv_proj", "v_proj", "v"),
976
+ ("gate_up_proj", "gate_proj", 0),
977
+ ("gate_up_proj", "up_proj", 1),
978
+ ("in_proj", "in_proj_qkvz", 0),
979
+ ("in_proj", "in_proj_ba", 1),
980
+ ]
981
+
982
+ params_dict = dict(self.named_parameters())
983
+ loaded_params: set[str] = set()
984
+ expert_params_mapping = self.get_expert_mapping()
985
+ for name, loaded_weight in weights:
986
+ if "rotary_emb.inv_freq" in name:
987
+ continue
988
+
989
+ if name.startswith("mtp."):
990
+ continue
991
+
992
+ for param_name, weight_name, shard_id in stacked_params_mapping:
993
+ if weight_name not in name:
994
+ continue
995
+
996
+ if "mlp.experts" in name:
997
+ continue
998
+
999
+ name = name.replace(weight_name, param_name)
1000
+ # Skip loading extra bias for GPTQ models.
1001
+ if name.endswith(".bias") and name not in params_dict:
1002
+ continue
1003
+ # Skip layers on other devices.
1004
+ if is_pp_missing_parameter(name, self):
1005
+ continue
1006
+ # name = apply_attn_prefix(name, params_dict)
1007
+ if name not in params_dict:
1008
+ continue
1009
+ param = params_dict[name]
1010
+ weight_loader = param.weight_loader
1011
+ weight_loader(param, loaded_weight, shard_id)
1012
+ break
1013
+ else:
1014
+ for mapping in expert_params_mapping:
1015
+ param_name, weight_name, expert_id, shard_id = mapping
1016
+ if weight_name not in name:
1017
+ continue
1018
+ name = name.replace(weight_name, param_name)
1019
+ # Skip layers on other devices.
1020
+ if is_pp_missing_parameter(name, self):
1021
+ continue
1022
+ # Skip loading extra bias for GPTQ models.
1023
+ if ((name.endswith(".bias") or name.endswith("_bias"))
1024
+ and name not in params_dict):
1025
+ continue
1026
+ param = params_dict[name]
1027
+ weight_loader = param.weight_loader
1028
+ weight_loader(param,
1029
+ loaded_weight,
1030
+ name,
1031
+ shard_id=shard_id,
1032
+ expert_id=expert_id)
1033
+ break
1034
+ else:
1035
+ # Skip loading extra bias for GPTQ models.
1036
+ if name.endswith(".bias") and name not in params_dict:
1037
+ continue
1038
+ if is_pp_missing_parameter(name, self):
1039
+ continue
1040
+ param = params_dict[name]
1041
+ weight_loader = getattr(param, "weight_loader",
1042
+ default_weight_loader)
1043
+ weight_loader(param, loaded_weight)
1044
+ loaded_params.add(name)
1045
+ return loaded_params
1046
+
1047
+
1048
+class Qwen3NextForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
1049
+ MixtureOfExperts, IsHybrid):
1050
+ packed_modules_mapping = {
1051
+ "qkv_proj": [
1052
+ "q_proj",
1053
+ "k_proj",
1054
+ "v_proj",
1055
+ ],
1056
+ "gate_up_proj": ["up_proj", "down_proj"],
1057
+ "in_proj": ["in_proj_qkvz", "in_proj_ba"],
1058
+ }
1059
+
1060
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1061
+ config = vllm_config.model_config.hf_config
1062
+ self.vllm_config = vllm_config
1063
+ self.model_config = vllm_config.model_config
1064
+ cache_config = vllm_config.cache_config
1065
+ lora_config = vllm_config.lora_config
1066
+ scheduler_config = vllm_config.scheduler_config
1067
+ assert not cache_config.enable_prefix_caching, \
1068
+ "Qwen3Next currently does not support prefix caching"
1069
+ assert envs.VLLM_USE_V1, "Qwen3Next requires VLLM_USE_V1"
1070
+ self.quant_config = vllm_config.quant_config
1071
+
1072
+ super().__init__()
1073
+ self.config = config
1074
+ self.scheduler_config = scheduler_config
1075
+ self.model = Qwen3NextModel(vllm_config=vllm_config,
1076
+ prefix=maybe_prefix(prefix, "model"))
1077
+ self.unpadded_vocab_size = config.vocab_size
1078
+ if lora_config:
1079
+ self.unpadded_vocab_size += lora_config.lora_extra_vocab_size
1080
+ self.lm_head = ParallelLMHead(
1081
+ self.unpadded_vocab_size,
1082
+ config.hidden_size,
1083
+ org_num_embeddings=config.vocab_size,
1084
+ padding_size=DEFAULT_VOCAB_PADDING_SIZE
1085
+ # We need bigger padding if using lora for kernel
1086
+ # compatibility
1087
+ if not lora_config else lora_config.lora_vocab_padding_size,
1088
+ )
1089
+ self.logits_processor = LogitsProcessor(self.unpadded_vocab_size,
1090
+ config.vocab_size)
1091
+ self.make_empty_intermediate_tensors = (
1092
+ self.model.make_empty_intermediate_tensors)
1093
+
1094
+ # Set MoE hyperparameters
1095
+ self.expert_weights = []
1096
+
1097
+ self.moe_layers: list[FusedMoE] = []
1098
+ example_layer = None
1099
+ for layer in self.model.layers:
1100
+ if isinstance(layer, PPMissingLayer):
1101
+ continue
1102
+
1103
+ assert isinstance(layer, Qwen3NextDecoderLayer)
1104
+ if isinstance(layer.mlp, Qwen3NextSparseMoeBlock):
1105
+ example_layer = layer.mlp
1106
+ self.moe_layers.append(layer.mlp.experts)
1107
+
1108
+ if example_layer is None:
1109
+ raise RuntimeError("No Qwen3Next layer found in the model.layers.")
1110
+
1111
+ self.num_moe_layers = len(self.moe_layers)
1112
+ self.num_expert_groups = 1
1113
+ self.num_shared_experts = 0
1114
+ self.num_logical_experts = example_layer.n_logical_experts
1115
+ self.num_physical_experts = example_layer.n_physical_experts
1116
+ self.num_local_physical_experts = example_layer.n_local_physical_experts
1117
+ self.num_routed_experts = example_layer.n_routed_experts
1118
+ self.num_redundant_experts = example_layer.n_redundant_experts
1119
+
1120
+ def set_eplb_state(
1121
+ self,
1122
+ expert_load_view: torch.Tensor,
1123
+ logical_to_physical_map: torch.Tensor,
1124
+ logical_replica_count: torch.Tensor,
1125
+ ) -> None:
1126
+ for layer_idx, layer in enumerate(self.moe_layers):
1127
+ # Register the expert weights.
1128
+ self.expert_weights.append(layer.get_expert_weights())
1129
+ layer.set_eplb_state(
1130
+ moe_layer_idx=layer_idx,
1131
+ expert_load_view=expert_load_view,
1132
+ logical_to_physical_map=logical_to_physical_map,
1133
+ logical_replica_count=logical_replica_count,
1134
+ )
1135
+
1136
+ def update_physical_experts_metadata(
1137
+ self,
1138
+ num_physical_experts: int,
1139
+ num_local_physical_experts: int,
1140
+ ) -> None:
1141
+ assert self.num_local_physical_experts == num_local_physical_experts
1142
+ self.num_physical_experts = num_physical_experts
1143
+ self.num_local_physical_experts = num_local_physical_experts
1144
+ self.num_redundant_experts = (num_physical_experts -
1145
+ self.num_logical_experts)
1146
+ for layer in self.model.layers:
1147
+ if isinstance(layer.mlp, Qwen3NextSparseMoeBlock):
1148
+ moe = layer.mlp
1149
+ moe.n_local_physical_experts = num_local_physical_experts
1150
+ moe.n_physical_experts = num_physical_experts
1151
+ moe.n_redundant_experts = self.num_redundant_experts
1152
+ moe.experts.update_expert_map()
1153
+
1154
+ def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
1155
+ return self.model.get_input_embeddings(input_ids)
1156
+
1157
+ def forward(
1158
+ self,
1159
+ input_ids: torch.Tensor,
1160
+ positions: torch.Tensor,
1161
+ intermediate_tensors: Optional[IntermediateTensors] = None,
1162
+ inputs_embeds: Optional[torch.Tensor] = None,
1163
+ **kwargs: object,
1164
+ ):
1165
+ hidden_states = self.model(input_ids, positions, intermediate_tensors,
1166
+ inputs_embeds)
1167
+
1168
+ return hidden_states
1169
+
1170
+ @classmethod
1171
+ def get_mamba_state_dtype_from_config(
1172
+ cls,
1173
+ vllm_config: "VllmConfig",
1174
+ ) -> tuple[torch.dtype, torch.dtype]:
1175
+ return MambaStateDtypeCalculator.gated_delta_net_state_dtype(
1176
+ vllm_config.model_config.dtype,
1177
+ vllm_config.cache_config.mamba_cache_dtype)
1178
+
1179
+ @classmethod
1180
+ def get_mamba_state_shape_from_config(
1181
+ cls, vllm_config: "VllmConfig"
1182
+ ) -> tuple[tuple[int, int], tuple[int, int]]:
1183
+ parallel_config = vllm_config.parallel_config
1184
+ hf_config = vllm_config.model_config.hf_config
1185
+ tp_size = parallel_config.tensor_parallel_size
1186
+ num_spec = (vllm_config.speculative_config.num_speculative_tokens
1187
+ if vllm_config.speculative_config else 0)
1188
+ return MambaStateShapeCalculator.gated_delta_net_state_shape(
1189
+ tp_size,
1190
+ hf_config.linear_num_key_heads,
1191
+ hf_config.linear_num_value_heads,
1192
+ hf_config.linear_key_head_dim,
1193
+ hf_config.linear_value_head_dim,
1194
+ hf_config.linear_conv_kernel_dim,
1195
+ num_spec,
1196
+ use_v1=True)
1197
+
1198
+ def compute_logits(
1199
+ self,
1200
+ hidden_states: torch.Tensor,
1201
+ sampling_metadata: SamplingMetadata,
1202
+ ) -> Optional[torch.Tensor]:
1203
+ return self.logits_processor(self.lm_head, hidden_states,
1204
+ sampling_metadata)
1205
+
1206
+ def load_weights(self, weights: Iterable[tuple[str,
1207
+ torch.Tensor]]) -> set[str]:
1208
+ loader = AutoWeightsLoader(
1209
+ self,
1210
+ skip_prefixes=["mtp."],
1211
+ )
1212
+ return loader.load_weights(weights)
1213
+
1214
+ def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1215
+ return self.model.get_expert_mapping()
1216
+
1217
+
1218
+def gdn_attention(
1219
+ hidden_states: torch.Tensor,
1220
+ output: torch.Tensor,
1221
+ layer_name: str,
1222
+) -> None:
1223
+ forward_context: ForwardContext = get_forward_context()
1224
+ self = forward_context.no_compile_layers[layer_name]
1225
+ self._forward(hidden_states=hidden_states, output=output)
1226
+
1227
+
1228
+def gdn_attention_fake(
1229
+ hidden_states: torch.Tensor,
1230
+ output: torch.Tensor,
1231
+ layer_name: str,
1232
+) -> None:
1233
+ return
1234
+
1235
+
1236
+direct_register_custom_op(
1237
+ op_name="gdn_attention",
1238
+ op_func=gdn_attention,
1239
+ mutates_args=["output"],
1240
+ fake_impl=gdn_attention_fake,
1241
+ dispatch_key=current_platform.dispatch_key,
1242
+)
1243
+
1244
+
1245
+# g = -self.A_log.float().exp() * F.softplus(a.float() + self.dt_bias)
1246
+@triton.jit
1247
+def fused_gdn_gating_kernel(
1248
+ g,
1249
+ A_log,
1250
+ a,
1251
+ dt_bias,
1252
+ seq_len,
1253
+ NUM_HEADS: tl.constexpr,
1254
+ beta: tl.constexpr,
1255
+ threshold: tl.constexpr,
1256
+ BLK_HEADS: tl.constexpr,
1257
+):
1258
+ i_b, i_s, i_d = tl.program_id(0), tl.program_id(1), tl.program_id(2)
1259
+ head_off = i_d * BLK_HEADS + tl.arange(0, BLK_HEADS)
1260
+ off = i_b * seq_len * NUM_HEADS + i_s * NUM_HEADS + head_off
1261
+ mask = head_off < NUM_HEADS
1262
+ blk_A_log = tl.load(A_log + head_off, mask=mask)
1263
+ blk_a = tl.load(a + off, mask=mask)
1264
+ blk_bias = tl.load(dt_bias + head_off, mask=mask)
1265
+ # If the model is loaded in fp16, without the .float() here, A might be -inf
1266
+ x = blk_a.to(tl.float32) + blk_bias.to(tl.float32)
1267
+ softplus_x = tl.where(beta * x <= threshold,
1268
+ (1 / beta) * tl.log(1 + tl.exp(beta * x)), x)
1269
+ blk_g = -tl.exp(blk_A_log.to(tl.float32)) * softplus_x
1270
+ tl.store(g + off, blk_g.to(g.dtype.element_ty), mask=mask)
1271
+
1272
+
1273
+def fused_gdn_gating(
1274
+ A_log: torch.Tensor,
1275
+ a: torch.Tensor,
1276
+ dt_bias: torch.Tensor,
1277
+ beta: float = 1.0,
1278
+ threshold: float = 20.0,
1279
+) -> torch.Tensor:
1280
+ batch, num_heads = a.shape
1281
+ seq_len = 1
1282
+ grid = (batch, seq_len, triton.cdiv(num_heads, 8))
1283
+ g = torch.empty_like(a, dtype=torch.float32)
1284
+ fused_gdn_gating_kernel[grid](g,
1285
+ A_log,
1286
+ a,
1287
+ dt_bias,
1288
+ seq_len,
1289
+ num_heads,
1290
+ beta,
1291
+ threshold,
1292
+ 8,
1293
+ num_warps=1)
1294
+ return g
vllm/model_executor/models/qwen3_next_mtp.py
new
+285
@@ -0,0 +1,285 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Inference-only Qwen3Next MTP model."""
4
+from collections.abc import Iterable
5
+from typing import Optional
6
+
7
+import torch
8
+from torch import nn
9
+
10
+from vllm.compilation.decorators import support_torch_compile
11
+from vllm.config import VllmConfig
12
+from vllm.distributed.parallel_state import get_pp_group
13
+from vllm.logger import init_logger
14
+from vllm.model_executor.layers.fused_moe import FusedMoE
15
+from vllm.model_executor.layers.linear import ColumnParallelLinear
16
+from vllm.model_executor.layers.logits_processor import LogitsProcessor
17
+from vllm.model_executor.layers.vocab_parallel_embedding import (
18
+ DEFAULT_VOCAB_PADDING_SIZE, ParallelLMHead, VocabParallelEmbedding)
19
+from vllm.model_executor.model_loader.weight_utils import default_weight_loader
20
+from vllm.model_executor.models.qwen3_next import (Qwen3NextDecoderLayer,
21
+ Qwen3NextRMSNorm)
22
+from vllm.model_executor.sampling_metadata import SamplingMetadata
23
+from vllm.sequence import IntermediateTensors
24
+from vllm.transformers_utils.configs import Qwen3NextConfig
25
+
26
+from .interfaces import SupportsPP
27
+from .utils import (AutoWeightsLoader, is_pp_missing_parameter,
28
+ make_empty_intermediate_tensors_factory, maybe_prefix)
29
+
30
+logger = init_logger(__name__)
31
+
32
+KVCache = tuple[torch.Tensor, torch.Tensor]
33
+
34
+
35
+@support_torch_compile
36
+class Qwen3NextMultiTokenPredictor(nn.Module):
37
+
38
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
39
+ super().__init__()
40
+
41
+ model_config = vllm_config.model_config
42
+ cache_config = vllm_config.cache_config
43
+ quant_config = vllm_config.quant_config
44
+ lora_config = vllm_config.lora_config
45
+ config: Qwen3NextConfig = model_config.hf_config
46
+
47
+ self.config = config
48
+ lora_vocab = ((lora_config.lora_extra_vocab_size *
49
+ (lora_config.max_loras or 1)) if lora_config else 0)
50
+ self.vocab_size = config.vocab_size + lora_vocab
51
+ self.org_vocab_size = config.vocab_size
52
+
53
+ self.mtp_start_layer_idx = config.num_hidden_layers
54
+ self.num_mtp_layers = getattr(config, "num_nextn_predict_layers", 1)
55
+
56
+ self.embed_tokens = VocabParallelEmbedding(
57
+ self.vocab_size,
58
+ config.hidden_size,
59
+ org_num_embeddings=config.vocab_size,
60
+ )
61
+
62
+ self.fc = ColumnParallelLinear(self.config.hidden_size * 2,
63
+ self.config.hidden_size,
64
+ gather_output=True,
65
+ bias=False,
66
+ return_bias=False)
67
+
68
+ self.layers = torch.nn.ModuleList(
69
+ Qwen3NextDecoderLayer(
70
+ config,
71
+ layer_type="full_attention",
72
+ model_config=model_config,
73
+ cache_config=cache_config,
74
+ quant_config=quant_config,
75
+ prefix=f'{prefix}.layers.{self.mtp_start_layer_idx + idx}',
76
+ ) for idx in range(self.num_mtp_layers))
77
+
78
+ self.make_empty_intermediate_tensors = (
79
+ make_empty_intermediate_tensors_factory(
80
+ ["hidden_states", "residual"], config.hidden_size))
81
+
82
+ self.norm = Qwen3NextRMSNorm(config.hidden_size,
83
+ eps=config.rms_norm_eps)
84
+ self.pre_fc_norm_hidden = Qwen3NextRMSNorm(config.hidden_size,
85
+ eps=config.rms_norm_eps)
86
+ self.pre_fc_norm_embedding = Qwen3NextRMSNorm(config.hidden_size,
87
+ eps=config.rms_norm_eps)
88
+
89
+ def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
90
+ return self.embed_tokens(input_ids)
91
+
92
+ def forward(
93
+ self,
94
+ input_ids: torch.Tensor,
95
+ positions: torch.Tensor,
96
+ hidden_states: torch.Tensor,
97
+ intermediate_tensors: Optional[IntermediateTensors] = None,
98
+ inputs_embeds: Optional[torch.Tensor] = None,
99
+ spec_step_idx: int = 0,
100
+ ) -> torch.Tensor:
101
+ if get_pp_group().is_first_rank:
102
+ if inputs_embeds is None:
103
+ inputs_embeds = self.get_input_embeddings(input_ids)
104
+ assert hidden_states.shape[-1] == inputs_embeds.shape[-1]
105
+ inputs_embeds = self.pre_fc_norm_embedding(inputs_embeds)
106
+ hidden_states = self.pre_fc_norm_hidden(hidden_states)
107
+ hidden_states = torch.cat([inputs_embeds, hidden_states], dim=-1)
108
+ hidden_states = self.fc(hidden_states)
109
+ residual = None
110
+ else:
111
+ assert intermediate_tensors is not None
112
+ hidden_states = intermediate_tensors["hidden_states"]
113
+ residual = intermediate_tensors["residual"]
114
+
115
+ current_step_idx = (spec_step_idx % self.num_mtp_layers)
116
+ hidden_states, residual = self.layers[current_step_idx](
117
+ positions=positions,
118
+ hidden_states=hidden_states,
119
+ residual=residual,
120
+ )
121
+
122
+ if not get_pp_group().is_last_rank:
123
+ return IntermediateTensors({
124
+ "hidden_states": hidden_states,
125
+ "residual": residual
126
+ })
127
+
128
+ hidden_states, _ = self.norm(hidden_states, residual)
129
+ return hidden_states
130
+
131
+ def load_weights(self, weights: Iterable[tuple[str,
132
+ torch.Tensor]]) -> set[str]:
133
+ stacked_params_mapping = [
134
+ # (param_name, shard_name, shard_id)
135
+ ("qkv_proj", "q_proj", "q"),
136
+ ("qkv_proj", "k_proj", "k"),
137
+ ("qkv_proj", "v_proj", "v"),
138
+ ("gate_up_proj", "gate_proj", 0),
139
+ ("gate_up_proj", "up_proj", 1),
140
+ ]
141
+
142
+ # Params for weights, fp8 weight scales, fp8 activation scales
143
+ # (param_name, weight_name, expert_id, shard_id)
144
+ expert_params_mapping = FusedMoE.make_expert_params_mapping(
145
+ ckpt_gate_proj_name="gate_proj",
146
+ ckpt_down_proj_name="down_proj",
147
+ ckpt_up_proj_name="up_proj",
148
+ num_experts=self.config.num_experts)
149
+
150
+ params_dict = dict(self.named_parameters())
151
+ loaded_params: set[str] = set()
152
+ for name, loaded_weight in weights:
153
+ if "rotary_emb.inv_freq" in name:
154
+ continue
155
+
156
+ for param_name, weight_name, shard_id in stacked_params_mapping:
157
+ if weight_name not in name:
158
+ continue
159
+
160
+ if "mlp.experts" in name:
161
+ continue
162
+
163
+ name = name.replace(weight_name, param_name)
164
+ # Skip loading extra bias for GPTQ models.
165
+ if name.endswith(".bias") and name not in params_dict:
166
+ continue
167
+ # Skip layers on other devices.
168
+ if is_pp_missing_parameter(name, self):
169
+ continue
170
+ if name not in params_dict:
171
+ continue
172
+ param = params_dict[name]
173
+ weight_loader = param.weight_loader
174
+ weight_loader(param, loaded_weight, shard_id)
175
+ break
176
+ else:
177
+ for mapping in expert_params_mapping:
178
+ param_name, weight_name, expert_id, shard_id = mapping
179
+ if weight_name not in name:
180
+ continue
181
+ name = name.replace(weight_name, param_name)
182
+ # Skip layers on other devices.
183
+ if is_pp_missing_parameter(name, self):
184
+ continue
185
+ # Skip loading extra bias for GPTQ models.
186
+ if ((name.endswith(".bias") or name.endswith("_bias"))
187
+ and name not in params_dict):
188
+ continue
189
+ param = params_dict[name]
190
+ weight_loader = param.weight_loader
191
+ weight_loader(param,
192
+ loaded_weight,
193
+ name,
194
+ shard_id=shard_id,
195
+ expert_id=expert_id)
196
+ break
197
+ else:
198
+ # Skip loading extra bias for GPTQ models.
199
+ if name.endswith(".bias") and name not in params_dict:
200
+ continue
201
+ if is_pp_missing_parameter(name, self):
202
+ continue
203
+
204
+ param = params_dict[name]
205
+ weight_loader = getattr(param, "weight_loader",
206
+ default_weight_loader)
207
+ weight_loader(param, loaded_weight)
208
+ loaded_params.add(name)
209
+ return loaded_params
210
+
211
+
212
+@support_torch_compile
213
+class Qwen3NextMTP(nn.Module, SupportsPP):
214
+ packed_modules_mapping = {
215
+ "qkv_proj": [
216
+ "q_proj",
217
+ "k_proj",
218
+ "v_proj",
219
+ ],
220
+ "gate_up_proj": ["up_proj", "down_proj"]
221
+ }
222
+
223
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
224
+ config = vllm_config.model_config.hf_config
225
+ self.vllm_config = vllm_config
226
+ cache_config = vllm_config.cache_config
227
+ assert not cache_config.enable_prefix_caching, \
228
+ "Qwen3NextMTP currently does not support prefix caching"
229
+
230
+ self.quant_config = vllm_config.quant_config
231
+
232
+ super().__init__()
233
+ self.config = config
234
+ self.model = Qwen3NextMultiTokenPredictor(vllm_config=vllm_config,
235
+ prefix=maybe_prefix(
236
+ prefix, "model"))
237
+ self.unpadded_vocab_size = config.vocab_size
238
+ self.lm_head = ParallelLMHead(self.unpadded_vocab_size,
239
+ config.hidden_size,
240
+ org_num_embeddings=config.vocab_size,
241
+ padding_size=DEFAULT_VOCAB_PADDING_SIZE)
242
+ self.logits_processor = LogitsProcessor(self.unpadded_vocab_size,
243
+ config.vocab_size)
244
+ self.make_empty_intermediate_tensors = (
245
+ self.model.make_empty_intermediate_tensors)
246
+
247
+ def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
248
+ return self.model.get_input_embeddings(input_ids)
249
+
250
+ def forward(
251
+ self,
252
+ input_ids: torch.Tensor,
253
+ positions: torch.Tensor,
254
+ hidden_states: torch.Tensor,
255
+ intermediate_tensors: Optional[IntermediateTensors] = None,
256
+ inputs_embeds: Optional[torch.Tensor] = None,
257
+ **kwargs: object,
258
+ ):
259
+ hidden_states = self.model(input_ids, positions, hidden_states,
260
+ intermediate_tensors, inputs_embeds)
261
+ return hidden_states
262
+
263
+ def compute_logits(
264
+ self,
265
+ hidden_states: torch.Tensor,
266
+ sampling_metadata: SamplingMetadata,
267
+ spec_step_idx: int = 0,
268
+ ) -> Optional[torch.Tensor]:
269
+ return self.logits_processor(self.lm_head, hidden_states,
270
+ sampling_metadata)
271
+
272
+ def load_weights(self, weights: Iterable[tuple[str,
273
+ torch.Tensor]]) -> set[str]:
274
+ shared_weight_names = ["embed_tokens", "lm_head"]
275
+
276
+ def remap_weight_names(weights):
277
+ for name, weight in weights:
278
+ if name.startswith("mtp."):
279
+ name = name.replace("mtp.", "model.")
280
+ elif not any(key in name for key in shared_weight_names):
281
+ continue
282
+ yield name, weight
283
+
284
+ loader = AutoWeightsLoader(self)
285
+ return loader.load_weights(remap_weight_names(weights))
vllm/model_executor/models/registry.py
+2
@@ -74,6 +74,7 @@ _TEXT_GENERATION_MODELS = {
74
"Gemma2ForCausalLM": ("gemma2", "Gemma2ForCausalLM"),
75
"Gemma3ForCausalLM": ("gemma3", "Gemma3ForCausalLM"),
76
"Gemma3nForCausalLM": ("gemma3n", "Gemma3nForCausalLM"),
77
+ "Qwen3NextForCausalLM": ("qwen3_next", "Qwen3NextForCausalLM"),
78
"GlmForCausalLM": ("glm", "GlmForCausalLM"),
79
"Glm4ForCausalLM": ("glm4", "Glm4ForCausalLM"),
80
"Glm4MoeForCausalLM": ("glm4_moe", "Glm4MoeForCausalLM"),
@@ -285,6 +286,7 @@ _SPECULATIVE_DECODING_MODELS = {
286
"ErnieMTPModel": ("ernie_mtp", "ErnieMTP"),
287
"Glm4MoeMTPModel": ("glm4_moe_mtp", "Glm4MoeMTP"),
288
"MedusaModel": ("medusa", "Medusa"),
289
+ "Qwen3NextMTP": ("qwen3_next_mtp", "Qwen3NextMTP"),
290
# Temporarily disabled.
291
# # TODO(woosuk): Re-enable this once the MLP Speculator is supported in V1.
292
# "MLPSpeculatorPreTrainedModel": ("mlp_speculator", "MLPSpeculator"),
vllm/transformers_utils/config.py
+1
-1
@@ -79,7 +79,7 @@ _CONFIG_REGISTRY: dict[str, type[PretrainedConfig]] = LazyConfigDict(
79
ultravox="UltravoxConfig",
80
step3_vl="Step3VLConfig",
81
step3_text="Step3TextConfig",
82
-)
82
+ qwen3_next="Qwen3NextConfig")
83
84
_CONFIG_ATTRS_MAPPING: dict[str, str] = {
85
"llm_config": "text_config",
vllm/transformers_utils/configs/__init__.py
+2
@@ -24,6 +24,7 @@ from vllm.transformers_utils.configs.nemotron import NemotronConfig
24
from vllm.transformers_utils.configs.nemotron_h import NemotronHConfig
25
from vllm.transformers_utils.configs.nemotron_vl import Nemotron_Nano_VL_Config
26
from vllm.transformers_utils.configs.ovis import OvisConfig
27
+from vllm.transformers_utils.configs.qwen3_next import Qwen3NextConfig
28
from vllm.transformers_utils.configs.speculators.base import SpeculatorsConfig
29
from vllm.transformers_utils.configs.step3_vl import (Step3TextConfig,
30
Step3VisionEncoderConfig,
@@ -50,4 +51,5 @@ __all__ = [
51
"Step3VLConfig",
52
"Step3VisionEncoderConfig",
53
"Step3TextConfig",
54
+ "Qwen3NextConfig",
55
]
vllm/transformers_utils/configs/qwen3_next.py
new
+275
@@ -0,0 +1,275 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+# Copyright 2025 The Qwen team, Alibaba Group and the HuggingFace Inc. team.
4
+# All rights reserved.
5
+#
6
+# Licensed under the Apache License, Version 2.0 (the "License");
7
+# you may not use this file except in compliance with the License.
8
+# You may obtain a copy of the License at
9
+#
10
+# http://www.apache.org/licenses/LICENSE-2.0
11
+#
12
+# Unless required by applicable law or agreed to in writing, software
13
+# distributed under the License is distributed on an "AS IS" BASIS,
14
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+# See the License for the specific language governing permissions and
16
+# limitations under the License.
17
+"""Qwen3-Next model configuration"""
18
+
19
+from transformers.configuration_utils import (PretrainedConfig,
20
+ layer_type_validation)
21
+from transformers.modeling_rope_utils import rope_config_validation
22
+from transformers.utils import logging
23
+
24
+logger = logging.get_logger(__name__)
25
+
26
+
27
+class Qwen3NextConfig(PretrainedConfig):
28
+ r"""
29
+ This is the configuration class to store the configuration of a [`Qwen3NextModel`]. It is used to instantiate a
30
+ Qwen3-Next model according to the specified arguments, defining the model architecture.
31
+ Instantiating a configuration with the defaults will yield a similar configuration to that of
32
+ Qwen3-Next-80B-A3B-Instruct [Qwen/Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct).
33
+
34
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
35
+ documentation from [`PretrainedConfig`] for more information.
36
+
37
+
38
+ Args:
39
+ vocab_size (`int`, *optional*, defaults to 151936):
40
+ Vocabulary size of the model. Defines the number of different tokens that can be represented by the
41
+ `inputs_ids`.
42
+ hidden_size (`int`, *optional*, defaults to 2048):
43
+ Dimension of the hidden representations.
44
+ intermediate_size (`int`, *optional*, defaults to 5632):
45
+ Dimension of the MLP representations.
46
+ num_hidden_layers (`int`, *optional*, defaults to 48):
47
+ Number of hidden layers in the Transformer encoder.
48
+ num_attention_heads (`int`, *optional*, defaults to 16):
49
+ Number of attention heads for each attention layer in the Transformer encoder.
50
+ num_key_value_heads (`int`, *optional*, defaults to 2):
51
+ This is the number of key_value heads that should be used to implement Grouped Query Attention. If
52
+ `num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
53
+ `num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
54
+ converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
55
+ by meanpooling all the original heads within that group. For more details checkout [this
56
+ paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to `32`.
57
+ hidden_act (`str`, *optional*, defaults to `"silu"`):
58
+ The non-linear activation function in the decoder.
59
+ max_position_embeddings (`int`, *optional*, defaults to 32768):
60
+ The maximum sequence length that this model might ever be used with.
61
+ initializer_range (`float`, *optional*, defaults to 0.02):
62
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
63
+ rms_norm_eps (`float`, *optional*, defaults to 1e-06):
64
+ The epsilon used by the rms normalization layers.
65
+ use_cache (`bool`, *optional*, defaults to `True`):
66
+ Whether or not the model should return the last key/values attentions (not used by all models). Only
67
+ relevant if `config.is_decoder=True`.
68
+ tie_word_embeddings (`bool`, *optional*, defaults to `False`):
69
+ Whether the model's input and output word embeddings should be tied.
70
+ rope_theta (`float`, *optional*, defaults to 10000.0):
71
+ The base period of the RoPE embeddings.
72
+ rope_scaling (`Dict`, *optional*):
73
+ Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
74
+ and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
75
+ accordingly.
76
+ Expected contents:
77
+ `rope_type` (`str`):
78
+ The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
79
+ 'llama3'], with 'default' being the original RoPE implementation.
80
+ `factor` (`float`, *optional*):
81
+ Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
82
+ most scaling types, a `factor` of x will enable the model to handle sequences of length x *
83
+ original maximum pre-trained length.
84
+ `original_max_position_embeddings` (`int`, *optional*):
85
+ Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
86
+ pretraining.
87
+ `attention_factor` (`float`, *optional*):
88
+ Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
89
+ computation. If unspecified, it defaults to value recommended by the implementation, using the
90
+ `factor` field to infer the suggested value.
91
+ `beta_fast` (`float`, *optional*):
92
+ Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
93
+ ramp function. If unspecified, it defaults to 32.
94
+ `beta_slow` (`float`, *optional*):
95
+ Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
96
+ ramp function. If unspecified, it defaults to 1.
97
+ `short_factor` (`List[float]`, *optional*):
98
+ Only used with 'longrope'. The scaling factor to be applied to short contexts (<
99
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
100
+ size divided by the number of attention heads divided by 2
101
+ `long_factor` (`List[float]`, *optional*):
102
+ Only used with 'longrope'. The scaling factor to be applied to long contexts (<
103
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
104
+ size divided by the number of attention heads divided by 2
105
+ `low_freq_factor` (`float`, *optional*):
106
+ Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
107
+ `high_freq_factor` (`float`, *optional*):
108
+ Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
109
+ partial_rotary_factor (`float`, *optional*, defaults to 0.25):
110
+ Percentage of the query and keys which will have rotary embedding.
111
+ attention_bias (`bool`, *optional*, defaults to `False`):
112
+ Whether to use a bias in the query, key, value and output projection layers during self-attention.
113
+ attention_dropout (`float`, *optional*, defaults to 0.0):
114
+ The dropout ratio for the attention probabilities.
115
+ head_dim (`int`, *optional*, defaults to 256):
116
+ Projection weights dimension in multi-head attention.
117
+ linear_conv_kernel_dim (`int`, *optional*, defaults to 4):
118
+ Kernel size of the convolution used in linear attention layers.
119
+ linear_key_head_dim (`int`, *optional*, defaults to 128):
120
+ Dimension of each key head in linear attention.
121
+ linear_value_head_dim (`int`, *optional*, defaults to 128):
122
+ Dimension of each value head in linear attention.
123
+ linear_num_key_heads (`int`, *optional*, defaults to 16):
124
+ Number of key heads used in linear attention layers.
125
+ linear_num_value_heads (`int`, *optional*, defaults to 32):
126
+ Number of value heads used in linear attention layers.
127
+ decoder_sparse_step (`int`, *optional*, defaults to 1):
128
+ The frequency of the MoE layer.
129
+ moe_intermediate_size (`int`, *optional*, defaults to 512):
130
+ Intermediate size of the routed expert.
131
+ shared_expert_intermediate_size (`int`, *optional*, defaults to 512):
132
+ Intermediate size of the shared expert.
133
+ num_experts_per_tok (`int`, *optional*, defaults to 10):
134
+ Number of selected experts.
135
+ num_experts (`int`, *optional*, defaults to 512):
136
+ Number of routed experts.
137
+ norm_topk_prob (`bool`, *optional*, defaults to `True`):
138
+ Whether to normalize the topk probabilities.
139
+ output_router_logits (`bool`, *optional*, defaults to `False`):
140
+ Whether or not the router logits should be returned by the model. Enabling this will also
141
+ allow the model to output the auxiliary loss, including load balancing loss and router z-loss.
142
+ router_aux_loss_coef (`float`, *optional*, defaults to 0.001):
143
+ The aux loss factor for the total loss.
144
+ mlp_only_layers (`list[int]`, *optional*, defaults to `[]`):
145
+ Indicate which layers use Qwen3NextMLP rather than Qwen3NextSparseMoeBlock
146
+ The list contains layer index, from 0 to num_layers-1 if we have num_layers layers
147
+ If `mlp_only_layers` is empty, `decoder_sparse_step` is used to determine the sparsity.
148
+ layer_types (`list[str]`, *optional*):
149
+ Types of each layer (attention or linear).
150
+
151
+ ```python
152
+ >>> from transformers import Qwen3NextModel, Qwen3NextConfig
153
+
154
+ >>> # Initializing a Qwen3Next style configuration
155
+ >>> configuration = Qwen3NextConfig()
156
+
157
+ >>> # Initializing a model from the Qwen3-Next-80B-A3B style configuration
158
+ >>> model = Qwen3NextModel(configuration)
159
+
160
+ >>> # Accessing the model configuration
161
+ >>> configuration = model.config
162
+ ```
163
+ """ # noqa: E501
164
+
165
+ model_type = "qwen3_next"
166
+ keys_to_ignore_at_inference = ["past_key_values"]
167
+
168
+ base_model_tp_plan = {
169
+ "layers.*.self_attn.q_proj": "colwise",
170
+ "layers.*.self_attn.k_proj": "colwise",
171
+ "layers.*.self_attn.v_proj": "colwise",
172
+ "layers.*.self_attn.o_proj": "rowwise",
173
+ "layers.*.mlp.experts.*.gate_proj": "colwise",
174
+ "layers.*.mlp.experts.*.up_proj": "colwise",
175
+ "layers.*.mlp.experts.*.down_proj": "rowwise",
176
+ "layers.*.mlp.shared_experts.gate_proj": "colwise",
177
+ "layers.*.mlp.shared_experts.up_proj": "colwise",
178
+ "layers.*.mlp.shared_experts.down_proj": "rowwise",
179
+ "layers.*.mlp.gate_proj": "colwise",
180
+ "layers.*.mlp.up_proj": "colwise",
181
+ "layers.*.mlp.down_proj": "rowwise",
182
+ }
183
+ base_model_pp_plan = {
184
+ "embed_tokens": (["input_ids"], ["inputs_embeds"]),
185
+ "layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
186
+ "norm": (["hidden_states"], ["hidden_states"]),
187
+ }
188
+
189
+ def __init__(
190
+ self,
191
+ vocab_size=151936,
192
+ hidden_size=2048,
193
+ intermediate_size=5632,
194
+ num_hidden_layers=48,
195
+ num_attention_heads=16,
196
+ num_key_value_heads=2,
197
+ hidden_act="silu",
198
+ max_position_embeddings=32768,
199
+ initializer_range=0.02,
200
+ rms_norm_eps=1e-6,
201
+ use_cache=True,
202
+ tie_word_embeddings=False,
203
+ rope_theta=10000.0,
204
+ rope_scaling=None,
205
+ partial_rotary_factor=0.25,
206
+ attention_bias=False,
207
+ attention_dropout=0.0,
208
+ head_dim=256,
209
+ linear_conv_kernel_dim=4,
210
+ linear_key_head_dim=128,
211
+ linear_value_head_dim=128,
212
+ linear_num_key_heads=16,
213
+ linear_num_value_heads=32,
214
+ decoder_sparse_step=1,
215
+ moe_intermediate_size=512,
216
+ shared_expert_intermediate_size=512,
217
+ num_experts_per_tok=10,
218
+ num_experts=512,
219
+ norm_topk_prob=True,
220
+ output_router_logits=False,
221
+ router_aux_loss_coef=0.001,
222
+ mlp_only_layers=None,
223
+ layer_types=None,
224
+ **kwargs,
225
+ ):
226
+ if mlp_only_layers is None:
227
+ mlp_only_layers = []
228
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
229
+ self.vocab_size = vocab_size
230
+ self.max_position_embeddings = max_position_embeddings
231
+ self.hidden_size = hidden_size
232
+ self.intermediate_size = intermediate_size
233
+ self.num_hidden_layers = num_hidden_layers
234
+ self.num_attention_heads = num_attention_heads
235
+ self.num_key_value_heads = num_key_value_heads
236
+ self.hidden_act = hidden_act
237
+ self.initializer_range = initializer_range
238
+ self.rms_norm_eps = rms_norm_eps
239
+ self.use_cache = use_cache
240
+ self.rope_theta = rope_theta
241
+ self.rope_scaling = rope_scaling
242
+ self.partial_rotary_factor = partial_rotary_factor
243
+ self.attention_bias = attention_bias
244
+ self.attention_dropout = attention_dropout
245
+ self.head_dim = head_dim
246
+ rope_config_validation(self)
247
+
248
+ self.layer_types = layer_types
249
+ if self.layer_types is None:
250
+ self.layer_types = [
251
+ "linear_attention" if bool((i + 1) % 4) else "full_attention"
252
+ for i in range(self.num_hidden_layers)
253
+ ]
254
+ layer_type_validation(self.layer_types)
255
+
256
+ # linear attention part
257
+ self.linear_conv_kernel_dim = linear_conv_kernel_dim
258
+ self.linear_key_head_dim = linear_key_head_dim
259
+ self.linear_value_head_dim = linear_value_head_dim
260
+ self.linear_num_key_heads = linear_num_key_heads
261
+ self.linear_num_value_heads = linear_num_value_heads
262
+
263
+ # MoE arguments
264
+ self.decoder_sparse_step = decoder_sparse_step
265
+ self.moe_intermediate_size = moe_intermediate_size
266
+ self.shared_expert_intermediate_size = shared_expert_intermediate_size
267
+ self.num_experts_per_tok = num_experts_per_tok
268
+ self.num_experts = num_experts
269
+ self.norm_topk_prob = norm_topk_prob
270
+ self.output_router_logits = output_router_logits
271
+ self.router_aux_loss_coef = router_aux_loss_coef
272
+ self.mlp_only_layers = mlp_only_layers
273
+
274
+
275
+__all__ = ["Qwen3NextConfig"]
vllm/v1/attention/backends/gdn_attn.py
new
+319
@@ -0,0 +1,319 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Backend for GatedDeltaNet attention."""
4
+from dataclasses import dataclass
5
+from typing import ClassVar, Optional
6
+
7
+import torch
8
+
9
+from vllm.attention.backends.abstract import AttentionBackend
10
+from vllm.attention.backends.utils import PAD_SLOT_ID
11
+from vllm.config import VllmConfig
12
+from vllm.v1.attention.backends.utils import (AttentionCGSupport,
13
+ AttentionMetadataBuilder,
14
+ CommonAttentionMetadata,
15
+ split_decodes_and_prefills)
16
+from vllm.v1.kv_cache_interface import AttentionSpec, MambaSpec
17
+
18
+
19
+class GDNAttentionBackend(AttentionBackend):
20
+
21
+ @staticmethod
22
+ def get_builder_cls() -> type["GDNAttentionMetadataBuilder"]:
23
+ return GDNAttentionMetadataBuilder
24
+
25
+
26
+@dataclass
27
+class GDNAttentionMetadata:
28
+ num_prefills: int
29
+ num_prefill_tokens: int
30
+ num_decodes: int
31
+ num_decode_tokens: int
32
+ num_spec_decodes: int
33
+ num_spec_decode_tokens: int
34
+
35
+ has_initial_state: Optional[torch.Tensor] = None
36
+
37
+ spec_query_start_loc: Optional[
38
+ torch.Tensor] = None # shape: [num_spec_decodes + 1,]
39
+ non_spec_query_start_loc: Optional[
40
+ torch.Tensor] = None # shape: [batch - num_spec_decodes + 1,]
41
+
42
+ spec_state_indices_tensor: Optional[
43
+ torch.Tensor] = None # shape: [batch, num_spec]
44
+ non_spec_state_indices_tensor: Optional[
45
+ torch.Tensor] = None # shape: [batch - num_spec_decodes,]
46
+ spec_sequence_masks: Optional[torch.Tensor] = None # shape: [batch,]
47
+ spec_token_masks: Optional[
48
+ torch.
49
+ Tensor] = None # shape: [num_prefill_tokens + num_decode_tokens,]
50
+ num_accepted_tokens: Optional[torch.Tensor] = None # shape: [batch,]
51
+
52
+
53
+class GDNAttentionMetadataBuilder(
54
+ AttentionMetadataBuilder[GDNAttentionMetadata]):
55
+
56
+ cudagraph_support = AttentionCGSupport.UNIFORM_BATCH
57
+
58
+ reorder_batch_threshold: ClassVar[int] = 1
59
+
60
+ def __init__(self, kv_cache_spec: AttentionSpec, layer_names: list[str],
61
+ vllm_config: VllmConfig, device: torch.device):
62
+ assert isinstance(kv_cache_spec, MambaSpec)
63
+ self.vllm_config = vllm_config
64
+ self.compilation_config = vllm_config.compilation_config
65
+ self.speculative_config = vllm_config.speculative_config
66
+ self.kv_cache_spec = kv_cache_spec
67
+ if self.speculative_config:
68
+ self.num_spec = self.speculative_config.num_speculative_tokens # noqa: E501
69
+ else:
70
+ self.num_spec = 0
71
+ self.use_spec_decode = self.num_spec > 0
72
+ self.reorder_batch_threshold = self.num_spec + 1 # type: ignore[misc]
73
+
74
+ self.use_full_cuda_graph = \
75
+ self.compilation_config.cudagraph_mode.has_full_cudagraphs()
76
+ self.decode_cudagraph_max_bs = min(
77
+ self.vllm_config.scheduler_config.max_num_seqs,
78
+ self.compilation_config.max_capture_size)
79
+
80
+ self.spec_state_indices_tensor = torch.empty(
81
+ (self.decode_cudagraph_max_bs, self.num_spec + 1),
82
+ dtype=torch.int32,
83
+ device=device,
84
+ )
85
+ self.non_spec_state_indices_tensor = torch.empty(
86
+ (self.decode_cudagraph_max_bs, ),
87
+ dtype=torch.int32,
88
+ device=device,
89
+ )
90
+ self.spec_sequence_masks = torch.empty(
91
+ (self.decode_cudagraph_max_bs, ),
92
+ dtype=torch.bool,
93
+ device=device,
94
+ )
95
+ self.spec_token_masks = torch.empty(
96
+ (self.decode_cudagraph_max_bs * (self.num_spec + 1), ),
97
+ dtype=torch.bool,
98
+ device=device,
99
+ )
100
+ self.spec_query_start_loc = torch.empty(
101
+ (self.decode_cudagraph_max_bs + 1, ),
102
+ dtype=torch.int32,
103
+ device=device,
104
+ )
105
+ self.non_spec_query_start_loc = torch.empty(
106
+ (self.decode_cudagraph_max_bs + 1, ),
107
+ dtype=torch.int32,
108
+ device=device,
109
+ )
110
+ self.num_accepted_tokens = torch.empty(
111
+ (self.decode_cudagraph_max_bs, ),
112
+ dtype=torch.int32,
113
+ device=device,
114
+ )
115
+
116
+ def build( # type: ignore[override]
117
+ self,
118
+ common_prefix_len: int,
119
+ common_attn_metadata: CommonAttentionMetadata,
120
+ num_accepted_tokens: Optional[torch.Tensor] = None,
121
+ num_draft_tokens: Optional[torch.Tensor] = None,
122
+ fast_build: bool = False,
123
+ ) -> GDNAttentionMetadata:
124
+ m = common_attn_metadata
125
+
126
+ query_start_loc = m.query_start_loc
127
+ context_lens = m.num_computed_tokens_cpu
128
+ context_lens_tensor = context_lens.to(query_start_loc.device)
129
+ seq_lens_tensor = m.seq_lens
130
+
131
+ if (not self.use_spec_decode or num_draft_tokens is None
132
+ or num_draft_tokens.sum().item() == 0):
133
+ spec_sequence_masks = None
134
+ else:
135
+ spec_sequence_masks = (num_draft_tokens > 0) & (
136
+ context_lens_tensor +
137
+ (num_draft_tokens + 1) == seq_lens_tensor)
138
+ if spec_sequence_masks.sum().item() == 0:
139
+ spec_sequence_masks = None
140
+
141
+ if spec_sequence_masks is None:
142
+ num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
143
+ split_decodes_and_prefills(m, decode_threshold=1))
144
+ num_spec_decodes = 0
145
+ num_spec_decode_tokens = 0
146
+ spec_token_masks = None
147
+ spec_state_indices_tensor = None
148
+ non_spec_state_indices_tensor = m.block_table_tensor[:, 0]
149
+ spec_query_start_loc = None
150
+ non_spec_query_start_loc = query_start_loc
151
+ num_accepted_tokens = None
152
+ else:
153
+ num_spec_decodes = spec_sequence_masks.sum().item()
154
+ query_lens = query_start_loc[1:] - query_start_loc[:-1]
155
+
156
+ non_spec_query_lens = query_lens[~spec_sequence_masks]
157
+ num_decodes = (non_spec_query_lens == 1).sum().item()
158
+ num_prefills = non_spec_query_lens.size(0) - num_decodes
159
+ num_decode_tokens = num_decodes
160
+ num_prefill_tokens = non_spec_query_lens.sum().item(
161
+ ) - num_decode_tokens
162
+
163
+ if num_prefills == 0 and num_decodes == 0:
164
+ spec_token_masks = torch.ones(
165
+ (min(num_spec_decodes *
166
+ (self.num_spec + 1), query_start_loc[-1].item())),
167
+ dtype=torch.bool,
168
+ device=query_start_loc.device)
169
+ spec_state_indices_tensor = m.block_table_tensor[:, :self.
170
+ num_spec + 1]
171
+ non_spec_state_indices_tensor = None
172
+ spec_query_start_loc = query_start_loc
173
+ non_spec_query_start_loc = None
174
+ else:
175
+ spec_token_masks = torch.repeat_interleave(
176
+ spec_sequence_masks, query_lens)
177
+ spec_state_indices_tensor = m.block_table_tensor[
178
+ spec_sequence_masks, :self.num_spec + 1]
179
+ non_spec_state_indices_tensor = \
180
+ m.block_table_tensor[~spec_sequence_masks, 0]
181
+
182
+ spec_query_start_loc = torch.zeros(
183
+ num_spec_decodes + 1,
184
+ dtype=torch.int32,
185
+ device=query_start_loc.device)
186
+ torch.cumsum(query_lens[spec_sequence_masks],
187
+ dim=0,
188
+ out=spec_query_start_loc[1:])
189
+ non_spec_query_start_loc = torch.zeros(
190
+ query_lens.size(0) - num_spec_decodes + 1,
191
+ dtype=torch.int32,
192
+ device=query_start_loc.device)
193
+ torch.cumsum(query_lens[~spec_sequence_masks],
194
+ dim=0,
195
+ out=non_spec_query_start_loc[1:])
196
+
197
+ num_spec_decode_tokens = min(
198
+ num_spec_decodes * (self.num_spec + 1),
199
+ spec_token_masks.size(0))
200
+ assert num_accepted_tokens is not None
201
+ num_accepted_tokens = num_accepted_tokens[spec_sequence_masks]
202
+
203
+ if num_prefills > 0:
204
+ has_initial_state = context_lens_tensor > 0
205
+ if spec_sequence_masks is not None:
206
+ has_initial_state = has_initial_state[~spec_sequence_masks]
207
+ else:
208
+ has_initial_state = None
209
+
210
+ # prepare tensors for cudagraph
211
+ if (self.use_full_cuda_graph and num_prefills == 0 and num_decodes == 0
212
+ and num_spec_decodes <= self.decode_cudagraph_max_bs):
213
+ num_total_tokens = self.vllm_config.pad_for_cudagraph(
214
+ m.num_actual_tokens)
215
+ batch_size = num_total_tokens // (self.num_spec + 1)
216
+
217
+ self.spec_state_indices_tensor[:num_spec_decodes].copy_(
218
+ spec_state_indices_tensor, non_blocking=True)
219
+ spec_state_indices_tensor = self.spec_state_indices_tensor[:
220
+ batch_size]
221
+ spec_state_indices_tensor[num_spec_decodes:].fill_(PAD_SLOT_ID)
222
+
223
+ self.spec_sequence_masks[:num_spec_decodes].copy_(
224
+ spec_sequence_masks, non_blocking=True)
225
+ spec_sequence_masks = self.spec_sequence_masks[:batch_size]
226
+ spec_sequence_masks[num_spec_decodes:].fill_(False)
227
+
228
+ assert spec_token_masks is not None
229
+ self.spec_token_masks[:spec_token_masks.size(0)].copy_(
230
+ spec_token_masks, non_blocking=True)
231
+ spec_token_masks = self.spec_token_masks[:m.num_actual_tokens]
232
+ spec_token_masks[spec_token_masks.size(0):].fill_(False)
233
+
234
+ self.spec_query_start_loc[:num_spec_decodes + 1].copy_(
235
+ spec_query_start_loc, non_blocking=True)
236
+ spec_num_query_tokens = spec_query_start_loc[
237
+ -1] # type: ignore[index]
238
+ spec_query_start_loc = self.spec_query_start_loc[:batch_size + 1]
239
+ spec_query_start_loc[num_spec_decodes +
240
+ 1:].fill_(spec_num_query_tokens)
241
+
242
+ self.num_accepted_tokens[:num_spec_decodes].copy_(
243
+ num_accepted_tokens, non_blocking=True)
244
+ num_accepted_tokens = self.num_accepted_tokens[:batch_size]
245
+ num_accepted_tokens[num_spec_decodes:].fill_(1)
246
+
247
+ if (self.use_full_cuda_graph and num_prefills == 0
248
+ and num_spec_decodes == 0
249
+ and num_decodes <= self.decode_cudagraph_max_bs):
250
+ num_total_tokens = self.vllm_config.pad_for_cudagraph(
251
+ m.num_actual_tokens)
252
+ batch_size = num_total_tokens
253
+
254
+ self.non_spec_state_indices_tensor[:num_decodes].copy_(
255
+ non_spec_state_indices_tensor, non_blocking=True)
256
+ non_spec_state_indices_tensor = \
257
+ self.non_spec_state_indices_tensor[:batch_size]
258
+ non_spec_state_indices_tensor[num_decodes:].fill_(PAD_SLOT_ID)
259
+
260
+ self.non_spec_query_start_loc[:num_decodes + 1].copy_(
261
+ non_spec_query_start_loc, non_blocking=True)
262
+ non_spec_num_query_tokens = non_spec_query_start_loc[
263
+ -1] # type: ignore[index]
264
+ non_spec_query_start_loc = \
265
+ self.non_spec_query_start_loc[:batch_size + 1]
266
+ non_spec_query_start_loc[num_decodes +
267
+ 1:].fill_(non_spec_num_query_tokens)
268
+
269
+ attn_metadata = GDNAttentionMetadata(
270
+ num_prefills=num_prefills,
271
+ num_prefill_tokens=num_prefill_tokens,
272
+ num_decodes=num_decodes,
273
+ num_decode_tokens=num_decode_tokens,
274
+ num_spec_decodes=num_spec_decodes,
275
+ num_spec_decode_tokens=num_spec_decode_tokens,
276
+ has_initial_state=has_initial_state,
277
+ spec_query_start_loc=spec_query_start_loc,
278
+ non_spec_query_start_loc=non_spec_query_start_loc,
279
+ spec_state_indices_tensor=spec_state_indices_tensor,
280
+ non_spec_state_indices_tensor=non_spec_state_indices_tensor,
281
+ spec_sequence_masks=spec_sequence_masks,
282
+ spec_token_masks=spec_token_masks,
283
+ num_accepted_tokens=num_accepted_tokens,
284
+ )
285
+ return attn_metadata
286
+
287
+ def build_for_cudagraph_capture(
288
+ self, common_attn_metadata: CommonAttentionMetadata):
289
+ """
290
+ This method builds the metadata for full cudagraph capture.
291
+ Currently, only decode is supported for full cudagraphs with Mamba.
292
+ """
293
+ m = common_attn_metadata
294
+
295
+ assert (m.num_reqs * (self.num_spec + 1) <= m.num_actual_tokens
296
+ and ((m.num_reqs + 1) * (self.num_spec + 1)
297
+ >= m.num_actual_tokens)), \
298
+ "GDN only supports decode-only full CUDAGraph capture. " \
299
+ "Make sure all cudagraph capture sizes <= max_num_seq."
300
+
301
+ num_accepted_tokens = torch.full((m.num_reqs, ),
302
+ m.max_query_len,
303
+ dtype=torch.int32,
304
+ device=m.query_start_loc.device)
305
+ num_drafted_tokens = torch.full((m.num_reqs, ),
306
+ self.num_spec,
307
+ dtype=torch.int32,
308
+ device=m.query_start_loc.device)
309
+
310
+ # Fixes query-start loc for spec-sequence-indices.
311
+ m.query_start_loc = torch.arange(0,
312
+ m.num_actual_tokens + 1,
313
+ step=m.max_query_len,
314
+ device=m.query_start_loc.device,
315
+ dtype=torch.int32)
316
+ m.num_computed_tokens_cpu = (m.seq_lens_cpu - torch.full(
317
+ (m.num_reqs, ), m.max_query_len, dtype=torch.int32, device='cpu'))
318
+
319
+ return self.build(0, m, num_accepted_tokens, num_drafted_tokens)
vllm/v1/core/single_type_kv_cache_manager.py
+40
-4
@@ -559,12 +559,48 @@ class MambaManager(SingleTypeKVCacheManager):
559
num_running_requests: int) -> int:
560
return 0
561
562
+ def get_num_blocks_to_allocate(
563
+ self, request_id: str, num_tokens: int,
564
+ new_computed_blocks: list[KVCacheBlock]) -> int:
565
+ """
566
+ Get the number of blocks needed to be allocated for the request.
567
+
568
+ Args:
569
+ request_id: The request ID.
570
+ num_tokens: The total number of tokens that need a slot (including
571
+ tokens that are already allocated).
572
+ new_computed_blocks: The new computed blocks just hitting the
573
+ prefix caching.
574
+
575
+ Returns:
576
+ The number of blocks
577
+ """
578
+
579
+ assert isinstance(self.kv_cache_spec, MambaSpec)
580
+ if self.kv_cache_spec.num_speculative_blocks > 0:
581
+ num_tokens += (self.kv_cache_spec.block_size *
582
+ self.kv_cache_spec.num_speculative_blocks)
583
+ num_required_blocks = cdiv(num_tokens, self.block_size)
584
+ num_new_blocks = (num_required_blocks - len(new_computed_blocks) -
585
+ len(self.req_to_blocks[request_id]))
586
+ # If a computed block of a request is an eviction candidate (in the
587
+ # free queue and ref_cnt == 0), it will be changed from a free block
588
+ # to a computed block when the request is allocated, so we also count
589
+ # it as needed to be allocated.
590
+ num_evictable_computed_blocks = sum(
591
+ blk.ref_cnt == 0 and not blk.is_null
592
+ for blk in new_computed_blocks)
593
+ return num_new_blocks + num_evictable_computed_blocks
594
+
595
def allocate_new_blocks(self, request_id: str,
596
num_tokens: int) -> list[KVCacheBlock]:
564
- new_blocks = super().allocate_new_blocks(request_id, num_tokens)
565
- assert len(self.req_to_blocks[request_id]) == 1, (
566
- "MambaManager should only allocate 1 block for each request.")
567
- return new_blocks
597
+ # Allocate extra `num_speculative_blocks` blocks for
598
+ # speculative decoding (MTP/EAGLE) with linear attention.
599
+ assert isinstance(self.kv_cache_spec, MambaSpec)
600
+ if self.kv_cache_spec.num_speculative_blocks > 0:
601
+ num_tokens += (self.kv_cache_spec.block_size *
602
+ self.kv_cache_spec.num_speculative_blocks)
603
+ return super().allocate_new_blocks(request_id, num_tokens)
604
605
606
class CrossAttentionManager(SingleTypeKVCacheManager):
vllm/v1/kv_cache_interface.py
+1
@@ -194,6 +194,7 @@ class MambaSpec(KVCacheSpec):
194
dtypes: tuple[torch.dtype]
195
page_size_padded: Optional[int] = None
196
mamba_type: str = "mamba2"
197
+ num_speculative_blocks: int = 0
198
199
@property
200
def page_size_bytes(self) -> int:
vllm/v1/spec_decode/eagle.py
+8
-2
@@ -218,7 +218,7 @@ class EagleProposer:
218
hidden_states=self.hidden_states[:num_input_tokens],
219
inputs_embeds=inputs_embeds,
220
)
221
- if self.method in ("deepseek_mtp", "ernie_mtp"):
221
+ if self.method in ("deepseek_mtp", "ernie_mtp", "qwen3_next_mtp"):
222
last_hidden_states = ret_hidden_states
223
hidden_states = last_hidden_states
224
else:
@@ -322,12 +322,18 @@ class EagleProposer:
322
with set_forward_context(per_layer_attn_metadata,
323
self.vllm_config,
324
num_tokens=input_batch_size):
325
- last_hidden_states, hidden_states = self.model(
325
+ ret_hidden_states = self.model(
326
input_ids=input_ids,
327
positions=self.positions[:input_batch_size],
328
hidden_states=self.hidden_states[:input_batch_size],
329
inputs_embeds=inputs_embeds,
330
)
331
+ if self.method in ("deepseek_mtp", "ernie_mtp",
332
+ "qwen3_next_mtp"):
333
+ last_hidden_states = ret_hidden_states
334
+ hidden_states = ret_hidden_states
335
+ else:
336
+ last_hidden_states, hidden_states = ret_hidden_states
337
hidden_states = hidden_states[:batch_size]
338
logits = self.model.compute_logits(last_hidden_states[:batch_size],
339
None)
vllm/v1/worker/block_table.py
+13
-7
@@ -156,9 +156,14 @@ class BlockTable:
156
class MultiGroupBlockTable:
157
"""The BlockTables for each KV cache group."""
158
159
- def __init__(self, max_num_reqs: int, max_model_len: int,
160
- max_num_batched_tokens: int, pin_memory: bool,
161
- device: torch.device, block_sizes: list[int]) -> None:
159
+ def __init__(self,
160
+ max_num_reqs: int,
161
+ max_model_len: int,
162
+ max_num_batched_tokens: int,
163
+ pin_memory: bool,
164
+ device: torch.device,
165
+ block_sizes: list[int],
166
+ num_speculative_tokens: int = 0) -> None:
167
# Note(hc): each dcp rank only store
168
# (max_model_len//dcp_world_size) tokens in kvcache,
169
# so the block_size which used for calc max_num_blocks_per_req
@@ -170,10 +175,11 @@ class MultiGroupBlockTable:
175
dcp_world_size = 1
176
177
self.block_tables = [
173
- BlockTable(block_size, max_num_reqs,
174
- cdiv(max_model_len, block_size * dcp_world_size),
175
- max_num_batched_tokens, pin_memory, device)
176
- for block_size in block_sizes
178
+ BlockTable(
179
+ block_size, max_num_reqs,
180
+ max(cdiv(max_model_len, block_size * dcp_world_size),
181
+ 1 + num_speculative_tokens), max_num_batched_tokens,
182
+ pin_memory, device) for block_size in block_sizes
183
]
184
185
def append_row(self, block_ids: tuple[list[int], ...],
vllm/v1/worker/gpu_input_batch.py
+17
@@ -83,6 +83,7 @@ class InputBatch:
83
logitsprocs: Optional[LogitsProcessors] = None,
84
is_spec_decode: bool = False,
85
is_pooling_model: bool = False,
86
+ num_speculative_tokens: int = 0,
87
):
88
self.is_pooling_model = is_pooling_model
89
self.is_spec_decode = is_spec_decode
@@ -127,6 +128,7 @@ class InputBatch:
128
pin_memory=pin_memory,
129
device=device,
130
block_sizes=block_sizes,
131
+ num_speculative_tokens=num_speculative_tokens,
132
)
133
134
# Sampling-related.
@@ -202,6 +204,14 @@ class InputBatch:
204
self.repetition_penalties_cpu_tensor.numpy()
205
self.repetition_penalties_reqs: set[str] = set()
206
207
+ # Speculative decoding
208
+ self.num_accepted_tokens_cpu_tensor = torch.ones((max_num_reqs, ),
209
+ dtype=torch.int64,
210
+ device="cpu",
211
+ pin_memory=pin_memory)
212
+ self.num_accepted_tokens_cpu = \
213
+ self.num_accepted_tokens_cpu_tensor.numpy()
214
+
215
# lora related
216
self.request_lora_mapping = np.zeros((self.max_num_reqs, ),
217
dtype=np.int32)
@@ -394,6 +404,9 @@ class InputBatch:
404
else:
405
raise NotImplementedError("Unrecognized request type")
406
407
+ # Speculative decoding: by default 1 token is generated.
408
+ self.num_accepted_tokens_cpu[req_index] = 1
409
+
410
# Add request lora ID
411
if request.lora_request:
412
lora_id = request.lora_request.lora_int_id
@@ -515,6 +528,8 @@ class InputBatch:
528
self.presence_penalties_cpu[i2], self.presence_penalties_cpu[i1]
529
self.repetition_penalties_cpu[i1], self.repetition_penalties_cpu[i2] = \
530
self.repetition_penalties_cpu[i2], self.repetition_penalties_cpu[i1]
531
+ self.num_accepted_tokens_cpu[i1], self.num_accepted_tokens_cpu[i2] =\
532
+ self.num_accepted_tokens_cpu[i2], self.num_accepted_tokens_cpu[i1]
533
534
swap_dict_values(self.generators, i1, i2)
535
swap_dict_values(self.bad_words_token_ids, i1, i2)
@@ -609,6 +624,8 @@ class InputBatch:
624
empty_index] = self.presence_penalties_cpu[last_req_index]
625
self.repetition_penalties_cpu[
626
empty_index] = self.repetition_penalties_cpu[last_req_index]
627
+ self.num_accepted_tokens_cpu[
628
+ empty_index] = self.num_accepted_tokens_cpu[last_req_index]
629
generator = self.generators.pop(last_req_index, None)
630
if generator is not None:
631
self.generators[empty_index] = generator
vllm/v1/worker/gpu_model_runner.py
+65
-9
@@ -53,9 +53,9 @@ from vllm.sampling_params import SamplingType
53
from vllm.sequence import IntermediateTensors, PoolerOutput
54
from vllm.tasks import GenerationTask, PoolingTask, SupportedTask
55
from vllm.utils import (STR_DTYPE_TO_TORCH_DTYPE, DeviceMemoryProfiler,
56
- GiB_bytes, LazyLoader, cdiv, check_use_alibi,
57
- get_dtype_size, is_pin_memory_available, round_up,
58
- supports_dynamo)
56
+ GiB_bytes, LazyLoader, check_use_alibi, get_dtype_size,
57
+ is_pin_memory_available, round_up, supports_dynamo)
58
+from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadataBuilder
59
from vllm.v1.attention.backends.utils import (
60
AttentionCGSupport, AttentionMetadataBuilder, CommonAttentionMetadata,
61
create_fast_prefill_custom_backend,
@@ -324,6 +324,10 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
324
self.hidden_size,
325
dtype=self.dtype,
326
numpy=False)
327
+ self.num_draft_tokens = self._make_buffer(self.max_num_reqs,
328
+ dtype=torch.int32)
329
+ self.num_accepted_tokens = self._make_buffer(self.max_num_reqs,
330
+ dtype=torch.int64)
331
332
# Only relevant for models using M-RoPE (e.g, Qwen2-VL)
333
if self.uses_mrope:
@@ -663,6 +667,31 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
667
# Refresh batch metadata with any pending updates.
668
self.input_batch.refresh_metadata()
669
670
+ def _update_states_after_model_execute(
671
+ self, output_token_ids: torch.Tensor) -> None:
672
+ """Update the cached states after model execution.
673
+
674
+ This is used for MTP/EAGLE for hybrid models, as in linear attention,
675
+ only the last token's state is kept. In MTP/EAGLE, for draft tokens
676
+ the state are kept util we decide how many tokens are accepted for
677
+ each sequence, and a shifting is done during the next iteration
678
+ based on the number of accepted tokens.
679
+ """
680
+ if not self.model_config.is_hybrid or not self.speculative_config:
681
+ return
682
+
683
+ # Find the number of accepted tokens for each sequence.
684
+ num_accepted_tokens = (torch.cat(
685
+ [
686
+ output_token_ids,
687
+ torch.full((output_token_ids.size(0), 1),
688
+ -1,
689
+ device=output_token_ids.device),
690
+ ],
691
+ dim=1) == -1).int().argmax(-1).cpu().numpy()
692
+ for i, num_tokens in enumerate(num_accepted_tokens):
693
+ self.input_batch.num_accepted_tokens_cpu[i] = num_tokens
694
+
695
def _init_mrope_positions(self, req_state: CachedRequestState):
696
image_grid_thw = []
697
video_grid_thw = []
@@ -936,6 +965,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
965
# We will ignore the sampled tokens from the partial requests.
966
# TODO: Support prompt logprobs.
967
logits_indices = query_start_loc[1:] - 1
968
+ num_draft_tokens = None
969
spec_decode_metadata = None
970
else:
971
# Get the number of draft tokens for each request.
@@ -950,6 +980,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
980
spec_decode_metadata = self._calc_spec_decode_metadata(
981
num_draft_tokens, cu_num_tokens)
982
logits_indices = spec_decode_metadata.logits_indices
983
+ self.num_draft_tokens.np[:num_reqs] = num_draft_tokens
984
+ self.num_draft_tokens.np[num_reqs:].fill(0)
985
+ self.num_draft_tokens.copy_to_gpu()
986
987
logits_indices_padded = None
988
if self.cache_config.kv_sharing_fast_prefill:
@@ -964,6 +997,11 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
997
num_computed_tokens_cpu = (
998
self.input_batch.num_computed_tokens_cpu_tensor[:num_reqs])
999
spec_decode_common_attn_metadata = None
1000
+ if use_spec_decode:
1001
+ self.num_accepted_tokens.np[:num_reqs] = (
1002
+ self.input_batch.num_accepted_tokens_cpu[:num_reqs])
1003
+ self.num_accepted_tokens.np[num_reqs:].fill(1)
1004
+ self.num_accepted_tokens.copy_to_gpu()
1005
1006
# Prepare the attention metadata for each KV cache group and make layers
1007
# in the same group share the same metadata.
@@ -1034,10 +1072,19 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1072
builder,
1073
)
1074
1075
+ extra_attn_metadata_args = {}
1076
+ if use_spec_decode and isinstance(builder,
1077
+ GDNAttentionMetadataBuilder):
1078
+ extra_attn_metadata_args = dict(
1079
+ num_accepted_tokens=self.num_accepted_tokens.
1080
+ gpu[:num_reqs],
1081
+ num_draft_tokens=self.num_draft_tokens.gpu[:num_reqs],
1082
+ )
1083
+
1084
attn_metadata_i = builder.build(
1085
common_prefix_len=common_prefix_len,
1086
common_attn_metadata=common_attn_metadata,
1040
- )
1087
+ **extra_attn_metadata_args)
1088
1089
for layer_name in attn_group.layer_names:
1090
attn_metadata[layer_name] = attn_metadata_i
@@ -1814,6 +1861,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1861
sampling_metadata,
1862
)
1863
sampler_output.sampled_token_ids = output_token_ids
1864
+ self._update_states_after_model_execute(output_token_ids)
1865
1866
return sampler_output
1867
@@ -2644,13 +2692,12 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2692
# Note: Overriding max_query_len to be the prefill tokens
2693
max_query_len = num_prefill_tokens
2694
elif uniform_decode:
2647
- assert not create_mixed_batch
2648
- num_reqs = cdiv(num_tokens, max_query_len)
2695
+ num_reqs = num_tokens // max_query_len
2696
assert num_reqs <= max_num_reqs, \
2697
"Do not capture num_reqs > max_num_reqs for uniform batch"
2698
num_scheduled_tokens_list = [max_query_len] * num_reqs
2699
if num_tokens % max_query_len != 0:
2653
- num_scheduled_tokens_list[-1] = num_tokens % max_query_len
2700
+ num_scheduled_tokens_list[-1] += num_tokens % max_query_len
2701
else:
2702
num_reqs = min(num_tokens, max_num_reqs)
2703
min_tokens_per_req = num_tokens // num_reqs
@@ -3297,6 +3344,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3344
is_spec_decode=bool(self.vllm_config.speculative_config),
3345
logitsprocs=self.input_batch.logitsprocs,
3346
is_pooling_model=self.is_pooling_model,
3347
+ num_speculative_tokens=(
3348
+ self.vllm_config.speculative_config.num_speculative_tokens
3349
+ if self.vllm_config.speculative_config else 0),
3350
)
3351
3352
def _allocate_kv_cache_tensors(
@@ -3647,7 +3697,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3697
3698
mamba_layers = get_layers_from_vllm_config(self.vllm_config, MambaBase)
3699
if len(mamba_layers) > 0:
3650
- if self.vllm_config.speculative_config is not None:
3700
+ if (self.vllm_config.speculative_config is not None
3701
+ and self.vllm_config.model_config.hf_config.model_type
3702
+ not in ["qwen3_next"]):
3703
raise NotImplementedError(
3704
"Mamba with speculative decoding is not supported yet.")
3705
if self.vllm_config.cache_config.enable_prefix_caching:
@@ -3666,7 +3718,11 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3718
dtypes=mamba_module.get_state_dtype(),
3719
block_size=max_model_len,
3720
page_size_padded=page_size_padded,
3669
- mamba_type=mamba_module.mamba_type)
3721
+ mamba_type=mamba_module.mamba_type,
3722
+ num_speculative_blocks=(
3723
+ self.speculative_config.num_speculative_tokens
3724
+ if self.speculative_config else 0),
3725
+ )
3726
3727
return kv_cache_spec
3728
vllm/worker/worker.py
+2
-1
@@ -78,7 +78,8 @@ class Worker(LocalOrDistributedWorkerBase):
78
"deepseek_mtp",
79
"glm4_moe_mtp",
80
"mimo_mtp",
81
- "ernie_mtp")) \
81
+ "ernie_mtp",
82
+ "qwen3_next_mtp")) \
83
else {"return_hidden_states": True}
84
85
ModelRunnerClass: Type[GPUModelRunnerBase] = ModelRunner