Convert IPC dataclasses to msgspec.Struct with opt-in msgpack transport (#28688)

Co-authored-by: Lianmin Zheng <lianminzheng@gmail.com>
This commit is contained in:
Rain Jiang
2026-06-26 12:04:03 -07:00
committed by GitHub
co-authored by Lianmin Zheng
parent 714011a40f
commit be1930133a
25 changed files with 694 additions and 354 deletions
+18 -4
View File
@@ -31,6 +31,8 @@ from sglang.test.test_utils import (
register_cuda_ci(est_time=134, stage="base-b", runner_config="1-gpu-small")
register_amd_ci(est_time=130, suite="stage-b-test-1-gpu-small-amd")
SERVER_ENV = {"SGLANG_USE_PICKLE_IPC": "0"}
class TestSRTEndpoint(CustomTestCase):
@classmethod
@@ -41,6 +43,7 @@ class TestSRTEndpoint(CustomTestCase):
cls.model,
cls.base_url,
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
env=SERVER_ENV,
other_args=(
"--enable-custom-logit-processor",
"--mem-fraction-static",
@@ -469,6 +472,12 @@ class TestSRTEndpoint(CustomTestCase):
response = requests.post(self.base_url + "/flush_cache")
assert response.status_code == 200
server_info = requests.get(self.base_url + "/server_info").json()
page_size = server_info.get("page_size") or 1
def align_down(num_tokens):
return num_tokens // page_size * page_size
def send_and_check_cached_tokens(input_ids):
response = requests.post(
self.base_url + "/generate",
@@ -483,10 +492,14 @@ class TestSRTEndpoint(CustomTestCase):
return response_json["meta_info"]["cached_tokens"]
self.assertEqual(send_and_check_cached_tokens(range(0, 100)), 0)
self.assertEqual(send_and_check_cached_tokens(range(0, 10000)), 100)
self.assertEqual(send_and_check_cached_tokens(range(0, 10000)), 9999)
self.assertEqual(send_and_check_cached_tokens(range(0, 1000)), 999)
self.assertEqual(send_and_check_cached_tokens(range(0, 11000)), 10000)
self.assertEqual(send_and_check_cached_tokens(range(0, 10000)), align_down(100))
self.assertEqual(
send_and_check_cached_tokens(range(0, 10000)), align_down(9999)
)
self.assertEqual(send_and_check_cached_tokens(range(0, 1000)), align_down(999))
self.assertEqual(
send_and_check_cached_tokens(range(0, 11000)), align_down(10000)
)
def test_get_server_info(self):
response = requests.get(self.base_url + "/server_info")
@@ -648,6 +661,7 @@ class TestTokenizeDetokenize(CustomTestCase):
cls.model,
cls.base_url,
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
env=SERVER_ENV,
)
cls.tokenizer = get_tokenizer(cls.model)
@@ -22,10 +22,12 @@ Current coverage:
import asyncio
import dataclasses
import json
import unittest
from types import SimpleNamespace
from sglang.srt.entrypoints import http_server
from sglang.srt.lora.lora_registry import LoRARef
from sglang.srt.server_args import ServerArgs
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
@@ -33,7 +35,9 @@ from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
def _call_server_info_with(server_args: ServerArgs) -> dict:
def _call_server_info_with(
server_args: ServerArgs, internal_states: list[dict] | None = None
) -> dict:
"""Invoke `http_server.server_info()` against a stub global state.
Bypasses the FastAPI HTTP layer (no TestClient): the handler is an
@@ -44,7 +48,7 @@ def _call_server_info_with(server_args: ServerArgs) -> dict:
"""
async def _fake_internal_state():
return [{"max_req_input_len": 1024}]
return internal_states or [{"max_req_input_len": 1024}]
stub_state = SimpleNamespace(
tokenizer_manager=SimpleNamespace(
@@ -291,6 +295,31 @@ class TestServerInfoExistingFieldsPreserved(CustomTestCase):
self.assertIn("kv_events", info)
self.assertIsNotNone(info["kv_events"])
def test_lora_refs_are_json_serializable_dicts(self):
lora_ref = LoRARef(
lora_id="lora-id",
lora_name="adapter",
lora_path="/tmp/adapter",
pinned=True,
)
args = ServerArgs(model_path="dummy")
args.lora_paths = [lora_ref]
info = _call_server_info_with(
args,
internal_states=[{"lora_paths": [lora_ref]}],
)
expected = {
"lora_id": "lora-id",
"lora_name": "adapter",
"lora_path": "/tmp/adapter",
"pinned": True,
}
self.assertEqual(info["lora_paths"], [expected])
self.assertEqual(info["internal_states"][0]["lora_paths"], [expected])
json.dumps(info)
if __name__ == "__main__":
unittest.main()
@@ -13,10 +13,11 @@ Covers:
"""
import asyncio
import dataclasses
import unittest
from unittest.mock import AsyncMock, MagicMock, Mock
import msgspec
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase, maybe_stub_sgl_kernel
@@ -35,7 +36,7 @@ _NOT_FINISHED = object() # Sentinel: request has not finished yet
# Categorised by value shape so that _make_batch_str_output can assign
# type-appropriate defaults without hardcoding every field name.
# When a field is renamed upstream, the old name simply won't appear in
# dataclasses.fields() and the new name will fall through to the
# msgspec.structs.fields() and the new name will fall through to the
# pattern-matching or safe fallback — no test breakage.
# ---------------------------------------------------------------------------
@@ -145,7 +146,7 @@ def _make_abort_req(rid: str, abort_message: str = "Aborted") -> AbortReq:
def _make_batch_str_output(rid: str, finished_reason=None) -> BatchStrOutput:
"""Create a minimal BatchStrOutput for a single request.
Uses dataclass field introspection so that new or renamed fields in
Uses struct field introspection so that new or renamed fields in
BatchStrOutput don't break this test. Only the fields that matter for
test logic (rids, finished_reasons, output_strs) are set explicitly;
all others receive type-appropriate defaults based on naming patterns.
@@ -159,7 +160,7 @@ def _make_batch_str_output(rid: str, finished_reason=None) -> BatchStrOutput:
fr = finished_reason
kwargs = {}
for f in dataclasses.fields(BatchStrOutput):
for f in msgspec.structs.fields(BatchStrOutput):
if f.name == "rids":
kwargs[f.name] = [rid]
elif f.name == "finished_reasons":
@@ -176,8 +177,8 @@ def _make_batch_str_output(rid: str, finished_reason=None) -> BatchStrOutput:
kwargs[f.name] = [None]
# Fields with class defaults — skip, let the default be used
elif (
f.default is not dataclasses.MISSING
or f.default_factory is not dataclasses.MISSING
f.default is not msgspec.NODEFAULT
or f.default_factory is not msgspec.NODEFAULT
):
continue
# Unknown required field — provide a safe per-request default.