Skip to content

[GDPVal] Harden production rollout and judging paths - #2588

Merged
Kh4L merged 9 commits into
gdpval-develfrom
spanev/gdpval-production-hardening
Sep 1, 2026
Merged

[GDPVal] Harden production rollout and judging paths#2588
Kh4L merged 9 commits into
gdpval-develfrom
spanev/gdpval-production-hardening

Conversation

@Kh4L

@Kh4L Kh4L commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Route malformed, truncated, or scoreless judge responses out of headline metrics and through retry-aware failure sidecars; preserve completed policy artifacts for re-judging and make deterministic payload/context failures terminal.
  • Bound GDPVal text, binary, Office, PDF, media, and ZIP handling before reads and request construction; preserve sidecar provenance, attachment fairness, and A/B swap invariance while avoiding repeated hashing for payloads that already fit.
  • Make standard and multi-stage collection honor finite dispatch budgets, stage-aware longest-first scheduling, attempt accounting, and cached-deliverable reuse; harden resume with config fingerprints, legacy migration, completion coverage checks, atomic writes, and stale-metric cleanup.
  • Allow opt-in bounded partial completion for non-final calibration stages after persisted timeouts, with overall and per-reference evidence floors, balanced fresh assignments, frozen evidence snapshots, and fail-closed resume. The final stage still requires full evidence for a headline ELO.
  • Sanitize unparsed raw tool-call blocks before provider requests and guarantee globally unique converted tool-call IDs without mutating stored trajectories.
  • Align reverification with the new judge-invalid failure contract, preserve file/reference context, migrate legacy invalid rows, and reject unsafe multi-stage judge-failure recovery.

Behavior changes

  • dispatch_budget_s now requires a finite positive num_samples_in_parallel.
  • Missing rubrics and deterministic request-size/context failures are terminal instead of consuming repeated judge attempts.
  • --judge-failed-only fails fast for multi-stage sidecars; multi-stage collection resume is the supported recovery path for those rows.
  • partial_completion is opt-in and valid only for non-final stages. Resume uses the same output path plus --resume; changing a policy after a partial outcome has been frozen fails closed.

Validation

  • env PYTHONPATH=. uv run pytest resources_servers/gdpval/tests -q
    • 310 passed, 9 optional-dependency skips
  • env PYTHONPATH=. uv run pytest tests/unit_tests/test_deliverables.py tests/unit_tests/test_dispatch_latency.py tests/unit_tests/test_rollout_collection.py tests/unit_tests/test_rollout_reverification.py -q
    • 229 passed
  • Focused multi-stage suite, including 41/45 calibration recovery, interrupted-final-stage resume, stale downstream invalidation, frozen-evidence validation, and crash-order handling:
    • 135 passed
  • Ruff check and format check pass for every changed Python file.
  • git diff --check passes.

@copy-pr-bot

copy-pr-bot Bot commented Aug 18, 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.

Signed-off-by: Serge Panev <spanev@nvidia.com>
@Kh4L
Kh4L force-pushed the spanev/gdpval-production-hardening branch from 8b63300 to f25365f Compare August 18, 2026 02:51
Signed-off-by: Serge Panev <spanev@nvidia.com>
@Kh4L
Kh4L force-pushed the spanev/gdpval-production-hardening branch from 0db371d to 39dde57 Compare August 18, 2026 20:54
@github-actions github-actions Bot added the sla:triage-overdue Review assignment is over the one-business-day SLA label Aug 19, 2026
Signed-off-by: Serge Panev <spanev@nvidia.com>
Comment thread nemo_gym/rollout_collection.py
@github-actions github-actions Bot removed the sla:triage-overdue Review assignment is over the one-business-day SLA label Aug 20, 2026
@agronskiy

Copy link
Copy Markdown
Contributor

@Kh4L it seems for me, the logic prevents timing out-tasks (the ones that could not finish in 3.5 hours cut-off) of stage-1 prevent starting stage-2. I think we should admit that some GDPVal tasks will timeout on stage-1 as either hard or smth like that -- and this should not prevent apporximately calibrating and starting stage-2. WDYT?

Signed-off-by: Serge Panev <spanev@nvidia.com>
@Kh4L

Kh4L commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Implemented the Stage 1 timeout recovery in 14125b765.

  • A non-final stage can now opt into bounded partial completion with overall and per-reference coverage floors.
  • An existing 41/45 Stage 1 run with four persisted timeouts can advance on --resume without redispatching those four long-running tasks.
  • Fresh policy-enabled stages balance task assignments across the selected references.
  • The accepted evidence and omitted keys are frozen, so later resume cannot silently change Stage 2 reference selection.
  • The final stage remains strict: an incomplete final stage emits no headline ELO and resume dispatches only its missing rows.

