Prhub

#1991 Cover sglang config parsing with unit tests through resolve_sglang_config

原始 PR 作者 fzyzcjy 合并时间 2026-09-04 13:41 文件变更 3 提交数 1 评论 1 代码增减 +223 / -126

执行摘要

为 SGLang 配置解析添加单元测试,通过 `resolve_sglang_config` 入口覆盖关键行为基线。

commit 消息明确指出:'The raw/resolved config split lands next; these tests pin the parsing behavior through the stable resolve_sglang_config entry so each step of that refactor can prove it kept the semantics.' 这是 Issue #1837 追踪重构的一部分,旨在为配置解析的公共 API 建立行为基线,防止后续重构破坏语义。

该 PR 是重构序列的重要前置,值得快速浏览以了解测试覆盖点。关注新增测试类(如 TestYamlShapeValidation)和迁移策略(从内部类到公共 API),这对理解后续重构的约束有价值。

讨论亮点

Review 中无争议讨论。审核者 guapisolo 批准并指出:'the resolution-coverage gap in #1991 is closed by #1992, #2063, and #2100. I verified the restored contracts at safe-merge checkpoint #2176 and through the checked tip #2649.' 这确认了本 PR 的测试基线被后续 PR 覆盖,覆盖缺口已闭合。

实现拆解

  1. 新增测试文件:在 tests/fast/backends/sglang_utils/test_sglang_config.py 添加新测试类(如 TestNumGpusPerEnginePrecedence、TestOverridesResolution),通过 _resolve_yaml 辅助函数构造 YAML 并调用 resolve_sglang_config,覆盖配置优先级、覆盖项传递、YAML 别名支持、缺失顶级键的拒绝等场景。
  2. 重构现有测试:在 tests/fast/ray/rollout/test_config_matrix.py 中,将原有直接构造 ModelConfig/ServerGroupConfig 的测试(如 TestServerGroupConfigValidation、TestModelConfigResolve)迁移到通过 _resolve_yaml 和 resolve_sglang_config 调用,移除了对内部类(如 ModelConfig)的直接导入和实例化,改为验证完整解析流程。
  3. 简化测试辅助:在 tests/fast/utils/test_sglang_config.py 中,引入 _resolve 辅助函数封装 resolve_sglang_config 调用,并删除 test_update_weights_default_true 测试(该行为已通过其他测试覆盖),统一测试入口。
  4. 验证配置一致性:所有测试均通过公共 API 验证配置解析结果,如 num_gpus_per_engine 优先级、overrides 解析、update_weights 推断、PD-disaggregation 检测等,确保重构后语义不变。
文件 模块 状态 重要度
tests/fast/backends/sglang_utils/test_sglang_config.py 配置解析 added 7.75
tests/fast/ray/rollout/test_config_matrix.py 配置矩阵 modified 7.73
tests/fast/utils/test_sglang_config.py 配置解析 modified 6.46

关键符号

_make_args _resolve_yaml _resolve

关键源码片段

tests/fast/backends/sglang_utils/test_sglang_config.py test-coverage

新增测试文件,通过 `_resolve_yaml` 和 `_make_args` 辅助函数覆盖 `resolve_sglang_config` 的解析路径,包括配置优先级、覆盖项、YAML 验证等,是本 PR 的核心测试补充。

# 辅助函数:构造默认 Namespace 参数用于测试
# defaults 包含所有 resolve_sglang_config 所需参数,允许通过 **overrides 覆盖
​
def _make_args(**overrides) -> Namespace:
    defaults = dict(
        sglang_config=None,
        prefill_num_servers=None,
        rollout_num_gpus=8,
        rollout_num_gpus_per_engine=1,
        eval_num_gpus=0,
        hf_checkpoint="/ckpt/actor",
    )
    defaults.update(overrides)
    return Namespace(**defaults)
​
​
# 辅助函数:写入 YAML 配置并调用 resolve_sglang_config
# 封装了配置路径创建和解析,简化测试用例
​
def _resolve_yaml(tmp_path, yaml_text: str, **args_overrides):
    cfg_path = tmp_path / "config.yaml"
    cfg_path.write_text(yaml_text)
    return resolve_sglang_config(_make_args(sglang_config=str(cfg_path), **args_overrides))
