La programmation GPU avec C++ AMP pour les performances extrêmes
-
Upload
microsoft-developpeurs -
Category
Documents
-
view
814 -
download
1
description
Transcript of La programmation GPU avec C++ AMP pour les performances extrêmes
Donnez votre avis !Depuis votre smartphone, sur : http://notes.mstechdays.fr
De nombreux lots à gagner toutes les heures !!!
Claviers, souris et jeux Microsoft…
Merci de nous aider à améliorer les TechDays
http://notes.mstechdays.fr
La programmation GPU avec C++ AMP pour les performances extrêmes
Bruno BoucardExpert Parallèle
FINAXYS
Code / Développement
http://blogs.msdn.com/devpara/LAN401
CONSEIL ET ARCHITECTURE IT-FINANCE•160 collaborateurs spécialisés en IT-Finance
•CA 2012 (prév.) de 16M€
•Une large palette d’expertises :
• Conseil SI & fonctionnel Finance,
• AMOA SI et métiers Finance
• Ingénierie logicielle (architecture, études, conception, développement, intégration continue)
• Technologies Microsoft C++ et C#/.NET
• Support et production applicative
•Un accompagnement global sur l’ensemble du cycle de vie projet, du cadrage à la recette, avec une démarche
qualité intégrée
•Une contractualisation sur mesure : forfait, centre de service, assistance technique groupée ou assistance
technique simple
•Nos références :
Ma société
De quoi allons-nous parler ?
Démo Cartoonizer !
•1ière Partie : L’essentiel de C++ AMP
– Contexte
– Fondamentaux
•2ième Partie: Concepts plus avancés
– Comprendre le calcul tuilé
– Mise au point avec Visual Studio 2012
•Conclusion
Agenda
La programmation GPU avec C++ AMP
• Bande passante mémoire élevée
• Faible consommation d'énergie/FLOP
• Haut niveau de parallélisme
• Pipelines d'exécution peu profonds
• Accès cache mémoire séquentiels
• Supporte la programmation parallèle orientée
données
• Programmation réservée à des spécialistes
Contexte
CPU vs GPU aujourd’hui
•Faible bande passante mémoire
•Forte consommation d’énergie/FLOP
•Niveau moyen de parallélisme
•Pipelines d’exécution profonds
•Accès cache mémoire aléatoires
•Supporte la programmation généraliste
•Programmation grand public
• CPUs et GPUs se rapprochent de plus en plus …• …mais rien n’est figé, le sujet est toujours en
mouvement …
• C++ AMP a été pensé comme une solution grand public pas seulement pour aujourd’hui, mais aussi pour demain
Contexte
CPU vs GPU aujourd’hui
image source: AMD
• Windows Phone• Windows RT• Windows Embedded• Xbox
•Windows Azure
•Windows Desktop
•Windows Server
•Windows HPC Server
Contexte
Plateformes d’exécution de C++ AMP*
... tourne aussi sur des plateformes non Microsoft via une spécification ouverte
* Certaines plateformes seront disponibles dans la prochaine version de C++ AMP
• Appartient à Visual C++ • Intégrée à Visual Studio• Librairie STL-like pour gérer
les données multidimensionnelles
• Construite sur Direct3D 11• Spécification ouverte
Contexte
Qu’est que C++ AMP ?
performance
portabilité
productivité
Fondamentaux
Transformer ce code séquentiel en code GPU
void AddArrays(int n, int * pA, int * pB, int * pC){
for (int i=0; i<n; i++)
{ pC[i] = pA[i] + pB[i]; }
}
#include <amp.h>using namespace concurrency;
void AddArrays(int n, int * pA, int * pB, int * pC){ array_view<int,1> a(n, pA); array_view<int,1> b(n, pB); array_view<int,1> c(n, pC); parallel_for_each( c.extent, [=](index<1> i) restrict(amp) { c[i] = a[i] + b[i]; } );}
void AddArrays(int n, int * pA, int * pB, int * pC){
for (int i=0; i<n; i++)
{ pC[i] = pA[i] + pB[i]; }
}
Fondamentaux
Comprendre le code C++ AMPvoid AddArrays(int n, int * pA, int * pB, int * pC){ array_view<int,1> a(n, pA); array_view<int,1> b(n, pB); array_view<int,1> c(n, pC); parallel_for_each(
c.extent, [=](index<1> i) restrict(amp) { c[i] = a[i] + b[i];
} );}
Les variables array_view sont capturées par valeur et leurs données associées sont copiées dans l’accélérateur à la demande
restrict(amp): dit au compilateur de forcer l’exécution de ce code sur le GPU
parallel_for_each: exécute l’expression lambda sur l’accélérateur une fois par thread
extent: le nombre de threads et leur topologie pour exécuter l’expression lambda
index: le thread ID qui exécute l’expression lambda, est utilisé pour retrouver les données
array_view: wrapping des données pour les traiter sur l’accélérateur
• Exécute l’expression lambda pour chaque point de l’extent• Semble synchrone, mais en réalité est asynchrone
Fondamentaux
Comprendre le fonctionnement du parallel_for_each
parallel_for_each( c.extent, [=](index<1> idx) restrict(amp) { c[idx] = a[idx] + b[idx]; } );}
Code noyau
Exécution sur la carte GPU
c[0]
= a
[0] +
b[0
]
0
c[1]
= a
[1] +
b[1
]
1
c[2]
= a
[2] +
b[2
]
2
c[3]
= a
[3] +
b[3
]
3
c[4]
= a
[4] +
b[4
]
4
c[5]
= a
[5] +
b[5
]
5
c[6]
= a
[6] +
b[6
]
6
c[7]
= a
[7] +
b[7
]
7
index
thread
Code noyau
• S'applique aux fonctions (y compris les lambda)• resctrict(…) informe le compilateur d’appliquer des
restrictions au langage– e.g. restrictions spécifiques, optimisations,
génération de code– Dans cette 1ère version, supporte deux directives • cpu – est le défaut implicite• amp – vérifie la conformité de la fonction vis à vis
de C++ AMPFondamentaux
Le mot clef restrict
Fondamentaux
Les restrictions du mot clef restrict
• Pas de
• récusions
• ‘volatile'
• fonctions virtuelles
• pointeurs de fonctions
• pointeurs vers des fonctions
membres
• pointeurs dans des structures
• pointeurs de pointeurs
• champs de bits
• Pas de
• goto ou labels
• throw, try, catch
• Variables globales ou statiques
• dynamic_cast ou typeid
• déclarations __asm__
• varargs
• types non supportés
• e.g. char, short, long double
Fondamentaux
Surcharge via le mot clef restrictdouble cos( double d ); // 1a code cpudouble cos( double d ) restrict(amp); // 1b code ampdouble bar( double d ) restrict(cpu,amp); // 2 supporte les deux mode
void some_method(array_view<double,2> c) { parallel_for_each( c.extent, [=](index<2> idx) restrict(amp) { //… double d0 = c[idx]; double d1 = bar(d0); // ok, bar supporte amp aussi double d2 = cos(d0); // ok, sélection de la surcharge amp //… });}
Fondamentaux
Gestion des données multidimensionnelles• index<N> - représente un point à N coordonnées
• extent<N> - taille de la données d’un espace à N dimensions
• N peut être n’importe quel nombre <=128
index<1> i(2);
extent<1> e(6);
6
index<2> i(0,2);
extent<2> e(3,4);4
3
index<3> i(2,0,1);
extent<3> e(3,2,2);2
23
• Vue sur les données stockées dans la mémoire CPU ou la mémoire GPU
• Capture par valeur [=] dans l’expression lambda• De type T et de rang N• Réclame un extent• Rectangulaire• Accès aux éléments déclenchent• des copies implicites
Fondamentaux
Le conteneur: array_view<T, N>
index<2> i(3,9);
int o = a[i]; // ou a[i] = 16;// ou int o = a(3, 9);
vector<int> v(10); extent<2> e(2,5); array_view<int,2> a(e, v);
// Les 2 lignes peuvent aussi s’écrire // array_view<int,2> a(2,5,v);
• Tableau multidimensionnel qui contient des éléments de type T de dimension N
• Les données sont stockées dans la mémoire de l'accélérateur
• Capture par référence [&] dans l’expression lambda
• Copie explicit
• Presque identique à l’interface array_view<T,N>
Fondamentaux
Le second conteneur: array<T, N>
vector<int> v(8 * 12);extent<2> e(8,12);
array<int,2> a(e);copy_async(v.begin(), v.end(), a);
parallel_for_each(e, [&](index<2> idx) restrict(amp) { a[idx] += 1;});copy(a, v.begin());
• accelerator – DX11 GPU
• Support GPU– WARP (Windows Advanced Rasterisation
Platform)• multi-core & vectorisazion (SSE) • Accessible via Direct3D
– REF• Emulateur qui simule un pilote
direct3d sur CPU– CPU
• Support pour optimiser les transferts de données entre l'hôte et un accelerator_view
Fondamentaux
Comprendre les accélérateurs
CPUs
System memory
GPUPCIeGPU
GPU
GPU
Host Accélérateurs
• accelerator View– Contexte pour
ordonnancer les threads GPU et gérer les données en mémoire
Fondamentaux
Démo Accélérateurs!
• Aij * Bjk = Cik – Chaque ligne A est multipliée par
toutes les colonnes entières de B– Chaque multiplication est sommée dans
une variable temporaire– Après avoir multiplié tous les éléments
de la ligne avec tous les éléments de la colonne courante
– Le résultat temporaire est placé dans la cellule correspondante de la « matrice produit » C
Fondamentaux
Algorithme produit matriciel
Fondamentaux
Produit matriciel du CPU au GPUvoid MatrixMultiplySerial( vector<float>& vC, const vector<float>& vA, const vector<float>& vB, int M, int N, int W ){
for (int row = 0; row < M; row++) { for (int col = 0; col < N; col++){ float sum = 0.0f; for(int i = 0; i < W; i++) sum += vA[row * W + i] * vB[i * N + col]; vC[row * N + col] = sum; } }}
void MatrixMultiplyAMP( vector<float>& vC, const vector<float>& vA, const vector<float>& vB, int M, int N, int W ){ array_view<const float,2> a(M,W,vA),b(W,N,vB); array_view<float,2> c(M,N,vC);c.discard_data(); parallel_for_each(c.extent, [=](index<2> idx) restrict(amp) { int row = idx[0]; int col = idx[1];
float sum = 0.0f; for(int i = 0; i < W; i++) sum += a(row, i) * b(i, col); c[idx] = sum; } );}
Fondamentaux
Démo Produit Matriciel !
Résumé de la première partie
1ière Partie: ce que vous avez appris ! Sur des volumes de données immenses, le calcul sur GPU offre des
performances extraordinaires Microsoft souhaite démocratiser la programmation GPU avec C++ AMP sur
toutes les plateformes
Les éléments fondamentaux class index<N> : pointe une valeur dans un conteneur multidimensionnel class extent<N> : taille d’un conteneur multidimensionnel restrict(amp, cpu) : restreint la syntaxe C++ pour exécuter une méthode
ou une lambda en mode noyau parallel_for_each : exécute une boucle en parallèle sur des données
multidimensionnelle class array_view<T,N> : wrapper multidimensionnel sur des données class accelerator : accélérateur utilisé en interne pour exécuter le code
parallèle class accelerator_view : isole l’accélérateur des différents usages
multithreads class array<T,N> : conteneur multidimensionnel sur des données
Une vue du matériel GPU pour un développeur
Comprendre le calcul tuilé
Registres par Threads
Mémoire Globale
………
……… ……
Thread
N’est pas montré:• Mémoire constante• Mémoire des
contrôleurs• Schedulers• Autres caches• Cas Multi-GPU
Une vue du matériel GPU pour un développeur
Comprendre le calcul tuilé
Registres par Threads
Mémoire Globale
………
Registres par Threads
……… ……
Tuile composée de threads
N’est pas montré:• Mémoire constante• Mémoire des
contrôleurs• Schedulers• Autres caches• Cas Multi-GPU
Une vue du matériel GPU pour un développeur
Comprendre le calcul tuilé
Cache Programmable
Registres par Threads
Mémoire Globale
………
Cache Programmable
Registres par Thread
……… …… Les variable tile_static
sont partagée par les threads dans la même tuile
N’est pas montré:• Mémoire constante• Mémoire des contrôleurs• Schedulers• Autres caches• Cas Multi-GPU
Tuile composée de threads
• Orchestrer des threads dans des tuiles– La version de base ne le
permet pas• parallel_for_each version
tuile accepte ces surcharges– tiled_extent<D0> ou
tiled_extent<D0, D1> ou tiled_extent<D0, D1, D2>
• L’expression lambda accepte– tiled_index<D0> ou
tiled_index<D0, D1> ou tiled_index<D0, D1, D2>
Comprendre le calcul tuilé
parallel_for_each: surcharge pour les tuiles
parallel_for_each(data.extent.tile<6>(), [=] (tiled_index<6> t_idx) restrict(amp) { … });
array_view<int,1> data(12, my_data);
parallel_for_each(data.extent, [=] (index<1> idx) restrict(amp) { … });
Techniques plus avancées: calcul tuilé
tiled_extent (depuis un extent)
extent<1> e(12);
0 1 2 3 4 5 6 7 8 9 10 11
tiled_extent<6> t_e = e.tile<6>();
0 1 2 3 4 5 6 7 8 9 10 11
extent<2> ee(2, 6);
tiled_extent<2, 2> t_ee = ee.tile<2, 2>();
0,0 0,1 0,2 0,3 0,4 0,5
0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
1,0 1,1 1,2 1,3 1,4 1,5
• Soit
• Lorsque la lambda est exécutée par– t_idx.global // index<2> (1,3)– t_idx.local // index<2> (1,1)– t_idx.tile // index<2> (0,1)– t_idx.tile_origin // index<2> (0,2)
Comprendre le calcul tuilé
tiled_index col 0 col 1 col 2 col 3 col 4 col 5
row 0
row 1
T
array_view<int,2> data(2, 6, p_my_data);parallel_for_each( data.extent.tile<2,2>(), [=] (tiled_index<2,2> t_idx)… { … });
T
• Le mot clef tile_static dédié au stockage mémoire• Second mot clef ajouté au langage C++• Représente la mémoire locale du GPU
• Usage du mot clé tile_static– Dans une boucle parallel_for_each tuilée– Le mot clef tile_static devant une déclaration de
variable locale place cette variable dans la mémoire locale GPU pour la tuile courante
– Cette mémoire est partagée par tous les threads associés à la tuile
– Applicable seulement pour les fonctions restrict(amp)
Comprendre le calcul tuilé
tile_static
• classe tile_barrier– Synchronise tous les threads au sein d’une tuile
jusqu'à ce que tous les accès à la mémoire soient terminés• t_idx.barrier.wait();• ~ t_idx.barrier.wait_with_all_memory_fence();
– Synchronise tous les threads au sein d’une tuile jusqu'à ce que tous les accès à la mémoire globale soient terminés• t_idx.barrier.wait_with_global_memory_fence();
– Synchronise tous les threads au sein d’une tuile jusqu'à ce que tous les accès à la mémoire la mémoire tile_static (locale) soient terminés• t_idx.barrier.wait_with_tile_static_memory_fence();
tile_barrier
Comprendre le calcul tuilé
Comprendre le calcul tuilé
Cacher avec tile_static1 static const int TS = 2; 2 array_view<int, 2> av(2, 6, my_vector); 3 parallel_for_each(av.extent.tile<TS,TS>(),
[=](tiled_index<TS,TS> t_idx) restrict(amp) 4 { 5 tile_static int t[TS][TS]; 6 t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global]; 7 8 if (t_idx.local == index<2>(0,0)) { 9 int temp = t[0][0] + t[0][1] + t[1][0] + t[1][1]; 10 av[t_idx.tile_origin] = temp; 11 } 12 }); 13 int sum = av(0,0) + av(0,2) + av(0,4); //the three tile_origins
0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
Comprendre le calcul tuilé
tile_static en action1 static const int TS = 2; 2 array_view<int, 2> av(2, 6, my_vector); 3 parallel_for_each(av.extent.tile<TS,TS>(),
[=](tiled_index<TS,TS> t_idx) restrict(amp) 4 { 5 tile_static int t[TS][TS]; 6 t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global]; 7 8 if (t_idx.local == index<2>(0,0)) { 9 int temp = t[0][0] + t[0][1] + t[1][0] + t[1][1]; 10 av[t_idx.tile_origin] = temp; 11 } 12 }); 13 int sum = av(0,0) + av(0,2) + av(0,4); //the three tile_origins
0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
Comprendre le calcul tuilé
Synchro avec tile_barrier1 static const int TS = 2; 2 array_view<int, 2> av(2, 6, my_vector); 3 parallel_for_each(av.extent.tile<TS,TS>(),
[=](tiled_index<TS,TS> t_idx) restrict(amp) 4 { 5 tile_static int t[TS][TS]; 6 t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global]; 7 tile_barrier.wait(); 8 if (t_idx.local == index<2>(0,0)) { 9 int temp = t[0][0] + t[0][1] + t[1][0] + t[1][1]; 10 av[t_idx.tile_origin] = temp; 11 } 12 }); 13 int sum = av(0,0) + av(0,2) + av(0,4); //the three tile_origins
0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
• Chargement des données pour 4 threads
• Les threads chargent plusieurs fois la même valeur depuis la mémoire globale !– Comment éviter ce problème ?
Comprendre le calcul tuilé
Retour sur la Multiplication Matricielle
Multiplication Matricielle – Exemple (tuilée)
Techniques plus avancées: calcul tuilé
void MatrixMultSimple(vector<float>& vC, const vector<float>& vA, const vector<float>& vB, int M, int N, int W ){
array_view<const float,2> a(M, W, vA), b(W, N, vB); array_view<float,2> c(M,N,vC); c.discard_data(); parallel_for_each(c.extent, [=] (index<2> idx) restrict(amp) { int row = idx[0]; int col = idx[1]; float sum = 0.0f;
for(int k = 0; k < W; k++) sum += a(row, k) * b(k, col);
c[idx] = sum; } );}
void MatrixMultTiled(vector<float>& vC, const vector<float>& vA, const vector<float>& vB, int M, int N, int W ){ static const int TS = 16; array_view<const float,2> a(M, W, vA), b(W, N, vB); array_view<float,2> c(M,N,vC); c.discard_data(); parallel_for_each(c.extent.tile< TS, TS >(), [=] (tiled_index< TS, TS> t_idx) restrict(amp) { int row = t_idx.local[0]; int col = t_idx.local[1]; float sum = 0.0f; for (int i = 0; i < W; i += TS) { tile_static float locA[TS][TS], locB[TS][TS]; locA[row][col] = a(t_idx.global[0], col + i); locB[row][col] = b(row + i, t_idx.global[1]); t_idx.barrier.wait(); for (int k = 0; k < TS; k++) sum += locA[row][k] * locB[k][col]; t_idx.barrier.wait(); } c[t_idx.global] = sum; } );}
void MatrixMultTiled(vector<float>& vC, const vector<float>& vA, const vector<float>& vB, int M, int N, int W ){ static const int TS = 16; array_view<const float,2> a(M, W, vA), b(W, N, vB); array_view<float,2> c(M,N,vC); c.discard_data(); parallel_for_each(c.extent.tile< TS, TS >(), [=] (tiled_index<TS, TS> t_idx) restrict(amp) { int row = t_idx.local[0]; int col = t_idx.local[1]; float sum = 0.0f; for (int i = 0; i < W; i += TS) { tile_static float locA[TS][TS], locB[TS][TS]; locA[row][col] = a(t_idx.global[0], col + i); locB[row][col] = b(row + i, t_idx.global[1]); t_idx.barrier.wait(); for (int k = 0; k < TS; k++) sum += locA[row][k] * locB[k][col]; t_idx.barrier.wait(); } c[t_idx.global] = sum; } );}
Phase 1 Phase 2
Comprendre le calcul tuilé
Démo Produit Matriciel Tuilé !
• Fonctionnalités bien connues du débogage Visual studio– Launch, Attach, Break, Stepping, Breakpoints, DataTips– Outillage orienté fenêtre
• Processes, Debug Output, Modules, Disassembly, Call Stack, Memory, Registers, Locals, Watch, Quick Watch
• Nouvelles fonctionnalités (à la fois le CPU et le GPU)– Parallel Stacks window, Parallel Watch window, Barrier
• Nouveautés spécifiques GPU– Emulateur, fenêtre GPU Threads , détection de races conditions
C++ AMP Parallel Debugger
Visual Studio
Debugger GPU en Action
Visual Studio
Résumé de la seconde partie
2ième Partie: ce que vous avez appris ! Comprendre le calcul tuilé
La macro architecture GPU pour les développeurs La classe tile_static pour cacher des données pour un groupe de threads Le classe tiled_extent< , , > permet à la surcharge parallel_foreach de
simplifier le calcul en tuilé La classe tiled_index< , , > permet à la surcharge parallel_foreach de
simplifier le calcul en tuilé La classe tile_barrier permet de synchroniser les threads au sein d’une tuile
Mise au point avec Visual Studio Configurer le mode Debug GPU Détecter les races conditions Utiliser la fenêtre Parallel GPU pour observer l’état de tous les threads et
naviguer entre eux Utiliser la fenêtre Parallel Watch pour observer des données pour chacun
des threads Exécuter toute une tuile de threads jusqu’à votre curseur
• Démocratisation de la programmation parallèle hétérogène– Haute performance accessible au grand public– Abstraction de la plateforme matérielle– Abstractions de haut niveau en C++ (pas en langage
C)– Repose sur un jeu minimal d’API– C++ AMP est une spécification libre– Etat de l’art de l’IDE de Visual Studio
Conclusion
C++ AMP en quelques mots
MSDN Forums to ask questions• http://social.msdn.microsoft.com/Forums/en/parallelcppnative/threads
MSDN Native parallelism blog (team blog)• http://blogs.msdn.com/nativeconcurrency/
C++ AMP Samples• http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/01/30/c-amp-sample-projects-for-download.aspx
Graphics in C++ AMP • http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/01/25/concurrency-graphics-in-c-amp.aspx
Interoperability between Direct 3D and C++ AMP• http://
blogs.msdn.com/b/nativeconcurrency/archive/2011/12/29/interoperability-between-direct-3d-and-c-amp.aspx
C++ AMP open spec• http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/02/03/c-amp-open-spec-published.aspx
Pour aller plus loin …
Pour creuser un peu plus …
Libraries for C++ AMP• http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/05/19/libraries-for-c-amp.aspx
C++ AMP for the OpenCL Programmer• http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/04/10/c-amp-for-the-opencl-programmer.aspx
C++ AMP for the CUDA Programmer• http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/04/11/c-amp-for-the-cuda-programmer.aspx
C++ AMP for the DirectCompute Programmer• http://
blogs.msdn.com/b/nativeconcurrency/archive/2012/04/09/c-amp-for-the-directcompute-programmer.aspx
Formez-vous en ligne
Retrouvez nos évènements
Faites-vous accompagner gratuitement
Essayer gratuitement nos solutions IT
Retrouver nos experts Microsoft
Pros de l’ITDéveloppeurs
www.microsoftvirtualacademy.com
http://aka.ms/generation-app
http://aka.ms/evenements-developpeurs
http://aka.ms/itcamps-france
Les accélérateursWindows Azure, Windows Phone,
Windows 8
http://aka.ms/telechargements
La Dev’Team sur MSDNhttp://aka.ms/devteam
L’IT Team sur TechNethttp://aka.ms/itteam