Skip to content

feat(grpo): make env-flagged sample masking optional - #3402

Merged
yuki-97 merged 6 commits into
NVIDIA-NeMo:mainfrom
michal2409:split/mask-env-flagged-gate
Jul 31, 2026
Merged

feat(grpo): make env-flagged sample masking optional#3402
yuki-97 merged 6 commits into
NVIDIA-NeMo:mainfrom
michal2409:split/mask-env-flagged-gate

Conversation

@michal2409

@michal2409 michal2409 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

NeMo-Gym environments and agents can flag individual rollout samples as "don't train on this" (the mask_sample flag, added in #3163). Flagged samples are dropped from the policy-gradient loss while still counting toward advantage computation. Currently this behavior is always on: every gym rollout batch carries the flags, and any sample the environment flags is silently masked out.

This PR makes that behavior configurable via one optional boolean, env.should_mask_flagged_samples (documented in the gym exemplar YAMLs, next to should_log_nemo_gym_responses):

  • Absent or true (default): behavior is unchanged — flags are carried in the batch and flagged samples are masked from the loss.
  • false: the mask_sample key is never added to the rollout batch, so no env-driven masking happens. Downstream loss code already treats a missing key as a no-op, so nothing else needs to change.

The key is read in a single helper (should_mask_flagged_samples in rollouts.py) used by the sync trainer, the async trajectory collector, and AsyncNemoGymRolloutImpl (so the future single-controller flow honors the gate too — there, turning it off drops the flag from the result before it lands in Completion.env_extras).

Why you'd want to turn it off: the flags can be too coarse to honor — Gym flags rollouts that hit max iterations even when they solve the task, and those are samples worth training on (#3201 (comment)). Also, how many samples get flagged is decided by the environment/agent and varies run to run, so masking changes the effective batch composition non-deterministically. For controlled experiments and benchmark runs we need the loss to see every sample; A/B runs showed the masked-sample ramp changing training dynamics mid-run. The gate makes that choice explicit while leaving the default exactly as it is.

@michal2409
michal2409 requested review from a team as code owners July 29, 2026 11:06
@copy-pr-bot

copy-pr-bot Bot commented Jul 29, 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.

Adds grpo.mask_env_flagged_samples (NotRequired). Absent or true keeps
today's behavior byte-for-byte: NeMo-Gym rollout batches carry the
env-driven mask_sample flags and _apply_mask_sample_filter masks the
flagged samples from the loss. Setting false keeps the mask_sample key
out of the rollout batch entirely (flags are still read from the env
results; they are just not attached to the training batch).

Why an off switch: on a large sequence-packed async NeMo-Gym run
(Qwen3.5-397B, 256 rollouts/step, Megatron sequence packing), the mere
presence of the [B]-bool mask_sample key in the batch reproducibly
collapsed training even when every flag was False (6-run A/B: 4/4
collapses with the key present, 2/2 healthy with it absent;
num_mask_sample_filtered reported 0 in the collapsing runs, so the
filter itself was not masking anything). We have not root-caused the
interaction between the extra per-sample key and the packed-batch
slicing yet; the gate is containment that lets affected runs opt out
without removing or altering the feature for everyone else.

_apply_mask_sample_filter and its tests are unchanged; the default path
keeps mask_sample in the batch as before.

Signed-off-by: Michal Futrega <mfutrega@nvidia.com>

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

overall LGTM, left some minor comments.

cc @ananthsub for this WAR for gym issue, details in #3201 (comment).

Comment thread nemo_rl/experience/rollouts.py
Comment thread nemo_rl/algorithms/grpo.py Outdated
Comment thread nemo_rl/algorithms/grpo.py Outdated
- Move the config key from grpo.mask_env_flagged_samples to
  env.should_mask_flagged_samples, following the
  should_log_nemo_gym_responses precedent
- Read it in one helper in rollouts.py with a docstring on when to turn
  it off; document the key in the gym exemplar YAMLs
- Apply the same gate in AsyncNemoGymRolloutImpl so the single-controller
  flow honors it too

Signed-off-by: Michal Futrega <mfutrega@nvidia.com>
Gym flags rollouts that hit max iterations even when they solve the
task, and those are samples worth training on (see NVIDIA-NeMo#3201
discussion).

Signed-off-by: Michal Futrega <mfutrega@nvidia.com>

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

@michal2409 thanks for the update!

the new added one in nemo_rl/experience/rollout_manager.py seems not pass the config correctly, and could you help to add some unit tests to guard? flagged inline.

Comment thread nemo_rl/experience/rollout_manager.py
Comment thread nemo_rl/experience/rollouts.py
…nager

RolloutManager never forwarded mask_env_flagged_samples to
AsyncNemoGymRolloutImpl, so env.should_mask_flagged_samples had no effect
on the single-controller path. Forward it and pass
should_mask_flagged_samples(master_config.env) from
setup_single_controller.

Add unit coverage: the config helper, the batched postprocess gate, the
streaming _result_to_completion gate, and a RolloutManager forwarding
test that fails if the wiring is dropped again.

Signed-off-by: Michal Futrega <mfutrega@nvidia.com>
@michal2409
michal2409 requested a review from a team as a code owner July 30, 2026 12:44
Comment thread nemo_rl/experience/rollout_manager.py
Signed-off-by: Michal Futrega <mfutrega@nvidia.com>
@yuki-97 yuki-97 added the CI:Lfast Runs a fast test suite and re-use nightly `main` container (but sync dependencies to PRs version) label Jul 31, 2026
@yuki-97

yuki-97 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

/ok to test 79514c2

@copy-pr-bot

copy-pr-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

/ok to test 79514c2

@yuki-97, there was an error processing your request: E2

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/

@yuki-97

yuki-97 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

/ok to test d305d56

@yuki-97
yuki-97 enabled auto-merge (squash) July 31, 2026 11:06
@yuki-97
yuki-97 merged commit e08fc27 into NVIDIA-NeMo:main Jul 31, 2026
140 of 142 checks passed
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)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants