Skip to content

Filter zero-advantage groups in AsyncGRPO - #7609

Merged
AmineDiro merged 12 commits into
mainfrom
async-grpo-filter-zero-advantage
Oct 28, 2026
Merged

AmineDiro merged 12 commits into
mainfrom
async-grpo-filter-zero-advantage

Conversation

@AmineDiro

@AmineDiro AmineDiro commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

The rollout worker now drops a group whose rollouts all got the same reward, or none of which could be scored, before it reaches the trainer. Every advantage in such a group is zero, so it costs a forward and backward and moves nothing. This is DAPO's dynamic sampling without resampling to a fixed group count.

The loop does not replace a dropped group, so batch/groups_per_step goes down while the batch keeps samples_per_step samples. Filtered groups never reach the collator, so an epoch takes more prompts than len(train_dataset).

Metrics: rollout/groups_filtered_zero_advantage counts dropped groups and reward_filtered is their mean reward. reward covers trained groups only, so the two are meant to be read together.


Note

Medium Risk
Changes GRPO training dynamics, rollout queue protocol, epoch boundaries, and checkpoint resume semantics; incorrect wiring could skip prompts or stall on the queue, but scope is confined to async GRPO rollout/scoring.

Overview
Async GRPO now skips groups that cannot produce a learning signal (all scorable rollouts share the same reward, so advantages are zero everywhere). The rollout worker enqueues a bare group_id on the buffer instead of RolloutSamples; the trainer records these in _dropped_groups and keeps pulling until the step still reaches samples_per_step, so more prompts may be generated per optimizer step when many groups are filtered.

Epoch stop, rollout_state.json resume, and the rollout worker protocol are updated so a dropped prompt still counts as consumed (trained βˆͺ dropped), matching one pass over the dataset without resampling to replace filtered groups. AsyncGRPOConfig now requires num_generations >= 2.

Logging distinguishes reward (mean over every scored group, including dropped) from per-sample reward_kept and rollout frac_reward_zero_std. Advantage scoring for partially unscorable groups is tightened so centering runs on scorable rollouts only. Docs and tests cover buffer behavior, checkpoints, and epoch counting with synthetic drops.

Reviewed by Cursor Bugbot for commit 9dcb8b5. Bugbot is set up for automated code reviews on this repo. Configure here.

@bot-ci-comment

bot-ci-comment Bot commented Oct 9, 2026

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot is paused β€” on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@qgallouedec
qgallouedec requested a review from lewtun October 12, 2026 05:12
@qgallouedec

Copy link
Copy Markdown
Member

thanks, as discussed yesterday, please link/close all the issues that asked for "dynamic sampling"

@AmineDiro

Copy link
Copy Markdown
Member Author

Closes #3708. Also requested in #3973 and #4764 (closed as duplicates), with an earlier GRPOTrainer attempt in #3758.

Also Related: #6367.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

…o-advantage

# Conflicts:
#	tests/experimental/test_async_grpo_trainer.py
…o-advantage

# Conflicts:
#	docs/source/async_grpo_trainer.md

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit aa84c8a. Configure here.

@qgallouedec qgallouedec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, lgtm overall. A few things before merging:

  1. reward_std == 0.0 (L1224) misses some constant groups: with a constant reward of 0.1 and 3, 6, 7, 12 or 64 generations, numpy gives a std around 1e-17 πŸ₯². Checking that all scored rewards are equal is exact.
  2. As planned in #7841 and #7835, let's log GRPO's frac_reward_zero_std (a fraction of groups) instead of rollout/zero_advantage_groups_total.
  3. reward now covers every scored group, but reward_std and rewards/<func> still come from the kept samples (L1259-L1260), so reward_std reads higher than in GRPO. Can we compute them in the worker before the filter, like reward, and drop reward_kept?
  4. The description still has the old metric names and says reward covers trained groups only. "Closes #3708" also needs to be in the description

@lewtun lewtun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for adding this @AmineDiro ! I left a few comments on top of Quentin's review.

Turbo nit on naming: we use "filtered" in the code (_filtered_groups, TestFilterZeroAdvantageGroups), "dropped" in the docs and "zero advantage" in the metric. Should we stick to just one wording consistently (e.g. dropped)?

Comment thread docs/source/async_grpo_trainer.md Outdated
| `rollout/vllm_preemptions_total` | requests the server preempted during the logging window (vLLM's `num_preemptions_total`, summed over engines). Reported only: a weight sync resets the server's caches, which preempts every running request, so it is not a pressure signal |
| `rollout/zero_advantage_groups_total`, `reward_kept` | groups dropped because every rollout got the same reward, and the mean reward of the groups that were trained on. `reward` is the policy's reward over every scored group, dropped ones included, so it is the one to track |

A group whose rollouts all received the same reward (or none of which could be scored) has zero advantage everywhere, so the worker drops it instead of enqueuing it: it would cost a forward and backward and move nothing. The loop does not resample to replace a dropped group, so `batch/groups_per_step` goes down while the batch keeps its `samples_per_step` samples. A dropped group still counts toward an epoch, which stays one pass over the prompts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are you sure batch/groups_per_step goes down? IIUC each step still fills up to samples_per_step from the kept groups, and the metric counts trained groups

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right! I mainly asked claude to right this feature it seemed to be easy enough to one shot 🫠 🫠 🫠 Thanks for reviewiing I should looked at this closer.

Fixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good to see humans still have an edge :)

@lewtun lewtun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for iterating! LGTM with a small nit about the docs

| `rollout/vllm_running_requests` | requests the server's engines are decoding (vLLM's `num_requests_running`, summed over engines). The gap to `rollout/inflight` is what sits in the API server process, building responses |
| `rollout/vllm_queued_requests` | requests the server has accepted but not yet scheduled (vLLM's `num_requests_waiting`, summed over engines). The cap does not grow while this is non-zero |
| `rollout/vllm_preemptions_total` | requests the server preempted during the logging window (vLLM's `num_preemptions_total`, summed over engines). Reported only: a weight sync resets the server's caches, which preempts every running request, so it is not a pressure signal |
| `frac_reward_zero_std`, `reward_kept` | fraction of groups dropped because every scored reward was equal, or fewer than two rollouts were scored (GRPO only counts the first case), and the mean reward of the groups that were trained on. `reward` is the policy's reward over every scored group, dropped ones included, so it is the one to track |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

turbo nit: put reward_kept on a new line so it's easier to read

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok noted I thiink we'll redo the docs in stable migration, I'll note this !

@AmineDiro
AmineDiro merged commit e29597a into main Oct 28, 2026
6 checks passed
@AmineDiro
AmineDiro deleted the async-grpo-filter-zero-advantage branch October 28, 2026 16:00
lewtun added a commit that referenced this pull request Nov 10, 2026
Rebase the group metrics on the zero-advantage group filtering from #7609:
`frac_reward_zero_std` now covers `group/active_frac`, `group/active_total`
and `group/scored_total`, so they are removed, and
`group/inactive_reward_mean` becomes `group/dropped_reward_mean`, keyed on the
same test that drops the group. Rename `group/unscorable_total` to
`group/unscorable_count` after #8186, so `_reduce_metric` sums it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants