C for Metal - Edelmetall für Computer auf Intel-Grafikkarten

Wie viele Intel-Prozessorkerne haben Sie auf Ihrem Computer? Wenn Sie ein auf Intel basierendes System verwenden, müssen Sie in den allermeisten Fällen Ihrer Antwort eines hinzufügen. Die Zusammensetzung fast aller Intel-Prozessoren - von Atom bis Xeon E3 natürlich, ohne den Core zu verpassen - umfasst seit vielen Jahren den integrierten Grafikkern Intel Graphics, der im Wesentlichen ein vollwertiger Prozessor ist und dementsprechend nicht nur Bilder auf dem Bildschirm anzeigen und Videos beschleunigen kann. sondern auch "gewöhnliche" Allzweckberechnungen durchführen. Wie kann dies effektiv genutzt werden? Schau unter den Schnitt.



Zunächst erklären wir kurz, warum es sich lohnt, sich auf eine Intel-GPU zu verlassen. Natürlich übersteigt die CPU-Leistung im System fast immer deutlich die GPU, da es sich auch um den Zentralprozessor handelt.

Es ist jedoch interessant festzustellen, dass die Leistung integrierter Intel-GPUs in den letzten zehn Jahren prozentual viel stärker gestiegen ist als die der CPU, und dieser Trend wird sich mit dem Aufkommen neuer diskreter Intel-Grafikkarten sicherlich fortsetzen. Darüber hinaus ist die GPU aufgrund ihrer Architektur (viele Vektorausführungsgeräte) viel besser für die Ausführung eines bestimmten Aufgabentyps geeignet - der Bildverarbeitung, dh der Ausführung von Operationen des gleichen Typs auf Datenarrays. Die GPU tut dies mit vollständiger interner Parallelisierung, verbraucht weniger Energie als die CPU und übertrifft sie in einigen Fällen sogar in absoluter Geschwindigkeit. Schließlich können GPU und CPU für ihre eigenen Aufgaben parallel arbeiten und so maximale Leistung und / oder minimalen Stromverbrauch des gesamten Systems erzielen.

- Ok, Intel. Wir haben uns entschieden, Intel GPU für allgemeine Berechnungen zu verwenden. Wie geht das?
- Der einfachste Weg, für den keine besonderen Grafikkenntnisse erforderlich sind (Direct3D- und OpenGL-Shader), ist OpenCL.

OpenCL-Kernel sind plattformunabhängig und werden automatisch auf allen im System verfügbaren Computergeräten ausgeführt - CPU, GPU, FPGA usw. Die Gebühr für diese Vielseitigkeit ist jedoch weit von der maximal möglichen Leistung für jeden Gerätetyp und insbesondere für die integrierte Intel-GPU entfernt. Hier können wir ein Beispiel geben: Wenn Sie Code auf einer Intel-GPU ausführen, die eine 16x16-Byte-Matrix transponiert, ist der Leistungsvorteil der direkten Programmierung der Intel-GPU achtmal höher als bei der OpenCL-Version!

Darüber hinaus unterstützt OpenCL einige der Funktionen, die zur Implementierung gängiger Algorithmen erforderlich sind (z. B. "breite Filter", die Daten aus einer großen Gruppe von Pixeln in einer einzelnen Transformation verwenden), einfach nicht.

Wenn Sie also maximale Geschwindigkeit auf der GPU und / oder etwas Komplizierteres benötigen, als unabhängig mit jedem Element des Arrays und seinen nächsten Nachbarn zu arbeiten, hilft Ihnen Intel C for Metal (ICM), ein Tool zum Entwickeln von Anwendungen, die auf Intel Graphics ausgeführt werden .

ICM - Willkommen in der Schmiede!


Unter dem Gesichtspunkt der Leistung und Funktionalität kann ICM als "Assembler für Intel-Grafikkarten" und in Bezug auf Schaltung und Benutzerfreundlichkeit als "Analogon von OpenCL für Intel-Grafikkarten" betrachtet werden.

ICM wird seit vielen Jahren intern von Intel bei der Entwicklung von Medienverarbeitungsprodukten auf der Intel-GPU verwendet. Aber im Jahr 2018 wurde ICM für die Öffentlichkeit freigegeben, und sogar mit Open Source!

