[refactor] Add the post-process resolution stage; migrate sampling_backend (stack 10/15) (#30072)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cheng Wan
2026-07-04 02:22:05 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent ae29c5a1dc
commit 5c95bf15c8
4 changed files with 210 additions and 18 deletions
+95 -1
View File
@@ -34,7 +34,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tupl
from sglang.srt.arg_groups.arg_utils import model_overridable_fields
from sglang.srt.runtime_context import resolve_flag_leaf
from sglang.srt.utils.common import is_xpu
from sglang.srt.utils.common import is_flashinfer_available, is_xpu
logger = logging.getLogger(__name__)
@@ -95,6 +95,72 @@ def _invoke_provider(
return declared
class ResolvedView:
"""Read-only view of the resolving configuration handed to post-process
passes.
During the dual-apply transition the view forwards every read to the live
``server_args`` — the pristine input plus the declarations replayed so far
plus any residual imperative writes — which is exactly the state the
legacy handler at the same slot observed. In the end state (dual-apply
retired) the same type overlays the accumulated declarations on the
pristine object. Writes are rejected: passes return declarations.
"""
__slots__ = ("_server_args", "_overlay")
def __init__(self, server_args: Any, overlay: Optional[Dict[str, Any]] = None):
object.__setattr__(self, "_server_args", server_args)
object.__setattr__(self, "_overlay", overlay or {})
def __getattr__(self, name: str) -> Any:
overlay = object.__getattribute__(self, "_overlay")
if name in overlay:
return overlay[name]
return getattr(object.__getattribute__(self, "_server_args"), name)
def __setattr__(self, name: str, value: Any) -> None:
raise AttributeError(
"ResolvedView is read-only; post-process passes return declarations"
)
# Ordered post-process passes (the normalization stage). List order is the
# end-state execution order and mirrors today's handler call sequence in
# __post_init__; during the transition each pass is invoked from its legacy
# slot via run_post_process_pass, so ordering is preserved byte-for-byte.
POST_PROCESS_PASSES: List[Callable[..., dict]] = []
def register_post_process(fn: Callable[..., dict]) -> Callable[..., dict]:
"""Register a post-process pass: ``fn(view) -> {field: resolved_value}``.
The pass reads a :class:`ResolvedView` (post-model-override state) and
must not mutate anything; validations may live in a pass (read + raise).
"""
POST_PROCESS_PASSES.append(fn)
return fn
def run_post_process_pass(server_args: Any, fn: Callable[..., dict]) -> None:
"""Transition-period invocation of one pass at its legacy handler slot.
Evaluates the pass on the live state (through a read-only view), appends
its declaration to the declaration stash, and dual-applies it in place —
byte-identical to the imperative handler write this replaces.
"""
declared = fn(ResolvedView(server_args))
if not isinstance(declared, dict):
raise TypeError(
f"post-process pass {fn.__qualname__} must return a dict, "
f"got {type(declared).__name__}"
)
if declared:
entry = (fn.__qualname__, dict(declared))
server_args._resolved_overrides.append(entry)
apply_declarations_to_server_args(server_args, [entry])
def collect_model_override_declarations(
architecture: str, server_args: Any, hf_config: Any
) -> List[Tuple[str, Dict[str, Any]]]:
@@ -239,6 +305,34 @@ def _step3p_overrides(server_args: Any, hf_config: Any) -> dict:
return overrides
# ---------------------------------------------------------------------------
# Post-process passes (normalization stage), in end-state execution order.
# Faithful ports of the legacy __post_init__ handlers; each is invoked from
# its legacy slot via run_post_process_pass during the transition.
# ---------------------------------------------------------------------------
@register_post_process
def _sampling_backend_default(view: Any) -> dict:
if view.sampling_backend is None:
return {
"sampling_backend": (
"flashinfer" if is_flashinfer_available() else "pytorch"
)
}
return {}
@register_post_process
def _deterministic_sampling_backend(view: Any) -> dict:
if view.enable_deterministic_inference and view.sampling_backend != "ascend":
logger.warning(
"Sampling backend is set to pytorch for deterministic inference."
)
return {"sampling_backend": "pytorch"}
return {}
@dataclasses.dataclass(frozen=True)
class OverrideRecord:
"""Provenance of one resolved write: ``base`` is the value before this
+1
View File
@@ -314,6 +314,7 @@ class Flags(_StaticFlags):
enable_multi_layer_eagle: bool = False
swa_full_tokens_ratio: float = 0.8
disable_hybrid_swa_memory: bool = False
sampling_backend: str | None = None
def freeze(self) -> None:
for field in dataclasses.fields(self):
+18 -10
View File
@@ -1430,6 +1430,7 @@ class ServerArgs:
Arg(
help="Choose the kernels for sampling layers.",
choices=SAMPLING_BACKEND_CHOICES,
model_overridable=True,
),
] = None
grammar_backend: A[
@@ -4611,10 +4612,14 @@ class ServerArgs:
self._validate_mamba_no_buffer(model_arch)
def _handle_sampling_backend(self):
if self.sampling_backend is None:
self.sampling_backend = (
"flashinfer" if is_flashinfer_available() else "pytorch"
)
# Moved to the resolution pipeline (arg_groups/overrides.py:
# _sampling_backend_default), invoked here at its legacy slot.
from sglang.srt.arg_groups.overrides import (
_sampling_backend_default,
run_post_process_pass,
)
run_post_process_pass(self, _sampling_backend_default)
def _get_default_attn_backend(self, use_mla_backend: bool, model_config):
"""
@@ -6307,12 +6312,15 @@ class ServerArgs:
)
self.flashinfer_allreduce_fusion_backend = None
# Check sampling backend
if self.sampling_backend != "ascend":
self.sampling_backend = "pytorch"
logger.warning(
"Sampling backend is set to pytorch for deterministic inference."
)
# The forced-pytorch sampling write moved to the resolution
# pipeline (arg_groups/overrides.py:
# _deterministic_sampling_backend), invoked at its legacy slot.
from sglang.srt.arg_groups.overrides import (
_deterministic_sampling_backend,
run_post_process_pass,
)
run_post_process_pass(self, _deterministic_sampling_backend)
is_deepseek_model = False
if parse_connector_type(self.model_path) != ConnectorType.INSTANCE:
try: