Zastosowanie technologii nVidia CUDA do zrównoleglenia algorytmu genetycznego dla problemu komiwojażera Adam Hrazdil Wydział Inżynierii Mechanicznej i Informatyki Kierunek informatyka, Rok V [email protected] Streszczenie W poniższej pracy zaprezentowano możliwości przetwarzania równoległego przy użyciu wielu rdzeni procesora graficznego na przykładzie problemu komiwojażera realizowanego przez algorytm genetyczny. Genetyczny algorytm został zaimplementowany w dwóch wersjach: sekwencyjnej działającej na CPU oraz równoległej z wykorzystaniem technologii CUDA. Zaprezentowana została architektura CUDA, porównano wyniki otrzymane przez dwie wersje algorytmu pod względem wydajności oraz sformułowano wnioski. 1 Wstęp Spośród wielu metod sztucznej inteligencji obliczeniowej algorytmy genetyczne AG (ang. Genetic Algorithms) doczekały się wielu zastosowań. Można je wykorzystywać do rozwiązywania rzeczywistych problemów z dowolnej dziedziny ludzkiej działalności. Rozwój algorytmów genetycznych został zapoczątkowany w roku 1975 pracą Johna Hollanda z Uniwersytetu Michigan [9]. Obecnie zastosowanie algorytmów genetycznych jest imponujące, stosowane są bowiem one w szeregowaniu zadań, modelowaniu finansowym, optymalizacji funkcji oraz harmonogramowaniu. Gwałtownie przeżywany rozwój algorytmów genetycznych pobudził w ostatnim czasie różne obszary nauki i techniki min. fizykę, biologię, programowanie układów układów scalonych VLSI (ang. Very Large Scale of Integration), a także zarządzanie. Rozróżnia się trzy zasadnicze grupy zastosowań AG [4]: algorytmy przeszukujące (Search), optymalizujące (Optimization) i uczące (Learning). Algorytmy genetyczne wykorzystują poszukiwania oparte na mechanizmach doboru naturalnego oraz dziedziczności. Łączą w sobie ewolucyjną zasadę przeżycia najlepiej przystosowanych osobników z systematyczną, choć zrandomizowaną wymianą informacji [2]. Algorytmy ewolucyjne i genetyczne są ściśle zdefiniowanymi procedurami matematycznymi wykorzystywanymi do rozwiązywania niektórych problemów, dla których standardowe algorytmy są nieznane lub mają nieakceptowalnie długi czas 1 działania. Zasada ich działania opiera się na mechanizmach skonstruowanych w oparciu o procesy zaobserwowane w biologicznej ewolucji. Zadanie komiwojażera (TSP - ang. Traveling Salesman Problem) [10] jest właśnie jednym z takich problemów. Ogólny problem komiwojażera sformułowany jest następująco: dane jest n miast, a każde dwa z nich połączone są drogą o określonej długości. W jednym z miast znajduje się komiwojażer, który chce odwiedzić wszystkie miasta w taki sposób, aby w każdym mieście znaleźć się dokładnie jeden raz, a na koniec wędrówki powrócić do miejsca startowego. Optymalnym rozwiązaniem jest trasa, dla której koszt objazdu jest najmniejszy. Na podstawie teorii grafów problem komiwojażera można przedstawić jako zadanie polegające na znalezieniu minimalnego cyklu Hamiltona w pełnym grafie ważonym. Miasta są wierzchołkami grafu, trasy pomiędzy nimi to krawędzie z wagami. Waga krawędzi może odpowiadać odległości pomiędzy miastami połączonymi tą krawędzią, czasowi podróży lub koszcie przejazdu. Zadanie sprowadza się do znalezienia permutacji miast (i1, i2,…, in), dla której koszt jest najmniejszy. Dla problemu komiwojażera nie jest znany algorytm efektywny, tzn. rozwiązujący ten problem w czasie wielomianowym. Należy on do klasy problemów NP-trudnych, w związku z tym niemożliwy jest przegląd zupełny przestrzeni rozwiązań. Dlatego jest często wybierany do weryfikacji nowych pomysłów w dziedzinie AG. 2 Ogólna charakterystyka architektury CUDA CUDA (ang. Compute Unified Device Architecture) [6] to technologia typu GPGPU (ang. General-Purpose Computing on Graphics Processing Units) opracowana przez firmę nVidia. Jest uniwersalną architekturą procesorów wielordzeniowych (głównie kart graficznych) umożliwiającą wykorzystanie ich mocy obliczeniowej do rozwiązywania ogólnych problemów numerycznych w sposób wydajniejszy niż w tradycyjnych, procesorach ogólnego zastosowania (CPU). Na rysunku 1 przedstawiono porównanie mocy obliczeniowych kolejnych generacji procesorów typu CPU firmy Intel oraz GPU firmy nVidia. 2 Integralną częścią architektury CUDA jest oparte na języku programowania C środowisko programistyczne wysokiego poziomu, w którego skład wchodzą m.in. specjalny kompilator (nvcc) oraz interfejs programowania aplikacji. Projekt architektury CUDA zakłada pełną skalowalność programów, napisany dziś program wykonywalny ma w przyszłości działać bez żadnych zmian na coraz wydajniejszych procesorach graficznych posiadających coraz większą liczbę rdzeni, rejestrów, pamięci operacyjnej i innych zasobów. Procesor (ang. Host) wykonuje kod sekwencyjnie do czasu wywołania funkcji jądra (ang. Kernel) działającej na karcie graficznej (ang. Device). Każdorazowe wywołanie tej funkcji tworzy na karcie graficznej nową siatkę wątków (ang. Grid). Wątki są zgrupowane w blokach (możliwa struktura 1D, 2D, 3D), maksymalnie można przydzielić 512 x 512 x 64 wątków w bloku. Bloki zgrupowane są w siatce, maksymalnie 65535 x 65535. Wymiary bloku i siatki podajemy jako argumenty wywołania funkcji jądra. Schemat wywołania funkcji jądra wywołanej przez procesor został przedstawiony na rysunku 2. Rys. 2. Schemat wywołania funkcji jądra (źródło [5]) Karta graficzna jest zrealizowana jako zbiór multiprocesorów (SM, ang. Single Multiprocessor), które są podstawową jednostką obliczeniową GPU. Są to skalarne, zmiennoprzecinkowe jednostki arytmetyczno-logiczne pojedynczej precyzji. Każdy multiprocesor zawiera osiem równoległych procesorów strumieniowych (SP, ang. Scalar Processor). SM zawiera również dwie jednostki specjalne (SFU, ang. Special Function Units), wykonujące bardziej złożone funkcje 3 matematyczne (np. pierwiastek, funkcja sinus). Każdy SM posiada także jedną jednostkę arytmetyczną podwójnej precyzji. Celem ułatwienia współpracy między wątkami wykonywanymi przez SP każdy SM zawiera 16 KB pamięci współdzielonej. Każdy multiprocesor ma zlecane do jednoczesnego wykonania jeden bądź więcej bloków wątków. 3 Implementacja algorytmu Algorytmy z wykorzystaniem technologii CUDA zostały zaimplementowane tak, aby sposób działania najwierniej odwzorowywał wersje sekwencyjne wykonywalne na CPU. Każda iteracja algorytmu jest jednorazowo wywołaną funkcją jądra. Jest to spowodowane tym, że przed rozpoczęciem nowej iteracji wszystkie operacje w poprzedniej iteracji muszą się zakończyć. Biblioteka CUDA podczas działającej funkcji jądra nie umożliwia synchronizacji bloków, dlatego nie można rozpocząć obliczeń dla nowej iteracji w tej samej funkcji jądra. Ponowne wywołanie funkcji jądra w tym samym wątku programu wykonywalnego na CPU daje gwarancje zakończenia działania wszystkich bloków. Populacja między wywołaniami funkcji jądra jest przechowywana w pamięci głównej GPU, która jest zachowywana między kolejnymi wywołaniami funkcji jądra do czasu jej jawnego zwolnienia. Opracowanie algorytmu genetycznego pod kątem odpowiedniego zadania wymaga zaprojektowania odpowiedniego kodowania dla chromosomów. W implementacji algorytmu zastosowana została reprezentacja ścieżkowa [1]. Krzyżowanie w przypadku reprezentacji ścieżkowej jest skomplikowaną czynnością. Wynikiem działania operatora krzyżowania musi być potomek akceptowalny (zawierający unikalną listę miast). Dla przyjętej reprezentacji zastosowano krzyżowanie z częściowym zastępowaniem [1] (ang. Partially Matched Crossover). Wynikiem działania operatora mutacji tak jak w przypadku krzyżowania, musi być chromosom akceptowalny. Dla chromosomu w zastosowanej reprezentacji zaimplementowano następujące operatory mutacji: • wzajemnej wymiany – wymienia się dwa losowe miasta, • inwersją – obraca się kolejność wszystkich genów w chromosomie, • przesunięciem – przesuwa się chromosom w wybraną stronę, końcowe geny które wychodzą poza zakres wstawiane są na początek, • lokalna optymalizacja - wybiera się miasto i zamienia sąsiadem, jeśli wynik zamiany przynosi skrócenie trasy. W selekcji osobników naśladuje się mechanizm przeżycia w naturze. Wobec tego oczekuje się, że osobnik o najwyższym stopniu przystosowania uzyska liczne potomstwo. Do najważniejszych metod selekcji należą [3]: • metoda elitarna, • metoda turniejowa, • metoda proporcjonalna ("Metoda koła ruletki "). Ocena danego osobnika polega na zsumowaniu odległości pomiędzy kolejnymi miastami wyznaczonymi przez daną reprezentację. Mniejsza wartość oceny jest tożsama z mniejszym kosztem odwiedzenia wszystkich miast. 4 4 Porównanie wydajności algorytmów Testy przeprowadzane były na komputerze wyposażonym w procesor AMD Phenom™ II X4 B50 taktowany rdzeniem 3,1 GHz, pamięć RAM DDR 3 taktowaną zegarem 1333 MHz oraz kartę graficzną GeForce GTX 275, posiadającą 30 multiprocesorów, taktowanych zegarem 1,40 GHz. Testy były prowadzone w środowisku Windows 7 Professional 64 bit. Kod był kompilowany za pomocą CUDA SDK 3.0 beta i Visual Studio 2008. Algorytmy zostały przetestowane dla wielkości zadania 500 miast. Miasta zostały losowo umieszczone na powierzchni o wymiarach 1200 x 850. Przykładowy chromosom populacji początkowej został pokazany na rysunku 3. Rys. populacji początkowej Wyniki porównywania obu algorytmów wykazały zbliżone długości tras dla analogicznych pokoleń. Wyniki zostały zaprezentowane na rysunku 4. Rys. 4. Porównanie przystosowania najlepszego osobnika algorytmów w kolejnych iteracjach algorytmu 5 Dla oceny wydajności algorytmów z wykorzystaniem różnych technologi wyniki testu przedstawiono na rysunku 5 jako wykres zależności przystosowania od czasu działania aplikacji. Rys. 5. Porównanie przystosowania najlepszego osobnika algorytmów w czasie Wyraźnie widoczny jest duży wzrost wydajności aplikacji korzystającej z technologii CUDA. Po 10 sekundach aplikacja wykorzystująca technologię CUDA i osiągnęła wynik lepszy od wersji sekwencyjnej po czasie 10 minut. Ostateczny wynik działania aplikacji wykorzystującej technologię CUDA został pokazany na rysunku 6. Osiągnięcie tego wyniku wymagało 60 tysięcy pokoleń. W tym czasie sekwencyjna aplikacja uzyskała 3 razy dłuższą trasę wykonując 1400 pokoleniach. Wyniki w testowanym przypadku wykazują 42 krotne przyśpieszenie. Rys. 6. Wynik działania algorytmu CUDA (najlepszy wynik w 60000 pokoleniu) Większa ilość miast wiąże się także ze zwiększeniem operacji wykonywanych na chromosomach. Ilość możliwych rozwiązań w przypadku większej ilości miast jest 6 też znacznie większa. Następnie badano wpływu rozmiaru chromosomu na czas wykonywania aplikacji. Wyniki testów zostały przedstawione na rysunku 7. Rys. 7. Wpływ rozmiaru chromosomu na czas działania aplikacji wykonującej technologie CUDA Porównanie czasów wykonywania obu aplikacji przedstawiono na rysunku 8. Badano przyśpieszenia w zależności o rozmiarów chromosomu. Maksymalne osiągnięte przyśpieszenie w tym przypadku osiągnięto dla 400 miast i czas wykonywania aplikacji z wykorzystaniem technologi CUDA był 58,6 razy krótszy. Dla chromosomów o długości większej od 320 wartości przyśpieszenia osiągnęły podobny poziom. Badając zachowanie algorytmu dla różnych długości chromosomów, testowano wydajność równoległych operatorów genetycznych. Rys. 8. Wpływ rozmiaru chromosomu na przyśpieszenie Aplikacje poddano testom badającym wpływ rozmiaru populacji na przyśpieszenie w porównaniu z wersją sekwencyjną wykonywalną na CPU. Pierwszy test przeprowadzono dla 512 osobników, następnie zwiększano w każdym następnym teście dwukrotnie rozmiar populacji, aż do wielkości 131072 osobników. Zwiększanie parametru rozmiaru populacji wprowadza większy potencjał genetyczny, ale ma też swoje minusy, prowadzi do wydłużenia czasu obliczeń, w wersji działającej na CPU czas działania zwiększa się proporcjonalnie do wielkości populacji. W przypadku wykorzystania karty graficznej zwiększenie ilości populacji także wpływa na zwiększenie czasu działania programu, jednak w testowanej aplikacji nie zawsze są one proporcjonalne do wielkości populacji. Wyniki zostały przedstawione na rysunku 9. 7 Rys. 9. Wpływ rozmiaru populacji na przyśpieszenie W wersji wykorzystującej CUDA jedno pokolenie jest jednorazowo wywołaną funkcją jądra. Wywołanie funkcji jądra przez komputer i przekazanie jej parametrów jest związane z pewnymi nakładami czasowymi. W przypadku mniejszych populacji ma to większe znaczenie, ponieważ nakład czasowy jest czynnikiem determinującym czas działania, zwiększając rozmiary populacji zwiększamy ilość obliczeń dla funkcji jądra a tym samym maleje znaczenie kosztów jej wywołania. 5 Podsumowanie W artykule opisano podstawowe założenia architektury CUDA. Na przykładzie algorytmów genetycznych wykazano istotną przewagę układów GPU nad klasycznym CPU w przypadku przeprowadzania obliczeń o znacznym stopniu równoległości. Otrzymane wyniki świadczą, że architektura CUDA świetnie sprawdza się w dziedzinie algorytmów genetycznych. Znacznie większą wydajność w stosunku do ceny oraz ogólnodostępność tej technologii mogą zacznie przyczynić się do rozwoju algorytmów ewolucyjnych. Bibliografia [1] Z. Michalewicz, Algorytmy genetyczne + struktury danych = programowanie Ewolucyjne, wydanie III, Warszawa: WNT, 2003. [2] D.E. Goldberg, Algorytmy genetyczne i ich zastosowania, Warszawa: WNT, 1998. [3] J. Arabas: Wykłady z algorytmów ewolucyjnych. Warszawa: WNT, 2001. [4] H. Kwaśnicka, Obliczenia ewolucyjne w sztucznej inteligencji, 1999 [5] D. L. Applegate, R. E. Bixby, V. Chvátal, W. J. Cook, The Traveling Salesman Problem: A Computational Study, 2006. [6] NVIDIA Corporation, NVidia CUDA Programming Guide 2.3, 2009. [7] NVIDIA Corporation, GeForce 8800 GPU Architecture Overview, 2006 . [8] NVIDIA Corporation, Advanced CUDA Training, 2008 [9] J. H. Holland, Adaptation in Natural and Artificial Systems, University of Michigan Press, 1975. [10] T. H. Cormen, C. E. Leiserson, R. L. Riverst, Wprowadzenie do algorytmów Warszawa WNT, 2002. 8