​
​
class TestNumGpusPerEnginePrecedence:
    def test_group_level_value_wins_over_model_and_args(self, tmp_path):
        """验证 server_group 级别的 num_gpus_per_engine 优先于模型级和参数默认值。"""
        cfg = _resolve_yaml(
            tmp_path,
            "sglang:\n"
            "  - name: actor\n"
            "    num_gpus_per_engine: 4\n" # 模型级值
            "    server_groups:\n"
            "      - worker_type: regular\n"
            "        num_gpus: 8\n"
            "        num_gpus_per_engine: 2\n", # 组级值,应优先
            rollout_num_gpus=8,
            rollout_num_gpus_per_engine=1, # 参数默认值
        )
        # 断言组级值覆盖模型级和参数级值
        assert cfg.models[0].server_groups[0].num_gpus_per_engine == 2
tests/fast/ray/rollout/test_config_matrix.py test-coverage

修改现有测试,从直接构造 `ModelConfig`/`ServerGroupConfig` 迁移到通过 `resolve_sglang_config` 调用,覆盖配置验证、PD-disaggregation 等场景,确保测试覆盖完整流程。

# 辅助函数:通过 YAML 文本和参数覆盖调用 resolve_sglang_config
​
def _resolve_yaml(tmp_path, yaml_text: str, **args_overrides):
    cfg_path = tmp_path / "config.yaml"
    cfg_path.write_text(yaml_text)
    return resolve_sglang_config(make_args(sglang_config=str(cfg_path), **args_overrides))
​
​
class TestServerGroupValidation:
    def test_invalid_worker_type_raises(self, tmp_path):
        """验证无效 worker_type 在解析时被拒绝。"""
        with pytest.raises((AssertionError, ValueError), match="worker_type"):
            _resolve_yaml(
                tmp_path,
                "sglang:\n"
                "  - name: actor\n"
                "    server_groups:\n"
                "      - worker_type: invalid\n" # 无效类型
                "        num_gpus: 8\n",
                rollout_num_gpus=8,
            )
​
    def test_zero_num_gpus_raises(self, tmp_path):
        """验证零 GPU 组在解析时被拒绝。"""
        with pytest.raises((AssertionError, ValueError), match="num_gpus"):
            _resolve_yaml(
                tmp_path,
                "sglang:\n"
                "  - name: actor\n"
                "    server_groups:\n"
                "      - worker_type: regular\n"
                "        num_gpus: 0\n", # 零 GPU
                rollout_num_gpus=0,
            )
​
    @pytest.mark.parametrize("wt", ["regular", "prefill", "decode", "placeholder"])
    def test_all_valid_worker_types_accepted(self, wt, tmp_path):
        """验证所有有效 worker_type 都能通过解析。"""
        cfg = _resolve_yaml(
            tmp_path,
            f"sglang:\n  - name: actor\n    server_groups:\n      - worker_type: {wt}\n        num_gpus: 8\n",
            rollout_num_gpus=8,
        )
        assert cfg.models[0].server_groups[0].worker_type == wt

评论区精华

测试覆盖缺口闭合 测试

审核者 guapisolo 确认本 PR 的覆盖缺口被后续 PR #1992, #2063, #2100 闭合,并验证了恢复的契约。

结论:测试覆盖完整,无未解决疑虑。 · 已解决

风险与影响

风险较低,因变更限于测试文件。潜在风险包括:测试覆盖不完整可能遗漏解析边界条件,但本 PR 已覆盖关键路径(如配置优先级、错误拒绝)。迁移测试时若遗漏断言可能弱化覆盖,但审核确认了缺口已闭合。

对用户和系统无直接影响,仅增强测试套件。对团队而言,为后续重构(如 PR#1992 的配置拆分)提供安全网,降低回归风险。测试覆盖从直接类构造迁移到公共 API,提升了测试的稳定性和代表性。

测试覆盖不完整可能遗漏边界条件

关联 Issue

#1837 Tracking issue for refactoring and enhancements

完整报告

参与讨论