TP 1 : Programmation CUDA de base

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 d'un GPU au sein d'un noeud de calcul CPU+GPU : il consiste à implanter un premier produit de matrices denses sur un GPU. On développera différents "kernels", et pour chacun on mesurera les performances d'un produit de matrices denses sur un GPU, en fonction de la granularité de la grille de blocs de threads. On étudiera la qualité de la "coalescence" de chaque version du kernel, pour identifier les solutions les plus intéressantes. Enfin, on comparera les performances obtenues sur GPU avec celle obtenues sur le CPU.

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 "1D" et du kernel K0 :
    1. Récupérez et compilez le squellete de programme OpenMP+CUDA.
      • Squellete, ou bien allez le recopier sur votre compte de TP par la commande : cp ~vialle/tmp/MatrixProduct-CUDA-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. Dans le fichier 'gpu.cu' complétez les routines de transferts de données : 'gpuSetDataOnGPU' et 'gpuGetResultOnCPU.
      • Vous devez compléter ces routines avec des appels à 'cudaMemcpyFromSymbol' et 'cudaMemcpyToSymbol'.
      • Compilez et exécutez votre code : vérifiez qu'il ne signale pas d'erreur.
    1. Implantez le kernel K0 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 segment 1D selon la dimension X,
      • un bloc de threads calcule les éléméments successifs d'une partie d'une ligne de C (les colonnes de C doivent être associées à la dimension X des blocs),
      • les lignes successives de C seront traitées par des blocs 1D différents (les lignes de C doivent être associées à la dimension Y des blocs).
    • Testez votre implantation sur une matrice de 1024x1024 DOUBLE (option -DDP active dans le 'Makefile' et changement de SIZE dans le 'main.h'), et vérifiez que vous obtenez les mêmes valeurs que sur CPU (MatrixProduct -t CPU -cpu-k 1 -cpu-nt 4, sur Tx, et -cpu-nt 6 sur Cameron)
    • Testez votre implantation sur une matrice de 1025x1025DOUBLE (vérifiez que vous obtenez les mêmes valeurs que sur CPU).
    1. Mesurez les performances du kernel 0 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 1D de threads, et mesurer les performances obtenues pour des blocs de 32 à 1024 threads.
    • Puis faites varier la taille des blocs de 32 à 1 thread.
    • Est-ce que la courbe de performance obtenue semble conforme à la théorie ? pourquoi ?
    1. Comparez aux meilleures performances obtenues sur CPU multi-coeurs en OpenMP avec un kernel de même niveau (kernel 0), 
    • MatrixProduct -t CPU -cpu-k 0 -cpu-nt 8 sur Tx
    • MatrixProduct -t CPU -cpu-k 0 -cpu-nt 12 sur Cameron
    • Calculez le speedup GPU vs CPU.
    2 - Etude de la coalescence du kernel K0 :

    Rappel : le kernel K0 se déploie avec des blocs 1D selon l'axe X, où chaque bloc traite des colonnes successives d'une même ligne de C.
    1. Etudier la "coalescence" :
      • des lectures de A,
      • des lectures de B,
      • des écritures dans C
    1. Que serait devenu la coalescence :
      • en utilisant la transposée de A ?
      • en utilisant la transposée de B ?
    1. On considère maintenant des blocs 1D selon l'axe X, MAIS où chaque bloc traite des lignes successives d'une même colonne de C.
      • Etudier à nouveau la coalescence obtenue lors des lectures de A ou de la transposée de A,
      • et lors la lecture de B ou de la transposée de B,
      • et lors des écritures de C.
    1. Quelle est la meilleure solution ?
    1. Echangez le calcul des numéros de ligne et de colonne traitées par chaque thread, pour vous mettre dans le cas où un bloc 1D en X traite des lignes successives d'une même colonne de C.
      • Mesurez les performances obtenues.
      • L'évolution des performances est-elle conforme aux prévisions ?
      3- Implantation d'une grille "2D" de blocs "2D" et du kernel K1 :
    1. Créez le kernel K1 en généralisant votre kernel K0 (et sa grille de blocs) pour qu'il supporte des blocs 2D (rectangulaires) de threads. 
    • Testez et validez votre implantation sur une matrice de 1024x1024 FLOAT : vérifiez que vous obtenez les mêmes valeurs qu'avec le kernel K0.
    • Puis sur une matrice de 1025x1025 FLOAT.
    Attention : la taille maximale d'un bloc est de 1024 threads !
    1. Mesurez les performances obtenues sur une matrice de 4096x4096 FLOAT.
    • Mesurez les performances obtenues avec des blocs "XxY" pour des tailles correspondant aux cases jaunes du fichier Excel.
    Notamment pour des tailles XxY de 8x8, 16x16, 32x32, puis de 32x8, 8x32, et 32x16 et 16x32 threads.
    1. Comparez les performances du kernel K1 avec :
      • les meilleures performances du kernel K0,
      • les meilleures performances obtenues sur CPU multi-coeurs en OpenMP,
      • calculez le speedup GPU vs CPU.
    1. La coalescence est-t-elle toujours respectée ?

    Document à rendre : 

    Vous rendrez un SEUL document par binôme : un fichier Word (.doc) ou PDF :
    • Taille maximale de vos rapports : <voir taille maximale annoncée en cours>
    • Date limite de remise de vos rapports : <voir date annoncée en cours>
    • Remise par e-mail à Stephane Vialle (Stephane.Vialle@centralesupelec.fr)