Université de Bretagne Occidentale Lab-STICC, UMR 6285 Programmation parallèle sur carte graphique avec OpenCL Emanuel Guével Master 2 LSE Encadré par : Anne Jeannin-Girardon Catherine Dezan Programmation GPGPU avec OpenCL : méthodologie, mesures de performances et implémentation d un essaim au sein d une architecture de système multi-agent existante. 1 er mars 2013
Table des matières I Architectures parallèles et outils pour la programmation sur GPU 2 1 Les architectures parallèles et leur programmation 2 1.1 Classification de Flynn..................... 2 1.2 Architecture des processeurs graphiques............ 3 1.3 Frameworks pour la programmation parallèle......... 6 2 Outils de profilage et de débuggage pour OpenCL 8 2.1 GDB (GNU Debugger)..................... 8 2.2 graphicremedy gdebugger................... 8 2.3 AMD CodeXL.......................... 9 2.4 Outils Nvidia (CUDA-GDB et Visual Profiler)........ 9 2.5 Synthèse sur les outils disponibles............... 10 II Implémentation d un algorithme parallèle et mesure de ses performances 11 3 Multiplication de matrices 11 4 Les différentes versions de l algorithme 12 4.1 Version séquentielle....................... 12 4.2 Version parallèle utilisant la mémoire globale uniquement.. 13 4.3 Version parallèle utilisant la mémoire locale.......... 13 5 Mesures et interprétation des performances 15 6 Conclusion sur les mesures de performances 17 III Implémentation parallèle d un essaim intelligent 19 7 Systèmes multi-agents 19 7.1 Définition............................. 19 7.2 Les essaims............................ 19 7.3 Intérêt de la programmation parallèle pour ces systèmes... 20 1
8 Étude de l article de E. Passos et al. 20 8.1 Comportements de l essaim................... 20 9 Architecture logicielle existante 21 9.1 Le système proies prédateurs.................. 21 9.2 Mécanismes de gestion mémoire................ 21 10 Implémentations des comportements 22 10.1 Ajout des comportements au système proies prédateurs... 22 10.2 Validation du travail....................... 23 11 Conclusion sur l implémentation de l essaim 24
Introduction Jusqu à 2004, la puissance de calcul des CPU (Central Processing Unit) augmente avec leur vitesse d horloge. Mais à causes des contraintes (échauffement des puces, consommation électrique élevée ), les constructeurs commence à exploiter une voie jusque là laissée de côté (du moins pour les processeurs grand public) : la parallélisation. Les processeurs graphiques ont adopté une architecture parallèle dès le milieu des années 90 [5]. Leur évolution et celle des frameworks pour leur programmation permettent aujourd hui de réaliser des applications génériques (par opposition au rendu graphique). En particulier, la puissance de calcul offerte par les GPU se prête bien à la simulation numérique, notamment dans le cadre distribué des systèmes multi-agents. Si ces systèmes ont une capacité de prédiction faible, ils permettent néanmoins de se placer dans le cadre de l expérimentation, par exemple pour affiner un modèle, déduire des paramètres, réaliser des observations etc. Le travail réalisé lors de ce projet comporte trois axes. Le premier est une étude du matériel mis à disposition (la carte graphique Nvidia GTX 690 récemment acquise par le laboratoire) ainsi que des outils disponibles permettant de le programmer. Le second est la prise en main du framework OpenCL au travers de la programmation d un algorithme parallèle et de l évaluation de ses performances. La troisième partie est l étude et l implémentation parallèle d un système complexe de type essaim au sein d une architecture logicielle existante développée au laboratoire. La programmation sur GPU ne permettant pas l allocation dynamique de la mémoire, cette architecture définie des mécanismes pour gérer la création et la disparition d entités. L essaim implémenté est le cas pratique qui permet de tester ces mécanismes. L essaim est obtenu en définissant les comportements locaux des différentes entités d un système multi-agents. 1
Première partie Architectures parallèles et outils pour la programmation sur GPU 1 Les architectures parallèles et leur programmation Cette partie est une introduction à la programmation parallèle. Nous commençons d abord par introduire les différents types d architecture parallèle telles que présentés par Flynn [13], puis nous focalisons cette étude sur les cartes graphiques NVidia, avec en particulier l étude de la GTX 690. Puis des frameworks pour la programmation parallèle sont présentés, notamment pour la programmation de puces graphiques. 1.1 Classification de Flynn Dans un article paru en 1972, Michael Flynn propose une classification des architectures d ordinateurs selon leurs flux d instructions et de données. Selon ces critères, il distingue quatre catégories : SISD (Single Instruction, Single Data), MISD (Multiple Instruction, Single Data), SIMD (Single Instruction, Multiple Data) et MIMD (Multiple Instruction, Multiple Data) (figure 1). Mémoires unique Mémoires Multiples Flux d instructions unique SISD MISD Flux d instructions multiples SIMD MIMD Figure 1 Classification des architecture d ordinateurs selon Flynn La catégorie SISD regroupe les systèmes non parallèles comme ceux comprenant uniquement un processeur mono-cœur. La seconde catégorie, MISD comprend les systèmes exécutant des instructions différentes pour une seule donnée. C est par exemple le cas des architectures pipeline. Cette catégorie utilise le parallélisme uniquement au niveau des instructions. La catégorie de système SIMD utilise le parallélisme au niveau de la mémoire. La dernière catégorie, MIMD, est celle des systèmes distribués et des CPU modernes. Chaque nœud (ou cœur dans le cas des CPU) a accès à une mémoire propre et effectue son traitement de façon indépendante. Dans la suite du rapport, nous nous focalisons sur les GPU, appartenant à la catégorie des SIMD. 2
1.2 Architecture des processeurs graphiques 1.2.1 Rappel historique Les processeurs graphiques (ou GPU, Graphics Processing Unit) sont des processeurs spécialisés conçus pour accélérer les opérations de rendu graphique, en 2D et en 3D. Ils ont évolué pour effectuer efficacement les opérations comportant de nombreux calculs sur des matrices et des vecteurs, calculs massivement parallèles. L apparition des langages de shaders en 2002 1 est un premier pas vers le GPGPU (General Purpose computing on Graphics Processing Unit). Les shaders sont des programmes prévus pour effectuer des calculs sur des surfaces et sur des pixels affichés à l écran (pour des effets comme le bump mapping ou la réflexion). En tirer parti pour accélérer des traitement calculatoires à finalité non graphique n est pas une tâche aisée, nécessitant des manipulations «de contournement», comme stocker des données dans des textures. Les langages de shaders de haut niveau (Cg, HLSL, GLSL) succèdent au langage assembleur des premiers shaders. On peut vraiment parler de GPGPU quand les notions des shaders propres au rendu graphique (vertex, pixels, textures) disparaissent. C est le cas à partir de 2006 : Nvidia permet alors de programmer sa GeForce 8 en langage C avec sa plateforme de développement Cuda [7]. La première version des spécifications d OpenCL sont publiées par le Khronos Group deux ans plus tard, en 2008 [8]. 1.2.2 Organisation des unités de calcul et de la mémoire Contrairement aux CPU modernes, dont l architecture permet un parallélisme de tâches, les GPU exploite un parallélisme de données (comme affirmé précédemment, un GPU appartient à la catégorie des SIMD). Un GPU comprend un grand nombre d unités de calcul (3072 pour la carte GTX 690 utilisée lors du projet les spécifications complètes sont disponibles en annexe p. 28). Ces unités sont regroupées en «work-groups». Au sein d un work-group, chaque unité effectue le même traitement sur des données différentes. Les threads sont programmés en groupe et la répartition du travail est effectuée par un composant matériel (il n y a pas besoin de le spécifier explicitement). La mémoire est répartie en trois niveaux : la mémoire privée, propre à une unité de calcul. Elle est de petite taille (de l ordre du kilo octet) et très rapide. la mémoire locale, commune aux unités d un même work-group. Sa 1. les premières cartes permettant l utilisation de vertex et pixel shaders sont la Ge- Force FX de Nvidia et Radeon 9700 d ATI, toutes deux mises sur le marché en 2002 [5] 3
taille est plus importante que celle de la mémoire privée mais ses performances sont aussi moins bonnes. la mémoire globale, accessible par chacune des unités. C est la plus grande des mémoires du GPU, et celle qui est la moins performante. Les frameworks de développement fournissent les mécanismes de synchronisation à utiliser pour éviter les problèmes liés à la concurrences. 1.2.3 Étude du matériel à disposition : la carte GTX 690 Différences entre les architectures Fermi et Kepler La carte mise à disposition étant de la marque Nvidia, nous comparons ici son architecture Kepler avec l architecture précédente, Fermi [3] [4]. FERMI GF100 FERMI GF104 KEPLER GK104 KEPLER GK110 Compute capability 2.0 2.1 3.0 3.5 Threads per warp 32 32 32 32 Max warps per multiprocessor 48 48 64 64 Max threads per multiprocessor 1536 1536 2048 2048 Max thread blocks per 8 8 16 16 multiprocessor 32 bit registers per multiprocessor 32768 32768 65536 65536 Max registers per 63 63 63 255 thread Max threads per thread 1024 1024 1024 1024 block Shared memory size 16/48K 16/48K 16/32/48K 16/32/48K configurations (bytes) Max X grid dimension 2 16 1 2 16 1 2 32 1 2 32 1 Hyper Q No No No Yes Dynamic Parallelism No No No Yes La différence la plus notable est l augmentation générale en nombre de ressources (nombre de threads, de registres, dimensions de la grille). On remarque aussi cette différence en comparant les schémas des deux architectures (figure 2). Une autre différence entre les deux architectures est la possibilité de configurer plus finement la taille de la mémoire locale (afin d en utiliser une partie comme mémoire globale). 4
(a) L architecture Fermi (illustration extraite de [3]) (b) L architecture Kepler (illustration extraite de [4]) Figure 2 Schémas des architectures de GPU Nvidia 5
Les nouvelles fonctionnalités GPUDirect et Dynamic Parallelism disponibles avec la nouvelle architecture Kepler ne peuvent être utilisée qu avec CUDA et non avec OpenCL. GPUDirect permet les communications sans intermédiaire entre le GPU et les autres périphériques (par exemple la carte réseau). Le cas mis en avant par Nvidia est celui des programmes MPI utilisant CUDA qui pourront désormais envoyer ou recevoir des données du réseau directement dans la mémoire de la carte graphique. Le «parallélisme dynamique» (Dynamic Parallelism) permet quant à lui au GPU de créer de nouvelle tâches. La création de tâches était jusque là effectuée uniquement par le CPU. Cette fonctionnalité devrait permettre de gagner du temps en supprimant des communications entre CPU et GPU. Pour gérer les tâches crées par le CPU et le GPU, une unité matérielle est ajoutée : «l unité de contrôle de la grille» (GMU, ou Grid Management Unit). Elle est chargée de répartir les travaux et de les ordonnancer (mise en pause et reprise). Enfin, la fonctionnalité Hyper-Q rend possible la création de tâches par plusieurs cœurs de CPU sur le même GPU. Nvidia ne précise pas si ces évolutions technologiques sont utilisables avec OpenCL, mais on peut supposer que c est le cas pour Hyper-Q, qui apparaît plus comme une correction que comme une nouveauté. 1.3 Frameworks pour la programmation parallèle 1.3.1 Threads Pour réaliser une application parallèle, il est possible d utiliser directement le mécanisme de threads fourni par le système d exploitation. Les threads natifs peuvent partager des données dans le même espace mémoire, ce qui oblige à utiliser des mécanismes de synchronisation comme les mutex ou les sémaphores. C est une solution plutôt contraignante : il faut gérer explicitement les threads pendant toute leur durée de vie. Les threads peuvent être utilisés dans des programmes «classiques» s exécutant sur un CPU. 1.3.2 MPI La bibliothèque MPI 2 (Message Passing Interface) fournit des mécanismes de communication entre processus, que ceux ci soit lancés sur la même machine ou sur des machines différentes au sein d un même réseau. Les programmes MPI sont lancés avec mpirun, permettant de choisir le nombre de processus à créer. 2. http://www.mcs.anl.gov/research/projects/mpi 6
1.3.3 OpenMP OpenMP 3 est une API définissant des directives de compilation ainsi qu un ensemble de fonctions pour la programmation parallèle. OpenMP est utilisé sur les systèmes à mémoire partagée. Le code 1 illustre une utilisation simple d OpenMP dans un programme C. La directive de compilation #pragma indique que la boucle for qui suit doit être exécutée en parallèle. La boucle sera divisée en tranche et le travail réparti entre différents threads. #pragma omp parallel for for (i = 0 ; i < 10000 ; i++) a[i] = 2 * i ; Code 1 Exemple d utilisation d OpenMP 1.3.4 OpenACC et OpenHMPP OpenACC 4 et OpenHMPP 5 (Open Hybrid Multicore Parallel Programming) sont des standards ouverts pour la programmation sur systèmes hétérogènes. Tous deux ont choisi la même approche qu OpenMP, à savoir la programmation parallèle au moyen de directive de compilation incluses dans le code. Les créateurs d OpenACC ont annoncé leur intention de mettre en commun les spécifications d OpenMP avec celles d OpenACC afin qu Open- MP supporte l utilisation d accélérateurs matériels [14]. OpenHMPP est implémenté au sein de compilateurs des sociétés Caps et PathScale. OpenACC sera implémenté dans les compilateurs des sociétés Caps, PGI et Cray. Aucune implémentation gratuite n est évoquée pour le moment. 1.3.5 OpenCL OpenCL 6 est un standard pour la programmation parallèle sur systèmes hétérogènes. Son intérêt principal est qu il est indépendant de l architecture matérielle. Il est cependant particulièrement bien adapté aux GPU : on retrouve des similarités entre les architecture matérielle des GPU et l abstraction offerte par OpenCL, à la fois dans son modèle d exécution qui réparti les calculs dans une grille et dans modèle mémoire hiérarchique (mémoire globale/locale/ privée). 3. http://openmp.org 4. http://www.openacc.org 5. http://www.openhmpp.org 6. https://www.khronos.org/opencl 7
Plusieurs grands constructeurs proposent des implémentations d OpenCL pour CPU et GPU (notamment AMD, Intel, Nvidia, IBM et ARM), et des implémentations pour FPGA, DSP et autres accélérateurs matériels sont prévues 7. OpenCL est composé d une API et d un langage basé sur le C, utilisé pour programmer les opérations à réaliser sur le périphérique compatible. Cette partie du programme est compilée à l exécution pour lui permettre d être indépendante de l architecture sous-jacente et de l implémentation. La dernière version des spécifications OpenCL (version 1.2) apporte plus de flexibilité. Elle introduit notamment la possibilité de diviser un périphérique afin de répartir la charge de travail explicitement (cela permet par exemple de n utiliser qu un cœur d un CPU). Jusqu à présent, les spécifications OpenCL 1.2 ont été implémentées par Intel et AMD. 1.3.6 Autres frameworks de GPGPU Il existe d autres frameworks de programmation pour processeurs graphiques, tel que Cuda (Nvidia) et ATI Stream (AMD). Néanmoins, ils ont l inconvénient de n être utilisable qu avec le matériel d un constructeur, ce qui limite l utilisation la portabilité des programmes créés. 2 Outils de profilage et de débuggage pour OpenCL Dans cette partie, nous étudions les outils disponibles pour débugger les programmes OpenCL et les profiler. Les outils présentés ont tous été testés lors du projet. 2.1 GDB (GNU Debugger) Le support de GDB pour débugger les kernels OpenCL a été annoncé par Intel [1] et AMD [2] concernant leurs implémentations respectives pour CPU. Toutefois, avec l implémentation d Intel, je n ai pu ni exécuter un kernel pas à pas ni y placer un point d arrêt ou encore lister ses variables. Le débuggage avec GDB sur CPU n a pas pu être testé car je n ai pas de matériel AMD à disposition. 2.2 graphicremedy gdebugger GDEBugger 8 est un outil de débuggage et d analyse des programmes OpenCL aussi bien qu OpenGL. 7. la société Altera a annoncé une implémentation pour FPGA [9], actuellement en cours de développement 8. http://www.gremedy.com 8
2.3 AMD CodeXL CodeXL 9 est la suite d outils d AMD pour le développement OpenCL. Il fournit un éditeur de texte ainsi qu un debugger reprenant l interface de gdebugger. Débugger un programme OpenCL avec cet outil n est possible que sur une carte AMD. 2.4 Outils Nvidia (CUDA-GDB et Visual Profiler) Nvidia fournit dans son SDK 10 des programmes pour le débuggage et le profilage des programmes conçus pour ses cartes graphiques. Favorisant en premier lieu leur propre framework de programmation GPGPU, CUDA, les dernières versions de ces outils ne fonctionnent plus avec les applications OpenCL, contrairement à celles des SDK plus anciens. CUDA-GDB Cette version modifiée de GDB permet de débugger les programmes CUDA exécuté sur le GPU. Il permet aussi de consulter des informations sur l état du périphérique, comme par exemple l occupation mémoire. Visual Profiler Cet outil de profilage relève les opérations effectuées sur la carte graphique (transfert mémoire par exemple), et affiche des conseils pour l amélioration des performances. L ancienne version de cet outil fonctionne avec OpenCL mais n affiche pas de piste d amélioration. Il faut interpréter les informations relevées pour en déduire les les optimisations à réaliser. 9. http://developer.amd.com/tools/heterogeneous-computing/codexl 10. https://developer.nvidia.com/cuda-downloads 9
Figure 3 Capture d écran de Nvidia Visual Profiler 2.5 Synthèse sur les outils disponibles Peu d outils sont disponibles pour le développement OpenCL. Les outils fournis par les constructeurs sont souvent conçus spécifiquement pour leur matériel. Il reste néanmoins quelques pistes à explorer. GPU Ocelot 11 permet une approche intéressante, bien qu il ne soit pas conçu spécifiquement pour la programmation OpenCL. Cet outil permet d interpréter du code PTX (Parallel Thread Execution), un code assembleur utilisé par les cartes graphiques Nvidia. Ce code PTX est produit par le compilateur Cuda ou à l exécution d un programme OpenCL. Pour récupérer le code PTX à partir d un code OpenCL, on peut le compiler avec clcc 12. Clcc fait appel aux bibliothèques OpenCL fournis par Nvidia. GPU Ocelot peut émuler et débugger le code ainsi généré. 11. http://code.google.com/p/gpuocelot 12. https://github.com/ljbade/clcc 10
Deuxième partie Implémentation d un algorithme parallèle et mesure de ses performances Dans cette partie, nous étudions une implémentation d une multiplication de matrices utilisant OpenCL. Ce n est pas tant l aspect algorithmique qui nous intéresse ici mais plutôt le degré de parallélisme que permet le produit de matrices. Nous faisons varier divers paramètres afin d apprécier leur incidence sur les performances de cette implémentation. Les mesures sont effectuées sur un ordinateur doté d un processeur Intel Core i7-2600 cadencé à 3,40 GHz et de la carte graphique Nvidia GTX 690. Les paramètres qui seront modifiés sont : le périphérique utilisé (CPU ou GPU) ; la stratégie d utilisation de la mémoire (utilisation de mémoire globale uniquement, partitionnement des matrices pour utiliser de la mémoire locale) ; l utilisation du déroulage de boucle. 3 Multiplication de matrices La multiplication de matrices s opère sur deux matrices et ne peut s effectuer que si le nombre de ligne de la première matrice est égal au nombre de colonnes de la seconde nb lignes A = nb colonnes B = n. La valeur de chaque case de la matrice C est calculée en fonction des valeurs de la ligne i correspondante dans la matrice A et de la colonne j de la matrice B tel que C ij = n k=0 A ik B kj (figure 4). 11
Wb B Hb col A row C Ha Ha Wa Wb Figure 4 Multiplication de matrices (illustration extraite de [11]) 4 Les différentes versions de l algorithme 4.1 Version séquentielle Dans un premier temps, on implémente une version séquentielle de la multiplication de matrices. Dans cette version, on calcule séquentiellement chaque case de la matrice résultante. Pour calculer une case, on parcourt à la fois la ligne correspondante dans la matrice A et la colonne correspondante dans la matrice B (code 2). for (int i = 0 ; i < Ha ; i++) { // parcours des lignes de A for (int j = 0 ; j < Wb ; j++) { // parcours des colonnes de B } } // calcul de la valeur de la case C ij for (int k = 0 ; k < n ; k++) { C[i][j] += A[i][k] * B[k][j]; } Code 2 Version séquentielle de la multiplication de matrices 12
4.2 Version parallèle utilisant la mémoire globale uniquement Pour la version parallèle utilisant OpenCL, chaque case de la matrice résultante est calculée par un work-item. On a donc l équivalent d un thread par case de la matrice d arrivée. Le code 3 est celui du kernel OpenCL de cette version. kernel void matrixmultiply( global float *ina, global float *inb, global float *outc, uint widtha, uint widthb) { // récupération des coordonnées de la case à calculer uint i = get_global_id(1); uint j = get_global_id(0); // calcul de la valeur de la case C ij float cell_value =.0f ; for (uint k = 0 ; k < widtha ; k++) { cell_value += ina[i*widtha + k] * inb[k*widthb + j]; } } // écriture de la valeur de la case en mémoire globale outc[i*widthb + j] = cell_value ; Code 3 Kernel OpenCL pour la multiplication de matrices utilisant la mémoire globale uniquement 4.3 Version parallèle utilisant la mémoire locale Afin d améliorer les performances de l algorithme, il peut être intéressant de faire diminuer le nombre d accès à la mémoire globale en utilisant la mémoire locale, qui est plus rapide. Pour ce faire, on divise la matrice en blocs, comme l illustre la figure 5. Le calcul se fait de manière similaire à la version précédente, sauf qu ici la somme de produits se fait sur les blocs là où elle était faite sur les cases. À la place d itérer sur les cases de la ligne de la première matrice et celles de la colonne de la seconde, on itère sur les blocs. Ces blocs sont des sous-matrices 13
blockrow BLOCK_SIZE-1 BLOCK_SIZE qui seront multipliées et additionnées pour donner la valeur du bloc résultat de la matrice d arrivée. Nous avons donc divisé la multiplication de matrices en plusieurs multiplication de matrices plus petites. Chaque multiplication Chapter 3. Programming Interface de blocs utilise de la mémoire locale qui est copiée depuis la mémoire globale. blockcol A B C 0 row BLOCK_SIZE-1 0 col C sub BLOCK_SIZE BLOCK_SIZE A.height B.height BLOCK_SIZE BLOCK_SIZE BLOCK_SIZE A.width B.width Figure 3-2. Matrix Multiplication with Shared Memory Figure 5 Division des matrices en blocs (illustration extraite de [12]) 3.2.4 Page-Locked Host Memory The runtime provides functions to allow the use of page-locked (also known as pinned) host memory (as opposed to regular pageable host memory allocated by malloc()): cudahostalloc() and cudafreehost() allocate and free page-locked host memory; cudahostregister() page-locks a range of memory allocated by malloc() (see reference manual for limitations). Using page-locked host memory has several benefits: Le code de cette version est disponible en annexe p.30. Copies between page-locked host memory and device memory can be performed concurrently with kernel execution 14 for some devices as mentioned in Section 3.2.5; On some devices, page-locked host memory can be mapped into the address space of the device, eliminating the need to copy it to or from device memory as detailed in Section 3.2.4.3;
5 Mesures et interprétation des performances Temps d'exécution de la multiplication de matrices (s) 250 200 150 100 50 cpu_global cpu_local2 cpu_local8 gpu_global gpu_local2 gpu_local32 0 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 Côté des matrices carrées multipliées (cases) seq Figure 6 Temps d exécution des différentes versions de la multiplication de matrices Le graphique de la figure 6 illustre les temps d exécution de la multiplication de matrices obtenus avec différents paramètres : matériel utilisé (CPU ou GPU), version séquentielle ou parallèle avec OpenCL et utilisation de mémoire locale. La figure 7 montre les accélérations obtenues avec les versions utilisant OpenCL. Fréquence d horloge Nombre de threads CPU Intel Core i7-2600 3.40 GHz 8 Bi-GPU Nvidia GTX 690 1 GHz 2 1536 Figure 8 Matériel utilisé pour les mesures Le matériel utilisé pour effectuer ces mesures est présenté sur la figure 6. Bien que la carte graphique dispose de deux GPU, un seul est utilisé pour les mesures. La première courbe (seq, en pointillés) présente le temps d exécution pour une version séquentielle du programme. Les autres courbes sont issues 15
45 Accélération par rapport à la version séquentielle 40 35 30 25 20 15 10 5 cpu_global cpu_local2 cpu_local4 cpu_local8 cpu_local16 cpu_local32 0 0 500 1000 1500 2000 2500 Côté des matrices carrées multipliées (cases) (a) Accélérations sur le CPU 3500 Accélération par rapport à la version séquentielle 3000 2500 2000 1500 1000 500 gpu_global gpu_local2 gpu_local4 gpu_local8 gpu_local16 gpu_local32 0 0 500 1000 1500 2000 2500 Côté des matrices carrées multipliées (cases) (b) Accélérations sur le GPU Figure 7 Accélérations des versions OpenCL par rapport à la version séquentielle 16
des exécutions de la version OpenCL du programme. Les trois courbes suivantes montrent les exécutions sur CPU et les trois dernières courbes celles sur GPU. L activation du déroulage de boucle n apparaît pas dans ce graphique car je n ai pas relevé d impact significatif de cette option sur les temps d exécution lors de mes tests. On peut distinguer deux types d exécutions parmi celles utilisant OpenCL : l un avec utilisation de mémoire locale, l autre sans. On remarquera que la version séquentielle est celle qui prend le plus de temps. Elle est 7,76 fois plus longue pour multiplier deux matrices de taille 2048 2048 que la version qui la suit (cpu_local2). Dans le cas des versions OpenCL, le GPU est toujours plus performant que le CPU (entre 7 et 60 fois plus rapide pour multiplier des matrices de 4096 4096). La mémoire locale permet ne permet pas d augmenter les performances lorsqu elle est mal utilisée. Si elle est sous-dimensionnée, comme ici avec les versions cpu_local2 et gpu_local2 (utilisant un bloc de mémoire locale de taille 2 2), les performances peuvent être moins bonnes que celles de la version utilisant de la mémoire globale uniquement (2 fois moins bonnes pour le CPU, 15 fois moins bonnes pour le GPU pour des matrices 2048 2048). À l inverse, un gain est visible lorsque la mémoire locale est utilisée à bon escient. Ainsi, pour des matrices 2048 2048, cpu_local8 est 1,5 fois plus rapide que cpu_simple et gpu_local32 est 3,8 fois plus rapide que gpu_simple. Les courbes des accélérations nous apprennent que les versions OpenCL sont plus performantes que la version séquentielle lorsque la taille de matrices à multiplier est supérieure à 16 16. Sur le graphique décrivant les accélérations obtenues sur CPU, on voit que la courbe cpu_local8 est au-dessus des autres courbes des versions utilisant la mémoire locale : les meilleures performances sont atteintes avec des blocs de 8 8 et sont légèrement inférieures avec des blocs de taille 16 16 ou 32 32. La version utilisant la mémoire globale semble tirer parti de l absence de transfert vers la mémoire locale : la RAM étant la mémoire globale du CPU, aucun transfert supplémentaire n est nécessaire. Il semble délicat de tirer des conclusions sans une étude plus approfondie de la façon dont est exécutée le programme OpenCL sur le CPU. Les courbes décrivant l accélération GPU sont par contre surprenantes : l accélération est élevée et augmente avec les matrices plus grandes (elle atteint une valeur de 3012,67 pour la version avec bloc de mémoire locale de 32 32 pour le produit de matrices de taille 2048 2048). 6 Conclusion sur les mesures de performances Nous avons pu voir dans cette première partie que l utilisation du GPU pour réaliser des calculs fortement parallèles permettait d améliorer les per- 17
formances de façon drastique. Toutefois, il ne faut pas perdre de vue l architecture matérielle utilisée : une tentative d optimisation peut en définitive dégrader les performances d un programme. De façon générale (cela ne s applique pas qu aux seuls processeurs graphiques), une bonne connaissance de l architecture est requise afin de programmer pour la performance. 18
Troisième partie Implémentation parallèle d un essaim intelligent Cette partie décrit l implémentation des comportements d un essaim au sein de l architecture logicielle développée au laboratoire. Dans un premier temps, nous donnons une définition des systèmes multi-agents. Ensuite, nous étudions un article de E. Passos décrivant la réalisation d un essaim sur GPU. Après une description de l architecture logicielle existante, nous abordons la mise en place de l essaim. Enfin, nous verrons des éléments permettant de valider le programme obtenu. 7 Systèmes multi-agents 7.1 Définition Un système multi-agents (abrégé SMA) est un système distribué composé d agents indépendants. Un agent est un élément de programme qui exécute des tâches. Chaque agent peut effectuer des actions en fonction de ses objectifs et de la perception limitée qu il a de son environnement. Le but de cette approche est d obtenir un comportement global complexe en modélisant un ensemble de comportements locaux simples. Selon les systèmes mis en place, les agents peuvent interagir avec leur environnement et communiquer entre eux, afin de se coordonner et de coopérer. Les systèmes multi-agents sont utilisés à la fois pour la simulation de l interaction d entités autonomes et pour le développement de systèmes distribués complexes. 7.2 Les essaims Ce que nous appelons ici essaim est un type particulier de système multiagents au sein duquel les entités effectuent des déplacements en groupes similaires à ceux que l on peut observer dans la nature, chez une grande variété d espèces animales. Une implémentation d essaim a été produite par Craig Reynolds en 1987 [16]. Le matériel qu il a utilisé à l époque (une machine Lisp), mettait 95 secondes à calculer une image pour un groupe de 80 individus. 19
7.3 Intérêt de la programmation parallèle pour ces systèmes Comme les agents d un système multi-agents sont autonomes, ils effectuent la plupart de leurs traitements de façon indépendante des autres agents. Implémenter un tel système en parallèle est donc une idée assez naturelle. De plus, le modèle d architecture SIMD des GPU convient bien à ces systèmes car ils comportent un grand nombre d agents qui réagissent de la même façon. 8 Étude de l article de E. Passos et al. Cet article [15], écrit par Erick Baptista Passos et sept autres personnes, décrit l implémentation d un essaim parallèle en deux dimensions sur GPU avec le framework CUDA. Dans cet essaim, les agents sont différenciés par un type qui représente une espèce d animal. La description de l implémentation comporte deux partie. La première est celle des mécanismes utilisés pour atteindre des performances permettant au système de gérer un million d agents tout en gardant un rendu graphique fluide. Cette partie est mise de côté car on souhaite conserver les mécanismes de l architecture existante. Une seconde partie de l article traite des comportements donnés aux agents. Nous portons plus d intérêt à celle-ci car ces comportements sont repris pour implémenter un essaim dans l architecture de SMA développée au laboratoire. 8.1 Comportements de l essaim Parmi les comportements implémentés dans l essaim de l article, il y a d abord le regroupement. Ce comportement fait qu un agent va se diriger vers les autres agents du même type. Il est mis en place en faisant une moyenne des positions des agents voisins détectés. Le regroupement est multiplié par un facteur défini pour le type d agent et par la distance à la position calculée, afin d influencer plus fortement les agents éloignés du groupe. Le second comportement évoqué est la répulsion. Elle permet d éviter les collisions trop fréquentes entre agents en contrebalançant la tendance au regroupement. Pour ce faire, La distance entre un agent et ses voisins est calculée, puis multipliée par un facteur de répulsion, là aussi défini selon le type. Le vecteur obtenu est ajouté au vecteur mouvement de l agent. Les deux autres comportements sont le suivi de la direction des voisins et le suivi de leader. Pour le premier, un agent est influencé par la direction des voisins du même type qu il tente de copier. Pour le dernier comportement, un leader est désigné par type d agent. Ce leader n est pas influencé par 20
le regroupement : il se déplace dans l environnement en ayant une position à atteindre. Le leader attire le agents du même type avec un facteur plus important que celui du regroupement. Chaque agent a un champ de vision, caractérisé par un angle, une direction et un distance. Seul les voisins présents dans le cône de vision ainsi formé sont considérés comme détectés et pris en compte lors du calcul des déplacements. 9 Architecture logicielle existante L application développée au laboratoire a servi de base au travail réalisé lors du projet. C est une architecture de SMA parallèle utilisant OpenCL. Elle gère des entités dans un environnement en deux dimensions. Chaque agent est géré par un work-item OpenCL. Chacun a donc un thread séparé. L architecture dispose aussi de fonctions de rendu graphique (utilisant OpenGL), ce qui permet de visualiser les positions des agents dans l environnement. 9.1 Le système proies prédateurs Un système proies/prédateurs est déjà en place dans cette architecture. Dans ce système, il y a deux types d entités : les proies et les prédateurs. Elles ont un comportement différents : les proies se reproduisent de façon continue alors que les prédateurs ne peuvent se reproduire que s ils ont préalablement absorbé une proie en entrant en contact avec elle. Toutes les entités se déplacent en suivant une marche aléatoire. Chacun des agents à une durée de vie limitée. On cherche à modifier ce système en y ajoutant les comportements d un essaim pour permettre de valider les mécanismes de gestion mémoire de l architecture. 9.2 Mécanismes de gestion mémoire OpenCL ne permet pas l allocation dynamique de la mémoire. Le nombre d agents en mémoire est fixe, ainsi que l espace occupé par les données associées, alors que la simulation requiert de gérer un nombre d entités variable au cours du temps. Les structures de données sont dimensionnées pour un nombre fixe d agents (c est le nombre maximal d agents présents simultanément dans le système). Pour permettre la création et la disparition des entités, l état de chaque agent est stocké : il peut ainsi être désactivés, auquel cas l entité n est pas prise en compte lors de la simulation. À la création d une nouvelle entité, un agent précédemment désactivé est réactivé. La recherche d un agent désactivé est problématique ici car elle ne se fait pas en 21
un temps constant. Le système a deux méthodes pour gérer la création d entités : une méthode stochastique et une méthode effectuant au préalable un tri afin de défragmenter l espace mémoire des données associées aux agents. Pour tester ces mécanismes, l essaim implémenté doit permettre une grande variation dans le nombre d entités, comme le système proies prédateurs existant, et doit demander plus de temps de calcul que celui-ci car on ne veut pas que la gestion de la mémoire soit le traitement qui prenne le plus de temps au système. 10 Implémentations des comportements Cette partie traite des modifications apportées au système proies prédateurs pour y mettre en place un essaim. Comme nous l avons vu, cet essaim doit respecter des contraintes pour permettre de tester les mécanismes de gestion mémoire de l architecture logicielle. 10.1 Ajout des comportements au système proies prédateurs Figure 9 Capture d écran de l essaim implémenté Les comportements ajoutés au système proies prédateurs sont le regroupement par type d entité et la répulsion. Les facteurs de regroupement sont défini par type d agents. Le regroupement est obtenu comme dans l article de E. Passos en calculant une position moyenne des voisins d un agent et en orientant cet agent vers la position obtenue. Un coefficient de regroupement négatif permet d indiquer la répulsion. La position moyenne est calculée en faisant la moyenne des positions des voisins avec pour chacun d eux le facteur de regroupement en coefficient. Si la somme des coefficients est positive, l agent est attiré par la position obtenue. Si la somme est négative, l agent va au contraire fuir cette position. La répulsion implémentée diffère de celle de l article car elle ne garantie pas une distance minimale entre les agents. 22
On peut voir les entités groupées sur la figure 9. C est une capture d écran de l application. Chaque point de couleur est un agent : les points verts sont les proies et les points rouges les prédateurs. 10.2 Validation du travail 6000 5000 Proies Predateurs 4000 nb entités 3000 2000 1000 0 0 5000 10000 15000 20000 25000 30000 35000 40000 pas de simulation Figure 10 Évolution des populations des proies et des prédateurs au cours du temps Afin de confirmer que le système mis en place répond bien au critères attendus en terme d évolution des populations d entités, nous procédons à des mesures, dont le résultat est visible à la figure 10. Les courbes de ce graphique représentent l évolution du nombre d entités (proies et prédateurs). On remarque tout d abord que les tailles des populations sont plutôt stables malgré des oscillations importantes, avec les proies 4 à 5 fois plus nombreuses que les prédateurs. De plus, on voit que les populations sont liées : lorsqu il y a une baisse du nombre de proies, le nombre de prédateurs augmente. Cela s explique par le fait que les entités évoluent en vase clos : les prédateurs ne peuvent se reproduire qu en absorbant des proies, ce qui a un impact sur les deux populations. Ces courbes sont un élément de validation du système : on peut voir que nous avons une grande fluctuation dans les tailles des populations. Ces variations permettront de tester les mécanismes de gestion mémoire de l architecture. 23
11 Conclusion sur l implémentation de l essaim Il était demandé d implémenter un essaim tout en conservant l allure de l évolution des populations du système proies prédateurs existant. L essaim mis en place répond au objectifs. On a bien observé des variations importantes qui devraient permettre de valider les mécanismes de gestion mémoire de l architecture. De futurs travaux sont envisageables. On peut d abord imaginer ajuster les paramètres de simulation afin d obtenir une évolution des populations différente (durée de vie, reproduction, coefficient de regroupement). On peut aussi envisager de mettre en place d autre comportements, à commencer par le champ de vision et le suivi de leader, ce qui permettrait de rendre la simulation plus intéressante. Enfin, il serait intéressant d évaluer les performances du système et celles de la carte GTX 690. Nous pourrions examiner le comportement du système à forte charge en simulant des populations de taille plus importantes, de l ordre du million d entités. Nous pourrions également observer l impact des mécanismes de gestion mémoire sur les performances, lors de l utilisation d un seul des mécanismes comme lors de leur utilisation conjointe, ce qui donnerai le moyen de procéder à des ajustements. J ai pu constater un temps d exécution du système multi-agents diminué lors de l utilisation de la carte GTX 690 par rapport à une carte de milieu de gamme (une Nvidia GT 6950) ; effectuer des mesures permettrait d estimer plus formellement l attrait de la carte GTX 690 pour ce type de système gourmand en ressources et en temps de calcul. 24
Table des figures 1 Classification des architecture d ordinateurs selon Flynn... 2 2 Schémas des architectures de GPU Nvidia........... 5 3 Capture d écran de Nvidia Visual Profiler........... 10 4 Multiplication de matrices.................... 12 5 Division des matrices en blocs.................. 14 6 Temps d exécution des différentes versions de la multiplication de matrices.......................... 15 8 Matériel utilisé pour les mesures................ 15 7 Accélérations des versions OpenCL par rapport à la version séquentielle............................ 16 9 Capture d écran de l essaim implémenté............ 22 10 Évolution des populations des proies et des prédateurs au cours du temps.......................... 23 Table des codes 1 Exemple d utilisation d OpenMP................ 7 2 Version séquentielle de la multiplication de matrices..... 12 3 Kernel OpenCL pour la multiplication de matrices utilisant la mémoire globale uniquement................. 13 4 Kernel OpenCL pour la multiplication de matrices utilisant la mémoire locale......................... 30 25
Références [1] «Intel SDK for OpenCL Applications 2013 Beta Release Notes». Intel Software. [En ligne]. (consultée le 4 février 2013) http://software.intel.com/en-us/articles/ intel-sdk-for-opencl-applications-2013-beta-release-notes [2] «Debugging Applications». AMD Developper Central. [En ligne]. (consultée le 4 février 2013) http://developer.amd.com/resources/heterogeneous-computing/ opencl-zone/programming-in-opencl/debugging-applications [3] «Fermi Compute Architecture White Paper». Nvidia Developer Center. [En ligne]. 2009. (consulté le 22 janvier 2013) http://www.nvidia.com/content/pdf/fermi_white_papers/ NVIDIA_Fermi_Compute_Architecture_Whitepaper.pdf [4] «Kepler Compute Architecture White Paper». Nvidia Developer Center. [En ligne]. 2012. (consulté le 22 janvier 2013) http://www.nvidia.com/content/pdf/kepler/ NVIDIA-Kepler-GK110-Architecture-Whitepaper.pdf [5] McClanahan, Chris. «History and Evolution of GPU Architecture». [En ligne]. 2010. (consulté le 13 février 2013) http://mcclanahoochie.com/blog/wp-content/uploads/2011/03/ gpu-hist-paper.pdf [6] Rege, Ashu. «An Introduction to Modern GPU Architecture» [En ligne]. 2008. (consulté le 13 février 2013) http://http.download.nvidia.com/developer/cuda/seminar/ TDCI_Arch.pdf [7] Buck, Ian. «The Evolution of GPU for General Purpose Computing». [En ligne]. 2010. (consulté le 13 février 2013) http://www.nvidia.com/content/gtc-2010/pdfs/2275_gtc2010. pdf [8] «The Khronos Group Releases OpenCL 1.0 Specification». [En ligne]. 9 décembre 2008. (consulté le 13 février 2013) http://www.khronos.org/news/press/2008/12/08 [9] «Altera Announces Industry s First OpenCL Program for FPGAs» [En ligne]. 2011. (consulté le 14 février 2013) http://www.altera.com/corporate/news_room/releases/2011/ products/nr-opencl.html [10] «GeForce GTX 690 Specifications» [En ligne]. (consulté le 14 février 2013). http://www.geforce.com/hardware/desktop-gpus/ geforce-gtx-690/specifications 26
[11] Gaster B., Howes L., Kaeli D., Mistry P. et Schaa D., «Heterogeneous Computing with OpenCL», Elsevier Science, ISBN 9780123877666, 2011. [12] «Cuda C Programming Guide» [En ligne]. 2012. (consulté le 14 février 2013). http://docs.nvidia.com/cuda/pdf/cuda_c_programming_guide. pdf [13] Flynn, Michael. «Some Computer Organizations and Their Effectiveness». IEEE Transactions on Computers, Vol. C-21. 1972. [14] «How does the OpenACC API relate to OpenMP API?». OpenACC FAQ. [En ligne]. (consulté le 18 février 2013). http://www.openacc-standard.org/node/49 [15] Passos E., Joselli M., Zamith M., Rocha J., Montenegro A., Clua E., Conci A., Feiji B., «Supermassive crowd simulation on GPU based on emergent behavior», dans Proceeding of the VII Brazilian Symposium on Computer Games and Digital Entertainement, p. 81 86, 2008. [16] Reynolds C., «Flocks, Herds, and Schools : A Distributed Behavioral Model». 1987. 27
Annexes Spécifications de la carte GTX 690 de Nvidia [10] GTX 690 GPU Engine Specs CUDA Cores 3072 Base Clock (MHz) 915 Boost Clock (MHz) 1019 Texture Fill Rate (billion/sec) 234 GTX 690 Memory Specs Memory Speed (Gbps) 6.0 Standard Memory Config 4096 MB (2048 MB per GPU) GDDR5 Memory Interface Width 512-bit (256-bit per GPU) Memory Bandwidth (GB/sec) 384 GTX 690 Support OpenGL 4.2 Bus Support PCI Express 3.0 Certified for Windows 7 Yes Supported Technologies 3D Vision, 3D Vision Surround, CUDA, DirectX 11, PhysX, SLI, TXAA, Adaptive VSync, GPU Boost, FXAA SLI Options Quad Display Support Multi Monitor Maximum Digital Resolution Maximum VGA Resolution HDCP HDMI Standard Display Connectors Audio Input for HDMI 4 displays 4096x2160 2048x1536 Yes Yes (via dongle) Two Dual Link DVI-I. One Dual link DVI-D. One Mini-Displayport 1.2 Internal 28
GTX 690 Graphics Card Dimensions Length Height Width 11.0 inches 4.376 inches Dual-slot Thermal and Power Specs Maximum GPU Tempurature (in C) Maximum Graphics Card Power (W) Minimum System Power Requirement (W) Supplementary Power Connectors 98 C 300 W 650 W Two 8-pin 3D Vision Ready 3D Blu-Ray 3D Gaming 3D Vision Live (Photos and Videos) Yes Yes Yes 29
Code de la multiplication avec OpenCL utilisant la mémoire locale kernel void matrixmultiplysh( const global float *ina, const global float *inb, global float *outc, const uint widtha, const uint widthb) { // Coordonnées globales uint gidx = get_global_id(0); uint gidy = get_global_id(1); // Coordonnées du workgroup uint gpidx = get_group_id(0); uint gpidy = get_group_id(1); // Coordonnées locales uint lidx = get_local_id(0); uint lidy = get_local_id(1); uint n = get_num_groups(0); // Espace utilisé en mémoire locale local float suba[blocksize * BLOCKSIZE]; local float subb[blocksize * BLOCKSIZE]; float sum = 0 ; // Pour chaque groupe de l'espace d'index for (int k = 0 ; k < n ; k++) { // Copie des données dans la mémoire locale suba[lidy*blocksize + lidx] = ina[widtha*(blocksize*k + lidy) + BLOCKSIZE*gpIdX + lidx]; subb[lidy*blocksize + lidx] = inb[widthb*(blocksize*gpidy + lidy) + BLOCKSIZE*k + lidx]; // Synchronisation: toutes les données doivent avoir été copiées // avant de continuer barrier(clk_local_mem_fence); // Multiplication des sous matrices for (int i = 0 ; i < BLOCKSIZE ; i++) { sum += suba[blocksize*i + lidx] * subb[blocksize*lidy + i]; } } // Copie du résultat dans outc outc[widthb*gidy + gidx] = sum ; } Code 4 Kernel OpenCL pour la multiplication de matrices utilisant la mémoire locale 30