GPU Computing - Warum, wann und wie. Plus einige Tests

Jeder weiß seit langem, dass man auf Grafikkarten nicht nur Spielzeug spielen, sondern auch Dinge ausführen kann, die nicht mit Spielen zusammenhängen, z. B. ein neuronales Netzwerk trainieren, sich an Kryptowährung erinnern oder wissenschaftliche Berechnungen durchführen. Wie es passiert ist , man kann es lesen hier , aber ich wollte das Thema berühren , warum die GPU auf die interessant sein können durchschnittliche Programmierer (nicht zu GameDev bezogen) , wie zu nähern Entwicklung auf der GPU , ohne viel Zeit damit zu verbringen, entscheiden , ob Schauen Sie in diese Richtung und "finden Sie an Ihren Fingern heraus", welchen Gewinn Sie erzielen können. 



Der Artikel wurde basierend auf meiner Präsentation in HighLoad ++ geschrieben. Es werden hauptsächlich die von NVIDIA angebotenen Technologien erörtert. Ich habe keinen Zweck, für Produkte zu werben, ich gebe sie nur als Beispiel, und wahrscheinlich findet sich etwas Ähnliches bei konkurrierenden Herstellern.

Warum auf die GPU zählen?


Zwei Prozessoren können nach unterschiedlichen Kriterien verglichen werden. Die wahrscheinlich beliebtesten sind die Häufigkeit und Anzahl der Kerne, die Größe der Caches usw. Letztendlich sind wir jedoch daran interessiert, wie viele Operationen ein Prozessor pro Zeiteinheit ausführen kann, welche Art von Operation dies ist, aber eine separate Frage Eine übliche Metrik ist die Anzahl der Gleitkommaoperationen pro Sekunde - Flops. Und wenn wir warm mit weich und in unserem Fall GPU mit CPU vergleichen möchten, ist diese Metrik nützlich.

Die folgende Grafik zeigt das Wachstum dieser Flops im Laufe der Zeit für Prozessoren und Grafikkarten.


(Daten werden aus offenen Quellen gesammelt, es gibt keine Daten für 2019-20 Jahre, weil dort nicht alles so schön ist, aber die GPUs trotzdem gewinnen.)

Nun, es ist verlockend, nicht wahr ? Wir verlagern alle Berechnungen von der CPU auf die GPU und erzielen die achtfache beste Leistung!

Aber natürlich ist nicht alles so einfach. Sie können nicht einfach alles auf die GPU übertragen. Wir werden weiter darüber sprechen.

GPU-Architektur und ihr Vergleich mit der CPU


Ich bringe vielen ein vertrautes Bild mit der Architektur der CPU und den Grundelementen:


CPU Core

Was ist das Besondere? Ein Kern und eine Reihe von Hilfsblöcken.

Schauen wir uns nun die GPU-Architektur an:


GPU-Kern

Eine Grafikkarte verfügt über viele Prozessorkerne, normalerweise mehrere Tausend, aber sie sind zu Blöcken zusammengefasst. Bei NVIDIA-Grafikkarten sind es normalerweise jeweils 32, und sie haben gemeinsame Elemente, einschließlich und Register. Die Architektur des GPU-Kerns und der logischen Elemente ist viel einfacher als auf der CPU, dh es gibt keine Prefetcher, Brunch-Prädiktoren und vieles mehr.

Nun, dies sind die Hauptunterschiede in der Architektur der CPU und der GPU, und tatsächlich legen sie Einschränkungen fest oder eröffnen umgekehrt die Möglichkeiten für das, was wir effektiv auf der GPU lesen können.

Ich habe einen weiteren wichtigen Punkt nicht erwähnt. Normalerweise „stöbern“ die Grafikkarte und der Prozessor nicht untereinander und schreiben Daten auf die Grafikkarte und lesen das Ergebnis zurück. Dies sind separate Vorgänge, die sich als „Engpass“ in Ihrem System herausstellen können, ein Diagramm der Pumpzeit im Verhältnis zur Größe Daten werden später in dem Artikel angegeben.

GPU-Einschränkungen und -Funktionen


Welche Einschränkungen bringt diese Architektur ausführbaren Algorithmen auf:

  • Wenn wir auf einer GPU rechnen, können wir nicht nur einen Kern auswählen, sondern es wird ein ganzer Block von Kernen zugewiesen (32 für NVIDIA).
  • Alle Kerne führen die gleichen Anweisungen aus, aber mit unterschiedlichen Daten (wir werden später darauf eingehen) werden solche Berechnungen als Single-Instruction-Multiple-Data oder SIMD bezeichnet (obwohl NVIDIA seine Verfeinerung einführt). 
  • Aufgrund des relativ einfachen Satzes von Logikblöcken und allgemeinen Registern mag die GPU die Verzweigung und die komplexe Logik in den Algorithmen wirklich nicht.

Welche Möglichkeiten eröffnet es:

  • Eigentlich ist die Beschleunigung der gleichen SIMD-Berechnungen. Das einfachste Beispiel ist das elementweise Hinzufügen von Matrizen, und wir analysieren es.

Reduktion klassischer Algorithmen auf SIMD-Darstellung


Transformation


Wir haben zwei Arrays, A und B, und wir möchten jedem Element von Array A ein Element aus Array B hinzufügen. Nachfolgend finden Sie ein Beispiel in C, obwohl ich hoffe, dass es für diejenigen klar ist, die diese Sprache nicht sprechen:

void func(float *A, float *B, size)
{ 
   for (int i = 0; i < size; i++) 
   { 
       A[i] += B[i]
   } 
}

Klassischer Loopback von Elementen in einer Schleife und linearer Laufzeit.

Nun wollen wir sehen, wie ein solcher Code für die GPU aussehen wird:

void func(float *A, float *B, size) 
{ 
   int i = threadIdx.x; 
   if (i < size) 
      A[i] += B[i] 
}

Und hier ist es schon interessant, dass die Variable threadIdx aufgetaucht ist, die wir anscheinend nirgendwo deklariert haben. Ja, sein System bietet uns. Stellen Sie sich vor, dass das Array im vorherigen Beispiel aus drei Elementen besteht und Sie es in drei parallelen Threads ausführen möchten. Dazu müssten Sie einen weiteren Parameter hinzufügen - den Index oder die Stream-Nummer. Dies ist, was die Grafikkarte für uns tut, obwohl sie den Index als statische Variable übergibt und mit mehreren Dimensionen gleichzeitig arbeiten kann - x, y, z.

Eine weitere Nuance: Wenn Sie eine große Anzahl paralleler Streams gleichzeitig starten möchten, müssen die Streams in Blöcke unterteilt werden (ein architektonisches Merkmal von Grafikkarten). Die maximale Blockgröße hängt von der Grafikkarte ab, und der Index des Elements, für das wir Berechnungen durchführen, muss wie folgt ermittelt werden:

int i = blockIdx.x * blockDim.x + threadIdx.x; // blockIdx –  , blockDim –  , threadIdx –    

Als Ergebnis haben wir: viele parallel laufende Threads, die denselben Code ausführen, jedoch unterschiedliche Indizes aufweisen, und dementsprechend Daten, d. H. das gleiche SIMD.

Dies ist das einfachste Beispiel. Wenn Sie jedoch mit der GPU arbeiten möchten, müssen Sie Ihre Aufgabe in dieselbe Form bringen. Leider ist dies nicht immer möglich und kann in einigen Fällen Gegenstand einer Dissertation werden, dennoch können klassische Algorithmen in diese Form gebracht werden.

Anhäufung


Lassen Sie uns nun sehen, wie die in die SIMD-Darstellung übertragene Aggregation aussehen wird:
 

Wir haben ein Array von n Elementen. In der ersten Stufe starten wir n / 2 Threads und jeder Thread fügt zwei Elemente hinzu, d. H. In einer Iteration addieren wir die Hälfte der Elemente im Array. Und dann wiederholen wir in der Schleife dasselbe für das neu erstellte Array, bis wir die letzten beiden Elemente aggregieren. Wie Sie sehen können, können wir umso weniger parallele Threads starten, je kleiner das Array ist, d. H. Auf einer GPU ist es sinnvoll, Arrays mit einer ausreichend großen Größe zusammenzufassen. Ein solcher Algorithmus kann verwendet werden, um die Summe der Elemente zu berechnen (vergessen Sie übrigens nicht den möglichen Überlauf des Datentyps, mit dem Sie arbeiten) und nach einem Maximum, Minimum oder nur einer Suche zu suchen.

Sortierung


Das Sortieren sieht aber schon viel komplizierter aus.

