- GPUs sind bei der Rechengeschwindigkeit dem Speicherzugriff weit überlegen, daher wird die Speicherhierarchie zum Performance-Flaschenhals
- Je nach Arithmetic Intensity (AI) werden Berechnungen in memory-bound oder compute-bound eingeteilt; der Schwellenwert der A100-GPU liegt bei etwa 13 FLOPs/Byte
- Zu den wichtigsten Strategien der Performance-Optimierung gehören Fusion und Tiling; Fusion reduziert unnötige Speicher-Roundtrips, Tiling maximiert die Datenwiederverwendung
- Für das Schreiben hochperformanter Kernel ist es wichtig, die strukturellen Eigenschaften der GPU-Hardware zu verstehen, etwa Synchronisierung, Coalesced Load und das Vermeiden von Bankkonflikten
- Weitere Aspekte wie Occupancy, Minimierung von Thread-Divergenz und Quantisierung haben erheblichen Einfluss auf die tatsächliche Performance
Compute- und Speicherhierarchie von GPUs
- GPUs haben im Allgemeinen eine deutlich höhere arithmetische Verarbeitungsleistung als Speicherbandbreite
- Zum Beispiel erreicht die NVIDIA A100 etwa 19,5 TFLOPS (32-Bit-Gleitkomma), während die Speicherbandbreite bei rund 1,5 TB/s liegt
- Während 4 Byte Daten gelesen werden, können Dutzende Rechenoperationen ausgeführt werden, daher ist Datenbewegung der Performance-Flaschenhals
- Global Memory (VRAM) ist ein langsamer Off-Chip-Speicher, in dem alle Daten liegen, während der Streaming Multiprocessor (SM) das Rechnen übernimmt
- Jeder SM besitzt einen schnellen On-Chip-Shared Memory (SRAM), der als vom Programm direkt verwalteter Cache genutzt werden kann
- Threads sind die kleinste Ausführungseinheit, und jeder Thread besitzt einen eigenen Registersatz
- 32 Threads bilden einen Warp, und ein Block ist ein Grid aus Threads, das auf demselben SM ausgeführt wird
Performance-Bereiche: memory-bound vs. compute-bound
- Die Performance eines Kernels ist entweder memory-bound (durch Datentransferrate begrenzt) oder compute-bound (durch die Rechenleistung des SM begrenzt)
- Arithmetic Intensity (AI) ist definiert als Total FLOPs / Total Bytes Accessed und ist eine wichtige Kennzahl
- Das Roofline-Modell stellt die tatsächlich erzielbare Performance eines Kernels in einem Diagramm mit AI auf der x-Achse und FLOPS/s auf der y-Achse dar
- Ist die AI niedrig und damit memory-bound, liegt die Performance auf der Diagonalen (durch die Speicherbandbreite begrenzt)
- Ist die AI hoch und damit compute-bound, liegt sie auf der Horizontalen (durch die maximale Rechenleistung begrenzt)
- Der Ridge Point der A100 ist 19,5 TFLOPS / 1,5 TB/s ≈ 13 FLOPs/Byte
- Wird die AI erhöht, steigt die Performance, und der Kernel kann den compute-bound-Bereich erreichen
Strategien zur Erhöhung der Arithmetic Intensity
- Einfaches Modell: Ein Thread berechnet genau ein C[i,j] → AI = 0,25 (sehr niedrig, memory-bound)
- Selbst wenn ein Thread ein 2x2-Tile berechnet, ist AI = 0,5 (immer noch niedrig)
- Um die AI zu erhöhen, müssen mehrere Threads blockweise große Tiles in den Shared Memory laden, um die Datenwiederverwendung zu maximieren
- Durch die Zusammenarbeit der Threads innerhalb eines Blocks kann AI > 13 erreicht werden, wodurch der compute-bound-Bereich möglich wird
Overhead-bound-Zustand
- Beim Zuweisen von Arbeit von der CPU (Host) an die GPU kann Overhead entstehen
- Sind GPU-Kernel zu klein oder zu zahlreich, wartet die GPU auf neue Arbeit
- Moderne Frameworks führen asynchrone Ausführung ein und queueen Command-Streams vorab, um den Overhead zu minimieren
Zwei zentrale Strategien zur Performance-Steigerung: Fusion und Tiling
Operator Fusion
- Bei einfachen Operationsketten, z. B.
y = relu(x + 1), verursacht jede Operation als separater Kernel Speicherverkehr zum und vom Global Memory
- Fusion fasst mehrere Operationen in einem einzigen Kernel zusammen, speichert Zwischenwerte nicht im Global Memory, sondern verarbeitet sie in Registern und schreibt nur das Endergebnis zurück
- Beispiele: JIT-Compiler wie Triton oder
torch.compile Inductor automatisieren dies
Tiling
- Bei komplexeren Operationen wie Matrixmultiplikation ist die AI im Single-Thread-Modell niedrig
- Nach dem Aufteilen in blockweise Tiles laden alle Threads im Block gemeinsam Datentiles in den Shared Memory und ermöglichen so umfangreiche Datenwiederverwendung
- Die Berechnung folgt dem dreistufigen Muster: "Load (Global Memory -> Shared Memory) - Synchronize - Compute"
Coalesced Load und Vektorisierung
- Beim Verschieben von Daten aus dem Global Memory in den Shared Memory ist Coalesced Access wichtig (32 Threads eines Warps greifen auf zusammenhängende 128-Byte-Bereiche zu)
- Durch Vektorisierung, z. B. mit
float4, können mehrere Datenwerte auf einmal geladen werden, was Hardware-Ressourcen spart und die Speicherbandbreitennutzung maximiert
- Daten-Alignment ist zwingend nötig; der K-Wert in Byte innerhalb einer Matrix sollte für Effizienz ein Vielfaches von 4 sein
Shared-Memory-Bänke und Bankkonflikte
- Shared Memory besteht aus 32 unabhängigen Bänken, daher sollten die 32 Threads eines Warps jeweils auf unterschiedliche Bänke zugreifen, um konfliktfrei zu arbeiten
- Zeilenweiser Zugriff verursacht keine Konflikte, spaltenweiser Zugriff dagegen schon (Zugriff auf dieselbe Bank)
- Für das B-Tile wird beim Laden eine Strategie vom Typ "Load and Transpose" verwendet, sodass es transponiert im Shared Memory gespeichert wird und bei der Berechnung überwiegend zeilenweise Zugriffe erfolgen, um Bankkonflikte zu vermeiden
Muster für schnelle On-Chip-Berechnungen
Grundstrategie 1: Ein Thread berechnet einen Output
- Unter der Begrenzung
BLOCK_DIM=32 liegt die maximale AI bei 8, daher ist der compute-bound-Bereich nicht erreichbar
Strategie 2: Ein Thread berechnet mehrere Outputs
- Bei
BLOCK_DIM=16 und TILE_DIM=64 berechnet ein Thread 4x4 Outputs → AI = 16
- Da AI > 13 ist, lässt sich auf einer A100 compute-bound-Performance erreichen
- Effiziente Berechnung ist mit vektorisierten Loads wie
float4 aus dem Shared Memory möglich
Praktische Grenzen des Tiling: Tile-Quantisierung
- Ist die Matrixgröße kein Vielfaches der Tile-Größe, berechnen Randblöcke Bereiche, die größer als nötig sind (unnötige Berechnung), und werden gepolstert
- Threads am Rand verhindern mit Guard-Bedingungen unnötige Speicherzugriffe, aber die Berechnungsschleife läuft identisch weiter, wodurch nutzlose Operationen entstehen (z. B.
C += A * 0)
Weitere Elemente des Performance-Tunings
Occupancy und Verbergen von Latenz
- Wenn ein Warp etwa auf Speicherzugriffe lange warten muss, schaltet der SM sofort auf einen anderen Warp um und reduziert so Leerlaufzeit (Latency Hiding)
- Werden mehrere Thread Blocks gleichzeitig zugewiesen, minimiert hohe Occupancy die Wartezeit
- Werden Block- oder Tile-Größen zu groß, sinkt die Zahl residenter Blocks, was die Occupancy reduziert und die Performance verschlechtert
Minimierung von Thread-Divergenz
- Kommt es innerhalb eines Warps zu if-else-Verzweigungen, werden beide Pfade nacheinander ausgeführt, wodurch die effektive Performance ungefähr halbiert wird
- Branchless Code mit
min, max usw. ist nötig, um Divergenz zu minimieren
Quantisierung
- Wird die Präzision von FP32 auf FP16/BFP16 reduziert, verdoppeln sich sowohl die bewegte Datenmenge pro Speichertransfer als auch die Menge verarbeitbarer Daten
- Auf einer A100 kann FP16-Rechenleistung 312 TFLOPS erreichen (gegenüber 19,5 TFLOPS bei FP32 relativ bis zu 16-fache Performance)
- Quantisierung kann im Roofline-Modell gleichzeitig eine Verschiebung nach rechts (Speichereffizienz) und nach oben (maximale Rechenleistung) bewirken
Gesamtzusammenfassung
- Die grundlegende Grenze der GPU-Performance ergibt sich aus dem Ungleichgewicht zwischen Speicherbandbreite und On-Chip-Rechenleistung
- Performance-Steigerung wird durch maximale Datenwiederverwendung (Tiling) und minimale Zwischenspeicher-Traffic-Kosten (Fusion) erreicht
- Um hochperformante Kernel zu schreiben und zu optimieren, müssen die Eigenschaften der Hardware-Struktur verstanden werden, darunter Warps, Bänke, Coalesced Access und Synchronisierung
- In der Praxis beeinflussen zusätzliche Faktoren wie Occupancy, Minimierung von Divergenz und Quantisierung die tatsächliche Geschwindigkeit direkt
- Das Design hochperformanter GPU-Berechnungen erfordert eine kombinierte Betrachtung aus theoretischer AI-Steigerung, Nutzung von Hardware-Eigenschaften sowie dem Umgang mit realen Datenlayouts und -größen
1 Kommentare
Hacker-News-Kommentare
Neugier, wie gut die Optimierung ganzer Programme auf Compiler-Ebene inzwischen funktioniert; die aktuelle Vorgehensweise, jede einzelne LLM-Architektur separat zu optimieren, wirkt irgendwie rückständig
Erfahrungsbericht über den Versuch, auf derselben 4070
llama.cppundvllmlaufen zu lassen, um mehr Prompts im Batch zu verarbeiten: Ab Batch 8 wurdellama.cppdrastisch langsamer, und obwohl die GPU-Auslastung ordentlich aussah, lag tatsächlich ein Bottleneck vor;vllmkam damit spürbar viel besser zurechtvllmnutzt einen paged KV-Cache und ein fully coalesced Layout, das die GPU bevorzugt, und liefert dadurch batch-optimierte Performance;llama.cppverwendet dagegen ein flaches Layout, das für einzelne Prompts gut ist, aber bei Batches die L2-Speicherzugriffsmuster zerstört und so zu Geschwindigkeitseinbußen führtGeteilte Erfahrung, dass in
llama.cppdurch Interleaving des KV-Tensors von[seq, head, dim]zu[head, seq, dim]die Art der Datenzufuhr zum fused attention kernel ausvllmnachvollzogen wurde und sich die Rechenleistung sofort etwa verdoppelteDer Bottleneck lag nicht an der GPU selbst, sondern daran, wie Shared-Memory-Zugriffe und Global Reads entworfen sind; genau dort setzt
vllmmit der Layout-Änderung anDie Analyse dieses Bottlenecks dauerte mehr als zwei Tage, war anhand der GPU-Auslastungsgrafen nicht erkennbar und wurde größtenteils nur durch Trial-and-Error verständlich
Es wird die Frage aufgeworfen, ob es eine Möglichkeit gibt, solche Experimente einfacher und iterativer per Hot Reload durchzuführen
Hinweis darauf, dass zwar gesagt wurde, die GPU sei nicht der Bottleneck, in Wirklichkeit aber die Ineffizienz des Speicherlayouts letztlich doch einen Bottleneck bei der Recheneffizienz der GPU verursachte
Erwähnung des gestern von einem DeepSeek-Mitarbeiter veröffentlichten Projekts
nano-vllm; mit nur 1200 Zeilen soll es schneller sein als vanillavllmhttps://github.com/GeeeekExplorer/nano-vllmFrage, ob das geänderte Layout in
llama.cppals Pull Request eingereicht wurde; eine Verdopplung wäre für alle ein großer GewinnEmpfehlung, auch das Projekt
ik_llama.cppauszuprobieren https://github.com/ikawrakow/ik_llama.cppEinschätzung, dass es sich um einen informativen Artikel handelt und dass der Inhalt eher davon handelt, welche Entscheidungen NVIDIA bei der Entwicklung der GPU-Architektur trifft; mit dem Hinweis, die Unterschiede zu anderen Anbietern nicht misszuverstehen
Zum Beispiel verschiebt sich beim AMD Instinct MI300 mit bis zu 160 TFLOPS bei FP32 und 6 TB/s HBM3/3E-Bandbreite der Ridge Point; das entspricht 27 FLOPs/Byte, also mehr als dem Doppelten des A100 mit 13 FLOPs/Byte. Der große HBM-Speicher (128–256 GB) verändert außerdem die realistischen Trade-offs zwischen Tiling-Tiefe und Occupancy. Solche GPUs sind allerdings teuer und bringen den Trade-off fehlender CUDA-Unterstützung mit sich
Meinung, dass NVIDIA-GPUs alternativlos sichtbar bleiben werden, solange AMD nicht deutlich mehr in Computing-Software investiert
Als Spoiler wird betont, dass letztlich nicht die Funktionsweise der GPU selbst entscheidend ist, sondern wie sie für Machine-Learning-Berechnungen genutzt wird
relu-Beispiel und der Erwähnung vontorchnicht viel mit Machine Learning zu tunMeinung, dass Kontrastfarben unbedingt verwendet werden sollten, mit Betonung auf Lesbarkeit
Erfahrungsbericht zur Nutzung von
font-weight: 300: Viele Mac-Designer entwickeln mit Blick auf die Font-Smoothing-Optionen und stellen Dinge so ein, dass sie im Allgemeinen wie „normal“ wirken; Macs lassen dünne Schriften halbwegs dicker erscheinen, weshalb Designer oft dünnere Fonts wählen, um einen „normalen“ Eindruck zu erzeugen; dazu wird ein passender Link geteilt https://news.ycombinator.com/item?id=23553486Vermutung, dass der Autor im Dark Mode editiert und formatiert hat; mit
edge://flags/#enable-force-darkseien die Links gut sichtbarHinweis, dass insbesondere Links und Kommentare in Code-Blöcken beim Lesen besonders anstrengend waren; Vorschlag, den Kontrast zu erhöhen, bei zugleich sehr positiver Bewertung der Inhaltsqualität
Kritik daran, dass die Website Alpha-Transparenz für Text verwendet und damit den Kontrast massiv verschlechtert
Vorschlag, dass ein Titel wie „Grundlegende Fakten über Nvidia-GPUs“ eigentlich passender wäre; mit der Erläuterung, dass der Begriff WARP ein Merkmal moderner Nvidia-GPUs ist und Nvidia-GPUs um 2003 noch reine Hardware für Videospiel-Rendering waren, also völlig anders als heutige GPUs für General-Purpose-Computing; zusammengefasst sei der Beitrag daher keine allgemein auf alle GPUs anwendbare Erklärung
Dank dafür, dass es sich um sehr gutes Einstiegsmaterial handelt; Rückblick, dass beim Selbstbau eines AI-PCs mehrere Tage lang zu GPUs recherchiert wurde und dieser Text die unverzichtbaren Kernthemen und hochgradig wertschöpfenden Anwendungsfelder wie Generative AI sehr gut zusammenfasse und deshalb enorm geholfen habe; besonders das Diagramm zur Speicherhierarchie der A100-GPU sei sehr nützlich gewesen
Verwunderung über die Verwendung von ASCII-Diagrammen