执行摘要
- 一句话:数据加载与 sglang 引擎初始化重叠以缩短启动时间
- 推荐动作:值得精读:该 PR 通过 fork-join 模式将 I/O 类加载与 GPU 引擎初始化重叠,是一个经典的启动优化案例。关注其如何保持 EPD 依赖同步而仅延迟最终
ray.get。
功能与动机
在 RolloutManager 启动时,先异步发起 sglang 引擎初始化,然后立即开始数据源加载,最后再等待引擎初始化完成,从而利用数据加载时间并行执行引擎初始化,缩短总体启动延迟。
实现拆解
-
修改 start_rollout_servers 返回类型(slime/ray/rollout.py):将返回值从 dict[str, Any] 改为 tuple[dict[str, Any], list[Any]],第二个元素是待完成的引擎初始化句柄列表。函数内部不再调用 ray.get,而是将所有非编码器组的初始化句柄(或 EPD 路径中的非编码器句柄)收集到 pending_init_handles 中返回。
-
同步修改 start_external_rollout_servers(slime/backends/sglang_utils/external.py):同样改为返回句柄列表,移除内部的 ray.get(init_handles),将句柄返回给调用者。
-
在 RolloutManager.__init__ 中重组启动流程(slime/ray/rollout.py):先调用 start_rollout_servers 获取 servers 和 rollout_init_handles,然后加载数据源、rollout 函数等(这些操作与引擎初始化可并行进行),最后在需要使用引擎前通过 ray.get(rollout_init_handles) 完成等待。
-
更新测试(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,非编码器组才异步启动,保证依赖顺序。
-
配置与文档:无额外变更。该优化对用户透明,不涉及新参数。
关键文件:
slime/ray/rollout.py(模块 调度器;类别 source;类型 core-logic;符号 start_rollout_servers, RolloutManager.init): 核心变更:修改 start_rollout_servers 返回异步句柄,并调整 RolloutManager 初始化顺序以实现数据加载与引擎初始化重叠。
slime/backends/sglang_utils/external.py(模块 外部后端;类别 source;类型 core-logic;符号 start_external_rollout_servers): 同步修改 start_external_rollout_servers 返回异步句柄,保持与 rollout.py 的行为一致。
tests/utils/test_sglang_config.py(模块 测试;类别 test;类型 test-coverage;符号 test_start_rollout_servers_defers_engine_wait, test_start_rollout_servers_waits_for_epd_encoder_before_non_encoder, FakeRemoteMethod): 新增两个测试用例验证异步启动的正确性,包括普通延迟等待和 EPD 编码器依赖顺序保持。
关键符号:start_rollout_servers, start_external_rollout_servers, RolloutManager.init
关键源码片段
slime/ray/rollout.py
核心变更:修改 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
同步修改 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
新增两个测试用例验证异步启动的正确性,包括普通延迟等待和 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.get
def 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"]
评论区精华
PR 为单一提交,无公开 review 评论。讨论主要在内部完成,无公开记录。
风险与影响
关联脉络
- PR #2057 Allow zero-GPU rollout router startup: 同样修改了 slime/ray/rollout.py 和 test_sglang_config.py,涉及 rollout 服务器启动流程,与本 PR 的异步优化相关联。
参与讨论