Seite 4: Innerer Aufbau – Die GPU

In Kooperation mit NVIDIA

Die GPU

Was ist eine Shadereinheit oder ein CUDA-Core?

CUDA-Core, Streamprozessor, Shadereinheit – dies alles und noch einige Begriffe mehr sind Synonyme für die Recheneinheiten in einer GPU, die sich um die eigentliche Berechnung der Daten kümmern. NVIDIA selbst beschreibt diese als CUDA-Kerne, wobei CUDA für Compute Unified Device Architecture steht. Die CUDA-Kerne sind aber nicht mit den Kernen eines Prozessors vergleichbar, da sie weit weniger komplex aufgebaut sind und auf eine sehr spezielle Art und Weise der Datenverarbeitung spezialisiert sind. Da GPUs inzwischen aber mehr leisten können, als über eine Shaderpipeline eine Grafik zu rendern, ist eine Vereinheitlichung der Begrifflichkeiten zu einem übergeordneten Namen wie Streamprozessor oder Unified Shader trefflich gewählt.

Der Begriff Streamprozessor trifft die Aufgabe ganz gut, denn es geht darum, Datenströme zu verarbeiten, die stetig anfallen und die sich auch sehr gut parallelisieren lassen. Inzwischen sprechen wir von mehreren tausend Shadereinheiten pro GPU und ebenso wie das Rendern einer Grafik sich gut parallelisieren lässt, ist dies auch für wissenschaftliche Berechnungen der Fall. Das hat GPUs im Serverbereich als Rechenbeschleuniger zum Durchbruch verholfen.

Ein Shader beschreibt aber nicht die eigentliche Hardware bzw. hier müssen Unterscheidungen gemacht werden. Im Falle moderner GPUs sind die Recheneinheiten auf Fließkomma- (Floating Point oder kurz FP) und Integer-Berechnungen (ganzzahlige Berechnungen abgekürzt mit INT) unterschiedlichster Komplexität ausgelegt. Typischerweise sind FP32- und INT32-Berechnungen, also Rechenoperationen mit einer Genauigkeit von 32 Bit für das Rendering einer Grafik am wichtigsten. Für den wissenschaftlichen Bereich immer wichtiger aber werden die höheren Genauigkeiten mit FP64 und auch dafür gibt es dedizierte Recheneinheiten in einer GPU. Neuerdings hat man allerdings erkannt, dass 32 und 64 Bit längst nicht überall notwendig sind. Um eine INT32-Recheneinheit nicht mit einer weniger komplexen Berechnung zu belegen, wurden Verfahren entwickelt, eine INT32-Rechenheit mit zweimal 16 Bit Integer-Berechnungen zu nutzen, die gleichzeitig ausgeführt werden können.

Noch einen Schritt weiter geht die Integration der Tensor Cores der Ampere-Architektur von NVIDIA, die auf noch weniger komplexe Berechnungen in INT8 und INT4 ausgelegt sind, dazu aber später mehr.

Anhand der Ampere-Architektur wollen wir uns den Aufbau der Recheneinheiten anschauen, denn es befinden sich nicht einfach nur mehrere tausend Shadereinheiten auf der GPU, sondern diese sind in einer bestimmten Form organisiert.

Die GA102-GPU verfügt über sieben GPCs (Graphics Processing Clusters) mit jeweils 12 SMs. Bei der GeForce RTX 3090 und GeForce RTX 3080 sind aber nicht alle SMs aktiviert. Die GA102-GPU käme theoretisch auf insgesamt 10.752 FP32-Einheiten (7 GPCs x 12 SMs x 128 FP32-Einheiten). Für die GeForce RTX 3090 aber sind zwei SMs deaktiviert worden und somit kommt die Karte auf "nur" 10.496 FP32-Einheiten. NVIDIA tut dies, um die Ausbeute der Chips so hoch wie möglich zu halten.

Für die GeForce RTX 3080 ist bei der GA102-GPU ein GPC komplett abgeschaltet und von den verbleibenden sechs nutzen nur vier die vollen 12 SMs – zwei sind auf zehn SMs beschränkt. Damit kommen wir bei 68 SMs auf die 8.704 FP32-Einheiten.

NVIDIA skaliert die Ampere-Architektur von einer GeForce RTX 3060 bis zur GeForce RTX 3090. Die aktuelle GeForce-RTX-30-Serie in der Übersicht:

Gegenüberstellung der GeForce-RTX-30-Serie
  GeForce RTX 3090 GeForce RTX 3080 Ti GeForce RTX 3080 GeForce RTX 3070 Ti
GPU Ampere (GA102) Ampere (GA102) Ampere (GA102) Ampere (GA104)
Transistoren 28 Milliarden 28 Milliarden 28 Milliarden 17,4 Milliarden
Fertigung 8 nm 8 nm 8 nm 8 nm
Chipgröße 628,4 mm² 628,4 mm² 628,4 mm² 392,5 mm²
FP32-ALUs 10.496 10.240 8.704 6.144
INT32-ALUs 5.248 5.120 4.352 3.072
SMs 82 80 68 48
Tensor Cores 328 320 272 192
RT Cores 82 80 68 48
Basis-Takt 1.400 MHz 1.365 MHz 1.440 MHz 1.580 MHz
Boost-Takt 1.700 MHz 1.665 MHz 1.710 MHz 1.770 MHz
Speicherkapazität 24 GB 12 GB 10 GB 8 GB
Speichertyp GDDR6X GDDR6X GDDR6X GDDR6X
Speichertakt 1.219 MHz 1.188 MHz 1.188 MHz 1.188 MHz
Speicherinterface 384 Bit 384 Bit 320 Bit 256 Bit
Speicherbandbreite 936 GB/s 912 GB/s 760 GB/s 608 GB/s
TDP 350 W 350 W 320 W 290 W
Gegenüberstellung der GeForce-RTX-30-Serie
  GeForce RTX 3070 GeForce RTX 3060 Ti GeForce RTX 3060
GPU Ampere (GA104) Ampere (GA104) Ampere (GA106)
Transistoren 17,4 Milliarden 17,4 Milliarden 12 Milliarden
Fertigung 8 nm 8 nm 8 nm
Chipgröße 392,5 mm² 392,5 mm² 276 mm²
FP32-ALUs 5.888 4.864 3.584
INT32-ALUs 2.944 2.432 1.792
SMs 46 38 28
Tensor Cores 184 152 112
RT Cores 46 38 28
Basis-Takt 1.500 MHz 1.410 MHz 1.320 MHz
Boost-Takt 1.730 MHz 1.665 MHz 1.780 MHz
Speicherkapazität 8 GB 8 GB 12 GB
Speichertyp GDDR6 GDDR6 GDDR6
Speichertakt 1.725 MHz 1.750 MHz 1.875 MHz
Speicherinterface 256 Bit 256 Bit 192 Bit
Speicherbandbreite 448 GB/s 448 GB/s 360 GB/s
TDP 220 W 200 W 170 W

Integer- und Fließkomma-Berechnungen gleichzeitig ausführen

Wie bereits erwähnt kann eine FP32-Recheneinheit auch 2x FP16 ausführen und gleiches gilt auch für INT16. Um die Rechenleistung flexibler zu machen und diese insgesamt anzuheben, hat NVIDIA mit der Turing-Architektur eine gleichzeitige Berechnung von Fließkomma- und Integer-Datensätzen ermöglicht. Dies setzt man auch für die Ampere-Architektur fort. Dazu hat sich NVIDIA die Ausführung dieser Berechnungen in der Rendering-Pipeline anhand dutzender Spiele angeschaut und konnte dabei feststellen, dass pro 100 FP-Berechnungen etwa ein Drittel an INT-Berechnungen anfallen. Dies entspricht einem Mittelwert, der allerdings auch von 20 % bis 50 % schwanken kann. Ist es nicht möglich FP- und INT-Berechnungen gleichzeitig auszuführen, gibt es hier gewisse Abhängigkeiten, die zu Verzögerungen in der Rendering-Pipeline führen können.

 

