• Aucun résultat trouvé

8.2 Éléments d’implémentation de SPECFEM3D/S_GPU 2

8.2.2 Deux stratégies de cohérence mémoire

Dans notre mise en œuvre hybride de la boucle en temps, les résultats CPU et les

résultats GPU doivent être cohérents. L’exécution des différentes parties de la boucle

principale doit en effet pouvoir utiliser des données valides sur CPU et GPU. Cette section

propose de présenter comment la cohérence mémoire a été mise en place pour les parties

1 et 3 avant de présenter deux approches de cohérence pour la partie 2 (appelées stratégie

A et stratégie B).

Cohérence mémoire des parties 1 et 3 Plutôt que de découper le calcul en une

partie CPU et une partie GPU puis d’effectuer des transferts pour assurer la cohérence

des deux espaces mémoire, nous avons préféré répliquer le calcul et éviter ainsi de coûteux

transferts. Les parties 1 et 3 sont donc exécutées entièrement sur CPU et sur GPU, et la

cohérence mémoire est donc naturellement assurée.

Cohérence mémoire de la partie 2 L’organisation kernel/mémoire de la partie 2 est

représentée sur la figure 8.6 : CPU et GPU traitent un ensemble différent de kernels. Cette

figure permet de visualiser les kernels exécutés et les zones mémoire impliquées : la partie

CPU traite les kernels 1 et 2 et la partie GPU le reste (kernel 3 à n). Dans chaque zone

mémoire un tableau accel privé est défini, il y a donc, dans notre exemple, un tableau

accelCPU et un tableauaccelGPU. À la fin de l’exécution de tous les kernels de calcul,

il faut que les différents espaces mémoire soient identiques.

Les données modifiées par la succession des exécutions du kernel de la partie 2 sont

contenues dans le tableauaccel. Ce sont donc les données de ce tableau qui doivent être

identiques entre les mémoires CPU et GPU : les parties CPU et GPU contiennent en effet

chacune un tableau accel local. À la fin de la partie 2, les deux tableaux accel locaux

aux GPU et CPU doivent être identiques.

Deux actions sont nécessaires pour assurer cette cohérence :

1. Fusion des données. Un opérateur, permettant de fusionner les tableauxaccelCPU

et GPU, doit être fourni par le programmeur. La place de cet opérateur est la fonction de

regroupement. Cet opérateur, qui est représenté sur la figure 8.6, doit prendre en compte

le fait qu’un élément du tableau mémoire ait pu être modifié du côté CPU et du côté GPU.

Sur la figure 8.6, un calcul CPU et un calcul GPU ont modifié tous deux la case mémoire

7 de leur version locale du tableauaccel. Dans le tableau fusionné, la case numéro 7 doit

donc réunir les résultats CPU et GPU.

2. Mise à jour des copies locales de accel. Les tableaux accel de la mémoire CPU

et GPU doivent être mis à jour pour qu’ils soient identiques et contiennent les données

fusionnées.

8.2. ÉLÉMENTS D’IMPLÉMENTATION DE SPECFEM3D/S_GPU 2 145

Figure 8.6 –Vue générale de l’hybridation de la partie 2 de SPECFEM3D

Pour assurer cette cohérence, et mettre en place la fusion des données et la mise à jour

des tableaux, nous avons mis en place deux stratégies qui sont discutées dans la suite de

cette partie.

8.2.2.1 Stratégie A : quelques « gros » transferts

Nous décrivons ici la première stratégie sur laquelle nous avons travaillé qui est

repré-sentée sur la figure 8.7. Les opérations effectuées par cette stratégie sont définies dans la

fonction de regroupement du calcul hybride. Ainsi, les opérations permettant de fusionner

les mémoires CPU et GPU sont effectuées après l’exécution des k kernels sur le CPU et

des(nk) kernels sur le GPU.

Une propriété de l’algorithme de la partie 2 permet d’assurer que pour créer un vecteur

accel

complet

qui comporte les contributions CPU (stockés dansaccel

CP U

) et les

contribu-tions GPU (dansaccel

GP U

), il suffit de sommer, éléments à éléments, ces deux vecteurs,

ce qui peut s’écrire :accel

CP U

[1..n] +accel

GP U

[1..n] =accel

complet

[1..n].

Ainsi, pour mettre en œuvre la stratégie A, nous avons créé une fonction de

regroupe-ment exécutant les trois étapes suivantes (qui sont visibles sur la figure 8.7) :

1. Transfert deaccel

GP U

depuis la mémoire GPU vers la mémoire CPU

2. Addition de accel

CP U

etaccel

GP U

dansaccel

complet

3. Transfert deaccel

complet

depuis la mémoire CPU vers la mémoire GPU

Après l’étape 1 et 2, la mémoire centrale contient les contributions CPU et GPU dans

accel

complet

. Cependant, nous avons vu qu’à la fin de la partie 2, accel

GP U

et accel

CP U

doivent être identiques : c’est l’étape 3 qui réalise cela en transférant le tableauaccel

complet

vers la mémoire GPU. Ainsi, puisque l’on transfère deux fois un tableau accel de n

éléments, le volume de données transféré est égal à2.n.

Cette approche est simple à mettre en place mais elle implique des transferts de

don-nées que l’on peut qualifier d’inutiles. En effet, dans la mémoire CPU (respectivement dans

la mémoire GPU), les kernels ne modifient qu’une partie du tableauaccel(puisqu’ils sont

répartis entre CPU et GPU). Ainsi, lors des étapes 1 et 3, des données non modifiées sont

Figure 8.7 –Détails des mises à jour mémoire de la stratégie A

transférées.

Cette stratégie ne sélectionne pas de manière précise les données à transférer ce qui

implique des transferts de zones mémoire non modifiées. Pour réduire ces transferts de

données, nous avons mis en place une autre stratégie appelée stratégie B.

8.2.2.2 Stratégie B : plusieurs « petits » transferts

Nous présentons ici une deuxième stratégie qui a demandé des investigations plus

pous-sées dans la compréhension du code de SPECFEM3D et qui est représentée sur la figure

8.8. L’objectif de cette stratégie est de réduire les transferts mémoire par rapport à la

stratégie A et d’utiliser la capacité de recouvrement transfert-calcul des cartes GPU.

La cohérence mémoire est réalisée en trois étapes :

Étape 1 : calcul des éléments à transférer Cette étape consiste à déterminer, pour

chaque kernel exécuté de la partie 2, un ensemble d’éléments du tableau accel modifiés

par l’exécution d’un kernel particulier. Les éléments sont sélectionnés de telle sorte qu’ils

ne seront pas modifiés par les kernels lancés par la suite. Il y a ainsi autant d’ensembles

d’éléments que d’exécution de kernels.

Grâce à ces ensembles d’éléments, il est possible, lors de l’exécution d’un kernel 2, de

transférer immédiatement les éléments de ces ensembles dans un autre espace mémoire.

Ce transfert est valide puisque ces ensembles d’éléments garantissent que les données

transférées ne seront pas modifiées dans la suite du calcul de la partie 2 de la boucle en

temps.

Sur la figure 8.8, la première exécution du kernel (appelé k1), modifie les éléments

(2,7,8,9,10) du tableauaccel. Cependant, l’élément 9 est également modifié par l’exécution

8.2. ÉLÉMENTS D’IMPLÉMENTATION DE SPECFEM3D/S_GPU 2 147

suivante du kernel (appelé k2). Ainsi, seuls les éléments (2,7,8,10) sont transférés à la fin

de k1. L’élément 9 est transféré ensuite, lorsque k2 a fini son exécution.

Le calcul des ensembles doit traiter plusieurs tableaux contenant plusieurs millions

d’éléments. Elle est très coûteuse en temps et est donc lancée une fois à l’initialisation de

SPECFEM3D, bien avant la boucle principale du code.

Étape 2 : transferts de données partiels Nous avons modifié le code du kernel 2

afin qu’après le calcul proprement dit, les CPU (respectivement les GPU) envoient les

élé-ments du tableauaccel, calculés à l’étape 1, vers la mémoire GPU (respectivement CPU).

L’exécution du kernel suivant peut se faire pendant le transfert mémoire car, par

construc-tion, l’étape 1 crée un ensemble d’éléments qui ne seront pas modifiés par l’exécution d’un

