środa, 15 sierpnia 2012

CUDA na Javie

Poniższy artykuł nie będzie ani o zjawiskach paranormalnych, „interwencji istot nadprzyrodzonych” (patrz: cud na Wikipedii) ani o śnieniu „na jawie” ;) Mianowicie CUDA (ang. Compute Unified Device Architecture) to opracowana przez Nvidię „uniwersalna architektura procesorów wielordzeniowych” występująca głównie na kartach graficznych. Architektura ta ma więc wspomagać rozwiazywanie skomplikowanych problemów numerycznych. Stworzono do tego framework OpenCL, który jest oparty na języku C (a dokładniej na C99). Jak słusznie zauważył Bartek, OpenCL jest osobną, alternatywą dla Cudy i nie będę się nią tutaj zajmował.

Przeglądając angielską wiki na temat CUDA, zauważyłem, że są dostępne 4 biblioteki dla Javy, współpracujące z technologią Nvidi: jCUDA, JCuda, JCublas, JCufft. Jako że moja karta graficzna (GeForce 9600M GT) w laptopie obsługuje CUDA w wersji 1.1 (aktualne dane można sprawdzić na stronie CUDA GPUs), postanowiłem trochę się pobawić tymi klockami i sprawić aby się choć trochę spociła.

Przyglądając się bliżej tym czterem bibliotekom Javowym, okazało się, że tak właściwie są to 2 projekty: jCUDA i JCuda. Pozostałe: JCublas i JCufft są de facto podprojektami JCuda. JCublas zajmuje się operacjami wektorowymi i macierzowymi. Natomiast JCufft zajmuje się szybką transformacją Fouriera. Ten kto kończył studia na Wydziale Elektroniki PWr, lub coś co jest związane z elektroniką, to wie o co chodzi ;) Druciarze rządzą!


Ściągnąłem więc na początek jCUDA wersję na Windowsa 64 bit.

Następnie utworzyłem projekt w IntelliJ Idea, skopiowałem zawartość katalogu examples (ze ściągniętego ZIP’a ) do src, a jcuda.jar, jcuda.dll i jcudafft.dll do lib i dodałem do projektu ze scope’m: "Compile"



Pierwsze uruchomienie klasy EnumDevices (widzę że jest tam jakiś main()) i dostajemy stacktrace’a:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no jcuda in java.library.path
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
 at java.lang.Runtime.loadLibrary0(Runtime.java:845)
 at java.lang.System.loadLibrary(System.java:1084)
 at jcuda.driver.CUDADriver.(CUDADriver.java:909)
 at jcuda.CUDA.init(CUDA.java:62)
 at jcuda.CUDA.(CUDA.java:42)
 at examples.EnumDevices.main(EnumDevices.java:20)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Myślałem początkowo, że to podczas uruchomienia, aplikacja nie widzi dołączanych JAR’ów. Zmiana scope’a na Runtime, powoduje jednak, że IDEA nie widzi biblioteki podczas kompilacji:


Wróciłem wiec do zasięgu compile. Nie chcąc grzebać sobie zbytnio w classpath’ie, ustawiłem właściwość java.library.path w konfiguracji uruchomieniowej wspomnianej klasy na katalog z wymaganymi dll’kami:



Po ponownym uruchomieniu otrzymałem ten oto stos wywołań:

Exception in thread "main" java.lang.UnsatisfiedLinkError: D:\Java_programy\IdeaWorkspace\jCUDAFirstExample\lib\jcuda.dll: Can't load AMD 64-bit .dll on a IA 32-bit platform
 at java.lang.ClassLoader$NativeLibrary.load(Native Method)
 at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1803)
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1728)
 at java.lang.Runtime.loadLibrary0(Runtime.java:823)
 at java.lang.System.loadLibrary(System.java:1028)
 at jcuda.driver.CUDADriver.(CUDADriver.java:909)
 at jcuda.CUDA.init(CUDA.java:62)
 at jcuda.CUDA.(CUDA.java:42)
 at examples.EnumDevices.main(EnumDevices.java:20)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Hmmm, wersje systemu mam 64 bitową, ale nie na procku AMD tylko Intel Core 2 Duo T6400 2.00 GHz.

Dobra ściągamy wersję 32 bitową na Winde. Podmiana plików, przebudowa projektu i Voila:

Total number of devices: 1
Name: GeForce 9600M GT
Version: 1.1
Clock rate: 1250000 MHz
Threads per block: 512

W przykładowym kodzie znajduje się jeszcze klasa LoadModule, uruchamiam więc:

