Was ist in CUDA das Zusammenwachsen des Gedächtnisses und wie wird es erreicht?


77

Was ist in der globalen CUDA-Speichertransaktion "verschmolzen"? Ich konnte es nicht verstehen, selbst nachdem ich meinen CUDA-Leitfaden durchgesehen hatte. Wie es geht? In einem Matrixbeispiel für eine CUDA-Programmieranleitung wird der zeilenweise Zugriff auf die Matrix als "zusammengeführt" oder als "zusammengeführt" bezeichnet. Welches ist richtig und warum?

Antworten:


153

Es ist wahrscheinlich, dass diese Informationen nur für die Berechnung von Capabality 1.x oder Cuda 2.0 gelten. Neuere Architekturen und cuda 3.0 verfügen über einen ausgefeilteren globalen Speicherzugriff, und tatsächlich werden "verschmolzene globale Lasten" für diese Chips nicht einmal profiliert.

Diese Logik kann auch auf den gemeinsam genutzten Speicher angewendet werden, um Bankkonflikte zu vermeiden.


Eine Koaleszenzspeichertransaktion ist eine Transaktion, bei der alle Threads in einem Half-Warp gleichzeitig auf den globalen Speicher zugreifen. Dies ist zu einfach, aber der richtige Weg, dies zu tun, besteht darin, aufeinanderfolgende Threads auf aufeinanderfolgende Speicheradressen zugreifen zu lassen.

Wenn die Threads 0, 1, 2 und 3 den globalen Speicher 0x0, 0x4, 0x8 und 0xc lesen, sollte dies ein zusammengeführtes Lesen sein.

Denken Sie in einem Matrixbeispiel daran, dass sich Ihre Matrix linear im Speicher befinden soll. Sie können dies tun, wie Sie möchten, und Ihr Speicherzugriff sollte die Anordnung Ihrer Matrix widerspiegeln. Also die 3x4 Matrix unten

0 1 2 3
4 5 6 7
8 9 a b

könnte Zeile für Zeile so gemacht werden, so dass (r, c) dem Speicher zugeordnet wird (r * 4 + c)

0 1 2 3 4 5 6 7 8 9 a b

Angenommen, Sie müssen einmal auf das Element zugreifen und sagen, Sie haben vier Threads. Welche Threads werden für welches Element verwendet? Wahrscheinlich auch nicht

thread 0:  0, 1, 2
thread 1:  3, 4, 5
thread 2:  6, 7, 8
thread 3:  9, a, b

oder

thread 0:  0, 4, 8
thread 1:  1, 5, 9
thread 2:  2, 6, a
thread 3:  3, 7, b

Welches ist besser? Was führt zu verschmolzenen Lesevorgängen und was nicht?

In beiden Fällen führt jeder Thread drei Zugriffe durch. Schauen wir uns den ersten Zugriff an und sehen, ob die Threads nacheinander auf den Speicher zugreifen. Bei der ersten Option ist der erste Zugriff 0, 3, 6, 9. Nicht aufeinanderfolgend, nicht zusammengeführt. Die zweite Option ist 0, 1, 2, 3. Aufeinander! Verschmolzen! Yay!

Der beste Weg ist wahrscheinlich, Ihren Kernel zu schreiben und ihn dann zu profilieren, um festzustellen, ob Sie nicht zusammengeführte globale Lasten und Speicher haben.


Vielen Dank für die Erklärung, welcher Thread auf welches Element zugreift. Derzeit habe ich die erste Option ( thread 0: 0, 1, 2 etc...), daher suche ich jetzt nach einer besseren Option :-)
Tim

@jmilloy - Ich möchte fragen, wie der Kernel profiliert werden soll, um nicht zusammengeführte globale Lasten und Speicher anzuzeigen.
Muradin

1
@muradin Können Sie den Visual Profiler verwenden? developer.nvidia.com/nvidia-visual-profiler
jmilloy

@jmilloy - Da ich in einer nicht grafischen Umgebung arbeite, habe ich nvprof im Befehlszeilenmodus gesucht und gefunden. Aber als ich es ausführen wollte, gab es einen Fehler: nvprof konnte libcuda.so.1 nicht laden. Es gibt keine solche Datei oder kein solches Verzeichnis! weißt du, warum?
Muradin

@jmilloy: Hallo, sehr schönes Beispiel! Danke! Ich wollte dich fragen, wenn du sagst, dass du den Profiler ausführen kannst, um zu sehen, ob du zusammengewachsen bist oder nicht, wie kannst du das tun? Zum Beispiel: nvprof --metrics gld_efficiency? Und je höher desto besser?
George

11

Memory Coalescing ist eine Technik, die eine optimale Nutzung der globalen Speicherbandbreite ermöglicht. Das heißt, wenn parallele Threads, die denselben Befehlszugriff auf aufeinanderfolgende Stellen im globalen Speicher ausführen, das günstigste Zugriffsmuster erreicht werden.

Geben Sie hier die Bildbeschreibung ein

Das Beispiel in der obigen Abbildung hilft bei der Erläuterung der zusammengeführten Anordnung:

