Humboldt-Universität zu Berlin - Mathematisch-Naturwissenschaftliche Fakultät - Rechnerbetriebsgruppe

Slurm

Einführung

Auf den Gruenau-Compute-Servern und den PC-Pools ist ein Queueing-System installiert, damit möglichst viele Berechnungen sinnvoll und, ohne dass andere Berechnungen gestört werden, gleichzeitig laufen können. Als Software wird Slurm verwendet. Im Folgendem wird die Verwendung von Slurm beschrieben.

Mit einem Queue-System kann man rechenintensivere Jobs in eine Warteschlange (englisch Queues) stellen, so dass diese ausgeführt werden, sobald genug Ressourcen zur Verfügung stehen.

Jeder PC ist dabei ein Node auf dem ein sogenannter Job, also ein oder mehrere Programme, ausgeführt werden. Es kann auch ein Job parallel auf mehreren Nodes laufen. Jeder Node ist grundsätzlich eine Ressource, bestehend aus einer Anzahl von CPU-Kernen, einer bestimmten Menge an Arbeitsspeicher und ggf. GPUs.

Um einen Job auf einem oder mehreren Nodes auszuführen, muss man sich nur auf einem der beteiligten Geräte einloggen. Dies funktioniert sowohl lokal im PC-Pool als auch über ssh.

 

Befehle (Auswahl)

Slurm bietet eine Vielzahl von Befehlen, wovon für die meisten Nutzer die Folgenden am nützlichsten sein dürften:

 

Informationen über Nodes:
  • sinfo -N -l listet die Nodes und deren Status. Hier sieht man auch direkt die verschiedenen Arten von Computing-Nodes und deren Verfügbarkeit.
$ sinfo -l
PARTITION   AVAIL  TIMELIMIT   JOB_SIZE ROOT OVERSUBS     GROUPS  NODES       STATE NODELIST
interactive    up    2:00:00        1-2   no       NO        all     57        idle adlershof,alex,bernau,britz,buch,buckow,chekov,dahlem,dax,dukat,erkner,forst,frankfurt,garak,gatow,gruenau[1-2,5-10],guben,karow,kes,kira,kudamm,lankwitz,marzahn,mitte,nauen,nog,odo,pankow,picard,pille,potsdam,prenzlau,quark,rudow,scotty,seelow,sisko,spandau,staaken,steglitz,sulu,tegel,templin,treptow,troi,uhura,wandlitz,wannsee,wedding,wildau
std*          up 4-00:00:00       1-16   no       NO        all     57        idle adlershof,alex,bernau,britz,buch,buckow,chekov,dahlem,dax,dukat,erkner,forst,frankfurt,garak,gatow,gruenau[1-2,5-10],guben,karow,kes,kira,kudamm,lankwitz,marzahn,mitte,nauen,nog,odo,pankow,picard,pille,potsdam,prenzlau,quark,rudow,scotty,seelow,sisko,spandau,staaken,steglitz,sulu,tegel,templin,treptow,troi,uhura,wandlitz,wannsee,wedding,wildau
gpu            up 4-00:00:00       1-16   no       NO        all     37        idle adlershof,alex,bernau,britz,buch,buckow,dahlem,erkner,forst,frankfurt,gatow,gruenau[1-2,9-10],guben,karow,kudamm,lankwitz,marzahn,mitte,nauen,pankow,potsdam,prenzlau,rudow,seelow,spandau,staaken,steglitz,tegel,templin,treptow,wandlitz,wannsee,wedding,wildau
gruenau        up 5-00:00:00        1-2   no       NO        all      8        idle gruenau[1-2,5-10]
pool           up 4-00:00:00       1-16   no       NO        all     49        idle adlershof,alex,bernau,britz,buch,buckow,chekov,dahlem,dax,dukat,erkner,forst,frankfurt,garak,gatow,guben,karow,kes,kira,kudamm,lankwitz,marzahn,mitte,nauen,nog,odo,pankow,picard,pille,potsdam,prenzlau,quark,rudow,scotty,seelow,sisko,spandau,staaken,steglitz,sulu,tegel,templin,treptow,troi,uhura,wandlitz,wannsee,wedding,wildau
  • scontrol show node [NODENAME] zeigt eine sehr detaillierte Übersicht aller Nodes bzw. eines einzelnen Nodes an. Hier sieht man alle Features, die ein Node anbietet. Zudem kann man auch die aktuelle Auslastung sehen.
$ scontrol show node adlershof
NodeName=adlershof Arch=x86_64 CoresPerSocket=4
   CPUAlloc=0 CPUTot=8 CPULoad=0.00
   AvailableFeatures=intel,avx2,skylake
   ActiveFeatures=intel,avx2,skylake
...

 

Informationen über das Submitten von Jobs:
  • sbatch JOBSCRIPT stellt ein Jobscript in die Warteschlange
  • srun PARAMETER lässt einen Job mit Parametern ad-hoc laufen. Dies sollte nur als Ersatz zu sbatch oder als Test-Command gesehen werden. Beispiele für srun / sbatch Befehle finden sich weiter unten auf der Seite.

 

Informationen über laufende Jobs:
  • squeue zeigt den Inhalt Warteschlangen (Queues) an
  • scontrol show job JOBNUMBER zeigt Informationen über einen bestimmten Job an
  • scancel JOBNUMBER bricht einen bestimmten Job ab


Weitere nützliche Befehle und Parameter finden sich im Slurm Cheat Sheet.

 

Partitionen

Je nach Anforderung des Programms stehen unterschiedliche Warteschlangen (von Slurm als Partitionen bezeichnet) zur Verfügung.

Die Standard-Warteschlange über alle verfügbaren Rechner (Pool + Grünau-Server) nennt sich std. Immer wenn in einer Job-Beschreibung keine Partitiion explizit genannt wird, dann wird std automatisch ausgewählt.

Des Weiteren existiert eine Warteschlange interactive, die sowohl im Zeitlimit als auch in der Anzahl der gleichzeitig nutzbaren Nodes beschränkt ist. Diese Warteschlange besitzt beim Abarbeiten der Jobs eine höhere Priorität und bietet sich damit für interaktive Testruns oder Konfigurationsaufgaben an.

Hinweis: Alle Nodes, die Teil des Pools sind, werden tagsüber (9-17 Uhr) nur zum Teil von Slurm ausgelastet. Für Compute-Nodes gilt diese Einschränkung nicht.

$ srun --partiton=interactive -n 1 --pty- bash -i

Die Warteschlangen lassen sich jeweils auch filtern, indem bestimmte Ressourcen (wie AVX512, GPU, ...) als Bedingung mit angegeben werden. In den folgenden Beispielen wird einmal ein Node mit einer GPU, einmal ein Node mit dem Feature AVX512 angefordert:

$ srun -n 1 --gres=gpu:1 ...
$ srun -n 1 -C avx512 ...

Beschreibung der Partitionen:

  1. std: Default-Partition. Wird benutzt, wenn keine Partition im Skript angegeben wird. Hier sind alle Nodes enthalten.
  2. interactive: Partition zum Testen von Jobs. Hier sind ausschließlich interaktive Jobs (mittels srun + passenden Parametern) erlaubt. Erlaubte Zeit sind max. 2h.
  3. gpu: GPU-Partition. Alle Nodes besitzen mind. 1 GPU. Um tatsächlich auch eine GPU anzufordern, muss dies mittels gres angegeben werden. Die kann die Einschränkung auf ein Modell, Treiber, Speicher oder eine Anzahl von GPUs sein. Jobs hier haben eine höhere Priorität für GPU-Programme.
  4. pool: Pool-Partition. Hier sind alle Pool-Rechner drin, ansonsten analog zu defq.
  5. gruenau: Grünau-Partition. Hier sind alle Grünau-Rechner mit drin, ansonsten analog zu defq. Es können maximal zwei Grünaus gleichzeitig allokiert werden.

 

Nutzung von sbatch

Mittels sbatch lassen sich vordefinierte Jobskripte abschicken. Die gesamte Konfiguration des Jobs und die zu bearbeitenden Befehle werden hierbei ausschließlich im Skript beschrieben. Typischerweise werden Jobskripte als Shell-Skripte geschrieben, sodass die erste Zeile wie folgt aussehen sollte:

