Slurm
Einführung
Auf den Gruenau-Compute-Servern 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 Compute-Server 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 (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 Warteschlangesrun PARAMETER
lässt einen Job mit Parametern ad-hoc laufen. Dies sollte nur als Ersatz zusbatch
oder als Test-Command gesehen werden. Beispiele fürsrun / sbatch
Befehle finden sich weiter unten auf der Seite.
Informationen über laufende Jobs:
squeue
zeigt den Inhalt Warteschlangen (Queues) anscontrol show job JOBNUMBER
zeigt Informationen über einen bestimmten Job anscancel 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 (Grünau-Server mit und ohne GPU) nennt sich std. Immer wenn in einer Job-Beschreibung keine Partition 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.
$ 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:
- std: Default-Partition. Wird benutzt, wenn keine Partition im Skript angegeben wird. Hier sind alle Nodes enthalten.
- interactive: Partition zum Testen von Jobs. Hier sind ausschließlich interaktive Jobs (mittels srun + passenden Parametern) erlaubt. Erlaubte Zeit sind max. 2h.
- 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.
- 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 /vol/tmp
. Dieser Ordner ist auf allen Nodes 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. Bitte behalten Sie Ihren Platzverbrauch im Blick.
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 beachten Sie auch 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.
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.