Das Verhältnis aus 1/3 INT32 und 2/3 FP32 zeigt sich auch in der Auslegung des Ampere Streaming Multiprocessor (SM) als Basisbaustein der Ampere-Architektur. Hier wurde die Anzahl der FP32-Recheneinheiten pro SM verdoppelt. Statt 64 der FP32-Einheiten pro SM gibt es nun 128. Hinzu kommen 64 INT32-Einheiten. Es gibt nun zwei Datenpfade pro Quadrant eines SMs, die teilweise parallel angesprochen werden können. Einer der Datenpfade besteht aus 16 FP32-Einheiten. Hier können also 16 FP32-Berechnungen pro Takt bearbeitet werden. Ein zweiter Datenpfad besteht aus jeweils 16 FP32- und INT32-Einheiten. Jeder der SM-Quadranten kann entweder 32 FP32-Operationen ausführen oder jeweils 16 FP32- und INT32-Operationen pro Takt. Für den gesamten SM bedeutet dies die mögliche Ausführung von 128 FP32-Operationen oder jeweils 64 FP32- und INT32-Operationen pro Takt.

Die gleichzeitige Ausführung mehrere Einheiten setzt sich aber auch an anderer Stelle fort. So arbeiten auch die RT und Tensor Cores parallel in der Render-Pipeline und somit wird die Gesamtzeit verkürzt, die für ein Rendering eines Frames notwendig ist.

Shadereinheiten sind also keine mysteriöse Größe mehr, sondern beschreiben einfach vielmehr die Anzahl der Rechenheiten in der GPU, wenngleich man zwischen verschiedenen Komplexitäten der Rechenkapazitäten unterscheiden sollte. Die Begrifflichkeiten können unterschiedlich gewählt werden, meinen aber meist ein und die gleiche Größe zur Beschreibung der Recheneinheiten in einer GPU.

Textureinheiten

In den Shadereinheiten werden sogenannte Shader vorprogrammiert, die dann wiederum die Berechnung bestimmter Werte vornehmen. So können Vertex-Shader dazu verwendet werden, geometrischen Berechnungen und dynamischen Veränderungen von Objekten vorzunehmen. Die Geometry-Shader wiederum berechnen aus den Punkten, Linien und Dreiecken die letztendliche Geometrie und den Aufbau der Objekte und es gibt auch noch Tessellation-Shader, die sogenannten Primitives (zum Beispiel Dreiecke) weiter unterteilen können.

Die Textureinheiten oder Texture Mapping Units (TMU) sind dafür verantwortlich, dass alle Flächen auch mit den dazugehörigen Texturen belegt werden. Die Textureinheiten sind dedizierte Recheneinheiten in einer GPU. Im Falle der Turing-Architektur kommt auf 16 Shadereinheiten eine Textureinheit. Die für eine Textureinheit notwendigen Daten liegen im Grafikspeicher und können von dort gelesen und in den Speicher geschrieben werden. Da die Textureinheiten inzwischen nicht mehr externe Recheneinheiten im eigentlichen Sinne, sondern Bestandteil der Grafikpipeline sind, können Objekte auch mehrfach von der Textureinheit verwendet werden. 

Für das Rendering eines Objekts reicht eine Textur längst nicht mehr aus, sondern es gibt mehrere Ebenen, die beispielsweise eine gewisse 3D-Optik einer eigentlichen flachen Textur erstellen können. Während das Objekt früher mehrfach berechnet und jeweils von der Textureinheit mit der jeweiligen Textur belegt wurde, reicht heute ein Renderprozess, auf den die Textureinheit aus einem Buffer heraus mehrfach auf dieses Objekt zugreifen kann.

Speichercontroller

Eine möglichst hohe Speicherbandbreite ist ebenso wichtig wie die Rechenleistung der GPU selbst. Nur wenn die Daten auch schnell vom Grafikspeicher an die GPU geliefert und darin wieder geschrieben werden können, können die Berechnungen auch entsprechend schnell durchgeführt werden. Man spricht hier gerne vom Backend in Form der Rechenleistung der GPU und dem Frontend in Form der Cache-Hierarchie und der Anbindung des Speichers. Jede GPU-Architektur wird entsprechend ausgelegt, profitiert mal von einer hohen Speicherbandbreite und ist mal nicht so sehr davon abhängig. Davon abgesehen sind natürlich alle Hersteller darauf aus, eine möglichst hohe Speicherbandbreite zu erreichen. Dabei spielt der Speichercontroller eine entscheidende Rolle.

