Skip to content

Run ASE jobs through a persistent worker that keeps models loaded - #1073

Open
kfir4444 wants to merge 4 commits into
mlip_run_fixesfrom
ase_worker
Open

kfir4444 wants to merge 4 commits into
mlip_run_fixesfrom
ase_worker

Conversation

@kfir4444

@kfir4444 kfir4444 commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What

A persistent ASE worker that keeps machine-learned potentials (MLIPs, here UMA) loaded between ARC jobs, and two scheduler/job fixes found while running ARC on MLIPs at scale on a PBS cluster.

Stack: builds on #1072 (mlip_run_fixes). It is the first of four PRs: worker (this one) ← Hessians (#1075) ← NEB (#1076) ← TS scans (#1077).

Changes

  1. A persistent ASE worker. Every ASE job started a Python process that loaded the model again: about 6.5 s, against 0.1–0.3 s of compute for a single point or optimization of a small molecule. A worker (ase_script.py --serve) keeps models loaded and takes jobs from a file spool (claims by atomic rename, heartbeats). The adapter submits a ticket to a live worker and falls back to running the job directly when no worker is left or its worker dies. A smoke test ran 1.64 times faster in wall time. The !F commit moves Fix failures of ARC runs on machine-learned potentials (ASE) found in a benchmark #1072's IRC test to the worker's run_job().
  2. ARC took an old opt job as a species' latest after rotating initiated_jobs.csv, because job numbers restart there. It now uses the order in which the jobs were spawned.
  3. A job PBS refused to delete ended the run. PBS refuses to delete a job that is already exiting, and qstat still lists it, so delete_job() raised. It now logs the error and leaves the job to finish.

Checks

  • Production: the worker ran the CC//UMA routes of a 116-species, 25-reaction benchmark and its unimolecular extension on zeus; 2 and 3 replaced crashes of those runs.
  • Tests, locally: ase_test.py, scheduler_test.py, trsh_test.py, species_test.py and checks/ts_test.py pass, except two tests that already fail on main in this environment because they run Arkane (test_check_rxn_e0, test_compute_rxn_e0).

Tests

  • test_serve_runs_tickets_and_survives_a_failed_job, test_a_claimed_ticket_cannot_be_claimed_or_withdrawn, test_live_workers_ignores_a_stale_heartbeat, test_execute_incore_through_a_live_worker, test_execute_through_worker_withdraws_when_no_worker_is_left, test_execute_through_worker_falls_back_when_its_worker_dies, test_a_live_worker_takes_jobs_without_blocking, test_a_worker_job_ends_when_its_ticket_does, test_a_ticket_no_worker_is_left_to_take_runs_directly, test_a_job_whose_worker_died_runs_directly, test_a_queue_job_id_is_not_a_ticket, test_no_live_worker_leaves_the_execution_type_alone, test_run_vibrational_analysis_ignores_a_stale_cache, and test_an_irc_job_ends_at_the_end_of_its_path (moved)

Comment thread arc/job/adapters/ase_test.py Fixed
stop_heartbeat.set()
try:
os.remove(heartbeat_path)
except FileNotFoundError:
Comment thread arc/scheduler.py Fixed
@kfir4444 kfir4444 changed the title Run ARC on machine-learned potentials: a worker, exact Hessians, NEB TS guesses, and TS rotor scans that stay on the saddle Run ASE jobs through a persistent worker that keeps models loaded Nov 2, 2026
write_json_atomic(os.path.join(dirs['done'], f'{ticket}.json'), content, dirs['tmp'])
try:
os.remove(os.path.join(dirs['running'], f'{ticket}.json'))
except FileNotFoundError:
@codecov

codecov Bot commented Nov 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.15%. Comparing base (b62a524) to head (4a8c031).

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##           mlip_run_fixes    #1073      +/-   ##
==================================================
+ Coverage           67.12%   67.15%   +0.02%     
==================================================
  Files                 123      123              
  Lines               43706    43772      +66     
  Branches            11161    11175      +14     
