Prhub

#26348 Optimize get load calls (/v1/loads) using shared-memory load snapshots

原始 PR 作者 merrymercy 合并时间 2026-05-30 04:40 文件变更 14 提交数 5 评论 4 代码增减 +1525 / -275

执行摘要

使用共享内存快照优化 /v1/loads 性能,延迟降低 10-100 倍

旧的负载获取方法依赖 ZMQ 通信,在 scheduler 执行长 chunked prefill 时会产生额外延迟。本 PR 的目标是将 get_loads 调用的延迟降低 10-100 倍,并减少不必要的 IPC 开销。

建议团队精读 load_snapshot.py 中双后端的设计模式(策略模式选择),以及 refresh_load_budget 中的 20ms 节流逻辑,这是性能与准确性权衡的典型实践。该 PR 为未来扩展实时监控和负载均衡提供了基础架构。

讨论亮点

本 PR 无 review 评论。原始 commit 来自 #25841(由 @cctry 提交),@merrymercy 在此基础上整合并修复了循环导入和格式化问题。

实现拆解

  1. 新增负载快照模块load_snapshot.py):定义 LoadSnapshot 数据类(使用 msgspec 序列化)和两种传输后端:SHM(mmap 写入 /dev/shm)和 ZMQ fallback。提供工厂函数根据配置选择后端。
  2. Scheduler 发布快照scheduler.py):Scheduler 初始化时创建 writer,并在每次调度循环后调用 publish_load_snapshot,根据配置的间隔(默认 15 次调度周期)决定是否发布。
  3. 重构 /v1/loads 端点v1_loads.py):移除旧的基于 dataclass 的序列化、聚合计算以及多种度量 dataclass 的导入。改为调用 tokenizer_manager.get_loads 获取 LoadSnapshot 列表,利用 to_dict 方法返回。新增 includeformat 查询参数。
  4. DP 控制器使用共享内存data_parallel_controller.py):移除原有的 handle_load_update_req(ZMQ 接收负载更新),改为在 dispatching_with_trace 中可选地调用 refresh_load_budget。该方法读取 load_snapshot_reader 的所有快照,并调用 DPBudget.update_budget(增加 last_timestamp 跳过陈旧数据)。刷新有 20ms 节流,避免高并发时每个 dispatch 都读。
  5. 配置与测试:在 server_args.py 添加 --load-snapshot-publish-interval 参数;在 environ.py 添加 SGLANG_LOAD_SNAPSHOT_USE_ZMQ 环境变量。新增单元测试 test_load_snapshot_backends.py(SHM 和 ZMQ 后端的读写、多 rank)、集成测试 test_load_snapshot_server.py(真实服务器无 DP 和普通 DP 场景),修改 test_v1_loads_aggregate.pytest_data_parallel_controller.py 适配新逻辑。
文件 模块 状态 重要度
python/sglang/srt/managers/load_snapshot.py 负载快照 added 9.08
python/sglang/srt/entrypoints/v1_loads.py 负载端点 modified 8.17
python/sglang/srt/managers/data_parallel_controller.py 数据并行控制 modified 8.15
python/sglang/srt/managers/scheduler.py 调度器 modified 7.66
test/registered/unit/managers/test_load_snapshot_backends.py 后端测试 added 7.76

关键符号

publish_load_snapshot refresh_load_budget create_load_snapshot_writer should_use_zmq zmq_reader_owner _format_loads_prometheus get_loads update_budget dispatching_with_trace

关键源码片段

python/sglang/srt/entrypoints/v1_loads.py core-logic

核心修改文件,重构 /v1/loads 端点,移除聚合逻辑,改为使用 LoadSnapshot.to_dict

@router.get("/v1/loads")
async def get_loads(
    dp_rank: Optional[int] = None,
    include: Optional[str] = None,
    format: Optional[str] = None,
    tokenizer_manager=Depends(_get_tokenizer_manager),
):
    """
    获取所有 DP rank 的负载指标。    查询参数:
        dp_rank: 过滤指定 DP rank(可选)
        include: 逗号分隔的 section 过滤(可选,如 core, memory 等)
        format: 响应格式,json(默认)或 prometheus
    """
    include_list = [s.strip() for s in include.split(",")] if include else None
​
    load_results = await tokenizer_manager.get_loads(
        include=include_list,
        dp_rank=dp_rank,
    )
​
    if format == "prometheus":
        # 使用 to_dict 生成 Prometheus 格式,支持 include 过滤
        return _format_loads_prometheus(load_results, include_set)
​
    # JSON 格式:每个 LoadSnapshot 通过 to_dict 转换为字典
    loads = [load.to_dict(include_set) for load in load_results]
    return {"loads": loads, "timestamp": ..., "version": ...}

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

共享内存文件在进程异常退出时可能残留,需确保清理逻辑(已有文件锁和自动清理机制)。多节点 ZMQ 模式依赖于网络和进程健康,PULL 套接字 owner 进程崩溃会导致快照中断。DPBudget 的 20ms 节流阈值在极端负载下可能导致短暂的调度决策使用陈旧数据,但这是经过权衡的设计。/v1/loads API 返回格式移除了 aggregate 字段,现有客户端需要适配。新代码引入了 msgspec 依赖和更多系统调用,但影响可控。

正面影响:/v1/loads 延迟大幅降低,scheduler 阻塞不再影响负载读取;DP 负载均衡数据获取更及时;减少 ZMQ 通信量。负面影响:API 返回格式变化,客户端需更新;共享内存路径在 Windows 上可能受限(未测试);团队需要维护两套后端(SHM 和 ZMQ),增加测试负担。

共享内存残留风险 API 响应格式变更 多节点 ZMQ 依赖 节流阈值可能不准确

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论