Pseudo Code nb2slurm

In these 4 notebooks: 0_config.ipynb, 1_build.ipynb, 2_submit.ipynb and 3_sync.ipynb we will explain the workings of nb2slurm in a ‘pseudo’ code way. In the montecarlo carlo example these four notebooks are condensed into one. But we do recommend to split them up if possible.

0. Configure your run

This is the only notebook you normally edit. Fill in the cells below, then run it top to bottom. It saves two files in the project root:

  • control_config.json — all your settings, read by 1_build, 2_submit and 3_sync (so they always agree).

  • jobs.json — the nested list of jobs to run.

Re-run this notebook whenever you change a setting.

Project

Name, the notebooks to run in order, and what varies per job.

project_name = "myproject"
notebooks = [
    "notebooks/0_settings.ipynb",  # first notebook: writes settings.json
    "notebooks/1_analysis.ipynb",
    "notebooks/2_more.ipynb",
]
varying = ["country", "region", "scenario"]  # the levels in jobs.json (below)

The jobs to run

A nested dict: each root-to-leaf path is one SLURM job, and the levels line up with varying above. nb2slurm builds the matching output folders and submits one job per leaf.

Format: a dict nests one more level, a list at the bottom means several jobs sharing that parent, and None/[] ends the path. It’s plain JSON, so for small runs type it by hand (below) and for big runs generate it in Python (a comprehension, a CSV, an API query, …) — see the README for the rules. The only thing that matters is that the finished dict lands in jobs.json.

(this is a simplified example)

jobs = {
    "Netherlands": {
        "north": ["green_climate", "climate_as_we_are", "heavy_industrialization"],
        "south": ["green_climate", "climate_as_we_are", "heavy_industrialization"],
    },
    "Germany": {
        "north": ["green_climate", "climate_as_we_are", "heavy_industrialization"],
        "south": ["green_climate", "climate_as_we_are", "heavy_industrialization"],
        "east": ["green_climate", "climate_as_we_are", "heavy_industrialization"],
        "west": ["green_climate", "climate_as_we_are", "heavy_industrialization"],
    },
}

HPC connection

Your cluster login details. The project dir is derived so it can’t drift.

No SSH key yet? nb2slurm.generate_key(key_type="ed25519") creates one and prints the public key to register with your HPC. If the key has a passphrase, ssh-add it (so rsync push/pull work too) or pass passphrase= to SSHConfig below.

username_on_hpc = "me"
hpc_host = "spider.surf.nl"
project_dir_on_hpc = f"/home/{username_on_hpc}/{project_name}"
ssh_key_file = "~/.ssh/id_ed25519"

Resources per job

What each SLURM job asks for, and where outputs go.

time_for_job_to_run = "04:00:00"  # HH:MM:SS wall-clock limit
cpus_per_job = 2
nodes_per_job = 1
output_directory = "output"  # could be "/scratch/me/output"

The time_for_job_to_run or wall-clock time lets the SLURM workload manager know how much time to allocate for your job. cpus_per_job are the number of cpu cores for the job. nodes_per_job are the number of nodes per job and is defaulted to 1. When it is one you request all the cores to be on the same compute node, which consists of 1 or 2 cpus. This is useful for high core count jobs like computational fluid dynamics for example. output_directory will define where the output of your project is stored, the output will follow the jobs structure.

We also provide the user with concurrency of the jobs in jobs_at_once. Meaning that the workload manager will have a number of jobs_at_once running per branch. This could take load of filesystems if needed. If set to 0 all jobs may run at once if there is available hardware.

jobs_at_once = 3  # max running in parallel, 0 means all jobs run at the same time with no concurrency

Data mounts (optional)

rclone mounts should be set up before running. You can provide more if needed in this list as well.

data_mounts = [
    {"remote": "dcache:/climate-data/caravan", "mountpoint": "/scratch/caravan"},
]

Save

Builds the objects from your settings and writes control_config.json and jobs.json. The other notebooks load these — you don’t edit them by hand, but it is possible.

import json
import pathlib
import nb2slurm

env = nb2slurm.Environment(
    name=conda_env_name,
    kernel=kernel_name,
    conda_packages=conda_packages,
    pip_packages=pip_packages,
)
wf = nb2slurm.Workflow(
    name=project_name,
    notebooks=notebooks,
    kernel=kernel_name,
    varying=varying,
    jobs_json="jobs.json",
    resources=dict(nodes=nodes_per_job, cpus=cpus_per_job, time=time_for_job_to_run),
    mounts=data_mounts,
    concurrency=jobs_at_once,
    output_dir=output_directory,
    environment=env,
)
cfg = nb2slurm.SSHConfig(
    host=hpc_host,
    user=username_on_hpc,
    remote_dir=project_dir_on_hpc,
    key_filename=ssh_key_file,
)

pathlib.Path("jobs.json").write_text(json.dumps(jobs, indent=2))
nb2slurm.save_config("control_config.json", workflow=wf, ssh=cfg)
print("saved control_config.json and jobs.json")