n°13
Septembre
2010
Programmation GPU

Les cartes graphiques ont toujours fait l’objet de beaucoup d’investissements et d’améliorations, étant donné les fortes demandes au niveau des performances de ces composants et la concurrence qui existe entre les différents producteurs (notamment ATI/AMD et Nvidia). Les traitements graphiques sont massivement parallèles. C’est pourquoi les GPU sont adaptés au calcul parallèle. Ces processeurs graphiques sont en effet composés d’une grande quantité de processeurs qui permettent l’exécution d’instructions sur un grand nombre de données de manière simultanée, et ainsi de réduire considérablement les temps de traitement graphiques.

Les évolutions des processeurs graphiques ont contribué à l’amélioration des cartes graphiques au point de rendre leur puissance de calcul très intéressante pour des applications non liées au graphisme. En effet, la puissance de calcul brute offerte est plus élevée de par le grand nombre de processeurs présents sur ces cartes. C’est pourquoi il n’est pas rare d’obtenir de grands facteurs d’accélération entre CPU et GPU pour une même application. De plus, le coût d’une carte graphique face au coût d’un cluster de calcul rend ce mode de programmation très attractif.

Depuis quelques temps, Nvidia a fait un grand pas dans le High Performance Computing en lançant CUDA (Compute Unified Device Architecture), une extension du langage C qui permet d’exploiter les capacités des cartes graphiques de la marque pour exécuter des instructions en parallèle.

Plus récemment, une collaboration entre les différents fournisseurs de CPU et de GPU a permis l’émergence du langage OpenCL (Open Computing Language). Ce langage, créé par le groupe Khronos (déjà à l’origine du standard OpenGL), est conçu de manière à pouvoir prendre en charge le parallélisme au niveau GPU mais aussi au niveau CPU.

Ces deux langages facilitent le développement d’applications sur carte graphique et ont le même modèle de programmation. Néanmoins, le portage d’un code séquentiel sur une technologie GPU nécessite un temps supplémentaire de programmation non négligeable.

La Programmation GPU

Tout d’abord, avant de porter une application sur une architecture hybride CPU/GPU, il faut déterminer les étapes les plus coûteuses d’un programme et déterminer si elles sont parallélisables. Il faut aussi garder à l’esprit que la programmation parallèle sur GPU suit un modèle SIMT (Single Instruction Multiple Thread).Ceci signifie que des groupes de threads doivent exécuter les mêmes instructions en même temps sur le GPU afin d’obtenir des gains de temps intéressants.

Lorsqu’on souhaite exécuter un morceau de code sur une carte graphique, il faut tout d’abord créer des kernels, qui ne sont autre que des morceaux de code parallèle exécutables sur le GPU, appelés par le CPU.

Les cartes graphiques disposent de leur propre mémoire RAM. Pour exécuter un kernel, il faut organiser de manière explicite les transferts de données nécessaire entre la carte mère et la carte graphique. De plus, la gestion de la mémoire est explicite sur une carte graphique : lors d’une opération de copie, il faut spécifier l’endroit où les données doivent être transférées (e.g. Mémoire globale, mémoire des textures, mémoire constante,...).

Ensuite, les kernels disposent d’arguments spécifiques au découpage de la tâche totale à réaliser. Il s’agit là d’associer des identifiants aux tâches qui s’exécutent en parallèle afin d’assurer le lien entre les instructions à effectuer et les données à traiter. En CUDA, ces subdivisions s’appellent des grilles, en OpenCL, ce sont des NDRanges. Elles sont composées de : threads (CUDA) / work items (OpenCL) qui sont une instruction ou un groupe d’instructions qui correspond à l’instanciation d’un kernel. blocs (CUDA) / work groups (OpenCL) qui sont des groupes de threads / work items qui doivent être indépendants les uns des autres. Afin de se repérer, chaque bloc (respectivement work group) possède un identifiant unique 1D ou 2D au sein de la grille (respectivement NDRange) et chaque thread (respectivement work item) possède un identifiant unique, 1D, 2D ou 3D au sein du bloc (respectivement work group) auquel il appartient.

