feat(ppo): add actor loss aggregation modes - #1417
Conversation
Group-level reward/advantage normalization sliced rows with a fixed `group_size` stride. When a rollout group is partial (some episodes return None and the workflow keeps the survivors), the rows no longer align with that stride: a trailing short group is left with std 0 and divided by eps (advantages explode), and a mid-batch short group shifts every later boundary so groups mix across prompts -- silently, even when the batch size is still divisible by group_size (e.g. sizes [4,4,3,1]). Normalization now accepts the actual per-group row counts via `group_sizes` and buckets by them; `compute_advantages` passes the rollout's `traj_group_sizes` (each trajectory is one prompt group). Without `group_sizes` the positional path remains but now raises on a non-divisible batch instead of mis-slicing. Full groups are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…groups
When some episodes of a group return None, GroupedRolloutWorkflow keeps the
survivors as a partial group. min_valid_group_size lets a run require a minimum
number of survivors and otherwise drop the whole group (return None) rather than
forming a partial one.
- InferenceEngineConfig.min_valid_group_size (default 1: keep every non-empty
group, i.e. unchanged behavior; set to gconfig.n_samples for full groups only),
validated to be between 1 and group_size.
- GroupedRolloutWorkflow drops a group when survivors < threshold, plumbed from
config at both wrap sites.
- Regenerated docs/{en,zh}/cli_reference.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces support for different loss aggregation levels (token, sequence, and prompt mean) to reduce per-token policy-gradient loss, alongside a min_valid_group_size parameter to handle under-filled rollout groups. The normalization, remote inference engine, and PPO actor loss calculations are updated accordingly, supported by new tests and documentation. The review feedback suggests addressing a potential device mismatch in the packed loss aggregation path, adding a validation check to ensure min_valid_group_size does not exceed n_samples, and translating the newly added parameter descriptions in the Chinese CLI reference.
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.
…(ScaleRL §3.2) AReaL hardcodes the policy-gradient loss to a global token mean. ScaleRL §3.2 treats the loss-aggregation level as a tunable axis; this makes it configurable via `actor.loss_aggregation`: - `token_mean` (default, unchanged, byte-identical): every token weighted equally (DAPO). - `seq_mean` (new): per-sequence token-weighted mean, then mean over sequences -- every response weighted equally (GRPO's classic per-sequence reduction, previously unavailable in AReaL). - `prompt_mean`: per-prompt-group token-weighted mean, then mean over groups -- every prompt weighted equally regardless of trajectory length (MiniMax-M1 / ScaleRL). One seam: `seq_mean` and `prompt_mean` share a single per-unit reduction in `aggregate_pg_loss` (unit = one sequence for seq_mean, `group_size` consecutive sequences for prompt_mean). Paired with `_make_loss_weight_fn` returning the unit count, the engine's existing `Σ(loss_mb·w_mb)/Σw_mb` (`loss_scale = local_weight/total_loss_weight`) realizes the exact global mean over the chosen unit -- so each mode is just a (reduction, weight) pair with no new cross-microbatch or cross-DP machinery. The shared `denom_mask` keeps the pre-rejection token count so rejection-sampling `action='mask'` is unaffected. Single source of truth: for `prompt_mean` the prompt-group size is `gconfig.n_samples`, so `group_size` is derived (not a separate knob), `mb_spec.granularity` is auto-bumped to a multiple of it, and `rollout.min_valid_group_size` is raised to it so under-filled rollout groups are dropped (prompt_mean groups positionally and needs whole groups; the partial-group fix this PR depends on handles the reward/advantage side). Tests (`tests/test_prompt_mean_loss.py`): per-mode values, packed==padded, `denom_mask`, the three-mode loss_fn/loss_weight_fn pairing invariant (reproduces the single-batch global mean), and the config derivations. CPU-only; existing `test_functional.py` unchanged; pre-commit clean (en/zh CLI docs regenerated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93aeca0 to
430cae6
Compare
Support DrGRPO-style actor pg_loss scaling by averaging each response's masked token sum against a positive fixed denominator. This keeps the existing engine loss-weight contract and rejects ambiguous divisor settings at config validation time.
27f3867 to
e3b9704
Compare
Represent actor loss aggregation as an explicit engine reduction contract so reviewers can compare the smaller local-scalar approach in areal-project#1417 with a cleaner numerator/normalizer interface.
Represent actor loss aggregation as an explicit engine reduction contract so reviewers can compare the smaller local-scalar approach in areal-project#1417 with a cleaner numerator/normalizer interface.
Represent actor loss aggregation as an explicit engine reduction contract so reviewers can compare the smaller local-scalar approach in areal-project#1417 with a cleaner numerator/normalizer interface.
Represent actor loss aggregation as an explicit engine reduction contract so reviewers can compare the smaller local-scalar approach in areal-project#1417 with a cleaner numerator/normalizer interface.
Represent actor loss aggregation as an explicit engine reduction contract so reviewers can compare the smaller local-scalar approach in areal-project#1417 with a cleaner numerator/normalizer interface.
Represent actor loss aggregation as an explicit engine reduction contract so reviewers can compare the smaller local-scalar approach in areal-project#1417 with a cleaner numerator/normalizer interface.
|
Final replacement: #1546. It keeps the strongest part of this conservative design, the existing loss_fn plus loss_weight_fn engine seam, while replacing positional fixed-size prompt grouping with explicit ragged trajectory-group metadata and correcting filtered-unit denominator behavior. #1443 is also closed, leaving #1546 as the sole public review target. The broader engine abstraction is archived separately at EazyReal#4. |
Description
AReaL currently aggregates the actor policy-gradient loss as a global token mean. That makes token count the unit of weighting: longer responses, and prompt groups with more valid tokens, contribute more to the update. Some PPO/GRPO recipes instead want the update to average over responses, over prompt groups, or against a fixed response-length denominator, so the averaging unit needs to be explicit rather than hard-coded.
This PR adds
actor.loss_aggregationfor the actorpg_loss. For one train step, letBbe the number of responses,G = gconfig.n_samples,P = B / G,M_ibe the valid-token count for responsei, andL = actor.loss_aggregation_divisor.token_meansum_i,t loss_it * mask_it / sum_i M_iseq_mean(1 / B) * sum_i token_mean(response_i)prompt_mean(1 / P) * sum_g token_mean(prompt_group_g)Gresponses one unit of weight.constant(1 / B) * sum_i (sum_t loss_it * mask_it / L)The implementation fits the existing engine contract instead of adding a second reduction path.
aggregate_pg_losscomputes the local scalar for the selected unit, and_make_loss_weight_fnreturns the matching unit count. The engine already reduces microbatches assum(local_loss * local_weight) / sum(local_weight), so the global result is exact across packing and distributed workers as long as each mode reports the right weight.Compared with the other OSS implementations checked for this change, this is the AReaL-shaped version of the same scalar contract: verl centralizes aggregation in
agg_losswith global batch metadata; SkyRL pre-scales advantages before the worker sums policy losses; prime-rl stays intentionally narrow with global token mean; and slime's counterpart needs a CP-aware reducer because CP shards the token numerator while sample/prompt denominators are whole-step data. In AReaL, the existing train-enginelocal scalar + loss weightcontract already carries the required numerator/denominator pair, so the PR keeps the change local to actor loss aggregation and loss-weight selection.For
prompt_mean, the group size is derived from the existing rollout configuration rather than exposed as another knob. The PR raises microbatch granularity androllout.min_valid_group_sizeto that group size so prompt groups stay whole when they are averaged. Forconstant,actor.loss_aggregation_divisormust be positive and finite, and it is rejected for every other mode. CISPO remains limited totoken_meanbecause the current CISPO implementation is token-level.Before this PR, users could only get token-weighted actor loss without patching the loss function. After it, the configured mode directly names the unit being averaged, while the default training objective is unchanged.
The focused tests cover per-mode values, packed and padded parity,
denom_mask, the loss/weight pairing invariant used by the engine reduction, config derivations for prompt groups, constant-divisor validation, and CISPO rejection outsidetoken_mean. The CLI reference is regenerated for both English and Chinese; the Chinese page has translated descriptions for the new loss-aggregation options.Tested with:
uv run pytest -q tests/test_prompt_mean_loss.py tests/test_cispo_loss.pyuv run ruff check areal/api/cli_args.py areal/trainer/ppo/actor.py areal/utils/functional/functional.py areal/utils/functional/__init__.py tests/test_prompt_mean_loss.py tests/test_cispo_loss.pyuv run ruff format --check areal/api/cli_args.py areal/trainer/ppo/actor.py areal/utils/functional/functional.py areal/utils/functional/__init__.py tests/test_prompt_mean_loss.py tests/test_cispo_loss.pyuv run python docs/generate_cli_docs.pyuv run pre-commit run --all-filesRelated Issue
Closes #1423. Stacked on #1416.
Type of Change
Checklist
pre-commit run --all-files)main/review-prcommand/create-pr