Die zwei beliebtesten Sortieralgorithmen auf der GPU sind:

  • Bitonische Sorte
  • Radix-Sortierung

Radix-Sort wird jedoch immer noch häufiger verwendet, und in einigen Bibliotheken ist eine produktionsbereite Implementierung zu finden. Ich werde nicht im Detail analysieren, wie diese Algorithmen funktionieren. Interessierte finden eine Beschreibung der Radix-Sortierung unter https://www.codeproject.com/Articles/543451/Parallel-Radix-Sort-on-the-GPU-using-Cplusplus- AMP und https://stackoverflow.com/a/26229897

Die Idee ist jedoch, dass selbst ein nichtlinearer Algorithmus wie das Sortieren auf eine SIMD-Ansicht reduziert werden kann.

Und bevor wir uns die reellen Zahlen ansehen, die von der GPU erhalten werden können, wollen wir herausfinden, wie man für dieses Wunder der Technologie programmiert.

Wo soll man anfangen


Die gängigsten zwei Technologien, die für die Programmierung unter der GPU verwendet werden können:

  • Opencl
  • Cuda

OpenCL ist ein Standard, der von den meisten Grafikkartenherstellern unterstützt wird, einschließlich Auf Mobilgeräten kann auch in OpenCL geschriebener Code auf der CPU ausgeführt werden.

Sie können OpenCL aus C / C ++ verwenden, es gibt Ordner für andere Sprachen.

Für OpenCL hat mir das Buch OpenCL in Action am besten gefallen . Es werden auch verschiedene Algorithmen auf der GPU beschrieben, einschließlich Bitonische Sortierung und Radix-Sortierung.

CUDA ist die proprietäre Technologie und das SDK von NVIDIA. Sie können in C / C ++ schreiben oder Bindungen zu anderen Sprachen verwenden.

Der Vergleich von OpenCL und CUDA ist etwas falsch, weil Einer ist der Standard, der andere ist das gesamte SDK. Trotzdem wählen viele Leute CUDA für die Entwicklung von Grafikkarten, obwohl die Technologie proprietär ist, obwohl sie kostenlos ist und nur auf NVIDIA-Karten funktioniert. Dafür gibt es mehrere Gründe:

  • API
  • , GPU, (host)
  • , ..

Zu den Besonderheiten gehört die Tatsache, dass CUDA über einen eigenen Compiler verfügt, der auch Standard-C / C ++ - Code kompilieren kann.

Das umfassendste CUDA-Buch, auf das ich gestoßen bin, war Professional CUDA C Programming , obwohl es bereits etwas veraltet ist, behandelt es dennoch viele technische Nuancen der Programmierung für NVIDIA-Karten.

Aber was ist, wenn ich nicht ein paar Monate damit verbringen möchte, diese Bücher zu lesen, mein eigenes Programm für eine Grafikkarte zu schreiben, zu testen und zu debuggen und dann herauszufinden, dass dies nichts für mich ist? 

Wie gesagt, es gibt eine große Anzahl von Bibliotheken , die die Komplexität der Entwicklung unter der GPU verstecken: XGBoost, cuBLAS, TensorFlow, PyTorch und andere, werden wir die betrachten Schub BibliothekDa es weniger spezialisiert ist als die anderen oben genannten Bibliotheken, implementiert es gleichzeitig grundlegende Algorithmen, z. B. Sortieren, Suchen, Aggregieren, und kann mit hoher Wahrscheinlichkeit auf Ihre Aufgaben angewendet werden.

Thrust ist eine C ++ - Bibliothek, die darauf abzielt, Standard-STL-Algorithmen durch GPU-basierte Algorithmen zu "ersetzen". Das Sortieren eines Arrays von Zahlen mithilfe dieser Bibliothek auf einer Grafikkarte sieht beispielsweise folgendermaßen aus:

thrust::host_vector<DataType> h_vec(size); //    
std::generate(h_vec.begin(), h_vec.end(), rand); //   
thrust::device_vector<DataType> d_vec = h_vec; //         
thrust::sort(d_vec.begin(), d_vec.end()); //    
thrust::copy(d_vec.begin(), d_vec.end(), h_vec.begin()); //   ,     

(Vergessen Sie nicht, dass das Beispiel von einem Compiler von NVIDIA kompiliert werden muss.)

