Prhub

#6461 [fully_async] feat: fully async profiling

原始 PR 作者 tardis-key 合并时间 2026-05-26 15:58 文件变更 5 提交数 5 评论 4 代码增减 +64 / -25

执行摘要

为 fully_async 模式添加 profiling 支持,统一步数定义

PR body 指出:“The concept of step is ambiguous in fully async mode. In rollout, rollout_step refers to the generation of each prompt. In the trainer, train_step means executing one mini-batch step. … Therefore, we follow the step definition of sync mode, and regard the completion of one update_weights as the end of one training step.” 为了与同步模式的 profiling 体验对齐,需要将 profiling 绑定到参数更新次数而非 mini-batch step。

建议精读。该 PR 是 fully_async 功能完善的重要一步,其中关于 step 概念统一的设计值得关注(区分 global_steps 与 current_param_version)。代码改动集中在少数文件,逻辑清晰,适合作为异步模式下 profiling 集成的参考实现。

讨论亮点
  1. gemini-code-assist[bot] 对 agent_loop.py 的评论:指出 generate 方法中使用了未定义的 kwargs 变量和缺少 DiffusionOutput 导入。但该文件最终未包含在 PR 变更集中,因此这两个问题未在本次 PR 中实际引入或解决。

  2. gemini-code-assist[bot] 对 fully_async_rollouter.py 的评论:指出 get_curr_step_profile 方法中访问 self.config.global_profiler.rollout_steps 可能因配置键不存在而引发 AttributeError。但在最终提交的代码中,get_curr_step_profile 方法并未出现,取而代之的是在 _fit_update_weights 中直接使用 global_profiler.steps。因此该问题已被隐式解决。

最终审批者 wuxibin89 直接 approved,未进一步评论。

实现拆解

  1. 扩展父类 profiling 方法:在 verl/experimental/separation/ray_trainer.py 中,为 _fit_start_profile_fit_stop_profile 增加可选的 should_profiler 参数。若外部传入该参数,则忽略内部 prev_step_profile / curr_step_profile 逻辑,直接使用传入值决定是否启停 profiler。这为子类提供了灵活的 profiling 控制点。

  2. 调整 FullyAsyncTrainer 初始化与 step 判断:在 verl/experimental/fully_async_policy/fully_async_trainer.pyfit 方法中,将 curr_step_profile 初始化为 False(不再依赖 global_steps 计算)。在 fit_step 中,使用 steps = self.config.global_profiler.steps(self.current_param_version + 1) in steps 计算 should_profile,并传递给 _fit_start_profile_fit_stop_profile

  3. 在权重更新前后控制 rollout profiling:在 _fit_update_weights 方法中,在调用 update_weights 之前,根据当前 current_param_version 是否在 profile steps 中,调用 self.rollouter._stop_profiling.remote() 停止旧 step 的 profiling;在 update_weights 之后,根据下一个 current_param_version + 1 是否在 profile steps 中,调用 self.rollouter._start_profiling.remote() 启动新 step 的 profiling。

  4. 在 Rollouter 中实现 profiling 启停接口:在 verl/experimental/fully_async_policy/fully_async_rollouter.py 中新增 _start_profiling_stop_profiling 两个 async 方法,分别委托给 self.llm_server_manager.start_profile()self.llm_server_manager.stop_profile(),复用 agent loop 已有的 profiling 能力。

  5. 更新 Ascend profiling 文档:在 docs/ascend_tutorial/dev_guide/performance/ascend_profiling_en.rstascend_profiling_zh.rst 中添加「Fully Async Policy 模式说明」,强调 global_profiler.steps 指每次 update_weights 后的 step,与同步模式一致,并指出该模式复用 AgentLoop 的采集能力,注意事项相同。

文件 模块 状态 重要度
verl/experimental/separation/ray_trainer.py 分离训练器 modified 7.34
verl/experimental/fully_async_policy/fully_async_trainer.py 异步训练器 modified 6.74
verl/experimental/fully_async_policy/fully_async_rollouter.py 异步回滚器 modified 6.29
docs/ascend_tutorial/dev_guide/performance/ascend_profiling_en.rst Ascend 文档 modified 2.02
docs/ascend_tutorial/dev_guide/performance/ascend_profiling_zh.rst Ascend 文档 modified 2.02

关键符号

_fit_start_profile _fit_stop_profile _start_profiling _stop_profiling fit fit_step _fit_update_weights

关键源码片段

verl/experimental/separation/ray_trainer.py core-logic

修改父类 profiling 方法 `_fit_start_profile` 和 `_fit_stop_profile`,添加 `should_profiler` 参数以支持外部控制,这是实现子类定制的基础。

# verl/experimental/separation/ray_trainer.py# 修改前:_fit_start_profile 无参数,自行决定 profile 开关
# 修改后:接受可选的 should_profiler,若不为 None 则直接使用,
# 否则使用原有的基于 prev/curr_step_profile 的逻辑。
def _fit_start_profile(self, should_profiler=None):
    timing_raw = self.timing_raw
    if should_profiler is not None:
        # 外部传入:直接覆盖自我状态
        self.curr_step_profile = should_profiler
    with marked_timer("start_profile", timing_raw):
        if should_profiler is None:
            # 原有判断:连续 profile 模式下判断起始条件
            do_profile = (
                not self.prev_step_profile and self.curr_step_profile
                if self.config.global_profiler.profile_continuous_steps
                else self.curr_step_profile
            )
        else:
            do_profile = should_profiler
        self._start_profiling(do_profile)def _fit_stop_profile(self, should_profiler=None):
    timing_raw = self.timing_raw
    with marked_timer("stop_profile", timing_raw):
        if should_profiler is None:
            # 原有判断:计算下一个 step 是否需要 profile
            self.next_step_profile = (
                self.global_steps + 1 in self.config.global_profiler.steps
                if self.config.global_profiler.steps is not None
                else False
            )
            do_profile = (
                self.curr_step_profile and not self.next_step_profile
                if self.config.global_profiler.profile_continuous_steps
                else self.curr_step_profile
            )
        else:
            do_profile = should_profiler
        self._stop_profiling(do_profile)
        self.prev_step_profile = self.curr_step_profile
        if should_profiler is None:
            self.curr_step_profile = self.next_step_profile
verl/experimental/fully_async_policy/fully_async_trainer.py core-logic

核心变更文件,调整 fit 和 fit_step 中的 profile 初始化与触发逻辑,并在 _fit_update_weights 中集成 rollouter profiling 启停。

# verl/experimental/fully_async_policy/fully_async_trainer.pyasync def fit(self):
    ...
    self.global_steps += 1
    self.prev_step_profile = False
    # 子类中不再依赖 global_steps 计算 curr_step_profile
    self.curr_step_profile = False # 修改前:基于 global_steps 初始化
    self.next_step_profile = False
    ...async def fit_step(self, batch_dict: dict = None):
    ...
    # 使用 (current_param_version + 1) 对应“即将完成的这次 update_weights”
    steps = self.config.global_profiler.steps
    should_profile = steps is not None and (self.current_param_version + 1) in steps
    self._fit_start_profile(should_profiler=should_profile) # 传递外部定义
    with marked_timer("step", self.timing_raw):
        ...
        await self._fit_update_weights()
        ...
    self._fit_stop_profile(should_profiler=should_profile)
    ...async def _fit_update_weights(self):
    if self.local_trigger_step != 1:
        return
    # 在参数同步之前停止当前 step 的 rollout profiling
    steps = self.config.global_profiler.steps
    last_profiler_step = self.current_param_version
    if steps is not None and last_profiler_step in steps:
        await asyncio.wrap_future(self.rollouter._stop_profiling.remote().future())
​
    with marked_timer("timing_s/param_sync", self.timing_raw):
        await self.checkpoint_manager.update_weights(global_steps=self.current_param_version)
        ...
​
    # 在参数同步之后启动下一个 step 的 rollout profiling
    profiler_step = last_profiler_step + 1
    if steps is not None and profiler_step in steps:
        await asyncio.wrap_future(self.rollouter._start_profiling.remote().future())
    ...

评论区精华

agent_loop.py 中 generate 方法的潜在缺陷 正确性

gemini-code-assist[bot] 指出 agent_loop.py 的 generate 方法使用了未定义的 kwargs 变量和缺少 DiffusionOutput 导入,可能导致 NameError。

结论:该文件未包含在最终 PR 变更集中,因此问题未实际出现或已在其他地方修复。 · outdated

fully_async_rollouter.py 中配置键 rollout_steps 的合法性 正确性

gemini-code-assist[bot] 指出 get_curr_step_profile 方法中 self.config.global_profiler.rollout_steps 可能因 OmegaConf struct 模式而引发 AttributeError,建议确认配置键。

结论:最终代码中删除了 get_curr_step_profile 方法,改为直接使用 global_profiler.steps,该问题不再存在。 · 已解决

风险与影响

  1. 异步时序风险:在 _fit_update_weights 中,profiling 的 start/stop 与实际的参数同步之间可能存在竞态条件。如果 rollouter 的 _stop_profiling_start_profiling 调用出现在网络或队列延迟之后,可能导致 profiling 数据与预期 step 不匹配。目前未看到显式的同步等待或回调保障。
  2. 缺少测试覆盖:PR 未包含任何新的测试用例,仅依赖手动的端到端验证(如 PR body 所示)。配置文件路径或边界情况(如 profile_continuous_steps 启用时)无法在 CI 中持续验证。
  3. 依赖内部接口llm_server_manager.start_profile()stop_profile() 是 agent loop 的内部方法,若未来 agent loop 重构,该接口可能失效或改变语义。
  4. 文档误导风险:中文文档中 global_profiler.steps 代表每一轮 update_weights 后的 step 的表述可能不够精确,用户容易混淆 mini-batch step 与 weight-update step 的计数起点。

用户影响:开发者和用户在 fully_async 模式训练时,现在可以通过与同步模式相同的配置方式启用 global_profiler.steps 进行性能剖析,降低了使用门槛。
系统影响:新增的 profiling 调用会增加训练流程中的异步 RPC 开销,但仅在指定 step 触发,整体开销可控。
团队影响:该 PR 清晰定义了异步模式下的 step 语义,为后续 metrics、checkpoint 等与 step 相关的功能提供了统一时间基准。

异步时序敏感 缺少测试覆盖 依赖 llm_server_manager 接口

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论