Skip to content

feat(eval): add gym eval plan to write a benchmark's task list - #4131

Merged
iguehring-nvidia merged 5 commits into
mainfrom
iguehring/gym-eval-plan
Oct 8, 2026
Merged

iguehring-nvidia merged 5 commits into
mainfrom
iguehring/gym-eval-plan

Conversation

@iguehring-nvidia

@iguehring-nvidia iguehring-nvidia commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds gym eval plan, which prepares one benchmark and writes a plan file: every task once, sorted by task id, with a SHA-256 digest of the exact input a run receives.

Why: Gym has no single command that answers "which tasks does this benchmark have, and with exactly what inputs?" in a stable form. The prepared JSONL depends on preparation details, its row order is not guaranteed, and many rows have no id, so collation falls back to the row position. gym eval plan produces that answer as one file: stable task ids, a digest of each task's exact run input, and a versioned, documented format. The plan stores ids and digests, not the inputs themselves, so it stays small; a run re-prepares the data, and the digests show whether it still matches the plan.

What the command does:

  • Selects the benchmark with the existing --benchmark NAME[/FLAVOR] lookup and runs its prepare.py (shared code with gym eval prepare).
  • Takes each task's id from task_id, problem_id or instance_id, the same precedence as materialize_task (feat(data): collate declared tasksets #3818); the field list is now defined once, as TASK_ID_FIELDS in nemo_gym/task_materialization.py, and used by materialize_task, rollout collection and the plan. Unlike collation, there is no fallback to the row position: a position-based id names a different task once rows are reordered, so benchmarks without an id field are refused with an actionable error.
  • Each task is {task_id: {taskset, task_id}, digest}. The digest is sha256(canonical JSON of task_input), where task_input is the MaterializedTask input with routing and bookkeeping fields (agent_ref, task_source, _ng_*) excluded. The taskset is the dataset's declared taskset, or the benchmark name when it declares none.
  • Records the Gym version and commit and the selected config paths (relative to the checkout).
  • Output is deterministic (sorted tasks, canonical JSON): same Gym commit and same prepared data give a byte-identical file.
  • --task-ids plans a subset; --schema prints the JSON Schema; the format is labelled nemo-gym.eval-plan.v1 and is meant to change only additively within that version.
  • Benchmarks with a prompt template (prompt_config) are refused for now; applying the template is a follow-up.

prepare_benchmark is split into _datasets_to_prepare and _prepare_datasets so both commands share them; its behaviour is unchanged, except that the "no preparable dataset" error no longer names gym eval prepare (it suggests --config <config path> or --benchmark <name>). The plan considers only the benchmark datasets among the datasets to prepare.

No issue filed: this is a new, additive command. Related: the Harbor task work (#3673 and follow-ups) produces the same MaterializedTask rows and pins, so plans for Harbor datasets should line up with it. Feedback on the file format is very welcome while this is a draft.

Validation

  • pytest tests/unit_tests/test_eval_plan.py: 20 passed (id precedence, ids and digests only (no inputs) in the file, declared taskset used, refusal of missing and duplicate ids, refusal of already-materialized rows as a config error, routing fields excluded from input and digest, subset selection, deterministic output, schema round trip, CLI: file written, subset, prompt-template refusal, several-benchmarks refusal, --out required, --schema).
  • pytest tests/unit_tests/test_cli_main.py tests/unit_tests/test_cli_compat.py tests/unit_tests/test_rollout_collection.py tests/unit_tests/test_task_materialization.py tests/unit_tests/test_benchmarks.py: all pass after rebasing onto main, except test_benchmarks.py::test_every_repo_benchmark_appears_in_listing, the DNS gaierror on my machine noted below.
  • pytest tests/unit_tests/test_benchmarks.py tests/unit_tests/test_cli_main.py tests/unit_tests/test_cli_compat.py: 335 passed, 1 failed. The failure (test_every_repo_benchmark_appears_in_listing) is a DNS gaierror while resolving four configs on my machine and fails identically on unmodified main.
  • pre-commit run --all-files: all hooks pass.
  • Real runs:
    • gym eval plan --benchmark swebench/verified/opencode --out plan.json: 500 tasks keyed by instance_id; running it twice gives byte-identical files.
    • --task-ids astropy__astropy-12907,django__django-11099: plan with those 2 tasks.
    • --benchmark terminal_bench_2_1/terminus_2 (fresh preparation, on main with fix(terminal_bench_2_1): give prepared rows a stable task_id and order #4135): 89 tasks, keyed by task folder name.
    • --benchmark aime24: refused because of its prompt template.

Rollouts

N/A: the command only prepares data and writes a file; no server, agent or model code changes.

Compatibility and benchmark impact

None. New command; gym eval prepare behaviour unchanged.

Checklist

  • I have read the contributing guidelines.
  • 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).
  • Documentation added or updated, or N/A with justification.
  • Pre-commit checks pass locally (pre-commit run --all-files) (so CI lint/format checks pass).
  • New source files include the required Apache-2.0 SPDX header.
  • All commits have DCO sign-off (git commit -s) (so the DCO check passes).

@iguehring-nvidia iguehring-nvidia self-assigned this Oct 6, 2026
@copy-pr-bot

copy-pr-bot Bot commented Oct 6, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

@yaoyu-33 yaoyu-33 added area:cli Command-line interfaces and user-facing command behavior complexity:medium Single-domain change with interacting parts or a moderate review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer labels Oct 6, 2026
@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/gym-eval-plan branch from 63baa51 to 342ca6f Compare October 7, 2026 05:46
Comment thread nemo_gym/cli/eval.py Resolved
Comment thread nemo_gym/eval_plan.py Resolved
Comment thread nemo_gym/cli/eval.py Resolved
Comment thread nemo_gym/eval_plan.py Resolved
khazic pushed a commit to khazic/Gym-lao that referenced this pull request Oct 7, 2026
NVIDIA-NeMo#4135)

## What does this PR do?

`benchmarks/terminal_bench_2_1/prepare.py` wrote rows in `glob()` order
(filesystem order) and without an id field. Gym's task identity
(`_trajectory_identity`, `materialize_task`) therefore fell back to the
row position, so "task 0" could name a different task on another machine
or after a re-clone.

This PR:
- adds `task_id` to every row, set to the task's folder name (e.g.
`path-tracing`). That is the id the Harbor task format uses for the same
task (NVIDIA-NeMo#3673 takes the folder name as `task_id`; NVIDIA-NeMo#3730 keeps TB2.1's
folder names), so the ids stay the same when TB2.1 moves to that format.
Folder names are unique by construction; `task_name`
(`terminal-bench/path-tracing`) is unchanged;
- writes rows in sorted order;
- checks that the 89 task ids are unique (`NUM_TASKS = 89` replaces the
literal in the existing count check).

