• Aucun résultat trouvé

3.2 Calculs généralistes sur processeurs graphiques

3.2.2 Modèles de programmation

Différents choix s’offrent aux développeurs pour tirer profit de la puissance de calcul des GPU dans le cadre du développement d’outils généralistes (non graphiques) avec des degrés de complexité et d’intrusivité dans le code distincts :

– programmation directe avec des langages bas niveaux type CUDA ou OpenCl ; – optimisation à l’aide de directives de compilation avec HMPP ou OpenAcc ;

– appel de bibliothèques externes optimisées pour les GPU telles que CUBLAS ou CUFFT. Nous passons en revue ces différents choix dans les paragraphes suivants.

Langages de programmation et API dédiés

C’est la voie la plus complexe mais également celle qui permet d’exploiter au mieux la puissance des GPU grâce à des langages adaptés à leur architecture particulière. CUDA a été le premier langage du genre. Dans un effort de standardisation OpenCl est apparu ensuite. CUDA Pour permettre aux développeurs non rompus à la programmation graphique d’exploi-ter les puissances de calcul offertes par les GPU, NVIDIA a introduit la couche d’abstraction CUDA. Ce modèle de programmation est basé sur une approche SPMD : le programmeur écrit une portion de code (le kernel) qui sera exécutée par plusieurs threads en parallèle en utilisant des données différentes. CUDA définit une hiérarchie de threads illustrée sur la figure 3.6 dans

Fig. 3.6: Modèle de programmation CUDA : des blocs de threads organisés en grilles.

le but d’organiser les calculs selon une certaine topologie. Ainsi les threads sont regroupés au sein de blocs de threads(thread blocks) qui peuvent être de dimensionnalité un, deux ou trois. Ces blocs sont à leur tour organisés en grilles 1D ou 2D. Cette organisation fait le lien entre l’exécution des threads et l’architecture matérielle des GPU. En effet, les threads appartenant au même bloc s’exécuteront sur le même SM, ce qui permet d’introduire des mécanismes de partage de données et de synchronisation. Les threads seront regroupés et traités par warps de 32 threads. Ce regroupement, qui n’était qu’une abstraction matérielle, permet depuis les GPU de compute capability 1.2 d’introduire des mécanismes de vote entre threads d’un même warp. Sur les architectures où les SM sont composés de 8 SP, le traitement d’un warp se fait en 4 cycles d’horloge en SIMD. Plus précisément, NVIDIA préfère utiliser l’acronyme SIMT (Single

Instruction Multiple Threads) pour décrire le modèle de fonctionnement des GPU. En effet, dans un modèle SIMD classique, tel que pour les instructions SSE des CPU, le programmeur (ou le compilateur) assemble les données (les vectorise) pour exécuter les mêmes traitements avant de les désassembler ensuite. De ce fait, il n’est pas possible de différencier les traitements en fonction des données. Dans le cas SIMT, il est possible d’effectuer cette différenciation en désolidarisant les threads lors de leur exécution. On parle alors de threads divergents. Dans le cas de divergence de threads d’un même warp, les traitements sont exécutés en série, ce qui induit une perte de performance. Les GPU avant Fermi ne permettaient de gérer qu’une seule grille de threads (et donc un seul noyau de calcul) à la fois. Avec Fermi a été introduite la possibilité de gérer plusieurs grilles (jusqu’à 16 actuellement et autant de noyaux) simultanément. Ceci a pour effet d’augmenter les possibilités de recouvrement entre calculs et accès aux données.

