Skip to content

feat(eval): export a finished run to MLflow or W&B with gym eval export - #4914

Merged
prokotg merged 8 commits into
prokotg/eval-export-exportersfrom
prokotg/eval-export-record-exporters
Oct 28, 2026
Merged

prokotg merged 8 commits into
prokotg/eval-export-exportersfrom
prokotg/eval-export-record-exporters

Conversation

@prokotg

@prokotg prokotg commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • gym eval export mlflow|wandb sends a finished run to the backend: its saved config, aggregate metrics and (optionally) rollouts. BaseExporter becomes an Exporter with export(artifacts), reusing the live exporters' hooks.
  • Guard: if <stem>_<exporter>_export_done exists the run is skipped without contacting the backend. A post-run export writes it only after the whole run is sent (atomically, holding the backend run id), so a failed export can be retried and a failed write leaves none. It is separate from the <stem>_<exporter>_run_id a live run records for resume. A live run id does not make the export skip, because it only shows that the run opened, not that every upload landed: the export goes ahead as a separate run, logs a warning and returns live_run_id. gym eval submit already rejects a live exporter plus the same export for one backend, so only a manual export after a live run is affected. A post-run export never reads or writes the live run id, so a later live resume does not reopen the export's run.
  • It fails before opening the backend unless _aggregate_metrics.json exists, is not older than the rollouts and records no failed agent (a partial result is never exported), and the saved config exists.
  • Rollouts upload unless the saved upload_rollouts is false (only that value is resolved, so an interpolated setting works); upload_rollouts= on the command overrides. Settings and credentials come from the export command, never from the masked saved config.
  • flatten_aggregate_metrics replaces two duplicate blocks in collection and reverification.
  • A WANDB_API_KEY in the environment is used when the command sets no key.
  • skip_exporters parser option, so the export command does not also open a live exporter; a missing SDK error names the extra to install. CLI reference and the live exporter settings page updated.

Part of #2977 (export-aware evaluation runs).

PR plan