Servers are unaffected: `TaskData` is `extra="allow"`, the Terminus and
OpenCode run requests accept extra fields, and nothing reads `task_id`
from TB2.1 rows today.

Motivation: stable ids are required by `gym eval plan` (NVIDIA-NeMo#4131), which
refuses position-based ids. With this change TB2.1 can be planned. No
separate issue.

## Validation

- Ran `prepare.py` via `gym eval plan --benchmark
terminal_bench_2_1/terminus_2` (with NVIDIA-NeMo#4131 applied locally): 89 tasks,
ids `adaptive-rejection-sampler`, `bn-fit-modify`, …; two runs produce
byte-identical plans. Without this change the same command is refused
with "89 of 89 tasks have no task id".
- `pre-commit run --files benchmarks/terminal_bench_2_1/prepare.py`:
pass.

## Rollouts

N/A: data-only change. The added field is ignored by the TB2.1 resources
server and both agents; the instruction, image and task folder of every
row are unchanged. Happy to run a smoke rollout if reviewers want one.

## Compatibility and benchmark impact

- Rollout outputs for TB2.1 now report the task's folder name as the
task id instead of the row index, so tooling that joins TB2.1 results
across runs by task id will see different keys for runs before and after
this change.
- Row order changes, so `_ng_task_index` values change. Don't resume
(`--resume`) a TB2.1 run started before this change after re-preparing
the data.
- Scores are unaffected.

## 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). N/A:
no existing tests cover this prepare script; validated by running it.
- [x] Documentation added or updated, or N/A with justification. N/A: no
documented behaviour changes.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass). Ran on the changed file.
- [x] New source files include the required Apache-2.0 SPDX header. N/A:
no new files.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
sephmard
sephmard previously approved these changes Oct 7, 2026

@sephmard sephmard 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.

Looks good overall — nice refactor, solid test coverage, left one small comment on a test gap. Approving.

Comment thread nemo_gym/cli/eval.py Resolved
Add `gym eval plan`, which prepares one benchmark and writes a plan file:
every task once, sorted by task id, with a SHA-256 digest of the exact
input a run receives. The plan stores ids and digests, not the inputs
themselves. The format is versioned (nemo-gym.eval-plan.v1), only
changes additively within a version, and `--schema` prints its JSON
Schema.

Task ids come from the row's task_id, problem_id or instance_id, the
same precedence materialize_task uses; unlike collation there is no
fallback to the row position, so benchmarks without ids are refused.
Their taskset is the dataset's declared taskset, or the benchmark name
when it declares none. Benchmarks with a prompt template are refused for
now.

prepare_benchmark is split into _datasets_to_prepare and
_prepare_datasets so the plan command reuses them; its behaviour is
unchanged. The plan considers only benchmark datasets among them.

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
…l prepare

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
…rror

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
@iguehring-nvidia

iguehring-nvidia commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Needs a review from Tim Ruffles (who needs to join Nemo org before he can be assigned) before merging.

@timruffles timruffles left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly what we need, TY!

@iguehring-nvidia
iguehring-nvidia added this pull request to the merge queue Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/37815277147

Merged via the queue into main with commit acd53a9 Oct 8, 2026
44 checks passed
@iguehring-nvidia
iguehring-nvidia deleted the iguehring/gym-eval-plan branch October 8, 2026 17:34

This branch was previously deployed

1 inactive deployment
public — 259e1873 Deployed Oct 7, 2026 by copy-pr-bot[bot] via release / finalize / notify #4593
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cli Command-line interfaces and user-facing command behavior complexity:medium Single-domain change with interacting parts or a moderate review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer sla:review-overdue Review response is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants