Ada : exécution sous environnement Intel d'un travail mixte MPI/OpenMP en batch

Les travaux sont gérés sur l'ensemble des nœuds par le logiciel LoadLeveler. Ils sont répartis dans des classes principalement en fonction du temps Elapsed, du nombre de cœurs et de la mémoire demandés. Vous pouvez consulter ici la structure des classes sur Ada.

Pour optimiser les performances des codes lors des exécutions sous un environnement Intel, l'IDRIS positionne automatiquement certaines variables d'environnement. Ainsi, la variable Intel I_MPI_PIN_DOMAIN est valorisée pour permettre le binding des processus MPI et des threads (jobs hybrides) sur les cœurs physiques de la machine.

Attention : un code compilé sous intel/2015.2 ou intel/2016.2 NE PEUT PAS être exécuté sous l'environnement IBM (via poe) : il y a incompatibilité entre la bibliothèque MPI IBM actuellement en production et les bibliothèques MPI Intel 5.0.xx et 5.1.xx faisant partie de ces nouveaux environnements Intel.

Attention : l'environnement intel/2013.0 ne peut pas être utilisé pour une exécution batch (job LoadLeveler). En effet, la commande mpirun associée est buggée et renvoie alors le message d'erreur suivant :

$ mpirun -np 8 ./a.out
[mpiexec@ada295] HYDT_bscd_ll_launch_procs (./tools/bootstrap/external/ll_launch.c:67): ll does not support user-defined host lists
*** glibc detected *** mpiexec.hydra: munmap_chunk(): invalid pointer: 0x00000000024bb660 ***

Pour soumettre un travail mixte MPI + threads (OpenMP ou pthreads) en batch, il faut :

  • Créer un script de soumission. Voici un exemple, enregistré dans le fichier intel_mixte.ll :
intel_mixte.ll
# Nom arbitraire du travail LoadLeveler
# @ job_name = Intel_mixte
# Fichier de sortie standard du travail
# @ output   = $(job_name).$(jobid)
# Fichier de sortie d'erreur du travail
# @ error    = $(job_name).$(jobid)
# Type de travail
# @ job_type = mpich
# Nombre de processus MPI demandes
# @ total_tasks = 8
# Nombre de tâches OpenMP/pthreads par processus MPI
# @ nb_threads  = 4
# @ resources = ConsumableCpus($(nb_threads))
# Permet le passage de total_tasks a mpirun via NB_TASKS
# ainsi que le nombre de threads/processus
# @ environment = OMP_NUM_THREADS=$(nb_threads); NB_TASKS=$(total_tasks)
# Temps du job hh:mm:ss (1h30mn ici)
# @ wall_clock_limit = 1:30:00
# @ queue
 
# Recommandation : compiler et exécuter vos codes sous un même environnement Intel.
# Donc, si necessaire, utilisez la commande module pour charger l'environnement approprie.
# Par exemple, si votre code est compile avec Intel/2016.2, de-commenter la ligne suivante :
#module load intel/2016.2
 
# Pour avoir l'echo des commandes
set -x
 
# Repertoire temporaire de travail
cd $TMPDIR
# La variable LOADL_STEP_INITDIR est automatiquement positionnee par
# LoadLeveler au repertoire dans lequel on tape la commande llsubmit
cp $LOADL_STEP_INITDIR/a.out .
# La memoire STACK max. (defaut 4Mo) utilisee (ici 16 Mo) par
# les variables privees de chaque thread.
export KMP_STACKSIZE=16m
# Il est aussi possible d'utiliser OMP_STACKSIZE
# export OMP_STACKSIZE=16M
 
# Execution d'un programme parallèle mixte (MPI + threads).
mpirun -np $NB_TASKS ./a.out
  • Soumettre ce script par la commande llsubmit :
$ llsubmit  intel_mixte.ll

Remarques :

  • Nous vous recommandons de compiler et d'exécuter vos codes sous le même environnement Intel : utilisez la même commande module load intel/… à l'exécution qu'à la compilation.
  • Dans cet exemple, on suppose que l'exécutable a.out se situe dans le répertoire de soumission, c'est-à-dire le répertoire dans lequel on entre la commande llsubmit (la variable LOADL_STEP_INITDIR est automatiquement valorisée par LoadLeveler).
  • Le fichier de sortie du calcul Intel_mixte.numero_job sera également créé dans le répertoire de soumission; l'éditer ou le modifier pendant le déroulement du travail peut bloquer celui-ci.
  • Pour une exécution de type Intel, vous devez spécifier la directive # @ job_type = mpich (au lieu de # @ job_type = parallel pour poe IBM).
  • Le nombre de processus MPI est indiqué par la directive # @ total_tasks =… comme pour un job hybride IBM.
  • Le nombre de threads par processus MPI est indiqué par les directives # @ nb_threads = … (ne pas utiliser un autre nom que nb_threads) et # @ resources = ConsumableCpus($(nb_threads)) (au lieu de # @ parallel_threads =… pour un job hybride IBM). Cette astuce permise par LoadLeveler, permet de réutiliser cette variable nb_threads dans les autres directives LoadLeveler et de transmettre sa valeur dans l'environnement d'exécution. Le nombre de threads par processus MPI est ainsi défini une seule fois et sa valeur automatiquement reportée dans les instructions shell du script ce qui évite de réserver plus de ressource que ce qui est réellement utilisé.
  • ATTENTION : le nombre de processus MPI (total_tasks) et le nombre de threads par processus MPI (nb_threads) à indiquer sont tels que le nombre total de cœurs réservés (total_tasks * nb_threads) soit inférieur ou égal à 2048.
  • L'exécution du binaire s'effectue via la commande mpirun avec, comme paramètre, le nombre total de processus MPI (mpirun -np $NB_TASKS ./a.out)
  • Notez que le nombre de processus MPI par nœud de calcul est automatiquement indiqué via la variable d'environnement I_MPI_PERHOST.
  • Mémoire : La valeur par défaut est de 3.5 Go par cœur réservé (donc par thread). Si vous demandez plus de 64 cœurs
    (total_tasks * nb_threads > 64) alors vous ne pouvez pas dépasser cette limite. Sinon, la valeur maximale que vous pouvez demander est de 7.0 Go par cœur réservé via le mot-clef as_limit. Notez que vous devez spécifier une limite par processus MPI correspondant à (nb_threads * 7.0 Go). Par exemple, si chaque processus MPI génère 4 threads : # @ as_limit = 28.0gb
  • Les variables privées OpenMP sont stockées dans des zones dites STACK associées à chaque thread. Chacune d'elle est limitée par défaut à 4Mo. Pour dépasser cette limite et aller par exemple jusqu'à 16Mo par thread, il faut utiliser la variable d'environnement KMP_STACKSIZE=16m ou OMP_STACKSIZE=16M. Notez que la valeur de OMP_STACKSIZE est automatiquement ajustée sur la valeur de KMP_STACKSIZE lorsque cette dernière est positionnée.
  • Si votre travail comporte des commandes séquentielles relativement longues (pré ou post-traitement, transferts ou archivages de gros fichiers,…), l'utilisation de travaux multisteps peut se justifier.