Intel C for Metal erhielt seinen aktuellen Namen vor einigen Monaten, bevor es Intel C for Media (das gleiche Akronym ICM oder nur CM oder sogar Cm) und noch früher das Media Development Framework (MDF) hieß. Wenn also irgendwo im Namen der Komponente, in der Dokumentation oder in den Open-Source-Kommentaren die alten Namen zusammentreffen - seien Sie nicht beunruhigt, dies ist ein historischer Wert.

Daher enthält der ICM-Anwendungscode wie in OpenCL zwei Teile: den auf dem Prozessor ausgeführten „administrativen“ und den auf der GPU ausgeführten Kernel. Es überrascht nicht, dass der erste Teil als Host und der zweite als Kernel bezeichnet wird.

Kernel sind eine Funktion zur Verarbeitung eines bestimmten Pixelblocks (oder nur von Daten), werden in der Sprache Intel C for Metal geschrieben und mit dem ICM-Compiler in den Intel GPU-Befehlssatz (ISA) kompiliert.

Der Host ist eine Art „Kernel-Team-Manager“, der den Datenübertragungsprozess zwischen der CPU und der GPU verwaltet und andere „Verwaltungsarbeiten“ über die ICM Runtime-Laufzeitbibliothek und den Intel GPU-Medientreiber ausführt.
Ein detaillierter ICM-Workflow sieht folgendermaßen aus:


  • ICM-Hostcode wird von jedem x86 C / C ++ - Compiler zusammen mit der gesamten Anwendung kompiliert.
  • Der ICM-Kernelcode wird vom ICM-Compiler in eine Binärdatei mit einem gemeinsamen Befehlssatz (Common ISA) kompiliert.
  • Zur Laufzeit wird dieser allgemeine Satz von JIT-Anweisungen in eine bestimmte Intel-GPU übersetzt.
  • Der ICM-Host ruft die ICM-Laufzeitbibliothek auf, um mit der GPU und dem Betriebssystem zu kommunizieren.

Ein paar weitere wichtige und nützliche Punkte:

  • Die in ICM zum Darstellen / Speichern von Daten verwendeten Oberflächen können mit DirectX 11 und 9 (DXVA unter Linux) gemeinsam genutzt werden.
  • Die GPU kann Daten sowohl aus dem Videospeicher als auch aus dem mit der CPU gemeinsam genutzten Systemspeicher aufnehmen und schreiben. ICM enthält spezielle Funktionen für beide Fälle der Datenübertragung in beide Richtungen. Gleichzeitig wird der Systemspeicher genau gemeinsam genutzt, und ein echtes Kopieren ist nicht erforderlich - dafür wird im ICM die sogenannte Nullkopie bereitgestellt.

ICM - im Abzug des Vulkans!


Bereits aus dem Namen "C for Iron" selbst folgt, dass die Gerätesprache dem internen Grafikgerät Intel entspricht. Das heißt, es berücksichtigt die Tatsache, dass der Code auf mehreren Dutzend Ausführungseinheiten (Ausführungseinheit) der Grafikkarte ausgeführt wird, von denen jede ein Vollvektorprozessor ist, der mehrere Threads gleichzeitig ausführen kann.

Die ICM-Sprache selbst ist C ++ mit einigen Einschränkungen und Erweiterungen. Im Vergleich zu C ++ fehlen ICM ... Zeiger, Speicherzuordnung und statische Variablen. Unter dem Verbot auch rekursive Funktionen. Es gibt jedoch eine explizite Vektormodell (SIMD) -Programmierung: Vektordatentypen - Vektor, Matrix und Oberfläche; Vektoroperationen an diesen Datentypen, Vektorbedingungen, falls / sonst, unabhängig für jedes Element des Vektors ausgeführt; sowie integrierte Funktionen für den Zugriff auf feste Funktionen der Intel GPU-Hardware.

Die Arbeit mit Vektoren, Matrizen und Flächen in realen Problemen wird durch Objekte von „Teilmengen“ erleichtert - aus den entsprechenden Basisobjekten können Sie nur die für Sie interessanten „Referenz“ -Blöcke oder als Sonderfall einzelne Elemente per Maske auswählen.

