Prhub

#1934 Add GPU placement validation before starting rollout engines

原始 PR 作者 fmh66 合并时间 2026-05-25 15:39 文件变更 3 提交数 2 评论 0 代码增减 +99 / -0

执行摘要

新增 GPU 放置位置验证,修复启动时 IndexError

修复 Issue #1896:多角色 megatron 配置重构(PR #1866)后,critic_only 测试因 placement group GPU 数量与 sglang 引擎配置不一致,在 start_engines 时触发 IndexError: list index out of range

建议快速合入并同步关联 Issue #1896 关闭。该 PR 设计简洁,验证逻辑独立可测试,是良好的防御性编程例证,值得阅读。

讨论亮点

未产生 review 评论讨论。

实现拆解

  1. 新增验证函数:在 slime/ray/rollout_validation.py 创建 validate_server_group_gpu_indices(),接受 placement group 的 gpu_offsetnum_gpu_per_enginenum_engines 等参数,计算所需 GPU 槽位并检查是否溢出可用 GPU 列表。
  2. 注入调用点:在 slime/ray/rollout.pyServerGroup.start_engines() 中,于索引 reordered_gpu_ids 之前调用验证函数,若配置不合法立即抛出 ValueError,附带工作类型、各配置项和修复提示。
  3. 单元测试覆盖:新增 tests/test_rollout_validation.py,包含三个测试用例:有效配置不抛出异常、空引擎组跳过验证、无效配置验证错误消息包含所有关键字段。
文件 模块 状态 重要度
slime/ray/rollout_validation.py rollout added 7.51
slime/ray/rollout.py rollout modified 5.94
tests/test_rollout_validation.py 测试 added 7.02

关键符号

validate_server_group_gpu_indices

关键源码片段

slime/ray/rollout_validation.py core-logic

新增验证函数的入口点,是本次变更的核心。

def validate_server_group_gpu_indices(
    *,
    worker_type: str,
    gpu_offset: int,
    num_gpus_per_engine: int,
    num_gpu_per_engine: int, # 单节点上每个引擎实际使用的 GPU 数
    num_engines: int,
    num_available_gpus: int, # placement group 中可用的 GPU ID 数量
    rollout_num_gpus: int,
    rollout_num_gpus_per_engine: int,
) -> None:
    # 空引擎组直接通过(例如 placeholder 类型)
    if num_engines == 0:
        return
​
    # 计算所需 GPU 槽位:偏移量 + 引擎数 × 每引擎 GPU 数
    required_gpu_slots = gpu_offset + num_engines * num_gpu_per_engine
​
    # 当所有参数合法且不超出可用 GPU 范围时,检查通过
    if gpu_offset >= 0 and num_gpu_per_engine > 0 and required_gpu_slots <= num_available_gpus:
        return
​
    # 否则抛出 ValueError,包含所有配置上下文以方便调试
    raise ValueError(
        "Invalid rollout server group GPU placement: "
        f"worker_type={worker_type}, "
        f"gpu_offset={gpu_offset}, "
        f"num_gpus_per_engine={num_gpus_per_engine}, "
        f"num_gpu_per_engine_on_node={num_gpu_per_engine}, "
        f"num_engines={num_engines}, "
        f"required_gpu_slots={required_gpu_slots}, "
        f"len(reordered_gpu_ids)={num_available_gpus}, "
        f"rollout_num_gpus={rollout_num_gpus}, "
        f"rollout_num_gpus_per_engine={rollout_num_gpus_per_engine}. "
        "Please align --rollout-num-gpus, --rollout-num-gpus-per-engine, "
        "and --sglang-config server_groups."
    )
tests/test_rollout_validation.py test-coverage

新增单元测试,覆盖验证函数的三个场景。

import pytest
from slime.ray.rollout_validation import validate_server_group_gpu_indices@pytest.mark.unit
def test_validate_server_group_gpu_indices_accepts_valid_config():
    # 有效配置:offset=2, 2 engines, 每引擎 1 GPU, 共 4 GPU,不越界
    validate_server_group_gpu_indices(
        worker_type="regular",
        gpu_offset=2,
        num_gpus_per_engine=1,
        num_gpu_per_engine=1,
        num_engines=2,
        num_available_gpus=4,
        rollout_num_gpus=4,
        rollout_num_gpus_per_engine=1,
    )@pytest.mark.unit
def test_validate_server_group_gpu_indices_allows_empty_group():
    # 空引擎组(num_engines=0)应直接通过
    validate_server_group_gpu_indices(
        worker_type="placeholder",
        gpu_offset=4,
        num_gpus_per_engine=1,
        num_gpu_per_engine=1,
        num_engines=0,
        num_available_gpus=4,
        rollout_num_gpus=4,
        rollout_num_gpus_per_engine=1,
    )@pytest.mark.unit
def test_validate_server_group_gpu_indices_reports_config_context():
    # 无效配置:offset=3, 1 engine, 每引擎 2 GPU, 共 4 GPU,需要 5 个槽位 > 4
    with pytest.raises(ValueError) as exc_info:
        validate_server_group_gpu_indices(
            worker_type="regular",
            gpu_offset=3,
            num_gpus_per_engine=2,
            num_gpu_per_engine=2,
            num_engines=1,
            num_available_gpus=4,
            rollout_num_gpus=4,
            rollout_num_gpus_per_engine=2,
        )
    # 验证错误消息中包含所有关键字段
    message = str(exc_info.value)
    assert "worker_type=regular" in message
    assert "gpu_offset=3" in message
    assert "num_gpus_per_engine=2" in message
    assert "num_engines=1" in message
    assert "required_gpu_slots=5" in message
    assert "len(reordered_gpu_ids)=4" in message
    assert "rollout_num_gpus=4" in message
    assert "rollout_num_gpus_per_engine=2" in message

评论区精华

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

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

风险与影响

低风险。仅添加前置检查,不改变引擎启动流程。但需注意当 num_gpu_per_engine 计算为 min(self.num_gpus_per_engine, args.num_gpus_per_node) 时,验证使用裁剪后的值而非原始值,若与后续实际索引逻辑不一致可能导致误报。

对用户:配置错误时更早获得清晰错误信息,提升调试效率。对系统:无性能影响,检查计算量极小。对团队:减少此类回归的排查成本。

回归修复 核心路径变更 配置耦合风险

关联 Issue

#1896 [Bug] test_qwen2.5_0.5B_ppo_critic_only_short.py fails with IndexError at start_engines after #1866 (multi-role megatron config + stale sglang defaults)

完整报告

参与讨论