• Keine Ergebnisse gefunden

Architektur und Programmierung von Grafik- und Koprozessoren

N/A
N/A
Protected

Academic year: 2022

Aktie "Architektur und Programmierung von Grafik- und Koprozessoren"

Copied!
18
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Architektur und Programmierung von Grafik- und Koprozessoren

General Purpose Programmierung auf Grafikprozessoren

Stefan Zellmann

Lehrstuhl f¨ur Informatik, Universit¨at zu K¨oln

SS2019

(2)

GPU Many-Core Skalierung

Thread-Blocks

I CUDA bildet parallele Programme aufuniforme Gitter ab.

I 1D: Wir sortieren N Zahlen. Dazu unterteilen wir die N Zahlen inBl¨ocke (Streifen) der Gr¨oße B ⇒ Gittergr¨oße:

G =dNBeBl¨ocke.

I 2D: Wir rendern ein Bild mitW ×H Pixeln. Dieses unterteilen wir in Bl¨ocke (Kacheln) der Gr¨oßeBx ×By Threads ⇒ Gittergr¨oße:Gx =dWB

xe,Gy =dBH

ye.

I 3D: Wir f¨uhren eine Berechnung auf einem CT Scan durch, dieser besteht aus Z Schichten mit Aufl¨osungX ×Y. Wir unterteilen in Bl¨ocke der Gr¨oße Bx ×By ×Bz und erhalten Gitter der Gr¨oße Gx =dBX

xe,Gy =dBY

ye,Gz =dBZ

ze.

(3)

GPU Many-Core Skalierung

Thread-Blocks

T(0,0) T(1,0)

T(0,1) T(1,1)

Block (0,0)

T(0,0) T(1,0)

T(0,1) T(1,1)

Block (0,1)

T(0,0) T(1,0)

T(0,1) T(1,1)

Block (1,0)

T(0,0) T(1,0)

T(0,1) T(1,1)

Block (1,1)

T(0,0) T(1,0)

T(0,1) T(1,1)

Block (2,0)

T(0,0) T(1,0)

T(0,1) T(1,1)

Block (2,1)

2D Gitter der Gr¨oße 3×2 mit Bl¨ocken von 2×2 Threads.

(4)

GPU Many-Core Skalierung

I GPGPU Programm bestehend aus M Bl¨ocken.

I N Cores (Nvidia:Streaming Multiprocessors (SM)) f¨uhrenM Bl¨ocke aus.

I Runtime Scheduler: Verteilung von Bl¨ocken auf Cores, evtl.

Lastverteilung (nicht n¨aher spezifiziert).

I Cores f¨uhren potentiell mehrere Bl¨ocke nacheinander aus.

I Andererseits: Applikation muss gen¨ugend Bl¨ocke bereitstellen, um Starvation zu vermeiden.

(5)

GPU Many-Core Skalierung

I Je nach Architektur: 16/32/.. Threads pro Block bilden eine Warp.

I Warps werden ¨ahnlich wie in SIMD Modell zusammen ausgef¨uhrt. Dynamisches Branching / Divergenz: Threads warten aufeinander, bis alle Threads alle Branches

abgearbeitet haben.

(6)

GPU Many-Core Skalierung

Runtime Scheduler

N Cores M Thread Blocks Block 0

Block 2

Block 1

Block 3

Block 4

Block 6

Block 5

Block 7

GPU Program

GPU

(7)

CUDA Speichermodell

I CUDA Speichermodell exponiert schnellen on-chip shared memory pro SM.

I Ahnlich als k¨¨ onnte man mit CPU direkt auf L1-Cache zugreifen.

I Threads / Warps auf einem SM m¨ussen bei Zugriffen synchronisiert werden.

I Je nach Architektur z. B. 16 kb, 64 kb o. ¨a.

I Alle Threads haben Zugriff auf globalen Speicher(DDR3). Je nach Architektur gecached oder nicht.

I Threads haben stark limitierte Anzahl an Registern (nicht explizit). Sind diese aufgebraucht⇒ Daten in lokalen Speicher (DDR3).

I Spezieller Speicherbereich f¨ur Konstanten (constant memory).

I Texturspeicher, gecached, schnelle lesende Zugriffe, nicht direkt adressierbar!

(8)

CUDA Speichermodell

Block

per-thread local memory

per-block shared memory

global memory (VRAM)

Block 0 Block 1

Block 2 Block 3

Grid

Thread

Abbildung:vgl. CUDA Toolkit Programming Guide.

(9)

CUDA Speichermodell

Registerspeicher

