[Config] Round 5.2: the per-model declarations get their own modules (#37087)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Cheng Wan
2026-08-30 02:24:35 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 7e751153eb
commit e51a3ae65e
44 changed files with 2898 additions and 1730 deletions
@@ -0,0 +1,376 @@
"""What the per-model override declarations are written against.
The declarations themselves live one directory down, in
``arg_groups/model_overrides/``: one module per model family, mirroring the
``models/`` naming. This module is what they all import -- the registry they
register into, the read-only views they are handed, and the few accessors that
answer questions about the model. It deliberately depends on nothing in
``overrides.py``, so a family module never has to import its way back up.
"""
import logging
from typing import Any, Callable, Dict, List, Optional, Tuple
from sglang.srt.platforms import current_platform
from sglang.srt.runtime_context import get_platform
from sglang.srt.utils.common import is_mps, is_no_spec_infer_or_topk_one
logger = logging.getLogger(__name__)
# Constant per-architecture overrides (populated by the migration sweeps).
MODEL_OVERRIDES: Dict[str, Dict[str, Any]] = {
# These models run in bfloat16 regardless of the requested dtype
# (faithful port of the legacy unconditional arch branch).
"MistralLarge3ForCausalLM": {"dtype": "bfloat16"},
"PixtralForConditionalGeneration": {"dtype": "bfloat16"},
}
# Derived per-architecture override providers, in registration order.
_MODEL_OVERRIDE_FNS: Dict[str, List[Callable[..., dict]]] = {}
# Predicate-keyed providers, in registration order — for legacy branches
# matched by substring/predicate on the architecture string rather than an
# exact name (e.g. '"Step3p5ForCausalLM" in model_arch').
_PREDICATE_OVERRIDE_FNS: List[Tuple[Callable[[str], bool], Callable[..., dict]]] = []
def register_model_override(architecture: str):
"""Register a derived-override provider for ``architecture``.
The decorated callable receives ``(server_args, hf_config)``, must not
mutate either, and returns a ``{field: resolved_value}`` dict (possibly
empty when nothing applies). Providers needing derived model data beyond
the HF config go through ``model_config_of(server_args)`` (cached,
read-only) — never anything mutating.
"""
def decorator(fn: Callable[..., dict]) -> Callable[..., dict]:
_MODEL_OVERRIDE_FNS.setdefault(architecture, []).append(fn)
return fn
return decorator
def register_model_override_predicate(predicate: Callable[[str], bool]):
"""Register a derived-override provider keyed by an architecture
predicate. Same callable contract as ``register_model_override``."""
def decorator(fn: Callable[..., dict]) -> Callable[..., dict]:
_PREDICATE_OVERRIDE_FNS.append((predicate, fn))
return fn
return decorator
def _invoke_provider(
fn: Callable[..., dict], server_args: Any, hf_config: Any
) -> Dict[str, Any]:
declared = fn(server_args, hf_config)
if not isinstance(declared, dict):
raise TypeError(
f"model override provider {fn.__qualname__} must return a dict, "
f"got {type(declared).__name__}"
)
return declared
class ResolvedView:
"""Read-only view of the resolving configuration handed to post-process
passes: the accumulated declarations overlaid on the pristine
``server_args`` (residual imperative writes of non-resolved fields show
through the fallthrough) — exactly the state the legacy handler at the
same slot observed. Writes are rejected: passes return declarations.
"""
__slots__ = ("_server_args", "_overlay")
def __init__(self, server_args: Any, overlay: Optional[Dict[str, Any]] = None):
object.__setattr__(self, "_server_args", server_args)
object.__setattr__(self, "_overlay", overlay or {})
def __getattr__(self, name: str) -> Any:
overlay = object.__getattribute__(self, "_overlay")
if name in overlay:
return overlay[name]
return getattr(object.__getattribute__(self, "_server_args"), name)
def __setattr__(self, name: str, value: Any) -> None:
raise AttributeError(
"ResolvedView is read-only; post-process passes return declarations"
)
class ResolvingConfig:
"""Live read view of the resolution result: the declaration stash over the
record's fields, looked up per read.
``ResolvedView`` snapshots the overlay when it is built, which is what a
post-process pass wants -- it reads the state at its slot. A resolver that
reads *after* declaring, or after calling something that declares, needs the
current answer instead, so this one walks the stash on every read. It falls
through to the field, which is where the raw input lives.
"""
__slots__ = ("_server_args",)
def __init__(self, server_args: Any):
object.__setattr__(self, "_server_args", server_args)
def __getattr__(self, name: str) -> Any:
server_args = object.__getattribute__(self, "_server_args")
for _source, declared in reversed(
getattr(server_args, "_resolved_overrides", None) or ()
):
if name in declared:
return declared[name]
return getattr(server_args, name)
def __setattr__(self, name: str, value: Any) -> None:
raise AttributeError(
"ResolvingConfig is read-only; resolution writes through declarations"
)
def resolving_view(server_args: Any) -> ResolvingConfig:
"""A live read view of what resolution has decided so far."""
return ResolvingConfig(server_args)
def _declaration_overlay(server_args: Any) -> Dict[str, Any]:
"""What the declarations say so far, last writer wins.
Nothing writes the fields, so a mid-resolution reader needs this to see a
decision at all; the fields keep what the caller supplied."""
overlay: Dict[str, Any] = {}
for _source, declared in getattr(server_args, "_resolved_overrides", None) or ():
overlay.update(declared)
return overlay
def resolved_view(server_args: Any) -> ResolvedView:
"""Read-only view of the resolving configuration: the declarations
overlaid on the fields, snapshotted per call.
For mid-resolution code that is not a pass (``__post_init__`` handlers and
hooks) that must answer with what resolution decided -- a declaration-only resolver (a model-specific
override, a registry entry) never writes the field, so a field read there
answers with the raw input."""
return ResolvedView(server_args, overlay=_declaration_overlay(server_args))
def attention_backends_of(cfg: Any) -> tuple:
"""(prefill, decode) attention backends of a config-shaped object (a
ResolvedView mid-resolution, or pristine server_args at dispatch time):
split fields fall back to the base backend."""
prefill = (
cfg.prefill_attention_backend
if cfg.prefill_attention_backend
else cfg.attention_backend
)
decode = (
cfg.decode_attention_backend
if cfg.decode_attention_backend
else cfg.attention_backend
)
return prefill, decode
def _register_for(*architectures: str):
"""Register one provider for several architectures (family lists)."""
def decorator(fn: Callable[..., dict]) -> Callable[..., dict]:
for architecture in architectures:
register_model_override(architecture)(fn)
return fn
return decorator
def record_of(view: Any) -> Any:
"""The record a view reads through.
For the few helpers a view cannot serve: `get_default_attn_backend` reads
through *both* overlays, so it needs the record the two views are built
from rather than either one of them.
"""
return object.__getattribute__(view, "_server_args")
def is_attention_backend_not_set(cfg: Any):
"""None of the three attention backends has been decided yet.
Takes the view rather than the record: every read is a view read, and the
callers that hold a view (the override providers) would otherwise have to
reach back through it for a record.
"""
return (
cfg.attention_backend is None
and cfg.prefill_attention_backend is None
and cfg.decode_attention_backend is None
)
def use_mla_backend(server_args: Any):
from sglang.srt.configs.model_config import AttentionArch
model_config = model_config_of(server_args)
return model_config.attention_arch == AttentionArch.MLA
def model_config_of(server_args: Any):
"""The model configuration this record describes, built once and memoised.
Takes a view as readily as the record: a view is a read overlay of one
record, the memo has to live on that record either way, and the callers
that hold a view would otherwise all have to unwrap it themselves.
"""
if isinstance(server_args, (ResolvedView, ResolvingConfig)):
server_args = record_of(server_args)
# Lazy init to avoid circular import
cfg = resolving_view(server_args)
from sglang.srt.configs.model_config import ModelConfig
memo = getattr(server_args, "_model_config", None)
if memo is not None:
# The key is the path this record carried when the cache was
# filled. The GGUF and ModelScope handlers declare a different
# `model_path`, and a configuration built before them describes
# another checkpoint. `ModelConfig` re-points its own `model_path`
# at the local pull directory when the weights sit behind an
# object-store URI, so its field is not the key. A configuration a
# fixture supplied carries no key and is handed back as it is.
built_from = getattr(server_args, "_model_config_built_from", None)
if built_from is None or built_from == cfg.model_path:
return memo
model_config = ModelConfig.from_server_args(server_args)
server_args._model_config = model_config
server_args._model_config_built_from = cfg.model_path
if model_config.is_hybrid_swa:
logger.info(
"Hybrid SWA model detected. architectures=%s",
model_config.hf_config.architectures,
)
return model_config
def mamba_extra_buffer_of(cfg: Any) -> bool:
"""Mid-resolution equivalent of runtime_context.mamba_extra_buffer_enabled:
reads the (possibly overlaid) strategy from a config-shaped object.
This is the one definition of the predicate: ``ServerArgs`` delegates its
member to it, and the runtime_context accessor is its post-publish sibling
(which cannot reuse it, because the two leaves land in different bags)."""
return cfg.disable_radix_cache is False and cfg.mamba_radix_cache_strategy in (
"extra_buffer",
"extra_buffer_lazy",
)
def get_default_attn_backend(server_args: Any, use_mla_backend: bool, model_config):
"""
Auto select the fastest attention backend.
1. Models with MHA Architecture (e.g: Llama, QWen)
1.1 We will turn on FA3 on hopper unless user use spec decode with topk > 1 or page_size > 1.
1.2 Use trtllm_mha for SM100/SM103 (Blackwell B200/GB200/B300) excluding spec with topk > 1.
Note: trtllm_mha does not support SM120, which will fall back to flashinfer.
1.3 In other cases, we will use flashinfer if available, otherwise use triton.
2. Models with MLA Architecture and using FA3
2.1 We will use FA3 backend on hopper.
2.2 We will use Flashinfer backend on blackwell.
2.3 Otherwise, we will use triton backend.
"""
cfg = resolving_view(server_args)
# OOT platforms provide their own default attention backend.
if current_platform.is_out_of_tree():
return current_platform.get_default_attention_backend()
# Whisper requires flashinfer for cross-attention CUDA graph support.
if "WhisperForConditionalGeneration" in (
model_config.hf_config.architectures or []
):
return "flashinfer"
if not use_mla_backend:
# MHA architecture
if get_platform().is_hopper_with_cuda_12_3 and is_no_spec_infer_or_topk_one(
resolved_view(server_args)
):
# Note: flashinfer 0.6.1 caused performance regression on Hopper attention kernel
# Before the kernel is fixed, we choose fa3 as the default backend on Hopper MHA
# ref: https://github.com/sgl-project/sglang/issues/17411
return "fa3"
elif (
get_platform().is_sm100
and is_no_spec_infer_or_topk_one(resolved_view(server_args))
and (
cfg.speculative_algorithm is None
or cfg.speculative_eagle_topk is not None
)
):
# trtllm_mha requires equal K/V row widths; fa4 carries
# v_head_dim through.
if model_config.has_asymmetric_kv:
return "fa4"
return "trtllm_mha"
elif get_platform().is_hip:
return "aiter"
elif is_mps():
return "torch_native"
else:
# FlashInfer does not support attention sinks.
if get_platform().has_flashinfer and not model_config.has_attention_sinks:
return "flashinfer"
return "triton"
else:
# MLA architecture
if get_platform().is_hopper_with_cuda_12_3:
return "fa3"
elif get_platform().is_sm100:
return "flashinfer"
elif get_platform().is_hip:
head_num = model_config.get_num_kv_heads(cfg.tp_size)
# TODO current aiter only support head number 16 or 128 head number
if head_num == 128 or head_num == 16:
return "aiter"
else:
return "triton"
elif is_mps():
return "torch_native"
else:
return "triton"
def _dspark_verify_on_decode_backend(
backend: Optional[str], q_len: int, kv_cache_dtype: Optional[str]
) -> bool:
"""Whether the MLA decode backend can serve a q_len-wide target verify."""
if backend == "trtllm_mla":
return True
if backend == "tokenspeed_mla":
return kv_cache_dtype == "fp8_e4m3" and q_len <= 8
if backend == "cutedsl_mla":
# cute-dsl monolithic MLA decode folds the verify tokens into the head
# dim (fold_sq), so it serves any DSPARK verify width. Needs flashinfer
# >= 0.6.15 (older builds reject q_len >= 5).
return True
return False
def _is_mxfp4_pack_quantized(hf_config: Any) -> bool:
qc = getattr(
getattr(hf_config, "text_config", hf_config), "quantization_config", None
)
if not isinstance(qc, dict):
return False
groups = qc.get("config_groups") or {}
return any(
"mxfp4" in str(g.get("format", ""))
for g in groups.values()
if isinstance(g, dict)
)
@@ -0,0 +1,37 @@
"""Per-model config-time override declarations, one module per family,
mirroring the ``models/`` naming.
Importing this package is what registers them. An architecture may be claimed
by more than one module here -- one supplies its attention shape, another its
MoE runner -- but two of them must never declare the *same* field for it:
nobody would own that value, and which module supplied it would come down to
the order of the imports below. ``test_model_override_split.py`` forbids the
overlap, which is why this list needs no particular order.
"""
from sglang.srt.arg_groups.model_overrides import deepseek_v2 # noqa: F401
from sglang.srt.arg_groups.model_overrides import deepseek_v4 # noqa: F401
from sglang.srt.arg_groups.model_overrides import exaone # noqa: F401
from sglang.srt.arg_groups.model_overrides import falcon_h1 # noqa: F401
from sglang.srt.arg_groups.model_overrides import gemma2_gemma3 # noqa: F401
from sglang.srt.arg_groups.model_overrides import gemma4 # noqa: F401
from sglang.srt.arg_groups.model_overrides import glm4_moe # noqa: F401
from sglang.srt.arg_groups.model_overrides import gpt_oss # noqa: F401
from sglang.srt.arg_groups.model_overrides import granitemoehybrid # noqa: F401
from sglang.srt.arg_groups.model_overrides import inkling # noqa: F401
from sglang.srt.arg_groups.model_overrides import interns2_mobius # noqa: F401
from sglang.srt.arg_groups.model_overrides import kimi_k3 # noqa: F401
from sglang.srt.arg_groups.model_overrides import lfm2 # noqa: F401
from sglang.srt.arg_groups.model_overrides import llama4 # noqa: F401
from sglang.srt.arg_groups.model_overrides import mimo_v2 # noqa: F401
from sglang.srt.arg_groups.model_overrides import minicpm # noqa: F401
from sglang.srt.arg_groups.model_overrides import minicpmv # noqa: F401
from sglang.srt.arg_groups.model_overrides import minimax_m2 # noqa: F401
from sglang.srt.arg_groups.model_overrides import minimax_m3 # noqa: F401
from sglang.srt.arg_groups.model_overrides import moss_vl # noqa: F401
from sglang.srt.arg_groups.model_overrides import muse_glimmer # noqa: F401
from sglang.srt.arg_groups.model_overrides import nemotron_h # noqa: F401
from sglang.srt.arg_groups.model_overrides import olmo2 # noqa: F401
from sglang.srt.arg_groups.model_overrides import qwen3_5 # noqa: F401
from sglang.srt.arg_groups.model_overrides import qwen3_moe # noqa: F401
from sglang.srt.arg_groups.model_overrides import qwen3_vl # noqa: F401
@@ -0,0 +1,145 @@
"""Config-time override declarations for deepseek_v2.
Architectures: DeepseekV32ForCausalLM, DeepseekV3ForCausalLM, Dots3NoteForCausalLM, GlmMoeDsaForCausalLM, KimiK25ForConditionalGeneration, LongcatFlashForCausalLM, LongcatFlashForCausalLMNextN, MistralLarge3ForCausalLM, PixtralForConditionalGeneration.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
resolving_view,
use_mla_backend,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for(
"DeepseekV3ForCausalLM",
"DeepseekV32ForCausalLM",
"KimiK25ForConditionalGeneration",
"MistralLarge3ForCausalLM",
"PixtralForConditionalGeneration",
"GlmMoeDsaForCausalLM",
"LongcatFlashForCausalLM",
"LongcatFlashForCausalLMNextN",
"Dots3NoteForCausalLM",
)
def _deepseek_family_overrides(server_args: Any, hf_config: Any) -> dict:
"""Order-safe declarations of the DeepSeek/DSA branch. The CP parallel
writes (enable_dp_attention/ep_size/moe_a2a_backend have post-monolith
writers), the kv-cache/split-backend defaults, the quant/moe block (read
before it by _set_default_dsa_kv_cache_dtype) and the env writes stay in
the branch."""
cfg = resolving_view(server_args)
from sglang.srt.configs.model_config import is_deepseek_dsa
overrides: Dict[str, Any] = {}
if is_deepseek_dsa(hf_config): # DeepSeek 3.2/GLM 5
# Set attention backend for DeepSeek
if is_attention_backend_not_set(cfg):
overrides["attention_backend"] = "dsa"
logger.info("Use dsa attention backend for DeepSeek with DSA.")
if not get_platform().is_npu and not get_platform().is_xpu: # CUDA or ROCm GPU
if cfg.enable_prefill_cp:
logger.warning(
"Context parallel feature is still under experiment. It has only been verified on Hopper platform."
)
overrides["enable_dp_attention"] = True
overrides["moe_dense_tp_size"] = 1
if cfg.cp_strategy == "zigzag":
overrides["moe_a2a_backend"] = "deepep"
overrides["ep_size"] = cfg.tp_size
logger.warning(
"zigzag DSA CP requires moe_dense_tp_size=1, "
"moe_a2a_backend=deepep, ep_size=tp_size, batch_size=1."
)
else:
assert (
cfg.dp_size == 1
), "interleave DSA CP does not support DP attention."
assert (
cfg.tp_size <= 8
), "Context parallel only supports single machine (tp_size <= 8). Cross-machine CP has precision issues."
# Note(kpham-sgl): Keep attn_tp_size == 1 under DSA CP.
# DSACPLayerCommunicator does not all-reduce attention-TP
# partial o_proj outputs before replicated dense FFNs.
attn_cp_size = cfg.tp_size // cfg.dp_size
overrides["attn_cp_size"] = attn_cp_size
logger.warning(
"Enabled DSA context parallel: "
f"strategy={cfg.cp_strategy}, dp_size={cfg.dp_size}, "
f"moe_dense_tp_size={overrides['moe_dense_tp_size']}, "
f"ep_size={overrides.get('ep_size', cfg.ep_size)}, tp_size={cfg.tp_size}, "
f"attn_cp_size={attn_cp_size}, "
f"kv_cache_dtype={cfg.kv_cache_dtype}, "
f"moe_a2a_backend={overrides.get('moe_a2a_backend', cfg.moe_a2a_backend)}, "
f"cuda_graph_config[prefill].backend=disabled"
)
# Deferred import to avoid a circular import at module-load
# time (dsa.utils imports the runtime-context accessors).
from sglang.srt.layers.attention.dsa.utils import (
aiter_can_use_preshuffle_paged_mqa,
)
if get_platform().is_hip and not aiter_can_use_preshuffle_paged_mqa():
# Legacy ROCm DSA path: aiter's gluon paged-MQA kernel is
# unavailable (Triton<3.5 and AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS
# not set, or SGLANG_DSA_HIP_DISABLE_PRESHUFFLE=1 / SGLANG_USE_AITER=0).
overrides["page_size"] = 1
logger.warning(
"Setting page size to 1 for DeepSeek DSA on ROCm "
"(aiter preshuffle paged-MQA path unavailable: "
"needs Triton>=3.5.0 or AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS=1)."
)
else:
overrides["page_size"] = 64
logger.warning("Setting page size to 64 for DeepSeek DSA.")
else:
# DeepSeek V3/R1/V3.1
if get_platform().is_sm100:
if (
cfg.attention_backend is None
and cfg.prefill_attention_backend is None
and cfg.decode_attention_backend is None
):
overrides["attention_backend"] = "trtllm_mla"
logger.info(
"Use trtllm_mla as attention backend on sm100 for DeepseekV3ForCausalLM"
)
# MLA prefill CP auto-config. Mirrors the NSA CP block above
# (minus the in-seq/round-robin mode split, which MLA CP does not support)
if cfg.enable_prefill_cp and use_mla_backend(server_args):
logger.warning(
"MLA prefill context parallel is still experimental. "
"Verified on Hopper with the fa3 backend."
)
overrides["enable_dp_attention"] = True
# TODO(kpham-sgl) Supports moe_dense_tp_size != 1.
overrides["moe_dense_tp_size"] = 1
overrides["moe_a2a_backend"] = "deepep"
overrides["ep_size"] = cfg.tp_size
logger.warning(
"For MLA CP, we have the following restrictions: moe_dense_tp_size == 1, moe_a2a_backend == deepep, ep_size == tp_size, batch_size == 1"
)
# FIXME(kpham-sgl): Keep attn_tp_size == 1 under MLA CP.
# DSACPLayerCommunicator does not all-reduce attention-TP
# partial o_proj outputs before replicated dense FFNs.
attn_cp_size = cfg.tp_size // cfg.dp_size
overrides["attn_cp_size"] = attn_cp_size
logger.warning(
f"Enable Context Parallel opt for MLA, "
f"Setting dp_size == {cfg.dp_size} and "
f"attn_cp_size == {attn_cp_size}, "
f"moe_dense_tp_size == {overrides['moe_dense_tp_size']}, "
f"ep_size == {overrides['ep_size']}, "
f"tp_size == {cfg.tp_size}, "
f"moe_a2a_backend {overrides['moe_a2a_backend']}, "
f"cuda_graph_config[prefill].backend=disabled"
)
return overrides
@@ -0,0 +1,76 @@
"""Config-time override declarations for deepseek_v4.
Architectures: DeepseekV4ForCausalLM.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
model_config_of,
resolving_view,
)
from sglang.srt.environ import envs
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("DeepseekV4ForCausalLM")
def _deepseek_v4_overrides(server_args: Any, hf_config: Any) -> dict:
"""DeepSeek V4 attention/page/window/MoE-runner defaults (from
arg_groups/deepseek_v4_hook.py). The kv-cache dtype and NPU split-backend
writes, the max_running_requests fill and the validations stay in the
hook at its legacy slot."""
cfg = resolving_view(server_args)
from sglang.srt.server_args import ServerArgs
model_arch = hf_config.architectures[0]
overrides: Dict[str, Any] = {"attention_backend": "dsv4"}
page_size = 256
if cfg.device == "npu":
# NPU keeps the device-aware "dsv4" backend (the registry routes it to
# the Ascend V4 subclass); only the pool geometry / dtype differ.
# set_default_server_args() pins all three backends to "ascend" for
# generic NPU models; override that here so V4 stays consistently on
# dsv4.
page_size = 128
overrides["prefill_attention_backend"] = "dsv4"
overrides["decode_attention_backend"] = "dsv4"
overrides["page_size"] = page_size
logger.info(
f"Use dsv4 attention backend for {model_arch}, setting page_size to {page_size}."
)
if cfg.swa_full_tokens_ratio == ServerArgs.swa_full_tokens_ratio:
overrides["swa_full_tokens_ratio"] = 0.1
logger.info(f"Setting swa_full_tokens_ratio to 0.1 for {model_arch}.")
if cfg.moe_runner_backend == "auto":
model_config = model_config_of(server_args)
# nvidia/DeepSeek-V4-Pro-NVFP4 uses the routed TRT-LLM runner.
if model_config.nvfp4_moe_meta is not None:
overrides["moe_runner_backend"] = "flashinfer_trtllm_routed"
logger.info(
"Use flashinfer_trtllm_routed as MoE runner backend for "
f"{model_arch} hybrid FP8+NVFP4 checkpoint."
)
elif (
cfg.device == "cuda"
and not get_platform().is_hip
and cfg.moe_a2a_backend == "none"
and not envs.SGLANG_DSV4_FP4_DEQUANT.get()
and model_config.is_fp4_experts
and (
get_platform().is_sm90
or get_platform().is_sm100
or get_platform().is_sm120
)
):
overrides["moe_runner_backend"] = "flashinfer_mxfp4"
logger.info(
"Use flashinfer_mxfp4 as MoE runner backend for " f"{model_arch}."
)
return overrides
@@ -0,0 +1,23 @@
"""Config-time override declarations for exaone.
Architectures: Exaone4ForCausalLM, ExaoneMoEForCausalLM.
"""
import logging
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
)
logger = logging.getLogger(__name__)
@_register_for("Exaone4ForCausalLM", "ExaoneMoEForCausalLM")
def _exaone_overrides(server_args: Any, hf_config: Any) -> dict:
if hf_config.sliding_window_pattern is not None:
logger.warning(
f"Disabling hybrid SWA memory for {hf_config.architectures[0]} as it is not yet supported."
)
return {"disable_hybrid_swa_memory": True}
return {}
@@ -0,0 +1,22 @@
"""Config-time override declarations for falcon_h1.
Architectures: FalconH1ForCausalLM, JetNemotronForCausalLM, JetVLMForConditionalGeneration.
"""
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
@_register_for(
"FalconH1ForCausalLM", "JetNemotronForCausalLM", "JetVLMForConditionalGeneration"
)
def _falcon_h1_jet_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if get_platform().is_sm100 and cfg.attention_backend is None:
return {"attention_backend": "triton"}
return {}
@@ -0,0 +1,29 @@
"""Config-time override declarations for gemma2_gemma3.
Architectures: Gemma2ForCausalLM, Gemma3ForCausalLM, Gemma3ForConditionalGeneration, Gemma3nForCausalLM, Gemma3nForConditionalGeneration.
"""
import logging
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
)
logger = logging.getLogger(__name__)
@_register_for(
"Gemma2ForCausalLM",
"Gemma3ForCausalLM",
"Gemma3ForConditionalGeneration",
"Gemma3nForCausalLM",
"Gemma3nForConditionalGeneration",
)
def _gemma2_gemma3_overrides(server_args: Any, hf_config: Any) -> dict:
# FIXME: https://github.com/sgl-project/sglang/pull/7367 is not compatible with gemma2 model.
# It failed at this test: https://github.com/sgl-project/sglang/actions/runs/16255155597/job/45890331952#step:4:736
logger.warning(
f"Disable hybrid SWA memory for {hf_config.architectures[0]} as it is not yet supported."
)
return {"disable_hybrid_swa_memory": True}
@@ -0,0 +1,47 @@
"""Config-time override declarations for gemma4.
Architectures: Gemma4ForCausalLM, Gemma4ForConditionalGeneration, Gemma4UnifiedForConditionalGeneration.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
model_config_of,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for(
"Gemma4ForConditionalGeneration",
"Gemma4ForCausalLM",
"Gemma4UnifiedForConditionalGeneration",
)
def _gemma4_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
default_attention_backend = "trtllm_mha" if get_platform().is_sm100 else "triton"
if is_attention_backend_not_set(cfg):
logger.info(
f"Use {default_attention_backend} as default attention backend for Gemma4"
)
overrides["attention_backend"] = default_attention_backend
# If only one split backend is set, keep the other side on a
# Gemma4-compatible fallback instead of letting generic backend selection
# choose an unsupported backend later.
elif cfg.attention_backend is None:
overrides["attention_backend"] = default_attention_backend
if get_platform().is_sm100 and cfg.moe_runner_backend == "auto":
if model_config_of(server_args).quantization == "modelopt_fp4":
overrides["quantization"] = "modelopt_fp4"
overrides["moe_runner_backend"] = "flashinfer_trtllm"
logger.info(
"Use flashinfer_trtllm as MoE runner backend on "
"SM100 for Gemma-4 (modelopt_fp4)"
)
return overrides
@@ -0,0 +1,50 @@
"""Config-time override declarations for glm4_moe.
Architectures: Glm4MoeForCausalLM.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("Glm4MoeForCausalLM")
def _glm4_moe_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
if get_platform().is_sm100:
quantization_config = getattr(hf_config, "quantization_config", None)
quant_method = (
quantization_config.get("quant_method")
if quantization_config is not None
else None
)
quantization = cfg.quantization
if (
quantization is None
and not server_args._quantization_explicitly_unset
and quant_method is not None
):
overrides["quantization"] = quant_method
quantization = quant_method
if (
quantization in {"modelopt_fp4", None}
and cfg.moe_a2a_backend == "none"
and cfg.moe_runner_backend == "auto"
):
overrides["moe_runner_backend"] = "flashinfer_trtllm"
logger.info(
"Use flashinfer_trtllm as MoE runner backend on sm100 for Glm4MoeForCausalLM"
)
logger.info(
"Enable TF32 matmul for Glm4MoeForCausalLM model to improve gate gemm performance."
)
overrides["enable_tf32_matmul"] = True
return overrides
@@ -0,0 +1,127 @@
"""Config-time override declarations for gpt_oss.
Architectures: GptOssForCausalLM.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
resolving_view,
)
from sglang.srt.environ import envs
from sglang.srt.hardware_backend.mlx.runtime import use_mlx
from sglang.srt.runtime_context import get_platform
from sglang.srt.utils.common import (
get_nvidia_driver_version,
is_cpu,
is_mps,
is_triton_kernels_available,
)
logger = logging.getLogger(__name__)
@_register_for("GptOssForCausalLM")
def _gpt_oss_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
# Set attention backend for GPT-OSS
if is_attention_backend_not_set(cfg):
if get_platform().is_sm100:
overrides["attention_backend"] = "trtllm_mha"
elif get_platform().is_sm90:
overrides["attention_backend"] = "fa3"
elif is_cpu() and get_platform().has_amx:
overrides["attention_backend"] = "intel_amx"
elif get_platform().is_xpu:
overrides["attention_backend"] = "intel_xpu"
elif get_platform().is_hip:
overrides["attention_backend"] = "aiter"
elif not (is_mps() and use_mlx()):
# Exempt MLX only -- it owns attention in its own runner. macOS
# without MLX still falls through to triton and fails fast below,
# rather than landing on torch_native (no sliding window, no sinks).
overrides["attention_backend"] = "triton"
if get_platform().is_xpu:
# Check for bf16 dtype on Intel XPU. Reads the pristine dtype request,
# which equals the legacy mid-branch read: dtype had no earlier writer
# for this arch.
if cfg.dtype == "auto":
logger.warning(
"GptOssForCausalLM on Intel XPU currently supports bfloat16 dtype only"
)
elif cfg.dtype not in ["bfloat16"]:
raise NotImplementedError(
f"GptOssForCausalLM on Intel XPU only supports bfloat16 dtype, "
f"but got '{cfg.dtype}'. Please use --dtype bfloat16 or remove --dtype to use auto."
)
quantization_config = getattr(hf_config, "quantization_config", None)
is_mxfp4_quant_format = (
quantization_config is not None
and quantization_config.get("quant_method") == "mxfp4"
)
if is_mxfp4_quant_format:
# use bf16 for mxfp4 triton kernels
overrides["dtype"] = "bfloat16"
if cfg.moe_runner_backend == "auto":
if get_platform().is_sm100 and is_mxfp4_quant_format:
overrides["moe_runner_backend"] = "flashinfer_mxfp4"
logger.warning(
"Detected SM100 and MXFP4 quantization format for GPT-OSS model, enabling FlashInfer MXFP4 MOE kernel."
)
elif get_platform().is_sm120 and is_mxfp4_quant_format:
overrides["moe_runner_backend"] = "flashinfer_mxfp4"
logger.warning(
"Detected SM120 and MXFP4 quantization format for GPT-OSS model, "
"enabling FlashInfer CUTLASS MXFP4 MOE kernel."
)
elif (
get_platform().is_hip and envs.SGLANG_USE_AITER.get()
) and is_mxfp4_quant_format:
overrides["moe_runner_backend"] = "auto"
logger.warning(
"Detected ROCm and MXFP4 quantization format for GPT-OSS model, enabling aiter MXFP4 MOE kernel."
)
## The AITER MXFP4 fused-MoE path for GPT-OSS expects the
## SEPARATED gate/up tile layout (matches the
## `gptoss_fp4_tuned_fmoe.csv` flydsl entries and the
## Mxfp4MoEMethod weight shuffle). Other AITER MXFP4
## callers default to INTERLEAVE; opt this path out
## unless the user explicitly overrode it.
# envs.SGLANG_USE_AITER_MOE_GU_ITLV.set(False)
elif get_platform().is_hip and envs.SGLANG_USE_AITER.get():
# For GPT-OSS bf16 on ROCm with aiter, use triton backend
# because aiter CK kernel doesn't support all GEMM dimensions
overrides["moe_runner_backend"] = "triton"
logger.warning(
"Detected ROCm with SGLANG_USE_AITER for GPT-OSS bf16 model, using triton MOE kernel."
)
elif get_platform().is_musa and envs.SGLANG_DEEPEP_BF16_DISPATCH.get():
overrides["moe_runner_backend"] = "deep_gemm"
logger.warning(
"Detected MUSA with SGLANG_DEEPEP_BF16_DISPATCH for bf16 model, using deep_gemm kernel."
)
elif (
cfg.ep_size == 1
and is_triton_kernels_available()
and cfg.quantization is None
and not (is_cpu() and get_platform().has_amx)
):
# The triton_kernels package segfaults on Blackwell (B200)
# with NVIDIA driver >= 595. Fall back to triton backend.
if get_platform().is_blackwell and get_nvidia_driver_version() >= (595,):
overrides["moe_runner_backend"] = "triton"
logger.warning(
"Detected GPT-OSS model on Blackwell with driver >= 595, "
"using triton MOE kernel to avoid triton_kernels SIGSEGV."
)
else:
overrides["moe_runner_backend"] = "triton_kernel"
logger.warning(
"Detected GPT-OSS model, enabling triton_kernels MOE kernel."
)
return overrides
@@ -0,0 +1,23 @@
"""Config-time override declarations for granitemoehybrid.
Architectures: GraniteMoeHybridForCausalLM.
"""
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
@_register_for("GraniteMoeHybridForCausalLM")
def _granite_moe_hybrid_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
has_mamba = any(
layer_type == "mamba" for layer_type in getattr(hf_config, "layer_types", [])
)
if has_mamba and get_platform().is_sm100 and cfg.attention_backend is None:
return {"attention_backend": "flashinfer"}
return {}
@@ -0,0 +1,76 @@
"""Config-time override declarations for inkling.
Architectures: InklingForConditionalGeneration, InklingForConditionalGenerationMTP.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
resolving_view,
)
from sglang.srt.environ import envs
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for(
"InklingForConditionalGeneration",
"InklingForConditionalGenerationMTP",
)
def _inkling_overrides(server_args: Any, hf_config: Any) -> dict:
"""Inkling architecture defaults: SWA / mamba KV-pool ratios tuned for the
hybrid-SWA layout, the extra-buffer mamba strategy, and the unified radix
tree (which Inkling requires — models/inkling.py asserts it). The full-graph
prefill default is set separately (inline, before cuda-graph resolution) —
see ServerArgs.__post_init__ / _apply_inkling_prefill_cuda_graph_default. The
server-arg defaults each yield to an explicit user value (compared against
the ServerArgs class default); the prefill declaration is materialized
before _parse_cuda_graph_config folds cuda_graph_backend_prefill into
prefill.backend, and an explicit --cuda-graph-backend-prefill /
--disable-prefill-cuda-graph still wins. The unified-radix env write follows
the MiniMax-M3 handler precedent (env is not a resolvable server-arg)."""
cfg = resolving_view(server_args)
from sglang.srt.server_args import ServerArgs
overrides: Dict[str, Any] = {}
# NOTE: the full-graph prefill default is NOT set here. cuda-graph config is
# resolved in __post_init__ before declarations are materialized, so a
# cuda_graph_backend_prefill declared here lands too late (the breakable
# default would already have been auto-disabled for this multimodal arch).
# It is set inline before _handle_cuda_graph_config instead.
if cfg.swa_full_tokens_ratio == ServerArgs.swa_full_tokens_ratio:
overrides["swa_full_tokens_ratio"] = 0.1
if cfg.mamba_full_memory_ratio == ServerArgs.mamba_full_memory_ratio:
overrides["mamba_full_memory_ratio"] = 0.1
# Inkling requires the extra-buffer mamba strategy (inkling.py asserts
# enable_mamba_extra_buffer()); the generic "auto" resolution does not cover
# Inkling, so pin it here. Yields to an explicit --mamba-scheduler-strategy.
#
# The default comparison answers "unset" only while nothing has declared the
# field first. `_mamba_radix_cache_resolution` would, from the slot just
# above `collect_model_override_declarations`, for an architecture whose
# linear-attention spec sets `uses_mamba_radix_cache`. Inkling has no such
# spec; giving it one silently stops this pin from firing, so compare
# against the unresolved token ("auto") if that day comes.
if cfg.mamba_radix_cache_strategy == ServerArgs.mamba_radix_cache_strategy:
overrides["mamba_radix_cache_strategy"] = "extra_buffer"
# Inkling attention runs only on the fa4 (Blackwell) or triton backends --
# models/inkling_common/attn.py asserts attention_backend in {fa4, triton}.
# The generic resolver would otherwise pick trtllm_mha (SM100) / fa3
# (Hopper), so a bare launch fails on the first attention forward. Pin a
# supported default when the user left every attention-backend flag unset
# (mirrors the MiniMax-M3 SM100 fa4-default above); an explicit
# --attention-backend / --prefill/decode-attention-backend still wins.
if is_attention_backend_not_set(cfg):
inkling_attn_backend = "fa4" if get_platform().is_sm100 else "triton"
overrides["attention_backend"] = inkling_attn_backend
logger.info(
f"Use {inkling_attn_backend} as the attention backend for Inkling "
"(requires fa4 or triton)."
)
envs.SGLANG_ENABLE_UNIFIED_RADIX_TREE.set(True)
return overrides
@@ -0,0 +1,20 @@
"""Config-time override declarations for interns2_mobius.
Architectures: InternS2MobiusForConditionalGeneration.
"""
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
@_register_for("InternS2MobiusForConditionalGeneration")
def _interns2_mobius_baseline_overrides(server_args: Any, hf_config: Any) -> dict:
"""Select the only MoE runner validated for the 2,560-expert baseline."""
cfg = resolving_view(server_args)
if cfg.moe_runner_backend == "auto":
return {"moe_runner_backend": "triton_kernel"}
return {}
@@ -0,0 +1,191 @@
"""Config-time override declarations for kimi_k3.
Architectures: KimiK3ForConditionalGeneration.
"""
import inspect
import logging
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_dspark_verify_on_decode_backend,
_is_mxfp4_pack_quantized,
_register_for,
attention_backends_of,
is_attention_backend_not_set,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
from sglang.srt.utils.common import get_device_name, is_mnnvl_fabric_device
logger = logging.getLogger(__name__)
def _require_kimi_k3_cutedsl_dcp_support() -> None:
try:
from flashinfer.decode import trtllm_batch_decode_with_kv_cache_mla
parameters = inspect.signature(trtllm_batch_decode_with_kv_cache_mla).parameters
except (ImportError, TypeError, ValueError) as exc:
raise RuntimeError(
"Kimi-K3 DCP with decode_attention_backend='cutedsl_mla' requires "
"FlashInfer 0.6.17 or newer with "
"trtllm_batch_decode_with_kv_cache_mla exposing enable_dcp."
) from exc
if "enable_dcp" not in parameters:
raise RuntimeError(
"Kimi-K3 DCP with decode_attention_backend='cutedsl_mla' requires "
"enable_dcp in the signature of "
"flashinfer.decode.trtllm_batch_decode_with_kv_cache_mla; upgrade "
"to FlashInfer 0.6.17 or newer."
)
@_register_for("KimiK3ForConditionalGeneration")
def _kimi_k3_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if cfg.dcp_size > 1:
overrides = {}
if cfg.enable_symm_mem:
logger.warning(
"Kimi-K3 DCP disables --enable-symm-mem due to decode CUDA "
"graph correctness issues."
)
overrides["enable_symm_mem"] = False
if cfg.speculative_algorithm == "DSPARK":
from sglang.srt.speculative.ragged_verify import (
RaggedVerifyMode,
read_ragged_verify_mode,
)
ragged_mode = read_ragged_verify_mode()
if ragged_mode is not RaggedVerifyMode.STATIC:
raise ValueError(
"Kimi-K3 DCP + DSPARK currently requires "
"SGLANG_RAGGED_VERIFY_MODE=static; compact/cap-accept are "
f"not validated under DCP (got {ragged_mode.value!r})."
)
# DSPARK target-verify + draft-extend must run on the decode
# (cutedsl_mla) backend, whose _run_decode_kernel implements the DCP
# signature (causal_seqs / cp_world / cp_rank). The default
# "prefill" routes verify to trtllm_mla, whose base _run_decode_kernel
# lacks that DCP path (TypeError: unexpected kwarg 'causal_seqs').
overrides["speculative_attention_mode"] = "decode"
prefill_backend, decode_backend = attention_backends_of(cfg)
if decode_backend == "cutedsl_mla" or decode_backend is None:
_require_kimi_k3_cutedsl_dcp_support()
logger.info(
"Kimi-K3 DCP keeps decode attention backend 'cutedsl_mla' "
f"(prefill={prefill_backend!r} -> 'trtllm_mla')."
)
overrides.update(
prefill_attention_backend="trtllm_mla",
decode_attention_backend="cutedsl_mla",
)
elif decode_backend == "tokenspeed_mla":
logger.info(
"Kimi-K3 DCP overrides attention backends: "
f"prefill={prefill_backend!r}, decode={decode_backend!r} -> "
"'tokenspeed_mla'."
)
logger.info(
"Kimi-K3 DCP with tokenspeed mla backend overrides KV cache dtype: "
f"{cfg.kv_cache_dtype!r} -> 'fp8_e4m3'."
)
overrides.update(
prefill_attention_backend="tokenspeed_mla",
decode_attention_backend="tokenspeed_mla",
kv_cache_dtype="fp8_e4m3",
)
else:
raise AssertionError(
f"Decode attention backend for Kimi-K3 DCP must be 'cutedsl_mla' or 'tokenspeed_mla', got {decode_backend!r}."
)
if cfg.dcp_replicate_q_proj is None:
logger.info("Kimi-K3 DCP enables replicated Q projection by default.")
overrides["dcp_replicate_q_proj"] = True
device_name = get_device_name()
dcp_comm_backend = "fi_a2a" if is_mnnvl_fabric_device() else "a2a"
logger.info(
"Kimi-K3 DCP selects communication backend on "
f"{device_name!r}: {cfg.dcp_comm_backend!r} -> "
f"{dcp_comm_backend!r}."
)
overrides["dcp_comm_backend"] = dcp_comm_backend
return overrides
if not (get_platform().is_sm100 and get_platform().device_sm in (100, 103)):
return {}
backends_unset = is_attention_backend_not_set(cfg)
if cfg.speculative_algorithm != "DSPARK":
if not backends_unset:
return {}
logger.info(
"Use trtllm_mla as the default prefill and decode attention "
"backend for Kimi-K3 on SM100/SM103."
)
return {
"decode_attention_backend": "trtllm_mla",
"prefill_attention_backend": "trtllm_mla",
}
# DSPARK: verify runs on the decode backend (mode=decode below), so this
# picks the verify kernel -- mode=prefill routes it to flashinfer, which is
# slow and syncs, while plain decode is cold under dspark.
q_len = cfg.speculative_num_draft_tokens or (
cfg.speculative_dspark_block_size + 1
if cfg.speculative_dspark_block_size is not None
# Checkpoint auto-infer happens after overrides; K3 draft uses block 7.
else 8
)
overrides = {}
if backends_unset:
backend = "trtllm_mla"
overrides["decode_attention_backend"] = backend
overrides["prefill_attention_backend"] = "trtllm_mla"
else:
# Explicit backend knobs keep priority, but the mode is a separate knob
# that still needs declaring -- else verify stays on the prefill backend,
# whose host-side plan (flashinfer by default) forces a per-step D2H.
_, backend = attention_backends_of(cfg)
if _dspark_verify_on_decode_backend(backend, q_len, cfg.kv_cache_dtype):
overrides["speculative_attention_mode"] = "decode"
logger.info(
"Kimi-K3 DSPARK on SM100/SM103: decode/verify attention backend "
f"{backend} (speculative_attention_mode=decode)."
)
else:
logger.warning(
f"Kimi-K3 DSPARK: decode attention backend {backend!r} cannot serve "
f"target verify at q_len={q_len}, so verify runs on the prefill "
"backend (speculative_attention_mode=prefill). A host-plan prefill "
"backend costs a per-step seq_lens D2H sync; leave the attention "
"backend knobs unset for the sync-free default."
)
return overrides
@_register_for("KimiK3ForConditionalGeneration")
def _kimi_k3_moe_runner_overrides(server_args: Any, hf_config: Any) -> dict:
# MoE runner default, independent of the attention-backend gate above.
# trtllm-gen fused MoE (flashinfer_mxfp4) beats marlin on both the decode
# (M=bs) and the target-verify (M=bs*(gamma+1)) regimes on SM100/SM103.
# SM107 uses the same packed-MXFP4 runner; leaving auto unresolved falls
# back to BF16 weight materialization during model loading.
cfg = resolving_view(server_args)
if cfg.moe_runner_backend != "auto":
return {}
if not (get_platform().is_sm100 and get_platform().device_sm in (100, 103, 107)):
return {}
if not _is_mxfp4_pack_quantized(hf_config):
return {}
logger.info(
"Kimi-K3 on SM100/SM103/SM107: moe_runner_backend=flashinfer_mxfp4 "
"(FlashInfer SiTU kernels)."
)
return {"moe_runner_backend": "flashinfer_mxfp4"}
@@ -0,0 +1,20 @@
"""Config-time override declarations for lfm2.
Architectures: Lfm2ForCausalLM, Lfm2MoeForCausalLM.
"""
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
@_register_for("Lfm2ForCausalLM", "Lfm2MoeForCausalLM")
def _lfm2_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if get_platform().is_sm100 and cfg.attention_backend is None:
return {"attention_backend": "flashinfer"}
return {}
@@ -0,0 +1,47 @@
"""Config-time override declarations for llama4.
Architectures: Llama4ForCausalLM, Llama4ForConditionalGeneration.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
# Keep in sync with LLAMA4_MODEL_ARCHS (server_args.py).
@_register_for("Llama4ForConditionalGeneration", "Llama4ForCausalLM")
def _llama4_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if cfg.device == "cpu":
return {}
overrides: Dict[str, Any] = {}
# Auto-select attention backend for Llama4 if not specified
if cfg.attention_backend is None:
if get_platform().is_sm100:
backend, platform = "trtllm_mha", "sm100"
elif get_platform().is_sm90:
backend, platform = "fa3", "sm90"
elif get_platform().is_hip:
backend, platform = "aiter", "hip"
elif cfg.device == "xpu":
backend, platform = "intel_xpu", "xpu"
else:
backend, platform = "triton", "other platforms"
logger.warning(
f"Use {backend} as attention backend on {platform} for Llama4 model"
)
overrides["attention_backend"] = backend
if get_platform().is_sm100 and cfg.moe_runner_backend == "auto":
if cfg.quantization in {"fp8", "modelopt_fp8"}:
overrides["moe_runner_backend"] = "flashinfer_trtllm"
logger.info(
"Use flashinfer_trtllm as MoE runner backend on SM100 for Llama4"
)
return overrides
@@ -0,0 +1,37 @@
"""Config-time override declarations for mimo_v2.
Architectures: MiMoV2FlashForCausalLM, MiMoV2ForCausalLM.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
from sglang.srt.utils.common import get_quantization_config
logger = logging.getLogger(__name__)
# Keep in sync with MIMO_V2_MODEL_ARCHS (server_args.py / configs/hf_config.py).
@_register_for("MiMoV2ForCausalLM", "MiMoV2FlashForCausalLM")
def _mimo_v2_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
if cfg.speculative_algorithm == "EAGLE":
logger.info("Enable multi-layer EAGLE speculative decoding for MiMoV2 model.")
overrides["enable_multi_layer_eagle"] = True
# On Blackwell "auto" falls through to the triton fused-MoE runner, ~12%
# slower at bs=1 decode. FP4 checkpoints use flashinfer_mxfp4 instead.
if (
get_platform().is_sm100
and cfg.moe_runner_backend == "auto"
and get_quantization_config(hf_config) == "fp8"
):
overrides["moe_runner_backend"] = "flashinfer_trtllm"
logger.info("MiMoV2 FP8 on SM100: moe_runner_backend=flashinfer_trtllm.")
return overrides
@@ -0,0 +1,66 @@
"""Config-time override declarations for minicpm.
Architectures: MiniCPMForCausalLM, MiniCPMSALAForCausalLM.
"""
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
resolving_view,
)
from sglang.srt.environ import envs
from sglang.srt.runtime_context import get_platform
@_register_for("MiniCPMForCausalLM", "MiniCPMSALAForCausalLM")
def _minicpm_sala_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if cfg.enable_dp_attention:
raise ValueError("MiniCPM does not support DP attention")
has_sparse_attention = getattr(hf_config, "has_minicpm_sparse_attention", False)
has_hybrid_attention = has_sparse_attention or getattr(
hf_config, "has_lightning_layers", False
)
overrides: Dict[str, Any] = {}
if has_hybrid_attention:
if cfg.enable_hierarchical_cache:
raise ValueError("MiniCPM SALA does not support hierarchical cache")
overrides["disable_radix_cache"] = True
if envs.SGLANG_MINICPM_FORCE_DENSE.get():
dense_backends = {
"minicpm_flashattn": ("fa4" if get_platform().is_blackwell else "fa3"),
"minicpm_flashinfer": "flashinfer",
}
# Literal keys keep the written-field set statically derivable; a loop
# variable hides it from the census in test_chain_read_ratchet.py.
dense_attention = dense_backends.get(cfg.attention_backend)
if dense_attention is not None:
overrides["attention_backend"] = dense_attention
dense_prefill = dense_backends.get(cfg.prefill_attention_backend)
if dense_prefill is not None:
overrides["prefill_attention_backend"] = dense_prefill
dense_decode = dense_backends.get(cfg.decode_attention_backend)
if dense_decode is not None:
overrides["decode_attention_backend"] = dense_decode
elif has_sparse_attention:
uses_sparse_backend = is_attention_backend_not_set(cfg) or any(
backend in ("minicpm_flashattn", "minicpm_flashinfer")
for backend in (
cfg.attention_backend,
cfg.prefill_attention_backend,
cfg.decode_attention_backend,
)
)
if uses_sparse_backend and cfg.disaggregation_mode != "null":
raise ValueError(
"MiniCPM sparse attention does not support PD disaggregation"
)
if is_attention_backend_not_set(cfg):
overrides["attention_backend"] = (
"minicpm_flashinfer"
if get_platform().is_blackwell
else "minicpm_flashattn"
)
return overrides
@@ -0,0 +1,20 @@
"""Config-time override declarations for minicpmv.
Architectures: MiniCPMV4_6ForConditionalGeneration.
"""
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
@_register_for("MiniCPMV4_6ForConditionalGeneration")
def _minicpm_v4_6_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if get_platform().is_sm100 and cfg.attention_backend is None:
return {"attention_backend": "triton"}
return {}
@@ -0,0 +1,36 @@
"""Config-time override declarations for minimax_m2.
Architectures: MiniMaxM2ForCausalLM.
"""
import logging
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
model_config_of,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("MiniMaxM2ForCausalLM")
def _minimax_m2_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides = {"enable_tf32_matmul": True}
logger.info(
"Enable TF32 matmul for MiniMaxM2ForCausalLM model to improve gate gemm performance."
)
if (
get_platform().is_sm100
and cfg.moe_runner_backend == "auto"
and model_config_of(server_args).quantization == "modelopt_fp4"
):
overrides["moe_runner_backend"] = "flashinfer_trtllm_routed"
logger.info(
"Use flashinfer_trtllm_routed as MoE runner backend on SM10X "
"for MiniMaxM2ForCausalLM with modelopt_fp4."
)
return overrides
@@ -0,0 +1,150 @@
"""Config-time override declarations for minimax_m3.
Architectures: MiniMaxM3SparseForCausalLM, MiniMaxM3SparseForConditionalGeneration.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
resolving_view,
)
from sglang.srt.environ import envs
from sglang.srt.runtime_context import get_platform
from sglang.srt.utils.common import get_quantization_config
logger = logging.getLogger(__name__)
@_register_for("MiniMaxM3SparseForCausalLM", "MiniMaxM3SparseForConditionalGeneration")
def _minimax_m3_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
quant_method = get_quantization_config(hf_config)
quant_resolved = cfg.quantization
if (
quant_resolved is None
and not server_args._quantization_explicitly_unset
and quant_method is not None
):
overrides["quantization"] = quant_method
quant_resolved = quant_method
if get_platform().is_hip:
if is_attention_backend_not_set(cfg):
overrides["attention_backend"] = "triton"
if cfg.moe_runner_backend == "auto" and quant_resolved == "mxfp8":
overrides["moe_runner_backend"] = "triton"
if not envs.USE_ROCM_AITER_ROPE_BACKEND.is_set():
envs.USE_ROCM_AITER_ROPE_BACKEND.set("0")
aiter_fusion_resolved = cfg.enable_aiter_allreduce_fusion
if cfg.ep_size > 1 and cfg.moe_a2a_backend == "none" and aiter_fusion_resolved:
logger.warning(
"Disable --enable-aiter-allreduce-fusion for MiniMax-M3 "
"standard EP on ROCm because the deferred fused all-reduce "
"corrupts sparse MoE partial outputs."
)
overrides["enable_aiter_allreduce_fusion"] = False
aiter_fusion_resolved = False
# By default MiniMax-M3 on ROCm keeps NCCL all-reduce (custom AR off)
# whenever aiter all-reduce fusion is not used. Opting in via
# SGLANG_M3_ALLOW_CUSTOM_AR keeps custom all-reduce enabled so the
# quick-reduce path (ROCM_QUICK_REDUCE_QUANTIZATION=INT4/INT6/INT8) can
# accelerate the large prefill all-reduce.
if not aiter_fusion_resolved and not envs.SGLANG_M3_ALLOW_CUSTOM_AR.get():
overrides["disable_custom_all_reduce"] = True
elif get_platform().is_sm100:
if is_attention_backend_not_set(cfg):
if (
cfg.kv_cache_dtype == "fp8_e4m3"
and not envs.SGLANG_DISABLE_M3_FP8_ATTN_GEMM.get()
):
# fp8 attention GEMMs activate whenever possible
# (m3_fp8_attn_gemm_enabled); only trtllm_mha serves the dense
# fp8-q path, so prefer it over fa4 for fp8 KV. The
# SGLANG_DISABLE_M3_FP8_ATTN_GEMM kill switch keeps the fa4
# default (pre-fp8 behavior).
overrides["attention_backend"] = "trtllm_mha"
else:
overrides["attention_backend"] = "fa4"
backend_resolved = overrides.get("attention_backend", cfg.attention_backend)
page_resolved = cfg.page_size
# fa4 (fmha_sm100) and trtllm_mha both allow the page_size == 128
# sparse block MSA needs (trtllm_mha via trtllm-gen's dynamic
# tokens-per-page kernels).
if page_resolved is None and backend_resolved in ("fa4", "trtllm_mha"):
overrides["page_size"] = 128
page_resolved = 128
if cfg.moe_runner_backend == "auto" and quant_resolved == "mxfp8":
overrides["moe_runner_backend"] = "deep_gemm"
elif cfg.moe_runner_backend == "auto" and quant_resolved == "modelopt_mixed":
overrides["moe_runner_backend"] = "flashinfer_trtllm_routed"
logger.info(
"MiniMax-M3 on SM100: attention_backend="
f"{overrides.get('attention_backend', cfg.attention_backend)}, page_size={page_resolved}, "
f"moe_runner_backend={overrides.get('moe_runner_backend', cfg.moe_runner_backend)}."
)
elif get_platform().is_sm90:
if is_attention_backend_not_set(cfg):
overrides["attention_backend"] = "fa3"
page_resolved = cfg.page_size
if (
page_resolved is None
and overrides.get("attention_backend", cfg.attention_backend) == "fa3"
):
overrides["page_size"] = 128
page_resolved = 128
logger.info(
"MiniMax-M3 on Hopper: attention_backend="
f"{overrides.get('attention_backend', cfg.attention_backend)}, page_size={page_resolved} "
"(MSA is SM100-only; sparse attention runs on the Triton path)."
)
# fp8 attention GEMMs have no opt-in flag: m3_fp8_attn_gemm_enabled
# (server_args.py) derives the mode from kv_cache_dtype (fp8_e4m3) +
# attention_backend (trtllm_mha) + SM100 at runtime. Surface the
# resolution here: warn on fp8_e5m2 (fmha_sm100's variant lookup would
# silently dispatch the e4m3 kernel, so e5m2 stays on the widening Triton
# path), log when the fp8 GEMM mode is active, and log when the
# SGLANG_DISABLE_M3_FP8_ATTN_GEMM kill switch suppresses it.
if cfg.kv_cache_dtype == "fp8_e5m2":
logger.warning(
"MiniMax-M3 with kv_cache_dtype fp8_e5m2: fp8 attention GEMMs stay "
"DISABLED (fmha_sm100's variant lookup would silently dispatch the "
"e4m3 kernel for e5m2); sparse attention runs on the widening "
"Triton path. Use --kv-cache-dtype fp8_e4m3 for fp8 attention GEMMs."
)
elif (
cfg.kv_cache_dtype == "fp8_e4m3"
and overrides.get("attention_backend", cfg.attention_backend) == "trtllm_mha"
and get_platform().is_sm100
):
if envs.SGLANG_DISABLE_M3_FP8_ATTN_GEMM.get():
logger.info(
"MiniMax-M3 fp8 attention GEMMs DISABLED by "
"SGLANG_DISABLE_M3_FP8_ATTN_GEMM: bf16 indexer + widening "
"Triton sparse path, bf16 q; dense layers keep trtllm_mha's "
"fp8 KV cache."
)
else:
logger.info(
"MiniMax-M3 fp8 attention GEMMs active (kv_cache_dtype fp8_e4m3 + "
"trtllm_mha on SM100): fp8 main/index KV, fp8-cast q, fp8 "
"sparse/MSA kernels. Set SGLANG_DISABLE_M3_FP8_ATTN_GEMM=1 to "
"force the pre-fp8 numerics."
)
moe_runner_resolved = overrides.get("moe_runner_backend", cfg.moe_runner_backend)
if quant_resolved is None and moe_runner_resolved in ("auto", "deep_gemm"):
if moe_runner_resolved == "deep_gemm":
logger.warning(
"MiniMax-M3: the deep_gemm MoE runner produces corrupted output "
"on bf16 full weights; overriding --moe-runner-backend to 'triton'."
)
overrides["moe_runner_backend"] = "triton"
return overrides
@@ -0,0 +1,34 @@
"""Config-time override declarations for moss_vl.
Architectures: MossVLForConditionalGeneration.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
attention_backends_of,
is_attention_backend_not_set,
resolved_view,
resolving_view,
)
logger = logging.getLogger(__name__)
@_register_for("MossVLForConditionalGeneration")
def _moss_vl_overrides(server_args: Any, hf_config: Any) -> dict:
overrides: Dict[str, Any] = {}
if is_attention_backend_not_set(resolving_view(server_args)):
overrides["prefill_attention_backend"] = "flashinfer"
logger.info("Use flashinfer as default prefill attention backend for Moss-VL")
prefill_backend = (
overrides.get("prefill_attention_backend")
or attention_backends_of(resolved_view(server_args))[0]
)
assert prefill_backend == "flashinfer", (
"MossVLForConditionalGeneration requires flashinfer prefill "
"attention backend for cross-attention custom mask support."
)
return overrides
@@ -0,0 +1,24 @@
"""Config-time override declarations for muse_glimmer.
Architectures: MuseGlimmerForCausalLM, MuseGlimmerForConditionalGeneration.
"""
import logging
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("MuseGlimmerForConditionalGeneration", "MuseGlimmerForCausalLM")
def _muse_glimmer_fp4_gemm_runner_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if get_platform().is_sm120 and cfg.fp4_gemm_runner_backend == "auto":
logger.info("Use marlin as FP4 GEMM runner backend on SM120 for Muse Glimmer")
return {"fp4_gemm_runner_backend": "marlin"}
return {}
@@ -0,0 +1,123 @@
"""Config-time override declarations for nemotron_h.
Architectures: NemotronHForCausalLM, NemotronHPuzzleForCausalLM.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
is_attention_backend_not_set,
model_config_of,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("NemotronHForCausalLM", "NemotronHPuzzleForCausalLM")
def _nemotron_h_overrides(server_args: Any, hf_config: Any) -> dict:
"""NemotronH quantization / MoE runner / attention backend defaults
(absorbed from the retired arg_groups/nemotron_h_hook.py; the mamba radix
cache handling and the triton-backend assert stay in the arch branch)."""
cfg = resolving_view(server_args)
model_arch = hf_config.architectures[0]
model_config = model_config_of(server_args)
overrides: Dict[str, Any] = {}
is_modelopt = model_config.quantization in [
"modelopt",
"modelopt_fp8",
"modelopt_fp4",
"modelopt_mixed",
]
quantization = cfg.quantization
if is_modelopt:
assert model_config.hf_config.mlp_hidden_act == "relu2"
if model_config.quantization == "modelopt":
quant_algo = model_config.hf_config.quantization_config["quant_algo"]
if quant_algo == "MIXED_PRECISION":
quantization = "modelopt_mixed"
else:
quantization = (
"modelopt_fp4" if quant_algo == "NVFP4" else "modelopt_fp8"
)
else:
quantization = model_config.quantization
overrides["quantization"] = quantization
has_w4a16_moe_layers = False
if is_modelopt and quantization == "modelopt_mixed":
has_w4a16_moe_layers = any(
info.get("quant_algo") == "W4A16_NVFP4" and ".experts." in name
for name, info in hf_config.quantization_config.get(
"quantized_layers", {}
).items()
)
if has_w4a16_moe_layers:
if cfg.moe_a2a_backend != "none":
raise ValueError("W4A16_NVFP4 MoE layers require --moe-a2a-backend=none.")
if cfg.moe_runner_backend not in ("auto", "marlin"):
raise ValueError(
"W4A16_NVFP4 MoE layers require --moe-runner-backend=marlin."
)
if cfg.moe_runner_backend == "auto":
overrides["moe_runner_backend"] = "marlin"
logger.info(
"Use marlin as MoE runner backend for "
f"{model_arch} with W4A16_NVFP4 MoE layers"
)
elif (is_modelopt or model_config.quantization is None) and (
cfg.moe_runner_backend == "auto"
):
if get_platform().is_sm100 and cfg.moe_a2a_backend == "none":
overrides["moe_runner_backend"] = "flashinfer_trtllm"
logger.info(
f"Use flashinfer_trtllm as MoE runner backend on sm100 for {model_arch}"
)
elif (
(
model_config.quantization in ("modelopt_fp4", "modelopt_mixed")
or quantization == "modelopt_fp4"
)
and get_platform().is_cuda
and (8, 0) <= get_platform().device_capability < (10, 0)
):
overrides["moe_runner_backend"] = "marlin"
logger.info(
"Use marlin as MoE runner backend on SM80-SM90 for "
f"{model_arch} {model_config.quantization}"
)
else:
overrides["moe_runner_backend"] = "flashinfer_cutlass"
if get_platform().is_blackwell and is_attention_backend_not_set(cfg):
if cfg.speculative_algorithm is not None:
speculative_algorithm = cfg.speculative_algorithm.upper()
if get_platform().is_sm100 and cfg.speculative_eagle_topk in (
None,
1,
):
overrides["attention_backend"] = "trtllm_mha"
if cfg.page_size is None:
overrides["page_size"] = 64
if cfg.mamba_radix_cache_strategy == "auto":
overrides["mamba_radix_cache_strategy"] = "extra_buffer"
if (
cfg.speculative_draft_attention_backend is None
and speculative_algorithm in ("EAGLE", "NEXTN", "DSPARK")
):
overrides["speculative_draft_attention_backend"] = "trtllm_mha"
else:
overrides["attention_backend"] = "triton"
if (
cfg.speculative_draft_attention_backend is None
and speculative_algorithm in ("EAGLE", "NEXTN", "DFLASH", "DSPARK")
):
overrides["speculative_draft_attention_backend"] = "flashinfer"
elif get_platform().is_sm100:
overrides["attention_backend"] = "trtllm_mha"
return overrides
@@ -0,0 +1,34 @@
"""Config-time override declarations for olmo2.
Architectures: Olmo2ForCausalLM.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("Olmo2ForCausalLM")
def _olmo2_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
# FIXME: https://github.com/sgl-project/sglang/pull/7367 is not compatible with Olmo3 model.
logger.warning(
f"Disabling hybrid SWA memory for {hf_config.architectures[0]} as it is not yet supported."
)
overrides["disable_hybrid_swa_memory"] = True
if cfg.attention_backend is None:
if get_platform().is_cuda and get_platform().is_sm100:
overrides["attention_backend"] = "trtllm_mha"
elif get_platform().is_cuda and get_platform().device_sm >= 80:
overrides["attention_backend"] = "fa3"
else:
overrides["attention_backend"] = "triton"
return overrides
@@ -0,0 +1,54 @@
"""Config-time override declarations for qwen3_5.
Architectures: InternS2MobiusForConditionalGeneration, InternS2PreviewForConditionalGeneration, Qwen3NextForCausalLM, Qwen3_5ForConditionalGeneration, Qwen3_5MoeForConditionalGeneration.
"""
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
get_default_attn_backend,
mamba_extra_buffer_of,
model_config_of,
resolved_view,
resolving_view,
use_mla_backend,
)
from sglang.srt.runtime_context import get_platform
@_register_for(
"Qwen3NextForCausalLM",
"Qwen3_5MoeForConditionalGeneration",
"InternS2PreviewForConditionalGeneration",
"InternS2MobiusForConditionalGeneration",
"Qwen3_5ForConditionalGeneration",
)
def _qwen3_5_hybrid_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if not get_platform().is_sm100 or cfg.attention_backend is not None:
return {}
sm100_default_attn_backend = "triton"
# trtllm_mha requires speculative_eagle_topk == 1 and page_size > 1.
# get_default_attn_backend handles the eagle_topk check.
# There is only one case where page_size=1 is required,
# which is when radix cache is enabled and both extra_buffer
# and spec decoding are disabled.
default_attn_backend = get_default_attn_backend(
server_args,
use_mla_backend=use_mla_backend(server_args),
model_config=model_config_of(server_args),
)
# The mamba radix-cache pass runs before this dispatch: read the
# declared strategy through the view (the legacy branch observed the
# already-written field here).
if default_attn_backend == "trtllm_mha" and not (
not mamba_extra_buffer_of(resolved_view(server_args))
and not cfg.disable_radix_cache
and cfg.speculative_algorithm is None
):
sm100_default_attn_backend = "trtllm_mha"
return {
"attention_backend": sm100_default_attn_backend,
"page_size": 64 if sm100_default_attn_backend == "trtllm_mha" else 1,
}
@@ -0,0 +1,50 @@
"""Config-time override declarations for qwen3_moe.
Architectures: InternS2PreviewForConditionalGeneration, Qwen3MoeForCausalLM, Qwen3NextForCausalLM, Qwen3VLMoeForConditionalGeneration, Qwen3_5ForConditionalGeneration, Qwen3_5MoeForConditionalGeneration.
"""
import logging
from typing import Any, Dict
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.runtime_context import get_platform
from sglang.srt.utils.common import get_quantization_config
logger = logging.getLogger(__name__)
@_register_for(
"Qwen3MoeForCausalLM",
"Qwen3VLMoeForConditionalGeneration",
"Qwen3NextForCausalLM",
"Qwen3_5MoeForConditionalGeneration",
"InternS2PreviewForConditionalGeneration",
"Qwen3_5ForConditionalGeneration",
)
def _qwen3_moe_family_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
overrides: Dict[str, Any] = {}
if get_platform().is_sm100:
quant_method = get_quantization_config(hf_config)
quantization = cfg.quantization
if (
quantization is None
and not server_args._quantization_explicitly_unset
and quant_method is not None
):
overrides["quantization"] = quant_method
quantization = quant_method
if (
(quantization in ("fp8", "modelopt_fp4") or quantization is None)
and cfg.moe_a2a_backend == "none"
and cfg.moe_runner_backend == "auto"
):
overrides["moe_runner_backend"] = "flashinfer_trtllm"
logger.info(
"Use flashinfer_trtllm as MoE runner backend on sm100 for "
f"{hf_config.architectures[0]}"
)
return overrides
@@ -0,0 +1,32 @@
"""Config-time override declarations for qwen3_vl.
Architectures: Qwen3VLForConditionalGeneration.
"""
import logging
from typing import Any
from sglang.srt.arg_groups.model_override_base import (
_register_for,
resolving_view,
)
from sglang.srt.environ import envs
from sglang.srt.runtime_context import get_platform
logger = logging.getLogger(__name__)
@_register_for("Qwen3VLForConditionalGeneration")
def _qwen3vl_overrides(server_args: Any, hf_config: Any) -> dict:
cfg = resolving_view(server_args)
if (
get_platform().is_hip
and envs.SGLANG_USE_AITER_UNIFIED_ATTN.get()
and cfg.page_size is None
):
logger.info(
"Setting page_size=16 for aiter unified attention on Qwen3VLForConditionalGeneration."
)
return {"page_size": 16}
return {}
File diff suppressed because it is too large Load Diff
@@ -910,3 +910,32 @@ def handle_multimodal_feature_transport(server_args: Any):
envs.SGLANG_USE_CUDA_IPC_TRANSPORT.set(
"1" if requested_transport == "cuda_ipc" else "0"
)
_ssl_verify_warned = False
def ssl_verify_of(cfg: Any):
"""What to pass as the requests library's ``verify=``.
A CA file means validate against it. SSL configured without one means
verification off -- self-signed certificates in development -- and that is
worth saying out loud, once. No SSL means the system CA bundle.
The warning is once per process: the message is about how this process was
configured, and a second engine repeating it says nothing new.
"""
global _ssl_verify_warned
if cfg.ssl_ca_certs:
return cfg.ssl_ca_certs
if cfg.ssl_certfile:
if not _ssl_verify_warned:
logger.warning(
"SSL is enabled but --ssl-ca-certs was not provided. Certificate "
"verification is DISABLED for internal health checks. For "
"production deployments, provide --ssl-ca-certs or use CA-signed "
"certificates."
)
_ssl_verify_warned = True
return False
return True
+3 -2
View File
@@ -483,6 +483,7 @@ from sglang.srt.entrypoints.v1_loads import router as v1_loads_router
v1_loads_router.route_class = ORJSONRoute
app.include_router(v1_loads_router)
from sglang.srt.arg_groups.serving_hook import ssl_verify_of
from sglang.srt.entrypoints.elastic_ep import router as elastic_ep_router
from sglang.srt.runtime_context import (
describe_kv_events_publisher,
@@ -2199,7 +2200,7 @@ def _execute_server_warmup(server_args: ServerArgs):
if server_args.api_key:
headers["Authorization"] = f"Bearer {server_args.api_key}"
ssl_verify = server_args.ssl_verify()
ssl_verify = ssl_verify_of(server_args)
# Wait until the server is launched
success = False
@@ -2375,7 +2376,7 @@ def _freeze_gc_after_server_warmup(server_args: ServerArgs):
server_args.url() + "/freeze_gc",
headers=freeze_headers,
timeout=10,
verify=server_args.ssl_verify(),
verify=ssl_verify_of(server_args),
)
res.raise_for_status()
except requests.exceptions.RequestException:
@@ -5,6 +5,7 @@ from typing import List, Optional, Tuple
import requests
import torch
from sglang.srt.arg_groups.serving_hook import ssl_verify_of
from sglang.srt.entrypoints.EngineBase import EngineBase
from sglang.srt.entrypoints.http_server import launch_server
from sglang.srt.server_args import ServerArgs
@@ -24,7 +25,7 @@ def launch_server_process(server_args: ServerArgs) -> multiprocessing.Process:
timeout = 300.0 # Increased timeout to 5 minutes for downloading large models
start_time = time.perf_counter()
ssl_verify = server_args.ssl_verify()
ssl_verify = ssl_verify_of(server_args)
with requests.Session() as session:
while time.perf_counter() - start_time < timeout:
@@ -74,7 +75,7 @@ class HttpServerEngineAdapter(EngineBase):
"""
url = f"{self.server_args.url()}/{endpoint}"
response = requests.post(
url, json=payload or {}, verify=self.server_args.ssl_verify()
url, json=payload or {}, verify=ssl_verify_of(self.server_args)
)
response.raise_for_status()
return response.json()
@@ -11,6 +11,10 @@ import msgspec
import torch
import torch.distributed as dist
from sglang.srt.arg_groups.overrides import (
modelexpress_transport_of,
modelexpress_url_of,
)
from sglang.srt.configs.device_config import DeviceConfig
from sglang.srt.configs.load_config import LoadConfig, LoadFormat
from sglang.srt.constants import GPU_MEMORY_TYPE_WEIGHTS
@@ -219,8 +223,8 @@ def build_load_config(
remote_instance_weight_loader_backend=get_model().remote_instance_weight_loader_backend,
remote_instance_weight_loader_transfer_engine=remote_instance_weight_transporter_engine,
remote_instance_weight_loader_transfer_engine_session_id=remote_instance_weight_transporter_session_id,
modelexpress_url=server_args.modelexpress_url,
modelexpress_transport=server_args.modelexpress_transport,
modelexpress_url=modelexpress_url_of(server_args),
modelexpress_transport=modelexpress_transport_of(server_args),
modelopt_config=modelopt_config,
rl_quant_profile=get_model().rl_quant_profile,
draft_model_idx=draft_model_idx,
+4 -4
View File
@@ -1717,8 +1717,8 @@ def pre_capture_activation_reserve_mb(gpu_mem: float | None) -> float:
Derived from published leaves across four bags (``disagg`` / ``schedule`` /
``exec.graph`` / ``spec``) plus the configured parallel sizes, so it follows
a post-publish override; ``ServerArgs.pre_capture_activation_reserve_mb`` is
the pre-publish equivalent and
a post-publish override; ``pre_capture_activation_reserve_mb_of`` in
``arg_groups.overrides`` is the config-shaped equivalent and
``TestDerivedPredicatesAgreeAcrossTiers`` pins the two equal.
"""
schedule = get_schedule()
@@ -2100,7 +2100,7 @@ def describe_kv_events_publisher(server_args: Any) -> Optional[dict]:
helpers the scheduler binds through — so the advertisement cannot
drift from the sockets.
"""
from sglang.srt.arg_groups.overrides import resolving_view
from sglang.srt.arg_groups.overrides import kv_event_block_size_of, resolving_view
# Lazy import so loading server_args doesn't pull in
# disaggregation / msgspec / zmq at module top level.
@@ -2135,7 +2135,7 @@ def describe_kv_events_publisher(server_args: Any) -> Optional[dict]:
"endpoint_host": host,
"endpoint_port_base": port,
"topic": cfg.topic,
"block_size": resolved.kv_event_block_size,
"block_size": kv_event_block_size_of(resolved),
"dp_size": resolved.dp_size,
}
# Load range, from the same resolver SchedulerLoadPublisher binds
+1 -85
View File
@@ -58,7 +58,6 @@ from sglang.srt.arg_groups.overrides import (
remote_instance_transfer_engine_of,
resolution_projection,
resolving_view,
supports_mamba_cache_extra_buffer,
)
from sglang.srt.environ import envs
from sglang.srt.function_call.function_call_parser import FunctionCallParser
@@ -3759,33 +3758,7 @@ class ServerArgs:
# CUDA graph configuration resolution
# ------------------------------------------------------------------
def pre_capture_activation_reserve_mb(self, gpu_mem: Optional[float]) -> float:
# Runtime activation working-set reserve for eager decode above the captured
# max_bs and transient prefill/logits; also covers fixed state caches.
cfg = resolving_view(self)
if cfg.disaggregation_mode == "decode":
running_requests = (
cfg.max_running_requests or cfg.cuda_graph_config.decode.max_bs or 1
)
activation_tokens = max(
running_requests * (cfg.speculative_num_draft_tokens or 1), 2048
)
elif cfg.chunked_prefill_size > 0:
activation_tokens = max(cfg.chunked_prefill_size, 2048)
else:
activation_tokens = max(cfg.max_prefill_tokens, 2048)
reserved_mem = (
512 + activation_tokens * 1.5 + cfg.tp_size * cfg.pp_size / 8 * 1024
)
if gpu_mem is not None and gpu_mem > 60 * 1024:
reserved_mem = max(reserved_mem, 10 * 1024)
return reserved_mem
def _support_mamba_cache_extra_buffer(self, model_arch: str):
return supports_mamba_cache_extra_buffer(self, model_arch)
# ===== END TO BE REFACTORED ====
# ===== END TO BE REFACTORED ====
LANGUAGE_MODEL_ONLY_ARCHITECTURES = ("MuseGlimmerForConditionalGeneration",)
@@ -4137,32 +4110,6 @@ class ServerArgs:
return cfg.startup_weight_load_mode == "overlap"
def ssl_verify(self):
"""Return the value for the requests library's verify= parameter.
When SSL is configured:
- If a CA certificate file is provided, return its path so requests
validates the server certificate against that CA.
- Otherwise, return False to disable certificate verification
(suitable for self-signed certificates in development/testing).
A warning is logged once when this happens.
When SSL is not configured, return True to use the system's default
CA bundle.
"""
if self.ssl_ca_certs:
return self.ssl_ca_certs
if self.ssl_certfile:
if not getattr(self, "_ssl_verify_warned", False):
logger.warning(
"SSL is enabled but --ssl-ca-certs was not provided. "
"Certificate verification is DISABLED for internal "
"health checks. For production deployments, provide "
"--ssl-ca-certs or use CA-signed certificates."
)
self._ssl_verify_warned = True
return False
return True
def __setattr__(self, name, value):
# Once resolution has finished the record is the READ-ONLY raw input
# the config bags were projected from. Resolved config changes go to the bags via
@@ -4190,42 +4137,11 @@ class ServerArgs:
check_server_args(self)
@property
def _parsed_modelexpress_config(self) -> dict:
cache = getattr(self, "_mx_config_cache", None)
if cache is not None:
return cache
if self.modelexpress_config is None:
result = {}
elif isinstance(self.modelexpress_config, str):
result = json.loads(self.modelexpress_config)
else:
result = self.modelexpress_config
self._mx_config_cache = result
return result
@property
def modelexpress_url(self) -> Optional[str]:
return self._parsed_modelexpress_config.get("url")
@property
def modelexpress_transport(self) -> str:
"""Transport backend for modelexpress."""
return self._parsed_modelexpress_config.get("transport", "nixl")
def remote_instance_weight_loader_use_transfer_engine(self, load_format=None):
"""``load_format`` overrides the seed's: a draft runner loading under
``--speculative-draft-load-format`` needs its own transfer engine."""
return remote_instance_transfer_engine_of(resolving_view(self), load_format)
@property
def kv_event_block_size(self) -> int:
"""Width KV events are emitted at: under DCP the radix tree pages at
``page_size * dcp_size`` (``mem_cache/kv_cache_builder.py``).
"""
cfg = resolving_view(self)
return cfg.page_size * self.dcp_size
# --------------------------------------------------------------------------
# Module-level ServerArgs helpers and runtime shims.
@@ -100,17 +100,22 @@ class TestBaseProcessorConfigExtraction(CustomTestCase):
override.install()
self.addCleanup(override.restore)
server_args = MagicMock()
server_args.mm_processor_worker_num = mm_processor_worker_num
server_args.mm_io_worker_num = mm_io_worker_num
server_args.mm_preprocess_cache_size_mb = None
server_args.tokenizer_worker_num = 1
server_args.trust_mm_content_hashes = False
server_args.media_url_max_file_size_mb = 64
# A bare MagicMock makes every attribute truthy, which silently sends
# the worker-count decision down the CPU branch. Pin what it reads.
server_args.disable_fast_image_processor = False
server_args.rl_on_policy_target = None
# A real record: a bare MagicMock makes every attribute truthy, which
# sends the worker-count decision down the wrong branch.
from sglang.srt.server_args import ServerArgs
server_args = ServerArgs(
model_path="dummy",
mm_process_config=mm_process_config,
allowed_media_domains=[],
mm_processor_worker_num=mm_processor_worker_num,
mm_io_worker_num=mm_io_worker_num,
mm_preprocess_cache_size_mb=None,
tokenizer_worker_num=1,
trust_mm_content_hashes=False,
media_url_max_file_size_mb=64,
disable_fast_image_processor=False,
)
hf_config = MagicMock()
mock_hf_processor = MagicMock()
@@ -0,0 +1,326 @@
"""The model-source axis, on PR CI.
Four ways a model path can name something that is not a local directory, and
until now only one of them was checked before merge:
- an object-store URI (``s3://`` / ``gs://`` / ``az://``), covered by
``test_model_config_cache.py`` and, end to end, by a ``nightly`` test;
- a Hub reference to a ``.gguf`` file;
- a ModelScope repo id;
- a remote-connector URL, which is any other ``scheme://`` and is reached by a
different arm of ``ModelConfig`` than the object-store one.
The last three had no registered test at all. That is how a change to
``get_model_config()``'s cache semantics went green through PR CI and broke two
days later in the nightly: the axis it broke was not being looked at.
None of this needs a network. The GGUF arm asks one resolver for a local path,
the ModelScope arm returns any path that already exists on disk untouched and
otherwise goes through two imports that can be stood in for, and the
remote-connector arm goes through one factory. Each case stubs exactly that
seam and checks what the handler declares -- and, where the path moves, that
the model-configuration cache notices.
"""
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=10, suite="base-a-test-cpu")
import json
import os
import shutil
import sys
import tempfile
import types
import unittest
from unittest import mock
import sglang.srt.connector as connector_module
from sglang.srt.arg_groups.model_path_hook import (
handle_modelscope_paths,
resolve_hf_gguf_model_path,
)
from sglang.srt.arg_groups.overrides import model_config_of, resolving_view
from sglang.srt.configs.model_config import ModelConfig
from sglang.srt.server_args import ServerArgs
from sglang.test.test_utils import CustomTestCase
_MINI_CONFIG = {
"architectures": ["LlamaForCausalLM"],
"model_type": "llama",
"hidden_size": 16,
"intermediate_size": 32,
"num_attention_heads": 2,
"num_key_value_heads": 2,
"num_hidden_layers": 2,
"vocab_size": 128,
"max_position_embeddings": 2048,
}
_GGUF_REFERENCE = "owner/repo"
_MODELSCOPE_REPO = "org/model"
_REMOTE_URL = "redis://host:6379/mini-llama"
class _ModelSourceCase(CustomTestCase):
def _directory(self) -> str:
directory = tempfile.mkdtemp(prefix="model_source_")
self.addCleanup(shutil.rmtree, directory, ignore_errors=True)
return directory
def _checkpoint(self) -> str:
directory = self._directory()
with open(os.path.join(directory, "config.json"), "w") as handle:
json.dump(_MINI_CONFIG, handle)
return directory
def _gguf_file(self) -> str:
path = os.path.join(self._directory(), "model.gguf")
open(path, "w").close()
return path
class TestTheGgufArm(_ModelSourceCase):
"""`resolve_hf_gguf_model_path` turns a Hub reference into a local file."""
def _resolving_to(self, resolved):
"""Stand in for the one Hub call, keyed on what it is asked about."""
table = resolved if isinstance(resolved, dict) else None
def _resolve(model, revision=None):
if table is not None:
return table.get(model)
return resolved
return mock.patch(
"sglang.srt.utils.hf_transformers_utils.resolve_hf_gguf_reference",
side_effect=_resolve,
)
def test_a_hub_reference_declares_the_local_path(self):
local = self._gguf_file()
server_args = ServerArgs(model_path=_GGUF_REFERENCE, device="cuda")
with self._resolving_to(local):
resolve_hf_gguf_model_path(server_args)
self.assertEqual(resolving_view(server_args).model_path, local)
# The record itself still carries what the operator typed.
self.assertEqual(server_args.model_path, _GGUF_REFERENCE)
def test_the_tokenizer_follows_only_when_it_was_the_same_reference(self):
local = self._gguf_file()
together = ServerArgs(
model_path=_GGUF_REFERENCE, tokenizer_path=_GGUF_REFERENCE, device="cuda"
)
with self._resolving_to(local):
resolve_hf_gguf_model_path(together)
self.assertEqual(resolving_view(together).tokenizer_path, local)
apart = ServerArgs(
model_path=_GGUF_REFERENCE, tokenizer_path="somewhere/else", device="cuda"
)
with self._resolving_to({_GGUF_REFERENCE: local}):
resolve_hf_gguf_model_path(apart)
self.assertEqual(resolving_view(apart).tokenizer_path, "somewhere/else")
def test_a_draft_gguf_is_resolved_on_its_own(self):
target, draft = self._gguf_file(), self._gguf_file()
server_args = ServerArgs(
model_path=_GGUF_REFERENCE,
speculative_draft_model_path="owner/draft",
device="cuda",
)
with self._resolving_to({_GGUF_REFERENCE: target, "owner/draft": draft}):
resolve_hf_gguf_model_path(server_args)
view = resolving_view(server_args)
self.assertEqual(view.model_path, target)
self.assertEqual(view.speculative_draft_model_path, draft)
def test_a_reference_that_is_not_a_gguf_declares_nothing(self):
server_args = ServerArgs(model_path=_GGUF_REFERENCE, device="cuda")
with self._resolving_to(None):
resolve_hf_gguf_model_path(server_args)
self.assertEqual(resolving_view(server_args).model_path, _GGUF_REFERENCE)
def test_the_declared_path_invalidates_the_model_configuration(self):
"""The point of pinning the declaration: a configuration built before
it describes the Hub reference, not the file that was downloaded."""
first, second = self._checkpoint(), self._checkpoint()
server_args = ServerArgs(model_path=first, device="cuda")
before = model_config_of(server_args)
self.assertEqual(before.model_path, first)
with self._resolving_to(second):
resolve_hf_gguf_model_path(server_args)
after = model_config_of(server_args)
self.assertIsNot(after, before)
self.assertEqual(after.model_path, second)
class TestTheModelScopeArm(_ModelSourceCase):
"""`handle_modelscope_paths` resolves repo ids against the local cache."""
def _modelscope(self, cache_root: str, downloads: dict):
"""Stand in for the two modules the handler imports on a cache miss."""
calls = []
def _snapshot_download(path, cache_dir=None, revision=None, **kwargs):
calls.append((path, cache_dir, revision, kwargs.get("ignore_patterns")))
return downloads[path]
hub = types.ModuleType("modelscope.hub.snapshot_download")
hub.snapshot_download = _snapshot_download
file_utils = types.ModuleType("modelscope.utils.file_utils")
file_utils.get_model_cache_root = lambda: cache_root
modules = {
"modelscope": types.ModuleType("modelscope"),
"modelscope.hub": types.ModuleType("modelscope.hub"),
"modelscope.hub.snapshot_download": hub,
"modelscope.utils": types.ModuleType("modelscope.utils"),
"modelscope.utils.file_utils": file_utils,
}
return mock.patch.dict(sys.modules, modules), calls
def test_a_path_already_on_disk_is_left_alone(self):
"""And nothing is imported to decide that -- the arm has to stay usable
on a host with no modelscope installed."""
local = self._directory()
server_args = ServerArgs(model_path=local, tokenizer_path=local, device="cuda")
imported = {name for name in sys.modules if name.startswith("modelscope")}
handle_modelscope_paths(server_args)
view = resolving_view(server_args)
self.assertEqual(view.model_path, local)
self.assertEqual(view.tokenizer_path, local)
self.assertEqual(
imported, {name for name in sys.modules if name.startswith("modelscope")}
)
def test_a_repo_id_resolves_against_the_modelscope_cache(self):
cache_root = self._directory()
os.makedirs(os.path.join(cache_root, _MODELSCOPE_REPO))
patch, _ = self._modelscope(cache_root, {})
server_args = ServerArgs(
model_path=_MODELSCOPE_REPO, tokenizer_path=_MODELSCOPE_REPO, device="cuda"
)
with patch:
handle_modelscope_paths(server_args)
cached = os.path.join(cache_root, _MODELSCOPE_REPO)
view = resolving_view(server_args)
self.assertEqual(view.model_path, cached)
self.assertEqual(view.tokenizer_path, cached)
def test_a_cache_miss_downloads_and_the_tokenizer_skips_the_weights(self):
downloaded = self._directory()
patch, calls = self._modelscope(
self._directory(), {_MODELSCOPE_REPO: downloaded}
)
server_args = ServerArgs(
model_path=_MODELSCOPE_REPO, tokenizer_path=_MODELSCOPE_REPO, device="cuda"
)
with patch:
handle_modelscope_paths(server_args)
view = resolving_view(server_args)
self.assertEqual(view.model_path, downloaded)
self.assertEqual(view.tokenizer_path, downloaded)
# The tokenizer download does not drag the weights along with it.
self.assertEqual(
[call[3] for call in calls], [None, ["*.bin", "*.safetensors"]]
)
def test_the_download_directory_is_searched_before_the_hub(self):
download_dir = self._directory()
os.makedirs(os.path.join(download_dir, _MODELSCOPE_REPO))
patch, calls = self._modelscope(self._directory(), {})
server_args = ServerArgs(
model_path=_MODELSCOPE_REPO,
tokenizer_path=_MODELSCOPE_REPO,
download_dir=download_dir,
device="cuda",
)
with patch:
handle_modelscope_paths(server_args)
self.assertEqual(
resolving_view(server_args).model_path,
os.path.join(download_dir, _MODELSCOPE_REPO),
)
self.assertEqual(calls, [])
def test_a_draft_repo_id_is_resolved_with_its_own_revision(self):
cache_root = self._directory()
drafted = self._directory()
patch, calls = self._modelscope(cache_root, {"org/draft": drafted})
local = self._directory()
server_args = ServerArgs(
model_path=local,
tokenizer_path=local,
speculative_draft_model_path="org/draft",
speculative_draft_model_revision="v2",
device="cuda",
)
with patch:
handle_modelscope_paths(server_args)
self.assertEqual(
resolving_view(server_args).speculative_draft_model_path, drafted
)
self.assertEqual([call[2] for call in calls], ["v2"])
class TestTheRemoteConnectorArm(_ModelSourceCase):
"""`ModelConfig` repoints itself for any other ``scheme://``.
`redis://` is the shape the object-store arm does not claim, so it is the
one that reaches `_maybe_pull_model_tokenizer_from_remote`.
"""
def _connected_to(self, directory):
state = {}
class _Client:
def pull_files(self, allow_pattern=None):
state["allow_pattern"] = allow_pattern
def get_local_dir(self):
return directory
return (
mock.patch.object(
connector_module, "create_remote_connector", return_value=_Client()
),
state,
)
def test_the_configuration_reads_from_the_pulled_directory(self):
pulled = self._checkpoint()
patch, state = self._connected_to(pulled)
with patch:
config = ModelConfig(model_path=_REMOTE_URL)
self.assertEqual(config.model_path, pulled)
# The weights stay where they are; only the metadata was pulled.
self.assertEqual(config.model_weights, _REMOTE_URL)
self.assertEqual(state["allow_pattern"], ["*config.json"])
def test_the_record_keeps_the_url_and_the_cache_stays_keyed_on_it(self):
"""Same movement the object-store arm makes: the configuration's path
moves, the record's does not, and the cache key follows the record."""
pulled = self._checkpoint()
patch, _ = self._connected_to(pulled)
server_args = ServerArgs(model_path=_REMOTE_URL, device="cuda")
with patch:
config = model_config_of(server_args)
self.assertEqual(server_args.model_path, _REMOTE_URL)
self.assertEqual(config.model_path, pulled)
self.assertIs(model_config_of(server_args), config)
if __name__ == "__main__":
unittest.main()
@@ -59,11 +59,14 @@ def _self_written_attributes() -> set:
class TestNoPublicNonFieldSlot(CustomTestCase):
def test_every_public_attribute_is_a_field(self):
written = _self_written_attributes()
self.assertGreater(
len(written),
3,
f"only {len(written)} self-writes found; the scan is broken, not the "
"record",
# Anchor on a name, not a count: the count falls every time a derived
# read leaves the record, so a floor erodes with what it measures.
self.assertIn(
"_resolution_finished",
written,
f"the scan did not find the resolution flag the record sets on "
f"itself, so it is the scan that is broken, not the record: "
f"{sorted(written)}",
)
fields = {field.name for field in dataclasses.fields(ServerArgs)}
stray = sorted(
@@ -0,0 +1,144 @@
"""Every `server_args.<name>()` in the tree names something the record has.
Removing a member from `ServerArgs` means rewriting its callers, and the ones
inside `server_args.py` are the ones you fix by reflex. The cross-file caller is
what bites: `ServerArgs.ssl_verify()` moved to `serving_hook.ssl_verify_of()` and
one call site kept the old spelling as `self.server_args.ssl_verify()` -- a grep
for `server_args.ssl_verify()` does not find that, and nothing else looks. Every
`HttpServerEngineAdapter` request raised `AttributeError` before sending.
So this resolves the call sites instead of grepping for them: every attribute
*called* on something statically known to be a record has to exist on the record.
It is deliberately not limited to methods the refactor touched -- the next
removal gets the same check for free.
`multimodal_gen` carries a different, same-named class outside this contract, as
the other record ratchets also record.
"""
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=6, suite="base-a-test-cpu")
import ast
import dataclasses
import pathlib
import unittest
import sglang
from sglang.srt.server_args import ServerArgs
from sglang.test.test_utils import CustomTestCase
_ROOTS = (
pathlib.Path(next(iter(sglang.__path__))) / "srt",
pathlib.Path(__file__).resolve().parents[3], # test/
)
_EXCLUDED = ("multimodal_gen",)
# Attribute names that hold a `ServerArgs`. `resolving_view` and `resolved_view`
# proxy the record but answer for names it does not carry, so they are not here.
_RECORD_NAMES = ("server_args", "_server_args")
def _is_record(node) -> bool:
"""`server_args`, `self.server_args`, `self._server_args`, `cls.server_args`."""
if isinstance(node, ast.Name):
return node.id in _RECORD_NAMES
if isinstance(node, ast.Attribute):
return node.attr in _RECORD_NAMES
return False
def _rebound_locally(tree) -> set:
"""Names assigned something that is plainly not a record.
`server_args` is also a natural name for a dict of CLI flags or a list of
argv strings in test helpers, and those legitimately answer `.update()` and
`.items()`. A function that assigns one of those to the name is not talking
about the record in that scope.
"""
literal = (ast.Dict, ast.List, ast.DictComp, ast.ListComp)
builders = {"dict", "list", "tuple", "set"}
def _not_a_record(value) -> bool:
if isinstance(value, literal):
return True
# `dict(...)` / `list(...)`, and an annotated `server_args: list[str] = [...]`
return (
isinstance(value, ast.Call) and getattr(value.func, "id", None) in builders
)
rebound = set()
for node in ast.walk(tree):
if isinstance(node, ast.AnnAssign):
targets, value = [node.target], node.value
elif isinstance(node, ast.Assign):
targets, value = node.targets, node.value
else:
continue
if value is None or not _not_a_record(value):
continue
for target in targets:
if isinstance(target, ast.Name) and target.id in _RECORD_NAMES:
rebound.add(target.id)
return rebound
def _called_members():
"""{name: [file:line]} for every `<record>.<name>(...)` in the tree."""
found: dict[str, list[str]] = {}
for root in _ROOTS:
for path in sorted(root.rglob("*.py")):
text = path.as_posix()
if any(part in text for part in _EXCLUDED):
continue
source = path.read_text(encoding="utf-8-sig")
if "server_args" not in source:
continue
try:
tree = ast.parse(source)
except SyntaxError:
continue
rebound = _rebound_locally(tree)
for node in ast.walk(tree):
if (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and _is_record(node.func.value)
and getattr(node.func.value, "id", None) not in rebound
):
found.setdefault(node.func.attr, []).append(
f"{path.name}:{node.lineno}"
)
return found
class TestRecordMemberCallsResolve(CustomTestCase):
def test_every_called_member_exists_on_the_record(self):
called = _called_members()
self.assertGreater(
len(called),
5,
f"only {len(called)} members called on a record; the scan is broken, "
"not the tree",
)
available = set(dir(ServerArgs)) | {
field.name for field in dataclasses.fields(ServerArgs)
}
missing = {
name: sites
for name, sites in sorted(called.items())
if name not in available
}
self.assertEqual(
{},
missing,
"these are called on a ServerArgs but the record has no such member -- "
"each one raises AttributeError at the call. A member that moved out of "
"the record has to be rewritten at every call site, including the ones "
f"reached through `self.server_args`: {missing}",
)
if __name__ == "__main__":
unittest.main()
@@ -55,6 +55,7 @@ from sglang.srt.arg_groups.serving_hook import (
handle_multimodal_feature_transport,
handle_ssl_validation,
handle_tokenizer_batching,
ssl_verify_of,
)
from sglang.srt.arg_groups.speculative_hook import handle_speculative_decoding
from sglang.srt.arg_groups.validation_hook import check_two_batch_overlap
@@ -1367,13 +1368,15 @@ class TestSSLArgs(unittest.TestCase):
self.assertTrue(server_args.url().startswith("https://"))
def test_ssl_verify_without_ssl(self):
# the derived read lives with the rest of the SSL handling now
server_args = ServerArgs(model_path="dummy")
self.assertIs(server_args.ssl_verify(), True)
self.assertIs(ssl_verify_of(server_args), True)
@patch("os.path.isfile", return_value=True)
def test_ssl_verify_with_ssl_no_ca(self, _mock_isfile):
server_args = self._validate_ssl(ssl_keyfile="key.pem", ssl_certfile="cert.pem")
self.assertIs(server_args.ssl_verify(), False)
self.assertIs(ssl_verify_of(server_args), False)
@patch("os.path.isfile", return_value=True)
def test_ssl_verify_with_ssl_and_ca(self, _mock_isfile):
@@ -1382,7 +1385,7 @@ class TestSSLArgs(unittest.TestCase):
ssl_certfile="cert.pem",
ssl_ca_certs="ca.pem",
)
self.assertEqual(server_args.ssl_verify(), "ca.pem")
self.assertEqual(ssl_verify_of(server_args), "ca.pem")
def test_ssl_ca_certs_without_certfile_raises(self):
with self.assertRaises(ValueError) as context:
@@ -2840,8 +2843,13 @@ class TestDcpKvEventContract(CustomTestCase):
def test_kv_event_block_size_widens_a_single_token_page(self):
# page_size=1 + DCP is a real deployment shape: the allocator is still
# paged, at dcp_size.
from sglang.srt.arg_groups.overrides import (
kv_event_block_size_of,
resolving_view,
)
args = ServerArgs(model_path="dummy", tp_size=8, dcp_size=8, page_size=1)
self.assertEqual(args.kv_event_block_size, 8)
self.assertEqual(kv_event_block_size_of(resolving_view(args)), 8)
if __name__ == "__main__":
+49 -21
View File
@@ -125,19 +125,17 @@ def _returned_field_names(function):
and node.func.value.id in returned
):
names |= {kw.arg for kw in node.keywords if kw.arg}
receiver = node.func.value
if isinstance(receiver, ast.Name) and receiver.id == "overrides":
# Positional dict literals are collected by the Dict walk;
# anything else is invisible.
for arg in node.args:
if not isinstance(arg, ast.Dict):
raise AssertionError(
f"opaque overrides.update() argument in {function.name}"
)
if any(kw.arg is None for kw in node.keywords):
raise AssertionError(
f"**kwargs overrides.update() in {function.name}"
)
# A positional dict literal has to be read here. The Dict walk above
# only reaches literals that are *returned* or assigned to a returned
# name, so `d.update({"field": value})` was being type-checked and
# then dropped -- silently, under a comment claiming otherwise.
for arg in node.args:
if isinstance(arg, ast.Dict):
top_level_keys(arg)
else:
raise AssertionError(f"opaque update() argument in {function.name}")
if any(kw.arg is None for kw in node.keywords):
raise AssertionError(f"**kwargs update() in {function.name}")
return names
@@ -151,12 +149,28 @@ def _declared_by_registry_and_passes():
`@register_model_override*` sees exactly one of them and reports a healthy
census over a channel it cannot see.
"""
import sys
from sglang.srt.arg_groups import overrides
tree = ast.parse((_SRT / "arg_groups/overrides.py").read_text(encoding="utf-8-sig"))
bodies = {
node.name: node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)
}
# Resolve each callable's body in the file it actually lives in. The
# declarations are spread over `arg_groups/model_overrides/`, one module per
# model family, and a scan hard-coded to `overrides.py` would find none of
# them -- and, worse, would keep reporting a healthy census while doing it.
bodies_by_module = {}
def _bodies(module_name):
if module_name not in bodies_by_module:
path = getattr(sys.modules[module_name], "__file__", None)
assert path, f"{module_name} has no source file"
module_tree = ast.parse(pathlib.Path(path).read_text(encoding="utf-8-sig"))
bodies_by_module[module_name] = {
node.name: node
for node in ast.walk(module_tree)
if isinstance(node, ast.FunctionDef)
}
return bodies_by_module[module_name]
callables = {fn for fns in overrides._MODEL_OVERRIDE_FNS.values() for fn in fns}
callables |= {
fn for _predicate, fn in getattr(overrides, "_PREDICATE_OVERRIDE_FNS", ())
@@ -165,11 +179,23 @@ def _declared_by_registry_and_passes():
fields = set()
for fn in callables:
body = bodies.get(getattr(fn, "__name__", ""))
if body is not None:
fields |= _returned_field_names(body)
name = getattr(fn, "__name__", "")
body = _bodies(fn.__module__).get(name)
# Loud, not silent: a body this scan cannot find is a field census it
# is not taking, and a narrower census makes every check downstream of
# it quietly vacuous.
assert body is not None, f"{fn.__module__}.{name} has no body to scan"
fields |= _returned_field_names(body)
# The literal arch -> {field: value} table, which has no callable at all.
for node in tree.body:
# It lives with the rest of the registry, in `model_override_base`.
from sglang.srt.arg_groups import model_override_base
table_tree = ast.parse(
pathlib.Path(model_override_base.__file__).read_text(encoding="utf-8-sig")
)
seen_table = False
for node in table_tree.body:
target = None
if isinstance(node, ast.Assign) and isinstance(node.targets[0], ast.Name):
target = node.targets[0].id
@@ -186,6 +212,8 @@ def _declared_by_registry_and_passes():
if not isinstance(key, ast.Constant):
raise AssertionError("non-literal override key")
fields.add(key.value)
seen_table = True
assert seen_table, "MODEL_OVERRIDES is not where this scan looks for it"
return fields
@@ -0,0 +1,120 @@
"""Two family modules must never declare the same field for the same architecture.
An architecture claimed by two family modules is normal -- ``Qwen3NextForCausalLM``
gets its attention shape from ``qwen3_5`` and its MoE runner from ``qwen3_moe``.
Two modules declaring the *same* field for it is not: nobody owns that value,
and which module supplies it is decided by nothing more deliberate than the
order the imports happen to be in. That is a defect in the declarations, so
this forbids it outright rather than choosing a winner.
The ordering follows from the rule and is not itself pinned. ``__init__.py`` is
a list of imports, importing is what registers, and the gate applies matching
declarations in registration order with the last writer winning -- so an
overlap would make an import list into a behavioural statement, which tools
reorder freely. With no overlap the list can be sorted however anyone likes.
The declared-field sets are read with the chain ratchet's own extractor rather
than a second implementation of the same scan, for the reason its docstring
gives: two censuses of one thing that disagree are worse than either alone.
"""
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
import ast
import importlib.util
import pathlib
import sys
import unittest
from sglang.srt.arg_groups import model_overrides
from sglang.srt.arg_groups.model_override_base import (
_MODEL_OVERRIDE_FNS,
MODEL_OVERRIDES,
)
from sglang.test.test_utils import CustomTestCase
_RATCHET = pathlib.Path(__file__).resolve().parent / "test_chain_read_ratchet.py"
def _returned_field_names(fn):
spec = importlib.util.spec_from_file_location("_chain_ratchet_for_split", _RATCHET)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
source = pathlib.Path(sys.modules[fn.__module__].__file__).read_text(
encoding="utf-8-sig"
)
body = next(
node
for node in ast.walk(ast.parse(source))
if isinstance(node, ast.FunctionDef) and node.name == fn.__name__
)
return module._returned_field_names(body)
class TestModelOverrideSplit(CustomTestCase):
def test_no_field_is_declared_by_two_family_modules(self):
contested = {
arch: fns for arch, fns in _MODEL_OVERRIDE_FNS.items() if len(fns) > 1
}
self.assertTrue(contested, "the scan found no architecture with two claimants")
for arch, fns in sorted(contested.items()):
with self.subTest(architecture=arch):
seen: dict[str, str] = {}
for fn in fns:
for field in _returned_field_names(fn):
earlier = seen.get(field)
self.assertIsNone(
earlier,
f"{arch}: {fn.__module__}.{fn.__name__} and {earlier} "
f"both declare {field!r}, so which one wins now depends "
f"on the order of the imports in "
f"arg_groups/model_overrides/__init__.py",
)
seen[field] = f"{fn.__module__}.{fn.__name__}"
def test_the_constant_table_does_not_contest_a_callable(self):
"""``MODEL_OVERRIDES`` applies before the callables, so a field it and a
callable both name is decided by that ordering instead."""
for arch, const in sorted(MODEL_OVERRIDES.items()):
for fn in _MODEL_OVERRIDE_FNS.get(arch, ()):
with self.subTest(architecture=arch, fn=fn.__name__):
self.assertFalse(
set(const) & _returned_field_names(fn),
f"{arch}: MODEL_OVERRIDES and {fn.__name__} both declare "
f"{sorted(set(const) & _returned_field_names(fn))}",
)
def test_the_import_list_names_every_family_module(self):
"""Importing is what registers, so a module missing from the list is a
family that silently stops applying -- and the tests that import a
provider directly would not notice."""
package = pathlib.Path(model_overrides.__file__).parent
on_disk = {
path.stem for path in package.glob("*.py") if path.stem != "__init__"
}
imported = {
alias.name
for node in ast.walk(ast.parse((package / "__init__.py").read_text()))
if isinstance(node, ast.ImportFrom)
and node.module == "sglang.srt.arg_groups.model_overrides"
for alias in node.names
}
self.assertEqual(on_disk, imported)
def test_every_declaration_comes_from_its_own_family_module(self):
"""The split itself: nothing was left behind in overrides.py."""
for arch, fns in _MODEL_OVERRIDE_FNS.items():
for fn in fns:
with self.subTest(architecture=arch, fn=fn.__name__):
self.assertTrue(
fn.__module__.startswith(
"sglang.srt.arg_groups.model_overrides."
),
f"{fn.__name__} still lives in {fn.__module__}",
)
if __name__ == "__main__":
unittest.main()
+117 -70
View File
@@ -15,8 +15,11 @@ from types import SimpleNamespace
from typing import Optional
from unittest.mock import patch
from sglang.srt.arg_groups import model_override_base as base_module
from sglang.srt.arg_groups import overrides as overrides_module
from sglang.srt.arg_groups.arg_utils import A, Arg, resolvable_fields
from sglang.srt.arg_groups.model_overrides import minicpm as minicpm_module
from sglang.srt.arg_groups.model_overrides import qwen3_5 as qwen3_5_module
from sglang.srt.arg_groups.overrides import (
collect_model_override_declarations,
register_model_override,
@@ -119,15 +122,30 @@ class TestDSparkCheckpointConfig(CustomTestCase):
self.assertTrue(get_dspark_sample_from_anchor(SimpleNamespace()))
def _hf(quant_method=None, **kw):
"""An hf config that states its own quantization.
`get_quantization_config(hf_config)` just reads
`hf_config.quantization_config["quant_method"]`, so a test says what the
checkpoint is by handing over a config that says it -- rather than stubbing
the reader in one module and hoping that is the module doing the reading.
"""
if quant_method is not None:
kw["quantization_config"] = {"quant_method": quant_method}
return SimpleNamespace(**kw)
class _IsolatedRegistry(CustomTestCase):
"""Run each test against empty registries (they are process-global)."""
def setUp(self):
super().setUp()
# The registries live in `model_override_base`; that is the one address
# to isolate, because the registrars and the collector both use it.
self._patches = [
patch.dict(overrides_module.MODEL_OVERRIDES, clear=True),
patch.dict(overrides_module._MODEL_OVERRIDE_FNS, clear=True),
patch.object(overrides_module, "_PREDICATE_OVERRIDE_FNS", []),
patch.dict(base_module.MODEL_OVERRIDES, clear=True),
patch.dict(base_module._MODEL_OVERRIDE_FNS, clear=True),
patch.object(base_module, "_PREDICATE_OVERRIDE_FNS", []),
]
for p in self._patches:
p.start()
@@ -140,7 +158,7 @@ class _IsolatedRegistry(CustomTestCase):
class TestModelOverrideRegistry(_IsolatedRegistry):
def test_const_then_callables_in_registration_order(self):
overrides_module.MODEL_OVERRIDES["FakeForCausalLM"] = {"a": 1}
base_module.MODEL_OVERRIDES["FakeForCausalLM"] = {"a": 1}
@register_model_override("FakeForCausalLM")
def _first(server_args, hf_config):
@@ -453,7 +471,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
with override_platform(is_blackwell=False):
overrides = overrides_module._minicpm_sala_overrides(args, config)
overrides = minicpm_module._minicpm_sala_overrides(args, config)
self.assertTrue(overrides["disable_radix_cache"])
self.assertEqual(overrides["attention_backend"], "minicpm_flashattn")
@@ -660,7 +678,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
def test_mimo_v2_declarations(self):
# Callable-level golden: MiMoV2 archs are hybrid (config-shape heavy),
# so the declaration is pinned directly for both provider inputs.
from sglang.srt.arg_groups.overrides import _mimo_v2_overrides
from sglang.srt.arg_groups.model_overrides.mimo_v2 import _mimo_v2_overrides
def _args(**kw):
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
@@ -677,7 +695,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
def test_mimo_v2_sm100_fp8_pins_flashinfer_trtllm_moe(self):
"""Blackwell FP8 must not be left on the triton fused-MoE runner."""
from sglang.srt.arg_groups.overrides import _mimo_v2_overrides
from sglang.srt.arg_groups.model_overrides.mimo_v2 import _mimo_v2_overrides
def _args(**kw):
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
@@ -685,23 +703,17 @@ class TestGoldenModelOverrides(_IsolatedPublish):
return SimpleNamespace(**defaults)
with override_platform(is_sm100=True):
with patch.object(
overrides_module, "get_quantization_config", return_value="fp8"
):
self.assertEqual(
_mimo_v2_overrides(_args(), None),
{"moe_runner_backend": "flashinfer_trtllm"},
)
# An explicit user choice is never overwritten.
self.assertEqual(
_mimo_v2_overrides(_args(moe_runner_backend="triton"), None), {}
)
self.assertEqual(
_mimo_v2_overrides(_args(), _hf("fp8")),
{"moe_runner_backend": "flashinfer_trtllm"},
)
# An explicit user choice is never overwritten.
self.assertEqual(
_mimo_v2_overrides(_args(moe_runner_backend="triton"), _hf("fp8")), {}
)
# FP4 checkpoints run through flashinfer_mxfp4, so they must not be
# pinned to flashinfer_trtllm.
with patch.object(
overrides_module, "get_quantization_config", return_value="mxfp4"
):
self.assertEqual(_mimo_v2_overrides(_args(), None), {})
self.assertEqual(_mimo_v2_overrides(_args(), _hf("mxfp4")), {})
def test_mimo_v2_family_is_registered(self):
with override_platform(is_sm100=False):
@@ -748,7 +760,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_w4a16_moe_uses_marlin_on_sm100(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -778,7 +792,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_nvfp4_moe_keeps_flashinfer_trtllm_on_sm100(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -808,7 +824,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_speculation_uses_arch_specific_attention_on_blackwell(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
cases = {
True: {
@@ -836,7 +854,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(overrides[key], value)
def test_nemotron_h_sm100_speculative_draft_backend_matrix(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
for algorithm in ("EAGLE", "NEXTN", "DSPARK"):
with self.subTest(algorithm=algorithm):
@@ -864,7 +884,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_sm100_speculation_preserves_explicit_cache_and_draft(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DSPARK"
@@ -884,7 +906,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_sm100_topk_tree_falls_back_to_triton(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "EAGLE"
@@ -902,7 +926,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("mamba_radix_cache_strategy", overrides)
def test_nemotron_h_target_only_sm120_defers_to_generic_attention_default(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
@@ -915,7 +941,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_target_only_sm100_uses_trtllm_mha(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
@@ -929,7 +957,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_explicit_split_attention_backend_wins(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DFLASH"
@@ -945,7 +975,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_w4a16_moe_rejects_a2a_backend(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -961,7 +993,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
_nemotron_h_overrides(server_args, hf_config)
def test_nemotron_h_w4a16_moe_rejects_non_marlin_runner(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -1021,7 +1055,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertTrue((self._publish(sa), self._leaf("disable_hybrid_swa_memory"))[1])
def test_exaone_without_pattern_declares_nothing(self):
from sglang.srt.arg_groups.overrides import _exaone_overrides
from sglang.srt.arg_groups.model_overrides.exaone import _exaone_overrides
self.assertEqual(
_exaone_overrides(None, SimpleNamespace(sliding_window_pattern=None)),
@@ -1049,7 +1083,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual((self._publish(sa), self._leaf("dtype"))[1], "auto")
def test_gpt_oss_xpu_dtype_validation_reads_pristine(self):
from sglang.srt.arg_groups.overrides import _gpt_oss_overrides
from sglang.srt.arg_groups.model_overrides.gpt_oss import _gpt_oss_overrides
with override_platform(is_xpu=True):
with self.assertRaises(NotImplementedError):
@@ -1427,7 +1461,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_deepseek_v4_overrides_at_callable_level(self):
from sglang.srt.arg_groups.overrides import _deepseek_v4_overrides
from sglang.srt.arg_groups.model_overrides.deepseek_v4 import (
_deepseek_v4_overrides,
)
from sglang.srt.server_args import ServerArgs
hf = SimpleNamespace(architectures=["DeepseekV4ForCausalLM"])
@@ -1540,7 +1576,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_overrides_at_callable_level(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
def _hf(quant_algo="NVFP4", *, include_quantization_config=True):
hf = SimpleNamespace(
@@ -1892,7 +1930,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
_cutedsl_prefill_backend_fill(_view())
def test_moss_vl_overrides_at_callable_level(self):
from sglang.srt.arg_groups.overrides import _moss_vl_overrides
from sglang.srt.arg_groups.model_overrides.moss_vl import _moss_vl_overrides
def _args(**kw):
defaults = dict(
@@ -2183,7 +2221,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_qwen3_5_hybrid_coupled_declaration(self):
from sglang.srt.arg_groups.overrides import _qwen3_5_hybrid_overrides
from sglang.srt.arg_groups.model_overrides.qwen3_5 import (
_qwen3_5_hybrid_overrides,
)
def _args(default_backend, **kw):
defaults = dict(
@@ -2201,7 +2241,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
return args
with override_platform(is_sm100=True), patch.object(
overrides_module,
qwen3_5_module,
"get_default_attn_backend",
lambda server_args, **_: server_args.default_backend_for_test,
):
@@ -2246,7 +2286,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(_qwen3_5_hybrid_overrides(_args("fa3"), None), {})
def test_qwen3vl_page_size(self):
from sglang.srt.arg_groups.overrides import _qwen3vl_overrides
from sglang.srt.arg_groups.model_overrides.qwen3_vl import _qwen3vl_overrides
with override_platform(is_hip=True):
with patch("sglang.srt.environ.envs.SGLANG_USE_AITER_UNIFIED_ATTN") as e:
@@ -2319,7 +2359,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_m3_fp8_attn_gemm_resolution(self):
from sglang.srt.arg_groups.overrides import _minimax_m3_overrides
from sglang.srt.arg_groups.model_overrides.minimax_m3 import (
_minimax_m3_overrides,
)
from sglang.srt.server_args import m3_fp8_attn_gemm_enabled
def _args(**kw):
@@ -2364,9 +2406,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
return ns
hf = SimpleNamespace()
with override_platform(is_hip=False), override_platform(
is_sm100=True
), patch.object(overrides_module, "get_quantization_config", return_value=None):
# `hf` carries no `quantization_config`, which is what an unquantized
# checkpoint looks like -- no stub needed to say so.
with override_platform(is_hip=False), override_platform(is_sm100=True):
# fp8_e4m3 KV: SM100 backend default flips to trtllm_mha (the only
# dense backend with the fp8-q GEMM path); page snaps to 128
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e4m3"), hf)
@@ -2378,7 +2420,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(ov["page_size"], 128)
# e5m2 KV: stays on fa4 + the widening Triton path, and warns
with self.assertLogs(
"sglang.srt.arg_groups.overrides", level="WARNING"
"sglang.srt.arg_groups.model_overrides.minimax_m3", level="WARNING"
) as logs:
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e5m2"), hf)
self.assertEqual(ov["attention_backend"], "fa4")
@@ -2505,13 +2547,17 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_monolith_attention_families_at_callable_level(self):
from sglang.srt.arg_groups.overrides import (
from sglang.srt.arg_groups.model_overrides.falcon_h1 import (
_falcon_h1_jet_overrides,
_gemma4_overrides,
_glm4_moe_overrides,
)
from sglang.srt.arg_groups.model_overrides.gemma4 import _gemma4_overrides
from sglang.srt.arg_groups.model_overrides.glm4_moe import _glm4_moe_overrides
from sglang.srt.arg_groups.model_overrides.granitemoehybrid import (
_granite_moe_hybrid_overrides,
_lfm2_overrides,
_llama4_overrides,
)
from sglang.srt.arg_groups.model_overrides.lfm2 import _lfm2_overrides
from sglang.srt.arg_groups.model_overrides.llama4 import _llama4_overrides
from sglang.srt.arg_groups.model_overrides.minicpmv import (
_minicpm_v4_6_overrides,
)
@@ -2673,7 +2719,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_deepseek_family_order_safe_declarations(self):
from sglang.srt.arg_groups.overrides import _deepseek_family_overrides
from sglang.srt.arg_groups.model_overrides.deepseek_v2 import (
_deepseek_family_overrides,
)
def _args(**kw):
defaults = dict(
@@ -2768,27 +2816,26 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(_deepseek_family_overrides(_args(), None), {})
def test_qwen3_moe_family_quant_absorption(self):
from sglang.srt.arg_groups.overrides import _qwen3_moe_family_overrides
from sglang.srt.arg_groups.model_overrides.qwen3_moe import (
_qwen3_moe_family_overrides,
)
with override_platform(is_sm100=True):
with patch.object(
overrides_module, "get_quantization_config", return_value="fp8"
):
self.assertEqual(
_qwen3_moe_family_overrides(
SimpleNamespace(
quantization=None,
_quantization_explicitly_unset=False,
moe_a2a_backend="none",
moe_runner_backend="auto",
),
SimpleNamespace(architectures=["Qwen3MoeForCausalLM"]),
self.assertEqual(
_qwen3_moe_family_overrides(
SimpleNamespace(
quantization=None,
_quantization_explicitly_unset=False,
moe_a2a_backend="none",
moe_runner_backend="auto",
),
{
"quantization": "fp8",
"moe_runner_backend": "flashinfer_trtllm",
},
)
_hf("fp8", architectures=["Qwen3MoeForCausalLM"]),
),
{
"quantization": "fp8",
"moe_runner_backend": "flashinfer_trtllm",
},
)
with override_platform(is_sm100=False):
self.assertEqual(_qwen3_moe_family_overrides(None, None), {})
+4 -1
View File
@@ -1200,6 +1200,9 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
def test_activation_reserve_matches_the_member(self):
from types import SimpleNamespace
from sglang.srt.arg_groups.overrides import (
pre_capture_activation_reserve_mb_of,
)
from sglang.srt.runtime_context import pre_capture_activation_reserve_mb
graph = SimpleNamespace(decode=SimpleNamespace(max_bs=64))
@@ -1231,7 +1234,7 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
args = _FakeResolvedArgs(cuda_graph_config=graph, **case)
get_context().set_server_args(args)
self.assertEqual(
ServerArgs.pre_capture_activation_reserve_mb(args, gpu_mem),
pre_capture_activation_reserve_mb_of(args, gpu_mem),
pre_capture_activation_reserve_mb(gpu_mem),
)