Wie Sie sehen können, ist push :: sort einem ähnlichen Algorithmus von STL sehr ähnlich. Diese Bibliothek verbirgt viele Schwierigkeiten, insbesondere die Entwicklung eines Unterprogramms (genauer gesagt des Kernels), das auf der Grafikkarte ausgeführt wird, aber gleichzeitig die Flexibilität verliert. Wenn wir beispielsweise mehrere Gigabyte Daten sortieren möchten, ist es logisch, ein Datenelement an die Karte zu senden, um mit dem Sortieren zu beginnen, und während der Sortierung weitere Daten an die Karte zu senden. Dieser Ansatz wird als Latenzverstecken bezeichnet und ermöglicht eine effizientere Nutzung der Serverzuordnungsressourcen. Wenn wir jedoch Bibliotheken auf hoher Ebene verwenden, bleiben diese Möglichkeiten leider verborgen. Für das Prototyping und die Messung der Leistung sind sie jedoch gleich, insbesondere mit Schub können Sie messen, welchen Overhead die Datenübertragung bietet.

Ich habe einen kleinen Benchmark geschrieben Wenn Sie diese Bibliothek verwenden, in der mehrere gängige Algorithmen mit unterschiedlichen Datenmengen auf der GPU ausgeführt werden, sehen wir uns die Ergebnisse an.

Ergebnisse des GPU-Algorithmus


Um die GPU zu testen, habe ich eine Instanz in AWS mit einer Tesla k80-Grafikkarte erstellt. Dies ist nicht die bisher leistungsstärkste Serverkarte (die leistungsstärkste Tesla v100), aber die günstigste und hat Folgendes an Bord:

  • 4992 CUDA-Kernel
  • 24 GB Speicher
  • 480 Gbit / s - Speicherbandbreite 

Und für Tests auf der CPU habe ich eine Instanz mit einer Intel Xeon-Prozessor-CPU E5-2686 v4 bei 2,30 GHz genommen

Transformation



Ausführungszeit der Transformation auf der GPU und der CPU in ms

Wie Sie sehen können, ist die übliche Transformation der Array-Elemente sowohl auf der GPU als auch auf der CPU zeitlich ungefähr gleich. Und warum? Da der Overhead für das Senden von Daten an die Karte und zurück den gesamten Leistungsschub verschlingt (wir werden den Overhead separat behandeln) und es relativ wenige Berechnungen auf der Karte gibt. Vergessen Sie auch nicht, dass Prozessoren auch SIMD-Anweisungen unterstützen und Compiler diese in einfachen Fällen effektiv verwenden können. 

Lassen Sie uns nun sehen, wie effizient die Aggregation auf der GPU erfolgt.

Anhäufung



Ausführungszeit der Aggregation auf GPU und CPU in ms

Im Aggregationsbeispiel sehen wir bereits eine signifikante Leistungssteigerung mit zunehmendem Datenvolumen. Es lohnt sich auch, darauf zu achten, dass wir eine große Datenmenge in den Speicher der Karte pumpen und nur ein aggregierter Wert zurückgenommen wird, d. H. Der Overhead für die Übertragung von Daten von der Karte in den RAM ist minimal.

Kommen wir zum interessantesten Beispiel - dem Sortieren.

Sortierung



Sortierzeit für GPU und CPU in ms

Trotz der Tatsache, dass wir das gesamte Datenarray an die Grafikkarte senden und umgekehrt, ist das Sortieren von 800 MB Daten an die GPU ungefähr 25-mal schneller als auf dem Prozessor.

Datenübertragungsaufwand


Wie aus dem Transformationsbeispiel hervorgeht, ist es nicht immer offensichtlich, ob die GPU auch bei parallelen Aufgaben effektiv ist. Der Grund dafür ist ein Overhead für die Übertragung von Daten aus dem RAM des Computers in den Speicher der Grafikkarte (in Spielekonsolen wird der Speicher übrigens von der CPU und der GPU gemeinsam genutzt, und es besteht keine Notwendigkeit, Daten zu übertragen). Eine der Eigenschaften einer Grafikkarte ist die Speicherbandbreite oder Speicherbandbreite, die die theoretische Bandbreite der Karte bestimmt. Für Tesla k80 sind es 480 GB / s, für Tesla v100 sind es bereits 900 GB / s. Die PCI Express-Version und die Implementierung der Datenübertragung auf die Karte wirken sich auch auf den Durchsatz aus. Dies kann beispielsweise in mehreren parallelen Streams erfolgen.

