Tests de scalabilité avec la MKL

ATTENTION - Cet article date de 2014, les tests ont été effectués sur Eos (le supercalculateur précédent Olympe). Eos avait 20 cœurs par nœud (40 si on utilisait l'hyperthreading)

Ces tests de passage à l'échelle sont effectuées en mémoire partagée.

Nous traitons ici différents cas, puisque nous faisons varier aussi bien la taille de la matrice carrée (5000, 10000, 25000 et 50000 lignes/colonnes), que le nombre de cœurs sur lequel est lancé ce calcul : 1 cœur (sequentiel), 5 cœurs, 10 cœurs, 20 cœurs, 40 cœurs (utilisation de l'hyperthreading, Eos avait 20 cœurs physiques par nœud).

Dans l’environnement par défaut

Squelette du script testé dans l’environnement par défaut :

#!/bin/bash
#SBATCH -J script_art421
#SBATCH -N 1
#SBATCH -n 1
#SBATCH --threads-per-core=1
#SBATCH --cpus-per-task=5

workdir=${SLURM_SUBMIT_DIR}/JOB_${SLURM_JOBID}
mkdir ${workdir}
cd ${workdir}
cp $0 .
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
MKL_NUM_THREADS=$OMP_NUM_THREADS

srun -N 1 -n 1 -c $OMP_NUM_THREADS ./mon_appli.exe > output_${SLURM_JOBID}.log

On s’intéresse au speed up de ce calcul, à savoir le ratio du temps de calcul en séquentiel (sur 1 cœur) sur le temps de calcul en parallèle sur n cœurs :

Sp=Tséquentiel/Tparallèle

Plus ce ratio se rapproche du nombre de cœurs utilisés pour le calcul parallèle, plus l’efficacité parallèle de ce code est bonne. Par exemple : si avec 10 cœurs on va dix fois plus vite qu’en séquentiel, alors le speed up vaut 10 et il est donc idéal.

Pour l’exemple traité ici, le speed up a été calculé sur la base des temps de restitution ci-dessous :

Temps de restitution
  Taille de la matrice
Cœurs 5000 10000 25000 50000
1 6,54 47,53 738,05 5886
5 2,11 10,50 149,85 1190
10 0,85 5,47 75,36 598
20 0,96 5,12 61,81 444
40 1,39 4,32 52,17 410
Speed-up associé
  Taille de la matrice
Cœurs 5000 10000 25000 50000
1 - - - -
5 3,11 4,53 4,93 4,95
10 7,74 8,69 9,79 9,84
20 6,79 9,29 11,94 13,25
40 4,69 11,01 14,15 14,34

Courbe de scalabilité (ou courbe d’extensibilité) : courbe affichant le speed up en fonction du nombre de cœurs sur lesquels est lancé le calcul

Ci-dessous la courbe de scalabilité associée au code calculant le résultat de la multiplication de deux matrices carrées non vides de différentes tailles (5000, 10000, 25000 et 50000 lignes/colonnes).
On peut voir que pour 5 cœurs (respectivement 10 cœurs), quelque soit la taille du problème, le speed up est très bon puisqu’il est très proche de 5 (respectivement 10).

Par contre pour le problème de taille 5000, augmenter encore le nombre de cœurs dégrade le speed up. La scalabilité dépend donc de la taille du problème étudié.

courbe de scalabilité

En initialisant la variable : OMP_PROC_BIND=true

Cette variable assure que lorsqu’une tâche est lancée sur un cpu, elle reste bien sur celui-ci. De plus, elle répartie les tâches uniquement sur les cœurs physiques, et dans l’ordre. Ainsi pour un nombre de tâches égal à 10, les threads occuperont les cœurs de 0 à 9.

Les temps de restitution que nous obtenons alors sont les suivants :

Temps de restitution
  Taille de la matrice
Cœurs 5000 10000 25000 50000
1 6,39 47,98 737.35 5910
5 1,92 9,99 149,43 1191
10 1,40 5,52 75,51 595
20 0,85 3,56 38,60 302
40 0,76 4,21 63,47 533
Gain relatif
  Taille de la matrice
Cœurs 5000 10000 25000 50000
1 2% -1% 0% 0%
5 9% 5% 0% 0%
10 -65% -1% 0% 1%
20 12% 30% 38% 32%
40 45% 2% -22% -30%

Pour le problème de petite taille (n=m=5000), les temps de restitutions sont très faibles (<2s) et varient d’un job à un autre, ce qui empêche de tirer une conclusion claire.
Pour les problèmes de plus grande taille (n=m=25000 et 50000), on voit que lorsqu’on utilise moins de la moitié du nœud (nombre de cœurs < ou = à 10), sans spécifier de placement particulier, les tâches se répartissent "naturellement" sur des cœurs différents. Lorsqu’on les place avec l’option OMP_PROC_BIND, cette répartition est plus "propre", mais ne change pas les temps de calcul.
Par contre lorsqu’on utilise la totalité du nœud sans spécifier de placement, certaines tâches vont se placer sur des cœurs physiques et leur cœurs logique associé, créant de l’hyperthreading. Avec l’option OMP_PROC_BIND toutes les tâches sont placées sur les cœurs physiques, on gagne systématiquement 30% de temps de calcul (pour cet exemple) et on améliore grandement le speed up. Pour les problèmes de grande taille, il est presque idéal : lorsqu’on fait tourner un job sur x cœurs, il va x fois plus vite qu’en séquentiel (sauf si on utilise l' hyperthreading).

Speed-up associé
  Taille de la matrice
Cœurs 5000 10000 25000 50000
1 - - - -
5 3,33 4,80 4,93 4,96
10 4.57 8,69 9,76 9,94
20 7,51 13,49 19,10 19,56
40 8,36 11,40 11,62 11,09

Courbe de scalabilité avec variable OMP_PROC_BIND

  1. En pointillés : speed up précédent
  2. En traits pleins : speed up avec la variable OMP_PROC_BIND=true
  3. 40 cœurs, soit 20 cœurs physiques + 20 cœurs logiques (hyperthreadés)

Voir aussi

FAQ

Questions fréquentes et messages d'erreurs

Utiliser la MKL avec les compilateurs Intel

Nul besoin de charger un module spécial pour utiliser la mkl : la mkl est intégrée dans tous les modules intel (la version 18.2 est chargée dans l’environnement par défaut sur Olympe)