Placement sur GPU

Placement sur GPU

Transformation par annotation de directives

Les solutions présentées dans cette section permettent de transformer un code source en programme parallèle grâce à des directives placées dans le code source original. L’utilisation d’annotations permet de conserver la forme originale du code séquentiel. Dans le cas où une architecture autre que le GPU viendrait à être utilisée, le code source original resterait ainsi compilable, en ignorant les directives ajoutées. Cela représente un plus indéniable en terme de portabilité. Au final, ces directives correspondent à un langage haut niveau. Celui-ci permet de demander des transformations à des compilateurs intégrant un interpréteur pour le langage de directives utilisé. Deux approches se distinguent au sujet des directives dans cet état de l’art. Certaines solutions comme OpenMP C to Cuda ou OpenMPC décrites dans les sections 2.1.4 et 2.1.5, ont choisi d’adapter aux GPUs les spécifications du standard Open Multi-Processing (OpenMP) présenté en section 2.1.3. Le sujet est loin d’être trivial car OpenMP a été initialement défini pour répondre aux problématiques des CPUs. D’autres en revanche, comme Mint ou hiCUDA décrits dans les sections 2.1.6 et 2.1.2, proposent leur propre modèle de directives, ainsi que l’interpréteur associé. Au final, OpenACC, décrit dans la section 2.1.8, a permis de converger vers un nouveau standard de directives mais pour accélérateurs cette fois. Comme pour OpenMP, le consortium d’OpenACC fournit une spécification de langage de directives. Différentes implémentations d’interpréteurs sont disponibles comme celle de PGI accelerator en section 2.1.9

HMPP

Hybrid Multicore Parallel Programming (HMPP) [50, 54] est un compilateur commercial qui a été développé par CAPS entreprise. Il permet de porter sur GPU du code C ou Fortran annoté au moyen de directives de type pragma hmpp. Ces directives permettent de gérer les transferts de données, ainsi que des fonctions appelées codelets pour un accélérateur cible. Dans le cadre des GPUs, ces fonctions correspondent aux kernels. HMPP gère l’utilisation simultanée de plusieurs accélérateurs de calculs au sein d’une architecture globale hétérogène. Pour cela, il permet au programmeur de découper un programme  en plusieurs sous-ensembles, chacun d’entre-eux étant adapté à une architecture cible. La distribution des calculs sur les différents GPU est réalisée de manière dynamique par le runtime de HMPP. Dans le cas où aucun GPU ne serait disponible, l’implémentation native est exécutée sur le CPU. HMPP ne semble plus actif en 2018. Un standard portant le nom d’Open HMPP a été dérivé de la version 2.3 de HMPP. L’entreprise CAPS ayant été impliquée dans le développement du standard OpenACC avant de faire faillite, nous considérerons OpenACC présenté dans le chapitre 2.1.8 comme l’évolution de HMPP. 2.1.2 hiCUDA hiCUDA [71, 72, 73] est un compilateur source-à-source fondé sur Open64. Ce compilateur utilise en entrée du code séquentiel C ou C++ annoté au moyen d’un langage de haut niveau. Les zones de code ainsi délimitées par des directives ’#pragma hicuda’ sont transformées de manière inter-procédurale en kernels CUDA afin d’être exécutés sur GPU. La phase d’analyse, et notamment l’analyse des dépendances, reste manuelle. Le placement des directives dans le code source original se fait au moyen d’un outil d’analyse ou par un programmeur ayant évalué le contenu du code source. Seules les analyses des régions de tableaux et des flots de données sont automatisées par les modules afférents d’Open64. De ce fait, hiCUDA permet de calculer les espaces mémoires utilisés par chaque kernel. Au final, cette donnée permet de ne transférer que les espaces ou sous-espaces mémoires nécessaires sur le GPU. Enfin, hiCUDA est complémentaire à CUDA-Lite présenté dans le chapitre 2.4.1. Celui-ci permet d’améliorer l’utilisation des shared memory dans le GPU à partir de code CUDA. Dans le cadre de hiCUDA, leur méthodologie de portage d’algorithmes séquentiels sur GPU [72] est composée de cinq étapes : 1. l’outlining permettant de créer le kernel encapsulant le code source annoté, 2. le tiling pour définir le placement des threads sur les niveaux hiérarchiques du GPU, 3. la génération des communications entre le CPU et le GPU, 4. l’optimisation de la bande passante d’accès aux données par sélection des mémoires du GPU, 5. l’optimisation du code source intégré dans le kernel. Les directives développées par Han et Abdelrahman [71] sont réparties dans deux catégories : le modèle de calculs et le modèle de données. Le modèle de calculs fournit quatre actions : — la création et l’encapsulation de code séquentiel dans un kernel, — le partitionnement cyclique ou en blocs des boucles sur les blocks de threads du GPU ou uniquement une distribution cyclique sur les threads du GPU pour des soucis de coalescence des accès aux données, — le placement de code sur un unique thread pour chaque block, — la synchronisation des threads au moyen de barrières. Le modèle de données fournit trois actions permettant d’utiliser la mémoire constante, la mémoire globale et la shared memory. La gestion des tableaux de taille dynamique est aussi permise par une directive associée dans le modèle de données. Enfin, hiCUDA ne gère pas l’utilisation de la texture memory sur les GPUs.

 OpenMP

