Co-authored-by: Yangmin Li <yangminl@nvidia.com> Co-authored-by: YAMY <74099316+YAMY1234@users.noreply.github.com>
984 lines
42 KiB
Python
984 lines
42 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Server-argument resolution for per-model and per-capability adjustments."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from typing import Any
|
|
|
|
from sglang.srt.arg_groups.overrides import (
|
|
_deepseek_moe_quant_resolution,
|
|
_deepseek_spec_moe_resolution,
|
|
_dsa_kv_cache_dtype_default,
|
|
_dsa_split_backend_resolution,
|
|
_enforce_disable_allreduce_fusion,
|
|
_flashinfer_allreduce_fusion_auto_enable,
|
|
_hrm_text_attention_force,
|
|
_mamba_radix_cache_resolution,
|
|
_sparse_head_overlap_disable,
|
|
attention_backends_of,
|
|
collect_model_override_declarations,
|
|
declare_resolution,
|
|
mamba_cache_chunk_size,
|
|
mamba_extra_buffer_of,
|
|
model_config_of,
|
|
resolved_view,
|
|
resolving_view,
|
|
run_post_process_pass,
|
|
use_mla_backend,
|
|
validate_declarations,
|
|
)
|
|
from sglang.srt.arg_groups.resolution_hooks import run_hook
|
|
from sglang.srt.configs.embedding_model_spec import BCGPrefillPolicy
|
|
from sglang.srt.configs.linear_attn_model_registry import get_linear_attn_spec_by_arch
|
|
from sglang.srt.connector import ConnectorType
|
|
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, Phase, with_phase
|
|
from sglang.srt.runtime_context import derive_attention_widths, get_platform
|
|
from sglang.srt.utils.common import (
|
|
get_quantization_config,
|
|
is_mps,
|
|
parse_connector_type,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _validate_dsa_tbo_index_sharing(server_args: Any, hf_config: Any) -> None:
|
|
cfg = resolving_view(server_args)
|
|
if not cfg.enable_two_batch_overlap:
|
|
return
|
|
|
|
index_topk_freq = getattr(hf_config, "index_topk_freq", 1) or 1
|
|
index_topk_pattern = getattr(hf_config, "index_topk_pattern", None)
|
|
indexer_types = getattr(hf_config, "indexer_types", None)
|
|
if (
|
|
index_topk_freq > 1
|
|
or (index_topk_pattern is not None and "S" in index_topk_pattern)
|
|
or (indexer_types is not None and "shared" in indexer_types)
|
|
):
|
|
raise ValueError(
|
|
"--enable-two-batch-overlap is not supported with DSA "
|
|
"index-topk sharing: the TBO op path does not propagate topk "
|
|
"indices across layers, so shared layers would run sparse "
|
|
"attention without indices. Got "
|
|
f"index_topk_freq={index_topk_freq!r}, "
|
|
f"index_topk_pattern={index_topk_pattern!r}, and "
|
|
f"indexer_types={indexer_types!r}."
|
|
)
|
|
|
|
|
|
def _rocm_fp8_wo_a_supported() -> bool:
|
|
"""True when ROCm can run the DeepSeek-V4 fp8 wo_a GEMM (gfx950 + aiter)."""
|
|
try:
|
|
from sglang.srt.models.deepseek_common.amd.deepseek_v4_wo_a_fp8 import (
|
|
is_wo_a_fp8_mxscale_supported,
|
|
)
|
|
|
|
return is_wo_a_fp8_mxscale_supported()
|
|
except Exception: # pragma: no cover - env-dependent
|
|
return False
|
|
|
|
|
|
def _probe_wo_a_weight_dtype(model_config: Any, download_dir: str | None) -> str | None:
|
|
"""Read one indexed wo_a dtype without downloading a weight shard."""
|
|
try:
|
|
from huggingface_hub import (
|
|
parse_local_safetensors_file_metadata,
|
|
parse_safetensors_file_metadata,
|
|
)
|
|
from transformers.utils.hub import cached_file
|
|
|
|
model_path = model_config.model_path
|
|
revision = (
|
|
getattr(model_config.hf_config, "_commit_hash", None)
|
|
or model_config.revision
|
|
)
|
|
index_path = cached_file(
|
|
model_path,
|
|
"model.safetensors.index.json",
|
|
revision=revision,
|
|
cache_dir=download_dir,
|
|
)
|
|
with open(index_path) as f:
|
|
weight_map = json.load(f).get("weight_map", {})
|
|
name = next((key for key in weight_map if key.endswith(".wo_a.weight")), None)
|
|
if name is None:
|
|
return None
|
|
|
|
shard = weight_map[name]
|
|
local_shard = os.path.join(os.path.dirname(index_path), shard)
|
|
metadata = (
|
|
parse_local_safetensors_file_metadata(local_shard)
|
|
if os.path.isfile(local_shard)
|
|
else parse_safetensors_file_metadata(model_path, shard, revision=revision)
|
|
)
|
|
return getattr(metadata.tensors.get(name), "dtype", None)
|
|
except Exception:
|
|
logger.debug("Unable to inspect the checkpoint wo_a dtype", exc_info=True)
|
|
return None
|
|
|
|
|
|
def _configure_rocm_fp8_wo_a_gemm(model_config: Any, download_dir: str | None) -> None:
|
|
flag = envs.SGLANG_OPT_FP8_WO_A_GEMM
|
|
if not _rocm_fp8_wo_a_supported():
|
|
flag.set(False)
|
|
return
|
|
if flag.is_set():
|
|
return
|
|
|
|
dtype = _probe_wo_a_weight_dtype(model_config, download_dir)
|
|
if dtype is not None and dtype != "F8_E4M3":
|
|
flag.set(False)
|
|
logger.info("Disabled ROCm fp8 wo_a GEMM for checkpoint dtype %s", dtype)
|
|
|
|
|
|
def handle_model_specific_adjustments(server_args: Any):
|
|
|
|
cfg = resolving_view(server_args)
|
|
from sglang.srt.configs.model_config import (
|
|
get_mimo_v2_fused_qkv_expected_tp_size,
|
|
is_deepseek_dsa,
|
|
)
|
|
|
|
if cfg.enable_deterministic_inference:
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_specific_adjustments",
|
|
enforce_disable_flashinfer_allreduce_fusion=True,
|
|
)
|
|
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_specific_adjustments",
|
|
uses_mamba_radix_cache=False,
|
|
)
|
|
if parse_connector_type(cfg.model_path) == ConnectorType.INSTANCE:
|
|
# No model overrides for an instance connector: no hf_config to
|
|
# key them on.
|
|
return
|
|
|
|
model_config = model_config_of(server_args)
|
|
hf_config = model_config.hf_config
|
|
model_arch = hf_config.architectures[0]
|
|
|
|
if model_arch == "InternS2MobiusForConditionalGeneration":
|
|
unsupported = []
|
|
if cfg.pp_size != 1:
|
|
unsupported.append("pipeline parallelism (--pp-size must be 1)")
|
|
if cfg.ep_size != 1:
|
|
unsupported.append("expert parallelism (--ep-size must be 1)")
|
|
if unsupported:
|
|
raise ValueError(
|
|
"Intern-S2-Mobius does not support: " + "; ".join(unsupported) + "."
|
|
)
|
|
|
|
if cfg.enable_dsa_cache_layer_split and not is_deepseek_dsa(hf_config):
|
|
raise ValueError(
|
|
"--enable-dsa-cache-layer-split is only supported for DSA "
|
|
"(DeepSeek Sparse Attention) models."
|
|
)
|
|
|
|
if cfg.enable_cp_decode_attn_tp:
|
|
from sglang.srt.layers.cp.cp_decode_attn_tp import (
|
|
CP_DECODE_ATTN_TP_SUPPORTED_ARCHS,
|
|
)
|
|
|
|
if model_arch not in CP_DECODE_ATTN_TP_SUPPORTED_ARCHS:
|
|
raise ValueError(
|
|
"--enable-cp-decode-attn-tp is only supported for models "
|
|
"whose attention linears are replicated across CP ranks "
|
|
f"(attn_tp_size=1). Got {model_arch}; supported: "
|
|
f"{sorted(CP_DECODE_ATTN_TP_SUPPORTED_ARCHS)}."
|
|
)
|
|
|
|
_hybrid_spec = get_linear_attn_spec_by_arch(model_arch)
|
|
if _hybrid_spec is not None and _hybrid_spec.uses_mamba_radix_cache:
|
|
handle_mamba_radix_cache(server_args, model_arch)
|
|
|
|
# Collect the declarative model overrides (registry) on the
|
|
# pristine config and stash them for publish-time flags resolution;
|
|
# server_args is never mutated — mid-resolution readers see the
|
|
# declared values through resolved_view, runtime readers through the
|
|
# flags tier.
|
|
|
|
model_overrides = collect_model_override_declarations(
|
|
model_arch, server_args, hf_config
|
|
)
|
|
validate_declarations(server_args, model_overrides)
|
|
server_args._resolved_overrides.extend(model_overrides)
|
|
|
|
if model_arch in (
|
|
"KimiLinearForCausalLM",
|
|
"KimiK3ForConditionalGeneration",
|
|
):
|
|
from sglang.srt.arg_groups.kimi_k3_hook import (
|
|
apply_kimi_k3_linear_attn_defaults,
|
|
apply_kimi_k3_spec_backend_defaults,
|
|
)
|
|
|
|
apply_kimi_k3_linear_attn_defaults(server_args)
|
|
apply_kimi_k3_spec_backend_defaults(server_args)
|
|
|
|
if model_arch in [
|
|
"DeepseekV4ForCausalLM",
|
|
]:
|
|
from sglang.srt.arg_groups.deepseek_v4_hook import (
|
|
apply_deepseek_v4_defaults,
|
|
)
|
|
|
|
apply_deepseek_v4_defaults(server_args, model_arch)
|
|
|
|
if model_arch in [
|
|
"DeepseekV3ForCausalLM",
|
|
"DeepseekV32ForCausalLM",
|
|
"KimiK25ForConditionalGeneration",
|
|
"MistralLarge3ForCausalLM",
|
|
"PixtralForConditionalGeneration",
|
|
"GlmMoeDsaForCausalLM",
|
|
"Glm5NextForConditionalGeneration",
|
|
"HYV4ForCausalLM",
|
|
"HYV4ForCausalLMNextN",
|
|
"LongcatFlashForCausalLM",
|
|
"Dots3NoteForCausalLM",
|
|
]:
|
|
# Set attention backend for DeepSeek
|
|
if is_deepseek_dsa(hf_config): # DeepSeek 3.2/GLM 5
|
|
if envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.is_set():
|
|
logger.warning(
|
|
f"Dense attention kv len threshold is manually set to {envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.get()} for DSA. Caution: This may cause performance regression if the threshold is larger than the index topk of model."
|
|
)
|
|
else:
|
|
# When threshold is not manually set, set it to the index topk of model
|
|
from sglang.srt.configs.model_config import get_dsa_index_topk
|
|
|
|
envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.set(
|
|
get_dsa_index_topk(hf_config)
|
|
)
|
|
logger.warning(
|
|
f"Set dense attention kv len threshold to model index_topk={envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.get()} for DeepSeek with DSA."
|
|
)
|
|
# The "dsa" attention fill moved to the override registry
|
|
# (arg_groups/overrides.py: _deepseek_family_overrides).
|
|
|
|
_validate_dsa_tbo_index_sharing(server_args, hf_config)
|
|
|
|
if (
|
|
not get_platform().is_npu and not get_platform().is_xpu
|
|
): # CUDA or ROCm GPU
|
|
if cfg.enable_prefill_cp:
|
|
# The DSA CP field declarations moved to the override
|
|
# registry (arg_groups/overrides.py:
|
|
# _deepseek_family_overrides).
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_specific_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config,
|
|
Phase.PREFILL,
|
|
backend=Backend.DISABLED,
|
|
),
|
|
)
|
|
else:
|
|
# Pure TP and partial DP Attention mode is active for DSA, logging a warning
|
|
if cfg.dp_size < cfg.tp_size:
|
|
_, attn_tp_size = derive_attention_widths(
|
|
tp_size=cfg.tp_size,
|
|
attn_cp_size=cfg.attn_cp_size,
|
|
dp_size=cfg.dp_size,
|
|
enable_dp_attention=cfg.enable_dp_attention,
|
|
)
|
|
logger.warning(
|
|
f"DSA with TP mode is active, dp_size={cfg.dp_size}, tp_size={cfg.tp_size}, "
|
|
f"attn_tp_size={attn_tp_size}, attention weights will be sharded across {attn_tp_size} ranks."
|
|
)
|
|
|
|
# The DSA page-size selection moved to the override registry
|
|
# (arg_groups/overrides.py: _deepseek_family_overrides).
|
|
|
|
import torch
|
|
|
|
major, _ = torch.cuda.get_device_capability()
|
|
|
|
run_post_process_pass(server_args, _dsa_kv_cache_dtype_default)
|
|
run_post_process_pass(server_args, _dsa_split_backend_resolution)
|
|
|
|
elif get_platform().is_xpu:
|
|
run_post_process_pass(server_args, _dsa_kv_cache_dtype_default)
|
|
run_post_process_pass(server_args, _dsa_split_backend_resolution)
|
|
# Disable fused topk (requires sgl-kernel ops not available on XPU)
|
|
if (
|
|
envs.SGLANG_DSA_FUSE_TOPK.is_set()
|
|
and envs.SGLANG_DSA_FUSE_TOPK.get()
|
|
):
|
|
logger.warning(
|
|
"Disabling fused topk for DeepSeek DSA on XPU (SGLANG_DSA_FUSE_TOPK=0). Not supported yet."
|
|
)
|
|
envs.SGLANG_DSA_FUSE_TOPK.set(False)
|
|
# Disable CUDA-JIT topk-v2 (TileLang/TVM-based, requires CUDA)
|
|
envs.SGLANG_OPT_USE_TOPK_V2.set(False)
|
|
|
|
if cfg.enable_prefill_cp:
|
|
assert cfg.disaggregation_mode != "decode", (
|
|
"CP is only supported for prefill when PD disaggregation, please remove --enable-prefill-cp."
|
|
)
|
|
if (
|
|
cfg.enable_dsa_cache_layer_split
|
|
and cfg.disaggregation_mode != "prefill"
|
|
):
|
|
if cfg.disaggregation_mode == "decode":
|
|
raise ValueError(
|
|
"--enable-dsa-cache-layer-split is not supported on "
|
|
"decode workers. This flag is a prefill-CP "
|
|
"optimization; decode receives full cache shards "
|
|
"through PD transfer."
|
|
)
|
|
raise ValueError(
|
|
"--enable-dsa-cache-layer-split is only supported on PD "
|
|
"prefill workers. Non-PD workers also run decode and "
|
|
"require ordinary local decode cache semantics."
|
|
)
|
|
if cfg.enable_dsa_cache_layer_split and (
|
|
not cfg.enable_prefill_cp or cfg.cp_strategy != "interleave"
|
|
):
|
|
raise ValueError(
|
|
"--enable-dsa-cache-layer-split requires "
|
|
"--enable-prefill-cp and --cp-strategy interleave."
|
|
)
|
|
# Layer split relies on the mooncake all-CP-rank KV/indexer
|
|
# transfer path. mori/nixl support is a temporary limitation
|
|
# and will be added later by the community.
|
|
if (
|
|
cfg.enable_dsa_cache_layer_split
|
|
and cfg.disaggregation_transfer_backend != "mooncake"
|
|
):
|
|
raise ValueError(
|
|
"--enable-dsa-cache-layer-split currently only supports "
|
|
"the mooncake transfer backend (mooncake / mooncake_tcp). "
|
|
f"Got --disaggregation-transfer-backend "
|
|
f"{cfg.disaggregation_transfer_backend!r}. mori/nixl "
|
|
"support will be added later by the community."
|
|
)
|
|
if cfg.enable_dsa_cache_layer_split and cfg.pp_size > 1:
|
|
raise ValueError(
|
|
"--enable-dsa-cache-layer-split is not supported with "
|
|
"pipeline parallelism (pp_size > 1) yet. It requires "
|
|
"prefill context parallelism, and CP + PP has not been "
|
|
"validated for this feature."
|
|
)
|
|
|
|
else:
|
|
# DeepSeek V3/R1/V3.1
|
|
if cfg.cuda_graph_config.prefill.backend != Backend.DISABLED:
|
|
logger.info("Piecewise CUDA graph is enabled, use MLA for prefill.")
|
|
|
|
# The sm100 trtllm_mla fill moved to the override registry
|
|
# (arg_groups/overrides.py: _deepseek_family_overrides).
|
|
|
|
# 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 use_mla_backend(server_args):
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_specific_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config,
|
|
Phase.PREFILL,
|
|
backend=Backend.DISABLED,
|
|
),
|
|
)
|
|
|
|
# Set moe backend for DeepSeek: the sm100 quant/moe resolution
|
|
# moved to the resolution pipeline (arg_groups/overrides.py:
|
|
# _deepseek_moe_quant_resolution -- a slot pass, because the DSA
|
|
# kv-cache-dtype default above must read the pristine
|
|
# quantization). The HIP arm (fusion log + spec_moe writes, the
|
|
# latter awaiting the speculative-hook migration) stays below.
|
|
|
|
run_post_process_pass(server_args, _deepseek_moe_quant_resolution)
|
|
if get_platform().is_hip:
|
|
if is_deepseek_dsa(hf_config) and not envs.SGLANG_OPT_USE_TOPK_V2.is_set():
|
|
# Prefer HIP top-k by default while honoring an explicit selection.
|
|
envs.SGLANG_OPT_USE_TOPK_V2.set(False)
|
|
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")
|
|
|
|
# The fp4-checkpoint draft spec-MoE resolution moved to the
|
|
# resolution pipeline (arg_groups/overrides.py:
|
|
# _deepseek_spec_moe_resolution), invoked here at its legacy
|
|
# slot.
|
|
|
|
run_post_process_pass(server_args, _deepseek_spec_moe_resolution)
|
|
|
|
elif model_arch in [
|
|
"DeepseekV4ForCausalLM",
|
|
]:
|
|
from sglang.srt.arg_groups.deepseek_v4_hook import (
|
|
validate_deepseek_v4_cp,
|
|
validate_deepseek_v4_mega_moe_token_budget,
|
|
validate_deepseek_v41_features,
|
|
)
|
|
|
|
# Before the CP validation: V4.1 rejects CP outright, the actionable message.
|
|
validate_deepseek_v41_features(server_args)
|
|
validate_deepseek_v4_cp(server_args)
|
|
validate_deepseek_v4_mega_moe_token_budget(server_args)
|
|
|
|
if get_platform().is_sm120:
|
|
# FP8 wo_a stays opt-in on SM120: only recent DeepGEMM builds ship
|
|
# the SM120 kernels, and deep_gemm_wrapper.configurer validates them.
|
|
if not envs.SGLANG_OPT_FP8_WO_A_GEMM.is_set():
|
|
envs.SGLANG_OPT_FP8_WO_A_GEMM.set(False)
|
|
# The default top-k v2 path still requires unsupported resources.
|
|
envs.SGLANG_OPT_USE_TOPK_V2.set(False)
|
|
if not envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.is_set():
|
|
envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.set(False)
|
|
if not envs.SGLANG_OPT_FUSE_MHC_POST_PRE.is_set():
|
|
envs.SGLANG_OPT_FUSE_MHC_POST_PRE.set(True)
|
|
if not envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.is_set():
|
|
envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.set(False)
|
|
# Out of the box the indexer runs the TileLang kernel (works on
|
|
# stock DeepGEMM); both knobs stay env-overridable so a DeepGEMM
|
|
# build with SM120 attention support can opt into
|
|
# fp8_paged_mqa_logits by setting them to 0.
|
|
if not envs.SGLANG_FP8_PAGED_MQA_LOGITS_TORCH.is_set():
|
|
envs.SGLANG_FP8_PAGED_MQA_LOGITS_TORCH.set(True)
|
|
if not envs.SGLANG_OPT_USE_TILELANG_INDEXER.is_set():
|
|
envs.SGLANG_OPT_USE_TILELANG_INDEXER.set(True)
|
|
elif get_platform().is_hip:
|
|
envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.set(False)
|
|
_configure_rocm_fp8_wo_a_gemm(model_config, cfg.download_dir)
|
|
envs.SGLANG_OPT_USE_JIT_INDEXER_METADATA.set(False)
|
|
envs.SGLANG_OPT_USE_TOPK_V2.set(True)
|
|
envs.SGLANG_OPT_USE_AITER_INDEXER.set(True)
|
|
envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.set(False)
|
|
envs.SGLANG_OPT_USE_TILELANG_MHC_POST.set(False)
|
|
envs.SGLANG_FP8_PAGED_MQA_LOGITS_TORCH.set(True)
|
|
envs.SGLANG_OPT_USE_MULTI_STREAM_OVERLAP.set(False)
|
|
envs.SGLANG_EAGER_INPUT_NO_COPY.set(True)
|
|
|
|
elif model_arch in ["GptOssForCausalLM"]:
|
|
# Attention backend selection + XPU dtype validation moved to the
|
|
# override registry (arg_groups/overrides.py: _gpt_oss_overrides).
|
|
# Exempt MLX only: none of these backends exist on MPS, and MLX runs
|
|
# attention inside its own runner, so attention_backend is still
|
|
# unset here. Plain macOS stays on the list -- torch_native has
|
|
# neither sliding window nor attention sinks.
|
|
if not (is_mps() and use_mlx()):
|
|
supported_backends = [
|
|
"triton",
|
|
"trtllm_mha",
|
|
"fa3",
|
|
"fa4",
|
|
"ascend",
|
|
"intel_amx",
|
|
"intel_xpu",
|
|
"aiter",
|
|
]
|
|
prefill_attn_backend, decode_attn_backend = attention_backends_of(
|
|
resolved_view(server_args)
|
|
)
|
|
assert (
|
|
prefill_attn_backend in supported_backends
|
|
and decode_attn_backend in supported_backends
|
|
), (
|
|
f"GptOssForCausalLM requires one of {supported_backends} attention backend, but got the following backends\n"
|
|
f"- Prefill: {prefill_attn_backend}\n"
|
|
f"- Decode: {decode_attn_backend}\n"
|
|
)
|
|
|
|
quant_method = get_quantization_config(hf_config)
|
|
is_mxfp4_quant_format = quant_method == "mxfp4"
|
|
if (
|
|
not resolved_view(server_args).enable_dp_attention
|
|
and cfg.nnodes == 1
|
|
and get_platform().is_hip
|
|
):
|
|
# TODO (Hubert): Put this back later
|
|
# server_args.enable_aiter_allreduce_fusion = True
|
|
logger.info("Enable Aiter AllReduce Fusion for GptOssForCausalLM")
|
|
quantization_config = getattr(hf_config, "quantization_config", None)
|
|
is_mxfp4_quant_format = (
|
|
quantization_config is not None
|
|
and quantization_config.get("quant_method") == "mxfp4"
|
|
)
|
|
# The mxfp4 dtype override moved to the override registry
|
|
# (arg_groups/overrides.py: _gpt_oss_overrides).
|
|
|
|
# The moe_runner_backend selection moved to the override registry
|
|
# (arg_groups/overrides.py: _gpt_oss_overrides).
|
|
|
|
if resolved_view(server_args).moe_runner_backend == "triton_kernel":
|
|
assert 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 = 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 (
|
|
expected_attn_tp_size is not None
|
|
and expected_attn_tp_size % effective_attn_tp_size != 0
|
|
):
|
|
raise ValueError(
|
|
"MiMoV2ForCausalLM requires effective attention TP "
|
|
f"size {expected_attn_tp_size} because its fused "
|
|
"qkv_proj weights are "
|
|
f"TP={expected_attn_tp_size}-interleaved; got "
|
|
f"{effective_attn_tp_size} "
|
|
f"(tp_size={cfg.tp_size}, dp_size={cfg.dp_size}, "
|
|
f"enable_dp_attention={view.enable_dp_attention}, "
|
|
f"attn_cp_size={view.attn_cp_size}). "
|
|
"Set --tp, --dp, --enable-dp-attention, and "
|
|
"--attention-context-parallel-size so the effective "
|
|
f"attention TP size is {expected_attn_tp_size}."
|
|
)
|
|
|
|
# enable_multi_layer_eagle for EAGLE moved to the override registry
|
|
# (arg_groups/overrides.py: _mimo_v2_overrides).
|
|
|
|
# MiMoV2 hierarchical cache runs on the unified radix tree, which
|
|
# is the default tree cache now. MiMoV2 has head_dim != v_head_dim,
|
|
# so the host KV pool uses asymmetric K/V allocation. Both
|
|
# kernel/page_first and direct/page_first_direct have split K/V
|
|
# transfer paths.
|
|
elif (
|
|
"Step3p5ForCausalLM" in model_arch
|
|
or "Step3p7ForConditionalGeneration" in model_arch
|
|
):
|
|
# Attention backend selection + EAGLE multi-layer +
|
|
# hierarchical-cache SWA writes moved to the override registry
|
|
# (arg_groups/overrides.py: _step3p_overrides).
|
|
pass
|
|
elif (
|
|
model_arch in ("Llama4ForConditionalGeneration", "Llama4ForCausalLM")
|
|
and cfg.device != "cpu"
|
|
):
|
|
# Attention backend auto-select moved to the override registry
|
|
# (arg_groups/overrides.py: _llama4_overrides).
|
|
attention_backend = resolved_view(server_args).attention_backend
|
|
assert attention_backend in {
|
|
"fa3",
|
|
"aiter",
|
|
"triton",
|
|
"ascend",
|
|
"trtllm_mha",
|
|
"intel_xpu",
|
|
}, (
|
|
f"fa3, aiter, triton, ascend, trtllm_mha or intel_xpu is required for Llama4 model but got {attention_backend}"
|
|
)
|
|
# The moe_runner_backend selection moved to the override registry
|
|
# (arg_groups/overrides.py: _llama4_overrides).
|
|
# Gemma2/Gemma3 (disable_hybrid_swa_memory) moved to the override registry
|
|
# (arg_groups/overrides.py: _gemma2_gemma3_overrides).
|
|
elif model_arch in (
|
|
"Gemma4ForConditionalGeneration",
|
|
"Gemma4ForCausalLM",
|
|
"Gemma4UnifiedForConditionalGeneration",
|
|
):
|
|
# Default attention backend selection moved to the override registry
|
|
# (arg_groups/overrides.py: _gemma4_overrides).
|
|
prefill_backend, decode_backend = attention_backends_of(
|
|
resolved_view(server_args)
|
|
)
|
|
accepted_backends = (
|
|
"trtllm_mha",
|
|
"triton",
|
|
"ascend",
|
|
"intel_xpu",
|
|
"intel_amx",
|
|
"aiter",
|
|
)
|
|
assert (
|
|
prefill_backend in accepted_backends and decode_backend in accepted_backends
|
|
), (
|
|
"Gemma4 only supports trtllm_mha, triton, ascend, intel_xpu, intel_amx, or "
|
|
f"aiter attention backend, got prefill={prefill_backend}, decode={decode_backend}"
|
|
)
|
|
|
|
# The quantization/moe_runner_backend resolution moved to the override
|
|
# registry (arg_groups/overrides.py: _gemma4_overrides).
|
|
elif model_arch == "MossVLForConditionalGeneration":
|
|
# The prefill attention backend default + validation moved to the
|
|
# override registry (arg_groups/overrides.py: _moss_vl_overrides).
|
|
pass
|
|
elif model_arch in ["Exaone4ForCausalLM", "ExaoneMoEForCausalLM"]:
|
|
if hf_config.sliding_window_pattern is not None:
|
|
# disable_hybrid_swa_memory moved to the override registry
|
|
# (arg_groups/overrides.py: _exaone_overrides).
|
|
# https://docs.sglang.ai/advanced_features/attention_backend.html
|
|
accepted_backends = ["fa3", "triton", "trtllm_mha"]
|
|
attention_backend = resolved_view(server_args).attention_backend
|
|
assert attention_backend in accepted_backends, (
|
|
f"One of the attention backends in {accepted_backends} is required for {model_arch}, but got {attention_backend}"
|
|
)
|
|
elif model_arch in ["Olmo2ForCausalLM"]:
|
|
# disable_hybrid_swa_memory + attention backend selection moved to
|
|
# the override registry (arg_groups/overrides.py: _olmo2_overrides).
|
|
|
|
# Flashinfer appears to degrade performance when sliding window attention
|
|
# is used for the Olmo2 architecture. Olmo2 does not use sliding window attention
|
|
# but Olmo3 does.
|
|
attention_backend = resolved_view(server_args).attention_backend
|
|
assert attention_backend != "flashinfer", (
|
|
"FlashInfer backend can significantly degrade the performance of Olmo3 models."
|
|
)
|
|
|
|
logger.info(f"Using {attention_backend} as attention backend for {model_arch}.")
|
|
elif model_arch in [
|
|
"Qwen3MoeForCausalLM",
|
|
"Qwen3VLMoeForConditionalGeneration",
|
|
"Qwen3NextForCausalLM",
|
|
"Qwen3_5MoeForConditionalGeneration",
|
|
"InternS2PreviewForConditionalGeneration",
|
|
"Qwen3_5ForConditionalGeneration",
|
|
"Qwen4ExpForConditionalGeneration",
|
|
]:
|
|
# The quantization/moe_runner_backend resolution moved to the
|
|
# override registry (arg_groups/overrides.py:
|
|
# _qwen3_moe_family_overrides); the hybrid sub-family's attention
|
|
# backend + page size defaults to _qwen3_5_hybrid_overrides.
|
|
pass
|
|
|
|
elif model_arch in ["Glm4MoeForCausalLM"]:
|
|
# The quantization/moe_runner_backend/enable_tf32_matmul resolution
|
|
# moved to the override registry (arg_groups/overrides.py:
|
|
# _glm4_moe_overrides).
|
|
pass
|
|
|
|
elif model_arch in ["Lfm2ForCausalLM", "Lfm2MoeForCausalLM"]:
|
|
# Attention backend selection moved to the override registry
|
|
# (arg_groups/overrides.py: _lfm2_overrides).
|
|
assert resolved_view(server_args).attention_backend != "triton", (
|
|
f"{model_arch} does not support triton attention backend, "
|
|
"as the first layer might not be an attention layer"
|
|
)
|
|
|
|
# MiniMaxM2ForCausalLM (enable_tf32_matmul) moved to the override registry
|
|
# (arg_groups/overrides.py: _minimax_m2_overrides).
|
|
|
|
# Qwen3VL aiter unified-attention page_size moved to the override registry
|
|
# (arg_groups/overrides.py: _qwen3vl_overrides).
|
|
|
|
# Hybrid-mamba radix cache handling for the per-arch branch call sites
|
|
# dissolved above: the resolution pass self-guards on the arch union
|
|
# (and the Granite layer_types probe), so one call covers them all.
|
|
# Hybrid-spec archs already resolved at the pre-dispatch call above;
|
|
# for them this re-invocation is an idempotent no-op plus validation.
|
|
# Kept ahead of the sparse-head pass: the legacy per-branch calls
|
|
# resolved before that tail write of disable_overlap_schedule.
|
|
handle_mamba_radix_cache(server_args, model_arch)
|
|
|
|
run_post_process_pass(server_args, _sparse_head_overlap_disable)
|
|
|
|
# The FlashInfer AllReduce Fusion auto-enable and the enforce-disable
|
|
# terminal moved to the resolution pipeline (arg_groups/overrides.py:
|
|
# _flashinfer_allreduce_fusion_auto_enable /
|
|
# _enforce_disable_allreduce_fusion), invoked here at their legacy
|
|
# slots.
|
|
|
|
run_post_process_pass(server_args, _flashinfer_allreduce_fusion_auto_enable)
|
|
run_post_process_pass(server_args, _enforce_disable_allreduce_fusion)
|
|
|
|
|
|
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,
|
|
)
|
|
|
|
cfg = resolving_view(server_args)
|
|
if parse_connector_type(cfg.model_path) == ConnectorType.INSTANCE:
|
|
return
|
|
|
|
model_config = model_config_of(server_args)
|
|
hf_config = model_config.hf_config
|
|
|
|
# HRM-Text needs bidirectional prompt attention (prefill), which only
|
|
# the Triton backend honors at the kernel level. Radix/prefix reuse is
|
|
# also unsafe: the recurrent forward writes direction-dependent KV
|
|
# across many slots.
|
|
is_hrm_text = getattr(
|
|
hf_config, "model_type", None
|
|
) == "hrm_text" or "HrmTextForCausalLM" in getattr(hf_config, "architectures", [])
|
|
# prefix_lm defaults to True upstream; defaulting False would skip the
|
|
# bidirectional-attention forcing and silently produce junk output.
|
|
if is_hrm_text and getattr(hf_config, "prefix_lm", True):
|
|
run_post_process_pass(server_args, _hrm_text_attention_force)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
chunked_prefill_size=-1,
|
|
)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
disable_radix_cache=True,
|
|
)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
disable_cuda_graph=True,
|
|
)
|
|
# cuda_graph_config was already parsed from the legacy boolean, so
|
|
# flipping the boolean alone would not stop graph capture.
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config, Phase.DECODE, backend=Backend.DISABLED
|
|
),
|
|
)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config, Phase.PREFILL, backend=Backend.DISABLED
|
|
),
|
|
)
|
|
logger.warning(
|
|
"HRM-Text (prefix_lm) detected: forcing --attention-backend "
|
|
"triton, --chunked-prefill-size -1, --disable-radix-cache, and "
|
|
"--disable-cuda-graph for correctness of the bidirectional "
|
|
"prompt attention."
|
|
)
|
|
|
|
# EmbeddingGemma is a Gemma3TextModel with bidirectional prompt
|
|
# attention. Prefix reuse and split prefills would reuse K/V states
|
|
# whose values depend on later prompt tokens, so both are invalid.
|
|
# Breakable CUDA Graph captures one complete prefill and is the graph
|
|
# mode validated for this encoder-style attention.
|
|
# Native encoder architectures declare a pooling-only task and do not
|
|
# need the legacy --is-embedding intent flag. Decoder checkpoints still
|
|
# require that explicit opt-in because their architecture alone does
|
|
# not distinguish embedding from generation serving.
|
|
#
|
|
# ``_handle_model_capability_adjustments`` is also exercised directly
|
|
# by a few focused tests that use a small ModelConfig stand-in. Keep
|
|
# the old predicate as a compatibility fallback while production
|
|
# ModelConfig instances use the central capability contract.
|
|
embedding_model_spec = getattr(model_config, "embedding_model_spec", None)
|
|
if (
|
|
embedding_model_spec is not None
|
|
and embedding_model_spec.auto_enable_embedding
|
|
and not cfg.is_embedding
|
|
):
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
is_embedding=True,
|
|
)
|
|
logger.info(
|
|
"Embedding architecture detected: enabling embedding mode automatically."
|
|
)
|
|
|
|
is_embedding_gemma = (
|
|
embedding_model_spec is not None
|
|
and embedding_model_spec.bcg_prefill_policy == BCGPrefillPolicy.FULL_ENCODER
|
|
)
|
|
if embedding_model_spec is None:
|
|
is_embedding_gemma = getattr(model_config, "is_embedding_gemma", False)
|
|
if is_embedding_gemma:
|
|
# This is an encoder-only model even though its HF architecture is
|
|
# named Gemma3TextModel. Marking it as embedding mode enables the
|
|
# FlashAttention raw-K/V fast path, which does not write or read
|
|
# the paged KV cache during its single prefill forward.
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
is_embedding=True,
|
|
)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
disable_radix_cache=True,
|
|
)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
chunked_prefill_size=-1,
|
|
)
|
|
# Submit a list-valued embeddings request atomically so BCG can
|
|
# replay its full prefill batch instead of starting item zero
|
|
# while the remaining texts are still being tokenized.
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
enable_tokenizer_batch_encode=True,
|
|
)
|
|
requested_prefill_backend = (
|
|
cfg.prefill_attention_backend or cfg.attention_backend
|
|
)
|
|
if (
|
|
get_platform().is_cuda
|
|
and (get_platform().is_sm90 or get_platform().is_sm100)
|
|
and requested_prefill_backend in (None, "fa3", "fa4")
|
|
):
|
|
# Hopper/Blackwell's default FA backend can consume raw K/V
|
|
# tensors for a single embedding prefill. Enable its no-KV
|
|
# pool path before memory-pool sizing; an explicit non-FA
|
|
# backend retains the existing paged-KV behavior.
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
prefill_only_disable_kv_cache=True,
|
|
)
|
|
# Through the registry, not a bare call: an out-of-tree
|
|
# replacement registered at this validator's own pipeline
|
|
# position must also win here, at this later re-validation after
|
|
# the Hopper/Blackwell no-KV-pool default declares itself.
|
|
run_hook(validate_prefill_only_disable_kv_cache_args, server_args)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config, Phase.DECODE, backend=Backend.DISABLED
|
|
),
|
|
)
|
|
if (
|
|
get_platform().is_cuda
|
|
and cfg.cuda_graph_config.prefill.backend != Backend.DISABLED
|
|
):
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config, Phase.PREFILL, backend=Backend.BREAKABLE
|
|
),
|
|
)
|
|
# CUDA-graph sizing has already run by this point and derives
|
|
# its generic maximum from the 8K chunked-prefill default.
|
|
# On the Hopper/Blackwell FA raw-K/V path, raise the unlocked
|
|
# default to a full eight-way 2K embedding batch; callers can
|
|
# still override this for larger aggregate prefills.
|
|
prefill_config = cfg.cuda_graph_config.prefill
|
|
# Unit-level capability tests may invoke this hook without
|
|
# running the full CUDA-graph configuration parser, which is
|
|
# where this internal lock set is normally initialized.
|
|
# Treat that minimal construction as having no user-locked
|
|
# graph settings.
|
|
cuda_graph_config_locked = getattr(
|
|
server_args, "_cuda_graph_config_locked", set()
|
|
)
|
|
if (Phase.PREFILL, "max_bs") not in cuda_graph_config_locked:
|
|
sizing = {
|
|
"max_bs": max(
|
|
prefill_config.max_bs or 0,
|
|
model_config.context_len,
|
|
16384,
|
|
)
|
|
}
|
|
if (Phase.PREFILL, "bs") not in cuda_graph_config_locked:
|
|
sizing["bs"] = generate_prefill_cuda_graph_batch_sizes(
|
|
sizing["max_bs"]
|
|
)
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config, Phase.PREFILL, **sizing
|
|
),
|
|
)
|
|
elif not get_platform().is_cuda:
|
|
# BCG is CUDA-only. Other graph backends do not support this
|
|
# encoder-style prefill, so retain the eager Triton path.
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
cuda_graph_config=with_phase(
|
|
cfg.cuda_graph_config, Phase.PREFILL, backend=Backend.DISABLED
|
|
),
|
|
)
|
|
logger.info(
|
|
"EmbeddingGemma detected: disabling radix cache and chunked "
|
|
"prefill; using breakable CUDA graph for CUDA prefill."
|
|
)
|
|
|
|
if (
|
|
model_config.is_multimodal
|
|
and not model_config.is_multimodal_chunked_prefill_supported
|
|
):
|
|
declare_resolution(
|
|
server_args,
|
|
"_handle_model_capability_adjustments",
|
|
chunked_prefill_size=-1,
|
|
)
|
|
logger.info(
|
|
f"Automatically turn off --chunked-prefill-size as it is not supported for "
|
|
f"{hf_config.model_type}"
|
|
)
|
|
|
|
|
|
def handle_mamba_radix_cache(server_args: Any, model_arch: str):
|
|
# Resolution moved to the resolution pipeline (arg_groups/overrides.py:
|
|
# _mamba_radix_cache_resolution), invoked here at each legacy call
|
|
# slot; this handler keeps the validation.
|
|
from sglang.srt.arg_groups.mamba_hook import (
|
|
validate_mamba_extra_buffer,
|
|
validate_mamba_no_buffer,
|
|
)
|
|
|
|
run_post_process_pass(server_args, _mamba_radix_cache_resolution)
|
|
view = resolved_view(server_args)
|
|
if not view.uses_mamba_radix_cache:
|
|
# auto is arch-gated, so only an explicit strategy reaches a non-mamba
|
|
# arch here, where it would arm the mamba paths and crash at prefill.
|
|
if mamba_extra_buffer_of(view):
|
|
raise ValueError(
|
|
f"--mamba-radix-cache-strategy {view.mamba_radix_cache_strategy} "
|
|
f"needs mamba state, got {model_arch}."
|
|
)
|
|
return
|
|
|
|
if mamba_extra_buffer_of(view):
|
|
validate_mamba_extra_buffer(
|
|
view,
|
|
model_arch,
|
|
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):
|
|
|
|
cfg = resolving_view(server_args)
|
|
if not cfg.language_model_only:
|
|
return
|
|
for flag, name in (
|
|
(cfg.encoder_only, "--encoder-only"),
|
|
(cfg.language_only, "--language-only"),
|
|
(cfg.enable_prefix_mm_cache, "--enable-prefix-mm-cache"),
|
|
(
|
|
cfg.enable_broadcast_mm_inputs_process,
|
|
"--enable-broadcast-mm-inputs-process",
|
|
),
|
|
(cfg.mm_enable_dp_encoder, "--mm-enable-dp-encoder"),
|
|
):
|
|
if flag:
|
|
raise ValueError(f"--language-model-only cannot be combined with {name}")
|
|
if cfg.disaggregation_mode != "null":
|
|
raise ValueError(
|
|
"--language-model-only is incompatible with --disaggregation-mode "
|
|
"prefill/decode"
|
|
)
|
|
architectures = model_config_of(server_args).hf_config.architectures
|
|
if not any(
|
|
a in server_args.LANGUAGE_MODEL_ONLY_ARCHITECTURES for a in architectures
|
|
):
|
|
raise ValueError(
|
|
f"--language-model-only does not support {architectures}. "
|
|
f"Supported: {list(server_args.LANGUAGE_MODEL_ONLY_ARCHITECTURES)}."
|
|
)
|