Deep-Learning-Algorithmen Deep-Learning mit GPUs

Grafikprozessor beschleunigen die Deep-Learning-Algorithmen. Wie man seinen Code auf diese Prozessoren bringt, erfahren Sie hier.
Grafikprozessor beschleunigen die Deep-Learning-Algorithmen. Wie man seinen Code auf diese Prozessoren bringt, erfahren Sie hier.

Grafik-Prozessoren – kurz GPUs – sind effektive Beschleuniger für Deep-Learning-Algorithmen. Doch wie transformiert man seinen Code auf diese Prozessoren? Mathworks bietet dazu den GPU Coder als Erweiterung für Matlab an. Wir fragten Alexander Schreiber wie das Tool arbeitet.

Zunächst, warum eignen sich für Deep-Learning-Implementierungen vor allem GPUs?
Alexander Schreiber: Deep-Learning-Algorithmen basieren auf tiefen neuronalen Netzen mit vielen Schichten. Innerhalb der einzelnen Schichten befinden sich jeweils eine Vielzahl von Knoten – auch Neuronen genannt – die innerhalb einer Schicht dieselben Operationen mit unterschiedlichen Parametern auf die Ausgangssignale einer vorhergehenden Schicht anwenden. Die mathematischen Operationen, die hierbei verwendet werden, sind

  • die Multiplikation der Eingangsak­tivierungen eines Neurons mit den jeweiligen Gewichten,
  • die Propagierungsfunktion, die meistens mittels einer Summation der gewichteten Eingangsaktivierungen implementiert wird,
  • die Addition eines Bias-Werts und
  • die lineare, binäre oder sigmoidale Aktivierungsfunktion.

Aufgrund dieser identischen Operationen mit unterschiedlichen Parametern innerhalb einer Schicht spricht man von einer »Single Program Multiple Data« (SPMD)-Architektur, einer Erweiterung des »Single Instruction Multiple Data« (SIMD)-Ansatzes.

GPUs eignen sich besonders für die Berechnung solcher Aufgaben. Sie
bestehen aus vielen sogenannten Streaming-Prozessoren, die mehrere Instanzen eines Programms (Threads) auf unterschiedliche Daten anwenden können. Abgebildet auf die Deep-Learning-Anwendung können die Operationen der jeweiligen Neuronen innerhalb einer bestimmten Schicht parallel zueinander berechnet werden. Dadurch verringert sich die Berechnungszeit im Vergleich zu klassischen CPU-Architekturen, da diese die einzelnen Neuronen auch innerhalb derselben Schicht nacheinander abarbeiten müssen.

Die parallele Programmierung beschleunigt Berechnungen, hat aber auch die Schwierigkeit, dass das Verhalten bei mehreren parallel laufenden Algorithmen schwerer nachzuvollziehen ist. Gibt es im GPU Coder eine bestimmte Lösung dieses Problems, das sich besonders von anderen Lösungen abhebt?
Schreiber: Die Implementierung von parallelen Systemen erfordert fundierte Kenntnisse sowohl über die mögliche Parallelität des Algorithmus, die Methoden paralleler Programmierung als auch über die Architektur der Zielplattform.
Generell unterscheidet man bei der möglichen Parallelität innerhalb eines Algorithmus zwischen Daten- und Aufgabenparallelität. Über die Datenparallelität haben wir schon bei der Berechnung von Deep-Learning-Algorithmen gesprochen. Dabei werden identische mathematische Operationen mit unterschiedlichen Parametern bzw. Daten ausgeführt. Dies tritt z.B. bei Matrizen- und Vektoroperationen wie auch bei Schleifenoperationen auf, bei denen die Ausführung des Schleifenkörpers unabhängig von den Ergebnissen der anderen Schleifendurchläufe ist. Die Laufzeit der verschiedenen identischen Operationen kann dabei ebenfalls als identisch angenommen werden. Die Ergebnisse müssen dann nicht speziell synchronisiert werden, um die nächste Operation innerhalb des Algorithmus zu starten.

Bei der Aufgabenparallelität können auch verschiedene Operationen mit wiederum unterschiedlichen Daten zueinander nebenläufig ausgeführt werden, wenn die jeweiligen Operationen nicht wechselseitig abhängig sind. Sind die Verarbeitungszweige durchgehend parallel zueinander, benötigt man auch in diesem Fall keine spezielle Synchronisierung. Werden die Verarbeitungszweige allerdings im Verlauf des Algorithmus wieder miteinander verknüpft, benötigt man entsprechende Synchronisierungsmechanismen der parallelen Programmierung, da aufgrund der nun unterschiedlichen Operationen auch deren Laufzeiten unterschiedlich sind. Schnellere Programmzweige müssen auf die gültigen Daten der langsameren warten.

Bei GPU-Systemen handelt es sich zudem um heterogene Systeme, da die sequenziellen Code-Teile weiterhin auf einer CPU ausgeführt werden, während die Verarbeitung der parallelen Anteile auf die GPU ausgelagert werden. Deshalb ergeben sich bei diesen Systemen Interaktionen zwischen den beiden Prozessortypen, die zum einen zeitintensiv sind, zum anderen koordiniert werden müssen.

Die Grundlage für den Arbeitsablauf des GPU Coders bildet die Matlab-Beschreibung der Funktionen, d.h. allgemeine Algorithmen oder speziell Deep-Learning-Netzwerke. Sicherlich kann die Effizienz des automatisch erzeugten CUDA-Codes durch eine optimierte Struktur des formulierten Algorithmus beeinflusst werden, z.B. durch einen geeigneten Aufbau von Schleifen bzw. die Nutzung von Vektor- und Matrizen­arithmetik bei allgemeinen Algorithmen. Die Art der Beschreibung unterscheidet sich generell aber nicht von einer klassischen, sequenziellen Matlab-Funktion.

Der GPU Coder optimiert dann automatisch die Implementierung des Algorithmus hinsichtlich der Erzeugung der CUDA-Kernels – also der elementaren Einheiten, die parallel als Threads auf der GPU ausgeführt werden – der Speicherplatzreservierung auf der GPU und der Minimierung des Datentransfers zwischen CPU und GPU. Dabei fokussiert sich der GPU Coder momentan auf die Ausnutzung der Datenparallelität innerhalb des Algorithmus, die sich aus Matrizen- bzw. Vektorarithmetik und Schleifen bei allgemeinen Algorithmen sowie aus der Architektur von Deep-Learning-Algorithmen ergeben.

Bezüglich der Datenhaltung analysiert der GPU Coder die Lokalität der Daten hinsichtlich sowohl der Prozessorplattformen, d.h. CPU und GPU, als auch der CUDA-Kernels, um unnötige und zeitraubende Kopiervorgänge sowohl zwischen den Prozessorplattformen als auch innerhalb der GPU zu vermeiden. Dabei werden möglichst viele zusammenhängende datenparallele Opera­tionen innerhalb desselben Kernels gruppiert. Bei der Datenverwaltung unterstützt der GPU Coder sowohl das klassische, verteilte Speichermodell als auch das sogenannte Unified-Memory-Modell.

Beim klassischen, verteilten Modell werden entsprechende Speicherbereiche innerhalb der lokalen Speichertypen der GPU mittels dedizierter Befehle
reserviert (cudaMalloc) bzw. wieder freigegeben (cudaFree). Die Synchronisierung zwischen den sequenziellen Programmteilen der CPU und den parallelen der GPU erfolgt durch die je­weiligen Kopiervorgänge zwischen den Plattformen.

Beim Unified-Memory-Modell wird das explizite Speichermanagement des verteilten Speichermodells durch die Definition eines von der CPU und GPU gemeinsam genutzten Speicherbereichs abstrahiert (cudaMallocManaged). Dieser Speicherbereich ist von beiden Seiten mittels ein­facher Zeigeroperationen erreichbar. Ein explizites Umkopieren der Daten zwischen den Speicherbereichen der beiden Prozessorplattformen ist nicht mehr nötig, sondern wird im Hintergrund erledigt. Allerdings werden spezielle Synchronisierungsbefehle benötigt, um dem jeweils anderen Prozessor die Gültigkeit der Daten anzuzeigen. Der GPU Coder fügt diese Synchronisierungsbefehle automatisch in den CUDA-Code ein.

Welche Eigenschaften, außer Schnelligkeit und Parallelisierung, hat der GPU Coder noch?
Schreiber: Die Beschleunigung und Parallelisierung durch GPUs ist keine einfach zugängliche Technologie, da die Programmierung von GPUs spezielle Kenntnisse voraussetzt. Zudem müssen die Algorithmen auch auf Parallelität analysiert und in der entsprechenden Programmiersprache, wie z.B. CUDA, formuliert werden.

Der GPU Coder bietet nun einen sehr einfachen Zugang zu GPUs, da die Algorithmen weiterhin wie gewohnt in Matlab formuliert werden können. Die Entwicklerin oder der Entwickler braucht sich weder mit den Analysen, der Syntax der CUDA-Spracherweiterung, noch um die Optimierung der Datenhaltung kümmern. Der GPU Coder übernimmt alle diese Aufgaben und der Algorithmus kann schnell und fehlerfrei auf der GPU-Plattform implementiert werden. Diese Implementierung ist zugleich sehr effizient bezüglich der  Verarbeitungsgeschwindigkeit und des Speicherverbrauchs. Durch die Analysen der Datenabhängigkeiten und -lokalitäten werden die passenden Speicherbereiche des heterogenen CPU-/GPU-Systems optimal genutzt. Dies ist eine Voraussetzung für eine hohe Verarbeitungsgeschwindigkeit des implementierten Algorithmus.

