[Model][Frontend] Add Ling 3.0 Flash BF16, MTP, and parser support (#51045)

Signed-off-by: zexplorerhj <zhjoneson@163.com>

zexplorerhj committed Aug 6, 2026 at 00:38 UTC d4da0c55af3aa231b6209bf77871f3ed36eab0d2
15 files changed +2070 -1
benchmarks/kernels/benchmark_moe.py
+1
@@ -777,6 +777,7 @@ def get_model_params(config):
777 intermediate_size = config.moe_intermediate_size
778 hidden_size = config.hidden_size
779 elif architecture in (
780 + "BailingMoeV3ForCausalLM",
781 "Qwen2MoeForCausalLM",
782 "Qwen3MoeForCausalLM",
783 "Qwen3NextForCausalLM",
docs/models/supported_models.md
+1
@@ -348,6 +348,7 @@ th {
348 | `BailingMoeForCausalLM` | Ling | `inclusionAI/Ling-lite-1.5`, `inclusionAI/Ling-plus`, etc. | ✅︎ | ✅︎ |
349 | `BailingMoeV2ForCausalLM` | Ling | `inclusionAI/Ling-mini-2.0`, etc. | ✅︎ | ✅︎ |
350 | `BailingMoeV2_5ForCausalLM` | Ling | `inclusionAI/Ling-2.5-1T`, `inclusionAI/Ring-2.5-1T` | | ✅︎ |
351 +| `BailingMoeV3ForCausalLM` | Ling | `inclusionAI/Ling-3.0-flash` | | ✅︎ |
352 | `BloomForCausalLM` | BLOOM, BLOOMZ, BLOOMChat | `bigscience/bloom`, `bigscience/bloomz`, etc. | | ✅︎ |
353 | `ChatGLMModel`, `ChatGLMForConditionalGeneration` | ChatGLM | `zai-org/chatglm2-6b`, `zai-org/chatglm3-6b`, `thu-coai/ShieldLM-6B-chatglm3`, etc. | ✅︎ | ✅︎ |
354 | `CohereForCausalLM`, `Cohere2ForCausalLM` | Command-R, Command-A | `CohereLabs/c4ai-command-r-v01`, `CohereLabs/c4ai-command-r7b-12-2024`, `CohereLabs/c4ai-command-a-03-2025`, `CohereLabs/command-a-reasoning-08-2025`, etc. | ✅︎ | ✅︎ |
tests/models/registry.py
+8
@@ -213,6 +213,9 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
213 "BailingMoeV2_5ForCausalLM": _HfExamplesInfo(
214 "inclusionAI/Ring-2.5-1T", trust_remote_code=True
215 ),
216 + "BailingMoeV3ForCausalLM": _HfExamplesInfo(
217 + "inclusionAI/Ling-3.0-flash", trust_remote_code=True
218 + ),
219 "BloomForCausalLM": _HfExamplesInfo(
220 "bigscience/bloom-560m", {"1b": "bigscience/bloomz-1b1"}
221 ),
@@ -1614,6 +1617,11 @@ _SPECULATIVE_DECODING_EXAMPLE_MODELS = {
1617 trust_remote_code=True,
1618 is_available_online=False,
1619 ),
1620 + "BailingMoeV3MTPModel": _HfExamplesInfo(
1621 + "inclusionAI/Ling-3.0-flash",
1622 + speculative_model="inclusionAI/Ling-3.0-flash",
1623 + trust_remote_code=True,
1624 + ),
1625 "DeepSeekMTPModel": _HfExamplesInfo(
1626 "luccafong/deepseek_mtp_main_random",
1627 speculative_model="luccafong/deepseek_mtp_draft_random",
tests/parser/engine/test_ling3.py new
+105
@@ -0,0 +1,105 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +import json
5 +
6 +from tests.parser.engine.replay_harness import MockTokenizer, _test_request
7 +from vllm.reasoning import ReasoningParserManager
8 +from vllm.tool_parsers import ToolParserManager
9 +
10 +
11 +def _tokenizer() -> MockTokenizer:
12 + vocab = {
13 + "<think>": 1,
14 + "</think>": 2,
15 + "<tool_call>": 3,
16 + "</tool_call>": 4,
17 + "<arg_key>": 5,
18 + "</arg_key>": 6,
19 + "<arg_value>": 7,
20 + "</arg_value>": 8,
21 + }
22 + return MockTokenizer(vocab=vocab, tokens=[])
23 +
24 +
25 +def test_ling3_registered():
26 + assert ReasoningParserManager.get_reasoning_parser("ling3") is not None
27 + assert ToolParserManager.get_tool_parser("ling3") is not None
28 +
29 +
30 +def test_ling3_defaults_thinking_on():
31 + parser_cls = ReasoningParserManager.get_reasoning_parser("ling3")
32 + parser = parser_cls(_tokenizer())
33 +
34 + assert parser.reasoning_start_str == "<think>"
35 + assert parser.reasoning_end_str == "</think>"
36 +
37 + reasoning, content = parser.extract_reasoning(
38 + "<think>reason</think>answer", _test_request()
39 + )
40 +
41 + assert reasoning == "reason"
42 + assert content == "answer"
43 +
44 +
45 +def test_ling3_disable_thinking_keeps_reasoning_as_content():
46 + parser_cls = ReasoningParserManager.get_reasoning_parser("ling3")
47 + parser = parser_cls(
48 + _tokenizer(),
49 + chat_template_kwargs={"enable_thinking": False},
50 + )
51 +
52 + reasoning, content = parser.extract_reasoning(
53 + "<think>reason</think>answer", _test_request()
54 + )
55 +
56 + assert reasoning is None
57 + assert content == "<think>reason</think>answer"
58 +
59 +
60 +def test_ling3_enable_thinking_keeps_open_reasoning_as_content():
61 + parser_cls = ReasoningParserManager.get_reasoning_parser("ling3")
62 + parser = parser_cls(
63 + _tokenizer(),
64 + chat_template_kwargs={"enable_thinking": True},
65 + )
66 +
67 + reasoning, content = parser.extract_reasoning(
68 + "<think>only reasoning", _test_request()
69 + )
70 +
71 + assert reasoning is None
72 + assert content == "only reasoning"
73 +
74 +
75 +def test_ling3_tool_call_without_newline():
76 + parser_cls = ToolParserManager.get_tool_parser("ling3")
77 + parser = parser_cls(_tokenizer())
78 +
79 + result = parser.extract_tool_calls(
80 + "<tool_call>get_weather<arg_key>city</arg_key>"
81 + "<arg_value>Beijing</arg_value></tool_call>",
82 + request=_test_request(),
83 + )
84 +
85 + assert result.tools_called
86 + assert result.content is None
87 + assert result.tool_calls[0].function.name == "get_weather"
88 + assert json.loads(result.tool_calls[0].function.arguments) == {
89 + "city": "Beijing",
90 + }
91 +
92 +
93 +def test_ling3_tool_call_without_args():
94 + parser_cls = ToolParserManager.get_tool_parser("ling3")
95 + parser = parser_cls(_tokenizer())
96 +
97 + result = parser.extract_tool_calls(
98 + "<tool_call>ping</tool_call>",
99 + request=_test_request(),
100 + )
101 +
102 + assert result.tools_called
103 + assert result.content is None
104 + assert result.tool_calls[0].function.name == "ping"
105 + assert json.loads(result.tool_calls[0].function.arguments) == {}
vllm/config/speculative.py
+12 -1
@@ -48,6 +48,7 @@ MTPModelTypes = Literal[
48 "qwen3_next_mtp",
49 "qwen3_5_mtp",
50 "longcat_flash_mtp",
51 + "bailing_hybrid_v3_mtp",
52 "minimax_m3_mtp",
53 "bailing_hybrid_mtp",
54 "mtp",
@@ -489,7 +490,9 @@ class SpeculativeConfig:
490 )
491
492 architectures = getattr(hf_config, "architectures", []) or []
492 - if (
493 + if initial_architecture == "BailingMoeV3ForCausalLM":
494 + hf_config.model_type = "bailing_hybrid_v3_mtp"
495 + elif (
496 hf_config.model_type == "bailing_hybrid"
497 or "BailingMoeV2_5ForCausalLM" in architectures
498 ):
@@ -502,6 +505,14 @@ class SpeculativeConfig:
505 "architectures": ["BailingMoeV25MTPModel"],
506 }
507 )
508 + if hf_config.model_type == "bailing_hybrid_v3_mtp":
509 + n_predict = getattr(hf_config, "num_nextn_predict_layers", None)
510 + hf_config.update(
511 + {
512 + "n_predict": n_predict,
513 + "architectures": ["BailingMoeV3MTPModel"],
514 + }
515 + )
516
517 if hf_config.model_type == "exaone_moe":
518 hf_config.model_type = "exaone_moe_mtp"
vllm/model_executor/layers/fused_moe/configs/E=512,N=192,device_name=NVIDIA_H20-3e.json new
+147
@@ -0,0 +1,147 @@
1 +{
2 + "triton_version": "3.6.0",
3 + "1": {
4 + "BLOCK_SIZE_M": 16,
5 + "BLOCK_SIZE_N": 32,
6 + "BLOCK_SIZE_K": 64,
7 + "GROUP_SIZE_M": 1,
8 + "num_warps": 4,
9 + "num_stages": 4
10 + },
11 + "2": {
12 + "BLOCK_SIZE_M": 16,
13 + "BLOCK_SIZE_N": 64,
14 + "BLOCK_SIZE_K": 64,
15 + "GROUP_SIZE_M": 1,
16 + "num_warps": 4,
17 + "num_stages": 4
18 + },
19 + "4": {
20 + "BLOCK_SIZE_M": 16,
21 + "BLOCK_SIZE_N": 64,
22 + "BLOCK_SIZE_K": 64,
23 + "GROUP_SIZE_M": 32,
24 + "num_warps": 4,
25 + "num_stages": 4
26 + },
27 + "8": {
28 + "BLOCK_SIZE_M": 16,
29 + "BLOCK_SIZE_N": 64,
30 + "BLOCK_SIZE_K": 64,
31 + "GROUP_SIZE_M": 64,
32 + "num_warps": 4,
33 + "num_stages": 4
34 + },
35 + "16": {
36 + "BLOCK_SIZE_M": 16,
37 + "BLOCK_SIZE_N": 128,
38 + "BLOCK_SIZE_K": 64,
39 + "GROUP_SIZE_M": 1,
40 + "num_warps": 4,
41 + "num_stages": 3
42 + },
43 + "24": {
44 + "BLOCK_SIZE_M": 16,
45 + "BLOCK_SIZE_N": 64,
46 + "BLOCK_SIZE_K": 64,
47 + "GROUP_SIZE_M": 1,
48 + "num_warps": 4,
49 + "num_stages": 4
50 + },
51 + "32": {
52 + "BLOCK_SIZE_M": 16,
53 + "BLOCK_SIZE_N": 64,
54 + "BLOCK_SIZE_K": 64,
55 + "GROUP_SIZE_M": 1,
56 + "num_warps": 4,
57 + "num_stages": 3
58 + },
59 + "48": {
60 + "BLOCK_SIZE_M": 16,
61 + "BLOCK_SIZE_N": 128,
62 + "BLOCK_SIZE_K": 64,
63 + "GROUP_SIZE_M": 16,
64 + "num_warps": 4,
65 + "num_stages": 3
66 + },
67 + "64": {
68 + "BLOCK_SIZE_M": 16,
69 + "BLOCK_SIZE_N": 128,
70 + "BLOCK_SIZE_K": 64,
71 + "GROUP_SIZE_M": 16,
72 + "num_warps": 4,
73 + "num_stages": 3
74 + },
75 + "96": {
76 + "BLOCK_SIZE_M": 16,
77 + "BLOCK_SIZE_N": 128,
78 + "BLOCK_SIZE_K": 64,
79 + "GROUP_SIZE_M": 16,
80 + "num_warps": 4,
81 + "num_stages": 3
82 + },
83 + "128": {
84 + "BLOCK_SIZE_M": 16,
85 + "BLOCK_SIZE_N": 128,
86 + "BLOCK_SIZE_K": 64,
87 + "GROUP_SIZE_M": 16,
88 + "num_warps": 4,
89 + "num_stages": 3
90 + },
91 + "256": {
92 + "BLOCK_SIZE_M": 16,
93 + "BLOCK_SIZE_N": 128,
94 + "BLOCK_SIZE_K": 64,
95 + "GROUP_SIZE_M": 1,
96 + "num_warps": 4,
97 + "num_stages": 3
98 + },
99 + "512": {
100 + "BLOCK_SIZE_M": 16,
101 + "BLOCK_SIZE_N": 128,
102 + "BLOCK_SIZE_K": 64,
103 + "GROUP_SIZE_M": 16,
104 + "num_warps": 4,
105 + "num_stages": 3
106 + },
107 + "1024": {
108 + "BLOCK_SIZE_M": 16,
109 + "BLOCK_SIZE_N": 128,
110 + "BLOCK_SIZE_K": 64,
111 + "GROUP_SIZE_M": 16,
112 + "num_warps": 4,
113 + "num_stages": 3
114 + },
115 + "1536": {
116 + "BLOCK_SIZE_M": 32,
117 + "BLOCK_SIZE_N": 128,
118 + "BLOCK_SIZE_K": 64,
119 + "GROUP_SIZE_M": 64,
120 + "num_warps": 4,
121 + "num_stages": 3
122 + },
123 + "2048": {
124 + "BLOCK_SIZE_M": 64,
125 + "BLOCK_SIZE_N": 128,
126 + "BLOCK_SIZE_K": 64,
127 + "GROUP_SIZE_M": 1,
128 + "num_warps": 4,
129 + "num_stages": 2
130 + },
131 + "3072": {
132 + "BLOCK_SIZE_M": 64,
133 + "BLOCK_SIZE_N": 128,
134 + "BLOCK_SIZE_K": 64,
135 + "GROUP_SIZE_M": 1,
136 + "num_warps": 4,
137 + "num_stages": 2
138 + },
139 + "4096": {
140 + "BLOCK_SIZE_M": 64,
141 + "BLOCK_SIZE_N": 128,
142 + "BLOCK_SIZE_K": 64,
143 + "GROUP_SIZE_M": 1,
144 + "num_warps": 4,
145 + "num_stages": 2
146 + }
147 +}
vllm/model_executor/models/bailing_moe_v3.py new
+1289
@@ -0,0 +1,1289 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""vLLM implementation for BailingMoeV3ForCausalLM.
4 +
5 +The HuggingFace reference model mixes MLA full-attention layers with Kimi
6 +Delta Attention linear layers and Bailing MoE blocks. This file keeps the V3
7 +module/weight names aligned with the reference implementation while reusing
8 +vLLM's parallel linear layers, MLA kernel, KDA kernel and fused MoE loader.
9 +"""
10 +
11 +import copy
12 +from collections.abc import Iterable
13 +
14 +import torch
15 +import torch.nn as nn
16 +import torch.nn.functional as F
17 +from einops import rearrange
18 +from transformers.configuration_utils import PretrainedConfig
19 +
20 +from vllm.compilation.decorators import support_torch_compile
21 +from vllm.config import CacheConfig, ModelConfig, VllmConfig, get_current_vllm_config
22 +from vllm.distributed import (
23 + get_pp_group,
24 + get_tensor_model_parallel_rank,
25 + get_tensor_model_parallel_world_size,
26 +)
27 +from vllm.forward_context import ForwardContext, get_forward_context
28 +from vllm.model_executor.custom_op import PluggableLayer
29 +from vllm.model_executor.layers.activation import SiluAndMul, SwigluStepAndMul
30 +from vllm.model_executor.layers.fused_moe import (
31 + FusedMoEFactory,
32 + fused_moe_make_expert_params_mapping,
33 +)
34 +from vllm.model_executor.layers.layernorm import RMSNorm
35 +from vllm.model_executor.layers.linear import (
36 + ColumnParallelLinear,
37 + MergedColumnParallelLinear,
38 + ReplicatedLinear,
39 + RowParallelLinear,
40 +)
41 +from vllm.model_executor.layers.logits_processor import LogitsProcessor
42 +from vllm.model_executor.layers.mamba.abstract import MambaBase
43 +from vllm.model_executor.layers.mamba.mamba_utils import (
44 + MambaStateCopyFuncCalculator,
45 + MambaStateDtypeCalculator,
46 + MambaStateShapeCalculator,
47 + is_conv_state_dim_first,
48 +)
49 +from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
50 + causal_conv1d_fn,
51 + causal_conv1d_update,
52 +)
53 +from vllm.model_executor.layers.mla import (
54 + MLAModules,
55 + MultiHeadLatentAttentionWrapper,
56 +)
57 +from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
58 +from vllm.model_executor.layers.rotary_embedding import get_rope
59 +from vllm.model_executor.layers.vocab_parallel_embedding import (
60 + ParallelLMHead,
61 + VocabParallelEmbedding,
62 +)
63 +from vllm.model_executor.model_loader.weight_utils import (
64 + default_weight_loader,
65 + maybe_remap_kv_scale_name,
66 + sharded_weight_loader,
67 +)
68 +from vllm.model_executor.utils import set_weight_attrs
69 +from vllm.sequence import IntermediateTensors
70 +from vllm.third_party.flash_linear_attention.ops.kda import (
71 + FusedRMSNormGated,
72 + chunk_kda,
73 + fused_kda_gate,
74 + fused_recurrent_kda,
75 + fused_recurrent_kda_fwd,
76 +)
77 +from vllm.utils.torch_utils import direct_register_custom_op
78 +from vllm.v1.attention.backend import AttentionMetadata
79 +from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
80 +from vllm.v1.attention.backends.registry import MambaAttentionBackendEnum
81 +
82 +from .interfaces import HasInnerState, IsHybrid, SupportsPP
83 +from .utils import PPMissingLayer, is_pp_missing_parameter, make_layers, maybe_prefix
84 +
85 +
86 +def bailing_v3_kda_attention(
87 + q_proj_states: torch.Tensor,
88 + k_proj_states: torch.Tensor,
89 + v_proj_states: torch.Tensor,
90 + g1: torch.Tensor,
91 + beta: torch.Tensor,
92 + core_attn_out: torch.Tensor,
93 + layer_name: str,
94 +) -> None:
95 + """Run Bailing V3's KDA state update outside the compiled graph."""
96 + forward_context: ForwardContext = get_forward_context()
97 + layer = forward_context.no_compile_layers[layer_name]
98 + layer._forward(
99 + q_proj_states=q_proj_states,
100 + k_proj_states=k_proj_states,
101 + v_proj_states=v_proj_states,
102 + g1=g1,
103 + beta=beta,
104 + core_attn_out=core_attn_out,
105 + )
106 +
107 +
108 +def bailing_v3_kda_attention_fake(
109 + q_proj_states: torch.Tensor,
110 + k_proj_states: torch.Tensor,
111 + v_proj_states: torch.Tensor,
112 + g1: torch.Tensor,
113 + beta: torch.Tensor,
114 + core_attn_out: torch.Tensor,
115 + layer_name: str,
116 +) -> None:
117 + return
118 +
119 +
120 +direct_register_custom_op(
121 + op_name="bailing_v3_kda_attention",
122 + op_func=bailing_v3_kda_attention,
123 + mutates_args=["core_attn_out"],
124 + fake_impl=bailing_v3_kda_attention_fake,
125 +)
126 +
127 +
128 +def _is_kda_layer(
129 + layer_idx: int, layer_group_size: int, num_hidden_layers: int
130 +) -> bool:
131 + return not (
132 + (layer_idx + 1) % layer_group_size == 0
133 + or layer_idx >= num_hidden_layers // layer_group_size * layer_group_size
134 + )
135 +
136 +
137 +def _get_kda_state_shape_for_config(
138 + vllm_config: VllmConfig,
139 +) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
140 + config = vllm_config.model_config.hf_config
141 + num_spec = (
142 + vllm_config.speculative_config.num_speculative_tokens
143 + if vllm_config.speculative_config
144 + else 0
145 + )
146 + shapes = MambaStateShapeCalculator.kda_state_shape(
147 + vllm_config.parallel_config.tensor_parallel_size,
148 + config.num_attention_heads,
149 + config.head_dim,
150 + conv_kernel_size=config.short_conv_kernel_size,
151 + num_spec=num_spec,
152 + )
153 +
154 + return shapes
155 +
156 +
157 +def _build_rope_parameters(config: PretrainedConfig) -> dict | None:
158 + rope_parameters = copy.deepcopy(getattr(config, "rope_parameters", None)) or {}
159 + if "rope_theta" not in rope_parameters and hasattr(config, "rope_theta"):
160 + rope_parameters["rope_theta"] = config.rope_theta
161 + # BailingMoeV3 stores the MLA rotary width explicitly in qk_rope_head_dim.
162 + # Some checkpoints also carry partial_rotary_factor=0.5, but applying it
163 + # here would shrink the rotary cache to half of the qk_rope_head_dim.
164 + rope_parameters.pop("partial_rotary_factor", None)
165 +
166 + rope_scaling = getattr(config, "rope_scaling", None)
167 + if isinstance(rope_scaling, dict):
168 + rope_scaling = copy.deepcopy(rope_scaling)
169 + if "type" in rope_scaling and "rope_type" not in rope_scaling:
170 + rope_scaling["rope_type"] = rope_scaling.pop("type")
171 + rope_scaling.pop("partial_rotary_factor", None)
172 + rope_parameters.update(rope_scaling)
173 +
174 + return rope_parameters or None
175 +
176 +
177 +def _get_layer_swiglu_limit(limit_list: list | None, layer_idx: int) -> float | None:
178 + if limit_list is None or layer_idx >= len(limit_list):
179 + return None
180 + limit = limit_list[layer_idx]
181 + if limit in (None, 0):
182 + return None
183 + return float(limit)
184 +
185 +
186 +def _load_a_log(param: torch.nn.Parameter, loaded_weight: torch.Tensor) -> None:
187 + tp_rank = get_tensor_model_parallel_rank()
188 + tp_size = get_tensor_model_parallel_world_size()
189 + if loaded_weight.ndim == 1:
190 + shard_size = loaded_weight.shape[0] // tp_size
191 + shard = loaded_weight.narrow(0, tp_rank * shard_size, shard_size)
192 + param.data.copy_(shard.view_as(param.data))
193 + else:
194 + sharded_weight_loader(2)(param, loaded_weight)
195 +
196 +
197 +class _IdentityProjection(nn.Module):
198 + def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, None]:
199 + return x, None
200 +
201 +
202 +class BailingMoeV3MLP(nn.Module):
203 + def __init__(
204 + self,
205 + intermediate_size: int,
206 + config: PretrainedConfig,
207 + quant_config: QuantizationConfig | None = None,
208 + reduce_results: bool | None = True,
209 + prefix: str = "",
210 + swiglu_limit: float | None = None,
211 + ) -> None:
212 + super().__init__()
213 + self.gate_up_proj = MergedColumnParallelLinear(
214 + config.hidden_size,
215 + [intermediate_size] * 2,
216 + bias=config.use_bias,
217 + quant_config=quant_config,
218 + prefix=f"{prefix}.gate_up_proj",
219 + )
220 + self.down_proj = RowParallelLinear(
221 + intermediate_size,
222 + config.hidden_size,
223 + bias=config.use_bias,
224 + quant_config=quant_config,
225 + reduce_results=reduce_results,
226 + prefix=f"{prefix}.down_proj",
227 + )
228 + self.act_fn = (
229 + SwigluStepAndMul(limit=swiglu_limit)
230 + if swiglu_limit not in (None, 0)
231 + else SiluAndMul()
232 + )
233 +
234 + def forward(self, x):
235 + x, _ = self.gate_up_proj(x)
236 + x = self.act_fn(x)
237 + x, _ = self.down_proj(x)
238 + return x
239 +
240 +
241 +class BailingMoeV3MLAAttention(nn.Module):
242 + def __init__(
243 + self,
244 + config: PretrainedConfig,
245 + quant_config: QuantizationConfig | None = None,
246 + layer_id: int = 0,
247 + prefix: str = "attention",
248 + cache_config: CacheConfig | None = None,
249 + ) -> None:
250 + super().__init__()
251 + self.hidden_size = config.hidden_size
252 + self.num_heads = config.num_attention_heads
253 + self.layer_id = layer_id
254 +
255 + self.qk_nope_head_dim = getattr(config, "qk_nope_head_dim", 128)
256 + self.qk_rope_head_dim = getattr(config, "qk_rope_head_dim", 64)
257 + self.qk_head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim
258 + self.v_head_dim = getattr(config, "v_head_dim", 128)
259 + self.q_lora_rank = getattr(config, "q_lora_rank", None)
260 + self.kv_lora_rank = getattr(config, "kv_lora_rank", 512)
261 +
262 + tp_size = get_tensor_model_parallel_world_size()
263 + assert self.num_heads % tp_size == 0
264 + self.num_local_heads = self.num_heads // tp_size
265 + self.scaling = self.qk_head_dim**-0.5
266 +
267 + if self.q_lora_rank is None:
268 + self.q_proj = ColumnParallelLinear(
269 + self.hidden_size,
270 + self.num_heads * self.qk_head_dim,
271 + bias=False,
272 + quant_config=quant_config,
273 + prefix=f"{prefix}.q_proj",
274 + )
275 + self.fused_qkv_a_proj = None
276 + self.q_a_layernorm = None
277 + self.q_b_proj = None
278 + self.kv_a_proj_with_mqa = ReplicatedLinear(
279 + self.hidden_size,
280 + self.kv_lora_rank + self.qk_rope_head_dim,
281 + bias=False,
282 + quant_config=quant_config,
283 + prefix=f"{prefix}.kv_a_proj_with_mqa",
284 + )
285 + else:
286 + self.fused_qkv_a_proj = MergedColumnParallelLinear(
287 + self.hidden_size,
288 + [self.q_lora_rank, self.kv_lora_rank + self.qk_rope_head_dim],
289 + bias=False,
290 + quant_config=quant_config,
291 + prefix=f"{prefix}.fused_qkv_a_proj",
292 + disable_tp=True,
293 + )
294 + self.q_a_layernorm = RMSNorm(self.q_lora_rank, eps=config.rms_norm_eps)
295 + self.q_b_proj = ColumnParallelLinear(
296 + self.q_lora_rank,
297 + self.num_heads * self.qk_head_dim,
298 + bias=False,
299 + quant_config=quant_config,
300 + prefix=f"{prefix}.q_b_proj",
301 + )
302 + self.q_proj = None
303 + self.kv_a_proj_with_mqa = None
304 +
305 + self.kv_a_layernorm = RMSNorm(self.kv_lora_rank, eps=config.rms_norm_eps)
306 + self.kv_b_proj = ColumnParallelLinear(
307 + self.kv_lora_rank,
308 + self.num_heads * (self.qk_nope_head_dim + self.v_head_dim),
309 + bias=False,
310 + quant_config=quant_config,
311 + prefix=f"{prefix}.kv_b_proj",
312 + )
313 + self.gated_attention_proj_granularity_type = getattr(
314 + config, "gated_attention_proj_granularity_type", None
315 + )
316 + if self.gated_attention_proj_granularity_type == "head_wise":
317 + g_out = self.num_heads
318 + elif self.gated_attention_proj_granularity_type == "element_wise":
319 + g_out = self.num_heads * self.v_head_dim
320 + else:
321 + g_out = 0
322 + self.g_proj = (
323 + ColumnParallelLinear(
324 + self.hidden_size,
325 + g_out,
326 + bias=False,
327 + quant_config=quant_config,
328 + prefix=f"{prefix}.g_proj",
329 + )
330 + if g_out > 0
331 + else None
332 + )
333 + self.dense = RowParallelLinear(
334 + self.num_heads * self.v_head_dim,
335 + self.hidden_size,
336 + bias=False,
337 + quant_config=quant_config,
338 + prefix=f"{prefix}.dense",
339 + )
340 +
341 + self.rotary_emb = get_rope(
342 + head_size=self.qk_rope_head_dim,
343 + max_position=getattr(config, "max_position_embeddings", 8192),
344 + is_neox_style=False,
345 + rope_parameters=_build_rope_parameters(config),
346 + )
347 + mla_modules = MLAModules(
348 + kv_a_layernorm=self.kv_a_layernorm,
349 + kv_b_proj=self.kv_b_proj,
350 + rotary_emb=self.rotary_emb,
351 + o_proj=_IdentityProjection(),
352 + fused_qkv_a_proj=self.fused_qkv_a_proj,
353 + kv_a_proj_with_mqa=self.kv_a_proj_with_mqa,
354 + q_a_layernorm=self.q_a_layernorm,
355 + q_b_proj=self.q_b_proj,
356 + q_proj=self.q_proj,
357 + indexer=None,
358 + is_sparse=False,
359 + topk_indices_buffer=None,
360 + )
361 + self.mla_attn = MultiHeadLatentAttentionWrapper(
362 + self.hidden_size,
363 + self.num_local_heads,
364 + self.scaling,
365 + self.qk_nope_head_dim,
366 + self.qk_rope_head_dim,
367 + self.v_head_dim,
368 + self.q_lora_rank,
369 + self.kv_lora_rank,
370 + mla_modules,
371 + cache_config,
372 + quant_config,
373 + prefix,
374 + )
375 +
376 + def forward(self, hidden_states: torch.Tensor, positions: torch.Tensor):
377 + attn_out = self.mla_attn(positions, hidden_states)
378 + if self.g_proj is not None:
379 + gate = torch.sigmoid(self.g_proj(hidden_states)[0].float()).to(
380 + hidden_states.dtype
381 + )
382 + if self.gated_attention_proj_granularity_type == "head_wise":
383 + attn_out = attn_out.view(-1, self.num_local_heads, self.v_head_dim)
384 + attn_out = attn_out * gate.unsqueeze(-1)
385 + attn_out = attn_out.reshape(hidden_states.shape[0], -1)
386 + else:
387 + attn_out = attn_out * gate
388 +
389 + return self.dense(attn_out)[0]
390 +
391 +
392 +# --8<-- [start:bailing_moe_v3_kimi_delta_attention]
393 +@PluggableLayer.register("bailing_moe_v3_kimi_delta_attention")
394 +class BailingMoeV3KimiDeltaAttention(PluggableLayer, MambaBase):
395 + # --8<-- [end:bailing_moe_v3_kimi_delta_attention]
396 +
397 + @property
398 + def mamba_type(self) -> MambaAttentionBackendEnum:
399 + return MambaAttentionBackendEnum.GDN_ATTN
400 +
401 + def get_state_dtype(
402 + self,
403 + ) -> tuple[torch.dtype, torch.dtype, torch.dtype, torch.dtype]:
404 + if self.model_config is None or self.cache_config is None:
405 + raise ValueError("model_config and cache_config must be set")
406 + return MambaStateDtypeCalculator.kda_state_dtype(
407 + self.model_config.dtype, self.cache_config.mamba_cache_dtype
408 + )
409 +
410 + def get_state_shape(
411 + self,
412 + ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
413 + return MambaStateShapeCalculator.kda_state_shape(
414 + self.tp_size,
415 + self.num_heads,
416 + self.head_dim,
417 + conv_kernel_size=self.conv_size,
418 + num_spec=self.num_speculative_tokens,
419 + )
420 +
421 + def __init__(
422 + self,
423 + config: PretrainedConfig,
424 + quant_config: QuantizationConfig | None = None,
425 + layer_id: int = 0,
426 + prefix: str = "attention",
427 + model_config: ModelConfig | None = None,
428 + cache_config: CacheConfig | None = None,
429 + num_speculative_tokens: int = 0,
430 + ) -> None:
431 + super().__init__()
432 + self.tp_size = get_tensor_model_parallel_world_size()
433 + self.hidden_size = config.hidden_size
434 + self.head_dim = config.head_dim
435 + self.num_heads = config.num_attention_heads
436 + self.local_num_heads = self.num_heads // self.tp_size
437 + self.conv_size = config.short_conv_kernel_size
438 + self.layer_id = layer_id
439 + self.prefix = prefix
440 + self.model_config = model_config
441 + self.cache_config = cache_config
442 + self.num_speculative_tokens = num_speculative_tokens
443 + self.safe_gate = getattr(config, "kda_safe_gate", True)
444 + self.lower_bound = getattr(config, "kda_lower_bound", -5.0)
445 + if not getattr(config, "no_kda_lora", False):
446 + raise ValueError("BailingMoeV3 KDA currently expects no_kda_lora=True")
447 +
448 + projection_size = self.head_dim * self.num_heads
449 + self.projection_size_per_partition = projection_size // self.tp_size
450 + self.qkvb_proj = MergedColumnParallelLinear(
451 + self.hidden_size,
452 + [projection_size, projection_size, projection_size, self.num_heads],
453 + bias=False,
454 + quant_config=quant_config,
455 + prefix=f"{prefix}.qkvb_proj",
456 + )
457 + self.f_proj = ColumnParallelLinear(
458 + self.hidden_size,
459 + projection_size,
460 + bias=False,
461 + quant_config=quant_config,
462 + prefix=f"{prefix}.f_proj",
463 + )
464 + self.dt_bias = nn.Parameter(
465 + torch.empty(projection_size // self.tp_size, dtype=torch.float32)
466 + )
467 + set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
468 +
469 + self.q_conv1d = ColumnParallelLinear(
470 + input_size=self.conv_size,
471 + output_size=projection_size,
472 + bias=False,
473 + params_dtype=torch.float32,
474 + prefix=f"{prefix}.q_conv1d",
475 + )
476 + self.k_conv1d = ColumnParallelLinear(
477 + input_size=self.conv_size,
478 + output_size=projection_size,
479 + bias=False,
480 + params_dtype=torch.float32,
481 + prefix=f"{prefix}.k_conv1d",
482 + )
483 + self.v_conv1d = ColumnParallelLinear(
484 + input_size=self.conv_size,
485 + output_size=projection_size,
486 + bias=False,
487 + params_dtype=torch.float32,
488 + prefix=f"{prefix}.v_conv1d",
489 + )
490 + self.q_conv1d.weight.data = self.q_conv1d.weight.data.unsqueeze(1)
491 + self.k_conv1d.weight.data = self.k_conv1d.weight.data.unsqueeze(1)
492 + self.v_conv1d.weight.data = self.v_conv1d.weight.data.unsqueeze(1)
493 +
494 + self.A_log = nn.Parameter(
495 + torch.empty(1, 1, self.local_num_heads, 1, dtype=torch.float32)
496 + )
497 + set_weight_attrs(self.A_log, {"weight_loader": _load_a_log})
498 +
499 + self.g_proj = ColumnParallelLinear(
500 + self.hidden_size,
501 + projection_size,
502 + bias=False,
503 + quant_config=quant_config,
504 + prefix=f"{prefix}.g_proj",
505 + )
506 + self.o_norm = FusedRMSNormGated(
507 + self.head_dim, eps=config.rms_norm_eps, activation="sigmoid"
508 + )
509 + self.o_proj = RowParallelLinear(
510 + projection_size,
511 + self.hidden_size,
512 + bias=False,
513 + quant_config=quant_config,
514 + prefix=f"{prefix}.o_proj",
515 + )
516 +
517 + compilation_config = get_current_vllm_config().compilation_config
518 + if prefix in compilation_config.static_forward_context:
519 + raise ValueError(f"Duplicate layer name: {prefix}")
520 + compilation_config.static_forward_context[prefix] = self
521 + splitting_op = "vllm::bailing_v3_kda_attention"
522 + if (
523 + compilation_config.splitting_ops is not None
524 + and splitting_op not in compilation_config.splitting_ops
525 + ):
526 + compilation_config.splitting_ops.append(splitting_op)
527 +
528 + def forward(
529 + self,
530 + hidden_states: torch.Tensor,
531 + positions: torch.Tensor,
532 + output: torch.Tensor,
533 + ) -> None:
534 + del positions
535 + num_tokens = hidden_states.size(0)
536 + qkvb = self.qkvb_proj(hidden_states)[0]
537 + q, k, v, beta_logits = qkvb.split(
538 + [
539 + self.projection_size_per_partition,
540 + self.projection_size_per_partition,
541 + self.projection_size_per_partition,
542 + self.local_num_heads,
543 + ],
544 + dim=-1,
545 + )
546 + beta = beta_logits.float().sigmoid().unsqueeze(0)
547 + g1 = self.f_proj(hidden_states)[0]
548 + g1 = fused_kda_gate(
549 + g1,
550 + self.A_log,
551 + self.head_dim,
552 + g_bias=self.dt_bias,
553 + lower_bound=self.lower_bound if self.safe_gate else None,
554 + )
555 + g1 = g1.unsqueeze(0)
556 + g2 = rearrange(
557 + self.g_proj(hidden_states)[0], "... (h d) -> ... h d", d=self.head_dim
558 + )
559 +
560 + core_attn_out = torch.zeros(
561 + (1, num_tokens, self.local_num_heads, self.head_dim),
562 + dtype=hidden_states.dtype,
563 + device=hidden_states.device,
564 + )
565 + torch.ops.vllm.bailing_v3_kda_attention(
566 + q, k, v, g1, beta, core_attn_out, self.prefix
567 + )
568 + core_attn_out = self.o_norm(core_attn_out, g2)
569 + core_attn_out = rearrange(core_attn_out, "1 n h d -> n (h d)")
570 + output[:] = self.o_proj(core_attn_out)[0]
571 +
572 + def _forward(
573 + self,
574 + q_proj_states: torch.Tensor,
575 + k_proj_states: torch.Tensor,
576 + v_proj_states: torch.Tensor,
577 + g1: torch.Tensor,
578 + beta: torch.Tensor,
579 + core_attn_out: torch.Tensor,
580 + ) -> None:
581 + forward_context = get_forward_context()
582 + attn_metadata: AttentionMetadata = forward_context.attn_metadata
583 + if attn_metadata is None:
584 + return
585 +
586 + assert isinstance(attn_metadata, dict)
587 + attn_metadata = attn_metadata[self.prefix]
588 + assert isinstance(attn_metadata, GDNAttentionMetadata)
589 + has_initial_state = attn_metadata.has_initial_state
590 + spec_query_start_loc = attn_metadata.spec_query_start_loc
591 + query_start_loc = attn_metadata.non_spec_query_start_loc
592 + spec_state_indices = attn_metadata.spec_state_indices_tensor
593 + state_indices = attn_metadata.non_spec_state_indices_tensor
594 + spec_sequence_masks = attn_metadata.spec_sequence_masks
595 + spec_token_indx = attn_metadata.spec_token_indx
596 + non_spec_token_indx = attn_metadata.non_spec_token_indx
597 + num_accepted_tokens = attn_metadata.num_accepted_tokens
598 + num_actual_tokens = attn_metadata.num_actual_tokens
599 + conv_state, recurrent_state = self.kv_cache
600 + recurrent_state_active = recurrent_state[..., : self.head_dim]
601 + if not is_conv_state_dim_first():
602 + conv_state = conv_state.transpose(-1, -2)
603 + conv_state_q, conv_state_k, conv_state_v = conv_state.chunk(3, dim=-2)
604 +
605 + q_proj_states = q_proj_states[:num_actual_tokens]
606 + k_proj_states = k_proj_states[:num_actual_tokens]
607 + v_proj_states = v_proj_states[:num_actual_tokens]
608 + g1 = g1[:, :num_actual_tokens]
609 + beta = beta[:, :num_actual_tokens]
610 +
611 + spec_only = (
612 + spec_sequence_masks is not None
613 + and attn_metadata.num_prefills == 0
614 + and attn_metadata.num_decodes == 0
615 + )
616 + if spec_sequence_masks is not None:
617 + assert spec_token_indx is not None
618 + assert non_spec_token_indx is not None
619 + if spec_only:
620 + q_proj_states_spec = q_proj_states
621 + k_proj_states_spec = k_proj_states
622 + v_proj_states_spec = v_proj_states
623 + g1_spec = g1
624 + beta_spec = beta
625 + q_proj_states_non_spec = None
626 + k_proj_states_non_spec = None
627 + v_proj_states_non_spec = None
628 + g1_non_spec = None
629 + beta_non_spec = None
630 + else:
631 + q_proj_states_spec = q_proj_states.index_select(0, spec_token_indx)
632 + k_proj_states_spec = k_proj_states.index_select(0, spec_token_indx)
633 + v_proj_states_spec = v_proj_states.index_select(0, spec_token_indx)
634 + g1_spec = g1.index_select(1, spec_token_indx)
635 + beta_spec = beta.index_select(1, spec_token_indx)
636 + q_proj_states_non_spec = q_proj_states.index_select(
637 + 0, non_spec_token_indx
638 + )
639 + k_proj_states_non_spec = k_proj_states.index_select(
640 + 0, non_spec_token_indx
641 + )
642 + v_proj_states_non_spec = v_proj_states.index_select(
643 + 0, non_spec_token_indx
644 + )
645 + g1_non_spec = g1.index_select(1, non_spec_token_indx)
646 + beta_non_spec = beta.index_select(1, non_spec_token_indx)
647 + else:
648 + q_proj_states_spec = None
649 + k_proj_states_spec = None
650 + v_proj_states_spec = None
651 + g1_spec = None
652 + beta_spec = None
653 + q_proj_states_non_spec = q_proj_states
654 + k_proj_states_non_spec = k_proj_states
655 + v_proj_states_non_spec = v_proj_states
656 + g1_non_spec = g1
657 + beta_non_spec = beta
658 +
659 + q_conv_weights = self.q_conv1d.weight.view(
660 + self.q_conv1d.weight.size(0), self.q_conv1d.weight.size(2)
661 + )
662 + k_conv_weights = self.k_conv1d.weight.view(
663 + self.k_conv1d.weight.size(0), self.k_conv1d.weight.size(2)
664 + )
665 + v_conv_weights = self.v_conv1d.weight.view(
666 + self.v_conv1d.weight.size(0), self.v_conv1d.weight.size(2)
667 + )
668 +
669 + if spec_sequence_masks is not None:
670 + assert q_proj_states_spec is not None
671 + assert k_proj_states_spec is not None
672 + assert v_proj_states_spec is not None
673 + assert spec_state_indices is not None
674 + q_spec = causal_conv1d_update(
675 + q_proj_states_spec,
676 + conv_state_q,
677 + q_conv_weights,
678 + self.q_conv1d.bias,
679 + activation="silu",
680 + conv_state_indices=spec_state_indices[:, 0][
681 + : attn_metadata.num_spec_decodes
682 + ],
683 + num_accepted_tokens=num_accepted_tokens,
684 + query_start_loc=spec_query_start_loc,
685 + max_query_len=spec_state_indices.size(-1),
686 + validate_data=False,
687 + )
688 + k_spec = causal_conv1d_update(
689 + k_proj_states_spec,
690 + conv_state_k,
691 + k_conv_weights,
692 + self.k_conv1d.bias,
693 + activation="silu",
694 + conv_state_indices=spec_state_indices[:, 0][
695 + : attn_metadata.num_spec_decodes
696 + ],
697 + num_accepted_tokens=num_accepted_tokens,
698 + query_start_loc=spec_query_start_loc,
699 + max_query_len=spec_state_indices.size(-1),
700 + validate_data=False,
701 + )
702 + v_spec = causal_conv1d_update(
703 + v_proj_states_spec,
704 + conv_state_v,
705 + v_conv_weights,
706 + self.v_conv1d.bias,
707 + activation="silu",
708 + conv_state_indices=spec_state_indices[:, 0][
709 + : attn_metadata.num_spec_decodes
710 + ],
711 + num_accepted_tokens=num_accepted_tokens,
712 + query_start_loc=spec_query_start_loc,
713 + max_query_len=spec_state_indices.size(-1),
714 + validate_data=False,
715 + )
716 + else:
717 + q_spec = k_spec = v_spec = None
718 +
719 + if attn_metadata.num_prefills > 0:
720 + assert q_proj_states_non_spec is not None
721 + assert k_proj_states_non_spec is not None
722 + assert v_proj_states_non_spec is not None
723 + q = causal_conv1d_fn(
724 + q_proj_states_non_spec.transpose(0, 1),
725 + q_conv_weights,
726 + self.q_conv1d.bias,
727 + activation="silu",
728 + conv_states=conv_state_q,
729 + has_initial_state=has_initial_state,
730 + cache_indices=state_indices,
731 + query_start_loc=query_start_loc,
732 + metadata=attn_metadata,
733 + ).transpose(0, 1)
734 + k = causal_conv1d_fn(
735 + k_proj_states_non_spec.transpose(0, 1),
736 + k_conv_weights,
737 + self.k_conv1d.bias,
738 + activation="silu",
739 + conv_states=conv_state_k,
740 + has_initial_state=has_initial_state,
741 + cache_indices=state_indices,
742 + query_start_loc=query_start_loc,
743 + metadata=attn_metadata,
744 + ).transpose(0, 1)
745 + v = causal_conv1d_fn(
746 + v_proj_states_non_spec.transpose(0, 1),
747 + v_conv_weights,
748 + self.v_conv1d.bias,
749 + activation="silu",
750 + conv_states=conv_state_v,
751 + has_initial_state=has_initial_state,
752 + cache_indices=state_indices,
753 + query_start_loc=query_start_loc,
754 + metadata=attn_metadata,
755 + ).transpose(0, 1)
756 + elif attn_metadata.num_decodes > 0:
757 + assert q_proj_states_non_spec is not None
758 + assert k_proj_states_non_spec is not None
759 + assert v_proj_states_non_spec is not None
760 + assert state_indices is not None
761 + decode_indices = state_indices[:num_actual_tokens]
762 + q = causal_conv1d_update(
763 + q_proj_states_non_spec,
764 + conv_state_q,
765 + q_conv_weights,
766 + self.q_conv1d.bias,
767 + activation="silu",
768 + conv_state_indices=decode_indices,
769 + validate_data=True,
770 + )
771 + k = causal_conv1d_update(
772 + k_proj_states_non_spec,
773 + conv_state_k,
774 + k_conv_weights,
775 + self.k_conv1d.bias,
776 + activation="silu",
777 + conv_state_indices=decode_indices,
778 + validate_data=True,
779 + )
780 + v = causal_conv1d_update(
781 + v_proj_states_non_spec,
782 + conv_state_v,
783 + v_conv_weights,
784 + self.v_conv1d.bias,
785 + activation="silu",
786 + conv_state_indices=decode_indices,
787 + validate_data=True,
788 + )
789 + else:
790 + q = k = v = None
791 +
792 + if q_spec is not None:
793 + q_spec, k_spec, v_spec = map(
794 + lambda x: rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim),
795 + (q_spec, k_spec, v_spec),
796 + )
797 + if q is not None:
798 + q, k, v = map(
799 + lambda x: rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim),
800 + (q, k, v),
801 + )
802 + if spec_sequence_masks is not None:
803 + assert g1_spec is not None
804 + assert beta_spec is not None
805 + assert spec_query_start_loc is not None
806 + assert spec_state_indices is not None
807 + out_spec, _ = fused_recurrent_kda_fwd(
808 + q=q_spec.contiguous(),
809 + k=k_spec.contiguous(),
810 + v=v_spec.contiguous(),
811 + g=g1_spec.contiguous(),
812 + beta=beta_spec.contiguous(),
813 + scale=self.head_dim**-0.5,
814 + initial_state=recurrent_state_active,
815 + inplace_final_state=True,
816 + cu_seqlens=spec_query_start_loc[: attn_metadata.num_spec_decodes + 1],
817 + ssm_state_indices=spec_state_indices,
818 + num_accepted_tokens=num_accepted_tokens,
819 + use_qk_l2norm_in_kernel=True,
820 + )
821 + else:
822 + out_spec = None
823 +
824 + if attn_metadata.num_prefills > 0:
825 + assert q is not None and k is not None and v is not None
826 + assert g1_non_spec is not None and beta_non_spec is not None
827 + assert state_indices is not None
828 + zero_idx = state_indices[~has_initial_state]
829 + recurrent_state[zero_idx] = 0
830 + initial_state = recurrent_state_active[state_indices].contiguous()
831 + out, last_state = chunk_kda(
832 + q=q,
833 + k=k,
834 + v=v,
835 + g=g1_non_spec,
836 + beta=beta_non_spec,
837 + initial_state=initial_state,
838 + output_final_state=True,
839 + use_qk_l2norm_in_kernel=True,
840 + safe_gate=self.safe_gate,
841 + lower_bound=self.lower_bound,
842 + cu_seqlens=query_start_loc,
843 + )
844 + recurrent_state_active[state_indices] = last_state
845 + elif attn_metadata.num_decodes > 0:
846 + assert q is not None and k is not None and v is not None
847 + assert g1_non_spec is not None and beta_non_spec is not None
848 + assert state_indices is not None
849 + out, _ = fused_recurrent_kda(
850 + q=q,
851 + k=k,
852 + v=v,
853 + g=g1_non_spec,
854 + beta=beta_non_spec,
855 + initial_state=recurrent_state_active,
856 + use_qk_l2norm_in_kernel=True,
857 + safe_gate=self.safe_gate,
858 + lower_bound=self.lower_bound,
859 + cu_seqlens=query_start_loc[: attn_metadata.num_decodes + 1],
860 + ssm_state_indices=state_indices,
861 + )
862 + else:
863 + out = None
864 +
865 + if spec_sequence_masks is not None and out is not None:
866 + assert spec_token_indx is not None and non_spec_token_indx is not None
867 + merged_out = torch.empty(
868 + (1, num_actual_tokens, *out_spec.shape[2:]),
869 + dtype=out_spec.dtype,
870 + device=out_spec.device,
871 + )
872 + merged_out.index_copy_(1, spec_token_indx, out_spec)
873 + merged_out.index_copy_(1, non_spec_token_indx, out)
874 + core_attn_out[0, :num_actual_tokens] = merged_out[0, :num_actual_tokens]
875 + elif spec_sequence_masks is not None:
876 + core_attn_out[0, :num_actual_tokens] = out_spec[0, :num_actual_tokens]
877 + else:
878 + core_attn_out[0, :num_actual_tokens] = out[0, :num_actual_tokens]
879 +
880 +
881 +class BailingMoeV3Gate(nn.Module):
882 + def __init__(
883 + self,
884 + config: PretrainedConfig,
885 + params_dtype: torch.dtype | None = None,
886 + ) -> None:
887 + super().__init__()
888 + if params_dtype is None:
889 + params_dtype = torch.float32
890 + self.weight = nn.Parameter(
891 + torch.empty((config.num_experts, config.hidden_size), dtype=params_dtype)
892 + )
893 + self.expert_bias = nn.Parameter(
894 + torch.empty((config.num_experts,), dtype=torch.float32)
895 + )
896 +
897 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
898 + return F.linear(hidden_states.to(self.weight.dtype), self.weight).to(
899 + hidden_states.dtype
900 + )
901 +
902 +
903 +class BailingMoeV3MoE(nn.Module):
904 + def __init__(
905 + self,
906 + config: PretrainedConfig,
907 + quant_config: QuantizationConfig | None = None,
908 + layer_id: int = 0,
909 + prefix: str = "",
910 + ) -> None:
911 + super().__init__()
912 + self.tp_size = get_tensor_model_parallel_world_size()
913 + self.num_experts = config.num_experts
914 + self.top_k = config.num_experts_per_tok
915 + self.hidden_size = config.hidden_size
916 + self.routed_scaling_factor = getattr(config, "routed_scaling_factor", 1.0)
917 + self.gate = BailingMoeV3Gate(config)
918 + self.expert_swiglu_limit = _get_layer_swiglu_limit(
919 + getattr(config, "expert_swiglu_limit_list", None), layer_id
920 + )
921 + self.share_expert_swiglu_limit = _get_layer_swiglu_limit(
922 + getattr(config, "share_expert_swiglu_limit_list", None), layer_id
923 + )
924 + shared_intermediate = (
925 + config.moe_shared_expert_intermediate_size * config.num_shared_experts
926 + )
927 + self.shared_experts = BailingMoeV3MLP(
928 + intermediate_size=shared_intermediate,
929 + config=config,
930 + quant_config=quant_config,
931 + reduce_results=False,
932 + prefix=f"{prefix}.shared_experts",
933 + swiglu_limit=self.share_expert_swiglu_limit,
934 + )
935 + self.experts = FusedMoEFactory(
936 + shared_experts=self.shared_experts,
937 + num_experts=self.num_experts,
938 + top_k=self.top_k,
939 + hidden_size=self.hidden_size,
940 + intermediate_size=config.moe_intermediate_size,
941 + renormalize=True,
942 + quant_config=quant_config,
943 + activation="silu",
944 + swiglu_limit=self.expert_swiglu_limit,
945 + prefix=f"{prefix}.experts",
946 + scoring_func="sigmoid",
947 + routed_scaling_factor=self.routed_scaling_factor,
948 + e_score_correction_bias=self.gate.expert_bias,
949 + num_expert_group=config.n_group,
950 + topk_group=config.topk_group,
951 + use_grouped_topk=True,
952 + router_logits_dtype=torch.float32,
953 + )
954 +
955 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
956 + num_tokens, hidden_size = hidden_states.shape
957 + hidden_states = hidden_states.contiguous().view(-1, hidden_size)
958 + router_logits = self.gate(hidden_states.to(torch.float32))
959 + hidden_states = self.experts(
960 + hidden_states=hidden_states, router_logits=router_logits
961 + )
962 + return hidden_states.view(num_tokens, hidden_size)
963 +
964 +
965 +class BailingMoeV3DecoderLayer(nn.Module):
966 + def __init__(
967 + self,
968 + config: PretrainedConfig,
969 + quant_config: QuantizationConfig | None = None,
970 + layer_id: int = 0,
971 + prefix: str = "layer",
972 + model_config: ModelConfig | None = None,
973 + cache_config: CacheConfig | None = None,
974 + num_speculative_tokens: int = 0,
975 + ) -> None:
976 + super().__init__()
977 + self.layer_id = layer_id
978 + self.hidden_size = config.hidden_size
979 + self.is_kda_layer = _is_kda_layer(
980 + layer_id, config.layer_group_size, config.num_hidden_layers
981 + )
982 + if self.is_kda_layer:
983 + self.self_attn = BailingMoeV3KimiDeltaAttention(
984 + config,
985 + quant_config=quant_config,
986 + layer_id=layer_id,
987 + prefix=f"{prefix}.self_attn",
988 + model_config=model_config,
989 + cache_config=cache_config,
990 + num_speculative_tokens=num_speculative_tokens,
991 + )
992 + else:
993 + self.self_attn = BailingMoeV3MLAAttention(
994 + config,
995 + quant_config=quant_config,
996 + layer_id=layer_id,
997 + prefix=f"{prefix}.self_attn",
998 + cache_config=cache_config,
999 + )
1000 +
1001 + if config.num_experts is not None and layer_id >= config.first_k_dense_replace:
1002 + self.mlp = BailingMoeV3MoE(
1003 + config,
1004 + quant_config=quant_config,
1005 + layer_id=layer_id,
1006 + prefix=f"{prefix}.mlp",
1007 + )
1008 + else:
1009 + self.mlp = BailingMoeV3MLP(
1010 + intermediate_size=config.intermediate_size,
1011 + config=config,
1012 + quant_config=quant_config,
1013 + reduce_results=True,
1014 + prefix=f"{prefix}.mlp",
1015 + )
1016 +
1017 + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
1018 + self.post_attention_layernorm = RMSNorm(
1019 + config.hidden_size, eps=config.rms_norm_eps
1020 + )
1021 +
1022 + def forward(
1023 + self,
1024 + hidden_states: torch.Tensor,
1025 + positions: torch.Tensor,
1026 + residual: torch.Tensor | None = None,
1027 + ) -> tuple[torch.Tensor, torch.Tensor | None]:
1028 + if residual is None:
1029 + residual = hidden_states
1030 + hidden_states = self.input_layernorm(hidden_states)
1031 + else:
1032 + hidden_states, residual = self.input_layernorm(hidden_states, residual)
1033 +
1034 + if self.is_kda_layer:
1035 + attn_output = torch.zeros_like(hidden_states)
1036 + self.self_attn(hidden_states, positions, attn_output)
1037 + else:
1038 + attn_output = self.self_attn(hidden_states, positions)
1039 +
1040 + hidden_states, residual = self.post_attention_layernorm(attn_output, residual)
1041 + hidden_states = self.mlp(hidden_states)
1042 + return hidden_states, residual
1043 +
1044 +
1045 +@support_torch_compile(
1046 + dynamic_arg_dims={
1047 + "input_ids": 0,
1048 + "positions": -1,
1049 + "intermediate_tensors": 0,
1050 + "inputs_embeds": 0,
1051 + }
1052 +)
1053 +class BailingMoeV3Model(nn.Module):
1054 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
1055 + super().__init__()
1056 + config = vllm_config.model_config.hf_config
1057 + model_config = vllm_config.model_config
1058 + quant_config = vllm_config.quant_config
1059 + cache_config = vllm_config.cache_config
1060 + num_speculative_tokens = (
1061 + vllm_config.speculative_config.num_speculative_tokens
1062 + if vllm_config.speculative_config
1063 + else 0
1064 + )
1065 +
1066 + self.config = config
1067 + self.vocab_size = config.vocab_size
1068 + self.embed_dim = config.hidden_size
1069 + self.num_layers = config.num_hidden_layers
1070 +
1071 + if get_pp_group().is_first_rank:
1072 + self.word_embeddings = VocabParallelEmbedding(
1073 + self.vocab_size,
1074 + self.embed_dim,
1075 + org_num_embeddings=self.vocab_size,
1076 + )
1077 + else:
1078 + self.word_embeddings = PPMissingLayer()
1079 +
1080 + def layer_fn(prefix: str):
1081 + layer_idx = int(prefix.split(".")[-1])
1082 + return BailingMoeV3DecoderLayer(
1083 + config=config,
1084 + quant_config=quant_config,
1085 + layer_id=layer_idx,
1086 + prefix=prefix,
1087 + model_config=model_config,
1088 + cache_config=cache_config,
1089 + num_speculative_tokens=num_speculative_tokens,
1090 + )
1091 +
1092 + self.start_layer, self.end_layer, self.layers = make_layers(
1093 + self.num_layers, layer_fn, prefix=f"{prefix}.layers"
1094 + )
1095 +
1096 + if get_pp_group().is_last_rank:
1097 + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
1098 + else:
1099 + self.norm = PPMissingLayer()
1100 +
1101 + @property
1102 + def embed_tokens(self) -> nn.Module:
1103 + return self.word_embeddings
1104 +
1105 + def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1106 + return self.word_embeddings(input_ids)
1107 +
1108 + def forward(
1109 + self,
1110 + input_ids: torch.Tensor | None,
1111 + positions: torch.Tensor,
1112 + intermediate_tensors: IntermediateTensors | None = None,
1113 + inputs_embeds: torch.Tensor | None = None,
1114 + ) -> torch.Tensor | IntermediateTensors:
1115 + if get_pp_group().is_first_rank:
1116 + hidden_states = (
1117 + self.word_embeddings(input_ids)
1118 + if inputs_embeds is None
1119 + else inputs_embeds
1120 + )
1121 + residual = None
1122 + else:
1123 + assert intermediate_tensors is not None
1124 + hidden_states = intermediate_tensors["hidden_states"]
1125 + residual = intermediate_tensors["residual"]
1126 +
1127 + for layer in self.layers[self.start_layer : self.end_layer]:
1128 + hidden_states, residual = layer(hidden_states, positions, residual)
1129 +
1130 + if not get_pp_group().is_last_rank:
1131 + return IntermediateTensors(
1132 + {"hidden_states": hidden_states, "residual": residual}
1133 + )
1134 + if residual is not None:
1135 + hidden_states, _ = self.norm(hidden_states, residual)
1136 + else:
1137 + hidden_states = self.norm(hidden_states)
1138 + return hidden_states
1139 +
1140 + def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1141 + return fused_moe_make_expert_params_mapping(
1142 + self,
1143 + ckpt_gate_proj_name="gate_proj",
1144 + ckpt_down_proj_name="down_proj",
1145 + ckpt_up_proj_name="up_proj",
1146 + num_experts=self.config.num_experts,
1147 + )
1148 +
1149 +
1150 +class BailingMoeV3ForCausalLM(nn.Module, HasInnerState, IsHybrid, SupportsPP):
1151 + packed_modules_mapping = {
1152 + "gate_up_proj": ["gate_proj", "up_proj"],
1153 + "qkvb_proj": ["q_proj", "k_proj", "v_proj", "b_proj"],
1154 + }
1155 +
1156 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
1157 + super().__init__()
1158 + config = vllm_config.model_config.hf_config
1159 + quant_config = vllm_config.quant_config
1160 + self.config = config
1161 + self.quant_config = quant_config
1162 + self.model = BailingMoeV3Model(
1163 + vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
1164 + )
1165 + if get_pp_group().is_last_rank:
1166 + self.lm_head = ParallelLMHead(
1167 + config.vocab_size,
1168 + config.hidden_size,
1169 + quant_config=quant_config,
1170 + prefix=maybe_prefix(prefix, "lm_head"),
1171 + )
1172 + self.logits_processor = LogitsProcessor(config.vocab_size)
1173 + else:
1174 + self.lm_head = PPMissingLayer()
1175 +
1176 + def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1177 + return self.model.embed_input_ids(input_ids)
1178 +
1179 + def forward(
1180 + self,
1181 + input_ids: torch.Tensor | None,
1182 + positions: torch.Tensor,
1183 + intermediate_tensors: IntermediateTensors | None = None,
1184 + inputs_embeds: torch.Tensor | None = None,
1185 + ) -> torch.Tensor | IntermediateTensors:
1186 + return self.model(input_ids, positions, intermediate_tensors, inputs_embeds)
1187 +
1188 + def compute_logits(self, hidden_states: torch.Tensor) -> torch.Tensor:
1189 + return self.logits_processor(self.lm_head, hidden_states)
1190 +
1191 + def make_empty_intermediate_tensors(
1192 + self, batch_size: int, dtype: torch.dtype, device: torch.device
1193 + ) -> IntermediateTensors:
1194 + return IntermediateTensors(
1195 + {
1196 + "hidden_states": torch.zeros(
1197 + (batch_size, self.config.hidden_size), dtype=dtype, device=device
1198 + ),
1199 + "residual": torch.zeros(
1200 + (batch_size, self.config.hidden_size), dtype=dtype, device=device
1201 + ),
1202 + }
1203 + )
1204 +
1205 + @classmethod
1206 + def get_mamba_state_shape_from_config(
1207 + cls, vllm_config: VllmConfig
1208 + ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
1209 + return _get_kda_state_shape_for_config(vllm_config)
1210 +
1211 + @classmethod
1212 + def get_mamba_state_dtype_from_config(
1213 + cls, vllm_config: VllmConfig
1214 + ) -> tuple[torch.dtype, torch.dtype, torch.dtype, torch.dtype]:
1215 + return MambaStateDtypeCalculator.kda_state_dtype(
1216 + vllm_config.model_config.dtype,
1217 + vllm_config.cache_config.mamba_cache_dtype,
1218 + )
1219 +
1220 + @classmethod
1221 + def get_mamba_state_copy_func(cls) -> tuple:
1222 + return MambaStateCopyFuncCalculator.kda_state_copy_func()
1223 +
1224 + def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1225 + return self.model.get_expert_mapping()
1226 +
1227 + def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1228 + params_dict = dict(self.named_parameters(remove_duplicate=False))
1229 + loaded_params: set[str] = set()
1230 + stacked_mappings = [
1231 + (".gate_up_proj", ".gate_proj", 0),
1232 + (".gate_up_proj", ".up_proj", 1),
1233 + (".qkvb_proj", ".q_proj", 0),
1234 + (".qkvb_proj", ".k_proj", 1),
1235 + (".qkvb_proj", ".v_proj", 2),
1236 + (".qkvb_proj", ".b_proj", 3),
1237 + (".fused_qkv_a_proj", ".q_a_proj", 0),
1238 + (".fused_qkv_a_proj", ".kv_a_proj_with_mqa", 1),
1239 + ]
1240 + expert_mappings = list(self.get_expert_mapping())
1241 +
1242 + def load_param(name: str, tensor: torch.Tensor, shard_id=None) -> bool:
1243 + if name not in params_dict or is_pp_missing_parameter(name, self):
1244 + return False
1245 + param = params_dict[name]
1246 + weight_loader = getattr(param, "weight_loader", default_weight_loader)
1247 + if shard_id is None:
1248 + weight_loader(param, tensor)
1249 + elif isinstance(shard_id, int):
1250 + weight_loader(param, tensor, shard_id)
1251 + else:
1252 + weight_loader(
1253 + param, tensor, name, expert_id=shard_id[0], shard_id=shard_id[1]
1254 + )
1255 + loaded_params.add(name)
1256 + return True
1257 +
1258 + def normalize_name(name: str) -> str | None:
1259 + if name.startswith("model.layers."):
1260 + layer_idx = int(name.split("model.layers.")[1].split(".")[0])
1261 + if layer_idx >= self.config.num_hidden_layers:
1262 + return None
1263 + name = name.replace("attention.", "self_attn.")
1264 + return maybe_remap_kv_scale_name(name, params_dict)
1265 +
1266 + for orig_name, weight in weights:
1267 + name = normalize_name(orig_name)
1268 + if name is None:
1269 + continue
1270 + loaded = False
1271 + for param_suf, weight_suf, shard_id in stacked_mappings:
1272 + if weight_suf in name:
1273 + mapped = name.replace(weight_suf, param_suf)
1274 + if load_param(mapped, weight, shard_id):
1275 + loaded = True
1276 + break
1277 + if loaded:
1278 + continue
1279 +
1280 + if ".mlp.experts." in name:
1281 + for param_name, weight_name, expert_id, shard_id in expert_mappings:
1282 + if weight_name in name:
1283 + mapped = name.replace(weight_name, param_name)
1284 + load_param(mapped, weight, (expert_id, shard_id))
1285 + break
1286 + continue
1287 +
1288 + load_param(name, weight)
1289 + return loaded_params
vllm/model_executor/models/bailing_moe_v3_mtp.py new
+389
@@ -0,0 +1,389 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Inference-only Bailing MoE v3 MTP model."""
4 +
5 +from collections.abc import Iterable
6 +
7 +import torch
8 +import torch.nn as nn
9 +from transformers.configuration_utils import PretrainedConfig
10 +
11 +from vllm.compilation.decorators import support_torch_compile
12 +from vllm.config import VllmConfig
13 +from vllm.distributed import get_pp_group
14 +from vllm.model_executor.layers.fused_moe import (
15 + fused_moe_make_expert_params_mapping,
16 +)
17 +from vllm.model_executor.layers.layernorm import RMSNorm
18 +from vllm.model_executor.layers.logits_processor import LogitsProcessor
19 +from vllm.model_executor.layers.vocab_parallel_embedding import (
20 + ParallelLMHead,
21 + VocabParallelEmbedding,
22 +)
23 +from vllm.model_executor.model_loader.weight_utils import (
24 + default_weight_loader,
25 + maybe_remap_kv_scale_name,
26 +)
27 +from vllm.model_executor.models.bailing_moe_v3 import (
28 + BailingMoeV3MLAAttention,
29 + BailingMoeV3MoE,
30 +)
31 +from vllm.model_executor.models.interfaces import SupportsPP
32 +from vllm.model_executor.models.utils import (
33 + PPMissingLayer,
34 + is_pp_missing_parameter,
35 + maybe_prefix,
36 +)
37 +from vllm.sequence import IntermediateTensors
38 +
39 +
40 +def _get_draft_hf_config(vllm_config: VllmConfig) -> PretrainedConfig:
41 + speculative_config = vllm_config.speculative_config
42 + if speculative_config is not None:
43 + draft_model_config = speculative_config.draft_model_config
44 + if draft_model_config is not None:
45 + return draft_model_config.hf_config
46 + return vllm_config.model_config.hf_config
47 +
48 +
49 +class BailingMoeV3MTPSharedHead(nn.Module):
50 + def __init__(
51 + self,
52 + config: PretrainedConfig,
53 + prefix: str,
54 + vllm_config: VllmConfig,
55 + ) -> None:
56 + super().__init__()
57 + self.head = ParallelLMHead(
58 + config.vocab_size,
59 + config.hidden_size,
60 + quant_config=vllm_config.quant_config,
61 + prefix=maybe_prefix(prefix, "head"),
62 + )
63 +
64 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
65 + return hidden_states
66 +
67 +
68 +class BailingMoeV3MultiTokenPredictorLayer(nn.Module):
69 + def __init__(
70 + self,
71 + *,
72 + vllm_config: VllmConfig,
73 + prefix: str,
74 + layer_id: int,
75 + ) -> None:
76 + super().__init__()
77 + config = _get_draft_hf_config(vllm_config)
78 + self.config = config
79 + self.layer_id = layer_id
80 + self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
81 + self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
82 + self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
83 + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
84 + self.self_attn = BailingMoeV3MLAAttention(
85 + config,
86 + quant_config=vllm_config.quant_config,
87 + layer_id=layer_id,
88 + prefix=maybe_prefix(prefix, "self_attn"),
89 + cache_config=vllm_config.cache_config,
90 + )
91 + self.post_attention_layernorm = RMSNorm(
92 + config.hidden_size, eps=config.rms_norm_eps
93 + )
94 + self.mlp = BailingMoeV3MoE(
95 + config,
96 + quant_config=vllm_config.quant_config,
97 + layer_id=layer_id,
98 + prefix=maybe_prefix(prefix, "mlp"),
99 + )
100 + self.final_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
101 + self.shared_head = BailingMoeV3MTPSharedHead(
102 + config,
103 + maybe_prefix(prefix, "shared_head"),
104 + vllm_config,
105 + )
106 +
107 + def forward(
108 + self,
109 + input_ids: torch.Tensor,
110 + positions: torch.Tensor,
111 + previous_hidden_states: torch.Tensor,
112 + inputs_embeds: torch.Tensor | None = None,
113 + spec_step_index: int = 0,
114 + ) -> torch.Tensor:
115 + del input_ids, spec_step_index
116 + assert inputs_embeds is not None
117 + inputs_embeds = self.enorm(inputs_embeds)
118 + previous_hidden_states = self.hnorm(previous_hidden_states)
119 +
120 + hidden_states = self.eh_proj(
121 + torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
122 + )
123 + residual = hidden_states
124 + hidden_states = self.input_layernorm(hidden_states)
125 +
126 + hidden_states = self.self_attn(hidden_states, positions)
127 + hidden_states = residual + hidden_states
128 +
129 + residual = hidden_states
130 + hidden_states = self.post_attention_layernorm(hidden_states)
131 + hidden_states = self.mlp(hidden_states)
132 + hidden_states = residual + hidden_states.to(residual.device)
133 + return self.final_layernorm(hidden_states)
134 +
135 +
136 +class BailingMoeV3MultiTokenPredictor(nn.Module):
137 + def __init__(
138 + self,
139 + *,
140 + vllm_config: VllmConfig,
141 + prefix: str = "",
142 + ) -> None:
143 + super().__init__()
144 + config = _get_draft_hf_config(vllm_config)
145 + self.mtp_start_layer_idx = config.num_hidden_layers
146 + self.num_mtp_layers = config.num_nextn_predict_layers
147 + self.layers = nn.ModuleDict(
148 + {
149 + str(idx): BailingMoeV3MultiTokenPredictorLayer(
150 + vllm_config=vllm_config,
151 + prefix=f"{prefix}.layers.{idx}",
152 + layer_id=idx,
153 + )
154 + for idx in range(
155 + self.mtp_start_layer_idx,
156 + self.mtp_start_layer_idx + self.num_mtp_layers,
157 + )
158 + }
159 + )
160 + if get_pp_group().is_first_rank:
161 + self.embed_tokens = VocabParallelEmbedding(
162 + config.vocab_size,
163 + config.hidden_size,
164 + org_num_embeddings=config.vocab_size,
165 + prefix=maybe_prefix(prefix, "embed_tokens"),
166 + )
167 + else:
168 + self.embed_tokens = PPMissingLayer()
169 + self.logits_processor = LogitsProcessor(config.vocab_size)
170 +
171 + def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
172 + return self.embed_tokens(input_ids)
173 +
174 + def forward(
175 + self,
176 + input_ids: torch.Tensor,
177 + positions: torch.Tensor,
178 + previous_hidden_states: torch.Tensor,
179 + inputs_embeds: torch.Tensor | None = None,
180 + spec_step_idx: int = 0,
181 + ) -> torch.Tensor:
182 + if inputs_embeds is None:
183 + inputs_embeds = self.embed_tokens(input_ids)
184 + current_step_idx = spec_step_idx % self.num_mtp_layers
185 + return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
186 + input_ids,
187 + positions,
188 + previous_hidden_states,
189 + inputs_embeds,
190 + current_step_idx,
191 + )
192 +
193 + def compute_logits(
194 + self,
195 + hidden_states: torch.Tensor,
196 + spec_step_idx: int = 0,
197 + lm_head: nn.Module | None = None,
198 + ) -> torch.Tensor:
199 + current_step_idx = spec_step_idx % self.num_mtp_layers
200 + mtp_layer = self.layers[str(self.mtp_start_layer_idx + current_step_idx)]
201 + head = lm_head if lm_head is not None else mtp_layer.shared_head.head
202 + return self.logits_processor(head, mtp_layer.shared_head(hidden_states))
203 +
204 +
205 +@support_torch_compile
206 +class BailingMoeV3MTPModel(nn.Module, SupportsPP):
207 + packed_modules_mapping = {
208 + "gate_up_proj": ["gate_proj", "up_proj"],
209 + "fused_qkv_a_proj": ["q_a_proj", "kv_a_proj_with_mqa"],
210 + }
211 +
212 + def __init__(
213 + self,
214 + *,
215 + vllm_config: VllmConfig,
216 + prefix: str = "",
217 + ) -> None:
218 + super().__init__()
219 + self.config = _get_draft_hf_config(vllm_config)
220 + self.model = BailingMoeV3MultiTokenPredictor(
221 + vllm_config=vllm_config,
222 + prefix=maybe_prefix(prefix, "model"),
223 + )
224 + self.lm_head: nn.Module | None = None
225 +
226 + def share_lm_head(self, lm_head: nn.Module) -> None:
227 + self.lm_head = lm_head
228 +
229 + def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
230 + return self.model.embed_input_ids(input_ids)
231 +
232 + def forward(
233 + self,
234 + input_ids: torch.Tensor | None,
235 + positions: torch.Tensor,
236 + hidden_states: torch.Tensor,
237 + intermediate_tensors: IntermediateTensors | None = None,
238 + inputs_embeds: torch.Tensor | None = None,
239 + spec_step_idx: int = 0,
240 + ) -> torch.Tensor:
241 + del intermediate_tensors
242 + return self.model(
243 + input_ids,
244 + positions,
245 + hidden_states,
246 + inputs_embeds,
247 + spec_step_idx,
248 + )
249 +
250 + def compute_logits(
251 + self,
252 + hidden_states: torch.Tensor,
253 + spec_step_idx: int = 0,
254 + ) -> torch.Tensor:
255 + return self.model.compute_logits(hidden_states, spec_step_idx, self.lm_head)
256 +
257 + def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
258 + return fused_moe_make_expert_params_mapping(
259 + self,
260 + ckpt_gate_proj_name="gate_proj",
261 + ckpt_down_proj_name="down_proj",
262 + ckpt_up_proj_name="up_proj",
263 + num_experts=self.config.num_experts,
264 + )
265 +
266 + def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
267 + stacked_params_mapping = [
268 + (".fused_qkv_a_proj", ".q_a_proj", 0),
269 + (".fused_qkv_a_proj", ".kv_a_proj_with_mqa", 1),
270 + (".gate_up_proj", ".gate_proj", 0),
271 + (".gate_up_proj", ".up_proj", 1),
272 + ]
273 + expert_params_mapping = list(self.get_expert_mapping())
274 + params_dict = dict(self.named_parameters(remove_duplicate=False))
275 + loaded_params: set[str] = set()
276 + loaded_mtp_layers: set[int] = set()
277 +
278 + def load_param(
279 + name: str,
280 + loaded_weight: torch.Tensor,
281 + shard_id=None,
282 + ) -> bool:
283 + name = maybe_remap_kv_scale_name(name, params_dict)
284 + if name is None:
285 + return False
286 + if name not in params_dict or is_pp_missing_parameter(name, self):
287 + return False
288 +
289 + param = params_dict[name]
290 + weight_loader = getattr(param, "weight_loader", default_weight_loader)
291 + if shard_id is None:
292 + weight_loader(param, loaded_weight)
293 + elif isinstance(shard_id, int):
294 + weight_loader(param, loaded_weight, shard_id)
295 + else:
296 + weight_loader(
297 + param,
298 + loaded_weight,
299 + name,
300 + expert_id=shard_id[0],
301 + shard_id=shard_id[1],
302 + )
303 + loaded_params.add(name)
304 + return True
305 +
306 + def get_spec_layer_idx(name: str) -> int | None:
307 + if not name.startswith("model.layers."):
308 + return None
309 + try:
310 + layer_idx = int(name.split("model.layers.", 1)[1].split(".", 1)[0])
311 + except (IndexError, ValueError):
312 + return None
313 + mtp_idx = layer_idx - self.config.num_hidden_layers
314 + if 0 <= mtp_idx < self.config.num_nextn_predict_layers:
315 + return layer_idx
316 + return None
317 +
318 + def normalize_name(name: str) -> str:
319 + name = name.replace(".attention.", ".self_attn.")
320 + return name.replace(
321 + "mlp.gate.e_score_correction_bias",
322 + "mlp.gate.expert_bias",
323 + )
324 +
325 + def load_lm_head(loaded_weight: torch.Tensor) -> None:
326 + for layer_idx in range(
327 + self.model.mtp_start_layer_idx,
328 + self.model.mtp_start_layer_idx + self.model.num_mtp_layers,
329 + ):
330 + name = f"model.layers.{layer_idx}.shared_head.head.weight"
331 + load_param(name, loaded_weight)
332 +
333 + for name, loaded_weight in weights:
334 + if "rotary_emb.inv_freq" in name:
335 + continue
336 +
337 + if name == "model.word_embeddings.weight":
338 + load_param("model.embed_tokens.weight", loaded_weight)
339 + continue
340 + if name == "lm_head.weight":
341 + load_lm_head(loaded_weight)
342 + continue
343 +
344 + spec_layer = get_spec_layer_idx(name)
345 + if spec_layer is None:
346 + continue
347 + name = normalize_name(name)
348 +
349 + loaded = False
350 + for param_name, weight_name, shard_id in stacked_params_mapping:
351 + if weight_name not in name:
352 + continue
353 + if "mlp.experts." in name and name not in params_dict:
354 + continue
355 + mapped_name = name.replace(weight_name, param_name)
356 + if load_param(mapped_name, loaded_weight, shard_id):
357 + loaded = True
358 + break
359 + if loaded:
360 + loaded_mtp_layers.add(spec_layer)
361 + continue
362 +
363 + if "mlp.experts" in name:
364 + for mapping in expert_params_mapping:
365 + param_name, weight_name, expert_id, shard_id = mapping
366 + if weight_name not in name:
367 + continue
368 + mapped_name = name.replace(weight_name, param_name)
369 + if load_param(mapped_name, loaded_weight, (expert_id, shard_id)):
370 + loaded = True
371 + break
372 + if loaded:
373 + loaded_mtp_layers.add(spec_layer)
374 + continue
375 +
376 + if load_param(name, loaded_weight):
377 + loaded_mtp_layers.add(spec_layer)
378 +
379 + for layer_idx in range(
380 + self.model.mtp_start_layer_idx,
381 + self.model.mtp_start_layer_idx + self.model.num_mtp_layers,
382 + ):
383 + if layer_idx not in loaded_mtp_layers:
384 + raise ValueError(
385 + f"Bailing V3 MTP speculative decoding layer {layer_idx} "
386 + "weights are missing from checkpoint. Use a checkpoint that "
387 + "includes MTP layer weights, or disable speculative decoding."
388 + )
389 + return loaded_params
vllm/model_executor/models/registry.py
+2
@@ -79,6 +79,7 @@ _TEXT_GENERATION_MODELS = {
79 "BailingMoeForCausalLM": ("bailing_moe", "BailingMoeForCausalLM"),
80 "BailingMoeV2ForCausalLM": ("bailing_moe", "BailingMoeV2ForCausalLM"),
81 "BailingMoeV2_5ForCausalLM": ("bailing_moe_linear", "BailingMoeV25ForCausalLM"),
82 + "BailingMoeV3ForCausalLM": ("bailing_moe_v3", "BailingMoeV3ForCausalLM"),
83 "BloomForCausalLM": ("bloom", "BloomForCausalLM"),
84 "ChatGLMModel": ("chatglm", "ChatGLMForCausalLM"),
85 "ChatGLMForConditionalGeneration": ("chatglm", "ChatGLMForCausalLM"),
@@ -641,6 +642,7 @@ _SPECULATIVE_DECODING_MODELS = {
642 "EagleDeepSeekMTPModel": ("deepseek_eagle", "EagleDeepseekV3ForCausalLM"),
643 "DeepSeekMTPModel": ("deepseek_mtp", "DeepSeekMTP"),
644 "DeepSeekV4MTPModel": ("vllm.models.deepseek_v4", "DeepSeekV4MTP"),
645 + "BailingMoeV3MTPModel": ("bailing_moe_v3_mtp", "BailingMoeV3MTPModel"),
646 "MiniMaxM3MTP": ("vllm.models.minimax_m3", "MiniMaxM3MTP"),
647 "BailingMoeV25MTPModel": ("bailing_moe_mtp", "BailingMoeV25MTPModel"),
648 "InklingMTPModel": ("vllm.models.inkling", "InklingMTP"),
vllm/parser/ling3.py new
+83
@@ -0,0 +1,83 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Ling3 parser for reasoning and tool calls.
4 +
5 +Ling3 uses the same XML tool-call format as GLM-4.7 and defaults thinking on.
6 +It follows the GLM-style ``<think>`` / ``</think>`` reasoning format and
7 +treats ``<tool_call>`` as an implicit reasoning terminator.
8 +"""
9 +
10 +from __future__ import annotations
11 +
12 +from dataclasses import replace
13 +from typing import TYPE_CHECKING
14 +
15 +from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
16 +from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
17 +from vllm.parser.engine.adapters import make_adapters
18 +from vllm.parser.engine.parser_engine import ParserEngine
19 +from vllm.parser.glm47_moe import (
20 + THINK_END,
21 + THINK_START,
22 + Glm47MoeParser,
23 + glm47_moe_config,
24 +)
25 +
26 +if TYPE_CHECKING:
27 + from vllm.tokenizers import TokenizerLike
28 + from vllm.tool_parsers.abstract_tool_parser import Tool
29 +
30 +
31 +class Ling3Parser(Glm47MoeParser):
32 + """Ling3 parser backed by the GLM XML parser engine."""
33 +
34 + def __init__(
35 + self,
36 + tokenizer: TokenizerLike,
37 + tools: list[Tool] | None = None,
38 + **kwargs,
39 + ) -> None:
40 + chat_kwargs = kwargs.get("chat_template_kwargs", {}) or {}
41 + thinking = chat_kwargs.get("thinking", None)
42 + enable_thinking = chat_kwargs.get("enable_thinking", None)
43 + self.thinking_enabled = (
44 + True
45 + if thinking is None and enable_thinking is None
46 + else bool(thinking) or bool(enable_thinking)
47 + )
48 + parser_config = replace(
49 + glm47_moe_config(thinking=self.thinking_enabled),
50 + name="ling3",
51 + )
52 + kwargs.setdefault(
53 + "parser_engine_config",
54 + parser_config,
55 + )
56 + ParserEngine.__init__(self, tokenizer, tools, **kwargs)
57 +
58 + @property
59 + def reasoning_start_str(self) -> str:
60 + return THINK_START
61 +
62 + @property
63 + def reasoning_end_str(self) -> str:
64 + return THINK_END
65 +
66 + def extract_reasoning(
67 + self,
68 + model_output: str,
69 + request: ChatCompletionRequest | ResponsesRequest,
70 + ) -> tuple[str | None, str | None]:
71 + if not self.thinking_enabled:
72 + return None, model_output
73 +
74 + reasoning, content = super().extract_reasoning(model_output, request)
75 + if reasoning and not content and "<tool_call>" not in model_output:
76 + return None, reasoning
77 + return reasoning, content
78 +
79 +
80 +(
81 + Ling3ParserReasoningAdapter,
82 + Ling3ParserToolAdapter,
83 +) = make_adapters(Ling3Parser)
vllm/reasoning/__init__.py
+4
@@ -60,6 +60,10 @@ _REASONING_PARSERS_TO_REGISTER = {
60 "glm47_moe_reasoning_parser",
61 "Glm47MoeParserReasoningAdapter",
62 ),
63 + "ling3": (
64 + "ling3_reasoning_parser",
65 + "Ling3ParserReasoningAdapter",
66 + ),
67 "openai_gptoss": (
68 "gptoss_reasoning_parser",
69 "GptOssReasoningParser",
vllm/reasoning/ling3_reasoning_parser.py new
+6
@@ -0,0 +1,6 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from vllm.parser.ling3 import Ling3ParserReasoningAdapter
5 +
6 +__all__ = ["Ling3ParserReasoningAdapter"]
vllm/tool_parsers/__init__.py
+4
@@ -58,6 +58,10 @@ _TOOL_PARSERS_TO_REGISTER = {
58 "glm47_moe_tool_parser",
59 "Glm47MoeModelToolParser",
60 ),
61 + "ling3": (
62 + "ling3_tool_parser",
63 + "Ling3ToolParser",
64 + ),
65 "granite-20b-fc": (
66 "granite_20b_fc_tool_parser",
67 "Granite20bFCToolParser",
vllm/tool_parsers/ling3_tool_parser.py new
+11
@@ -0,0 +1,11 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from __future__ import annotations
5 +
6 +from vllm.parser.ling3 import Ling3ParserToolAdapter
7 +
8 +
9 +class Ling3ToolParser(Ling3ParserToolAdapter): # type: ignore[valid-type, misc]
10 + supports_required_and_named = False
11 + structural_tag_model = "glm_4_7"
vllm/transformers_utils/model_arch_config_convertor.py
+8
@@ -289,6 +289,7 @@ class ModelArchConfigConvertorBase:
289 "pangu_ultra_moe_mtp",
290 "bailing_hybrid",
291 "bailing_hybrid_mtp",
292 + "bailing_hybrid_v3_mtp",
293 ):
294 # check is deepseek_v4 model
295 if hasattr(self.hf_text_config, "compress_ratios"):
@@ -571,6 +572,12 @@ class BailingHybridMTPModelArchConfigConvertor(ModelArchConfigConvertorBase):
572 return getattr(self.hf_text_config, "num_nextn_predict_layers", 0)
573
574
575 +class BailingHybridV3MTPModelArchConfigConvertor(
576 + BailingHybridMTPModelArchConfigConvertor
577 +):
578 + pass
579 +
580 +
581 class Qwen3_5MTPModelArchConfigConvertor(ModelArchConfigConvertorBase):
582 def get_num_hidden_layers(self) -> int:
583 return getattr(self.hf_text_config, "mtp_num_hidden_layers", 0)
@@ -693,6 +700,7 @@ MODEL_ARCH_CONFIG_CONVERTORS = {
700 "moss_audio": MossAudioModelArchConfigConvertor,
701 "mpt": MPTModelArchConfigConvertor,
702 "nemotron-nas": NemotronNasModelArchConfigConvertor,
703 + "bailing_hybrid_v3_mtp": BailingHybridV3MTPModelArchConfigConvertor,
704 "pangu_ultra_moe_mtp": PanguUltraMoeMTPModelArchConfigConvertor,
705 "qwen3_5_mtp": Qwen3_5MTPModelArchConfigConvertor,
706 "qwen3_next_mtp": Qwen3NextMTPModelArchConfigConvertor,