#!/bin/bash

Im Folgenden werden dann Parameter eingefügt, die die Konfiguration betreffen. Hierbei können auch Konditionen und Abhängigkeiten definiert. Jede Konfigurationszeile beginnt mit dem Magicword #SBATCH. Beispiel:

### Weise Slurm an, vier Knoten zu allokieren
#SBATCH --nodes=4 

Bei erfolgreicher Ausführung gibt Slurm die Job-Nummer zurück:

$ sbatch job.slurm
Submitted batch job 67109096

squeue --jobs=<ID> gibt weitere Informationen über den Job innerhalb der Warteschlange zurück. Wird kein Output-Parameter festgelegt, erstellt Slurm im Ausführungsordner eine Ausgabedatei slurm_<JOB-ID>.out.

Wird eine Kombination von Ressourcen-Anforderungen nicht von der gewählten Partition unterstützt, gibt Slurm beim ausführen von sbatch eine entsprechende Fehlermeldung zurück.

Wichtige Parameter:
Parameter Funktion
--job-name=<name> Jobname. Wenn keiner angegeben wird, erzeugt Slurm selbst einen Namen.
--output=<path> Ausgabepfad, sowohl für Ergebnisse als auch Fehler. Wenn kein Pfad angegeben wird, landen beide Ausgaben im gleichen Ordner wie das Skript.
--time=<runlimit> Runtime-Limit in hours:min:sec. Sobald die Zeit abgelaufen ist, wird der Job automatisch gekillt und die Ressourcen wieder freigegeben.
--mem=<memlimit> Arbeitsspeicher, der pro Node allokiert wird. Dies ist besonders hilfreich, da meist mehrere Jobs parallel auf einem Node laufen. Siehe auch die Hinweise zu Best-Practices.
--nodes=<# of nodes> Anzahl an Nodes, auf denen der Job laufen soll. Dies kann sowohl bedeuten, dass der gleiche Job mehrfach oder das per paralleler Programmierung ein Job auf mehreren Nodes laufen soll.
--partition=<partition> Gibt die Partition an, auf der der Job laufen soll. Wenn keine Partition angegeben wird, wird die Default-Partition benutzt.
--gres=<gres>

Reserviert generische Hardware-Ressourcen.

--constraint=<constraint>

Reserviert Nodes mit bestimmten Features.


Parallel Programming (Open MP)
 
--cpus-per-task=<num_threads> Anzahl der Threads, die einem Task zur Verfügung stehen. Sind z.B. 4 CPU-Kerne (ohne Hyperthreads) auf einem Knoten verfügbar und soll ein Task berechnet werden, wäre entsprechend cpus-per-task=4
--ntasks-per-core=<num_hyperthreads> Anzahl der Hyperthreads pro CPU-Core. Jeder Wert >1 aktiviert Hyperthreading (wenn verfügbar). Hinweis: Manche Prozessoren im Pool unterstützen kein Hyperthreading.
--ntasks-per-node=1 Empfohlene Einstellung für Open MP (ohne MPI).

Parallel Programming (MPI)
 
--ntasks-per-node=<num_procs> Anzahl der Tasks (Prozesse) pro Knoten. Im Fall von MPI-only parallelen Programmierung sollte diese Zahl der Anzahl der echten CPU-Kerne (ohne Hyperthreading) auf einem Node entsprechen.
--ntasks-per-core=1 Empfohlene Einstellung für MPI (ohne Open MP).
--cpus-per-task=1 Empfohlene Einstellung für MPI (ohne Open MP).

 

 

Beispielskripte (sbatch)

Hello World (Print hostname)

hello_v1.sh: Vier Nodes geben jeweils 1x ihren Hostnamen zurück

#!/bin/bash

# Job name
#SBATCH --job-name=hello-slurm
# Number of Nodes
#SBATCH --nodes=4
# Number of processes per Node
#SBATCH --ntasks-per-node=1
# Number of CPU-cores per task
#SBATCH --cpus-per-task=1

srun hostname

 

Output:

adlershof
alex
britz
buch

 

hello_v2.sh: Zwei Nodes geben jeweils 2x ihren Hostnamen zurück

