Skip to content

fix(sc): apply advantage clipping and overlong filtering - #3927

Merged
yuki-97 merged 7 commits into
mainfrom
yifu/clip_adv
Sep 3, 2026
Merged

fix(sc): apply advantage clipping and overlong filtering#3927
yuki-97 merged 7 commits into
mainfrom
yifu/clip_adv

Conversation

@yfw

@yfw yfw commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports the remaining SingleController parity from #2686:

  • apply configured GRPO advantage clipping after estimation and emit ordinary advantage metrics from the clipped values while retaining pre-clip OPD statistics
  • convert truncated completions to a zero sample_mask when overlong filtering is enabled
  • propagate and validate the setting for SingleController GRPO and PPO, with documentation and regression coverage

The change composes with the message-violation payload fields now on main, reuses the existing TransferQueue schema, and does not alter rollout topology, batching, lifecycle, or retry behavior. It also unblocks validation of the shipped MOPD SingleController nightly and lets the PPO SingleController nightly inherit enabled overlong filtering from its parent recipe.

Validation

  • pytest -q tests/unit/single_controller tests/unit/experience/test_payload.py — 733 passed
  • review-specific regression selection — 18 passed
  • targeted pre-commit hooks — passed
  • Pyrefly — 0 errors across 259 modules
  • git diff --check

Known limitation

The existing SingleController empty-step guard still rejects a step when every completion across that step is filtered; mixed valid/truncated batches follow the intended masked-loss behavior.

Apply configured GRPO advantage bounds after estimation while preserving pre-clipping metrics. Propagate overlong filtering into SingleController payload conversion so truncated completions receive a zero sample mask.

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw
yfw requested review from a team as code owners August 31, 2026 05:17
@copy-pr-bot

copy-pr-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the Documentation Improvements or additions to documentation label Aug 31, 2026

@yfw yfw left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The parity port checks out. I traced the filter/clip ordering against all three legacy paths and SC matches grpo_sync step for step — overlong filtering lands on the same side of compute_advantage as legacy (grpo.py:3372 -> :3403 -> :3540; ppo.py:1507-1514 -> :1546), so no estimator sees a mask it would not have seen on the legacy path. 975 passed across tests/unit/single_controller/ and tests/unit/experience/; pre-commit run --all-files is clean. Both new tests are strong under mutation — deleting the clip block reddens only test_advantage_stage_clips_training_values_after_recording_metrics, and the False leg of the payload parametrize is what catches filtering applied unconditionally.

Worth adding to the PR description: this also repairs a nightly that cannot currently start. examples/configs/recipes/llm/mopd-qwen3-1.7b-3n8g-megatron-pack-single-controller.yaml inherits grpo.overlong_filtering: true from its parent (mopd-qwen3-1.7b-3n8g-megatron-pack.yaml:7) without overriding it, and is listed in tests/test_suites/nightly.txt:277. Resolving that recipe and running the real validator: it passes at this head, and raises NotImplementedError on main (the list entry at config.py:783 as of ccbcd4cc5). It is the only shipped SC recipe whose behaviour this PR changes.

That points at one coverage gap worth closing here: no test runs validate_single_controller_config against a shipped recipe. test_setup.py:398 test_single_controller_mopd_recipe_resolves_to_runtime_contract loads that exact recipe but only MasterConfig.model_validates it and asserts fields, so it stayed green throughout. Adding validate_single_controller_config(config) there — the symbol is not currently imported in that file — would have caught this, and would catch the next one.

Three inline comments follow. All are low severity; none blocks.

Generated by Claude Code

Comment thread nemo_rl/algorithms/single_controller.py Outdated
Comment thread docs/guides/single-controller.md Outdated
Comment thread nemo_rl/algorithms/single_controller_utils/config.py Outdated
Resolve the SingleController payload conflicts while preserving message-violation fields and overlong filtering. Address review feedback for clipped advantage metrics, pre-clip OPD statistics, empty-step diagnostics, and shipped recipe validation.

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw yfw added the CI:Lfast Runs a fast test suite and re-use nightly `main` container (but sync dependencies to PRs version) label Sep 1, 2026
@yfw

yfw commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test bfbb5fe

@yuki-97 yuki-97 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.

Scope: the SC advantage-clipping + overlong-filtering port (16 files), checked against the three legacy paths — grpo.py, grpo_sync.py, ppo.py.

Checked:

  • Clip ordering — relative to the message-level advantage penalties, and pre- vs post-clip metric emission.
  • OPD statistics — that they stay on the estimator output rather than the clipped tensor.
  • token_mask * sample_mask — the mask the estimator actually receives once filtering is on.
  • truncated population — on both the env-rollout and the Gym rollout path.
  • Recipe inheritance — for the two SC recipes that resolve overlong_filtering: true.
  • The new unit coverage.

Parity holds throughout.

Remaining comments are four nits — one test-coverage gap, one docstring wording, and two on the metric/select block in _advantage_stage. None blocking.

Comment thread nemo_rl/algorithms/single_controller_utils/config.py Outdated
Comment thread nemo_rl/algorithms/single_controller.py
Comment thread nemo_rl/algorithms/single_controller.py Outdated
Co-authored-by: Yuki Huang <yukih@nvidia.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
yfw added 2 commits September 1, 2026 19:00
Reuse the precomputed response advantages on PPO while recomputing them after GRPO clipping.

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test fdb049f

yfw added 2 commits September 2, 2026 20:55
Resolve SingleController sample-filtering conflicts by adopting the upstream raw-mask pipeline and retaining GRPO advantage clipping.

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw

yfw commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 796e46e

@yuki-97
yuki-97 enabled auto-merge (squash) September 3, 2026 06:41
@yuki-97
yuki-97 merged commit b7ce030 into main Sep 3, 2026
85 checks passed
@yuki-97
yuki-97 deleted the yifu/clip_adv branch September 3, 2026 08:47
cspades pushed a commit to cspades/RL that referenced this pull request Sep 3, 2026
…#3927)

Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
Co-authored-by: Yuki Huang <yukih@nvidia.com>
pthombre added a commit that referenced this pull request Sep 4, 2026
Main's environment sample masking (#3766) and overlong filtering (#3927)
made the SingleController advantage stage read the per-sample
``mask_sample`` and ``truncated`` fields unconditionally. The native
``pack_payload`` path emits both from each Completion; the capture-mode
``BlackboxFinalizer`` assembles its own train batch from receipts and
never did, so the first streamed batch of a token-capture run died with
``KeyError: 'mask_sample'`` (job 6899709, 64-node rlvr_dolphin_sc).

Thread the two flags through the metadata-only ``FinalizationRequest``:
the rollout manager reads the gym ``instance_config.mask_sample`` flag
and the completion's ``truncated`` flag at dispatch time, the finalizer
actor checks they are parallel to the receipts, and ``finalize_group``
publishes them as bool tensors next to the existing seven fields. Both
new parameters are required so every caller states them.

The ``mask_env_flagged_samples`` gate now pops the flag from receipt
results as well as token results inside ``AsyncNemoGymRolloutImpl``, so
the request builder reads env_extras without reaching into the impl
(job 6903474 died on exactly that reach).

Receipt completions set ``truncated=False`` by existing convention, so
overlong filtering remains inert in capture mode; that is unchanged
behaviour, now visible in the row schema instead of a KeyError.

Signed-off-by: Pranav Thombre <pthombre@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI:Lfast Runs a fast test suite and re-use nightly `main` container (but sync dependencies to PRs version) Documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants