Mit CUDA hat Nvidia eine universelle Plattform für Parallel Computing und ein Programmiermodell geschaffen, das insbesondere in der wissenschaftlichen Forschung genutzt wird, beispielweise im Rahmen von Deep-Learning-Projekten.
Dieser Artikel beleuchtet die Entstehungsgeschichte von CUDA und vermittelt Ihnen alles Wissenswerte zu bevorzugten Anwendungsbereichen, Toolkit und Bibliotheken. Zudem werfen wir auch einen Blick auf die Grundlagen der Programmierarbeit mit CUDA.
CUDA - Definition & Historie
CUDA ist sowohl eine Parallel-Computing-Plattform als auch ein Programmiermodell, das allgemeine Berechnungen auf Graphics Processing Units (GPUs) von Nvidia ermöglicht. Dabei beschleunigt CUDA rechenintensive Anwendungen, indem die Leistung der Grafikprozessoren für parallele Berechnungen eingesetzt wird. Zwar gibt es auch andere APIs für Grafikprozessoren - etwa OpenCL (dazu gleich mehr) - und auch konkurrierende GPUs - etwa von AMD. Die Kombination von CUDA mit Nvidia-GPUs hat sich aber in diversen Anwendungsgebieten durchgesetzt - beispielsweise Deep Learning - und liefert die Basis für einige der weltweit schnellsten Rechner.
Grafikkarten sind wohl so alt wie der PC selbst - zumindest, wenn man IBMs Monochrome Display Adapter aus dem Jahr 1981 als Grafikkarte wertet. Ab 1988 nahm das Geschäft mit 3D-Grafikbeschleunigern für PCs Fahrt auf. Im Wesentlichen war es die Gaming-Industrie, die die Evolution von GPUs in Gang setzte, insbesondere durch die immer populärer werdenden Egoshooter. Ab 1996 wollte Nvidia ein Teil vom Kuchen abhaben und warf einige erste, schwache Produkte im Bereich der 3D-Beschleuniger auf den Markt. Das Unternehmen lernte schnell und stellte schließlich im Jahr 1999 mit der GeForce 256 die erste Grafikkarte vor, die auch offiziell als GPU bezeichnet wurde.
Erst Jahre später wurden GPUs auch für mathematische, wissenschaftliche und technische Berechnungen eingesetzt. 2003 stellte ein Forschungsteam mit "Brook" unter der Leitung von Ian Buck das erste Programmiermodell zur Erweiterung von C um datenparallele Konstrukte vor, das weitere Verbreitung fand. Buck wechselte später zu Nvidia und leitete dort die Einführung von CUDA im Jahr 2006 - der ersten kommerziellen Lösung für allgemeine Berechnungen auf GPUs.
Der CUDA-Konkurrent OpenCL wurde im Jahr 2009 mit dem Ziel eingeführt, einen Standard für heterogenes Computing zu schaffen, der nicht auf die Kombination aus Intel/AMD-CPUs und Nvidia-GPUs beschränkt ist. Obwohl OpenCL wegen seiner Offenheit attraktiv wirkt, kommt es im Zusammenspiel mit Nvidia-GPUs nicht an die Performanz von CUDA heran. Viele Deep-Learning-Frameworks unterstützen OpenCL entweder überhaupt nicht oder werden erst nachträglich (und oft halbherzig) um Support ergänzt.
CUDA - Anwendungsbereiche
Die Kombination aus CUDA und Nvidia-Grafikprozessoren kommt in vielen Bereichen zum Einsatz, die eine hohe Fließkommaberechnungsleistung erfordern. Dazu gehören:
Finanzberechnungen
Klima-, Wetter- und Ozeanmodellierung
Datenwissenschaft und Analytics
Deep Learning und maschinelles Lernen
Verteidigung und Nachrichtendienste
Fertigung
Medien und Unterhaltung
Medizinische Bildgebung
Öl- und Gasindustrie
wissenschaftliche Forschung / Supercomputing
Security
Tools und Management
Deep Learning mit CUDA
Deep Learning hat einen enormen Bedarf an Rechengeschwindigkeit: Um die Modelle für Google Translate zu trainieren, führten die Teams von Google im Jahr 2016 Hunderte von einwöchigen TensorFlow-Durchläufen mit GPUs durch - sie hatten zu diesem Zweck 2.000 Server-GPUs von Nvidia gekauft. Ohne Grafikprozessoren hätte jeder dieser Trainingsläufe Monate statt einer Woche gedauert. Für den produktiven Einsatz dieser TensorFlow-Übersetzungsmodelle verwendete Google einen neuen kundenspezifischen Verarbeitungschip - die TPU (Tensor Processing Unit).
Neben TensorFlow nutzen auch viele andere Deep-Learning-Frameworks CUDA für ihre GPU-Unterstützung, darunter:
Caffe2,
Chainer,
Databricks,
H2O.ai,
Keras,
MATLAB,
MXNet,
PyTorch,
Theano und
Torch.
In den meisten Fällen verwenden sie die cuDNN-Bibliothek für die Berechnungen mit neuronalen Netzen. Diese Bibliothek ist für das Training der Deep-Learning-Frameworks so bedeutsam, dass alle Frameworks, die eine bestimmte Version von cuDNN verwenden, im Wesentlichen die gleichen Leistungswerte für gleichwertige Anwendungsfälle aufweisen. Wenn CUDA und cuDNN von Version zu Version verbessert werden, profitieren alle Deep-Learning-Frameworks, die auf die neue Version aktualisiert werden davon. Die Performance unterscheidet sich von Framework zu Framework, wenn auf mehrere GPUs und Knoten skaliert wird.
CUDA - Toolkit & Bibliotheken
Das CUDA-Toolkit beinhaltet:
Bibliotheken,
Debugging- und Optimierungs-Tools,
einen Compiler,
eine Dokumentation und
eine Laufzeitbibliothek zur Bereitstellung Ihrer Anwendungen.
Zudem enthält es weitere Komponenten für Deep Learning, lineare Algebra, Signalverarbeitung und parallele Algorithmen. Im Allgemeinen unterstützen die CUDA-Bibliotheken alle Nvidia-GPU-Familien. Die beste Performance versprechen jedoch Grafikprozessoren der neuesten Generation. Die Verwendung einer oder mehrerer Bibliotheken ist der einfachste Weg, die Vorteile von GPUs zu nutzen - sofern die benötigten Algorithmen in der entsprechenden Bibliothek implementiert sind.
CUDA-Bibliotheken für Deep Learning
Im Bereich Deep Learning gibt es drei wichtige GPU-beschleunigte Bibliotheken:
cuDNN (siehe oben),
TensorRT (leistungsstarker Deep-Learning-Inferenz-Optimierer und -Laufzeitumgebung) und
DeepStream (Video-Inferenz-Bibliothek).
CUDA-Bibliotheken für lineare Algebra / Mathematik
Lineare Algebra ist die Grundlage für Tensor-Berechnungen und damit für Deep Learning. BLAS (Basic Linear Algebra Subprograms) ist eine Sammlung von Matrixalgorithmen, die 1989 in Fortran implementiert wurde und seither von Wissenschaftlern und Ingenieuren verwendet wird.
cuBLAS ist eine GPU-beschleunigte Version von BLAS und die leistungsstärkste Art, Matrixarithmetik mit GPUs durchzuführen.
cuSPARSE behandelt spärliche Matrizen.
CUDA-Bibliotheken für Signalverarbeitung
Die schnelle Fourier-Transformation (FFT) ist einer der grundlegenden Algorithmen für die Signalverarbeitung; sie wandelt ein Signal (z. B. eine Audiowellenform) in ein Spektrum von Frequenzen um.
cuFFT ist eine GPU-beschleunigte Version von FFT.
Codecs, die Standards wie H.264 verwenden, kodieren/komprimieren und dekodieren/dekomprimieren Videos für die Übertragung und Anzeige. Nvidias Video Codec SDK beschleunigt diesen Prozess mit GPUs.
CUDA-Bibliotheken für parallele Algorithmen
Für parallele Algorithmen stehen drei verschiedene Bibliotheken für verschiedene Zwecke zur Verfügung:
NCCL (Nvidia Collective Communications Library) dient der Skalierung von Anwendungen auf mehrere GPUs und Knoten.
nvGRAPH dient der parallelen Graphenanalyse.
Thrust ist eine C++ Vorlagenbibliothek für CUDA, die auf der C++ Standard Template Library basiert.
CUDA vs. CPU-Leistung
In einigen Fällen können Sie CUDA-Funktionen anstelle der entsprechenden CPU-Funktionen verwenden. Zum Beispiel können die gemm
-Matrixmultiplikationsroutinen von BLAS durch GPU-Versionen ersetzt werden, indem man sie einfach mit der NVBLAS-Bibliothek verknüpft:
int main () {
// Allocate CPU memory and fill arrays
…
// Matrix multiplication on CPU or GPU: C = alpha*op(A) *op/B) + beta*C
sgemm (TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC);
//Free CPU memory
…
return 0;
}
Programmieren mit CUDA: Grundlagen
Wenn Sie keine für Ihre Programme passenden CUDA-Bibliotheksroutinen finden, müssen Sie sich an der Low-Level-Programmierung mit CUDA versuchen. Das ist heute viel einfacher als beispielsweise noch in den späten 2000er Jahren, was unter anderem daran liegt, dass die Syntax inzwischen einfacher ist und bessere Entwicklungs-Tools zur Verfügung stehen.
Einziges Manko: Die Unterstützung für macOS ist nach einigen Jahren in der Bedeutungslosigkeit in die Unbrauchbarkeit entschwunden: Alles, was Sie mit dieser Version tun können, ist, Debugging- und Profiling-Sitzungen zu kontrollieren, die unter Linux oder Windows laufen.
Um die CUDA-Programmierung zu verstehen, betrachten Sie diese einfache C/C++-Routine, um zwei Arrays zu addieren:
void add(int n, float *x, float *y)
{
for (int i = 0; i < n; i++)
y[i] = x[i] + y[i];
}
Sie können daraus einen Kernel machen, der auf der GPU läuft, indem Sie das Schlüsselwort __global__
zur Deklaration hinzufügen und den Kernel mit der dreifachen Klammersyntax aufrufen:
add<<<1, 1>>>(N, x, y);
Sie müssen auch Ihre malloc/new
- und free/delete
-Aufrufe in cudaMallocManaged
und cudaFree
ändern, um Speicherplatz auf der GPU zuzuweisen. Schließlich müssen Sie warten, bis eine GPU-Berechnung abgeschlossen ist, bevor Sie die Ergebnisse auf der CPU verwenden - was Sie mit cudaDeviceSynchronize
erledigen.
Die dreifache Klammer oben verwendet einen Thread-Block und einen Thread. Aktuelle Nvidia-GPUs können viele Blöcke und Threads verarbeiten. Ein Tesla P100 Grafikprozessor, der auf der Pascal-GPU-Architektur basiert, verfügt beispielsweise über 56 Streaming-Multiprozessoren (SMs), die jeweils bis zu 2048 aktive Threads unterstützen können.
Der Kernel-Code muss seinen Block- und Thread-Index kennen, um seinen Offset in den übergebenen Arrays zu finden. Der parallelisierte Kernel verwendet häufig eine Grid-Stride-Schleife, wie die folgende:
__global__
void add(int n, float *x, float *y)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
for (int i = index; i < n; i += stride)
y[i] = x[i] + y[i];
}
Wenn Sie sich die Beispiele im CUDA-Toolkit ansehen, werden Sie feststellen, dass es noch mehr zu beachten gibt als die hier beschriebenen Grundlagen. Zum Beispiel müssen einige CUDA-Funktionsaufrufe in checkCudaErrors()
-Aufrufe verpackt werden.
Zusammenfassend lässt sich sagen, dass Sie Ihre Anwendungen mit GPUs auf vielen Ebenen beschleunigen können. (fm)
Dieser Beitrag basiert auf einem Artikel unserer US-Schwesterpublikation InfoWorld.