
Fortsetzung von Artikel 1 und Artikel 2 .
Im Folgenden werde ich auf die Erfahrungen des Autors bei der Verwendung der GPU für Berechnungen eingehen, auch als Teil der Erstellung eines Bots für die Teilnahme am AI-Minicup. Vielmehr handelt es sich um einen Aufsatz zum Thema GPU.
- Dein Name ist magisch ...
- Weißt du, Joel? Die Magie geht ...
In der Kindheit sprechen wir über das Alter, in dem die Chemie in der Schule noch nicht im Gange ist oder gerade erst beginnt, der Autor war fasziniert von der brennenden Reaktion, so dass seine Eltern ihn nicht störten und das Moskauer Ödland in der Nähe des Hauses gelegentlich von Blitzen verschiedener Kinderaktivitäten beleuchtet wurde, einer hausgemachten Rakete auf Schwarz Schießpulver, auf Zuckernitrat-Karamell usw. Zwei Umstände schränkten die Phantasien der Kinder ein: die Zersetzung von Nitroglycerin in einem Heimlabor mit einer zischenden Decke aus Säuren und die Fahrt zu einem Polizeiraum, um Chemikalien in einem der Verteidigungsunternehmen zu beschaffen, die großzügig im U-Bahn-Gebiet Aviamotornaya verstreut sind.
Und dann erschien eine Physikschule mit Yamaha-MSX-Computern, ein programmierbarer MK-Rechner zu Hause, und es blieb keine Zeit für Chemie. Die Interessen des Kindes haben sich auf Computer verlagert. Und was dem Autor bei der ersten Bekanntschaft mit dem Computer fehlte, war die brennende Reaktion, seine Programme schwelten, es gab nicht dieses Gefühl natürlicher Kraft. Sie konnten den Prozess der Optimierung von Berechnungen in Spielen sehen, aber zu diesem Zeitpunkt wusste der Autor nicht, wie er die Berechnung von sin () durch die Wertetabelle dieser Funktion ersetzen sollte, es gab kein Internet ...
So konnte der Autor ein Gefühl der Freude von Rechenleistung, sauberem Brennen bekommen, ich benutze GPU in Berechnungen.
Auf einem Habr gibt es einige gute Artikel über Berechnungen auf GPU. Es gibt auch viele Beispiele im Internet, daher wurde beschlossen, nur am Samstagmorgen über persönliche Gefühle zu schreiben, und es ist möglich, andere zur Massenparallelität zu drängen.
Beginnen wir mit einfachen Formularen. GPU-Computing unterstützt mehrere Frameworks, die bekanntesten sind jedoch NVIDIA CUDA und OpenCL. Wir werden CUDA nehmen und müssen sofort unsere Programmiersprachen auf C ++ beschränken. Es gibt Bibliotheken für die Verbindung mit CUDA in anderen Programmiersprachen, z. B. ALEA GPU in C #. Dies ist jedoch eher das Thema eines separaten Übersichtsartikels.
Da sie nicht gleichzeitig ein Massenauto mit einem Strahltriebwerk herstellen konnten, obwohl einige seiner Indikatoren höher sind als die eines Verbrennungsmotors, sind parallele Berechnungen bei realen Problemen nicht immer möglich. Die Hauptanwendung für paralleles Rechnen: Sie benötigen eine Aufgabe, die ein Element mit Massencharakter, die Multiplizität, enthält. In unserem Fall, einen Bot zu erstellen, fällt ein neuronales Netzwerk (viele Neuronen, neuronale Verbindungen) unter die Masse und eine Population von Bots (Berechnung der Bewegungsdynamik, Kollisionen für jeden Bot dauert einige Zeit, wenn die Bots zwischen 300 und 1000 liegen, gibt der Zentralprozessor auf und Sie werden nur langsam beobachten Schwelen Ihres Programms, z. B. lange Pausen zwischen den Visualisierungsrahmen).
Die beste Massenoption ist, wenn jedes Element der Berechnungen nicht vom Ergebnis der Berechnungen für ein anderes Element der Liste abhängt. Beispielsweise ist die einfache Aufgabe des Sortierens eines Arrays bereits mit allen Arten von Tricks überwachsen, da die Position der Zahl im Array von anderen Zahlen abhängt und nicht in einem parallelen Zyklus in die Stirn genommen werden kann . Um den Wortlaut zu vereinfachen: Das erste Anzeichen für ein erfolgreiches Massenzeichen ist, dass Sie, wenn Sie die Position eines Elements im Array nicht ändern müssen, frei Berechnungen daran durchführen, die Werte anderer Elemente dafür übernehmen, es jedoch nicht von seiner Stelle verschieben können. So etwas wie ein Märchen: Ändern Sie nicht die Reihenfolge der Elemente, sonst verwandelt sich die GPU in einen Kürbis.
In modernen Programmiersprachen gibt es Konstruktionen, die parallel auf mehreren Kernen eines Zentralprozessors oder logischen Threads ausgeführt werden können, und sie sind weit verbreitet, aber der Autor konzentriert den Leser auf Massenparallelität, wenn die Anzahl der ausgeführten Module Hunderte oder Tausende von Einheiten überschreitet.
Die ersten Elemente paralleler Strukturen erschienen: ein paralleler Zyklus . Für die meisten Aufgaben wird es ausreichen. Im weitesten Sinne ist dies die Quintessenz
paralleles Rechnen.
Ein Beispiel für das Schreiben der Hauptschleife in CUDA (Kernel):
int tid = blockIdx.x * blockDim.x + threadIdx.x; int threadN = gridDim.x * blockDim.x; for (int pos = tid; pos < numElements; pos += threadN) { // pos, , thread pos. : thread , thread pos=1146 thread c pos=956. . . }
In der Dokumentation und den Überprüfungen für CUDA wurde viel über GPU-Blöcke, über Threads, die in diesen Blöcken erzeugt werden, und darüber, wie die Aufgabe auf ihnen parallelisiert werden kann, geschrieben. Wenn Sie jedoch ein Array von Daten haben und diese eindeutig aus Massenelementen bestehen, verwenden Sie die obige Schleifenform, da sie in ihrer Form einer regulären Schleife optisch ähnlich ist, was angenehm, aber leider nicht inhaltlich ist.
Ich denke, der Leser versteht bereits, dass sich die Klasse der Aufgaben in Bezug auf die massenparallele Programmierung schnell verengt. Wenn wir über das Erstellen von Spielen, 3D- Rendering-Engines , neuronalen Netzen, Videobearbeitung und anderen ähnlichen Aufgaben sprechen, ist das Löschen für unabhängige Leseraktionen stark abgenutzt. Für diese Aufgaben gibt es große Programme, kleine Programme, Frameworks, bekannte und unbekannte Bibliotheken. Das heißt, der Bereich bleibt nur vom Thema entfernt, um Ihre eigene kleine Computerrakete zu erstellen, nicht SpaceX und Roscosmos, sondern etwas Heimeliges, aber für die Berechnungen völlig Böses.

Hier ist ein Bild einer vollständig brennenden Rakete abgebildet.
Apropos Aufgaben, die ein paralleler Zyklus in Ihren Händen nicht lösen kann. Und die Entwickler von CUDA in der Person von NVIDIA-Entwicklern haben bereits darüber nachgedacht.
Es gibt an einigen Stellen eine Thrust-Bibliothek, die nützlich ist, bis "keine Optionen" anders ausgeführt werden. Übrigens, fand nicht die vollständige Bewertung auf Habré.
Um zu verstehen, wie es funktioniert, müssen Sie zuerst drei Sätze über die Prinzipien von CUDA sagen. Wenn Sie mehr Wörter benötigen, können Sie den Link lesen .
Die Prinzipien von CUDA:
Berechnungen finden auf der GPU statt, deren Programm der Kernel ist, und Sie müssen sie in C schreiben. Der Kernel kommuniziert wiederum nur mit dem GPU-Speicher, und Sie müssen die Daten aus dem Hauptprogramm in den Videoprozessorspeicher laden und wieder in das Programm hochladen. Ausgefeilte Algorithmen auf CUDA erfordern Flexibilität.
Die Thrust-Bibliothek entfernt also die Routine und übernimmt einige der "komplexen" Aufgaben für CUDA, z. B. das Summieren oder Sortieren von Arrays. Sie müssen keinen separaten Kernel mehr schreiben, Zeiger in den Speicher laden und Daten von diesen Zeigern in den GPU-Speicher kopieren. Das ganze Rätsel wird vor Ihren Augen im Hauptprogramm und mit einer Geschwindigkeit auftreten, die CUDA etwas unterlegen ist. Die Thrust-Bibliothek ist in CUDA geschrieben, daher ist dies in Bezug auf die Leistung ein einzelnes Beerenfeld.
In Thrust müssen Sie ein Array (push :: vector) in seiner Bibliothek erstellen, das mit regulären Arrays (std :: vector) kompatibel ist. Das heißt natürlich, nicht alles ist so einfach, aber die Bedeutung dessen, was der Autor sagte, ähnelt der Wahrheit. Es gibt wirklich zwei Arrays, eines auf der GPU (Gerät), das andere im Hauptprogramm (Host).
Ein Beispiel zeigt die Einfachheit der Syntax (X-, Y-, Z-Arrays):
// initialize X to 0,1,2,3, .... thrust::sequence(X.begin(), X.end()); // compute Y = -X thrust::transform(X.begin(), X.end(), Y.begin(), thrust::negate<int>()); // fill Z with twos thrust::fill(Z.begin(), Z.end(), 2); // compute Y = X mod 2 thrust::transform(X.begin(), X.end(), Z.begin(), Y.begin(), thrust::modulus<int>()); // replace all the ones in Y with tens thrust::replace(Y.begin(), Y.end(), 1, 10);
Sie können sehen, wie harmlos es vor dem Hintergrund der Erstellung des CUDA-Kernels aussieht und wie viele Funktionen in Thrust vorhanden sind. Angefangen von der Arbeit mit Zufallsvariablen, die in CUDA von einer separaten cuRAND-Bibliothek (vorzugsweise von einem separaten Kernel ausgeführt) ausgeführt wird, bis zum Sortieren, Summieren und Schreiben Ihrer Funktionen nach Funktionen in der Nähe der Kernelfunktionen.
Der Autor hat wenig Erfahrung mit CUDA und C ++, zwei Monate. Über dieses Jahr über C #. Dies widerspricht natürlich leicht dem Beginn des Artikels über seine frühe Bekanntschaft mit Computern, Schulphysik und angewandter Mathematik als Ausbildung. Ich werde es sagen. Aber für das, was ich in diesem Artikel schreibe, ist es nicht so, dass ich alles so beherrsche, sondern dass sich C ++ als komfortable Sprache herausstellte (ich hatte vor dem Hintergrund von Artikeln im Habrr-Typ „Lambda-Funktionen → Überladung interner Operatoren“ ein wenig Angst davor alles neu definieren "), es ist klar, dass die Jahre seiner Entwicklung zu recht freundlichen Entwicklungsumgebungen (IDEs) geführt haben. Die Sprache selbst in ihrer neuesten Version, es scheint, als würde sie Müll aus dem Speicher sammeln, ich weiß nicht, wie es vorher war. Zumindest führten die vom Autor für die einfachsten algorithmischen Konstruktionen geschriebenen Programme tagelang zu Rechenalgorithmen für Bots, und es gab keine Speicherlecks und andere Fehler bei hoher Last. Dies gilt auch für CUDA, zunächst scheint es kompliziert zu sein, aber es basiert auf einfachen Prinzipien und natürlich ist es schwierig, Orte auf GPUs an Orten zu initialisieren, an denen es viele gibt, aber dann haben Sie Ihre eigene kleine Rakete mit Rauch von der Grafikkarte.
Von den Objektklassen für das Training mit der GPU empfiehlt der Autor zellulare Automaten . Zu einer Zeit gab es eine Zunahme der Popularität und Mode für sie, aber dann ergriffen neuronale Netze die Köpfe der Entwickler.
Bis zu:
"Jede Größe in der Physik, einschließlich Zeit und Raum, ist endlich und diskret."
als kein zellularer Automat.
Aber es ist schön, wenn drei einfache Formeln dies schaffen können:
Wenn es interessant sein wird, über zellulare Automaten auf CUDA zu lesen, schreiben Sie in die Kommentare, es wird typisiertes Material für einen kleinen Artikel geben.
Und dies ist die Quelle für zellulare Automaten (unter dem Video befinden sich Links zu den Quellen):
Die Idee, nach dem Frühstück einen Artikel in einem Atemzug zu schreiben, scheint mir zu funktionieren. Zweite Kaffeezeit. Ich wünsche Ihnen einen schönen Wochenendleser.