Turing : Choix du nombre de processus par nœud de calcul et positionnement des processus

Choix du nombre de processus par nœud de calcul

Attention : le choix du nombre de processus utilisé par nœud de calcul a des conséquences importantes sur la comptabilité de vos travaux (voir les notes sur la comptabilité des travaux). Pour rappel, il vous est facturé le nombre de cœurs réservés multiplié par le temps d'horloge consommé.

Chaque nœud de calcul possède 16 cœurs dédiés aux calculs (un 17ème cœur est réservé au système d’exploitation). Chaque cœur est capable d'exécuter jusqu'à 4 threads hardware. Un thread hardware peut être utilisé pour un thread au sens multithreading ou OpenMP, mais aussi pour un processus MPI. En résumé, il est possible d'exécuter jusqu'à 64 processus MPI, threads ou un mélange des 2 (par exemple 4 processus MPI chacun avec 16 threads OpenMP).

Les cœurs de la Blue Gene/Q sont capables d'effectuer jusqu'à 2 opérations par cycle d'horloge : une instruction en virgule flottante et une instruction autre (opération sur des entiers, instruction de chargement ou d'écriture dans la mémoire ou de contrôle). L'instruction en virgule flottante peut porter sur un vecteur de 4 réels double précision et donc réaliser 4 opérations en virgule flottante par cycle. Si, en plus, il s'agit d'opérations de type FMA (Fused Multiply-Add, i.e. “a*x+b”), on peut compter 8 opérations en virgule flottante par cycle.

Cependant, les 2 instructions pouvant être réalisées par cycle doivent l'être par 2 threads hardware différents. Donc, exécuter plusieurs processus ou threads par cœur peut donner des gains en performance. C'est d'autant plus vrai qu'un processus ou thread peut être bloqué en attente de données de la mémoire pendant qu'un autre a encore assez de données pour continuer à travailler pendant ce temps. Selon les applications, des gains potentiels existent en utilisant plus d'un thread hardware par cœur.

Le choix du nombre de processus par nœud se fait via l'option --ranks-per-node de la commande runjob (voir la page sur runjob). Par exemple :

runjob --np 2048 --ranks-per-node 32 : ./my_executable my_arg1 my_arg2

Les valeurs acceptées sont 1, 2, 4, 8, 16, 32 et 64. La mémoire disponible par processus est déterminée en prenant 16 Go divisé par cette valeur (en réalité, elle est légèrement inférieure car une petite partie de la mémoire est réservé au système d'exploitation). Voici un tableau illustrant les différentes possibilités :

--ranks-per-node Nombre de processus MPI/noeud Nombre maximum de threads par processus Mémoire max par processus MPI
1 1 64 16 Go
2 2 32 8 Go
4 4 16 4 Go
8 8 8 2 Go
16 16 4 1 Go
32 32 2 512 Mo
64 64 1 256 Mo

Taille du bloc d'exécution

Chaque travail est lancé dans un bloc. Un bloc est un ensemble de nœuds de calcul. A cause de l'architecture matérielle de la machine, un bloc doit contenir un multiple de 64 nœuds de calcul (soit un multiple de 1024 cœurs). Attention : seules certaines tailles de blocs sont autorisées : 64, 128, 256, 512, 1024, 2048 ou 4096 nœuds. En cas de choix différent, le système rejetera le travail.

Sa taille est codée dans les travaux à l'aide de la directive LoadLeveler bg_size.

# @ bg_size = 64

Sur Turing, il est également possible d'exécuter des jobs n'utilisant qu'une partie des nœuds de calcul à l'intérieur d'un bloc (bg_size inférieur à 64). Dans ce cas, les ressources réseaux (tore 5D) peuvent être partagées avec d'autres travaux. Il peut donc y avoir un impact sur les performances des communications MPI et sur les entrées/sorties (un seul nœud d'I/O pour 64 nœuds de calcul). Ici aussi, seules certaines tailles de sous-blocs sont autorisées : 1, 2, 4, 8, 16 ou 32 nœuds de calcul.

Choix du nombre de processus MPI

Il faut aussi faire attention à la manière de coder le nombre de processus MPI dans la commande mpirun par rapport à la taille du bloc. Le nombre de processus MPI doit être spécifié dans la commande runjob (option --np).

Le nombre de processus MPI ne doit pas dépasser les ressources disponibles dans le bloc que vous avez alloué. Le nombre de processus MPI ne doit pas dépasser le nombre de nœuds de calcul multiplié par le nombre de processus par nœud spécifié avec l'option --ranks-per-node de runjob. Il est possible d'utiliser un nombre de processus inférieur à ces valeurs mais les ressources gaspillées vous seront comptabilisées.

Positionnement des processus MPI (mapping)

Le positionnement des processus MPI ou mapping des processus MPI sur les cœurs de la machine peut avoir une influence importante sur les performances des communications.

Le réseau de communication MPI de la Blue Gene/Q est caractérisé par une topologie de type tore 5D. Cela signifie que chaque nœud de calcul est connecté directement à 10 voisins (deux dans chaque direction).

Si toutes les communications point-à-point sur la machine ont lieu entre des processus voisins dans la topologie, il sera possible de s'approcher des performances maximales de ce réseau et ainsi d'obtenir des performances très élevées pour les communications et une très bonne extensibilité.

Tore 5D

Si les communications ont lieu avec des processus lointains dans la topologie, les performances seront réduites car la latence augmentera mais également le trafic sur les liens entre chaque nœud de calcul (en effet, un message devra traverser plusieurs liens pour aller jusqu'à son destinataire). Il faut donc essayer que les processus qui communiquent ensemble soient proches dans la topologie.

Les cinq axes de la topologie sont nommés A, B, C, D et E. Une sixième dimension, appelée T, existe et correspond aux différents processus à l'intérieur d'un nœud de calcul.

Par défaut, la numérotation des processus MPI est faite selon un mapping ABCDET. Cela signifie que les processus sont placés consécutivement en faisant varier la dernière coordonnée T, ensuite E, puis D, C, B et A. Le mapping conseillé est aussi ABCDET. Il devrait donner de bonnes performances pour la majorité des codes car les processus de rangs proches seront également plus proches physiquement.

Le choix du mapping se fait via l'option --mapping de runjob. Elle prend soit un mapping prédéfini de la forme ABCDET et toutes ses permutations (TABDCE, BATCDE…), soit le nom d'un fichier de mapping. Un fichier de mapping doit contenir une ligne par processus MPI et chaque ligne doit donner les coordonnées du processus correspondant au numéro de la ligne dans la topologie (un entier par dimension A, B, C, D, E et T séparés par des espaces).

Exemple de soumission d'un travail avec un mapping TEDCBA :

job.ll
# @ shell            = /bin/bash
# @ job_type         = BLUEGENE
# @ job_name         = job_tedcba
# @ output           = $(job_name).$(jobid)
# @ error            = $(job_name).$(jobid)
# @ wall_clock_limit = 1:00:00
# @ bg_size          = 64
# @ queue
 
runjob --np 2048 --ranks-per-node 32 --mapping TEDCBA : ./my_executable my_arg1 my_arg2