[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:
co-authored by
Claude Opus 5
parent
7e751153eb
commit
e51a3ae65e
@@ -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(
|
envs.SGLANG_USE_CUDA_IPC_TRANSPORT.set(
|
||||||
"1" if requested_transport == "cuda_ipc" else "0"
|
"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
|
||||||
|
|||||||
@@ -483,6 +483,7 @@ from sglang.srt.entrypoints.v1_loads import router as v1_loads_router
|
|||||||
v1_loads_router.route_class = ORJSONRoute
|
v1_loads_router.route_class = ORJSONRoute
|
||||||
app.include_router(v1_loads_router)
|
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.entrypoints.elastic_ep import router as elastic_ep_router
|
||||||
from sglang.srt.runtime_context import (
|
from sglang.srt.runtime_context import (
|
||||||
describe_kv_events_publisher,
|
describe_kv_events_publisher,
|
||||||
@@ -2199,7 +2200,7 @@ def _execute_server_warmup(server_args: ServerArgs):
|
|||||||
if server_args.api_key:
|
if server_args.api_key:
|
||||||
headers["Authorization"] = f"Bearer {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
|
# Wait until the server is launched
|
||||||
success = False
|
success = False
|
||||||
@@ -2375,7 +2376,7 @@ def _freeze_gc_after_server_warmup(server_args: ServerArgs):
|
|||||||
server_args.url() + "/freeze_gc",
|
server_args.url() + "/freeze_gc",
|
||||||
headers=freeze_headers,
|
headers=freeze_headers,
|
||||||
timeout=10,
|
timeout=10,
|
||||||
verify=server_args.ssl_verify(),
|
verify=ssl_verify_of(server_args),
|
||||||
)
|
)
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import List, Optional, Tuple
|
|||||||
import requests
|
import requests
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.arg_groups.serving_hook import ssl_verify_of
|
||||||
from sglang.srt.entrypoints.EngineBase import EngineBase
|
from sglang.srt.entrypoints.EngineBase import EngineBase
|
||||||
from sglang.srt.entrypoints.http_server import launch_server
|
from sglang.srt.entrypoints.http_server import launch_server
|
||||||
from sglang.srt.server_args import ServerArgs
|
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
|
timeout = 300.0 # Increased timeout to 5 minutes for downloading large models
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
ssl_verify = server_args.ssl_verify()
|
ssl_verify = ssl_verify_of(server_args)
|
||||||
|
|
||||||
with requests.Session() as session:
|
with requests.Session() as session:
|
||||||
while time.perf_counter() - start_time < timeout:
|
while time.perf_counter() - start_time < timeout:
|
||||||
@@ -74,7 +75,7 @@ class HttpServerEngineAdapter(EngineBase):
|
|||||||
"""
|
"""
|
||||||
url = f"{self.server_args.url()}/{endpoint}"
|
url = f"{self.server_args.url()}/{endpoint}"
|
||||||
response = requests.post(
|
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()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import msgspec
|
|||||||
import torch
|
import torch
|
||||||
import torch.distributed as dist
|
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.device_config import DeviceConfig
|
||||||
from sglang.srt.configs.load_config import LoadConfig, LoadFormat
|
from sglang.srt.configs.load_config import LoadConfig, LoadFormat
|
||||||
from sglang.srt.constants import GPU_MEMORY_TYPE_WEIGHTS
|
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_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=remote_instance_weight_transporter_engine,
|
||||||
remote_instance_weight_loader_transfer_engine_session_id=remote_instance_weight_transporter_session_id,
|
remote_instance_weight_loader_transfer_engine_session_id=remote_instance_weight_transporter_session_id,
|
||||||
modelexpress_url=server_args.modelexpress_url,
|
modelexpress_url=modelexpress_url_of(server_args),
|
||||||
modelexpress_transport=server_args.modelexpress_transport,
|
modelexpress_transport=modelexpress_transport_of(server_args),
|
||||||
modelopt_config=modelopt_config,
|
modelopt_config=modelopt_config,
|
||||||
rl_quant_profile=get_model().rl_quant_profile,
|
rl_quant_profile=get_model().rl_quant_profile,
|
||||||
draft_model_idx=draft_model_idx,
|
draft_model_idx=draft_model_idx,
|
||||||
|
|||||||
@@ -1717,8 +1717,8 @@ def pre_capture_activation_reserve_mb(gpu_mem: float | None) -> float:
|
|||||||
|
|
||||||
Derived from published leaves across four bags (``disagg`` / ``schedule`` /
|
Derived from published leaves across four bags (``disagg`` / ``schedule`` /
|
||||||
``exec.graph`` / ``spec``) plus the configured parallel sizes, so it follows
|
``exec.graph`` / ``spec``) plus the configured parallel sizes, so it follows
|
||||||
a post-publish override; ``ServerArgs.pre_capture_activation_reserve_mb`` is
|
a post-publish override; ``pre_capture_activation_reserve_mb_of`` in
|
||||||
the pre-publish equivalent and
|
``arg_groups.overrides`` is the config-shaped equivalent and
|
||||||
``TestDerivedPredicatesAgreeAcrossTiers`` pins the two equal.
|
``TestDerivedPredicatesAgreeAcrossTiers`` pins the two equal.
|
||||||
"""
|
"""
|
||||||
schedule = get_schedule()
|
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
|
helpers the scheduler binds through — so the advertisement cannot
|
||||||
drift from the sockets.
|
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
|
# Lazy import so loading server_args doesn't pull in
|
||||||
# disaggregation / msgspec / zmq at module top level.
|
# 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_host": host,
|
||||||
"endpoint_port_base": port,
|
"endpoint_port_base": port,
|
||||||
"topic": cfg.topic,
|
"topic": cfg.topic,
|
||||||
"block_size": resolved.kv_event_block_size,
|
"block_size": kv_event_block_size_of(resolved),
|
||||||
"dp_size": resolved.dp_size,
|
"dp_size": resolved.dp_size,
|
||||||
}
|
}
|
||||||
# Load range, from the same resolver SchedulerLoadPublisher binds
|
# Load range, from the same resolver SchedulerLoadPublisher binds
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ from sglang.srt.arg_groups.overrides import (
|
|||||||
remote_instance_transfer_engine_of,
|
remote_instance_transfer_engine_of,
|
||||||
resolution_projection,
|
resolution_projection,
|
||||||
resolving_view,
|
resolving_view,
|
||||||
supports_mamba_cache_extra_buffer,
|
|
||||||
)
|
)
|
||||||
from sglang.srt.environ import envs
|
from sglang.srt.environ import envs
|
||||||
from sglang.srt.function_call.function_call_parser import FunctionCallParser
|
from sglang.srt.function_call.function_call_parser import FunctionCallParser
|
||||||
@@ -3759,32 +3758,6 @@ class ServerArgs:
|
|||||||
# CUDA graph configuration resolution
|
# 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",)
|
LANGUAGE_MODEL_ONLY_ARCHITECTURES = ("MuseGlimmerForConditionalGeneration",)
|
||||||
@@ -4137,32 +4110,6 @@ class ServerArgs:
|
|||||||
|
|
||||||
return cfg.startup_weight_load_mode == "overlap"
|
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):
|
def __setattr__(self, name, value):
|
||||||
# Once resolution has finished the record is the READ-ONLY raw input
|
# 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
|
# the config bags were projected from. Resolved config changes go to the bags via
|
||||||
@@ -4190,42 +4137,11 @@ class ServerArgs:
|
|||||||
|
|
||||||
check_server_args(self)
|
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):
|
def remote_instance_weight_loader_use_transfer_engine(self, load_format=None):
|
||||||
"""``load_format`` overrides the seed's: a draft runner loading under
|
"""``load_format`` overrides the seed's: a draft runner loading under
|
||||||
``--speculative-draft-load-format`` needs its own transfer engine."""
|
``--speculative-draft-load-format`` needs its own transfer engine."""
|
||||||
return remote_instance_transfer_engine_of(resolving_view(self), load_format)
|
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.
|
# Module-level ServerArgs helpers and runtime shims.
|
||||||
|
|||||||
@@ -100,17 +100,22 @@ class TestBaseProcessorConfigExtraction(CustomTestCase):
|
|||||||
override.install()
|
override.install()
|
||||||
self.addCleanup(override.restore)
|
self.addCleanup(override.restore)
|
||||||
|
|
||||||
server_args = MagicMock()
|
# A real record: a bare MagicMock makes every attribute truthy, which
|
||||||
server_args.mm_processor_worker_num = mm_processor_worker_num
|
# sends the worker-count decision down the wrong branch.
|
||||||
server_args.mm_io_worker_num = mm_io_worker_num
|
from sglang.srt.server_args import ServerArgs
|
||||||
server_args.mm_preprocess_cache_size_mb = None
|
|
||||||
server_args.tokenizer_worker_num = 1
|
server_args = ServerArgs(
|
||||||
server_args.trust_mm_content_hashes = False
|
model_path="dummy",
|
||||||
server_args.media_url_max_file_size_mb = 64
|
mm_process_config=mm_process_config,
|
||||||
# A bare MagicMock makes every attribute truthy, which silently sends
|
allowed_media_domains=[],
|
||||||
# the worker-count decision down the CPU branch. Pin what it reads.
|
mm_processor_worker_num=mm_processor_worker_num,
|
||||||
server_args.disable_fast_image_processor = False
|
mm_io_worker_num=mm_io_worker_num,
|
||||||
server_args.rl_on_policy_target = None
|
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()
|
hf_config = MagicMock()
|
||||||
mock_hf_processor = 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):
|
class TestNoPublicNonFieldSlot(CustomTestCase):
|
||||||
def test_every_public_attribute_is_a_field(self):
|
def test_every_public_attribute_is_a_field(self):
|
||||||
written = _self_written_attributes()
|
written = _self_written_attributes()
|
||||||
self.assertGreater(
|
# Anchor on a name, not a count: the count falls every time a derived
|
||||||
len(written),
|
# read leaves the record, so a floor erodes with what it measures.
|
||||||
3,
|
self.assertIn(
|
||||||
f"only {len(written)} self-writes found; the scan is broken, not the "
|
"_resolution_finished",
|
||||||
"record",
|
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)}
|
fields = {field.name for field in dataclasses.fields(ServerArgs)}
|
||||||
stray = sorted(
|
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_multimodal_feature_transport,
|
||||||
handle_ssl_validation,
|
handle_ssl_validation,
|
||||||
handle_tokenizer_batching,
|
handle_tokenizer_batching,
|
||||||
|
ssl_verify_of,
|
||||||
)
|
)
|
||||||
from sglang.srt.arg_groups.speculative_hook import handle_speculative_decoding
|
from sglang.srt.arg_groups.speculative_hook import handle_speculative_decoding
|
||||||
from sglang.srt.arg_groups.validation_hook import check_two_batch_overlap
|
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://"))
|
self.assertTrue(server_args.url().startswith("https://"))
|
||||||
|
|
||||||
def test_ssl_verify_without_ssl(self):
|
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")
|
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)
|
@patch("os.path.isfile", return_value=True)
|
||||||
def test_ssl_verify_with_ssl_no_ca(self, _mock_isfile):
|
def test_ssl_verify_with_ssl_no_ca(self, _mock_isfile):
|
||||||
server_args = self._validate_ssl(ssl_keyfile="key.pem", ssl_certfile="cert.pem")
|
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)
|
@patch("os.path.isfile", return_value=True)
|
||||||
def test_ssl_verify_with_ssl_and_ca(self, _mock_isfile):
|
def test_ssl_verify_with_ssl_and_ca(self, _mock_isfile):
|
||||||
@@ -1382,7 +1385,7 @@ class TestSSLArgs(unittest.TestCase):
|
|||||||
ssl_certfile="cert.pem",
|
ssl_certfile="cert.pem",
|
||||||
ssl_ca_certs="ca.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):
|
def test_ssl_ca_certs_without_certfile_raises(self):
|
||||||
with self.assertRaises(ValueError) as context:
|
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):
|
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
|
# page_size=1 + DCP is a real deployment shape: the allocator is still
|
||||||
# paged, at dcp_size.
|
# 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)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -125,19 +125,17 @@ def _returned_field_names(function):
|
|||||||
and node.func.value.id in returned
|
and node.func.value.id in returned
|
||||||
):
|
):
|
||||||
names |= {kw.arg for kw in node.keywords if kw.arg}
|
names |= {kw.arg for kw in node.keywords if kw.arg}
|
||||||
receiver = node.func.value
|
# A positional dict literal has to be read here. The Dict walk above
|
||||||
if isinstance(receiver, ast.Name) and receiver.id == "overrides":
|
# only reaches literals that are *returned* or assigned to a returned
|
||||||
# Positional dict literals are collected by the Dict walk;
|
# name, so `d.update({"field": value})` was being type-checked and
|
||||||
# anything else is invisible.
|
# then dropped -- silently, under a comment claiming otherwise.
|
||||||
for arg in node.args:
|
for arg in node.args:
|
||||||
if not isinstance(arg, ast.Dict):
|
if isinstance(arg, ast.Dict):
|
||||||
raise AssertionError(
|
top_level_keys(arg)
|
||||||
f"opaque overrides.update() argument in {function.name}"
|
else:
|
||||||
)
|
raise AssertionError(f"opaque update() argument in {function.name}")
|
||||||
if any(kw.arg is None for kw in node.keywords):
|
if any(kw.arg is None for kw in node.keywords):
|
||||||
raise AssertionError(
|
raise AssertionError(f"**kwargs update() in {function.name}")
|
||||||
f"**kwargs overrides.update() in {function.name}"
|
|
||||||
)
|
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
@@ -151,12 +149,28 @@ def _declared_by_registry_and_passes():
|
|||||||
`@register_model_override*` sees exactly one of them and reports a healthy
|
`@register_model_override*` sees exactly one of them and reports a healthy
|
||||||
census over a channel it cannot see.
|
census over a channel it cannot see.
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
from sglang.srt.arg_groups import overrides
|
from sglang.srt.arg_groups import overrides
|
||||||
|
|
||||||
tree = ast.parse((_SRT / "arg_groups/overrides.py").read_text(encoding="utf-8-sig"))
|
# Resolve each callable's body in the file it actually lives in. The
|
||||||
bodies = {
|
# declarations are spread over `arg_groups/model_overrides/`, one module per
|
||||||
node.name: node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)
|
# 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 fns in overrides._MODEL_OVERRIDE_FNS.values() for fn in fns}
|
||||||
callables |= {
|
callables |= {
|
||||||
fn for _predicate, fn in getattr(overrides, "_PREDICATE_OVERRIDE_FNS", ())
|
fn for _predicate, fn in getattr(overrides, "_PREDICATE_OVERRIDE_FNS", ())
|
||||||
@@ -165,11 +179,23 @@ def _declared_by_registry_and_passes():
|
|||||||
|
|
||||||
fields = set()
|
fields = set()
|
||||||
for fn in callables:
|
for fn in callables:
|
||||||
body = bodies.get(getattr(fn, "__name__", ""))
|
name = getattr(fn, "__name__", "")
|
||||||
if body is not None:
|
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)
|
fields |= _returned_field_names(body)
|
||||||
|
|
||||||
# The literal arch -> {field: value} table, which has no callable at all.
|
# 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
|
target = None
|
||||||
if isinstance(node, ast.Assign) and isinstance(node.targets[0], ast.Name):
|
if isinstance(node, ast.Assign) and isinstance(node.targets[0], ast.Name):
|
||||||
target = node.targets[0].id
|
target = node.targets[0].id
|
||||||
@@ -186,6 +212,8 @@ def _declared_by_registry_and_passes():
|
|||||||
if not isinstance(key, ast.Constant):
|
if not isinstance(key, ast.Constant):
|
||||||
raise AssertionError("non-literal override key")
|
raise AssertionError("non-literal override key")
|
||||||
fields.add(key.value)
|
fields.add(key.value)
|
||||||
|
seen_table = True
|
||||||
|
assert seen_table, "MODEL_OVERRIDES is not where this scan looks for it"
|
||||||
return fields
|
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()
|
||||||
@@ -15,8 +15,11 @@ from types import SimpleNamespace
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from unittest.mock import patch
|
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 import overrides as overrides_module
|
||||||
from sglang.srt.arg_groups.arg_utils import A, Arg, resolvable_fields
|
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 (
|
from sglang.srt.arg_groups.overrides import (
|
||||||
collect_model_override_declarations,
|
collect_model_override_declarations,
|
||||||
register_model_override,
|
register_model_override,
|
||||||
@@ -119,15 +122,30 @@ class TestDSparkCheckpointConfig(CustomTestCase):
|
|||||||
self.assertTrue(get_dspark_sample_from_anchor(SimpleNamespace()))
|
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):
|
class _IsolatedRegistry(CustomTestCase):
|
||||||
"""Run each test against empty registries (they are process-global)."""
|
"""Run each test against empty registries (they are process-global)."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
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 = [
|
self._patches = [
|
||||||
patch.dict(overrides_module.MODEL_OVERRIDES, clear=True),
|
patch.dict(base_module.MODEL_OVERRIDES, clear=True),
|
||||||
patch.dict(overrides_module._MODEL_OVERRIDE_FNS, clear=True),
|
patch.dict(base_module._MODEL_OVERRIDE_FNS, clear=True),
|
||||||
patch.object(overrides_module, "_PREDICATE_OVERRIDE_FNS", []),
|
patch.object(base_module, "_PREDICATE_OVERRIDE_FNS", []),
|
||||||
]
|
]
|
||||||
for p in self._patches:
|
for p in self._patches:
|
||||||
p.start()
|
p.start()
|
||||||
@@ -140,7 +158,7 @@ class _IsolatedRegistry(CustomTestCase):
|
|||||||
|
|
||||||
class TestModelOverrideRegistry(_IsolatedRegistry):
|
class TestModelOverrideRegistry(_IsolatedRegistry):
|
||||||
def test_const_then_callables_in_registration_order(self):
|
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")
|
@register_model_override("FakeForCausalLM")
|
||||||
def _first(server_args, hf_config):
|
def _first(server_args, hf_config):
|
||||||
@@ -453,7 +471,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
with override_platform(is_blackwell=False):
|
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.assertTrue(overrides["disable_radix_cache"])
|
||||||
self.assertEqual(overrides["attention_backend"], "minicpm_flashattn")
|
self.assertEqual(overrides["attention_backend"], "minicpm_flashattn")
|
||||||
@@ -660,7 +678,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
def test_mimo_v2_declarations(self):
|
def test_mimo_v2_declarations(self):
|
||||||
# Callable-level golden: MiMoV2 archs are hybrid (config-shape heavy),
|
# Callable-level golden: MiMoV2 archs are hybrid (config-shape heavy),
|
||||||
# so the declaration is pinned directly for both provider inputs.
|
# 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):
|
def _args(**kw):
|
||||||
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
|
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):
|
def test_mimo_v2_sm100_fp8_pins_flashinfer_trtllm_moe(self):
|
||||||
"""Blackwell FP8 must not be left on the triton fused-MoE runner."""
|
"""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):
|
def _args(**kw):
|
||||||
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
|
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
|
||||||
@@ -685,23 +703,17 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
return SimpleNamespace(**defaults)
|
return SimpleNamespace(**defaults)
|
||||||
|
|
||||||
with override_platform(is_sm100=True):
|
with override_platform(is_sm100=True):
|
||||||
with patch.object(
|
|
||||||
overrides_module, "get_quantization_config", return_value="fp8"
|
|
||||||
):
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
_mimo_v2_overrides(_args(), None),
|
_mimo_v2_overrides(_args(), _hf("fp8")),
|
||||||
{"moe_runner_backend": "flashinfer_trtllm"},
|
{"moe_runner_backend": "flashinfer_trtllm"},
|
||||||
)
|
)
|
||||||
# An explicit user choice is never overwritten.
|
# An explicit user choice is never overwritten.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
_mimo_v2_overrides(_args(moe_runner_backend="triton"), None), {}
|
_mimo_v2_overrides(_args(moe_runner_backend="triton"), _hf("fp8")), {}
|
||||||
)
|
)
|
||||||
# FP4 checkpoints run through flashinfer_mxfp4, so they must not be
|
# FP4 checkpoints run through flashinfer_mxfp4, so they must not be
|
||||||
# pinned to flashinfer_trtllm.
|
# pinned to flashinfer_trtllm.
|
||||||
with patch.object(
|
self.assertEqual(_mimo_v2_overrides(_args(), _hf("mxfp4")), {})
|
||||||
overrides_module, "get_quantization_config", return_value="mxfp4"
|
|
||||||
):
|
|
||||||
self.assertEqual(_mimo_v2_overrides(_args(), None), {})
|
|
||||||
|
|
||||||
def test_mimo_v2_family_is_registered(self):
|
def test_mimo_v2_family_is_registered(self):
|
||||||
with override_platform(is_sm100=False):
|
with override_platform(is_sm100=False):
|
||||||
@@ -748,7 +760,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_nemotron_h_w4a16_moe_uses_marlin_on_sm100(self):
|
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(
|
server_args, hf_config = self._nemotron_h_args(
|
||||||
quantized_layers={
|
quantized_layers={
|
||||||
@@ -778,7 +792,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_nemotron_h_nvfp4_moe_keeps_flashinfer_trtllm_on_sm100(self):
|
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(
|
server_args, hf_config = self._nemotron_h_args(
|
||||||
quantized_layers={
|
quantized_layers={
|
||||||
@@ -808,7 +824,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_nemotron_h_speculation_uses_arch_specific_attention_on_blackwell(self):
|
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 = {
|
cases = {
|
||||||
True: {
|
True: {
|
||||||
@@ -836,7 +854,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertEqual(overrides[key], value)
|
self.assertEqual(overrides[key], value)
|
||||||
|
|
||||||
def test_nemotron_h_sm100_speculative_draft_backend_matrix(self):
|
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"):
|
for algorithm in ("EAGLE", "NEXTN", "DSPARK"):
|
||||||
with self.subTest(algorithm=algorithm):
|
with self.subTest(algorithm=algorithm):
|
||||||
@@ -864,7 +884,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertNotIn("speculative_draft_attention_backend", overrides)
|
self.assertNotIn("speculative_draft_attention_backend", overrides)
|
||||||
|
|
||||||
def test_nemotron_h_sm100_speculation_preserves_explicit_cache_and_draft(self):
|
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, hf_config = self._nemotron_h_args(quantized_layers={})
|
||||||
server_args.speculative_algorithm = "DSPARK"
|
server_args.speculative_algorithm = "DSPARK"
|
||||||
@@ -884,7 +906,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertNotIn("speculative_draft_attention_backend", overrides)
|
self.assertNotIn("speculative_draft_attention_backend", overrides)
|
||||||
|
|
||||||
def test_nemotron_h_sm100_topk_tree_falls_back_to_triton(self):
|
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, hf_config = self._nemotron_h_args(quantized_layers={})
|
||||||
server_args.speculative_algorithm = "EAGLE"
|
server_args.speculative_algorithm = "EAGLE"
|
||||||
@@ -902,7 +926,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertNotIn("mamba_radix_cache_strategy", overrides)
|
self.assertNotIn("mamba_radix_cache_strategy", overrides)
|
||||||
|
|
||||||
def test_nemotron_h_target_only_sm120_defers_to_generic_attention_default(self):
|
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={})
|
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):
|
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={})
|
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):
|
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, hf_config = self._nemotron_h_args(quantized_layers={})
|
||||||
server_args.speculative_algorithm = "DFLASH"
|
server_args.speculative_algorithm = "DFLASH"
|
||||||
@@ -945,7 +975,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertNotIn("speculative_draft_attention_backend", overrides)
|
self.assertNotIn("speculative_draft_attention_backend", overrides)
|
||||||
|
|
||||||
def test_nemotron_h_w4a16_moe_rejects_a2a_backend(self):
|
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(
|
server_args, hf_config = self._nemotron_h_args(
|
||||||
quantized_layers={
|
quantized_layers={
|
||||||
@@ -961,7 +993,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
_nemotron_h_overrides(server_args, hf_config)
|
_nemotron_h_overrides(server_args, hf_config)
|
||||||
|
|
||||||
def test_nemotron_h_w4a16_moe_rejects_non_marlin_runner(self):
|
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(
|
server_args, hf_config = self._nemotron_h_args(
|
||||||
quantized_layers={
|
quantized_layers={
|
||||||
@@ -1021,7 +1055,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertTrue((self._publish(sa), self._leaf("disable_hybrid_swa_memory"))[1])
|
self.assertTrue((self._publish(sa), self._leaf("disable_hybrid_swa_memory"))[1])
|
||||||
|
|
||||||
def test_exaone_without_pattern_declares_nothing(self):
|
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(
|
self.assertEqual(
|
||||||
_exaone_overrides(None, SimpleNamespace(sliding_window_pattern=None)),
|
_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")
|
self.assertEqual((self._publish(sa), self._leaf("dtype"))[1], "auto")
|
||||||
|
|
||||||
def test_gpt_oss_xpu_dtype_validation_reads_pristine(self):
|
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 override_platform(is_xpu=True):
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(NotImplementedError):
|
||||||
@@ -1427,7 +1461,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_deepseek_v4_overrides_at_callable_level(self):
|
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
|
from sglang.srt.server_args import ServerArgs
|
||||||
|
|
||||||
hf = SimpleNamespace(architectures=["DeepseekV4ForCausalLM"])
|
hf = SimpleNamespace(architectures=["DeepseekV4ForCausalLM"])
|
||||||
@@ -1540,7 +1576,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_nemotron_h_overrides_at_callable_level(self):
|
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):
|
def _hf(quant_algo="NVFP4", *, include_quantization_config=True):
|
||||||
hf = SimpleNamespace(
|
hf = SimpleNamespace(
|
||||||
@@ -1892,7 +1930,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
_cutedsl_prefill_backend_fill(_view())
|
_cutedsl_prefill_backend_fill(_view())
|
||||||
|
|
||||||
def test_moss_vl_overrides_at_callable_level(self):
|
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):
|
def _args(**kw):
|
||||||
defaults = dict(
|
defaults = dict(
|
||||||
@@ -2183,7 +2221,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_qwen3_5_hybrid_coupled_declaration(self):
|
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):
|
def _args(default_backend, **kw):
|
||||||
defaults = dict(
|
defaults = dict(
|
||||||
@@ -2201,7 +2241,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
with override_platform(is_sm100=True), patch.object(
|
with override_platform(is_sm100=True), patch.object(
|
||||||
overrides_module,
|
qwen3_5_module,
|
||||||
"get_default_attn_backend",
|
"get_default_attn_backend",
|
||||||
lambda server_args, **_: server_args.default_backend_for_test,
|
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), {})
|
self.assertEqual(_qwen3_5_hybrid_overrides(_args("fa3"), None), {})
|
||||||
|
|
||||||
def test_qwen3vl_page_size(self):
|
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 override_platform(is_hip=True):
|
||||||
with patch("sglang.srt.environ.envs.SGLANG_USE_AITER_UNIFIED_ATTN") as e:
|
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):
|
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
|
from sglang.srt.server_args import m3_fp8_attn_gemm_enabled
|
||||||
|
|
||||||
def _args(**kw):
|
def _args(**kw):
|
||||||
@@ -2364,9 +2406,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
return ns
|
return ns
|
||||||
|
|
||||||
hf = SimpleNamespace()
|
hf = SimpleNamespace()
|
||||||
with override_platform(is_hip=False), override_platform(
|
# `hf` carries no `quantization_config`, which is what an unquantized
|
||||||
is_sm100=True
|
# checkpoint looks like -- no stub needed to say so.
|
||||||
), patch.object(overrides_module, "get_quantization_config", return_value=None):
|
with override_platform(is_hip=False), override_platform(is_sm100=True):
|
||||||
# fp8_e4m3 KV: SM100 backend default flips to trtllm_mha (the only
|
# fp8_e4m3 KV: SM100 backend default flips to trtllm_mha (the only
|
||||||
# dense backend with the fp8-q GEMM path); page snaps to 128
|
# dense backend with the fp8-q GEMM path); page snaps to 128
|
||||||
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e4m3"), hf)
|
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)
|
self.assertEqual(ov["page_size"], 128)
|
||||||
# e5m2 KV: stays on fa4 + the widening Triton path, and warns
|
# e5m2 KV: stays on fa4 + the widening Triton path, and warns
|
||||||
with self.assertLogs(
|
with self.assertLogs(
|
||||||
"sglang.srt.arg_groups.overrides", level="WARNING"
|
"sglang.srt.arg_groups.model_overrides.minimax_m3", level="WARNING"
|
||||||
) as logs:
|
) as logs:
|
||||||
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e5m2"), hf)
|
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e5m2"), hf)
|
||||||
self.assertEqual(ov["attention_backend"], "fa4")
|
self.assertEqual(ov["attention_backend"], "fa4")
|
||||||
@@ -2505,13 +2547,17 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_monolith_attention_families_at_callable_level(self):
|
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,
|
_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,
|
_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,
|
_minicpm_v4_6_overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2673,7 +2719,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_deepseek_family_order_safe_declarations(self):
|
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):
|
def _args(**kw):
|
||||||
defaults = dict(
|
defaults = dict(
|
||||||
@@ -2768,12 +2816,11 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
self.assertEqual(_deepseek_family_overrides(_args(), None), {})
|
self.assertEqual(_deepseek_family_overrides(_args(), None), {})
|
||||||
|
|
||||||
def test_qwen3_moe_family_quant_absorption(self):
|
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 override_platform(is_sm100=True):
|
||||||
with patch.object(
|
|
||||||
overrides_module, "get_quantization_config", return_value="fp8"
|
|
||||||
):
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
_qwen3_moe_family_overrides(
|
_qwen3_moe_family_overrides(
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
@@ -2782,7 +2829,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
|||||||
moe_a2a_backend="none",
|
moe_a2a_backend="none",
|
||||||
moe_runner_backend="auto",
|
moe_runner_backend="auto",
|
||||||
),
|
),
|
||||||
SimpleNamespace(architectures=["Qwen3MoeForCausalLM"]),
|
_hf("fp8", architectures=["Qwen3MoeForCausalLM"]),
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
"quantization": "fp8",
|
"quantization": "fp8",
|
||||||
|
|||||||
@@ -1200,6 +1200,9 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
|
|||||||
def test_activation_reserve_matches_the_member(self):
|
def test_activation_reserve_matches_the_member(self):
|
||||||
from types import SimpleNamespace
|
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
|
from sglang.srt.runtime_context import pre_capture_activation_reserve_mb
|
||||||
|
|
||||||
graph = SimpleNamespace(decode=SimpleNamespace(max_bs=64))
|
graph = SimpleNamespace(decode=SimpleNamespace(max_bs=64))
|
||||||
@@ -1231,7 +1234,7 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
|
|||||||
args = _FakeResolvedArgs(cuda_graph_config=graph, **case)
|
args = _FakeResolvedArgs(cuda_graph_config=graph, **case)
|
||||||
get_context().set_server_args(args)
|
get_context().set_server_args(args)
|
||||||
self.assertEqual(
|
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),
|
pre_capture_activation_reserve_mb(gpu_mem),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user