In Fig. (A) sind n Vektoren der Länge m linear gespeichert. Das Element i des Vektors j wird mit v j i bezeichnet . Jeder Thread im GPU-Kernel ist einem Vektor mit einer Länge von m zugeordnet. Threads in CUDA sind in einem Array von Blöcken gruppiert, und jeder Thread in der GPU hat eine eindeutige ID, die definiert werden kann als indx=bd*bx+tx, wobei bddie bxBlockdimension die Blockindex und bezeichnettx der Threadindex in jedem Block ist.

Vertikale Pfeile zeigen den Fall, dass parallele Threads auf die ersten Komponenten jedes Vektors zugreifen, dh auf die Adressen 0, m , 2 m ... des Speichers. Wie in Fig. (A) gezeigt, ist in diesem Fall der Speicherzugriff nicht aufeinanderfolgend. Durch Nullstellen der Lücke zwischen diesen Adressen (rote Pfeile in der obigen Abbildung) wird der Speicherzugriff zusammengeführt.

Das Problem wird hier jedoch etwas knifflig, da die zulässige Größe der residenten Threads pro GPU-Block auf begrenzt ist bd. Daher kann eine koaleszierte Datenanordnung durchgeführt werden, indem die ersten Elemente der ersten bdVektoren in aufeinanderfolgender Reihenfolge gespeichert werden, gefolgt von den ersten Elementen der zweiten bd-Vektoren und so weiter. Die übrigen Vektorelemente werden auf ähnliche Weise gespeichert, wie in Fig. (B) gezeigt. Wenn n (Anzahl der Vektoren) kein Faktor von bdist, müssen die verbleibenden Daten im letzten Block mit einem trivialen Wert aufgefüllt werden, z. B. 0.

In der linearen Datenspeicherung in Fig. (A) wird die Komponente i (0 ≤ i < m ) des Vektors indx (0 ≤ indx < n ) angesprochen durch m × indx +i; Die gleiche Komponente in dem zusammengewachsenen Speichermuster in Fig. (b) wird angesprochen als

(m × bd) ixC + bd × ixB + ixA,

wo ixC = floor[(m.indx + j )/(m.bd)]= bx, ixB = jund ixA = mod(indx,bd) = tx.

Zusammenfassend wird im Beispiel des Speicherns einer Anzahl von Vektoren mit der Größe m die lineare Indizierung auf die koaleszierte Indizierung abgebildet, und zwar gemäß:

m.indx +i −→ m.bd.bx +i .bd +tx

Diese Datenumordnung kann zu einer signifikant höheren Speicherbandbreite des globalen GPU-Speichers führen.


Quelle: "GPU-basierte Beschleunigung von Berechnungen in der nichtlinearen Finite-Elemente-Deformationsanalyse." Internationale Zeitschrift für numerische Methoden in der biomedizinischen Technik (2013).


9

Wenn die Threads in einem Block auf aufeinanderfolgende globale Speicherorte zugreifen, werden alle Zugriffe von der Hardware zu einer einzigen Anforderung zusammengefasst (oder zusammengeführt). Im Matrixbeispiel sind die Matrixelemente in der Zeile linear angeordnet, gefolgt von der nächsten Zeile usw. Für z. B. 2x2-Matrix und 2 Threads in einem Block sind die Speicherorte wie folgt angeordnet:

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

Beim Zeilenzugriff greift Thread1 auf (0,0) und (1,0) zu, die nicht zusammengeführt werden können. Beim Spaltenzugriff greift Thread1 auf (0,0) und (0,1) zu, die zusammengeführt werden können, weil sie benachbart sind.


7
nett und prägnant, aber ... denken Sie daran, dass es bei coalesced nicht um zwei serielle Zugriffe von Thread1 geht, sondern um einen gleichzeitigen Zugriff von Thread1 und Thread2 parallel. Wenn in Ihrem Beispiel für den Zeilenzugriff Thread1 auf (0,0) und (1,0) zugreift, gehe ich davon aus, dass Thread2 auf (0,1) und (1,1) zugreift. Somit ist der erste parallele Zugriff 1: (0,0) und 2: (0,1) -> zusammengeführt!
Jmilloy

3

Die Kriterien für das Zusammenführen sind im CUDA 3.2-Programmierhandbuch ausführlich dokumentiert , Abschnitt G.3.2, gut dokumentiert. Die Kurzversion lautet wie folgt: Threads im Warp müssen nacheinander auf den Speicher zugreifen, und die Wörter, auf die zugegriffen wird, sollten> = 32 Bit sein. Zusätzlich sollte die Basisadresse, auf die der Warp zugreift, 64-, 128- oder 256-Byte sein, ausgerichtet für 32-, 64- bzw. 128-Bit-Zugriffe.

Tesla2- und Fermi-Hardware können 8- und 16-Bit-Zugriffe problemlos zusammenführen. Sie werden jedoch am besten vermieden, wenn Sie eine maximale Bandbreite wünschen.

Beachten Sie, dass das Koaleszieren trotz Verbesserungen der Tesla2- und Fermi-Hardware keinesfalls überholt ist. Selbst auf Hardware der Tesla2- oder Fermi-Klasse kann das Nichtzusammenführen globaler Speichertransaktionen zu einem doppelten Leistungseinbruch führen. (Auf Hardware der Fermi-Klasse scheint dies nur dann der Fall zu sein, wenn ECC aktiviert ist. Zusammenhängende, aber nicht zusammengeführte Speichertransaktionen verursachen bei Fermi einen Treffer von etwa 20%.)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.