Repository navigation
[GDPVal] Fix long-context budgeting and compaction - #3121
Merged
Merged
Conversation
Signed-off-by: Serge Panev <spanev@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
Kh4L
marked this pull request as ready for review
September 8, 2026 20:01
agronskiy
approved these changes
Sep 9, 2026
This was referenced Sep 24, 2026
khazic
pushed a commit
to khazic/Gym-lao
that referenced
this pull request
Oct 1, 2026
…nt runtime (NVIDIA-NeMo#3685) ## What does this PR do? This PR moves the Stirrup sandbox and runtime work from `gdpval-devel` to main. On devel it landed as NVIDIA-NeMo#2338, NVIDIA-NeMo#2395, NVIDIA-NeMo#2588, NVIDIA-NeMo#3086, NVIDIA-NeMo#3121 and NVIDIA-NeMo#3333 (sandbox part). It is one of a series of PRs cut by file from a single tested devel snapshot. It touches `responses_api_agents/stirrup_agent/` and the GDPVal sandbox build script in `nemotron_recipes/`. The GDPVal prompt rewrite that describes this sandbox is in NVIDIA-NeMo#3686, not here. **Sandbox (NVIDIA-NeMo#3333)** - `containers/gdpval.def` now builds the published GDPval-AA v2 environment: - `debian:trixie` with Debian's Python 3.13.5. The build fails on any other interpreter version. - The 419 published Python pins, installed verbatim and checked for exact versions afterwards. - Carlito/Caladea (metric-compatible Calibri/Cambria) and the Noto fonts. - Both published manifests are vendored as plain text: 419 Python pins and the 762-package apt closure. On arm64 the build drops exactly the 5 pins in `gdpval_aa_v2_arm64_exclusions.txt`. Any other pin that fails to install fails the build. - `verify_gdpval_sandbox.py` is staged into the image. It checks a built `.sif` against both manifests, then runs the toolchain end to end. - The definition stages its manifests with relative `%files` paths, so it has to be built from `containers/`. The README command is updated for that. `nemotron_recipes/.../build-gdpval-sif.sh` now builds from the definition's directory, where before it built from the repo root, which fails with this definition. - `_build_command_script` is pulled out of `ApptainerCodeExecToolProvider._exec` with no behaviour change. A test runs two provider-built commands in one bash and checks that only files persist between calls. **Model client and agent (NVIDIA-NeMo#2338, NVIDIA-NeMo#3121)** - `DynamicMaxTokensChatCompletionsClient`: - When the tokenizer renders the complete prompt, the remaining context also caps `max_completion_tokens`. A prompt with no room left raises Stirrup's `ContextOverflowError` before the request is sent. - For token counting only, tool-call arguments are JSON-decoded before template rendering, as vLLM does. - Without a tokenizer, or when tokenization fails, the character estimate counts the whole JSON-serialized history, tool calls included. - New arguments: - `min_completion_tokens`, default 1024 (the old constant). - `prompt_estimator_truncate_history_thinking`, default `None`. It only affects the estimate. - `truncation_recovery`, default `False`. When on, a turn that uses its whole budget without a schema-valid tool call is followed by one turn that requests thinking off and adds a one-time instruction. The instruction is not recorded in the trajectory. - `NeMoAgent.summarize_messages`: - Strips a leading `<think>` block from compaction summaries. - Makes up to 3 summary attempts. Retries add a stricter text-only instruction, and the last attempt runs without tools. If all 3 fail it raises instead of wiping the history. - New `min_compaction_summary_words` argument, default 1. **Provider payload and trajectories (NVIDIA-NeMo#2395, NVIDIA-NeMo#2588, NVIDIA-NeMo#3086)** - `to_provider_openai_messages` sends only the standard OpenAI message keys: - Stirrup's serializer also emits `reasoning_content`, `thinking_blocks` and `metadata` on assistant messages, flat `tool_call_id`/`name`/`arguments`/`signature` fields on tool calls, and `name` on tool messages. - Main's strict chat schema rejects those fields. So any multi-turn request whose history has an assistant turn with tool calls or reasoning currently fails against a Gym model server. - Unparsed `<tool_call>` / `<function=...>` blocks are removed from the provider copy of assistant text. This applies only to assistant turns with no parsed `tool_calls`, and an unclosed block is stripped to the end of the message. The recorded history keeps them. - `convert_stirrup_history_to_output_items` makes `call_id` unique within a response when the server reuses ids across turns (e.g. `code_exec:0`). Call/output pairing is preserved, and trajectories without repeated ids are unchanged. - Tavily `web_fetch`: - If markdown extraction is empty, it tries a second plain-text pass that includes comments. If that is also empty it returns an explicit error. - An extraction under 1% of a page of 20,000+ characters gets a "likely a fragment" note at the start. The new client and agent options are documented in the Stirrup README as constructor arguments. The agent config keys that set them come with NVIDIA-NeMo#3686, which owns `stirrup_agent/app.py`. **Issue:** none. This promotes work that was already reviewed and merged on `gdpval-devel`. **Dependencies:** - CI: none. This PR passes the Stirrup and GDPVal suites on main by itself. - Runtime, already true on main before this PR: main's strict chat schema returns 422 for the `chat_template_kwargs` that Stirrup's client sends on every call. Running Stirrup against a Gym vLLM model server therefore needs NVIDIA-NeMo#3682. - Thinking control: under NVIDIA-NeMo#3682's current default (`forward_request_chat_template_kwargs: false`), the model server accepts request `chat_template_kwargs` and drops them. That means Stirrup's `enable_thinking`, and the thinking-off half of `truncation_recovery`, reach vLLM only when the policy model server sets `forward_request_chat_template_kwargs: true`. The GDPVal config in NVIDIA-NeMo#3686 doesn't set it, so there only the recovery instruction takes effect. The default is still open on NVIDIA-NeMo#3682. - NVIDIA-NeMo#3686 (lands after this one): - It brings the GDPVal prompt rewrite (`code_exec`, no shell state between calls, the 5-minute limit, the AA v2 package summary). It also brings the reference-file path fix that prompt relies on, and the tests that tie the prompt to this sandbox's manifests. - It adds the agent config keys `context_window_tokens`, `min_completion_tokens`, `prompt_estimator_truncate_history_thinking`, `min_compaction_summary_words` and `truncation_recovery`. - Until it lands, main's prompt keeps advertising `run_shell` and packages the image doesn't have (torch, keras, jax, dlib). Main's current image doesn't have them either. The new options keep their defaults, and the context window still comes from the request's `max_output_tokens` (fallback 262144). ## Validation Main `1c8261080` + only this PR's 17 files, macOS, Python 3.13, per-server venvs: - `cd responses_api_agents/stirrup_agent && python -m pytest tests -q`: **224 passed, 2 skipped**. The skips need GNU `timeout` or a real Apptainer image (`GDPVAL_CONTAINER_PATH`). - `cd resources_servers/gdpval && python -m pytest tests -q` (gdpval imports Stirrup): **156 passed**. - With the rest of the series applied: Stirrup 319 passed / 2 skipped, GDPVal 564 passed. The 12 prompt-content tests that moved to NVIDIA-NeMo#3686 fail against main's current prompt, which is why they live there. - `pre-commit run --all-files`: all hooks pass, no files modified. - Schema probe against main's `NeMoGymChatCompletionCreateParamsNonStreaming`: | History | Stirrup's `to_openai_messages` | `to_provider_openai_messages` | |---|---|---| | Assistant tool call + tool result | rejected (44 errors) | validates | | Assistant reasoning, no tool calls | rejected (14 errors) | validates | | Text only | validates | validates | Adding `chat_template_kwargs` is still rejected until NVIDIA-NeMo#3682. - Build script: I ran it with a stub `apptainer` that checks every `%files` source resolves from the build directory. - The new script passes for the default, a relative and an absolute output path. - Main's script fails: `gdpval_aa_v2_python_requirements.txt` is not found from the repo root. - Container: built and verified with `verify_gdpval_sandbox.py` in NVIDIA-NeMo#3333. The image was not rebuilt for this PR, and only comment lines under `containers/` have changed since. | Arch | Interpreter | Pins | apt-closure entries missing | Verifier failures | |---|---|---|---|---| | x86_64 | 3.13.5 | 419/419 | 0 | 0 | | aarch64 | 3.13.5 | 414/419 (the 5 listed exclusions) | 3 | 0 | - Core unit tests: not run. This PR changes nothing under `nemo_gym/`, `scripts/` or `tests/`. ## Rollouts **Live GDPVal smoke (final code):** all seven PRs of the series applied together on main `1c8261080`, run with the devel-only SLURM launcher on a 4-task AA-v2 comparison smoke (one 4-GPU policy node, CPU judging): rollout 4/4 tasks saved and finished (1h04m, 0 failure rows), Office preparation 7/7 converted, comparison judging 4/4 tasks judged with 0 imputed and 0 excluded, 3 wins / 1 loss. Per-task outcomes match an earlier gdpval-devel run on the same checkpoint, tasks and references. No rate limiting occurred, so the judge retry budget was not exercised live. ## Compatibility and benchmark impact - **Sandbox image** (takes effect only after a rebuild): - Existing `.sif` images keep working. - Build from `containers/` (`cd responses_api_agents/stirrup_agent/containers && apptainer build gdpval.sif gdpval.def`) or with the recipes script. A build started from the repo root cannot find the `%files` inputs. - Python packages: - The base moves from `python:3.13-bookworm` to `debian:trixie`, with Python fixed at 3.13.5. - Python packages become exact pins. Every Python package the old definition installed is still in the pin set. - apt: `unar` is dropped (it isn't in the AA v2 closure), and three packages move to their trixie names. - Deliberate differences from AA v2: we keep a compiler toolchain, we add `espeak-ng`, and the per-command limit is 5 minutes (AA uses 10). - **GDPVal results:** runs on a rebuilt image are not comparable with runs on an image built from the old definition. Package versions, fonts and LibreOffice all change. The prompt change in NVIDIA-NeMo#3686 moves scores further. - **Token budgeting (all Stirrup runs):** - With `model_id` set and a template that renders, `max_completion_tokens` can drop below 1024 near the context limit, and a full context raises `ContextOverflowError` before the request is sent. - Without `model_id`, or when tokenization fails, the estimate is larger for every history, most for tool-heavy ones. Budgets shrink only near the context limit, and the 1024 floor still applies. - **Constructor validation:** `max_completion_tokens_cap`, `min_completion_tokens` and `min_compaction_summary_words` below 1 now raise `ValueError`. - **Logging:** a WARNING is logged every time a call uses its whole completion budget without a usable tool call, even when `truncation_recovery` is off. - **Compaction:** empty or think-only summaries now get up to 3 attempts and then fail the rollout. Before, the run continued with an empty summary. - The override uses private Stirrup internals (`_unwind_context_overflow`, `_active_tools`, `_logger`). They are stable under the existing `stirrup>=0.1.12,<0.2` pin, but a Stirrup minor release may need a follow-up. - **Provider requests:** - The non-standard message keys listed above are no longer sent, so earlier-turn reasoning is not replayed. - Unparsed tool-call blocks are removed from the provider copy of assistant text. - **Trajectories:** a repeated tool `call_id` gets a `#n` suffix from its second occurrence onward. - **`web_fetch`:** - A page with no extractable text now returns `success=False` with an error. Before, it returned an empty body. - A fragmentary extraction gets a note at the start. - **Opt-in behaviour:** `truncation_recovery` is off by default. When on, the recovery instruction is appended as a user message, so the provider can see two user messages in a row. - NVIDIA-NeMo#3686 turns it on for GDPVal and also sets `min_completion_tokens: 8192` and `min_compaction_summary_words: 50`. Other Stirrup users are unaffected. - Until NVIDIA-NeMo#3686 lands, those keys in agent YAML have no effect: `StirrupAgentWrapperConfig` accepts extra keys and ignores them. ## Checklist - [ ] I have read the [contributing guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md). - [x] The change is focused; unrelated "drive-by" edits are tracked as separate issues/PRs. - [x] Tests added or updated and pass locally, or N/A for docs-only / non-code changes (so CI unit/server checks pass when applicable). - [x] Documentation added or updated, or N/A with justification. - [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so CI lint/format checks pass). - [x] New source files include the required Apache-2.0 SPDX header. - [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check passes). Signed-off-by: Serge Panev <spanev@nvidia.com>
khazic
pushed a commit
to khazic/Gym-lao
that referenced
this pull request
Oct 1, 2026
…IA-NeMo#3686) ## What does this PR do? This is NVIDIA-NeMo#3686 of the series that moves `gdpval-devel` to main, cut by file. It covers GDPVal judging in the resources server, the GDPVal benchmark config and README, the local-judge overlays, and the Stirrup agent's GDPVal task code (`app.py`, `file_reader.py`, `prompts/`). Every file is copied whole from one snapshot: `gdpval-devel` merged with main, plus NVIDIA-NeMo#2046's review fixes and pre-promotion fixes, including the fixes listed at the end of this section. The work comes from devel PRs NVIDIA-NeMo#2103, NVIDIA-NeMo#2331, NVIDIA-NeMo#2338, NVIDIA-NeMo#2588, NVIDIA-NeMo#3086, NVIDIA-NeMo#3121, NVIDIA-NeMo#3131, NVIDIA-NeMo#3184, NVIDIA-NeMo#3335 and NVIDIA-NeMo#3648, and from NVIDIA-NeMo#2046. **Judge transport per provider (comparison mode; NVIDIA-NeMo#2588, NVIDIA-NeMo#3131)** - Each panel member (GPT-5.5, Gemini 3.1 Pro, Claude Opus 4.8) has its own fixed-model `openai_model` proxy. All three default to the same endpoint (`JUDGE_BASE_URL`). - Each member declares how it receives PDFs and rendered Office files (`media_mode`: `native_pdf`, `images_and_text` or `native_pdf_overflow_images`), plus provider limits: native-PDF pages, documents and bytes, image bytes, video count, raster DPI tiers, and a serialized-request cap. - A deterministic preflight removes any member that can't carry a matchup within its limits. If every member is removed for every matchup, the row fails as terminal `transport_ineligible`. - These per-member settings apply to comparison mode only. Rubric mode samples from the same panel but sends every member the server-level `judge_media_mode` (native PDFs by default), with no per-member limits or preflight. - Audio and video capability are separate flags (`handles_audio`, `handles_video`, `on_missing_av_judge`). Video goes to video-capable members. Audio that the routed member can't read is dropped with a warning. - Attachments are bounded per file, per section and per request (raw and encoded bytes), with ZIP member limits and text caps. An attachment left out for size is replaced by a marker the judge sees. - Legacy `.doc`, `.ppt` and `.xls` files are preconverted. When two Office files share a stem, each gets its own `<name>.<ext>.pdf` (NVIDIA-NeMo#2331). A failed OOXML conversion is retried once from a python-docx/python-pptx/openpyxl re-save. **Validity of judge results (NVIDIA-NeMo#2588, NVIDIA-NeMo#3648)** - Rubric: a truncated, scoreless or unparseable reply is flagged `invalid_judge_response` with a `scoring_error` instead of scoring 0 or a partial score. - Structured rubric only (`rubric_scoring_mode: structured`): negative criterion points no longer count toward `max_possible` (NVIDIA-NeMo#3648). - Comparison: the verdict is the last `BOXED[...]` token, a reply with no verdict is dropped instead of counted as a tie, and the prompt ends with a final-verdict reminder. - A request that is too large or overflows the context window fails as permanent. Throttling is retried; rubric retries now also match 502, "bad gateway" and "too many requests". - `strict_comparison_trials` (opt-in) rejects a row unless every planned vote is valid. - Stirrup turns an invalid judge response into a retryable `judge_invalid` failure that keeps the finished deliverable (`reuse_cached_deliverable`), so the retry re-judges it without re-running the policy. - Deliverable conversion runs off the event loop. **Local judges (NVIDIA-NeMo#2046, NVIDIA-NeMo#2103)** - `judge_media_mode: images_and_text` rasterizes PDF and Office pages with PyMuPDF and attaches the extracted text (`media_conversion.py`). `python -m resources_servers.gdpval.inspect_conversion` writes out what an `images_and_text` judge receives. - Overlays for a Kimi-K2.6 judge that gym spawns (`gdpval_kimi_local_judge.yaml`, `local_vllm_model/configs/moonshotai/Kimi-K2.6.yaml`) and for a self-hosted MiniMax-M3 endpoint (`gdpval_minimax_selfhosted_judge.yaml`). **Inputs (NVIDIA-NeMo#3131, NVIDIA-NeMo#3335)** - `judge_reference_files_recursive` (opt-in) includes nested files under a task's `reference_files/`. `judge_reference_files_from_eval` (opt-in) reads `reference_files/` from the eval task directory instead of each reference model's directory. - `count_eval_missing_as_loss` with `missing_eval_task_ids` (opt-in, multi-stage `stage_index` 1 only) scores a listed task with no eval deliverable as a loss. The resources server and the Stirrup agent must both set them. - `setup_libreoffice.py` skips the reinstall when LibreOffice's `javaldx` works, and the apt timeout is 1800 s. **Stirrup GDPVal task code (NVIDIA-NeMo#2338, NVIDIA-NeMo#2588, NVIDIA-NeMo#3121, NVIDIA-NeMo#3131, NVIDIA-NeMo#3184)** - New agent config fields, defaulting to the old behaviour: `context_window_tokens` (passed as the client's `max_tokens`), `min_completion_tokens`, `prompt_estimator_truncate_history_thinking` and `truncation_recovery` (passed to NVIDIA-NeMo#3685's client), and `min_compaction_summary_words` (passed to NVIDIA-NeMo#3685's `NeMoAgent`). - The GDPVal prompts (`prompts/`, moved here from NVIDIA-NeMo#3685 so they land with the code that builds their reference paths) name the registered `code_exec` tool instead of `run_shell`, list only packages the AA v2 sandbox installs, and state the `/root` working directory, the 5-minute command limit and that shell state does not persist. Reference files are listed relative to the sandbox working directory, without the `gdpval_ref_files_<id>/` prefix. - The verify cache is keyed by the row's namespace. The file reader bounds text extraction, sniffs unknown extensions for text, lists archive contents, and degrades without LibreOffice. **Benchmark config** - Starts the three member proxies alongside `gdpval_judge_model`, each with `max_http_attempts: 1800`, and sets each member's media limits. - Claude judge: `thinking: {type: adaptive}` with `output_config.effort: high` and no temperature. - `judge_pdf_max_pages: 1000`. - Stirrup: `min_completion_tokens: 8192`, `context_window_tokens: 262144`, `min_compaction_summary_words: 50`, `truncation_recovery: true`. - NVIDIA-NeMo#3684's `retry_terminal_timeouts` and `retry_invalid_judge_responses`, both `true`. **Fixes made for this PR** - The removed `handles_audio_video` member key is now migrated to both new flags with a deprecation warning; combining it with either new flag is an error. Before, it was silently ignored. - `inspect_conversion.py` crashed at start (`ImportError` on `IGNORE_FILES`). It and `comparison.py` now read `IGNORE_FILES` from `nemo_gym.deliverables`. - README and config comments corrected: per-member proxies, Claude's adaptive thinking, the comparison-only scope of per-member transport, how the page cap works in comparison mode, the judge timeout and retry layers, and the Kimi overlay. The README now also documents the judge input options, attachment environment variables and aggregate metrics. - New tests for the overlays, the comparison retry loop, truncated rubric JSON, the inspector, and the Stirrup client and agent arguments. - The NVIDIA-NeMo#3666 media-preparation helpers (`media.py`, and `check_reference_inputs` and `office` moved into it from `preconvert.py`) have no caller in this series and stay on `gdpval-devel`. Issue: none. Most of this was reviewed in the devel PRs above; the fixes in the last group are new here. Related: NVIDIA-NeMo#2046, whose changes and review fixes are included. **Depends on NVIDIA-NeMo#3682, NVIDIA-NeMo#3685 and NVIDIA-NeMo#3684; merge after them.** It does not need NVIDIA-NeMo#3681 or NVIDIA-NeMo#3683. - NVIDIA-NeMo#3684: `comparison.py` and Stirrup's `file_reader.py` import `nemo_gym/deliverables.py` at module scope, so both suites fail to collect without it. The benchmark config sets C3's two retry flags, which main's config silently ignores. C3's rollout collection also carries `reuse_cached_deliverable` onto the retried row; without it a `judge_invalid` retry re-runs the policy. - NVIDIA-NeMo#3682: the Claude judge sends `thinking` and `output_config`, which main's strict chat schema rejects with a 422. Main's audio part also accepts only wav and mp3. - NVIDIA-NeMo#3685: Stirrup's `app.py` passes the new arguments to NVIDIA-NeMo#3685's client and agent; main's client raises `TypeError: ... unexpected keyword argument 'min_completion_tokens'`. The prompt tests read NVIDIA-NeMo#3685's vendored sandbox manifests. - Open on NVIDIA-NeMo#3682: under its current default (`forward_request_chat_template_kwargs: false`), `vllm_model` drops request `chat_template_kwargs`. The thinking-off half of `truncation_recovery` then does not reach a policy served by `vllm_model`; only the one-turn instruction applies. This config does not set the flag. ## Validation macOS, Python 3.13. Each server suite ran in a venv built from that server's `requirements.txt`, with `PYTHONPATH` set to the tree under test. ```bash cd resources_servers/gdpval && python -m pytest tests -q cd responses_api_agents/stirrup_agent && python -m pytest tests -q python -m pytest tests/unit_tests/test_rollout_collection.py tests/unit_tests/test_openai_utils.py tests/unit_tests/test_deliverables.py -q ``` | Tree | GDPVal | Stirrup | |---|---|---| | main `1c8261080` + NVIDIA-NeMo#3681, C2, C3, S and this PR | 414 passed | 321 passed, 2 skipped | | Same, with `cryptography` not importable | 411 passed, 3 skipped (`test_media_conversion` text extraction) | — | | Full series, including NVIDIA-NeMo#3687 | 603 passed | 321 passed, 2 skipped | The three core unit files: 507 passed on the first tree. Each dependency was confirmed by removing one PR's files: | Removed | GDPVal | Stirrup | |---|---|---| | NVIDIA-NeMo#3681 | no change | no change | | NVIDIA-NeMo#3682 | 5 failed: 4 cases of `test_benchmark_panel_reaches_intended_upstream_models_and_credentials`, and `test_every_supported_audio_extension_passes_proxy_ingress` | no change | | NVIDIA-NeMo#3684 | collection errors (`No module named 'nemo_gym.deliverables'`) | collection errors, same cause | | NVIDIA-NeMo#3685 | no change | 5 failed: 4 prompt tests that read NVIDIA-NeMo#3685's manifests, and `test_runtime_knobs_reach_the_client_and_agent` (the `TypeError` above) | | This PR alone | collection errors, same cause | collection errors, same cause | `pre-commit run --files` on this PR's files passed with no changes; the checklist item below is scoped the same way. `uvx ruff@0.9.9 format --check` and `check` are clean on all 44 files, and new files carry the SPDX header. ## Rollouts **Live GDPVal smoke (final code):** all seven PRs of the series applied together on main `1c8261080`, run with the devel-only SLURM launcher on a 4-task AA-v2 comparison smoke (one 4-GPU policy node, CPU judging): rollout 4/4 tasks saved and finished (1h04m, 0 failure rows), Office preparation 7/7 converted, comparison judging 4/4 tasks judged with 0 imputed and 0 excluded, 3 wins / 1 loss. Per-task outcomes match an earlier gdpval-devel run on the same checkpoint, tasks and references. No rate limiting occurred, so the judge retry budget was not exercised live. This PR stays a draft until NVIDIA-NeMo#3682, NVIDIA-NeMo#3685 and NVIDIA-NeMo#3684 merge. ## Compatibility and benchmark impact GDPVal scores after this PR are not comparable with main's. Re-baseline rubric runs and comparison/ELO runs. **Configuration** - Panel members: `handles_audio_video` is replaced by `handles_audio` and `handles_video` (the old key is migrated with a warning). Member names must resolve uniquely, or verify raises `ValueError`. A `native_pdf_overflow_images` member needs `max_native_pdf_pages` and `max_native_pdf_bytes_per_document`. - New resources-server fields (defaults): `judge_media_mode` (`native_pdf`), `judge_pdf_render_dpi` (144), `judge_pdf_max_pages` (50; the benchmark sets 1000), `judge_pdf_include_text` (true), `judge_max_images_per_request` (450), `judge_handles_audio` / `judge_handles_video` (false), `on_missing_av_judge` (`warn`), `judge_reference_files_recursive` / `judge_reference_files_from_eval` (false), `strict_comparison_trials` (false), `count_eval_missing_as_loss` (false), `missing_eval_task_ids` (`[]`), and the per-member transport fields (unset means no limit). - In comparison mode a raster judge renders up to `max(judge_pdf_max_pages, judge_max_images_per_request)` pages per file, and is not used for a matchup whose pages and images exceed `judge_max_images_per_request`. The overlays' `judge_pdf_max_pages: 30` therefore bounds rubric mode only. - New environment variables: `GDPVAL_JUDGE_REQUEST_TIMEOUT_SECONDS` (comparison default goes from 120 s to 300 s; rubric is 1800 s, where the SDK's 600 s with two retries applied before), `GDPVAL_MAX_FILE_BYTES_FOR_JUDGE` (250 MiB, as before, now overridable), the five section/total attachment and request limits, `JUDGE_GPT_MODEL`, `JUDGE_GEMINI_MODEL`, `JUDGE_CLAUDE_MODEL`, `JUDGE_GEMINI_API_KEY` (falls back to `JUDGE_API_KEY`) and `GDPVAL_GEMINI_MAX_CONCURRENT_REQUESTS` (2). - Judge SDK clients use `max_retries=0`; GDPVal's own loops and each proxy's `max_http_attempts` retry instead. `max_http_attempts: 1800` gives about 15 minutes of retries at 0.5 s per request. Main's retry set also covers 404, 408 and 500, so a persistent 404 (such as an unknown model route) takes the whole window to surface. A comparison call times out after 300 s, is not retried, and time queued behind the Gemini proxy's 2-request limit counts toward it; the proxy can keep retrying an abandoned call until its window closes. The README documents how to lower the window per proxy. - The Claude proxy's `extra_body` sends `timeout: 900` and `request_timeout: 900` in every request, as gateway-side keys for the default endpoint. Endpoints that reject unknown fields need them removed. - The benchmark config starts `gdpval_gpt55_judge_model`, `gdpval_gemini31_judge_model` and `gdpval_claude48_judge_model` alongside `gdpval_judge_model`. With `retry_terminal_timeouts: true`, `reference_missing`, `eval_missing` and `transport_ineligible` are terminal within a run and re-checked on `--resume` (NVIDIA-NeMo#3684). **Changes that move GDPVal scores** - Rubric: invalid judge replies no longer score 0 or a partial score. The resources server's rubric `aggregate_metrics` leaves `invalid_judge_response` rows out of the mean for any agent, and adds `rubric/aggregate_rows_total`, `rubric/aggregate_rows_included`, `rubric/legacy_invalid_rows_excluded` and `rubric/aggregate_rows_included_fraction`. - Structured rubric: negative points are excluded from `max_possible`. Binary mode is unchanged. - Comparison verdicts: last `BOXED[...]` token, no-verdict replies dropped, final-verdict reminder in the prompt. `eval_missing` (no `finish_params.json`) was scored as a loss; it is now a terminal failure in the sidecar, like `reference_missing` and `transport_ineligible`. - Comparison payloads: an Office file's rendered PDF is attached once (main attached it under both names). Office files with no render and file types the judge can't read are left out instead of sent as filename stubs. Every rendered `.xlsx` also carries a structured-cells text block (formulas and cached values, up to 120k characters). Each member sees PDFs in its own representation and within its own limits. - Comparison aggregates: when rows carry `expected_final_stage_index` (written by NVIDIA-NeMo#3687), the headline `comparison/eval_elo` comes from that stage. If it is missing, incomplete or unfit, the headline is withheld (`comparison/final_stage_degraded=1`) rather than taken from another stage; rows without the field keep the last-stage headline. New keys: `comparison/stage_<k>/{judged_tasks,judged_votes,imputed_loss_tasks,imputed_loss_votes}`, `comparison/headline_stage_index`, `comparison/final_stage_*` and `comparison/expected_final_stage_*`. `comparison/win_rate` is omitted when no vote was judged. `judge_response` gains `total_invalid` and a per-judge `invalid_count`. - Claude judge: adaptive thinking with high effort and no temperature (was `thinking: enabled`). - Policy (set in the GDPVal config): `min_completion_tokens: 8192` (the code default stays 1024), `min_compaction_summary_words: 50` and `truncation_recovery: true`. The rewritten prompt names the real tool, package set and working directory, and its reference-file paths exist in the sandbox. **Every `stirrup_agent` config** - `timeout_exceeded` is no longer stamped terminal; on `--resume` it is retried up to `NEMO_GYM_MAX_ROLLOUT_ATTEMPTS`. - A verify result with `invalid_judge_response` becomes a failure instead of a scored row: retryable `judge_invalid`, or terminal `permanent` when the server sets `invalid_judge_retryable: false`. - A `/verify` error whose text matches GDPVal's permanent markers (413, "maximum context length", "too many tokens", "input is too long" and similar) is stamped terminal `permanent` and not retried, unless a throttle marker is also present. - The request's `max_output_tokens` now caps completion tokens; the context window comes from `context_window_tokens` (default 262144). - Failure rows after `/verify` carry the policy response plus `deliverables_dir`, `elapsed_seconds` and `_ng_timeout_inflight`. `judge_only` and `rerun_incomplete` fall back to a flat `task_<id>/` directory. - Stirrup now imports `resources_servers.gdpval` (`scoring`, `judge_panel`, `preconvert`, `media_conversion`), while the GDPVal server already imports Stirrup's file reader. **Known limitations** - `requirements.txt` lists `cryptography>=42.0`, but the root `pyproject.toml` `exclude-dependencies` drops it from server venvs, and an `overrides.txt` does not bring it back. Without it, pdfminer can't load: the `images_and_text` text copy is empty (a warning is logged), 3 `test_media_conversion` tests skip, and rubric `read_deliverable_files` returns an error string for PDFs (also true on main). The fix is a change to the root `pyproject.toml`. - Rubric judge sampling is seeded per task, so a `judge_invalid` retry draws the same member. A provider that rejects a task's payload in rubric mode fails every attempt until the attempt limit. - ZIP members skipped by the archive limits are logged but not marked in the judge request. - The README's multi-stage sections describe `partial_completion` and other behaviour that lands with NVIDIA-NeMo#3687. ## Checklist - [x] I have read the [contributing guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md). - [x] The change is focused; unrelated "drive-by" edits are tracked as separate issues/PRs. - [x] Tests added or updated and pass locally, or N/A for docs-only / non-code changes (so CI unit/server checks pass when applicable). - [x] Documentation added or updated, or N/A with justification. - [x] Pre-commit checks pass locally (scoped: `pre-commit run --files` on this PR's files). - [x] New source files include the required Apache-2.0 SPDX header. - [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check passes). Signed-off-by: Serge Panev <spanev@nvidia.com> Co-authored-by: Marta Stepniewska-Dziubinska <marta-sd@users.noreply.github.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
max_output_tokens; request output limits now cap completion output without shrinking the assumed context windowMotivation
A paired AA/Gym rollout analysis found two harness-level long-context failures. Raw-history fallback counting included reasoning that the served Nemotron template omitted, collapsing later completion budgets to 1,024 tokens and producing repeated invalid tool calls. Separately, reasoning-model compaction responses were replayed with their leading think blocks, adding millions of unnecessary prefill tokens and occasionally replacing progress with near-empty summaries.
Validation
At HEAD
470a5e324:pre-commit run --all-files: passedHSG real-checkpoint canary for the core budgeting/compaction commit
3e496e220:6924145:COMPLETED, exit0, 4 GPUs/lustre/fs1/portfolios/llmservice/projects/llmservice_modelalignment_ppo/users/abukharin/super35_dualds0821_official_hf_pipeline/hf_full/full_generalist_prod/iter_12500/lustre/fs1/portfolios/llmservice/projects/llmservice_nemotron_ultra/users/spanev/gdpval_context_fix_validation_20260904/results/job-6924145/context-fix-canary-receipt.jsonHSG full GDPVal task-only validation at HEAD
470a5e324:code_exec({})calls fell from 364 in the historical run to 1; leading-think retained summaries fell from 94 to 0; under-50-word summaries fell from 8 to 06927667:COMPLETED, exit0, all eight central audits PASS2281eae7c1531f93181ab15dc5dc620f8aa8133bf8077cdd449f5fbf5e3ac966One shard's campaign-local end-audit helper exited after collection because it used
str.splitlines()on a valid JSON string containing U+2028. Its 28/28 LF-delimited rows were intact, and the corrected central audit passed them before canonical publication. This was isolated to the validation helper, not the Gym changes.The historical comparison used the same ordered 220 tasks, equivalent template semantics, 32 GPUs, and aggregate concurrency 160. Its checkpoint was the
pure_lm_ab/hf_textwrapper, while the full validation usedofficial_hf_pipeline/hf_full; their language tensors map one-to-one, but the comparison is directional E2E evidence rather than a code-only causal A/B.The follow-up HEAD commit's output-limit/context-capacity separation is causally covered by the dedicated 8K/262K regression tests; the full run used an outer limit of 262K and provides outcome evidence for that boundary.