Das Batch-System Open Grid Scheduler/Grid Engine

Sie nutzen die Rechenknoten des HPC-Clusters mit einem Batch-System.

Dazu wird Open Grid Scheduler/Grid Engine (http://gridscheduler.sourceforge.net/), vormals bekannt als Sun Grid Engine (SGE), eingesetzt.

Programme werden dabei zu Jobs in Jobdateien zusammengefasst und dem Batch-System mit dem Kommando qsub übergeben. Mit dem Kommando qstat wird der Zustand der Jobs in den Warteschlangen angezeigt. Freunde einer grafischen Oberfläche können mit dem Kommando qmon alles bedienen.

Die Auslastung des Clusters kann mit Ganglia (http://dirac-meister.basisit.de/ganglia/?c=dirac) beobachtet werden.

Warteschlangen oder Queues

Jobs werden in Warteschlangen (Queues) eingereiht und darin abgearbeitet.

Es gibt Warteschlangen mit unterschiedlichen Eigenschaften:

(Die Zahl der Knoten erhöht sich noch abhängig vom dem Stand des Umbaus auf die in Klammern genannte.)

Queue-  max.      CPU-        Knoten-  Hinweise
Name  	Laufzeit  Kerne       zahl 
-----------------------------------------------------------------------------------------------
all.q	  1 h	   1088        17        für Tests auf den Interlagos-Knoten (64 Kerne/Rechner)
magny	168 h       432         9        Magny-Cours-Knoten (48 Kerne/Rechner)
inter	168 h	   1088        17        Interlagos-Knoten (64 Kerne/Rechner)
max 672 h 128 2 Interlagos-Knoten (64 Kerne/Rechner) nur nach Anmeldung

In allen Jobdateien muss der maximale Speicherverbrauch in GB mit dem Parameter vf= angegeben werden. Die Warteschlange kann bei qsub mit dem Parameter -q angegeben werden, ohne Angabe wird sie von der Open Grid Engine ausgewählt.

Die Zahl der aktiven Jobs je Nutzer, der Speicherverbrauch und die Zahl der genutzten CPU-Kerne (Slots) bei Parallel-Jobs sind begrenzt.

Zur Wartung, Erweiterung oder Änderung der Konfiguration können Jobs in den Warteschlangen inter und magny bereits nach 24 Stunden abgebrochen werden.

Die Queue max kann nur nach Anmeldung und Freischaltung benutzt werden. Sie ist für langlaufende Programme (bis vier Wochen) im Ausnahmefall vorgesehen.

Jobdateien

Jobs werden mit Jobdateien beschrieben. Das sind ausführbare Shell-Skripte, in denen die gewünschten Variablen gesetzt und die Programme aufgerufen werden. Der Open Grid Engine selbst werden Parameter mit bestimmten Kommentarzeilen übergeben.

Hier ein Beispiel einer Jobdatei /home/dmf/Jobs/job.sh:

#! /bin/bash
#$ -S /bin/bash
#$ -cwd
#$ -l vf=1G
/home/dmf/SRC/Thread/twod 4

Dieser Job auf dirac-meister wird mit

cd /home/dmf/Jobs
qsub job.sh

in die Default-Warteschlange all.q eingereiht. Im Beispiel soll das Programm /home/dmf/SRC/Thread/twod mit Argument 4 auf einem Rechenknoten ausgeführt werden. Bei der Ausführung ist /home/dmf/Jobs/ die Default-Umgebung durch die Option -cwd.

-l vf=1G fordert 1 GB RAM für den Job an.

Der Speicherbedarf muss mit der Variablen vf= angegeben werden. Mehr als der in den Knoten verfügbare Speicher kann nicht nicht angefordert werden. Fehlt dieser Parameter, startet der Job nicht. Dennoch sind Überbuchungen des Speichers möglich.

Jobs starten nicht, wenn sie nicht in der Warteschlange definierte Ressourcen anfordern.

Die Grid-Engine überwacht die anderen Jobs und die aktuelle Last auf den Knoten und entscheidet dann, ob ein Job gestartet wird. Danach ist die Kontrolle weitgehend aufgehoben. Unser Beispiel-Job startet deshalb erst, wenn die notwendigen Ressourcen, hier 1 GB RAM und die benötigte Zahl der CPU-Kerne (hier nur einer) frei (durch Ermittlung der aktuellen CPU-Load auf den Knoten berechnet) sind. Die Grid-Engine legt bei diesem Beispiel im Verzeichnis /home/dmf/Jobs folgendes an:

  • job.sh.oJob-ID Ausgabe (stdout) des Programms
  • job.sh.eJob-ID Fehler (stderr) des Programms

Die fortlaufende Job-ID wird dem Job vom Batch-System zugewiesen. Die Batch-Parameter können auch direkt mit dem qsub-Kommando zusammen angegeben werden, eine Übersicht liefert auf dirac-meister man qsub.

Bei vielen Jobs wird die Zahl der Dateien schnell unübersichtlich. Sie können deshalb diese Dateien in ein anderes Verzeichnis schreiben lassen, indem sie folgendes in der Jobdatei einfügen:

#$ -o Verzeichnisname
#$ -e Verzeichnisname

Als Verzeichnisnamen können Sie z.B. ein Unterverzeichnis in ihrem Heimatverzeichnis auf dirac-meister oder ein von allen Rechenknoten erreichbares Verzeichnis angeben. Verwenden Sie kein lokales Verzeichnis auf den Rechenknoten, z.B. /tmp.

Hilfe bei Startproblemen

Mit den Kommandos

qalter -w v Job-ID

qstat -j<tt> Job-ID

kann bei hängenden Jobs der Grund für den Wartezustand oder Fehler herausgefunden werden. Mit <tt>qalter können dann einige Parameter eines Jobs nachträglich geändert werden, so dass dann der Job vielleicht doch noch starten kann.

Start von parallelisierten Programmen

Auch parallele ausführbare Programme (z.B. mit OpenMPI) werden mit dem Batch-System gestartet. Dabei reserviert und initialisiert die Parallel-Umgebung die dafür benötigten Ressourcen.

Die gewünschte Parallel-Umgebung muss zusätzlich vor Abgabe des Jobs durch Ausführen der passenden Initialisierung ausgewählt werden. Damit wird das passende Environment eingerichtet, damit die richtigen Kommandos und Bibliotheken gefunden werden können.

Für OpenMPI 1.8.8. mit RDMA unter Verwendung der Intel-Compiler sieht das in der Batch-Datei beispielsweise so aus:

#
# Hier openMPI 1.8.8 mit RDMA und den Intel-Compilern
#
setenv PATH /opt/openmpi-intel-1.8.8-rdma/bin/:${PATH}
setenv LD_LIBRARY_PATH /opt/openmpi-intel-1.8.8-rdma/lib64/:/opt/intel/lib/intel64/:${LD_LIBRARY_PATH}

Diese Initialisierungs-Befehle müssen auch vor der Übersetzung Ihrer Programme ausgeführt werden. Nur so werden die gewünschten Compiler, Include-Dateien und Bibiotheken beim Übersetzen und später die dazu passende Parallel-Umgebung benutzt.

Das Beispielprogramm mpi_hello.c:

// Ein einfacher MPI-Test
// Übersetzen mit:
// $ mpicc -o mpi_hello mpi_hello.c

// modifiziert um einige sinnlose Berechnungen, um mehr
// Rechenzeit zu verbrauchen
// Dr. Andreas Tomiak, 14.04.2016
//
#include <stdio.h>
#include <mpi.h>

#include <stdlib.h>
/* 100000 Elemente */
#define MAX 1000000
/* 100 Runden */
#define RUNDEN 100

/* ein Array von großen zu kleinen Werten */
int test_array1[MAX];
int test_array2[MAX];

/* in umgekehrter Reihenfolge erstellen */
void init_test_array(int *array) {
   int i, j;
   for(i = MAX,j=0; i >= 0; i--,j++)
      array[j] = i;
}

static void *bubble1(void* val) {
   int i, temp, elemente=MAX;
   while(elemente--)
      for(i = 1; i <= elemente; i++)
         if(test_array1[i-1] > test_array1[i]) {
            temp=test_array1[i];
            test_array1[i]=test_array1[i-1];
            test_array1[i-1]=temp;
         }
}

static void *bubble2(void* val) {
   int i, temp, elemente=MAX;
   while(elemente--)
      for(i = 1; i <= elemente; i++)
         if(test_array2[i-1] > test_array2[i]) {
            temp=test_array2[i];
            test_array2[i]=test_array2[i-1];
            test_array2[i-1]=temp;
         }
}


int main (argc, argv)
     int argc;
     char *argv[];
{
  int rank,size;

  int i, j;
  int cnt=RUNDEN;
  init_test_array(test_array1);
  init_test_array(test_array2);

  MPI_Init(&argc,&argv); /* starts MPI */
  MPI_Comm_rank(MPI_COMM_WORLD,&rank); /* get current process id */
  MPI_Comm_size(MPI_COMM_WORLD,&size); /* get number of processes */
  printf("Hello world from process %d of %d\n",rank,size);

  while(cnt--) {
    bubble1(NULL);
    bubble2(NULL);
    }

  sleep(10); 
  MPI_Finalize();
  return 0;
}

Übersetzt wird das Beispielprogramm mpi_hello.c so (Das Arbeitsverzeichnis sei /home/dat/tests/ompi):

cd /home/dat/tests/ompi
setenv PATH /opt/openmpi-intel-1.8.8-rdma/bin/:${PATH}
setenv LD_LIBRARY_PATH /opt/openmpi-intel-1.8.8-rdma/lib64/:/opt/intel/lib/intel64/:${LD_LIBRARY_PATH}
mpicc -o mpi_hello mpi_hello.c -o

Die korrekte Variante des Compiler-Wrappers mpicc für openMPI mit dem Intel-Compiler wird über die korrekte Wahl der Pfade $PATH und $LD_LIBRARY_PATH sichergestellt. Das Startmöglichkeit des Binary kann man mit dem Loader-Befehl

ldd mpi_hello

prüfen. Die Ausgabe sollte dann Pfade wie /opt/openmpi-intel-1.8.8-rdma/lib64/libmpi.so.1 enthalten und alle Bibliotheken sollten gefunden worden sein.

Betrachten wir jetzt die zugehörige Jobdatei mpi_hello.sh zum Starten des Programms mpi_hello:

#!/bin/sh
## Nutzung der Intel-Compiler und openMPI 1.8.8 mit RDMA (Inifiniband)
## Dr. Andreas Tomiak, 01.06.2016
## starten mit: 
## $ qsub -q inter mpi_hello.sh
# alle Umgebungs-Variablen exportieren
#$ -V
# den Jobnamen setzen
#$ -N mpi_hello
# das Arbeitsverzeichnis setzen
#$ -cwd
# stdout und stderr zusammenfassen
#$ -j y
# Verwendeter Speicher
#$ -l vf=10M
## Parallel-Umgebung ompi-t-f (tight und fill_up)
## und Zahl der gewünschten Prozesse anfordern
#$ -pe ompi-t-f  64-640
# resource reservation einschalten
#$ -R y
# Zeitbegrenzungen definieren
# Maximale harte Walltime ist 16 Minuten (danach wird mit KILL beendet)
#$ -l h_rt=00:16:00
# Maximale weiche Walltime ist 15 Minuten (danach wird SIGUSR2 gesendet)
#$ -l s_rt=00:15:00
# Pfad für Binaries und Bibliotheken setzen
# muss mit PATH und LD_LIBRARY_PATH beim Übersetzen übereinstimmen!
# Hier openMPI 1.8.8 mit RDMA und den Intel-Compilern
setenv PATH /opt/openmpi-intel-1.8.8-rdma/bin/:${PATH}
setenv LD_LIBRARY_PATH /opt/openmpi-intel-1.8.8-rdma/lib64/:/opt/intel/lib/intel64/:${LD_LIBRARY_PATH}
# Fehlerhafte hw_loc-Meldungen unterdrücken
setenv HWLOC_HIDE_ERRORS 1
# Hier geht es endlich los, rdma erzwingen (wenn nicht vorhanden, wird dennoch TCP als Ersatz verwendet)
# Limits aufheben (Achtung: Evtl. andere Limits durch die spezielle Umgebung von sge_execd)
unlimit
limit
# Der Befehl mit dem Start des Rechenjobs, alles davor sind Initialisierung und Debugging-Ausgaben
mpirun -v --mca btl ^tcp -np $NSLOTS /home/dat/tests/ompi/mpi_hello &
  • -S /bin/bash fordert die Bash als Shell.
  • -cwd nimmt das aktuelle Verzeichnis als Arbeitsverzeichnis des Jobs.
  • ompi ist eine von mehreren möglichen Parallelumgebungen, hier werden 64 bis 640 Instanzen (Rechenkerne) gefordert.
  • Die Speicheranforderung vf=10M gilt je Prozess, im Beispiel werden also insgesamt bis zu 6,4 GB Speicher angefordert.
  • Der Parameter von mpirun --mca btl ^tcp erzwingt die Interprozesskommunikation über das schnelle Inifiniband-RDMA.

Der Open Grid Scheduler/Grid Engine versucht, die Prozesse auf möglichst wenigen Knoten zu halten, falls die Speicheranforderung es zulässt. Wenn weniger als 640 Kerne frei sind, bekommt man weniger, und startet deshalb $NSLOTS Instanzen. Bei einer Angabe -pe ompi 640 würde der Job nicht starten, wenn weniger als 640 CPU-Kerne für verfügbar sind.

Die Art der Verteilung der Prozesse über die Rechenknoten wird über die gewählte Parallel-Umgebung (hier ompi-t-f) festgelegt.

Mit

qsub -q inter mpi_hello.sh

kann der Job nun gestartet werden.


Als Ergebnis des Jobs erhält man zusätzlich zu den anderen Job-Ausgabedateien die Dateien

  • mpi_hello.poJob-ID für die Standard-Ausgabe
  • mpi_hello.peJob-ID für die Fehlerausgabe

im gewählten Arbeitsverzeichnis.

Verfügbare Parallelisierungs-Bibliotheken

Es sind aktuell drei Parallelisierungs-Bibliotheken (alles OpenMPI-Varianten) verfügbar:

Bibliothek    Compiler   Interprozess-Kommunikation
----------------------------------------------------
OpenMPI 1.8.8 Gnu        4*10 GBit/s-Infiniband-RDMA
OpenMPI 1.8.8 Intel      1 GBit/s-Ethernet
OpenMPI 1.8.8 Intel      4*10 GBit/s-Infiniband-RDMA

Für jede dieser Bibliotheken kann eine der folgenden Open Grid Scheduler/Grid Engine Parallel-Umgebungen zum Start der Prozesse verwendet werden:

Name     Job-Steuerung Verteilstrategie
         durch GE      auf den Knoten
---------------------------------------
ompi-t-p eng           $pe_slots
ompi-t-f eng           $fill_up
ompi-t-r eng           $round_robin
ompi-p   nur bei Start $pe_slots
ompi-f   nur bei Start $fill_up
ompi-r   nur bei Start $round_robin


Die Pfade und Parameter in den Job-Skripten müssen an die verwendeten Compiler und Parallel-Bibliotheken angepasst sein. Die Übersetzung der Programme erfordert dazu gehörige angepasste Kommandos und Parameter. Unterschiede hierbei können unerwartete Seiteneffekte auslösen.

In den Parallel-Umgebungen werden u.a. zwei wesentliche Parameter für die Verteilung der Prozesse auf die Knoten einerseits und die Bindung an den Open Grid Scheduler andererseits definiert.

Default für die Prozess-Verteilung auf die Knoten ist das Verfahren $fill_up, d.h. es wird ein Knoten mit Prozessen so lange gefüllt, bis alle seine Slots (entspricht in der Regel der CPU-Core-Anzahl) belegt sind. Danach wird der nächste Knoten benützt. Bei $round_robin werden die Prozesse über die Knoten verteilt, einer auf den ersten, der zweite auf den zweiten Knoten, dann der dritte auf den dritten verfügbaren Knoten und so weiter bis alle Prozesse verteilt sind. Die Strategie $pe_slots wird nur genau einen Knoten verwenden, damit schließt man die Verteilung auf andere Konten aus.

Der andere Parameter bestimmt die Bindung an den Scheduler ein. Ist dieser gesetzt, so wird der Scheduler die Kindprozesse selbst überwachen (enge Kopplung), sonst lässt er sie nach dem Start in Ruhe. Bei loser Kopplung müssen Sie die Parameter für mpirun und das nötige hostfile mit den Namen der Rechenknoten selbst definieren.

Die von Ihnen gewünschte Anwendung und der verwendete Compiler schränkt auch die Auswahl der möglichen Umgebungen und Queues ein. Ein Beispiel: GROMACS arbeitet nur mit openMPI zusammen und sie müssen alles mit dem Gnu-Compiler übersetzen.

Array-Jobs

Wenn sie viele Instanzen des Programms mit verschiedenen Parametersätzen nacheinander starten möchten, so können sie Array-Jobs nutzen:

#!/bin/bash
#$ -cwd
#$ -l vf=1G
./run_job < input-$SGE_TASK_ID > output-$SGE_TASK_ID

Das Job-Skript wird dann mehrmals gestartet, wobei die Shell-Variable $SGE_TASK_ID als "Laufvariable" hochgezählt wird. Startet man beispielsweise unser Beispiel mit dem Aufruf

qsub -t 1-10 jobscript

wird es zehnmal ausgeführt, und führt dabei das Programm mit den Eingabedateien input-1 bis input-10 aus und schreibt die Ergebnisse in output-1 bis output-10.

 

Weiterführendes

 

N1 Grid Engine 6 User's Guide (PDF): http://www.helmholtz-berlin.de/media/media/angebote/it/anleitungen/817-6117.pdf

MPI- und Thread-Parallelisierung auf dirac (PDF) http://www.helmholtz-berlin.de/media/media/angebote/it/anleitungen/mpi_thread_parallelisierung.pdf

Using MPI, Portable Parallel Programming with the Message-Passing Interface second edition, William Gropp, Ewing Lusk, Anthony Skjellum, MIT Press

MPI forum: http://www.mpi-forum.org/

Message Passing Interface (Wikipedia): https://de.wikipedia.org/wiki/Message_Passing_Interface

MPI Dokumente: http://www.mpi-forum.org/docs/docs.html

openMPI: https://www.open-mpi.org