Schauen wir uns die praktischen Ergebnisse an, die für die Tesla k80-Grafikkarte in der Amazon-Cloud erzielt wurden:


Zeit zum Übertragen von Daten auf die GPU, Sortieren und

Zurücksenden von Daten in den



RAM in ms HtoD - Übertragen von Daten auf die GPU -Grafikkarte Ausführung - Sortieren auf der Grafikkarte DtoH - Kopieren von Daten von der Grafikkarte in den RAM

Das erste, was zu beachten ist, ist, dass das Lesen von Daten von der Grafikkarte schneller ist als schreibe sie dort auf.

Zweitens: Wenn Sie mit einer Grafikkarte arbeiten, können Sie eine Latenz von 350 Mikrosekunden erreichen. Dies reicht möglicherweise bereits für einige Anwendungen mit geringer Latenz aus.

Die folgende Tabelle zeigt einen Overhead für weitere Daten:


Zeit zum Übertragen von Daten an die GPU, Sortieren und Zurücksenden von Daten in den RAM in ms

Servernutzung


Die häufigste Frage ist, wie sich eine Spiel-Grafikkarte von einer Server-Grafikkarte unterscheidet. Entsprechend den Merkmalen sind sie sehr ähnlich, aber die Preise unterscheiden sich erheblich.


Die Hauptunterschiede zwischen dem Server (NVIDIA) und der Spielkarte:

  • Herstellergarantie (die Spielkarte ist nicht für die Verwendung auf Servern vorgesehen)
  • Mögliche Virtualisierungsprobleme für eine Consumer-Grafikkarte
  • Verfügbarkeit des Fehlerkorrekturmechanismus auf der Serverkarte
  • Die Anzahl der parallelen Threads (keine CUDA-Kerne) oder die Unterstützung für Hyper-Q, mit der Sie mit der Karte von mehreren Threads auf der CPU aus arbeiten können. Laden Sie beispielsweise Daten von einem Thread auf eine Karte hoch und starten Sie Berechnungen von einem anderen

Dies sind vielleicht die wichtigsten Unterschiede, die ich gefunden habe.

Multithreading


Nachdem wir herausgefunden haben, wie der einfachste Algorithmus auf der Grafikkarte ausgeführt wird und welche Ergebnisse zu erwarten sind, ist die nächste logische Frage, wie sich die Grafikkarte bei der Verarbeitung mehrerer paralleler Anforderungen verhält. Als Antwort habe ich zwei Diagramme des Rechnens auf der GPU und einen Prozessor mit 4 und 32 Kernen:


Die Zeit, die benötigt wird, um mathematische Berechnungen auf der GPU und der CPU mit Matrizen von 1000 x 60 in ms durchzuführen

. Dieses Diagramm führt Berechnungen mit Matrizen von 1000 x 60 Elementen durch. Die Berechnungen werden aus mehreren Programmströmen gestartet. Für jeden CPU-Stream wird ein separater Stream für die GPU erstellt (es wird genau das Hyper-Q verwendet). 

Wie Sie sehen, kommt der Prozessor mit dieser Last sehr gut zurecht, während die Latenz für eine Anforderung pro GPU mit zunehmender Anzahl paralleler Anforderungen erheblich zunimmt.


Die Zeit für die Durchführung mathematischer Berechnungen auf der GPU und der CPU mit Matrizen von 10.000 x 60 in ms.

In der zweiten Grafik sind dieselben Berechnungen, jedoch mit zehnmal längeren Matrizen, und die GPU verhält sich unter einer solchen Last viel besser. Diese Grafiken sind sehr bezeichnend, und wir können daraus schließen: Das Verhalten unter Last hängt von der Art der Last selbst ab. Ein Prozessor kann Matrixberechnungen auch recht effizient durchführen, jedoch bis zu einem gewissen Grad. Für eine Grafikkarte ist es charakteristisch, dass bei einer kleinen Rechenlast die Leistung ungefähr linear abfällt. Mit zunehmender Last und der Anzahl paralleler Threads kommt die Grafikkarte besser zurecht. 

Es ist schwierig zu vermuten, wie sich die GPU in verschiedenen Situationen verhält. Wie Sie jedoch sehen können, kann eine Serverkarte unter bestimmten Bedingungen Anforderungen aus mehreren parallelen Streams recht effizient verarbeiten.

