Ensemble Launcher¶
Ensemble Launcher is a lightweight Python tool for launching and orchestrating ensembles of tasks across HPC compute nodes.
Instead of submitting one PBS job per task, you request a single allocation and let Ensemble Launcher pack many tasks onto the nodes you already hold. A hierarchical master/worker tree distributes the tasks, which lets it scale from a single node to the whole machine. Tasks in the same ensemble can be heterogeneous (different node counts, process counts, and GPU requirements), can be shell commands, MPI applications, or Python callables, and get their CPU/GPU affinity set for you.
There are two ways to run it:
- Batch mode -- describe all your tasks up front, call
run(), and block until everything finishes. This is the common case for parameter sweeps. - Cluster mode -- start the orchestrator as a long-lived service and submit tasks to it dynamically from a client. Use this when tasks are generated as results come in (e.g. an AI model steering a simulation campaign).
Installation¶
The hpc extra pulls in mpi4py, which is needed to launch MPI tasks and to spawn sub-masters across nodes. See the ALCF docs for more details on using Python on Aurora.
For future sessions, load the frameworks module and re-run the activate line.
Aurora System Configuration¶
Ensemble Launcher needs to know how many CPUs and GPUs each node has so it can pin tasks. A ready-made Aurora configuration is provided:
This reserves cores 0 and 52 for the operating system and exposes the 12 Aurora GPU tiles as separate GPUs. Task GPU affinity is applied through ZE_AFFINITY_MASK, which is already the default gpu_selector.
The list of compute nodes is read automatically from $PBS_NODEFILE, so you do not need to pass Nodes inside a PBS job.
Batch Mode¶
In batch mode, you first describe the ensemble by writing a JSON file. The cmd_template is expanded once per parameter set:
| sweep.json | |
|---|---|
This creates 24 tasks, each using one node with 12 MPI ranks and one GPU per rank. Use "relation": "many-to-many" for a Cartesian product over multiple parameters instead of element-wise pairing.
Submit the driver script with a standard PBS job script:
| submit.sh | |
|---|---|
With two nodes, the 24 tasks above run 2 at a time until the ensemble completes.
Instead of a JSON file you can pass a dictionary of Task objects, where executable is either a shell command string or a Python callable:
Cluster Mode¶
In cluster mode the orchestrator runs as a separate process and writes its ZMQ address to checkpoint_dir. A ClusterClient reads that address, submits tasks, and gets back concurrent.futures.Future objects. Tasks can be submitted at any time until you call stop().
run_cluster.py:
Submit it with the same PBS script as above, replacing python run_sweep.py with python run_cluster.py.
EnsembleLauncher is also a context manager, so stop() can be handled for you:
Command Line Interface¶
The el command runs an ensemble without a Python driver script. Configurations that would be passed as SystemConfig and LauncherConfig objects are supplied as JSON files.
In batch mode it blocks until every task finishes and writes results.json:
For cluster mode, set "cluster": true in the launcher config:
| launcher_cluster.json | |
|---|---|
Background the el start command
el start does not return in cluster mode -- the orchestrator is spawned as a child process and the CLI waits on it. Put it in the background with & so the rest of your job script can run.
el stop sends SIGTERM to the orchestrator (its PID is stored in .el_launcher.pid in the working directory) and it exits gracefully.
Debugging¶
Turn on logging and status reporting through LauncherConfig:
Logs are written to logs/master-*.log and logs/worker-*.log.
To watch GPU usage, SSH into one of your compute nodes in another window and run:
More details: