diff --git a/exercices/02-correction-gamma/fig/.gitkeep b/exercices/02-correction-gamma/fig/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/exercices/02-correction-gamma/fig/CUDA-GridBlockThread-Structure.png b/exercices/02-correction-gamma/fig/CUDA-GridBlockThread-Structure.png new file mode 100644 index 0000000000000000000000000000000000000000..ff10e1130c5e113e382033bd6cfa3d43f57c42f6 Binary files /dev/null and b/exercices/02-correction-gamma/fig/CUDA-GridBlockThread-Structure.png differ diff --git a/exercices/02-correction-gamma/fig/Gamma-transformation-of-grey-level.png b/exercices/02-correction-gamma/fig/Gamma-transformation-of-grey-level.png new file mode 100644 index 0000000000000000000000000000000000000000..a5abb6adaddee6b31e03462e406040d65534ff54 Binary files /dev/null and b/exercices/02-correction-gamma/fig/Gamma-transformation-of-grey-level.png differ diff --git a/exercices/02-correction-gamma/fig/g31567.png b/exercices/02-correction-gamma/fig/g31567.png new file mode 100644 index 0000000000000000000000000000000000000000..6e746334af1458959392415c868b7b3817e041b7 Binary files /dev/null and b/exercices/02-correction-gamma/fig/g31567.png differ diff --git a/exercices/02-correction-gamma/fig/g31611.png b/exercices/02-correction-gamma/fig/g31611.png new file mode 100644 index 0000000000000000000000000000000000000000..64bdb65feaa52f65106b6846629a05755bc16deb Binary files /dev/null and b/exercices/02-correction-gamma/fig/g31611.png differ diff --git a/exercices/02-correction-gamma/readme.md b/exercices/02-correction-gamma/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..9989119613f52aa2266b2e819d6fdd9a6485ae90 --- /dev/null +++ b/exercices/02-correction-gamma/readme.md @@ -0,0 +1,179 @@ +# Kernel grid en deux dimensions + +[[_TOC_]] + +Le but de cet exercice est que vous arriver à travailler avec une grille 2D de block et threads sur GPU. +En effet, la limite de blocks que vous pouvez avoir sur la direction x est de 2'147'483'647. +Si vous souhaitez aller au-dela de cette limite, ceci même pour traiter de données en 1D, vous deverez utiliser les autres dimension à disposition. + +Ce qu'il faut savoir c'est que: +- le nombre maximum de blocks en dim x est 2'147'483'647 +- la taille maximum d'un block en dim x est 1'024 +- le nombre maximum de blocks en dim y est 65'535 +- la taille maximum d'un block en dim y est 1'024 +- le nombre maximum de blocks en dim z est 65'535 +- la taille maximum d'un block en dim z est 64 + +Le nombre maximum de threads par block est de 1'024. + +Ceci en date de l'exercice et peut varier en fonction de compute capability de CUDA. +La compute capability de CUDA définit l'ensemble d'attribut que vous avez avec votre device. +Que ce soit hardware ou software. +Ceci dépend de la génération du GPU utilisé. + +Pour cet exercice, il ne sera pas nécessaire de pousser ces limites en terme de dimension sur le GPU. +Mais il faut être capable d'exploiter les dimensions offerte par CUDA pour: +- aller au dela de la limite 2'147'483'647 si nécessaire +- être capable de travailler aisément sur une structure régulière en 2 ou 3D + +Rappelez vous également que les GPU on une mémoire limitée. +Jetez un oeil aux [spécifications des machines à l'UniGE](https://doc.eresearch.unige.ch/hpc/hpc_clusters#for_advanced_users) ou de votre [propore GPU](https://developer.nvidia.com/cuda-gpus). + +## Exécuter un kernel sur une grille 2D + +Pour exécuter un kernel 2D, cela se fait fondamentalement comme une grille 1D: avec les paramètre d'exécution du kernel (`my_kernel<<<GRID_SIZE,BLOCK_SIZE>>>`). +Vous avez probablement passé de simple entier lors de la série précédente. +Mais pour pouvoir utiliser des dimensions supérieur il faut instancier un `dim3`. +Un `dim3` vous permet de spécifier les tailles en `x`, `y` et `z` (dans cet ordre), comme par exemple: +```c +const dim3 BLOCK_SIZE = dim3(3, 2); +``` +où je définis un grille de taille 3 x 2 (= 6 blocks). +Notez que le `dim3` n'est pas une erreur et est équivalent à: +```c +const dim3 BLOCK_SIZE = dim3(3, 2, 1); +``` +Vous pouvez faire de même avec la taille des blocks: +```c +const dim3 GRID_SIZE = dim3(4, 3); +``` +et lancer le kernel de la sorte: +```c +my_kernel<<<GRID_SIZE, BLOCK_SIZE>>>(...); +``` + +Vous pouvez ensuite utiliser ces dimensions dans votre kernel, comme en 1D: +```c +int rows = blockIdx.y*blockDim.y + threadIdx.y; +int cols = blockIdx.x*blockDim.x + threadIdx.x; +``` + +Si la notion grille 2D n'est pas claire, voici un schéma pour vous aider : + + + +où, probablement, on a: +```c +const dim3 BLOCK_SIZE = dim3(3, 3); +const dim3 GRID_SIZE = dim3(3, 3); +``` + +## Exercice 1: addition de vecteurs + +Adaptez l'exercice de la série de l'addition de vecteurs de manière à ce que vos vecteurs soient projetés sur une kernel grid en 2D en place d'une grille en 1D. +Imaginez que les vecteurs sont projeté rangée par rangée ou colone par colone. + +C'est-à-dire que vous passerez d'une répartition sur une grille 1D comme: + + + +à une répartition sur une grille 2D comme: + + + +## Exercice 2: correction de luminosité d'une image + +Nous allons à présent utliser la kernel grid 2D pour travailler "naturellement" avec des données en 2D. +Nous appliquerons ceci avec le réhaussement d'une image. +Notez que pour des raisons de simplicité, l'image ne sera qu'une simple matrice d'entiers. +Nous ne lirons pas d'image au format PNG, JPG, ou autre format existant. + +(Vous avez vu, lors du cours de VISNUM ???), que l'on obtient une bonne correction de la luminosité +en appliquant une transformation non-linéaire, appelée [correction gamma](https://en.wikipedia.org/wiki/Gamma_correction). + +Cette fonction dépend d'un paramètre nommé gamma, et voici à quoi ressemble cette transformation pour quelques valeurs de gamma choisie sur des niveau de gris: + + +Attention sur cette figure les `L` sont les différent niveau de gris or les niveaux de gris sont souvent normalisé entre 0 (noir) et 1 (blanc) dans les implémentation. + +Voici un pseudo-code illustrant l'application de la transformation gamma sur une image: +```python +# habituellement un pixel est codé sur un unsigned int 8, +# un entier de 8 bits (noir: 0 -> blanc: 255) +def func_gamma(img, gamma): + X,Y = dim(img) + G = zeros(X,Y) + for i in X: + for j in Y: + pixel_norm = img[i,j].as_double()/255.0 + new_pixel = 255 * pow(pixel_norm, gamma) + G[i,j] <- new_pixel.as_uint8() + return G +``` + +### Travail à réaliser + +Implémenter la transformation gamma sur GPU. +Il n'y a pas besoin de viser les performances pour cet exercice. +Assurez-vous surtout que vous ayez bien compris le modèle de programmation CUDA (mouvement mémoire, exécution de kernels et synchronization). + +### Lecture de l'image + +Pour l'image, pas besoin de lire une vrai image. +Générez simplement une matrice d'entiers en 2D contenant uniquement de pixels en niveau de gris. +Ce sont génàralement des entiers allant de 0 à 255. +Cette image doit être générée sur le host et copiée sur le device. + +Attention, à la connaissance de l'auteur, CUDA ne support pas les entiers non signé sur 8 bits. +Mais comme ce n'est pas très important pour cet exercice, vous pouvez utiliser de simple entiers non +signés (`unsigned int`). Pensez à vos conversions de type pour le calcul dans votre fonction de transformation. + +Voici un exemple d'image qui génère un dégradé de gris. +Mais vous êtes libre de tester avec la matrice qui vous plait. +Encore, pour cet exercice, + +```c +// non tested (bad teacher, bad...), but should be fine +int* gray_Scale(int n, int m) { + int n_pixels = n/m; + char *img = malloc(n_pixels); + if (!img) return NULL; + + int extra_pix = n_pixels % 255; + int per_gray = n_pixels / 255; + int g = 0; + while (i < count) { + for (int k = 0; k < per_gray; ++k) { + img[i] = g; + i++; + } + + if (extra_pix > 0) { + img[i] = g; + i++; + extra_pix--; + } + + g++; + } + return img; +} +``` + +Comparez également le résultat obtenu par votre code GPU avec une implémentation sur CPU. + +Si vous souhaitez **vraiment** lire et écrire une image sur CPU en **C++**, sachez que [openimageio (OIO)](https://sites.google.com/site/openimageio/home) est disponible sous forme de module sur les clusters: + +``` +OpenImageIO: OpenImageIO/1.7.17 + +Description: + OpenImageIO is a library for reading and writing images, and a bunch of related + classes, utilities, and applications. + +You will need to load all module(s) on any one of the lines below before the +"OpenImageIO/1.7.17" module is available to load. + + icc/2017.1.132-GCC-6.3.0-2.27 impi/2017.1.132 + ifort/2017.1.132-GCC-6.3.0-2.27 impi/2017.1.132 +```