I vgl. Vorlesungseinheit 3: GPUs haben “riesige” Register Files.

Zum Vergleich:

I Tesla P100: 256 KB Register File pro Core/SM (64 K 32-bit Register)14,3 MB Registerspeicher(gesamteGPU) (!) I Intel Skylake: 180 Integer Register, 168 Floating Point Vektor

Register. Gr¨oße nicht ganz klar, vermutlich 256-bit oder 512-bit1. Obere Schranke,28-Core Skylake Prozessor: (348× 512-bit)ochstens 22 KB Registerspeicher(gesamte CPU).

I Warp Scheduler planen mehrere Warps auf SM. Zustand aller aktiven Warps verbleiben in Registern. Wartet eine Warp (z. B. wegen DDR Speicherzugriff), kann andere Warp geplant werden, die z. B. Arithmetik macht ⇒ wenig Kosten f¨ur Kontext Switch, da Zustand der Warps in Registern.

1http://www.agner.org/optimize/blog/read.php?i=962

(10)

CUDA Speichermodell

Registerspeicher

Da Zustand der aktiven Warps in Registern verbleibt, ist Umschalten zwischen Warps ausgesprochen schnell.

Registerallokation ohnehin schonschwierigesOptimierungsproblem.

Durch mehrere Warps nun noch komplizierter.

Compiler alloziert Register basierend auf Instruktionen in Compute Kernel. Zu große Kernels⇒ Register Spilling in DDR Speicher (“H¨ochststrafe”).

“Große” Kernel⇒ weniger Warps k¨onnen gleichzeitig ausgef¨uhrt werden.

Tool, um optimaleAuslastungbasierend auf Register Count zu berechnen: “Occupancy Calculator.xls”.

(11)

CUDA Speichermodell

Shared Memory

I Schneller on-chip Speicher, ¨ahnlich wie L1 Cache.

I Alle Threads, die gemeinsam auf SM ausgef¨uhrt werden, teilen sich Shared Memory.

I Speicher muss dediziert von Host alloziert werden (keine Speicherallokation aus GPU Programm selbst heraus).

I Zugriffe m¨ussen synchronisiert werden (eingebaute Funktion syncthreads(), s. u.).

I Niedrige Zugriffslatenz (ca. 30-90 Taktzyklen).

I Zum Vergleich: L1 Cache Hit auf CPU ca. 4-5 Taktzyklen.

(12)

CUDA Speichermodell

Globaler Speicher (DDR)

I Enorm hohe Bandbreite (Nvidia P100 z. B. bis zu 720 GB/s2) I Daf¨ur enorm hohe Latenz (200-800 Taktzyklen).

I Auf GPUs ist es wichtig, immer “gen¨ugend” Compute Instruktionen zu haben, um diese Latenz zu verstecken.

I Zugriffe auf globalen Speicher m¨ussen koaleszierend sein (benachbarte Threads lesen nicht aus und schreiben nicht in gleiche Speicherzelle).

I z. B. durch 16-Byte alignierte Datenstrukturen.

Sonst Bankkonflikte⇒ noch h¨ohere Latenz.

2https://devblogs.nvidia.com/beyond-gpu-memory-limits-unified-memory- pascal/

(13)

CUDA Speichermodell

Texturspeicher

I Spezieller, gecachter Speicher optimiert f¨ur lokale Speicherzugriffe (Implementierungsdetails unbekannt).

I 1D, 2D und 3D Texturen.

I Genau wie Grafik-APIs: bounds checking (Wrap, Clamp, Mirror etc.), Hardware Support f¨ur lineare Interpolation.

I Vor Kepler Architektur (GTX 680): fixe Anzahl an Texturen, sehr unflexibel. Seit Kepler: “Texture Objects” (a.k.a.

“Bindless Textures”).

I Vorher: Texture Atlas, um mehrere Texturen in eine zu packen.

I Heute: variable Anzahl GPU Texturen.

(14)

CUDA Speichermodell

Konstanter Speicher

Sehr kleiner (je nach Architektur etwa 64 kbinsgesamt), gecachter Speicher f¨ur Konstanten.

Konstanter Speicher in DDR3, je nach Architektur etwa 8 kb Cache pro SM.

Ein Thread liest von Speicheradresse, Broadcast an alle anderen Threads. Broadcast 4 Taktzyklen.

⇒verwende konstanten Speicher, wenn alle Threads in einem Block von der gleichen Speicheradresse lesen. Sonst Cache Misses (sehr teuer).

(15)

Kompilieren mit nvcc und CUDA Runtime API