==================================================
+ Hits                29338    29393      +55     
+ Misses              11224    11223       -1     
- Partials             3144     3156      +12     
Flag Coverage Δ
functionaltests 67.15% <ø> (+0.02%) ⬆️
unittests 67.15% <ø> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI 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.

🟡 Changes recommended

Critical worker-ticket lifecycle defects can hang runs, trigger concurrent or duplicate execution, and cause stale results to be accepted.

10 open issues
What changed in this PR

Adds persistent ASE workers so ARC can reuse loaded machine-learned potentials across jobs instead of reloading models for every calculation. This provides the worker layer for the follow-on ASE enhancements.

Changes:

  • Introduces a file-spool worker with model caching, heartbeats, ticket tracking, and direct-execution fallback.
  • Adds worker lifecycle tests and isolates finite-difference vibration caches by job.
  • Uses insertion order for optimization troubleshooting and makes local queue-deletion failures nonfatal.
File Description
arc/​settings/​settings.py Adds worker spool configuration through ASE_WORKERS.
arc/​scheduler.py Tracks worker tickets and changes optimization-attempt selection.
arc/​job/​local.py Logs unresolved deletion failures instead of raising.
arc/​job/​adapters/​scripts/​ase_script.py Implements persistent workers, reusable model caches, and job-scoped vibration caches.
arc/​job/​adapters/​ase_test.py Tests ticket handling, worker execution, fallback, cache isolation, and IRC endpoints.
arc/​job/​adapters/​ase_adapter.py Adds asynchronous worker submission and ticket-status handling.

Review effort: Max

self.script_path = os.path.join(os.path.dirname(__file__), 'scripts', 'ase_script.py')
# A live persistent worker runs this calculator's jobs on this machine. They are handed to it as
# local queue jobs: submitted without waiting, and tracked by their ticket IDs (see execute_queue()).
self.worker_spool = self.determine_worker_spool()
self.script_path = os.path.join(os.path.dirname(__file__), 'scripts', 'ase_script.py')
# A live persistent worker runs this calculator's jobs on this machine. They are handed to it as
# local queue jobs: submitted without waiting, and tracked by their ticket IDs (see execute_queue()).
self.worker_spool = self.determine_worker_spool()
reactions=reactions,
rotor_index=rotor_index,
server=server,
server='local' if self.worker_spool else server,
Comment on lines +392 to +393
self.job_id = submit_ticket(self.worker_spool, os.path.abspath(os.path.join(self.local_path, 'input.yml')),
label=self.job_name)
if content.get('started') and content.get('finished'):
self.initial_time = datetime.datetime.fromtimestamp(content['started'])
self.final_time = datetime.datetime.fromtimestamp(content['finished'])
return 'done'
if state == 'queued' and not live_workers(spool) and withdraw_ticket(spool, ticket):
logger.warning(f'No live ASE worker on {spool}; running {self.job_name} directly.')
return self.run_instead_of_worker()
if state == 'running' and content.get('worker') not in live_workers(spool):
Comment on lines +448 to +451
self.initial_time = datetime.datetime.now()
self.run_directly()
self.final_time = datetime.datetime.now()
return 'done'
Comment thread arc/scheduler.py
self.server_job_ids.extend(check_running_jobs_ids())
if specific_server in (None, 'local'):
# Jobs handed to a persistent ASE worker run on this machine, tracked by their ticket IDs.
self.server_job_ids.extend(active_worker_tickets())
Comment thread arc/job/local.py
Comment on lines +170 to +173
# E.g., PBS refuses to delete a job that is already exiting, and qstat still lists it. The job ends on its
# own and the caller has dropped it from its running jobs, so this must not end the ARC run.
logger.error(f'Job {job_id} was scheduled for deletion, but the deletion command has appeared to errored. '
f'The job is still running.')
raise RuntimeError(f'Could not delete job {job_id}')
f'The job is still running; leaving it to finish on its own.')
Comment thread arc/scheduler.py
Comment on lines +4880 to +4882
opt_jobs = list(self.job_dict[label]['opt'].values())
job = opt_jobs[-1] if opt_jobs else None
previous_job = opt_jobs[-2] if len(opt_jobs) > 1 else None

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants