Προγραμματισμός GPUs μέσω του περιβάλλοντος CUDA Κωνσταντινίδης Ηλίας Υποψήφιος Διδάκτωρ Τμήμα Πληροφορικής & Τηλεπικοινωνιών Εθνικό και Καποδιστριακό Πανεπιστήμιο Αθηνών
Νόμος Moore density doubles/18m Έως 2003 σήμαινε ταχύτητα, clock frequency doubles/18m Προβλήματα: energy consumption, heat dissipation, lower frequency Από 2003 σημαίνει core doubles/18m Συνέπειες: explicit parallelism (Από elite σε mainstream) δύο τάσεις: multi-cores, many-cores
Multi-Core (CPUs) E.g. Intel i7 Out of order execution Multiple instruction issue Full x86 set Hyper-threading 2 H/W threads Designed for maximum speed for seq. progs. Core double/18m
Many-cores (GPUs) E.g. Nvidia GTX 480 Execution throughput Large number of smaller cores (ALUs) GTX280 240 cores Multi-threaded In order Single instruction issue Shares control and Instruction cache (8 cores)
CPU vs GPU
Τάση αύξησης υπολογιστικών επιδόσεων
Κατανομή transistors στη CPU και στη GPU Περισσότερα transistors αφιερώνονται στην επεξεργασία (ALUs) αντί μνήμης cache/control
Κατανομή transistors στη CPU και στη GPU Nehalem (Intel Core i7) Τι ποσοστό της επιφάνειας αποτελεί η cache memory;
Σχεδιαστικές Επιλογές CPU Optimum for seq. programming Sophisticated control (reduce instruction/data latency) => they do not to contribute to peak calculation speeds 2009-4 cores 2010 8 cores
Memory Bandwidth Graphic chips 10x CPU chips 2006 GTX 8800 86.4 GB/s DRAM 2008 GTX 200-150 GB/s CPUs up to 50 GB/s (system S/W, legacy applications, I/O devices)
GPUs Numeric Computing Engines (accelerators) Do not perform well on tasks that CPUs are designed to perform well Application use CPU+GPU CUDA join CPU/GPU execution Now wide spread accessible IEEE floating point compatibility (single/double)
NVidia GTX-480 15 SMs (Streaming Multiprocessors) Κάθε SM περιλαμβάνει 32 SPs (Streaming Processors) Σύνολο 15x32 = 480 SPs! Μέγιστο bandwidth 177.4GB/sec
Αρχιτεκτονική Fermi Η GPU είναι ένας μαζικά παράλληλος επεξεργαστής
Σύγκριση προδιαγραφών GPUs GeForce GTX 285 GeForce GTX 480 Transistor count 1.4B 3.0B 2.15B Radeon HD 5870 Process node 55 nm @ TSMC 40 nm @ TSMC 40 nm @ TSMC Core clock 648 MHz 700 MHz 850 MHz "Hot" (shader) clock 1476 MHz 1401 MHz -- Memory clock 1300 MHz 924 MHz 1200 MHz Memory transfer rate 2600 MT/s 3696 MT/s 4800 MT/s Memory bus width 512 bits 384 bits 256 bits Memory bandwidth 166.4 GB/s 177.4 GB/s 153.6 GB/s ALUs 240 480 1600 Peak single-precision arithmetic rate 0.708 Tflops 1.35 Tflops 2.72 Tflops Peak double-precision arithmetic rate 88.5 Gflops 168 Gflops 544 Gflops ROPs 32 48 32 ROP rate 21.4 Gpixels/s 33.6 Gpixels/s 27.2 Gpixels/s INT8 bilinear texel rate (Half rate for FP16) 51.8 Gtexels/s 42.0 Gtexels/s 68.0 Gtexels/s
CUDA Compute Unified Development Architecture GPGPU (General Purpose Computation on Graphics Processing Unit) Δεν εμπλέκει το υποσύστημα γραφικών (π.χ. OpenGL, DirectX) Απλή γλώσσα C με κάποια χαρακτηριστικά της C++ και κάποιες επεκτάσεις του CUDA
Διαδικασία μεταγλώττισης Αρχείο.cu Μεικτός κώδικας C/C++ και CUDA Μεταγλωττιστής nvcc Αρχείο.c Μεταγλωττιστής C κώδικα Object code Αρχείο.cubin &.ptx PTX κώδικας Linker Εκτελέσιμο αρχείο
Μοντέλο λειτουργίας του CUDA Δέσμευση μνήμης στην GPU Μεταφορά δεδομένων από τον χώρο μνήμης της CPU στον χώρο μνήμης της GPU Εκτέλεση προγράμματος στη GPU Μεταφορά αποτελεσμάτων από το χώρο μνήμης της GPU στο χώρο μνήμης της CPU αποτελέσματα δεδομένα Παράλληλη εκτέλεση
Ο πυρήνας (kernel) Έχει τη μορφή συνάρτησης και αποτελεί τον κώδικα που θα εκτελεστεί από κάθε νήμα της GPU Η εκτέλεση του κάθε νήματος διαφοροποιείται με βάση την τιμή ενσωματωμένης μεταβλητής που ταυτοποιεί μοναδικά το κάθε νήμα (ThreadIdx και BlockIdx) Ο κώδικας αυτός μεταφέρεται στην περιοχή μνήμης της GPU και εκτελείται κατά ομάδες εκατοντάδων νημάτων (blocks) παράλληλα σε όλους τους διαθέσιμους SMs Ο SM είναι ένας μαζικά πολυνηματικός συνεπεξεργαστής (massively multithreaded coprocessor), υποστηρίζοντας μέχρι και 1536 ενεργά νήματα (GPUs με compute capability 2.0) Η δρομολόγηση των νημάτων γίνεται μέσω του υλικού
Τα νήματα είναι οργανωμένα σε Blocks Τα Blocks είναι οργανωμένα σε Grid Τα μεγέθη τους καθορίζονται από τον προγραμματιστή Ιεραρχία νημάτων
Ιεραρχία μνήμης Τοπική μνήμη (local memory) Ιδιωτική (private) Διαμοιραζόμενη μνήμη (shared memory) Διαμοιραζόμενη μεταξύ νημάτων του ίδιου block Μέγεθος (16KB ή 48KB)/SM Καθολική μνήμη (global memory) Δημόσια (public)
Ιεραρχία μνήμης (2) Σταθερή μνήμη (constant memory) max 64 KB Μνήμη υφής (texture memory) Μνήμη συσκευής (device memory)
Δυναμική διαχείριση πόρων του SM Κάθε SM διαθέτει ένα σύνολο πόρων: Καταχωρητές (registers) Διαμοιραζόμενη μνήμη (shared memory) Ενεργά (active) λέγονται τα blocks, τα νήματα των οποίων βρίσκονται σε διαδικασία εκτέλεσης στον SM Τα ενεργά blocks νημάτων διαμοιράζονται τους παραπάνω πόρους του SM Οι πόροι πρέπει να επαρκούν για την εκτέλεση ενός τουλάχιστον block νημάτων ώστε να ξεκινήσει η εκτέλεση
Ο SM είναι ένας πολυνηματικός επεξεργαστής Ο κάθε SM μπορεί και δρομολογεί ταυτόχρονα μέχρι και 8 blocks νημάτων Ο αριθμός αυτός περιορίζεται από τις απαιτήσεις του block σε πόρους (καταχωρητές και διαμοιραζόμενη μνήμη) Στόχος είναι να επιλεγεί μέγεθος block τέτοιο ώστε να μεγιστοποιείται το πλήθος των ενεργών νημάτων στον SM Μεγάλος αριθμός ενεργών νημάτων μπορεί να «κρύψει» καθυστερήσεις στην πρόσβαση μνήμης (latency hiding) και την pipeline
Κλιμάκωση σε πολλούς SMs Η δρομολόγηση των blocks του kernel πραγματοποιείται αυτόματα χωρίς την παρέμβαση του προγραμματιστή Μικρότεροι χρόνοι σε συσκευές με μεγαλύτερο πλήθος SMs
Η GPU είναι SIMD; Κάθε ενεργό block νημάτων διαμοιράζεται σε ομάδες νημάτων που λέγονται στημόνια (warps) και εκτελούνται κατά SIMD (Single Instruction Multiple Data) Η NVidia την αποκαλεί SIMT (Single Instruction Multiple Thread) αρχιτεκτονική Το σημερινό μέγεθος του στημονίου είναι 32 Μέγιστη απόδοση επιτυγχάνεται όταν τα νήματα ενός στημονίου ακολουθούν κοινές διακλαδώσεις Η απόδοση μειώνεται σε περιπτώσεις όπου τα νήματα αποκλίνουν (π.χ. if ή while constructs)
Παράδειγμα 1 Έστω ότι θέλουμε να αθροίσουμε 2 διανύσματα, Α και Β, και να αποθηκεύσουμε το άθροισμα σε ένα 3 ο διάνυσμα C, δηλ c i = a i + b i Σε κλασικό ακολουθιακό υπολογισμό θα είχαμε: void docalc(void){ for(int i=0; i<n; i++) c[i] = a[i] + b[i]; }
Ακολουθιακή Υλοποίηση 0 1 2 3 4 5 6 7 8 9 + + + + + + + + + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Μαζικά Παράλληλη Υλοποίηση 0 1 2 3 4 5 6 7 8 9 + + + + + + + + + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Παράδειγμα 1 (kernel) Σε CUDA θα ήταν απαραίτητο να γράψουμε την συνάρτηση για τον kernel: global void kadd(float *a, float *b, float *c, int N){ const unsigned int i = blockidx.x * blockdim.x + threadidx.x; if( i<n ) c[i] = a[i] + b[i]; } Η κλήση του kernel γίνεται ως εξής: Void faddvectors(float *ad, float *bd, float *cd, int N){ const int BLOCK_SIZE = 64; dim3 dimbl(block_size); // Μέγεθος block σε νήματα dim3 dimgr((n+block_size-1)/block_size); //Μέγεθος grid σε blocks kadd<<<dimgr, dimbl>>>(ad, bd, cd, N); }
Παράδειγμα 1 (παρατηρήσεις) Κάθε νήμα υπολογίζει ένα μόνο στοιχείο Για τον υπολογισμό π.χ. 15 10 6 στοιχείων δημιουργούνται ισάριθμα νήματα Η GPU με την διαχείριση μεγάλου αριθμού νημάτων από το υλικό επιτρέπει τέτοιου είδους fine grained διαμερισμό Καθαροί υπολογιστικοί χρόνοι: Σε CPU: 99 msecs (Athlon64 2.2GHz) Σε GPU: 2.6 msecs (GTX 465)
Παράδειγμα 1 (παρατηρήσεις 2) Οι χρόνοι δεν περιλαμβάνουν τον χρόνο μεταφοράς των δεδομένων από την μνήμη του συστήματος στην κάρτα γραφικών και αντιστρόφως (bottleneck?). Το bandwidth του PCI-E 16x δεν ξεπερνάει τα 4GB/sec, για κάθε κατεύθυνση και αυτά υπό συγκεκριμένες προϋποθέσεις.
Επιλογή μεγέθους Block Κρίσιμος παράγοντας για μεγιστοποίηση της απόδοσης Επηρεάζει το δείκτη χρησιμοποίησης (occupancy) Πρέπει να αποτελεί πολλαπλάσιο του μεγέθους στημονίου (32 σήμερα) ώστε να γίνεται σωστή εκμετάλλευση των υπολογιστικών πόρων Περιορισμένο πλήθος καταχωρητών του SM και μεγέθους της shared memory Οι G80 GPU έχουν 8192 καταχωρητές και 16KB shared memory ανά SM
Απόκλιση στημονίων Εντολές ελέγχου ροής (π.χ. if, for, while) μπορούν να προκαλέσουν απόκλιση στην εκτέλεση των νημάτων ενός στημονίου. Σε αυτή την περίπτωση διαφορετικά νήματα του ίδιου στημονίου εκτελούν διαφορετικά μονοπάτια κώδικα. Τέτοιες περιπτώσεις προκαλούν εκτέλεση όλων των μονοπατιών από ολόκληρο το στημόνι και μείωση της αποτελεσματικότητας Ο προγραμματιστής οφείλει να φροντίζει όσο το δυνατό τα νήματα στο ίδιο στημόνιο να ακολουθούν κοινά μονοπάτια
Καθυστέρηση μνήμης Η μνήμη στην GPU είναι βελτιστοποιημένη για υψηλό bandwidth και όχι για μικρή καθυστέρηση Η πρόσβαση μνήμης κοστίζει 400-600 κύκλους όταν η πρόσβαση γίνεται στην μνήμη της συσκευής Η καθυστέρηση στην πρόσβαση στη μνήμη μπορεί να «κρυφτεί» εφόσον διατηρούνται πολλά νήματα ενεργά στον SM Ο προγραμματιστής πρέπει να φροντίζει ώστε να μεγιστοποιήσει το πλήθος των ενεργών νημάτων στους SMs (δείκτης χρησιμοποίησης) H constant memory και η texture memory μπορούν να χρησιμοποιηθούν για δεδομένα μόνο ανάγνωσης
Προσβάσεις στη μνήμη Η GPU μπορεί να πραγματοποιεί τις προσβάσεις στη μνήμη ενός στημονίου συγχωνευμένα (coalesced) σε ένα ή δύο transactions για όλα τα νήματα του στημονίου Τα νήματα στο (ημι)στημόνιο πρέπει να προσπελάσουν συνεχόμενες θέσεις μνήμης με βάσει την αύξουσα τιμή (ThreadIdx) του νήματος Τα δεδομένα πρέπει να είναι στοιχισμένα (aligned) στη μνήμη Ανάλογα με τον δείκτη Compute Capability τα κριτήρια διαφοροποιούνται
Προσβάσεις στη μνήμη (G80) Συγχωνευμένες προσβάσεις Μη συγχωνευμένες προσβάσεις
Γενικά συμπεράσματα Εφαρμόζεται εύκολα σε προβλήματα στα οποία υπάρχει παραλληλισμός δεδομένων Υψηλές επιδόσεις σε προβλήματα με μεγάλο ποσοστό υπολογισμών και κυρίως floating point operations μονής ακρίβειας Μνήμη υψηλών επιδόσεων σε bandwidth Η τήρηση των κανόνων βελτιστοποίησης είναι ιδιαίτερα κρίσιμη για την επίτευξη ικανοποιητικών επιδόσεων Οι επιδόσεις τους κορυφώνονται σε μεγάλου μεγέθους προβλήματα
Απαιτήσεις λογισμικού Updated NVidia Device Driver CUDA Toolkit CUDA Software Development Kit Emulator Βιβλιοθήκες CUBLAS (για υπολογισμούς γραμμικής άλγεβρας) CUFFT (για Fast Fourier Transform) CUSPARSE (για υπολογισμούς με αραιούς πίνακες) CURAND (γεννήτρια τυχαίων αριθμών) Tuning Occupancy calculator Visual profiler
Παραπομπές NVidia CUDA site http://www.nvidia.com/cuda NVidia CUDA Toolkit Compiler Manual & references (CUDA Programming Guide) NVidia CUDA SDK Examples Case studies
Βιβλιογραφία CUDA BY EXAMPLE Jason Sanders, Edward Kandrot Programming Massively Parallel Processors David B. Kirk, Wen-mei W. Hwu
OpenCL Ανοικτό πρότυπο προγραμματισμού ετερογενών αρχιτεκτονικών Υποστηρίζεται από πολλούς μεγάλους κατασκευαστές (Intel, AMD, Nvidia, κλπ.) Έχει πολλά κοινά στοιχεία με το CUDA Μειονέκτημα: Δεν είναι ακόμα αρκετά ώριμο και αναπτύσσεται πιο συντηρητικά από το CUDA
Ερωτήσεις;