[D3D11] (erledigt) Optimale Gruppengröße für Compute-Shader

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8261
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

[D3D11] (erledigt) Optimale Gruppengröße für Compute-Shader

Beitrag von Krishty »

Hi,

Ich erledige per Compute-Shader eine Aufgabe, die keine geteilten Daten erfordert – also straight-forward perfekt parallel, wie man es von Fullscreen-Quads kennt. Ich bin deshalb davon ausgegangen, dass eine Gruppengröße von 1×1 und ein Dispatch mit der Anzahl der Pixel ausreichend wäre. (GPU startet so viele Threads wie ich Pixel habe – alle Prozessoren mit je einem Pixel ausgelastet, bis alle Pixel durch sind – Leben schön.)

Nun ist die Performance aber unter aller Sau und ich muss relativ große Gruppen (>8×8 Pixel) benutzen, um auch nur in die Nähe der Performance eines Full-Screen-Quads zu kommen. Könnt ihr mir erklären, warum? Soweit ich das verstanden habe, sollten Gruppen ja nur Performance-Vorteile bringen, wenn Daten untereinander geteilt werden können (z.B. um Texture-Samples zu fetchen), was aber bei mir nicht der Fall ist.

Falls das Hardware-spezifisch ist, ist doch das ganze Konzept für den Popo, weil man a) jeden Shader für jede Karte optimieren muss und b) im Falle von Postprocessing nicht mehr beliebig große Backbuffer benutzen kann, sondern immer nur ein Vielfaches der Gruppengröße?

Gruß, Ky

Edit:
DCPerf.png
DCPerf2.png
(Quelle) Yay. Ich freue mich jetzt schon drauf. Es wäre ja auch viel zu einfach gewesen, schon mit D3D9 ein IDirect3DDevice9::FillRenderTargetWithShader(::IDirect3DRenderTarget9 * ToBeFilled, ::IDirect3DPixelShader9 * WithThis) einzuführen … wo bleibt denn der Spaß, wenn man nicht die Wahl hat, entweder für jede Kartengeneration einen neuen Compute-Shader zu kompilieren oder ganz klassisch ein Fullscreen-Quad zu rendern und dafür Input-Layout, Vertex-Shader, Geometry-Shader, Pixel-Shader, Blend-State, Depth-Stencil-State und Rasterizer-State setzen zu müssen? Zumal es ja so gut wie keinen Fall gibt, wo man eine Textur oder den Bildschirm mal komplett überschreiben will. Die nächste Runde Kotzen geht auf mich!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4864
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Schrompf »

Nimm den Weg über ein Fullscreen-Quad, pack alles in ne Klasse und denk nicht mehr drüber nach! Auf die Art gibt's auch schöne saubere Schnittstellen :-)
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8261
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Krishty »

Schrompf hat geschrieben:Nimm den Weg über ein Fullscreen-Quad, pack alles in ne Klasse und denk nicht mehr drüber nach! Auf die Art gibt's auch schöne saubere Schnittstellen :-)
Hatte ich vorher, ist aber für den Popo. Saubere Trennung zwischen dem Quad und dem Nutzer ist nicht möglich, weil das Quad Input-Layout, Vertex-Shader, Geo-Shader, Rasterizer-State, Blend-State und Depth-Stencil-State setzen muss und der Nutzer dem gegenüber Pixel-Shader, Konstanten und Ressourcen. Dann kommt man in die Bredouille, ob das Quad all diese States nach dem Rendern wiederherstellen können sollte (was verschwenderisch, aber angenehm ist) oder nicht (was schnell ist, aber anstrengend). Mit Compute-Shaders braucht man keinen State zu ändern – die Render-Pipeline bleibt vollkommen unangetastet – sondern ruft statt Quad.Draw() Dispatch() auf und ist fertig. Das hat eine bestechende Anmut, die es mir sofort angetan hat.

Full-Screen-Quads benutze ich nurnoch, wenn ich Sub-Samples intakt halten muss – Multisampling können Compute-Shaders nämlich nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von eXile »

Mhh, dann bleibt noch die Frage nach dem Warum. Warum ist gerade die Gruppengröße für die Performance entscheidend? Was ist da genau im Treiber und der Architektur los?
Benutzeravatar
Krishty
Establishment
Beiträge: 8261
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Krishty »

Gute Frage, ich kann es nicht nachvollziehen.

