Programmation massivement multifilaire sur GPU
Transcription
Programmation massivement multifilaire sur GPU
Programmation parallèle et distribuée (GIF-4104/7104) Programmation massivement multifilaire sur GPU avec OpenCL (hiver 2013) © Marc Parizeau, Département de génie électrique et de génie informatique OpenCL • Open Computing Language ✓ spécification ouverte et libre • Initialement conçu par Apple ✓ proposé au Khronos Group en 2008 (version 1.0) ✓ consortium sans but lucratif, financé par diverses compagnies dont ATI, Intel, NVIDIA, SGI et Sun/Oracle • Framework pour écrire des programmes qui s'exécutent à la fois sur des CPUs et GPUs ✓ CPU = Central Processing Unit ✓ GPU = Graphics Processing Unit ✓ GPGPU = General Purpose computing on Graphics Processing Unit • 2 Version actuelle: OpenCL 1.2 (nov. 2011) GPGPU et OpenCL • Les GPUs sont caractérisés par des centaines de cœurs de traitement 3 GPGPU et OpenCL Plan • Architecture des GPUs ✓ ATI/AMD ✓ NVIDIA ✓ Cell • OpenCL ✓ la plateforme ✓ les principales fonctions de l'API ✓ les étapes d'un programme ✓ la mémoire ✓ l'ordonnancement des fils d'exécution • 4 CUDA (Compute Unified Device Architecture) GPGPU et OpenCL CPU • Beaucoup de surface investie en logique de contrôle ✓ optimisé pour réduire la latence des opérations unifilaires ✓ cache multi-niveau ✓ peu de registres ✓ réordonnancement des instructions pour alimenter les pipelines 5 Control Logic L2 Cache L3 Cache ALU L1 Cache ~ 25GBPS System Memory A present day multicore CPU could have more than one ALU ( typically < 32) and some of the cache hierarchy is usually shared across cores Perhaad Mistry & Dana Schaa, Northeastern Univ Computer Architecture Research Lab, with Ben Gaster, AMD © 2011 GPGPU et OpenCL GPU • Beaucoup de cœurs ✓ moins de logique ✓ beaucoup de registres • Beaucoup de fils High Bandwidth bus to ALUs d'exécution parallèles On Board System Memory ✓ commutation à faible latence • Beaucoup d'unités ✓ mémoire rapide permet d'alimenter toutes les unités arithmétiques • Optimisé pour le débit 6 Simple ALUs arithmétiques par cœur Cache Perhaad Mistry & Dana Schaa, Northeastern Univ Computer Architecture Research Lab, with Ben Gaster, AMD © 2011 GPGPU et OpenCL CPU vs. GPU - GFLOPs 7 GPGPU et OpenCL Bande passante mémoire 8 GPGPU et OpenCL GPU AMD 9 GPGPU et OpenCL • Par exemple, la carte AMD 5870 - Cypress ✓ 20 compute units ✓ 16 cœurs par unit ✓ 5 multiply-add par cœur ✓ 2.72 téraflops (simple précision) ✓ 544 gigaflops (double précision) 10 GPGPU et OpenCL Moteur SIMD • Un engin SIMD est One SIMD Engine aussi appelé «compute unit» ✓ un compute unit regroupe 16 cœurs ✓ un cœur peut effectuer jusqu'à 5 multiply-add par cycle ✓ les cœurs d'un même groupe exécutent tous les mêmes séquences d'instructions 11 One Stream Core Instruction and Control Flow T-Processing Element Branch Execution Unit Processing Elements General Purpose Registers Source: AMD Accelerated Parallel Processing OpenCL Programming Guide GPGPU et OpenCL AMD et OpenCL • Compute device ✓ un GPU physique • Compute unit ✓ ensemble de processeurs élémentaires qui exécutent la même instruction • Processing element ✓ un des coeurs (Stream Core) pouvant exécuter X opérations par cycle 12 GPGPU et OpenCL Architecture mémoire (AMD 5870) SIMD Engine LDS, Registers chaque compute unit L1 Cache ✓ local data store (LDS) Compute Unit to Memory X-bar L2 Cache Write Cache Atomic Path LDS Perhaad Mistry & Dana Schaa, Northeastern Univ Computer Architecture Research Lab, with Ben Gaster, AMD © 2011 13 • Mémoire locale à ✓ registres • Cache L1 pour chaque compute unit • Cache L2 partagée entre les compute units GPGPU et OpenCL AMD et OpenCL • Une partie de la mémoire globale est exposée à OpenCL • La mémoire locale Private Memory Private Memory Private Memory Private Memory Workitem 1 Workitem 1 Workitem 1 Workitem 1 Compute Unit 1 Compute Unit N correspond au LDS • La mémoire privée utilise les registres • La mémoire constante Local Memory Local Memory Global / Constant Memory Data Cache Compute Device Global Memory Compute Device Memory utilise la cache L1 14 GPGPU et OpenCL CUDA Core Dispatch Port Operand Collector GPU NVIDIA FP Unit Int Unit Result Queue • Par exemple, la carte GTX 480 Instruction Cache Warp Scheduler Warp Scheduler Dispatch Unit Dispatch Unit Register File 32768 x 32bit ✓ 15 streaming multiprocessors (SM) ✓ chaque SM comporte 32 cœurs CUDA (processeurs élémentaires) ✓ total de 480 cœurs CUDA • • 15 Un SM exécute des fils en groupe de 32 appelé «warp» Les cœurs CUDA comportent un ALU et un FPU chacun LDST Core Core Core Core Core Core Core Core Core Core Core Core Core Core Core Core LDST Core Core Core Core LDST Core Core Core Core Core Core Core Core Core Core Core Core LDST LDST SFU LDST LDST SFU LDST LDST SFU LDST LDST SFU LDST Interconnect Memory L1 Cache / 64kB Shared Memory L2 Cache Perhaad Mistry & Dana Schaa, Northeastern Univ Computer Architecture Research Lab, with Ben Gaster, AMD © 2011 GPGPU et OpenCL GPU NVIDIA 16 GPGPU et OpenCL Architecture mémoire • La Cache L1 est configurable pour chaque SM Registers Thread Block ✓ mémoire partagée ✓ mémoire globale • • 17 La mémoire partagée sert entre les fils d'un même groupe Chaque SM possède aussi une banque de 32Ko pour les registres Shared Memory L1 Cache L2 Cache Global Memory Perhaad Mistry & Dana Schaa, Northeastern Univ Computer Architecture Research Lab, with Ben Gaster, AMD © 2011 GPGPU et OpenCL NVIDIA et OpenCL • Comme pour AMD, un Private Memory Private Memory Private Memory Private Memory Workitem 1 Workitem 1 Workitem 1 Workitem 1 Compute Unit 1 Compute Unit N Local Memory Local Memory Global / Constant Memory Data Cache Compute Device Global Memory Compute Device Memory Perhaad Mistry & Dana Schaa, Northeastern Univ Computer Architecture Research Lab, with Ben Gaster, AMD © 2011 18 sous-ensemble de la mémoire est exposée à OpenCL • La mémoire partagée configurable sert de mémoire locale pour chaque compute unit • La mémoire privée utilise les registres GPGPU et OpenCL Cell SPE 0 • Processeur de la ✓ jusqu'à 8 processeurs «synergétiques» SPE 3 SPU SPU SPU LS LS LS LS 25 GBPS 25 GBPS 25 GBPS 25 GBPS Element Interconnect ~ 200GBPS LS = Local store per SPE of 256KB L1 and L2 Cache PPE 19 SPE 2 SPU PS3 ✓ 1 processeurs PPC pour le host SPE 1 Memory & Interrupt Controller POWER PC GPGPU et OpenCL GPGPU en pratique • Un usage efficace des GPUs, quels qu'ils soient, implique: ✓ la décomposition du problème en milliers de petites tâches indépendantes, de manière à pouvoir exploiter tout le matériel et minimiser la latence ✓ que ces tâches soit souvent les mêmes pour exploiter le modèle SIMD ✓ que les tâches soient surtout de nature arithmétique (éviter à tout prix la combinatoire) 20 GPGPU et OpenCL Compilateur OpenCL • OpenCL utilise la OpenCL Compute Program technologie de compilateur LLVM («low level virtual machine») ✓ pour compiler les noyaux (et pour GPU et pour CPU) LLVM Front-end LLVM IR Nvidia PTX AMD CAL IL x86 ✓ Processus bourré d’optimisations, différent selon le hardware 21 GPGPU et OpenCL Plan • Architecture des GPUs ✓ ATI/AMD ✓ NVIDIA ✓ Cell • OpenCL ✓ la plateforme ✓ les principales fonctions de l'API ✓ les étapes d'un programme ✓ la mémoire ✓ l'ordonnancement des fils d'exécution • 22 CUDA GPGPU et OpenCL Architecture de OpenCL • OpenCL est un environnement pour la programmation parallèle dans un contexte hétérogène ✓ CPU + GPU(s) • Articulé autour de quatre modèles: ✓ plateforme ✓ exécution ✓ mémoire ✓ programmation 23 GPGPU et OpenCL Plateformes • Chaque implantation OpenCL définit la plateforme qui permet à l'hôte (CPU) d'interagir avec le ou les «devices» (GPUs) ✓ plusieurs plateformes différentes peuvent coexister ✓ un hôte + une ou plusieurs plateformes ✓ ex. : une implémentation d’OpenCL fournie par NVidia pour parler à votre carte graphique, une autre par Intel pour le CPU ou pour utiliser votre carte graphique on-board 24 GPGPU et OpenCL ✓ Chaque périphérique (compute device) contient plusieurs unités de calcul ✓ Chaque unité de calcul contient plusieurs processeurs élémentaires ✓ Chaque processeur élémentaire peut faire plusieurs opérations par cycle 25 GPGPU et OpenCL 26 GPGPU et OpenCL 27 GPGPU et OpenCL Points saillants • API des plateformes (sur l’hôte) ✓ Couche d’abstraction pour les ressources ✓ Interroger, choisir et initialiser des périphériques ✓ Créer des contextes d’exécution et des files de travail (workqueues) • API runtime (sur l’hôte) ✓ Configurer et lancer l’exécution de noyaux ✓ Gérer l’allocation et les transferts de mémoire • Langage OpenCL ✓ Noyaux de calcul qui seront exécutés sur le périphérique ✓ Langage basé sur le C ✓ Peut être compilé en ligne par votre programme ou hors ligne 28 GPGPU et OpenCL Structure de base d’un programme OpenCL 1. Identifier la/les plateformes et périphériques 2. Créer des contextes et des files 3. Créer et compiler des noyaux 4. Créer des objets mémoire 5. Initialiser les objets mémoire, y placer des données 6. Lancer l’exécution des noyaux 7. Attendre la fin de l’exécution 8. Récupérer le résultat 9. Répéter 5-8 tant qu’il reste des traitements 29 GPGPU et OpenCL Sélection d'une plateforme • Permet d'obtenir une liste de plateformes disponibles ✓ num_entries spécifie le nombre d'entrées disponibles dans le tableau platforms ✓ num_platforms retourne le nombre total de plateformes disponibles 30 GPGPU et OpenCL Information à propos d'une plateforme • Retourne de l'information spécifique à propos de la plateforme platform 31 GPGPU et OpenCL Sélection d'un device • Permet d'obtenir une liste de devices pour la plateforme platform 32 GPGPU et OpenCL Information sur un device • Retourne de l'information à propos du device device ✓ des tonnes d'information sont disponibles! ✓ surtout à propos de ses capacités ✓ sert à optimiser le code... 33 GPGPU et OpenCL Création d'un contexte • Permet de créer un contexte OpenCL ✓ pour un ou plusieurs devices • Permet de gérer dynamiquement le «runtime» de OpenCL ✓ la fonction pfn_notify sera appelée par OpenCL en cas d'erreur pour ce contexte 34 GPGPU et OpenCL Information sur le contexte • Retourne de l'information concernant le contexte context 35 GPGPU et OpenCL Création d'une file de commandes • Permet de créer une file de commandes ✓ sert à enfiler des opérations à effectuer sur le GPU ✓ on peut créer plusieurs files de commande dans un même contexte ✓ chaque file sera indépendante; aucune synchronisation nécessaire 36 GPGPU et OpenCL Information sur une file • Retourne des information spécifiques sur la file de commandes command_queue 37 GPGPU et OpenCL Création d'un buffer • Permet de créer un buffer dans la mémoire du GPU pour transférer des données dans le contexte context ✓ le CPU est responsable d’allouer la mémoire sur le GPU ✓ tous les transferts entre le CPU et le GPU doivent être explicites ✓ copie les données dans host_ptr en même temps 38 GPGPU et OpenCL Lecture d'un buffer • Insère une opération de lecture dans la file command_queue ✓ permet de transférer le contenu d'un buffer vers la mémoire du CPU ✓ encore une fois, opération initiée par le CPU 39 GPGPU et OpenCL Écriture d'un buffer • Insère une opération d'écriture dans la file command_queue ✓ permet de transférer de l'information depuis la mémoire du CPU vers un buffer du GPU 40 GPGPU et OpenCL Copie d'un buffer • Permet de copier le contenu d'un buffer vers un autre buffer 41 GPGPU et OpenCL Création d'une image 2D • Permet la création d'un objet image de dimension: ✓ image_width (pixels) ✓ image_height (pixels) ✓ image_row_pitch (octets) • 42 Format 3D également. GPGPU et OpenCL Images • Types supportés : ✓ R, RG, RBG, RGBA, luminance, etc. ✓ 8 / 16 / 32 bits signés/non-signés, floats ✓ Interpolation linéaire, gestion des effets de bord • Pourquoi? ✓ Accès mémoire accéléré par le hardware sur le GPU ✓ Permet réutilisation de features existants dans les GPUs pour les textures • Désavantages ✓ L’écriture est plus lente ✓ Limite au niveau des formats supportés 43 GPGPU et OpenCL Formats d'image 44 GPGPU et OpenCL 45 GPGPU et OpenCL Formats d'image supportés • Permet d'obtenir la liste des formats d'image supportés pour une certaine plateforme 46 GPGPU et OpenCL • Les formats suivants sont toujours supportés: • On peut lire, écrire et copier des images: clEnqueueReadImage() clEnqueueWriteImage() clEnqueueCopyImage() 47 GPGPU et OpenCL Création d'un programme • Permet de créer un noyau qui s'exécutera sur le GPU à partir de code source ✓ les lignes du code sont contenues dans strings ✓ elles sont terminées par des zéros ✓ sinon les longueurs des lignes sont spécifiées par lengths 48 GPGPU et OpenCL Compilation d'un programme • Permet de compiler et de linker le code source d'un programme ✓ on peut spécifier plusieurs devices ✓ si non nul, la fonction pfn_notify est appelée pour signaler la fin de l'opération 49 GPGPU et OpenCL Création d'un noyau • Création d'un objet noyau à partir du programme program ✓ le programme doit avoir été compilé préalablement ✓ le noyau kernel_name doit avoir été déclaré dans le programme avec le qualificatif «__kernel» ✓ c’est l’objet qu’on va exécuter 50 GPGPU et OpenCL • Permet de créer tous les noyaux déclarés dans le programme program • Permet de spécifier les arguments d'un noyau 51 GPGPU et OpenCL Exécution d'un noyau • Permet d'exécuter un noyau sur un device ✓ en utilisant work_dim dimensions ✓ le nombre total de «work-items» (le «local_work_size») doit être inférieur ou égal à CL_DEVICE_MAX_WORK_GROUP_SIZE ✓ le global_work_size doit être divisible par le local_work_size ✓ la commande attend la fin des événements de la event_wait_list 52 GPGPU et OpenCL Exemple - vecadd • Addition de deux vecteurs de valeurs flottantes 53 GPGPU et OpenCL Cas à 2 dimensions indices locaux: sx et sy indices globaux: gx et gy 54 GPGPU et OpenCL Modèle d'exécution • L'exécution d'un noyau provoque la définition d'un espace d'indices ✓ NDRange = «N Dimensional Range» • Une instance de noyau = 1 «work-item» ✓ correspond à 1 point dans l'espace d'exécution ✓ c'est la granularité la plus fine • Les «work-items» sont organisés en groupes ✓ correspond à 1 point dans l'espace des «compute-units» ✓ tous les «work-items» d'un même groupe s'exécutent à l'intérieur du même «compute unit» ✓ c'est un deuxième niveau de granularité • 55 Les instances de «work-group» s'exécutent en parallèle sur les «compute-units» disponibles GPGPU et OpenCL Fonction diverses • uint get_work_dim (): retourne le nombre de dimensions du NDRange • size_t get_global_size (uint dimindx): retourne le nombre global de fils dans la dimension dimindx • size_t get_global_id (uint dimindx): retourne l'indice global du fil d'exécution appelant, dans la dimension dimindx ✓ les gx et gy de tout à l’heure • 56 size_t get_local_size (uint dimindx): retourne le nombre de fils par groupe dans la dimension dimindx GPGPU et OpenCL • size_t get_local_id (uint dimindx): retourne l'indice local du fil d'exécution appelant, pour la dimension dimindx ✓ les sx et sy de tout à l’heure • size_t get_num_groups (uint dimindx): retourne le nombre de groupes dans la dimension dimindx • size_t get_group_id (uint dimindx): retourne l'indice du groupe dans la dimension dimindx 57 GPGPU et OpenCL Types de mémoire 58 GPGPU et OpenCL 59 GPGPU et OpenCL • Ce sont à peu près les mêmes étapes qui doivent être effectuées, peu importe l'application ✓ c'est donc ardu, mais pas nécessairement difficile • OpenCL nous permet tout de même d'exploiter du matériel hétérogène à l'aide d'un même programme exécutable ✓ on peut aussi exploiter plusieurs GPUs sans changer le programme ✓ il faut donc accepter de faire un certain nombre d'initialisations • Avec le wrapper C++ d’OpenCL on réduit la quantité de poutine requise. 60 GPGPU et OpenCL Restrictions • Pour les noyaux: ✓ pas d'appels récursifs ✓ librairie de fonctions standards ✓ peut fonctionner avec CPUs • Group_size: ✓ au moins la dimension d'un «warp» (NVIDIA) ou d'un «wavefront» (AMD) ✓ idéalement une puissance de 2 61 GPGPU et OpenCL Plateformes de développement • Apple: ✓ OpenCL est installé par défaut avec les outils de développement (Xcode) • AMD: ✓ http://developer.amd.com/GPU/AMDAPPSDK/ DOWNLOADS/Pages/default.aspx • NVIDIA: ✓ http://developer.nvidia.com/object/opencldownload.html 62 GPGPU et OpenCL Structure de base d’un programme OpenCL 1. Initialiser plateformes, périphériques, contextes, files d’opérations 2. Créer et compiler des noyaux 3. Créer des objets mémoire, les remplir (clEnqueueWriteBuffer) 4. Lancer l’exécution des noyaux (clEnqueueNDRangeKernel) 5. Attendre la fin de l’exécution 6. Récupérer le résultat (clEnqueueReadBuffer) 7. Répéter 3-6 tant qu’il reste des traitements 63 GPGPU et OpenCL 64 GPGPU et OpenCL Cas à 2 dimensions indices locaux: sx et sy indices globaux: gx et gy 65 GPGPU et OpenCL Contrôle du flot dans un work-group • Instruction SIMD ✓ division du programme en warps (nvidia) ou wavefronts (amd) ✓ exécution d’une même instruction pour tous les processeurs du compute unit (appelé lockstep execution) • Malgré le lockstep, chaque work-item peut prendre un chemin d’exécution différent ✓ les instructions des deux chemins doivent être lancées pour tous les threads ✓ les processeurs qui n’ont pas pris le branchement attendent • 66 Une façon optimale d’exploiter les branchement et de les utiliser avec une granularité correspondant au work-groups GPGPU et OpenCL Définir la taille des workgroups • global_size = global_size_0 * global_size_1 * ... • Le choix optimal des local_work_size dépendra de l’architecture de la carte. ✓ le nombre de threads éxécutant les mêmes instructions en parallèle pour chaque Compute Unit est différent (32 pour nvidia, 64 pour ATI) ✓ cela dépend du nombre de coeurs dans chaque Compute Unit • Utiliser un multiple de ce chiffre, sinon des coeurs seront inutilisés. Truc simple, puissance de 2 : ✓ nvidia : 32, 64, 128, 256, 512 ✓ amd : 64, 128, 256, 512 ✓ les local_size_i doivent être un multiple des global_size_i (possible de tricher) • Tenir compte des besoins en mémoire (locale et privée) de vos work-groups, si ceux-ci sont gourmands, utiliser des valeurs plus petites 67 GPGPU et OpenCL L’occupation (occupancy) • Les work-groups sont assignés aux compute units ✓ Lorsqu’un work-group est assigné à un compute unit, il ne peut pas être swappé avant d’avant terminé l’exécution de ses threads • S’il y a suffisamment de ressources, plusieurs work-groups peuvent s’exécuter sur un même compute unit en alternance ✓ permet de cacher la latence des accès mémoire • 68 Le terme occupation est utilisé pour décrire combien les ressources d’un compute unit sont utilisées GPGPU et OpenCL L’occupation - registres • La disponibilité des registres et un facteur limitatif • Le nombre maximum de registres demandés par votre noyau doit être disponible pour tous les threads du work-group ✓ ex: GPU avec 16384 registres par compute unit roulant un noyau qui nécessite 35 registres par thread ✓ Chaque compute unit peut exécuter combien de threads au maximum? ✓ Qu’est-ce que ça implique pour la taille du work-group? 69 GPGPU et OpenCL L’occupation - autre facteurs • Mémoire locale ✓ 32-64 KB pour un même compute unit ✓ tous les work-group doivent se partager cette mémoire • Nb. max de threads ✓ Les cartes ont un nombre maximum de threads pouvant être actifs en même temps ✓ Varie selon la carte, selon le fabricant. ✓ NVidia : limite par Compute Unit. AMD : limite globale pour le GPU au complet + limite locale. 70 GPGPU et OpenCL Autres outils pour noyaux • Types vecteur : typen, où n est une puissance de 2 (e.g., float2, float4, ...) ✓ n = 3 est supporté, aligné en mémoire comme n=4 • Fonctions built-ins math pour valeurs continues ou discrètes ✓ sin, asin, log, exp, etc., pour tous les types incluant vecteurs ✓ fonctions spéciales pour entiers, supportant le bit-shifting, la troncation et bien d’autres. Exemple : upsample(int hi, int lo) // retourne (hi << 8) | lo ✓ fonctions géométriques: produit scalaire, produit vectoriel, norme de vecteurs, calcul de distances 71 GPGPU et OpenCL Produit de matrices • Problème à 2 dimensions • Chaque fil permet de calculer un élément du résultat ✓ le global size correspond au nombre d'éléments dans la matrice 72 GPGPU et OpenCL Solution séquentielle // A(NxP) * B(PxM) = C(NxM) // Iterate over the rows of Matrix A for(int n = 0; n < N; n++) { // Iterate over the columns of Matrix B for(int m = 0; m < M; m++) { C[n*M + m] = 0; // Multiply and accumulate the values in the current row // of A and column of B for(int p = 0; p < P; p++) { C[n*M + m] += A[n*P + p] * B[p*M + m]; } } } 73 GPGPU et OpenCL __kernel void mmul(const int M, const int N, const int P, __global float* A, __global float* B, __global float* C) { int p; int col = get_global_id(0); int row = get_global_id(1); float tmp; if((row < N) && (col < M)) { tmp = 0.0; for(p=0; p<P; p++) tmp += A[row*P + p] * B[p*M + col]; } C[row*M+col] = tmp; } 74 GPGPU et OpenCL Temps d’exécution • Multiplication de deux matrices 1000x1000 ✓ 0.0435s pour le code GPU (GTX Nvidia 660 Ti) ✓ 2.59s pour une variante avec OMP (Intel Core i5, 4 coeurs sans hyperthreading) ✓ 7.20s pour un seul processeur (même CPU) • Speedup ✓ GPU-séquentiel : 165 ✓ GPU-OpenMP : 60 ✓ Pour être équitable, il manquerait une comparaison avec opérations SSE 75 GPGPU et OpenCL • Problème? ✓ on accède plusieurs fois aux éléments des matrices qui sont stockées dans la mémoire globale ✓ la mémoire globale est lente ✓ surtout lorsqu'elle doit fournir beaucoup de fils d'exécution • Solution? ✓ copier les matrices dans la mémoire locale ✓ chaque fil doit participer à la recopie ✓ compliqué, donc s’assurer qu’on a besoin d’un telle optimisation 76 GPGPU et OpenCL #define BLOCK_SIZE 8 __kernel __attribute__((reqd_work_group_size(BLOCK_SIZE, BLOCK_SIZE, 1))) void floatMatrixMultLocals(__global float * MResp, __global __global int * q) { //Identification of this workgroup int i = get_group_id(0); int j = get_group_id(1); float * M1, __global float * M2, //Identification of work-item int idX = get_local_id(0); int idY = get_local_id(1); //matrixes dimensions int p = get_global_size(0); int r = get_global_size(1); int qq = q[0]; //Number of submatrixes to be processed by each worker (Q dimension) int numSubMat = qq/BLOCK_SIZE; float4 resp = (float4)(0,0,0,0); __local float A[BLOCK_SIZE][BLOCK_SIZE]; __local float B[BLOCK_SIZE][BLOCK_SIZE]; for { (int k=0; k<numSubMat; k++) //Copy submatrixes to local memory. Each worker copies one element //Notice that A[i,k] accesses elements starting from M[BLOCK_SIZE*i, BLOCK_SIZE*j] A[idX][idY] = M1[BLOCK_SIZE*i + idX + p*(BLOCK_SIZE*k+idY)]; B[idX][idY] = M2[BLOCK_SIZE*k + idX + qq*(BLOCK_SIZE*j+idY)]; barrier(CLK_LOCAL_MEM_FENCE); for (int k2 = 0; k2 < BLOCK_SIZE; k2+=4) { float4 temp1=(float4)(A[idX][k2], A[idX][k2+1], A[idX][k2+2], A[idX][k2+3]); float4 temp2=(float4)(B[k2][idY], B[k2+1][idY], B[k2+2][idY], B[k2+3][idY]); resp += temp1 * temp2; } barrier(CLK_LOCAL_MEM_FENCE); } MResp[BLOCK_SIZE*i + idX + p*(BLOCK_SIZE*j+idY)] = resp.x+resp.y+resp.z+resp.w; } 77 GPGPU et OpenCL Mémoire locale • Accessible pour tous les work-items d’un work-group • Définition dans le noyau directement, ou passé comme paramètre ✓ préfixe __local • Il faut synchroniser les accès pour s’assurer que l’écriture est bien acheminée ✓ (même si parfois la connaissance du hardware nous permettrait de sauter cette étape) ✓ Utilisation de barrières (local, global) 78 GPGPU et OpenCL • Voici un exemple de programme avec mémoire locale: __kernel void localAccess( __global float* A, __global float* B, __local float* C ) { __local float aLocalArray[1]; if( get_local_id(0) == 0 ) { aLocalArray[0] = A[0]; } C[get_local_id(0)] = A[get_global_id(0)]; barrier( CLK_LOCAL_MEM_FENCE ); float neighborSum = C[get_local_id(0)] + aLocalArray[0]; if( get_local_id(0) > 0 ) neighborSum = neighborSum + C[get_local_id(0)-1]; B[get_global_id(0)] = neighborSum; } ✓ Allocation du paramètre local err = clSetKernelArg(kernel_object, 3, sizeof(float)*len_C, NULL); 79 GPGPU et OpenCL Accès à la mémoire • Soit un tableau d'entiers x (32 bits) X: 0x00001232 0 0x00001232 1 2 3 4 5 6 0x00001236 7 0x00001248 . . . . . . . • Un fil veut accéder à l'élément x[0] int temp = x[0]; 81 GPGPU et OpenCL Problème... • Les bus de mémoire sur les GPUs sont très larges ✓ par exemple 256 bits (32 octets) ✓ permet des bandes passantes plus élevées • Les accès en mémoire doivent cependant être alignés avec la largeur du bus: Desired address: 0x00001232 Bus mask: 0xFFFFFFE0 Bus access: 0x00001220 • 82 Tous les accès de 0x00001220 à 0x0000123F produiront l'adresse 0x00001220 GPGPU et OpenCL Histogramme • Statistiques sur un ensemble de données • Pratique pour faire des statistiques, mesures des quantiles, etc. 83 GPGPU et OpenCL Histogramme • Solution séquentielle : for (int i = 0; i < BIN_COUNT; i++) result[i] = 0; for (int i = 0; i < data.size(); i++) result[computeBin(data[i])]++; • Comment paralléliser? • Est-ce qu’on peut lancer un thread pour chaque point dans data? 84 GPGPU et OpenCL Version naïve __kernel void naive_histo(__global int *d_bins, __const int *d_in, __const int BIN_COUNT) { int myId = get_global_id(0); int myItem = d_in[myId]; int myBin = myItem % BIN_COUNT; d_bins[myBins]++; } 85 GPGPU et OpenCL Version naïve (fixed) __kernel void naive_histo(__global int *d_bins, __const int *d_in, __const int BIN_COUNT) { int myId = get_global_id(0); int myItem = d_in[myId]; int myBin = myItem % BIN_COUNT; atomic_inc(&d_bins[myBins]); } • Fonctionne, mais sous-optimal à cause de l’accès atomique ✓ Un seul thread incrémente un bin à la fois.. presque séquentiel pour un petit nombre de bins ✓ le GPU cache la latence en switchant à d’autres blocs de threads pour calculer, mais pas grand chose à calculer dans ce cas-ci... • 86 Solution : calculer un histogramme local sur chaque work-group GPGPU et OpenCL Histogramme local • Supposons que nos données sont une image : ✓ histogramme sur les couleurs 87 GPGPU et OpenCL Histogramme local • Opération de réduction requise à la fin ✓ réalisable en parallèle 88 GPGPU et OpenCL Paramètres pour l’exécution • Nombre de work-groups : ✓ Choisir un nombre le plus petit possible ✓ Idéalement, nb work-groups = nb compute units ✓ Limite la complexité de l’opération de réduction à la fin • Nombre de work-items par work-group ✓ Le premier défini le second (ou vice versa) ✓ De ce côté, les critères définis précédemment sont encore bons ✓ Avoir 2-4 fois le nombre de threads habituellement dans un warp ou wavefront permet d’utiliser le compute unit plus efficacement • 89 Compromis à faire GPGPU et OpenCL Optimisation des accès mémoire • L’opération ‘histogramme’ ne dépend pas de l’ordre des données • Faire une seule lecture à la fois est inefficace (e.g., data[0]) • Supposant un GPU AMD : lecture de 128 bit sur chaque work-item ✓ le hardware est optimisé pour faire la lecture de pixels à quatre composantes 32 bits, donne la meilleure performance ✓ lecture de vecteurs de taille 4 90 GPGPU et OpenCL Accès mémoire • Accès mémoires consécutifs pour work-items consécutifs : uint g_id = get_global_id(0); uint stride = get_global_size(0); for (i=0, idx=g_id; i<n4VectorsPerWorkItem; i++, idx += stride) { uint4 temp = Image[idx]; ... • Chaque work-item lit ses pixels en série : uint g_id = get_global_id(0); for (i=0, idx=g_id*n4VectorsPerWorkItem; I<n4Vectors PerWorkItem; i++, idx++) { uint4 temp = Image[idx]; 91 GPGPU et OpenCL Accès mémoire (illustré) 92 GPGPU et OpenCL Remplissage des histogrammes locaux • Encore une fois, opération atomiques (évite les courses critiques) uint4 px = read_imageui(img, flags, uint2(idx, y)); atom_inc(&tmp_histogram[px.x]); atom_inc(&tmp_histogram[256+px.y]); atom_inc(&tmp_histogram[512+px.z]); 93 GPGPU et OpenCL Réduction • Concatener les résultats : opération standard de réduction • Définir pour un histogramme avec NUM_BINS * 3 valeurs (3 couleurs) ✓ il faut voir ces 3 histogrammes comme un long vecteur ✓ réduction (addition) sur global_work_size vecteurs de NUM_BINS*3 valeurs 94 GPGPU et OpenCL Réduction simple • Comment? • Supposons une réduction simple (somme) pour un vecteur d’entiers (plutôt qu’un vecteur de vecteurs) ✓ Paramètres : {1, 2, 3, 4}, + ✓ Résultat : 1 + 2 + 3 + 4 = 10 • En parallèle : (1 + 2) + (3 + 4) • Pour de très grands vecteurs, possible de procéder en deux phases 95 GPGPU et OpenCL Réduction d’histogrammes • Noyau exécutant la somme de tous les histogrammes partiels/locaux créés dans la première étape • Un thread par bin (boîte), effectue la somme de tous les histogrammes locaux pour une seule tranche ✓ opération assez simple, car besoins différents... il s’agit d’un cas spécial de réduction. ✓ lorsqu’on a plus de données, on va plutôt dans l’autre sens ✓ imaginer un vecteur de 2^20 données 96 GPGPU et OpenCL Résultat • Histogramme pour image 1920x1080 • Temps d’exécution séquentiel ~ 17.3ms • Temps d’exécution parallèle ~ 0.453ms • Possible d’aller optimiser encore plus en utilisant d’autres stratégies... ✓ Il y a des conflits dans la mémoire locale, au niveau des opérations atomiques ✓ Utiliser plusieurs histogrammes locaux par work-group, effectuer un reduce de plus (temps requis minimal) ✓ Dépend du nombre de bins encore une fois, plus il est petit, plus ça sera intéressant (car plus de conflits) 97 GPGPU et OpenCL Analyser la performance • Profilage de la performance avec les évènements ✓ Doit être activé préalablement dans la queue ✓ Permet de savoir quand une tâche (read, write, kernel) a été mise dans la file, lancée, terminée. • Activation : queue = clCreateCommandQueue( context, device, CL_QUEUE_PROFILING_ENABLE, &err); • Pour la suite, on passe un objet event à chaque appel de fonction : err = clEnqueueNDRangeKernel(queue, kernel, ..., &event); 98 GPGPU et OpenCL Analyser la performance • Pour récuperer l’info : cl_int clGetEventProfilingInfo (cl_event event, "cl_profiling_info param_name, "size_t param_value_size, "void *param_value, "size_t *param_value_size_ret) • enum cl_profiling_info: ✓ CL_PROFILING_COMMAND_QUEUED, CL_PROFILING_COMMAND_SUBMIT, CL_PROFILING_COMMAND_START, CL_PROFILING_COMMAND_END • Retourne une valeur de temps absolue en ns 99 GPGPU et OpenCL AMD Accelerated parallel processing profiler (APP) • Deux modes d’utilisation: ✓ plug-in pour Visual Studio 2008/2010 ✓ outil ligne de commande pour Windows/Linux • Essentiellement, le plug-in lit la sortie de l’outil ligne de commande et le dessine plus élégamment • Permet de déterminer si votre application est limitée par l’exécution du noyau ou bien les transferts de données 100 GPGPU et OpenCL CHAPTER 12 OpenCL profiling and debugging APP (visualisation) 101FIGURE 12.1 GPGPU et OpenCL Débugger votre code • Il existe un debugger : gDEBugger ✓ fonctionne pour OpenCL et OpenGL ✓ à vous de voir si vous voulez vous en servir • Plus simple : extension AMD printf ✓ attention, un printf dans un noyau va imprimer pour chaque work_item ✓ ceci étant dit, on l’active comme suit : #pragma OPENCL EXTENSION cl_amd_printf : enable 102 GPGPU et OpenCL Autres façons de faire du GPGPU • Utilisation de primitives OpenGL, du pipeline de rendu graphique ✓ il faut programmer des shaders en GLSL • Exploiter les textures, les viewports • Demande de réfléchir beaucoup avant de trouver une solution (ex. comment faire une FFT avec des triangles?) 103 GPGPU et OpenCL Exemple : réduction avec OpenGL • Stocker les données dans une texture 2D • Effectuer le rendu d’un carré sur lequel la texture est appliquée ✓ Aligner le carré avec la caméra ✓ Le carré doit faire le 1/4 de la taille de la texture, cela va forcer une interpolation • Utiliser la sortie (le rendu) de ce carré comme entrée pour la passe suivante • Répéter jusqu’à la fin 104 GPGPU et OpenCL Shader résultant uniform sampler2D u_Data; in vec2 fs_Texcoords; out float out_MaxValue; void main(void) { float v0 = texture(u_Data, fs_Texcoords).r; float v1 = textureOffset(u_Data, fs_Texcoords, ivec2(0, 1)).r; float v2 = textureOffset(u_Data, fs_Texcoords, ivec2(1, 0)).r; float v3 = textureOffset(u_Data, fs_Texcoords, ivec2(1, 1)).r; out_MaxValue = max(max(v0, v1), max(v2, v3)); } • Intéressant, mais compliqué. Plus simple avec OpenCL 105 GPGPU et OpenCL CUDA • À peu de choses près, fonctionne de la même façon ✓ les GPU NVidia sont très similaires à ceux d’AMD • Un compilateur externe vient remplacer le compilateur ‘c’ standard. ✓ nvcc ✓ Permet d’avoir les noyaux dans le même fichier source que votre programme principal (le compilateur reconnaît les attributs et traite le code différemment) • Ne fonctionne pas sur des CPUs ✓ on ne parle plus de calcul hétérogène, mais strictement de GPGPU (pour cartes NVidia). 106 GPGPU et OpenCL Comparaison avec CUDA • 107 CUDA OpenCL GPGPU et OpenCL Programme CUDA (noyau) __global__ void vectorAdd(const float *A, const float *B, float *C, int numElements) { int i = blockDim.x * blockIdx.x + threadIdx.x; if (i < numElements) { C[i] = A[i] + B[i]; } } • Différence dans les indices de thread 108 GPGPU et OpenCL Programme CUDA (host) • Fichier... 109 GPGPU et OpenCL Programme CUDA • Beaucoup moins de gestion requise dans le programme hôte ✓ On sait à quoi s’attendre... le compilateur fait également une bonne partie du travail • Remarquez que le noyau est pratiquement identique 110 GPGPU et OpenCL Conclusion • OpenCL permet d'exploiter la puissance de calcul des GPGPUs de manière indépendante de la plateforme ✓ définir le contexte (device + queue) ✓ définir le programme et les noyaux ✓ spécifier les paramètres des noyaux ✓ exécuter un NDRange (dimension du problème + dimension du groupe de travail • Attention à la mémoire ✓ avec des centaines de fils d'exécution parallèles, la mémoire globale devient vite un goulot d'étranglement 111 GPGPU et OpenCL • Documents et information: ✓ http://www.khronos.org/opencl/ ✓ http://www.khronos.org/registry/cl/specs/ opencl-1.1.pdf ✓ http://www.khronos.org/registry/cl/specs/openclcplusplus-1.1.pdf ✓ http://www.khronos.org/files/opencl-1-1-quickreference-card.pdf • Tutoriels: ✓ http://developer.amd.com/zones/OpenCLZone/ universities/Pages/default.aspx? cmpid=DevBanner_UnivKit ✓ http://opencl.codeplex.com/wikipage?title=OpenCL %20Tutorials%20-%201 112 GPGPU et OpenCL