config: ServerArgs holds the raw input (#36255)
This commit is contained in:
@@ -87,22 +87,25 @@ def launch_server_process(
|
||||
server_args: ServerArgs, worker_port: int, dp_id: int
|
||||
) -> mp.Process:
|
||||
"""Launch a single server process with the given args and port."""
|
||||
# This binding is installed against a released sglang, so it cannot call
|
||||
# into helpers newer than that wheel. Copy first, then write through the
|
||||
# sanctioned channel if the record is resolved (a resolved record refuses
|
||||
# plain assignment), else assign.
|
||||
worker_args = copy.deepcopy(server_args)
|
||||
changes = {
|
||||
"port": worker_port,
|
||||
"base_gpu_id": dp_id * server_args.tp_size,
|
||||
"dp_size": 1,
|
||||
}
|
||||
late = getattr(worker_args, "_late_resolution", None)
|
||||
if late is not None and getattr(worker_args, "_declarations_materialized", False):
|
||||
late("sglang_router.launch_server_process", **changes)
|
||||
# Three channels, newest first. A wheel that has the read-only record but not
|
||||
# `replace_resolved` still has `_late_resolution`, and plain assignment there
|
||||
# raises; only a wheel with neither accepts `setattr`.
|
||||
replace_resolved = getattr(server_args, "replace_resolved", None)
|
||||
if replace_resolved is not None:
|
||||
worker_args = replace_resolved("sglang_router.launch_server_process", **changes)
|
||||
else:
|
||||
for field, value in changes.items():
|
||||
setattr(worker_args, field, value)
|
||||
worker_args = copy.deepcopy(server_args)
|
||||
late = getattr(worker_args, "_late_resolution", None)
|
||||
if late is not None:
|
||||
late("sglang_router.launch_server_process", **changes)
|
||||
else:
|
||||
for field, value in changes.items():
|
||||
setattr(worker_args, field, value)
|
||||
server_args = worker_args
|
||||
|
||||
proc = mp.Process(target=run_server, args=(server_args, dp_id))
|
||||
@@ -188,7 +191,11 @@ def main():
|
||||
server_args.resolve_once()
|
||||
router_args = RouterArgs.from_cli_args(args, use_router_prefix=True)
|
||||
|
||||
# Find available ports for workers
|
||||
# Find available ports for workers. The count is the operator's requested
|
||||
# replica count, which is the raw field on purpose: `--dwdp-size` makes
|
||||
# resolution declare a `dp_size` that describes one multi-rank server's
|
||||
# internal topology, and spawning that many single-rank children would ask
|
||||
# for dp_size^2 GPUs.
|
||||
worker_ports = find_available_ports(
|
||||
args.router_dp_worker_base_port, server_args.dp_size
|
||||
)
|
||||
|
||||
@@ -856,6 +856,62 @@ def test_launch_server_process_and_cleanup(monkeypatch):
|
||||
assert (p1.pid, _sig.SIGTERM) in calls and (p2.pid, _sig.SIGTERM) in calls
|
||||
assert (p2.pid, _sig.SIGKILL) in calls
|
||||
|
||||
|
||||
def test_launch_server_process_declares_on_a_resolved_record(monkeypatch):
|
||||
"""A record that carries its resolution takes the declaration channel.
|
||||
|
||||
The stub above has no `replace_resolved`, so it exercises the older wheels'
|
||||
path. A current `ServerArgs` refuses plain assignment once resolution has
|
||||
finished; the per-worker values reach the child as a declaration on a copy,
|
||||
and the parent keeps what the operator passed.
|
||||
"""
|
||||
_install_sglang_stubs(monkeypatch)
|
||||
import importlib
|
||||
|
||||
ls = importlib.import_module("sglang_router.launch_server")
|
||||
|
||||
created = {}
|
||||
|
||||
class FakeProcess:
|
||||
def __init__(self, target, args):
|
||||
created["target"] = target
|
||||
created["args"] = args
|
||||
self.pid = 4243
|
||||
|
||||
def start(self):
|
||||
created["started"] = True
|
||||
|
||||
monkeypatch.setattr(ls.mp, "Process", FakeProcess)
|
||||
|
||||
calls = []
|
||||
|
||||
class ResolvedServerArgs:
|
||||
def __init__(self, **fields):
|
||||
self.port = fields.get("port", 30000)
|
||||
self.base_gpu_id = fields.get("base_gpu_id", 0)
|
||||
self.dp_size = fields.get("dp_size", 4)
|
||||
self.tp_size = fields.get("tp_size", 2)
|
||||
|
||||
def replace_resolved(self, source, **changes):
|
||||
calls.append((source, dict(changes)))
|
||||
fields = dict(vars(self))
|
||||
fields.update(changes)
|
||||
return ResolvedServerArgs(**fields)
|
||||
|
||||
parent = ResolvedServerArgs()
|
||||
proc = ls.launch_server_process(parent, worker_port=31002, dp_id=3)
|
||||
|
||||
assert created.get("started") is True
|
||||
assert proc.pid == 4243
|
||||
assert len(calls) == 1
|
||||
source, changes = calls[0]
|
||||
assert source == "sglang_router.launch_server_process"
|
||||
assert changes == {"port": 31002, "base_gpu_id": 6, "dp_size": 1}
|
||||
|
||||
worker = created["args"][0]
|
||||
assert (worker.port, worker.base_gpu_id, worker.dp_size) == (31002, 6, 1)
|
||||
assert (parent.port, parent.base_gpu_id, parent.dp_size) == (30000, 0, 4)
|
||||
|
||||
def test_validation_error_handling(self):
|
||||
"""Test error handling when validation fails."""
|
||||
args = RouterArgs(
|
||||
|
||||
Reference in New Issue
Block a user