Skip to content

feat(e2e_eval): unify example-tests into recipe-driven run_eval#1009

Open
KayMKM wants to merge 5 commits into
mainfrom
yuesu/merge-eval-example-tests-rebased
Open

feat(e2e_eval): unify example-tests into recipe-driven run_eval#1009
KayMKM wants to merge 5 commits into
mainfrom
yuesu/merge-eval-example-tests-rebased

Conversation

@KayMKM

@KayMKM KayMKM commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Unifies the two e2e evaluation scripts (run_eval.py and the example-only
run_example_tests.py) into a single recipe-driven runner: build a model, run
winml perf, and — when perf passes — run winml eval, writing one unified
eval_result.json per (model, task, precision). Also adds two --clean-cache
disk-hygiene fixes so long matrix runs don't fill the disk.

Recipe-driven runner

  • Add utils/recipes.py: data-driven discovery of examples/recipes/<slug>
    configs (fp16/w8a16/w8a8), single and composite (encoder/decoder,
    image/text-encoder).
  • run_eval.py expands the registry into one job per recipe precision variant;
    builds via winml build -c, falling back to winml config when a model has
    no recipe. Perf-gated accuracy reuses the existing both-mode gating.
  • Accuracy phase now only runs winml eval and records the full metrics +
    dataset; delta/verdict grading vs the PyTorch baseline is left to the report
    site. Inline baseline removed; offline --update-baseline maintains the
    baseline cache.
  • Remove runner-side report generation (generate_report.py,
    analyze_results.py); slim reporter.py/accuracy.py (keep compute_delta
    • thresholds for the site). Drop the in-pipeline generate_report step.

Disk hygiene under --clean-cache

  • Per-job build artifacts: remove a job's export/optimized/model.onnx
    (+ .data/.bin) once its eval_result.json is written, keeping only the
    JSON the report site reads — peak disk stays at ~one job.
  • Stray ORT/QNN scratch models: sweep temporary models ORT/QNN leak into the
    working directory (UUID-named <uuid>.onnx/.data, QNN EP-context dumps
    <uuid>.onnx_<EP>.bin, and sym_shape_infer_temp.onnx). Matches only
    UUID-prefixed or the fixed temp name so real files are never touched; runs at
    startup and after each job.

Notes

KayMKM added 3 commits July 1, 2026 11:04
Unify the two e2e evaluation scripts (run_eval.py and the example-only run_example_tests.py) into a single recipe-driven runner that builds a model, runs winml perf, and — when perf passes — runs winml eval, writing one unified eval_result.json per (model, task, precision).

- Add utils/recipes.py: data-driven discovery of examples/recipes/<slug> configs (fp16/w8a16/w8a8), single and composite (encoder/decoder, image/text-encoder).
- run_eval.py expands the registry into one job per recipe precision variant; builds via 'winml build -c' (fallback to 'winml config' when a model has no recipe). Perf-gated accuracy reuses the existing both-mode gating.
- Accuracy phase now only runs winml eval and records the full metrics+dataset; delta/verdict grading vs the PyTorch baseline is left to the report site. Inline baseline removed; offline --update-baseline mode maintains the baseline cache.
- Remove runner-side report generation (generate_report.py, analyze_results.py); slim reporter.py/accuracy.py (keep compute_delta + thresholds for the site). Drop the in-pipeline generate_report step.
- Tests for recipe discovery, job expansion, recipe build (single/composite/vitisai/failure), recipe-vs-fallback eval, and the new accuracy schema.
Recipe builds write export/optimized/model.onnx (+ .data/.bin) into each job's result dir via -o; across many models these fill the disk. With --clean-cache, remove a job's build artifacts once its eval_result.json is written, keeping only the JSON facts the report site reads, so peak disk stays at ~one job.
…an-cache

onnxruntime/QNN leak temporary ONNX models into the subprocess working directory (the repo root): UUID-named <uuid>.onnx/.data, QNN EP-context dumps (<uuid>.onnx_<EP>.bin), and sym_shape_infer_temp.onnx from shape-inference preprocessing. These sit outside any path the harness controls via -o/--output, so neither the per-job artifact cleanup nor the HF/winml cache cleanup removed them, and they piled up across jobs until the disk filled — even with --clean-cache (reported on a QNN box where EP-context dumps are large).