Separate PRs; steps 1-8 are stacked (stack #4910). Goal: make gym eval export pluggable so a finished run can go to tracking systems without live exporters, and gym eval submit can export after each benchmark.

  1. save the masked, unresolved config from gym eval run (feat(eval): save the masked config beside rollouts from gym eval run #4908)
  2. RunArtifacts and path derivation helpers (feat(eval): add RunArtifacts to derive a run's files from its rollouts path #4909)
  3. Exporter base class and registry, starting with atif, plus the positional exporter CLI argument (--format deprecated) (feat(eval): add the Exporter base class and registry, with atif as the first exporter #4911)
  4. This PR: post-run mlflow and wandb export: shared metrics flattening, BaseExporter.export() and the already-exported guard
  5. isolated-venv subprocess runner for directory exporters (feat(eval): add IsolatedExporter to run an exporter's backend work in its own venv #4915)
  6. EvalRDL exporter (feat(eval): export a finished run to EvalRDL with gym eval export evalrdl #4965)
  7. gym eval submit integration: an export map with job-level defaults and per-benchmark overrides, validation, install extras, and a job-script tail (Slurm only at first) (feat(orchestration): run exports after each benchmark of gym eval submit #4974)
  8. contributor guide: add an exporter (user-facing docs land in each step's PR) (docs(contribute): add a guide to writing an exporter for gym eval export #5077)

Validation

  • 42 tests in test_record_exporters.py: export flow, done-marker timing and atomic write, its own file apart from the live run id, a live run id reported and not skipping, skip, failure and retry, preconditions (including a failed agent in the aggregate file), upload intent (including interpolated settings), W&B key from the environment, skip_exporters, missing-SDK error, mocked MLflow and W&B exports. pre-commit: passed.
  • Real MLflow run (file store, MLFLOW_ALLOW_FILE_STORE=true): one FINISHED run with the flattened metrics, params from the saved config, and global_config.json and rollouts.json artifacts; a second invocation printed skipped. The same real local store also covered: a live run then an export (two runs, the live marker untouched), a live resume (reopens the live run, not the export's), a fresh live run, and a failed upload then a retry (no marker, the retry succeeds). W&B is mocks only.

Known gaps

  • A run logged live and then exported by hand ends up in the backend twice (see the guard). Skipping safely would need the live path to record its own completion at every export site.
  • Live runs also log coverage/* metrics; they are not in the aggregate file, so post-run export omits them.
  • A backend failure partway through leaves a partial MLflow run marked FINISHED; the retry creates a new one.

Rollout evidence

N/A: no agent, verifier or model behavior is touched. Live logging behaves as before; only the metrics flattening moved into a shared function.

@prokotg
prokotg added this pull request to stack #4910 October 22, 2026 12:33
@copy-pr-bot

copy-pr-bot Bot commented Oct 22, 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.

@prokotg

prokotg commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 5f0d7c1

@prokotg

prokotg commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

@svcnvidia-nemo-ci svcnvidia-nemo-ci 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.

HOLD — four RISK findings affect export completeness, configuration handling, retryability, and W&B authentication. One NOTE corrects the credential-source documentation.

Reviewed all 10 changed files and traced the affected CLI, artifact-writing, backend, and regression-test paths. This was a static review; tests and backend calls were not executed. Suggested patches and proposed checks require verification before adoption.

Note: formal approval is not available for this request, so this review is posted as a comment only.

Review generated by codex. This was a full review. Request a full review with /review reset=true.

Comment thread nemo_gym/exporters/base.py Resolved
Comment thread nemo_gym/exporters/base.py Resolved
Comment thread nemo_gym/exporters/base.py Resolved
Comment thread nemo_gym/exporters/base.py Resolved
@prokotg
prokotg force-pushed the prokotg/eval-export-record-exporters branch from 5f0d7c1 to 88ecef4 Compare October 23, 2026 09:52
@prokotg

prokotg commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 88ecef4

@prokotg

prokotg commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test d9d5c32

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

Two targeted comments on export completion state and integration with #5029. The second concerns inherited live behavior.

Comment thread nemo_gym/exporters/base.py Outdated
Comment on lines +230 to +232
marker = artifacts.exporter_run_id_fpath(self.name)
if marker.exists():
return ExportResult.skipped(reason="already exported", run_id=marker.read_text().strip())

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.

This marker proves that a tracking run was opened, not that its uploads completed. If live rollout upload fails, evaluation can still finish with valid artifacts, but a later gym eval export mlflow returns 'already exported' without attempting the missing upload. I reproduced that with a fake backend; removing the marker lets export succeed. Could we distinguish resumable run identity from completion evidence for the requested export? The live ID still needs to be written during setup for resume.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks for reproducing it. A post-run export now has its own marker, <stem>_<name>_export_done (written atomically, only after everything was sent) and doesn't touch <stem>_<name>_run_id anymore, so the live path keeps the run id file to itself. Fixed in bbcf507.

One behavior change: a live run id no longer makes gym eval export skip. The export goes ahead as a separate run, logs a warning and returns live_run_id. So a run logged live and then exported by hand can show up twice, which I think beats a silent skip, since the live id only says the run opened. gym eval submit already refuses live + the same export for a backend, so it can't hit this. Making the live path record real completion would mean touching every export site in collection and reverification, so I left that out of this PR.

Covered by tests, and I ran live-then-export, export-then-live-resume, re-export and failed-then-retried upload against a real local MLflow store.

Comment on lines +207 to +208
if self._exported_artifacts is not None:
return None

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.

On the live path this still reopens an existing ID when resume_from_cache=False, so a fresh evaluation at the same output path logs into the previous evaluation. #5029 fixes that decision, including resubmit and cache-miss fallback. I suggest merging #5029 first and rebasing this stack on main. Here, retain the post-hoc return None first, then apply #5029's continuation check to live calls; keep pending post-hoc ID publication separate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, the stack is rebased and merged with it the way you described: post-run return None first, your continuation check for live calls, and the pending post-run id kept separate. On top of that the post-run export no longer shares the run id file with the live path (see the other thread).

@prokotg

prokotg commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Blocked by #5029 (live exporters: reopen the tracking run only on resume, log each landed rollout once).

Both PRs rewrite _recorded_run_id and share the <stem>_<name>_run_id marker, and #5029 changes when a live run reopens it. Landing it first lets this PR rebase once against the final run-id handling. When it does, the early None for post-run exports must stay ahead of #5029's flag checks, so an export never reopens a run.

Removing blocked once #5029 is in.

@prokotg

prokotg commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

/review

BaseExporter gains export(artifacts): it logs the run's saved config, aggregate metrics and
rollouts through the same hooks live runs use, skips a run it already exported, and writes the
run id marker only after the whole run is sent. Live and post-run metrics share one flattener.

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…inished runs

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…mplete

Refuse an aggregate file with a failed agent instead of exporting a partial result, resolve only the
upload setting of an interpolated saved config, publish the already-exported marker atomically, keep
a WANDB_API_KEY from the environment when the command sets none, and name env.yaml in the docs.

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…ide and the upload filter

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…y users

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…re not uploaded

The rollouts file is now only required when the rollouts are uploaded, and the freshness check
compares against it only when it is there. The error for a missing file says how to skip the upload.

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…tead of writing an empty marker

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
…'s run id

A live run writes its run id when the backend opens, so that file shows the
run opened, not that every upload landed. Sharing it as the already-exported
marker made gym eval export skip a run whose live upload had failed, and made
a later live resume reopen the export's run. A post-run export now writes
<stem>_<name>_export_done and ignores the live run id, logging and reporting
it when there is one. The live path reads and writes only <stem>_<name>_run_id.

Signed-off-by: Tomasz Grzegorzek <tgrzegorzek@nvidia.com>
@prokotg

prokotg commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 0e4dcd9

@prokotg

prokotg commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

/review

@svcnvidia-nemo-ci svcnvidia-nemo-ci 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.

SHIP — no eligible findings remain in this follow-up.

Verified against current source and regression assertions:

  • Failed-agent aggregates are rejected before backend setup; repaired aggregates export both agents. Selective upload-setting resolution preserves canonical-key precedence and unrelated unresolved expressions (implementation, regressions).
  • Completion-marker publication remains atomic, with partial-write failure leaving no marker and allowing retry. Post-run completion markers are separate from live resumable IDs (publication, regressions).
  • W&B retains an inherited API key unless command configuration overrides it (setup, credential assertions). The documentation names env.yaml, matching the parser.

Also checked optional-rollout export, missing backend IDs, command/live setup separation, and live-resume isolation along their directly affected paths. No eligible introduced regression was established.

This was a static follow-up of review 5663786908, not a fresh whole-PR review. The previously reviewed tree was unavailable, limiting exact inter-review comparison. One delegated report was unavailable; its artifact-readiness/upload topic was independently inspected by the parent. Tests and backend calls were not executed.

Note: formal approval is not available for this request, so this review is posted as a comment only.

Review generated by codex. This was a findings-only review. Request a full review with /review reset=true.

@github-actions

Copy link
Copy Markdown
Contributor

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

This branch was previously deployed

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

Labels

area:evaluation Evaluation, rollout collection, reward profiling, comparison, and exporters feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants