CUDA und Remote-GPU

CUDA ist gut für alle, solange eine Grafikkarte von Nvidia zur Hand ist. Aber was tun, wenn sich auf Ihrem Lieblingslaptop keine Nvidia-Grafikkarte befindet? Oder müssen Sie die Entwicklung in einer virtuellen Maschine durchführen?


Ich werde versuchen, in diesem Artikel eine Lösung wie das rCUDA-Framework (Remote CUDA) in Betracht zu ziehen, die bei einer Nvidia-Grafikkarte hilfreich ist, jedoch nicht auf dem Computer installiert ist, auf dem CUDA-Anwendungen gestartet werden sollen. Für diejenigen, die interessiert sind, willkommen bei Katze.


TLDR

rCUDA (Remote CUDA) - ein Framework, das die CUDA-API implementiert und es Ihnen ermöglicht, eine Remote-Grafikkarte zu verwenden. Es ist eine funktionierende Beta-Version, die nur unter Linux verfügbar ist. Das Hauptziel von rCUDA ist die vollständige Kompatibilität mit der CUDA-API. Sie müssen Ihren Code in keiner Weise ändern, sondern nur spezielle Umgebungsvariablen festlegen.


Was ist rCUDA?


rCUDA (Remote CUDA) ist ein Framework, das die CUDA-API implementiert und es Ihnen ermöglicht, eine auf dem Remotecomputer befindliche Grafikkarte für CUDA-Computing zu verwenden, ohne Änderungen an Ihrem Code vorzunehmen. Entwickelt an der Polytechnischen Universität von Valencia ( rcuda-Team ).


Einschränkungen


Derzeit werden nur GNU / Linux-Systeme unterstützt. Entwickler versprechen jedoch Windows-Unterstützung für die Zukunft. Die aktuelle Version von rCUDA, 18.03beta, ist mit CUDA 5-8 kompatibel, dh CUDA 9 wird nicht unterstützt. Die Entwickler erklärten die vollständige Kompatibilität mit der CUDA-API mit Ausnahme von Grafiken.


Mögliche Anwendungsfälle


  1. Das Ausführen von CUDA-Anwendungen in einer virtuellen Maschine beim Weiterleiten einer Grafikkarte ist unpraktisch oder unmöglich, z. B. wenn die Grafikkarte von einem Host belegt ist oder wenn mehr als eine virtuelle Maschine vorhanden ist.
  2. Laptop ohne diskrete Grafikkarte.
  3. Der Wunsch, mehrere Grafikkarten zu verwenden (Clustering). Theoretisch können Sie alle verfügbaren Grafikkarten im Team verwenden, auch gemeinsam.

Kurze Anleitung


Testkonfiguration


Die Tests wurden mit der folgenden Konfiguration durchgeführt:


Server:
Ubuntu 16.04, GeForce GTX 660


Kunde:
Eine virtuelle Maschine mit Ubuntu 16.04 auf einem Laptop ohne diskrete Grafikkarte.


RCUDA bekommen


Die schwierigste Etappe. Leider besteht die einzige Möglichkeit, eine Kopie dieses Frameworks zu erhalten, derzeit darin, das entsprechende Anfrageformular auf der offiziellen Website auszufüllen. Die Entwickler versprechen jedoch, innerhalb von 1-2 Tagen zu antworten. In meinem Fall haben sie mir am selben Tag eine Verteilung geschickt.


Installieren Sie CUDA


Zuerst müssen Sie das CUDA Toolkit auf dem Server und dem Client installieren (auch wenn der Client keine NVIDIA-Grafikkarte besitzt). Dazu können Sie es von der offiziellen Website herunterladen oder das Repository verwenden. Die Hauptsache ist, eine Version zu verwenden, die nicht höher als 8 ist. In diesem Beispiel wird das .run-Installationsprogramm von der offiziellen Site verwendet .


chmod +x cuda_8.0.61_375.26_linux.run ./cuda_8.0.61_375.26_linux.run 

Wichtig! Auf dem Client sollten Sie die Installation des NVIDIA-Treibers ablehnen. Standardmäßig ist das CUDA Toolkit unter / usr / local / cuda / verfügbar. Installieren Sie CUDA Samples, Sie benötigen sie.


Installieren Sie rCUDA


Wir werden das von den Entwicklern erhaltene Archiv in unser Home-Verzeichnis auf dem Server und auf dem Client entpacken.


 tar -xvf rCUDA*.tgz -C ~/ mv ~/rCUDA* ~/rCUDA 

Sie müssen diese Aktionen sowohl auf dem Server als auch auf dem Client ausführen.


Starten des rCUDA-Daemons auf dem Server


 export PATH=$PATH/usr/local/cuda/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/home/<XXX>/rCUDA/lib/cudnn cd ~/rCUDA/bin ./rCUDAd 

Ersetzen Sie <XXX> durch Ihren Benutzernamen. Verwenden Sie ./rCUDAd -iv, wenn Sie eine ausführliche Ausgabe sehen möchten.


Client-Setup


Öffnen wir das Terminal auf dem Client, in dem wir in Zukunft den CUDA-Code ausführen werden. Auf der Clientseite müssen wir die Standard-CUDA-Bibliotheken durch rCUDA-Bibliotheken "ersetzen", für die wir der Umgebungsvariablen LD_LIBRARY_PATH die entsprechenden Pfade hinzufügen. Wir müssen auch die Anzahl der Server und ihre Adressen angeben (in meinem Beispiel ist es einer).


 export PATH=$PATH/usr/local/cuda/bin export LD_LIBRARY_PATH=/home/<XXX>/rCUDA/lib/:$LD_LIBRARY_PATH export RCUDA_DEVICE_COUNT=1 #    (),     export RCUDA_DEVICE_0=<IP  >:0 #     

Montage und Start


Versuchen wir, einige Beispiele zu erstellen und auszuführen.


Beispiel 1


Beginnen wir mit einem einfachen deviceQuery-Beispiel, in dem einfach die CUDA-Einstellungen für ein kompatibles Gerät angezeigt werden, in unserem Fall für die Remote-GTX660.


 cd <YYY>/NVIDIA_CUDA-8.0_Samples/1_Utilities/deviceQuery make EXTRA_NVCCFLAGS=--cudart=shared 

Wichtig! Ohne EXTRA_NVCCFLAGS = - cudart = shared wird das Wunder nicht funktionieren
Ersetzen Sie <JJJ> durch den Pfad, den Sie bei der Installation von CUDA für CUDA-Beispiele angegeben haben.


Führen Sie das zusammengestellte Beispiel aus:


 ./deviceQuery 

Wenn Sie alles richtig gemacht haben, ist das Ergebnis ungefähr so:


Ergebnis
 ./deviceQuery Starting... CUDA Device Query (Runtime API) version (CUDART static linking) Detected 1 CUDA Capable device(s) Device 0: "GeForce GTX 660" CUDA Driver Version / Runtime Version 9.0 / 8.0 CUDA Capability Major/Minor version number: 3.0 Total amount of global memory: 1994 MBytes (2090991616 bytes) ( 5) Multiprocessors, (192) CUDA Cores/MP: 960 CUDA Cores GPU Max Clock rate: 1072 MHz (1.07 GHz) Memory Clock rate: 3004 Mhz Memory Bus Width: 192-bit L2 Cache Size: 393216 bytes Maximum Texture Dimension Size (x,y,z) 1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096) Maximum Layered 1D Texture Size, (num) layers 1D=(16384), 2048 layers Maximum Layered 2D Texture Size, (num) layers 2D=(16384, 16384), 2048 layers Total amount of constant memory: 65536 bytes Total amount of shared memory per block: 49152 bytes Total number of registers available per block: 65536 Warp size: 32 Maximum number of threads per multiprocessor: 2048 Maximum number of threads per block: 1024 Max dimension size of a thread block (x,y,z): (1024, 1024, 64) Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535) Maximum memory pitch: 2147483647 bytes Texture alignment: 512 bytes Concurrent copy and kernel execution: Yes with 1 copy engine(s) Run time limit on kernels: Yes Integrated GPU sharing Host Memory: No Support host page-locked memory mapping: Yes Alignment requirement for Surfaces: Yes Device has ECC support: Disabled Device supports Unified Addressing (UVA): Yes Device PCI Domain ID / Bus ID / location ID: 0 / 1 / 0 Compute Mode: < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) > deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.0, CUDA Runtime Version = 8.0, NumDevs = 1, Device0 = GeForce GTX 660 Result = PASS 

Das Wichtigste, was wir sehen sollten:


Device0 = GeForce GTX 660
Ergebnis = PASS

Großartig! Wir haben es geschafft, die CUDA-Anwendung auf einem Computer ohne diskrete Grafikkarte zu erstellen und auszuführen. Zu diesem Zweck wurde eine auf einem Remote-Server installierte Grafikkarte verwendet.


Wichtig! Wenn die Anwendungsausgabe mit Zeilen des Formulars beginnt:


 mlock error: Cannot allocate memory rCUDA warning: 1007.461 mlock error: Cannot allocate memory 

Dies bedeutet, dass der Datei "/etc/security/limits.conf" auf dem Server und auf dem Client die folgenden Zeilen hinzugefügt werden müssen:


 * hard memlock unlimited * soft memlock unlimited 

Somit erlauben Sie allen Benutzern (*) unbegrenzten (unbegrenzten) Blockierungsspeicher (memlock). Es wäre sogar noch besser, * durch den gewünschten Benutzer zu ersetzen und statt unbegrenzt weniger Fettrechte zu wählen.


Beispiel 2


Versuchen wir jetzt etwas Interessanteres. Wir werden die Implementierung des Skalarprodukts von Vektoren unter Verwendung von gemeinsamem Speicher und Synchronisation testen ("CUDA-Technologie in Beispielen" Sanders J. Kendrot E. 5.3.1).


In diesem Beispiel berechnen wir das Skalarprodukt zweier Vektoren der Dimension 33 * 1024 und vergleichen die Antwort mit dem auf der CPU erhaltenen Ergebnis.


dotProd.cu
 #include <stdio.h> #define imin(a,b) (a<b?a:b) const int N = 33 * 1024; const int threadsPerBlock = 256; const int blocksPerGrid = imin(32, (N+threadsPerBlock-1) / threadsPerBlock); __global__ void dot(float* a, float* b, float* c) { __shared__ float cache[threadsPerBlock]; int tid = threadIdx.x + blockIdx.x * blockDim.x; int cacheIndex = threadIdx.x; float temp = 0; while (tid < N){ temp += a[tid] * b[tid]; tid += blockDim.x * gridDim.x; } // set the cache values cache[cacheIndex] = temp; // synchronize threads in this block __syncthreads(); // for reductions, threadsPerBlock must be a power of 2 // because of the following code int i = blockDim.x/2; while (i != 0){ if (cacheIndex < i) cache[cacheIndex] += cache[cacheIndex + i]; __syncthreads(); i /= 2; } if (cacheIndex == 0) c[blockIdx.x] = cache[0]; } int main (void) { float *a, *b, c, *partial_c; float *dev_a, *dev_b, *dev_partial_c; // allocate memory on the cpu side a = (float*)malloc(N*sizeof(float)); b = (float*)malloc(N*sizeof(float)); partial_c = (float*)malloc(blocksPerGrid*sizeof(float)); // allocate the memory on the gpu cudaMalloc((void**)&dev_a, N*sizeof(float)); cudaMalloc((void**)&dev_b, N*sizeof(float)); cudaMalloc((void**)&dev_partial_c, blocksPerGrid*sizeof(float)); // fill in the host memory with data for(int i=0; i<N; i++) { a[i] = i; b[i] = i*2; } // copy the arrays 'a' and 'b' to the gpu cudaMemcpy(dev_a, a, N*sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, N*sizeof(float), cudaMemcpyHostToDevice); dot<<<blocksPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_partial_c); // copy the array 'c' back from the gpu to the cpu cudaMemcpy(partial_c,dev_partial_c, blocksPerGrid*sizeof(float), cudaMemcpyDeviceToHost); // finish up on the cpu side c = 0; for(int i=0; i<blocksPerGrid; i++) { c += partial_c[i]; } #define sum_squares(x) (x*(x+1)*(2*x+1)/6) printf("GPU - %.6g \nCPU - %.6g\n", c, 2*sum_squares((float)(N-1))); // free memory on the gpu side cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_partial_c); // free memory on the cpu side free(a); free(b); free(partial_c); } 