Différents niveaux de mémoire existent sur la carte graphique. Ces niveaux diffèrent selon qu’ils sont accessibles en écriture ou non par les threads, mis en cache ou non, visibles par tous les threads ou pas. La Figure 3.7 illustre schématiquement les différents espaces disponibles et les possibilités d’accès et de partage qui y sont associées. Les flèches indiquent les sens des accès possibles (par exemple accès en lecture seule à la mémoire constante de façon cohérente entre

3.2. Calculs généralistes sur processeurs graphiques 49 Global Memory Constant Memory Texture Memory Thread Block (0,0) Shared Memory Registers Registers Thread(0,0) Thread(1,0) Local Memory Local Memory Thread Block (1,0) Shared Memory Registers Registers Thread(0,0) Thread(1,0) Local Memory Local Memory

Fig. 3.7: Hiérarchie mémoire selon le modèle de programmation CUDA.

CUDA OpenCL

Streaming Multiprocessor (SM) Compute Unit (CU) Streaming Processor (SP) Processing Element (PE)

Shared Memory Local Memory

Kernel Kernel

Thread Work item

Thread block Work group

Tab. 3.1: Equivalences entre les terminologies CUDA et OpenCL.

tous les threads et accès en lecture et en écriture aux registres privés pour chaque thread). OpenCL L’effort de développement de OpenCL initié par Apple a été placé sous la houlette du Khronos Group (gérant également le standard OpenGL). OpenCL vise à offrir une plateforme de développement commune pour la variété d’architectures parallèles existantes. Il existe ainsi des implémentations d’OpenCL pour les accélérateurs graphiques d’AMD et de NVIDIA ainsi que pour les processeurs généralistes x86. Divers projets d’implémentations visant les FPGA existent également. OpenCL peut être vu comme le pendant standardisé et multi-plateformes de CUDA. En effet, il existe des similitudes marquées entre les deux plateformes, aussi bien au niveau de l’architecture que du modèle de programmation, seule une différence de vocabulaire faisant la différence. Le tableau 3.1 reprend certaines équivalences entre concepts CUDA et OpenCL. Nous proposons en Annexe un comparaison complète entre les implémentations en CUDA et en OpenCL d’un solveur 2D de l’équation d’onde.

Directives de compilation : HMPP et OpenAcc

Pour faciliter la tâche du programmeur désirant tirer parti des possibilités des GPU sans pour autant faire de la programmation graphique ou être lié à un type particulier de cartes (ou même d’accélérateurs), sont apparus des outils de plus haut niveau permettant de délé-guer la génération de code adapté aux accélérateurs au compilateur suivant des annotations du programmeur formulées sous la formes de directives de compilation. HMPP, développé par la société CAPS, et OpenAcc, standard émergeant développé par CAPS, NVIDIA, PGi et Cray, sont deux implémentations de ce modèle.

Au cours de nos travaux, nous avons utilisé HMPP (Hybrid Multicore Parallel Processing) pour développer certaines des applications qui seront décrites dans la seconde partie de ce manus-crit. HMPP propose une ensemble d’outils permettant de gérer, et éventuellement de générer, du code pouvant s’exécuter sur divers types d’accélérateurs (SSE, CUDA ou encore OpenCL dans sa version la plus récente) par le biais principalement d’annotations introduites dans le programme d’origine. HMPP est une solution séduisante à plus d’un titre. Tout d’abord, l’utili-sation des annotations (#pragma à la OpenMP) permet de garder l’intégrité du code d’origine qui pourra toujours être compilé et exécuté sous sa forme initiale. Ensuite le support d’exécution offert par HMPP permet de gérer dynamiquement la présence d’accélérateurs matériels et d’exé-cuter le cas échéant le code disponible sur la plateforme la plus adaptée (possibilité d’utiliser des conditionnelles sur la taille des données par exemple). C’est cette facette que nous avons le plus exploitée dans le cadre de ces travaux. Au cours de l’avancement de cette thèse, le générateur HMPP permettant de générer le code GPU a gagné en efficacité, ce qui en a fait une alternative intéressante à l’écriture de code CUDA.

OpenACC adopte une approche très similaire à celle de HMPP. A la différence de HMPP, OpenACC est cependant un standard supporté actuellement par les compilateurs CAPS, PGI et Cray. Des efforts sont actuellement menés pour intégrer ce standard au sein d’OpenMP. Bibliothèques GPGPU

Diverses implémentations GPU de bibliothèques de calcul largement utilisées en calcul scien-tifique permettent aux développeurs d’exploiter la puissance de calcul des GPU sans effort de programmation particulier, en substituant simplement les appels à ces bibliothèques aux appels aux bibliothèques standards et en gérant éventuellement les transferts de données entre hôte et accélérateur. On citera à titre d’exemple cuBLAS [101], cuSparse [105] et MAGMA [1] pour l’algèbre linéaire, cuFFT [102] pour le calcul de transformées de Fourier, NVIDIA Performance

Primitives [107] pour le traitement du signal et des images, etc.