Extend functionality by subclassing Executor
mainExecutor classes to gain precise control over the job submission lifecycle. For example, you can implement an executor that automatically creates a unique logging folder for every job submitted.repository·main·Indexed 23 days ago
https://github.com/facebookincubator/submititA lightweight Python 3.8+ tool for submitting Python functions for computation within a Slurm cluster. It provides a concurrent.futures-like API to switch between local and Slurm execution, featuring AutoExecutor for job management, map_array for Slurm job arrays, and support for checkpointing to handle job preemption or timeouts.
Executor classes to gain precise control over the job submission lifecycle. For example, you can implement an executor that automatically creates a unique logging folder for every job submitted.You can run a single job that spans multiple tasks across one or several nodes using tasks_per_node and nodes in update_parameters().
submitit.JobEnvironment() inside your function to access cluster-agnostic metadata like num_tasks, local_rank, node, and global_rank. This is typically used to chunk inputs so each task processes a subset of data.job.task(rank) to access a specific task as if it were a standalone Job object (e.g., to get its specific stdout() or result()).import submitit
from math import ceil
def my_func(inputs):
job_env = submitit.JobEnvironment()
# Use job_env to chunk inputs
num_items_per_task = int(ceil(len(inputs) / job_env.num_tasks))
r = job_env.local_rank
task_chunk = inputs[r * num_items_per_task: (r + 1) * num_items_per_task]
return process(task_chunk)
executor = submitit.AutoExecutor(folder="log_test")
# 3 tasks per node * 2 nodes = 6 total tasks
executor.update_parameters(tasks_per_node=3, nodes=2, timeout_min=1, slurm_partition="dev")
job = executor.submit(my_func, my_large_input_list)
# Access specific task results
print(job.task(2).result())
# Access concatenated stdout of all tasks
print(job.stdout())The DelayedSubmission class encapsulates all information required to execute a job: the function, positional arguments (args), and keyword arguments (kwargs).
While typically handled internally by the Executor.submit method, you may interact with this object if you are implementing custom checkpointing. It can be instantiated similarly to how submit is used:
from submitit import DelayedSubmission
delayed = DelayedSubmission(my_func, 1, 2, kwarg=10)When you submit a function and its arguments, submitit pickles the function and arguments. A batch file is then executed on the cluster, which loads the pickled object, computes the function with the provided arguments, and pickles the output into a new file. Once this file is available, your Job instance can recover it.
Important Requirements:
submitit) are installed in that environment.Job instance. If it is not catchable, you must inspect the log files.Generated Files: For each job, the following files are typically created:
<job_id>_submitted.pkl: The task file.<job_id>_result.pkl: The output file.batchfile_<uuid>.sh: The batch file (where <uuid> is generated by submitit).<job_id>_<task_id>_log.out: The stdout log file.<job_id>_<task_id>_log.err: The stderr log file.Note: <job_id> refers to the ID from the scheduler (e.g., Slurm).
While both provide a concurrent.futures style API, they serve different purposes:
dask.distributed: Distributes tasks to a pool of workers. It is better suited for submitting many small tasks to a cluster without overloading the scheduler.submitit: Submits jobs directly to the cluster (e.g., as individual Slurm jobs). It is a lower-level interface that provides more direct control over individual jobs, including direct access to stdout/stderr and advanced features like checkpointing during preemption or timeouts.Use submitit when: You need direct control over Slurm job parameters, individual log access, or checkpointing capabilities. Avoid using it for a high volume of very small tasks.
Checkpointing allows submitit to requeue a job (after preemption or timeout) by editing the submitted task according to the current state of the computation.
To enable checkpointing, you must submit a callable (an instance of a class with a __call__ method) rather than a standard function. This is because a standard function's state cannot be accessed during requeueing.
When a job is requeued, submitit checks if the callable has a __submitit_checkpoint__ or checkpoint method. If found, it calls this method with the same arguments used in the original __call__ method. The checkpoint method is responsible for preparing the new submission and must return a submitit.helpers.DelayedSubmission object (which behaves like executor.submit) or None if the job should not be requeued.
Cluster Requirement: For preemptions to be recognized, your Slurm cluster must be configured with SlurmctldParameters=preempt_send_user_signal.
To submit many jobs efficiently, use executor.map_array. This is preferred over individual submit calls because it submits all jobs in a single Slurm call, avoiding scheduler flooding and allowing you to control parallelism.
executor.update_parameters(slurm_array_parallelism=N) to cap how many jobs run concurrently.<array job id>_<array task id> (e.g., 17390420_15).map_array creates one pickle per job. If your function uses large objects (like PyTorch models), serialize them once and pass the file path instead of the object itself.Batch Context Manager: You can convert a standard for loop of submit calls into a job array by wrapping the loop in with executor.batch():. The jobs are only actually submitted when exiting the context.
a = [1, 2, 3, 4]
b = [10, 20, 30, 40]
executor = submitit.AutoExecutor(folder=log_folder)
# Limit parallelism to 2 jobs at once
executor.update_parameters(slurm_array_parallelism=2)
# Submit as an array
jobs = executor.map_array(add, a, b)
# Alternatively, using the batch context manager:
jobs = []
with executor.batch():
for arg in whatever:
job = executor.submit(myfunc, arg)
jobs.append(job)
# Jobs are submitted here, upon exiting the contextTo speed up optimization by running multiple function evaluations concurrently, you should use an executor compatible with submitit (like AutoExecutor).
num_workers in the optimizer constructor to specify how many concurrent evaluations you intend to run.minimize method with an executor: Pass your submitit executor instance to the minimize method. nevergrad will automatically handle submitting jobs to the cluster, ensuring no more than num_workers jobs are running in parallel.Note: This pattern is best suited for evaluations that take at least tens of minutes. Using it for very short tasks may overload the cluster with job submission overhead.
If you simply want to resubmit the current callable in its current state with the same initial arguments, you can derive your class from submitit.helpers.Checkpointable. This helper implements a generic checkpoint method that automatically returns a DelayedSubmission of the current instance with its current arguments.
import submitit
class MyTask(submitit.helpers.Checkpointable):
def __call__(self, arg1, arg2):
# your logic here
passTo ensure reliable execution and clear error reporting, follow these best practices for function definition:
sys.path.append for imports: Modules added to the path via sys.path.append before submission often cannot be correctly pickled. If you must use custom paths, use a 'lazy import' pattern by performing the sys.path.append and the import inside the function being submitted.threading.Lock) as default arguments in the functions you intend to submit.To switch between executing on Slurm and another cluster, you can implement a custom plugin. A plugin must provide implementations for four core classes: Executor, Job, InfoWatcher, and JobEnvironment.
Key responsibilities for these classes include:
Executor.submit: Creates a Job from a function, managing log files and the Python executable.Executor._convert_parameters: Translates standardized submitit parameters into cluster-specific ones.InfoWatcher.get_info: Retrieves the current state of a job (e.g., pending, running) using a job ID.JobEnvironment: Manages signal handlers and requeuing logic to ensure the job behaves correctly within the target cluster environment.The submitit.AutoExecutor class is the primary interface for submitting Python functions to a cluster (like Slurm) or running them locally. It follows the concurrent.futures.Executor API.
Key features:
folder where job information, logs, and results are stored. Use %j in the path to automatically include the job ID at runtime.update_parameters() to configure job settings. Cluster-specific options must be prefixed with the cluster name (e.g., slurm_partition for Slurm).job.result() waits for completion and returns the output. If the job fails, it raises a FailedJobError containing the traceback.import submitit
def add(a, b):
return a + b
log_folder = "log_test/%j"
executor = submitit.AutoExecutor(folder=log_folder)
# Configure parameters (e.g., timeout and Slurm partition)
executor.update_parameters(timeout_min=4, slurm_partition="dev")
# Submit the job
job = executor.submit(add, 5, 7)
print(job.job_id)
# Get the result
output = job.result()
assert output == 12