TP 3 : Programmation CUDA utilisant la shared memory

Mineure CalHau-2

Stéphane Vialle, CentraleSupélec & LRI, Stéphane.Vialle@centralesupelec.fr  

Co-auteur du TP :  Laércio Lima Pilla, CNRS & LRI, pilla@lri.fr

Objectifs du TP :

Ce TP a pour objectif de pratiquer la programmation optimisée d'un GPU en utilisant la shared memory de chaque Stream-Multiprocessor. On étudiera notamment la mise au point de synchronisations minimales mais suffisantes des threads (au sein d'un bloc), puis le traitement des boundaries pour la mise au point de kernel génériques.

Plate-forme de développement : 

Les machines utilisées seront celles des clusters Tx ou Cameron du DCE de CentraleSupélec :

L'environnement CUDA C et C++ est disponible sur chaque machine (et donc le compilateur "nvcc" et les drivers pour utiliser le GPU).

Vous utiliserez les comptes de TP "gpucs1_#i", où #i est une valeur entière entre 1 et 16.

Depuis votre poste de travail :

Ex: oarsub -p "cluster='Tx'" -l nodes=1,walltime=4:00:00 -I

Travail à effectuer :

Remarques préliminaires :
  • Le squelette de programme que vous utiliserez contient un code de produit de matrices denses en OpenMP et CUDA.
  • La partie OpenMP est complète, et est destinée à permettre de vérifier les résultats obtenus en CUDA.
  • La partie CUDA est en partie développée, mais il vous reste à compléter le fichier gpu.cu :
    • Le squelette est compilable et contient une aide intégrée : exécutez 'make' puis './MatrixProduct -h'.
    • Pour valider votre premier code vous compilerez en Double Précision (le type "T_real" devient le type "double")  avec "-DDP" dans le Makefile, les résultats seront identiques sur CPU et sur GPU, mais les performances des GPU s'effondreront (car il s'agit de cartes GPU grand public non adaptées à la Double Précision).
    • Pour faire vos mesures de performances vous compilerez en Simple Précision (le type "T_real" devient le type "float") avec "#-DDP" dans le Makefile. La simple précision est adaptée aux capacités des GeForce GTX1080 et RTX2080, mais il se peut que vous observiez des différences entre les calculs sur CPU et sur GPU!!
    1 - Implantation d'une grille "2D" de blocs "2D" et du kernel K2 utilisant la shared memory :
    1. Récupérez et compilez le squellete de programme OpenMP+CUDA.
      • Squelette, ou bien allez le recopier sur votre compte de TP par la commande : cp ~vialle/tmp/MatrixProduct-CUDA-shm-enonce.zip .
      • Compilez ce squelette et testez son fonctionnement sur CPU (exécutez la commande ./MatrixProduct -h pour voir les détails de fonctionnement de l'application).
      1. Implantez le kernel K2 et sa grille de blocs de threads dans le fichier 'gpu.cu' pour que :
        • chaque thread calcule un élément complet de la matrice C = AxB,
        • un bloc de threads soit un bloc 2D carré qui exploite la shared memory
        • les accès à la mémoire globale soient coalescents en lecture et en écriture
        • votre programme fonctionne pour des tailles de matrices telles que : SIZE = q.BLOCK_SIZE_XY
      • Testez votre implantation sur une matrice de 4096x4096 FLOAT (#-DDP dans le 'Makefile'), et vérifiez que vous obtenez les mêmes valeurs qu'avec le kernel K1 sur GPU
      1. Mesurez les performances du kernel 2 obtenues sur une matrice de 4096x4096 FLOAT éléments (#-DDP dans le Makefile).
      • Récupérez le fichier Excel de saisi des résultats, et complétez-le au fur et à mesure du TP.
      • Faites varier la taille de vos blocs 2D carrés de threads, et mesurer les performances obtenues pour des blocs de 1x1 à 32x32 threads.
      • Est-ce que les performances obtenues semblent conforme à la théorie ? pourquoi ?
      1. Calcul du nombre d'accès en mémoire globale 
      • Calculez le nombre d'accès à la mémoire globale (en lecture et en écriture) sans utiliser la shared memory (kernel K1) et en l'utilisant (kernel K2), avec des matrices de SIZExSIZE éléments et des blocs de BSXYxBSXY.
      • Calculez le gain obtenu en nombre d'accès par rapport au kernel K1.
      • Faites l'appliction numérique pour des matrices de 4096x4096 et des blocs de 32x32 eléments.
      2 - Implantation d'une grille "2D" de blocs "2D" et du kernel K3 utilisant la shared memory ET GENERIQUE
      1. Implantez un kernel K3 en généralisant votre kernel K2 pour qu'il s'applique à des matrices de taille quelconque
        • Etudier les différents problèmes de boundaries qui se posent
        • Testez le fonctionnement de votre code sur des matrices de 4096x4096 FLOAT, et comparez les résultats à ceux du kernel K1
        • PUIS testez votre programme sur des matrices de 4097x4097 FLOAT, et comparez les résultats à ceux du kernel K1
        1. Mesurez les performances de votre kernel K3
          • Mesurez les performances sur des matrices de 4096x4096 FLOAT pour des blocs de 32x32
          • Complétez le fichier Excel
          • Comparez les aux performances du kernel K2 : notez-vous un impact des boundaries ?

        Document à rendre : 

        Vous rendrez un SEUL document par binôme : un fichier Word (.doc) ou PDF :
        • Taille maximale de vos rapports : 2 pages
        • Date limite de remise de vos rapports : 48h avant l'IO
        • Remise par e-mail à Stephane Vialle (Stephane.Vialle@centralesupelec.fr)