Skip to content

[framework] fix: emit reward=0 trajectory for failed validation sessions - #152

Draft
RichardFido wants to merge 1 commit into
verl-project:mainfrom
RichardFido:main
Draft

[framework] fix: emit reward=0 trajectory for failed validation sessions#152
RichardFido wants to merge 1 commit into
verl-project:mainfrom
RichardFido:main

Conversation

@RichardFido

@RichardFido RichardFido commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Failed validation sessions (runner exception / timeout / sandbox failure) previously called abort_session, producing no trajectory in the val TransferQueue. reward/mean@1 averages only non-aborted sequences (aborted_mask = response_length == 0 in verl/trainer/ppo/metric_utils.py), so a failed session that had produced partial tokens vanished from the denominator and inflated the reported val reward. This PR finalizes such sessions instead, emitting their partial output as reward=0 / finished=False trajectories so they count as zeros in the val mean. Train-side failure handling is unchanged.

Changes

Thread partition_id through _run_session_with_concurrency_limit → _run_session so the failure branch can tell val from train.
For val-only failures, replace abort_session with _finalize_failed_validation_session: finalize whatever the gateway already produced, rewrite each trajectory to reward_score=0.0 + reward_info={..., "reward": 0.0, "finished": False}, write to the val TQ.
Apply _select_session_trajectories(..., trajectory_selection) before writing, mirroring the success path — a length-split multi-chain session writes the same trajectory count a success would (the val mean is per-sequence).
Merge (not overwrite) reward_info to preserve runner-posted diagnostics (acc, materialization_reason); only reward/finished are forced.
Best-effort throughout: finalize_session and the TQ write are each try/except-wrapped and never mask the original failure; CancelledError is a BaseException, caught by the earlier except so cancellation still aborts.

PR title

Use [area] type: summary.

  • Areas: agents, framework, gateway, logging, sandbox, tasks, tools, training, app, docs, examples, ci, build, deps, misc
  • Types: feat, fix, refactor, perf, test, docs, chore, revert
  • Separate multiple areas with comma-space: [agents, sandbox] feat: add isolated harness execution
  • Prefix compatibility-breaking work with [BREAKING]: [BREAKING][tasks, docs] refactor: replace task config schema
  • A stacked series may start with [1/N]: [1/N][gateway] refactor: split protocol adapters

Checklist

  • The PR is focused and linked to an issue or explains why no issue is needed.
  • The title follows the format above and names the layer that owns the change.
  • Tests cover the behavior, or the Validation section explains why they are not practical.
  • User-facing API, config, and workflow changes include documentation or runnable examples.
  • Compatibility impact and migration steps are documented.
  • Logs, fixtures, and examples contain no credentials or private data.
  • pre-commit run --all-files --show-diff-on-failure passes.

@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.55556% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
uni_agent/framework/framework.py 75.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

A failed session (runner exception / timeout / sandbox failure) previously
called abort_session and vanished from the TransferQueue. Downstream
reward/mean averages only non-aborted sequences
(``aborted_mask = response_length == 0`` in metric_utils), so a failed
session silently dropped from the denominator and inflated the reported
reward — masking real regressions.

Replace abort-and-drop with: abort the live session (discard its partial
output — a failed rollout's tokens have undefined correctness), then write a
minimal reward=0 placeholder (1 prompt + 1 response token, response_mask=0,
finished=False). The mask keeps it out of the policy/value gradient
(train/critic); its non-zero response_length keeps it counted as a zero in
the val reward/mean denominator. Uniform across val and train so critic and
val stay consistent; CancelledError still takes the pure abort path above
(it is a BaseException, caught before this branch).

Test: a crashed val/train session now lands a masked reward=0 dummy
(materialization_reason="session_failed") in TQ alongside the successful
session's real trajectory, and the uid stays tagged finished on partial
failure so the val scorer still reads it.
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.

2 participants