Schauen wir uns zum Beispiel den ICM-Code an, der einen linearen Filter implementiert und einen Wert ersetzt
RGB-Farben jedes Pixels nach seinem Durchschnittswert und 8 Nachbarn im Bild:
I (x, y) = [I (x-1, y-1) + I (x-1, y) + I (x-1, y + 1) + I (x, y-1) +
+ I (x, y) + I (x, y + 1) + I (x + 1, y-1) + I (x + 1, y) + I (x + 1, y + 1)] / 9

Wenn sich die Farben (Daten) in der Matrix als R8G8B8 befinden , lautet die Berechnung mit Aufteilung des Eingabebildes in Blöcke mit 6 x 8 Pixeln (6 x 24 Byte Datenelemente) wie folgt:

_GENX_MAIN_ void linear(SurfaceIndex inBuf, SurfaceIndex outBuf, uint h_pos, uint v_pos){ //    8x32 matrix<uchar, 8, 32> in; //   6x24 matrix<uchar, 6, 24> out; matrix<float, 6, 24> m; //    read(inBuf h_pos*24, v_pos*6, in); //    -  m = in.select<6,1,24,1>(1,3); m += in.select<6,1,24,1>(0,0); m += in.select<6,1,24,1>(0,3); m += in.select<6,1,24,1>(0,6); m += in.select<6,1,24,1>(1,0); m += in.select<6,1,24,1>(1,6); m += in.select<6,1,24,1>(2,0); m += in.select<6,1,24,1>(2,3); m += in.select<6,1,24,1>(2,6); //  -   9   * 0.111f; out = m * 0.111f; //   write(outBuf, h_pos*24, v_pos*6, out); } 

  • Die Größe der Matrizen wird in der Form <Datentyp, Höhe, Breite> festgelegt.
  • Der Operator select <v_size, v_stride, h_size, h_stride> (i, j) gibt die Submatrix beginnend mit dem Element (i, j) zurück . v_size zeigt die Anzahl der ausgewählten Zeilen, v_stride - den Abstand zwischen ausgewählten Zeilen h_size - die Anzahl der ausgewählten Spalten, h_stride - den Abstand zwischen ihnen .

Bitte beachten Sie, dass die Größe der 8x32-Eingabematrix gewählt wird, da der 8x30-Block zwar algorithmisch ausreicht, um die Werte aller Pixel im 6x24-Block zu berechnen, der Datenblock jedoch nicht in Bytes, sondern in 32-Bit-Dword-Elementen im ICM gelesen wird.

Der obige Code ist in der Tat ein vollwertiger ICM-Kernel. Wie bereits erwähnt, wird es vom ICM-Compiler in zwei Schritten kompiliert (Vorkompilierung und anschließende JIT-Übersetzung). Der ICM-Compiler basiert auf LLVM und kann auf Wunsch in den Quellen studiert und von Ihnen selbst erstellt werden .

Aber was macht der ICM-Host? Ruft Funktionen der ICM Runtime-Laufzeitbibliothek auf, die:

  • Erstellen, Initialisieren und Löschen nach Verwendung des GPU-Geräts (CmDevice) sowie von Oberflächen mit Benutzerdaten, die in Kerneln verwendet werden (CmSurface);
  • Mit Kerneln arbeiten - Laden Sie sie aus vorkompilierten .isa- Dateien herunter, bereiten Sie ihre Argumente vor und geben Sie den Teil der Daten an, mit dem jeder Kernel arbeiten wird.
  • Erstellen und verwalten Sie die Kernel-Ausführungswarteschlange.
  • Sie steuern den Betrieb der Threads, die jeden Kernel auf der GPU ausführen.
  • Ereignisse verwalten (CmEvent) - Synchronisationsobjekte der GPU und der CPU;
  • Übertragen Sie Daten zwischen der GPU und der CPU bzw. zwischen System und Videospeicher.
  • Melden Sie Fehler und messen Sie die Betriebszeit der Kernel.

Der einfachste Hostcode sieht folgendermaßen aus:

 //  CmDevice cm_result_check(::CreateCmDevice(p_cm_device, version)); //  hello_world_genx.isa std::string isa_code = isa::loadFile("hello_world_genx.isa"); //    isa  CmProgram CmProgram *p_program = nullptr; cm_result_check(p_cm_device->LoadProgram(const_cast<char* >(isa_code.data()),isa_code.size(), p_program)); //  hello_world . CmKernel *p_kernel = nullptr; cm_result_check(p_cm_device->CreateKernel(p_program, "hello_world", p_kernel)); //       CmKernel CmThreadSpace *p_thread_space = nullptr; cm_result_check(p_cm_device->CreateThreadSpace(thread_width, thread_height, p_thread_space)); //   . cm_result_check(p_kernel->SetKernelArg(0, sizeof(thread_width), &thread_width)); //  CmTask –      //         //     . CmTask *p_task = nullptr; cm_result_check(p_cm_device->CreateTask(p_task)); cm_result_check(p_task->AddKernel(p_kernel)); //   CmQueue *p_queue = nullptr; cm_result_check(p_cm_device->CreateQueue(p_queue)); //    GPU (    ). CmEvent *p_event = nullptr; cm_result_check(p_queue->Enqueue(p_task, p_event, p_thread_space)); //   . cm_result_check(p_event->WaitForTaskFinished()); 

Wie Sie sehen, ist das Erstellen und Verwenden von Kerneln und eines Hosts nicht kompliziert. Alles ist einfach!

Die einzige Schwierigkeit, vor der man warnen muss, um in die reale Welt zurückzukehren: Derzeit ist in der öffentlich verfügbaren Version von ICM die einzige Möglichkeit, Kernel zu debuggen, printf-Nachrichten. Wie man sie richtig benutzt, zeigt das Beispiel Hello, World .

ICM - kein Heavy Metal!


Nun wollen wir sehen, wie es in der Praxis funktioniert. Das ICM Developer Kit ist für Windows und Linux verfügbar und enthält für beide Betriebssysteme den ICM Compiler, Dokumentation und Anwendungsfälle für Lernprogramme. Eine detaillierte Beschreibung dieser Schulungsbeispiele wird separat heruntergeladen .

Für Linux enthält das Paket auch einen Medientreiber im Benutzermodus für VAAPI mit einer integrierten ICM Runtime-Laufzeitbibliothek. Für Windows funktioniert der übliche Intel-Grafiktreiber für Windows mit ICM. Die ICM Runtime-Laufzeitbibliothek ist im DLL-Satz dieses Treibers enthalten. Das ICM-Paket enthält nur die Link-LIB-Datei dafür. Wenn der Treiber aus irgendeinem Grund auf Ihrem System fehlt, wird er von der Intel-Website heruntergeladen und der ordnungsgemäße Betrieb von ICM in den Treibern wird ab Version 15.60 - 2017 garantiert.

Den Quellcode der Komponenten finden Sie hier:


Der weitere Inhalt dieses Abschnitts gilt ausschließlich für Windows, die allgemeinen Grundsätze für die Arbeit mit ICM gelten jedoch auch für Linux.

Für die „normale“ Arbeit mit dem ICM-Paket benötigen Sie Visual Studio ab 2015 und Cmake ab Version 3.2. Gleichzeitig sind die Konfigurations- und Skriptdateien der Schulungsbeispiele für VS 2015 konzipiert. Um neuere Versionen von VS-Dateien verwenden zu können, müssen Sie die Pfade zu den VS-Komponenten selbst untersuchen und bearbeiten.

Lernen Sie ICM für Windows kennen:

  • Laden Sie das Archiv herunter .
  • Pack es aus;
  • Wir starten (vorzugsweise in der VS-Befehlszeile) das Konfigurationsskript setupenv.bat mit drei Parametern: der Intel-GPU-Generation (entsprechend dem Prozessor, in den die GPU eingebaut ist, kann standardmäßig belassen werden: gen9), der Kompilierungsplattform: x86 \ x64 und der DirectX-Version für Teilen mit ICM: dx9 / dx11.

Danach können Sie einfach alle Trainingsbeispiele erstellen. Im Beispielordner führt das Skript build_all.bat dies aus oder generiert Projekte für Microsoft Visual Studio. Dadurch wird das Skript create_vs.bat mit dem Namen eines bestimmten Beispiels als Parameter erstellt.

Wie Sie sehen können, ist die ICM-Anwendung eine EXE-Datei mit dem Host-Teil und eine ISO-Datei mit dem entsprechenden vorkompilierten GPU-Teil.

Das ICM-Paket enthält verschiedene Beispiele - vom einfachsten Hello, World, das die Grundprinzipien des ICM-Betriebs zeigt, bis zum ziemlich komplizierten - der Implementierung des Algorithmus zum Ermitteln des "maximalen Durchflusses - minimalen Schnitts" des Diagramms (Max-Flow-Min-Cut-Problem), das bei der Bildsegmentierung und dem Zusammenfügen verwendet wird .

Alle ICM-Fallstudien sind direkt im Code und in der bereits erwähnten separaten Beschreibung gut dokumentiert. Es wird empfohlen, sich genau mit ICM zu befassen - Beispiele nacheinander zu studieren und auszuführen und sie dann an Ihre Bedürfnisse anzupassen.

Für ein allgemeines Verständnis aller vorhandenen ICM-Funktionen wird dringend empfohlen, die „Spezifikation“ - die ICM-Beschreibung cmlangspec.html im Ordner \ documents \ compiler \ html \ cmlangspec - zu studieren .

Insbesondere wird die API der in der Hardware implementierten ICM-Funktionen beschrieben - Zugriff auf die sogenannten Textur-Sampler (Sampler) - ein Mechanismus zum Filtern von Bildern verschiedener Formate sowie zum Bewerten der Bewegung (Motion Estimation) zwischen Videobildern und einigen Videoanalysefunktionen.

ICM - Streik solange es heiß ist!


In Bezug auf die Leistung von ICM-Anwendungen sollte beachtet werden, dass Fallstudien das Messen der Arbeitszeit umfassen, sodass Sie durch Ausführen auf dem Zielsystem und Vergleichen mit Ihren Aufgaben die Angemessenheit der Verwendung von ICM für sie bewerten können.

Allgemeine Überlegungen zur ICM-Leistung sind recht einfach:

  • Denken Sie beim Entladen von Berechnungen auf einer GPU an den Aufwand für die Übertragung von CPU <-> GPU-Daten und die Synchronisierung dieser Geräte. Daher ist ein Beispiel wie Hello, World kein guter Kandidat für eine ICM-Implementierung. Die Algorithmen für Computer Vision, KI und jede nicht triviale Verarbeitung von Datenarrays, insbesondere bei einer Änderung der Reihenfolge dieser Daten im Prozess oder am Ausgang, sind jedoch die Anforderungen von ICM.
  • Darüber hinaus muss beim Entwerfen eines ICM-Codes das interne GPU-Gerät berücksichtigt werden. Es ist daher ratsam, eine ausreichende Anzahl (> 1000) von GPU-Threads zu erstellen und alle mit Arbeit zu laden. In diesem Fall empfiehlt es sich, die Bilder für die Verarbeitung in kleine Blöcke aufzuteilen. Die spezifische Art der Partitionierung sowie die Wahl eines bestimmten Verarbeitungsalgorithmus zur Erzielung maximaler Leistung ist jedoch keine triviale Aufgabe. Dies gilt jedoch für jede Art der Arbeit mit einer GPU (und CPU).

Haben Sie OpenCL-Code, aber seine Leistung gefällt Ihnen nicht? Oder CUDA-Code, aber Sie möchten auf einer viel größeren Anzahl von Plattformen arbeiten? Dann lohnt sich ein Blick auf ICM.

ICM ist ein lebendiges und sich entwickelndes Produkt. Sie können an seiner Verwendung und Entwicklung teilnehmen - die entsprechenden Repositories auf github warten auf Ihre Commits. Alle für beide Prozesse erforderlichen Informationen finden Sie in diesem Artikel und in den Readme-Dateien auf github. Und wenn etwas fehlt, wird es nach Ihren Fragen in den Kommentaren angezeigt.

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


All Articles