Erstellen und ausführen:


 /usr/local/cuda/bin/nvcc --cudart=shared dotProd.cu -o dotProd ./dotProd 

Dieses Ergebnis sagt uns, dass bei uns alles in Ordnung ist:


GPU - 2.57236e + 13
CPU - 2,57236e + 13

Beispiel 3


Führen Sie einen weiteren Standard-CUDA-MatrixMulCUBLAS-Test durch (Matrixmultiplikation).


 cd < YYY>/NVIDIA_CUDA-8.0_Samples/0_Simple/matrixMulCUBLAS make EXTRA_NVCCFLAGS=--cudart=shared ./matrixMulCUBLAS 

Ergebnis

[Matrix Multiply CUBLAS] - Starten ...
GPU-Gerät 0: "GeForce GTX 660" mit Rechenfähigkeit 3.0


MatrixA (640.480), MatrixB (480.320), MatrixC (640.320)
Berechnungsergebnis mit CUBLAS ... fertig.
Leistung = 436,24 GFlop / s, Zeit = 0,451 ms, Größe = 196608000 Ops
Berechnungsergebnis mit Host-CPU ... fertig.
Vergleich der CUBLAS-Matrix Multiplizieren mit den CPU-Ergebnissen: PASS


HINWEIS: Die CUDA-Proben sind nicht für Leistungsmessungen gedacht. Die Ergebnisse können variieren, wenn GPU Boost aktiviert ist.


Interessant für uns:


Leistung = 436,24 GFlop / s,
Vergleich der CUBLAS-Matrix Multiplizieren mit den CPU-Ergebnissen: PASS

Sicherheit


In der Dokumentation zu rCUDA wurde keine Autorisierungsmethode erwähnt. Ich denke, im Moment ist es am einfachsten, den Zugriff auf den gewünschten Port (8308) nur von einer bestimmten Adresse aus zu öffnen.


Mit iptables sieht es folgendermaßen aus:


 iptables -A INPUT -m state --state NEW -p tcp -s < > --dport 8308 -j ACCEPT 

Im Übrigen überlasse ich das Sicherheitsproblem den Rahmen dieses Beitrags.


Quellen und Links

[1] http://www.rcuda.net/pub/rCUDA_guide.pdf
[2] http://www.rcuda.net/pub/rCUDA_QSG.pdf
[3] C. Reaño, F. Silla, G. Shainer und S. Schultz, „Lokale und entfernte GPUs verhalten sich ähnlich wie EDR 100G InfiniBand“, im Rahmen der Internationalen Middleware-Konferenz, Vancouver, BC, Kanada, Dezember 2015.
[4] C. Reaño und F. Silla, „Ein Leistungsvergleich von CUDA Remote GPU Virtualization Frameworks“, im Rahmen der Internationalen Konferenz für Cluster Computing, Chicago, IL, USA, September 2015.

Source: https://habr.com/ru/post/de416127/


All Articles