#!/bin/bash

# Job name
#SBATCH --job-name=hello-slurm
# Number of Nodes
#SBATCH --nodes=2
# Number of processes per Node
#SBATCH --ntasks-per-node=2
# Number of CPU-cores per task
#SBATCH --cpus-per-task=1

srun hostname

 

Output:

adlershof
adlershof
alex
alex

 

Parallel Programming (OpenMP)

openmp.sh: Hier wird ein Programm mit vier Threads auf einem Node ausgeführt. Hierzu wird zunächst die Anzahl der angefordeten CPU-Kerne (ohne Hyperthreads) auf vier gesetzt. Diese Zahl wird dann an OpenMP weitergereicht.

#!/bin/bash

# Job name
#SBATCH --job-name=openmp-slurm
# Number of Nodes
#SBATCH --nodes=1
# Number of processes per Node
#SBATCH --ntasks-per-node=1
# Number of CPU-cores per task
#SBATCH --cpus-per-task=4
# Disable Hyperthreads
#SBATCH --ntasks-per-core=1

export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK}

srun ./my_openmp_program

 

Parallel Programming (MPI)

hello_mpi.sh: Ähnlich wie im Beispiel Hello World geben hier alle beteiligten Node eine Ausgabe zurück. Der Code für dieses Beispiel befindet sich hier. Die Kommunikation und Synchronisation geschieht hier allerdings über MPI. srun bietet mehrere Protokolle für die Übertragung der MPI-Daten an. Eine Liste der unterstützten Protokolle erhält man mittels folgendem Befehl:

$ srun --mpi=list
srun: MPI types are...
srun: none
srun: pmix_v3
srun: pmix
srun: pmi2

Zusätzlich zum Protokoll muss noch eine MPI-Implementierung (siehe Abschnitt mpi-selector) gewählt werden. Nicht alle MPI-Implementierungen unterstützen jedes Übertragungsprotokoll. Eine gute Übersicht über verfügbare Kombinationen und Best-Practices finden sich hier.

 

Folgendes Skript startet auf 2 Nodes jeweils vier Prozesse, welche über pmix_v3 miteinander kommunizieren. Der Code wurde vorher mittels OpenMPI 4 kompiliert: mpic++ mpi_hello.cpp -o mpi_hello

 

!/bin/bash

# Job Name
#SBATCH --job-name=mpi-hello
# Number of Nodes
#SBATCH --nodes=2
# Number of processes per Node
#SBATCH --ntasks-per-node=4
# Number of CPU-cores per task
#SBATCH --cpus-per-task=1

module load gnu-openmpi/4.0.5
# Kompiliert mit OpenMPI 4 srun --mpi=pmix_v3 mpi_hello

 

Output:

Hello world from processor gatow, rank 2 out of 8 processors
Hello world from processor gatow, rank 3 out of 8 processors
Hello world from processor gatow, rank 0 out of 8 processors
Hello world from processor gatow, rank 1 out of 8 processors
Hello world from processor karow, rank 4 out of 8 processors
Hello world from processor karow, rank 5 out of 8 processors
Hello world from processor karow, rank 6 out of 8 processors
Hello world from processor karow, rank 7 out of 8 processors

 

Mixed Parallel Programming (OpenMP + MPI)

hello_hybrid.sh: Es gibt auch die Möglichkeit OpenMP und MPI miteinander zu kombinieren. Jeder gestartete MPI-Prozess kann dann mehrere Threads auf mehreren CPU-Cores starten. Der Code für dieses Beispiel befindet sich hier. Das folgende Slurm-Skript startet insgesamt vier Prozesse auf 2 Nodes, welche wiederrum jeweils 2 Threads starten. Der Code wurde vorher mittels OpenMPI 4 kompiliert: mpic++ -fopenmp hybrid_hello.cpp -o hybrid_hello

 

!/bin/bash

# Job Name
#SBATCH --job-name=hybrid-hello
# Number of Nodes
#SBATCH --nodes=2
# Number of processes per Node
#SBATCH --ntasks-per-node=2
# Number of tasks in total
#SBATCH --ntasks=4
# Number of CPU-cores per task
#SBATCH --cpus-per-task=2

