DisBatch
Dynamic dispatch of a list of command-line tasks, locally or on a cluster. Supports retrying failed tasks, and adding/removing compute resources on-the-fly.
Install / Use
/learn @flatironinstitute/DisBatchREADME
disBatch
Distributed processing of a batch of tasks.
Quickstart
Install with pip:
pip install disbatch
Create a file called Tasks with a list of commands you want to run. These should be Bash commands as one would run on the command line:
myprog arg0 &> myprog_0.log
myprog arg1 &> myprog_1.log
...
myprog argN &> myprog_N.log
This file can have as many tasks (lines) as you like. The ... is just a stand-in and wouldn't literally be in the task file.
Then, to run 5 tasks at a time in parallel on your local machine, run:
disBatch -s localhost:5 Tasks
disBatch will start the first five running concurrently. When one finishes, the next will be started until all are done.
Note that this effectively means that tasks (lines) may run in any order.
To distribute this work on a Slurm cluster instead of locally, run:
sbatch -n 5 disBatch Tasks
You may need to provide additional arguments specific to your cluster to specify a partition, time limit, etc.
The same invocation as a Slurm batch script would look like:
#!/bin/bash
# file: job.sh
#SBATCH -n 5
disBatch Tasks
Submit as usual with sbatch job.sh. disBatch will inspect the environment as see that it is running under Slurm, and use
srun internally to launch persistent task slots.
Overview
One common usage pattern for distributed computing involves processing a long list of commands (aka tasks):
myprog -a 0 -b 0 -c 0
myprog -a 0 -b 0 -c 1
...
myprog -a 9 -b 9 -c 9
One could run this by submitting 1,000 separate jobs to a cluster, but that may present problems for the queuing system and can behave badly if the system is configured to handle jobs in a simple first come, first serve fashion. For short tasks, the job launch overhead may dominate the runtime, too.
One could simplify this by using, e.g., Slurm job arrays, but each job in a job array is an independent Slurm job, so this suffers from the same per-job overheads as if you submitted 1000 independent jobs. Furthermore, if nodes are being allocated exclusively (i.e. the nodes that are allocated to your job are not shared by other jobs), then the job array approach can hugely underutilize the compute resources unless each task is using a full node's worth of resources.
And what if you don't have a cluster available, but do have a collection of networked computers? Or you just want to make use of multiple cores on your own computer?
In any event, when processing such a list of tasks, it is helpful to acquire metadata about the execution of each task: where it ran, how long it took, its exit return code, etc.
disBatch has been designed to support this usage in a simple and portable way, as well as to provide the sort of metadata that can be helpful for debugging and reissuing failed tasks.
It can take as input a file, each of whose lines is a task in the form of a Bash command. For example, the file could consists of the 1000 commands listed above. It launches the tasks one after the other until all specified execution resources are in use. Then as one executing task exits, the next task in the file is launched. This repeats until all the lines in the file have been processed.
Each task is run in a new shell; i.e. all lines are independent of one another.
Here's a more complicated example, demonstrating controlling the execution environment and capturing the output of the tasks:
( cd /path/to/workdir ; source SetupEnv ; myprog -a 0 -b 0 -c 0 ) &> task_0_0_0.log
( cd /path/to/workdir ; source SetupEnv ; myprog -a 0 -b 0 -c 1 ) &> task_0_0_1.log
...
( cd /path/to/workdir ; source SetupEnv ; myprog -a 9 -b 9 -c 8 ) &> task_9_9_8.log
( cd /path/to/workdir ; source SetupEnv ; myprog -a 9 -b 9 -c 9 ) &> task_9_9_9.log
Each line uses standard Bash syntax. Let's break it down:
- the
( ... ) &> task_0_0_0.logcaptures all output (stdout and stderr) from any command in the parentheses and writes it totask_0_0_0.log; cd /path/to/workdirchanges the working directory;source SetupEnvexecutes a script calledSetupEnv, which could contain commands likeexport PATH=...ormodule load ...to set up the environment;myprog -a 0 -b 0 -c 0is the command you want to run.
The semicolons between the last 3 statements are Bash syntax to run a series of commands on the same line.
You can simplify this kind of task file with the #DISBATCH PREFIX and #DISBATCH SUFFIX directives. See the #DISBATCH directives section for full details, but here's how that could look:
#DISBATCH PREFIX ( cd /path/to/workdir ; source SetupEnv ; myprog
#DISBATCH SUFFIX ) &> task_${DISBATCH_TASKID}.log
-a 0 -b 0 -c 0
-a 0 -b 0 -c 1
...
-a 9 -b 9 -c 9
Note that for a simple environment setup, you don't need a source SetupEnv. You can just set an environment variable directly in the task line, as you can in Bash:
export LD_LIBRARY_PATH=/d0/d1/d2:$LD_LIBRARY_PATH ; rest ; of ; command ; sequence
For more complex set ups, command sequences and input/output redirection requirements, you could place everything in a small shell script with appropriate arguments for the parts that vary from task to task, say RunMyprog.sh:
#!/bin/bash
id=$1
shift
cd /path/to/workdir
module purge
module load gcc openblas python3
export LD_LIBRARY_PATH=/d0/d1/d2:$LD_LIBRARY_PATH
myProg "$@" > results/${id}.out 2> logs/${id}.log
The task file would then contain:
./RunMyprog.sh 0_0_0 -a 0 -b 0 -c 0
./RunMyprog.sh 0_0_1 -a 0 -b 0 -c 1
...
./RunMyprog.sh 9_9_8 -a 9 -b 9 -c 8
./RunMyprog.sh 9_9_9 -a 9 -b 9 -c 9
See #DISBATCH directives for more ways to simplify task lines. disBatch also sets some environment variables that can be used in your commands as arguments or to generate task-specifc file names:
DISBATCH_JOBID: A name disBatch creates that should be unique to the jobDISBATCH_NAMETASKS: The basename of the task fileDISBATCH_REPEAT_INDEX: See the repeat construct in #DISBATCH directivesDISBATCH_STREAM_INDEX: The 1-based line number of the line from the task file that generated the task "DISBATCH_TASKID: 0-based sequential counter value that uniquely identifies each task
Appending _ZP to any of the last three will produce a 0-padded value (to six places). If these variables are used to create file names, 0-padding will result in files names that sort correctly.
Once you have created the task file, running disBatch is straightforward. For example, working with a cluster managed by Slurm, all that needs to be done is to submit a job like the following:
sbatch -n 20 -c 4 disBatch TaskFileName
This particular invocation will allocate sufficient resources to process 20 tasks at a time, each of which needs 4 cores. disBatch will use environment variables initialized by Slurm to determine the execution resources to use for the run. This invocation assumes an appropriately installed disBatch is in your PATH, see installation for details.
disBatch also allows the pool of execution resources to be increased or decreased during the course of a run:
sbatch -n 10 -c 4 ./TaskFileName_dbUtil.sh
will add enough resources to run 10 more tasks concurrently. TaskFileName_dbUtl.sh is a utility script created by disBatch when the run starts (the actual name is a little more complex, see startup).
Various log files will be created as the run unfolds:
TaskFileName_*_status.txt: status of every task (details below).*elides a unique identifier disBatch creates to distinguish one run from another. This is the most important output file and we recommend checking it after every run.TaskFileName_*_[context|driver|engine].log: The disBatch driver log file contains details mostly of interest in case of a problem with disBatch itself. (The driver log file name can be changed with-l). It can generally be ignored by end users (but keep it around in the event that something did go wrong—it will aid debugging). The*_[context|engine].logfiles contain similar information for the disBatch components that manage execution resources.disBatch_*_kvsinfo.txt: TCP address of invoked KVS server if any (for additional advanced status monitoring)
[!TIP] The
*_status.txtfile is the most important disBatch output file and we recommend checking it after every run.
While disBatch is a Python 3 application, it can run tasks from any language environment—anything you can run from a shell can be run as a task.
Status file
The status file is the most important disBatch output file and we recommend checking it after every run. The filename is TaskFileName_*_status.txt. It contains tab-delimited lines of the form:
314 315 -1 worker032 8016 0 10.0486528873 1458660919.78 1458660929.83 0 "" 0 "" cd /path/to/workdir ; myprog -a 3 -b 1 -c 4 > task_3_1_4.log 2>&1
These fields are:
- Flags: The first field, blank in this case, may contain
E,O,R,B, orSflags. Each program/task should be invoked in such a way that standard error and standard output end up in appropriate files. If that is not the caseEorOflags will be raised.Rindicates that the task returned a non-zero exit code.Bindicates a barrier.Sindicates the job was skipped (this may happen during "resume" runs). - Task ID: The
314is the 0-based index of the task (starting from the beginning of the task file, incremented for each task, including repeats). - Line number: The
315is the 1-based line from the task file. Blank lines, comments, directives
