Skip to content

feat(ppo): make loss aggregation configurable - #1546

Open
EazyReal wants to merge 8 commits into
areal-project:mainfrom
EazyReal:oss/loss-aggregation-modes-final
Open

feat(ppo): make loss aggregation configurable#1546
EazyReal wants to merge 8 commits into
areal-project:mainfrom
EazyReal:oss/loss-aggregation-modes-final

Conversation

@EazyReal

@EazyReal EazyReal commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Description

AReaL currently hard-codes the actor policy-gradient objective as a global token
mean. This PR makes the averaging unit configurable without changing the
training-engine API or any backend implementation.

actor.loss_aggregation Objective Equal-weight unit
token_mean (default) mean over valid response tokens token
seq_mean mean of per-response token means response
prompt_mean mean of per-prompt-group token means prompt group
constant mean response token sum divided by loss_aggregation_divisor response

Each mode supplies a local mean and its matching weight to AReaL's existing
TrainEngine.train_batch(loss_fn, loss_weight_fn) contract. The engine already
combines microbatches as
sum(local_mean * local_weight) / sum(local_weight), producing the intended
global objective without a second distributed-reduction path.

The implementation also:

  • preserves the existing token_mean dtype and reduction path;
  • carries actual trajectory-group sizes through nested microbatch splitting, so
    ragged prompt groups remain explicit and atomic;
  • keeps the original denominator when rejection sampling or M2PO narrows the
    numerator;
  • shares one reduction implementation between padded and packed inputs, and
    fails fast when a non-token packed input has no sequence boundaries;
  • rejects non-token actor aggregation with teacher-logp distillation, whose KL
    term has a separate token-normalized objective; and
  • rejects non-token actor aggregation with enable_tree_training, because
    tree-packed batches carry no per-sequence boundaries to average over.

The diff deliberately contains no FSDP, Megatron, Archon, or public engine-API
changes. It replaces the earlier review surfaces in #1417 and #1443 with a
single clean implementation.

Related Issue

Fixes #1423.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📝 Documentation update
  • ♻️ Refactoring
  • ⚡ Performance improvement
  • ✅ Test coverage improvement

Validation

  • python -m pytest -q tests/test_prompt_mean_loss.py tests/test_cispo_loss.py tests/test_ppo_stats.py tests/test_functional.py tests/test_rejection_sampling.py tests/test_seqpack.py — 251 passed
  • pre-commit run --all-files — passed every hook
  • git diff --check — passed
  • Independent landing review — passed

The focused suite covers all four formulas, default token-mean parity, padded and
packed parity, ragged groups, nested group-preserving microbatch splits, filtered
numerators, and partition-invariant callback pairing. Multi-GPU backend tests
were not rerun because the backend implementations are unchanged.

Checklist

  • I have read the
    Contributing Guide
  • Pre-commit hooks pass (pre-commit run --all-files)
  • Relevant tests pass; new tests added for new functionality
  • Documentation updated; generated English and Chinese CLI references are
    current
  • Branch is up to date with main
  • Self-reviewed via /review-pr command
  • This PR was created by a coding agent via /create-pr
  • This PR is a breaking change

Breaking Change Details (if applicable): No API or configuration migration is
needed, and the default token_mean path is unchanged for default configs. One
numeric change does reach existing users: with actor.m2_threshold set, the
policy-gradient denominator is now the valid-token count before M2PO narrows
the mask, which is what the engine's per-microbatch weight already used. The two
previously disagreed — the loss divided by the post-M2PO count while the weight
was the pre-M2PO count — so each microbatch's gradient was scaled by
n_before / n_after. M2PO runs will therefore see different loss values and
gradient magnitudes on the same data; that is the point of the fix, and no
configuration change is required to adopt it.

Additional Context

This is the sole public review surface for the loss-aggregation feature. The
broader experimental LossReduction/LossTerm engine abstraction has been
preserved separately for future design work rather than mixed into this feature
PR.

Archived design prototype: EazyReal#4

@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 configurable policy-gradient loss aggregation modes (token_mean, seq_mean, prompt_mean, and constant) for PPO actor training, along with corresponding CLI configuration options, documentation updates, and comprehensive unit tests. The review feedback identifies a performance concern in the newly added _sequence_sums function in areal/utils/functional/loss_aggregation.py, where checking torch.any(sequence_lengths < 0) inside an if statement triggers a GPU-to-CPU synchronization on every microbatch. It is recommended to remove this redundant check from the hot path to avoid degrading training throughput.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread areal/utils/functional/loss_aggregation.py Outdated
EazyReal added a commit to EazyReal/AReaL that referenced this pull request Jul 18, 2026
Trust packer-owned cumulative sequence lengths and provide exact repeat output sizes so aggregation does not synchronize to discover device-side values or shapes.

Refs: areal-project#1546
EazyReal added 2 commits July 21, 2026 05:33
Allow PPO-family actor losses to weight tokens, sequences, prompt groups, or fixed-length response sums without changing the engine contract.

Key changes:
- Pair each local policy-gradient mean with its matching engine weight
- Preserve explicit prompt-group boundaries across microbatch splitting
- Keep token_mean and existing backend behavior unchanged
- Document and regression-test padded, packed, ragged, and filtered inputs

Refs: areal-project#1423
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
Trust packer-owned cumulative sequence lengths and provide exact repeat output sizes so aggregation does not synchronize to discover device-side values or shapes.

Refs: areal-project#1546
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
@EazyReal
EazyReal force-pushed the oss/loss-aggregation-modes-final branch from d4997ad to aeb6391 Compare July 21, 2026 05:35
Comment thread areal/api/cli_args.py Outdated
loss_aggregation: str = field(
default="token_mean",
metadata={
"help": "Policy-gradient loss reduction. 'token_mean' averages valid "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please note the format of the docstring.

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.

fixed

Present each supported loss aggregation choice as a name-description pair so generated references follow the existing config documentation style.

Refs: areal-project#1546
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
Comment thread areal/api/cli_args.py Outdated
"loss_aggregation_divisor. Non-token modes require sequence "
"boundaries; tree-packed actor training currently supports only "
"'token_mean'.",
"help_zh": "Policy-gradient loss 的归约方式。"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please change the description to English.

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.

Removed — dropped the help_zh strings and reverted the doc generator's language plumbing, so config help stays English-only like every other field; the zh CLI reference is regenerated accordingly.

return torch.where(mask, loss, 0).to(torch.float32)


def _resolve_masks(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These free functions are best added to PolicyGradientReduction.

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.

Moved them into PolicyGradientReduction as private methods — they had no consumer outside the class.

…uction

Give the reduction contract a single home by moving the module-level
aggregation helpers into the dataclass they serve; no consumer other
than PolicyGradientReduction ever used them.

Refs: areal-project#1546
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
Drop the help_zh metadata and the language plumbing in the CLI docs
generator; config help stays English everywhere, matching every other
field, and the zh reference is regenerated accordingly.

Refs: areal-project#1546
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
…n-modes-final

# Conflicts:
#	areal/utils/functional/functional.py
The help text already states tree-packed actor training supports only
token_mean, but the combination passed config validation and died on the
first train_batch (tree microbatches carry no cu_seqlens), after launch
and a full rollout. tau2's shipped configs enable tree training, so any
of their users opting into seq_mean/prompt_mean/constant would hit it.

Signed-off-by: Max Wang <maxwill@vmax.ai>
group_sizes, pg_reduction, and denominator_mask were missing from the
otherwise-complete Args sections of ppo_actor_loss_fn, sapo_loss_fn, and
cispo_loss_fn; also drop a stray trailing comment in the CISPO tests.

Signed-off-by: Max Wang <maxwill@vmax.ai>
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.

[Feature] Configurable loss aggregation level — token / seq / prompt mean (ScaleRL §3.2)

2 participants