config: the resolution callbacks into the record go to zero (#36972)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
48b88e1256
commit
b65e677e48
@@ -29,8 +29,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_attention_backend_compatibility(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
model_config_of,
|
||||
use_mla_backend,
|
||||
)
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
model_config = server_args.get_model_config()
|
||||
model_config = model_config_of(server_args)
|
||||
|
||||
# The attention_backend write clusters of this handler moved to the
|
||||
# resolution pipeline (arg_groups/overrides.py), each invoked below at
|
||||
@@ -127,7 +133,7 @@ def handle_attention_backend_compatibility(server_args: Any):
|
||||
|
||||
run_post_process_pass(server_args, _cutedsl_prefill_backend_fill)
|
||||
|
||||
prefill_backend, decode_backend = server_args._resolved_attention_backends()
|
||||
prefill_backend, decode_backend = attention_backends_of(resolved_view(server_args))
|
||||
if "trtllm_mha" in (prefill_backend, decode_backend):
|
||||
if prefill_backend == "trtllm_mha" and not (
|
||||
is_sm90_supported() or is_sm100_supported() or is_sm120_supported()
|
||||
@@ -184,8 +190,8 @@ def handle_attention_backend_compatibility(server_args: Any):
|
||||
# Other platforms backends
|
||||
run_post_process_pass(server_args, _attention_backend_platform_fallbacks)
|
||||
|
||||
prefill_backend, decode_backend = server_args._resolved_attention_backends()
|
||||
if server_args.use_mla_backend() and prefill_backend == "intel_xpu":
|
||||
prefill_backend, decode_backend = attention_backends_of(resolved_view(server_args))
|
||||
if use_mla_backend(server_args) and prefill_backend == "intel_xpu":
|
||||
raise ValueError(
|
||||
"intel_xpu backend is only supported on decode for MLA models, please set --decode-attention-backend to intel_xpu and do not set --attention-backend or --prefill-attention-backend to intel_xpu for prefill instead use triton."
|
||||
)
|
||||
@@ -451,6 +457,8 @@ def handle_multi_item_scoring(server_args: Any):
|
||||
changing it silently could surprise users who intentionally picked
|
||||
a non-flashinfer backend.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if not cfg.enable_mis:
|
||||
return
|
||||
@@ -488,7 +496,7 @@ def handle_multi_item_scoring(server_args: Any):
|
||||
chunked_prefill_size=-1,
|
||||
)
|
||||
|
||||
prefill_backend, decode_backend = server_args._resolved_attention_backends()
|
||||
prefill_backend, decode_backend = attention_backends_of(resolved_view(server_args))
|
||||
assert prefill_backend == "flashinfer" and decode_backend == "flashinfer", (
|
||||
"Multi-item scoring requires flashinfer attention backend for custom attention mask support. "
|
||||
f"Please set --attention-backend flashinfer when using --enable-mis. "
|
||||
@@ -497,6 +505,7 @@ def handle_multi_item_scoring(server_args: Any):
|
||||
|
||||
|
||||
def handle_deterministic_inference(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
from sglang.srt.server_args import (
|
||||
RADIX_SUPPORTED_DETERMINISTIC_ATTENTION_BACKEND,
|
||||
)
|
||||
@@ -549,7 +558,7 @@ def handle_deterministic_inference(server_args: Any):
|
||||
is_deepseek_model = False
|
||||
if parse_connector_type(cfg.model_path) != ConnectorType.INSTANCE:
|
||||
try:
|
||||
hf_config = server_args.get_model_config().hf_config
|
||||
hf_config = model_config_of(server_args).hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
is_deepseek_model = model_arch in [
|
||||
"DeepseekV2ForCausalLM",
|
||||
|
||||
@@ -110,6 +110,8 @@ def apply_cuda_graph_compatibility(server_args: Any):
|
||||
prefill backend (this folds in the old
|
||||
--enforce-piecewise-cuda-graph contract).
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of, model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if (Phase.PREFILL, "backend") in server_args._cuda_graph_config_locked:
|
||||
return
|
||||
@@ -120,11 +122,13 @@ def apply_cuda_graph_compatibility(server_args: Any):
|
||||
# this runs first, so piecewise would otherwise silently win.
|
||||
if (
|
||||
cfg.cuda_graph_config.prefill.backend == Backend.BREAKABLE
|
||||
and server_args.get_model_config().is_multimodal_piecewise_cuda_graph_supported
|
||||
and not server_args.get_model_config().is_multimodal_breakable_cuda_graph_supported
|
||||
and model_config_of(server_args).is_multimodal_piecewise_cuda_graph_supported
|
||||
and not model_config_of(
|
||||
server_args
|
||||
).is_multimodal_breakable_cuda_graph_supported
|
||||
# Keep trtllm_mla on the preferred breakable path, which now serves
|
||||
# MLA by falling back to the flashinfer MLA impl for extend.
|
||||
and server_args._resolved_attention_backends()[0] != "trtllm_mla"
|
||||
and attention_backends_of(resolved_view(server_args))[0] != "trtllm_mla"
|
||||
):
|
||||
logger.info(
|
||||
"Using tc_piecewise CUDA graph for validated multimodal " "decoder prefill."
|
||||
@@ -149,12 +153,14 @@ def disable_tc_piecewise_cudagraph_if_incompatible(server_args: Any):
|
||||
"""TcPiecewise (torch.compile + piecewise) is incompatible with
|
||||
these configurations. Most are torch.compile / dynamo limitations.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
|
||||
rules = [
|
||||
(
|
||||
"model-arch blacklist",
|
||||
lambda: server_args.get_model_config().is_piecewise_cuda_graph_disabled_model,
|
||||
lambda: model_config_of(server_args).is_piecewise_cuda_graph_disabled_model,
|
||||
),
|
||||
("DP attention", lambda: resolved_view(server_args).enable_dp_attention),
|
||||
("full torch.compile mode", lambda: cfg.enable_torch_compile),
|
||||
@@ -177,8 +183,10 @@ def disable_tc_piecewise_cudagraph_if_incompatible(server_args: Any):
|
||||
("LoRA", lambda: bool(cfg.lora_paths) or cfg.enable_lora),
|
||||
(
|
||||
"multimodal model",
|
||||
lambda: server_args.get_model_config().is_multimodal
|
||||
and not server_args.get_model_config().is_multimodal_piecewise_cuda_graph_supported,
|
||||
lambda: model_config_of(server_args).is_multimodal
|
||||
and not model_config_of(
|
||||
server_args
|
||||
).is_multimodal_piecewise_cuda_graph_supported,
|
||||
),
|
||||
(
|
||||
"GGUF quantization",
|
||||
@@ -236,6 +244,8 @@ def disable_breakable_cudagraph_if_incompatible(server_args: Any):
|
||||
memory-saver rejection in its own __init__; config-time rules can be
|
||||
added here as they're discovered.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
from sglang.srt.configs.model_config import is_deepseek_v4
|
||||
from sglang.srt.layers.cp.bcg import supports_prefill_cp_bcg
|
||||
@@ -245,7 +255,7 @@ def disable_breakable_cudagraph_if_incompatible(server_args: Any):
|
||||
# c4 indexer scratch is pinned in the capture pool and OOMs. Disable.
|
||||
(
|
||||
"DeepSeek-V4 (heavy capture-pool memory pressure)",
|
||||
lambda: is_deepseek_v4(server_args.get_model_config().hf_config),
|
||||
lambda: is_deepseek_v4(model_config_of(server_args).hf_config),
|
||||
),
|
||||
# CP all_gather replay size mismatch under BCG.
|
||||
(
|
||||
@@ -271,8 +281,10 @@ def disable_breakable_cudagraph_if_incompatible(server_args: Any):
|
||||
# Multimodal prefill replay faults under BCG; allowlisted archs opt back in.
|
||||
(
|
||||
"multimodal model",
|
||||
lambda: server_args.get_model_config().is_multimodal
|
||||
and not server_args.get_model_config().is_multimodal_breakable_cuda_graph_supported,
|
||||
lambda: model_config_of(server_args).is_multimodal
|
||||
and not model_config_of(
|
||||
server_args
|
||||
).is_multimodal_breakable_cuda_graph_supported,
|
||||
),
|
||||
]
|
||||
for name, predicate in rules:
|
||||
@@ -319,6 +331,8 @@ def disable_prefill_cuda_graph_for_deepseek_trtllm_mla(server_args: Any):
|
||||
breakable) trtllm_mla falls back to FlashAttention for prefill and regresses
|
||||
performance, so disable whichever prefill graph backend is in effect.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of, model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
|
||||
if (Phase.PREFILL, "backend") in server_args._cuda_graph_config_locked:
|
||||
@@ -327,10 +341,10 @@ def disable_prefill_cuda_graph_for_deepseek_trtllm_mla(server_args: Any):
|
||||
return
|
||||
if (
|
||||
"DeepseekV3ForCausalLM"
|
||||
not in server_args.get_model_config().hf_config.architectures
|
||||
not in model_config_of(server_args).hf_config.architectures
|
||||
):
|
||||
return
|
||||
prefill_attention_backend, _ = server_args._resolved_attention_backends()
|
||||
prefill_attention_backend, _ = attention_backends_of(resolved_view(server_args))
|
||||
if prefill_attention_backend != "trtllm_mla":
|
||||
return
|
||||
logger.warning(
|
||||
@@ -362,7 +376,7 @@ def apply_deepep_adjustments(server_args: Any):
|
||||
if bs is None:
|
||||
# 2048 = documented prefill default; max_bs unresolved here.
|
||||
max_bs = cfg.cuda_graph_config.prefill.max_bs or 2048
|
||||
bs = server_args._generate_prefill_cuda_graph_batch_sizes(max_bs)
|
||||
bs = generate_prefill_cuda_graph_batch_sizes(server_args, max_bs)
|
||||
aligned = sorted({((b + 7) // 8) * 8 for b in bs})
|
||||
if aligned != sorted(bs):
|
||||
logger.info(
|
||||
@@ -389,6 +403,8 @@ def apply_inkling_prefill_cuda_graph_default(server_args: Any):
|
||||
auto-disabled for this multimodal arch, and declarative model overrides
|
||||
materialize too late to steer cuda-graph resolution. Honors an explicit
|
||||
--cuda-graph-backend-prefill / --disable-prefill-cuda-graph."""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if (
|
||||
cfg.cuda_graph_backend_prefill is not None
|
||||
@@ -396,7 +412,7 @@ def apply_inkling_prefill_cuda_graph_default(server_args: Any):
|
||||
or parse_connector_type(cfg.model_path) == ConnectorType.INSTANCE
|
||||
):
|
||||
return
|
||||
arch = server_args.get_model_config().hf_config.architectures[0]
|
||||
arch = model_config_of(server_args).hf_config.architectures[0]
|
||||
if arch in (
|
||||
"InklingForConditionalGeneration",
|
||||
"InklingForConditionalGenerationMTP",
|
||||
@@ -409,13 +425,15 @@ def apply_inkling_prefill_cuda_graph_default(server_args: Any):
|
||||
|
||||
|
||||
def apply_muse_glimmer_prefill_cuda_graph_max_bs_default(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if (
|
||||
cfg.cuda_graph_max_bs_prefill is not None
|
||||
or parse_connector_type(cfg.model_path) == ConnectorType.INSTANCE
|
||||
):
|
||||
return
|
||||
arch = server_args.get_model_config().hf_config.architectures[0]
|
||||
arch = model_config_of(server_args).hf_config.architectures[0]
|
||||
if arch in ("MuseGlimmerForCausalLM", "MuseGlimmerForConditionalGeneration"):
|
||||
declare_resolution(
|
||||
server_args,
|
||||
@@ -430,7 +448,7 @@ def handle_cuda_graph_config(server_args: Any):
|
||||
parse_cuda_graph_config(server_args)
|
||||
apply_cuda_graph_compatibility(server_args)
|
||||
apply_deepep_adjustments(server_args)
|
||||
server_args._apply_cuda_graph_disaggregation_roles()
|
||||
apply_cuda_graph_disaggregation_roles(server_args)
|
||||
validate_cuda_graph_config(server_args)
|
||||
# Warn on the final resolved config (not inside the compat cascade —
|
||||
# that path is skipped when the user explicitly sets the backend,
|
||||
@@ -453,3 +471,102 @@ def validate_cuda_graph_config(server_args: Any):
|
||||
f"--cuda-graph-config[{phase}].backend={backend!r} not allowed; "
|
||||
f"allowed: {ALLOWED_BACKENDS_PER_PHASE[phase]}"
|
||||
)
|
||||
|
||||
|
||||
def generate_prefill_cuda_graph_batch_sizes(server_args: Any, max_bs: int):
|
||||
"""
|
||||
Generate the list of batch sizes for prefill CUDA graph capture
|
||||
based on max_bs. For tc_piecewise prefill, bs carries the
|
||||
captured token count (one shape knob per phase).
|
||||
"""
|
||||
capture_sizes = (
|
||||
list(range(4, 33, 4))
|
||||
+ list(range(48, 257, 16))
|
||||
+ list(range(288, 513, 32))
|
||||
+ list(range(576, 1024 + 1, 64))
|
||||
+ list(range(1280, 4096 + 1, 256))
|
||||
+ list(range(4608, max_bs + 1, 512))
|
||||
)
|
||||
|
||||
capture_sizes = [s for s in capture_sizes if s <= max_bs]
|
||||
|
||||
return capture_sizes
|
||||
|
||||
|
||||
def generate_decode_cuda_graph_batch_sizes(server_args: Any, max_bs: int):
|
||||
"""
|
||||
Generate the list of batch sizes for CUDA graph capture based on max_bs.
|
||||
This integrates the logic from cuda_graph_runner.py.
|
||||
"""
|
||||
cfg = resolving_view(server_args)
|
||||
# Handle disable_cuda_graph_padding as the first condition for both spec and non-spec
|
||||
if cfg.disable_cuda_graph_padding:
|
||||
capture_bs = list(range(1, max_bs + 1))
|
||||
elif cfg.speculative_algorithm is None:
|
||||
# Normal case:
|
||||
capture_bs = (
|
||||
[1, 2, 4, 8, 12]
|
||||
+ list(range(16, 257, 8))
|
||||
+ list(range(272, 512, 16))
|
||||
+ list(range(512, max_bs + 1, 32))
|
||||
)
|
||||
else:
|
||||
# Spec decoding case: less padding for smaller batch sizes
|
||||
capture_bs = (
|
||||
list(range(1, 9, 1))
|
||||
+ list(range(10, 33, 2))
|
||||
+ list(range(40, 65, 4))
|
||||
+ list(range(72, 257, 8))
|
||||
+ list(range(272, max_bs + 1, 16))
|
||||
)
|
||||
|
||||
capture_bs = [bs for bs in capture_bs if bs <= max_bs]
|
||||
|
||||
if max_bs not in capture_bs:
|
||||
capture_bs.append(max_bs)
|
||||
|
||||
return capture_bs
|
||||
|
||||
|
||||
def generate_cpu_graph_batch_sizes(server_args: Any):
|
||||
"""
|
||||
Generate the list of batch sizes for CPU graph capture based on torch_compile_max_bs.
|
||||
"""
|
||||
cfg = resolving_view(server_args)
|
||||
if cfg.disable_cuda_graph_padding:
|
||||
capture_bs = list(range(1, cfg.torch_compile_max_bs + 1))
|
||||
else:
|
||||
capture_bs = sorted(
|
||||
set().union(
|
||||
range(1, 17),
|
||||
range(18, 31, 2),
|
||||
range(32, 81, 4),
|
||||
range(84, cfg.torch_compile_max_bs + 1, 8),
|
||||
{cfg.torch_compile_max_bs},
|
||||
)
|
||||
)
|
||||
capture_bs = [bs for bs in capture_bs if bs <= cfg.torch_compile_max_bs]
|
||||
|
||||
return capture_bs
|
||||
|
||||
|
||||
def apply_cuda_graph_disaggregation_roles(server_args: Any):
|
||||
cfg = resolving_view(server_args)
|
||||
if cfg.disaggregation_mode == "prefill":
|
||||
if (Phase.DECODE, "backend") not in server_args._cuda_graph_config_locked:
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"_apply_cuda_graph_disaggregation_roles",
|
||||
cuda_graph_config=with_phase(
|
||||
cfg.cuda_graph_config, Phase.DECODE, backend=Backend.DISABLED
|
||||
),
|
||||
)
|
||||
elif cfg.disaggregation_mode == "decode":
|
||||
if (Phase.PREFILL, "backend") not in server_args._cuda_graph_config_locked:
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"_apply_cuda_graph_disaggregation_roles",
|
||||
cuda_graph_config=with_phase(
|
||||
cfg.cuda_graph_config, Phase.PREFILL, backend=Backend.DISABLED
|
||||
),
|
||||
)
|
||||
|
||||
@@ -27,6 +27,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def handle_expert_pack(server_args: Any) -> None:
|
||||
"""Normalize expert-pack settings and report all startup errors together."""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if cfg.load_format != "expert_pack":
|
||||
return
|
||||
@@ -146,7 +148,7 @@ def handle_expert_pack(server_args: Any) -> None:
|
||||
)
|
||||
else:
|
||||
try:
|
||||
hf_config = server_args.get_model_config().hf_config
|
||||
hf_config = model_config_of(server_args).hf_config
|
||||
except Exception as exc:
|
||||
errors.append(f"failed to load expert_pack model config: {exc}")
|
||||
else:
|
||||
|
||||
@@ -68,6 +68,8 @@ def handle_hicache_ratio_default(server_args: Any):
|
||||
|
||||
|
||||
def resolve_hicache_dcp_compatibility(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import use_mla_backend
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if cfg.dcp_size <= 1 or not cfg.enable_hierarchical_cache:
|
||||
return
|
||||
@@ -95,7 +97,7 @@ def resolve_hicache_dcp_compatibility(server_args: Any):
|
||||
"--enable-hisparse with --dcp-size > 1 is not supported: the "
|
||||
"HiSparse host pool is constructed without DCP translation."
|
||||
)
|
||||
if not server_args.use_mla_backend():
|
||||
if not use_mla_backend(server_args):
|
||||
raise NotImplementedError(
|
||||
"HiCache with --dcp-size > 1 is only supported for MLA models: "
|
||||
"the index translation lives in MLATokenToKVPoolHost, and the "
|
||||
|
||||
@@ -19,7 +19,8 @@ HISPARSE_KV_CACHE_DTYPES = ("bfloat16", "fp8_e4m3")
|
||||
|
||||
|
||||
def _is_hip() -> bool:
|
||||
from sglang.srt.server_args import is_hip
|
||||
"""The one place this family asks about ROCm, and the seam the tests patch."""
|
||||
from sglang.srt.utils.common import is_hip
|
||||
|
||||
return is_hip()
|
||||
|
||||
@@ -81,6 +82,8 @@ def validate_hisparse_kv_cache_dtype(server_args: ServerArgs) -> None:
|
||||
|
||||
def validate_hisparse(server_args: ServerArgs) -> None:
|
||||
"""Validate --enable-hisparse constraints (model class, radix cache, DSA backend)."""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if not cfg.enable_hisparse:
|
||||
return
|
||||
@@ -90,7 +93,7 @@ def validate_hisparse(server_args: ServerArgs) -> None:
|
||||
is_deepseek_v4,
|
||||
)
|
||||
|
||||
hf_config = server_args.get_model_config().hf_config
|
||||
hf_config = model_config_of(server_args).hf_config
|
||||
is_v4_hisparse = is_deepseek_v4(hf_config)
|
||||
is_hip = _is_hip()
|
||||
assert is_deepseek_dsa(hf_config) or is_v4_hisparse, (
|
||||
|
||||
@@ -37,13 +37,15 @@ def handle_mxfp8_kv_cache_compatibility(server_args: Any) -> None:
|
||||
|
||||
def handle_kv4_compatibility(server_args: Any) -> None:
|
||||
"""Check FP4 KV cache compatibility with the attention backend"""
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of, use_mla_backend
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
|
||||
if cfg.kv_cache_dtype not in ("nvfp4", "fp4_mx_block16"):
|
||||
return
|
||||
|
||||
use_mla_backend = server_args.use_mla_backend()
|
||||
prefill_backend, decode_backend = server_args._resolved_attention_backends()
|
||||
uses_mla = use_mla_backend(server_args)
|
||||
prefill_backend, decode_backend = attention_backends_of(resolved_view(server_args))
|
||||
attention_backend = resolved_view(server_args).attention_backend
|
||||
|
||||
if is_cuda():
|
||||
@@ -64,7 +66,7 @@ def handle_kv4_compatibility(server_args: Any) -> None:
|
||||
)
|
||||
else:
|
||||
if prefill_backend == "fa4":
|
||||
if use_mla_backend: # FA4 + MLA
|
||||
if uses_mla: # FA4 + MLA
|
||||
KV4_FA4_MLA_BACKEND_CHOICES = [
|
||||
"cutlass_mla",
|
||||
"flashinfer",
|
||||
@@ -85,7 +87,7 @@ def handle_kv4_compatibility(server_args: Any) -> None:
|
||||
f"{KV4_FA4_MHA_BACKEND_CHOICES}, but got {decode_backend}"
|
||||
)
|
||||
else:
|
||||
if use_mla_backend: # !FA4 + MLA
|
||||
if uses_mla: # !FA4 + MLA
|
||||
KV4_ATTENTION_MLA_BACKEND_CHOICES = [
|
||||
"cutlass_mla",
|
||||
"flashinfer",
|
||||
@@ -120,6 +122,8 @@ def handle_prefill_only_disable_kv_cache(server_args: Any) -> None:
|
||||
still None, backends haven't settled yet and the resolved (prefill,
|
||||
decode) pair would be a stale (None, None).
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
|
||||
if not cfg.prefill_only_disable_kv_cache:
|
||||
@@ -130,7 +134,7 @@ def handle_prefill_only_disable_kv_cache(server_args: Any) -> None:
|
||||
"_handle_attention_backend_compatibility() so the prefill backend is resolved."
|
||||
)
|
||||
|
||||
prefill_backend, _ = server_args._resolved_attention_backends()
|
||||
prefill_backend, _ = attention_backends_of(resolved_view(server_args))
|
||||
if prefill_backend not in ("fa3", "fa4"):
|
||||
raise ValueError(
|
||||
"--prefill-only-disable-kv-cache currently requires the FA prefill backend "
|
||||
@@ -194,6 +198,8 @@ def handle_cache_compatibility(server_args: Any) -> None:
|
||||
|
||||
|
||||
def handle_unified_memory_pool(server_args: Any) -> None:
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if not cfg.enable_unified_memory:
|
||||
return
|
||||
@@ -236,7 +242,7 @@ def handle_unified_memory_pool(server_args: Any) -> None:
|
||||
# Both roles: verify routes to either backend depending on
|
||||
# --speculative-attention-mode.
|
||||
spec_allowed = {"triton", "trtllm_mla", "cutedsl_mla", "tokenspeed_mla"}
|
||||
spec_backends = set(server_args._resolved_attention_backends())
|
||||
spec_backends = set(attention_backends_of(resolved_view(server_args)))
|
||||
spec_backends.discard(None)
|
||||
assert spec_backends <= spec_allowed, (
|
||||
"--enable-unified-memory + DSPARK requires spec-verify-audited "
|
||||
@@ -273,6 +279,8 @@ def handle_page_major_kv_layout(server_args: Any):
|
||||
# The unified pool stores state in the page-major envelope-strided layout, so
|
||||
# enabling it implies --enable-page-major-kv-layout — routing it through the
|
||||
# single page-major path + stride-aware Triton asserts (set before the guard).
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of, use_mla_backend
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if cfg.enable_unified_memory:
|
||||
declare_resolution(
|
||||
@@ -287,14 +295,14 @@ def handle_page_major_kv_layout(server_args: Any):
|
||||
# exposes each layer as a DENSE contiguous per-layer view
|
||||
# (build_dense_mla_views), which the paged MLA kernels consume directly,
|
||||
# with their kv_indices / block tables remapped to dense ids. Names below
|
||||
# are the RESOLVED ids from _resolved_attention_backends: "flashinfer" is
|
||||
# are the RESOLVED ids from attention_backends_of: "flashinfer" is
|
||||
# FlashInferMLAAttnBackend for an MLA model, "trtllm_mla" the trtllm
|
||||
# decode kernel; "cutedsl_mla" and "tokenspeed_mla" subclass
|
||||
# TRTLLMMLABackend and inherit its dense read/write path; "fa3" remaps its
|
||||
# page_table (in-kernel for captured decode, one funnel for eager).
|
||||
# flashmla / cutlass_mla share the create_flashmla block-table path and
|
||||
# can be added the same way once exercised.
|
||||
if cfg.enable_unified_memory and server_args.use_mla_backend():
|
||||
if cfg.enable_unified_memory and use_mla_backend(server_args):
|
||||
allowed_full = {
|
||||
"triton",
|
||||
"fa3",
|
||||
@@ -305,7 +313,7 @@ def handle_page_major_kv_layout(server_args: Any):
|
||||
}
|
||||
else:
|
||||
allowed_full = {"triton"}
|
||||
backends = set(server_args._resolved_attention_backends())
|
||||
backends = set(attention_backends_of(resolved_view(server_args)))
|
||||
backends.discard(None)
|
||||
assert backends <= allowed_full, (
|
||||
"--enable-page-major-kv-layout requires the Triton attention backend "
|
||||
@@ -327,7 +335,7 @@ def handle_page_major_kv_layout(server_args: Any):
|
||||
# are MLA-hybrid) from GDN models (GQA-hybrid) for the KDA-only caveat.
|
||||
decode_allowed = {"triton", "flashinfer"}
|
||||
prefill_allowed = {"triton", "flashkda"}
|
||||
if server_args.use_mla_backend():
|
||||
if use_mla_backend(server_args):
|
||||
decode_allowed.update({"cutedsl", "helion"})
|
||||
prefill_allowed.update({"cutedsl", "helion"})
|
||||
resolved_linear_decode = cfg.linear_attn_decode_backend or cfg.linear_attn_backend
|
||||
@@ -403,7 +411,7 @@ def validate_prefill_only_disable_kv_cache_args(server_args: Any):
|
||||
# Context-parallel prefill stages K/V through cp_allgather_and_save_kv_cache,
|
||||
# which writes to the pool via set_kv_buffer. NoOpMHATokenToKVPool intentionally
|
||||
# raises on writes, so the engine would boot fine but fail on the first request.
|
||||
if server_args._resolved().attn_cp_size > 1:
|
||||
if resolved_view(server_args).attn_cp_size > 1:
|
||||
raise ValueError(
|
||||
"--prefill-only-disable-kv-cache is incompatible with --attn-cp-size > 1: "
|
||||
"the context-parallel attention path writes K/V to the pool via set_kv_buffer, "
|
||||
|
||||
@@ -7,6 +7,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
declare_late_resolution,
|
||||
resolving_view,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
@@ -23,7 +24,9 @@ def check_lora_server_args(server_args: Any):
|
||||
# Enable LoRA if any LoRA paths are provided for backward compatibility.
|
||||
if cfg.lora_paths:
|
||||
if cfg.enable_lora is None:
|
||||
server_args._late_resolution("check_lora_server_args", enable_lora=True)
|
||||
declare_late_resolution(
|
||||
server_args, "check_lora_server_args", enable_lora=True
|
||||
)
|
||||
logger.warning(
|
||||
"--enable-lora is set to True because --lora-paths is provided."
|
||||
)
|
||||
@@ -34,8 +37,8 @@ def check_lora_server_args(server_args: Any):
|
||||
|
||||
if cfg.enable_lora:
|
||||
if cfg.enable_lora_overlap_loading is None:
|
||||
server_args._late_resolution(
|
||||
"check_lora_server_args", enable_lora_overlap_loading=False
|
||||
declare_late_resolution(
|
||||
server_args, "check_lora_server_args", enable_lora_overlap_loading=False
|
||||
)
|
||||
|
||||
if cfg.enable_lora_overlap_loading:
|
||||
@@ -90,11 +93,12 @@ def check_lora_server_args(server_args: Any):
|
||||
"Expected a string or a dictionary."
|
||||
)
|
||||
parsed_lora_paths.append(lora_ref)
|
||||
server_args._late_resolution(
|
||||
"check_lora_server_args", lora_paths=parsed_lora_paths
|
||||
declare_late_resolution(
|
||||
server_args, "check_lora_server_args", lora_paths=parsed_lora_paths
|
||||
)
|
||||
elif isinstance(cfg.lora_paths, dict):
|
||||
server_args._late_resolution(
|
||||
declare_late_resolution(
|
||||
server_args,
|
||||
"check_lora_server_args",
|
||||
lora_paths=[
|
||||
LoRARef(
|
||||
@@ -107,7 +111,9 @@ def check_lora_server_args(server_args: Any):
|
||||
],
|
||||
)
|
||||
elif cfg.lora_paths is None:
|
||||
server_args._late_resolution("check_lora_server_args", lora_paths=[])
|
||||
declare_late_resolution(
|
||||
server_args, "check_lora_server_args", lora_paths=[]
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid type for --lora-paths: {type(cfg.lora_paths)}. "
|
||||
@@ -117,7 +123,8 @@ def check_lora_server_args(server_args: Any):
|
||||
# Normalize target modules to a set; keep {"all"} as a sentinel
|
||||
# that gets resolved model-awarely in lora_manager.init_lora_shapes().
|
||||
if cfg.lora_target_modules:
|
||||
server_args._late_resolution(
|
||||
declare_late_resolution(
|
||||
server_args,
|
||||
"check_lora_server_args",
|
||||
lora_target_modules=set(cfg.lora_target_modules),
|
||||
)
|
||||
|
||||
@@ -9,9 +9,11 @@ from typing import Any
|
||||
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
declare_resolution,
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.model_executor.cuda_graph_config import Backend
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,6 +42,17 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
|
||||
The coefficient 1.5 is a heuristic value, in the future, we can do better estimation by looking at the model types, hidden sizes or even do a dummy run.
|
||||
"""
|
||||
from sglang.srt.arg_groups.cuda_graph_hook import (
|
||||
generate_cpu_graph_batch_sizes,
|
||||
generate_decode_cuda_graph_batch_sizes,
|
||||
generate_prefill_cuda_graph_batch_sizes,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
model_config_of,
|
||||
post_capture_kv_sizing_planned,
|
||||
use_mla_backend,
|
||||
)
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
# A copy, so an earlier declaration keeps the value it recorded.
|
||||
cuda_graph_config = copy.deepcopy(cfg.cuda_graph_config)
|
||||
@@ -139,10 +152,8 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
# Set cuda graph batch sizes
|
||||
if cfg.device != "cpu":
|
||||
if decode_cuda_graph_config.bs is None:
|
||||
decode_cuda_graph_config.bs = (
|
||||
server_args._generate_decode_cuda_graph_batch_sizes(
|
||||
decode_cuda_graph_config.max_bs
|
||||
)
|
||||
decode_cuda_graph_config.bs = generate_decode_cuda_graph_batch_sizes(
|
||||
server_args, decode_cuda_graph_config.max_bs
|
||||
)
|
||||
else:
|
||||
decode_cuda_graph_config.max_bs = max(decode_cuda_graph_config.bs)
|
||||
@@ -164,7 +175,7 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
torch_compile_max_bs=cfg.torch_compile_max_bs
|
||||
or decode_cuda_graph_config.max_bs,
|
||||
)
|
||||
decode_cuda_graph_config.bs = server_args._generate_cpu_graph_batch_sizes()
|
||||
decode_cuda_graph_config.bs = generate_cpu_graph_batch_sizes(server_args)
|
||||
|
||||
assert (
|
||||
cfg.torch_compile_max_bs > 0
|
||||
@@ -175,7 +186,7 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
# Refer to pr #15927, by default we set the prefill max_bs to the chunked prefill size.
|
||||
# For MLA backend, the introduction of piecewise cuda graph will influence the kernel dispatch difference compared to the original mode.
|
||||
# To avoid the performance regression, we set max_bs to 2048 by default.
|
||||
if not server_args.use_mla_backend():
|
||||
if not use_mla_backend(server_args):
|
||||
prefill_cuda_graph_config.max_bs = cfg.chunked_prefill_size
|
||||
else:
|
||||
prefill_cuda_graph_config.max_bs = 2048
|
||||
@@ -194,10 +205,8 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
)
|
||||
|
||||
if prefill_cuda_graph_config.bs is None:
|
||||
prefill_cuda_graph_config.bs = (
|
||||
server_args._generate_prefill_cuda_graph_batch_sizes(
|
||||
prefill_cuda_graph_config.max_bs
|
||||
)
|
||||
prefill_cuda_graph_config.bs = generate_prefill_cuda_graph_batch_sizes(
|
||||
server_args, prefill_cuda_graph_config.max_bs
|
||||
)
|
||||
|
||||
if cuda_graph_config != cfg.cuda_graph_config:
|
||||
@@ -208,7 +217,7 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
)
|
||||
|
||||
if cfg.mem_fraction_static is None:
|
||||
if server_args.post_capture_kv_sizing_planned():
|
||||
if post_capture_kv_sizing_planned(server_args):
|
||||
# Post-capture sizing measures free memory after graph capture, so
|
||||
# skip the graph/activation reserve; keep only the floor + parallel slack.
|
||||
reserved_mem = 1536
|
||||
@@ -230,11 +239,11 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
reserved_mem += activation_tokens * 1.5
|
||||
# Some adjustments for large parallel size
|
||||
reserved_mem += cfg.tp_size * cfg.pp_size / 8 * 1024
|
||||
reserved_mem += server_args.reserve_for_graph_mb()
|
||||
reserved_mem += reserve_for_graph_mb(server_args)
|
||||
if gpu_mem is not None and gpu_mem > 60 * 1024:
|
||||
reserved_mem = max(reserved_mem, 10 * 1024)
|
||||
# Reserve headroom for DeepEP all-to-all buffers on top of the floor.
|
||||
reserved_mem += server_args.reserve_for_deepep_a2a_mb()
|
||||
reserved_mem += reserve_for_deepep_a2a_mb(server_args)
|
||||
|
||||
declare_resolution(
|
||||
server_args,
|
||||
@@ -250,14 +259,14 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
# so we adjust the mem_fraction_static accordingly. The VLM encoder
|
||||
# only runs on the prefill stage, so PD decode engines do not need
|
||||
# this headroom; prefill engines and normal (non-PD) engines do.
|
||||
model_config = server_args.get_model_config()
|
||||
model_config = model_config_of(server_args)
|
||||
if (
|
||||
model_config.is_multimodal
|
||||
and not cfg.language_only
|
||||
and not cfg.language_model_only
|
||||
and cfg.disaggregation_mode != "decode"
|
||||
):
|
||||
server_args.adjust_mem_fraction_for_vlm(model_config)
|
||||
adjust_mem_fraction_for_vlm(server_args, model_config)
|
||||
|
||||
# If symm mem is enabled and prealloc size is not set, set it to 4GB
|
||||
if cfg.enable_symm_mem and not envs.SGLANG_SYMM_MEM_PREALLOC_GB_SIZE.is_set():
|
||||
@@ -266,3 +275,104 @@ def handle_gpu_memory_settings(server_args: Any, gpu_mem):
|
||||
"Symmetric memory is enabled, setting symmetric memory prealloc size to 4GB as default."
|
||||
"Use environment variable SGLANG_SYMM_MEM_PREALLOC_GB_SIZE to change the prealloc size."
|
||||
)
|
||||
|
||||
|
||||
def reserve_for_graph_mb(server_args: Any) -> float:
|
||||
from sglang.srt.arg_groups.overrides import use_mla_backend
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
decode_cuda_graph_config = cfg.cuda_graph_config.decode
|
||||
prefill_cuda_graph_config = cfg.cuda_graph_config.prefill
|
||||
|
||||
reserved_mem = 0.0
|
||||
if (
|
||||
cfg.disaggregation_mode != "prefill"
|
||||
and decode_cuda_graph_config.backend != Backend.DISABLED
|
||||
):
|
||||
reserved_mem += decode_cuda_graph_config.max_bs * 2
|
||||
|
||||
if (
|
||||
resolved_view(server_args).enable_dp_attention
|
||||
and cfg.disaggregation_mode != "prefill"
|
||||
):
|
||||
# DP attention needs more padding for some operations, and much more for large
|
||||
# cuda graph max bs (torch allocator / implementation inefficiencies).
|
||||
reserved_mem += decode_cuda_graph_config.max_bs * cfg.dp_size * 3
|
||||
if decode_cuda_graph_config.max_bs > 300:
|
||||
reserved_mem += decode_cuda_graph_config.max_bs * cfg.dp_size * 1.5
|
||||
|
||||
if (
|
||||
cfg.disaggregation_mode != "decode"
|
||||
and prefill_cuda_graph_config.backend != Backend.DISABLED
|
||||
):
|
||||
if not use_mla_backend(server_args):
|
||||
# Only non-torch memory is counted; torch memory is reused by cuda graph capture.
|
||||
reserved_mem += len(prefill_cuda_graph_config.bs) * 8
|
||||
else:
|
||||
# MLA backend overhead is much higher than expected with fa3.
|
||||
reserved_mem += 1.5 * 1024
|
||||
|
||||
if (
|
||||
prefill_cuda_graph_config.backend == Backend.BREAKABLE
|
||||
and resolved_view(server_args).moe_a2a_backend == "deepep"
|
||||
):
|
||||
# Prefill-BCG DeepEP delta (bridge pool + NVL first-touch
|
||||
# during capture); decode-side DeepEP is a baseline cost.
|
||||
reserved_mem += 1 * 1024
|
||||
|
||||
return reserved_mem
|
||||
|
||||
|
||||
def reserve_for_deepep_a2a_mb(server_args: Any) -> float:
|
||||
# DeepEP all-to-all buffers captured in the decode graph are real extra
|
||||
# allocations, reserved on top of the floor.
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
decode_cuda_graph_config = cfg.cuda_graph_config.decode
|
||||
if (
|
||||
cfg.disaggregation_mode != "prefill"
|
||||
and decode_cuda_graph_config.backend != Backend.DISABLED
|
||||
and resolved_view(server_args).moe_a2a_backend == "deepep"
|
||||
):
|
||||
return 2 * 1024
|
||||
return 0.0
|
||||
|
||||
|
||||
def adjust_mem_fraction_for_vlm(server_args: Any, model_config):
|
||||
cfg = resolving_view(server_args)
|
||||
vision_config = getattr(model_config.hf_config, "vision_config", None)
|
||||
if vision_config is None:
|
||||
return
|
||||
|
||||
# roughly reduce the mem_fraction_static base on params of Vit
|
||||
original_server_arg_mem_fraction = cfg.mem_fraction_static
|
||||
# a base mem_fraction_static factor for regular Vit
|
||||
base_mem_fraction_reduction_ratio = 0.95
|
||||
|
||||
vit_num_layers = getattr(vision_config, "num_hidden_layers", 24)
|
||||
vit_hidden_size = getattr(vision_config, "hidden_size", 1024)
|
||||
|
||||
# baseline ViT params (ViT-L/14)
|
||||
baseline_vit_layers = 24
|
||||
baseline_vit_hidden_size = 1024
|
||||
|
||||
# weight params count
|
||||
current_complexity_score = vit_num_layers * (vit_hidden_size**2)
|
||||
baseline_complexity_score = baseline_vit_layers * (baseline_vit_hidden_size**2)
|
||||
complexity_ratio = (
|
||||
current_complexity_score / baseline_complexity_score
|
||||
if baseline_complexity_score > 0
|
||||
else 1.0
|
||||
)
|
||||
|
||||
# every time the complexity grows 100%, adjust final factor for 10%
|
||||
sensitivity_scale = 0.1
|
||||
dynamic_adjustment_factor = 1.0 - sensitivity_scale * (complexity_ratio - 1.0)
|
||||
dynamic_adjustment_factor = max(0.8, min(1.05, dynamic_adjustment_factor))
|
||||
|
||||
final_overall_factor = base_mem_fraction_reduction_ratio * dynamic_adjustment_factor
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"adjust_mem_fraction_for_vlm",
|
||||
mem_fraction_static=original_server_arg_mem_fraction * final_overall_factor,
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any
|
||||
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
declare_resolution,
|
||||
mamba_cache_chunk_size,
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
)
|
||||
@@ -34,6 +35,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_model_specific_adjustments(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
model_config_of,
|
||||
use_mla_backend,
|
||||
)
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
from sglang.srt.configs.model_config import (
|
||||
get_mimo_v2_fused_qkv_expected_tp_size,
|
||||
@@ -57,7 +64,7 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
# key them on.
|
||||
return
|
||||
|
||||
model_config = server_args.get_model_config()
|
||||
model_config = model_config_of(server_args)
|
||||
hf_config = model_config.hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
|
||||
@@ -203,10 +210,14 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
import torch
|
||||
|
||||
major, _ = torch.cuda.get_device_capability()
|
||||
server_args._set_default_dsa_kv_cache_dtype(
|
||||
major, resolved_view(server_args).quantization
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_dsa_kv_cache_dtype_default,
|
||||
_dsa_split_backend_resolution,
|
||||
run_post_process_pass,
|
||||
)
|
||||
server_args._set_default_dsa_backends(major)
|
||||
|
||||
run_post_process_pass(server_args, _dsa_kv_cache_dtype_default)
|
||||
run_post_process_pass(server_args, _dsa_split_backend_resolution)
|
||||
|
||||
if cfg.enable_prefill_cp:
|
||||
assert (
|
||||
@@ -270,7 +281,7 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
# MLA prefill CP auto-config: the field declarations moved to
|
||||
# the override registry (arg_groups/overrides.py:
|
||||
# _deepseek_family_overrides).
|
||||
if cfg.enable_prefill_cp and server_args.use_mla_backend():
|
||||
if cfg.enable_prefill_cp and use_mla_backend(server_args):
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"_handle_model_specific_adjustments",
|
||||
@@ -304,7 +315,7 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
# here for the rest of the DSA family (DeepSeek-V3.2 /
|
||||
# GLM-5.x) that shares the same decode top-k path.
|
||||
envs.SGLANG_OPT_USE_TOPK_V2.set(False)
|
||||
if not server_args._resolved().enable_dp_attention and cfg.nnodes == 1:
|
||||
if not resolved_view(server_args).enable_dp_attention and cfg.nnodes == 1:
|
||||
# TODO (Hubert): Put this back later
|
||||
# server_args.enable_aiter_allreduce_fusion = True
|
||||
logger.info("Enable Aiter AllReduce Fusion for DeepseekV3ForCausalLM")
|
||||
@@ -372,8 +383,8 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
"intel_xpu",
|
||||
"aiter",
|
||||
]
|
||||
prefill_attn_backend, decode_attn_backend = (
|
||||
server_args._resolved_attention_backends()
|
||||
prefill_attn_backend, decode_attn_backend = attention_backends_of(
|
||||
resolved_view(server_args)
|
||||
)
|
||||
assert (
|
||||
prefill_attn_backend in supported_backends
|
||||
@@ -387,7 +398,7 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
quant_method = get_quantization_config(hf_config)
|
||||
is_mxfp4_quant_format = quant_method == "mxfp4"
|
||||
if (
|
||||
not server_args._resolved().enable_dp_attention
|
||||
not resolved_view(server_args).enable_dp_attention
|
||||
and cfg.nnodes == 1
|
||||
and is_hip()
|
||||
):
|
||||
@@ -407,13 +418,13 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
|
||||
if resolved_view(server_args).moe_runner_backend == "triton_kernel":
|
||||
assert (
|
||||
server_args._resolved().ep_size == 1
|
||||
resolved_view(server_args).ep_size == 1
|
||||
), "Triton kernel MoE is only supported when ep_size == 1"
|
||||
|
||||
elif model_arch in ("MiMoV2ForCausalLM", "MiMoV2FlashForCausalLM"):
|
||||
if model_arch == "MiMoV2ForCausalLM" and not cfg.encoder_only:
|
||||
expected_attn_tp_size = get_mimo_v2_fused_qkv_expected_tp_size(hf_config)
|
||||
view = server_args._resolved()
|
||||
view = resolved_view(server_args)
|
||||
attn_dp_size = cfg.dp_size if view.enable_dp_attention else 1
|
||||
effective_attn_tp_size = cfg.tp_size // attn_dp_size // view.attn_cp_size
|
||||
if (
|
||||
@@ -476,7 +487,9 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
):
|
||||
# Default attention backend selection moved to the override registry
|
||||
# (arg_groups/overrides.py: _gemma4_overrides).
|
||||
prefill_backend, decode_backend = server_args._resolved_attention_backends()
|
||||
prefill_backend, decode_backend = attention_backends_of(
|
||||
resolved_view(server_args)
|
||||
)
|
||||
accepted_backends = (
|
||||
"trtllm_mha",
|
||||
"triton",
|
||||
@@ -585,9 +598,13 @@ def handle_model_specific_adjustments(server_args: Any):
|
||||
|
||||
|
||||
def handle_model_capability_adjustments(server_args: Any):
|
||||
from sglang.srt.arg_groups.cuda_graph_hook import (
|
||||
generate_prefill_cuda_graph_batch_sizes,
|
||||
)
|
||||
from sglang.srt.arg_groups.kv_cache_hook import (
|
||||
validate_prefill_only_disable_kv_cache_args,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if parse_connector_type(cfg.model_path) == ConnectorType.INSTANCE:
|
||||
@@ -597,7 +614,7 @@ def handle_model_capability_adjustments(server_args: Any):
|
||||
run_post_process_pass,
|
||||
)
|
||||
|
||||
model_config = server_args.get_model_config()
|
||||
model_config = model_config_of(server_args)
|
||||
hf_config = model_config.hf_config
|
||||
|
||||
# HRM-Text needs bidirectional prompt attention (prefill), which only
|
||||
@@ -768,8 +785,8 @@ def handle_model_capability_adjustments(server_args: Any):
|
||||
)
|
||||
}
|
||||
if (Phase.PREFILL, "bs") not in cuda_graph_config_locked:
|
||||
sizing["bs"] = server_args._generate_prefill_cuda_graph_batch_sizes(
|
||||
sizing["max_bs"]
|
||||
sizing["bs"] = generate_prefill_cuda_graph_batch_sizes(
|
||||
server_args, sizing["max_bs"]
|
||||
)
|
||||
declare_resolution(
|
||||
server_args,
|
||||
@@ -831,13 +848,15 @@ def handle_mamba_radix_cache(server_args: Any, model_arch: str):
|
||||
validate_mamba_extra_buffer(
|
||||
view,
|
||||
model_arch,
|
||||
mamba_cache_chunk_size_of=lambda: server_args.mamba_cache_chunk_size,
|
||||
mamba_cache_chunk_size_of=lambda: mamba_cache_chunk_size(server_args),
|
||||
)
|
||||
else:
|
||||
validate_mamba_no_buffer(view, model_arch)
|
||||
|
||||
|
||||
def handle_language_model_only(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if not cfg.language_model_only:
|
||||
return
|
||||
@@ -858,7 +877,7 @@ def handle_language_model_only(server_args: Any):
|
||||
"--language-model-only is incompatible with --disaggregation-mode "
|
||||
"prefill/decode"
|
||||
)
|
||||
architectures = server_args.get_model_config().hf_config.architectures
|
||||
architectures = model_config_of(server_args).hf_config.architectures
|
||||
if not any(
|
||||
a in server_args.LANGUAGE_MODEL_ONLY_ARCHITECTURES for a in architectures
|
||||
):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
@@ -177,7 +178,7 @@ def handle_load_format(server_args: Any):
|
||||
load_format="gguf",
|
||||
)
|
||||
|
||||
if cfg.load_format == "auto" and server_args._is_mistral_native_format():
|
||||
if cfg.load_format == "auto" and is_mistral_native_format(server_args):
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"_handle_load_format",
|
||||
@@ -306,3 +307,62 @@ def validate_transfer_engine(server_args: Any):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def is_mistral_native_format(server_args: Any) -> bool:
|
||||
"""True iff the checkpoint requires load_format=mistral.
|
||||
|
||||
Looks for consolidated*.safetensors with no competing
|
||||
model*.safetensors; when both weight formats ship in the
|
||||
same checkpoint (e.g. Mistral-7B-Instruct-v0.3) the HF path is
|
||||
preferred to avoid loading Mistral-named weights into an
|
||||
HF-named architecture.
|
||||
|
||||
Name override: mistral-large-3 / mistral-small-4 /
|
||||
leanstral always treat as Mistral-native when params.json
|
||||
is present -- those families need Mistral weight loading
|
||||
regardless of which weight files happen to be present.
|
||||
"""
|
||||
cfg = resolving_view(server_args)
|
||||
_MISTRAL_NATIVE_PATTERNS = (
|
||||
"mistral-large-3",
|
||||
"mistral-small-4",
|
||||
"leanstral",
|
||||
)
|
||||
name_matches = any(
|
||||
p in str(cfg.model_path).lower() for p in _MISTRAL_NATIVE_PATTERNS
|
||||
)
|
||||
|
||||
def _check_format(has_params, has_consolidated, has_hf_weights) -> bool:
|
||||
if has_params and name_matches:
|
||||
return True
|
||||
return has_consolidated and not has_hf_weights
|
||||
|
||||
if os.path.isdir(cfg.model_path):
|
||||
return _check_format(
|
||||
has_params=os.path.exists(os.path.join(cfg.model_path, "params.json")),
|
||||
has_consolidated=bool(
|
||||
glob.glob(os.path.join(cfg.model_path, "consolidated*.safetensors"))
|
||||
),
|
||||
has_hf_weights=bool(
|
||||
glob.glob(os.path.join(cfg.model_path, "model*.safetensors"))
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
from huggingface_hub import HfApi
|
||||
|
||||
files = {s.rfilename for s in HfApi().model_info(cfg.model_path).siblings}
|
||||
return _check_format(
|
||||
has_params="params.json" in files,
|
||||
has_consolidated=any(
|
||||
f.startswith("consolidated") and f.endswith(".safetensors")
|
||||
for f in files
|
||||
),
|
||||
has_hf_weights=any(
|
||||
f.startswith("model") and f.endswith(".safetensors") and "/" not in f
|
||||
for f in files
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -8,7 +8,10 @@ import os
|
||||
from typing import Any
|
||||
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
cutedsl_moe_max_num_tokens,
|
||||
declare_resolution,
|
||||
max_prefill_buffer_tokens,
|
||||
max_speculative_num_draft_tokens,
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
)
|
||||
@@ -24,6 +27,8 @@ def handle_moe_kernel_config(server_args: Any):
|
||||
# The quantization-driven runner resolutions moved to the pipeline
|
||||
# (arg_groups/overrides.py: _moe_runner_backend_quant_constraints);
|
||||
# the compatibility asserts and fusion writes stay below.
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_moe_runner_backend_quant_constraints,
|
||||
@@ -50,7 +55,7 @@ def handle_moe_kernel_config(server_args: Any):
|
||||
# modelopt_mixed with non-NVFP4 MoE layers is rejected at load time.
|
||||
assert (
|
||||
view.quantization in ["modelopt_fp4", "modelopt_mixed", "nvfp4_online"]
|
||||
or server_args.get_model_config().nvfp4_moe_meta is not None
|
||||
or model_config_of(server_args).nvfp4_moe_meta is not None
|
||||
), f"Invalid quantization '{view.quantization}'. \nFlashInfer CuteDSL MOE currently supports only: 'modelopt_fp4', 'modelopt_mixed' (with NVFP4 MoE layers), 'nvfp4_online', or hybrid NVFP4 models."
|
||||
assert view.ep_size in [
|
||||
1,
|
||||
@@ -116,6 +121,8 @@ def handle_a2a_moe(server_args: Any):
|
||||
# the resolution pipeline (arg_groups/overrides.py:
|
||||
# _a2a_backend_overrides / _a2a_ep_size); the per-backend logs,
|
||||
# asserts, fusion/deepep_mode/env/cuda-graph writes stay below.
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_a2a_backend_overrides,
|
||||
@@ -259,7 +266,7 @@ def handle_a2a_moe(server_args: Any):
|
||||
logger.warning("--deepep-mode is ignored for Flashinfer MoE A2A")
|
||||
if not envs.SGLANG_MOE_NVFP4_DISPATCH.is_set() and (
|
||||
resolved_view(server_args).quantization == "modelopt_fp4"
|
||||
or server_args.get_model_config().nvfp4_moe_meta is not None
|
||||
or model_config_of(server_args).nvfp4_moe_meta is not None
|
||||
):
|
||||
envs.SGLANG_MOE_NVFP4_DISPATCH.set(True)
|
||||
logger.warning(
|
||||
@@ -291,7 +298,7 @@ def handle_a2a_moe(server_args: Any):
|
||||
# Skip validation if disaggregation mode is decode.
|
||||
if cfg.chunked_prefill_size > 0 and cfg.disaggregation_mode != "decode":
|
||||
assert (
|
||||
server_args._required_mori_dispatch_tokens_per_rank()
|
||||
required_mori_dispatch_tokens_per_rank(server_args)
|
||||
) <= envs.SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK.get(), (
|
||||
"SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK (default 4096) "
|
||||
"must be >= the per-rank MoRI dispatch tokens "
|
||||
@@ -339,7 +346,7 @@ def handle_a2a_moe(server_args: Any):
|
||||
# Skip validation if disaggregation mode is decode
|
||||
if cfg.chunked_prefill_size > 0 and cfg.disaggregation_mode != "decode":
|
||||
assert (
|
||||
server_args._required_pplx_dispatch_tokens_per_rank()
|
||||
required_pplx_dispatch_tokens_per_rank(server_args)
|
||||
) <= envs.SGLANG_PPLX_NUM_MAX_DISPATCH_TOKENS_PER_RANK.get(), (
|
||||
"SGLANG_PPLX_NUM_MAX_DISPATCH_TOKENS_PER_RANK (default 128) "
|
||||
"must be >= the per-rank pplx dispatch tokens "
|
||||
@@ -372,7 +379,7 @@ def validate_deepep_v2_dispatch_token_budget(server_args: Any) -> None:
|
||||
|
||||
capacity = envs.SGLANG_DEEPEP_V2_NUM_MAX_DISPATCH_TOKENS_PER_RANK.get()
|
||||
if view.disaggregation_mode != "decode":
|
||||
prefill_tokens = server_args.max_prefill_buffer_tokens() or (
|
||||
prefill_tokens = max_prefill_buffer_tokens(server_args) or (
|
||||
view.max_prefill_tokens or 0
|
||||
)
|
||||
if prefill_tokens > capacity:
|
||||
@@ -396,7 +403,7 @@ def validate_deepep_v2_dispatch_token_budget(server_args: Any) -> None:
|
||||
per_rank_pool_bs = max(1, view.max_running_requests // attn_dp_size)
|
||||
graph_bs = min(graph_bs, per_rank_pool_bs)
|
||||
tokens_per_req = (
|
||||
server_args.max_speculative_num_draft_tokens or 1
|
||||
max_speculative_num_draft_tokens(server_args) or 1
|
||||
if view.speculative_algorithm
|
||||
else 1
|
||||
)
|
||||
@@ -413,6 +420,8 @@ def validate_deepep_v2_dispatch_token_budget(server_args: Any) -> None:
|
||||
|
||||
def validate_deepep_v2_model_architecture(server_args: Any) -> None:
|
||||
"""Allow DeepEP v2 only where its model workflow is validated."""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
if (
|
||||
parse_connector_type(resolved_view(server_args).model_path)
|
||||
== ConnectorType.INSTANCE
|
||||
@@ -424,7 +433,7 @@ def validate_deepep_v2_model_architecture(server_args: Any) -> None:
|
||||
)
|
||||
|
||||
architectures = (
|
||||
getattr(server_args.get_model_config().hf_config, "architectures", None) or []
|
||||
getattr(model_config_of(server_args).hf_config, "architectures", None) or []
|
||||
)
|
||||
|
||||
architecture = architectures[0] if architectures else None
|
||||
@@ -458,7 +467,7 @@ def validate_cutedsl_a2a_token_budget(server_args: Any):
|
||||
and cfg.disaggregation_mode != "decode"
|
||||
):
|
||||
return
|
||||
required_tokens = server_args.cutedsl_moe_max_num_tokens()
|
||||
required_tokens = cutedsl_moe_max_num_tokens(server_args)
|
||||
max_dispatch_tokens_per_rank = (
|
||||
envs.SGLANG_FLASHINFER_NUM_MAX_DISPATCH_TOKENS_PER_RANK.get() or 1024
|
||||
)
|
||||
@@ -481,3 +490,18 @@ def validate_cutedsl_a2a_token_budget(server_args: Any):
|
||||
f"{required_per_rank}` or lower the relevant limit "
|
||||
f"(e.g. --max-prefill-tokens) to <= {max_cutedsl_tokens}."
|
||||
)
|
||||
|
||||
|
||||
def required_mori_dispatch_tokens_per_rank(server_args: Any) -> int:
|
||||
"""Max tokens a single rank dispatches through MoRI in one forward."""
|
||||
cfg = resolving_view(server_args)
|
||||
return cfg.chunked_prefill_size
|
||||
|
||||
|
||||
def required_pplx_dispatch_tokens_per_rank(server_args: Any) -> int:
|
||||
"""Max tokens a single rank dispatches through pplx in one forward."""
|
||||
cfg = resolving_view(server_args)
|
||||
required = cfg.chunked_prefill_size
|
||||
if cfg.cuda_graph_max_bs_decode is not None:
|
||||
required = max(required, cfg.cuda_graph_max_bs_decode)
|
||||
return required
|
||||
|
||||
@@ -38,12 +38,14 @@ import dataclasses
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import field_names, resolvable_fields
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.hardware_backend.mlx.runtime import use_mlx
|
||||
from sglang.srt.model_executor.cuda_graph_config import Backend
|
||||
from sglang.srt.platforms import current_platform
|
||||
from sglang.srt.utils.common import (
|
||||
cpu_has_amx_support,
|
||||
get_device_capability,
|
||||
@@ -57,9 +59,11 @@ from sglang.srt.utils.common import (
|
||||
is_flashinfer_available,
|
||||
is_gfx95_supported,
|
||||
is_hip,
|
||||
is_hopper_with_cuda_12_3,
|
||||
is_mnnvl_fabric_device,
|
||||
is_mps,
|
||||
is_musa,
|
||||
is_no_spec_infer_or_topk_one,
|
||||
is_npu,
|
||||
is_sm90_supported,
|
||||
is_sm100_supported,
|
||||
@@ -94,7 +98,7 @@ def register_model_override(architecture: str):
|
||||
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 ``server_args.get_model_config()`` (cached,
|
||||
the HF config go through ``model_config_of(server_args)`` (cached,
|
||||
read-only) — never anything mutating.
|
||||
"""
|
||||
|
||||
@@ -293,7 +297,7 @@ def declare_resolution(server_args: Any, source: str, **fields: Any) -> None:
|
||||
The stash *is* the resolution result: the bags are projected from it,
|
||||
`resolution_result` answers from it, and no field is written. A resolver
|
||||
reading a field another resolver may have decided must read `resolving_view`
|
||||
(or `ServerArgs._resolved()`), which
|
||||
(or `resolved_view(server_args)`), which
|
||||
`test_resolution_reads_the_declarations` pins.
|
||||
|
||||
For resolvers inside ``__post_init__``; launcher-stage resolution goes
|
||||
@@ -460,8 +464,7 @@ def resolved_view(server_args: Any) -> ResolvedView:
|
||||
overlaid on the fields, snapshotted per call.
|
||||
|
||||
For mid-resolution code that is not a pass (``__post_init__`` handlers and
|
||||
hooks), and for the record's own members that must answer with what
|
||||
resolution decided -- a declaration-only resolver (a model-specific
|
||||
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))
|
||||
@@ -702,7 +705,7 @@ def _kimi_k3_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
|
||||
if not (is_sm100_supported() and get_device_sm() in (100, 103)):
|
||||
return {}
|
||||
backends_unset = server_args.is_attention_backend_not_set()
|
||||
backends_unset = is_attention_backend_not_set(cfg)
|
||||
if cfg.speculative_algorithm != "DSPARK":
|
||||
if not backends_unset:
|
||||
return {}
|
||||
@@ -809,7 +812,7 @@ def _deepseek_family_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
|
||||
if is_deepseek_dsa(hf_config): # DeepSeek 3.2/GLM 5
|
||||
# Set attention backend for DeepSeek
|
||||
if server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
overrides["attention_backend"] = "dsa"
|
||||
logger.info("Use dsa attention backend for DeepSeek with DSA.")
|
||||
if not is_npu() and not is_xpu(): # CUDA or ROCm GPU
|
||||
@@ -882,7 +885,7 @@ def _deepseek_family_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
)
|
||||
# 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 server_args.use_mla_backend():
|
||||
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."
|
||||
@@ -944,7 +947,7 @@ def _minimax_m2_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
if (
|
||||
is_sm100_supported()
|
||||
and cfg.moe_runner_backend == "auto"
|
||||
and server_args.get_model_config().quantization == "modelopt_fp4"
|
||||
and model_config_of(server_args).quantization == "modelopt_fp4"
|
||||
):
|
||||
overrides["moe_runner_backend"] = "flashinfer_trtllm_routed"
|
||||
logger.info(
|
||||
@@ -971,7 +974,7 @@ def _minimax_m3_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
quant_resolved = quant_method
|
||||
|
||||
if is_hip():
|
||||
if server_args.is_attention_backend_not_set():
|
||||
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"
|
||||
@@ -994,7 +997,7 @@ def _minimax_m3_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
if not aiter_fusion_resolved and not envs.SGLANG_M3_ALLOW_CUSTOM_AR.get():
|
||||
overrides["disable_custom_all_reduce"] = True
|
||||
elif is_sm100_supported():
|
||||
if server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
if (
|
||||
cfg.kv_cache_dtype == "fp8_e4m3"
|
||||
and not envs.SGLANG_DISABLE_M3_FP8_ATTN_GEMM.get()
|
||||
@@ -1025,7 +1028,7 @@ def _minimax_m3_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
f"moe_runner_backend={overrides.get('moe_runner_backend', cfg.moe_runner_backend)}."
|
||||
)
|
||||
elif is_sm90_supported():
|
||||
if server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
overrides["attention_backend"] = "fa3"
|
||||
page_resolved = cfg.page_size
|
||||
if (
|
||||
@@ -1117,7 +1120,7 @@ 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 server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
if is_sm100_supported():
|
||||
overrides["attention_backend"] = "trtllm_mha"
|
||||
elif is_sm90_supported():
|
||||
@@ -1254,7 +1257,7 @@ 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 is_sm100_supported() else "triton"
|
||||
if server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
logger.info(
|
||||
f"Use {default_attention_backend} as default attention backend for Gemma4"
|
||||
)
|
||||
@@ -1265,7 +1268,7 @@ def _gemma4_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
elif cfg.attention_backend is None:
|
||||
overrides["attention_backend"] = default_attention_backend
|
||||
if is_sm100_supported() and cfg.moe_runner_backend == "auto":
|
||||
if server_args.get_model_config().quantization == "modelopt_fp4":
|
||||
if model_config_of(server_args).quantization == "modelopt_fp4":
|
||||
overrides["quantization"] = "modelopt_fp4"
|
||||
overrides["moe_runner_backend"] = "flashinfer_trtllm"
|
||||
logger.info(
|
||||
@@ -1278,12 +1281,12 @@ def _gemma4_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
@_register_for("MossVLForConditionalGeneration")
|
||||
def _moss_vl_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
overrides: Dict[str, Any] = {}
|
||||
if server_args.is_attention_backend_not_set():
|
||||
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 server_args.get_attention_backends()[0]
|
||||
or attention_backends_of(resolved_view(server_args))[0]
|
||||
)
|
||||
assert prefill_backend == "flashinfer", (
|
||||
"MossVLForConditionalGeneration requires flashinfer prefill "
|
||||
@@ -1323,7 +1326,7 @@ def _minicpm_sala_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
if dense_decode is not None:
|
||||
overrides["decode_attention_backend"] = dense_decode
|
||||
elif has_sparse_attention:
|
||||
uses_sparse_backend = cfg.is_attention_backend_not_set() or any(
|
||||
uses_sparse_backend = is_attention_backend_not_set(cfg) or any(
|
||||
backend in ("minicpm_flashattn", "minicpm_flashinfer")
|
||||
for backend in (
|
||||
cfg.attention_backend,
|
||||
@@ -1335,7 +1338,7 @@ def _minicpm_sala_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
raise ValueError(
|
||||
"MiniCPM sparse attention does not support PD disaggregation"
|
||||
)
|
||||
if cfg.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
overrides["attention_backend"] = (
|
||||
"minicpm_flashinfer"
|
||||
if is_blackwell_supported()
|
||||
@@ -1413,7 +1416,7 @@ def _deepseek_v4_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
logger.info(f"Setting swa_full_tokens_ratio to 0.1 for {model_arch}.")
|
||||
|
||||
if cfg.moe_runner_backend == "auto":
|
||||
model_config = server_args.get_model_config()
|
||||
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"
|
||||
@@ -1477,7 +1480,7 @@ def _inkling_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
# 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 server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
inkling_attn_backend = "fa4" if is_sm100_supported() else "triton"
|
||||
overrides["attention_backend"] = inkling_attn_backend
|
||||
logger.info(
|
||||
@@ -1495,7 +1498,7 @@ def _nemotron_h_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
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 = server_args.get_model_config()
|
||||
model_config = model_config_of(server_args)
|
||||
overrides: Dict[str, Any] = {}
|
||||
|
||||
is_modelopt = model_config.quantization in [
|
||||
@@ -1565,7 +1568,7 @@ def _nemotron_h_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
else:
|
||||
overrides["moe_runner_backend"] = "flashinfer_cutlass"
|
||||
|
||||
if is_blackwell_supported() and cfg.is_attention_backend_not_set():
|
||||
if is_blackwell_supported() and is_attention_backend_not_set(cfg):
|
||||
if cfg.speculative_algorithm is not None:
|
||||
speculative_algorithm = cfg.speculative_algorithm.upper()
|
||||
if is_sm100_supported() and cfg.speculative_eagle_topk in (
|
||||
@@ -1607,13 +1610,14 @@ def _qwen3_5_hybrid_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
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.
|
||||
# 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 = server_args._get_default_attn_backend(
|
||||
use_mla_backend=server_args.use_mla_backend(),
|
||||
model_config=server_args.get_model_config(),
|
||||
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
|
||||
@@ -1746,7 +1750,7 @@ def _olmo2_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
def _step3p_overrides(server_args: Any, hf_config: Any) -> dict:
|
||||
cfg = resolving_view(server_args)
|
||||
overrides: Dict[str, Any] = {}
|
||||
if server_args.is_attention_backend_not_set():
|
||||
if is_attention_backend_not_set(cfg):
|
||||
if is_blackwell_supported():
|
||||
logger.info("Auto-select fa4 attention backend for Step3p7 on Blackwell.")
|
||||
overrides["attention_backend"] = "fa4"
|
||||
@@ -1857,7 +1861,7 @@ def _mamba_radix_cache_resolution(view: Any) -> dict:
|
||||
get_linear_attn_spec_by_arch,
|
||||
)
|
||||
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
|
||||
in_branch = model_arch in _MAMBA_RADIX_CACHE_ARCHS
|
||||
@@ -1895,7 +1899,7 @@ def _dsa_kv_cache_dtype_default(view: Any) -> dict:
|
||||
PRISTINE dsa split backends (their resolution runs after this pass)."""
|
||||
from sglang.srt.configs.model_config import is_deepseek_dsa
|
||||
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
if hf_config.architectures[0] not in _DEEPSEEK_FAMILY_ARCHS:
|
||||
return {}
|
||||
if not is_deepseek_dsa(hf_config):
|
||||
@@ -1965,7 +1969,7 @@ def _dsa_split_backend_resolution(view: Any) -> dict:
|
||||
capability. The hisparse arm takes precedence under --enable-hisparse."""
|
||||
from sglang.srt.configs.model_config import is_deepseek_dsa
|
||||
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
if hf_config.architectures[0] not in _DEEPSEEK_FAMILY_ARCHS:
|
||||
return {}
|
||||
if not is_deepseek_dsa(hf_config):
|
||||
@@ -2065,7 +2069,7 @@ def _deepseek_moe_quant_resolution(view: Any) -> dict:
|
||||
backend for DeepSeek"), NOT a dispatch-time declaration: the DSA
|
||||
kv-cache-dtype default earlier in the branch must read the PRISTINE
|
||||
quantization, so this resolution has to stay at its legacy slot."""
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
if model_arch not in _DEEPSEEK_FAMILY_ARCHS:
|
||||
return {}
|
||||
@@ -2152,7 +2156,7 @@ def _deepseek_spec_moe_resolution(view: Any) -> dict:
|
||||
quantization (after _deepseek_moe_quant_resolution) and the pre-a2a
|
||||
ep_size, exactly like the legacy in-branch writes."""
|
||||
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
if model_arch not in _DEEPSEEK_FAMILY_ARCHS:
|
||||
return {}
|
||||
@@ -2198,7 +2202,7 @@ def _deepseek_v4_kv_cache_dtype(view: Any) -> dict:
|
||||
"""Slot pass in the DeepSeek V4 hook: default the kv-cache dtype to FP8
|
||||
(bfloat16 on NPU, where the pool geometry differs) and validate the
|
||||
result. The NPU split-backend writes stay in the hook."""
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
if model_arch != "DeepseekV4ForCausalLM":
|
||||
return {}
|
||||
@@ -2271,7 +2275,7 @@ def _flashinfer_allreduce_fusion_auto_enable(view: Any) -> dict:
|
||||
single-node systems. Reads the mid-resolution enable_dp_attention /
|
||||
moe_a2a_backend (after the DeepSeek CP and a2a declarations), exactly
|
||||
like the legacy tail block."""
|
||||
model_arch = view.get_model_config().hf_config.architectures[0]
|
||||
model_arch = model_config_of(view).hf_config.architectures[0]
|
||||
if envs.SGLANG_FLASHINFER_MNNVL_CUTEDSL_AR_FUSION.get() and model_arch in {
|
||||
"Qwen3_5MoeForCausalLM",
|
||||
"Qwen3_5MoeForConditionalGeneration",
|
||||
@@ -2345,7 +2349,7 @@ def _deterministic_is_deepseek_model(view: Any) -> bool:
|
||||
if parse_connector_type(view.model_path) == ConnectorType.INSTANCE:
|
||||
return False
|
||||
try:
|
||||
hf_config = view.get_model_config().hf_config
|
||||
hf_config = model_config_of(view).hf_config
|
||||
return hf_config.architectures[0] in [
|
||||
"DeepseekV2ForCausalLM",
|
||||
"DeepseekV3ForCausalLM",
|
||||
@@ -2413,8 +2417,8 @@ def _attention_backend_default(view: Any) -> dict:
|
||||
): # override the default attention backend
|
||||
return {"attention_backend": view.prefill_attention_backend}
|
||||
if view.attention_backend is None:
|
||||
backend = view._get_default_attn_backend(
|
||||
view.use_mla_backend(), view.get_model_config()
|
||||
backend = get_default_attn_backend(
|
||||
view, use_mla_backend(view), model_config_of(view)
|
||||
)
|
||||
logger.info(
|
||||
f"Attention backend not specified. Use {backend} backend by default."
|
||||
@@ -2600,7 +2604,7 @@ def _fa4_page_constraint(view: Any) -> dict:
|
||||
or view.decode_attention_backend == "fa4"
|
||||
or view.prefill_attention_backend == "fa4"
|
||||
)
|
||||
and not view.use_mla_backend()
|
||||
and not use_mla_backend(view)
|
||||
and is_sm100_supported()
|
||||
# EAGLE topk>1 spec runs the two-pass page-tree cascade, which the FA4
|
||||
# CUTLASS kernel aborts on at page_size>1. That path only works at
|
||||
@@ -2641,7 +2645,7 @@ def _attention_backend_platform_fallbacks(view: Any) -> dict:
|
||||
def _intel_xpu_page_constraint(view: Any) -> dict:
|
||||
_, decode_backend = attention_backends_of(view)
|
||||
if decode_backend == "intel_xpu":
|
||||
if view.use_mla_backend():
|
||||
if use_mla_backend(view):
|
||||
supported_page_sizes = [16, 32, 64, 128]
|
||||
msg = "Intel XPU attention backend for MLA Decode"
|
||||
else:
|
||||
@@ -2658,7 +2662,7 @@ def _intel_xpu_page_constraint(view: Any) -> dict:
|
||||
@register_post_process
|
||||
def _attention_backend_dual_chunk(view: Any) -> dict:
|
||||
if (
|
||||
getattr(view.get_model_config().hf_config, "dual_chunk_attention_config", None)
|
||||
getattr(model_config_of(view).hf_config, "dual_chunk_attention_config", None)
|
||||
is not None
|
||||
):
|
||||
if view.attention_backend is None:
|
||||
@@ -3036,3 +3040,305 @@ def _hrm_text_attention_force(view: Any) -> dict:
|
||||
"attention."
|
||||
)
|
||||
return {"attention_backend": "triton"}
|
||||
|
||||
|
||||
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 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 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 (
|
||||
is_sm100_supported()
|
||||
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 is_hip():
|
||||
return "aiter"
|
||||
elif is_mps():
|
||||
return "torch_native"
|
||||
else:
|
||||
# FlashInfer does not support attention sinks.
|
||||
if is_flashinfer_available() and not model_config.has_attention_sinks:
|
||||
return "flashinfer"
|
||||
return "triton"
|
||||
else:
|
||||
# MLA architecture
|
||||
if is_hopper_with_cuda_12_3():
|
||||
return "fa3"
|
||||
elif is_sm100_supported():
|
||||
return "flashinfer"
|
||||
elif 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 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 should_report_expert_balancedness(server_args: Any) -> bool:
|
||||
cfg = resolving_view(server_args)
|
||||
return cfg.expert_balancedness_report_mode != "off"
|
||||
|
||||
|
||||
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 post_capture_kv_sizing_planned(server_args: Any) -> bool:
|
||||
"""Whether the mem_fraction heuristic may skip the graph reserve; must be
|
||||
False for any config the runtime won't post-capture-size, else it gets an
|
||||
under-reserved fraction."""
|
||||
cfg = resolving_view(server_args)
|
||||
mla_enabled = use_mla_backend(server_args)
|
||||
if not envs.SGLANG_ENABLE_POST_CAPTURE_KV_SIZING.get():
|
||||
return False
|
||||
if cfg.device != "cuda":
|
||||
return False
|
||||
if cfg.dcp_size != 1:
|
||||
return False
|
||||
if mla_enabled:
|
||||
return False
|
||||
if cfg.kv_cache_dtype == "fp4_e2m1":
|
||||
return False
|
||||
if cfg.prefill_only_disable_kv_cache:
|
||||
return False
|
||||
if cfg.enable_memory_saver:
|
||||
return False
|
||||
if envs.SGLANG_MOONCAKE_CUSTOM_MEM_POOL.get() is not None:
|
||||
return False
|
||||
|
||||
if (
|
||||
cfg.disaggregation_mode != "prefill"
|
||||
and cfg.cuda_graph_config.decode.backend == Backend.DISABLED
|
||||
):
|
||||
return False
|
||||
|
||||
if cfg.disaggregation_mode != "decode":
|
||||
prefill_cfg = cfg.cuda_graph_config.prefill
|
||||
# We can only skip eager activation headroom when the largest
|
||||
# prefill forward batch size is already graph-captured. Otherwise,
|
||||
# an eager forward will need more memory and lead to OOM.
|
||||
if (
|
||||
prefill_cfg.backend == Backend.DISABLED
|
||||
or cfg.chunked_prefill_size <= 0
|
||||
or max_prefill_buffer_tokens(server_args) > max(prefill_cfg.bs or (0,))
|
||||
):
|
||||
return False
|
||||
|
||||
from sglang.srt.configs.model_config import is_deepseek_v4, is_minimax_sparse
|
||||
|
||||
hf_config = model_config_of(server_args).hf_config
|
||||
if is_deepseek_v4(hf_config) or is_minimax_sparse(hf_config):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def cutedsl_moe_max_num_tokens(server_args: Any) -> int:
|
||||
"""Largest number of tokens a single forward routes through a CuteDSL
|
||||
MoE layer on one (DP) rank. Single source of truth for both the
|
||||
standard-allgather wrapper buffers and the FlashInfer A2A dispatcher
|
||||
budget. Max over the prefill (max_prefill_tokens), piecewise-prefill
|
||||
capture, and decode/verify bounds; num_tokens_per_req is
|
||||
speculative_num_draft_tokens under speculative decoding, else 1.
|
||||
"""
|
||||
cfg = resolving_view(server_args)
|
||||
if cfg.speculative_algorithm:
|
||||
num_tokens_per_req = cfg.speculative_num_draft_tokens or 1
|
||||
else:
|
||||
num_tokens_per_req = 1
|
||||
prefill_tokens = cfg.max_prefill_tokens
|
||||
cg_config = cfg.cuda_graph_config
|
||||
if cg_config is not None and cg_config.prefill.backend == Backend.TC_PIECEWISE:
|
||||
prefill_tokens = max(prefill_tokens, cg_config.prefill.max_bs or 0)
|
||||
decode_max_bs = (cg_config.decode.max_bs if cg_config is not None else 0) or 0
|
||||
decode_tokens = decode_max_bs * num_tokens_per_req
|
||||
return max(prefill_tokens, decode_tokens)
|
||||
|
||||
|
||||
def max_prefill_buffer_tokens(server_args: Any) -> int:
|
||||
"""Prefill-buffer ceiling: chunked_prefill_size, except PP dynamic
|
||||
chunking can grow chunks toward max_prefill_tokens and probe at 1.25x."""
|
||||
cfg = resolving_view(server_args)
|
||||
chunked = (
|
||||
cfg.chunked_prefill_size
|
||||
if cfg.chunked_prefill_size and cfg.chunked_prefill_size > 0
|
||||
else 0
|
||||
)
|
||||
tokens = chunked
|
||||
if cfg.enable_dynamic_chunking and cfg.pp_size > 1 and chunked:
|
||||
tokens = max(tokens, cfg.max_prefill_tokens or 0, math.ceil(chunked * 1.25))
|
||||
return tokens
|
||||
|
||||
|
||||
def mamba_cache_chunk_size(server_args: Any) -> int:
|
||||
# For mamba cache with extra buffer, the chunk size is the max of FLA_CHUNK_SIZE
|
||||
# (or mamba_chunk_size if it is defined in the model's config) and page_size.
|
||||
# It is used to determine the caching point in a sequence during prefill.
|
||||
# A pre-seeded `_mamba_cache_chunk_size` (fixtures supply one so a dummy
|
||||
# model never loads an HF config) is honored as-is; otherwise the memo
|
||||
# is only kept once the record is resolved, because `page_size` below
|
||||
# is resolution-written.
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
if not hasattr(server_args, "_mamba_cache_chunk_size"):
|
||||
|
||||
try:
|
||||
from sglang.kernels.ops.attention.fla.chunk_delta_h import (
|
||||
CHUNK_SIZE as FLA_CHUNK_SIZE,
|
||||
)
|
||||
except ImportError:
|
||||
# Must match sglang.kernels.ops.attention.fla.chunk_delta_h.CHUNK_SIZE
|
||||
FLA_CHUNK_SIZE = 64
|
||||
|
||||
hf_config = model_config_of(server_args).hf_config
|
||||
chunk_size = getattr(hf_config, "mamba_chunk_size", FLA_CHUNK_SIZE)
|
||||
page_size = resolved_view(server_args).page_size
|
||||
assert (
|
||||
max(chunk_size, page_size) % min(chunk_size, page_size) == 0
|
||||
), f"For SSM models, either chunk_size or page_size must be divisible by the other, got {chunk_size=}, {page_size=}"
|
||||
if not getattr(server_args, "_resolution_finished", False):
|
||||
return max(chunk_size, page_size)
|
||||
server_args._mamba_cache_chunk_size = max(chunk_size, page_size)
|
||||
return server_args._mamba_cache_chunk_size
|
||||
|
||||
|
||||
def max_speculative_num_draft_tokens(server_args: Any) -> Optional[int]:
|
||||
"""Return the maximum draft-token count speculative decoding may use.
|
||||
|
||||
Memoized only once the record is resolved: an answer computed off a raw
|
||||
record describes inputs resolution is about to rewrite (auto speculative
|
||||
sizing fills `speculative_num_draft_tokens` in), and a cache filled that
|
||||
early would keep answering with it.
|
||||
"""
|
||||
cfg = resolving_view(server_args)
|
||||
|
||||
memo = server_args.__dict__.get("_max_speculative_num_draft_tokens")
|
||||
if memo is not None:
|
||||
return memo
|
||||
if cfg.speculative_num_draft_tokens is None:
|
||||
result = None
|
||||
elif not cfg.speculative_adaptive:
|
||||
result = cfg.speculative_num_draft_tokens
|
||||
else:
|
||||
from sglang.srt.speculative.adaptive_spec_params import (
|
||||
resolve_candidate_steps_from_config,
|
||||
)
|
||||
|
||||
candidate_steps = resolve_candidate_steps_from_config(
|
||||
cfg_path=cfg.speculative_adaptive_config,
|
||||
)
|
||||
# TODO: adaptive spec currently requires topk=1, so each runtime
|
||||
# state needs steps + 1 draft-token slots. Revisit this if topk>1
|
||||
# is supported.
|
||||
result = max(candidate_steps) + 1
|
||||
if getattr(server_args, "_resolution_finished", False):
|
||||
server_args._max_speculative_num_draft_tokens = result
|
||||
return result
|
||||
|
||||
@@ -11,6 +11,7 @@ from sglang.srt.arg_groups.overrides import (
|
||||
declare_resolution,
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
should_report_expert_balancedness,
|
||||
)
|
||||
from sglang.srt.connector import ConnectorType
|
||||
from sglang.srt.environ import envs
|
||||
@@ -21,12 +22,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_context_parallelism(server_args: Any):
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
if parse_connector_type(cfg.model_path) != ConnectorType.INSTANCE:
|
||||
from sglang.srt.configs.model_config import is_deepseek_dsa
|
||||
from sglang.srt.layers.cp.utils import CP_V2_DEFAULT_MODEL_CLASSES
|
||||
|
||||
model_config = server_args.get_model_config()
|
||||
model_config = model_config_of(server_args)
|
||||
hf_config = model_config.hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
if model_arch in CP_V2_DEFAULT_MODEL_CLASSES:
|
||||
@@ -154,6 +157,10 @@ def handle_dcp_validation(server_args: Any):
|
||||
def handle_data_parallelism(server_args: Any):
|
||||
# The dp_size==1 resets moved to the resolution pipeline
|
||||
# (arg_groups/overrides.py: _data_parallelism_defaults).
|
||||
from sglang.srt.arg_groups.cuda_graph_hook import (
|
||||
generate_prefill_cuda_graph_batch_sizes,
|
||||
)
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_data_parallelism_defaults,
|
||||
@@ -213,8 +220,8 @@ def handle_data_parallelism(server_args: Any):
|
||||
):
|
||||
clamped = {"max_bs": cfg.chunked_prefill_size}
|
||||
if (Phase.PREFILL, "bs") not in server_args._cuda_graph_config_locked:
|
||||
clamped["bs"] = server_args._generate_prefill_cuda_graph_batch_sizes(
|
||||
clamped["max_bs"]
|
||||
clamped["bs"] = generate_prefill_cuda_graph_batch_sizes(
|
||||
server_args, clamped["max_bs"]
|
||||
)
|
||||
declare_resolution(
|
||||
server_args,
|
||||
@@ -636,7 +643,7 @@ def handle_expert_distribution_metrics(server_args: Any):
|
||||
"prometheus, both."
|
||||
)
|
||||
|
||||
if server_args.should_report_expert_balancedness() and (
|
||||
if should_report_expert_balancedness(server_args) and (
|
||||
cfg.expert_distribution_recorder_mode is None
|
||||
):
|
||||
declare_resolution(
|
||||
|
||||
@@ -186,6 +186,7 @@ def _alias_bootstrap_port_to_api_port(server_args: ServerArgs) -> None:
|
||||
|
||||
def handle_encoder_disaggregation(server_args: Any):
|
||||
from sglang.srt.arg_groups.model_hook import handle_language_model_only
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
from sglang.srt.arg_groups.validation_hook import validate_ib_devices
|
||||
from sglang.srt.server_args import resolve_encoder_transfer_backend
|
||||
|
||||
@@ -223,7 +224,7 @@ def handle_encoder_disaggregation(server_args: Any):
|
||||
)
|
||||
|
||||
# Validate model type for encoder disaggregation
|
||||
hf_config = server_args.get_model_config().hf_config
|
||||
hf_config = model_config_of(server_args).hf_config
|
||||
model_arch = hf_config.architectures[0]
|
||||
if cfg.encoder_transfer_backend == "auto":
|
||||
declare_resolution(
|
||||
|
||||
@@ -751,6 +751,8 @@ def handle_multimodal_feature_transport(server_args: Any):
|
||||
may still auto-select CUDA VMM. The legacy CUDA IPC flag and environment
|
||||
variable remain supported so existing deployments map to this policy.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
requested_transport = cfg.mm_feature_transport
|
||||
legacy_ipc_is_set = envs.SGLANG_USE_CUDA_IPC_TRANSPORT.is_set()
|
||||
@@ -785,7 +787,7 @@ def handle_multimodal_feature_transport(server_args: Any):
|
||||
"--encoder-transfer-backend instead."
|
||||
)
|
||||
elif (
|
||||
server_args.get_model_config().is_multimodal
|
||||
model_config_of(server_args).is_multimodal
|
||||
and is_cuda()
|
||||
and cfg.disaggregation_mode == "null"
|
||||
):
|
||||
@@ -803,7 +805,7 @@ def handle_multimodal_feature_transport(server_args: Any):
|
||||
supports_cuda_vmm_feature_transport,
|
||||
)
|
||||
|
||||
if supports_cuda_vmm_feature_transport(server_args.get_model_config()):
|
||||
if supports_cuda_vmm_feature_transport(model_config_of(server_args)):
|
||||
requested_transport = "cuda_vmm"
|
||||
logger.info(
|
||||
"Multimodal feature transport auto-resolved to "
|
||||
|
||||
@@ -337,11 +337,12 @@ def _handle_dflash(server_args: ServerArgs) -> None:
|
||||
|
||||
|
||||
def _target_checkpoint_bundles_dspark_draft(server_args: ServerArgs) -> bool:
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
from sglang.srt.speculative.dspark_components.dspark_config import (
|
||||
checkpoint_bundles_dspark_draft,
|
||||
)
|
||||
|
||||
return checkpoint_bundles_dspark_draft(server_args.get_model_config().hf_config)
|
||||
return checkpoint_bundles_dspark_draft(model_config_of(server_args).hf_config)
|
||||
|
||||
|
||||
def _handle_dspark(server_args: ServerArgs) -> None:
|
||||
@@ -662,6 +663,8 @@ def _handle_frozen_kv_mtp(server_args: ServerArgs) -> None:
|
||||
|
||||
|
||||
def _handle_eagle_family(server_args: ServerArgs) -> None:
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
@@ -706,7 +709,7 @@ def _handle_eagle_family(server_args: ServerArgs) -> None:
|
||||
"eagle speculative decoding."
|
||||
)
|
||||
|
||||
model_arch = server_args.get_model_config().hf_config.architectures[0]
|
||||
model_arch = model_config_of(server_args).hf_config.architectures[0]
|
||||
if model_arch in [
|
||||
"DeepseekV32ForCausalLM",
|
||||
"DeepseekV3ForCausalLM",
|
||||
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
)
|
||||
from sglang.srt.distributed.device_communicators.mooncake_transfer_engine import (
|
||||
@@ -389,7 +390,7 @@ def validate_ib_devices(server_args: Any, device_str: Optional[str]) -> Optional
|
||||
|
||||
|
||||
def validate_experimental_sgl_marlin(server_args: Any):
|
||||
view = server_args._resolved()
|
||||
view = resolved_view(server_args)
|
||||
if view.moe_runner_backend != "experimental_sgl_marlin":
|
||||
return
|
||||
|
||||
|
||||
@@ -48,7 +48,10 @@ import torch
|
||||
import uvloop
|
||||
import zmq
|
||||
|
||||
from sglang.srt.arg_groups.overrides import resolving_view
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
)
|
||||
from sglang.srt.elastic_ep.expert_backup_manager import run_expert_backup_manager
|
||||
from sglang.srt.entrypoints.engine_info_bootstrap_server import (
|
||||
EngineInfoBootstrapServer,
|
||||
@@ -1627,6 +1630,8 @@ class Engine(EngineScoreMixin, EngineBase):
|
||||
|
||||
|
||||
def _set_envs_and_config(server_args: ServerArgs):
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
# Set global environments
|
||||
# MNNVL fabric (GB200/GB300) multi-node: cross-node NVLink needs NCCL's
|
||||
@@ -1678,7 +1683,7 @@ def _set_envs_and_config(server_args: ServerArgs):
|
||||
|
||||
# Check flashinfer version
|
||||
if not get_bool_env_var("SGLANG_SKIP_SGL_KERNEL_VERSION_CHECK"):
|
||||
if "flashinfer" in cfg.get_attention_backends():
|
||||
if "flashinfer" in attention_backends_of(resolved_view(cfg)):
|
||||
assert_pkg_version(
|
||||
"flashinfer_python",
|
||||
"0.6.17",
|
||||
|
||||
@@ -38,6 +38,7 @@ import einops
|
||||
import torch
|
||||
import torch.distributed
|
||||
|
||||
from sglang.srt.arg_groups.overrides import should_report_expert_balancedness
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
from sglang.srt.observability.metrics_collector import (
|
||||
@@ -179,7 +180,7 @@ class _ExpertDistributionRecorderReal(ExpertDistributionRecorder):
|
||||
for k in self._accumulator.get_single_pass_gatherer_keys()
|
||||
}
|
||||
|
||||
if server_args.should_report_expert_balancedness():
|
||||
if should_report_expert_balancedness(server_args):
|
||||
logger.info(
|
||||
"ExpertDistributionRecorder auto start record since "
|
||||
f"expert_balancedness_report_mode={get_exec().moe.expert_balancedness_report_mode}"
|
||||
@@ -718,7 +719,7 @@ class _UtilizationRateAccumulatorMixin(_Accumulator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._enable = self._server_args.should_report_expert_balancedness()
|
||||
self._enable = should_report_expert_balancedness(self._server_args)
|
||||
|
||||
if self._enable:
|
||||
self.window_sizes = EPLB_BALANCEDNESS_WINDOW_SIZES
|
||||
|
||||
@@ -44,7 +44,7 @@ def set_default_server_args(args: "ServerArgs"):
|
||||
"""
|
||||
Set default server arguments for NPU backend.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import resolving_view
|
||||
from sglang.srt.arg_groups.overrides import resolving_view, use_mla_backend
|
||||
|
||||
cfg = resolving_view(args)
|
||||
|
||||
@@ -148,7 +148,7 @@ def set_default_server_args(args: "ServerArgs"):
|
||||
"set_default_server_args",
|
||||
hicache_io_backend="kernel_ascend",
|
||||
)
|
||||
if args.use_mla_backend():
|
||||
if use_mla_backend(args):
|
||||
declare_resolution(
|
||||
args,
|
||||
"set_default_server_args",
|
||||
|
||||
@@ -74,7 +74,12 @@ def create_trtllm_mla_backend(runner):
|
||||
if not runner.use_mla_backend:
|
||||
raise ValueError("trtllm_mla backend can only be used with MLA models.")
|
||||
if get_parallel().dcp_enabled and get_spec().speculative_algorithm is not None:
|
||||
_, decode_backend = runner.server_args.get_attention_backends()
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
resolved_view,
|
||||
)
|
||||
|
||||
_, decode_backend = attention_backends_of(resolved_view(runner.server_args))
|
||||
if decode_backend == "trtllm_mla":
|
||||
raise ValueError(
|
||||
"trtllm_mla cannot serve decode context parallelism with speculative "
|
||||
|
||||
@@ -56,7 +56,7 @@ from sglang.srt.layers.attention.trtllm_mla_backend import (
|
||||
)
|
||||
from sglang.srt.layers.dcp.layout import get_dcp_lens
|
||||
from sglang.srt.layers.logits_processor import get_in_autotune_dummy_run
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
from sglang.srt.runtime_context import get_parallel, max_speculative_num_draft_tokens
|
||||
from sglang.srt.utils import is_flashinfer_available, is_tokenspeed_mla_available
|
||||
|
||||
if is_flashinfer_available():
|
||||
@@ -145,9 +145,7 @@ class TokenspeedMLABackend(TRTLLMMLABackend):
|
||||
self.device,
|
||||
self.num_q_heads,
|
||||
self.kv_lora_rank,
|
||||
max_q_len=(
|
||||
model_runner.server_args.max_speculative_num_draft_tokens or 1
|
||||
),
|
||||
max_q_len=(max_speculative_num_draft_tokens() or 1),
|
||||
)
|
||||
|
||||
# Pre-JIT the prefill kernel variants. Each cute.compile takes 1-2
|
||||
|
||||
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
from sglang.srt.layers.cp.base import get_cp_strategy
|
||||
from sglang.srt.layers.cp.padding import get_cp_padding_align_size
|
||||
from sglang.srt.layers.cp.utils import (
|
||||
@@ -42,11 +43,11 @@ if TYPE_CHECKING:
|
||||
|
||||
def supports_prefill_cp_bcg(server_args: ServerArgs) -> bool:
|
||||
"""Return whether the selected prefill-CP configuration supports BCG."""
|
||||
from sglang.srt.arg_groups.overrides import resolving_view
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of, resolving_view
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
resolved = server_args._resolved()
|
||||
prefill_attention_backend, _ = server_args._resolved_attention_backends()
|
||||
resolved = resolved_view(server_args)
|
||||
prefill_attention_backend, _ = attention_backends_of(resolved_view(server_args))
|
||||
return (
|
||||
cfg.enable_prefill_cp
|
||||
and resolved.attn_cp_size == cfg.tp_size
|
||||
@@ -64,7 +65,7 @@ def filter_prefill_cp_bcg_capture_num_tokens(
|
||||
capture_num_tokens: list[int], server_args: ServerArgs
|
||||
) -> list[int]:
|
||||
"""Keep only token buckets where the zigzag CP strategy can run."""
|
||||
min_num_tokens = server_args._resolved().attn_cp_size * 2
|
||||
min_num_tokens = resolved_view(server_args).attn_cp_size * 2
|
||||
filtered = [size for size in capture_num_tokens if size >= min_num_tokens]
|
||||
if not filtered:
|
||||
raise ValueError(
|
||||
|
||||
@@ -76,8 +76,10 @@ def create_kt_config_from_server_args(
|
||||
if server_args.kt_weight_path is None:
|
||||
return None
|
||||
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
num_layers = getattr(
|
||||
server_args.get_model_config().hf_config, "num_hidden_layers", None
|
||||
model_config_of(server_args).hf_config, "num_hidden_layers", None
|
||||
)
|
||||
|
||||
return KTConfig(
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.arg_groups.overrides import resolving_view
|
||||
from sglang.srt.arg_groups.overrides import cutedsl_moe_max_num_tokens, resolving_view
|
||||
from sglang.srt.layers.communicator import (
|
||||
CommunicateWithAllReduceAndLayerNormFn,
|
||||
LayerCommunicator,
|
||||
@@ -37,7 +37,7 @@ def resolve_max_m(model_runner) -> int:
|
||||
decode_config = server_args.cuda_graph_config.decode
|
||||
prefill_config = server_args.cuda_graph_config.prefill
|
||||
candidates = [
|
||||
server_args.cutedsl_moe_max_num_tokens(),
|
||||
cutedsl_moe_max_num_tokens(model_runner.server_args),
|
||||
model_runner.max_running_requests,
|
||||
decode_config.max_bs,
|
||||
prefill_config.max_bs,
|
||||
|
||||
@@ -29,8 +29,10 @@ logger = logging.getLogger(__name__)
|
||||
def is_post_capture_kv_active(
|
||||
*, server_args: ServerArgs, is_draft_worker: bool
|
||||
) -> bool:
|
||||
from sglang.srt.arg_groups.overrides import post_capture_kv_sizing_planned
|
||||
|
||||
return (
|
||||
server_args.post_capture_kv_sizing_planned()
|
||||
post_capture_kv_sizing_planned(server_args)
|
||||
and current_platform.is_cuda()
|
||||
and not is_draft_worker
|
||||
)
|
||||
|
||||
@@ -44,6 +44,7 @@ from sglang.srt.runtime_context import (
|
||||
get_parallel,
|
||||
get_schedule,
|
||||
get_spec,
|
||||
max_speculative_num_draft_tokens,
|
||||
)
|
||||
from sglang.srt.utils.common import (
|
||||
ceil_align,
|
||||
@@ -780,9 +781,7 @@ class DSV4PoolConfigurator(MemoryPoolConfigurator):
|
||||
self.swa_page_size = cfg.window_size
|
||||
self.swa_ratio = get_schedule().swa_full_tokens_ratio
|
||||
self.is_speculative = get_spec().speculative_algorithm is not None
|
||||
self.online_c128_mtp_max_draft_tokens = (
|
||||
kvc.server_args.max_speculative_num_draft_tokens or 0
|
||||
)
|
||||
self.online_c128_mtp_max_draft_tokens = max_speculative_num_draft_tokens() or 0
|
||||
self.requested_max_running_requests_per_worker = (
|
||||
get_schedule().max_running_requests // kvc.ps.attn_dp_size
|
||||
if get_schedule().max_running_requests is not None
|
||||
@@ -814,7 +813,7 @@ class DSV4PoolConfigurator(MemoryPoolConfigurator):
|
||||
if self.is_speculative:
|
||||
# Ring is sized once here, so it must serve the largest adaptive tier.
|
||||
self._assert_ring_serves_draft_tokens(
|
||||
kvc.server_args.max_speculative_num_draft_tokens or 0
|
||||
max_speculative_num_draft_tokens() or 0
|
||||
)
|
||||
|
||||
self.bytes_per_full_token = self._get_bytes_per_full_token()
|
||||
|
||||
@@ -30,7 +30,9 @@ from sglang.srt.runtime_context import (
|
||||
get_disagg,
|
||||
get_exec,
|
||||
get_model,
|
||||
get_schedule,
|
||||
get_spec,
|
||||
max_prefill_buffer_tokens,
|
||||
)
|
||||
from sglang.srt.utils import empty_context, log_info_on_rank0
|
||||
|
||||
@@ -342,9 +344,7 @@ def maybe_flashinfer_autotune_extend(
|
||||
mr = runner.model_runner
|
||||
# Prefer the per-rank scheduler buffer while preserving the legacy ceiling
|
||||
# when chunked prefill is disabled.
|
||||
num_tokens = (
|
||||
mr.server_args.max_prefill_buffer_tokens() or mr.server_args.max_prefill_tokens
|
||||
)
|
||||
num_tokens = max_prefill_buffer_tokens() or get_schedule().max_prefill_tokens
|
||||
if num_tokens <= (decode_num_tokens or 0):
|
||||
return # decode-shaped autotune already covered these buckets
|
||||
is_pd_prefill_target = (
|
||||
|
||||
@@ -15,6 +15,8 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from sglang.srt.arg_groups.overrides import declare_resolution
|
||||
|
||||
METADATA_FORMAT_VERSION = 3
|
||||
GGUF_SHARD_SUFFIX_RE = re.compile(r"-\d{5}-of-\d{5}\.gguf$")
|
||||
DEEPSEEK_METADATA_FORMAT_VERSION = 4
|
||||
@@ -215,7 +217,8 @@ def prepare_raw_kimi_server_args(
|
||||
model_path,
|
||||
tokenizer_dir=tokenizer_path,
|
||||
)
|
||||
server_args._declare(
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"prepare_raw_kimi_server_args",
|
||||
model_path=str(assets["model_dir"]),
|
||||
tokenizer_path=str(assets["model_dir"]),
|
||||
@@ -538,7 +541,8 @@ def prepare_raw_deepseek_server_args(
|
||||
config_sha256 = _deepseek_digest(
|
||||
model_value.get("config_sha256"), "model.config_sha256"
|
||||
)
|
||||
server_args._declare(
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"prepare_raw_deepseek_server_args",
|
||||
model_path=str(model_config.parent),
|
||||
tokenizer_path=str(model_config.parent),
|
||||
|
||||
@@ -1672,7 +1672,7 @@ def max_prefill_buffer_tokens() -> int:
|
||||
|
||||
Every input is a published leaf (``schedule`` plus the configured PP size),
|
||||
so this derives from the bags and follows a post-publish override;
|
||||
``ServerArgs.max_prefill_buffer_tokens`` is the pre-publish equivalent and
|
||||
``overrides.max_prefill_buffer_tokens`` is the pre-publish equivalent and
|
||||
``TestDerivedPredicatesAgreeAcrossTiers`` pins the two equal.
|
||||
"""
|
||||
import math
|
||||
@@ -1726,17 +1726,19 @@ def pre_capture_activation_reserve_mb(gpu_mem: float | None) -> float:
|
||||
# --- Derived config accessors ------------------------------------------------
|
||||
#
|
||||
# A few values are computed from several config fields plus the HF config, so
|
||||
# they are ``ServerArgs`` members rather than namespace leaves. Business code
|
||||
# must not reach for the startup record to get them: these accessors are the
|
||||
# named home, and this module — which owns the slot — is the only place that
|
||||
# reads it. Each one keeps the member's exact semantics, including which model
|
||||
# they are derived accessors rather than namespace leaves. Business code must
|
||||
# not reach for the startup record to get them: these accessors are the named
|
||||
# home, and this module — which owns the slot — is the only place that reads
|
||||
# it. Each one keeps the pre-publish function's exact semantics, including which model
|
||||
# config it derives from (always the process's, i.e. the target's).
|
||||
|
||||
|
||||
def mamba_cache_chunk_size() -> int:
|
||||
"""The caching point granularity for mamba state: ``max(the model's mamba
|
||||
chunk size, page_size)``. Cached on the config after the first call."""
|
||||
return get_server_args().mamba_cache_chunk_size
|
||||
from sglang.srt.arg_groups.overrides import mamba_cache_chunk_size as _of
|
||||
|
||||
return _of(get_server_args())
|
||||
|
||||
|
||||
def mamba_checkpoint_grid(tree_page: int) -> int:
|
||||
@@ -1759,7 +1761,7 @@ def max_speculative_num_draft_tokens() -> int | None:
|
||||
"""The largest draft-token count speculative decoding may use.
|
||||
|
||||
All three inputs are ``spec`` leaves, so this derives from the bags and
|
||||
follows a post-publish override; ``ServerArgs.max_speculative_num_draft_tokens``
|
||||
follows a post-publish override; ``overrides.max_speculative_num_draft_tokens``
|
||||
is the pre-publish equivalent. Adaptive spec resolves the count from its
|
||||
candidate-step table instead of the flat field.
|
||||
"""
|
||||
@@ -1788,7 +1790,9 @@ def _adaptive_draft_token_bound(cfg_path: str | None) -> int:
|
||||
|
||||
def uses_mla_backend() -> bool:
|
||||
"""Whether this process's model runs the MLA attention path."""
|
||||
return get_server_args().use_mla_backend()
|
||||
from sglang.srt.arg_groups.overrides import use_mla_backend
|
||||
|
||||
return use_mla_backend(get_server_args())
|
||||
|
||||
|
||||
def attention_backends() -> tuple:
|
||||
@@ -1796,7 +1800,7 @@ def attention_backends() -> tuple:
|
||||
back to ``attention_backend``.
|
||||
|
||||
All three inputs are ``exec.kernel`` leaves, so this derives from the bags
|
||||
and follows a post-publish override; ``ServerArgs.get_attention_backends``
|
||||
and follows a post-publish override; ``overrides.attention_backends_of``
|
||||
is the pre-publish equivalent the resolution pipeline uses. A built runner
|
||||
stamps its own resolved pair (``ModelRunner.prefill_attention_backend_str``);
|
||||
read that when there is a runner in hand.
|
||||
@@ -1810,7 +1814,9 @@ def attention_backends() -> tuple:
|
||||
|
||||
def process_model_config():
|
||||
"""The process's ``ModelConfig`` (built once from the published config)."""
|
||||
return get_server_args().get_model_config()
|
||||
from sglang.srt.arg_groups.overrides import model_config_of
|
||||
|
||||
return model_config_of(get_server_args())
|
||||
|
||||
|
||||
def cutedsl_moe_max_num_tokens() -> int:
|
||||
@@ -1818,7 +1824,7 @@ def cutedsl_moe_max_num_tokens() -> int:
|
||||
|
||||
Every input is a published leaf (``spec``, ``schedule``, ``exec.graph``), so
|
||||
this derives from the bags and follows a post-publish override;
|
||||
``ServerArgs.cutedsl_moe_max_num_tokens`` is the pre-publish equivalent the
|
||||
``overrides.cutedsl_moe_max_num_tokens`` is the pre-publish equivalent the
|
||||
resolution pipeline uses. Max over the prefill bound, the piecewise-prefill
|
||||
capture, and the decode/verify bound.
|
||||
"""
|
||||
|
||||
@@ -37,11 +37,8 @@ import argparse
|
||||
import copy
|
||||
import dataclasses
|
||||
import functools
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
from typing import Any, Callable, Dict, List, Literal, Optional, Union
|
||||
@@ -56,11 +53,9 @@ from sglang.srt.arg_groups.argparse_actions import (
|
||||
LoRAPathAction,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
mamba_extra_buffer_lazy_of,
|
||||
mamba_extra_buffer_of,
|
||||
remote_instance_transfer_engine_of,
|
||||
resolved_view,
|
||||
resolving_view,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
@@ -69,23 +64,14 @@ from sglang.srt.lora.lora_registry import LoRARef
|
||||
from sglang.srt.model_executor.cuda_graph_config import (
|
||||
Backend,
|
||||
CudaGraphConfig,
|
||||
Phase,
|
||||
parse_cuda_graph_config_arg,
|
||||
with_phase,
|
||||
)
|
||||
from sglang.srt.parser.reasoning_parser import ReasoningParser
|
||||
from sglang.srt.platforms import current_platform
|
||||
from sglang.srt.speculative.decoupled_spec_io import DecoupledSpecIpcConfig
|
||||
from sglang.srt.utils.common import (
|
||||
LORA_TARGET_ALL_MODULES,
|
||||
SUPPORTED_LORA_TARGET_MODULES,
|
||||
human_readable_int,
|
||||
is_flashinfer_available,
|
||||
is_hip,
|
||||
is_hopper_with_cuda_12_3,
|
||||
is_mps,
|
||||
is_no_spec_infer_or_topk_one,
|
||||
is_sm100_supported,
|
||||
json_list_type,
|
||||
nullable_str,
|
||||
)
|
||||
@@ -3745,7 +3731,7 @@ class ServerArgs:
|
||||
|
||||
# Everything outside the fields, enumerated from the instance: the raw
|
||||
# snapshot, the stash, and what resolution memoized -- including the
|
||||
# `get_model_config()` memo, which the copy carries over rather than
|
||||
# model-configuration memo, which the copy carries over rather than
|
||||
# rebuild.
|
||||
field_names = {field.name for field in dataclasses.fields(self)}
|
||||
for name, value in vars(self).items():
|
||||
@@ -3763,91 +3749,10 @@ class ServerArgs:
|
||||
object.__setattr__(replacement, "_resolution_finished", True)
|
||||
return replacement
|
||||
|
||||
def _declare(self, source: str, **fields: Any) -> None:
|
||||
"""This record's handlers declaring their resolution writes.
|
||||
|
||||
See ``arg_groups.overrides.declare_resolution``, which the hooks these
|
||||
handlers call reach directly.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import declare_resolution
|
||||
|
||||
declare_resolution(self, source, **fields)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# CUDA graph configuration resolution
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _apply_cuda_graph_disaggregation_roles(self):
|
||||
cfg = resolving_view(self)
|
||||
if cfg.disaggregation_mode == "prefill":
|
||||
if (Phase.DECODE, "backend") not in self._cuda_graph_config_locked:
|
||||
self._declare(
|
||||
"_apply_cuda_graph_disaggregation_roles",
|
||||
cuda_graph_config=with_phase(
|
||||
cfg.cuda_graph_config, Phase.DECODE, backend=Backend.DISABLED
|
||||
),
|
||||
)
|
||||
elif cfg.disaggregation_mode == "decode":
|
||||
if (Phase.PREFILL, "backend") not in self._cuda_graph_config_locked:
|
||||
self._declare(
|
||||
"_apply_cuda_graph_disaggregation_roles",
|
||||
cuda_graph_config=with_phase(
|
||||
cfg.cuda_graph_config, Phase.PREFILL, backend=Backend.DISABLED
|
||||
),
|
||||
)
|
||||
|
||||
def post_capture_kv_sizing_planned(self) -> bool:
|
||||
"""Whether the mem_fraction heuristic may skip the graph reserve; must be
|
||||
False for any config the runtime won't post-capture-size, else it gets an
|
||||
under-reserved fraction."""
|
||||
cfg = resolving_view(self)
|
||||
# use_mla_backend is a method at args time but ModelRunner overwrites it
|
||||
# with a bool on global_server_args (see the FIXME there) -- handle both.
|
||||
use_mla = self.use_mla_backend
|
||||
mla_enabled = use_mla() if callable(use_mla) else use_mla
|
||||
if not envs.SGLANG_ENABLE_POST_CAPTURE_KV_SIZING.get():
|
||||
return False
|
||||
if cfg.device != "cuda":
|
||||
return False
|
||||
if cfg.dcp_size != 1:
|
||||
return False
|
||||
if mla_enabled:
|
||||
return False
|
||||
if cfg.kv_cache_dtype == "fp4_e2m1":
|
||||
return False
|
||||
if cfg.prefill_only_disable_kv_cache:
|
||||
return False
|
||||
if cfg.enable_memory_saver:
|
||||
return False
|
||||
if envs.SGLANG_MOONCAKE_CUSTOM_MEM_POOL.get() is not None:
|
||||
return False
|
||||
|
||||
if (
|
||||
cfg.disaggregation_mode != "prefill"
|
||||
and cfg.cuda_graph_config.decode.backend == Backend.DISABLED
|
||||
):
|
||||
return False
|
||||
|
||||
if cfg.disaggregation_mode != "decode":
|
||||
prefill_cfg = cfg.cuda_graph_config.prefill
|
||||
# We can only skip eager activation headroom when the largest
|
||||
# prefill forward batch size is already graph-captured. Otherwise,
|
||||
# an eager forward will need more memory and lead to OOM.
|
||||
if (
|
||||
prefill_cfg.backend == Backend.DISABLED
|
||||
or cfg.chunked_prefill_size <= 0
|
||||
or self.max_prefill_buffer_tokens() > max(prefill_cfg.bs or (0,))
|
||||
):
|
||||
return False
|
||||
|
||||
from sglang.srt.configs.model_config import is_deepseek_v4, is_minimax_sparse
|
||||
|
||||
hf_config = self.get_model_config().hf_config
|
||||
if is_deepseek_v4(hf_config) or is_minimax_sparse(hf_config):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
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.
|
||||
@@ -3870,347 +3775,13 @@ class ServerArgs:
|
||||
reserved_mem = max(reserved_mem, 10 * 1024)
|
||||
return reserved_mem
|
||||
|
||||
def reserve_for_graph_mb(self) -> float:
|
||||
cfg = resolving_view(self)
|
||||
decode_cuda_graph_config = cfg.cuda_graph_config.decode
|
||||
prefill_cuda_graph_config = cfg.cuda_graph_config.prefill
|
||||
|
||||
reserved_mem = 0.0
|
||||
if (
|
||||
cfg.disaggregation_mode != "prefill"
|
||||
and decode_cuda_graph_config.backend != Backend.DISABLED
|
||||
):
|
||||
reserved_mem += decode_cuda_graph_config.max_bs * 2
|
||||
|
||||
if (
|
||||
self._resolved().enable_dp_attention
|
||||
and cfg.disaggregation_mode != "prefill"
|
||||
):
|
||||
# DP attention needs more padding for some operations, and much more for large
|
||||
# cuda graph max bs (torch allocator / implementation inefficiencies).
|
||||
reserved_mem += decode_cuda_graph_config.max_bs * cfg.dp_size * 3
|
||||
if decode_cuda_graph_config.max_bs > 300:
|
||||
reserved_mem += decode_cuda_graph_config.max_bs * cfg.dp_size * 1.5
|
||||
|
||||
if (
|
||||
cfg.disaggregation_mode != "decode"
|
||||
and prefill_cuda_graph_config.backend != Backend.DISABLED
|
||||
):
|
||||
if not self.use_mla_backend():
|
||||
# Only non-torch memory is counted; torch memory is reused by cuda graph capture.
|
||||
reserved_mem += len(prefill_cuda_graph_config.bs) * 8
|
||||
else:
|
||||
# MLA backend overhead is much higher than expected with fa3.
|
||||
reserved_mem += 1.5 * 1024
|
||||
|
||||
if (
|
||||
prefill_cuda_graph_config.backend == Backend.BREAKABLE
|
||||
and resolved_view(self).moe_a2a_backend == "deepep"
|
||||
):
|
||||
# Prefill-BCG DeepEP delta (bridge pool + NVL first-touch
|
||||
# during capture); decode-side DeepEP is a baseline cost.
|
||||
reserved_mem += 1 * 1024
|
||||
|
||||
return reserved_mem
|
||||
|
||||
def reserve_for_deepep_a2a_mb(self) -> float:
|
||||
# DeepEP all-to-all buffers captured in the decode graph are real extra
|
||||
# allocations, reserved on top of the floor.
|
||||
|
||||
cfg = resolving_view(self)
|
||||
decode_cuda_graph_config = cfg.cuda_graph_config.decode
|
||||
if (
|
||||
cfg.disaggregation_mode != "prefill"
|
||||
and decode_cuda_graph_config.backend != Backend.DISABLED
|
||||
and resolved_view(self).moe_a2a_backend == "deepep"
|
||||
):
|
||||
return 2 * 1024
|
||||
return 0.0
|
||||
|
||||
def _generate_decode_cuda_graph_batch_sizes(self, max_bs: int):
|
||||
"""
|
||||
Generate the list of batch sizes for CUDA graph capture based on max_bs.
|
||||
This integrates the logic from cuda_graph_runner.py.
|
||||
"""
|
||||
cfg = resolving_view(self)
|
||||
# Handle disable_cuda_graph_padding as the first condition for both spec and non-spec
|
||||
if cfg.disable_cuda_graph_padding:
|
||||
capture_bs = list(range(1, max_bs + 1))
|
||||
elif cfg.speculative_algorithm is None:
|
||||
# Normal case:
|
||||
capture_bs = (
|
||||
[1, 2, 4, 8, 12]
|
||||
+ list(range(16, 257, 8))
|
||||
+ list(range(272, 512, 16))
|
||||
+ list(range(512, max_bs + 1, 32))
|
||||
)
|
||||
else:
|
||||
# Spec decoding case: less padding for smaller batch sizes
|
||||
capture_bs = (
|
||||
list(range(1, 9, 1))
|
||||
+ list(range(10, 33, 2))
|
||||
+ list(range(40, 65, 4))
|
||||
+ list(range(72, 257, 8))
|
||||
+ list(range(272, max_bs + 1, 16))
|
||||
)
|
||||
|
||||
capture_bs = [bs for bs in capture_bs if bs <= max_bs]
|
||||
|
||||
if max_bs not in capture_bs:
|
||||
capture_bs.append(max_bs)
|
||||
|
||||
return capture_bs
|
||||
|
||||
def _generate_cpu_graph_batch_sizes(self):
|
||||
"""
|
||||
Generate the list of batch sizes for CPU graph capture based on torch_compile_max_bs.
|
||||
"""
|
||||
cfg = resolving_view(self)
|
||||
if cfg.disable_cuda_graph_padding:
|
||||
capture_bs = list(range(1, cfg.torch_compile_max_bs + 1))
|
||||
else:
|
||||
capture_bs = sorted(
|
||||
set().union(
|
||||
range(1, 17),
|
||||
range(18, 31, 2),
|
||||
range(32, 81, 4),
|
||||
range(84, cfg.torch_compile_max_bs + 1, 8),
|
||||
{cfg.torch_compile_max_bs},
|
||||
)
|
||||
)
|
||||
capture_bs = [bs for bs in capture_bs if bs <= cfg.torch_compile_max_bs]
|
||||
|
||||
return capture_bs
|
||||
|
||||
def _generate_prefill_cuda_graph_batch_sizes(self, max_bs: int):
|
||||
"""
|
||||
Generate the list of batch sizes for prefill CUDA graph capture
|
||||
based on max_bs. For tc_piecewise prefill, bs carries the
|
||||
captured token count (one shape knob per phase).
|
||||
"""
|
||||
capture_sizes = (
|
||||
list(range(4, 33, 4))
|
||||
+ list(range(48, 257, 16))
|
||||
+ list(range(288, 513, 32))
|
||||
+ list(range(576, 1024 + 1, 64))
|
||||
+ list(range(1280, 4096 + 1, 256))
|
||||
+ list(range(4608, max_bs + 1, 512))
|
||||
)
|
||||
|
||||
capture_sizes = [s for s in capture_sizes if s <= max_bs]
|
||||
|
||||
return capture_sizes
|
||||
|
||||
def _set_default_dsa_kv_cache_dtype(self, major: int, quantization: str) -> None:
|
||||
# Moved to the resolution pipeline (arg_groups/overrides.py:
|
||||
# _dsa_kv_cache_dtype_default), invoked here at its legacy slot.
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_dsa_kv_cache_dtype_default,
|
||||
run_post_process_pass,
|
||||
)
|
||||
|
||||
run_post_process_pass(self, _dsa_kv_cache_dtype_default)
|
||||
|
||||
def _set_default_dsa_backends(self, major: int) -> None:
|
||||
# Moved to the resolution pipeline (arg_groups/overrides.py:
|
||||
# _dsa_split_backend_resolution), invoked here at its legacy slot.
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_dsa_split_backend_resolution,
|
||||
run_post_process_pass,
|
||||
)
|
||||
|
||||
run_post_process_pass(self, _dsa_split_backend_resolution)
|
||||
|
||||
def _support_mamba_cache_extra_buffer(self, model_arch: str):
|
||||
from sglang.srt.arg_groups.overrides import supports_mamba_cache_extra_buffer
|
||||
|
||||
return supports_mamba_cache_extra_buffer(self, model_arch)
|
||||
|
||||
def _get_default_attn_backend(self, 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(self)
|
||||
# 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 is_hopper_with_cuda_12_3() and is_no_spec_infer_or_topk_one(
|
||||
resolved_view(self)
|
||||
):
|
||||
# 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 (
|
||||
is_sm100_supported()
|
||||
and is_no_spec_infer_or_topk_one(resolved_view(self))
|
||||
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 is_hip():
|
||||
return "aiter"
|
||||
elif is_mps():
|
||||
return "torch_native"
|
||||
else:
|
||||
# FlashInfer does not support attention sinks.
|
||||
if is_flashinfer_available() and not model_config.has_attention_sinks:
|
||||
return "flashinfer"
|
||||
return "triton"
|
||||
else:
|
||||
# MLA architecture
|
||||
if is_hopper_with_cuda_12_3():
|
||||
return "fa3"
|
||||
elif is_sm100_supported():
|
||||
return "flashinfer"
|
||||
elif is_hip():
|
||||
head_num = model_config.get_num_kv_heads(self.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 cutedsl_moe_max_num_tokens(self) -> int:
|
||||
"""Largest number of tokens a single forward routes through a CuteDSL
|
||||
MoE layer on one (DP) rank. Single source of truth for both the
|
||||
standard-allgather wrapper buffers and the FlashInfer A2A dispatcher
|
||||
budget. Max over the prefill (max_prefill_tokens), piecewise-prefill
|
||||
capture, and decode/verify bounds; num_tokens_per_req is
|
||||
speculative_num_draft_tokens under speculative decoding, else 1.
|
||||
"""
|
||||
cfg = resolving_view(self)
|
||||
if cfg.speculative_algorithm:
|
||||
num_tokens_per_req = cfg.speculative_num_draft_tokens or 1
|
||||
else:
|
||||
num_tokens_per_req = 1
|
||||
prefill_tokens = cfg.max_prefill_tokens
|
||||
cg_config = cfg.cuda_graph_config
|
||||
if cg_config is not None and cg_config.prefill.backend == Backend.TC_PIECEWISE:
|
||||
prefill_tokens = max(prefill_tokens, cg_config.prefill.max_bs or 0)
|
||||
decode_max_bs = (cg_config.decode.max_bs if cg_config is not None else 0) or 0
|
||||
decode_tokens = decode_max_bs * num_tokens_per_req
|
||||
return max(prefill_tokens, decode_tokens)
|
||||
|
||||
def max_prefill_buffer_tokens(self) -> int:
|
||||
"""Prefill-buffer ceiling: chunked_prefill_size, except PP dynamic
|
||||
chunking can grow chunks toward max_prefill_tokens and probe at 1.25x."""
|
||||
cfg = resolving_view(self)
|
||||
chunked = (
|
||||
cfg.chunked_prefill_size
|
||||
if cfg.chunked_prefill_size and cfg.chunked_prefill_size > 0
|
||||
else 0
|
||||
)
|
||||
tokens = chunked
|
||||
if cfg.enable_dynamic_chunking and cfg.pp_size > 1 and chunked:
|
||||
tokens = max(tokens, cfg.max_prefill_tokens or 0, math.ceil(chunked * 1.25))
|
||||
return tokens
|
||||
|
||||
def _required_mori_dispatch_tokens_per_rank(self) -> int:
|
||||
"""Max tokens a single rank dispatches through MoRI in one forward."""
|
||||
cfg = resolving_view(self)
|
||||
return cfg.chunked_prefill_size
|
||||
|
||||
def _required_pplx_dispatch_tokens_per_rank(self) -> int:
|
||||
"""Max tokens a single rank dispatches through pplx in one forward."""
|
||||
cfg = resolving_view(self)
|
||||
required = cfg.chunked_prefill_size
|
||||
if cfg.cuda_graph_max_bs_decode is not None:
|
||||
required = max(required, cfg.cuda_graph_max_bs_decode)
|
||||
return required
|
||||
|
||||
# ===== END TO BE REFACTORED ====
|
||||
|
||||
def _is_mistral_native_format(self) -> bool:
|
||||
"""True iff the checkpoint requires load_format=mistral.
|
||||
|
||||
Looks for consolidated*.safetensors with no competing
|
||||
model*.safetensors; when both weight formats ship in the
|
||||
same checkpoint (e.g. Mistral-7B-Instruct-v0.3) the HF path is
|
||||
preferred to avoid loading Mistral-named weights into an
|
||||
HF-named architecture.
|
||||
|
||||
Name override: mistral-large-3 / mistral-small-4 /
|
||||
leanstral always treat as Mistral-native when params.json
|
||||
is present -- those families need Mistral weight loading
|
||||
regardless of which weight files happen to be present.
|
||||
"""
|
||||
cfg = resolving_view(self)
|
||||
_MISTRAL_NATIVE_PATTERNS = (
|
||||
"mistral-large-3",
|
||||
"mistral-small-4",
|
||||
"leanstral",
|
||||
)
|
||||
name_matches = any(
|
||||
p in str(cfg.model_path).lower() for p in _MISTRAL_NATIVE_PATTERNS
|
||||
)
|
||||
|
||||
def _check_format(has_params, has_consolidated, has_hf_weights) -> bool:
|
||||
if has_params and name_matches:
|
||||
return True
|
||||
return has_consolidated and not has_hf_weights
|
||||
|
||||
if os.path.isdir(cfg.model_path):
|
||||
return _check_format(
|
||||
has_params=os.path.exists(os.path.join(cfg.model_path, "params.json")),
|
||||
has_consolidated=bool(
|
||||
glob.glob(os.path.join(cfg.model_path, "consolidated*.safetensors"))
|
||||
),
|
||||
has_hf_weights=bool(
|
||||
glob.glob(os.path.join(cfg.model_path, "model*.safetensors"))
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
from huggingface_hub import HfApi
|
||||
|
||||
files = {s.rfilename for s in HfApi().model_info(cfg.model_path).siblings}
|
||||
return _check_format(
|
||||
has_params="params.json" in files,
|
||||
has_consolidated=any(
|
||||
f.startswith("consolidated") and f.endswith(".safetensors")
|
||||
for f in files
|
||||
),
|
||||
has_hf_weights=any(
|
||||
f.startswith("model")
|
||||
and f.endswith(".safetensors")
|
||||
and "/" not in f
|
||||
for f in files
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
LANGUAGE_MODEL_ONLY_ARCHITECTURES = ("MuseGlimmerForConditionalGeneration",)
|
||||
|
||||
# The strided-layout Triton requirement is enforced via
|
||||
@@ -4587,52 +4158,6 @@ class ServerArgs:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_model_config(self):
|
||||
# Lazy init to avoid circular import
|
||||
cfg = resolving_view(self)
|
||||
from sglang.srt.configs.model_config import ModelConfig
|
||||
|
||||
memo = getattr(self, "_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(self, "_model_config_built_from", None)
|
||||
if built_from is None or built_from == cfg.model_path:
|
||||
return memo
|
||||
|
||||
model_config = ModelConfig.from_server_args(self)
|
||||
self._model_config = model_config
|
||||
self._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 _resolved(self):
|
||||
"""Read-only view of the resolving configuration: declared fields
|
||||
resolve from the declaration stash."""
|
||||
|
||||
return resolved_view(self)
|
||||
|
||||
def _late_resolution(self, source: str, **fields) -> None:
|
||||
"""Resolve fields at the launcher's validation stage (pre-publish).
|
||||
|
||||
See ``arg_groups.overrides.declare_late_resolution``: the decision goes
|
||||
to this instance's declaration stash, so every holder of it carries the
|
||||
decision and publishes bags that answer with it. Refused outright once
|
||||
the config is published.
|
||||
"""
|
||||
from sglang.srt.arg_groups.overrides import declare_late_resolution
|
||||
|
||||
declare_late_resolution(self, source, **fields)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# Once resolution has finished the record is the READ-ONLY raw input
|
||||
# the config bags were projected from. Resolved config changes go to the bags via
|
||||
@@ -4651,153 +4176,17 @@ class ServerArgs:
|
||||
)
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
def _resolved_attention_backends(self):
|
||||
"""Mid-resolution (prefill, decode) backends: reads through the pass
|
||||
view so declared fields resolve from the declaration stash."""
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
)
|
||||
|
||||
return attention_backends_of(resolved_view(self))
|
||||
|
||||
def get_attention_backends(self):
|
||||
"""The (prefill, decode) pair resolution decided.
|
||||
|
||||
Reads through the declaration stash, not the fields: the model-specific
|
||||
overrides declare into the stash without writing the fields, so a field
|
||||
read answers with what the operator typed.
|
||||
"""
|
||||
return attention_backends_of(resolved_view(self))
|
||||
|
||||
def use_mla_backend(self):
|
||||
from sglang.srt.configs.model_config import AttentionArch
|
||||
|
||||
model_config = self.get_model_config()
|
||||
return model_config.attention_arch == AttentionArch.MLA
|
||||
|
||||
def is_attention_backend_not_set(self):
|
||||
cfg = resolving_view(self)
|
||||
return (
|
||||
cfg.attention_backend is None
|
||||
and cfg.prefill_attention_backend is None
|
||||
and cfg.decode_attention_backend is None
|
||||
)
|
||||
|
||||
def enable_mamba_extra_buffer(self) -> bool:
|
||||
return mamba_extra_buffer_of(resolving_view(self))
|
||||
|
||||
def enable_mamba_extra_buffer_lazy(self) -> bool:
|
||||
return mamba_extra_buffer_lazy_of(resolving_view(self))
|
||||
|
||||
@property
|
||||
def max_speculative_num_draft_tokens(self) -> Optional[int]:
|
||||
"""Return the maximum draft-token count speculative decoding may use.
|
||||
|
||||
Memoized only once the record is resolved: an answer computed off a raw
|
||||
record describes inputs resolution is about to rewrite (auto speculative
|
||||
sizing fills `speculative_num_draft_tokens` in), and a cache filled that
|
||||
early would keep answering with it.
|
||||
"""
|
||||
cfg = resolving_view(self)
|
||||
|
||||
memo = self.__dict__.get("_max_speculative_num_draft_tokens")
|
||||
if memo is not None:
|
||||
return memo
|
||||
if cfg.speculative_num_draft_tokens is None:
|
||||
result = None
|
||||
elif not cfg.speculative_adaptive:
|
||||
result = cfg.speculative_num_draft_tokens
|
||||
else:
|
||||
from sglang.srt.speculative.adaptive_spec_params import (
|
||||
resolve_candidate_steps_from_config,
|
||||
)
|
||||
|
||||
candidate_steps = resolve_candidate_steps_from_config(
|
||||
cfg_path=cfg.speculative_adaptive_config,
|
||||
)
|
||||
# TODO: adaptive spec currently requires topk=1, so each runtime
|
||||
# state needs steps + 1 draft-token slots. Revisit this if topk>1
|
||||
# is supported.
|
||||
result = max(candidate_steps) + 1
|
||||
if getattr(self, "_resolution_finished", False):
|
||||
self._max_speculative_num_draft_tokens = result
|
||||
return result
|
||||
|
||||
@property
|
||||
def mamba_cache_chunk_size(self) -> int:
|
||||
# For mamba cache with extra buffer, the chunk size is the max of FLA_CHUNK_SIZE
|
||||
# (or mamba_chunk_size if it is defined in the model's config) and page_size.
|
||||
# It is used to determine the caching point in a sequence during prefill.
|
||||
# A pre-seeded `_mamba_cache_chunk_size` (fixtures supply one so a dummy
|
||||
# model never loads an HF config) is honored as-is; otherwise the memo
|
||||
# is only kept once the record is resolved, because `page_size` below
|
||||
# is resolution-written.
|
||||
if not hasattr(self, "_mamba_cache_chunk_size"):
|
||||
|
||||
try:
|
||||
from sglang.kernels.ops.attention.fla.chunk_delta_h import (
|
||||
CHUNK_SIZE as FLA_CHUNK_SIZE,
|
||||
)
|
||||
except ImportError:
|
||||
# Must match sglang.kernels.ops.attention.fla.chunk_delta_h.CHUNK_SIZE
|
||||
FLA_CHUNK_SIZE = 64
|
||||
|
||||
hf_config = self.get_model_config().hf_config
|
||||
chunk_size = getattr(hf_config, "mamba_chunk_size", FLA_CHUNK_SIZE)
|
||||
page_size = resolved_view(self).page_size
|
||||
assert (
|
||||
max(chunk_size, page_size) % min(chunk_size, page_size) == 0
|
||||
), f"For SSM models, either chunk_size or page_size must be divisible by the other, got {chunk_size=}, {page_size=}"
|
||||
if not getattr(self, "_resolution_finished", False):
|
||||
return max(chunk_size, page_size)
|
||||
self._mamba_cache_chunk_size = max(chunk_size, page_size)
|
||||
return self._mamba_cache_chunk_size
|
||||
|
||||
def check_server_args(self):
|
||||
from sglang.srt.arg_groups.validation_hook import check_server_args
|
||||
|
||||
check_server_args(self)
|
||||
|
||||
def adjust_mem_fraction_for_vlm(self, model_config):
|
||||
cfg = resolving_view(self)
|
||||
vision_config = getattr(model_config.hf_config, "vision_config", None)
|
||||
if vision_config is None:
|
||||
return
|
||||
|
||||
# roughly reduce the mem_fraction_static base on params of Vit
|
||||
original_server_arg_mem_fraction = cfg.mem_fraction_static
|
||||
# a base mem_fraction_static factor for regular Vit
|
||||
base_mem_fraction_reduction_ratio = 0.95
|
||||
|
||||
vit_num_layers = getattr(vision_config, "num_hidden_layers", 24)
|
||||
vit_hidden_size = getattr(vision_config, "hidden_size", 1024)
|
||||
|
||||
# baseline ViT params (ViT-L/14)
|
||||
baseline_vit_layers = 24
|
||||
baseline_vit_hidden_size = 1024
|
||||
|
||||
# weight params count
|
||||
current_complexity_score = vit_num_layers * (vit_hidden_size**2)
|
||||
baseline_complexity_score = baseline_vit_layers * (baseline_vit_hidden_size**2)
|
||||
complexity_ratio = (
|
||||
current_complexity_score / baseline_complexity_score
|
||||
if baseline_complexity_score > 0
|
||||
else 1.0
|
||||
)
|
||||
|
||||
# every time the complexity grows 100%, adjust final factor for 10%
|
||||
sensitivity_scale = 0.1
|
||||
dynamic_adjustment_factor = 1.0 - sensitivity_scale * (complexity_ratio - 1.0)
|
||||
dynamic_adjustment_factor = max(0.8, min(1.05, dynamic_adjustment_factor))
|
||||
|
||||
final_overall_factor = (
|
||||
base_mem_fraction_reduction_ratio * dynamic_adjustment_factor
|
||||
)
|
||||
self._declare(
|
||||
"adjust_mem_fraction_for_vlm",
|
||||
mem_fraction_static=original_server_arg_mem_fraction * final_overall_factor,
|
||||
)
|
||||
|
||||
@property
|
||||
def _parsed_modelexpress_config(self) -> dict:
|
||||
cache = getattr(self, "_mx_config_cache", None)
|
||||
@@ -4952,10 +4341,6 @@ class ServerArgs:
|
||||
descriptor["load_topic"] = LOAD_TOPIC
|
||||
return descriptor
|
||||
|
||||
def should_report_expert_balancedness(self) -> bool:
|
||||
cfg = resolving_view(self)
|
||||
return cfg.expert_balancedness_report_mode != "off"
|
||||
|
||||
def should_log_expert_balancedness_to_server_log(self) -> bool:
|
||||
cfg = resolving_view(self)
|
||||
|
||||
|
||||
@@ -2088,20 +2088,27 @@ def _wait_for_gpu_idle_in_ci(
|
||||
pass
|
||||
|
||||
|
||||
# Names the runner kits stamp onto a record that are not members of it.
|
||||
# `ModelRunner` computes `use_mla_backend` on itself; the kits copy that bool
|
||||
# onto the record they hand the runner, and `hasattr` cannot see it.
|
||||
_RUNNER_WRITTEN_NAMES = frozenset({"use_mla_backend"})
|
||||
|
||||
|
||||
def server_args_variant(server_args, **fields):
|
||||
"""A modified deep copy of a config, for a test double whose fixture
|
||||
differs from the (possibly published, read-only) config it starts from.
|
||||
The receiver is untouched; the copy keeps its read-only guard.
|
||||
|
||||
A name may also shadow a method with a fixture value (the runner kits set
|
||||
``use_mla_backend``, a method ModelRunner itself overwrites at init);
|
||||
names that exist nowhere on the class fail loudly."""
|
||||
A name may also be one the kits stamp on rather than a field (see
|
||||
``_RUNNER_WRITTEN_NAMES``); names that exist nowhere fail loudly."""
|
||||
variant = copy.deepcopy(server_args)
|
||||
cls = type(variant)
|
||||
unknown = {
|
||||
name
|
||||
for name in fields
|
||||
if name not in cls.__dataclass_fields__ and not hasattr(cls, name)
|
||||
if name not in cls.__dataclass_fields__
|
||||
and not hasattr(cls, name)
|
||||
and name not in _RUNNER_WRITTEN_NAMES
|
||||
}
|
||||
if unknown:
|
||||
raise ValueError(f"unknown ServerArgs field(s): {sorted(unknown)}")
|
||||
|
||||
Reference in New Issue
Block a user