sgl-project--sglang
94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
418 行
14 KiB
Python
418 行
14 KiB
Python
"""Unit tests for LoadSnapshot SHM and ZMQ backends."""
|
|
|
|
import os
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
|
|
from sglang.srt.managers.load_snapshot import (
|
|
LoadSnapshot,
|
|
ShmLoadSnapshotReader,
|
|
ShmLoadSnapshotWriter,
|
|
ZmqLoadSnapshotWriter,
|
|
ZmqShmLoadSnapshotReader,
|
|
_zmq_addr_for,
|
|
create_load_snapshot_reader,
|
|
create_load_snapshot_writer,
|
|
should_use_zmq,
|
|
zmq_reader_owner,
|
|
)
|
|
from sglang.test.ci.ci_register import register_cpu_ci
|
|
from sglang.test.test_utils import CustomTestCase, maybe_stub_sgl_kernel
|
|
|
|
maybe_stub_sgl_kernel()
|
|
|
|
|
|
register_cpu_ci(est_time=15, suite="base-a-test-cpu")
|
|
|
|
|
|
def _temp_path() -> str:
|
|
fd, path = tempfile.mkstemp()
|
|
os.close(fd)
|
|
os.unlink(path)
|
|
return path
|
|
|
|
|
|
def _ipc_addr() -> str:
|
|
fd, path = tempfile.mkstemp(prefix="sglang_test_zmq_", suffix=".sock")
|
|
os.close(fd)
|
|
os.unlink(path)
|
|
return f"ipc://{path}"
|
|
|
|
|
|
def _warmup_zmq(writers, reader, attempts=20, interval=0.05):
|
|
"""Send warmup messages until the reader receives from all writers."""
|
|
expected = {w.dp_rank for w in writers}
|
|
received = set()
|
|
for _ in range(attempts):
|
|
for w in writers:
|
|
w.write(LoadSnapshot(dp_rank=w.dp_rank, timestamp=-1.0, num_running_reqs=0))
|
|
time.sleep(interval)
|
|
for rank in expected:
|
|
load = reader.read(rank)
|
|
if load is not None:
|
|
received.add(rank)
|
|
if received >= expected:
|
|
return
|
|
raise RuntimeError(f"warmup failed: expected {expected}, received {received}")
|
|
|
|
|
|
class TestShmRoundTrip(CustomTestCase):
|
|
def test_single_rank_write_read(self):
|
|
path = _temp_path()
|
|
writer = ShmLoadSnapshotWriter(path, dp_size=1, dp_rank=0)
|
|
reader = ShmLoadSnapshotReader(path, dp_size=1)
|
|
try:
|
|
writer.write(LoadSnapshot(dp_rank=0, num_running_reqs=5, timestamp=1.0))
|
|
load = reader.read(0)
|
|
self.assertIsNotNone(load)
|
|
self.assertEqual(load.num_running_reqs, 5)
|
|
self.assertEqual(load.timestamp, 1.0)
|
|
finally:
|
|
reader.close()
|
|
writer.close()
|
|
if os.path.exists(path):
|
|
os.unlink(path)
|
|
|
|
def test_multi_rank_write_read_all(self):
|
|
path = _temp_path()
|
|
writers = []
|
|
try:
|
|
for rank in range(4):
|
|
w = ShmLoadSnapshotWriter(path, dp_size=4, dp_rank=rank)
|
|
w.write(
|
|
LoadSnapshot(
|
|
dp_rank=rank,
|
|
num_running_reqs=rank * 10,
|
|
timestamp=1.0,
|
|
)
|
|
)
|
|
writers.append(w)
|
|
|
|
reader = ShmLoadSnapshotReader(path, dp_size=4)
|
|
loads = reader.read_all()
|
|
self.assertEqual(len(loads), 4)
|
|
for i, load in enumerate(loads):
|
|
self.assertEqual(load.dp_rank, i)
|
|
self.assertEqual(load.num_running_reqs, i * 10)
|
|
reader.close()
|
|
finally:
|
|
for w in writers:
|
|
w.close()
|
|
if os.path.exists(path):
|
|
os.unlink(path)
|
|
|
|
def test_reader_empty_before_writer(self):
|
|
path = _temp_path()
|
|
reader = ShmLoadSnapshotReader(path, dp_size=2)
|
|
self.assertEqual(reader.read_all(), [])
|
|
self.assertIsNone(reader.read(0))
|
|
reader.close()
|
|
|
|
|
|
class TestZmqRoundTrip(CustomTestCase):
|
|
def test_single_rank_zmq_to_shm(self):
|
|
shm_path = _temp_path()
|
|
addr = _ipc_addr()
|
|
reader = ZmqShmLoadSnapshotReader(addr, shm_path, dp_size=2)
|
|
writer = ZmqLoadSnapshotWriter(addr, dp_size=2, dp_rank=0)
|
|
try:
|
|
_warmup_zmq([writer], reader)
|
|
|
|
writer.write(LoadSnapshot(dp_rank=0, num_running_reqs=7, timestamp=2.0))
|
|
time.sleep(0.05)
|
|
|
|
load = reader.read(0)
|
|
self.assertIsNotNone(load)
|
|
self.assertEqual(load.num_running_reqs, 7)
|
|
self.assertEqual(load.timestamp, 2.0)
|
|
finally:
|
|
writer.close()
|
|
reader.close()
|
|
if os.path.exists(shm_path):
|
|
os.unlink(shm_path)
|
|
|
|
def test_multi_rank_zmq(self):
|
|
shm_path = _temp_path()
|
|
addr = _ipc_addr()
|
|
dp_size = 4
|
|
reader = ZmqShmLoadSnapshotReader(addr, shm_path, dp_size)
|
|
writers = []
|
|
try:
|
|
for rank in range(dp_size):
|
|
w = ZmqLoadSnapshotWriter(addr, dp_size, dp_rank=rank)
|
|
writers.append(w)
|
|
|
|
_warmup_zmq(writers, reader)
|
|
|
|
for rank, w in enumerate(writers):
|
|
w.write(
|
|
LoadSnapshot(dp_rank=rank, num_running_reqs=rank + 1, timestamp=3.0)
|
|
)
|
|
time.sleep(0.05)
|
|
|
|
loads = reader.read_all()
|
|
self.assertEqual(len(loads), dp_size)
|
|
for load in loads:
|
|
self.assertEqual(load.num_running_reqs, load.dp_rank + 1)
|
|
finally:
|
|
for w in writers:
|
|
w.close()
|
|
reader.close()
|
|
if os.path.exists(shm_path):
|
|
os.unlink(shm_path)
|
|
|
|
def test_read_returns_latest(self):
|
|
shm_path = _temp_path()
|
|
addr = _ipc_addr()
|
|
reader = ZmqShmLoadSnapshotReader(addr, shm_path, dp_size=1)
|
|
writer = ZmqLoadSnapshotWriter(addr, dp_size=1, dp_rank=0)
|
|
try:
|
|
_warmup_zmq([writer], reader)
|
|
|
|
for i in range(10):
|
|
writer.write(
|
|
LoadSnapshot(dp_rank=0, num_running_reqs=i, timestamp=float(i))
|
|
)
|
|
time.sleep(0.05)
|
|
|
|
load = reader.read(0)
|
|
self.assertIsNotNone(load)
|
|
self.assertEqual(load.num_running_reqs, 9)
|
|
self.assertEqual(load.timestamp, 9.0)
|
|
finally:
|
|
writer.close()
|
|
reader.close()
|
|
if os.path.exists(shm_path):
|
|
os.unlink(shm_path)
|
|
|
|
def test_zmq_writer_noblock_without_reader(self):
|
|
addr = _ipc_addr()
|
|
writer = ZmqLoadSnapshotWriter(addr, dp_size=1, dp_rank=0)
|
|
try:
|
|
writer.write(LoadSnapshot(dp_rank=0, num_running_reqs=1, timestamp=1.0))
|
|
finally:
|
|
writer.close()
|
|
ipc_path = addr[len("ipc://") :]
|
|
if os.path.exists(ipc_path):
|
|
os.unlink(ipc_path)
|
|
|
|
def test_reader_ipc_cleanup(self):
|
|
addr = _ipc_addr()
|
|
shm_path = _temp_path()
|
|
ipc_path = addr[len("ipc://") :]
|
|
reader = ZmqShmLoadSnapshotReader(addr, shm_path, dp_size=1)
|
|
self.assertTrue(os.path.exists(ipc_path))
|
|
reader.close()
|
|
self.assertFalse(os.path.exists(ipc_path))
|
|
if os.path.exists(shm_path):
|
|
os.unlink(shm_path)
|
|
|
|
|
|
class TestFactoryFunctions(CustomTestCase):
|
|
def test_shm_mode(self):
|
|
server_args = SimpleNamespace(
|
|
enable_dp_attention=False,
|
|
nnodes=1,
|
|
dp_size=1,
|
|
load_balance_method="round_robin",
|
|
node_rank=0,
|
|
tokenizer_worker_num=1,
|
|
)
|
|
port_args = SimpleNamespace(instance_id="test_shm_factory")
|
|
writer = create_load_snapshot_writer(
|
|
server_args, port_args, dp_size=1, dp_rank=0
|
|
)
|
|
self.assertIsInstance(writer, ShmLoadSnapshotWriter)
|
|
reader = create_load_snapshot_reader(
|
|
server_args, port_args, caller="TokenizerManager"
|
|
)
|
|
self.assertIsInstance(reader, ShmLoadSnapshotReader)
|
|
reader.close()
|
|
writer.close()
|
|
from sglang.srt.managers.load_snapshot import shm_path_for
|
|
|
|
path = shm_path_for("test_shm_factory")
|
|
if os.path.exists(path):
|
|
os.unlink(path)
|
|
|
|
def test_zmq_mode_via_env(self):
|
|
server_args = SimpleNamespace(
|
|
enable_dp_attention=False,
|
|
nnodes=1,
|
|
dp_size=1,
|
|
load_balance_method="round_robin",
|
|
node_rank=0,
|
|
tokenizer_worker_num=1,
|
|
)
|
|
port_args = SimpleNamespace(instance_id="test_zmq_factory")
|
|
os.environ["SGLANG_LOAD_SNAPSHOT_USE_ZMQ"] = "1"
|
|
try:
|
|
writer = create_load_snapshot_writer(
|
|
server_args, port_args, dp_size=1, dp_rank=0
|
|
)
|
|
self.assertIsInstance(writer, ZmqLoadSnapshotWriter)
|
|
reader = create_load_snapshot_reader(
|
|
server_args, port_args, caller="TokenizerManager"
|
|
)
|
|
self.assertIsInstance(reader, ZmqShmLoadSnapshotReader)
|
|
reader.close()
|
|
writer.close()
|
|
finally:
|
|
del os.environ["SGLANG_LOAD_SNAPSHOT_USE_ZMQ"]
|
|
|
|
def test_should_use_zmq_multinode_dp_attention(self):
|
|
args = SimpleNamespace(enable_dp_attention=True, nnodes=2)
|
|
self.assertTrue(should_use_zmq(args))
|
|
|
|
|
|
class TestZmqReaderOwner(CustomTestCase):
|
|
"""At most one process binds the zmq PULL socket across all callers."""
|
|
|
|
CALLERS = ("TokenizerManager", "MultiTokenizerRouter", "DataParallelController")
|
|
|
|
@staticmethod
|
|
def _args(**overrides):
|
|
base = dict(
|
|
enable_dp_attention=True,
|
|
nnodes=2,
|
|
node_rank=0,
|
|
dp_size=1,
|
|
load_balance_method="round_robin",
|
|
tokenizer_worker_num=1,
|
|
)
|
|
base.update(overrides)
|
|
return SimpleNamespace(**base)
|
|
|
|
def _owners(self, args):
|
|
return {c for c in self.CALLERS if zmq_reader_owner(args, c)}
|
|
|
|
def test_zmq_disabled_no_owner(self):
|
|
args = self._args(enable_dp_attention=False, nnodes=1)
|
|
self.assertEqual(self._owners(args), set())
|
|
|
|
def test_non_zero_node_rank_no_owner(self):
|
|
args = self._args(node_rank=1, dp_size=4, tokenizer_worker_num=8)
|
|
self.assertEqual(self._owners(args), set())
|
|
|
|
def test_tokenizer_manager_owns_when_dp1(self):
|
|
self.assertEqual(self._owners(self._args(dp_size=1)), {"TokenizerManager"})
|
|
|
|
def test_multi_tokenizer_router_owns_in_multi_tokenizer_dp1(self):
|
|
args = self._args(dp_size=1, tokenizer_worker_num=8)
|
|
self.assertEqual(self._owners(args), {"MultiTokenizerRouter"})
|
|
|
|
def test_multi_tokenizer_router_owns_in_multi_tokenizer_round_robin(self):
|
|
args = self._args(dp_size=4, tokenizer_worker_num=8)
|
|
self.assertEqual(self._owners(args), {"MultiTokenizerRouter"})
|
|
|
|
def test_data_parallel_controller_owns_load_aware(self):
|
|
for method in ("total_tokens", "total_requests"):
|
|
args = self._args(
|
|
dp_size=4, tokenizer_worker_num=8, load_balance_method=method
|
|
)
|
|
self.assertEqual(self._owners(args), {"DataParallelController"})
|
|
|
|
def test_tokenizer_manager_owns_dp4_round_robin(self):
|
|
args = self._args(dp_size=4, tokenizer_worker_num=1)
|
|
self.assertEqual(self._owners(args), {"TokenizerManager"})
|
|
|
|
def test_at_most_one_owner_across_configs(self):
|
|
for dp_size in (1, 4):
|
|
for tw in (1, 8):
|
|
for method in ("round_robin", "total_tokens", "total_requests"):
|
|
for node_rank in (0, 1):
|
|
args = self._args(
|
|
dp_size=dp_size,
|
|
tokenizer_worker_num=tw,
|
|
load_balance_method=method,
|
|
node_rank=node_rank,
|
|
)
|
|
self.assertLessEqual(len(self._owners(args)), 1, args)
|
|
|
|
|
|
class TestZmqAddr(CustomTestCase):
|
|
def test_ipc_for_single_node(self):
|
|
port_args = SimpleNamespace(instance_id="myinstance")
|
|
addr = _zmq_addr_for(port_args)
|
|
self.assertTrue(addr.startswith("ipc://"))
|
|
self.assertIn("myinstance", addr)
|
|
|
|
def test_tcp_from_port_args(self):
|
|
from sglang.srt.utils.network import NetworkAddress
|
|
|
|
port_args = SimpleNamespace(
|
|
instance_id="myinstance",
|
|
load_collector_ipc_name=NetworkAddress("10.0.0.1", 29506).to_tcp(),
|
|
)
|
|
addr = _zmq_addr_for(port_args)
|
|
self.assertTrue(addr.startswith("tcp://"))
|
|
self.assertIn("10.0.0.1", addr)
|
|
|
|
|
|
class TestEndToEndZmqSimulation(CustomTestCase):
|
|
"""Simulate multi-node DP attention on single machine using IPC."""
|
|
|
|
def test_full_flow_dp_size_2(self):
|
|
shm_path = _temp_path()
|
|
addr = _ipc_addr()
|
|
dp_size = 2
|
|
|
|
reader = ZmqShmLoadSnapshotReader(addr, shm_path, dp_size)
|
|
|
|
writers = []
|
|
for rank in range(dp_size):
|
|
w = ZmqLoadSnapshotWriter(addr, dp_size, dp_rank=rank)
|
|
writers.append(w)
|
|
|
|
try:
|
|
_warmup_zmq(writers, reader)
|
|
|
|
for rank, w in enumerate(writers):
|
|
w.write(
|
|
LoadSnapshot(
|
|
dp_rank=rank,
|
|
timestamp=1.0,
|
|
num_running_reqs=10 + rank,
|
|
num_waiting_reqs=5 + rank,
|
|
num_total_tokens=100 + rank * 50,
|
|
)
|
|
)
|
|
time.sleep(0.05)
|
|
|
|
loads = reader.read_all()
|
|
self.assertEqual(len(loads), dp_size)
|
|
self.assertEqual(loads[0].num_running_reqs, 10)
|
|
self.assertEqual(loads[1].num_running_reqs, 11)
|
|
self.assertEqual(loads[0].num_total_tokens, 100)
|
|
self.assertEqual(loads[1].num_total_tokens, 150)
|
|
|
|
for rank, w in enumerate(writers):
|
|
w.write(
|
|
LoadSnapshot(
|
|
dp_rank=rank,
|
|
timestamp=2.0,
|
|
num_running_reqs=20 + rank,
|
|
num_waiting_reqs=0,
|
|
num_total_tokens=200 + rank * 50,
|
|
)
|
|
)
|
|
time.sleep(0.05)
|
|
|
|
loads = reader.read_all()
|
|
self.assertEqual(loads[0].num_running_reqs, 20)
|
|
self.assertEqual(loads[1].num_running_reqs, 21)
|
|
self.assertEqual(loads[0].num_total_tokens, 200)
|
|
self.assertEqual(loads[1].num_total_tokens, 250)
|
|
finally:
|
|
for w in writers:
|
|
w.close()
|
|
reader.close()
|
|
if os.path.exists(shm_path):
|
|
os.unlink(shm_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|