Proseminar GPU-Computing Cuda vs. OpenCL SS 2013 Alexander Stepanov
Inhaltsverzeichnis 1. Einführung: Warum GPU Computing? CPU vs. GPU GPU Architektur 2. CUDA Architektur Beispiel Matrix Multiplikation 3. OpenCL Architektur Beispiel Matrix Multiplikation 4. CUDA vs. OpenCL 5. Fazit
CPU vs. GPU Top CPUs: AMD FX-8350 Opteron 6386 SE i7-3970x Xeon E5-2690 Takt 4,2 GHz 3,2 GHz 4,0 GHz 3,8 GHz Kerne Threads 8 8 16 16 6 12 8 16 GFLOPs (SP DP) 256 64 179 158 109 348 224 Top GPUs: HD 7870 GHz HD 7990 GTX Titan GTX 690 Takt 1.050 MHz 1.000 MHz 876 MHz 1.019 MHz Shader 2.048 2 2.048 2.688 2 1.536 GFLOPs (SP DP) 4.096 1024 2 4.096 2 1024 4.500 1.300 2 2.810 2 117 Speicher (GDDR5) 3 GB 2 3 GB 6 GB 2 2 GB
CPU vs. GPU http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html http://en.wikipedia.org/wiki/flynn's_taxonomy
GPU Architektur (GK110 GTX Titan) http://www.nvidia.de/content/pdf/kepler/nvidia-kepler-gk110-architecture-whitepaper.pdf
GPU Architektur (GK110 GTX Titan) L1-Cache: 64 KB / SMX Data-Cache: 48 KB /SMX L2-Cache: 1.536 KB 196 SP Cores / SMX 64 DPU / SMX 32 SFU / SMX 16 Texture Units / SMX 4 Warp Scheduler / SMX http://www.nvidia.de/content/pdf/kepler/nvidia-kepler-gk110-architecture-whitepaper.pdf
CPU vs. GPU GPU Massiv Parallelisiert Wenig Cache Langsam bei Verzweigungen CPU Großer Befehlssatz Branch Prediction
CUDA (Compute Unified Device Architecture) 2007 von NVIDIA released Wird nur von NVIDIA gepflegt und läuft nur auf deren Hardware Aktuellste Version: 5.0 Sprache: CUDA-C und PTX Assembler Läuft auf Windows, Linux und Mac OS X
CUDA Ablauf: CPU kopiert kompilierten Kernel-Code und Daten auf die GPU. GPU führt den Code in Warps (32 Threads) aus und überträgt das Ergebnis zurück zum Host Warp-Scheduler: Partitioniert mehrere Blöcke zu Warps Ein Warp führt eine Instruktion gleichzeitig aus (SIMD) http://sidkashyap.wordpress.com/2013/05/02/cuda-faq/
CUDA // Kernel Code global void mmult ( const float *A, const float *B, float *C, int width) { int i = blockidx.x * blockdim.x + threadidx.x; int j = blockidx.y * blockdim.y + threadidx.y; http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html global zeichnet Kernelcode aus. Wird zur Laufzeit vom Nvcc (NVIDIA CUDA Compiler) kompiliert. device (global), constant (constant), share (shared) sind Schlüsselwörter für die Speicherhierarchie in CUDA Nachteil: Matrixgröße beschränkt auf Blockgröße => Kernel mehrere Zellen berechnen lassen => Mehr Blöcke zur Problemlösung beauftragen } float val = 0; for( int k = 0; k < width; k++ ) val += A[width *j+k] * B[width *k+i]; C[width *j+i] = val ;
CUDA // Host Code host int main (int argc, char ** argv ) { // Matrizengrößen A (1024 x 512), B (512 x 2048) => C (512 x 512) size_t sizea = 1024 * 512 * sizeof(float); size_t sizeb = 512 * 2048 * sizeof(float); size_t sizec = 512 * 512 * sizeof(float); // Matrizen im Host anlegen float *h_a = (float *) malloc(sizea); float *h_b = (float *) malloc(sizeb); float *h_c = (float *) malloc(sizec); // Matrizenspeicher auf der GPU (Device) reservieren float* d_a, d_b, d_c; cudamalloc (( void **) &d_a, sizea ); cudamalloc (( void **) &d_b, sizeb ); cudamalloc (( void **) &d_c, sizec ); // Matrizen vom Host zur GPU kopieren cudamemcpy(d_a, h_a, sizea, cudamemcpyhosttodevice); cudamemcpy(d_b, h_b, sizeb, cudamemcpyhosttodevice); // Kernel vorbereiten und ausführen dim3 threads(16, 16) ; //Blockgröße dim3 grid(512 / threads.x, 512 / threads.y ); //Gridgröße mmult<<<grid, threads>>>(d_a, d_b, d_c, 512); } // Ergebnis Matrix d_c von der GPU zum Host h_c kopieren cudamemcpy (h_c, d_c, sizec, cudamemcpydevicetohost );
OpenCL (Open Computing Language) Spezifikation Ende 2008 veröffentlich Aktuelle Version: 1.2 Wurde ursprünglich von Apple entwickelt, jetzt in den Händen der Kronos Group Offener Standard (wie OpenGL) Sprache: Kernel in OpenCL C und Host in C/C++ Plattform- und Geräteunabhängige Gerätehersteller kümmern sich selbst um den OpenCL-Support in ihren Chips
OpenCL Ablauf (Ähnlich wie bei CUDA): CPU kopiert kompilierten Kernel-Code und Daten auf die GPU. GPU führt den Code in den Compute Units aus und überträgt das Ergebnis zurück zum Host http://www.khronos.org/assets/uploads/developers/library/overview/opencl_overview.pdf
OpenCL const char kernel_src [] = " kernel void mmult ( global const float *A, " " global const float *B, " " global float *C, " " int wa, int wb) " "{ " " int i = get_global_id (0); " " int j = get_global_id (1); " " " " float val = 0; " " for( int k = 0; k < wa; k++ ) " " val += A[wA*j+k] * B[wB*k+i]; " " C[wA*j+i] = val; " "} "; http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html Kernelcode wird als String übergeben und dann zur Laufzeit kompiliert. Schlüsselwörter für Speicherhierarchie: global (global), constant (constant), local (share) und private (privat)
OpenCL int main (int argc, const char * argv []) { // Matrizengrößen A (1024 x 512), B (512 x 2048) => C (512 x 512) size_t sizea = 1024 * 512 * sizeof(float); size_t sizeb = 512 * 2048 * sizeof(float); size_t sizec = 512 * 512 * sizeof(float); // Matrizen im Host anlegen float *h_a = (float *) malloc(sizea); float *h_b = (float *) malloc(sizeb); float *h_c = (float *) malloc(sizec); // Auf welcher Platform soll das laufen cl_uint num_platforms ; cl_platform_id platform ; cl_int err = clgetplatformids (1, & platform, & num_platforms ); // Auf welchem Gerät soll das laufen cl_device_id device ; clgetdeviceids(platform, CL_DEVICE_TYPE_GPU, 1, &device, 0); // Erstelle context, command queue, program und kernel cl_context context = clcreatecontext(0, 1, &device, 0, 0, &err); cl_command_queue cmd_queue = clcreatecommandqueue(context, device, 0, 0); cl_program program = clcreateprogramwithsource(context, 1, &kernel_src, 0, &err); clbuildprogram(program, 0, 0, 0, 0, 0); cl_kernel kernel = clcreatekernel (program, " mmult ", &err); } // Matrizen erstellen float * h_a = new float[sizea]; float * h_b = new float[sizeb]; float * h_c = new float[sizec]; cl_mem d_a = clcreatebuffer ( context, CL_MEM_READ_ONLY, sizea, 0, 0); cl_mem d_b = clcreatebuffer ( context, CL_MEM_READ_ONLY, sizeb, 0, 0); cl_mem d_c = clcreatebuffer ( context, CL_MEM_WRITE_ONLY, sizec, 0, 0); // Speicher reservieren clsetkernelarg(kernel, 0, sizeof(cl_mem), &d_a); clsetkernelarg(kernel, 1, sizeof(cl_mem), &d_b); clsetkernelarg(kernel, 2, sizeof(cl_mem), &d_c); // work size berechnen size_t ws_global [] = {512, 512}; size_t ws_local [] = {16, 8}; // 128 items per group // Eingabematrizen zur GPU kopieren, Kernel starten und Ergebnis zurückkopieren clenqueuewritebuffer(cmd_queue, d_a, CL_FALSE, 0, sizea, h_a, 0, 0, 0); clenqueuewritebuffer(cmd_queue, d_b, CL_FALSE, 0, sizeb, h_b, 0, 0, 0); clenqueuendrangekernel(cmd_queue, kernel, 2, 0, ws_global, ws_local, 0, 0, 0); clenqueuereadbuffer(cmd_queue, d_c, CL_FALSE, 0, sizec, h_c, 0, 0, 0); clfinish(cmd_queue);
CUDA vs. OpenCL Wegen CUDA nur auf NVIDIA GPUs vergleichbar OpenCL ist von vornerein benachteiligt -> Wird mit CUDA umgesetzt Vergleich ist somit wegen dem Performanceverlust von Interesse CUDA Global Memory Constant Memory Shared Memory Local Memory Thread Thread-block OpenCL Global Memory Constant Memory Local Memory Private Memory Work-item Work-group http://www.evga.com/forums/tm.aspx?m=91863&mpage=1
CUDA vs. OpenCL Test aus A Performance Comparison of CUDA and OpenCL 2010 von Kamran Karimi, Neil G. Dickson und Firas Hamze von D-Wave Systems Inc. in Canada Quanten-Spin-System Simulation mit AQUA (Adiabatic QUantum Algorthms) GPU: GTX 260 mit 192 Kernen Chiptakt: 576 MHz CUDA-Version: 2.3 OpenCL: 1.0
CUDA vs. OpenCL CUDA -> OpenCL: Syntaktische Anpassung (siehe Terminologie) keine Referenzen in OpenCL Qubits Kernel Laufzeit Datentransfer Gesamtlaufzeit 8 1,12 0,94 649,05 Kb 1,51 16 1,23 1,24 1.633,32 Kb 1,38 32 1,19 1,4 3.553,44 Kb 1,26 48 1,45 1,32 8.210,22 Kb 1,5 72 1,63 1,58 15.338,77 Kb 1,68 96 1,17 1,36 33.124,49 Kb 1,22 128 1,12 1,34 49.541,04 Kb 1,16
CUDA vs. OpenCL CUDA unterstützt spezifische Hardware-Features (z.b. Textur-Speicher) CUDA-Compiler besser optimiert => Selbst Kernel optimieren z.b. Schleifen entrollen A Comprehensive Performance Comparison of CUDA and OpenCL - 2011 International Conference on Parallel Processing - Jianbin Fang, Ana Lucia Varbanescu and Henk Sips
Fazit Cuda: Hardwarenäher (PTX Assembler) => Performanter Von nur einem Hersteller betreut: schnellere Updates Großer Umfang an Tools OpenCL: Universeller