config: the resolution callbacks into the record go to zero (#36972)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
48b88e1256
commit
b65e677e48
@@ -58,12 +58,12 @@ class TestServerArgsIBDeviceValidation(unittest.TestCase):
|
||||
real_listdir = os.listdir
|
||||
|
||||
with patch(
|
||||
"sglang.srt.server_args.os.path.isdir",
|
||||
"sglang.srt.arg_groups.validation_hook.os.path.isdir",
|
||||
side_effect=lambda path: (
|
||||
True if path == "/sys/class/infiniband" else real_isdir(path)
|
||||
),
|
||||
), patch(
|
||||
"sglang.srt.server_args.os.listdir",
|
||||
"sglang.srt.arg_groups.validation_hook.os.listdir",
|
||||
side_effect=lambda path: (
|
||||
available_devices
|
||||
if path == "/sys/class/infiniband"
|
||||
|
||||
@@ -88,8 +88,9 @@ class TestMultimodalPiecewiseCudaGraph(CustomTestCase):
|
||||
"sglang.srt.arg_groups.cuda_graph_hook"
|
||||
".disable_tc_piecewise_cudagraph_if_incompatible"
|
||||
) as disable_if_incompatible,
|
||||
patch.object(
|
||||
args, "_resolved_attention_backends", return_value=("fa3", "fa3")
|
||||
patch(
|
||||
"sglang.srt.arg_groups.overrides.attention_backends_of",
|
||||
return_value=("fa3", "fa3"),
|
||||
),
|
||||
):
|
||||
apply_cuda_graph_compatibility(args)
|
||||
@@ -116,12 +117,11 @@ class TestMultimodalPiecewiseCudaGraph(CustomTestCase):
|
||||
args._cuda_graph_config_locked = set()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
args,
|
||||
"_resolved_attention_backends",
|
||||
patch(
|
||||
"sglang.srt.arg_groups.overrides.attention_backends_of",
|
||||
return_value=("trtllm_mla", "trtllm_mla"),
|
||||
),
|
||||
patch.object(args, "use_mla_backend", return_value=True),
|
||||
patch("sglang.srt.arg_groups.overrides.use_mla_backend", return_value=True),
|
||||
):
|
||||
apply_cuda_graph_compatibility(args)
|
||||
|
||||
@@ -137,9 +137,8 @@ class TestMultimodalPiecewiseCudaGraph(CustomTestCase):
|
||||
)
|
||||
args._cuda_graph_config_locked = {(Phase.PREFILL, "backend")}
|
||||
|
||||
with patch.object(
|
||||
args,
|
||||
"_resolved_attention_backends",
|
||||
with patch(
|
||||
"sglang.srt.arg_groups.overrides.attention_backends_of",
|
||||
return_value=("trtllm_mla", "trtllm_mla"),
|
||||
):
|
||||
apply_cuda_graph_compatibility(args)
|
||||
@@ -185,10 +184,7 @@ class TestMultimodalPiecewiseCudaGraph(CustomTestCase):
|
||||
args.disable_radix_cache = False
|
||||
args.chunked_prefill_size = 2048
|
||||
|
||||
with (
|
||||
patch.object(args, "get_model_config", return_value=args._model_config),
|
||||
patch("sglang.srt.arg_groups.model_hook.is_cuda", return_value=True),
|
||||
):
|
||||
with (patch("sglang.srt.arg_groups.model_hook.is_cuda", return_value=True),):
|
||||
handle_model_capability_adjustments(args)
|
||||
|
||||
self.assertTrue(resolution_result(args, "disable_radix_cache"))
|
||||
@@ -215,7 +211,7 @@ class TestMultimodalPiecewiseCudaGraph(CustomTestCase):
|
||||
hf_config=SimpleNamespace(architectures=["BertModel"]),
|
||||
)
|
||||
|
||||
with patch.object(args, "get_model_config", return_value=args._model_config):
|
||||
if True: # the record already carries the seeded configuration
|
||||
handle_model_capability_adjustments(args)
|
||||
|
||||
self.assertTrue(resolution_result(args, "is_embedding"))
|
||||
|
||||
@@ -87,7 +87,10 @@ from sglang.srt.mem_cache.unified_radix_cache import (
|
||||
_OngoingPrefetch,
|
||||
_OngoingWriteThrough,
|
||||
)
|
||||
from sglang.srt.runtime_context import get_server_args, get_serving
|
||||
from sglang.srt.runtime_context import (
|
||||
get_serving,
|
||||
mamba_cache_chunk_size,
|
||||
)
|
||||
from sglang.srt.sampling.sampling_params import SamplingParams
|
||||
from sglang.srt.server_args import (
|
||||
ServerArgs,
|
||||
@@ -5225,7 +5228,7 @@ class UnifiedRadixCacheSuite:
|
||||
if not self.cfg.has_mamba or self.cfg.has_swa or self.cfg.page_size != 1:
|
||||
self.skipTest("requires page_size=1 Full+Mamba")
|
||||
cache, allocator, req_to_token_pool = build_fixture(self.cfg)
|
||||
chunk_size = get_server_args().mamba_cache_chunk_size
|
||||
chunk_size = mamba_cache_chunk_size()
|
||||
tokens = self._make_seq(1, chunk_size + 1)
|
||||
self._insert(cache, allocator, req_to_token_pool, tokens)
|
||||
leaf = cache.match_prefix(
|
||||
@@ -5258,7 +5261,7 @@ class UnifiedRadixCacheSuite:
|
||||
if not self.cfg.has_mamba or self.cfg.has_swa or self.cfg.page_size != 1:
|
||||
self.skipTest("requires page_size=1 Full+Mamba")
|
||||
cache, allocator, req_to_token_pool = self._build_hicache_fixture()
|
||||
chunk_size = get_server_args().mamba_cache_chunk_size
|
||||
chunk_size = mamba_cache_chunk_size()
|
||||
prefix = self._make_seq(1, chunk_size)
|
||||
tokens = prefix + self._make_seq(1000, chunk_size + 1)
|
||||
self._insert(cache, allocator, req_to_token_pool, prefix)
|
||||
@@ -5283,7 +5286,7 @@ class UnifiedRadixCacheSuite:
|
||||
if not self.cfg.has_mamba or self.cfg.has_swa or self.cfg.page_size != 1:
|
||||
self.skipTest("requires page_size=1 Full+Mamba")
|
||||
cache, allocator, req_to_token_pool = self._build_hicache_fixture()
|
||||
chunk_size = get_server_args().mamba_cache_chunk_size
|
||||
chunk_size = mamba_cache_chunk_size()
|
||||
prefix = self._make_seq(1, chunk_size)
|
||||
tokens = prefix + self._make_seq(1000, chunk_size + 1)
|
||||
self._insert(cache, allocator, req_to_token_pool, prefix)
|
||||
|
||||
@@ -50,10 +50,7 @@ def test_packed_speculative_extend_is_limited_to_pd_prefill_target(mode, error):
|
||||
|
||||
def test_chunked_prefill_disabled_uses_legacy_token_ceiling():
|
||||
model_runner = SimpleNamespace(
|
||||
server_args=SimpleNamespace(
|
||||
max_prefill_buffer_tokens=Mock(return_value=0),
|
||||
max_prefill_tokens=32768,
|
||||
),
|
||||
server_args=SimpleNamespace(),
|
||||
is_generation=True,
|
||||
is_draft_worker=False,
|
||||
spec_algorithm=SimpleNamespace(is_speculative=lambda: False),
|
||||
@@ -76,6 +73,12 @@ def test_chunked_prefill_disabled_uses_legacy_token_ceiling():
|
||||
"get_disagg",
|
||||
return_value=SimpleNamespace(disaggregation_mode="prefill"),
|
||||
),
|
||||
patch.object(flashinfer_autotune, "max_prefill_buffer_tokens", return_value=0),
|
||||
patch.object(
|
||||
flashinfer_autotune,
|
||||
"get_schedule",
|
||||
return_value=SimpleNamespace(max_prefill_tokens=32768),
|
||||
),
|
||||
patch.object(flashinfer_autotune, "run_flashinfer_autotune_forward"),
|
||||
patch.object(flashinfer_autotune.torch.cuda, "empty_cache"),
|
||||
):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""`get_model_config()` caches, and the key is the path the record carried.
|
||||
"""`model_config_of()` caches, and the key is the path the record carried.
|
||||
|
||||
Two movements of a `model_path` reach this cache, and only the first one means
|
||||
the cached configuration describes the wrong checkpoint:
|
||||
@@ -20,6 +20,7 @@ import tempfile
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from sglang.srt.arg_groups.overrides import declare_resolution, model_config_of
|
||||
from sglang.srt.configs.model_config import ModelConfig
|
||||
from sglang.srt.environ import EnvField, envs
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
@@ -117,7 +118,7 @@ class TestTheModelConfigCache(CustomTestCase):
|
||||
self.assertEqual(server_args.model_path, _OBJECT_STORE_URI)
|
||||
self.assertEqual(cached.model_path, pulled)
|
||||
|
||||
self.assertIs(server_args.get_model_config(), cached)
|
||||
self.assertIs(model_config_of(server_args), cached)
|
||||
|
||||
def test_a_declared_model_path_rebuilds_the_configuration(self):
|
||||
"""The GGUF and ModelScope shape: the record's own path moved."""
|
||||
@@ -125,14 +126,15 @@ class TestTheModelConfigCache(CustomTestCase):
|
||||
second_checkpoint = self._checkpoint()
|
||||
|
||||
server_args = ServerArgs(model_path=first_checkpoint, device="cuda")
|
||||
first = server_args.get_model_config()
|
||||
first = model_config_of(server_args)
|
||||
self.assertEqual(first.model_path, first_checkpoint)
|
||||
|
||||
server_args._declare(
|
||||
declare_resolution(
|
||||
server_args,
|
||||
"test_a_declared_model_path_rebuilds_the_configuration",
|
||||
model_path=second_checkpoint,
|
||||
)
|
||||
second = server_args.get_model_config()
|
||||
second = model_config_of(server_args)
|
||||
self.assertIsNot(second, first)
|
||||
self.assertEqual(second.model_path, second_checkpoint)
|
||||
|
||||
@@ -151,11 +153,11 @@ class TestTheModelConfigCache(CustomTestCase):
|
||||
model_path=second_checkpoint,
|
||||
)
|
||||
|
||||
rebuilt = copy_.get_model_config()
|
||||
rebuilt = model_config_of(copy_)
|
||||
self.assertEqual(rebuilt.model_path, second_checkpoint)
|
||||
self.assertIs(copy_.get_model_config(), rebuilt)
|
||||
self.assertIs(model_config_of(copy_), rebuilt)
|
||||
# The parent keeps the configuration it resolved with.
|
||||
self.assertEqual(server_args.get_model_config().model_path, first_checkpoint)
|
||||
self.assertEqual(model_config_of(server_args).model_path, first_checkpoint)
|
||||
|
||||
def test_a_supplied_configuration_is_handed_back(self):
|
||||
"""A configuration nothing in here built carries no key, so nothing
|
||||
@@ -164,7 +166,7 @@ class TestTheModelConfigCache(CustomTestCase):
|
||||
stand_in = SimpleNamespace(model_path="somewhere/else")
|
||||
server_args._model_config = stand_in
|
||||
|
||||
self.assertIs(server_args.get_model_config(), stand_in)
|
||||
self.assertIs(model_config_of(server_args), stand_in)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -30,7 +30,7 @@ _SRT = pathlib.Path(sglang.__file__).resolve().parent / "srt"
|
||||
# Two quantities sharing one name.
|
||||
_READ_BEFORE_RESOLUTION = frozenset({"is_embedding"})
|
||||
|
||||
# Declared after the first `get_model_config()`, so the cached configuration
|
||||
# Declared after the first `model_config_of()`, so the cached configuration
|
||||
# holds the earlier value. Nothing reads the stale copy today (its one consumer
|
||||
# is on the `is_draft_model` branch, built after resolution), and fixing it
|
||||
# means moving the build or the hook. Pinned so a second field in this position
|
||||
@@ -109,7 +109,7 @@ def _registry_collection_is_after_the_build():
|
||||
|
||||
Handler-local ordering only -- the caller still has to compare against the
|
||||
pipeline-wide first build, which sits in an *earlier* step: hoisting the
|
||||
collection above this handler's own `get_model_config()` call does not move
|
||||
collection above this handler's own `model_config_of()` call does not move
|
||||
it above the configuration another handler already cached.
|
||||
"""
|
||||
handler = None
|
||||
@@ -145,7 +145,7 @@ def _registry_collection_is_after_the_build():
|
||||
name = func.id
|
||||
else:
|
||||
continue
|
||||
if name == "get_model_config" and build is None:
|
||||
if name == "model_config_of" and build is None:
|
||||
build = node.lineno
|
||||
if name == "collect_model_override_declarations" and collect is None:
|
||||
collect = node.lineno
|
||||
@@ -192,11 +192,12 @@ def _server_args_names(tree, path):
|
||||
and value.args[0].id in names
|
||||
)
|
||||
# `resolved = self._resolved()` is the same view, spelled as the
|
||||
# record's own member.
|
||||
# resolution vocabulary.
|
||||
member = (
|
||||
isinstance(value, ast.Call)
|
||||
and isinstance(value.func, ast.Attribute)
|
||||
and value.func.attr == "_resolved"
|
||||
and isinstance(value.func, ast.Name)
|
||||
and value.func.id == "resolved_view"
|
||||
and isinstance(value.func.value, ast.Name)
|
||||
and value.func.value.id in names
|
||||
)
|
||||
@@ -267,7 +268,7 @@ def _late_resolution_fields():
|
||||
if isinstance(node.func, ast.Attribute)
|
||||
else getattr(node.func, "id", "")
|
||||
)
|
||||
if called in ("_late_resolution", "declare_late_resolution"):
|
||||
if called == "declare_late_resolution":
|
||||
fields |= {kw.arg for kw in node.keywords if kw.arg}
|
||||
return fields
|
||||
|
||||
@@ -488,14 +489,14 @@ def _declaration_positions():
|
||||
wanted = _constructor_reads()
|
||||
|
||||
def build_site():
|
||||
"""(step index, method name, line) of the first `get_model_config()`."""
|
||||
"""(step index, method name, line) of the first `model_config_of()`."""
|
||||
for index, step in enumerate(steps):
|
||||
for method in reached[step]:
|
||||
for node in ast.walk(methods[method]):
|
||||
if (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "get_model_config"
|
||||
and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "model_config_of"
|
||||
):
|
||||
return index, step, method, node.lineno
|
||||
return None
|
||||
@@ -532,8 +533,8 @@ def _declaration_positions():
|
||||
same_body = index == build_index and method == build_method
|
||||
rank = 0 if same_body and node.lineno < build_line_in_body else 1
|
||||
if (
|
||||
isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "_declare"
|
||||
isinstance(node.func, ast.Name)
|
||||
and node.func.id == "declare_resolution"
|
||||
):
|
||||
fields = {kw.arg for kw in node.keywords if kw.arg}
|
||||
# A handler that calls an imported hook (the Kimi and DeepSeek
|
||||
@@ -669,8 +670,8 @@ class TestModelConfigReadsResolvedInput(CustomTestCase):
|
||||
for method in reached[step]
|
||||
if any(
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "get_model_config"
|
||||
and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "model_config_of"
|
||||
for node in ast.walk(methods[method])
|
||||
)
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ class TestNoPublicNonFieldSlot(CustomTestCase):
|
||||
written = _self_written_attributes()
|
||||
self.assertGreater(
|
||||
len(written),
|
||||
5,
|
||||
3,
|
||||
f"only {len(written)} self-writes found; the scan is broken, not the "
|
||||
"record",
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ under its own default configuration.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from sglang.srt.arg_groups.kv_cache_hook import handle_page_major_kv_layout
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
@@ -66,13 +67,18 @@ def _accepts(
|
||||
"mamba_backend": "triton",
|
||||
}.items():
|
||||
object.__setattr__(sa, name, value)
|
||||
sa.use_mla_backend = lambda: use_mla
|
||||
sa._resolved_attention_backends = lambda: [backend]
|
||||
try:
|
||||
handle_page_major_kv_layout(sa)
|
||||
return True
|
||||
except AssertionError:
|
||||
return False
|
||||
# `use_mla_backend` asks the model configuration, which this stand-in has
|
||||
# no room for; the case under test is what the handler does with the answer.
|
||||
# The handler imports it inside the function, so the source module is
|
||||
# where the patch has to go.
|
||||
with mock.patch(
|
||||
"sglang.srt.arg_groups.overrides.use_mla_backend", return_value=use_mla
|
||||
):
|
||||
try:
|
||||
handle_page_major_kv_layout(sa)
|
||||
return True
|
||||
except AssertionError:
|
||||
return False
|
||||
|
||||
|
||||
class TestPageMajorBackendAllowlist(unittest.TestCase):
|
||||
|
||||
@@ -24,7 +24,6 @@ import unittest
|
||||
import unittest.mock
|
||||
|
||||
import sglang
|
||||
from sglang.srt import server_args as server_args_module
|
||||
from sglang.srt.arg_groups.overrides import resolution_result
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.test.ci.ci_register import register_cpu_ci
|
||||
@@ -147,7 +146,7 @@ def _late_resolvers():
|
||||
if isinstance(node.func, ast.Attribute)
|
||||
else getattr(node.func, "id", None)
|
||||
)
|
||||
if called in ("declare_late_resolution", "_late_resolution"):
|
||||
if called == "declare_late_resolution":
|
||||
return True
|
||||
if called and reaches(called, seen):
|
||||
return True
|
||||
@@ -887,7 +886,9 @@ class TestResolutionDeclarations(CustomTestCase):
|
||||
# The pipeline asks the platform other questions on the way through
|
||||
# (whether it is out of tree, whether it supports piecewise capture),
|
||||
# and which of those it reaches depends on the host.
|
||||
class _Plugin(type(server_args_module.current_platform)):
|
||||
from sglang.srt.platforms import current_platform
|
||||
|
||||
class _Plugin(type(current_platform)):
|
||||
device_name = "oot"
|
||||
|
||||
def apply_server_args_defaults(self, server_args):
|
||||
|
||||
@@ -36,7 +36,7 @@ import unittest.mock
|
||||
import torch
|
||||
|
||||
import sglang
|
||||
from sglang.srt.arg_groups.overrides import resolution_result
|
||||
from sglang.srt.arg_groups.overrides import model_config_of, resolution_result
|
||||
from sglang.srt.environ import EnvField, envs
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.srt.utils import is_cuda
|
||||
@@ -519,7 +519,7 @@ class TestProgramsResolveBeforeReadingResolution(CustomTestCase):
|
||||
from sglang.srt.server_args import ServerArgs as _ServerArgs
|
||||
|
||||
srt = pathlib.Path(next(iter(sglang.__path__))).resolve() / "srt"
|
||||
declarers = {"_declare", "declare_resolution", "declare_late_resolution"}
|
||||
declarers = {"declare_resolution", "declare_late_resolution"}
|
||||
fields = set()
|
||||
field_names = {field.name for field in _dataclasses.fields(_ServerArgs)}
|
||||
# The record plus every module under `arg_groups/`: a handler declares
|
||||
@@ -826,10 +826,10 @@ class TestACopyStaysResolved(_RestoresProcessState, CustomTestCase):
|
||||
def test_the_copy_carries_what_resolution_left_on_the_record(self):
|
||||
"""Not just the stash and the flag.
|
||||
|
||||
`get_model_config()` memoizes on the record, and that cache is filled
|
||||
`model_config_of()` memoizes on the record, and that cache is filled
|
||||
during resolution. A copy that is marked resolved but arrives without it
|
||||
cannot fill it -- the read-only guard refuses the cache write -- so the
|
||||
first `get_model_config()` raises. That is what killed the Ray
|
||||
first `model_config_of()` raises. That is what killed the Ray
|
||||
schedulers, and it is why the carry is enumerated from the instance
|
||||
rather than from a list of names.
|
||||
"""
|
||||
@@ -846,7 +846,7 @@ class TestACopyStaysResolved(_RestoresProcessState, CustomTestCase):
|
||||
[],
|
||||
f"the copy did not carry what resolution left on the record: {missing}",
|
||||
)
|
||||
self.assertIsNotNone(copy_.get_model_config())
|
||||
self.assertIsNotNone(model_config_of(copy_))
|
||||
# Containers are copied, so the copy's declaration stays with it.
|
||||
self.assertEqual(
|
||||
len(parent._resolved_overrides) + 1, len(copy_._resolved_overrides)
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
nothing. The fields keep what the caller passed, so a resolver that reads a
|
||||
field another resolver may have decided reads the raw input -- silently, and
|
||||
only on the configurations where that other resolver fires. The whole pipeline
|
||||
therefore reads through `resolving_view` (or `ServerArgs._resolved()`, which is
|
||||
the same view spelled as the record's own member), and this pins that there is
|
||||
therefore reads through `resolving_view` (or `resolved_view`, which is
|
||||
the same view after resolution has finished), and this pins that there is
|
||||
nothing left reading a field directly.
|
||||
|
||||
Subjects: every function in `arg_groups/` that takes a config, every
|
||||
@@ -83,7 +83,6 @@ def _field_reads(fn, holders):
|
||||
|
||||
_DECLARERS = frozenset(
|
||||
{
|
||||
"_declare",
|
||||
"declare_resolution",
|
||||
"declare_late_resolution",
|
||||
"declare_direct_writes",
|
||||
@@ -472,7 +471,7 @@ class TestResolutionReadsTheDeclarations(CustomTestCase):
|
||||
members = _record_members()
|
||||
# The floor is here to catch the scan collapsing, not to pin the
|
||||
# class's size.
|
||||
self.assertGreater(len(members), 40, f"only {len(members)} members were found")
|
||||
self.assertGreater(len(members), 25, f"only {len(members)} members were found")
|
||||
offenders = []
|
||||
for name, fn in sorted(members.items()):
|
||||
holders = _holders(fn) | {"self"}
|
||||
|
||||
@@ -37,7 +37,10 @@ from sglang.srt.arg_groups.moe_hook import (
|
||||
validate_deepep_v2_dispatch_token_budget,
|
||||
validate_deepep_v2_speculative_draft,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import resolution_result
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
cutedsl_moe_max_num_tokens,
|
||||
resolution_result,
|
||||
)
|
||||
from sglang.srt.arg_groups.parallel_hook import (
|
||||
handle_context_parallelism,
|
||||
handle_data_parallelism,
|
||||
@@ -825,9 +828,7 @@ class TestHiSparseDsaBackendPolicy(unittest.TestCase):
|
||||
)
|
||||
defaults.update(kw)
|
||||
view = ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
with (
|
||||
patch("sglang.srt.configs.model_config.is_deepseek_dsa", return_value=True),
|
||||
@@ -845,21 +846,21 @@ class TestHiSparseDsaBackendPolicy(unittest.TestCase):
|
||||
),
|
||||
}
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=False)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=False)
|
||||
def test_hisparse_defaults_to_flashmla_sparse_on_cuda_bfloat16(self, _mock_is_hip):
|
||||
resolved = self._resolve("bfloat16")
|
||||
|
||||
self.assertEqual(resolved["dsa_prefill_backend"], "flashmla_sparse")
|
||||
self.assertEqual(resolved["dsa_decode_backend"], "flashmla_sparse")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=False)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=False)
|
||||
def test_hisparse_defaults_to_flashmla_kv_on_cuda_fp8(self, _mock_is_hip):
|
||||
resolved = self._resolve("fp8_e4m3")
|
||||
|
||||
self.assertEqual(resolved["dsa_prefill_backend"], "flashmla_kv")
|
||||
self.assertEqual(resolved["dsa_decode_backend"], "flashmla_kv")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=False)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=False)
|
||||
def test_hisparse_accepts_flashinfer_sparse_mla_on_cuda_fp8(self, _mock_is_hip):
|
||||
"""SM120 GLM DSA resolves both DSA backends to flashinfer_sparse_mla, so
|
||||
the fp8 hisparse allow-set must admit it or --enable-hisparse cannot
|
||||
@@ -876,14 +877,14 @@ class TestHiSparseDsaBackendPolicy(unittest.TestCase):
|
||||
validate_hisparse_dsa_backend(server_args, "dsa_prefill_backend", "prefill")
|
||||
validate_hisparse_dsa_backend(server_args, "dsa_decode_backend", "decode")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=True)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=True)
|
||||
def test_hisparse_defaults_to_tilelang_on_rocm(self, _mock_is_hip):
|
||||
resolved = self._resolve("bfloat16")
|
||||
|
||||
self.assertEqual(resolved["dsa_prefill_backend"], "tilelang")
|
||||
self.assertEqual(resolved["dsa_decode_backend"], "tilelang")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=True)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=True)
|
||||
def test_hisparse_preserves_rocm_user_backend_and_defaults_missing_side(
|
||||
self, _mock_is_hip
|
||||
):
|
||||
@@ -892,7 +893,7 @@ class TestHiSparseDsaBackendPolicy(unittest.TestCase):
|
||||
self.assertEqual(resolved["dsa_prefill_backend"], "tilelang")
|
||||
self.assertEqual(resolved["dsa_decode_backend"], "tilelang")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=True)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=True)
|
||||
def test_hisparse_accepts_aiter_backend_on_rocm(self, _mock_is_hip):
|
||||
server_args = ServerArgs(
|
||||
model_path="dummy",
|
||||
@@ -905,7 +906,7 @@ class TestHiSparseDsaBackendPolicy(unittest.TestCase):
|
||||
validate_hisparse_dsa_backend(server_args, "dsa_prefill_backend", "prefill")
|
||||
validate_hisparse_dsa_backend(server_args, "dsa_decode_backend", "decode")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=True)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=True)
|
||||
def test_hisparse_rejects_cuda_backend_on_rocm(self, _mock_is_hip):
|
||||
server_args = ServerArgs(
|
||||
model_path="dummy",
|
||||
@@ -917,7 +918,7 @@ class TestHiSparseDsaBackendPolicy(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "tilelang"):
|
||||
validate_hisparse_dsa_backend(server_args, "dsa_prefill_backend", "prefill")
|
||||
|
||||
@patch("sglang.srt.server_args.is_hip", return_value=False)
|
||||
@patch("sglang.srt.arg_groups.hisparse_hook._is_hip", return_value=False)
|
||||
def test_hisparse_rejects_rocm_backend_on_cuda(self, _mock_is_hip):
|
||||
server_args = ServerArgs(
|
||||
model_path="dummy",
|
||||
@@ -969,7 +970,7 @@ class TestFa4PageSizeAutoForce(CustomTestCase):
|
||||
args.prefill_attention_backend = prefill
|
||||
args.decode_attention_backend = decode
|
||||
args.page_size = page_size
|
||||
# Short-circuit get_model_config(): the fa4 page_size branch only needs
|
||||
# Short-circuit model_config_of(): the fa4 page_size branch only needs
|
||||
# use_mla_backend() (mocked) and is_sm100_supported() (mocked), not a
|
||||
# real model_config. Pre-set the attribute so get_model_config returns
|
||||
# early without touching ModelConfig.from_server_args.
|
||||
@@ -978,7 +979,7 @@ class TestFa4PageSizeAutoForce(CustomTestCase):
|
||||
return args
|
||||
|
||||
@patch("sglang.srt.arg_groups.overrides.is_sm100_supported", return_value=True)
|
||||
@patch("sglang.srt.server_args.ServerArgs.use_mla_backend", return_value=False)
|
||||
@patch("sglang.srt.arg_groups.overrides.use_mla_backend", return_value=False)
|
||||
def test_combined_attention_backend_fa4_forces_page_size_128(
|
||||
self, _mock_mla, _mock_sm100
|
||||
):
|
||||
@@ -993,7 +994,7 @@ class TestFa4PageSizeAutoForce(CustomTestCase):
|
||||
self.assertEqual(resolved_view(args).page_size, 128)
|
||||
|
||||
@patch("sglang.srt.arg_groups.overrides.is_sm100_supported", return_value=True)
|
||||
@patch("sglang.srt.server_args.ServerArgs.use_mla_backend", return_value=False)
|
||||
@patch("sglang.srt.arg_groups.overrides.use_mla_backend", return_value=False)
|
||||
def test_explicit_prefill_fa4_forces_page_size_128(self, _mock_mla, _mock_sm100):
|
||||
# `--prefill-attention-backend fa4`: the previously-covered path.
|
||||
args = self._make_args(attention_backend=None, prefill="fa4", page_size=1)
|
||||
@@ -1678,7 +1679,7 @@ class TestAdaptiveSpecArgs(CustomTestCase):
|
||||
args.speculative_adaptive = True
|
||||
args.speculative_adaptive_config = f.name
|
||||
args.device = "cuda"
|
||||
args.get_model_config = lambda: SimpleNamespace(
|
||||
args._model_config = SimpleNamespace(
|
||||
hf_config=SimpleNamespace(
|
||||
architectures=["LlamaForCausalLM"],
|
||||
get_text_config=lambda: SimpleNamespace(),
|
||||
@@ -1870,7 +1871,9 @@ class TestCudaGraphDisaggregationRoles(CustomTestCase):
|
||||
)
|
||||
with (
|
||||
patch("sglang.srt.utils.is_cuda", return_value=True),
|
||||
patch.object(ServerArgs, "use_mla_backend", return_value=False),
|
||||
patch(
|
||||
"sglang.srt.arg_groups.overrides.use_mla_backend", return_value=False
|
||||
),
|
||||
):
|
||||
handle_cuda_graph_config(args)
|
||||
return args
|
||||
@@ -1943,7 +1946,9 @@ class TestPrefillCudaGraphLoRACompatibility(CustomTestCase):
|
||||
)
|
||||
with (
|
||||
patch("sglang.srt.utils.is_cuda", return_value=True),
|
||||
patch.object(ServerArgs, "use_mla_backend", return_value=False),
|
||||
patch(
|
||||
"sglang.srt.arg_groups.overrides.use_mla_backend", return_value=False
|
||||
),
|
||||
):
|
||||
handle_cuda_graph_config(args)
|
||||
return args
|
||||
@@ -2007,7 +2012,9 @@ class TestBreakableCudaGraphMultimodalAllowlist(CustomTestCase):
|
||||
)
|
||||
with (
|
||||
patch("sglang.srt.utils.is_cuda", return_value=True),
|
||||
patch.object(ServerArgs, "use_mla_backend", return_value=False),
|
||||
patch(
|
||||
"sglang.srt.arg_groups.overrides.use_mla_backend", return_value=False
|
||||
),
|
||||
):
|
||||
handle_cuda_graph_config(args)
|
||||
return args
|
||||
@@ -2096,7 +2103,7 @@ class TestCutedslMoeMaxNumTokens(CustomTestCase):
|
||||
return server_args
|
||||
|
||||
def test_prefill_dominates_in_default_config(self):
|
||||
self.assertEqual(self._args().cutedsl_moe_max_num_tokens(), 16384)
|
||||
self.assertEqual(cutedsl_moe_max_num_tokens(self._args()), 16384)
|
||||
|
||||
def test_speculative_decoding_scales_decode_bound(self):
|
||||
# decode bound 512 * 8 dominates the small prefill/piecewise bounds
|
||||
@@ -2106,7 +2113,7 @@ class TestCutedslMoeMaxNumTokens(CustomTestCase):
|
||||
speculative_algorithm="EAGLE",
|
||||
speculative_num_draft_tokens=8,
|
||||
)
|
||||
self.assertEqual(args.cutedsl_moe_max_num_tokens(), 4096)
|
||||
self.assertEqual(cutedsl_moe_max_num_tokens(args), 4096)
|
||||
|
||||
def test_piecewise_bound_excluded_when_disabled(self):
|
||||
args = self._args(
|
||||
@@ -2114,7 +2121,7 @@ class TestCutedslMoeMaxNumTokens(CustomTestCase):
|
||||
disable_piecewise_cuda_graph=True,
|
||||
cuda_graph_max_bs=64,
|
||||
)
|
||||
self.assertEqual(args.cutedsl_moe_max_num_tokens(), 512)
|
||||
self.assertEqual(cutedsl_moe_max_num_tokens(args), 512)
|
||||
|
||||
|
||||
class TestSamplingBackendTokenOracleEnvGate(CustomTestCase):
|
||||
@@ -2466,10 +2473,9 @@ class TestDeepEPv2Args(CustomTestCase):
|
||||
dp_size=8,
|
||||
enable_dp_attention=True,
|
||||
)
|
||||
with patch.object(
|
||||
ServerArgs,
|
||||
"max_speculative_num_draft_tokens",
|
||||
new=property(lambda _self: 16),
|
||||
with patch(
|
||||
"sglang.srt.arg_groups.moe_hook.max_speculative_num_draft_tokens",
|
||||
return_value=16,
|
||||
):
|
||||
with envs.SGLANG_DEEPEP_V2_NUM_MAX_DISPATCH_TOKENS_PER_RANK.override(128):
|
||||
with self.assertRaisesRegex(ValueError, "tokens/request=16"):
|
||||
|
||||
@@ -20,7 +20,7 @@ def _make_spec_args(device: str, algorithm: str = "EAGLE", **overrides) -> Serve
|
||||
args.speculative_num_steps = 3
|
||||
args.speculative_eagle_topk = 1
|
||||
args.speculative_num_draft_tokens = 4
|
||||
args.get_model_config = lambda: SimpleNamespace(
|
||||
args._model_config = SimpleNamespace(
|
||||
hf_config=SimpleNamespace(
|
||||
architectures=["LlamaForCausalLM"],
|
||||
get_text_config=lambda: SimpleNamespace(),
|
||||
|
||||
@@ -40,7 +40,7 @@ _OWNERS = ("server_args.py", "runtime_context.py", "arg_groups/")
|
||||
# startup default wherever it is written, and `benchmark/` ships too.
|
||||
_READS_SCANNED = _PACKAGE
|
||||
|
||||
_DECLARERS = ("_declare", "declare_resolution", "declare_late_resolution")
|
||||
_DECLARERS = ("declare_resolution", "declare_late_resolution")
|
||||
|
||||
|
||||
def _declared_by_keyword():
|
||||
@@ -190,10 +190,10 @@ def _declared_by_registry_and_passes():
|
||||
|
||||
|
||||
def _declared_by_late_resolution():
|
||||
"""Keywords of `self._late_resolution(...)`, the fourth declarer spelling.
|
||||
"""Keywords of `declare_late_resolution(record, ...)`, the late spelling.
|
||||
|
||||
It forwards `**fields` to `declare_late_resolution`, so the keywords sit at
|
||||
its call sites and a scan for the declarer's own name finds none of them.
|
||||
The fields sit at the call sites rather than in the declarer, so a scan
|
||||
that only knew the declarer's own definition would find none of them.
|
||||
"""
|
||||
# The record plus `arg_groups/`: a hook calls it on the record it was
|
||||
# handed, so scanning the record's file alone finds nothing.
|
||||
@@ -203,8 +203,8 @@ def _declared_by_late_resolution():
|
||||
for node in ast.walk(ast.parse(source.read_text(encoding="utf-8-sig"))):
|
||||
if (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "_late_resolution"
|
||||
and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "declare_late_resolution"
|
||||
):
|
||||
fields |= {keyword.arg for keyword in node.keywords if keyword.arg}
|
||||
return fields
|
||||
@@ -540,13 +540,20 @@ class TestNoChainReadsOfResolvedConfig(CustomTestCase):
|
||||
len(by_late),
|
||||
3,
|
||||
f"only {len(by_late)} fields are declared late; the "
|
||||
"`_late_resolution` keyword scan broke",
|
||||
"`declare_late_resolution` keyword scan broke",
|
||||
)
|
||||
# The three mechanisms are not the same set: if any became a subset of
|
||||
# the keyword scan, that scan would be doing all the work and a
|
||||
# regression in the others would be invisible.
|
||||
# The data channel is not the keyword scan's subset: if it became one,
|
||||
# that scan would be doing all the work and a regression here would be
|
||||
# invisible. The late channel *is* a subset, and deliberately so --
|
||||
# `declare_late_resolution` is a keyword declarer like the others now
|
||||
# that the record hosts no forwarding member, so its own floor above is
|
||||
# what pins it.
|
||||
self.assertTrue(by_data - by_keyword, "the data channel adds nothing")
|
||||
self.assertTrue(by_late - by_keyword, "late resolution adds nothing")
|
||||
self.assertTrue(
|
||||
by_late <= by_keyword,
|
||||
"late resolution declares outside the keyword channel; it is the "
|
||||
"same spelling, so the two cannot disagree",
|
||||
)
|
||||
|
||||
def test_nothing_reads_a_resolved_field_off_a_borrowed_record(self):
|
||||
found = _chain_reads(_resolution_written())
|
||||
|
||||
@@ -15,6 +15,7 @@ from types import SimpleNamespace
|
||||
from typing import Optional
|
||||
from unittest.mock import patch
|
||||
|
||||
from sglang.srt.arg_groups import attention_hook
|
||||
from sglang.srt.arg_groups import overrides as overrides_module
|
||||
from sglang.srt.arg_groups.arg_utils import A, Arg, resolvable_fields
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
@@ -24,6 +25,7 @@ from sglang.srt.arg_groups.overrides import (
|
||||
validate_declarations,
|
||||
)
|
||||
from sglang.srt.configs.minicpm import MiniCPMHybridConfig
|
||||
from sglang.srt.configs.model_config import AttentionArch
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.runtime_context import (
|
||||
get_context,
|
||||
@@ -357,14 +359,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
enable_dp_attention=enable_dp_attention,
|
||||
enable_hierarchical_cache=enable_hierarchical_cache,
|
||||
)
|
||||
args.is_attention_backend_not_set = lambda: all(
|
||||
backend is None
|
||||
for backend in (
|
||||
args.attention_backend,
|
||||
args.prefill_attention_backend,
|
||||
args.decode_attention_backend,
|
||||
)
|
||||
)
|
||||
mixer_types = []
|
||||
if sparse_attention:
|
||||
mixer_types.append("minicpm4")
|
||||
@@ -456,7 +450,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
disaggregation_mode="null",
|
||||
enable_dp_attention=False,
|
||||
enable_hierarchical_cache=False,
|
||||
is_attention_backend_not_set=lambda: True,
|
||||
)
|
||||
config = SimpleNamespace(
|
||||
has_minicpm_sparse_attention=True,
|
||||
@@ -630,7 +623,11 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
|
||||
def test_minimax_m2_sm10x_nvfp4_uses_routed_trtllm(self):
|
||||
"""MiniMax-M2 NVFP4 auto must avoid the unsupported plain TRT-LLM path."""
|
||||
with patch.object(overrides_module, "is_sm100_supported", return_value=True):
|
||||
# Every module that asks: the attention handler validates what the
|
||||
# override family picks, and each holds its own import.
|
||||
with patch.object(
|
||||
overrides_module, "is_sm100_supported", return_value=True
|
||||
), patch.object(attention_hook, "is_sm100_supported", return_value=True):
|
||||
explicit = self._construct(
|
||||
"MiniMaxM2ForCausalLM",
|
||||
"llama",
|
||||
@@ -765,8 +762,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
speculative_draft_attention_backend=None,
|
||||
page_size=None,
|
||||
mamba_radix_cache_strategy="auto",
|
||||
is_attention_backend_not_set=lambda: True,
|
||||
get_model_config=lambda: model_config,
|
||||
_model_config=model_config,
|
||||
),
|
||||
hf_config,
|
||||
)
|
||||
@@ -971,7 +967,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
server_args.speculative_algorithm = "DFLASH"
|
||||
server_args.prefill_attention_backend = "triton"
|
||||
server_args.speculative_draft_attention_backend = "fa3"
|
||||
server_args.is_attention_backend_not_set = lambda: False
|
||||
|
||||
with (
|
||||
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
|
||||
@@ -1093,7 +1088,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
_gpt_oss_overrides(
|
||||
SimpleNamespace(
|
||||
dtype="float16",
|
||||
is_attention_backend_not_set=lambda: False,
|
||||
attention_backend="triton",
|
||||
prefill_attention_backend=None,
|
||||
decode_attention_backend=None,
|
||||
),
|
||||
SimpleNamespace(architectures=["GptOssForCausalLM"]),
|
||||
)
|
||||
@@ -1313,7 +1310,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
# dual-chunk config: mismatched explicit backend raises verbatim
|
||||
def _mc(dual):
|
||||
return SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(
|
||||
_model_config=SimpleNamespace(
|
||||
hf_config=SimpleNamespace(dual_chunk_attention_config=dual)
|
||||
),
|
||||
attention_backend="fa3",
|
||||
@@ -1473,9 +1470,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
swa_full_tokens_ratio=ServerArgs.swa_full_tokens_ratio,
|
||||
moe_a2a_backend="none",
|
||||
moe_runner_backend="auto",
|
||||
get_model_config=lambda: SimpleNamespace(
|
||||
is_fp4_experts=True, nvfp4_moe_meta=None
|
||||
),
|
||||
_model_config=SimpleNamespace(is_fp4_experts=True, nvfp4_moe_meta=None),
|
||||
)
|
||||
defaults.update(kw)
|
||||
return SimpleNamespace(**defaults)
|
||||
@@ -1527,12 +1522,10 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
# FP8 checkpoints and non-CUDA platforms keep their platform-specific
|
||||
# auto-resolution paths.
|
||||
fp8_model_config = lambda: SimpleNamespace(
|
||||
is_fp4_experts=False, nvfp4_moe_meta=None
|
||||
)
|
||||
fp8_model_config = SimpleNamespace(is_fp4_experts=False, nvfp4_moe_meta=None)
|
||||
self.assertNotIn(
|
||||
"moe_runner_backend",
|
||||
_deepseek_v4_overrides(_args(get_model_config=fp8_model_config), hf),
|
||||
_deepseek_v4_overrides(_args(_model_config=fp8_model_config), hf),
|
||||
)
|
||||
self.assertNotIn(
|
||||
"moe_runner_backend",
|
||||
@@ -1569,7 +1562,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
self.assertEqual(
|
||||
_deepseek_v4_overrides(
|
||||
_args(
|
||||
get_model_config=lambda: SimpleNamespace(
|
||||
_model_config=SimpleNamespace(
|
||||
is_fp4_experts=False, nvfp4_moe_meta=object()
|
||||
)
|
||||
),
|
||||
@@ -1604,15 +1597,10 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
speculative_draft_attention_backend=None,
|
||||
page_size=None,
|
||||
mamba_radix_cache_strategy="auto",
|
||||
get_model_config=lambda: mc,
|
||||
_model_config=mc,
|
||||
)
|
||||
defaults.update(kw)
|
||||
args = SimpleNamespace(**defaults)
|
||||
args.is_attention_backend_not_set = lambda: (
|
||||
args.attention_backend is None
|
||||
and args.prefill_attention_backend is None
|
||||
and args.decode_attention_backend is None
|
||||
)
|
||||
return args
|
||||
|
||||
hf = _hf()
|
||||
@@ -1725,9 +1713,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
return ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -1821,9 +1807,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
return ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -1952,15 +1936,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
ns = SimpleNamespace(**defaults)
|
||||
ns.is_attention_backend_not_set = lambda: (
|
||||
ns.attention_backend is None
|
||||
and ns.prefill_attention_backend is None
|
||||
and ns.decode_attention_backend is None
|
||||
)
|
||||
ns.get_attention_backends = lambda: (
|
||||
ns.prefill_attention_backend or ns.attention_backend,
|
||||
ns.decode_attention_backend or ns.attention_backend,
|
||||
)
|
||||
return ns
|
||||
|
||||
# nothing set: prefill defaults to flashinfer
|
||||
@@ -1991,9 +1966,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
return ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -2037,9 +2010,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
defaults = dict(kv_cache_dtype="auto", device="cuda")
|
||||
defaults.update(kw)
|
||||
return ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -2078,9 +2049,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
return ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
|
||||
with patch.object(overrides_module, "is_hip", return_value=True):
|
||||
@@ -2148,9 +2117,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
return ResolvedView(
|
||||
SimpleNamespace(
|
||||
get_model_config=lambda: SimpleNamespace(hf_config=hf), **defaults
|
||||
)
|
||||
SimpleNamespace(_model_config=SimpleNamespace(hf_config=hf), **defaults)
|
||||
)
|
||||
|
||||
# arch guard: non-mamba arch declares nothing
|
||||
@@ -2255,17 +2222,28 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
def _args(default_backend, **kw):
|
||||
defaults = dict(
|
||||
attention_backend=None,
|
||||
_get_default_attn_backend=lambda **_: default_backend,
|
||||
use_mla_backend=lambda: False,
|
||||
get_model_config=lambda: None,
|
||||
prefill_attention_backend=None,
|
||||
decode_attention_backend=None,
|
||||
mamba_radix_cache_strategy="auto",
|
||||
disable_radix_cache=False,
|
||||
speculative_algorithm=None,
|
||||
)
|
||||
defaults.update(kw)
|
||||
return SimpleNamespace(**defaults)
|
||||
args = SimpleNamespace(**defaults)
|
||||
args.default_backend_for_test = default_backend
|
||||
return args
|
||||
|
||||
with patch.object(overrides_module, "is_sm100_supported", return_value=True):
|
||||
with patch.object(
|
||||
overrides_module, "is_sm100_supported", return_value=True
|
||||
), patch.object(
|
||||
overrides_module,
|
||||
"get_default_attn_backend",
|
||||
lambda server_args, **_: server_args.default_backend_for_test,
|
||||
), patch.object(
|
||||
overrides_module, "use_mla_backend", return_value=False
|
||||
), patch.object(
|
||||
overrides_module, "model_config_of", return_value=None
|
||||
):
|
||||
# radix on + no extra buffer + no spec -> page_size=1 path
|
||||
self.assertEqual(
|
||||
_qwen3_5_hybrid_overrides(_args("trtllm_mha"), None),
|
||||
@@ -2422,11 +2400,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
)
|
||||
defaults.update(kw)
|
||||
ns = SimpleNamespace(**defaults)
|
||||
ns.is_attention_backend_not_set = lambda: (
|
||||
ns.attention_backend is None
|
||||
and ns.prefill_attention_backend is None
|
||||
and ns.decode_attention_backend is None
|
||||
)
|
||||
return ns
|
||||
|
||||
hf = SimpleNamespace()
|
||||
@@ -2475,6 +2448,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
prefill_attention_backend=None,
|
||||
speculative_draft_attention_backend=None,
|
||||
page_size=1,
|
||||
# `use_mla_backend` reads the model configuration; a non-MLA
|
||||
# one keeps these assertions about the page constraints.
|
||||
_model_config=SimpleNamespace(attention_arch=None),
|
||||
)
|
||||
defaults.update(kw)
|
||||
return ResolvedView(SimpleNamespace(**defaults))
|
||||
@@ -2533,7 +2509,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
_fa4_page_constraint(
|
||||
_view(
|
||||
attention_backend="fa4",
|
||||
use_mla_backend=lambda: False,
|
||||
speculative_eagle_topk=None,
|
||||
)
|
||||
),
|
||||
@@ -2543,7 +2518,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
_fa4_page_constraint(
|
||||
_view(
|
||||
attention_backend="fa4",
|
||||
use_mla_backend=lambda: False,
|
||||
speculative_eagle_topk=2, # EAGLE topk>1 keeps default
|
||||
)
|
||||
),
|
||||
@@ -2554,7 +2528,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
_intel_xpu_page_constraint(
|
||||
_view(
|
||||
decode_attention_backend="intel_xpu",
|
||||
use_mla_backend=lambda: False,
|
||||
)
|
||||
),
|
||||
{"page_size": 128},
|
||||
@@ -2563,7 +2536,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
_intel_xpu_page_constraint(
|
||||
_view(
|
||||
decode_attention_backend="intel_xpu",
|
||||
use_mla_backend=lambda: True,
|
||||
_model_config=SimpleNamespace(attention_arch=AttentionArch.MLA),
|
||||
page_size=16, # MLA decode accepts 16
|
||||
)
|
||||
),
|
||||
@@ -2585,7 +2558,8 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
defaults = dict(
|
||||
device="cuda",
|
||||
attention_backend=None,
|
||||
is_attention_backend_not_set=lambda: True,
|
||||
prefill_attention_backend=None,
|
||||
decode_attention_backend=None,
|
||||
# keep the (now-absorbed) quant/moe blocks inert so these
|
||||
# assertions stay attention-only
|
||||
moe_runner_backend="triton",
|
||||
@@ -2674,7 +2648,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
_quantization_explicitly_unset=False,
|
||||
moe_a2a_backend="none",
|
||||
moe_runner_backend="auto",
|
||||
get_model_config=lambda: SimpleNamespace(
|
||||
_model_config=SimpleNamespace(
|
||||
hf_config=SimpleNamespace(
|
||||
architectures=[arch], quantization_config=quant_cfg
|
||||
)
|
||||
@@ -2742,7 +2716,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
|
||||
def _args(**kw):
|
||||
defaults = dict(
|
||||
is_attention_backend_not_set=lambda: True,
|
||||
attention_backend=None,
|
||||
prefill_attention_backend=None,
|
||||
decode_attention_backend=None,
|
||||
@@ -2869,7 +2842,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
|
||||
defaults = dict(
|
||||
speculative_algorithm=None,
|
||||
enable_hierarchical_cache=False,
|
||||
is_attention_backend_not_set=lambda: False,
|
||||
attention_backend="triton",
|
||||
prefill_attention_backend=None,
|
||||
decode_attention_backend=None,
|
||||
)
|
||||
defaults.update(kw)
|
||||
return SimpleNamespace(**defaults)
|
||||
|
||||
@@ -16,6 +16,18 @@ from unittest.mock import patch
|
||||
import sglang as _sglang
|
||||
import sglang.srt.server_args as server_args_module
|
||||
from sglang.srt.arg_groups.arg_utils import NS, A, Arg
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
mamba_cache_chunk_size as mamba_cache_chunk_size_of,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
max_prefill_buffer_tokens as max_prefill_buffer_tokens_of,
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
resolved_view,
|
||||
)
|
||||
from sglang.srt.runtime_context import (
|
||||
Flags,
|
||||
ParallelContext,
|
||||
@@ -404,7 +416,7 @@ class TestServerArgsScopedOverride(_IsolatedServerArgs):
|
||||
published = (
|
||||
get_context().override_server_args(_mamba_cache_chunk_size=64).install()
|
||||
)
|
||||
self.assertEqual(published.mamba_cache_chunk_size, 64)
|
||||
self.assertEqual(mamba_cache_chunk_size_of(published), 64)
|
||||
|
||||
def test_installed_config_arms_the_strict_guard(self):
|
||||
# The published dummy must behave like a resolved config: bare writes
|
||||
@@ -1156,7 +1168,7 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
|
||||
)
|
||||
get_context().set_server_args(args)
|
||||
self.assertEqual(
|
||||
ServerArgs.max_prefill_buffer_tokens(args),
|
||||
max_prefill_buffer_tokens_of(args),
|
||||
max_prefill_buffer_tokens(),
|
||||
)
|
||||
|
||||
@@ -1243,7 +1255,7 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
|
||||
)
|
||||
get_context().set_server_args(args)
|
||||
self.assertEqual(
|
||||
ServerArgs.get_attention_backends(args),
|
||||
attention_backends_of(resolved_view(args)),
|
||||
attention_backends(),
|
||||
)
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from sglang.test.test_utils import CustomTestCase
|
||||
register_cpu_ci(est_time=2, suite="base-a-test-cpu")
|
||||
|
||||
import sglang
|
||||
from sglang.srt.arg_groups.overrides import attention_backends_of, resolved_view
|
||||
|
||||
_PACKAGE_ROOT = Path(next(iter(sglang.__path__))) / "srt"
|
||||
|
||||
@@ -181,7 +182,7 @@ class TestSplitBackendsReachTheDecisions(CustomTestCase):
|
||||
("decode_attention_backend", "flashinfer"),
|
||||
):
|
||||
object.__setattr__(args, name, value)
|
||||
self.assertIn("flashinfer", args.get_attention_backends())
|
||||
self.assertIn("flashinfer", attention_backends_of(resolved_view(args)))
|
||||
|
||||
def test_support_triton_is_the_regression_being_guarded(self):
|
||||
from sglang.srt.utils.common import support_triton
|
||||
|
||||
@@ -483,8 +483,8 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
||||
tgts = [node.target]
|
||||
elif (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "_declare"
|
||||
and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "declare_resolution"
|
||||
):
|
||||
targets |= {
|
||||
kw.arg
|
||||
@@ -622,7 +622,7 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
||||
for path in sorted(root.rglob("*.py")):
|
||||
rel = path.relative_to(root).as_posix()
|
||||
source = path.read_text(encoding="utf-8-sig")
|
||||
if "_late_resolution" not in source:
|
||||
if "declare_late_resolution" not in source:
|
||||
continue
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
|
||||
Reference in New Issue
Block a user