The recovery configuration and --resume/RERUN_INCOMPLETE distinction are documented in benchmarks/gdpval/README.md. The exact 41/45 → partial calibration → interrupted final stage → single-row final resume path is covered by the focused suite.

agronskiy and others added 3 commits August 24, 2026 11:57
…le (#2704)

On top of #2588, `PartialStagePolicy.waivable_failure_classes` becomes a
configurable tuple instead of being hardcoded to
`("timeout_exceeded",)`. The default is unchanged, so existing configs
behave identically.

## Change

- Commit: `36767c8052c76c7047551262e19fb1afd481233e`, branched off
`14125b7650317b1d1269987c840ea70deba7e5f4` (the tip of #2588 at the
time).
- Files touched: `resources_servers/gdpval/multistage_elo.py` (new
field), `resources_servers/gdpval/multistage_orchestrator.py` (7 edits:
policy record now emits `newly_waivable_failure_classes`; the waiver
check consults the configured set; `_WAIVABLE_FAILURE_CLASSES`
validation frozenset is `{"timeout_exceeded", "transient"}`; snapshot
revalidation maps the record key `newly_waivable_failure_classes` back
to the config key `waivable_failure_classes`), plus 3 new tests in
`resources_servers/gdpval/tests/test_multistage_orchestrator.py`
(`test_transient_omission_advances_when_explicitly_waivable`,
`test_unknown_waivable_failure_class_is_rejected`,
`test_empty_waivable_failure_classes_is_rejected`).
- Test status: 313 passed, 9 skipped across
`resources_servers/gdpval/tests/`.

## Motivation

The operative blocker was NOT the waiver scope: `partial_completion` is
opt-in and was never set, so `multistage_orchestrator.py:1090` took the
strict branch and required every planned non-final rollout to carry
battle evidence. That cost two GDPVal-AA-v2 runs their entire stage 2
(`dc6c776f3af506df` and `6c5ee8b81773b2b6`, 44/220 each, `eval_elo`
null). Once `partial_completion` is enabled, a `transient` judge failure
still stopped the stage because the waiver set was hardcoded — which
this PR fixes.

## Production evidence

From Nemotron Super 3.5 run `590aeb2c72b1de1d` on this commit:

- stage 1 log line: `[multistage-elo] stage 1/2 accepted partial
calibration: success coverage 91.1%, omitted 4 rollout(s)`
- omitted rollouts and classes: task 78 `timeout_exceeded`, task 155
`timeout_exceeded`, task 159 `timeout_exceeded`, task 136 `transient`
- `success_fraction` 0.9111, worst per-reference 0.8, `multistage_state`
stage-0 status `partial_complete`
- stage 2 then ran to completion: `eval ELO = 737.5` over 4 refs. The
predecessor run died at 44/220 with `eval_elo` null.
- The run also spanned a slurm TIMEOUT resume, and the partial stage was
correctly reloaded: `[multistage-elo] resuming multi-stage run from
cache (fingerprint match)` and `[multistage-elo] stage 1/2 reused
partial result from cache: eval ELO = 627.4 (41 cached rollout(s))` — so
the `newly_waivable_failure_classes` <-> `waivable_failure_classes`
round-trip works across legs.
- A second run on the same commit, Qwen3.5-122B-A10B `df318339a24e9384`,
completed at `eval ELO = 882.6` with a clean 45/45 stage-1 close (the
waiver path did not fire there).

## Pre-merge validation

Against `36767c80`, with policy `{min_success_fraction: 0.9,
min_per_reference_success_fraction: 0.5,
min_successful_rows_per_reference: 1, waivable_failure_classes:
[timeout_exceeded, transient]}` and a synthetic 45-rollout stage: an
omission of class `timeout_exceeded` accepts at `success_fraction`
0.9778, `transient` accepts at 0.9778, and `legitimate` is rejected.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…re (#2796)

A comparison verify against a reference model that has **no deliverable
for the task** returned `reward: 0.0` with `judge_response: {"error":
"reference_missing"}` and no failure class, so the harness persisted it
as an ordinary **success row**. A success row carrying no battle
evidence is rejected outright by the non-final-stage coverage gate in
`_partial_stage_outcome` (`if set(successful_by_key) -
fit_eligible_keys: return None`), which runs *before any threshold is
consulted*; the waiver machinery never sees the row either, since
`waivable_failure_classes` only applies to rows that failed. Net effect:
one missing reference file killed an entire multi-stage run, no
`partial_completion` setting could relax it, and the run reported a
stage incomplete while every configured gate had passed. This PR stamps
the response with `_ng_failure_class=reference_missing` and marks it
terminal — retrying cannot make the file appear, the same reasoning
already applied to `skipped`. The row then leaves the success set,
becomes visible in the failures sidecar, and is governed by the stage's
`partial_completion` fractions like any other unusable row. It also sets
`extra="allow"` on `GDPValVerifyResponse`: the underscore-prefixed
harness keys cannot be declared as pydantic fields, and the default
`extra="ignore"` dropped them **silently**, so a response that tried to
stamp a failure class was serialised without one.

Observed three times on GDPVal-AA-v2 in two days — DeepSeek V4 Flash
(`9e39df84` / `qwen35_397b`), Inkling-Small (`1752cb53` /
`qwen35_397b`), Nemotron 3 Ultra (`e222075d` / `qwen36_35b`) — where the
first two lost stage 2 entirely at 97.8% and 95.6% coverage and the
third survived only because it landed in the final stage, which is never
coverage-gated. This is a **mitigation, not a cure**: the underlying
defect is incomplete reference deliverable sets (`qwen35_397b` 212/220,
`qwen36_35b` 216/220, `gptoss_120b` 218/220 — 14 missing pairs), and
backfilling those removes the cause. A reviewer may reasonably prefer
the stricter structural fix of refusing to *plan* a (task, reference)
pair whose reference deliverable is absent, so a run never draws one;
that would live in the task-distribution builder and is not attempted
here. Tests: `test_verify_comparison_missing_reference` and
`test_reference_ids_empty_yields_no_references` extended to assert both
keys survive `model_dump()`; new `TestReferenceMissingCoverage`
(terminal omission accepted without being waivable, still rejected below
`min_success_fraction`, and an evidence-less success row still
hard-rejects — the gate is not weakened) and
`TestReferenceMissingIsTerminal`. Diff is confined to
`resources_servers/gdpval/`; CI green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
Add an opt-in strict comparison contract for reportable ELO runs. Reject
missing artifacts, failed matchups, invalid verdicts, and incomplete
vote panels before results enter the resume cache. Keep legacy behavior
as the default and fingerprint strict runs separately.


## Checklist

- [X] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [ ] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [ ] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [ ] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format/copyright pass).
- [ ] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

Signed-off-by: Serge Panev <spanev@nvidia.com>
Kh4L added 2 commits August 31, 2026 13:03
…lity replay (#2896)

Ports the per-provider judge transport layer, executed and validated in
a full 220-task GDPVal evaluation campaign, onto the
production-hardening branch.

## Changes

- Per-judge media transport modes: `native_pdf`, `images_and_text`, and
`native_pdf_overflow_images` (native PDFs kept; over-cap PDFs rasterized
at a pinned DPI with every page retained).
- Provider-specific request limits as judge-panel config:
serialized-request bytes, native-PDF page/document/byte caps (aggregate
and per-document), video count, raster DPI tiers.
- Per-document native-PDF cap (`max_native_pdf_bytes_per_document`)
forces lossless full rasterization of any PDF above the provider's
per-file limit; page-count overflow rasterizes only the required prefix,
keeping the suffix native.
- Deterministic transport eligibility preflight before any billable
call, with seeded schedule replay; exclusions recorded per row
(`media_routing_exclusions`, `transport_by_judge`).
- Overflow eligibility also enforces the judge's aggregate document/byte
caps against the post-overflow native remainder, and a render page cap
so a plan the renderer cannot satisfy is excluded at preflight instead
of failing at dispatch.
- New `transport_assignment.py`: provider-free task/reference
reassignment repair using exact artifact footprints against provider
caps.
- `transport_assignment_repair` settings enter the multistage
fingerprint, so changing them invalidates journals instead of silently
replaying stale plans on resume.
- Final verdict reminder block appended to judge messages; the text
budget's framing reserve includes its serialized upper bound so the
request-size invariant holds at any configured cap.

## Validation

- `resources_servers/gdpval/tests`: 379 passed, 3 skipped, including 16
new tests in `test_transport_modes.py` (overflow planning/application,
eligibility receipts, seeded replay, assignment solver capacity
contract, fingerprint invalidation).
- `ruff check` / `ruff format` clean.
- Same code path that produced a strict 220/220-task, 880/880-trial
judging PASS in the validating campaign, rebased onto this branch.

First of four planned child PRs (media caps/tiling, provider
timeout/retry hardening, transport receipts follow). Draft until the
example three-provider judge panel config lands.

---------

Signed-off-by: Serge Panev <spanev@nvidia.com>
…eligibility

Signed-off-by: Serge Panev <spanev@nvidia.com>
@Kh4L
Kh4L merged commit 2ce39b1 into gdpval-devel Sep 1, 2026
15 of 16 checks passed
@Kh4L
Kh4L deleted the spanev/gdpval-production-hardening branch September 1, 2026 00:05
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