Wir werden einige weitere Fragen besprechen, die Sie möglicherweise haben, wenn Sie sich dennoch für die Verwendung der GPU in Ihren Projekten entscheiden.

Ressourcenlimit


Wie bereits erwähnt, sind die beiden Hauptressourcen einer Grafikkarte Rechenkerne und Speicher.

Zum Beispiel haben wir mehrere Prozesse oder Container, die eine Grafikkarte verwenden, und wir möchten die Grafikkarte zwischen ihnen teilen können. Leider gibt es dafür keine einfache API. NVIDIA bietet vGPU- Technologie an , aber ich habe die Tesla k80-Karte nicht in der Liste der unterstützten Karten gefunden. Soweit ich aus der Beschreibung ersehen kann, konzentriert sich die Technologie mehr auf virtuelle Anzeigen als auf Berechnungen. Vielleicht bietet AMD etwas passenderes an.

Wenn Sie die GPU in Ihren Projekten verwenden möchten, sollten Sie sich daher darauf verlassen, dass die Anwendung ausschließlich die Grafikkarte verwendet, oder Sie steuern programmgesteuert die Größe des zugewiesenen Speichers und die Anzahl der für Berechnungen verwendeten Kerne.

Container und GPU


Wenn Sie das Ressourcenlimit herausgefunden haben, lautet die folgende logische Frage: Was ist, wenn sich mehrere Grafikkarten auf dem Server befinden?

Auch hier können Sie auf Anwendungsebene entscheiden, welche GPU verwendet wird.

Ein weiterer bequemer Weg sind Docker-Container. Sie können normale Container verwenden, aber NVIDIA bietet seine NGC- Container mit optimierten Versionen verschiedener Software, Bibliotheken und Treibern an. Für einen Container können Sie die Anzahl der verwendeten GPUs und deren Sichtbarkeit für den Container begrenzen. Der Overhead bei der Containernutzung beträgt ca. 3%.

Arbeiten Sie in einem Cluster


Eine andere Frage: Was tun, wenn Sie eine Aufgabe auf mehreren GPUs innerhalb desselben Servers oder Clusters ausführen möchten?

Wenn Sie eine Bibliothek gewählt haben, die Schub ähnelt, oder eine Lösung auf niedrigerer Ebene, muss die Aufgabe manuell gelöst werden. Hochrangige Frameworks, beispielsweise für maschinelles Lernen oder neuronale Netze, unterstützen normalerweise die Möglichkeit, mehrere Karten sofort zu verwenden.

Darüber hinaus möchte ich darauf hinweisen, dass NVIDIA beispielsweise eine Schnittstelle für den direkten Datenaustausch zwischen Karten bietet - NVLINK , die erheblich schneller als PCI Express ist. Und es gibt eine Technologie für den direkten Zugriff auf den Speicher der Karte von anderen PCI Express-Geräten - GPUDirect RDMA , inkl. und Netzwerk .

Empfehlungen


Wenn Sie erwägen, die GPU in Ihren Projekten zu verwenden, ist die GPU höchstwahrscheinlich für Sie geeignet, wenn:

  • Ihre Aufgabe kann auf eine SIMD-Ansicht reduziert werden
  • Es ist möglich, die meisten Daten vor den Berechnungen auf die Karte zu laden (Cache)
  • Die Herausforderung besteht in intensivem Computing

Sie sollten auch im Voraus Fragen stellen:

  • Wie viele parallele Abfragen werden sein 
  • Welche Latenz erwarten Sie?
  • Benötigen Sie eine Karte für Ihre Last? Benötigen Sie einen Server mit mehreren Karten oder einen Cluster von GPU-Servern? 

Das ist alles, ich hoffe, dass das Material für Sie nützlich ist und Ihnen hilft, die richtige Entscheidung zu treffen!

Verweise


Benchmark und Ergebnisse auf github - https://github.com/tishden/gpu_benchmark/tree/master/cuda

Zusätzlich zum Thema eine Aufzeichnung des Berichts „GPU-Datenbanken - Architektur, Leistung und Nutzungsaussichten“

NVIDIA NGC Containers Webinar - http : //bit.ly/2UmVIVt oder http://bit.ly/2x4vJKF

All Articles