__host__ h_func() {}

__device__ func() {}

__global__ kernel() { if (threadIdx.x == 0) func();

} int main() { kernel<<<1,1>>>();

}

CUDA Source

nvcc

__device__ func() {}

__global__ kernel() { if (threadIdx.x == 0) func();

}

Device Code

__host__ h_func() {}

int main() { kernel<<<1,1>>>();

}

Host Code

__host__ h_func() {}

int main() { cudaLaunch(..);

}

Patched Code

cudaConfigureCall(..);

start: .reg .b32 r1, r2;

PTX Code

.global .f32; Machine Code

Object Code Driver

Compiler Runtime

1. nvcc verarbeitet.cu Dateien mit Host- und Device Code I Device CodePTX (Nvidias GPU ISA).

I Host Codegepatchter Host Code, Routinen zum Laden von PTX und Aufruf von GPU Kernels.

2. Runtime: Treiber kompiliert PTX in Chip-spezifischen Maschinencode.

3. Host Programm ruft GPU Maschinencode auf.

(16)

Kompilieren mit nvcc und CUDA Runtime API

I Device Compiler Teil von nvcc ¨ubersetzt nach PTX (und alternativ nach cubin).

I Treiber ¨ubersetztzur Laufzeit PTX Code in Maschinencode (just-in-time (JIT) compilation).

I Maschinencode wird im User-Verzeichnis gecached, JIT nur bei erstem Programmstart nach Rekompilieren.

I JIT Compiler kann dediziert f¨ur Target-Platform / GPU Architektur optimieren.

I Ubersetzen nach cubin¨ ⇒PTX Code editierbar, kann nach nvcc Lauf handoptimiert werden.

I PTX unterst¨utzt 64-bit. Entweder gesamte Toolchain (Host &

Device) 32-bit oder 64-bit.

(17)

CUDA Programmiermodell

Single Instruction Multiple Thread(SIMT). ¨Ahnlich wie SIMD, jedoch nichtexplizit.

Keine expliziten SIMD Instruktionen. Thread Funktionen (Kernels) exponieren Instruktionsfluss eines einzelnen Threads.

Nvidia PTX ISA: keine SIMD opcodes (anders als AMD GPUs).

Implizit: alle Threads in Warp f¨uhren die gleichen Instruktionen aus. Betritt ein Thread aus der Warp einen Branch (if..else), warten alle anderen Threads inaktiv.

(18)

CUDA Programmiermodell

Auf einem SM laufen i. d. R. mehrere Warps gleichzeitig. Alle Threads/Warps auf SM haben geteilten (on-chip) Speicher (shared memory) (siehe CUDA Speichermodell)⇒Barrier Synchronisation einzelner Warps.

Kernels werden von CPU aus angestoßen. CPU initiiert auch Speichertransfers. Kernels und andere Operationen asynchron.

Referenzen

ÄHNLICHE DOKUMENTE

“Sampling” bestimmt. Dies ist eine in den Prozessor eingebaute Funktionalit¨ at, die, falls aktiviert, die Instruktionspipeline zu definierten Zeitpunkten anh¨ alt und

C++ spezifiziert keine Garbage Collection (der Standard schließt sie nicht explizit aus, aber es gibt keine Implementierungen, die mir bekannt sind). Daher m¨ ussen alle Ressourcen,

I Desktop GPUs implementieren traditionell Immediate-Mode Rendering: Verarbeitung orientiert sich an Reihenfolge, in der Dreiecke submittiert wurden (nicht zu verwechseln

I Vor und nach Skalarprodukt m¨ ussen Threads synchronisiert werden, damit nicht schon andere Threads aus Warp anderen Block in Shared Memory laden... I Seit Nvidia Kepler

Die Template Klasse sync queue in der beigef¨ ugten Datei queue.cpp wird von mehreren Threads gleichzeitig als Schlange zum Austauschen von Nachrichten genutzt. Intern verwaltet

(2) Die Bin¨ arrepr¨ asentation der sich ergebenden nat¨ urlichen Zahl k setzt sich immer abwechselnd aus den Bin¨ arziffern der Zahlpaare (i, j) zusammen. Tragen Sie das Ergebnis

Trifft man die vereinfachende Annahme, dass geometrische, aus Dreiecken zusammengesetzte Objekte geschlossen sind und immer nur von außen betrachtet werden, kann man vor dem

Der Ray Tracer geht im Weiteren davon aus, dass totale interne Reflektion aufgetreten ist, wenn refract() einen 0-Vektor zurückgegeben hat und ruft in dem Fall die Funktion