Neben den Änderungen in den SMs gibt es auch solche im Aufbau der ROPs bzw. der Kopplung zwischen den ROPs und den Speichercontrollern. Bis zur Turing-Generation sind die ROPs immer am Speicherinterface angeschlossen. Pro 32-Bit-Speichercontroller waren acht ROPs vorhanden. Änderte sich die Anzahl der Speichercontroller und damit die Größe des Speicherinterface, galt dies auch für die ROPs. Für die Ampere-Architektur sind die ROPs im GPC untergebracht. Pro GPC gibt es zwei ROP-Partitionen, die jeweils acht ROPs enthalten. 

Damit ergibt sich auch eine andere Berechnung für die Anzahl der ROPs bei der GeForce RTX 3080. Wir sprechen hier von sechs GPCs mit jeweils 2x 8 ROPs also 96 ROPs ingesamt. Für die GeForce RTX 3090 sind es sieben GPCs mit 2x 8 ROPs und dementsprechend 112 ROPs. NVIDIA hat die Integration der ROPs in dieser Form ausgeführt, um das Render-Backend nicht mehr derart abhängig vom Speicherinterface zu machen. So hat man nun zwar ein 320 Bit breites Speicherinterface für die GeForce RTX 3080 umgesetzt, kann aber 96 anstatt nur 80 ROPs verwenden.

Das Speicherinterface ist weiterhin in 32-Bit-Blöcken aufgeteilt. Je nach gewünschter Breite des Speicherinterfaces oder der Speicherkapazität die zum Einsatz kommen soll, kann hier in diesen Schritten gewählt werden.

Tensor und RT Cores

Tensor Cores der 3. Generation

Mit der Turing-Architektur führt NVIDIA zwei neue Recheneinheiten ein, die so bisher noch nicht auf einer GPU verwendet wurden. Zwar kennen wir die Tensor Cores bereits von der Volta-Architektur, hier wurden sie aber für wissenschaftliche Berechnungen verwendet. In der Ampere-Architektur kommt nun schon die dritte Generation der Tensor Cores zum Einsatz.

Ein Tensor Core ist darauf ausgelegt eine Matrix-Multiplikation durchzuführen. Matrix-Multiplikationen (BLAS GEMM) sind der wichtigste Bestandteil für das Training und Inferencing von Deep-Learning-Netzwerken. Für eine Matrixmultiplikation werden die Werte A0,0 und B0,0, A0,1 und B0,1, A0,2 und B0,2 sowie A0,3 und B0,3 jeweils multipliziert und aufaddiert. Das Ergebnis wird in C0,0 eingetragen. Für 4x4-Matrizen wird dies analog für alle 16 Datenfelder ausgeführt.

Konnten die Tensor Cores bisher nur für INT16- und FP16-Berechnungen verwendet werden, arbeitet die dritte Generation auch mit FP32 und FP64. Dies ist vor allem für die höhere Genauigkeit im HPC-Segment wichtig. Für die GeForce-GPUs aber spielt eine geringere Genauigkeit die wesentlich wichtigere Rolle.

So können die Tensor Cores der Turing-Architektur 64 FP16 Fused Multiply–Add (FMA) pro Tensor Core durchführen. Für Ampere sind es in vollgepackten Matrizzen 128 für die GA102-GPU und sogar 256 für die GA100-GPU. Kommt die Sparsity/Sparse Matrix oder die dünnbesetzte/schwachbesetzte Matrix hinzu, sind es sogar 256 FP16 FMA für die GA102-GPU und 512 für die GA100-GPU. Die Tensor Cores der Turing-Architektur unterstützen keine Sparsity.

RT Cores der 2. Generation

RT Cores sind weitere spezielle Hardwareeinheiten in der Ampere-Architektur, die aber schon in der Turing-Architektur zum Einsatz kamen. Die zweite Generation der RT Cores soll doppelt so viele Intersection-Berechnungen durchführen können wie ihre Vorgänger. Ein vollständiges Ray Tracing ist selbst mit mehreren tausend Shader- und sonstigen Hardwareeinheiten noch immer zu aufwendig. Daher nutzt NVIDIA in der Ampere-Architektur die RT Cores, um die für das Ray Tracing notwendigen Berechnungen ausführen zu können. Die RT Cores sind darauf ausgelegt, bestimmte Berechnungen besonders effektiv ausführen zu können. Bei allen Ray-Tracing-Techniken geht es derzeit noch darum, den Rechenaufwand zu reduzieren und dazu gibt es zahlreiche Techniken.

