2 S'il fallait en réduire les raisons à une seule, c'est vraisemblablement la concurrence commerciale et la croissance du marché des jeux vidéos qui a poussé les fabricants de cartes graphiques à une innovation permanente qui a donnée naissance aux GPUs.
3 Le rendu graphique dans ce cadre est une opération exigeante en calcul mais intrinsèquement parallèle car chaque pixel est traité individuellement. L'amélioration des résolutions à aussi contribuer à faire évoluer la nature de ces besoins de \textit{parallèle} à \textit{massivement parallèle}, un écran actuel pouvant comporter plus de 2,5 millions de pixels.
5 La technologie de fabrication étant identique à celle de CPUs, c'est donc au niveau de la répartitions des fonctionnalités que les GPUs se distinguent : là où un CPU comporte quelques c\oe urs de calcul et beaucoup de transistors dédiés à la réalisation de mémoire cache et de contrôle de flux, un GPU présente plusieurs unités comportant chacun une grande quantité de c\oe urs de calcul ne disposant que de très peu de mémoire cache et de rudimentaires capacités de contrôle, comme l'illustrent les schémas de la figure \ref{fig-gpucpu1}.
9 \includegraphics[width=10cm]{Chapters/chapter1b/img/gpucpu1.png}
10 \caption{Comparaison des structures d'un c\oe ur de GPU et d'un c\oe ur de CPU (d'après \cite{CUDAPG}).}
14 Cette spécialisation des circuit GPUs a permis d'en améliorer les performances brutes beaucoup plus rapidement que pour les CPUs, au fil des évolutions de la technologie. Il en est allé de même pour les débits mémoire théoriques. Les graphiques de la figure \ref{fig-gpucpu2} comparent les rythmes de ces évolutions pour les GPU Nvidia\textregistered et pour les CPU Intel\textregistered.
16 Les problèmes recquérant les capacités de calcul spécifiques des GPU ne sont cependant pas limités aux questions de rendu graphique, aussi les scientifiques ont-ils très vite cherché à tirer parti de la puissance de calcul croissante des GPUs pour traiter d'autres types de problèmes, faisant sens à l'acronyme GPGPU.
20 \subfigure[Nombre maximum théorique d'opérations en virgule flottante par seconde en fonction de l'année et de l'architecture.]{\includegraphics[height=5cm]{Chapters/chapter1b/img/gpucpu2a.png}}\quad
21 \subfigure[Bande passante théorique maximale des diverses architectures.]{\includegraphics[height=5cm]{Chapters/chapter1b/img/gpucpu2b.png}}
22 \caption{Comparaison des performances des GPUs Nvidia et des CPU Intel (d'après \cite{CUDAPG}).}
26 Malgré des caractéristiques prometteuses, les GPGPUs n'ont pas immédiatement declenché une vague d'expérimentations scientifiques dans des domaines variés, l'essentiel des travaux étant liés à la construction et la visualisation des données issues de l'instrumentation d'imagerie médicale.
28 C'est la parution de l'extension de langage CUDA qui a réellement démocratisé l'emploi des GPGPUs et favorisé l'émergence de travaux variés. CUDA est une extension de haut niveau du langage C permetant d'écrire facilement des fonctions s'exécutant en parallèle sur le GPU, que l'on appelle des \textit{kernels}. L'extension CUDA permet également de gérer de manière transparente les changements d'échelle du parallélisme d'une architecture à une autre ou tout simplement les dimensions de la grille de calcul. Ces possibiltés rendent l'accès facile à la programmation GPU.
31 \subsection{Le matériel}
32 Pour bien tirer parti des capacités des GPUs, il est important d'en comprendre l'organisation matérielle. Nous limiterons cette présentation à l'architecture dite \textit{Fermi} à laquelle appartient le GPU C2070 qui a servi à l'essentiel de nos expérimentations et dont la constitution est détaillée par les diagrammes de la figure \ref{fig-c2070}.
34 Le circuit se divise en 4 groupes de processeurs de flux (les SMs), chacun groupe en comprenant 3 ou 4 pour un total de 14 SMs.
35 Chaque SM héberge à son tour 32 c\oe urs de calcul (les SPs).
36 Les threads sont exécutés par paquets de 2$\times$16, soit 32 (les warps) sur les 32 c\oe urs de calcul.
40 \subfigure[Organisation en groupes de SMs ]{\includegraphics[height=5cm]{Chapters/chapter1b/img/fermi.png}}\quad
41 \subfigure[Constitution d'un SM.]{\includegraphics[height=5cm]{Chapters/chapter1b/img/fermi-sm.png}}
42 \caption{Organisation des GPU d'architecture Fermi, comme le C2070 (d'après www.hpcresearch.nl).}
46 Les ressources mémoire sont de plusieurs nature tant du point de vue volume disponible, que portée ou bien débit. Il faut retenir que les registres sont peu nombreux, embarqués sur le circuit (on-chip) et présentent les meilleures performances alors que la mémoire principale, dite globale, est externe au circuit (off-chip), de grande capacité mais présente des latences importantes de plusieurs centaines de cycles d'horloge.
47 Le fabricant fournit des indications essentiellement qualitatives concernant les latences d'accès aux mémoires, mais ne fournit pas de chiffres sur les latences réelles en fonction des accès et de la proximité du cache lorsqu'un des 3 niveaux est sollicité. Le tableau \ref{tab-gpu-memoire} présente une synthèse des caractéristiques du modèle C2070, issues d'expérimentations menées par nos soins à l'aide des micro-tests présentés dans \cite{wong2010demystifying}.
49 Une petite quantité de mémoire on-chip est présente sur chaque SM et permet la communication entre les threads s'exécutant sur ce SM. Cette mémoire est appelée \textit{mémoire partagée} et permet des débits bien supérieurs à la mémoire globale.
53 \begin{tabular}[htb]{lcccccc}
55 &\multicolumn{4}{c}{Propriétés}\\
57 &Emplacement &Portée&Latence &débit &Taille \\
58 &(on/off-chip)& &(cycles)&(Go/s)&(octets)\\
60 Registres& on-chip &thread&1 &8000& 32K par SM \\
61 Partagée & on-chip &bloc &38 &1300& 48K \\
63 Constante& off-chip &grille&370/46/140 &8000& 64K \\
64 Texture & off-chip &grille&500/260/372&\ldots & 6G \\
65 Locale & off-chip &thread&550 &\ldots & 512K \\
66 Globale & off-chip &grille&580/80/350 &144 & 6G \\
69 \caption{Caractéristiques des différents types de mémoire disponibles sur le GPU. Pour les mémoires cachées, les latences sont données selon l'accès \textit{sans-cache/L1/L2/}. Les mesures ont été obtenues à l'aide des microprogrammes de test de \cite{wong2010demystifying}.}
70 \label{tab-gpu-memoire}
73 \subsection{Le logiciel}
74 Dans le modèle CUDA, chaque \textit{kernel} est exécuté par un certain nombre de threads. Chaque thread possède un identifiant unique, accessible à l'intérieur du kernel, ce qui permet d'en individualiser le traitement.
75 L'ensemble des threads est organisé en plusieurs blocs indépendants, puis ces blocs en grille. Pour faciliter la représentation de modéles variés, les blocs de threads ainsi que la grille de blocs peuvent être décrits par des tableaux à une, deux ou trois dimensions.
76 Le nombre total de threads appartenant à un même bloc est cependant limité, selon la version de GPU, à 512 ou 1024. Le diagramme de la figure \ref{fig-threads} illustre l'organisation d'une grille de calcul à 2 dimensions et des ses blocs, également à 2 dimensions, situation qui correspond à la majorité des modèles d'exécution de nos \textit{kernels} de traitememnt d'image.
80 \includegraphics[height=6cm]{Chapters/chapter1b/img/threads.png}
81 \caption{Représentation d'une grille de calcul en 2D et des blocs de threads, à 2 dimensions, qui la composent.}
86 Au sein d'un même bloc, les threads peuvent communiquer efficacement grâce à la mémoire partagée, rapide et présentant une faible latence.
87 La communication inter-blocs passe nécessairement par la mémoire globale dont l'emploi est pénalisant.
89 L'emploi de la mémoire partagée n'est cependant pas transparent comme peut l'être un cache de niveau 1 (L1) et les motifs d'accès doivent respecter un certain nombre de conditions pour en obtenir les performances attendues. Le non respect de ces contraintes conduit le plus souvent à un bout programme dont l'exécution s'avère plus lente que son équivalent CPU.
91 \subsection{l'Occupancy}
92 Pour atteindre les meilleures performances possible, le fabricant Nvidia recommande d'avoir toujours suffisamment de threads dans chaque bloc et de blocs dans la grile et ce, pour masquer les latences des accès aux mémoires, mais aussi celles des instructions arithmétiques.
93 Il définit un indice nommé \textit{occupancy}, que l'on pourrait franciser par \textit{charge} qui représente, à chaque instant, le rapport du nombre de blocs de threads actifs par SM sur le nombre maximum de threads actifs par SM (1536 sur C2070).
95 La valeur de l'\textit{occupancy} peut se trouver limitée par un usage trop intensif des ressources mémoire par les threads (registres, méoire partagée) ou bien par une grille de calcul mal dimensionnée. L'ensemble des paramètres intervenant dans le calcul l'\textit{occupancy} est pris en compte dans une feuille de tableur (l'\textit{occupancy calculator}) fournie par Nvidia pour aider les développeurs à bien employer les ressources des divers modèles de GPU.
97 Les limitations de l'\textit{occupancy} ont pour origine :
99 \item {\bf l'usage des registres}. Si chaque thread utilise le maximum de registres possible ($63+1=64$), le bloc de thread affecté au SM ne peut donc activer simultanément que $32K/64=512$ threads, soit une \textit{occupancy} de $512/1536=0.33$.
100 \item {\bf l'usage de la mémoire partagée}. L'architecture Fermi permet de choisir la répartition entre cache L1 et mémoire partgée, soit 16K/48K, soit 48K/16K. En configuration 48K de mémoire partagée, si chaque thread en emploie 48 octets, le GPU ne peut activer que $48K/48=1024$ threads, soit une une \textit{occupancy} de $1024/1536=0.66$.
101 \item {\bf la taille des blocs}. Un SM ne pouvant activer que 8 blocs simultanément, la taille des blocs limite donc potentiellement l'\textit{occupancy}. Si on exécute un \textit{kernel} sur une grille de calcul dont les blocs ont le minimum de 32 threads, les 8 blocs actifs représenteront alors 256 threads, soit une \textit{occupancy} de $256/1536=0.16$.
104 Nous verrons que cette notion d'\textit{occupancy} si elle conserve du sens, peut toutefois être remise en question en optimisant d'autres aspects permettant d'arriver à une réduction de l'effet des latences, comme le parallélisme d'instructions ou l'augmentation du volume des transactions. Ces techniques ainsi que l'emploi fin des mémoires du GPU permettent d'obtenir des performances élevées sur GPU, parfois inenvisageables en suivant les préscriptions du constructeur.