Prhub

#2082 Overlapping data loading and sglang initialization

原始 PR 作者 zhuzilin 合并时间 2026-06-15 23:03 文件变更 3 提交数 1 评论 0 代码增减 +142 / -19

执行摘要

数据加载与 sglang 引擎初始化重叠以缩短启动时间

在 RolloutManager 启动时,先异步发起 sglang 引擎初始化,然后立即开始数据源加载,最后再等待引擎初始化完成,从而利用数据加载时间并行执行引擎初始化,缩短总体启动延迟。

值得精读:该 PR 通过 fork-join 模式将 I/O 类加载与 GPU 引擎初始化重叠,是一个经典的启动优化案例。关注其如何保持 EPD 依赖同步而仅延迟最终 ray.get

讨论亮点

PR 为单一提交,无公开 review 评论。讨论主要在内部完成,无公开记录。

实现拆解

  1. 修改 start_rollout_servers 返回类型slime/ray/rollout.py):将返回值从 dict[str, Any] 改为 tuple[dict[str, Any], list[Any]],第二个元素是待完成的引擎初始化句柄列表。函数内部不再调用 ray.get,而是将所有非编码器组的初始化句柄(或 EPD 路径中的非编码器句柄)收集到 pending_init_handles 中返回。

  2. 同步修改 start_external_rollout_serversslime/backends/sglang_utils/external.py):同样改为返回句柄列表,移除内部的 ray.get(init_handles),将句柄返回给调用者。

  3. RolloutManager.__init__ 中重组启动流程slime/ray/rollout.py):先调用 start_rollout_servers 获取 serversrollout_init_handles,然后加载数据源、rollout 函数等(这些操作与引擎初始化可并行进行),最后在需要使用引擎前通过 ray.get(rollout_init_handles) 完成等待。

  4. 更新测试tests/utils/test_sglang_config.py):

    • 新增 test_start_rollout_servers_defers_engine_wait:验证引擎启动被推迟(ray.get 未被立即调用),并正确返回 init_handles
    • 新增 test_start_rollout_servers_waits_for_epd_encoder_before_non_encoder:验证 EPD 模式下编码器组仍先同步启动并收集 URL,非编码器组才异步启动,保证依赖顺序。
  5. 配置与文档:无额外变更。该优化对用户透明,不涉及新参数。

文件 模块 状态 重要度
slime/ray/rollout.py 调度器 modified 7.02
slime/backends/sglang_utils/external.py 外部后端 modified 6.35
tests/utils/test_sglang_config.py 测试 modified 7.47

关键符号

start_rollout_servers start_external_rollout_servers RolloutManager.__init__

关键源码片段

slime/ray/rollout.py core-logic

核心变更:修改 start_rollout_servers 返回异步句柄,并调整 RolloutManager 初始化顺序以实现数据加载与引擎初始化重叠。

# slime/ray/rollout.py ( 部分关键变更 )def start_rollout_servers(args, pg) -> tuple[dict[str, Any], list[Any]]:
    """Start rollout servers without waiting for final engine initialization.    Returns ``(servers, init_handles)`` where servers maps model name to
    ``RolloutServer`` and init_handles contains pending ``engine.init`` refs.
    """
    config = _resolve_sglang_config(args)
    servers: dict[str, RolloutServer] = {}
    pending_init_handles: list[Any] = [] # 收集所有待完成的初始化句柄
    # ... 遍历模型配置,对每个 group 启动引擎并收集句柄 ...
    # 对于 EPD 模式,编码器组仍同步等待 URL,非编码器句柄加入 pending_init_handles
    # 对于非 EPD 模式,所有句柄加入 pending_init_handles,不再立即 ray.get
    # ...
    return servers, pending_init_handles
​
​
# 在 RolloutManager.__init__ 中 :
class RolloutManager:
    def __init__(self, args, pg):
        # ...
        rollout_init_handles: list[Any] = []
        if self.args.debug_train_only:
            self.servers: dict[str, Any] = {}
        else:
            init_http_client(args)
            # 先异步启动引擎,获得句柄
            self.servers, rollout_init_handles = start_rollout_servers(args, pg)
​
        # 数据加载和函数导入(与引擎初始化并行)
        data_source_cls = load_function(self.args.data_source_path)
        self.data_source = data_source_cls(args)
        # ... 加载 generate_rollout 等 ...
​
        # 在数据加载完成后,等待引擎初始化完成
        if rollout_init_handles:
            ray.get(rollout_init_handles)
        # ... 继续其他初始化 ...
slime/backends/sglang_utils/external.py core-logic

同步修改 start_external_rollout_servers 返回异步句柄,保持与 rollout.py 的行为一致。

# slime/backends/sglang_utils/external.py ( 关键变更 )def start_external_rollout_servers(args, *, start_router) -> tuple[dict[str, ExternalRolloutServer], list]:
    # ... 构建引擎、收集 init_handles ...
    # 原代码 : if init_handles: ray.get(init_handles)
    # 现在 : 移除 ray.get,直接返回句柄
    servers = {
        "default": ExternalRolloutServer(
            engines=engines,
            # ...
        )
    }
    return servers, init_handles # 将未完成的句柄返回给调用者
tests/utils/test_sglang_config.py test-coverage

新增两个测试用例验证异步启动的正确性,包括普通延迟等待和 EPD 编码器依赖顺序保持。

# tests/utils/test_sglang_config.py ( 新增测试 )def test_start_rollout_servers_defers_engine_wait(self, monkeypatch):
    from slime.ray import rollout as rollout_module
​
    # 模拟 start_router 和 start_engines,记录 ray.get 调用
    # ...
    servers, init_handles = rollout_module.start_rollout_servers(args, pg=(None, [], []))
    assert init_handles == ["init-0"] # 返回句柄而非立即等待
    assert ray_get_calls == [] # 验证引擎未立即被等待
    # 后续调用期会由 RolloutManager 在数据加载后 ray.getdef test_start_rollout_servers_waits_for_epd_encoder_before_non_encoder(self, monkeypatch):
    # 创建包含 encoder 和 regular 的配置
    # 模拟 start_engines 在 encoder 时返回 FakeEngine(带 get_url),regular 时返回普通对象
    # 验证 encoder 组的 init 句柄被等待(通过 get_url 调用验证),非 encoder 句柄被延迟
    # ...
    servers, init_handles = rollout_module.start_rollout_servers(args, pg=(None, [], []))
    # 验证返回的 init_handles 只包含非编码器句柄,编码器已同步完成
    assert init_handles == ["regular-init-0"]

评论区精华

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

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

风险与影响

  1. 错误传递延迟:引擎初始化异常将在 ray.get(rollout_init_handles) 时抛出,如果数据加载已经完成,异常报告会延迟,但不改变最终行为。
  2. EPD 依赖顺序:PR 保留了编码器组启动的同步顺序,仅将非编码器组的等待延迟,因此 EPD 的依赖正确性不受影响。
  3. 外部引擎路径start_external_rollout_servers 移除内部的 ray.get 改为返回句柄,外部调用者需要同步更新(此处仍为同一 PR 内的 RolloutManager),无兼容性问题。
  4. 启动时间影响:仅影响启动阶段的并发性,对运行时无影响。

用户影响:无感知,启动时间略有缩短。
系统影响:启动阶段并发度提升,可能减少从提交到推理开始的时间。
团队影响:核心流程变化,未来若更改启动逻辑需注意异步句柄的传递。

启动时序变更 错误传递延迟 依赖同步保持

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论