94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
733 lines
26 KiB
Python
733 lines
26 KiB
Python
# Copyright 2023-2026 SGLang Team
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""A single structured accessor for process-static runtime state.
|
|
|
|
``get_parallel()`` returns a ``ParallelContext`` whose attributes — tp / dcp / pp /
|
|
moe / attn size and rank, plus the process-group handles — each delegate live to
|
|
the canonical getter in ``distributed.parallel_state`` / ``layers.dp_attention``.
|
|
Returned values are exactly what those getters return; this is a read-through
|
|
wrapper, not a cache. It gives call-sites one import and one naming scheme in
|
|
place of a dozen free functions, plus a test-only ``override()`` hook to force a
|
|
topology without monkeypatching the underlying getters.
|
|
|
|
``get_server_args()`` returns the process-wide ``ServerArgs`` (the config
|
|
tier). The context owns the storage: publishing goes through
|
|
``RuntimeContext.set_server_args`` (the legacy
|
|
``set_global_server_args_for_scheduler`` / ``get_global_server_args`` in
|
|
``server_args.py`` are thin shims over this slot), and the object is returned
|
|
by reference — the same live instance everywhere, never a copy.
|
|
|
|
``get_flags()`` returns the runtime-flags tier. Resolved configuration lives
|
|
on ``server_args`` fields (declarations materialize at the end of
|
|
``__post_init__``), so this tier only carries genuine runtime state that is
|
|
not a function of the configuration alone — today the capture lifecycle
|
|
(``flags.capture``). Flags live in typed dataclass groups; reads and writes
|
|
are plain attribute access, and each group offers a transactional, test-only
|
|
``override(**kw)``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
from contextlib import contextmanager
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
from sglang.srt.server_args import ServerArgs
|
|
|
|
|
|
# Imported lazily so this module has no import-time dependencies: any module can
|
|
# import get_parallel at module level without risking an import cycle.
|
|
def _ps():
|
|
from sglang.srt.distributed import parallel_state
|
|
|
|
return parallel_state
|
|
|
|
|
|
def _dp():
|
|
from sglang.srt.layers import dp_attention
|
|
|
|
return dp_attention
|
|
|
|
|
|
_PARALLEL_FIELDS = frozenset(
|
|
{
|
|
"world_size",
|
|
"world_rank",
|
|
"tp_size",
|
|
"tp_rank",
|
|
"pp_size",
|
|
"pp_rank",
|
|
"moe_ep_size",
|
|
"moe_ep_rank",
|
|
"moe_dp_size",
|
|
"moe_dp_rank",
|
|
"moe_tp_size",
|
|
"moe_tp_rank",
|
|
"attn_tp_size",
|
|
"attn_tp_rank",
|
|
"attn_cp_size",
|
|
"attn_cp_rank",
|
|
"dcp_enabled",
|
|
"dcp_size",
|
|
"dcp_rank",
|
|
"attn_dcp_size",
|
|
"attn_dcp_rank",
|
|
"attn_dp_size",
|
|
"attn_dp_rank",
|
|
"world_group",
|
|
"tp_group",
|
|
"pp_group",
|
|
"moe_ep_group",
|
|
"moe_dp_group",
|
|
"moe_tp_group",
|
|
"attn_tp_group",
|
|
"attn_cp_group",
|
|
"dcp_group",
|
|
}
|
|
)
|
|
|
|
|
|
class ParallelContext:
|
|
"""Parallel-topology namespace; the only instance state is ``_overrides``."""
|
|
|
|
__slots__ = ("_overrides",)
|
|
|
|
def __init__(self):
|
|
self._overrides = {}
|
|
|
|
def _v(self, name, getter):
|
|
overrides = self._overrides
|
|
return overrides[name] if name in overrides else getter()
|
|
|
|
@contextmanager
|
|
def override(self, **kwargs):
|
|
"""Temporarily force parallel values, restoring on exit. Validates keys and
|
|
supports nesting."""
|
|
unknown = set(kwargs) - _PARALLEL_FIELDS
|
|
if unknown:
|
|
raise ValueError(f"unknown parallel field(s): {sorted(unknown)}")
|
|
saved = dict(self._overrides)
|
|
self._overrides.update(kwargs)
|
|
try:
|
|
yield self
|
|
finally:
|
|
self._overrides = saved
|
|
|
|
@property
|
|
def world_size(self) -> int:
|
|
return self._v("world_size", _ps().get_world_size)
|
|
|
|
@property
|
|
def world_rank(self) -> int:
|
|
return self._v("world_rank", _ps().get_world_rank)
|
|
|
|
@property
|
|
def tp_size(self) -> int:
|
|
return self._v("tp_size", _ps().get_tensor_model_parallel_world_size)
|
|
|
|
@property
|
|
def tp_rank(self) -> int:
|
|
return self._v("tp_rank", _ps().get_tensor_model_parallel_rank)
|
|
|
|
@property
|
|
def pp_size(self) -> int:
|
|
return self._v("pp_size", _ps().get_pipeline_model_parallel_world_size)
|
|
|
|
@property
|
|
def pp_rank(self) -> int:
|
|
return self._v("pp_rank", _ps().get_pipeline_model_parallel_rank)
|
|
|
|
@property
|
|
def moe_ep_size(self) -> int:
|
|
return self._v("moe_ep_size", _ps().get_moe_expert_parallel_world_size)
|
|
|
|
@property
|
|
def moe_ep_rank(self) -> int:
|
|
return self._v("moe_ep_rank", _ps().get_moe_expert_parallel_rank)
|
|
|
|
@property
|
|
def moe_dp_size(self) -> int:
|
|
return self._v("moe_dp_size", _ps().get_moe_data_parallel_world_size)
|
|
|
|
@property
|
|
def moe_dp_rank(self) -> int:
|
|
return self._v("moe_dp_rank", _ps().get_moe_data_parallel_rank)
|
|
|
|
@property
|
|
def moe_tp_size(self) -> int:
|
|
return self._v("moe_tp_size", _ps().get_moe_tensor_parallel_world_size)
|
|
|
|
@property
|
|
def moe_tp_rank(self) -> int:
|
|
return self._v("moe_tp_rank", _ps().get_moe_tensor_parallel_rank)
|
|
|
|
@property
|
|
def attn_tp_size(self) -> int:
|
|
return self._v("attn_tp_size", _ps().get_attn_tensor_model_parallel_world_size)
|
|
|
|
@property
|
|
def attn_tp_rank(self) -> int:
|
|
return self._v("attn_tp_rank", _ps().get_attn_tensor_model_parallel_rank)
|
|
|
|
@property
|
|
def attn_cp_size(self) -> int:
|
|
return self._v("attn_cp_size", _ps().get_attn_context_model_parallel_world_size)
|
|
|
|
@property
|
|
def attn_cp_rank(self) -> int:
|
|
return self._v("attn_cp_rank", _ps().get_attn_context_model_parallel_rank)
|
|
|
|
@property
|
|
def dcp_size(self) -> int:
|
|
return self._v("dcp_size", _ps().get_dcp_world_size)
|
|
|
|
@property
|
|
def dcp_rank(self) -> int:
|
|
return self._v("dcp_rank", _ps().get_dcp_rank)
|
|
|
|
@property
|
|
def dcp_enabled(self) -> bool:
|
|
def getter():
|
|
if _ps().get_dcp_group_no_assert() is None:
|
|
return False
|
|
return self.dcp_size > 1
|
|
|
|
return self._v("dcp_enabled", getter)
|
|
|
|
@property
|
|
def attn_dcp_size(self) -> int:
|
|
return self._v(
|
|
"attn_dcp_size", lambda: self.dcp_size if self.dcp_enabled else 1
|
|
)
|
|
|
|
@property
|
|
def attn_dcp_rank(self) -> int:
|
|
return self._v(
|
|
"attn_dcp_rank", lambda: self.dcp_rank if self.dcp_enabled else 0
|
|
)
|
|
|
|
@property
|
|
def attn_dp_size(self) -> int:
|
|
return self._v("attn_dp_size", _dp().get_attention_dp_size)
|
|
|
|
@property
|
|
def attn_dp_rank(self) -> int:
|
|
return self._v("attn_dp_rank", _dp().get_attention_dp_rank)
|
|
|
|
@property
|
|
def world_group(self) -> Any:
|
|
return self._v("world_group", _ps().get_world_group)
|
|
|
|
@property
|
|
def tp_group(self) -> Any:
|
|
return self._v("tp_group", _ps().get_tp_group)
|
|
|
|
@property
|
|
def pp_group(self) -> Any:
|
|
return self._v("pp_group", _ps().get_pp_group)
|
|
|
|
@property
|
|
def moe_ep_group(self) -> Any:
|
|
return self._v("moe_ep_group", _ps().get_moe_ep_group)
|
|
|
|
@property
|
|
def moe_dp_group(self) -> Any:
|
|
return self._v("moe_dp_group", _ps().get_moe_dp_group)
|
|
|
|
@property
|
|
def moe_tp_group(self) -> Any:
|
|
return self._v("moe_tp_group", _ps().get_moe_tp_group)
|
|
|
|
@property
|
|
def attn_tp_group(self) -> Any:
|
|
return self._v("attn_tp_group", _ps().get_attn_tp_group)
|
|
|
|
@property
|
|
def attn_cp_group(self) -> Any:
|
|
return self._v("attn_cp_group", _ps().get_attn_cp_group)
|
|
|
|
@property
|
|
def dcp_group(self) -> Any:
|
|
return self._v("dcp_group", _ps().get_dcp_group)
|
|
|
|
|
|
class _FlagGroupBase:
|
|
"""Shared flag-group behavior: typo-safe writes + transactional ``override()``.
|
|
|
|
Groups are plain dataclasses; ``__dataclass_fields__`` is the single source
|
|
of truth for which leaves exist, so a mistyped name fails loudly instead of
|
|
creating a stray attribute.
|
|
"""
|
|
|
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
if name not in type(self).__dataclass_fields__:
|
|
raise AttributeError(
|
|
f"{type(self).__name__} has no flag '{name}' (leaves are "
|
|
"declared as dataclass fields; check for typos)"
|
|
)
|
|
object.__setattr__(self, name, value)
|
|
|
|
@contextmanager
|
|
def override(self, **kwargs):
|
|
"""Temporarily force flag values, restoring on exit. Transactional
|
|
(keys validated before any write) — the test-only injection
|
|
primitive."""
|
|
fields = type(self).__dataclass_fields__
|
|
unknown = set(kwargs) - set(fields)
|
|
if unknown:
|
|
raise ValueError(
|
|
f"unknown flag(s) for {type(self).__name__}: {sorted(unknown)}"
|
|
)
|
|
saved = {name: getattr(self, name) for name in kwargs}
|
|
for name, value in kwargs.items():
|
|
object.__setattr__(self, name, value)
|
|
try:
|
|
yield self
|
|
finally:
|
|
for name, value in saved.items():
|
|
object.__setattr__(self, name, value)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class CaptureFlags(_FlagGroupBase):
|
|
"""Capture-time flags; never frozen (written during cuda-graph capture)."""
|
|
|
|
# Seeded from server_args at publish; a model whose _can_torch_compile is
|
|
# False clears it during warmup (the only post-publish writer).
|
|
enable_torch_compile: bool = False
|
|
|
|
# Set for the duration of decode/spec graph capture (model_capture_mode).
|
|
# While set, dispose_tensor() is a no-op so deep_gemm's pre-permute does not
|
|
# free hidden_states that the dual-stream MoE shared expert reads afterward.
|
|
disable_dispose_tensor: bool = False
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class MoeFlags(_FlagGroupBase):
|
|
"""MoE runtime flags, materialized by ``initialize_moe_config`` (scheduler
|
|
init, after distributed setup). ``a2a_backend`` / ``runner_backend`` /
|
|
``disable_fp4_allgather`` are the ACTIVE values: the speculative contexts
|
|
in ``layers.moe.utils`` swap them around draft-model forwards. Values are
|
|
the parsed enums from ``layers.moe.utils``; ``None`` means "not
|
|
initialized yet" and the accessors fall back lazily.
|
|
"""
|
|
|
|
a2a_backend: Any = None
|
|
runner_backend: Any = None
|
|
speculative_runner_backend: Any = None
|
|
speculative_a2a_backend: Any = None
|
|
deepep_mode: Any = None
|
|
deepep_config: str | None = None
|
|
tbo_enabled: bool | None = None
|
|
sbo_enabled: bool | None = None
|
|
tbo_token_distribution_threshold: float | None = None
|
|
disable_fp4_allgather: bool | None = None
|
|
quantization: str | None = None
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class DpFlags(_FlagGroupBase):
|
|
"""DP-attention runtime flags, materialized by ``initialize_dp_attention``
|
|
(after distributed setup; reads the model config). Topology values
|
|
(sizes/ranks) stay on ``layers.dp_attention`` until the parallel vertical
|
|
migrates them."""
|
|
|
|
enabled: bool = False
|
|
# Hybrid-SSM models materialize idle ranks via the MAX_LEN fabricated-row
|
|
# conversion (set when hf_config has hybrid_override_pattern).
|
|
max_len_with_idle: bool = False
|
|
# DP gathered-buffer allocation metadata (model hidden size / dtype /
|
|
# device), set by initialize_dp_attention alongside the flags above.
|
|
buffer_hidden_size: Any = None
|
|
buffer_dtype: Any = None
|
|
buffer_device: Any = None
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class Flags(_FlagGroupBase):
|
|
"""Root of the runtime-flags tier.
|
|
|
|
Resolved configuration lives on ``server_args`` fields (materialized at
|
|
the end of ``__post_init__``) — this tier only carries genuine runtime
|
|
state whose value is not a function of the configuration alone, grouped
|
|
by lifecycle (``capture``) or subsystem (``moe`` / ``dp``).
|
|
"""
|
|
|
|
capture: CaptureFlags = dataclasses.field(default_factory=CaptureFlags)
|
|
moe: MoeFlags = dataclasses.field(default_factory=MoeFlags)
|
|
dp: DpFlags = dataclasses.field(default_factory=DpFlags)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class Resources(_FlagGroupBase):
|
|
"""Process-level resource handles: named slots with one reset lifecycle,
|
|
scoped test injection via ``override()``, and the creation/publish
|
|
semantics kept in the owning modules' accessors (which are thin shims
|
|
over these slots)."""
|
|
|
|
# CUDA graph memory pool shared across the prefill and decode graph
|
|
# backends (created lazily by model_executor.runner_utils.pool).
|
|
graph_memory_pool: Any = None
|
|
# EPLB: per-process recorder and the publish-once location metadata
|
|
# (owning accessors live in sglang.srt.eplb).
|
|
expert_distribution_recorder: Any = None
|
|
expert_location_metadata: Any = None
|
|
# LPLB: layer_id -> solver.
|
|
lplb_solvers: dict = dataclasses.field(default_factory=dict)
|
|
# Named side streams (see RuntimeContext.get_stream): name -> stream.
|
|
streams: dict = dataclasses.field(default_factory=dict)
|
|
# Named persistent buffers (see RuntimeContext.get_buffer): name -> tensor.
|
|
# Accessors with bespoke semantics (grow-only, per-device keys) manage
|
|
# their entries directly.
|
|
buffers: dict = dataclasses.field(default_factory=dict)
|
|
# Persistent reusable CUDA events for non-EP DP TBO, keyed by
|
|
# (kind, subbatch) — see dp_attention._tbo_event for why reuse matters.
|
|
tbo_event_pool: dict = dataclasses.field(default_factory=dict)
|
|
# State capturers (installed by their subsystems when capture is on).
|
|
indexer_capturer: Any = None
|
|
experts_capturer: Any = None
|
|
# The shared TCPStore created during distributed initialization.
|
|
tcp_store: Any = None
|
|
# Trace verbosity; the accessor seeds it lazily from SGLANG_TRACE_LEVEL.
|
|
trace_level: Any = None
|
|
|
|
|
|
class ForwardFlags:
|
|
"""Per-forward runtime flags with one API and two backings.
|
|
|
|
Flags read only from eager Python are backed by context variables, so
|
|
nested scopes and threads stay isolated (a new thread sees the defaults).
|
|
Flags that are read or written *inside torch.compile-traced model code*
|
|
(``_GRAPH_VISIBLE``) are backed by plain dict slots instead: dynamo
|
|
cannot trace ``ContextVar.get``/``set``, while plain reads it guards on
|
|
— the storage form these flags had before joining the tier. Their
|
|
writers and readers are single-threaded per process (TBO interleaves
|
|
ubatches on one thread; attention-TP input scattering excludes TBO), so
|
|
context isolation is not needed for correctness.
|
|
|
|
``scoped(**kw)`` — the one regular write path — restores on exit for
|
|
both backings. ``set()`` exists for the legacy unscoped setters' shims.
|
|
"""
|
|
|
|
_DEFAULTS = {
|
|
"multi_stream": False,
|
|
"moe_output_buffer": None,
|
|
# Attention-TP input-scattering (set per forward by
|
|
# AttnTpContext.maybe_input_scattered / set_attn_inputs).
|
|
"attn_input_scattered": False,
|
|
"attn_inputs": None,
|
|
# Sticky across forwards: every ForwardBatch construction writes it;
|
|
# graph runners force False around capture.
|
|
"is_extend_in_batch": False,
|
|
# Per-layer MLP collective control (set by decoder via scoped()
|
|
# around the MLP / MoE / hybrid mixer call).
|
|
# fuse_mlp_allreduce: next residual+LN absorbs the post-MLP all-reduce.
|
|
# mlp_reduce_scatter: postprocess will reduce-scatter (skip MLP AR).
|
|
# flashinfer_trtllm_bypass: deepseek dual-stream graph topk bypass.
|
|
"fuse_mlp_allreduce": False,
|
|
"mlp_reduce_scatter": False,
|
|
"flashinfer_trtllm_bypass": False,
|
|
}
|
|
|
|
# Read/written inside compiled graphs (vocab embedding, communicator,
|
|
# EP dispatch, DP gather/scatter, MLP/MoE skip-AR): plain-slot backed.
|
|
# Before moving a flag out of this set, prove no read/write site sits
|
|
# under torch.compile.
|
|
_GRAPH_VISIBLE = frozenset(
|
|
{
|
|
"attn_input_scattered",
|
|
"attn_inputs",
|
|
"is_extend_in_batch",
|
|
"fuse_mlp_allreduce",
|
|
"mlp_reduce_scatter",
|
|
"flashinfer_trtllm_bypass",
|
|
}
|
|
)
|
|
|
|
__slots__ = ("_vars", "_plain")
|
|
|
|
def __init__(self):
|
|
import contextvars
|
|
|
|
object.__setattr__(
|
|
self,
|
|
"_plain",
|
|
{
|
|
name: default
|
|
for name, default in self._DEFAULTS.items()
|
|
if name in self._GRAPH_VISIBLE
|
|
},
|
|
)
|
|
object.__setattr__(
|
|
self,
|
|
"_vars",
|
|
{
|
|
name: contextvars.ContextVar(f"forward.{name}", default=default)
|
|
for name, default in self._DEFAULTS.items()
|
|
if name not in self._GRAPH_VISIBLE
|
|
},
|
|
)
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
plain = self._plain
|
|
if name in plain:
|
|
return plain[name]
|
|
try:
|
|
return self._vars[name].get()
|
|
except KeyError:
|
|
raise AttributeError(
|
|
f"ForwardFlags has no flag '{name}' (flags are declared in "
|
|
"ForwardFlags._DEFAULTS; check for typos)"
|
|
) from None
|
|
|
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
raise AttributeError(
|
|
"ForwardFlags is written through scoped(**kw) (or the legacy "
|
|
"set() shim), never by attribute assignment"
|
|
)
|
|
|
|
def set(self, name: str, value: Any) -> None:
|
|
"""Unscoped write for legacy setter shims; persists until the next
|
|
write (current context only, for contextvar-backed flags)."""
|
|
if name in self._plain:
|
|
self._plain[name] = value
|
|
else:
|
|
self._vars[name].set(value)
|
|
|
|
@contextmanager
|
|
def scoped(self, **kwargs):
|
|
"""Set flags for the current scope, restoring on exit. Transactional
|
|
(keys validated before any write) and exception-safe."""
|
|
unknown = set(kwargs) - set(self._DEFAULTS)
|
|
if unknown:
|
|
raise ValueError(f"unknown forward flag(s): {sorted(unknown)}")
|
|
plain_saved = [
|
|
(name, self._plain[name]) for name in kwargs if name in self._plain
|
|
]
|
|
tokens = []
|
|
for name, value in kwargs.items():
|
|
if name in self._plain:
|
|
self._plain[name] = value
|
|
else:
|
|
tokens.append((self._vars[name], self._vars[name].set(value)))
|
|
try:
|
|
yield self
|
|
finally:
|
|
for var, token in reversed(tokens):
|
|
var.reset(token)
|
|
for name, value in reversed(plain_saved):
|
|
self._plain[name] = value
|
|
|
|
|
|
class RuntimeContext:
|
|
"""Container for the structured runtime accessors; exposes ``parallel``,
|
|
``server_args``, ``flags``, ``resources``, and ``forward``."""
|
|
|
|
__slots__ = ("parallel", "_server_args", "flags", "resources", "forward")
|
|
|
|
def __init__(self, parallel: ParallelContext):
|
|
self.parallel = parallel
|
|
self._server_args: ServerArgs | None = None
|
|
self.flags = Flags()
|
|
self.resources = Resources()
|
|
self.forward = ForwardFlags()
|
|
|
|
def get_stream(self, name: str) -> Any:
|
|
"""Named process-level CUDA side stream: get-or-create, shared by
|
|
name (the keyed-lazy pattern of the persistent buffers). Creation is
|
|
a driver call that must stay outside cuda-graph capture — call sites
|
|
lease their stream at init/warmup time."""
|
|
stream = self.resources.streams.get(name)
|
|
if stream is None:
|
|
import torch
|
|
|
|
stream = torch.cuda.Stream()
|
|
self.resources.streams[name] = stream
|
|
return stream
|
|
|
|
def set_stream(self, name: str, stream: Any) -> Any:
|
|
"""Install (or replace) the named stream — explicit injection for
|
|
tests and backends that bring their own stream."""
|
|
self.resources.streams[name] = stream
|
|
return stream
|
|
|
|
def get_buffer(self, name: str, factory: Any) -> Any:
|
|
"""Named process-level persistent buffer: get-or-create via
|
|
``factory()``, shared by name (the keyed-lazy pattern of the
|
|
persistent buffers / named streams)."""
|
|
buf = self.resources.buffers.get(name)
|
|
if buf is None:
|
|
buf = factory()
|
|
self.resources.buffers[name] = buf
|
|
return buf
|
|
|
|
@property
|
|
def server_args(self) -> ServerArgs:
|
|
"""The process-wide ``ServerArgs`` (context-owned slot)."""
|
|
server_args = self._server_args
|
|
if server_args is None:
|
|
# Verbatim legacy message: tests and user scripts may match on it.
|
|
raise ValueError("Global server args is not set yet!")
|
|
return server_args
|
|
|
|
def set_server_args(self, server_args: ServerArgs) -> None:
|
|
"""Publish the process-wide ``ServerArgs`` into the context-owned slot.
|
|
|
|
Overwrite-allowed: a re-publish replaces the slot (test kits re-publish
|
|
per test; production ordering discipline lives at the call-sites, e.g.
|
|
the draft-worker guard in ``ModelRunner.__init__``). The published
|
|
object already carries the resolved configuration (declarations
|
|
materialize at the end of ``__post_init__``).
|
|
"""
|
|
# Seed the capture tier for the new lifecycle (defaults for sentinel
|
|
# and mock publishes, which carry no config).
|
|
self.flags.capture.enable_torch_compile = getattr(
|
|
server_args, "enable_torch_compile", False
|
|
)
|
|
self._server_args = server_args
|
|
|
|
def override_server_args(self, **fields) -> _ServerArgsOverride:
|
|
"""Test-only scoped override for the config tier — the sibling of
|
|
``get_parallel().override()`` and the flag groups' ``override()``:
|
|
tests force execution paths by overriding the context instead of
|
|
hand-building config objects.
|
|
|
|
``install()`` (or entering it as a context manager) publishes a fresh
|
|
dummy-boundary ``ServerArgs`` carrying ``fields`` and returns it;
|
|
``restore()`` (or exiting) reinstates whatever the slot held before.
|
|
|
|
Transitional — to be deprecated: it exists because production code
|
|
still branches on raw ``server_args`` fields at runtime, so forcing a
|
|
path needs a full config in the slot. As those readers migrate onto
|
|
the named runtime tiers (flags / resources / forward), prefer the
|
|
finer-grained overrides; once they cover the branching surface this
|
|
override loses its clients and goes away.
|
|
"""
|
|
return _ServerArgsOverride(self, fields)
|
|
|
|
|
|
class _ServerArgsOverride:
|
|
"""Scoped config override (see ``RuntimeContext.override_server_args``).
|
|
|
|
Deliberately a plain class rather than a generator context manager:
|
|
fixtures that live for a whole test case install the override without a
|
|
``with`` block, and a suspended generator would run its restore whenever
|
|
the garbage collector closes it — un-publishing the active config at a
|
|
nondeterministic point.
|
|
"""
|
|
|
|
__slots__ = ("_context", "_fields", "_previous", "_previous_capture", "_installed")
|
|
|
|
def __init__(self, context: RuntimeContext, fields: dict):
|
|
self._context = context
|
|
self._fields = fields
|
|
self._previous: ServerArgs | None = None
|
|
self._previous_capture = False
|
|
self._installed = False
|
|
|
|
def install(self) -> ServerArgs:
|
|
"""Publish a fresh dummy-boundary ``ServerArgs`` carrying the
|
|
overrides (written through ``ServerArgs.override`` for provenance);
|
|
returns the published instance."""
|
|
from sglang.srt.server_args import ServerArgs
|
|
|
|
assert not self._installed, "override_server_args already installed"
|
|
self._previous = self._context._server_args
|
|
self._previous_capture = self._context.flags.capture.enable_torch_compile
|
|
server_args = ServerArgs(model_path="dummy")
|
|
if self._fields:
|
|
server_args.override(source="test-override", **self._fields)
|
|
# The dummy boundary skips materialization, which would leave the
|
|
# strict mutation guard unarmed on the published object — mark it
|
|
# materialized so bare post-publish writes raise like they do on a
|
|
# fully resolved config.
|
|
object.__setattr__(server_args, "_declarations_materialized", True)
|
|
self._context.set_server_args(server_args)
|
|
self._installed = True
|
|
return server_args
|
|
|
|
def restore(self) -> None:
|
|
"""Reinstate the previously published config (or the empty slot)."""
|
|
if not self._installed:
|
|
return
|
|
self._installed = False
|
|
previous, self._previous = self._previous, None
|
|
if previous is None:
|
|
self._context._server_args = None
|
|
else:
|
|
self._context.set_server_args(previous)
|
|
# set_server_args reseeds the capture tier from the published object
|
|
# (and the empty-slot path does not touch it at all); the snapshot
|
|
# puts back the exact pre-install runtime state either way.
|
|
self._context.flags.capture.enable_torch_compile = self._previous_capture
|
|
|
|
def __enter__(self) -> ServerArgs:
|
|
return self.install()
|
|
|
|
def __exit__(self, *exc) -> None:
|
|
self.restore()
|
|
|
|
|
|
_PARALLEL = ParallelContext()
|
|
_CONTEXT = RuntimeContext(parallel=_PARALLEL)
|
|
|
|
|
|
def get_context() -> RuntimeContext:
|
|
return _CONTEXT
|
|
|
|
|
|
def get_parallel() -> ParallelContext:
|
|
return _PARALLEL
|
|
|
|
|
|
def get_server_args() -> ServerArgs:
|
|
return _CONTEXT.server_args
|
|
|
|
|
|
def get_flags() -> Flags:
|
|
return _CONTEXT.flags
|
|
|
|
|
|
def get_resources() -> Resources:
|
|
return _CONTEXT.resources
|
|
|
|
|
|
def get_forward() -> ForwardFlags:
|
|
return _CONTEXT.forward
|
|
|
|
|
|
def get_stream(name: str) -> Any:
|
|
return _CONTEXT.get_stream(name)
|
|
|
|
|
|
def set_stream(name: str, stream: Any) -> Any:
|
|
return _CONTEXT.set_stream(name, stream)
|
|
|
|
|
|
def get_buffer(name: str, factory: Any) -> Any:
|
|
return _CONTEXT.get_buffer(name, factory)
|
|
|
|
|
|
def reset_context() -> None:
|
|
"""Clear the context-owned store (unit-test teardown): drop the published
|
|
``server_args`` and install fresh ``Flags`` and ``Resources``.
|
|
|
|
Wrapper subsystems (``parallel``) hold no state and are unaffected.
|
|
"""
|
|
_CONTEXT._server_args = None
|
|
_CONTEXT.flags = Flags()
|
|
_CONTEXT.resources = Resources()
|
|
_CONTEXT.forward = ForwardFlags()
|