feat(kv-events): expose structured KV-event publisher block on /server_info (#25844)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kangyan-Zhou
2026-05-23 01:59:45 +08:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 06c23d55b5
commit 085777210c
4 changed files with 395 additions and 2 deletions
@@ -256,10 +256,15 @@ class ZmqEventPublisher(EventPublisher):
self._pub = self._ctx.socket(zmq.PUB)
self._pub.set_hwm(self._hwm)
# Heuristic: bind if wildcard / * present, else connect.
# bind stable, connect volatile convention
# bind stable, connect volatile convention.
# ``0.0.0.0`` is the IPv4 bind-all wildcard alongside ``*``
# and ``::``; ``/server_info`` advertises it as a wildcard,
# so the publisher must bind it for the advertised endpoint
# to actually be listening.
if (
"*" in self._endpoint
or "::" in self._endpoint
or "0.0.0.0" in self._endpoint
or self._endpoint.startswith("ipc://")
or self._endpoint.startswith("inproc://")
):
+7 -1
View File
@@ -636,12 +636,18 @@ async def server_info():
await _global_state.tokenizer_manager.get_internal_state()
)
server_args = _global_state.tokenizer_manager.server_args
# server_args.model_config is not serializable but should be excluded by asdict.
return {
**dataclasses.asdict(_global_state.tokenizer_manager.server_args),
**dataclasses.asdict(server_args),
**_global_state.scheduler_info,
"internal_states": internal_states,
"version": __version__,
# Structured KV-event publisher descriptor for KV-aware routers.
# `None` when publishing is disabled or misconfigured; see
# `ServerArgs.describe_kv_events_publisher` for the precise contract.
"kv_events": server_args.describe_kv_events_publisher(),
}
+86
View File
@@ -7430,6 +7430,92 @@ class ServerArgs:
else:
return False
def describe_kv_events_publisher(self) -> Optional[dict]:
"""Return a structured description of this server's KV-event
publisher, or `None` if publishing is disabled / misconfigured.
This is the wire contract surfaced under the `kv_events` key on
`/server_info` so KV-aware routers (e.g. the SGLang model
gateway) can subscribe per-worker without operator-supplied port
coordination. The router constructs the per-DP-rank SUB endpoint
as ``tcp://<worker_host>:<endpoint_port_base + dp_rank>`` for
every rank reported in ``dp_size``.
Returned descriptor shape:
{
"publisher": "zmq",
"endpoint_host": "*", # may be a ZMQ wildcard
# ("*", "0.0.0.0", "::");
# subscribers MUST substitute
# the worker URL's host when
# dialing
"endpoint_port_base": 5557, # base TCP port; per-rank
# port = base + dp_rank
"topic": "", # ZMQ topic prefix on the
# SUB filter (empty =
# subscribe-all)
"block_size": <page_size>, # subscribers MUST hash
# prompts at this size
"dp_size": <dp_size>, # number of SUB sockets
# to open
}
Returns ``None`` (i.e. "no publisher to describe") when any of:
* ``--kv-events-config`` is unset / empty / malformed JSON,
* the configured publisher is ``"null"``,
* ``page_size`` is missing or non-positive (a placeholder
``block_size`` would cause silent KV-cache misses by hashing
prompts at the wrong granularity on the router side),
* the endpoint is not a routable TCP address (``inproc://`` /
``ipc://``, missing port, non-integer port, or port outside
``1..65535``).
Reuses ``KVEventsConfig.from_cli`` for JSON parsing; the inline
``rfind(":")`` endpoint split mirrors
``ZmqEventPublisher.offset_endpoint_port`` rather than adding a
new module-level helper.
"""
# Lazy import so loading ``server_args`` doesn't pull in
# disaggregation / msgspec / zmq at module top level.
from sglang.srt.disaggregation.kv_events import KVEventsConfig
raw = self.kv_events_config
page_size = self.page_size
if not raw or page_size is None or page_size <= 0:
return None
try:
cfg = KVEventsConfig.from_cli(raw)
except Exception:
# Malformed JSON / schema mismatch. The publisher would
# have failed at server startup; ``/server_info`` must
# keep working, so just report "no publisher" to consumers.
return None
if cfg.publisher == "null" or not cfg.endpoint:
return None
if not cfg.endpoint.startswith("tcp://"):
return None
body = cfg.endpoint[len("tcp://") :]
last_colon = body.rfind(":")
if last_colon < 0:
return None
host = body[:last_colon]
try:
port = int(body[last_colon + 1 :])
except ValueError:
return None
if not host or not (0 < port < 65536):
return None
return {
"publisher": cfg.publisher,
"endpoint_host": host,
"endpoint_port_base": port,
"topic": cfg.topic,
"block_size": page_size,
"dp_size": self.dp_size,
}
# NOTE: This is a global variable to hold the server args for scheduler.
_global_server_args: Optional[ServerArgs] = None