Prhub

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

原始 PR 作者 Kangyan-Zhou 合并时间 2026-05-23 01:59 文件变更 4 提交数 3 评论 5 代码增减 +395 / -2

执行摘要

在 /server_info 暴露 KV-event publisher 描述符,支持路由器自发现

来自 PR body:'Pre-patch, KV-aware routers (e.g. the SGLang model gateway) had to be told each worker's publisher port out-of-band via operator config. That doesn't scale to multi-replica router deployments… With this patch the contract becomes self-describing per worker.'

值得精读。设计决策清晰:将 introspection 方法放在配置对象自身,使用懒加载避免循环依赖,安全返回 null 而非异常。review 中的绑定一致性问题提醒了跨文件契约验证的重要性。测试驱动方式(绕过 HTTP 层直接调用 handler)也值得参考。

讨论亮点

Review 中 JustinTong0323 指出关键问题:describe_kv_events_publisher 会广告 tcp://0.0.0.0:7777,但 ZmqEventPublisher._socket_setup() 只对 *::ipc://inproc:// 执行 bind,对 0.0.0.0 会执行 connect(),导致路由器连接的端口实际未被监听。PR 作者随后在第二次提交中修复,将 0.0.0.0 加入 bind 通配符列表,并在 server_args 注释中明确该行为。

实现拆解

  1. 在 ServerArgs 上新增描述方法python/sglang/srt/server_args.py 新增 describe_kv_events_publisher() 方法,懒加载 KVEventsConfig(避免顶层导入 ZMQ/msgspec)。它解析 kv_events_config 字符串,验证 endpoint 必须为 tcp://、端口在 1-65535、page_size 为正,返回结构化 dict 或 None

  2. 在 /server_info 中注入字段python/sglang/srt/entrypoints/http_server.pyserver_info() 处理函数将 server_args.describe_kv_events_publisher() 结果以 kv_events 键加入响应字典,同时保留了原有所有字段。

  3. 修复 publisher 绑定逻辑python/sglang/srt/disaggregation/kv_events.py_socket_setup() 添加对 0.0.0.0 的绑定检测,确保广告的 tcp://0.0.0.0:<port> 实际可监听。该修复由 review 评论触发。

  4. 新增全面测试test/registered/unit/entrypoints/test_server_info.py 通过桩化全局状态直接调用 server_info(),覆盖 happy path、未配置、显式 null、格式错误、非 TCP 端点、端口超界、page_size 无效等 13 个用例,并回归验证原有字段不被移除。

文件 模块 状态 重要度
python/sglang/srt/server_args.py 配置层 modified 7.6
test/registered/unit/entrypoints/test_server_info.py 测试 added 7.76
python/sglang/srt/entrypoints/http_server.py 请求路由 modified 5.27
python/sglang/srt/disaggregation/kv_events.py 事件发布 modified 5.47

关键符号

describe_kv_events_publisher server_info _socket_setup

关键源码片段

test/registered/unit/entrypoints/test_server_info.py test-coverage

新增 296 行全覆盖测试,通过桩调用 handler,验证 13 种场景(正常、禁用、格式错误、端口边界等),并守护已有字段不被破坏

# test_server_info.py — 桩化调用 & 核心测试类片段
def _call_server_info_with(server_args: ServerArgs) -> dict:
    """直接调用 http_server.server_info(),避免启动 FastAPI 和模型服务器。
    通过 SimpleNamespace 桩化 _global_state,返回 handler 的响应 dict。
    """
    async def _fake_internal_state():
        return [{"max_req_input_len": 1024}]
    stub_state = SimpleNamespace(
        tokenizer_manager=SimpleNamespace(
            server_args=server_args,
            get_internal_state=_fake_internal_state,
        ),
        scheduler_info={"max_req_input_len": 1024},
    )
    prior_state = http_server.get_global_state()
    http_server.set_global_state(stub_state)
    try:
        return asyncio.run(http_server.server_info())
    finally:
        http_server._global_state = prior_stateclass TestServerInfoKvEventsField(CustomTestCase):
    """验证 kv_events 字段在各种配置下的正确性"""
    def test_kv_events_key_present_when_publishing_enabled(self):
        args = ServerArgs(
            model_path="dummy",
            kv_events_config='{"publisher": "zmq", "endpoint": "tcp://*:5557", "topic": "kv"}',
            page_size=64, dp_size=2,
        )
        info = _call_server_info_with(args)
        self.assertIn("kv_events", info)
        self.assertEqual(info["kv_events"], {
            "publisher": "zmq",
            "endpoint_host": "*",
            "endpoint_port_base": 5557,
            "topic": "kv",
            "block_size": 64,
            "dp_size": 2,
        })
​
    def test_kv_events_descriptor_carries_specific_host_and_topic(self):
        args = ServerArgs(
            model_path="dummy",
            kv_events_config='{"publisher": "zmq", "endpoint": "tcp://0.0.0.0:7777", "topic": "kv"}',
            page_size=128, dp_size=1,
        )
        info = _call_server_info_with(args)
        self.assertIsNotNone(info["kv_events"])
        self.assertEqual(info["kv_events"]["endpoint_host"], "0.0.0.0")
        self.assertEqual(info["kv_events"]["endpoint_port_base"], 7777)
        self.assertEqual(info["kv_events"]["block_size"], 128)
​
    def test_kv_events_is_null_when_no_publisher_configured(self):
        args = ServerArgs(model_path="dummy") # no --kv-events-config
        info = _call_server_info_with(args)
        self.assertIn("kv_events", info)
        self.assertIsNone(info["kv_events"])

评论区精华

描述符广告 0.0.0.0 与 publisher 绑定行为不一致 正确性

JustinTong0323 指出 describe_kv_events_publisher 会广告 tcp://0.0.0.0:7777,但 _socket_setup 未将 0.0.0.0 视为绑定通配符,导致 publisher 执行 connect 而非 bind,路由器连接失败。

结论:PR 作者在第二次提交中修复:将 0.0.0.0 加入 _socket_setup 的绑定条件,并更新 serve_args 注释明确 0.0.0.0 为通配符。 · 已解决

风险与影响

  • 绑定一致性依赖:描述符中的 host 判断与 publisher 实际绑定逻辑需保持同步。未来若修改绑定条件,需同时更新描述符的 endpoint 验证。
  • 配置错误静默掩盖KVEventsConfig.from_cli 解析异常被 describe_kv_events_publisher 吞噬并返回 None,避免了 /server_info 崩溃,但可能让操作员难以察觉配置错误(如 JSON 语法错误)。不过 PR 有测试覆盖未配置情况,且生产环境中启动时会报错。
  • page_size 假设:描述符假设 page_size 在服务器生命周期内固定,如果将来支持动态调整,描述符可能过期。
  • kv_events.py 改动影响面:新增 0.0.0.0 绑定可能影响已有依赖非绑定行为的用户,但更符合预期(通配符 IP 应绑定)。
  • 用户:无直接行为变化,但为上层路由组件(如 SGLang model gateway)提供 KV-event publisher 的自发现能力,降低多副本路由器部署的配置复杂度。
  • 系统/server_info 响应新增 kv_events 字段,对原有字段完全向后兼容。新增 296 行测试,覆盖边界条件,显著降低回归风险。
  • 团队:新增一处维护点(describe_kv_events_publisher),但逻辑集中且与 publisher 实现解耦(懒加载)。review 中发现的绑定 bug 已修复,避免未来难以排查的连通性问题。
绑定一致性依赖 配置错误静默掩盖 page_size 静态假设

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论