Prhub

#1884 Extract the rollout cell state machine into cell_state.py

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

执行摘要

提取 Rollout 状态机到独立模块,提升代码组织和可维护性。

根据 PR commit message 和关联 Issue #1837,目的是将状态机提取到独立模块,以便将状态机提升到 ServerCell 而不拖拽引擎模块。这旨在改善代码组织,使状态定义与业务逻辑分离。

本 PR 是一个有价值的重构,值得精读以理解状态机的设计和模块化决策。作为系列重构的一部分,建议结合 Issue #1837 的其他 PR(如 #2048、#2049)一起查看,以把握整体演进方向。

讨论亮点

审查者 guapisolo 批准了 PR,评论为 'LGTM in the #1837 stack context',指出这是一个干净的提取,没有改变生命周期语义,且通过了后续 PR #2176 的安全合并检查。没有其他争议或未解决疑虑。

实现拆解

  1. 新增 miles/ray/rollout/cell_state.py:定义 AddrInfo 模型、状态类层次(StateBase, StateStopped, StateAllocatedBase, StateAllocatedUninitialized, StateAllocatedAlive)和类型别名 CellState,从 server_engine.py 移动而来,并改用 FrozenStrictBaseModel 作为基类以增强不变性。
  2. 修改 miles/ray/rollout/server_engine.py:移除 AddrInfo 和状态类的定义,改为从 cell_state 导入所有相关符号;更新 ServerEngine 类内部对状态类的引用(如 StateStopped 替代 _StateStopped),类型注解也相应调整。
  3. 修改 miles/ray/rollout/server_cell.py:更新导入语句,从 cell_state 导入 AddrInfo,从 server_engine 仅导入 ServerEngine,以适应新的模块划分。
  4. 更新测试文件:在 tests/fast/ray/rollout/test_rollout_server_router_registration.py、tests/fast/ray/rollout/test_server_engine.py 和 tests/fast/ray/rollout/test_server_cell.py 中,将 AddrInfo 的导入源从 server_engine 改为 cell_state,确保测试继续正常工作。
文件 模块 状态 重要度
miles/ray/rollout/cell_state.py 状态机 added 8.23
miles/ray/rollout/server_engine.py 引擎服务 modified 8.61
miles/ray/rollout/server_cell.py 单元格 modified 4.99
tests/fast/ray/rollout/test_rollout_server_router_registration.py 路由器注册 modified 4.13
tests/fast/ray/rollout/test_server_engine.py 引擎测试 modified 4.13
tests/fast/ray/rollout/test_server_cell.py 单元格测试 modified 3.92

关键符号

ServerEngine AddrInfo StateBase StateStopped StateAllocatedBase StateAllocatedUninitialized StateAllocatedAlive

关键源码片段

miles/ray/rollout/cell_state.py core-logic

新增文件,定义了独立的状态机模型(AddrInfo 和状态类),是本次重构的核心产物,为后续模块化提升奠定基础。

# miles/ray/rollout/cell_state.py
# 提取自 server_engine.py 的状态机定义,作为独立模块供 rollout 组件使用。
import ray
from pydantic import ConfigDict
​
from miles.utils.pydantic_utils import FrozenStrictBaseModel
​
​
class AddrInfo(FrozenStrictBaseModel):
    """服务器地址信息,包含 URL 和可选的引导端口。"""
    server_url: str
    bootstrap_port: int | None = None
​
​
# ------------------------- states -----------------------------
​
​
class StateBase(FrozenStrictBaseModel):
    """状态基类,所有状态共享的配置:禁止额外字段、冻结、允许任意类型。"""
    model_config = ConfigDict(extra="forbid", frozen=True, arbitrary_types_allowed=True)
​
​
class StateStopped(StateBase):
    """停止状态:表示引擎未分配或已停止。"""
    pass
​
​
class StateAllocatedBase(StateBase):
    """已分配状态基类:包含 Actor 句柄和可选的地址信息。"""
    actor_handle: ray.actor.ActorHandle
    addr_info: AddrInfo | None = None
​
​
class StateAllocatedUninitialized(StateAllocatedBase):
    """已分配但未初始化状态:已分配 Actor 但地址信息尚未设置。"""
    pass
​
​
class StateAllocatedAlive(StateAllocatedBase):
    """已分配且存活状态:Actor 已分配并完成初始化。"""
    pass
​
​
CellState = StateStopped | StateAllocatedUninitialized | StateAllocatedAlive
miles/ray/rollout/server_engine.py core-logic

核心源码文件,移除了内联的状态定义并改为从 cell_state 导入,展示了重构后的依赖关系。

# miles/ray/rollout/server_engine.py
# 重构后,ServerEngine 类从 cell_state 导入状态模型,保持状态机逻辑不变。
from __future__ import annotations
​
import logging
​
import ray
​
from miles.backends.sglang_utils.sglang_api_client import SGLangApiClient
from miles.ray.rollout.cell_state import (
    AddrInfo,
    CellState,
    StateAllocatedAlive,
    StateAllocatedBase,
    StateAllocatedUninitialized,
    StateStopped,
)
​
logger = logging.getLogger(__name__)
​
​
class ServerEngine:
    def __init__(self):
        self._state = StateStopped() # 初始状态改为公共名称
​
    def mark_allocated_uninitialized(self, actor_handle: ray.actor.ActorHandle):
        # 状态转换使用从 cell_state 导入的类型
        self._change_state("mark_allocated", StateStopped, StateAllocatedUninitialized(actor_handle=actor_handle))
​
    # ... 其他方法类似,所有状态类引用均已更新 ...
​
    def _change_state(
        self,
        debug_name: str,
        old_state_cls: type[CellState] | tuple[type[CellState], ...], # 类型注解使用新的 CellState 别名
        new_state: CellState,
    ) -> None:
        # 状态变更逻辑不变,但类型更清晰
        logger.info(f"{debug_name} start old={self._state}")
        assert isinstance(self._state, old_state_cls), f"{self._state=}"
        self._state = new_state
        logger.info(f"{debug_name} end new={self._state}")

评论区精华

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

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

风险与影响

风险较低,因为这是纯重构,没有改变功能逻辑或运行时行为。主要潜在风险是导入错误可能导致循环依赖,但变更范围小,且所有测试文件已同步更新。状态机类名从私有(带下划线前缀)变为公共(无下划线前缀),可能影响 API 稳定性,但这些类目前仅在 rollout 内部使用。

对用户无直接影响,因为这是内部重构。对系统的影响是提高了代码模块化,为未来将状态机提升到 ServerCell 做准备,有助于降低模块间耦合。对团队的影响是改善了代码可维护性,使状态逻辑更清晰,便于后续开发和调试。

导入重构 状态机迁移

关联 Issue

#1837 Tracking issue for refactoring and enhancements

完整报告

参与讨论