Welche Projekte/Innovationen können umgesetzt werden, die davor nicht oder nur schwer realisierbar waren? Können Sie dazu vielleicht ein Beispiel nennen?
Schreiber: Innovation benötigt agile Arbeitsabläufe und integrierte Arbeitsumgebungen, die schnelles Ausprobieren und Bewerten verschiedener Lösungsansätze und deren Umsetzung auf reale Hardware ermöglichen. Dadurch können die für die Problem­stellung optimalen Lösungen auf der Zielplattform implementiert werden. Klassische Ansätze hemmen dabei häufig die Produktivität durch unterbrochene Werkzeugketten und langwieriges manuelles Codieren.

Matlab bietet dagegen eine integrierte Arbeitsumgebung. Komplexe Algorithmen können mittels einer weit verbreiteten Sprache einfach entworfen und formuliert werden, auch unter Nutzung vorgefertigter anwendungsspezifischer Erweiterungen wie z.B. für Signal- und Bildverarbeitung und Deep Learning. Schon bei der Verifikation der Algorithmen können GPUs mittels der »Parallel Computing Toolbox« für die Simula­tionsbeschleunigung im Hintergrund eingesetzt werden, ohne dass der Entwickler Kenntnisse in paralleler Programmierung benötigt. Darauf aufbauend ermöglicht der GPU Coder die schnelle, fehlerfreie und effiziente Umsetzung der Algorithmen sowohl für die Verifikation unter Nutzung einer zusätzlichen GPU auf der Host-Plattform als auch für die Implementierung auf der eingebetteten Zielplattform.

Das gesamte Ökosystem mit Matlab als Basis und dem GPU Coder für die Implementierung ermöglicht die Umsetzung von Projekten, die bislang u.U. aufgrund fehlender Expertise z.B. in paralleler Programmierung nicht möglich gewesen wären. Diese Projekte können in den unterschiedlichsten Industrien und Anwendungsbereichen sein. Letztlich setzt nur der Ideenreichtum der Entwickler die Grenzen für neue Projekte und Anwendungen.

Welchen Stellenwert, wenn eine Einschätzung möglich ist,  kann diese Entwicklung im größeren Kontext einnehmen? – etwa in Bezug auf Trendthemen wie autonome Systeme in der Industrie/ im Auto oder bei Robotern im Alltag aber auch im Arbeitsalltag der Entwickler.
Schreiber: Die digitale Transformation stellt die unterschiedlichsten Industrien vor neue Herausforderungen. Autonome Systeme im Automobil- und Maschinenbau benötigen neuartige Algorithmen, um selbstständig Objekte zu erkennen und zu klassifizieren sowie Entscheidungen zu treffen. Flexible Produktionsplanung zusammen mit Ausfallvorhersage und prädiktiver Wartungsplanung, welche auf der automatisierten Auswertung großer Datenmengen verteilter Sensoren im gesamten Produktionsablauf basieren, sind Schlüsselelemente von Industrie 4.0 oder dem Internet of Things (IoT). Ähnliche Anforderungen für die schnelle Verarbeitung großer Datenmengen haben Finanzanwendungen, um nur einige Beispiele zu nennen. Allen Anwendungen gemein ist der Bedarf an der schnellen Berechnung komplexer Algorithmen, die nur schwer oder u.U. gar nicht in einer geschlossenen mathematischen Form formuliert werden können.

Parallele Berechnung auf GPUs im Allgemeinen und speziell die Anwendung von Deep-Learning-Algorithmen sind momentan favorisierte Lösungsansätze, um diese Herausforderungen zu meistern. Dadurch kann das angelernte Wissen, das in den tiefen neuronalen Netzen gespeichert ist, durch Parallelisierung schnell auch auf Datensätze angewandt werden, die nicht Teil des Trainingsdatensatzes waren. Die Entwurfsmethodik, basierend auf Matlab, der Deep Learning Toolbox, der Parallel Computing Toolbox, dem GPU Coder sowie anderen applikationsspezifischen Erweiterungen, vereinfacht es den Entwicklern, diese Algorithmen schnell und effizient zu entwickeln, zu verifizieren und zu implementieren. Die Entwickler können sich auf die Problemlösung konzentrieren und dabei auf eine integrierte Entwicklungsumgebung vertrauen.

 

 

Alexander Schreiber arbeitet seit 2008 bei MathWorks als Principal Application Engineer und Technical Account Manager. Er deckt die Anwendungsbereiche autonomes Systemdesign, HW/SW Co-Design, automatische Codegenerierung und Verifikation (HDL, C, CUDA) ab. Vor MathWorks arbeitete Schreiber als ASIC-Designer und Projektmanager für EDA- und Halbleiterunternehmen. Der Dipl-Ing. studierte Elektrotechnik mit den Schwerpunkten Regelungstechnik und Prozessauto­matisierung an der Universität Stuttgart.