Skip to content

docs: state the trained-batch divisor in per-rollout mean loss - #602

Open
hiro-nikaitou wants to merge 2 commits into
microsoft:mainfrom
hiro-nikaitou:docs/per-rollout-mean-row-divisor
Open

hiro-nikaitou wants to merge 2 commits into
microsoft:mainfrom
hiro-nikaitou:docs/per-rollout-mean-row-divisor

Conversation

@hiro-nikaitou

Copy link
Copy Markdown

Summary

per_rollout_mean divides every training row by rollout_token_count * num_trained_rows. #593 read the second factor as an implementation slip and asked whether the divisor should be the number of distinct rollouts instead. The thread confirms the row-count divisor is intentional: it is the inverse of the extra PPO mini-batch update units that the same rows create, and the two cancel at the update level.

This change writes that down where users configure the loss mode. No behaviour changes.

  • docs/20-trainer-configuration.md: state both divisors and the trace_aggregator.level consequence in Per-rollout mean loss.
  • agentlightning/verl/per_rollout_loss.py: spell out both operands of the divisor in the normalize_advantages_by_rollout docstring.

Two different reporters have already read the row-based divisor as a bug, so the intended semantics are worth stating explicitly. This only documents semantics that are already pinned by tests/verl/test_per_rollout_loss.py.

Closes #593.

Validation

  • uv run --locked --no-sync pytest -q tests/verl/test_per_rollout_loss.py -> 4 passed
  • uv run --locked --no-sync ruff check . -> All checks passed!; ruff format --check agentlightning/verl/per_rollout_loss.py -> 1 file already formatted
  • uv run --locked --no-sync python scripts/check_headers.py -> passed
  • git diff --check -> no whitespace errors
  • uv sync --frozen --no-default-groups --group docs + uv run --locked --no-sync mkdocs build --strict -> Documentation built in 17.44 seconds, exit 0
  • Doc/code consistency check over the paragraph: asserts num_trained_rows=len(batch) in trainer.py, the rollout_token_counts[rollout_id] * num_trained_rows divisor in per_rollout_loss.py, and the presence of both doc statements. 3/5 checks passed before this change, 5/5 after.
  • Arithmetic behind the new paragraph, using the real normalize_advantages_by_rollout and the mini-batch computation copied from trainer.py:518-523: two rollouts over two rows -> per-rollout mass {0.5, 0.5}, batch mass 1.0, update units 2; the same two rollouts over three rows -> {0.3333, 0.3333}, batch mass 0.6667, update units 3. mass * units stays 2.0 in both cases.

Not run: GPU rollouts / real training (no GPU here); the numbers above come from the adapter's own normalization function, not from an end-to-end loss.

This patch was prepared with AI assistance. I have reviewed every line and can explain it.

`per_rollout_mean` divides every training row by `rollout_token_count *
num_trained_rows`. Issue microsoft#593 read the second factor as an implementation
slip; the thread confirms the row-count divisor is intentional, because it is
the inverse of the extra PPO mini-batch update units that the same rows
create, so the two cancel at the update level.

State that where users configure the loss mode, and spell out both operands of
the divisor in the normalize_advantages_by_rollout docstring. No behaviour
changes.

Signed-off-by: hiro-nikaitou <vieteviete@proton.me>
Copilot AI balanced review requested due to automatic review settings September 23, 2026 10:12

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Replace the unsupported cancellation claim with the directly implemented normalized-mass invariant.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Documents the trained-row divisor used by per-rollout mean loss without changing behavior.

Changes:

  • Clarifies loss normalization in trainer documentation.
  • Expands the normalization function docstring.
File Description
docs/​20-trainer-configuration.md Documents divisors, but its update-level cancellation claim is not guaranteed by the training loop.
agentlightning/​verl/​per_rollout_loss.py Clarifies both normalization divisor operands.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/20-trainer-configuration.md Outdated

`actor_rollout_ref.actor.policy_loss.loss_mode: per_rollout_mean` normalizes the policy loss at the rollout level. It prevents a rollout from receiving more optimization weight only because it produced more training rows.

Rows that belong to the same rollout share one token denominator, and each row is additionally divided by the number of rows in the trained batch. That second divisor is the inverse of the extra PPO mini-batch update units those rows produce, so the two cancel at the update level: splitting one rollout across more rows, for example by switching `agentlightning.trace_aggregator.level`, adds proportional update units without changing the rollout's effective optimization weight.
Each retained row is consumed once per PPO epoch, and rows are floored to
complete mini-batches and can be dropped by `max_ppo_update_times`, so extra
rows do not create proportional update units. State the invariant the code
implements instead of the cancellation it does not guarantee.

Signed-off-by: hiro-nikaitou <vieteviete@proton.me>
@hiro-nikaitou

Copy link
Copy Markdown
Author

Rewritten in 875f838 so the section states the invariant the trainer implements instead of the cancellation it does not guarantee.

Each retained rollout now reads as carrying a normalized token mass of 1 / num_trained_rows, where num_trained_rows counts the rows entering the actor update. Because every retained row is consumed once per PPO epoch, extra rows partition that mass across more mini-batch update units. Rows that do not fill a complete mini-batch are dropped for alignment and agentlightning.max_ppo_update_times can cap them, so extra rows need not create proportional update units. The rest of the section is unchanged.

This branch has not been deployed

No deployments
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.

per_rollout_mean divides by training rows instead of rollouts, so the loss scale changes when a rollout spans several rows

2 participants