Turing : exécution MPMD d'un couplage de codes en batch

Le modèle d'éxécution MPMD (Multiple Program Multiple Data) est supporté sur Turing. Différents exécutables sont lancés et communiquent entre eux en utilisant MPI. Tous les processus MPI sont inclus au sein du même communicateur MPI_COMM_WORLD.

Sur Turing ce modèle d'exécution est réalisé à l'aide d'un fichier texte (nommé dans les exemples mapfile). Ce fichier contient deux sections :

  • une section pour spécifier les associations entre les processus MPI et les exécutables à l'aide des directives #mpmdbegin, #mpmdcmd, #mpmdend.
  • la deuxième section indique les coordonnées du processus MPI dans le tore ABCDET. La première ligne correspond aux coordonnées ABCDET du processus MPI 0, la seconde aux coordonnées du processus MPI 1…

Exemple de couplage MPMD de quelques processus MPI

L'exemple mpmd.ll décrit une exécution sur 3 nœuds de 7 processus MPI issus de 3 codes MPI/OpenMP :

  • le processus MPI de rang 0 est issu de ./a_mixte.out et s'exécutera sur un nœud. Les coordonnées du coeur d'exécution seront A=B=C=D=E=T=0.
  • Les 2 processus MPI de rang 1 et 2 sont issus de ./b_mixte.out et s'exécuteront au sein d'un nœud. Les coordonnées des coeurs d'exécutions seront : A=B=C=D=0, E=1, T=0..1
  • Les 4 processus MPI de rang 3 à 6 sont issus de ./c_mixte.out et s'exécuteront au sein d'un nœud. Les coordonnées des coeurs d'exécutions seront : A=B=C=0, D=1, E=0, T=0..3

Dans cet exemple, chaque processus MPI aura la possibilité d'exécuter au maximum 16 threads OpenMP car le nombre maximal de processus MPI par noeud est de 4 (option –rank-per-node 4 de la commande runjob).

mpmd.ll
# @ job_name = mpmd
# @ output   = $(job_name).$(jobid)
# @ error    = $(output)
# @ job_type = bluegene
# @ bg_size = 3
# @ wall_clock_limit = 0:30:00
# @ queue
 
# Construction du fichier mapfile
cat >>mapfile <<EOF
#mpmdbegin 0-0
#mpmdcmd ./a_mixte.out
#mpmdend
#mpmdbegin 1-2
#mpmdcmd ./b_mixte.out
#mpmdend
#mpmdbegin 3-6
#mpmdcmd ./c_mixte.out
#mpmdend
0 0 0 0 0 0
0 0 0 0 1 0
0 0 0 0 1 1
0 0 0 1 0 0
0 0 0 1 0 1
0 0 0 1 0 2
0 0 0 1 0 3
EOF
 
runjob \
       --ranks-per-node 4 \
       --np 7 \
       --mapping mapfile \
       --exe ./a_mixte.out

Exemple de couplage MPMD d'un nombre important de processus MPI

La difficulté réside dans la construction du fichier mapfile. La numérotation des cœurs au sein du système ABCDET nécessite de connaître les bornes maximales des coordonnées A, B, C,D, E, T. Dans un job batch elles peuvent être récupérées à l'aide de la variable d'environnement LOADL_BG_SHAPE comme dans l'exemple mpmd-bgsize64.ll. La variable LOADL_BG_SHAPE, automatiquement valorisée dans un job batch, donne le profil du bloc alloué.

À titre d'exemple, pour bg_size=64, la variable LOADL_BG_SHAPE, exprimée en nombre de nœuds dans chaque dimension AxBxCxDxE, peut avoir pour valeur 2x2x4x2x2 :

  • A, B, D et E varient alors de 0 à 1,
  • C varie de 0 à 3,
  • T varie de 0 à T_MAX-1 où T_MAX est le nombre de processus MPI par nœud précisé dans l'argument –ranks-per-node de runjob.

Si bg_size>=512, la variable LOADL_BG_SHAPE est exprimé en nombre de midplane dans chaque dimension AxBxCxD (E est considéré comme égal à 2). L'extraction des informations données par LOADL_BG_SHAPE est décrite dans la fonction bash max_abcdet ci-dessous.

La numérotation par défaut du processus MPI au sein du système de coordonnées ABCDET consiste à incrémenter T (lettre la plus à droite), puis E, D, C, B, A. C'est ce qui est réalisé par la fonction bash incr_abcdet ci-dessous.

Pour plus d'informations sur ces fichiers mapfile, vous pouvez consulter :

On décrit dans mpmd-bgsize64.ll un exemple de couplage de 769 processus MPI issus de 3 codes MPI/OpenMP différents :

  • Le processus MPI de rang 0 est issu de ./a_mixte.out. Il s'exécute sur un nœud.
  • Les 512 processus MPI de rang 1 à 512 sont issus de ./b_mixte.out. Ils s'exécutent sur 32 nœuds.
  • Les 256 processus MPI de rang 513 à 768 sont issus de ./c_mixte.out. Ils s'exécutent sur 16 nœuds.