Allesamt zielen diese darauf ab, dass gewisse Primitives sowieso nicht in der Nähe des Strahls liegen und daher nicht getroffen werden können – sie sind in jedem Fall verdeckt und spielen für die Darstellung der Szene keine Rolle. Das exponentielle Wachstum der zu berechnenden Rays sorgt dafür, dass so wenige Primitives wie möglich getestet werden sollten, um den Rechenaufwand in Grenzen zu halten.

Eines dieser Verfahren ist die Bounding Volume Hierarchy (BVH). Bei der BVH wird eine Szene in immer kleinere Blöcke aufgeteilt und diese wiederum werden den Primitives zugewiesen. Letztendlich muss der Ray nur noch in den Blöcken betrachtet werden, die er auf dem Weg zum Primitiv kreuzt. Das Verfahren ähnelt den Voxeln, die NVIDIA für die Voxel Global Illumination (VXGI) verwendet. BVH verwendet nun eine Hierarchie, welche genau aufzeigt, für welchen Block und letztendlich welches Primitiv die Berechnung für das Ray Tracing durchgeführt werden muss.

BVH lässt sich auf klassischen GPU-Architekturen und auch auf CPUs nur in Software ausführen. Dies bedeutet für die Shader, dass diese pro Ray mehrere tausend Instruktionen berechnen müssen, die immer wieder mit Schleifen für das Auffinden der Sub-Box oder des letztendlichen Primitiv durchzogen sind. Erst am Schluss kann das eigentliche Shading für diesen Ray fortgesetzt werden. Hier kommen dann die RT Cores ins Spiel. Diese besitzen Special Function Units (SFU), die auf die Aufteilung der Sub-Boxen bis hin zum Primitiv und Evaluierung bei Auftreffen des Rays auf einem Primitiv, optimiert sind. Die Shader starten den Prozess zur Berechnung bzw. fragen diese an, der RT Core übernimmt die Berechnung und gibt sein Ergebnis an die Shader zurück, die dann wiederum ihre Rendering-Pipeline durchlaufen können.

Da NVIDIA für die Ampere-Architektur die Anzahl der RT Cores pro SM nicht verändert hat, ergibt sich durch die Mehrzahl der SMs pro GPU dennoch eine theoretische Skalierung der RT-Leistung. Es gibt aber auch weitere Verbesserungen in der Art und Weise wie die RT Cores funktioniere bzw. welche Berechnungsschritte sie ausführen.

Eine Herausforderung in der Berechnung der Intersections ist ein Raytracing auf Objekte, die sich bewegen und auf die auch ein Motion Blur angewendet wird. Für Turing-GPUs bzw. deren RT Cores konnte dies zu einem Flaschenhals in der Raytracing-Leistung werden. Die zweite Generation der RT Cores bietet nun parallel arbeitende Einheiten, die eine bessere Annäherung durch Interpolation in Motion Blur Effekten ermöglicht. Über eine zeitabhängige Funktion werden die Intersections vorausberechnet und das Raytracing nur noch auf Bereiche angewendet, die auch wirklich notwendig sind.

L1- und L2-Caches

Zwischen den Funktionseinheiten (Shadereinheiten, RT und Tensor Cores) und dem Grafikspeicher gibt es noch mindestens zwei weitere Speicherebenen, ohne die eine GPU nicht ihre Leistung würde abrufen können. Es geht bei den Caches darum, die notwendigen Informationen so nahe wie möglich an den Funktionseinheiten zu haben. Vom Grafikspeicher werden die Daten daher in den L2- und später in den L1-Cache übertragen.

Entsprechend der Ausbaustufe gibt es unterschiedliche Größen des L2-Caches in der Ampere-Architektur. Für die GeForce RTX 3080 beläuft sich dieser auf 5.120 kB, bei der GeForce RTX 3070 sind es 4.096 kB und die GeForce RTX 3090 kann auf 6.144 kB zurückgreifen.

NVIDIA hat für Ampere den L1-Data-Cache von 96 kB auf 128 kB vergrößert. Die Datenrate zum L1-Cache wurde abermals verdoppelt. Diese Maßnahme hatte NVIDIA schon einmal von Pascal zur Turing vollzogen. Der Register bleibt mit 16.384 Einträgen zu jeweils 32 Bit identisch. Dies gilt auch für die Anzahl der Load- und Store-Einheiten.

In Kooperation mit NVIDIA