kernel suivant.

Les éléments à transférer sont répartis de manière non contiguë dans le tableauaccel.

Plutôt que d’initier un grand nombre de transferts d’un seul élément, peu efficace, nous

avons préféré regrouper chaque élément à transférer dans un tableau intermédiaire avant

d’initier le transfert de ce tableau intermédiaire. Nous considérons sur la figure 8.8, quatre

exécutions de kernel (k1, k2, k3 et kn), il y a donc quatre transferts partiels de données

contenus dans des tableaux intermédiaires.

La stratégie B consiste donc à lancer un kernel, puis, à la fin de son exécution, de

trans-férer, grâce aux informations calculées à l’étape 1, les données qu’il a modifiées.

Simulta-nément au transfert, le kernel suivant peut être lancé. Cela permet deux optimisations par

rapport à la stratégie précédente :

1. Optimiser l’exécution grâce à des recouvrements calcul-transfert, puisque lors d’un

transfert, l’exécution d’un kernel a lieu.

2. Réduire les données transférées : l’étape 1 construit une information qui permet de

transférer uniquement des points modifiés. Ainsi, contrairement à la stratégie A,

seul les éléments deaccelindispensables sont transférés ce qui réduit le volume de

données à transférer.

Étape 3 : Fonction de regroupement Les transferts mémoire étant fractionnés et

lancés directement après chaque kernel, la fonction de regroupement n’initie aucun

trans-fert. Elle a cependant la charge de regrouper ensemble chaque fraction de données, et ce

sur la partie CPU et sur la partie GPU. Comme les données modifiées ne sont pas

conti-guës en mémoire, nous avons vu que les données étaient transférées grâce à des tableaux

intermédiaires. La fonction de regroupement va donc reconstruire les données à partir de

ces tableaux avant de les fusionner.

La stratégie B réduit la taille des transferts mais complexifie grandement son

inté-gration dans SPECFEM3D. Plusieurs calculs supplémentaires, absents de la stratégie A,

sont en effet nécessaires. L’évaluation de ces deux stratégies, dans la partie suivante, nous

permettra ainsi de comprendre la meilleur voie à adopter.

Figure 8.8 –Détails des mises à jour mémoire de la stratégie B

8.3 Performance de SPECFEM3D/S_GPU 2

Dans cette partie, nous allons évaluer les performances des calculs hybrides de notre

version de SPECFEM3D adaptée à S_GPU 2. Il s’agira pour cela d’évaluer les stratégies

A et B. L’objectif est de comprendre s’il est préférable d’avoir un code de cohérence

mémoire simple, et donc de communiquer des données inutiles, comme la stratégie A,

ou, au contraire, d’avoir un code de cohérence mémoire complexe pour communiquer

uniquement ce qui a été modifié, comme la stratégie B.

Nous allons, pour cela, mener plusieurs expériences qui consistent à faire varier la

quantité de calculs attribuée à la partie GPU et à la partie CPU. Après la description des

expériences, nous présentons les performances atteintes par la stratégie A et la stratégie

B en prenant soin d’analyser le poids des transferts de données et des opérations de mise

à jour mémoire. La partie finale de cette étude est une comparaison des deux stratégies

avec le code original de SPECFEM3D sur un calculateur hybride.

8.3.1 Description des expériences

Dans ces expériences, il s’agit de faire varier le nombre d’exécutions de kernel de la

partie 2 attribué aux CPU et aux GPU. Dans le système physique utilisé, nous avons une

succession de 25 kernels à lancer ; nous commençons donc, pour chaque tâche MPI, par

exécuter un kernel sur les threads CPU et 24 sur le thread GPU, puis deux sur le CPU

et 23 sur le GPU et ainsi de suite jusqu’à 24 sur le CPU et 1 sur le GPU. Pour évaluer

l’effet de la puissance CPU nous utilisons tout d’abord 2 threads OpenMP par tâche MPI

(ce qui simulera un CPU de faible puissance) puis 5 threads par tâche MPI (nous aurons

ici un CPU de plus grande puissance).

8.3. PERFORMANCE DE SPECFEM3D/S_GPU 2 149