Non-agentic evals: parameterize evalchemy sbatch paths by $USER - #85
Non-agentic evals: parameterize evalchemy sbatch paths by $USER#85mrinal-bespoke wants to merge 5 commits into
Conversation
…ed runtime falls back) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… prior commit) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…st the miniforge3 root Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The --output paths are now $USER-scoped via SLURM's %u. That is correct, but
SLURM opens the log file BEFORE the job script runs and does not create
intermediate directories, so a new operator whose
/leonardo_work/AIFAC_5C0_290/$USER/experiments/delphi-eval/ does not exist yet
gets an instant job failure ('Unable to open file') that reads like a launch bug
rather than a missing mkdir.
The job script cannot self-heal this -- its own mkdir -p runs far too late. So
document the one-time setup inline, next to the path it applies to, including
the qwen3-baseline subdirectory that only that sbatch needs.
No behaviour change: comments only. Byte-identical for the original operator,
whose directories already exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l checkout Review catch (P2). _OTA_REPO defaulted to $WORK/$USER/code/OpenThoughts-Agent, which only holds for operators who cloned at exactly that path. Anyone else fell through to the BASH_SOURCE branch — which under SLURM is the /var/spool/slurmd/jobNNN/ copy — so it re-created the very ExitCode-127 'No such file or directory' that the comment three lines above says this block exists to prevent. The blast radius was wider than the exec target. _OTA_REPO also feeds CHAT_TEMPLATE_OVERRIDE (delphi_eval) and THINK_TEMPLATE (delphi_rl_think_eval), so a noncanonical checkout silently pointed lm-eval at a missing chat template instead of reporting that the repo was not found. Resolve by PROBING for a marker file (eval/evalchemy/evalchemy_eval.sbatch), taking the first candidate that actually contains it: 1. OTAGENT_REPO_DIR explicit override 2. $WORK/$USER/code/... canonical per-user checkout 3. SLURM_SUBMIT_DIR + parents where the operator actually ran sbatch 4. shared bfeuer00 checkout same borrow pattern as conda/evalchemy-marin 5. script dir + parents interactive / non-SLURM only Walking up from SLURM_SUBMIT_DIR matters because operators typically submit from a subdirectory of the repo, not its root. If nothing matches, fail LOUDLY listing every candidate tried, instead of exec'ing a nonexistent path and leaving a bare 127. Unchanged for the original operator: candidate 2 hits first when USER=bfeuer00. Verified: canonical operator, noncanonical checkout via SLURM_SUBMIT_DIR, noncanonical with only the spool dir (falls back to shared), explicit override, and the nothing-found path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nativeatom
left a comment
There was a problem hiding this comment.
It looks good now, with a few adjustment options that potentially increase the robustness.
Force-set → env-overridable: a real behavior change masked by the "no-op" claim (medium)
In the base, HF_HOME, HF_HUB_CACHE, and CACHE_ROOT were force-set (export HF_HOME=…/data/hub, etc.), overriding any ambient value. OUT_ROOT was also force-set in delphi_rl_think_eval.sbatch and qwen3_eval.sbatch (OUT in qwen3). In the head these become ${VAR:-default} (evalchemy_eval.sbatch:80-81,96, delphi_rl_think_eval.sbatch:97-98,103,116, qwen3_eval.sbatch:64-65,72,90).
Consequence: if the submitting shell exports any of these (e.g. a HF_HOME pointing at the read-only $HOME, which is a common Hugging-Face habit, or a CACHE_ROOT from another workflow), SLURM propagates it and the script now honours it instead of the work-FS path. For the original operator this can silently redirect the HF/JIT caches away from the pre-populated work-FS copy and fail the eval (or, worse, write into read-only $HOME). This contradicts the letter of the "no-op" claim, which is only guaranteed when none of these variables are set in the submit environment.
This is consistent with the PR's stated design ("explicit env var still wins"), so it is not an accidental bug — but it is a semantic change that the "no-op" wording understates. A one-line mitigation is to keep these force-set (or force-set to _OTA_WORK default unless an explicit opt-in var is given), or to document that a new/other operator must ensure these are unset.
Fallback validation is inconsistent: _pick uses -e, not a marker (low)
The PR's own "Notable fixes" argues that testing only for a directory is insufficient because a partial install can exist. That reasoning was applied to conda (_pick_conda checks /envs/evalchemy-marin), but not to the other two read-only fallbacks:
evalchemy-marin clone — _pick "${_OTA_WORK}/code/evalchemy-marin" … (evalchemy_eval.sbatch:71, delphi_rl_think_eval.sbatch:93, qwen3_eval.sbatch:57) only tests -e on the directory. A half-created/empty own clone would be chosen over the complete shared one, and the job would then fail at python -m eval.eval instead of falling back.
nltk_data — _pick "${_OTA_WORK}/data/nltk_data" … (evalchemy_eval.sbatch:92) has the same weakness: an empty own dir would shadow the populated shared one.
Same bug class the author already fixed for conda, just at lower likelihood. Recommend testing a marker (e.g. pyproject.toml/eval/eval.py for the clone, tokenizers/punkt for nltk) or reusing _pick_conda-style subdir validation.
OUT_ROOT semantics differ between qwen3 and the others (low)
evalchemy_eval.sbatch:110 and delphi_rl_think_eval.sbatch:116 define OUT = OUT_ROOT/RUN_NAME with OUT_ROOT defaulting to …/experiments/delphi-eval. qwen3_eval.sbatch:90 defines OUT = OUT_ROOT/RUN_NAME but with OUT_ROOT defaulting to …/experiments/delphi-eval/qwen3-baseline. A user who sets OUT_ROOT=/custom gets /custom/ from qwen3, dropping the qwen3-baseline grouping that the default (and the --output log path) provides. Not a bug for default usage, but the meaning of OUT_ROOT is not uniform across the four scripts.
Repo-probe ordering can prefer a stale canonical checkout (informational)
_ota_first_repo checks ${_OTA_WORK}/code/OpenThoughts-Agent (candidate #2) before SLURM_SUBMIT_DIR (candidate #3). If an operator has an outdated checkout at the canonical path but is submitting a newer script from a different clone, candidate #2 wins and the job runs against the stale tree. This preserves the original operator's historical behavior (the old code also hardcoded that checkout), so it is not a regression, but it is a subtlety worth knowing for any operator who keeps multiple checkouts.
Problem
The four evalchemy sbatch scripts hardcode
/leonardo_work/AIFAC_5C0_290/bfeuer00/...for every path — conda root, the evalchemy-marin clone, nltk data, HF cache, JIT
caches, and the output root. A second operator cannot run a non-agentic eval at all.
Change
Split paths by whether they are read-only shared runtime or per-user writable:
submitter's own copy if present, else fall back to the shared
bfeuer00one. A newoperator does not have to duplicate a ~20 GB conda env to run an eval.
HF_HOME/HF_HUB_CACHE,CACHE_ROOT,OUT_ROOT) — always per-user.#SBATCH --outputuses SLURM's%u.Each is
${VAR:-<default>}, so an explicit env var still wins.This is a no-op for the original operator: with
USER=bfeuer00every path resolvesto the same byte-identical string as before.
Notable fixes
Conda fallback must validate the env, not the root. Testing only for a
miniforge3directory is not enough — an operator can have a partial install (e.g.
otagentbut notevalchemy-marin) and would then fail withEnvironmentNameNotFound: evalchemy-marininstead of falling back to the shared root. The check is now for
<root>/envs/evalchemy-marin.--outputdirectories must pre-exist. SLURM opens the log file before the job scriptruns and does not create intermediate directories, so a new operator gets an instant
"Unable to open file" that reads like a launch bug rather than a missing mkdir. The script
cannot self-heal this — its own
mkdir -pruns far too late — so the one-time setup isdocumented inline next to each path.
Reviewer note
With
HF_HUB_OFFLINE=1andHF_HOMEnow per-user, a new operator's cache starts empty,so models will not resolve until they pre-cache or symlink them in. That is inherent to
making the cache writable (one shared HF cache across operators is not safe), and the
symlink workaround is documented in the diff — but it is a genuine behaviour change for a
new user.
Testing
bash -nclean on all four scripts.new operator (shared fallback for runtime, per-user for writable), and partial install
(correctly prefers shared over an incomplete own root).
🤖 Generated with Claude Code