export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK}

module load gnu-openmpi/4.0.5 # Kompiliert mit OpenMPI 4 srun --mpi=pmix_v3 hybrid_hello

 

Output:

Hello from thread 0 of 2 in rank 0 of 4 on gatow
Hello from thread 1 of 2 in rank 0 of 4 on gatow
Hello from thread 1 of 2 in rank 1 of 4 on gatow
Hello from thread 0 of 2 in rank 1 of 4 on gatow
Hello from thread 1 of 2 in rank 2 of 4 on karow
Hello from thread 0 of 2 in rank 2 of 4 on karow
Hello from thread 1 of 2 in rank 3 of 4 on karow
Hello from thread 0 of 2 in rank 3 of 4 on karow

 

GPU Programming (Tensorflow)

tensorflow_gpu.sh: Um mind. eine GPU nutzen zu können, muss in Slurm mittels gres eine passende Ressource angefordert werden. Mögliche Anforderungen können generisch gewählt sein, wie eine bestimmte Mindestanzahl an GPU-Karten (--gres:gpu:2) oder ein bestimmtes CUDA-Computing Level (--constraint=cu75). Alternativ kann auch eine bestimmte GPU angefordert werden. Der Code für dieses Beispiel befindet sich hier. Einen Überblick über alle verfügbaren Modelle und deren gres-Bezeichnungen bzw. Vielfachheit finden sich auf der Übersichtsseite zu GPU-Servern.

#!/bin/bash

# Job Name
#SBATCH --job-name=tensorflow-gpu
# Number of Nodes
#SBATCH --nodes=1
# Set the GPU-Partition (opt. but recommended)
#SBATCH --partition=gpu
# Allocate node with certain GPU
#SBATCH --gres=gpu:gtx745

module load cuda

python mnist_classify.py

 

Output (trunc.):

2021-03-29 12:20:10.976419: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1304] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 3591 MB memory) -> physical GPU (device: 0, name: GeForce GTX 745, pci bus id: 0000:01:00.0, compute capability: 5.0)
...
Train on 60000 samples
Epoch 1/10
...
10000/10000 - 0s - loss: 1.4880 - acc: 0.9741
('\nTest accuracy:', 0.9741)

 

Ordnerstruktur

Der Pfad, in dem sowohl das Jobskript als auch das ausführende Programm liegt, muss auf allen Nodes unter dem gleichen Pfad verfügbar sein. Das Programm muss daher im System installiert oder Teil eines globalen Filesystem sein (siehe Übersicht File-Server). Für Slurm-Tasks mit eigens kompilierten Programmen und größeren Input/Output-Dateien empfiehlt sich /glusterfs/dfs-gfs-dist. Dieser Ordner ist auf allen Nodes (sowohl Pool als auch Compute) verfügbar. Zur besseren Übersicht sollte ein Unterordner mit dem eigenen Nutzernamen erstellt werden. Per Default ist der Ordner nur zugänglich für den eigenen Nutzer.

cd /glusterfs/dfs-gfs-dist
mkdir brandtfa
ls -la
> drwx------    2 brandtfa maks           4096 18. Mär 11:29 brandtfa

Zur Sicherung der Daten (vor allem Ergebnisse der Rechnungen) kann das eigene HOME-Verzeichnis genutzt werden. Dies kann auch bei kleineren Programmen und Datensätzen für die Berechnung selbst benutzt werden. Bitte beachtet hier die Größenbeschränkung.

 

MPI

Im Falle von Multi-Prozess Programmen, die entweder auf einem oder auf mehreren Nodes laufen sollen, kommt MPI als Kommunikationsstandard zum Einsatz. Hierbei sind auf allen Nodes mehrere Implementierungen installiert:

  • openmpi
  • openmpi2
  • openmpi4
  • mpich
  • impi (Intel MPI)

 

Jede der Implementierungen bietet eigene Header, Blbliotheken und Binaries. Programme, die MPI nutzen sollen, müssen mit den Compilern der jeweiligen MPI-Umgebung kompiliert werden. Die Besonderheit ist hier - nach dem Einloggen ist zunächst keine der Implementierungen im Pfad verfügbar und muss aktiviert werden.

