[Feature] Add Muse Glimmer model support (#34262)

Co-authored-by: sglang-bot <232288953+sglang-bot@users.noreply.github.com>
Co-authored-by: Brayden Zhong <brayden.zhong@radixark.ai>
Co-authored-by: Jimmy Shong <69131491+Jiminator@users.noreply.github.com>
Co-authored-by: hnyls2002 <lsyincs@gmail.com>
Co-authored-by: Alex Nails <alex.nails@radixark.ai>
Co-authored-by: Liangsheng Yin <hnyls2002@gmail.com>
This commit is contained in:
sglang-bot
2026-08-11 15:41:52 -07:00
committed by GitHub
co-authored by sglang-bot Brayden Zhong Jimmy Shong hnyls2002 Alex Nails Liangsheng Yin
parent 9c1517df4a
commit fde9ad2531
47 changed files with 5009 additions and 50 deletions
@@ -0,0 +1,94 @@
import unittest
from sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kits.eval_accuracy_kit import GSM8KMixin
from sglang.test.test_utils import (
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
DEFAULT_URL_FOR_TEST,
CustomTestCase,
popen_launch_server,
)
register_cuda_ci(est_time=900, stage="nightly", runner_config="1-gpu-large")
TARGET_MODEL = "meta-models/Muse-Glimmer-30B"
DRAFT_MODEL = "meta-models/Muse-Glimmer-30B-assistant"
class TestMuseGlimmerDflashAssistantGSM8K(CustomTestCase, GSM8KMixin):
"""GSM8K + DFlash accept-length regression test for the native
MuseGlimmerAssistantModel draft (``meta-models/Muse-Glimmer-30B-assistant``).
This checkpoint loads through sglang's own native ``models/dflash.py`` /
``configs/muse_glimmer.py::MuseGlimmerAssistantConfig`` with no extra wheel
dependency. Integrating it surfaced two bugs that fail *silently*, never raise, and do not move
GSM8K accuracy at temperature 0 (speculative decoding always falls back
to the target's own correct token on a draft miss, so a broken draft
still produces exactly the target's answers -- just slower):
1. The vendor's weight names (``encoder.fc.weight`` /
``encoder.output_norm_enc.weight``) didn't match what
``DFlashDraftModel.load_weights`` expected (``fc.weight`` /
``hidden_norm.weight``), so those two tensors silently stayed at
random init.
2. The vendor's ``target_layer_ids`` are in the HF "output of layer k"
convention; ``models/muse_glimmer.py::set_dflash_layers_to_capture``
uses ids as-is (Muse Glimmer's own draft configs carry llama.cpp's
layer-*input* convention), so every captured layer was off by one.
Both together collapsed real (non-simulated) accept_length to ~1.00 at
``--speculative-dflash-block-size 5`` -- effectively no speculation, only
the mandatory bonus token -- with GSM8K accuracy unaffected throughout.
``gsm8k_accept_length_thres`` is therefore the actual regression guard
here; the accuracy threshold alone would not have caught this.
Measured after both fixes, same block size, same target: median
accept_length 3.12 (this draft) vs 3.09 (our own GGUF-converted draft,
the previous ground truth) across batch sizes 1/4/8 at 512in/256out --
statistically indistinguishable. 2.5 leaves headroom below that for
workload variance while sitting well above the ~1.0 broken-state value.
"""
model = TARGET_MODEL
gsm8k_backend = (
"sgl_eval" # chat completions API, not /generate or raw /completions
)
gsm8k_score_threshold = 0.85
gsm8k_num_examples = 200
gsm8k_accept_length_thres = 2.5
@classmethod
def setUpClass(cls):
cls.base_url = DEFAULT_URL_FOR_TEST
cls.process = popen_launch_server(
cls.model,
cls.base_url,
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
other_args=[
"--reasoning-parser",
"muse",
"--tool-call-parser",
"muse",
"--language-model-only",
"--speculative-algorithm",
"DFLASH",
"--speculative-draft-model-path",
DRAFT_MODEL,
"--speculative-draft-load-format",
"auto",
"--speculative-dflash-block-size",
"5",
"--mem-fraction-static",
"0.85",
],
)
@classmethod
def tearDownClass(cls):
if hasattr(cls, "process") and cls.process:
kill_process_tree(cls.process.pid)
if __name__ == "__main__":
unittest.main()
@@ -311,6 +311,80 @@ class ReasoningRequestForwardingTestCase(unittest.TestCase):
self.assertFalse(parser_cls.call_args.kwargs["force_reasoning"])
class SkipSpecialTokensForwardingTestCase(CustomTestCase):
"""The skip_special_tokens override from _process_messages must reach the
engine sampling params; muse's channel markers die in detok otherwise."""
def _create_responses_sampling_params(self, serving):
serving.default_chat_template_kwargs = None
rendered = MessageProcessingResult(
prompt="prompt",
prompt_ids=[1, 2, 3],
image_data=None,
audio_data=None,
video_data=None,
modalities=[],
stop=[],
)
captured = {}
async def fake_generate(
request_id,
request_prompt,
adapted_request,
sampling_params,
context,
**kwargs,
):
captured["sampling_params"] = sampling_params
context.append_output(
{
"text": "done",
"meta_info": {
"prompt_tokens": 3,
"completion_tokens": 1,
"cached_tokens": 0,
},
}
)
yield context
serving._generate_with_builtin_tools = fake_generate
request = ResponsesRequest(
model="x",
input="answer",
request_id="resp_skip_special",
store=False,
)
with (
patch.object(
serving, "_apply_conversation_template", return_value=rendered
),
patch(
"sglang.srt.entrypoints.openai.serving_responses.ReasoningParser"
) as parser_cls,
):
parser_cls.return_value.parse_non_stream.return_value = (None, "done")
response = asyncio.run(serving.create_responses(request))
self.assertEqual(response.status, "completed")
return captured["sampling_params"]
def test_marker_preserving_parser_disables_skip_special_tokens(self):
serving = make_serving()
serving.reasoning_parser = "muse"
params = self._create_responses_sampling_params(serving)
self.assertFalse(params["skip_special_tokens"])
def test_default_parser_keeps_skip_special_tokens(self):
serving = make_serving()
params = self._create_responses_sampling_params(serving)
# The chat request's True is a synthesized default (ResponsesRequest has
# no such field), so leave it unset for --preferred-sampling-params.
self.assertNotIn("skip_special_tokens", params)
class InputItemNormalizationTestCase(CustomTestCase):
def test_function_call_becomes_assistant_tool_call(self):
normalized = OpenAIServingResponses._normalize_response_message_for_chat(
@@ -167,6 +167,7 @@ class NonHarmonyStreamTestCase(CustomTestCase):
parser_cls.return_value.parse_stream_chunk.side_effect = (
fake_parse_stream_chunk
)
parser_cls.return_value.parse_stream_end.return_value = ("", [])
fixture = StreamFixture(serving, request)
events = fixture.run(chunks)
@@ -178,6 +179,35 @@ class NonHarmonyStreamTestCase(CustomTestCase):
self.assertEqual(output[1]["name"], "get_weather")
self.assertEqual(output[2]["content"][0]["text"], "It's sunny.")
def test_reasoning_parser_flushed_at_stream_end(self):
"""Bug regression: the stream loop never drained text the reasoning
parser held back as a possible marker prefix, so a response whose text
genuinely ends with e.g. "<|e" lost that tail on /v1/responses (chat
flushes via parse_stream_end; responses did not)."""
serving = make_serving()
serving.reasoning_parser = "muse"
serving.tool_call_parser = None
request = ResponsesRequest(model="x", input="hi", stream=True, store=False)
text = (
" to=self<|message|>think<|eom|>"
"<|start|>assistant to=user<|message|>Answer<|e"
)
fixture = StreamFixture(serving, request)
events = fixture.run(
[
engine_chunk(text[:30], 4),
engine_chunk(text, 9, finish=True),
]
)
streamed = "".join(
p["delta"]
for ev, p in zip(event_types(events), event_payloads(events))
if ev == "response.output_text.delta"
)
self.assertEqual(streamed, "Answer<|e")
class MultiToolCallStreamingOrderTestCase(CustomTestCase):
"""The wire order of message / function_call items across tool-call deltas."""
@@ -0,0 +1,432 @@
"""Unit tests for the Muse Glimmer ATEM tool-call detector — no server, no model loading.
The expectations here are pinned to the checkpoint's own ``response_template``
(``MUSE_GLIMMER_RESPONSE_SCHEMA`` in ``tokenizer_config.json``) and to the vendor's
reference parser, with particular attention to channel scoping: an
``<atem:invoke>`` that only appears inside a reasoning block or a final answer
must never become a real tool call.
"""
import json
from sglang.srt.entrypoints.openai.protocol import Function, Tool
from sglang.srt.function_call.function_call_parser import FunctionCallParser
from sglang.srt.function_call.muse_glimmer_detector import MuseGlimmerDetector
from sglang.srt.parser.reasoning_parser import ReasoningParser
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(1.0, "base-a-test-cpu")
DOUBLED = "get_weather.get_weather"
def atem(name: str, **params: str) -> str:
body = "".join(
f'<atem:parameter name="{k}">{v}</atem:parameter>\n' for k, v in params.items()
)
return (
f'<atem:function_calls>\n<atem:invoke name="{name}">\n{body}'
f"</atem:invoke>\n</atem:function_calls>"
)
class TestMuseGlimmerDetector(CustomTestCase):
def setUp(self):
self.tools = [
Tool(
type="function",
function=Function(
name="get_weather",
description="Get weather",
parameters={
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
),
),
]
# ---- helpers ----------------------------------------------------------
def parse(self, text):
"""Non-streaming parse -> (normal_text, [(name, args), ...])."""
result = MuseGlimmerDetector().detect_and_parse(text, self.tools)
return result.normal_text, [
(c.name, json.loads(c.parameters)) for c in result.calls if c.name
]
def parse_streaming(self, text, chunk_size):
detector = MuseGlimmerDetector()
normal, calls = [], []
for i in range(0, len(text), chunk_size):
result = detector.parse_streaming_increment(
text[i : i + chunk_size], self.tools
)
normal.append(result.normal_text)
calls.extend(
(c.name, json.loads(c.parameters)) for c in result.calls if c.name
)
return "".join(normal), calls
def assert_streaming_matches(self, text):
"""Streaming must agree with one-shot parsing at every chunk boundary."""
expected = self.parse(text)
for chunk_size in (1, 2, 3, 5, 7, 13, 29, 100):
self.assertEqual(
self.parse_streaming(text, chunk_size),
expected,
f"streaming diverged at chunk_size={chunk_size}",
)
# ---- tool extraction --------------------------------------------------
def test_single_tool_call(self):
text = (
f" to=self<|message|>Need weather.<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>{atem(DOUBLED, city='Paris')}"
)
normal, calls = self.parse(text)
self.assertEqual(calls, [("get_weather", {"city": "Paris"})])
self.assertEqual(normal, "Need weather.")
self.assert_streaming_matches(text)
def test_parallel_tool_calls(self):
text = (
f" to=self<|message|>Two cities.<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>"
f"{atem(DOUBLED, city='Paris')}<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>{atem(DOUBLED, city='Tokyo')}"
)
_, calls = self.parse(text)
self.assertEqual(
calls,
[("get_weather", {"city": "Paris"}), ("get_weather", {"city": "Tokyo"})],
)
self.assert_streaming_matches(text)
def test_tool_call_then_final_answer(self):
text = (
f" to=self<|message|>r<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>"
f"{atem(DOUBLED, city='Paris')}<|eom|>"
f"<|start|>assistant to=user<|message|>It is sunny."
)
normal, calls = self.parse(text)
self.assertEqual(calls, [("get_weather", {"city": "Paris"})])
self.assertIn("It is sunny.", normal)
self.assert_streaming_matches(text)
def test_namespaced_name_passes_through(self):
tools = [
Tool(
type="function",
function=Function(
name="weather.get",
description="d",
parameters={"type": "object", "properties": {}},
),
)
]
text = (
f" to=self<|message|>r<|eom|><|start|>assistant to=weather.get<|message|>"
f"{atem('weather.get', city='Paris')}"
)
result = MuseGlimmerDetector().detect_and_parse(text, tools)
self.assertEqual([c.name for c in result.calls], ["weather.get"])
def test_parameter_value_typing(self):
"""``allow_non_json: True`` — JSON literals decode, bare strings do not."""
invoke = (
'<atem:function_calls>\n<atem:invoke name="get_weather">\n'
'<atem:parameter name="s">hello world</atem:parameter>\n'
'<atem:parameter name="i">42</atem:parameter>\n'
'<atem:parameter name="b">true</atem:parameter>\n'
'<atem:parameter name="n">null</atem:parameter>\n'
'<atem:parameter name="o">{"a": 1}</atem:parameter>\n'
'<atem:parameter name="l">[1, 2]</atem:parameter>\n'
"</atem:invoke>\n</atem:function_calls>"
)
text = f"<|start|>assistant to=get_weather<|message|>{invoke}"
_, calls = self.parse(text)
self.assertEqual(
calls[0][1],
{
"s": "hello world",
"i": 42,
"b": True,
"n": None,
"o": {"a": 1},
"l": [1, 2],
},
)
def test_multiline_parameter_value(self):
value = 'line1\nline2\n"quoted"\n'
text = (
f"<|start|>assistant to=get_weather<|message|>"
f'<atem:function_calls>\n<atem:invoke name="get_weather">\n'
f'<atem:parameter name="code">{value}</atem:parameter>\n'
f"</atem:invoke>\n</atem:function_calls>"
)
_, calls = self.parse(text)
self.assertEqual(calls[0][1], {"code": value})
self.assert_streaming_matches(text)
# ---- channel scoping (safety) -----------------------------------------
def test_invoke_inside_reasoning_is_not_a_call(self):
text = (
f" to=self<|message|>Maybe I call {atem(DOUBLED, city='X')} — no.<|eom|>"
f"<|start|>assistant to=user<|message|>I will not call it."
)
_, calls = self.parse(text)
self.assertEqual(calls, [])
self.assert_streaming_matches(text)
def test_invoke_inside_final_answer_is_not_a_call(self):
text = (
f" to=self<|message|>r<|eom|><|start|>assistant to=user<|message|>"
f"You would write:\n{atem(DOUBLED, city='X')}"
)
_, calls = self.parse(text)
self.assertEqual(calls, [])
self.assert_streaming_matches(text)
def test_invoke_inside_truncated_reasoning_is_not_a_call(self):
"""Generation cut mid-CoT leaves no closing ``<|eom|>`` to anchor on."""
text = f" to=self<|message|>I could call {atem(DOUBLED, city='X')} but"
_, calls = self.parse(text)
self.assertEqual(calls, [])
self.assert_streaming_matches(text)
def test_truncated_tool_channel_drops_partial_invoke(self):
"""A token cap mid-invoke must not fabricate a call from partial
arguments, and ATEM scaffolding must not leak into content."""
text = (
f" to=self<|message|>Need weather.<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>"
f'<atem:function_calls>\n<atem:invoke name="{DOUBLED}">\n'
f'<atem:parameter name="city">Par'
)
normal, calls = self.parse(text)
self.assertEqual(calls, [])
self.assertEqual(normal, "Need weather.")
self.assert_streaming_matches(text)
def test_prose_opening_with_to_equals_does_not_stall(self):
"""A bare ``to=...`` header opens the stream, so prose that happens to
start the same way is ambiguous. Mis-reading it as a header parks the
parser waiting for a ``<|message|>`` that never arrives and strands the
whole response in the buffer."""
for text in ("to=x is the syntax.", "to= is an assignment", "to=a<b is false"):
detector = MuseGlimmerDetector()
streamed = "".join(
detector.parse_streaming_increment(ch, self.tools).normal_text
for ch in text
)
self.assertEqual(streamed, text)
self.assertEqual(detector._buffer, "", "text stranded in the buffer")
def test_unframed_atem_is_content_not_a_call(self):
"""Deliberately stricter than the vendor; see ``_is_tool_channel``."""
text = atem(DOUBLED, city="Paris")
normal, calls = self.parse(text)
self.assertEqual(calls, [])
self.assertEqual(normal, text)
# ---- integration with the reasoning parser ----------------------------
def test_pipeline_with_reasoning_parser(self):
"""The real serving order: reasoning parser first, then this detector."""
raw = (
f" to=self<|message|>Need weather.<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>"
f"{atem(DOUBLED, city='Paris')}<|eom|>"
f"<|start|>assistant to=user<|message|>It is sunny in Paris."
)
reasoning, remainder = ReasoningParser("muse").parse_non_stream(raw)
content, calls = FunctionCallParser(self.tools, "muse").parse_non_stream(
remainder
)
self.assertEqual(reasoning, "Need weather.")
self.assertEqual(content, "It is sunny in Paris.")
self.assertEqual(
[(c.name, json.loads(c.parameters)) for c in calls],
[("get_weather", {"city": "Paris"})],
)
def test_pipeline_quoted_invoke_stays_content(self):
"""A quoted ATEM block must survive as text, not become a call."""
raw = (
f" to=self<|message|>r<|eom|><|start|>assistant to=user<|message|>"
f"Example:\n{atem(DOUBLED, city='X')}"
)
_, remainder = ReasoningParser("muse").parse_non_stream(raw)
content, calls = FunctionCallParser(self.tools, "muse").parse_non_stream(
remainder
)
self.assertEqual(calls, [])
self.assertIn("<atem:invoke", content)
def pipeline_stream(self, raw, chunk_size, tool_call_parser_active=True):
"""The real streaming order: reasoning deltas feed the tool parser."""
rp = ReasoningParser("muse", tool_call_parser_active=tool_call_parser_active)
fp = FunctionCallParser(self.tools, "muse")
reasoning_parts, content_parts, calls = [], [], []
chunks = [raw[i : i + chunk_size] for i in range(0, len(raw), chunk_size)]
for i, chunk in enumerate(chunks):
reasoning, normal = rp.parse_stream_chunk(chunk)
if i == len(chunks) - 1:
end_reasoning, end_normal = rp.parse_stream_end()
reasoning = (reasoning or "") + (end_reasoning or "")
normal = (normal or "") + (end_normal or "")
if reasoning:
reasoning_parts.append(reasoning)
if normal:
content, chunk_calls = fp.parse_stream_chunk(normal)
content_parts.append(content)
calls.extend(
(c.name, json.loads(c.parameters)) for c in chunk_calls if c.name
)
end_content, end_calls = fp.parse_stream_end()
content_parts.append(end_content)
calls.extend((c.name, json.loads(c.parameters)) for c in end_calls if c.name)
return "".join(reasoning_parts), "".join(content_parts), calls
def test_pipeline_quoted_header_in_answer_is_not_a_call(self):
"""The answer keeps its channel framing on the way to this detector, so
a quoted header inside it stays inside the ``to=user`` body. Goes red if
the reasoning parser unwraps the answer before the tool parser runs, or
if ``detect_and_parse`` regains a scan-ahead ``<|start|>`` search."""
for quoted_at in ("after prose:\n", ""):
raw = (
f" to=self<|message|>r<|eom|><|start|>assistant to=user<|message|>"
f"{quoted_at}<|start|>assistant to={DOUBLED}<|message|>"
f"{atem(DOUBLED, city='X')}"
)
_, remainder = ReasoningParser(
"muse", tool_call_parser_active=True
).parse_non_stream(raw)
content, calls = FunctionCallParser(self.tools, "muse").parse_non_stream(
remainder
)
self.assertEqual(
calls, [], f"quoted header parsed as a call ({quoted_at!r})"
)
self.assertIn("<atem:invoke", content)
for chunk_size in (1, 7, 100):
_, s_content, s_calls = self.pipeline_stream(raw, chunk_size)
self.assertEqual(s_calls, [])
self.assertIn("<atem:invoke", s_content)
def test_pipeline_preamble_before_tool_call_streams(self):
"""A real ``to=user`` message may precede the tool channel; its
terminator is what re-arms the header state. Goes red if the hand-off
drops the ``to=user`` terminator again."""
raw = (
f" to=self<|message|>r<|eom|><|start|>assistant to=user<|message|>"
f"Let me check.<|eom|><|start|>assistant to={DOUBLED}<|message|>"
f"{atem(DOUBLED, city='Paris')}"
)
want_calls = [("get_weather", {"city": "Paris"})]
_, remainder = ReasoningParser(
"muse", tool_call_parser_active=True
).parse_non_stream(raw)
content, calls = FunctionCallParser(self.tools, "muse").parse_non_stream(
remainder
)
self.assertEqual(
[(c.name, json.loads(c.parameters)) for c in calls], want_calls
)
self.assertIn("Let me check.", content)
for chunk_size in (1, 7, 100):
_, s_content, s_calls = self.pipeline_stream(raw, chunk_size)
self.assertEqual(s_calls, want_calls, f"chunk_size={chunk_size}")
self.assertIn("Let me check.", s_content)
def test_pipeline_plain_answer_stays_clean_without_tool_parse(self):
"""Serving skips the tool detector when ``has_tool_call()`` is false, so
a turn with no ATEM block must come out of the reasoning parser already
unwrapped. Goes red if framing is preserved unconditionally."""
raw = (
" to=self<|message|>r<|eom|>"
"<|start|>assistant to=user<|message|>Hello there."
)
reasoning, remainder = ReasoningParser(
"muse", tool_call_parser_active=True
).parse_non_stream(raw)
self.assertFalse(
FunctionCallParser(self.tools, "muse").has_tool_call(remainder)
)
self.assertEqual(reasoning, "r")
self.assertEqual(remainder, "Hello there.")
def test_reasoning_finish_flushes_unframed_text(self):
"""An unframed turn never emits ``<|message|>``, so nothing leaves the
buffer until the end-of-stream flush. Goes red if the Muse Glimmer reasoning
detector loses its ``finish()`` override."""
rp = ReasoningParser("muse")
_, streamed = rp.parse_stream_chunk("Just plain text.")
_, flushed = rp.parse_stream_end()
self.assertEqual((streamed or "") + (flushed or ""), "Just plain text.")
def test_interleaved_reasoning_blocks_join_with_newline(self):
"""A turn may reason, call a tool, then reason again; the reference
schema joins the blocks with a newline. Goes red if the reasoning
detector concatenates the bodies directly, gluing two thoughts into
one word ("...thoughtsecond...")."""
raw = (
f" to=self<|message|>first thought<|eom|>"
f"<|start|>assistant to={DOUBLED}<|message|>"
f"{atem(DOUBLED, city='Paris')}<|eom|>"
f"<|start|>assistant to=self<|message|>second thought<|eom|>"
f"<|start|>assistant to=user<|message|>done"
)
reasoning, _ = ReasoningParser(
"muse", tool_call_parser_active=True
).parse_non_stream(raw)
self.assertEqual(reasoning, "first thought\nsecond thought")
for chunk_size in (1, 7, 100):
s_reasoning, s_content, s_calls = self.pipeline_stream(raw, chunk_size)
self.assertEqual(s_reasoning, "first thought\nsecond thought")
self.assertEqual(s_calls, [("get_weather", {"city": "Paris"})])
self.assertIn("done", s_content)
def test_stream_end_flushes_partial_marker(self):
"""An answer ending in a marker prefix (``<|st``) is held back while
streaming in case it grows into ``<|start|>``; the stream's end proves
it never will. Goes red if the tool parser loses its stream-end flush
(``parse_stream_end`` / detector ``finish``)."""
raw = (
" to=self<|message|>r<|eom|>"
"<|start|>assistant to=user<|message|>answer<|st"
)
for chunk_size in (1, 7, 100):
_, content, calls = self.pipeline_stream(raw, chunk_size)
self.assertEqual(calls, [])
self.assertEqual(content, "answer<|st", f"chunk_size={chunk_size}")
def test_whitespace_before_header_is_tolerated(self):
"""The model may put whitespace between ``<|eom|>`` and the next
``<|start|>``; it travels with the header text. Goes red if the header
state requires ``<|start|>`` at exactly the first byte again."""
text = (
f" to=self<|message|>r<|eom|>\n<|start|>assistant to={DOUBLED}"
f"<|message|>{atem(DOUBLED, city='Paris')}"
)
_, calls = self.parse(text)
self.assertEqual(calls, [("get_weather", {"city": "Paris"})])
self.assert_streaming_matches(text)
def test_registered_in_parser_enum(self):
self.assertIs(
FunctionCallParser.ToolCallParserEnum["muse"], MuseGlimmerDetector
)
if __name__ == "__main__":
import unittest
unittest.main()
@@ -0,0 +1,186 @@
"""Unit tests for the MLX remote-code gate.
mlx-lm executes ``config.json``'s ``model_file`` unconditionally at load
time, so SGLang refuses such checkpoints before any checkpoint Python runs
unless the server was started with ``--trust-remote-code``. The refusal
tests prove non-execution with a sentinel ``model_file`` whose import would
leave an observable marker.
"""
from __future__ import annotations
import importlib.util
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from sglang.test.ci.ci_register import register_cpu_ci, register_mlx_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
register_mlx_ci(est_time=5, suite="stage-a-unit-test-mlx")
_HAS_MLX = (
importlib.util.find_spec("mlx") is not None
and importlib.util.find_spec("mlx_lm") is not None
)
from sglang.srt.hardware_backend.mlx.remote_code_gate import ( # noqa: E402
RemoteCodeGateError,
ensure_remote_code_allowed,
)
_SENTINEL = "GATE FAILED: checkpoint python executed"
def _make_checkpoint(tmp: Path, config: dict, *, with_sentinel: bool = True) -> Path:
(tmp / "config.json").write_text(json.dumps(config))
if with_sentinel:
# Importing this file would create marker.txt — the refusal tests
# assert it never appears.
(tmp / "evil.py").write_text(
"from pathlib import Path\n"
f"Path(__file__).parent.joinpath('marker.txt').write_text({_SENTINEL!r})\n"
)
return tmp
class TestEnsureRemoteCodeAllowed(CustomTestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.dir = Path(self._tmp.name)
def tearDown(self):
self._tmp.cleanup()
def _assert_sentinel_not_executed(self):
self.assertFalse(
(self.dir / "marker.txt").exists(),
"checkpoint python executed despite gate refusal",
)
def test_refuses_model_file_without_trust(self):
_make_checkpoint(
self.dir, {"model_type": "muse_glimmer", "model_file": "evil.py"}
)
with self.assertRaisesRegex(RemoteCodeGateError, "--trust-remote-code"):
ensure_remote_code_allowed(self.dir, trust_remote_code=False)
self._assert_sentinel_not_executed()
def test_allows_model_file_with_trust(self):
_make_checkpoint(
self.dir, {"model_type": "muse_glimmer", "model_file": "evil.py"}
)
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
# The gate itself never imports the file either way.
self._assert_sentinel_not_executed()
def test_builtin_checkpoint_passes_without_trust(self):
_make_checkpoint(self.dir, {"model_type": "qwen3"}, with_sentinel=False)
ensure_remote_code_allowed(self.dir, trust_remote_code=False)
def test_missing_config_rejected(self):
with self.assertRaisesRegex(RemoteCodeGateError, "no config.json"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
def test_malformed_config_rejected(self):
(self.dir / "config.json").write_text("{not json")
with self.assertRaisesRegex(RemoteCodeGateError, "not valid JSON"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
def test_non_object_config_rejected(self):
(self.dir / "config.json").write_text('["a", "b"]')
with self.assertRaisesRegex(RemoteCodeGateError, "JSON object"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
def test_missing_model_file_target_rejected(self):
_make_checkpoint(
self.dir,
{"model_type": "muse_glimmer", "model_file": "nope.py"},
with_sentinel=False,
)
with self.assertRaisesRegex(RemoteCodeGateError, "does not exist"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
def test_absolute_model_file_rejected(self):
_make_checkpoint(
self.dir,
{"model_type": "muse_glimmer", "model_file": "/etc/anything.py"},
with_sentinel=False,
)
with self.assertRaisesRegex(RemoteCodeGateError, "relative path"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
def test_traversal_model_file_rejected(self):
_make_checkpoint(
self.dir,
{"model_type": "muse_glimmer", "model_file": "../outside.py"},
with_sentinel=False,
)
with self.assertRaisesRegex(RemoteCodeGateError, "relative path"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
def test_non_string_model_file_rejected(self):
_make_checkpoint(
self.dir,
{"model_type": "muse_glimmer", "model_file": 42},
with_sentinel=False,
)
with self.assertRaisesRegex(RemoteCodeGateError, "non-string"):
ensure_remote_code_allowed(self.dir, trust_remote_code=True)
@unittest.skipUnless(_HAS_MLX, "requires mlx + mlx_lm")
class TestModelRunnerGateWiring(CustomTestCase):
"""The runner must gate BEFORE calling mlx_lm's loader, on the same
resolved directory it then loads from."""
class _StopInit(Exception):
pass
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.dir = Path(self._tmp.name)
def tearDown(self):
self._tmp.cleanup()
def test_refusal_precedes_loader_call(self):
from sglang.srt.hardware_backend.mlx.model_runner import MlxModelRunner
_make_checkpoint(
self.dir, {"model_type": "muse_glimmer", "model_file": "evil.py"}
)
with patch(
"sglang.srt.hardware_backend.mlx.model_runner.mlx_lm_load"
) as loader:
with self.assertRaisesRegex(RemoteCodeGateError, "--trust-remote-code"):
MlxModelRunner(model_path=str(self.dir), trust_remote_code=False)
loader.assert_not_called()
self.assertFalse((self.dir / "marker.txt").exists())
def test_trusted_load_uses_resolved_directory(self):
from sglang.srt.hardware_backend.mlx.model_runner import MlxModelRunner
_make_checkpoint(
self.dir, {"model_type": "muse_glimmer", "model_file": "evil.py"}
)
with patch(
"sglang.srt.hardware_backend.mlx.model_runner.mlx_lm_load",
side_effect=self._StopInit,
) as loader:
with self.assertRaises(self._StopInit):
MlxModelRunner(model_path=str(self.dir), trust_remote_code=True)
loader.assert_called_once()
called_path = loader.call_args.args[0]
self.assertEqual(
Path(called_path).resolve(),
self.dir.resolve(),
"loader must receive the same directory the gate inspected",
)
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -0,0 +1,278 @@
"""Unit tests for the Muse Glimmer MLX model file's load path — no weights, no server.
End-to-end coverage needs the private packaged artifact, so the checkpoint-schema
logic is pinned here with tiny synthetic weights instead:
1. ``flatten_rc_config`` — RC nested-config translation, including the two
convention conversions (qk_scale_factor gains sqrt(head_dim); NoPE layers
come from zeros in ``layer_rope_theta``).
2. ``ModelArgs`` validation — derived ``no_rope_layers``/``layer_types``,
rejection of inconsistent or malformed lists, format-version check.
3. ``sanitize`` — all three accepted weight layouts (raw HF, RC multimodal,
packaged) plus rejection of incomplete or mislabeled checkpoints. The
positional RC norm renames and the per-head q/gate interleave are verified
numerically, since getting either silently wrong still yields a model that
runs but computes garbage.
"""
from __future__ import annotations
import importlib.util
import unittest
from sglang.test.ci.ci_register import register_cpu_ci, register_mlx_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=6, suite="base-a-test-cpu")
register_mlx_ci(est_time=6, suite="stage-a-unit-test-mlx")
_HAS_MLX = (
importlib.util.find_spec("mlx") is not None
and importlib.util.find_spec("mlx_lm") is not None
)
_SKIP_REASON = "requires mlx + mlx_lm"
if _HAS_MLX:
import mlx.core as mx
from sglang.srt.hardware_backend.mlx.models.muse_glimmer_mlx import (
Model,
ModelArgs,
flatten_rc_config,
)
# Tiny architecture: 4 layers so the derived NoPE pattern (last layer NoPE
# with every_n_layers_nope=4) exercises both layer types.
_TINY = dict(
hidden_size=8,
num_hidden_layers=4,
num_attention_heads=2,
num_key_value_heads=1,
head_dim=4,
intermediate_size=16,
vocab_size=32,
every_n_layers_nope=4,
sliding_window=4,
max_position_embeddings=64,
)
_RC_TEXT_CONFIG = dict(
hidden_size=8,
num_hidden_layers=4,
num_attention_heads=2,
num_key_value_heads=1,
head_dim=4,
intermediate_size=16,
vocab_size=32,
rms_norm_eps=1e-5,
post_norm_eps=1e-8,
max_position_embeddings=64,
qk_scale_factor=0.5,
output_multiplier=0.2,
final_logit_softcapping=20.0,
sliding_window=4,
layer_rope_theta=[500000.0, 500000.0, 500000.0, 0],
)
def _raw_weights(args):
"""A complete raw HF export with deterministic values."""
mx.random.seed(0)
H, D, hid = args.num_attention_heads, args.head_dim, args.hidden_size
kv = args.num_key_value_heads * D
weights = {
"model.embed_tokens.weight": mx.random.normal((args.vocab_size, hid)),
"model.norm.weight": mx.random.normal((hid,)),
"lm_head.weight": mx.random.normal((args.vocab_size, hid)),
}
for i in range(args.num_hidden_layers):
p = f"model.layers.{i}."
weights.update(
{
p + "self_attn.q_proj.weight": mx.random.normal((H * D, hid)),
p + "self_attn.k_proj.weight": mx.random.normal((kv, hid)),
p + "self_attn.v_proj.weight": mx.random.normal((kv, hid)),
p + "self_attn.o_proj.weight": mx.random.normal((hid, H * D)),
p + "self_attn.output_gate_proj.weight": mx.random.normal((H * D, hid)),
p + "input_layernorm.weight": mx.full((hid,), 0.10),
p + "post_attn_norm.weight": mx.full((hid,), 0.20),
p + "post_attention_layernorm.weight": mx.full((hid,), 0.30),
p + "post_ffn_norm.weight": mx.full((hid,), 0.40),
p
+ "mlp.gate_proj.weight": mx.random.normal(
(args.intermediate_size, hid)
),
p
+ "mlp.up_proj.weight": mx.random.normal((args.intermediate_size, hid)),
p
+ "mlp.down_proj.weight": mx.random.normal(
(hid, args.intermediate_size)
),
}
)
return weights
def _rc_weights(args):
"""The same export in the RC multimodal layout (nested prefix, RC names,
a vision tower to be dropped, no output-gate fusion)."""
raw = _raw_weights(args)
rc = {}
renames = {
"self_attn.output_gate_proj.weight": "self_attn.gate_proj.weight",
"post_attn_norm.weight": "post_attention_layernorm.weight",
"post_attention_layernorm.weight": "pre_feedforward_layernorm.weight",
"post_ffn_norm.weight": "post_feedforward_layernorm.weight",
}
for name, w in raw.items():
if not name.startswith("model."):
rc[name] = w # lm_head stays top-level in the RC layout too
continue
rest = name[len("model.") :]
for raw_suffix, rc_suffix in renames.items():
if rest.endswith(raw_suffix):
rest = rest[: -len(raw_suffix)] + rc_suffix
break
rc["model.language_model." + rest] = w
rc["model.vision_tower.patch_embed.weight"] = mx.zeros((4, 4))
return rc
@unittest.skipUnless(_HAS_MLX, _SKIP_REASON)
class TestFlattenRcConfig(CustomTestCase):
def test_field_mapping_and_conversions(self):
flat = flatten_rc_config({"text_config": dict(_RC_TEXT_CONFIG)})
self.assertEqual(flat["model_type"], "muse_glimmer")
# RC qk_scale_factor is against SDPA's 1/sqrt(head_dim).
self.assertAlmostEqual(flat["qk_scale_factor"], 0.5 * 4**0.5)
self.assertEqual(flat["output_soft_cap_temp"], 20.0)
# The current vendor export stores q/k in the NeoX rotary
# layout; the RC path always reads that convention.
self.assertIs(flat["rope_is_neox_style"], True)
# normalize_tok_embeddings must stay at the ModelArgs default (True):
# the 20260806 export ships the raw table (needs the runtime norm),
# and on older baked-table exports the scaleless RMS norm is
# idempotent, so always-on covers both generations.
self.assertNotIn("normalize_tok_embeddings", flat)
self.assertIs(ModelArgs.normalize_tok_embeddings, True)
# Zeros in layer_rope_theta mark NoPE layers.
self.assertEqual(flat["no_rope_layers"], [1, 1, 1, 0])
def test_non_silu_activation_rejected(self):
cfg = dict(_RC_TEXT_CONFIG, hidden_activation="gelu")
with self.assertRaisesRegex(ValueError, "silu"):
flatten_rc_config({"text_config": cfg})
def test_model_args_from_dict_accepts_rc_schema(self):
args = ModelArgs.from_dict({"text_config": dict(_RC_TEXT_CONFIG)})
self.assertEqual(args.no_rope_layers, [1, 1, 1, 0])
self.assertEqual(
args.layer_types,
["sliding_attention"] * 3 + ["full_attention"],
)
@unittest.skipUnless(_HAS_MLX, _SKIP_REASON)
class TestModelArgsValidation(CustomTestCase):
def test_derives_nope_and_layer_types(self):
args = ModelArgs(**_TINY)
self.assertEqual(args.no_rope_layers, [1, 1, 1, 0])
self.assertEqual(
args.layer_types,
["sliding_attention"] * 3 + ["full_attention"],
)
def test_layer_types_must_match_no_rope_layers(self):
with self.assertRaisesRegex(ValueError, "disagrees"):
ModelArgs(**_TINY, layer_types=["full_attention"] * 4)
def test_wrong_length_no_rope_layers_rejected(self):
with self.assertRaisesRegex(ValueError, "entries"):
ModelArgs(**_TINY, no_rope_layers=[1, 0])
def test_non_binary_no_rope_flags_rejected(self):
with self.assertRaisesRegex(ValueError, "non-binary"):
ModelArgs(**_TINY, no_rope_layers=[1, 1, 2, 0])
def test_unknown_format_version_rejected(self):
with self.assertRaisesRegex(ValueError, "muse_glimmer_mlx_format"):
ModelArgs(**_TINY, muse_glimmer_mlx_format=99)
@unittest.skipUnless(_HAS_MLX, _SKIP_REASON)
class TestSanitize(CustomTestCase):
def _model(self, **overrides):
return Model(ModelArgs(**dict(_TINY, **overrides)))
def test_raw_export_folds_norms_and_fuses_gate(self):
model = self._model()
raw = _raw_weights(model.args)
out = model.sanitize(dict(raw))
# Offset norms gain +1.0; the final norm does not.
norm = out["model.layers.0.input_layernorm.weight"]
self.assertTrue(mx.allclose(norm, mx.full(norm.shape, 1.10)))
self.assertTrue(mx.allclose(out["model.norm.weight"], raw["model.norm.weight"]))
# q/gate interleave is per-head [q_head; gate_head].
H, D = model.args.num_attention_heads, model.args.head_dim
hid = model.args.hidden_size
fused = out["model.layers.0.self_attn.q_proj.weight"]
self.assertEqual(tuple(fused.shape), (2 * H * D, hid))
per_head = fused.reshape(H, 2 * D, hid)
q = raw["model.layers.0.self_attn.q_proj.weight"].reshape(H, D, hid)
g = raw["model.layers.0.self_attn.output_gate_proj.weight"].reshape(H, D, hid)
self.assertTrue(mx.allclose(per_head[:, :D, :], q))
self.assertTrue(mx.allclose(per_head[:, D:, :], g))
self.assertNotIn("model.layers.0.self_attn.output_gate_proj.weight", out)
def test_sanitized_raw_weights_load_and_forward(self):
model = self._model()
out = model.sanitize(_raw_weights(model.args))
model.load_weights(list(out.items()))
logits = model(mx.array([[1, 2, 3]], dtype=mx.int32), cache=model.make_cache())
self.assertEqual(tuple(logits.shape), (1, 3, model.args.vocab_size))
self.assertTrue(bool(mx.all(mx.isfinite(logits))))
def test_rc_layout_positional_renames(self):
model = self._model()
out = model.sanitize(_rc_weights(model.args))
# Distinct per-norm constants prove each RC name landed in its
# positional slot (+1 folded): RC post_attention_layernorm ->
# raw post_attn_norm (0.20), RC pre_feedforward_layernorm ->
# raw post_attention_layernorm (0.30).
for raw_name, value in (
("input_layernorm", 1.10),
("post_attn_norm", 1.20),
("post_attention_layernorm", 1.30),
("post_ffn_norm", 1.40),
):
w = out[f"model.layers.0.{raw_name}.weight"]
self.assertTrue(
mx.allclose(w, mx.full(w.shape, value)),
f"{raw_name} expected {value}",
)
self.assertFalse(any("vision" in k for k in out))
self.assertFalse(any("language_model" in k for k in out))
def test_packaged_artifact_passes_through(self):
model = self._model(muse_glimmer_mlx_format=1)
packaged = {"model.embed_tokens.weight": mx.zeros((32, 8))}
self.assertIs(model.sanitize(packaged), packaged)
def test_packaged_marker_with_raw_keys_rejected(self):
model = self._model(muse_glimmer_mlx_format=1)
raw = _raw_weights(ModelArgs(**_TINY))
with self.assertRaisesRegex(ValueError, "raw-checkpoint keys"):
model.sanitize(raw)
def test_incomplete_raw_checkpoint_rejected(self):
model = self._model()
raw = _raw_weights(model.args)
del raw["model.layers.0.mlp.up_proj.weight"]
with self.assertRaisesRegex(ValueError, "missing"):
model.sanitize(raw)
if __name__ == "__main__":
unittest.main()
@@ -75,7 +75,7 @@ class TestKVCacheQuantRegistry(CustomTestCase):
from sglang.srt.runtime_context import get_context
runner = object.__new__(ModelRunner)
runner.server_args = SimpleNamespace()
runner.server_args = SimpleNamespace(speculative_draft_kv_cache_dtype=None)
runner.draft_attention_backend = None
# The runner reads the requested dtype off the model bag, so the double
# publishes it rather than carrying it on a stand-in config.
@@ -89,6 +89,7 @@ class TestModelOverridableWhitelist(CustomTestCase):
"decode_attention_backend",
"flashinfer_allreduce_fusion_backend",
"fp8_gemm_runner_backend",
"fp4_gemm_runner_backend",
"disable_custom_all_reduce",
"enable_aiter_allreduce_fusion",
"enable_symm_mem",