[Config] msgspec.Struct for the config tier (#38753)
This commit is contained in:
@@ -10,6 +10,8 @@ import os
|
||||
import time
|
||||
from typing import Callable, Optional
|
||||
|
||||
import msgspec
|
||||
import msgspec.structs
|
||||
import requests
|
||||
|
||||
from sglang.srt.entrypoints.http_server import launch_server
|
||||
@@ -21,7 +23,7 @@ DEFAULT_TIMEOUT = 600
|
||||
|
||||
# Field defaults of ServerArgs, used to detect when --host/--port were set
|
||||
# explicitly (and would be silently ignored in connect mode).
|
||||
_SERVER_ARGS_DEFAULTS = {f.name: f.default for f in dataclasses.fields(ServerArgs)}
|
||||
_SERVER_ARGS_DEFAULTS = {f.name: f.default for f in msgspec.structs.fields(ServerArgs)}
|
||||
|
||||
|
||||
def server_is_up(base_url: str, timeout: float = DEFAULT_TIMEOUT) -> bool:
|
||||
|
||||
@@ -6,6 +6,8 @@ import warnings
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
import aiohttp
|
||||
import msgspec
|
||||
import msgspec.structs
|
||||
import requests
|
||||
|
||||
from sglang.lang.backend.base_backend import BaseBackend
|
||||
@@ -383,7 +385,12 @@ class Runtime:
|
||||
# Pre-allocate a port before building the config, so the config is born
|
||||
# with the port this runtime will serve on.
|
||||
requested_port = kwargs.pop(
|
||||
"port", ServerArgs.__dataclass_fields__["port"].default
|
||||
"port",
|
||||
next(
|
||||
f.default
|
||||
for f in msgspec.structs.fields(ServerArgs)
|
||||
if f.name == "port"
|
||||
),
|
||||
)
|
||||
for port in range(requested_port, 40000):
|
||||
if is_port_available(port):
|
||||
|
||||
@@ -53,12 +53,14 @@ from typing import (
|
||||
get_type_hints,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
import msgspec.structs
|
||||
|
||||
A = Annotated
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Arg:
|
||||
"""CLI argument metadata attached to a dataclass field via ``Annotated``."""
|
||||
class Arg(msgspec.Struct, frozen=True):
|
||||
"""CLI argument metadata attached to a field via ``Annotated``."""
|
||||
|
||||
help: str = ""
|
||||
choices: list | None = None
|
||||
@@ -90,8 +92,7 @@ class Arg:
|
||||
fallback: Any = None
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Derived:
|
||||
class Derived(msgspec.Struct, frozen=True):
|
||||
"""Metadata for a field the configuration implies, not one anyone types.
|
||||
|
||||
The other half of a namespace. An ``Arg`` field is the operator's input and
|
||||
@@ -118,8 +119,7 @@ class Derived:
|
||||
fn: str = ""
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class NS:
|
||||
class NS(msgspec.Struct, frozen=True):
|
||||
"""Namespace-path marker for a ServerArgs field, attached alongside the
|
||||
field's metadata in ``Annotated``:
|
||||
|
||||
@@ -151,7 +151,7 @@ def namespace_of(cls) -> dict:
|
||||
the per-field ``NS`` marker. A field with neither is absent from the map
|
||||
(the coverage lint flags them). Non-dataclass types yield an empty map.
|
||||
"""
|
||||
if not dataclasses.is_dataclass(cls):
|
||||
if not is_record(cls):
|
||||
return {}
|
||||
# An assembled record: the collector recorded who declared each field,
|
||||
# because there are no base classes left to ask.
|
||||
@@ -164,10 +164,10 @@ def namespace_of(cls) -> dict:
|
||||
continue
|
||||
for name in getattr(base, "__annotations__", {}):
|
||||
out.setdefault(name, path)
|
||||
if len(out) == len(dataclasses.fields(cls)):
|
||||
if len(out) == len(record_fields(cls)):
|
||||
return out
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
for field in dataclasses.fields(cls):
|
||||
for field in record_fields(cls):
|
||||
if field.name in out:
|
||||
continue
|
||||
tp = hints.get(field.name, field.type)
|
||||
@@ -182,9 +182,9 @@ def namespace_of(cls) -> dict:
|
||||
@functools.cache
|
||||
def field_names(cls) -> frozenset:
|
||||
"""Names of ``cls`` dataclass fields — what a declaration may name."""
|
||||
if not dataclasses.is_dataclass(cls):
|
||||
if not is_record(cls):
|
||||
return frozenset()
|
||||
return frozenset(field.name for field in dataclasses.fields(cls))
|
||||
return frozenset(field.name for field in record_fields(cls))
|
||||
|
||||
|
||||
@functools.cache
|
||||
@@ -194,11 +194,11 @@ def resolvable_fields(cls) -> frozenset:
|
||||
|
||||
Non-dataclass types (e.g. mock config objects in tests) have no Arg
|
||||
metadata and yield an empty whitelist."""
|
||||
if not dataclasses.is_dataclass(cls):
|
||||
if not is_record(cls):
|
||||
return frozenset()
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
names = set()
|
||||
for field in dataclasses.fields(cls):
|
||||
for field in record_fields(cls):
|
||||
_, arg = _unwrap_annotated(hints.get(field.name, field.type))
|
||||
if arg is not None and arg.resolvable:
|
||||
names.add(field.name)
|
||||
@@ -213,11 +213,11 @@ def fallbacks_of(cls) -> dict:
|
||||
beside the help text of the field it belongs to rather than in whatever
|
||||
hook used to fill it in.
|
||||
"""
|
||||
if not dataclasses.is_dataclass(cls):
|
||||
if not is_record(cls):
|
||||
return {}
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
out = {}
|
||||
for field in dataclasses.fields(cls):
|
||||
for field in record_fields(cls):
|
||||
_, arg = _unwrap_annotated(hints.get(field.name, field.type))
|
||||
if arg is not None and arg.fallback is not None:
|
||||
# Two things `with_fallback` relies on and cannot check itself,
|
||||
@@ -254,6 +254,26 @@ def with_fallback(cls, name: str, value: Any) -> Any:
|
||||
return fallbacks_of(cls).get(name, value)
|
||||
|
||||
|
||||
def record_fields(cls):
|
||||
"""The declared fields of a record, Struct or dataclass.
|
||||
|
||||
`ServerArgs` and the namespace classes are `msgspec.Struct`; the config-bag
|
||||
tests build ad-hoc dataclasses spanning namespaces, and the helpers here are
|
||||
driven with both. Anything else yields nothing.
|
||||
"""
|
||||
if isinstance(cls, type) and issubclass(cls, msgspec.Struct):
|
||||
return msgspec.structs.fields(cls)
|
||||
if dataclasses.is_dataclass(cls):
|
||||
return dataclasses.fields(cls)
|
||||
return ()
|
||||
|
||||
|
||||
def is_record(cls) -> bool:
|
||||
"""Whether ``cls`` declares fields the way a record does."""
|
||||
target = cls if isinstance(cls, type) else type(cls)
|
||||
return issubclass(target, msgspec.Struct) or dataclasses.is_dataclass(cls)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -314,10 +334,16 @@ def _infer_type_func(tp):
|
||||
|
||||
|
||||
def _field_default(field):
|
||||
"""Return the default value for a dataclass field, or _MISSING."""
|
||||
if field.default is not _MISSING:
|
||||
"""Return the default value for a field, or `_MISSING`.
|
||||
|
||||
The two record shapes spell "no default" differently -- a Struct field says
|
||||
`msgspec.NODEFAULT`, a dataclass field `dataclasses.MISSING` -- so both are
|
||||
normalized here and every caller below tests against `_MISSING` alone.
|
||||
"""
|
||||
absent = (_MISSING, msgspec.NODEFAULT)
|
||||
if field.default not in absent:
|
||||
return field.default
|
||||
if field.default_factory is not _MISSING:
|
||||
if field.default_factory not in absent:
|
||||
return field.default_factory()
|
||||
return _MISSING
|
||||
|
||||
@@ -347,7 +373,7 @@ def add_cli_args_from_dataclass(parser, cls, *, fields: list[str] | None = None)
|
||||
"""
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
|
||||
for field in dataclasses.fields(cls):
|
||||
for field in record_fields(cls):
|
||||
if fields is not None and field.name not in fields:
|
||||
continue
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ from __future__ import annotations
|
||||
import dataclasses
|
||||
from typing import Any, Dict, List, Tuple, get_type_hints
|
||||
|
||||
import msgspec
|
||||
import msgspec.structs
|
||||
|
||||
from sglang.srt.arg_groups.field_order import POSITIONAL_FIELD_ORDER
|
||||
|
||||
|
||||
@@ -46,7 +49,7 @@ def collect_input_fields(
|
||||
defaults: Dict[str, Any] = {}
|
||||
for source in sources:
|
||||
hints = get_type_hints(source, include_extras=True)
|
||||
for field in dataclasses.fields(source):
|
||||
for field in msgspec.structs.fields(source):
|
||||
if field.name in annotations:
|
||||
raise ValueError(
|
||||
f"{field.name!r} is declared by both "
|
||||
@@ -54,10 +57,10 @@ def collect_input_fields(
|
||||
"a field belongs to exactly one namespace"
|
||||
)
|
||||
annotations[field.name] = (source, hints[field.name])
|
||||
if field.default is not dataclasses.MISSING:
|
||||
if field.default is not msgspec.NODEFAULT:
|
||||
defaults[field.name] = field.default
|
||||
elif field.default_factory is not dataclasses.MISSING:
|
||||
defaults[field.name] = dataclasses.field(
|
||||
elif field.default_factory is not msgspec.NODEFAULT:
|
||||
defaults[field.name] = msgspec.field(
|
||||
default_factory=field.default_factory
|
||||
)
|
||||
known = [n for n in POSITIONAL_FIELD_ORDER if n in annotations]
|
||||
|
||||
@@ -9,18 +9,18 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import (
|
||||
Callable,
|
||||
List,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import A
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Device:
|
||||
class Device(msgspec.Struct):
|
||||
"""Namespace ``device``."""
|
||||
|
||||
_NS_PATH = "device"
|
||||
|
||||
@@ -9,13 +9,14 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import (
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -24,8 +25,7 @@ from sglang.srt.arg_groups.choices import DISAGG_TRANSFER_BACKEND_CHOICES
|
||||
from sglang.srt.utils.common import json_list_type
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Disagg:
|
||||
class Disagg(msgspec.Struct):
|
||||
"""Namespace ``disagg``."""
|
||||
|
||||
_NS_PATH = "disagg"
|
||||
@@ -137,7 +137,7 @@ class Disagg:
|
||||
choices=["auto", "zmq_to_scheduler", "zmq_to_tokenizer", "mooncake"],
|
||||
),
|
||||
] = "auto"
|
||||
encoder_urls: A[List[str], "List of encoder server urls."] = dataclasses.field(
|
||||
encoder_urls: A[List[str], "List of encoder server urls."] = msgspec.field(
|
||||
default_factory=list
|
||||
)
|
||||
encoder_bootstrap_port: A[
|
||||
@@ -147,7 +147,7 @@ class Disagg:
|
||||
encoder_register_urls: A[
|
||||
List[str],
|
||||
"One or more EncoderBootstrapServer URLs to register this encoder with on startup, for dynamic encoder discovery. Example: --encoder-register-urls http://prefill0:8997 http://prefill1:8997. Used with --encoder-only servers.",
|
||||
] = dataclasses.field(default_factory=list)
|
||||
] = msgspec.field(default_factory=list)
|
||||
enable_adaptive_dispatch_to_encoder: A[
|
||||
bool,
|
||||
"When enabled, adaptively dispatch: multi-image requests go to encoder in language_only epd mode, single-image requests are processed locally.",
|
||||
|
||||
@@ -10,13 +10,14 @@ how config is shaped at runtime.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
from typing import (
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -38,8 +39,7 @@ from sglang.srt.model_executor.cuda_graph_config import (
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecFeatures:
|
||||
class ExecFeatures(msgspec.Struct):
|
||||
"""Namespace ``exec.features``."""
|
||||
|
||||
_NS_PATH = "exec.features"
|
||||
@@ -105,8 +105,7 @@ class ExecFeatures:
|
||||
] = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecKernel:
|
||||
class ExecKernel(msgspec.Struct):
|
||||
"""Namespace ``exec.kernel``."""
|
||||
|
||||
_NS_PATH = "exec.kernel"
|
||||
@@ -302,8 +301,7 @@ class ExecKernel:
|
||||
] = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecMamba:
|
||||
class ExecMamba(msgspec.Struct):
|
||||
"""Namespace ``exec.mamba``."""
|
||||
|
||||
_NS_PATH = "exec.mamba"
|
||||
@@ -443,8 +441,7 @@ class ExecMamba:
|
||||
] = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecGraph:
|
||||
class ExecGraph(msgspec.Struct):
|
||||
"""Namespace ``exec.graph``."""
|
||||
|
||||
_NS_PATH = "exec.graph"
|
||||
@@ -533,8 +530,7 @@ class ExecGraph:
|
||||
] = 32
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecComm:
|
||||
class ExecComm(msgspec.Struct):
|
||||
"""Namespace ``exec.comm``."""
|
||||
|
||||
_NS_PATH = "exec.comm"
|
||||
@@ -609,8 +605,7 @@ class ExecComm:
|
||||
] = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecMoe:
|
||||
class ExecMoe(msgspec.Struct):
|
||||
"""Namespace ``exec.moe``."""
|
||||
|
||||
_NS_PATH = "exec.moe"
|
||||
@@ -812,8 +807,7 @@ class ExecMoe:
|
||||
] = None
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecOverlap:
|
||||
class ExecOverlap(msgspec.Struct):
|
||||
"""Namespace ``exec.overlap``."""
|
||||
|
||||
_NS_PATH = "exec.overlap"
|
||||
@@ -834,8 +828,7 @@ class ExecOverlap:
|
||||
] = 0.48
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecOffload:
|
||||
class ExecOffload(msgspec.Struct):
|
||||
"""Namespace ``exec.offload``."""
|
||||
|
||||
_NS_PATH = "exec.offload"
|
||||
@@ -872,8 +865,7 @@ class ExecOffload:
|
||||
] = None
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecDllm:
|
||||
class ExecDllm(msgspec.Struct):
|
||||
"""Namespace ``exec.dllm``."""
|
||||
|
||||
_NS_PATH = "exec.dllm"
|
||||
@@ -897,8 +889,7 @@ class ExecDllm:
|
||||
] = True
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExecDeterministic:
|
||||
class ExecDeterministic(msgspec.Struct):
|
||||
"""Namespace ``exec.deterministic``."""
|
||||
|
||||
_NS_PATH = "exec.deterministic"
|
||||
|
||||
@@ -10,13 +10,14 @@ how config is shaped at runtime.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -29,8 +30,7 @@ from sglang.srt.utils.common import (
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Lora:
|
||||
class Lora(msgspec.Struct):
|
||||
"""Namespace ``lora``."""
|
||||
|
||||
_NS_PATH = "lora"
|
||||
|
||||
@@ -9,7 +9,6 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -17,6 +16,8 @@ from typing import (
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -24,8 +25,7 @@ from sglang.srt.arg_groups.arg_utils import (
|
||||
from sglang.srt.arg_groups.choices import RADIX_EVICTION_POLICY_CHOICES
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Memory:
|
||||
class Memory(msgspec.Struct):
|
||||
"""Namespace ``memory``."""
|
||||
|
||||
_NS_PATH = "memory"
|
||||
|
||||
@@ -9,7 +9,6 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -20,14 +19,15 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Mm:
|
||||
class Mm(msgspec.Struct):
|
||||
"""Namespace ``mm``."""
|
||||
|
||||
_NS_PATH = "mm"
|
||||
@@ -91,7 +91,7 @@ class Mm:
|
||||
"Restrict client-supplied HTTP(S) image, video, and audio URLs to these "
|
||||
"exact hostnames. Redirect destinations are checked against the same "
|
||||
"allowlist. When unset, remote media from any domain is allowed.",
|
||||
] = dataclasses.field(default_factory=list)
|
||||
] = msgspec.field(default_factory=list)
|
||||
media_url_max_file_size_mb: A[
|
||||
int,
|
||||
"Maximum size in MiB for one client-supplied remote media download. "
|
||||
|
||||
@@ -9,7 +9,6 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import (
|
||||
Dict,
|
||||
List,
|
||||
@@ -18,6 +17,8 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -34,8 +35,7 @@ from sglang.srt.utils.common import (
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Model:
|
||||
class Model(msgspec.Struct):
|
||||
"""Namespace ``model``."""
|
||||
|
||||
_NS_PATH = "model"
|
||||
|
||||
@@ -10,7 +10,6 @@ how config is shaped at runtime.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -19,6 +18,8 @@ from typing import (
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -26,8 +27,7 @@ from sglang.srt.arg_groups.arg_utils import (
|
||||
from sglang.srt.utils.common import json_list_type
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Observability:
|
||||
class Observability(msgspec.Struct):
|
||||
"""Namespace ``observability``."""
|
||||
|
||||
_NS_PATH = "observability"
|
||||
@@ -68,7 +68,7 @@ class Observability:
|
||||
help="Exclude uvicorn access logs whose request path starts with any of these prefixes. Defaults to empty (disabled). Example: --uvicorn-access-log-exclude-prefixes /metrics /health",
|
||||
nargs="*",
|
||||
),
|
||||
] = dataclasses.field(default_factory=list)
|
||||
] = msgspec.field(default_factory=list)
|
||||
crash_dump_folder: A[
|
||||
Optional[str],
|
||||
"Folder path to dump requests from the last 5 min before a crash (if any). If not specified, crash dumping is disabled.",
|
||||
|
||||
@@ -10,9 +10,10 @@ how config is shaped at runtime.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
from typing import Optional
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -20,8 +21,7 @@ from sglang.srt.arg_groups.arg_utils import (
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Parallel:
|
||||
class Parallel(msgspec.Struct):
|
||||
"""Namespace ``parallel``."""
|
||||
|
||||
_NS_PATH = "parallel"
|
||||
|
||||
@@ -9,12 +9,13 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -22,8 +23,7 @@ from sglang.srt.arg_groups.arg_utils import (
|
||||
from sglang.srt.utils.common import human_readable_int
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Schedule:
|
||||
class Schedule(msgspec.Struct):
|
||||
"""Namespace ``schedule``."""
|
||||
|
||||
_NS_PATH = "schedule"
|
||||
|
||||
@@ -9,7 +9,6 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -18,6 +17,8 @@ from typing import (
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -25,8 +26,7 @@ from sglang.srt.arg_groups.arg_utils import (
|
||||
from sglang.srt.utils.common import json_list_type
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Serving:
|
||||
class Serving(msgspec.Struct):
|
||||
"""Namespace ``serving``."""
|
||||
|
||||
_NS_PATH = "serving"
|
||||
|
||||
@@ -9,12 +9,13 @@ how config is shaped at runtime.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import (
|
||||
Literal,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
A,
|
||||
Arg,
|
||||
@@ -26,8 +27,7 @@ from sglang.srt.arg_groups.choices import (
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Spec:
|
||||
class Spec(msgspec.Struct):
|
||||
"""Namespace ``spec``."""
|
||||
|
||||
_NS_PATH = "spec"
|
||||
|
||||
@@ -32,7 +32,6 @@ Two declaration forms, keyed on ``hf_config.architectures[0]``:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
@@ -41,6 +40,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
|
||||
from sglang.srt.arg_groups import model_override_base
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
field_names,
|
||||
is_record,
|
||||
resolvable_fields,
|
||||
with_fallback,
|
||||
)
|
||||
@@ -159,7 +159,7 @@ def declare_resolution(server_args: Any, source: str, **fields: Any) -> None:
|
||||
again, so a declaration afterwards is a silent no-op; post-publish changes
|
||||
go to the bags through ``get_context().override(...)``.
|
||||
"""
|
||||
if dataclasses.is_dataclass(type(server_args)):
|
||||
if is_record(server_args):
|
||||
unknown = sorted(set(fields) - field_names(type(server_args)))
|
||||
if unknown:
|
||||
raise AttributeError(f"{source}: {unknown} are not ServerArgs fields")
|
||||
@@ -229,7 +229,7 @@ def record_foreign_defaults(
|
||||
A stand-in record (tests drive the hooks with a plain namespace) has no
|
||||
view to read, so the resolver runs against it directly and uncaptured.
|
||||
"""
|
||||
if not dataclasses.is_dataclass(server_args):
|
||||
if not is_record(server_args):
|
||||
return resolve(server_args)
|
||||
recorder = _ForeignDefaults(server_args)
|
||||
result = resolve(recorder)
|
||||
@@ -1726,7 +1726,7 @@ def validate_declarations(
|
||||
"""
|
||||
# Non-dataclass fixtures carry no Arg metadata (mirrors the
|
||||
# resolvable_fields escape); only real ServerArgs is validated.
|
||||
if not dataclasses.is_dataclass(type(server_args)):
|
||||
if not is_record(server_args):
|
||||
return
|
||||
whitelist = resolvable_fields(type(server_args))
|
||||
for source, decl in declarations:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import record_fields
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
declare_resolution,
|
||||
model_config_of,
|
||||
@@ -158,7 +158,7 @@ def _alias_bootstrap_port_to_api_port(server_args: ServerArgs) -> None:
|
||||
cfg = resolving_view(server_args)
|
||||
default_port = next(
|
||||
f.default
|
||||
for f in dataclasses.fields(server_args)
|
||||
for f in record_fields(type(server_args))
|
||||
if f.name == "disaggregation_bootstrap_port"
|
||||
)
|
||||
if cfg.disaggregation_bootstrap_port not in (
|
||||
|
||||
@@ -8,9 +8,9 @@ it.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import Any
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import record_fields
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_page_size_default,
|
||||
_pipeline_parallel_overlap_disable,
|
||||
@@ -49,7 +49,7 @@ def run_resolution_pipeline(server_args: Any) -> None:
|
||||
# stash is the resolution result the projection reads.
|
||||
server_args._raw_input = {
|
||||
field.name: getattr(server_args, field.name)
|
||||
for field in dataclasses.fields(server_args)
|
||||
for field in record_fields(type(server_args))
|
||||
}
|
||||
|
||||
# Preserve launcher-stage declarations made before Engine starts. They are
|
||||
|
||||
@@ -44,6 +44,7 @@ from typing import (
|
||||
cast,
|
||||
)
|
||||
|
||||
import msgspec
|
||||
import torch
|
||||
import uvloop
|
||||
import zmq
|
||||
@@ -266,7 +267,7 @@ class Engine(EngineScoreMixin, EngineBase):
|
||||
# There was no command line, so the call is what the operator
|
||||
# asked for. `log_level` is filled in above when absent, so it
|
||||
# shows here even when the caller did not pass it.
|
||||
object.__setattr__(
|
||||
msgspec.Struct.__setattr__(
|
||||
server_args,
|
||||
"_launch_command",
|
||||
"Engine(" + ", ".join(f"{k}={v!r}" for k, v in kwargs.items()) + ")",
|
||||
|
||||
@@ -47,7 +47,6 @@ test-only ``override(**kw)``.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import functools
|
||||
import logging
|
||||
import math
|
||||
@@ -56,6 +55,8 @@ import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
import msgspec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
@@ -461,28 +462,29 @@ def _install_derived_widths() -> None:
|
||||
_install_derived_widths()
|
||||
|
||||
|
||||
class _FlagGroupBase:
|
||||
class _FlagGroupBase(msgspec.Struct):
|
||||
"""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.
|
||||
``__struct_fields__`` is the single source of truth for which leaves exist,
|
||||
so a mistyped name fails loudly instead of creating a stray attribute. The
|
||||
write goes through ``super().__setattr__``: a ``Struct`` keeps its fields in
|
||||
its own layout, so ``object.__setattr__`` does not reach them.
|
||||
"""
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
if name not in type(self).__dataclass_fields__:
|
||||
if name not in type(self).__struct_fields__:
|
||||
raise AttributeError(
|
||||
f"{type(self).__name__} has no flag '{name}' (leaves are "
|
||||
"declared as dataclass fields; check for typos)"
|
||||
"declared as struct fields; check for typos)"
|
||||
)
|
||||
object.__setattr__(self, name, value)
|
||||
super().__setattr__(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__
|
||||
fields = type(self).__struct_fields__
|
||||
unknown = set(kwargs) - set(fields)
|
||||
if unknown:
|
||||
raise ValueError(
|
||||
@@ -490,15 +492,14 @@ class _FlagGroupBase:
|
||||
)
|
||||
saved = {name: getattr(self, name) for name in kwargs}
|
||||
for name, value in kwargs.items():
|
||||
object.__setattr__(self, name, value)
|
||||
setattr(self, name, value)
|
||||
try:
|
||||
yield self
|
||||
finally:
|
||||
for name, value in saved.items():
|
||||
object.__setattr__(self, name, value)
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class CaptureFlags(_FlagGroupBase):
|
||||
"""Capture-time flags; never frozen (written during cuda-graph capture)."""
|
||||
|
||||
@@ -512,7 +513,6 @@ class CaptureFlags(_FlagGroupBase):
|
||||
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`` /
|
||||
@@ -552,7 +552,6 @@ class MoeFlags(_FlagGroupBase):
|
||||
speculative_context: bool = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class DpFlags(_FlagGroupBase):
|
||||
"""DP-attention runtime flags, materialized by ``initialize_dp_attention``
|
||||
(after distributed setup; reads the model config). Topology values
|
||||
@@ -576,7 +575,6 @@ class DpFlags(_FlagGroupBase):
|
||||
buffer_device: Any = None
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SpFlags(_FlagGroupBase):
|
||||
"""LayerNorm sequence-parallelism flags, materialized by
|
||||
``initialize_layernorm_sp`` (after distributed setup; reads the model
|
||||
@@ -585,7 +583,6 @@ class SpFlags(_FlagGroupBase):
|
||||
enabled: bool = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Flags(_FlagGroupBase):
|
||||
"""Root of the runtime-flags tier.
|
||||
|
||||
@@ -595,13 +592,12 @@ class Flags(_FlagGroupBase):
|
||||
by lifecycle (``capture``) or subsystem (``moe`` / ``dp`` / ``sp``).
|
||||
"""
|
||||
|
||||
capture: CaptureFlags = dataclasses.field(default_factory=CaptureFlags)
|
||||
moe: MoeFlags = dataclasses.field(default_factory=MoeFlags)
|
||||
dp: DpFlags = dataclasses.field(default_factory=DpFlags)
|
||||
sp: SpFlags = dataclasses.field(default_factory=SpFlags)
|
||||
capture: CaptureFlags = msgspec.field(default_factory=CaptureFlags)
|
||||
moe: MoeFlags = msgspec.field(default_factory=MoeFlags)
|
||||
dp: DpFlags = msgspec.field(default_factory=DpFlags)
|
||||
sp: SpFlags = msgspec.field(default_factory=SpFlags)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Resources(_FlagGroupBase):
|
||||
"""Process-level resource handles: named slots with one reset lifecycle,
|
||||
scoped test injection via ``override()``, and the creation/publish
|
||||
@@ -616,16 +612,16 @@ class Resources(_FlagGroupBase):
|
||||
expert_distribution_recorder: Any = None
|
||||
expert_location_metadata: Any = None
|
||||
# LPLB: layer_id -> solver.
|
||||
lplb_solvers: dict = dataclasses.field(default_factory=dict)
|
||||
lplb_solvers: dict = msgspec.field(default_factory=dict)
|
||||
# Named side streams (see RuntimeContext.get_stream): name -> stream.
|
||||
streams: dict = dataclasses.field(default_factory=dict)
|
||||
streams: dict = msgspec.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)
|
||||
buffers: dict = msgspec.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)
|
||||
tbo_event_pool: dict = msgspec.field(default_factory=dict)
|
||||
# State capturers (installed by their subsystems when capture is on).
|
||||
indexer_capturer: Any = None
|
||||
experts_capturer: Any = None
|
||||
@@ -1290,7 +1286,7 @@ class _ServerArgsOverride:
|
||||
# Underscore names seed private property caches (the strict guard
|
||||
# exempts them); everything else must be a real config field.
|
||||
unknown = {name for name in self._fields if not name.startswith("_")} - set(
|
||||
type(server_args).__dataclass_fields__
|
||||
type(server_args).__struct_fields__
|
||||
)
|
||||
if unknown:
|
||||
raise ValueError(
|
||||
@@ -1303,7 +1299,7 @@ class _ServerArgsOverride:
|
||||
# real field, and seeding it as a raw attribute would leave the earlier
|
||||
# declaration authoritative, so `resolution_result` and the bag would
|
||||
# both keep answering the pre-override value.
|
||||
fields = set(type(server_args).__dataclass_fields__)
|
||||
fields = set(type(server_args).__struct_fields__)
|
||||
declared = {n: v for n, v in self._fields.items() if n in fields}
|
||||
if declared:
|
||||
declare_resolution(server_args, "override_server_args", **declared)
|
||||
@@ -1311,7 +1307,7 @@ class _ServerArgsOverride:
|
||||
# and friends), which are not configuration and never were.
|
||||
seeds = {n: v for n, v in self._fields.items() if n not in fields}
|
||||
for name, value in seeds.items():
|
||||
object.__setattr__(server_args, name, value)
|
||||
msgspec.Struct.__setattr__(server_args, name, value)
|
||||
ctx.set_server_args(server_args)
|
||||
self._installed = True
|
||||
return server_args
|
||||
@@ -1680,7 +1676,7 @@ def set_global_dwdp_manager(manager: Any) -> None:
|
||||
def _group_leaves(group: _FlagGroupBase) -> dict[str, Any]:
|
||||
"""The leaf values of a flag group, recursively."""
|
||||
leaves: dict[str, Any] = {}
|
||||
for name in type(group).__dataclass_fields__:
|
||||
for name in type(group).__struct_fields__:
|
||||
value = getattr(group, name)
|
||||
if isinstance(value, _FlagGroupBase):
|
||||
leaves[name] = _group_leaves(value)
|
||||
|
||||
@@ -42,9 +42,13 @@ import tempfile
|
||||
import uuid
|
||||
from typing import Any, NoReturn
|
||||
|
||||
import msgspec
|
||||
|
||||
from sglang.kernels.ops.kv_canary.consts import RealKvHashMode
|
||||
from sglang.srt.arg_groups.arg_utils import (
|
||||
add_cli_args_from_dataclass,
|
||||
is_record,
|
||||
record_fields,
|
||||
)
|
||||
from sglang.srt.arg_groups.argparse_actions import (
|
||||
DeprecatedStoreTrueAction,
|
||||
@@ -170,13 +174,14 @@ from sglang.srt.utils.common import ( # noqa: F401
|
||||
|
||||
|
||||
def _plain(value: Any) -> Any:
|
||||
"""``dataclasses.asdict``'s conversion, applied to one value: dataclasses
|
||||
become dicts, containers recurse, everything else is deep-copied (a caller
|
||||
mutating the dump must not reach the live configuration)."""
|
||||
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
||||
"""``asdict``'s conversion, applied to one value: a record -- Struct or
|
||||
dataclass, since nested config values are both -- becomes a dict, containers
|
||||
recurse, everything else is deep-copied (a caller mutating the dump must not
|
||||
reach the live configuration)."""
|
||||
if not isinstance(value, type) and is_record(value):
|
||||
return {
|
||||
field.name: _plain(getattr(value, field.name))
|
||||
for field in dataclasses.fields(value)
|
||||
for field in record_fields(type(value))
|
||||
}
|
||||
if isinstance(value, tuple) and hasattr(value, "_fields"): # namedtuple
|
||||
return type(value)(*(_plain(item) for item in value))
|
||||
@@ -307,7 +312,7 @@ class ServerArgs:
|
||||
"""This configuration as a plain dict of resolved field values.
|
||||
|
||||
What the whole-object readbacks report (`/server_info` and its gRPC and
|
||||
in-process twins). `dataclasses.asdict(self)` reads the fields, which
|
||||
in-process twins). A plain `asdict` reads the fields, which
|
||||
carry the raw input; this reads the declarations, so it answers with what
|
||||
resolution decided. Nested dataclass fields are expanded
|
||||
the way `asdict` expands them; the private resolution bookkeeping and the
|
||||
@@ -316,7 +321,7 @@ class ServerArgs:
|
||||
|
||||
return {
|
||||
field.name: _plain(resolution_result(self, field.name))
|
||||
for field in dataclasses.fields(self)
|
||||
for field in record_fields(type(self))
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -350,7 +355,7 @@ class ServerArgs:
|
||||
"--sampling-backend",
|
||||
type=str,
|
||||
choices=sampling_backend_choices,
|
||||
default=ServerArgs.sampling_backend,
|
||||
default=_declared_default("sampling_backend"),
|
||||
help="Choose the kernels for sampling layers.",
|
||||
)
|
||||
|
||||
@@ -359,7 +364,7 @@ class ServerArgs:
|
||||
"--reasoning-parser",
|
||||
type=str,
|
||||
choices=["auto"] + reasoning_parser_choices,
|
||||
default=ServerArgs.reasoning_parser,
|
||||
default=_declared_default("reasoning_parser"),
|
||||
help=f"Specify the parser for reasoning models. "
|
||||
f"Use 'auto' to detect from chat template. "
|
||||
f"Options include: {reasoning_parser_choices}.",
|
||||
@@ -369,7 +374,7 @@ class ServerArgs:
|
||||
"--tool-call-parser",
|
||||
type=str,
|
||||
choices=["auto"] + tool_call_parser_choices,
|
||||
default=ServerArgs.tool_call_parser,
|
||||
default=_declared_default("tool_call_parser"),
|
||||
help=f"Specify the parser for handling tool-call interactions. "
|
||||
f"Use 'auto' to detect from chat template. "
|
||||
f"Options include: {tool_call_parser_choices}.",
|
||||
@@ -377,7 +382,7 @@ class ServerArgs:
|
||||
parser.add_argument(
|
||||
"--kv-canary-real-data",
|
||||
type=str,
|
||||
default=ServerArgs.kv_canary_real_data,
|
||||
default=_declared_default("kv_canary_real_data"),
|
||||
choices=[m.name.lower() for m in RealKvHashMode],
|
||||
help=(
|
||||
"Check the real KV-cache in the canary. "
|
||||
@@ -415,9 +420,7 @@ class ServerArgs:
|
||||
# Some dataclass fields (e.g. stat_loggers) intentionally have no CLI
|
||||
# surface and won't appear on the argparse Namespace. Skip them so the
|
||||
# dataclass default applies.
|
||||
attrs = [
|
||||
attr.name for attr in dataclasses.fields(cls) if hasattr(args, attr.name)
|
||||
]
|
||||
attrs = [attr.name for attr in record_fields(cls) if hasattr(args, attr.name)]
|
||||
return cls(**{attr: getattr(args, attr) for attr in attrs})
|
||||
|
||||
def get_tokenizer_worker_class(self):
|
||||
@@ -469,7 +472,26 @@ class ServerArgs:
|
||||
"resolved config; a value one runner owns travels as a "
|
||||
"constructor argument."
|
||||
)
|
||||
object.__setattr__(self, name, value)
|
||||
# The Struct's own setter, spelled explicitly: this method is copied
|
||||
# into the class `defstruct` builds, so a zero-argument `super()` would
|
||||
# still close over the class it was written in. `object.__setattr__`
|
||||
# does not reach a Struct's fields at all.
|
||||
msgspec.Struct.__setattr__(self, name, value)
|
||||
|
||||
def __reduce__(self):
|
||||
"""Pickle the record *and* what resolution left on it.
|
||||
|
||||
A Struct pickles its fields; everything else lives in the `dict=True`
|
||||
namespace and would be dropped, which for this record means the input
|
||||
snapshot, the declaration stash and the resolution flags -- the whole
|
||||
reason a child can publish what its parent decided without resolving
|
||||
again. Reconstruction restores the fields first and the bookkeeping
|
||||
after, so the seal is re-armed only once the fields are in place.
|
||||
"""
|
||||
return (
|
||||
_rebuild_server_args,
|
||||
(type(self), msgspec.structs.asdict(self), dict(self.__dict__)),
|
||||
)
|
||||
|
||||
def check_server_args(self):
|
||||
from sglang.srt.arg_groups.validation_hook import check_server_args
|
||||
@@ -522,9 +544,23 @@ ServerArgs._NS_BY_FIELD = _namespaces
|
||||
# The classes themselves, so the bag projection can find the declarations
|
||||
# that are not fields -- the derived half of each namespace.
|
||||
ServerArgs._NAMESPACES = _INPUT_NAMESPACES
|
||||
for _name, _value in _defaults.items():
|
||||
setattr(ServerArgs, _name, _value)
|
||||
ServerArgs = dataclasses.dataclass(ServerArgs)
|
||||
# `dict=True` so the record can carry what is not configuration -- the input
|
||||
# snapshot, the declaration stash, the resolution flags, the memo slots. A
|
||||
# Struct has no `__dict__` without it, and those are exactly the underscore
|
||||
# names `_underscore_field_names()` is careful *not* to include.
|
||||
ServerArgs = msgspec.defstruct(
|
||||
"ServerArgs",
|
||||
[
|
||||
(_name, _ann, _defaults[_name]) if _name in _defaults else (_name, _ann)
|
||||
for _name, _ann in ServerArgs.__annotations__.items()
|
||||
],
|
||||
namespace={
|
||||
_k: _v
|
||||
for _k, _v in vars(ServerArgs).items()
|
||||
if _k not in ("__dict__", "__weakref__", "__annotations__")
|
||||
},
|
||||
dict=True,
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -586,9 +622,7 @@ def _underscore_field_names() -> frozenset:
|
||||
by spelling would leave exactly one leaf writable on a read-only record.
|
||||
"""
|
||||
return frozenset(
|
||||
field.name
|
||||
for field in dataclasses.fields(ServerArgs)
|
||||
if field.name.startswith("_")
|
||||
field.name for field in record_fields(ServerArgs) if field.name.startswith("_")
|
||||
)
|
||||
|
||||
|
||||
@@ -631,6 +665,28 @@ def get_global_server_args() -> NoReturn:
|
||||
)
|
||||
|
||||
|
||||
def _rebuild_server_args(cls, fields, bookkeeping):
|
||||
"""Rebuild a pickled record: fields through the constructor, the rest after."""
|
||||
record = cls(**fields)
|
||||
record.__dict__.update(bookkeeping)
|
||||
return record
|
||||
|
||||
|
||||
def _declared_default(name: str):
|
||||
"""The declared default of a field, for a manual `add_argument`.
|
||||
|
||||
`ServerArgs.<field>` used to answer with it. The record is a Struct now, so
|
||||
that expression returns the slot descriptor instead -- which argparse
|
||||
happily stores as the default, and the first reader gets a
|
||||
`member_descriptor` where it expected a string.
|
||||
"""
|
||||
return next(
|
||||
field.default
|
||||
for field in msgspec.structs.fields(ServerArgs)
|
||||
if field.name == name
|
||||
)
|
||||
|
||||
|
||||
def prepare_server_args(argv: list[str]) -> ServerArgs:
|
||||
"""
|
||||
Prepare the server arguments from the command line arguments.
|
||||
@@ -669,7 +725,7 @@ def prepare_server_args(argv: list[str]) -> ServerArgs:
|
||||
# Not a field: the record's fields are the configuration, and this is how
|
||||
# the configuration was asked for. It rides along on the record so a
|
||||
# subprocess copy can answer the same question the launcher can.
|
||||
object.__setattr__(server_args, "_launch_command", " ".join(argv))
|
||||
server_args._launch_command = " ".join(argv)
|
||||
return server_args
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ from types import ModuleType, SimpleNamespace
|
||||
from typing import Any, Awaitable, Callable, List, Optional, Tuple
|
||||
|
||||
import aiohttp
|
||||
import msgspec
|
||||
import numpy as np
|
||||
import requests
|
||||
import torch
|
||||
@@ -2118,7 +2119,7 @@ def server_args_variant(server_args, **fields):
|
||||
unknown = {
|
||||
name
|
||||
for name in fields
|
||||
if name not in cls.__dataclass_fields__
|
||||
if name not in cls.__struct_fields__
|
||||
and not hasattr(cls, name)
|
||||
and name not in _RUNNER_WRITTEN_NAMES
|
||||
}
|
||||
@@ -2130,16 +2131,14 @@ def server_args_variant(server_args, **fields):
|
||||
stash = getattr(variant, "_resolved_overrides", None)
|
||||
if stash is None:
|
||||
stash = []
|
||||
object.__setattr__(variant, "_resolved_overrides", stash)
|
||||
msgspec.Struct.__setattr__(variant, "_resolved_overrides", stash)
|
||||
declared = {
|
||||
name: value
|
||||
for name, value in fields.items()
|
||||
if name in cls.__dataclass_fields__
|
||||
name: value for name, value in fields.items() if name in cls.__struct_fields__
|
||||
}
|
||||
if declared:
|
||||
stash.append(("server_args_variant", dict(declared)))
|
||||
for name, value in fields.items():
|
||||
object.__setattr__(variant, name, value)
|
||||
msgspec.Struct.__setattr__(variant, name, value)
|
||||
return variant
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user