L'option –ranks-per-node indique 16 processus MPI par nœud : chaque processus MPI aura donc la possibilité d'exécuter au maximum 4 threads OpenMP.

mpmd-bgsize64.ll
# @ job_name = mpmd
# @ output   = $(job_name).$(jobid)
# @ error    = $(output)
# @ job_type = bluegene
# @ bg_size  = 64
# @ wall_clock_limit = 0:10:00
# @ queue
 
 
max_abcdet () {
# À partir de la distribution des nœuds ou des midplanes fournie par LOADL_BG_SHAPE (LoadLeveler)
# Calcul des coordonnées maximum dans chaque direction du Tore 5D 
# (a_max, b_max, c_max, d_max, e_max, t_max)
    shape=(${LOADL_BG_SHAPE//x/ })
    case ${#shape[*]} in
        5) 
   # LOADL_BG_SHAPE exprimé en nombre de nœuds
            ((a_max=shape[0]))
            ((b_max=shape[1]))
            ((c_max=shape[2]))
            ((d_max=shape[3]))
            ((e_max=shape[4]))
            ;;
        4)
   # LOADL_BG_SHAPE exprimé en nombre de midplanes      
            ((a_max=shape[0]*4))
            ((b_max=shape[1]*4))
            ((c_max=shape[2]*4))
            ((d_max=shape[3]*4))
            ((e_max=2))
            ;;
        *) echo " *** ERREUR : LOADL_BG_SHAPE=${LOADL_BG_SHAPE} non attendu"
            ;;
    esac
    ((t_max=RUNJOB_RANKS_PER_NODE))
}
 
 
incr_abcdet () {
# Incrémentation des coordonnées au sein du Tore 5D (a, b, c, d, e, t)
    ((t=(t+1)%t_max))
    if ((t==0))
    then
	((e=(e+1)%e_max))
	if ((e==0))
	then
	    ((d=(d+1)%d_max))
	    if ((d==0))
	    then
		((c=(c+1)%c_max))
		if ((c==0))
		then
		    ((b=(b+1)%b_max))
		    if ((b==0)) 
		    then
			((a+=1))
		    fi
		fi
	    fi
	fi
    fi
}
 
set -x
 
# Paramètre de runjob : option  --ranks-per-node
export RUNJOB_RANKS_PER_NODE=16
 
# 1ère section du fichier de mapping
# Association entre numéro de processus MPI et exécutable
cat >mapfile <<EOF
#mpmdbegin 0-0
#mpmdcmd ./a_mixte.out
#mpmdend
#mpmdbegin 1-512
#mpmdcmd ./b_mixte.out
#mpmdend
#mpmdbegin 513-768
#mpmdcmd ./c_mixte.out
#mpmdend
EOF
 
# Calcul des bornes supérieures des coordonnées au sein du Tore 5D
max_abcdet
 
# Initialisation des coordonnées au sein du Tore 5D (a, b, c, d, e, t)
((a=b=c=d=e=t=0))
 
# 2ème section du fichier de mapping
# Écriture des coordonnées de chaque processus MPI
 
# 1 processus MPI rang 0 / 1 nœud occupé
echo "$a $b $c $d $e $t" >> mapfile
 
# Passage au nœud suivant car autre exécutable
((t=t_max-1))
incr_abcdet
 
# 512 processus MPI rang 1 à 512 / 32 nœuds occupés
for i in {1..512}
do
    echo "$a $b $c $d $e $t" >> mapfile
    incr_abcdet
done
 
# 256 processus MPI rang 513 à 768 / 16 nœuds occupés
for i in {1..256}
do
    echo "$a $b $c $d $e $t" >> mapfile
    incr_abcdet
done
 
# Exécution MPMD sous le contrôle du fichier de mapping
runjob \
       --ranks-per-node $RUNJOB_RANKS_PER_NODE \
       --np 769 \
       --mapping mapfile \
       --exe ./a_mixte.out

Remarques et limitations

  • il n'est pas possible d'avoir plusieurs exécutables sur un nœud. Un seul exécutable est lancé sur un nœud.
  • Le nombre de processus MPI par nœud (précisé dans l'argument –ranks-per-node de runjob) doit correspondre au maximum du nombre de processus MPI placés sur chacun des nœuds.
  • Comme pour une exécution avec un seul exécutable, la mémoire disponible sur tous les nœuds dépend de l'argument –ranks-per-node de runjob.
  • Pour plusieurs exécutables hybrides MPI/OpenMP, le nombre de threads OpenMP doit être identiques. S'il n'est pas précisé avec –envs OMP_NUM_THREADS, il est égal au minimum de threads qu'il est possible de placer pour compléter un nœud entier.
  • L'exécutable précisé dans l'argument –exe de runjob est un argument ignoré par la commande. Cependant il doit être précisé.