Der empfohlene Weg hierfür ist die Nutzung von modules:

$ module load gnu-openmpi/4.0.5
$ mpirun --version
mpirun (Open MPI) 4.0.5.0.88d8972a4085 Report bugs to http://www.open-mpi.org/community/help/

Diese lassen sich auch in Slurm-Skripten benutzen. Hierfür sollten die module load Befehle vor anderen Skripten aufgerufen werden (siehe Beispielskripte zur MPI- oder GPU-Programmierung).

Eine Alternative ist die Nutzung von  mpi-selector zur Aktivierung, allerdings sollten wenn möglich modules benutzt werden.

# Get list of all installed MPI-versions
$ mpi-selector --list
mpich
openmpi
openmpi2
openmpi4
$ mpi-selector --set openmpi4
$ mpi-selector --query 
default:openmpi4
level:user

Hinweis: Die MPI-Umgebung ist erst nach einem erneuten Login verfügbar.

$ mpirun --version
mpirun (Open MPI) 4.0.5.0.88d8972a4085

Report bugs to http://www.open-mpi.org/community/help/

 

 

Best Practices

Slurm nutzen wo möglich!

Sollte ein Programm mehr Zeit und Ressourcen verbrauchen und ist das Ergebnis der Berechnung nicht zeitkritisch, sollte es mittels des Queue-Systems ausgeführt werden. Hiermit wird eine ordentliche Auslastung des PC-Pools gewährleistet, da die Ressourcen automatisch verwaltet werden.

Es sollten insbesondere (mit Ausnahme schneller Berechnungen) keine rechenintensive Programme über eine entfernte Konsole gestartet werden, denn dies stört nicht nur die Person, die vor dem Rechner sitzt und kaum mehr arbeiten kann, sondern auch andere Leute, die die Warteschlange benutzen. Slurm hat keine Möglichkeit direkt ausgeführte Programme zu managen. Daher kann es zu einem Over-Provisioning von Ressourcen kommen, wenn beide Systeme parallel laufen.

 

Partitionen helfen

Während prinzipiell alle Jobs auf der Standard-Partition std bearbeitet werden können, empfiehlt es sich bei speziellen Anforderungen die dafür passenden Partitionen zu wählen. Auf den Einzelpartitionen wie gpu oder gruenau besitzen die Jobs höhere Priorität, das bedeutet bei mehrfacher Ressourcenanfrage werden diese Jobs auf einem Node schneller bearbeitet.

 

Nur soviel verbrauchen wie angegeben

Es wird nicht geprüft, ob ein Programm wirklich nur die angegebene Anzahl an Kernen benötigt. Es ist aber in Ihrem Interesse und dem anderer, wenn Sie richtige Werte angeben und Ihr Programm auf diese Werte beschränken. Es wird versucht, die Ressourcen bestmöglich auszulasten, das heißt, wenn zwei Jobs angeben, jeweils 16 Kerne zu benötigen, können diese auf einem Node mit 32 Kernen gleichzeitig laufen. Sollte diese Angabe nicht stimmen und ein Programm mehr Kerne auslasten, können die Ressourcen nicht mehr optimal verteilt werden und beide Jobs auf dem Node benötigen mehr Zeit.

 

Limits setzen

Mittels des Parameters time können Sie angeben, wann Ihr Programm spätestens beendet wird. Dies sollten Sie nutzen, um zu verhindern, dass Ihr Programm wegen eines Fehlers nicht beendet und damit Nodes blockiert. Hinweis: Die interactive Partition setzt ein automatisches Zeitlimit von 2h.

 

Daten sichern

Es kann immer vorkommen, dass ein Job ungewollt abbricht. Das kann passieren, weil die maximale Laufzeit (time) abläuft, weil es einen Bug im Programm gibt, oder, weil es ein Fehler an der Maschine gibt. Sichern Sie sich deshalb, wenn möglich, regelmäßig Zwischenergebnisse, um von diesem Punkt die Berechnung neu starten zu können.

 

Aufräumen nicht vergessen

Daten, die nach der Rechnung nicht mehr benötigt werden, sollten am Ende des Skripts gelöscht werden.