Babel : travaux multi-étapes

Attention : si vous faites des transferts de fichiers avec Gaya, nous vous conseillons plutôt de consulter la page sur les travaux multi-étapes avec transferts de fichiers avec Gaya.

Un job comprend souvent un phase de préparation des données, de transferts de fichiers entre différents répertoires (pour les transferts avec Gaya, voir cette page), une phase de calcul parallèle, puis une phase de traitement des résultats.

Il serait dommage de monopoliser des centaines de coeurs Blue Gene/P pendant les phases purement séquentielles. Aussi, nous allons utiliser la notion d'étape (ou step) de LoadLeveler : les trois phases décrites précedemment peuvent être enchaînées dans un même job. Les phases séquentielles seront exécutées sur la machine frontale (Power5) et la phase de calcul parallèle sur les noeuds de calcul de la machine.

Notes importantes :

  • Sur Babel le répertoire TMPDIR est conservé entre les différentes étapes d'un même job ;
  • La frontale n'étant pas une machine destinée à faire des pré- et post-traitements lourds, les ressources allouées aux classes séquentielles sont volontairement limitées. La machine Ulam est par contre dimensionnée pour ce type d'usages ;
  • Les classes séquentielles de Babel ne sont pas facturées.

Voici un exemple de travail enchaînant la préparation des données, l'exécution d'un programme MPI sur 256 coeurs puis l'archivage des résultats dans une dernière étape. Le fichier de soumission, appelons-le job_multi.ll, est le suivant :

#=========== Global directives ===========
#@ shell    = /bin/bash
#@ job_name = test_multi-steps
#@ output   = $(job_name).$(step_name).$(jobid)
#@ error    = $(output)

#=========== Step 1 directives ===========
#======= Sequential preprocessing ========
#@ step_name = sequential_preprocessing
#@ job_type  = serial
#@ cpu_limit = 0:15:00
#@ queue

#=========== Step 2 directives ===========
#============= Parallel step =============
#@ step_name  = parallel_step
#@ dependency = (sequential_preprocessing == 0)
# (submit only if previous step completed without error)
#@ job_type   = bluegene
#@ bg_size    = 64
#@ wall_clock_limit = 1:00:00
#@ queue

#=========== Step 3 directives ===========
#======= Sequential postprocessing =======
#@ step_name  = sequential_postprocessing
#@ dependency = (parallel_step >= 0)
# (submit even if previous step completed with an error)
#@ job_type   = serial
#@ cpu_limit  = 0:15:00
#@ queue

case $LOADL_STEP_NAME in
  #============ Step 1 commands ============
  #======= Sequential preprocessing ========
  sequential_preprocessing )
    set -ex
    cd $tmpdir
    cp $LOADL_STEP_INITDIR/test/src/coucou_MPI.f .
    mpixlf90_r coucou_MPI.f -o coucou_MPI.exe

    tar xvf $WORKDIR/mydata/data.tar
    ls -l
    ;;

  #============ Step 2 commands ============
  #============= Parallel step =============
  parallel_step )
    set -x
    cd $tmpdir
    mpirun -mode VN -np 256 -mapfile TXYZ ./coucou_MPI.exe
    ;;

  #============ Step 3 commands ============
  #======= Sequential postprocessing =======
  sequential_postprocessing )
    set -x
    cd $tmpdir
    tar cvf $WORKDIR/results/result.tar *.dat
    ;;
esac

Ce job est séparé en deux sections. La première contient l'ensemble des directives destinées au gestionnaire de queues LoadLeveler, tandis que la deuxième rassemble les commandes à exécuter par les différentes étapes (steps). Les deux parties peuvent également être mélangées mais cela risque de nuire à la lisibilité et à la compréhension du job.

Les directives LoadLeveler de chaque step doivent se terminer par une directive #@ queue.

La directive #@ dependency de la seconde étape indique que celle-ci ne doit s'exécuter que si la première s'est terminée normalement (valeur de retour égale à zéro). Pour la troisième étape, la dépendance choisie est telle que l'étape s'exécutera même en cas d'erreur lors de l'étape précédente (cela peut être très utile pour récupérer des résultats partiels même après un crash de l'application ou un dépassement de temps). Il est nécessaire de mettre des directives de dépendances si l'on veut s'assurer que les différentes étapes s'exécutent les unes après les autres (sans cette directive, tous les steps peuvent démarrer indépendemment les uns des autres dès que des ressources sont disponibles).

La soumission de ce travail crée en réalité trois travaux contenant le même script shell (mais recevant des ressources différentes). Ils ne se distinguent que par leur nom d'étape. Pour qu'ils exécutent chacun des commandes différentes, il est nécessaire d'effectuer un branchement différent pour chaque step en utilisant leur nom (stocké dans la variable LOADL_STEP_NAME). Cela est fait via l'utilisation d'un case. Son utilisation est simple, il suffit de vous inspirer de l'exemple. N'oubliez pas d'ajouter ;; (double point-virgule) pour séparer chaque branche et esac à la fin.

Pour soumettre ce travail à trois étapes, placez-vous dans le répertoire contenant job_multi.ll et tapez :

llsubmit job_multi.ll

Dans ce job, notez l'utilisation dans la première étape de la commande set -ex. Elle permet d'interrompre l'étape en cours dès qu'une commande renvoie un code de retour différent de zéro. Ainsi, en prenant en compte la relation de dépendance #@ dependency = (zone_sequentielle_pretraitement == 0), la deuxième étape ne s'exécutera que si toutes les commandes se sont correctement exécutées. Si la commande set -ex n'est pas utilisée, alors le test de dépendance de l'étape en cours se fait sur le code de retour de la dernière commande de l'étape précédente.

Notez également que, lors du démarrage de chaque step, le répertoire par défaut est celui d'où a été soumis le job ; c'est là que seront écrits les fichiers de sortie. D'autre part, il est indispensable de spécifier un fichier de sortie différent pour chaque step, faute de quoi la sortie du dernier step écrase les sorties des precédents (voir la ligne output dans le fichier de soumission). Enfin, les différentes options de la commande mpirun sont détaillées dans cette page.