Prhub

#7677 [rollout] test: simplify router test suite, drop stale yaml comment

原始 PR 作者 touch869 合并时间 2026-09-02 18:52 文件变更 2 提交数 1 评论 1 代码增减 +148 / -190

执行摘要

优化路由器测试套件,清理过时注释。

作为 PR#7115(为 rollout 服务器引入可插拔路由器)的后续维护,本次变更旨在提升测试代码的质量与可维护性。具体动机包括:

1) 清除 rollout.yaml 中关于 router_config_path 的一个过时注释(pkg:// 支持已在 #7115 合入前移除);
2) 简化 test_router_on_cpu.py 测试套件,消除重复测试和仅验证 mock 内部状态的测试,转而使用行为驱动的断言;
3) 首次为 LLMServerClient 的 require_acquire_fields/require_release_fields 延迟查询和缓存行为提供客户端侧测试覆盖。

这是一个典型的测试维护 PR,虽然变更量不小,但逻辑清晰、目标明确。它非常值得精读,尤其是测试重构的思路(如使用行为计数器验证缓存机制、清理冗余测试)对于编写高质量单元测试有很好的借鉴意义。关注点在于理解作者如何从“测试 mock 内部”转向“测试交互行为”的设计决策。

讨论亮点

本次 PR 的 review 过程中没有技术讨论。合入者 wuxibin89 直接给予了批准。作者在 PR body 中详细说明了每个变更的理由和达成的测试效果,表明这是一个清晰、低风险的清理任务。

实现拆解

  1. 配置注释修正:在 verl/trainer/config/rollout/rollout.yaml 中,将 router_config_path 字段上方的注释从 "Supports pkg://<package>/<path>." 修正为 "Relative paths resolve against the working directory, then the verl package root.",以反映当前路径解析机制(由 resolve_config_path 实现)。
  2. 测试文件重构:对 tests/workers/rollout/test_router_on_cpu.py 进行全面重构,从 22 个测试精简为 20 个,代码行数减少。
    - 引入 mock 计数器:为 _MockPluginLoadBalancer 添加 acquire_field_queries 和 release_field_queries 属性及 get_require_query_counts() 方法,用于追踪 require_acquire_fields 和 require_release_fields 被调用的次数,从而以行为方式验证客户端是否遵循了“查询一次并缓存”的逻辑。
    - 简化测试 fixture:将原本分散的 Ray 初始化代码提取为模块级 ray_session fixture,并新增 _write_router_yaml 和 _plugin_lb 辅助函数,简化了插件路由器的创建过程。
    - 重组测试类与增强断言:将测试按功能分组为 TestGetRouterHandleDefault、TestGetRouterHandlePluginExtensionYaml 和 TestRequireFields。例如,test_client_acquire_serializes_only_declared_fields 不再检查 LLMServerClient 的私有缓存属性 _lb_require_acquire_fields,而是通过 get_require_query_counts 验证 mock 的 RPC 调用次数(构造时 (0,0) -> 首次获取后 (1,1) -> 第二次获取后仍为 (1,1)),并新增了对 client._release_server() 路径的覆盖。
    - 清理冗余测试:移除了两个仅断言 mock 自身行为的测试(test_acquire_least_loaded 和 test_plugin_declares_prompt_ids),它们的覆盖已被更精确的分布断言或新的客户端测试所取代。
  3. 测试验证:所有更改通过了 pytest tests/workers/rollout/test_router_on_cpu.py 和 pytest tests/experimental/agent_loop/test_basic_agent_loop.py -k LoadBalancer,确保现有功能不受影响。
文件 模块 状态 重要度
tests/workers/rollout/test_router_on_cpu.py 路由测试 modified 7.73
verl/trainer/config/rollout/rollout.yaml 配置管理 modified 2.9

关键符号

get_require_query_counts _write_router_yaml _plugin_lb

关键源码片段

tests/workers/rollout/test_router_on_cpu.py test-coverage

核心测试文件,经历了全面重构,是本次 PR 的主要变更对象。

# 测试重构的关键:为 mock 插件添加查询计数器,用于验证客户端行为
class _MockPluginLoadBalancer:
    # ... ( 其他方法 )
    def __init__(self, ...):
        # ... ( 其他属性 )
        self.acquire_field_queries = 0 # 追踪 require_acquire_fields 被调用次数
        self.release_field_queries = 0 # 追踪 require_release_fields 被调用次数
​
    def get_require_query_counts(self) -> tuple[int, int]:
        """返回 (require_acquire_fields, require_release_fields) 的 RPC 调用次数。"""
        return (self.acquire_field_queries, self.release_field_queries)
​
    def require_acquire_fields(self) -> list[str]:
        """协议方法:声明此内容感知型 mock 路由依赖 prompt token ids。"""
        self.acquire_field_queries += 1 # 每次调用增加计数
        return ["prompt_ids"]
​
    def require_release_fields(self) -> list[str]:
        """协议方法:声明通过 request_id 归属释放。"""
        self.release_field_queries += 1
        return ["request_id"]
​
# 测试客户端延迟查询与缓存行为的新测试
def test_client_acquire_serializes_only_declared_fields(self, ray_session, tmp_path):
    """验证声明被懒查询并缓存,仅声明的字段通过 RPC 传输。"""
    lb = _plugin_lb({"s0": None}, tmp_path)
    client = LLMServerClient(config=OmegaConf.create({}), load_balancer_handle=lb)
    # 构造时未发起 RPC 查询
    assert ray.get(lb.get_require_query_counts.remote()) == (0, 0)
    sid, _ = asyncio.run(client._acquire_server("req-1", prompt_ids=[1, 2, 3], ...))
    # 首次 acquire 后,两个声明查询各被调用一次
    assert ray.get(lb.get_require_query_counts.remote()) == (1, 1)
    # 第二次 acquire 复用缓存,不再发起查询
    sid, _ = asyncio.run(client._acquire_server("req-2", prompt_ids=[4]))
    assert ray.get(lb.get_require_query_counts.remote()) == (1, 1)
    # 验证 release 路径也正确传递了声明的字段
    client._release_server(sid, request_id="req-2")
    assert ray.get(lb.get_releases.remote()) == [(sid, "req-2")]

评论区精华

PR 设计决策与实现细节 设计

作者在 PR body 中详细阐述了重构动机和实现方法,包括使用 mock 计数器验证延迟查询、增强断言、清理冗余测试等具体技术决策。

结论:变更逻辑清晰,设计合理,通过增强行为断言提升了测试的可靠性和表达力。 · 已解决

风险与影响

测试质量风险:测试重构可能无意中移除或削弱了对某些边界情况的覆盖。但通过 PR body 中的描述,可以看到作者有意识地用更强的断言(如检查 mock 的分布、RPC 调用次数)替代了脆弱的内部状态检查,整体测试质量是提升的。
配置风险:对 rollout.yaml 注释的修正消除了用户的潜在误解,降低了配置错误的风险。
回归风险:由于主要变更是测试代码和文档注释,且通过了现有测试套件,对生产代码逻辑的直接回归风险极低。

对用户的影响:无直接影响。配置注释的修正有助于用户正确理解 router_config_path 的配置方式。
对系统的影响:无功能性影响。本次变更纯粹是测试和文档层面的优化。
对团队的影响:显著提升了测试代码的可读性、可维护性和断言强度。引入的 mock 计数器模式为验证类似客户端-服务端缓存交互提供了范例。更清晰的测试组织有助于未来路由器功能的开发和调试。

测试重构可能遗漏边界情况

关联 Issue

#6383 [RFC] KV-cache-aware request load balancer for rollout servers

完整报告

参与讨论