This commit is contained in:
Xinyuan Tong
2026-04-29 21:13:51 +08:00
committed by GitHub
parent 4c1eefca4f
commit 1279ae0787
8 changed files with 885 additions and 5 deletions
+4 -1
View File
@@ -499,7 +499,10 @@ class ModelConfig:
or "LongcatFlashForCausalLMNextN" in self.hf_config.architectures
or "DotsVLMForCausalLM" in self.hf_config.architectures
or "MistralLarge3ForCausalLM" in self.hf_config.architectures
or "PixtralForConditionalGeneration" in self.hf_config.architectures
or (
"PixtralForConditionalGeneration" in self.hf_config.architectures
and getattr(self.hf_text_config, "kv_lora_rank", None) is not None
)
or "MistralLarge3ForCausalLMEagle" in self.hf_config.architectures
or "KimiK25ForConditionalGeneration" in self.hf_config.architectures
):
+102
View File
@@ -13,19 +13,81 @@
# ==============================================================================
"""Inference-only Mistral model."""
import logging
from collections.abc import Iterable
from typing import List
import regex as re
import torch
from transformers.models.mistral3.modeling_mistral3 import Mistral3MultiModalProjector
from sglang.srt.managers.schedule_batch import MultimodalDataItem
from sglang.srt.models.llama import LlamaForCausalLM
logger = logging.getLogger(__name__)
class MistralForCausalLM(LlamaForCausalLM):
pass
class MistralForCausalLMMistralFormat(MistralForCausalLM):
"""Mistral GQA model loaded from mistral native format (params.json).
Handles weight name remapping from mistral native format to HF/Llama
format. This is the GQA counterpart to MistralLarge3ForCausalLM which
handles MLA models in mistral native format.
"""
# fmt: off
remapping = {
r"layers\.(\d+)\.attention_norm\.weight": r"model.layers.\1.input_layernorm.weight",
r"layers\.(\d+)\.attention\.wq\.(\w+)": r"model.layers.\1.self_attn.q_proj.\2",
r"layers\.(\d+)\.attention\.wk\.(\w+)": r"model.layers.\1.self_attn.k_proj.\2",
r"layers\.(\d+)\.attention\.wv\.(\w+)": r"model.layers.\1.self_attn.v_proj.\2",
r"layers\.(\d+)\.attention\.wo\.(\w+)": r"model.layers.\1.self_attn.o_proj.\2",
r"layers\.(\d+)\.ffn_norm\.weight": r"model.layers.\1.post_attention_layernorm.weight",
r"layers\.(\d+)\.feed_forward\.w1\.(\w+)": r"model.layers.\1.mlp.gate_proj.\2",
r"layers\.(\d+)\.feed_forward\.w2\.(\w+)": r"model.layers.\1.mlp.down_proj.\2",
r"layers\.(\d+)\.feed_forward\.w3\.(\w+)": r"model.layers.\1.mlp.up_proj.\2",
r"norm\.weight": "model.norm.weight",
r"tok_embeddings\.weight": "model.embed_tokens.weight",
r"output\.weight": "lm_head.weight",
}
# fmt: on
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
return super().load_weights(self._remap_mistral_to_llama(weights))
def _remap_mistral_to_llama(
self, weights: Iterable[tuple[str, torch.Tensor]]
) -> Iterable[tuple[str, torch.Tensor]]:
"""Remap Mistral native format weight names to HF/Llama format."""
for name, loaded_weight in weights:
# Pass through weights already in HF/Llama layout so this loader
# tolerates mixed-format checkpoints (e.g. native body + HF-style
# multi_modal_projector weights spliced in by a parent class).
if name.startswith("model.") or name.startswith("lm_head."):
yield name, loaded_weight
continue
for k, v in self.remapping.items():
match = re.fullmatch(k, name)
if match:
name = match.expand(v)
break
else:
logger.warning(f"Unrecognized weight: {name}. Skipping.")
continue
if name.endswith(".qscale_act"):
name = re.sub(r"\.qscale_act$", ".input_scale", name)
elif name.endswith(".qscale_weight"):
name = re.sub(r"\.qscale_weight$", ".weight_scale", name)
yield name, loaded_weight
class Mistral3ForConditionalGeneration:
MULTIMODAL_PROJECTOR_TYPE = Mistral3MultiModalProjector
@@ -89,5 +151,45 @@ class Mistral3ForConditionalGeneration:
def __call__(self, *args, **kwargs):
return self.inner(*args, **kwargs)
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
"""Normalize transformers v5 Mistral3 weight names for
LlavaForConditionalGeneration.load_weights.
v5 checkpoints lay out Mistral3 weights as:
model.language_model.{embed_tokens,layers.*,norm}.*
model.vision_tower.*
model.multi_modal_projector.*
lm_head.*
The Llava loader routes by top-level `language_model.` /
`vision_tower.` prefixes, stripping one segment before forwarding to
the sub-module. The sub-module's own `load_weights` expects the
standard HF layout: `model.layers.*`, `model.embed_tokens.weight`,
`lm_head.weight` for Llama, and `vision_tower` internals at their
top level. So we rewrite:
model.language_model.X -> language_model.model.X
model.vision_tower.X -> vision_tower.X
model.multi_modal_projector.X -> multi_modal_projector.X
lm_head.X -> language_model.lm_head.X
"""
def normalize(ws):
for name, w in ws:
if name.startswith("model.language_model."):
rest = name[len("model.language_model.") :]
name = "language_model.model." + rest
elif name.startswith("model.vision_tower."):
name = "vision_tower." + name[len("model.vision_tower.") :]
elif name.startswith("model.multi_modal_projector."):
name = (
"multi_modal_projector."
+ name[len("model.multi_modal_projector.") :]
)
elif name.startswith("lm_head."):
name = "language_model." + name
yield name, w
return self.inner.load_weights(normalize(weights))
EntryClass = [MistralForCausalLM, Mistral3ForConditionalGeneration]
+16 -4
View File
@@ -45,6 +45,7 @@ from sglang.srt.managers.mm_utils import (
)
from sglang.srt.managers.schedule_batch import MultimodalDataItem, MultimodalInputs
from sglang.srt.model_loader.weight_utils import default_weight_loader
from sglang.srt.models.mistral import MistralForCausalLMMistralFormat
from sglang.srt.models.mistral_large_3 import MistralLarge3ForCausalLM
USE_XFORMERS_OPS = False
@@ -94,10 +95,21 @@ class PixtralForConditionalGeneration(nn.Module):
self.vision_args = VisionEncoderArgs(**vision_args)
self.language_model = MistralLarge3ForCausalLM(
config=self.config.text_config,
quant_config=kwargs.get("quant_config"),
)
# Choose language model based on text architecture:
# MLA text configs use DeepSeek V3 backbone (model_type="deepseek_v3"),
# GQA text configs use the standard Llama-style Mistral backbone.
text_config = self.config.text_config
is_mla = getattr(text_config, "model_type", "") == "deepseek_v3"
if is_mla:
self.language_model = MistralLarge3ForCausalLM(
config=text_config,
quant_config=kwargs.get("quant_config"),
)
else:
self.language_model = MistralForCausalLMMistralFormat(
config=text_config,
quant_config=kwargs.get("quant_config"),
)
self.vision_encoder = VisionTransformer(self.vision_args)
@@ -227,6 +227,14 @@ def get_hf_text_config(config: PretrainedConfig):
if getattr(_converted, "dtype", None) is None and parent_dtype is not None:
_converted.dtype = parent_dtype
setattr(config, _attr, _converted)
elif _sub is not None and parent_dtype is not None:
# transformers v5 multimodal configs (e.g. Mistral3Config) carry
# `dtype` only on the top-level config, leaving the sub-configs at
# None. Without this, _get_and_verify_dtype falls back to float32
# and then "auto" downcasts to float16, which overflows the Pixtral
# vision tower on real images and produces NaN features.
if getattr(_sub, "dtype", None) is None:
_sub.dtype = parent_dtype
# Priority: thinker_config > llm_config > language_config > text_config
if hasattr(config, "thinker_config"):
@@ -73,6 +73,22 @@ def adapt_config_dict(
config_dict["architectures"] = ["MixtralForCausalLM"]
else:
config_dict["architectures"] = ["MistralForCausalLM"]
config_dict["model_type"] = "mistral"
# Mistral models use non-interleaved RoPE (is_neox_style=False),
# unlike Llama which defaults to True.
config_dict["rope_is_neox_style"] = False
# Remove None-valued MLA fields that would shadow defaults in
# model_config._derive_model_shapes (getattr returns None instead
# of the fallback when the attribute exists but is None).
for mla_key in (
"q_lora_rank",
"qk_rope_head_dim",
"qk_nope_head_dim",
"kv_lora_rank",
"v_head_dim",
):
if config_dict.get(mla_key) is None:
config_dict.pop(mla_key, None)
if bool(config_dict.get("yarn")):
config_dict = _remap_mistral_yarn_args(config_dict)