model: support Qwen3.5 (#18489)

Co-authored-by: 瑀澈 <yuche.lz@alibaba-inc.com>
This commit is contained in:
Zheng Li
2026-02-10 00:27:59 +08:00
committed by GitHub
co-authored by 瑀澈
parent 0b4d4f2838
commit 27c447653d
17 changed files with 1923 additions and 9 deletions
@@ -66,6 +66,7 @@ def get_model_config(
"Qwen3MoeForCausalLM", "Qwen3MoeForCausalLM",
"Qwen3NextForCausalLM", "Qwen3NextForCausalLM",
"Qwen3VLMoeForConditionalGeneration", "Qwen3VLMoeForConditionalGeneration",
"Qwen3_5MoeForConditionalGeneration",
]: ]:
E = config.num_experts // ep_size E = config.num_experts // ep_size
topk = config.num_experts_per_tok topk = config.num_experts_per_tok
+3
View File
@@ -18,6 +18,7 @@ from sglang.srt.configs.longcat_flash import LongcatFlashConfig
from sglang.srt.configs.nano_nemotron_vl import NemotronH_Nano_VL_V2_Config from sglang.srt.configs.nano_nemotron_vl import NemotronH_Nano_VL_V2_Config
from sglang.srt.configs.nemotron_h import NemotronHConfig from sglang.srt.configs.nemotron_h import NemotronHConfig
from sglang.srt.configs.olmo3 import Olmo3Config from sglang.srt.configs.olmo3 import Olmo3Config
from sglang.srt.configs.qwen3_5 import Qwen3_5Config, Qwen3_5MoeConfig
from sglang.srt.configs.qwen3_next import Qwen3NextConfig from sglang.srt.configs.qwen3_next import Qwen3NextConfig
from sglang.srt.configs.step3_vl import ( from sglang.srt.configs.step3_vl import (
Step3TextConfig, Step3TextConfig,
@@ -43,6 +44,8 @@ __all__ = [
"KimiLinearConfig", "KimiLinearConfig",
"KimiK25Config", "KimiK25Config",
"Qwen3NextConfig", "Qwen3NextConfig",
"Qwen3_5Config",
"Qwen3_5MoeConfig",
"DotsVLMConfig", "DotsVLMConfig",
"DotsOCRConfig", "DotsOCRConfig",
"FalconH1Config", "FalconH1Config",
@@ -319,6 +319,13 @@ class ModelConfig:
self.hf_config.architectures[0] = "Qwen3NextForCausalLMMTP" self.hf_config.architectures[0] = "Qwen3NextForCausalLMMTP"
self.hf_config.num_nextn_predict_layers = 1 self.hf_config.num_nextn_predict_layers = 1
if is_draft_model and self.hf_config.architectures[0] in [
"Qwen3_5ForConditionalGeneration",
"Qwen3_5MoeForConditionalGeneration",
]:
self.hf_config.architectures[0] = "Qwen3_5ForCausalLMMTP"
self.hf_config.num_nextn_predict_layers = 1
if is_draft_model and self.hf_config.architectures[0] == "ExaoneMoEForCausalLM": if is_draft_model and self.hf_config.architectures[0] == "ExaoneMoEForCausalLM":
self.hf_config.architectures[0] = "ExaoneMoEForCausalLMMTP" self.hf_config.architectures[0] = "ExaoneMoEForCausalLMMTP"
self.hf_config.num_nextn_predict_layers = 1 self.hf_config.num_nextn_predict_layers = 1
@@ -1193,6 +1200,8 @@ multimodal_model_archs = [
"Qwen2_5_VLForConditionalGeneration", "Qwen2_5_VLForConditionalGeneration",
"Qwen3VLForConditionalGeneration", "Qwen3VLForConditionalGeneration",
"Qwen3VLMoeForConditionalGeneration", "Qwen3VLMoeForConditionalGeneration",
"Qwen3_5ForConditionalGeneration",
"Qwen3_5MoeForConditionalGeneration",
"Qwen3OmniMoeForConditionalGeneration", "Qwen3OmniMoeForConditionalGeneration",
"KimiVLForConditionalGeneration", "KimiVLForConditionalGeneration",
"InternVLChatModel", "InternVLChatModel",
+113
View File
@@ -0,0 +1,113 @@
from transformers import PretrainedConfig
from sglang.srt.configs.qwen3_next import Qwen3NextConfig
from sglang.srt.configs.qwen3_vl import Qwen3VLVisionConfig
class Qwen3_5VisionConfig(Qwen3VLVisionConfig):
model_type = "qwen3_5"
base_config_key = "vision_config"
class Qwen3_5TextConfig(Qwen3NextConfig):
model_type = "qwen3_5_text"
base_config_key = "text_config"
def __init__(
self,
**kwargs,
):
super().__init__(**kwargs)
if self.rope_scaling is None:
self.rope_scaling = {}
class Qwen3_5Config(PretrainedConfig):
r"""
This is the configuration class to store the configuration of a [`Qwen3_5Model`]. It is used to instantiate a
Qwen3.5 model according to the specified arguments, defining the model architecture. Instantiating a configuration
with the defaults will yield a similar configuration to that of
Qwen3.5.
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
documentation from [`PretrainedConfig`] for more information.
Args:
text_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3_5TextConfig`):
The config object or dictionary of the text backbone.
vision_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3_5VisionConfig`):
The config object or dictionary of the vision backbone.
image_token_id (`int`, *optional*, defaults to 151655):
The image token index to encode the image prompt.
video_token_id (`int`, *optional*, defaults to 151656):
The video token index to encode the image prompt.
vision_start_token_id (`int`, *optional*, defaults to 151652):
The start token index to encode the image prompt.
vision_end_token_id (`int`, *optional*, defaults to 151653):
The end token index to encode the image prompt.
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
Whether to tie the word embeddings.
```python
>>> from transformers import Qwen3_5ForConditionalGeneration, Qwen3_5Config
>>> # Initializing a Qwen3.5 style configuration
>>> configuration = Qwen3_5Config()
>>> # Initializing a model from the Qwen3.5 style configuration
>>> model = Qwen3_5ForConditionalGeneration(configuration)
>>> # Accessing the model configuration
>>> configuration = model.config
```"""
model_type = "qwen3_5"
sub_configs = {
"vision_config": Qwen3_5VisionConfig,
"text_config": Qwen3_5TextConfig,
}
keys_to_ignore_at_inference = ["past_key_values"]
def __init__(
self,
text_config=None,
vision_config=None,
image_token_id=151655,
video_token_id=151656,
vision_start_token_id=151652,
vision_end_token_id=151653,
tie_word_embeddings=False,
**kwargs,
):
if isinstance(vision_config, dict):
self.vision_config = self.sub_configs["vision_config"](**vision_config)
elif vision_config is None:
self.vision_config = self.sub_configs["vision_config"]()
if isinstance(text_config, dict):
self.text_config = self.sub_configs["text_config"](**text_config)
elif text_config is None:
self.text_config = self.sub_configs["text_config"]()
self.image_token_id = image_token_id
self.video_token_id = video_token_id
self.vision_start_token_id = vision_start_token_id
self.vision_end_token_id = vision_end_token_id
super().__init__(**kwargs, tie_word_embeddings=tie_word_embeddings)
class Qwen3_5MoeVisionConfig(Qwen3_5VisionConfig):
model_type = "qwen3_5_moe"
class Qwen3_5MoeTextConfig(Qwen3_5TextConfig):
model_type = "qwen3_5_moe_text"
class Qwen3_5MoeConfig(Qwen3_5Config):
model_type = "qwen3_5_moe"
sub_configs = {
"vision_config": Qwen3_5MoeVisionConfig,
"text_config": Qwen3_5MoeTextConfig,
}
@@ -104,6 +104,8 @@ class LogitsProcessorOutput:
## Part 5: Customized Info ## Part 5: Customized Info
customized_info: Optional[Dict[str, List[Any]]] = None customized_info: Optional[Dict[str, List[Any]]] = None
mm_input_embeds: Optional[torch.Tensor] = None
@dataclasses.dataclass @dataclasses.dataclass
class LogitsMetadata: class LogitsMetadata:
@@ -146,6 +148,8 @@ class LogitsMetadata:
# Whether this batch is prefill-only (no token generation needed) # Whether this batch is prefill-only (no token generation needed)
is_prefill_only: bool = False is_prefill_only: bool = False
mm_input_embeds: Optional[torch.Tensor] = None
@classmethod @classmethod
def from_forward_batch(cls, forward_batch: ForwardBatch): def from_forward_batch(cls, forward_batch: ForwardBatch):
if ( if (
@@ -196,6 +200,7 @@ class LogitsMetadata:
global_num_tokens_for_logprob_cpu=forward_batch.global_num_tokens_for_logprob_cpu, global_num_tokens_for_logprob_cpu=forward_batch.global_num_tokens_for_logprob_cpu,
global_num_tokens_for_logprob_gpu=forward_batch.global_num_tokens_for_logprob_gpu, global_num_tokens_for_logprob_gpu=forward_batch.global_num_tokens_for_logprob_gpu,
dp_padding_mode=DpPaddingMode.SUM_LEN, dp_padding_mode=DpPaddingMode.SUM_LEN,
mm_input_embeds=forward_batch.mm_input_embeds,
) )
def compute_dp_attention_metadata(self): def compute_dp_attention_metadata(self):
@@ -341,6 +346,7 @@ class LogitsProcessor(nn.Module):
return LogitsProcessorOutput( return LogitsProcessorOutput(
next_token_logits=sampled_logits, next_token_logits=sampled_logits,
hidden_states=hidden_states_to_store, hidden_states=hidden_states_to_store,
mm_input_embeds=logits_metadata.mm_input_embeds,
) )
# Start to process input logprobs # Start to process input logprobs
@@ -386,6 +392,7 @@ class LogitsProcessor(nn.Module):
input_top_logprobs_idx=logprobs_result.input_top_logprobs_idx, input_top_logprobs_idx=logprobs_result.input_top_logprobs_idx,
input_token_ids_logprobs_val=logprobs_result.input_token_ids_logprobs_val, input_token_ids_logprobs_val=logprobs_result.input_token_ids_logprobs_val,
input_token_ids_logprobs_idx=logprobs_result.input_token_ids_logprobs_idx, input_token_ids_logprobs_idx=logprobs_result.input_token_ids_logprobs_idx,
mm_input_embeds=logits_metadata.mm_input_embeds,
) )
def _get_pruned_states( def _get_pruned_states(
@@ -1067,6 +1074,10 @@ class LogitsProcessor(nn.Module):
input_top_logprobs_idx=input_top_logprobs_idx, input_top_logprobs_idx=input_top_logprobs_idx,
input_token_ids_logprobs_val=input_token_ids_logprobs_val, input_token_ids_logprobs_val=input_token_ids_logprobs_val,
input_token_ids_logprobs_idx=input_token_ids_logprobs_idx, input_token_ids_logprobs_idx=input_token_ids_logprobs_idx,
# FIXME: These fields are not logits-related but are passed through here as a
# workaround since ForwardBatch is local to forward_batch_generation().
# They should be moved to GenerationBatchResult to keep this class clean.
mm_input_embeds=logits_metadata.mm_input_embeds,
) )
+5 -1
View File
@@ -1825,7 +1825,9 @@ class MRotaryEmbedding(RotaryEmbedding):
**kwargs, **kwargs,
) )
if ( if (
model_type.startswith("qwen3_vl") or model_type.startswith("qwen3_vl_moe") model_type.startswith("qwen3_vl")
or model_type.startswith("qwen3_vl_moe")
or model_type.startswith("qwen3_5")
) and video_grid_thw is not None: ) and video_grid_thw is not None:
video_grid_thw = torch.repeat_interleave( video_grid_thw = torch.repeat_interleave(
video_grid_thw, video_grid_thw[:, 0], dim=0 video_grid_thw, video_grid_thw[:, 0], dim=0
@@ -1925,6 +1927,8 @@ class MRotaryEmbedding(RotaryEmbedding):
"qwen2_vl", "qwen2_vl",
"qwen3_vl", "qwen3_vl",
"qwen3_vl_moe", "qwen3_vl_moe",
"qwen3_5",
"qwen3_5_moe",
): ):
t_index = ( t_index = (
torch.arange(llm_grid_t, device=position_ids.device) torch.arange(llm_grid_t, device=position_ids.device)
+1
View File
@@ -1121,6 +1121,7 @@ def general_mm_embed_routine(
if isinstance(feature, torch.Tensor) and feature.is_cuda: if isinstance(feature, torch.Tensor) and feature.is_cuda:
mm_item.feature = feature.to("cpu", non_blocking=True) mm_item.feature = feature.to("cpu", non_blocking=True)
forward_batch.mm_inputs = None forward_batch.mm_inputs = None
forward_batch.mm_input_embeds = input_embeds
else: else:
input_embeds = embed_tokens(input_ids) input_embeds = embed_tokens(input_ids)
# Copy to pre-allocated buffer if available (for CUDA graph address stability) # Copy to pre-allocated buffer if available (for CUDA graph address stability)
@@ -350,6 +350,7 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
# Speculative decoding # Speculative decoding
spec_info: Optional[SpecInput] = None spec_info: Optional[SpecInput] = None
spec_algorithm: SpeculativeAlgorithm = None spec_algorithm: SpeculativeAlgorithm = None
mm_input_embeds: Optional[torch.Tensor] = None
capture_hidden_mode: CaptureHiddenMode = None capture_hidden_mode: CaptureHiddenMode = None
# For padding # For padding
@@ -38,6 +38,8 @@ from sglang.srt.configs import (
Lfm2Config, Lfm2Config,
NemotronH_Nano_VL_V2_Config, NemotronH_Nano_VL_V2_Config,
NemotronHConfig, NemotronHConfig,
Qwen3_5Config,
Qwen3_5MoeConfig,
Qwen3NextConfig, Qwen3NextConfig,
) )
from sglang.srt.configs.device_config import DeviceConfig from sglang.srt.configs.device_config import DeviceConfig
@@ -1548,8 +1550,15 @@ class ModelRunner(ModelRunnerKVCacheMixin):
@property @property
def hybrid_gdn_config(self): def hybrid_gdn_config(self):
config = self.model_config.hf_config config = self.model_config.hf_config.get_text_config()
if isinstance(config, Qwen3NextConfig | JetNemotronConfig | JetVLMConfig): if isinstance(
config,
Qwen3NextConfig
| Qwen3_5Config
| Qwen3_5MoeConfig
| JetNemotronConfig
| JetVLMConfig,
):
return config return config
return None return None
@@ -2532,7 +2541,9 @@ class ModelRunner(ModelRunnerKVCacheMixin):
def model_is_mrope(self) -> bool: def model_is_mrope(self) -> bool:
"""Detect if the model has "mrope" rope_scaling type. """Detect if the model has "mrope" rope_scaling type.
mrope requires keep "rope_deltas" between prompt and decoding phases.""" mrope requires keep "rope_deltas" between prompt and decoding phases."""
rope_scaling = getattr(self.model_config.hf_text_config, "rope_scaling", {}) rope_scaling = getattr(
self.model_config.hf_text_config, "rope_parameters", None
) or getattr(self.model_config.hf_text_config, "rope_scaling", {})
if rope_scaling is None: if rope_scaling is None:
return False return False
is_mrope_enabled = "mrope_section" in rope_scaling is_mrope_enabled = "mrope_section" in rope_scaling
File diff suppressed because it is too large Load Diff
+415
View File
@@ -0,0 +1,415 @@
# Copyright 2023-2024 SGLang Team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Inference-only Qwen3_5 MTP model."""
import logging
from typing import Iterable, Optional, Tuple
import torch
from torch import nn
from transformers import PretrainedConfig
from sglang.srt.distributed import get_pp_group, get_tensor_model_parallel_world_size
from sglang.srt.layers.layernorm import GemmaRMSNorm
from sglang.srt.layers.logits_processor import LogitsProcessor
from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE
from sglang.srt.layers.vocab_parallel_embedding import (
ParallelLMHead,
VocabParallelEmbedding,
)
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
from sglang.srt.model_loader.weight_utils import default_weight_loader
from sglang.srt.models.qwen3_5 import Qwen3_5AttentionDecoderLayer
from sglang.srt.utils import add_prefix
logger = logging.getLogger(__name__)
class Qwen3_5MultiTokenPredictor(nn.Module):
def __init__(self, config: PretrainedConfig, quant_config=None, prefix: str = ""):
super().__init__()
self.config = config
self.vocab_size = config.vocab_size
self.mtp_start_layer_idx = config.num_hidden_layers
self.num_mtp_layers = getattr(config, "mtp_num_hidden_layers", 1)
self.embed_tokens = VocabParallelEmbedding(
self.vocab_size,
config.hidden_size,
)
self.fc = nn.Linear(2 * config.hidden_size, config.hidden_size, bias=False)
config.full_attention_interval = 1
self.layers = torch.nn.ModuleList(
[
Qwen3_5AttentionDecoderLayer(
config,
idx,
quant_config,
prefix=add_prefix(f"layers.{idx}", prefix),
)
for idx in range(self.num_mtp_layers)
]
)
self.norm = GemmaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.pre_fc_norm_hidden = GemmaRMSNorm(
config.hidden_size, eps=config.rms_norm_eps
)
self.pre_fc_norm_embedding = GemmaRMSNorm(
config.hidden_size, eps=config.rms_norm_eps
)
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
return self.embed_tokens(input_ids)
@torch.no_grad()
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
forward_batch: torch.Tensor,
input_embeds: Optional[torch.Tensor] = None,
**kwargs,
):
# if get_pp_group().is_first_rank:
assert input_embeds is None
input_embeds = forward_batch.mm_input_embeds
if (
forward_batch.forward_mode.is_extend()
and forward_batch.contains_mm_inputs()
and not forward_batch.forward_mode.is_draft_extend()
):
assert input_embeds is not None
input_embeds = torch.cat(
[input_embeds[:-1], self.embed_tokens(input_ids[-1].unsqueeze(0))]
)
if input_embeds is None:
input_embeds = self.embed_tokens(input_ids)
hidden_states = forward_batch.spec_info.hidden_states
# Some idle batch has 0 batch size. GemmaRMSNorm.forward would fail due to bs=0.
if not forward_batch.forward_mode.is_idle():
input_embeds = self.pre_fc_norm_embedding(input_embeds)
hidden_states = self.pre_fc_norm_hidden(hidden_states)
hidden_states = torch.cat([input_embeds, hidden_states], dim=-1)
hidden_states = self.fc(hidden_states)
residual = None
if self.num_mtp_layers == 1:
hidden_states, residual = self.layers[0](
positions=positions,
hidden_states=hidden_states,
residual=residual,
forward_batch=forward_batch,
)
else:
raise ("not implementation for other mtp layers[self.num_mtp_layers > 1]")
if not get_pp_group().is_last_rank:
# For pipeline parallel, return intermediate tensors
return hidden_states
hidden_states, _ = self.norm(hidden_states, residual)
return hidden_states
class Qwen3_5ForCausalLMMTP(nn.Module):
def __init__(
self,
config: PretrainedConfig,
quant_config=None,
prefix: str = "",
) -> None:
super().__init__()
self.is_multimodal = hasattr(config, "text_config")
if self.is_multimodal:
config = config.text_config
self.config = config
self.tp_size = get_tensor_model_parallel_world_size()
self.quant_config = quant_config
self.pp_group = get_pp_group()
self.model = Qwen3_5MultiTokenPredictor(
config, quant_config, prefix=add_prefix("mtp", prefix)
)
if get_pp_group().is_last_rank:
if config.tie_word_embeddings:
self.lm_head = self.model.embed_tokens
else:
self.lm_head = ParallelLMHead(
config.vocab_size,
config.hidden_size,
quant_config=quant_config,
prefix=add_prefix("lm_head", prefix),
)
else:
# For pipeline parallel, create a placeholder layer
self.lm_head = nn.Linear(1, 1, bias=False)
self.logits_processor = LogitsProcessor(config)
def get_embed_and_head(self):
return self.model.embed_tokens.weight, self.lm_head.weight
def set_embed_and_head(self, embed, head):
del self.model.embed_tokens.weight
if not self.config.tie_word_embeddings:
del self.lm_head.weight
self.model.embed_tokens.weight = embed
self.lm_head.weight = head
torch.cuda.empty_cache()
torch.cuda.synchronize()
@torch.no_grad()
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
forward_batch: ForwardBatch,
input_embeds: Optional[torch.Tensor] = None,
**kwargs,
):
hidden_states = self.model(
input_ids,
positions,
forward_batch,
input_embeds,
)
if not get_pp_group().is_last_rank:
# For pipeline parallel, return intermediate results
return hidden_states
return self.logits_processor(
input_ids, hidden_states, self.lm_head, forward_batch
)
def load_weights(
self, weights: Iterable[Tuple[str, torch.Tensor]], is_mtp: bool = False
):
stacked_params_mapping = [
# (param_name, shard_name, shard_id)
("qkv_proj", "q_proj", "q"),
("qkv_proj", "k_proj", "k"),
("qkv_proj", "v_proj", "v"),
("gate_up_proj", "gate_proj", 0),
("gate_up_proj", "up_proj", 1),
]
# Params for MoE experts (non-fused/fused)
num_experts = getattr(self.config, "num_experts", None)
if num_experts is not None:
expert_params_mapping = FusedMoE.make_expert_params_mapping(
ckpt_gate_proj_name="gate_proj",
ckpt_down_proj_name="down_proj",
ckpt_up_proj_name="up_proj",
num_experts=num_experts,
)
else:
expert_params_mapping = []
# Skip loading extra parameters for GPTQ/modelopt models.
ignore_suffixes = (
".bias",
"_bias",
".k_scale",
"_k_scale",
".v_scale",
"_v_scale",
".weight_scale",
"_weight_scale",
".input_scale",
"_input_scale",
)
# fused experts: experts.w13_weight / experts.w2_weight
is_fused_expert = False
fused_expert_params_mapping = [
("experts.w13_weight", "experts.gate_up_proj", 0, "w1"),
("experts.w2_weight", "experts.down_proj", 0, "w2"),
]
def load_fused_expert_weights(
name: str,
params_dict: dict,
loaded_weight: torch.Tensor,
shard_id: str,
num_experts: int,
):
param = params_dict[name]
weight_loader = param.weight_loader
# Let EP MoE layer handle expert_ids that do not belong to local moe rank
for expert_id in range(num_experts):
curr_expert_weight = loaded_weight[expert_id]
weight_loader(
param,
curr_expert_weight,
name,
shard_id,
expert_id,
)
return True
params_dict = dict(self.named_parameters())
loaded_params: set[str] = set()
for name, loaded_weight in weights:
if "rotary_emb.inv_freq" in name:
continue
# Only process MTP branch weights
if "mtp" not in name:
continue
# Some checkpoints use model.language_model.mtp.* prefix
if "language_model" in name:
name = name.replace(r"model.language_model.", r"model.")
if name.startswith("mtp."):
# Remove the mtp. prefix for processing
name = name.replace("mtp.", "model.")
if ".self_attn." in name:
name = name.replace(".self_attn", "")
# 1) Process stacked parameters (q_proj/k_proj/v_proj & gate_proj/up_proj)
for param_name, weight_name, shard_id in stacked_params_mapping:
# Check if this is a fused expert weight
if "experts.gate_up_proj" in name or "experts.down_proj" in name:
is_fused_expert = True
expert_params_mapping = fused_expert_params_mapping
# Skip non-matching weights
if weight_name not in name:
continue
# Skip MoE experts.* here, handled separately below
if "mlp.experts" in name:
continue
name_mapped = name.replace(weight_name, param_name)
# Skip loading extra parameters for GPTQ/modelopt models.
if (
name_mapped.endswith(ignore_suffixes)
and name_mapped not in params_dict
):
continue
if name_mapped not in params_dict:
continue
param = params_dict[name_mapped]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight, shard_id)
name = name_mapped
break
else:
# 2) Process MoE expert weights (including fused experts)
is_expert_weight = False
for mapping in expert_params_mapping:
param_name, weight_name, expert_id, shard_id = mapping
if weight_name not in name:
continue
is_expert_weight = True
name_mapped = name.replace(weight_name, param_name)
# Fused experts: single checkpoint weight contains multiple experts
if is_fused_expert and num_experts is not None:
if "experts.gate_up_proj" in name:
# gate_up_proj fused: split into w1 / w3
loaded_w1, loaded_w3 = loaded_weight.chunk(2, dim=-2)
load_fused_expert_weights(
name_mapped,
params_dict,
loaded_w1,
"w1",
num_experts,
)
load_fused_expert_weights(
name_mapped,
params_dict,
loaded_w3,
"w3",
num_experts,
)
else:
# down_proj fused: distribute entire weight
load_fused_expert_weights(
name_mapped,
params_dict,
loaded_weight,
shard_id,
num_experts,
)
else:
# Non-fused expert, load by expert_id/shard
if (
name_mapped.endswith(ignore_suffixes)
and name_mapped not in params_dict
):
continue
if name_mapped not in params_dict:
break
param = params_dict[name_mapped]
weight_loader = param.weight_loader
weight_loader(
param,
loaded_weight,
name_mapped,
shard_id=shard_id,
expert_id=expert_id,
)
name = name_mapped
break
else:
# Skip expert weight if not handled by current rank
if is_expert_weight:
continue
# 3) Regular non-stacked / non-expert parameters, use default loader
if name.endswith(ignore_suffixes) and name not in params_dict:
continue
if name in params_dict:
param = params_dict[name]
weight_loader = getattr(
param, "weight_loader", default_weight_loader
)
weight_loader(param, loaded_weight)
else:
logger.warning_once(
f"Parameter {name} not found in params_dict, skip loading"
)
loaded_params.add(name)
return loaded_params
EntryClass = [Qwen3_5ForCausalLMMTP]
+4 -1
View File
@@ -617,7 +617,10 @@ class Qwen3HybridAttentionDecoderLayer(nn.Module):
self.scaling = self.head_dim**-0.5 self.scaling = self.head_dim**-0.5
self.rope_theta = getattr(config, "rope_theta", 10000) self.rope_theta = getattr(config, "rope_theta", 10000)
self.max_position_embeddings = getattr(config, "max_position_embeddings", 8192) self.max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
self.rope_scaling = getattr(config, "rope_scaling", None) if "rope_parameters" in config:
self.rope_scaling = getattr(config, "rope_parameters", None)
else:
self.rope_scaling = getattr(config, "rope_scaling", None)
self.partial_rotary_factor = config.partial_rotary_factor self.partial_rotary_factor = config.partial_rotary_factor
self.layer_id = layer_id self.layer_id = layer_id
@@ -7,6 +7,7 @@ from typing import List, Union
import numpy as np import numpy as np
import torch import torch
import torchvision import torchvision
from decord import VideoReader
from PIL import Image from PIL import Image
from torchvision.transforms import InterpolationMode from torchvision.transforms import InterpolationMode
@@ -15,6 +16,10 @@ from sglang.srt.layers.rotary_embedding import MRotaryEmbedding
from sglang.srt.managers.schedule_batch import Modality, MultimodalDataItem from sglang.srt.managers.schedule_batch import Modality, MultimodalDataItem
from sglang.srt.models.qwen2_5_vl import Qwen2_5_VLForConditionalGeneration from sglang.srt.models.qwen2_5_vl import Qwen2_5_VLForConditionalGeneration
from sglang.srt.models.qwen2_vl import Qwen2VLForConditionalGeneration from sglang.srt.models.qwen2_vl import Qwen2VLForConditionalGeneration
from sglang.srt.models.qwen3_5 import (
Qwen3_5ForConditionalGeneration,
Qwen3_5MoeForConditionalGeneration,
)
from sglang.srt.models.qwen3_omni_moe import Qwen3OmniMoeForConditionalGeneration from sglang.srt.models.qwen3_omni_moe import Qwen3OmniMoeForConditionalGeneration
from sglang.srt.models.qwen3_vl import Qwen3VLForConditionalGeneration from sglang.srt.models.qwen3_vl import Qwen3VLForConditionalGeneration
from sglang.srt.models.qwen3_vl_moe import Qwen3VLMoeForConditionalGeneration from sglang.srt.models.qwen3_vl_moe import Qwen3VLMoeForConditionalGeneration
@@ -148,6 +153,9 @@ async def preprocess_video(
image_factor: int = IMAGE_FACTOR, image_factor: int = IMAGE_FACTOR,
video_config: dict = {}, video_config: dict = {},
) -> torch.Tensor: ) -> torch.Tensor:
# preprocessed video
if not isinstance(vr, VideoReader):
return vr
entry_time = time.perf_counter() entry_time = time.perf_counter()
total_frames, video_fps = len(vr), vr.get_avg_fps() total_frames, video_fps = len(vr), vr.get_avg_fps()
@@ -226,6 +234,8 @@ class QwenVLImageProcessor(SGLangBaseProcessor):
Qwen2_5_VLForConditionalGeneration, Qwen2_5_VLForConditionalGeneration,
Qwen3VLForConditionalGeneration, Qwen3VLForConditionalGeneration,
Qwen3VLMoeForConditionalGeneration, Qwen3VLMoeForConditionalGeneration,
Qwen3_5ForConditionalGeneration,
Qwen3_5MoeForConditionalGeneration,
Qwen3OmniMoeForConditionalGeneration, Qwen3OmniMoeForConditionalGeneration,
] ]
@@ -326,7 +336,12 @@ class QwenVLImageProcessor(SGLangBaseProcessor):
preprocess_time = time.perf_counter() preprocess_time = time.perf_counter()
# NOTE: for qwen3-vl, video_meta need to be passed in, since do_sample_frames is already done in preprocess_video # NOTE: for qwen3-vl, video_meta need to be passed in, since do_sample_frames is already done in preprocess_video
if self.hf_config.model_type in ("qwen3_vl", "qwen3_vl_moe"): if self.hf_config.model_type in (
"qwen3_vl",
"qwen3_vl_moe",
"qwen3_5",
"qwen3_5_moe",
):
mm_items, input_ids, ret = self.process_and_combine_mm_data( mm_items, input_ids, ret = self.process_and_combine_mm_data(
base_output, base_output,
self.mm_tokens, self.mm_tokens,
+6 -2
View File
@@ -1558,7 +1558,11 @@ class ServerArgs:
"Use flashinfer_trtllm as MoE runner backend on sm100 for " "Use flashinfer_trtllm as MoE runner backend on sm100 for "
f"{model_arch}" f"{model_arch}"
) )
elif model_arch in ["Qwen3NextForCausalLM"]: elif model_arch in [
"Qwen3NextForCausalLM",
"Qwen3_5MoeForConditionalGeneration",
"Qwen3_5ForConditionalGeneration",
]:
if is_sm100_supported(): if is_sm100_supported():
quant_method = get_quantization_config(hf_config) quant_method = get_quantization_config(hf_config)
if self.quantization is None and quant_method is not None: if self.quantization is None and quant_method is not None:
@@ -1573,7 +1577,7 @@ class ServerArgs:
): ):
self.moe_runner_backend = "flashinfer_trtllm" self.moe_runner_backend = "flashinfer_trtllm"
logger.info( logger.info(
"Use flashinfer_trtllm as MoE runner backend on sm100 for Qwen3NextForCausalLM" f"Use flashinfer_trtllm as MoE runner backend on sm100 for {model_arch}"
) )
self._handle_mamba_radix_cache( self._handle_mamba_radix_cache(
model_arch=model_arch, model_arch=model_arch,
@@ -291,7 +291,11 @@ class EAGLEWorker(TpModelWorker):
self.draft_model_runner.tp_group self.draft_model_runner.tp_group
), speculative_moe_backend_context(), speculative_moe_a2a_backend_context(): ), speculative_moe_backend_context(), speculative_moe_a2a_backend_context():
self.forward_draft_extend( self.forward_draft_extend(
batch, logits_output.hidden_states, next_token_ids, seq_lens_cpu batch,
logits_output.hidden_states,
next_token_ids,
seq_lens_cpu,
logits_output.mm_input_embeds,
) )
return GenerationBatchResult( return GenerationBatchResult(
logits_output=logits_output, logits_output=logits_output,
@@ -856,6 +860,7 @@ class EAGLEWorker(TpModelWorker):
hidden_states: torch.Tensor, hidden_states: torch.Tensor,
next_token_ids: torch.Tensor, next_token_ids: torch.Tensor,
seq_lens_cpu: Optional[torch.Tensor], seq_lens_cpu: Optional[torch.Tensor],
mm_input_embeds: Optional[torch.Tensor] = None,
): ):
"""Run draft model extend. This API modifies the states of the batch. """Run draft model extend. This API modifies the states of the batch.
@@ -880,6 +885,8 @@ class EAGLEWorker(TpModelWorker):
model_worker_batch, self.draft_model_runner model_worker_batch, self.draft_model_runner
) )
forward_batch.return_logprob = False forward_batch.return_logprob = False
if mm_input_embeds is not None:
forward_batch.mm_input_embeds = mm_input_embeds
logits_output = self.draft_model_runner.forward(forward_batch).logits_output logits_output = self.draft_model_runner.forward(forward_batch).logits_output
if self.enable_nan_detection: if self.enable_nan_detection:
detect_nan(logits_output) detect_nan(logits_output)
+2
View File
@@ -1000,6 +1000,8 @@ def load_video(video_file: Union[str, bytes], use_gpu: bool = True):
tmp_file.write(video_bytes) tmp_file.write(video_bytes)
tmp_file.close() tmp_file.close()
vr = VideoReader(tmp_file.name, ctx=ctx) vr = VideoReader(tmp_file.name, ctx=ctx)
elif isinstance(video_file, (list, tuple, torch.Tensor, np.ndarray)):
vr = video_file
else: else:
raise ValueError(f"Unsupported video input type: {type(video_file)}") raise ValueError(f"Unsupported video input type: {type(video_file)}")
@@ -62,6 +62,8 @@ from sglang.srt.configs import (
NemotronH_Nano_VL_V2_Config, NemotronH_Nano_VL_V2_Config,
NemotronHConfig, NemotronHConfig,
Olmo3Config, Olmo3Config,
Qwen3_5Config,
Qwen3_5MoeConfig,
Qwen3NextConfig, Qwen3NextConfig,
Step3p5Config, Step3p5Config,
Step3VLConfig, Step3VLConfig,
@@ -93,6 +95,8 @@ _CONFIG_REGISTRY: List[Type[PretrainedConfig]] = [
NemotronH_Nano_VL_V2_Config, NemotronH_Nano_VL_V2_Config,
NemotronHConfig, NemotronHConfig,
DeepseekVLV2Config, DeepseekVLV2Config,
Qwen3_5Config,
Qwen3_5MoeConfig,
JetNemotronConfig, JetNemotronConfig,
JetVLMConfig, JetVLMConfig,
KimiK25Config, KimiK25Config,