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:
co-authored by
Lianmin Zheng
parent
714011a40f
commit
be1930133a
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user