Programowanie Równoległe wykład, 21.01.2013 CUDA, przykłady

Transkrypt

Programowanie Równoległe wykład, 21.01.2013 CUDA, przykłady
Programowanie Równoległe
wykład, 21.01.2013
CUDA, przykłady praktyczne 1
Maciej Matyka
Instytut Fizyki Teoretycznej
Motywacja
l
CPU vs GPU (anims)
Plan CUDA w praktyce
• Wykład 1: CUDA w praktyce
l
aplikacja
• Wykład 2: Algorytmy n ciał
• Wykład 3: Thrust CUDA
CUDA pod Windows
• Microsoft Visual C++ (2008, 2010)
– można użyć darmowej wersji Express
• Sterownik (ze strony Parallel NSIGHT):
– 266.58_desktop_win7_winvista_64bit_international_whql.exe
• CUDA Toolkit 3.2
• CUDA SDK 3.2
• nVidia Parallel NSIGHT HOST/MONITOR 1.51.11018
– Uwaga: może sygnalizować problem z SP1 i .NET, ale
nas interesuje na razie tylko konfiguracja do
projektów, a nie debugowanie więc nie przejmujemy
się.
Parallel NSIGHT - projekty
Pozycja w menu START:
(przykład na żywo kompilacja - Windows)
Uwaga: Visual 2010
• W menu Project->Properties wybieramy
Platform Toolset v90!
Kompilacja LINUX
• Korzystamy z pracowni 426
• Komputery mają zainstalowane karty nVidia GeForce
• Środowisko CUDA jest zainstalowane
• Kompilacja (plik program.cu)
> nvcc program.cu
> ./a.out
• Kompilacja CUDA + GLUT (OpenGL)
> nvcc main.cpp kernels.cu -lglut –lGLU
> ./a.out
Hello World
• Pierwszy prosty program w CUDA
1. CPU wywołuje funkcję na GPU
– Funkcja wstawia do pamięci GPU ciąg „Hello world!”
2. Kopiujemy dane z GPU do pamięci CPU
3. Dane wypisujemy przy pomocy funkcji i/o (cout etc.)
Hello World
• Pierwszy prosty program w CUDA
1. CPU wywołuje funkcję na GPU
– Funkcja wstawia do pamięci GPU ciąg „Hello world!”
2. Kopiujemy dane z GPU do pamięci CPU
3. Dane wypisujemy przy pomocy funkcji i/o (cout etc.)
Hello World
#include <stdio.h>
#include <cuda.h>
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
int main(void)
{
helloWorldOnDevice <<< 1, 1 >>> ();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*13, 0,
cudaMemcpyDeviceToHost);
printf("%s",napis_host);
}
Hello World
#include <stdio.h>
#include <cuda.h>
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
int main(void)
{
helloWorldOnDevice <<< 1, 1 >>> ();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*13, 0,
cudaMemcpyDeviceToHost);
printf("%s",napis_host);
}
Hello World
#include <stdio.h>
#include <cuda.h>
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
int main(void)
{
helloWorldOnDevice <<< 1, 1 >>> ();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*13, 0,
cudaMemcpyDeviceToHost);
printf("%s",napis_host);
}
Hello World
#include <stdio.h>
#include <cuda.h>
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
int main(void)
{
helloWorldOnDevice <<< 1, 1 >>> ();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*13, 0,
cudaMemcpyDeviceToHost);
printf("%s",napis_host);
}
Hello World
#include <stdio.h>
#include <cuda.h>
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
int main(void)
{
helloWorldOnDevice <<< 1, 1 >>> ();
char napis_host[14];
const char *symbol="napis_device";
(dla CUDA > 4.1
nie trzeba przekazywać
nazwy, wystarczy symbol)
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*13, 0,
cudaMemcpyDeviceToHost);
printf("%s",napis_host);
}
Hello World
#include <stdio.h>
#include <cuda.h>
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
int main(void)
{
helloWorldOnDevice <<< 1, 1 >>> ();
char napis_host[14];
const char *symbol="napis_device";
(dla CUDA > 4.1
nie trzeba przekazywać
nazwy, wystarczy symbol)
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*13, 0,
cudaMemcpyDeviceToHost);
printf("%s",napis_host);
}
Deklaracja funkcji na GPU
• Funkcja uruchamiana na GPU to kernel (jądro)
• Zmienne na GPU – przedrostek __device__
• Preambuła jądra - przedrostek __global__
char napis_device[14];
__device__ char napis_device[14];
void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
__global__ void helloWorldOnDevice(void)
{
napis_device[0] = 'H';
napis_device[1] = 'e';
…
napis_device[11] = '!';
napis_device[12] = '\n';
napis_device[13] = 0;
}
CPU
GPU
Wywołanie funkcji na GPU
• Wywołanie funkcji GPU:
funkcja <<< numBlocks, threadsPerBlock >>> ( parametry );
• numBlocks
– liczba bloków w macierzy
wątków
• threadsPerBlock – liczba wątków na blok
W naszym przykładzie <<<1,1>>> oznaczało
uruchomienie jądra na jednym wątku który zawierał
się w jednym jedynym bloku macierzy wątków.
numBlocks, threadsPerBlock
<<< 1 , 1 >>>
<<< 4 , 1 >>>
<<< 1 , 4 >>>
<<< dim3(3,2,1) , 4 >>>
A tak to widzi nVidia
CUDA Programming Guide 3.2
<<< dim3(3,2,1) , dim3(4,3,1) >>>
Hello World wielowątkowo
1. CPU wywołuje funkcję na GPU
– Funkcja wstawia do pamięci GPU ciąg „Hello world!”
– jedna litera na jeden wątek!
Hello World wielowątkowo
• jedna litera na jeden wątek
• stwórzmy 14 bloków po 1 wątku
• Każdy wątek „widzi” swój numer i numer bloku
• Numer bloku -> miejsce w tablicy do skopiowania
Hello World wielowątkowo
• Jądro dla hello world w wersji wielowątkowej:
__constant__ __device__ char hw[] = "Hello World!\n";
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
int idx = blockIdx.x;
napis_device[idx] = hw[idx];
}
…
helloWorldOnDevice <<< 14, 1 >>> ();
• idx
– zawiera numer bloku w którym znajduje się wątek
– mapowanie blok/wątek -> fragment problemu
– wątek z idx-ego bloku kopiuje idx-ą literę napisu
– Kopiowanie GPU-GPU
– (bez sensu, ale chodzi nam o najprostszy problem)
Hello World wielowątkowo
• Jądro dla hello world w wersji wielowątkowej:
__constant__ __device__ char hw[] = "Hello World!\n";
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
int idx = blockIdx.x;
napis_device[idx] = hw[idx];
}
…
helloWorldOnDevice <<< 14, 1 >>> ();
• idx
– zawiera numer bloku w którym znajduje się wątek
– mapowanie blok/wątek -> fragment problemu
– wątek z idx-ego bloku kopiuje idx-ą literę napisu
– Kopiowanie GPU-GPU
– (bez sensu, ale chodzi nam o najprostszy problem)
Hello World wielowątkowo
• Jądro dla hello world w wersji wielowątkowej:
__constant__ __device__ char hw[] = "Hello World!\n";
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
int idx = blockIdx.x;
napis_device[idx] = hw[idx];
}
…
helloWorldOnDevice <<< 14, 1 >>> ();
• idx
– zawiera numer bloku w którym znajduje się wątek
– mapowanie blok/wątek -> fragment problemu
– wątek z idx-ego bloku kopiuje idx-ą literę napisu
– Kopiowanie GPU-GPU
– (bez sensu, ale chodzi nam o najprostszy problem)
Hello World wielowątkowo
• Jądro dla hello world w wersji wielowątkowej:
__constant__ __device__ char hw[] = "Hello World!\n";
__device__ char napis_device[14];
__global__ void helloWorldOnDevice(void)
{
int idx = blockIdx.x;
napis_device[idx] = hw[idx];
}
…
helloWorldOnDevice <<< 14, 1 >>> ();
• idx
– zawiera numer bloku w którym znajduje się wątek
– mapowanie blok/wątek -> fragment problemu
– wątek z idx-ego bloku kopiuje idx-ą literę napisu
– Kopiowanie GPU-GPU
– (bez sensu, ale chodzi nam o najprostszy problem)
Hello World wielowątkowo
Jedno z zadań na ćwiczenia to napisanie programu
helloworld_parallel.cu tak, by wykonywany
był przez 14 wątków w jednym bloku.
Projekt wieloplikowy w CUDA
•
•
•
•
•
Kernele GPU trzymam w *.cu
Interfejsy C++ do kerneli GPU w *.cu
Deklaracje interfejsów trzymam w *.h
W plikach C++ ładuję ww deklaracje interfejsów
A w praktyce?
Projekt wieloplikowy CUDA
main.cpp
#include <cuda.h>
#include <cuda_runtime.h>
#include <stdio.h>
#include "kernels.h"
int main(void)
{
call_helloworld();
cudaThreadSynchronize();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*14, 0,
cudaMemcpyDeviceToHost);
printf("%s\n",napis_host);
}
(dla CUDA > 4.1
nie trzeba przekazywać
nazwy, wystarczy symbol)
Projekt wieloplikowy CUDA
main.cpp
#include <cuda.h>
#include <cuda_runtime.h>
#include <stdio.h>
#include "kernels.h"
int main(void)
{
call_helloworld();
cudaThreadSynchronize();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*14, 0,
cudaMemcpyDeviceToHost);
printf("%s\n",napis_host);
}
kernels.h
void call_helloworld(void);
Projekt wieloplikowy CUDA
kernels.cu
#include <cuda.h>
__device__ char napis_device[14];
__constant__ __device__ char hw[] = "Hello
World!\n\0";
main.cpp
#include <cuda.h>
#include <cuda_runtime.h>
#include <stdio.h>
#include "kernels.h"
__global__ void helloWorldOnDevice(void)
{
int idx = threadIdx.x;
napis_device[idx] = hw[idx];
}
void call_helloworld(void)
{
helloWorldOnDevice <<< 1, 15 >>> ();
}
int main(void)
{
call_helloworld();
cudaThreadSynchronize();
char napis_host[14];
const char *symbol="napis_device";
cudaMemcpyFromSymbol (napis_host,
symbol, sizeof(char)*14, 0,
cudaMemcpyDeviceToHost);
printf("%s\n",napis_host);
}
kernels.h
void call_helloworld(void);
Graficzne hello world
• Punkt materialny na ekranie (OpenGL)
• Jego pozycja odczytywana z pamięci GPU
• Kernel zmienia pozycję punktu
– np. ruch jednostajny x = x + vdt
– warunki cykliczne: x = x-L, gdy x > L
Otwarcie okna OpenGL
• Minimalny kod w GLUT (GL Utility Toolkit)
• Schemat:
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH |
GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("CUDA Hello World
GFX");
C1
// register callbacks
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutIdleFunc(idleFunction);
// enter GLUT event processing cycle
glutMainLoop();
}
void changeSize(int w, int h)
{
float ratio = 1.0 * w / h;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glViewport(0, 0, w, h);
gluOrtho2D(-1,1,-1,1);
glMatrixMode(GL_MODELVIEW);
}
void idleFunction(void)
{
C3
C2
glutPostRedisplay();
}
void renderScene(void)
{
C4
glutSwapBuffers();
}
Więcej: http://www.lighthouse3d.com/opengl/
•
•
•
•
C1
C2
C3
C4
–
–
–
–
ustawienie współrzędnych w pamięci GPU
przesunięcie punktu (GPU)
skopiowanie x,y GPU->CPU
narysowanie punktu na ekranie (CPU)
Współrzędne punktu
• Deklaracja zmiennych w pamięci GPU
• Słowo kluczowe __device__
__device__ float px_gpu;
__device__ float py_gpu;
• Proste jądro ustawiające wartości px/py:
__global__ void setParDevice(float x, float y)
{
px_gpu = x;
py_gpu = y;
}
void call_setParDevice (float x, float y)
{
setParDevice <<< 1, 1 >>> (x, y);
}
• Ustawienie punktu:
call_setParDevice(0,0);
C1
Przesunięcie punktu
• Przesuwamy składową x wg wzoru:
x = x + dx * dt;
__global__ void moveParDevice(float dxdt)
{
px_gpu = px_gpu + dxdt;
if(px_gpu>1) px_gpu = px_gpu - 2;
}
void call_movepar(float dxdt)
{
moveParDevice <<< 1, 1 >>> (dxdt);
}
• Wywołanie funkcji:
C2
call_movepar(0.04);
Ustawienie punktu - cudaMemcpy
• CUDA Reference Manual:
cudaError_t
• src
cudaMemcpyFromSymbol(
const void *dst,
const char *symbol,
size_t count,
size_t offset,
enum cudaMemcpyKind kind)
– adres docelowy do skopiowania
• symbol
– zmienna źródłowa w pamięci globalnej lub stałej jako adres
lub ciąg znaków z nazwą zmiennej
• count
– rozmiar danych w bajtach
• offset
– przesunięcie względem adresu symbol
• kind - typ transferu (np. cudaMemcpyDeviceToHost )
Pobieranie pozycji punktu
• Tablice symbol_px(y)_gpu zawierają nazwy
zmiennych w pamięci GPU
float px_cpu, py_cpu;
C3
const char *symbol_px_gpu="px_gpu";
const char *symbol_py_gpu="py_gpu";
cudaMemcpyFromSymbol (&px_cpu, symbol_px_gpu, sizeof(float),
0, cudaMemcpyDeviceToHost);
cudaMemcpyFromSymbol (&py_cpu, symbol_py_gpu, sizeof(float),
0, cudaMemcpyDeviceToHost);
• Podobnej funkcji można też użyć do transferu
CPU->GPU (np. ustawianie pozycji punktu)
cudaMemcpyToSymbol(…)
Narysowanie punktu na ekranie
• Kod OpenGL
void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glColor3f(1,1,1);
glPointSize(5.0);
C4
glBegin(GL_POINTS)
glVertex3f(px_cpu, py_cpu,0);
glEnd();
glutSwapBuffers();
}
• Efektywność? GPU -> CPU -> OpenGL -> GPU …
W działaniu
• Jak to działa? Do sprawdzenia w ramach ćwiczeń.
Ćwiczenia
Kodem można się trochę pobawić:
• Więcej punktów (tablica px[], py[])
• Przesunięcie punktów w pętli
• Wielowątkowe przesunięcie punktów
• Oddziaływanie grawitacyjne z centrum
przyciągania (liczenie pierwiastka na punkt)
• Porównanie wydajności CPU->GPU
Za tydzień
Następny wykład: środa 23.01.2013.
Wstępny plan:
– Algorytm n ciał dla CUDA
– pokażemy jak potężnym narzędziem może być GPU!
– Lepsza współpraca z API OpenGL
– (pozbędziemy się kopiowania GPU->CPU)
Literatura
• http://www.sdsc.edu/us/training/assets/docs/NVIDIA-02-BasicsOfCUDA.pdf
ODRZUCONE
Ustawienie punktu - cudaMemcpy
• Za CUDA Reference Manual:
cudaError_t
• symbol
cudaMemcpyToSymbol (
const char symbol,
const void src,
size_t count,
size_t offset,
enum cudaMemcpyKind kind)
– zmienna docelowa w pamięci globalnej lub stałej jako
adres lub ciąg znaków z nazwą zmiennej
• src
– adres danych do skopiowania
• count
– rozmiar danych w bajtach
• offset
– przesunięcie względem adresu symbol
• kind - typ transferu (np. cudaMemcpyHostToDevice )
Ustawienie punktu - cudaMemcpy
• Przykład:
__device__ float px;
__device__ float py;
void call_setParDevice2(float x, float y)
{
// px <- x
// py <- y
const char *symbol_px="px_gpu";
const char *symbol_py="py_gpu";
cudaMemcpyToSymbol(symbol_px, &x, sizeof(float),
0, cudaMemcpyHostToDevice);
cudaMemcpyToSymbol(symbol_py, &y, sizeof(float),
0, cudaMemcpyHostToDevice);
}