Hoffentlich arbeite ich mal an einem AAA-Titel. Eines nachts kurz vor dem Release schleiche ich mich ins Shader-Verzeichnis und setze überall die Gruppengröße auf 1, 1, 1. Die Kunden sind daran gewöhnt, dass Spiele bis zum vierten Patch nicht flüssig laufen; das wird also niemanden stören. Aber wenn ATI und Nvidia dann für Benchmark-Fights ihre Treiber optimieren und sich dafür die Shader angucken, werden sie die Hände über dem Kopf zusammenschlagen und für diesen Fall eine Optimierung, bzw ein Fallback auf Pixel-Shader-Verhalten durchdrücken. Nur zwei oder drei Jungentwicklern ist mein Tun aufgefallen – aber sie trauten sich nicht, was zu sagen und beobachteten, wie sich mein Plan entfaltete. Um mir zum triumphalen Sieg der Vernunft zu gratulieren und dafür zu danken, dass ich ihnen gezeigt habe, dass auch Einzelne die IT-Welt ein Stückchen besser machen zu können, suchen sie mein Apartement auf – aber es ist leer. Sie hören eine Autotür zuschlagen, laufen zum Fenster und sehen ein Auto gen Sonnenuntergang fahren.

Oder die Performance ist absolut furchtbar, im Commit-Log steht mein Name und ich werde im Keller des Studios gelyncht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Aramis »

Nope, du wirst in den Kundensupport versetzt.
Benutzeravatar
Lynxeye
Establishment
Beiträge: 145
Registriert: 27.02.2009, 16:50
Echter Name: Lucas
Wohnort: Hildesheim
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Lynxeye »

eXile hat geschrieben:Mhh, dann bleibt noch die Frage nach dem Warum. Warum ist gerade die Gruppengröße für die Performance entscheidend? Was ist da genau im Treiber und der Architektur los?
Die Antwort steht schon auf der einen Folie mit drauf: Wenn die Gruppengröße größer ist als die Wavefrontgröße der GraKa, kann der Hardwaredispatchprozessor immer genügend Threads im Flug halten um die Speicherlatenzen optimal zu verstecken. Und da GPUs keine Caches haben um die Latenzen zu kaschieren ist das ein richtig großer Punkt in der Gesamtperformance.
Benutzeravatar
Krishty
Establishment
Beiträge: 8261
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Krishty »

Ich verstehe nur nicht, warum er das nicht kann. Beim Rendern von Geometrie kann er die Threads von den 2×2-Gruppen, in denen die Pixel verarbeitet werden, ja auch vernünftig aufteilen. Wo ist der fundamentale Unterschied? Nicht einmal mit Lokalität der Ergebnisse lässt sich noch argumentieren, jetzt, wo Scattering erlaubt ist. Wenn also das Full-Screen-Quad 2m Pixel gerendert haben will, was macht den Unterschied um den Faktor zehn zu einem Dispatch auf 2m Pixel-Blöcke?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Lynxeye
Establishment
Beiträge: 145
Registriert: 27.02.2009, 16:50
Echter Name: Lucas
Wohnort: Hildesheim
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Lynxeye »

Beim rendern von Geometrie werden von den ROPs automatisch 8x8 (bei den Radeons) große Tiles erzeugt, auf die dann die Fragment Shader losgelassen werden. Sobald du Compute-Shader verwendest fallen die ROPs komplett aus der Pipeline und du musst somit per Hand für die richtige Aufteilung sorgen.
Benutzeravatar
Krishty
Establishment
Beiträge: 8261
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Krishty »

Okay, wir kommen der Sache näher. Kannst du mir noch genau sagen, was du mit ROPs meinst? Den Rasterizer? Mein Wissen über die GPU-Architektur stammt noch aus der Ära 2007, und da kamen die Raster Operation Units nach den Shadern, also im Output Merger :/
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von eXile »

Für alle, die mit dem Begriff wavefront hier überhaupt nichts anfangen konnten:
A warp in CUDA, then, is a group of 32 threads, which is the minimum size of the data processed in SIMD fashion by a CUDA multiprocessor.
Und wavefront ist einfach die AMD-Bezeichnung für warp.
Lynxeye hat geschrieben:und du musst somit per Hand für die richtige Aufteilung sorgen.
Warum kann der Graphikprozessor große Threadgroups auf die verarbeitenden Kerne aufteilen, aber nicht mehrere kleine Threadgroups auf einen Kern zusammenfassen?
Alexander Kornrumpf
Moderator
Beiträge: 2116
Registriert: 25.02.2009, 13:37

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Alexander Kornrumpf »

Warum kann der Graphikprozessor große Threadgroups auf die verarbeitenden Kerne aufteilen, aber nicht mehrere kleine Threadgroups auf einen Kern zusammenfassen?
Wenn die GPU die ganze Scheduling Logik einer CPU hätte wäre sie eine CPU.
Benutzeravatar
Lynxeye
Establishment
Beiträge: 145
Registriert: 27.02.2009, 16:50
Echter Name: Lucas
Wohnort: Hildesheim
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Lynxeye »

Krishty hat geschrieben:Okay, wir kommen der Sache näher. Kannst du mir noch genau sagen, was du mit ROPs meinst? Den Rasterizer? Mein Wissen über die GPU-Architektur stammt noch aus der Ära 2007, und da kamen die Raster Operation Units nach den Shadern, also im Output Merger :/
Ja, ich meine damit die Raster Operation Units. Bei einer Unified Shader Architektur ist das zwar nicht mehr ganz so deutlich zu sehen, aber diese liegen in der Grafikpipeline zwischen den Vertex und Fragment Shadern.

Wenn du also ein Fullscreen Quad renderst, läuft das erst durch den VS, dann durch die ROPs, wird dort in die 8x8 Pixel Blöcke aufgeteilt und landet in dieser Konstellation bei den FS, wo du ja dann deine Operationen ausführst.

Bei der Compute Shader Ausführung wird im Sinne der Effizienz der Schritt VS und ROP weggelassen, dort werden die Programme direkt in einer Shader Stufe ausgeführt. Dafür musst du aber halt die Aufteilung selbst erledigen.
eXile hat geschrieben:Warum kann der Graphikprozessor große Threadgroups auf die verarbeitenden Kerne aufteilen, aber nicht mehrere kleine Threadgroups auf einen Kern zusammenfassen?
Weil du dann in Hardware die gesamte Shedulerlogik nachbauen müsstet, die heute auf CPUs das Betriebssystem bietet. Und diese Hardware müsste dann insgesamt 320 Kerne bei den aktuellen Radeons verwalten. Damit würde man mehr Transistoren für die Steuerlogik verbraten als für die Recheneinheiten. Keine gute Idee, oder?
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von eXile »

Lynxeye hat geschrieben:Weil du dann in Hardware die gesamte Shedulerlogik nachbauen müsstet, die heute auf CPUs das Betriebssystem bietet. Und diese Hardware müsste dann insgesamt 320 Kerne bei den aktuellen Radeons verwalten. Damit würde man mehr Transistoren für die Steuerlogik verbraten als für die Recheneinheiten. Keine gute Idee, oder?
Das ist doch mal ein Grund! :) Auch wenn natürlich Nvidia anscheinend in die nächsten Generationen (Maxwell und Kepler) wieder viel reinstopfen wollen:
Jen-Hsun Huang hat geschrieben:Between now and Maxwell, we will introduce virtual memory, pre-emption, enhance the ability of the GPU to autonomously process, so that it's non-blocking of the CPU, not waiting for the CPU, relies less on the transfer overheads that we see today. These will take GPU computing to the next level, along with a very large speed up in performance,
Nachtrag: Wenn man also eine maximale Unabhängikeit bei den zu verarbeitenden Threads darstellen will, müsste man also statt (1,1,1) theoretisch (Bildschirmauflösung in x-Richtung, Bildschirmauflösung in y-Richtung,1) schreiben Siehe Krishtys Antwort; in der Praxis natürlich mit Rumprobiererei bessere Größen herausfinden.
Zuletzt geändert von eXile am 26.09.2010, 16:34, insgesamt 1-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8261
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Krishty »

Okay, die Beiträge bringen mich weiter, aber warum die Scheduler-Logik dann so enorm wäre, weiß ich immernoch nicht. Darum aus eigener Recherche:

Nvidias Fermi benutzt 16 Streaming Multiprocessor Units, von denen jede 32 CUDA Cores enthält verwaltet. Also 16 Mal das, was eigenen Speicher besitzt, seine Arbeit an 32 Rechenkerne dirigiert und man in der CS-Welt durch eine Thread Group beschreibt.

Bei ATIs Radeon HD 5000-Serie arbeiten 20 Shader Cluster, die jeweils aus 16 Shader Units bestehen. Also 20 Thread Groups, respektive.

Wenn man für einen Compute Shader Dispatch() aufruft, gibt man nicht an, wie viele Threads man haben will, sondern wie viele Streaming Multiprocessor Units (Nvidia) bzw. Shader Cluster (ATI) man anschmeißen will. Dispatch(screen_width, screen_height, 1) bedeutete also, dass man für jeden Pixel auf dem Bildschirm eine Streaming Multiprocessor Unit oder ein Shader Cluster anschmisse, die/der einen einzelnen Thread ausführen würde, während die übrigen 31 CUDA Cores (Nvidia) oder 15 Shader Units (ATI) darin leer liefen.

Umgekehrt würde eXiles Dispatch(1, 1, 1) bedeuten, dass 15 Streaming Multiprocessor Units oder 19 Shader Clusters leerliefen, während sich 32 CUDA Cores oder 16 Shader Units durch alle Pixel des Bildschirms wühlen müssten, was genauso langsam wäre als das andere Extrem. Compute Shaders unterbinden das, indem eine Thread-Gruppe aus nicht mehr als 1024 Threads bestehen darf – mehr als 32×32 Pixel pro Thread-Gruppe sind also eh nicht kompilierbar.

Die goldene Mitte, nämlich genau so viele Threads pro Gruppe, wie die Hardware hat, ist wiederum wegen der von Lynxeye genannten Speicherlatenzen ineffizient.

Um das Ganze also architekturabhängig zu machen, hätte man einbauen müssen:
  • dass das numthreads-Attribut weggelassen werden darf
  • eine Funktion DispatchThreads(), die anhand der Anzahl an Thread Groups und ihrer jeweiligen Rechenkerne der verbauten Hardware die optimale Gruppengröße bestimmt und die Shader darin gruppiert zur Hardware weitergegeben hätte
  • und dass nur Shader ohne numthreads-Attribut mit DispatchThreads() ausgeführt werden können.
Das wäre tatsächlich ein wenig viel Aufwand geworden. Darum ist das am wenigsten von Architektur und GPU-Entwicklung abhängige Vorgehen:
  • Hat man hohe Auflösungen, z.B. für Postprocessing, möglichst alle 1024 Threads ausnutzen. Das hält die GPUs z.B. bei 1080p-Full-HD so lange in Schach, bis Modelle mit mehr als 2000 Shader Clusters herauskommen, was noch dauern dürfte (wird natürlich schon vorher langsam ineffizienter, aber wir wollen ja keine Erbsen zählen).
  • Hat man niedrige Auflösungen, z.B. für kleine Texturen und Physikberechnungen – so, dass man mit 1024 Threads nicht mehr alle SMUs bzw. SCs auslasten kann – dann vertraut man darauf, dass Thread Groups auch in Zukunft nicht mehr als 32 Kerne dirigieren können werden (weil ja sonst der Verwaltungsaufwand zu groß wird) und nimmt eine Zahl wie 128, um trotz Speicherlatenz und Stalls eine gute Auslastung pro Gruppe bei möglichst vielen parallelen Gruppen zu erreichen.
  • Bei echten CS-Anwendungen ist die Gruppengröße eh davon abhängig, welche Daten untereinander geteilt werden müssen, da kann man dieses Wissen nur für Feintuning nutzen.
Bleibt das Problem, dass die Größe des Ziels ein Vielfaches der Gruppengröße sein muss. Ist es legal, einfach aufzurunden und die überschüssigen Pixel mitzunehmen? Zumindest auf meiner HD 5700 kann ich beliebig über den Rand hinausschreiben und es passiert nichts – aber ich habe keine Lust darauf, dass es nach dem nächsten Treiber-Update oder einer anderen Karte kracht …

Edit: ATI hat natürlich keine 320 Shader Cluster, sondern 20. 320 waren die Shader Units.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Jörg »

Lynxeye hat geschrieben:Und da GPUs keine Caches haben um die Latenzen zu kaschieren ist das ein richtig großer Punkt in der Gesamtperformance.
Das ist Quatsch, natuerlich haben GPUs einen Cache. Und meist nicht nur einen :) Der richtig grosse Punkt ist, genug zu rechnen, dass man trotz Speicherdurchsatzlimitierung die Karte wenigstens auslastet *g* Also lieber weniger lesen und schreiben, und dafuer (wenn moeglich) mehrere Berechnung in einem CS zusammenfassen. Denn selbst wenn du viele Threads hast, sobald alle auf den Speicher warten, nuetzt dir auch guter Scheduler nicht mehr so viel. Es sei denn, er hat ein paar "auf Halde", die noch was rechnen koennen...
Benutzeravatar
Lynxeye
Establishment
Beiträge: 145
Registriert: 27.02.2009, 16:50
Echter Name: Lucas
Wohnort: Hildesheim
Kontaktdaten:

Re: [D3D11] (erledigt) Optimale Gruppengröße für Compute-Sha

Beitrag von Lynxeye »

Sorry, ich habe mich da wahrscheinlich nicht klar genug ausgedrückt.

Natürlich haben GPU Caches. Diese verkürzen aber nicht die Speicherlatenzen, sondern mindern nur den Bandbreitenbedarf zum Speicher. Das ist ein vollkommen anderer Ansatz als bei den CPUs, dort ist die Speicherbandbreite groß genug, allerdings limitieren dort die Latenzen, da eben nicht eine riesige Anzahl an Threads auf Ausführung warten. Wenn ich dazu komme suche ich noch mal den Link raus, in dem NVidia dies ausführlich erläutert hat.

Edit:
Steht im CUDA Programming Guide. Punkt 5.3.2.5
[...]a cache hit reduces DRAM bandwidth demand but not fetch latency.
Antworten