[GDPVal] Harden production rollout and judging paths - #2588
Merged
Conversation
Signed-off-by: Serge Panev <spanev@nvidia.com>
Kh4L
force-pushed
the
spanev/gdpval-production-hardening
branch
from
August 18, 2026 02:51
8b63300 to
f25365f
Compare
Signed-off-by: Serge Panev <spanev@nvidia.com>
Kh4L
force-pushed
the
spanev/gdpval-production-hardening
branch
from
August 18, 2026 20:54
0db371d to
39dde57
Compare
Signed-off-by: Serge Panev <spanev@nvidia.com>
agronskiy
reviewed
Aug 20, 2026
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>
Contributor
Author
|
Implemented the Stage 1 timeout recovery in
The recovery configuration and |
…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>
agronskiy
approved these changes
Aug 31, 2026
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Behavior changes
dispatch_budget_snow requires a finite positivenum_samples_in_parallel.--judge-failed-onlyfails fast for multi-stage sidecars; multi-stage collection resume is the supported recovery path for those rows.partial_completionis 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 -qenv 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 -qgit diff --checkpasses.