CUDA cudniejsze przyk ady
Agenda: CPU vs. GPU Mnożenie macierzy CPU Mnożenie macierzy - GPU Sploty
Macierze CPU vs. GPU CPU: GPU: Mnożenie wykonywane w kolejnych iteracjach pętli. Przechodzimy przez pierwszy wiersz tabeli M i pierwszą kolumnę macierzy N, w pętli liczymy ich iloczyn skalarny i od razu zapisujemy go w macierzy wyjściowej. paralelne wykonanie właściwego mnożenia macierzy każdy wątek wpisuje jeden element do macierzy wynikowej Cdn...
CPU Istotny dla zrozumienia różnicy pomiędzy tradycyjnym mnożeniem macierzy, a analogicznymi obliczeniami na GPU jest fakt linearnego adresowania macierzy w pierwszym przypadku. Właśnie z linearnego adresowania macierzy wynika występujący w iteracji indeks postaci: i*width+k. (i numer wiersza; Width wymiar n macierzy; k-numer kolumny) Adresowanie linearne pokazano schematycznie na ilustracji:
CPU Jak widad, jest to podejście typowo sekwencyjne
Coś ciekawszego, czyli: GPU
GPU - (C = AB) Wersja najprostsza: Jest to wersja niezoptymalizowana. Każdy half warp oblicza jeden rząd tile a C, polegając przy tym na jednym rzędzie z A i całym tile u z B. W każdej iteracji pętli wszystkie wątki w half warpie czytają tę samą wartośd z pamięci globalnej. global void simplemultiply(float *a, float* b, float *c, int N) { int row = blockidx.y * blockdim.y + threadidx.y; int col = blockidx.x * blockdim.x + threadidx.x; float sum = 0.0f; } for (int i = 0; i < TILE_DIM; i++) { sum += a[row*tile_dim+i] * b[i*n+col]; } c[row*n+col] = sum;
GPU - (C = AB) Wersja 2: Pierwszym z możliwych ulepszeo jest wykorzystanie pamięci współdzielonej. W drugiej wersji algorytmu wczytujemy tile z A do pamięci współdzielonej. global void coalescedmultiply(float *a, float* b, float *c, int N) { shared float atile[tile_dim][tile_dim]; int row = blockidx.y * blockdim.y + threadidx.y; int col = blockidx.x * blockdim.x + threadidx.x; float sum = 0.0f; atile[threadidx.y][threadidx.x] = a[row*tile_dim+threadidx.x]; for (int i = 0; i < TILE_DIM; i++) { sum += atile[threadidx.y][i]* b[i*n+col]; } c[row*n+col] = sum; }
GPU - (C = AB) Wersja 3: Kolejnym możliwym ulepszeniem jest jednorazowe wczytywanie całego rzędu macierzy B do pamięci współdzielonej. global void sharedabmultiply(float *a, float* b, float *c, int N) { shared float atile[tile_dim][tile_dim], btile[tile_dim][tile_dim]; int row = blockidx.y * blockdim.y + threadidx.y; int col = blockidx.x * blockdim.x + threadidx.x; float sum = 0.0f; atile[threadidx.y][threadidx.x] = a[row*tile_dim+threadidx.x]; btile[threadidx.y][threadidx.x] = b[threadidx.y*n+col]; syncthreads(); for (int i = 0; i < TILE_DIM; i++) { sum += atile[threadidx.y][i]* btile[i][threadidx.x]; } c[row*n+col] = sum; }
Capability 1.1 - NVIDIA GeForce GT 9600M in a MacBook Pro Laptop, 4 multiprocessors, 32 cores Porównanie: Capability 1.2 - NVIDIA GeForce GT 330M in a MacBook Pro Laptop, 6 multiprocessors, 48 cores
Capability 1.3 - NVIDIA Tesla C1060 running in Earlham's cluster, 30 multiprocessors, 240 cores Porównanie: Capability 2.0 - NVIDIA Tesla M2070 at the Texas Advanced Computing Center, 14 multiprocessors, 448 cores Matrix Multiplication with CUDA A basic introduction to the CUDA programming model Robert Hochberg
Porównanie: Optymalizacja NVIDIA GeForce GTX 280(1.3) NVIDIA GeForce GTX 8800(1.0) Bez optymalizacji 8.7 GBps 0.7 GBps shared float atile 14.3 GBps 8.2 GBps shared float atile, btile 29.7 GBps 15.7 GBps Matrix Multiplication with CUDA NVIDIA CUDA C Best Practices Guide
Sploty Splot znajduje szerokie zastosowanie w przetwarzaniu obrazów. Operacja splotu oblicza nową wartośd piksela obrazu na podstawie wartości pikseli sąsiadujących. Przed zastosowaniem splotu: Po zastosowaniu splotu:
Sploty Simple box blur: (jak widad, maska ma efekt uśredniający) maska: Przed zastosowaniem splotu: Po zastosowaniu splotu:
Sploty Gaussian blur: maska: Przed zastosowaniem splotu: Po zastosowaniu splotu:
Sploty Naiwna implementacja: W najprostszej wersji implementacji splotu każdy blok wątków przetwarza jeden blok obrazu. Każdy wątek generuje na wyjściu jeden piksel.
Brutalna konfrontacja: (z rzeczywistością) Po zmodyfikowaniu naiwnego algorytmu zagadnienie zaczyna się komplikowad Uwzględnienie w algorytmie otoczki, niezbędnej do przeliczenia brzegowych pikseli powoduje, że wątki odpowiedzialne wcześniej za wczytanie otaczających pikseli będą bezczynne przez cały czas przeliczania maski. Sploty
Autorki Urszula Jędrzejczak Katarzyna Ostrowicz
Koniec Bibliografia: CUDA by Example: An Introduction to General-Purpose GPU Programming Jason Sanders,Edward Kandrot Programming Massively Parallel Processors: A Hands-on Approach David B. Kirk, Wen-mei W. Hwu Dokumentacja NVIDIA: CUDA C Best Practices Guide Version 3.1 z 2010-05-28 Image Convolution with CUDA Victor Podlozhnyuk[http://developer.download.nvidia.com/compute/cuda/1.1- Beta/x86_64_website/projects/convolutionSeparable/doc/convolutionSeparabl e.pdf] Matrix Multiplication with CUDA A basic introduction to the CUDA programming modelrobert Hochberg [http://www.shodor.org/media/content//petascale/materials/upmodule s/matrixmultiplication/moduledocument.pdf] http://www.aishack.in/2010/08/image-convolutionexamples/