Add _clean_stray_cwd_artifacts(), matching only UUID-prefixed names or the fixed sym_shape_infer_temp so real files are never touched. Sweep the cwd at startup (clear a prior interrupted run's mess) and after every job under --clean-cache, alongside the existing job-artifact and cache cleanups.
@KayMKM KayMKM requested a review from a team as a code owner July 1, 2026 03:13
KayMKM added 2 commits July 1, 2026 11:33
…h cleanup

- --retry-failed only matched the coarse accuracy_status values (PASS/FAIL/SKIPPED/NOT_RUN), but the help text advertised the removed ACCURACY_REGRESSION verdict, so documented retries silently matched nothing. Update the help to list the actually-valid values (perf failure classifications + accuracy FAIL).

- Document that an empty winml-eval metrics map ({}) is intentionally treated as FAIL (not PASS), aligning the comment with the legacy baseline path's is-not-None check.

- Sweep the EP engine-compile timing dump (timing_log.csv) that some execution providers leak into the process cwd, alongside the existing UUID/sym-shape scratch cleanup under --clean-cache, so it cannot accumulate and fill the disk.

- Add tests for _should_skip_existing retry matching and the extended stray-cwd cleanup.
With --eval-type accuracy/both and --continue, a perf-only eval_result.json (accuracy=null) is no longer skipped: the model is rebuilt and winml eval is run, reusing the already-recorded perf instead of re-benchmarking, and the accuracy is merged back into the same result file. This lets a fast --eval-type perf sweep be topped up with accuracy later.

- Add _needs_accuracy_backfill(): backfill when accuracy is missing and wanted (always for accuracy mode; only when perf passed for both mode; never for perf mode). _should_skip_existing() returns False for these so plain --continue tops them up.
- Extract _build_for_job() shared by the normal and backfill paths.
- Preserve the recorded perf verbatim; only replace the accuracy section and extend eval_types_run.
- Update --continue help and README; add TestAccuracyBackfill.

@DingmaomaoBJTU DingmaomaoBJTU left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Well-structured refactor: recipe-driven eval with clean separation of concerns. All 97 tests pass. The runner now records facts only (metrics + dataset maps) and defers baseline grading to the report site. Recipe discovery is data-driven — drop a new precision config and it's picked up automatically. LGTM.

@DingmaomaoBJTU DingmaomaoBJTU left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice consolidation of the two eval scripts. The recipe-driven job expansion, perf-gated accuracy, and the --clean-cache disk-hygiene sweeps read cleanly and are well covered (103 new tests pass locally; ruff check is clean). I verified _read_eval_output matches EvalResult.to_dict()'s top-level metrics/dataset keys, and that the removed generate_report.py/analyze_results.py plus the trimmed reporter/accuracy symbols have no remaining references anywhere. A couple of minor, non-blocking notes inline.

# skip run_model and only (re)build + run accuracy. A build that
# now fails records a skipped accuracy without touching perf.
if not build_result["success"]:
accuracy_result = {"skipped": True, "skip_reason": "build_failed"}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When a backfilled (re)build fails here, accuracy is recorded as {skipped: build_failed} while the reused perf stays passed, so the job still exits 0. And because _should_skip_existing skips a passed-perf result whose accuracy is skipped, a later --continue / --retry-failed won't re-attempt it — the only recovery is deleting eval_result.json. This mirrors the accuracy-only path at ~L2813, so it may be intentional, but for a resume-oriented feature a transient build failure being permanently masked (and not counted as a failure) is surprising. Consider leaving accuracy as None on build failure so the next --continue backfills it again.

winml_key = ds_config.get("winml_metric_key") or ds_config.get("metric")
if winml_key and metrics and winml_key in metrics:
num_samples = (dataset or {}).get("samples", ds_config.get("num_samples"))
metric = {"metric": winml_key, "value": float(metrics[winml_key]), "num_samples": num_samples}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor: this branch isn't ruff format-clean. ruff format rewraps this line (106 chars) into a multi-line dict, and also touches the _build_jobs signature, the BACKFILL safe_print, and two spots in tests/unit/eval/test_run_eval_script.py. CI's lint job only runs ruff check (which passes), so it won't block — but the repo's pre-commit includes ruff-format, so worth running uvx ruff format scripts/e2e_eval/run_eval.py tests/unit/eval/test_run_eval_script.py before merge.

@zhenchaoni zhenchaoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

1. Build artifacts go to the wrong place.
Recipe builds run winml build -c … -o eval_results/<date>/models/<slug>. Since -o and --use-cache are mutually exclusive, artifacts never reach ~/.cache/winml/artifacts. The fallback path still uses --use-cache, so within one run, recipe jobs and fallback jobs store artifacts in two different locations. This contradicts your goal of keeping everything in the winml cache.

2. Build failure silently mislabels precision.
When a recipe build fails, onnx_paths is empty, so perf/eval fall back to -m <hf_id> and rebuild at the default precision — then record the result under the recipe's precision slug (e.g. a failed fp16 build gets tested and stored as fp16 but actually ran w8a16). A failed recipe build should be a hard failure, not a silent fallback.

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.

3 participants