Skip to content

Pipelined step boundary: run end-of-step bookkeeping off the event loop - #2901

Open
s-noghabi wants to merge 1 commit into
parambole/l3-convergefrom
noghabi/mlperf-send-early-on-2877
Open

s-noghabi wants to merge 1 commit into
parambole/l3-convergefrom
noghabi/mlperf-send-early-on-2877

Conversation

@s-noghabi

Copy link
Copy Markdown
Collaborator

Stacked on #2877 (parambole/l3-converge). Addresses review finding F1: with pipeline_step_boundary on, end-of-step bookkeeping still ran on the event loop and blocked micro-batch 2 of step N+1.

What changed (only when pipeline_step_boundary is on)

  • After the next step's mb1 has been sent ahead, the step-N bookkeeping runs in a background task (_finish_step_bookkeeping):
    • it awaits the send-ahead;
    • it runs metric reduction and _log_consumed_trajectories in asyncio.to_thread;
    • then it sets last_step_result, writes the Train step log, and calls on_step_end.
  • The loop moves straight on to step N+1: _step += 1, then _release_window(). Dispatch, polling and prepack are no longer frozen for about 2.5 s at each boundary. On at-sph1-s1a, the median for metrics + log was 1.35 s and for GC 1.14 s.
  • gc.collect() stays inline, at the point where the base loop runs it, which is during mb1 compute. Running it in the background holds the GIL and starves the next step's prepack feeds. That cost stages in the tests.
  • Callback order is preserved. on_step_begin(N+1) is deferred until on_step_end(N) has run (_begin_step).
  • Pending bookkeeping is awaited before the next weight sync, because it reads policy_version. It is also awaited on loop errors, so on_step_end(N) runs before the error surfaces, and when training finishes. A failure in bookkeeping fails the run.
  • The flag-off path is unchanged. The frozen-base-loop equivalence tests still pass.

Tests

Supersedes #2885 / #2886.

…oundaries

With pipeline_step_boundary on, the loop used to await the send-ahead and then
run metric reduction, trajectory logging, the step log and on_step_end inline
before starting step N+1, which froze dispatch/polling for ~2.5 s per boundary.
Move that bookkeeping into a background task (reduction via asyncio.to_thread),
keep gc.collect inline during mb1 compute, defer on_step_begin(N+1) until
on_step_end(N) has run, await pending bookkeeping before the next weight sync
and on loop errors. Flag-off path unchanged.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request optimizes the pipelined training boundary by offloading heavy end-of-step bookkeeping tasks to a background thread. By decoupling metric reduction and logging from the main event loop, the system can now proceed to the next training step without waiting for the previous step's post-processing to finish. This change significantly reduces idle time in the pipeline, ensuring that critical stages like dispatch and prepack remain responsive while maintaining strict ordering and error propagation guarantees.

Highlights

  • Background Bookkeeping: Moved end-of-step bookkeeping (metric reduction, logging, and on_step_end) to a background task when pipeline_step_boundary is enabled, preventing it from blocking the event loop and the start of the next step.
  • Performance Improvement: Dispatch, polling, and prepack stages are no longer frozen during the ~2.5s boundary period, significantly improving throughput by allowing the next step to proceed immediately.
  • Callback Integrity: Ensured on_step_begin for the next step is deferred until on_step_end of the current step completes, preserving the original callback execution order.
  • Robustness: Added error handling to ensure that failures in background bookkeeping tasks correctly fail the overall training run, and added comprehensive tests to verify behavior.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces background bookkeeping for pipelined step boundaries in the RL program orchestrator, allowing step k+1 to start and send its second micro-batch without waiting for step k's metric reduction, logging, and host-memory release. These bookkeeping tasks are offloaded to a background task (_finish_step_bookkeeping) and run off the event loop using asyncio.to_thread. Corresponding unit tests have been added to verify this behavior and ensure callbacks maintain their correct order. The feedback suggests improving type safety by replacing Any with more specific type annotations for the background task parameter in _log_task_failure and the filtered_groups parameter in _finish_step_bookkeeping.

pass


def _log_task_failure(task: asyncio.Task[Any]) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide, strict and specific type hints should be used instead of Any whenever a more specific type can be expressed. Since _pending_bookkeeping is typed as asyncio.Task[None] | None, the task parameter in _log_task_failure can be typed as asyncio.Task[None].

Suggested change
def _log_task_failure(task: asyncio.Task[Any]) -> None:
def _log_task_failure(task: asyncio.Task[None]) -> None:
References
  1. Type hints & Strict Typing: Always annotate function signatures, attributes, and data structures with concrete, specific types rather than Any. Avoid Any whenever a specific type can be expressed. (link)

policy_training_time: float,
weight_sync_time: float,
metrics_fetch_time: float,
filtered_groups: list[Any],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide, strict and specific type hints should be used instead of Any whenever a more specific type can be expressed. Since _drain_filtered_groups returns list[list[datatypes.TrajectoryItem]], filtered_groups can be typed as list[list[datatypes.TrajectoryItem]] instead of list[Any].

Suggested change
filtered_groups: list[Any],
filtered_groups: list[list[datatypes.TrajectoryItem]],
References
  1. Type hints & Strict Typing: Always annotate function signatures, attributes, and data structures with concrete, specific types rather than Any. Avoid Any whenever a specific type can be expressed. (link)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants