[Model][Frontend] Add Ling 3.0 Flash BF16, MTP, and parser support (#51045)
Signed-off-by: zexplorerhj <zhjoneson@163.com>
This commit is contained in:
@@ -777,6 +777,7 @@ def get_model_params(config):
|
||||
intermediate_size = config.moe_intermediate_size
|
||||
hidden_size = config.hidden_size
|
||||
elif architecture in (
|
||||
"BailingMoeV3ForCausalLM",
|
||||
"Qwen2MoeForCausalLM",
|
||||
"Qwen3MoeForCausalLM",
|
||||
"Qwen3NextForCausalLM",
|
||||
|
||||
@@ -348,6 +348,7 @@ th {
|
||||
| `BailingMoeForCausalLM` | Ling | `inclusionAI/Ling-lite-1.5`, `inclusionAI/Ling-plus`, etc. | ✅︎ | ✅︎ |
|
||||
| `BailingMoeV2ForCausalLM` | Ling | `inclusionAI/Ling-mini-2.0`, etc. | ✅︎ | ✅︎ |
|
||||
| `BailingMoeV2_5ForCausalLM` | Ling | `inclusionAI/Ling-2.5-1T`, `inclusionAI/Ring-2.5-1T` | | ✅︎ |
|
||||
| `BailingMoeV3ForCausalLM` | Ling | `inclusionAI/Ling-3.0-flash` | | ✅︎ |
|
||||
| `BloomForCausalLM` | BLOOM, BLOOMZ, BLOOMChat | `bigscience/bloom`, `bigscience/bloomz`, etc. | | ✅︎ |
|
||||
| `ChatGLMModel`, `ChatGLMForConditionalGeneration` | ChatGLM | `zai-org/chatglm2-6b`, `zai-org/chatglm3-6b`, `thu-coai/ShieldLM-6B-chatglm3`, etc. | ✅︎ | ✅︎ |
|
||||
| `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. | ✅︎ | ✅︎ |
|
||||
|
||||
@@ -213,6 +213,9 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
|
||||
"BailingMoeV2_5ForCausalLM": _HfExamplesInfo(
|
||||
"inclusionAI/Ring-2.5-1T", trust_remote_code=True
|
||||
),
|
||||
"BailingMoeV3ForCausalLM": _HfExamplesInfo(
|
||||
"inclusionAI/Ling-3.0-flash", trust_remote_code=True
|
||||
),
|
||||
"BloomForCausalLM": _HfExamplesInfo(
|
||||
"bigscience/bloom-560m", {"1b": "bigscience/bloomz-1b1"}
|
||||
),
|
||||
@@ -1614,6 +1617,11 @@ _SPECULATIVE_DECODING_EXAMPLE_MODELS = {
|
||||
trust_remote_code=True,
|
||||
is_available_online=False,
|
||||
),
|
||||
"BailingMoeV3MTPModel": _HfExamplesInfo(
|
||||
"inclusionAI/Ling-3.0-flash",
|
||||
speculative_model="inclusionAI/Ling-3.0-flash",
|
||||
trust_remote_code=True,
|
||||
),
|
||||
"DeepSeekMTPModel": _HfExamplesInfo(
|
||||
"luccafong/deepseek_mtp_main_random",
|
||||
speculative_model="luccafong/deepseek_mtp_draft_random",
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
import json
|
||||
|
||||
from tests.parser.engine.replay_harness import MockTokenizer, _test_request
|
||||
from vllm.reasoning import ReasoningParserManager
|
||||
from vllm.tool_parsers import ToolParserManager
|
||||
|
||||
|
||||
def _tokenizer() -> MockTokenizer:
|
||||
vocab = {
|
||||
"<think>": 1,
|
||||
"</think>": 2,
|
||||
"<tool_call>": 3,
|
||||
"</tool_call>": 4,
|
||||
"<arg_key>": 5,
|
||||
"</arg_key>": 6,
|
||||
"<arg_value>": 7,
|
||||
"</arg_value>": 8,
|
||||
}
|
||||
return MockTokenizer(vocab=vocab, tokens=[])
|
||||
|
||||
|
||||
def test_ling3_registered():
|
||||
assert ReasoningParserManager.get_reasoning_parser("ling3") is not None
|
||||
assert ToolParserManager.get_tool_parser("ling3") is not None
|
||||
|
||||
|
||||
def test_ling3_defaults_thinking_on():
|
||||
parser_cls = ReasoningParserManager.get_reasoning_parser("ling3")
|
||||
parser = parser_cls(_tokenizer())
|
||||
|
||||
assert parser.reasoning_start_str == "<think>"
|
||||
assert parser.reasoning_end_str == "</think>"
|
||||
|
||||
reasoning, content = parser.extract_reasoning(
|
||||
"<think>reason</think>answer", _test_request()
|
||||
)
|
||||
|
||||
assert reasoning == "reason"
|
||||
assert content == "answer"
|
||||
|
||||
|
||||
def test_ling3_disable_thinking_keeps_reasoning_as_content():
|
||||
parser_cls = ReasoningParserManager.get_reasoning_parser("ling3")
|
||||
parser = parser_cls(
|
||||
_tokenizer(),
|
||||
chat_template_kwargs={"enable_thinking": False},
|
||||
)
|
||||
|
||||
reasoning, content = parser.extract_reasoning(
|
||||
"<think>reason</think>answer", _test_request()
|
||||
)
|
||||
|
||||
assert reasoning is None
|
||||
assert content == "<think>reason</think>answer"
|
||||
|
||||
|
||||
def test_ling3_enable_thinking_keeps_open_reasoning_as_content():
|
||||
parser_cls = ReasoningParserManager.get_reasoning_parser("ling3")
|
||||
parser = parser_cls(
|
||||
_tokenizer(),
|
||||
chat_template_kwargs={"enable_thinking": True},
|
||||
)
|
||||
|
||||
reasoning, content = parser.extract_reasoning(
|
||||
"<think>only reasoning", _test_request()
|
||||
)
|
||||
|
||||
assert reasoning is None
|
||||
assert content == "only reasoning"
|
||||
|
||||
|
||||
def test_ling3_tool_call_without_newline():
|
||||
parser_cls = ToolParserManager.get_tool_parser("ling3")
|
||||
parser = parser_cls(_tokenizer())
|
||||
|
||||
result = parser.extract_tool_calls(
|
||||
"<tool_call>get_weather<arg_key>city</arg_key>"
|
||||
"<arg_value>Beijing</arg_value></tool_call>",
|
||||
request=_test_request(),
|
||||
)
|
||||
|
||||
assert result.tools_called
|
||||
assert result.content is None
|
||||
assert result.tool_calls[0].function.name == "get_weather"
|
||||
assert json.loads(result.tool_calls[0].function.arguments) == {
|
||||
"city": "Beijing",
|
||||
}
|
||||
|
||||
|
||||
def test_ling3_tool_call_without_args():
|
||||
parser_cls = ToolParserManager.get_tool_parser("ling3")
|
||||
parser = parser_cls(_tokenizer())
|
||||
|
||||
result = parser.extract_tool_calls(
|
||||
"<tool_call>ping</tool_call>",
|
||||
request=_test_request(),
|
||||
)
|
||||
|
||||
assert result.tools_called
|
||||
assert result.content is None
|
||||
assert result.tool_calls[0].function.name == "ping"
|
||||
assert json.loads(result.tool_calls[0].function.arguments) == {}
|
||||
@@ -48,6 +48,7 @@ MTPModelTypes = Literal[
|
||||
"qwen3_next_mtp",
|
||||
"qwen3_5_mtp",
|
||||
"longcat_flash_mtp",
|
||||
"bailing_hybrid_v3_mtp",
|
||||
"minimax_m3_mtp",
|
||||
"bailing_hybrid_mtp",
|
||||
"mtp",
|
||||
@@ -489,7 +490,9 @@ class SpeculativeConfig:
|
||||
)
|
||||
|
||||
architectures = getattr(hf_config, "architectures", []) or []
|
||||
if (
|
||||
if initial_architecture == "BailingMoeV3ForCausalLM":
|
||||
hf_config.model_type = "bailing_hybrid_v3_mtp"
|
||||
elif (
|
||||
hf_config.model_type == "bailing_hybrid"
|
||||
or "BailingMoeV2_5ForCausalLM" in architectures
|
||||
):
|
||||
@@ -502,6 +505,14 @@ class SpeculativeConfig:
|
||||
"architectures": ["BailingMoeV25MTPModel"],
|
||||
}
|
||||
)
|
||||
if hf_config.model_type == "bailing_hybrid_v3_mtp":
|
||||
n_predict = getattr(hf_config, "num_nextn_predict_layers", None)
|
||||
hf_config.update(
|
||||
{
|
||||
"n_predict": n_predict,
|
||||
"architectures": ["BailingMoeV3MTPModel"],
|
||||
}
|
||||
)
|
||||
|
||||
if hf_config.model_type == "exaone_moe":
|
||||
hf_config.model_type = "exaone_moe_mtp"
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"triton_version": "3.6.0",
|
||||
"1": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 32,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 4
|
||||
},
|
||||
"2": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 64,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 4
|
||||
},
|
||||
"4": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 64,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 32,
|
||||
"num_warps": 4,
|
||||
"num_stages": 4
|
||||
},
|
||||
"8": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 64,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 64,
|
||||
"num_warps": 4,
|
||||
"num_stages": 4
|
||||
},
|
||||
"16": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"24": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 64,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 4
|
||||
},
|
||||
"32": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 64,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"48": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 16,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"64": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 16,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"96": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 16,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"128": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 16,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"256": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"512": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 16,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"1024": {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 16,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"1536": {
|
||||
"BLOCK_SIZE_M": 32,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 64,
|
||||
"num_warps": 4,
|
||||
"num_stages": 3
|
||||
},
|
||||
"2048": {
|
||||
"BLOCK_SIZE_M": 64,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 2
|
||||
},
|
||||
"3072": {
|
||||
"BLOCK_SIZE_M": 64,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 2
|
||||
},
|
||||
"4096": {
|
||||
"BLOCK_SIZE_M": 64,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 1,
|
||||
"num_warps": 4,
|
||||
"num_stages": 2
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,389 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Inference-only Bailing MoE v3 MTP model."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from vllm.compilation.decorators import support_torch_compile
|
||||
from vllm.config import VllmConfig
|
||||
from vllm.distributed import get_pp_group
|
||||
from vllm.model_executor.layers.fused_moe import (
|
||||
fused_moe_make_expert_params_mapping,
|
||||
)
|
||||
from vllm.model_executor.layers.layernorm import RMSNorm
|
||||
from vllm.model_executor.layers.logits_processor import LogitsProcessor
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
||||
ParallelLMHead,
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
from vllm.model_executor.model_loader.weight_utils import (
|
||||
default_weight_loader,
|
||||
maybe_remap_kv_scale_name,
|
||||
)
|
||||
from vllm.model_executor.models.bailing_moe_v3 import (
|
||||
BailingMoeV3MLAAttention,
|
||||
BailingMoeV3MoE,
|
||||
)
|
||||
from vllm.model_executor.models.interfaces import SupportsPP
|
||||
from vllm.model_executor.models.utils import (
|
||||
PPMissingLayer,
|
||||
is_pp_missing_parameter,
|
||||
maybe_prefix,
|
||||
)
|
||||
from vllm.sequence import IntermediateTensors
|
||||
|
||||
|
||||
def _get_draft_hf_config(vllm_config: VllmConfig) -> PretrainedConfig:
|
||||
speculative_config = vllm_config.speculative_config
|
||||
if speculative_config is not None:
|
||||
draft_model_config = speculative_config.draft_model_config
|
||||
if draft_model_config is not None:
|
||||
return draft_model_config.hf_config
|
||||
return vllm_config.model_config.hf_config
|
||||
|
||||
|
||||
class BailingMoeV3MTPSharedHead(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
config: PretrainedConfig,
|
||||
prefix: str,
|
||||
vllm_config: VllmConfig,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.head = ParallelLMHead(
|
||||
config.vocab_size,
|
||||
config.hidden_size,
|
||||
quant_config=vllm_config.quant_config,
|
||||
prefix=maybe_prefix(prefix, "head"),
|
||||
)
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BailingMoeV3MultiTokenPredictorLayer(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
vllm_config: VllmConfig,
|
||||
prefix: str,
|
||||
layer_id: int,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
config = _get_draft_hf_config(vllm_config)
|
||||
self.config = config
|
||||
self.layer_id = layer_id
|
||||
self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
|
||||
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
self.self_attn = BailingMoeV3MLAAttention(
|
||||
config,
|
||||
quant_config=vllm_config.quant_config,
|
||||
layer_id=layer_id,
|
||||
prefix=maybe_prefix(prefix, "self_attn"),
|
||||
cache_config=vllm_config.cache_config,
|
||||
)
|
||||
self.post_attention_layernorm = RMSNorm(
|
||||
config.hidden_size, eps=config.rms_norm_eps
|
||||
)
|
||||
self.mlp = BailingMoeV3MoE(
|
||||
config,
|
||||
quant_config=vllm_config.quant_config,
|
||||
layer_id=layer_id,
|
||||
prefix=maybe_prefix(prefix, "mlp"),
|
||||
)
|
||||
self.final_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
self.shared_head = BailingMoeV3MTPSharedHead(
|
||||
config,
|
||||
maybe_prefix(prefix, "shared_head"),
|
||||
vllm_config,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids: torch.Tensor,
|
||||
positions: torch.Tensor,
|
||||
previous_hidden_states: torch.Tensor,
|
||||
inputs_embeds: torch.Tensor | None = None,
|
||||
spec_step_index: int = 0,
|
||||
) -> torch.Tensor:
|
||||
del input_ids, spec_step_index
|
||||
assert inputs_embeds is not None
|
||||
inputs_embeds = self.enorm(inputs_embeds)
|
||||
previous_hidden_states = self.hnorm(previous_hidden_states)
|
||||
|
||||
hidden_states = self.eh_proj(
|
||||
torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
|
||||
)
|
||||
residual = hidden_states
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
|
||||
hidden_states = self.self_attn(hidden_states, positions)
|
||||
hidden_states = residual + hidden_states
|
||||
|
||||
residual = hidden_states
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
hidden_states = self.mlp(hidden_states)
|
||||
hidden_states = residual + hidden_states.to(residual.device)
|
||||
return self.final_layernorm(hidden_states)
|
||||
|
||||
|
||||
class BailingMoeV3MultiTokenPredictor(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
vllm_config: VllmConfig,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
config = _get_draft_hf_config(vllm_config)
|
||||
self.mtp_start_layer_idx = config.num_hidden_layers
|
||||
self.num_mtp_layers = config.num_nextn_predict_layers
|
||||
self.layers = nn.ModuleDict(
|
||||
{
|
||||
str(idx): BailingMoeV3MultiTokenPredictorLayer(
|
||||
vllm_config=vllm_config,
|
||||
prefix=f"{prefix}.layers.{idx}",
|
||||
layer_id=idx,
|
||||
)
|
||||
for idx in range(
|
||||
self.mtp_start_layer_idx,
|
||||
self.mtp_start_layer_idx + self.num_mtp_layers,
|
||||
)
|
||||
}
|
||||
)
|
||||
if get_pp_group().is_first_rank:
|
||||
self.embed_tokens = VocabParallelEmbedding(
|
||||
config.vocab_size,
|
||||
config.hidden_size,
|
||||
org_num_embeddings=config.vocab_size,
|
||||
prefix=maybe_prefix(prefix, "embed_tokens"),
|
||||
)
|
||||
else:
|
||||
self.embed_tokens = PPMissingLayer()
|
||||
self.logits_processor = LogitsProcessor(config.vocab_size)
|
||||
|
||||
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
|
||||
return self.embed_tokens(input_ids)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids: torch.Tensor,
|
||||
positions: torch.Tensor,
|
||||
previous_hidden_states: torch.Tensor,
|
||||
inputs_embeds: torch.Tensor | None = None,
|
||||
spec_step_idx: int = 0,
|
||||
) -> torch.Tensor:
|
||||
if inputs_embeds is None:
|
||||
inputs_embeds = self.embed_tokens(input_ids)
|
||||
current_step_idx = spec_step_idx % self.num_mtp_layers
|
||||
return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
|
||||
input_ids,
|
||||
positions,
|
||||
previous_hidden_states,
|
||||
inputs_embeds,
|
||||
current_step_idx,
|
||||
)
|
||||
|
||||
def compute_logits(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
spec_step_idx: int = 0,
|
||||
lm_head: nn.Module | None = None,
|
||||
) -> torch.Tensor:
|
||||
current_step_idx = spec_step_idx % self.num_mtp_layers
|
||||
mtp_layer = self.layers[str(self.mtp_start_layer_idx + current_step_idx)]
|
||||
head = lm_head if lm_head is not None else mtp_layer.shared_head.head
|
||||
return self.logits_processor(head, mtp_layer.shared_head(hidden_states))
|
||||
|
||||
|
||||
@support_torch_compile
|
||||
class BailingMoeV3MTPModel(nn.Module, SupportsPP):
|
||||
packed_modules_mapping = {
|
||||
"gate_up_proj": ["gate_proj", "up_proj"],
|
||||
"fused_qkv_a_proj": ["q_a_proj", "kv_a_proj_with_mqa"],
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
vllm_config: VllmConfig,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.config = _get_draft_hf_config(vllm_config)
|
||||
self.model = BailingMoeV3MultiTokenPredictor(
|
||||
vllm_config=vllm_config,
|
||||
prefix=maybe_prefix(prefix, "model"),
|
||||
)
|
||||
self.lm_head: nn.Module | None = None
|
||||
|
||||
def share_lm_head(self, lm_head: nn.Module) -> None:
|
||||
self.lm_head = lm_head
|
||||
|
||||
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
|
||||
return self.model.embed_input_ids(input_ids)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids: torch.Tensor | None,
|
||||
positions: torch.Tensor,
|
||||
hidden_states: torch.Tensor,
|
||||
intermediate_tensors: IntermediateTensors | None = None,
|
||||
inputs_embeds: torch.Tensor | None = None,
|
||||
spec_step_idx: int = 0,
|
||||
) -> torch.Tensor:
|
||||
del intermediate_tensors
|
||||
return self.model(
|
||||
input_ids,
|
||||
positions,
|
||||
hidden_states,
|
||||
inputs_embeds,
|
||||
spec_step_idx,
|
||||
)
|
||||
|
||||
def compute_logits(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
spec_step_idx: int = 0,
|
||||
) -> torch.Tensor:
|
||||
return self.model.compute_logits(hidden_states, spec_step_idx, self.lm_head)
|
||||
|
||||
def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
|
||||
return fused_moe_make_expert_params_mapping(
|
||||
self,
|
||||
ckpt_gate_proj_name="gate_proj",
|
||||
ckpt_down_proj_name="down_proj",
|
||||
ckpt_up_proj_name="up_proj",
|
||||
num_experts=self.config.num_experts,
|
||||
)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
stacked_params_mapping = [
|
||||
(".fused_qkv_a_proj", ".q_a_proj", 0),
|
||||
(".fused_qkv_a_proj", ".kv_a_proj_with_mqa", 1),
|
||||
(".gate_up_proj", ".gate_proj", 0),
|
||||
(".gate_up_proj", ".up_proj", 1),
|
||||
]
|
||||
expert_params_mapping = list(self.get_expert_mapping())
|
||||
params_dict = dict(self.named_parameters(remove_duplicate=False))
|
||||
loaded_params: set[str] = set()
|
||||
loaded_mtp_layers: set[int] = set()
|
||||
|
||||
def load_param(
|
||||
name: str,
|
||||
loaded_weight: torch.Tensor,
|
||||
shard_id=None,
|
||||
) -> bool:
|
||||
name = maybe_remap_kv_scale_name(name, params_dict)
|
||||
if name is None:
|
||||
return False
|
||||
if name not in params_dict or is_pp_missing_parameter(name, self):
|
||||
return False
|
||||
|
||||
param = params_dict[name]
|
||||
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
||||
if shard_id is None:
|
||||
weight_loader(param, loaded_weight)
|
||||
elif isinstance(shard_id, int):
|
||||
weight_loader(param, loaded_weight, shard_id)
|
||||
else:
|
||||
weight_loader(
|
||||
param,
|
||||
loaded_weight,
|
||||
name,
|
||||
expert_id=shard_id[0],
|
||||
shard_id=shard_id[1],
|
||||
)
|
||||
loaded_params.add(name)
|
||||
return True
|
||||
|
||||
def get_spec_layer_idx(name: str) -> int | None:
|
||||
if not name.startswith("model.layers."):
|
||||
return None
|
||||
try:
|
||||
layer_idx = int(name.split("model.layers.", 1)[1].split(".", 1)[0])
|
||||
except (IndexError, ValueError):
|
||||
return None
|
||||
mtp_idx = layer_idx - self.config.num_hidden_layers
|
||||
if 0 <= mtp_idx < self.config.num_nextn_predict_layers:
|
||||
return layer_idx
|
||||
return None
|
||||
|
||||
def normalize_name(name: str) -> str:
|
||||
name = name.replace(".attention.", ".self_attn.")
|
||||
return name.replace(
|
||||
"mlp.gate.e_score_correction_bias",
|
||||
"mlp.gate.expert_bias",
|
||||
)
|
||||
|
||||
def load_lm_head(loaded_weight: torch.Tensor) -> None:
|
||||
for layer_idx in range(
|
||||
self.model.mtp_start_layer_idx,
|
||||
self.model.mtp_start_layer_idx + self.model.num_mtp_layers,
|
||||
):
|
||||
name = f"model.layers.{layer_idx}.shared_head.head.weight"
|
||||
load_param(name, loaded_weight)
|
||||
|
||||
for name, loaded_weight in weights:
|
||||
if "rotary_emb.inv_freq" in name:
|
||||
continue
|
||||
|
||||
if name == "model.word_embeddings.weight":
|
||||
load_param("model.embed_tokens.weight", loaded_weight)
|
||||
continue
|
||||
if name == "lm_head.weight":
|
||||
load_lm_head(loaded_weight)
|
||||
continue
|
||||
|
||||
spec_layer = get_spec_layer_idx(name)
|
||||
if spec_layer is None:
|
||||
continue
|
||||
name = normalize_name(name)
|
||||
|
||||
loaded = False
|
||||
for param_name, weight_name, shard_id in stacked_params_mapping:
|
||||
if weight_name not in name:
|
||||
continue
|
||||
if "mlp.experts." in name and name not in params_dict:
|
||||
continue
|
||||
mapped_name = name.replace(weight_name, param_name)
|
||||
if load_param(mapped_name, loaded_weight, shard_id):
|
||||
loaded = True
|
||||
break
|
||||
if loaded:
|
||||
loaded_mtp_layers.add(spec_layer)
|
||||
continue
|
||||
|
||||
if "mlp.experts" in name:
|
||||
for mapping in expert_params_mapping:
|
||||
param_name, weight_name, expert_id, shard_id = mapping
|
||||
if weight_name not in name:
|
||||
continue
|
||||
mapped_name = name.replace(weight_name, param_name)
|
||||
if load_param(mapped_name, loaded_weight, (expert_id, shard_id)):
|
||||
loaded = True
|
||||
break
|
||||
if loaded:
|
||||
loaded_mtp_layers.add(spec_layer)
|
||||
continue
|
||||
|
||||
if load_param(name, loaded_weight):
|
||||
loaded_mtp_layers.add(spec_layer)
|
||||
|
||||
for layer_idx in range(
|
||||
self.model.mtp_start_layer_idx,
|
||||
self.model.mtp_start_layer_idx + self.model.num_mtp_layers,
|
||||
):
|
||||
if layer_idx not in loaded_mtp_layers:
|
||||
raise ValueError(
|
||||
f"Bailing V3 MTP speculative decoding layer {layer_idx} "
|
||||
"weights are missing from checkpoint. Use a checkpoint that "
|
||||
"includes MTP layer weights, or disable speculative decoding."
|
||||
)
|
||||
return loaded_params
|
||||
@@ -79,6 +79,7 @@ _TEXT_GENERATION_MODELS = {
|
||||
"BailingMoeForCausalLM": ("bailing_moe", "BailingMoeForCausalLM"),
|
||||
"BailingMoeV2ForCausalLM": ("bailing_moe", "BailingMoeV2ForCausalLM"),
|
||||
"BailingMoeV2_5ForCausalLM": ("bailing_moe_linear", "BailingMoeV25ForCausalLM"),
|
||||
"BailingMoeV3ForCausalLM": ("bailing_moe_v3", "BailingMoeV3ForCausalLM"),
|
||||
"BloomForCausalLM": ("bloom", "BloomForCausalLM"),
|
||||
"ChatGLMModel": ("chatglm", "ChatGLMForCausalLM"),
|
||||
"ChatGLMForConditionalGeneration": ("chatglm", "ChatGLMForCausalLM"),
|
||||
@@ -641,6 +642,7 @@ _SPECULATIVE_DECODING_MODELS = {
|
||||
"EagleDeepSeekMTPModel": ("deepseek_eagle", "EagleDeepseekV3ForCausalLM"),
|
||||
"DeepSeekMTPModel": ("deepseek_mtp", "DeepSeekMTP"),
|
||||
"DeepSeekV4MTPModel": ("vllm.models.deepseek_v4", "DeepSeekV4MTP"),
|
||||
"BailingMoeV3MTPModel": ("bailing_moe_v3_mtp", "BailingMoeV3MTPModel"),
|
||||
"MiniMaxM3MTP": ("vllm.models.minimax_m3", "MiniMaxM3MTP"),
|
||||
"BailingMoeV25MTPModel": ("bailing_moe_mtp", "BailingMoeV25MTPModel"),
|
||||
"InklingMTPModel": ("vllm.models.inkling", "InklingMTP"),
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Ling3 parser for reasoning and tool calls.
|
||||
|
||||
Ling3 uses the same XML tool-call format as GLM-4.7 and defaults thinking on.
|
||||
It follows the GLM-style ``<think>`` / ``</think>`` reasoning format and
|
||||
treats ``<tool_call>`` as an implicit reasoning terminator.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
|
||||
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
|
||||
from vllm.parser.engine.adapters import make_adapters
|
||||
from vllm.parser.engine.parser_engine import ParserEngine
|
||||
from vllm.parser.glm47_moe import (
|
||||
THINK_END,
|
||||
THINK_START,
|
||||
Glm47MoeParser,
|
||||
glm47_moe_config,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.tokenizers import TokenizerLike
|
||||
from vllm.tool_parsers.abstract_tool_parser import Tool
|
||||
|
||||
|
||||
class Ling3Parser(Glm47MoeParser):
|
||||
"""Ling3 parser backed by the GLM XML parser engine."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer: TokenizerLike,
|
||||
tools: list[Tool] | None = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
chat_kwargs = kwargs.get("chat_template_kwargs", {}) or {}
|
||||
thinking = chat_kwargs.get("thinking", None)
|
||||
enable_thinking = chat_kwargs.get("enable_thinking", None)
|
||||
self.thinking_enabled = (
|
||||
True
|
||||
if thinking is None and enable_thinking is None
|
||||
else bool(thinking) or bool(enable_thinking)
|
||||
)
|
||||
parser_config = replace(
|
||||
glm47_moe_config(thinking=self.thinking_enabled),
|
||||
name="ling3",
|
||||
)
|
||||
kwargs.setdefault(
|
||||
"parser_engine_config",
|
||||
parser_config,
|
||||
)
|
||||
ParserEngine.__init__(self, tokenizer, tools, **kwargs)
|
||||
|
||||
@property
|
||||
def reasoning_start_str(self) -> str:
|
||||
return THINK_START
|
||||
|
||||
@property
|
||||
def reasoning_end_str(self) -> str:
|
||||
return THINK_END
|
||||
|
||||
def extract_reasoning(
|
||||
self,
|
||||
model_output: str,
|
||||
request: ChatCompletionRequest | ResponsesRequest,
|
||||
) -> tuple[str | None, str | None]:
|
||||
if not self.thinking_enabled:
|
||||
return None, model_output
|
||||
|
||||
reasoning, content = super().extract_reasoning(model_output, request)
|
||||
if reasoning and not content and "<tool_call>" not in model_output:
|
||||
return None, reasoning
|
||||
return reasoning, content
|
||||
|
||||
|
||||
(
|
||||
Ling3ParserReasoningAdapter,
|
||||
Ling3ParserToolAdapter,
|
||||
) = make_adapters(Ling3Parser)
|
||||
@@ -60,6 +60,10 @@ _REASONING_PARSERS_TO_REGISTER = {
|
||||
"glm47_moe_reasoning_parser",
|
||||
"Glm47MoeParserReasoningAdapter",
|
||||
),
|
||||
"ling3": (
|
||||
"ling3_reasoning_parser",
|
||||
"Ling3ParserReasoningAdapter",
|
||||
),
|
||||
"openai_gptoss": (
|
||||
"gptoss_reasoning_parser",
|
||||
"GptOssReasoningParser",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from vllm.parser.ling3 import Ling3ParserReasoningAdapter
|
||||
|
||||
__all__ = ["Ling3ParserReasoningAdapter"]
|
||||
@@ -58,6 +58,10 @@ _TOOL_PARSERS_TO_REGISTER = {
|
||||
"glm47_moe_tool_parser",
|
||||
"Glm47MoeModelToolParser",
|
||||
),
|
||||
"ling3": (
|
||||
"ling3_tool_parser",
|
||||
"Ling3ToolParser",
|
||||
),
|
||||
"granite-20b-fc": (
|
||||
"granite_20b_fc_tool_parser",
|
||||
"Granite20bFCToolParser",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from vllm.parser.ling3 import Ling3ParserToolAdapter
|
||||
|
||||
|
||||
class Ling3ToolParser(Ling3ParserToolAdapter): # type: ignore[valid-type, misc]
|
||||
supports_required_and_named = False
|
||||
structural_tag_model = "glm_4_7"
|
||||
@@ -289,6 +289,7 @@ class ModelArchConfigConvertorBase:
|
||||
"pangu_ultra_moe_mtp",
|
||||
"bailing_hybrid",
|
||||
"bailing_hybrid_mtp",
|
||||
"bailing_hybrid_v3_mtp",
|
||||
):
|
||||
# check is deepseek_v4 model
|
||||
if hasattr(self.hf_text_config, "compress_ratios"):
|
||||
@@ -571,6 +572,12 @@ class BailingHybridMTPModelArchConfigConvertor(ModelArchConfigConvertorBase):
|
||||
return getattr(self.hf_text_config, "num_nextn_predict_layers", 0)
|
||||
|
||||
|
||||
class BailingHybridV3MTPModelArchConfigConvertor(
|
||||
BailingHybridMTPModelArchConfigConvertor
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class Qwen3_5MTPModelArchConfigConvertor(ModelArchConfigConvertorBase):
|
||||
def get_num_hidden_layers(self) -> int:
|
||||
return getattr(self.hf_text_config, "mtp_num_hidden_layers", 0)
|
||||
@@ -693,6 +700,7 @@ MODEL_ARCH_CONFIG_CONVERTORS = {
|
||||
"moss_audio": MossAudioModelArchConfigConvertor,
|
||||
"mpt": MPTModelArchConfigConvertor,
|
||||
"nemotron-nas": NemotronNasModelArchConfigConvertor,
|
||||
"bailing_hybrid_v3_mtp": BailingHybridV3MTPModelArchConfigConvertor,
|
||||
"pangu_ultra_moe_mtp": PanguUltraMoeMTPModelArchConfigConvertor,
|
||||
"qwen3_5_mtp": Qwen3_5MTPModelArchConfigConvertor,
|
||||
"qwen3_next_mtp": Qwen3NextMTPModelArchConfigConvertor,
|
||||
|
||||
Reference in New Issue
Block a user