[Config] Round 5.2: the per-model declarations get their own modules (#37087)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Cheng Wan
2026-08-30 02:24:35 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 7e751153eb
commit e51a3ae65e
44 changed files with 2898 additions and 1730 deletions
@@ -100,17 +100,22 @@ class TestBaseProcessorConfigExtraction(CustomTestCase):
override.install()
self.addCleanup(override.restore)
server_args = MagicMock()
server_args.mm_processor_worker_num = mm_processor_worker_num
server_args.mm_io_worker_num = mm_io_worker_num
server_args.mm_preprocess_cache_size_mb = None
server_args.tokenizer_worker_num = 1
server_args.trust_mm_content_hashes = False
server_args.media_url_max_file_size_mb = 64
# A bare MagicMock makes every attribute truthy, which silently sends
# the worker-count decision down the CPU branch. Pin what it reads.
server_args.disable_fast_image_processor = False
server_args.rl_on_policy_target = None
# A real record: a bare MagicMock makes every attribute truthy, which
# sends the worker-count decision down the wrong branch.
from sglang.srt.server_args import ServerArgs
server_args = ServerArgs(
model_path="dummy",
mm_process_config=mm_process_config,
allowed_media_domains=[],
mm_processor_worker_num=mm_processor_worker_num,
mm_io_worker_num=mm_io_worker_num,
mm_preprocess_cache_size_mb=None,
tokenizer_worker_num=1,
trust_mm_content_hashes=False,
media_url_max_file_size_mb=64,
disable_fast_image_processor=False,
)
hf_config = MagicMock()
mock_hf_processor = MagicMock()
@@ -0,0 +1,326 @@
"""The model-source axis, on PR CI.
Four ways a model path can name something that is not a local directory, and
until now only one of them was checked before merge:
- an object-store URI (``s3://`` / ``gs://`` / ``az://``), covered by
``test_model_config_cache.py`` and, end to end, by a ``nightly`` test;
- a Hub reference to a ``.gguf`` file;
- a ModelScope repo id;
- a remote-connector URL, which is any other ``scheme://`` and is reached by a
different arm of ``ModelConfig`` than the object-store one.
The last three had no registered test at all. That is how a change to
``get_model_config()``'s cache semantics went green through PR CI and broke two
days later in the nightly: the axis it broke was not being looked at.
None of this needs a network. The GGUF arm asks one resolver for a local path,
the ModelScope arm returns any path that already exists on disk untouched and
otherwise goes through two imports that can be stood in for, and the
remote-connector arm goes through one factory. Each case stubs exactly that
seam and checks what the handler declares -- and, where the path moves, that
the model-configuration cache notices.
"""
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=10, suite="base-a-test-cpu")
import json
import os
import shutil
import sys
import tempfile
import types
import unittest
from unittest import mock
import sglang.srt.connector as connector_module
from sglang.srt.arg_groups.model_path_hook import (
handle_modelscope_paths,
resolve_hf_gguf_model_path,
)
from sglang.srt.arg_groups.overrides import model_config_of, resolving_view
from sglang.srt.configs.model_config import ModelConfig
from sglang.srt.server_args import ServerArgs
from sglang.test.test_utils import CustomTestCase
_MINI_CONFIG = {
"architectures": ["LlamaForCausalLM"],
"model_type": "llama",
"hidden_size": 16,
"intermediate_size": 32,
"num_attention_heads": 2,
"num_key_value_heads": 2,
"num_hidden_layers": 2,
"vocab_size": 128,
"max_position_embeddings": 2048,
}
_GGUF_REFERENCE = "owner/repo"
_MODELSCOPE_REPO = "org/model"
_REMOTE_URL = "redis://host:6379/mini-llama"
class _ModelSourceCase(CustomTestCase):
def _directory(self) -> str:
directory = tempfile.mkdtemp(prefix="model_source_")
self.addCleanup(shutil.rmtree, directory, ignore_errors=True)
return directory
def _checkpoint(self) -> str:
directory = self._directory()
with open(os.path.join(directory, "config.json"), "w") as handle:
json.dump(_MINI_CONFIG, handle)
return directory
def _gguf_file(self) -> str:
path = os.path.join(self._directory(), "model.gguf")
open(path, "w").close()
return path
class TestTheGgufArm(_ModelSourceCase):
"""`resolve_hf_gguf_model_path` turns a Hub reference into a local file."""
def _resolving_to(self, resolved):
"""Stand in for the one Hub call, keyed on what it is asked about."""
table = resolved if isinstance(resolved, dict) else None
def _resolve(model, revision=None):
if table is not None:
return table.get(model)
return resolved
return mock.patch(
"sglang.srt.utils.hf_transformers_utils.resolve_hf_gguf_reference",
side_effect=_resolve,
)
def test_a_hub_reference_declares_the_local_path(self):
local = self._gguf_file()
server_args = ServerArgs(model_path=_GGUF_REFERENCE, device="cuda")
with self._resolving_to(local):
resolve_hf_gguf_model_path(server_args)
self.assertEqual(resolving_view(server_args).model_path, local)
# The record itself still carries what the operator typed.
self.assertEqual(server_args.model_path, _GGUF_REFERENCE)
def test_the_tokenizer_follows_only_when_it_was_the_same_reference(self):
local = self._gguf_file()
together = ServerArgs(
model_path=_GGUF_REFERENCE, tokenizer_path=_GGUF_REFERENCE, device="cuda"
)
with self._resolving_to(local):
resolve_hf_gguf_model_path(together)
self.assertEqual(resolving_view(together).tokenizer_path, local)
apart = ServerArgs(
model_path=_GGUF_REFERENCE, tokenizer_path="somewhere/else", device="cuda"
)
with self._resolving_to({_GGUF_REFERENCE: local}):
resolve_hf_gguf_model_path(apart)
self.assertEqual(resolving_view(apart).tokenizer_path, "somewhere/else")
def test_a_draft_gguf_is_resolved_on_its_own(self):
target, draft = self._gguf_file(), self._gguf_file()
server_args = ServerArgs(
model_path=_GGUF_REFERENCE,
speculative_draft_model_path="owner/draft",
device="cuda",
)
with self._resolving_to({_GGUF_REFERENCE: target, "owner/draft": draft}):
resolve_hf_gguf_model_path(server_args)
view = resolving_view(server_args)
self.assertEqual(view.model_path, target)
self.assertEqual(view.speculative_draft_model_path, draft)
def test_a_reference_that_is_not_a_gguf_declares_nothing(self):
server_args = ServerArgs(model_path=_GGUF_REFERENCE, device="cuda")
with self._resolving_to(None):
resolve_hf_gguf_model_path(server_args)
self.assertEqual(resolving_view(server_args).model_path, _GGUF_REFERENCE)
def test_the_declared_path_invalidates_the_model_configuration(self):
"""The point of pinning the declaration: a configuration built before
it describes the Hub reference, not the file that was downloaded."""
first, second = self._checkpoint(), self._checkpoint()
server_args = ServerArgs(model_path=first, device="cuda")
before = model_config_of(server_args)
self.assertEqual(before.model_path, first)
with self._resolving_to(second):
resolve_hf_gguf_model_path(server_args)
after = model_config_of(server_args)
self.assertIsNot(after, before)
self.assertEqual(after.model_path, second)
class TestTheModelScopeArm(_ModelSourceCase):
"""`handle_modelscope_paths` resolves repo ids against the local cache."""
def _modelscope(self, cache_root: str, downloads: dict):
"""Stand in for the two modules the handler imports on a cache miss."""
calls = []
def _snapshot_download(path, cache_dir=None, revision=None, **kwargs):
calls.append((path, cache_dir, revision, kwargs.get("ignore_patterns")))
return downloads[path]
hub = types.ModuleType("modelscope.hub.snapshot_download")
hub.snapshot_download = _snapshot_download
file_utils = types.ModuleType("modelscope.utils.file_utils")
file_utils.get_model_cache_root = lambda: cache_root
modules = {
"modelscope": types.ModuleType("modelscope"),
"modelscope.hub": types.ModuleType("modelscope.hub"),
"modelscope.hub.snapshot_download": hub,
"modelscope.utils": types.ModuleType("modelscope.utils"),
"modelscope.utils.file_utils": file_utils,
}
return mock.patch.dict(sys.modules, modules), calls
def test_a_path_already_on_disk_is_left_alone(self):
"""And nothing is imported to decide that -- the arm has to stay usable
on a host with no modelscope installed."""
local = self._directory()
server_args = ServerArgs(model_path=local, tokenizer_path=local, device="cuda")
imported = {name for name in sys.modules if name.startswith("modelscope")}
handle_modelscope_paths(server_args)
view = resolving_view(server_args)
self.assertEqual(view.model_path, local)
self.assertEqual(view.tokenizer_path, local)
self.assertEqual(
imported, {name for name in sys.modules if name.startswith("modelscope")}
)
def test_a_repo_id_resolves_against_the_modelscope_cache(self):
cache_root = self._directory()
os.makedirs(os.path.join(cache_root, _MODELSCOPE_REPO))
patch, _ = self._modelscope(cache_root, {})
server_args = ServerArgs(
model_path=_MODELSCOPE_REPO, tokenizer_path=_MODELSCOPE_REPO, device="cuda"
)
with patch:
handle_modelscope_paths(server_args)
cached = os.path.join(cache_root, _MODELSCOPE_REPO)
view = resolving_view(server_args)
self.assertEqual(view.model_path, cached)
self.assertEqual(view.tokenizer_path, cached)
def test_a_cache_miss_downloads_and_the_tokenizer_skips_the_weights(self):
downloaded = self._directory()
patch, calls = self._modelscope(
self._directory(), {_MODELSCOPE_REPO: downloaded}
)
server_args = ServerArgs(
model_path=_MODELSCOPE_REPO, tokenizer_path=_MODELSCOPE_REPO, device="cuda"
)
with patch:
handle_modelscope_paths(server_args)
view = resolving_view(server_args)
self.assertEqual(view.model_path, downloaded)
self.assertEqual(view.tokenizer_path, downloaded)
# The tokenizer download does not drag the weights along with it.
self.assertEqual(
[call[3] for call in calls], [None, ["*.bin", "*.safetensors"]]
)
def test_the_download_directory_is_searched_before_the_hub(self):
download_dir = self._directory()
os.makedirs(os.path.join(download_dir, _MODELSCOPE_REPO))
patch, calls = self._modelscope(self._directory(), {})
server_args = ServerArgs(
model_path=_MODELSCOPE_REPO,
tokenizer_path=_MODELSCOPE_REPO,
download_dir=download_dir,
device="cuda",
)
with patch:
handle_modelscope_paths(server_args)
self.assertEqual(
resolving_view(server_args).model_path,
os.path.join(download_dir, _MODELSCOPE_REPO),
)
self.assertEqual(calls, [])
def test_a_draft_repo_id_is_resolved_with_its_own_revision(self):
cache_root = self._directory()
drafted = self._directory()
patch, calls = self._modelscope(cache_root, {"org/draft": drafted})
local = self._directory()
server_args = ServerArgs(
model_path=local,
tokenizer_path=local,
speculative_draft_model_path="org/draft",
speculative_draft_model_revision="v2",
device="cuda",
)
with patch:
handle_modelscope_paths(server_args)
self.assertEqual(
resolving_view(server_args).speculative_draft_model_path, drafted
)
self.assertEqual([call[2] for call in calls], ["v2"])
class TestTheRemoteConnectorArm(_ModelSourceCase):
"""`ModelConfig` repoints itself for any other ``scheme://``.
`redis://` is the shape the object-store arm does not claim, so it is the
one that reaches `_maybe_pull_model_tokenizer_from_remote`.
"""
def _connected_to(self, directory):
state = {}
class _Client:
def pull_files(self, allow_pattern=None):
state["allow_pattern"] = allow_pattern
def get_local_dir(self):
return directory
return (
mock.patch.object(
connector_module, "create_remote_connector", return_value=_Client()
),
state,
)
def test_the_configuration_reads_from_the_pulled_directory(self):
pulled = self._checkpoint()
patch, state = self._connected_to(pulled)
with patch:
config = ModelConfig(model_path=_REMOTE_URL)
self.assertEqual(config.model_path, pulled)
# The weights stay where they are; only the metadata was pulled.
self.assertEqual(config.model_weights, _REMOTE_URL)
self.assertEqual(state["allow_pattern"], ["*config.json"])
def test_the_record_keeps_the_url_and_the_cache_stays_keyed_on_it(self):
"""Same movement the object-store arm makes: the configuration's path
moves, the record's does not, and the cache key follows the record."""
pulled = self._checkpoint()
patch, _ = self._connected_to(pulled)
server_args = ServerArgs(model_path=_REMOTE_URL, device="cuda")
with patch:
config = model_config_of(server_args)
self.assertEqual(server_args.model_path, _REMOTE_URL)
self.assertEqual(config.model_path, pulled)
self.assertIs(model_config_of(server_args), config)
if __name__ == "__main__":
unittest.main()
@@ -59,11 +59,14 @@ def _self_written_attributes() -> set:
class TestNoPublicNonFieldSlot(CustomTestCase):
def test_every_public_attribute_is_a_field(self):
written = _self_written_attributes()
self.assertGreater(
len(written),
3,
f"only {len(written)} self-writes found; the scan is broken, not the "
"record",
# Anchor on a name, not a count: the count falls every time a derived
# read leaves the record, so a floor erodes with what it measures.
self.assertIn(
"_resolution_finished",
written,
f"the scan did not find the resolution flag the record sets on "
f"itself, so it is the scan that is broken, not the record: "
f"{sorted(written)}",
)
fields = {field.name for field in dataclasses.fields(ServerArgs)}
stray = sorted(
@@ -0,0 +1,144 @@
"""Every `server_args.<name>()` in the tree names something the record has.
Removing a member from `ServerArgs` means rewriting its callers, and the ones
inside `server_args.py` are the ones you fix by reflex. The cross-file caller is
what bites: `ServerArgs.ssl_verify()` moved to `serving_hook.ssl_verify_of()` and
one call site kept the old spelling as `self.server_args.ssl_verify()` -- a grep
for `server_args.ssl_verify()` does not find that, and nothing else looks. Every
`HttpServerEngineAdapter` request raised `AttributeError` before sending.
So this resolves the call sites instead of grepping for them: every attribute
*called* on something statically known to be a record has to exist on the record.
It is deliberately not limited to methods the refactor touched -- the next
removal gets the same check for free.
`multimodal_gen` carries a different, same-named class outside this contract, as
the other record ratchets also record.
"""
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=6, suite="base-a-test-cpu")
import ast
import dataclasses
import pathlib
import unittest
import sglang
from sglang.srt.server_args import ServerArgs
from sglang.test.test_utils import CustomTestCase
_ROOTS = (
pathlib.Path(next(iter(sglang.__path__))) / "srt",
pathlib.Path(__file__).resolve().parents[3], # test/
)
_EXCLUDED = ("multimodal_gen",)
# Attribute names that hold a `ServerArgs`. `resolving_view` and `resolved_view`
# proxy the record but answer for names it does not carry, so they are not here.
_RECORD_NAMES = ("server_args", "_server_args")
def _is_record(node) -> bool:
"""`server_args`, `self.server_args`, `self._server_args`, `cls.server_args`."""
if isinstance(node, ast.Name):
return node.id in _RECORD_NAMES
if isinstance(node, ast.Attribute):
return node.attr in _RECORD_NAMES
return False
def _rebound_locally(tree) -> set:
"""Names assigned something that is plainly not a record.
`server_args` is also a natural name for a dict of CLI flags or a list of
argv strings in test helpers, and those legitimately answer `.update()` and
`.items()`. A function that assigns one of those to the name is not talking
about the record in that scope.
"""
literal = (ast.Dict, ast.List, ast.DictComp, ast.ListComp)
builders = {"dict", "list", "tuple", "set"}
def _not_a_record(value) -> bool:
if isinstance(value, literal):
return True
# `dict(...)` / `list(...)`, and an annotated `server_args: list[str] = [...]`
return (
isinstance(value, ast.Call) and getattr(value.func, "id", None) in builders
)
rebound = set()
for node in ast.walk(tree):
if isinstance(node, ast.AnnAssign):
targets, value = [node.target], node.value
elif isinstance(node, ast.Assign):
targets, value = node.targets, node.value
else:
continue
if value is None or not _not_a_record(value):
continue
for target in targets:
if isinstance(target, ast.Name) and target.id in _RECORD_NAMES:
rebound.add(target.id)
return rebound
def _called_members():
"""{name: [file:line]} for every `<record>.<name>(...)` in the tree."""
found: dict[str, list[str]] = {}
for root in _ROOTS:
for path in sorted(root.rglob("*.py")):
text = path.as_posix()
if any(part in text for part in _EXCLUDED):
continue
source = path.read_text(encoding="utf-8-sig")
if "server_args" not in source:
continue
try:
tree = ast.parse(source)
except SyntaxError:
continue
rebound = _rebound_locally(tree)
for node in ast.walk(tree):
if (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and _is_record(node.func.value)
and getattr(node.func.value, "id", None) not in rebound
):
found.setdefault(node.func.attr, []).append(
f"{path.name}:{node.lineno}"
)
return found
class TestRecordMemberCallsResolve(CustomTestCase):
def test_every_called_member_exists_on_the_record(self):
called = _called_members()
self.assertGreater(
len(called),
5,
f"only {len(called)} members called on a record; the scan is broken, "
"not the tree",
)
available = set(dir(ServerArgs)) | {
field.name for field in dataclasses.fields(ServerArgs)
}
missing = {
name: sites
for name, sites in sorted(called.items())
if name not in available
}
self.assertEqual(
{},
missing,
"these are called on a ServerArgs but the record has no such member -- "
"each one raises AttributeError at the call. A member that moved out of "
"the record has to be rewritten at every call site, including the ones "
f"reached through `self.server_args`: {missing}",
)
if __name__ == "__main__":
unittest.main()
@@ -55,6 +55,7 @@ from sglang.srt.arg_groups.serving_hook import (
handle_multimodal_feature_transport,
handle_ssl_validation,
handle_tokenizer_batching,
ssl_verify_of,
)
from sglang.srt.arg_groups.speculative_hook import handle_speculative_decoding
from sglang.srt.arg_groups.validation_hook import check_two_batch_overlap
@@ -1367,13 +1368,15 @@ class TestSSLArgs(unittest.TestCase):
self.assertTrue(server_args.url().startswith("https://"))
def test_ssl_verify_without_ssl(self):
# the derived read lives with the rest of the SSL handling now
server_args = ServerArgs(model_path="dummy")
self.assertIs(server_args.ssl_verify(), True)
self.assertIs(ssl_verify_of(server_args), True)
@patch("os.path.isfile", return_value=True)
def test_ssl_verify_with_ssl_no_ca(self, _mock_isfile):
server_args = self._validate_ssl(ssl_keyfile="key.pem", ssl_certfile="cert.pem")
self.assertIs(server_args.ssl_verify(), False)
self.assertIs(ssl_verify_of(server_args), False)
@patch("os.path.isfile", return_value=True)
def test_ssl_verify_with_ssl_and_ca(self, _mock_isfile):
@@ -1382,7 +1385,7 @@ class TestSSLArgs(unittest.TestCase):
ssl_certfile="cert.pem",
ssl_ca_certs="ca.pem",
)
self.assertEqual(server_args.ssl_verify(), "ca.pem")
self.assertEqual(ssl_verify_of(server_args), "ca.pem")
def test_ssl_ca_certs_without_certfile_raises(self):
with self.assertRaises(ValueError) as context:
@@ -2840,8 +2843,13 @@ class TestDcpKvEventContract(CustomTestCase):
def test_kv_event_block_size_widens_a_single_token_page(self):
# page_size=1 + DCP is a real deployment shape: the allocator is still
# paged, at dcp_size.
from sglang.srt.arg_groups.overrides import (
kv_event_block_size_of,
resolving_view,
)
args = ServerArgs(model_path="dummy", tp_size=8, dcp_size=8, page_size=1)
self.assertEqual(args.kv_event_block_size, 8)
self.assertEqual(kv_event_block_size_of(resolving_view(args)), 8)
if __name__ == "__main__":
+49 -21
View File
@@ -125,19 +125,17 @@ def _returned_field_names(function):
and node.func.value.id in returned
):
names |= {kw.arg for kw in node.keywords if kw.arg}
receiver = node.func.value
if isinstance(receiver, ast.Name) and receiver.id == "overrides":
# Positional dict literals are collected by the Dict walk;
# anything else is invisible.
for arg in node.args:
if not isinstance(arg, ast.Dict):
raise AssertionError(
f"opaque overrides.update() argument in {function.name}"
)
if any(kw.arg is None for kw in node.keywords):
raise AssertionError(
f"**kwargs overrides.update() in {function.name}"
)
# A positional dict literal has to be read here. The Dict walk above
# only reaches literals that are *returned* or assigned to a returned
# name, so `d.update({"field": value})` was being type-checked and
# then dropped -- silently, under a comment claiming otherwise.
for arg in node.args:
if isinstance(arg, ast.Dict):
top_level_keys(arg)
else:
raise AssertionError(f"opaque update() argument in {function.name}")
if any(kw.arg is None for kw in node.keywords):
raise AssertionError(f"**kwargs update() in {function.name}")
return names
@@ -151,12 +149,28 @@ def _declared_by_registry_and_passes():
`@register_model_override*` sees exactly one of them and reports a healthy
census over a channel it cannot see.
"""
import sys
from sglang.srt.arg_groups import overrides
tree = ast.parse((_SRT / "arg_groups/overrides.py").read_text(encoding="utf-8-sig"))
bodies = {
node.name: node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)
}
# Resolve each callable's body in the file it actually lives in. The
# declarations are spread over `arg_groups/model_overrides/`, one module per
# model family, and a scan hard-coded to `overrides.py` would find none of
# them -- and, worse, would keep reporting a healthy census while doing it.
bodies_by_module = {}
def _bodies(module_name):
if module_name not in bodies_by_module:
path = getattr(sys.modules[module_name], "__file__", None)
assert path, f"{module_name} has no source file"
module_tree = ast.parse(pathlib.Path(path).read_text(encoding="utf-8-sig"))
bodies_by_module[module_name] = {
node.name: node
for node in ast.walk(module_tree)
if isinstance(node, ast.FunctionDef)
}
return bodies_by_module[module_name]
callables = {fn for fns in overrides._MODEL_OVERRIDE_FNS.values() for fn in fns}
callables |= {
fn for _predicate, fn in getattr(overrides, "_PREDICATE_OVERRIDE_FNS", ())
@@ -165,11 +179,23 @@ def _declared_by_registry_and_passes():
fields = set()
for fn in callables:
body = bodies.get(getattr(fn, "__name__", ""))
if body is not None:
fields |= _returned_field_names(body)
name = getattr(fn, "__name__", "")
body = _bodies(fn.__module__).get(name)
# Loud, not silent: a body this scan cannot find is a field census it
# is not taking, and a narrower census makes every check downstream of
# it quietly vacuous.
assert body is not None, f"{fn.__module__}.{name} has no body to scan"
fields |= _returned_field_names(body)
# The literal arch -> {field: value} table, which has no callable at all.
for node in tree.body:
# It lives with the rest of the registry, in `model_override_base`.
from sglang.srt.arg_groups import model_override_base
table_tree = ast.parse(
pathlib.Path(model_override_base.__file__).read_text(encoding="utf-8-sig")
)
seen_table = False
for node in table_tree.body:
target = None
if isinstance(node, ast.Assign) and isinstance(node.targets[0], ast.Name):
target = node.targets[0].id
@@ -186,6 +212,8 @@ def _declared_by_registry_and_passes():
if not isinstance(key, ast.Constant):
raise AssertionError("non-literal override key")
fields.add(key.value)
seen_table = True
assert seen_table, "MODEL_OVERRIDES is not where this scan looks for it"
return fields
@@ -0,0 +1,120 @@
"""Two family modules must never declare the same field for the same architecture.
An architecture claimed by two family modules is normal -- ``Qwen3NextForCausalLM``
gets its attention shape from ``qwen3_5`` and its MoE runner from ``qwen3_moe``.
Two modules declaring the *same* field for it is not: nobody owns that value,
and which module supplies it is decided by nothing more deliberate than the
order the imports happen to be in. That is a defect in the declarations, so
this forbids it outright rather than choosing a winner.
The ordering follows from the rule and is not itself pinned. ``__init__.py`` is
a list of imports, importing is what registers, and the gate applies matching
declarations in registration order with the last writer winning -- so an
overlap would make an import list into a behavioural statement, which tools
reorder freely. With no overlap the list can be sorted however anyone likes.
The declared-field sets are read with the chain ratchet's own extractor rather
than a second implementation of the same scan, for the reason its docstring
gives: two censuses of one thing that disagree are worse than either alone.
"""
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
import ast
import importlib.util
import pathlib
import sys
import unittest
from sglang.srt.arg_groups import model_overrides
from sglang.srt.arg_groups.model_override_base import (
_MODEL_OVERRIDE_FNS,
MODEL_OVERRIDES,
)
from sglang.test.test_utils import CustomTestCase
_RATCHET = pathlib.Path(__file__).resolve().parent / "test_chain_read_ratchet.py"
def _returned_field_names(fn):
spec = importlib.util.spec_from_file_location("_chain_ratchet_for_split", _RATCHET)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
source = pathlib.Path(sys.modules[fn.__module__].__file__).read_text(
encoding="utf-8-sig"
)
body = next(
node
for node in ast.walk(ast.parse(source))
if isinstance(node, ast.FunctionDef) and node.name == fn.__name__
)
return module._returned_field_names(body)
class TestModelOverrideSplit(CustomTestCase):
def test_no_field_is_declared_by_two_family_modules(self):
contested = {
arch: fns for arch, fns in _MODEL_OVERRIDE_FNS.items() if len(fns) > 1
}
self.assertTrue(contested, "the scan found no architecture with two claimants")
for arch, fns in sorted(contested.items()):
with self.subTest(architecture=arch):
seen: dict[str, str] = {}
for fn in fns:
for field in _returned_field_names(fn):
earlier = seen.get(field)
self.assertIsNone(
earlier,
f"{arch}: {fn.__module__}.{fn.__name__} and {earlier} "
f"both declare {field!r}, so which one wins now depends "
f"on the order of the imports in "
f"arg_groups/model_overrides/__init__.py",
)
seen[field] = f"{fn.__module__}.{fn.__name__}"
def test_the_constant_table_does_not_contest_a_callable(self):
"""``MODEL_OVERRIDES`` applies before the callables, so a field it and a
callable both name is decided by that ordering instead."""
for arch, const in sorted(MODEL_OVERRIDES.items()):
for fn in _MODEL_OVERRIDE_FNS.get(arch, ()):
with self.subTest(architecture=arch, fn=fn.__name__):
self.assertFalse(
set(const) & _returned_field_names(fn),
f"{arch}: MODEL_OVERRIDES and {fn.__name__} both declare "
f"{sorted(set(const) & _returned_field_names(fn))}",
)
def test_the_import_list_names_every_family_module(self):
"""Importing is what registers, so a module missing from the list is a
family that silently stops applying -- and the tests that import a
provider directly would not notice."""
package = pathlib.Path(model_overrides.__file__).parent
on_disk = {
path.stem for path in package.glob("*.py") if path.stem != "__init__"
}
imported = {
alias.name
for node in ast.walk(ast.parse((package / "__init__.py").read_text()))
if isinstance(node, ast.ImportFrom)
and node.module == "sglang.srt.arg_groups.model_overrides"
for alias in node.names
}
self.assertEqual(on_disk, imported)
def test_every_declaration_comes_from_its_own_family_module(self):
"""The split itself: nothing was left behind in overrides.py."""
for arch, fns in _MODEL_OVERRIDE_FNS.items():
for fn in fns:
with self.subTest(architecture=arch, fn=fn.__name__):
self.assertTrue(
fn.__module__.startswith(
"sglang.srt.arg_groups.model_overrides."
),
f"{fn.__name__} still lives in {fn.__module__}",
)
if __name__ == "__main__":
unittest.main()
+117 -70
View File
@@ -15,8 +15,11 @@ from types import SimpleNamespace
from typing import Optional
from unittest.mock import patch
from sglang.srt.arg_groups import model_override_base as base_module
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.model_overrides import minicpm as minicpm_module
from sglang.srt.arg_groups.model_overrides import qwen3_5 as qwen3_5_module
from sglang.srt.arg_groups.overrides import (
collect_model_override_declarations,
register_model_override,
@@ -119,15 +122,30 @@ class TestDSparkCheckpointConfig(CustomTestCase):
self.assertTrue(get_dspark_sample_from_anchor(SimpleNamespace()))
def _hf(quant_method=None, **kw):
"""An hf config that states its own quantization.
`get_quantization_config(hf_config)` just reads
`hf_config.quantization_config["quant_method"]`, so a test says what the
checkpoint is by handing over a config that says it -- rather than stubbing
the reader in one module and hoping that is the module doing the reading.
"""
if quant_method is not None:
kw["quantization_config"] = {"quant_method": quant_method}
return SimpleNamespace(**kw)
class _IsolatedRegistry(CustomTestCase):
"""Run each test against empty registries (they are process-global)."""
def setUp(self):
super().setUp()
# The registries live in `model_override_base`; that is the one address
# to isolate, because the registrars and the collector both use it.
self._patches = [
patch.dict(overrides_module.MODEL_OVERRIDES, clear=True),
patch.dict(overrides_module._MODEL_OVERRIDE_FNS, clear=True),
patch.object(overrides_module, "_PREDICATE_OVERRIDE_FNS", []),
patch.dict(base_module.MODEL_OVERRIDES, clear=True),
patch.dict(base_module._MODEL_OVERRIDE_FNS, clear=True),
patch.object(base_module, "_PREDICATE_OVERRIDE_FNS", []),
]
for p in self._patches:
p.start()
@@ -140,7 +158,7 @@ class _IsolatedRegistry(CustomTestCase):
class TestModelOverrideRegistry(_IsolatedRegistry):
def test_const_then_callables_in_registration_order(self):
overrides_module.MODEL_OVERRIDES["FakeForCausalLM"] = {"a": 1}
base_module.MODEL_OVERRIDES["FakeForCausalLM"] = {"a": 1}
@register_model_override("FakeForCausalLM")
def _first(server_args, hf_config):
@@ -453,7 +471,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
with override_platform(is_blackwell=False):
overrides = overrides_module._minicpm_sala_overrides(args, config)
overrides = minicpm_module._minicpm_sala_overrides(args, config)
self.assertTrue(overrides["disable_radix_cache"])
self.assertEqual(overrides["attention_backend"], "minicpm_flashattn")
@@ -660,7 +678,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
def test_mimo_v2_declarations(self):
# Callable-level golden: MiMoV2 archs are hybrid (config-shape heavy),
# so the declaration is pinned directly for both provider inputs.
from sglang.srt.arg_groups.overrides import _mimo_v2_overrides
from sglang.srt.arg_groups.model_overrides.mimo_v2 import _mimo_v2_overrides
def _args(**kw):
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
@@ -677,7 +695,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
def test_mimo_v2_sm100_fp8_pins_flashinfer_trtllm_moe(self):
"""Blackwell FP8 must not be left on the triton fused-MoE runner."""
from sglang.srt.arg_groups.overrides import _mimo_v2_overrides
from sglang.srt.arg_groups.model_overrides.mimo_v2 import _mimo_v2_overrides
def _args(**kw):
defaults = dict(speculative_algorithm=None, moe_runner_backend="auto")
@@ -685,23 +703,17 @@ class TestGoldenModelOverrides(_IsolatedPublish):
return SimpleNamespace(**defaults)
with override_platform(is_sm100=True):
with patch.object(
overrides_module, "get_quantization_config", return_value="fp8"
):
self.assertEqual(
_mimo_v2_overrides(_args(), None),
{"moe_runner_backend": "flashinfer_trtllm"},
)
# An explicit user choice is never overwritten.
self.assertEqual(
_mimo_v2_overrides(_args(moe_runner_backend="triton"), None), {}
)
self.assertEqual(
_mimo_v2_overrides(_args(), _hf("fp8")),
{"moe_runner_backend": "flashinfer_trtllm"},
)
# An explicit user choice is never overwritten.
self.assertEqual(
_mimo_v2_overrides(_args(moe_runner_backend="triton"), _hf("fp8")), {}
)
# FP4 checkpoints run through flashinfer_mxfp4, so they must not be
# pinned to flashinfer_trtllm.
with patch.object(
overrides_module, "get_quantization_config", return_value="mxfp4"
):
self.assertEqual(_mimo_v2_overrides(_args(), None), {})
self.assertEqual(_mimo_v2_overrides(_args(), _hf("mxfp4")), {})
def test_mimo_v2_family_is_registered(self):
with override_platform(is_sm100=False):
@@ -748,7 +760,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_w4a16_moe_uses_marlin_on_sm100(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -778,7 +792,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_nvfp4_moe_keeps_flashinfer_trtllm_on_sm100(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -808,7 +824,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_speculation_uses_arch_specific_attention_on_blackwell(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
cases = {
True: {
@@ -836,7 +854,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(overrides[key], value)
def test_nemotron_h_sm100_speculative_draft_backend_matrix(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
for algorithm in ("EAGLE", "NEXTN", "DSPARK"):
with self.subTest(algorithm=algorithm):
@@ -864,7 +884,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_sm100_speculation_preserves_explicit_cache_and_draft(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DSPARK"
@@ -884,7 +906,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_sm100_topk_tree_falls_back_to_triton(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "EAGLE"
@@ -902,7 +926,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("mamba_radix_cache_strategy", overrides)
def test_nemotron_h_target_only_sm120_defers_to_generic_attention_default(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
@@ -915,7 +941,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_target_only_sm100_uses_trtllm_mha(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
@@ -929,7 +957,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_explicit_split_attention_backend_wins(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DFLASH"
@@ -945,7 +975,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_w4a16_moe_rejects_a2a_backend(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -961,7 +993,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
_nemotron_h_overrides(server_args, hf_config)
def test_nemotron_h_w4a16_moe_rejects_non_marlin_runner(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
server_args, hf_config = self._nemotron_h_args(
quantized_layers={
@@ -1021,7 +1055,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertTrue((self._publish(sa), self._leaf("disable_hybrid_swa_memory"))[1])
def test_exaone_without_pattern_declares_nothing(self):
from sglang.srt.arg_groups.overrides import _exaone_overrides
from sglang.srt.arg_groups.model_overrides.exaone import _exaone_overrides
self.assertEqual(
_exaone_overrides(None, SimpleNamespace(sliding_window_pattern=None)),
@@ -1049,7 +1083,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual((self._publish(sa), self._leaf("dtype"))[1], "auto")
def test_gpt_oss_xpu_dtype_validation_reads_pristine(self):
from sglang.srt.arg_groups.overrides import _gpt_oss_overrides
from sglang.srt.arg_groups.model_overrides.gpt_oss import _gpt_oss_overrides
with override_platform(is_xpu=True):
with self.assertRaises(NotImplementedError):
@@ -1427,7 +1461,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_deepseek_v4_overrides_at_callable_level(self):
from sglang.srt.arg_groups.overrides import _deepseek_v4_overrides
from sglang.srt.arg_groups.model_overrides.deepseek_v4 import (
_deepseek_v4_overrides,
)
from sglang.srt.server_args import ServerArgs
hf = SimpleNamespace(architectures=["DeepseekV4ForCausalLM"])
@@ -1540,7 +1576,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_nemotron_h_overrides_at_callable_level(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
from sglang.srt.arg_groups.model_overrides.nemotron_h import (
_nemotron_h_overrides,
)
def _hf(quant_algo="NVFP4", *, include_quantization_config=True):
hf = SimpleNamespace(
@@ -1892,7 +1930,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
_cutedsl_prefill_backend_fill(_view())
def test_moss_vl_overrides_at_callable_level(self):
from sglang.srt.arg_groups.overrides import _moss_vl_overrides
from sglang.srt.arg_groups.model_overrides.moss_vl import _moss_vl_overrides
def _args(**kw):
defaults = dict(
@@ -2183,7 +2221,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_qwen3_5_hybrid_coupled_declaration(self):
from sglang.srt.arg_groups.overrides import _qwen3_5_hybrid_overrides
from sglang.srt.arg_groups.model_overrides.qwen3_5 import (
_qwen3_5_hybrid_overrides,
)
def _args(default_backend, **kw):
defaults = dict(
@@ -2201,7 +2241,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
return args
with override_platform(is_sm100=True), patch.object(
overrides_module,
qwen3_5_module,
"get_default_attn_backend",
lambda server_args, **_: server_args.default_backend_for_test,
):
@@ -2246,7 +2286,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(_qwen3_5_hybrid_overrides(_args("fa3"), None), {})
def test_qwen3vl_page_size(self):
from sglang.srt.arg_groups.overrides import _qwen3vl_overrides
from sglang.srt.arg_groups.model_overrides.qwen3_vl import _qwen3vl_overrides
with override_platform(is_hip=True):
with patch("sglang.srt.environ.envs.SGLANG_USE_AITER_UNIFIED_ATTN") as e:
@@ -2319,7 +2359,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_m3_fp8_attn_gemm_resolution(self):
from sglang.srt.arg_groups.overrides import _minimax_m3_overrides
from sglang.srt.arg_groups.model_overrides.minimax_m3 import (
_minimax_m3_overrides,
)
from sglang.srt.server_args import m3_fp8_attn_gemm_enabled
def _args(**kw):
@@ -2364,9 +2406,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
return ns
hf = SimpleNamespace()
with override_platform(is_hip=False), override_platform(
is_sm100=True
), patch.object(overrides_module, "get_quantization_config", return_value=None):
# `hf` carries no `quantization_config`, which is what an unquantized
# checkpoint looks like -- no stub needed to say so.
with override_platform(is_hip=False), override_platform(is_sm100=True):
# fp8_e4m3 KV: SM100 backend default flips to trtllm_mha (the only
# dense backend with the fp8-q GEMM path); page snaps to 128
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e4m3"), hf)
@@ -2378,7 +2420,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(ov["page_size"], 128)
# e5m2 KV: stays on fa4 + the widening Triton path, and warns
with self.assertLogs(
"sglang.srt.arg_groups.overrides", level="WARNING"
"sglang.srt.arg_groups.model_overrides.minimax_m3", level="WARNING"
) as logs:
ov = _minimax_m3_overrides(_m3_args(kv_cache_dtype="fp8_e5m2"), hf)
self.assertEqual(ov["attention_backend"], "fa4")
@@ -2505,13 +2547,17 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_monolith_attention_families_at_callable_level(self):
from sglang.srt.arg_groups.overrides import (
from sglang.srt.arg_groups.model_overrides.falcon_h1 import (
_falcon_h1_jet_overrides,
_gemma4_overrides,
_glm4_moe_overrides,
)
from sglang.srt.arg_groups.model_overrides.gemma4 import _gemma4_overrides
from sglang.srt.arg_groups.model_overrides.glm4_moe import _glm4_moe_overrides
from sglang.srt.arg_groups.model_overrides.granitemoehybrid import (
_granite_moe_hybrid_overrides,
_lfm2_overrides,
_llama4_overrides,
)
from sglang.srt.arg_groups.model_overrides.lfm2 import _lfm2_overrides
from sglang.srt.arg_groups.model_overrides.llama4 import _llama4_overrides
from sglang.srt.arg_groups.model_overrides.minicpmv import (
_minicpm_v4_6_overrides,
)
@@ -2673,7 +2719,9 @@ class TestGoldenModelOverrides(_IsolatedPublish):
)
def test_deepseek_family_order_safe_declarations(self):
from sglang.srt.arg_groups.overrides import _deepseek_family_overrides
from sglang.srt.arg_groups.model_overrides.deepseek_v2 import (
_deepseek_family_overrides,
)
def _args(**kw):
defaults = dict(
@@ -2768,27 +2816,26 @@ class TestGoldenModelOverrides(_IsolatedPublish):
self.assertEqual(_deepseek_family_overrides(_args(), None), {})
def test_qwen3_moe_family_quant_absorption(self):
from sglang.srt.arg_groups.overrides import _qwen3_moe_family_overrides
from sglang.srt.arg_groups.model_overrides.qwen3_moe import (
_qwen3_moe_family_overrides,
)
with override_platform(is_sm100=True):
with patch.object(
overrides_module, "get_quantization_config", return_value="fp8"
):
self.assertEqual(
_qwen3_moe_family_overrides(
SimpleNamespace(
quantization=None,
_quantization_explicitly_unset=False,
moe_a2a_backend="none",
moe_runner_backend="auto",
),
SimpleNamespace(architectures=["Qwen3MoeForCausalLM"]),
self.assertEqual(
_qwen3_moe_family_overrides(
SimpleNamespace(
quantization=None,
_quantization_explicitly_unset=False,
moe_a2a_backend="none",
moe_runner_backend="auto",
),
{
"quantization": "fp8",
"moe_runner_backend": "flashinfer_trtllm",
},
)
_hf("fp8", architectures=["Qwen3MoeForCausalLM"]),
),
{
"quantization": "fp8",
"moe_runner_backend": "flashinfer_trtllm",
},
)
with override_platform(is_sm100=False):
self.assertEqual(_qwen3_moe_family_overrides(None, None), {})
+4 -1
View File
@@ -1200,6 +1200,9 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
def test_activation_reserve_matches_the_member(self):
from types import SimpleNamespace
from sglang.srt.arg_groups.overrides import (
pre_capture_activation_reserve_mb_of,
)
from sglang.srt.runtime_context import pre_capture_activation_reserve_mb
graph = SimpleNamespace(decode=SimpleNamespace(max_bs=64))
@@ -1231,7 +1234,7 @@ class TestDerivedPredicatesAgreeAcrossTiers(_IsolatedServerArgs):
args = _FakeResolvedArgs(cuda_graph_config=graph, **case)
get_context().set_server_args(args)
self.assertEqual(
ServerArgs.pre_capture_activation_reserve_mb(args, gpu_mem),
pre_capture_activation_reserve_mb_of(args, gpu_mem),
pre_capture_activation_reserve_mb(gpu_mem),
)