Enfin, une fois qu’un premier portage a été effectué, il n’est pas rare d’avoir à consacrer du temps à l’optimisation du programme, autant au niveau des kernels que de la gestion de la mémoire. Aussi, il est souvent nécessaire de spécialiser son application en fonction du matériel utilisé (modèle de la carte graphique, ...).

Tout ceci implique un effort de programmation supplémentaire à fournir lors du portage d’une application sur une architecture hybride CPU/GPU.

CUDA

Comme mentionné plus haut, cette bibliothèque de Nvidia est une extension au langage C pour la programmation GPU. La première version officielle est parue en février 2007. CUDA permet l’utilisation de deux niveaux d’API : une API bas niveau appelée CUDA driver API, une API plus haut niveau appelée CUDA runtime API ou CUDA C. L’API haut niveau permet une programmation plus aisée. L’initialisation, la gestion du contexte et des modules sont faites de manière implicite, ce qui raccourcit considérablement la longueur du code. Néanmoins, l’API bas niveau offre des fonctionnalités supplémentaires qui peuvent s’avérer intéressantes pour certains projets. Malheureusement, cet outil n’est disponible que pour les processeurs de la marque au caméléon, ce qui restreint considérablement la portabilité d’un code utilisant cette bibliothèque. Ceci est toutefois nuancé par le fait que Nvidia se partage l’essentiel du marché avec ATI/AMD. Cet inconvénient n’a pourtant pas arrêté les développeurs, qui ont fait apparaître toute une gamme d’outils utilisant ces bibliothèques : CUBLAS pour l’algèbre linéaire, CuDPP pour les tris ou les réductions et d’autres encore. Ces outils s’avèrent être de puissants alliés lors du développement d’un code sur carte graphique. De plus, le support offert par la marque au caméléon est bien fourni de par les nombreux guides existants, et permet un apprentissage éclairé de l’utilisation de cette bibliothèque, sous réserve d’avoir de bonnes bases en C. OpenCL Cette extension du C est issue d’un partenariat entre AMD, Apple, Intel et Nvidia, tous réunis au sein du Khronos Compute Working Group. Le but était de pouvoir programmer sur des systèmes parallèles hétérogènes visant des plateformes GPU mais aussi CPU-multicoeurs. Les premières versions d’OpenCL sont apparues au courant de l’année 2009, ce qui en fait un outil de programmation relativement jeune. Ici, il n’y a qu’une seule API disponible qui est équivalente à l’API bas niveau de CUDA (Driver API). La principale force d’OpenCL réside en sa portabilité. En effet, il est possible, avec ce langage, de programmer à la fois des CPU-multicoeurs mais aussi des GPU quelle qu’en soit la marque (AMD/ATI, Nvidia,...). Le deuxième point positif est le caractère public et open source de cet outil. Pour l’instant, ce langage ne dispose pas encore d’autant d’outil que son prédécesseur CUDA. Ceci le rend un peu moins populaire pour le moment au sein des développeurs. Il faut en effet développer ses propres outils à chaque besoin particulier (réduction, tri, multiplication matricielle, ...). Néanmoins, ces outils commencent à apparaître peu à peu.

Conclusion

Au niveau des performances, même si CUDA a parfois un léger avantage sur les GPU Nvidia, les deux outils se valent en terme de temps d’exécutions. Le plus gros point noir reste cependant la portabilité des performances. Afin d’avoir des gains de temps optimaux, il faut quasiment systématiquement programmer en fonction de son hardware qui a ses propres contraintes, aussi bien en CUDA qu’en OpenCL. Pour le futur de ces langages, ils continueront à perdurer tant que les GPU offriront une puissance de calcul brute à moindre coût face aux CPU. On peut néanmoins penser qu’OpenCL, grâce à sa portabilité, finira par dépasser CUDA en terme de nombre d’utilisateurs.

Matthieu Kuhn

Rechercher
     

Responsables éditoriaux : Dominique Boutigny et Cristinel Diaconu
Comité de rédaction : Virginie Dutruel, Sébastien Grégoire, Eric Legay, Gaëlle Shifrin et Tiffany Thome

logo CCIN2P3
© CCIN2P3