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

Documents pareils