Execution: workstation & HPC¶
Where walkers run is a pure configuration choice via the execution section.
The science is identical across backends.
Local (multi-GPU workstation)¶
The default. Walkers run as subprocesses across CPU worker slots or GPU device slots, with GPU device ids assigned dynamically as workers free up.
execution:
backend: local
engine:
platform_name: CUDA
gpu_ids: [0, 1, 2, 3] # optional; auto-detected otherwise
spawning:
max_workers: 4 # concurrent walkers
Each walker is pinned to a specific GPU slot: OpenMM uses the CUDA
DeviceIndex platform property, while the GROMACS and Amber subprocess engines
set CUDA_VISIBLE_DEVICES. With more walkers than slots, walkers are streamed
onto slots as they free up.
Restricted GPU visibility
If you pre-set CUDA_VISIBLE_DEVICES to a non-contiguous subset before
launching the local backend, OpenMM's DeviceIndex is numbered relative
to the visible set. Prefer one worker per fully-visible GPU, or let a
scheduler backend do the binding (below).
SLURM¶
Each iteration's walkers are submitted as one array job
(#SBATCH --array=0-N). Trails-MD renders the script, submits with
sbatch --parsable, polls squeue, and collects per-walker result markers from
the shared filesystem. Failed or missing walkers are resubmitted up to
max_retries times.
execution:
backend: slurm
partition: gpu
account: my_alloc
walltime: "02:00:00"
cpus_per_task: 8
gpus_per_task: 1
memory: "16G"
max_retries: 2
poll_interval: 30
max_in_flight: 64 # cap concurrent array elements (SLURM `%N`)
marker_grace: 30 # tolerate shared-FS metadata lag (seconds)
wait_timeout: null # ceiling on waiting for one array job; null = derive from walltime
module_loads:
- "module load cuda/12.2"
- "module load openmm"
extra_directives:
- "#SBATCH --qos=normal"
Robustness at scale¶
max_in_flightcaps how many array elements run at once (rendered as--array=0-N%max_in_flight), so a batch of hundreds/thousands of walkers does not flood the scheduler or hit submit-rate limits.wait_timeoutbounds how long the driver waits for one iteration's array job before cancelling it (scancel/qdel) and treating unfinished walkers as failed — a held or unschedulable job cannot hang the campaign. Leftnull, a generous ceiling is derived fromwalltime.marker_gracekeeps re-checking result markers after the job leaves the queue, absorbing NFS/Lustre/GPFS metadata lag so genuinely-successful walkers are not misreported as failed.
Array-size limits
One array element is submitted per walker. SLURM's MaxArraySize (default
1001) and PBS's max_array_size cap a single array; set max_array_size to
split a larger batch into sequential sub-arrays. For very large fan-out see
HPC scaling.
PBS (OpenPBS / PBS Pro)¶
The same model with PBS array jobs (#PBS -J 0-N, qsub, qstat). This backend
targets OpenPBS / PBS Pro (#PBS -J, PBS_ARRAY_INDEX); classic Torque
(#PBS -t, PBS_ARRAYID) is not currently supported. On PBS the submit
environment is not exported by default — reconstruct it via module_loads, or
add "#PBS -V" to extra_directives:
execution:
backend: pbs
partition: gpuq # PBS queue
walltime: "02:00:00"
cpus_per_task: 8
gpus_per_task: 1
memory: "16gb"
module_loads:
- "module load openmm"
How it works¶
- Each walker is a self-contained task pickled to the iteration's
_jobs/directory. An array element loads its task and runspython -m trails_md.execution.run_task, writing a JSON result marker. - Completion is filesystem-driven (result markers), not scheduler accounting — robust to flaky queue state. A walker that dies without a marker is treated as failed and resubmitted.
- Requirements: a shared filesystem visible to compute nodes, and the
trails-mdpackage importable in the job environment (hencemodule_loads/ activating your conda env in the job, e.g. viaextra_directives).
Choosing resources¶
cpus_per_task / gpus_per_task / memory are per walker (one array
element). For CPU-only HPC, set gpus_per_task: 0 and an OpenMM CPU
platform (or a CPU GROMACS/Amber build) and scale out across many array tasks.
Choosing your MD engine¶
The engine is engine.md_engine (openmm | gromacs | amber). OpenMM is
used natively through its Python API — no external executable, just the openmm
package importable in the job environment. GROMACS and Amber are external
programs, so you select them by making the executable available (module or path)
and pointing the config at it:
engine:
md_engine: gromacs
gromacs_executable: gmx # a name on PATH, or an absolute path
gromacs_include_dir: /opt/gromacs/share/gromacs/top # holds the *.ff force-field dirs
engine:
md_engine: amber
amber_executable: pmemd.cuda # pmemd | pmemd.cuda | sander; PATH or absolute path
On a scheduler backend the array jobs run in fresh shells, so the engine's
module must be loaded there too: add it to execution.module_loads (e.g.
"module load gromacs/2024"), which is replayed inside every walker job. Giving
an absolute *_executable path avoids depending on a module to set PATH.
The HPC test suite exercises all three engines this way — see
hpc_tests/RUNBOOK.md.
Fault tolerance for long campaigns¶
By default (min_success_fraction: 1.0) any walker failure aborts the iteration
so nothing proceeds on partial data. For long multi-day HPC campaigns where an
occasional transient node/GPU/integrator failure is expected, set a threshold
below 1.0:
min_success_fraction: 0.9 # continue if >= 90% of walkers succeed
Failed walkers (and their lineage parents) are dropped and sampling continues
with the survivors; the spawner restores the full walker count next iteration.
Scheduler backends additionally resubmit failed walkers up to max_retries
times before they count as failures.
Testing on your cluster¶
Before a production campaign, validate the backend end-to-end with the
ready-made suite in hpc_tests/
(SLURM + PBS, CPU-only + GPU). It runs preflight → --check → a tiny run →
result validation and writes structured JSON, with a debugging playbook
(hpc_tests/DEBUGGING.md) keyed to each failure mode.
Adding a scheduler¶
Subclass SchedulerBackend (trails_md/execution/scheduler.py), implement
the directive/submit/poll hooks, and call
ExecutionBackendFactory.register(...). The submit → poll → collect → retry
machinery is inherited. For the persistent-worker-pool model used by mature
tools like WESTPA, see HPC scaling.