OpenMP est aujourd’hui devenu un standard pour la parallélisation de code sur CPU à partir de code C, C++ ou Fortran. Le consortium OpenMP Architecture Review Board responsable d’OpenMP, publie régulièrement les spécifications d’un langage haut niveau définissant des directives. Le placement dans le code source original de ces directives peut être effectuée manuellement par le développeur ou au moyen d’un paralléliseur automatique de code tel que ROSE [140, 141] ou Pluto [28, 29]. Des compilateurs tels que GCC, Intel ISL ou encore LLVM [86] intègrent un interpréteur de directives OpenMP dont le rôle est d’appliquer les transformations de code adéquates au placement du code original sur architecture parallèle. Le modèle OpenMP fork-join, caractérisé par un thread maître dirigeant un pool de threads worker est comparable au fonctionnement du GPU. Ainsi, la révision 4.0 des spécifications d’OpenMP publiée en 2013 apporte de nouvelles directives permettant aux compilateurs de déporter des portions de code sur différents types d’accélérateurs dont les GPUs [99]. Enfin, on retiendra qu’OpenMP est principalement basé sur le parallélisme de boucles comme pour les GPUs.

« OpenMP C to CUDA »

OpenMP C to CUDA [110, 109] est un compilateur source-à-source permettant de transformer automatiquement un code annoté avec des directives OpenMP en code CUDA. Le compilateur OMPi [49] a servi de base pour son implémentation et les Scanner et Parser de code d’OMPi ont été étendus pour prendre en compte les directives Cuda. Concernant les transformations de code, la directive omp parallel est transformée en générant dans l’ordre : les allocations mémoires sur GPU, les transferts des données du CPU vers les zones mémoire GPU précédemment allouées, l’appel du ou des kernels concernés, les transferts de données du GPU vers le CPU et enfin les désallocations mémoire sur GPU. Les boucles annotées avec la directive omp for sont transformées en un kernel CUDA en utilisant les techniques d’outlining détaillées à la suite. OpenMP C to CUDA est cependant limité à un unique niveau de boucle omp for. L’analyse des bornes et du pas d’itération de la boucle for concernée permet de définir la quantité de threads CUDA à allouer. Le corps de la boucle for est modifié avant d’être transféré dans le kernel CUDA. Les indices de boucles sont ainsi recalculés pour s’adapter à l’architecture en blocks/threads du GPU et les variables sont modifiées en fonction de leur statut OpenMP, shared ou private. Les variables scalaires et les tableaux de type shared sont transférés dans la mémoire globale du GPU avant le lancement du kernel et sont récupérés ensuite par le CPU. Pour chaque variable scalaire private, un tableau de la taille du nombre de threads est alloué dans la mémoire globale du GPU. De ce fait chaque thread aura sa propre variable privée. Si cette technique permet de réduire l’occupation des registres, les performances devraient cependant être pénalisées du fait de l’utilisation systématique de la mémoire globale du GPU. Enfin, il est à noter que seuls les tableaux à bornes statiques sont pris en compte. À notre connaissance, aucune optimisation visant à réduire la redondance des transferts mémoire n’est utilisée. Pour les portions de code CPU, afin de conserver une compatibilité avec le standard C99, la configuration et le lancement des kernels CUDA utilisent l’implémentation CUDA driver API [125] du langage CUDA détaillée dans la section 1.4.3.

OpenMPC

Comme OpenMP C to CUDA détaillé au chapitre 2.1.4, OpenMPC [90, 89, 91] est un compilateur source-à-source permettant de transformer automatiquement un code annoté 28 CHAPITRE 2. ÉTAT DE L’ART : PLACEMENT SUR GPU avec l’API OpenMP en code CUDA. Il aborde notamment les problématiques liées à l’utilisation de directives spécifiquement définies pour CPU afin de générer du code GPU. Le challenge repose ici sur la gestion des divergences architecturales entre CPU et GPU. La méthodologie de traduction et d’optimisation source-à-source se décompose en étapes successives développées en utilisant le framework de compilation Cetus [87]. Le code source est analysé par le Cetus Parser afin de générer une représentation intermédiaire au format Cetus IR qui servira de base de travail pour les transformations. OpenMPC a la particularité de réutiliser les directives standards OpenMP. Ainsi, les directives OpenMP parallel sont interprétées comme des kernels CUDA potentiels et chaque itération de directives for ou sections est distribuée sur les threads du GPU. Les directives de synchronisation donnent lieu à une séparation du kernel concerné en deux sous-kernels afin de respecter les dépendances de données. Des directives supplémentaires peuvent aussi être fournies par l’utilisateur au moyen d’un fichier annexe, ce qui permet de ne pas modifier les directives OpenMP existantes. Celles-ci seront appliquées pour chacune des régions de code correspondantes. De même, un fichier définissant des variables d’environnement peut être utilisé pour définir les spécifications du GPU ciblé lors des phases d’optimisation et de transformation de code. OpenMPC permet d’améliorer la coalescence des données entre threads dans un block au moyen de deux transformations de code. La première, parallel loop-swap, permute deux boucles parallèles au sein d’un même nid lorsque celles-ci sont régulières et mal ordonnancées afin d’essayer de se ramener à un pas unitaire d’accès au données. Dans le même but, la seconde, loop-collapsing, compresse deux boucles de profondeurs différentes lorsque les fonctions d’accès aux données sont au contraire irrégulières. Ce cas provient généralement de l’utilisation de code à contrôle dynamique ou de tableaux à accès indirect. Les fonctions d’accès ne peuvent alors être résolues lors de la compilation.

Formation et coursTélécharger le document complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *