Grundlegendes zu CUDA-Rasterdimensionen, Blockdimensionen und Thread-Organisation (einfache Erklärung) [geschlossen]


161

Wie sind Threads organisiert, um von einer GPU ausgeführt zu werden?


Das CUDA-Programmierhandbuch sollte ein guter Ausgangspunkt dafür sein. Ich würde auch empfehlen, die CUDA-Einführung von hier aus zu lesen .
Tom

Antworten:


287

Hardware

Wenn ein GPU-Gerät beispielsweise über 4 Multiprozessor-Einheiten verfügt und diese jeweils 768 Threads ausführen können, werden zu einem bestimmten Zeitpunkt nicht mehr als 4 * 768 Threads tatsächlich parallel ausgeführt (wenn Sie mehr Threads geplant haben, warten sie Sie sind dran).

Software

Threads sind in Blöcken organisiert. Ein Block wird von einer Mehrfachverarbeitungseinheit ausgeführt. Die Threads eines Blocks können mithilfe von 1Dimension (x) -, 2Dimensions (x, y) - oder 3Dim-Indizes (x, y, z) identifiziert (indiziert) werden. In unserem Beispiel ist jedoch in jedem Fall x y z <= 768 (es gelten andere Einschränkungen) zu x, y, z siehe Anleitung und Gerätefähigkeit).

Wenn Sie mehr als diese 4 * 768-Threads benötigen, benötigen Sie natürlich mehr als 4 Blöcke. Blöcke können auch 1D, 2D oder 3D indiziert werden. Es gibt eine Warteschlange von Blöcken, die darauf warten, in die GPU eingegeben zu werden (in unserem Beispiel verfügt die GPU über 4 Multiprozessoren und nur 4 Blöcke werden gleichzeitig ausgeführt).

Nun ein einfacher Fall: Verarbeiten eines 512x512-Bildes

Angenommen, ein Thread soll ein Pixel (i, j) verarbeiten.

Wir können Blöcke mit jeweils 64 Threads verwenden. Dann brauchen wir 512 * 512/64 = 4096 Blöcke (also 512x512 Threads = 4096 * 64)

Es ist üblich, die Threads in 2D-Blöcken mit blockDim = 8 x 8 (die 64 Threads pro Block) zu organisieren (um die Indizierung des Bildes zu vereinfachen). Ich nenne es lieber threadsPerBlock.

dim3 threadsPerBlock(8, 8);  // 64 threads

und 2D gridDim = 64 x 64 Blöcke (die benötigten 4096 Blöcke). Ich nenne es lieber numBlocks.

dim3 numBlocks(imageWidth/threadsPerBlock.x,  /* for instance 512/8 = 64*/
              imageHeight/threadsPerBlock.y); 

Der Kernel wird folgendermaßen gestartet:

myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );       

Endlich: Es wird so etwas wie eine "Warteschlange mit 4096 Blöcken" geben, in der ein Block darauf wartet, einem der Multiprozessoren der GPU zugewiesen zu werden, damit seine 64 Threads ausgeführt werden.

Im Kernel wird das von einem Thread zu verarbeitende Pixel (i, j) folgendermaßen berechnet:

uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;

11
Wenn jeder Block 768 Threads ausführen kann, warum nur 64 verwenden? Wenn Sie das maximale Limit von 768 verwenden, haben Sie weniger Blöcke und damit eine bessere Leistung.
Aliza

10
@Aliza: Blöcke sind logisch , das Limit von 768 Threads gilt für jede physische Verarbeitungseinheit. Sie verwenden Blöcke gemäß den Spezifikationen Ihres Problems, um die Arbeit auf die Threads zu verteilen. Es ist unwahrscheinlich, dass Sie für jedes Problem immer Blöcke mit 768 Threads verwenden können. Stellen Sie sich vor, Sie müssen ein 64x64-Bild (4096 Pixel) verarbeiten. 4096/768 = 5,3333333 Blöcke?
Cibercitizen1

1
Block sind logisch, aber jeder Block ist einem Kern zugeordnet. Wenn mehr Blöcke als der Kern vorhanden sind, werden die Blöcke in die Warteschlange gestellt, bis die Kerne frei werden. In Ihrem Beispiel können Sie 6 Blöcke verwenden und die zusätzlichen Threads nichts tun lassen (2/3 der Threads im 6. Block).
Aliza

3
@ cibercitizen1 - Ich denke, Alizas Punkt ist gut: Wenn möglich, möchte man so viele Threads pro Block wie möglich verwenden. Wenn es eine Einschränkung gibt, die weniger Threads erfordert, ist es besser, in einem zweiten Beispiel zu erklären, warum dies der Fall sein könnte (aber zuerst den einfacheren und wünschenswerteren Fall zu erklären).

6
@thouis Ja, vielleicht. Der Fall ist jedoch, dass die von jedem Thread benötigte Speichermenge anwendungsabhängig ist. In meinem letzten Programm ruft beispielsweise jeder Thread eine Optimierungsfunktion für kleinste Quadrate auf, die "viel" Speicher benötigt. So viel, dass Blöcke nicht größer als 4x4-Threads sein können. Trotzdem war die erzielte Beschleunigung im Vergleich zur sequentiellen Version dramatisch.
Cibercitizen1

9

Angenommen, eine 9800GT-GPU:

  • Es verfügt über 14 Multiprozessoren (SM)
  • Jeder SM hat 8 Thread-Prozessoren (AKA-Stream-Prozessoren, SP oder Kerne).
  • erlaubt bis zu 512 Threads pro Block
  • Warpsize ist 32 (was bedeutet, dass jeder der 14x8 = 112 Thread-Prozessoren bis zu 32 Threads planen kann)

https://www.tutorialspoint.com/cuda/cuda_threads.htm

Ein Block kann nicht mehr aktive Threads als 512 haben, daher __syncthreadskann nur eine begrenzte Anzahl von Threads synchronisiert werden. dh wenn Sie Folgendes mit 600 Threads ausführen:

func1();
__syncthreads();
func2();
__syncthreads();

dann muss der Kernel zweimal ausgeführt werden und die Ausführungsreihenfolge lautet:

  1. func1 wird für die ersten 512 Threads ausgeführt
  2. func2 wird für die ersten 512 Threads ausgeführt
  3. func1 wird für die verbleibenden Threads ausgeführt
  4. func2 wird für die verbleibenden Threads ausgeführt

Hinweis:

Der Hauptpunkt ist __syncthreadseine blockweite Operation, bei der nicht alle Threads synchronisiert werden.


Ich bin mir nicht sicher, wie viele Threads __syncthreadsgenau synchronisiert werden können, da Sie einen Block mit mehr als 512 Threads erstellen und den Warp die Planung übernehmen lassen können. Nach meinem Verständnis ist es genauer zu sagen: func1 wird zumindest für die ersten 512 Threads ausgeführt.

Bevor ich diese Antwort bearbeitet habe (im Jahr 2010), habe ich gemessen, dass 14x8x32-Threads mit synchronisiert wurden __syncthreads.

Ich würde mich sehr freuen, wenn jemand dies erneut testen würde, um genauere Informationen zu erhalten.


Was passiert, wenn func2 () von den Ergebnissen von func1 () abhängt? Ich denke, das ist falsch
Chris

@Chris Ich habe das vor sieben Jahren geschrieben, aber wenn ich mich richtig erinnere, habe ich dies getestet und bin zu dem Schluss gekommen, dass sich Kernel mit mehr Threads als GPU so verhalten. Wenn Sie diesen Fall testen und ein anderes Ergebnis erzielen, muss ich diesen Beitrag löschen.
Bizhan

Entschuldigung, ich denke, das ist auch falsch, dass die GPU nur 112 Threads gleichzeitig ausführen kann.
Steven Lu

@StevenLu hast du es versucht? Ich denke auch nicht, dass 112 gleichzeitige Threads für eine GPU Sinn machen. 112 ist die Anzahl der Stream-Prozessoren. Ich kann mich jetzt kaum an CUDA erinnern :)
Bizhan

1
@StevenLu Die maximale Anzahl von Threads ist hier nicht das Problem, __syncthreadsist eine blockweite Operation und die Tatsache, dass nicht alle Threads synchronisiert werden, ist ein Ärgernis für CUDA-Lernende. Also habe ich meine Antwort basierend auf den Informationen, die Sie mir gegeben haben, aktualisiert. Ich weiß das wirklich zu schätzen.
Bizhan,
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.