Systèmes Hautes Performances

Portail informatique
Ce premier exercice permet d\'étudier l\'environnement de développement utilisé pour programmer en CUDA. Il s\'agit d\'utiliser des commandes Unix pour vérifier l\'état de la machine puis de compiler et lancer un programme CUDA. Vérification de la présence du driver Nvidia
  • Utilisez la commande lspci
Pour pouvoir compiler, il faut aussi que la version du kit de développement soit en adéquation avec la version du pilote de la carte vidéo.
  • Utilisez la commande nvidia-settings
Vérification de la présence logicielle
  • Par défaut, le package cuda est installé sous /usr/local/cuda. Vérifiez la présence du compilateur nvcc
Initialisez les variables d\'environnement
  • Utilisez le fichier asr5-env.sh disponible dans votre répertoire de travail
Le programme est dans le fichier deviceQuery.cu.
  • Utilisez le Makefile pour compiler le programme et lancez l\'exécution de deviceQuery
Soit deux nombre complexes C (constante) et Z0, définissant l'équation Zn+1 = Zn2 + C.
L'ensemble de Julia correspondant à la frontière de l'ensemble des valeurs z0 pour lesquelles la suite bornée. Il représente le chaos, car une petite perturbation au départ se répercute en un changement radicale (référence : wikipédia).
L'équation itérative est évaluée pour des points (des pixels dans notre cas) situés dans le plan complexe. Un point n'est pas dans l'ensemble si le processus d'itération de l'équation diverge pour ce point. Inversement, se les valeurs obtenues par l'équation restent finies, le point appartient à l'ensemble.
Le corps du code qui calcule l'ensemble de Julia vous est fourni. Pour le compiler, il vous faudra d'abord télécharger cette archive. Pour le compiler, il faut utiliser les options -lglut -lGLU-lGL . L'archive fournit ce qu'il faut pour que l'ensemble soit visualisé.
Le code est comme suit :
  1. La fonction main initialise une image bitmap, qui représente les points qu'on veut afficher s'ils appartient à l'ensemble.
  2. La fonction kernel itère sur l'ensemble des points. Si le point fait partie de l'ensemble (en faisant appel à la fonction Julia). Si le point appartient à l'ensemble, sa couleur sera rouge, sinon elle sera noire.
  3. La fonction Julia commence par traduire les coordonnées du pixel en coordonnées dans le plan complexe. L'image est décalée de DIM/2 (pour la center). Pour garantir que l'image s'étende de -1.0 à 1.0, la valeur trouvée est divisée par DIM/2. Enfin, la variable scale permet de zoomer. Comme dit précédemment, la fonction va valuler les valeur de l'équation Zn+1 = Zn2 + C. Positionnez C à -0.8 + 0.156i (vous pourrez changer la valeur par la suite). Nous arrêtons le calcul après 200 itérations, et considérons que la suite diverge si le carré du module est supérieure à 1000.
Une structure, définissant les nombres complexes, et certaines opérations sur ces nombres est fournie (cuComplex). Ecrivez une version GPU de ce code. Notez qu'un appel à bitmap.image_size() fournit la taille de l'image (utile pour faire le cudaMalloc).
Il s'agit ici de réaliser une opération de type SAXPY c'est-à-dire multiplier le vecteur X par une constante A puis ajouter le résultat au vecteur Y ==> Y = A * X + Y.
Dans un programme classique avec un seul CPU, il faut utiliser une répétition et parcourir successivement tous les éléments des deux vecteurs. Avec le GPU, il n'y aura qu'une seule opération par thread.
Pour cela, il faut :
  1. initialiser les deux vecteurs X et Y,
  2. les transférer dans la mémoire du GPU,
  3. effectuer le calcul sur le GPU,
  4. récupérer le résultat en mémoire centrale.
Ecrivez le code effectuant le calcul de SAXPY en vous basant sur ce squelette (Makefile). Commencez par une version sans thread, puis une version avec threads en utilisant 256 threads.
Le but de cet exercice est de produire une image animée, qui sera pas la suite affichée. Tout comme l'exercice 2, vous n'aurez pas besoin d'écrire la partie affichage/animation, l'ensemble des fonctions est fourni.
Le code CPU est disponible ici. Les fichiers nécessaires à l'affichage sont ceux utilisés pour l'ensemble de Julia.
La fonction kernel représente le coeur du programme. Elle parcours l'ensemble des points, calcule leurs positions et produit une vague sinusoïdale.
Ecrivez la version GPU de ce code. Remarque : La structure DataBlock contient une bitmap qui peut être utilisé de la même façon que l'exercice 2 pour accéder au pointeur dev_bitmap.
Le but de cet exercice est d'obtenir en sortie du GPU un tableau symétrique du tableau d'entrée. Ecrivez une version utilisant uniquement la mémoire globale, et une avec mémoire partagée. Utilisez un vecteur de taille 256 * 1024 et 256 threads distribués sur des blocks 1D, qui eux sont distribués sur une grille 1D.
Pour vous aider, vous pouvez vous appuyer sur le squelette du code sans mémoire partagé et le squelette du code avec mémoire partagée ainsi que du Makefile.
Le but de cet exercice est d'obtenir en sortie du GPU le produit de deux matrices carrées C = AxB.
Dans un premier temps, écrivez une version utilisant uniquement la mémoire globale en utilisant des blocks deux dimensions de taille 32. Pour vous aider, vous pouvez vous appuyer sur le squelette suivant (squelette, (Makefile), qui initialise les matrices. Comparez le temps d'exécution avec une version openMP que vous devez écrire. Les algorithmes de produit de matrices les plus performants décomposent les algorithmes en tuile (tile). Les étapes de l'algorithmes sur GPU sont décrites à la fin de l'énoncé. Des explications plus détaillées vous seront fournies en cours. Ecrivez l'algorithme par tuile puis comparer les temps d'exécution des deux implémentations (avec et sans tuile). Comparez également le temps d'exécution lorsque TILE_WIDTH=16 et TILE_WIDTH=32. . Etapes de l'algorithme du produit de matrices par tuile :
  1. Chaque thread de chaque block va calculer un élément la matrice C.
  2. Tant que le calcul n'est pas fini, le thread met à jour une variable temporaire qui contient le résultat du calcul
  3. Dans chaque block, une tuile de la matrice A et une tuile de la matrice B sont stockées en mémoire partagée (dans des vecteurs 2 dimensions [TILE_WIDTH][TILE_WIDTH])
  4. Chaque thread copie l'élément des matrices A et B en mémoire partagée puis effectue le calcul et le stocke dans la variable temporaire.
  5. La valeur temporaire est recopié en mémoire globale dans la matrice C lorsque tous les thread du block ont fini.
Comme dit dans le cours, CUDA fournit de nombreuses bibliothèques telles que cuBLAS pour l'algèbre linéaire. Comparez le temps d'exécution du code que vous avez écrit à la question 2 au temps d'exécution fourni ici qui utilise cuBLAS. Notez que pour compiler ce code, vous devez ajouter -lcublas à la compilation. .