Exception in thread "main" CUDA Driver error: 301
 at jcuda.CUDA.setError(CUDA.java:1874)
 at jcuda.CUDA.loadModule(CUDA.java:375)
 at examples.LoadModule.main(LoadModule.java:36)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

I idę do linii gdzie poleciał wyjątek:

File cubinFile = new File("resources", "test_module.cubin");
cuda.loadModule(cubinFile.getAbsolutePath());

W archiwum z biblioteką jest jeszcze katalog resource, wiec kopiuję go do projektu i dołączam jako zasób:



Od razu nowe skróty klawiaturowe do zapamiętania, dostępne w oknie Project Structure - Modules - dodaj jako: Alt + S Sources, Alt + T Test Sources, Alt + E Excluded.

I otrzymujemy miły komunikat po uruchomieniu:

Test passed

Z ciekawości jeszcze zajrzałem co się znajduje w podpakiecie driver przykładowego kodu. Po uruchomieniu okazuje się, że aplikacje wypisują to samo na ekran. Porównałem kdiff3’em, co się zmienia w kodzie. Jak można przeczytać w release note w pliku readme.txt od wersji 1.1 biblioteki wprowadzono bardziej obiektowy sposób korzystania z biblioteki, aby nam Javowcom było łatwiej pracować :)

Added object oriented support for jCUDA and CUDA interface.

Niestety aby dowiedzieć się coś więcej o tej bibliotece, trzeba się zarejestrować na stronie producenta. Próbowałem z dwóch kont (gmail’owe i studenckie), ale zero odzewu. Później się doszukałem, że rejestracja jest tymczasowo nieaktywna i ze wyślą e-mail’a aktywacyjnego, gdy rejestracja będzie znów dostępna…

Jako, ze nie chcę pracować z biblioteką do której nie ma ogólnodostępnej dokumentacji, a rejestracja nic nie daje, wziąłem  się za drugą dostępną bibliotekę JCuda. Nauczony powyższym doświadczeniem od razu ściągnąłem wersję 4.0 32 bitową, dokumentację (są javadoc’i) i kod źródłowy. Początkowo postanowiłem przejrzeć przykłady: JCuda Code samples.

Zacząłem od pierwszego JCublasSample. Ściągnąłem, skopiowałem do src i to co było w ZIPie z JCudą skopiowałem do lib w moim projekcie. Następnie dodałem je jako zależności z zakresem Compile.


Pierwsze uruchomienie i oczywiście stacktrace:

Creating input data...
Performing Sgemm with Java...
Performing Sgemm with JCublas...
Error while loading native library "JCublas-windows-x86_64" with base name "JCublas"

Operating system name: Windows 7
Architecture         : amd64
Architecture bit size: 64
Stack trace from the attempt to load the library as a resource:
java.lang.NullPointerException: No resource found with name '/lib/JCublas-windows-x86_64.dll'
 at jcuda.LibUtils.loadLibraryResource(LibUtils.java:144)
 at jcuda.LibUtils.loadLibrary(LibUtils.java:80)
 at jcuda.jcublas.JCublas.initialize(JCublas.java:94)
 at jcuda.jcublas.JCublas.(JCublas.java:82)
 at JCublasSample.sgemmJCublas(JCublasSample.java:64)
 at JCublasSample.testSgemm(JCublasSample.java:49)
 at JCublasSample.main(JCublasSample.java:25)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Stack trace from the attempt to load the library as a file:
java.lang.UnsatisfiedLinkError: no JCublas-windows-x86_64 in java.library.path
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
 at java.lang.Runtime.loadLibrary0(Runtime.java:845)
 at java.lang.System.loadLibrary(System.java:1084)
 at jcuda.LibUtils.loadLibrary(LibUtils.java:90)
 at jcuda.jcublas.JCublas.initialize(JCublas.java:94)
 at jcuda.jcublas.JCublas.(JCublas.java:82)
 at JCublasSample.sgemmJCublas(JCublasSample.java:64)
 at JCublasSample.testSgemm(JCublasSample.java:49)
 at JCublasSample.main(JCublasSample.java:25)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Exception in thread "main" java.lang.UnsatisfiedLinkError: Could not load the native library
 at jcuda.LibUtils.loadLibrary(LibUtils.java:122)
 at jcuda.jcublas.JCublas.initialize(JCublas.java:94)
 at jcuda.jcublas.JCublas.(JCublas.java:82)
 at JCublasSample.sgemmJCublas(JCublasSample.java:64)
 at JCublasSample.testSgemm(JCublasSample.java:49)
 at JCublasSample.main(JCublasSample.java:25)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Process finished with exit code 1

Z czego interesujące jest to:

no JCublas-windows-x86_64 in java.library.path

problem rozwiązuję jak w przypadku pierwszej biblioteki, czyli ustawiam właściwość java.library.path dla wirtualnej maszyny Javy w konfiguracji uruchomieniowej przykładu. Uruchamiam i ten sam błąd. Przyglądam się bliżej nazwie brakującego pliku: JCublas-windows-x86_64. Okazuje się, że w wskazanym katalogu mam taki plik ale bez przyrostka _64, czyli pewnie framework, na podstawie wyświetlanej wartości „Architecture: amd64” poszukuje automatycznie bibliotek 64bitowych. No dobra, ściągnąłem, podmieniłem, uruchamiam i znów myślałem, że dostałem to samo.

Jednak gdy się bliżej przyjrzałem, to się okazało ze jest trochę odmienny:

java.lang.UnsatisfiedLinkError: D:\Java_programy\IdeaWorkspace\JCudaExample01\lib\JCublas-windows-x86_64.dll: Can't find dependent libraries

Zajrzałem więc do JCuda Tutorial’a i tamta strona zaprowadziła mnie do JCuda FAQ na jakimś formu:

Type 2: UnsatisfiedLinkError: Can't find dependent libraries

Czyli jednak czegoś nie doinstalowałem związanego bezpośrednio z CUDĄ. Ściągnąłem więc CUDA Toolkit (64 bit) zainstalowałem i uruchomiłem ponownie komputer. Po odpaleniu aplikacji dostałem:

Creating input data...
Performing Sgemm with Java...
Performing Sgemm with JCublas...
testSgemm FAILED

Już jest lepiej, ale chyba coś dalej jest nie tak. Zajrzałem w kod i się okazało, że przykładowa aplikacja korzysta z biblioteki JCublas, a ta wykorzystuje cuBLAS (ang. CUDA Basic Linear Algebra Subroutines), czyli bibliotekę wspomagającą podstawowe obliczenia algebry liniowej. Nie chciało mi się zgłębiać co jest nie tak i uruchomiłem inny przykład.

Klasa JCufftSample wymaga do uruchomienia dodatkowo javovej biblioteki JTransforms, która to liczy m.in. transformatę Fouriera. Uruchomienie tego przykładu wykrywało w moim przypadku architekturę 32 bitową:

Operating system name: Windows 7
Architecture         : x86
Architecture bit size: 32

No i oczywiście sypało wyjątkami. Postanowiłem więc w katalogu lib trzymać zarówno 64 bitowe jaki 32 bitowe dll’ki. Prawdopodobnie dzięki temu udało mi się wywalić wirtualną maszynę:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0ad2de60, pid=4376, tid=6112
#
# JRE version: 6.0_22-b04
# Java VM: Java HotSpot(TM) Client VM (17.1-b03 mixed mode, sharing windows-x86 )

Trochę zniechęcająca jest praca z tymi bibliotekami. Ale tak to zazwyczaj jest, jak się bawimy ze sprzętem, że na początku bardzo ciężko się z nim dogadać, a później to już jakoś lepiej idzie.

Jako że początkowo korzystałem z bibliotek w wersji 0.4.0-beta1 dla CUDA 4.0 postanowiłem zrobić downgrade do JCuda 0.3.2a. dla CUDA 3.2. Taka konfiguracja sprawia mi najmniej problemów. JCufftSample zaczął działać, i najprostszy, podstawowy test z tutorial’a też.
Proponuję wszystkim zaczynać od tego przykładu, gdyż ten test alokuje pewną pamięć w przestrzeni GPU i ją zwalnia – nic więcej.

Teraz to by było na tyle odnośnie cudów. Kolejne porady, co jeszcze należy doinstalować i jak przygotować środowisko do pracy z tym wynalazkiem w kolejnej części.

No i przepraszam za rozjechane listingi kodu pod Chrome'm, ale coś SyntaxHighlighter się pod tą przeglądarką rozjeżdża.

4 komentarze:

  1. OpenCL to z tego co pamiętam alternatywa dla CUDA nVidii. W OpenCLu można (wieloplatformowo?) pisać także dla kart ATi. Polecam czystą CUDĘ dla C/C++ i VisualStudio.

    OdpowiedzUsuń
    Odpowiedzi
    1. Rzeczywiście. Musiałem niedokładnie doczytać / zrozumieć, gdy o tym czytałem. Tekst poprawiony.

      Usuń
  2. Zakład Teorii Obwodów - przy tych panach, Katyń wydaje się być weekendowym wypadem do kolegów z Rosji.

    OdpowiedzUsuń
    Odpowiedzi
    1. Widzę, że inni również mają niemiłe wspomnienia z ekipą ZTO.

      Usuń