Comment les blocs / warps / threads CUDA sont-ils mappés sur les cœurs CUDA?

J’utilise CUDA depuis quelques semaines, mais j’ai des doutes quant à l’allocation de blocs / warps / thread. J’étudie l’architecture d’un sharepoint vue didactique (projet universitaire).

Tout d’abord, j’aimerais comprendre si j’ai bien compris ces faits:

  1. Le programmeur écrit un kernel et organise son exécution dans une grid de blocs de threads.

  2. Chaque bloc est affecté à un multiprocesseur de streaming (SM). Une fois assigné, il ne peut pas migrer vers un autre SM.

  3. Chaque SM divise ses propres blocs en Warps (actuellement avec une taille maximale de 32 threads). Tous les threads d’un warp s’exécutent simultanément sur les ressources du SM.

  4. L’exécution réelle d’un thread est effectuée par les kernelx CUDA contenus dans le SM. Il n’y a pas de correspondance spécifique entre les threads et les cœurs.

  5. Si un warp contient 20 threads, mais actuellement il n’y a que 16 cœurs disponibles, le warp ne fonctionnera pas.

  6. Par contre, si un bloc contient 48 threads, il sera divisé en 2 warps et ils s’exécuteront en parallèle à condition que la mémoire soit suffisante.

  7. Si un thread démarre sur un core, alors il est bloqué pour l’access à la mémoire ou pour une longue opération en virgule flottante, son exécution pourrait reprendre sur un autre core.

Sont-ils corrects?

Maintenant, je possède une GeForce 560 Ti. Selon les spécifications, elle est équipée de 8 SM, chacun contenant 48 cœurs CUDA (384 cœurs au total).

Mon but est de s’assurer que chaque kernel de l’architecture exécute les mêmes instructions. En supposant que mon code ne nécessite pas plus de registres que ceux disponibles dans chaque SM, j’ai imaginé différentes approches:

  1. Je crée 8 blocs de 48 threads chacun, de sorte que chaque SM dispose d’un bloc à exécuter. Dans ce cas, les 48 threads s’exécuteront-ils en parallèle dans le SM (en exploitant les 48 cœurs disponibles pour eux)?

  2. Y a-t-il une différence si je lance 64 blocs de 6 threads? (En supposant qu’ils seront mappés uniformément entre les SM)

  3. Si je “submerge” le GPU dans le travail planifié (création de 1024 blocs de 1024 thread chacun, par exemple), est-il raisonnable de supposer que tous les cœurs seront utilisés à un moment donné et effectueront les mêmes calculs (en supposant que les threads ne calez jamais)?

  4. Est-il possible de vérifier ces situations en utilisant le profileur?

  5. Y a-t-il une référence pour ce genre de choses? J’ai lu le guide de programmation CUDA et les chapitres consacrés à l’architecture matérielle dans “Programmation de processeurs massivement parallèles” et “Conception et développement d’applications CUDA”; mais je n’ai pas pu obtenir de réponse précise.

Deux des meilleures références sont

  1. Livre blanc sur l’architecture de calcul NVIDIA Fermi
  2. GF104 Avis

Je vais essayer de répondre à chacune de vos questions.

Le programmeur divise le travail en threads, les threads en blocs de thread et les blocs de thread dans les grids. Le dissortingbuteur de travaux de calcul alloue des blocs de threads aux stream multiprocesseurs (SM). Une fois qu’un bloc de thread est dissortingbué à un SM, les ressources du bloc de thread sont allouées (déformations et mémoire partagée) et les threads sont divisés en groupes de 32 threads appelés warps. Une fois qu’une chaîne est allouée, elle s’appelle une chaîne active. Les deux ordonnanceurs de chaîne sélectionnent deux chaînes actives par cycle et envoient des chaînes aux unités d’exécution. Pour plus de détails sur les unités d’exécution et la répartition des instructions, voir 1 p.7-10 et 2 .

4 ‘ . Il y a une correspondance entre laneid (index des threads dans une chaîne) et un core.

5 ‘ . Si un warp contient moins de 32 threads, il sera dans la plupart des cas exécuté de la même manière que s’il comportait 32 threads. Les Warps peuvent avoir moins de 32 threads actifs pour plusieurs raisons: le nombre de threads par bloc n’est pas divisible par 32, le programme exécute un bloc divergent pour que les threads qui ne prennent pas le chemin actuel soient marqués comme inactifs ou un thread dans le warp.

6 ‘ . Un bloc de thread sera divisé en WarpsPerBlock = (ThreadsPerBlock + WarpSize – 1) / WarpSize Il n’est pas nécessaire que les ordonnanceurs de chaîne sélectionnent deux warps du même bloc de thread.

7 ‘ . Une unité d’exécution ne bloquera pas une opération de mémoire. Si une ressource n’est pas disponible lorsqu’une instruction est prête à être envoyée, l’instruction sera à nouveau dissortingbuée lorsque la ressource sera disponible. Les warps peuvent se bloquer sur les barrières, sur les opérations de mémoire, sur les opérations de texture, sur les dépendances de données, etc. Sur Fermi, il est utile d’avoir au moins 2 warps éligibles par cycle afin que le programmateur warp puisse émettre une instruction.

Voir la référence 2 pour les différences entre un GTX480 et un GTX560.

Si vous lisez le matériel de référence (quelques minutes), je pense que vous constaterez que votre objective n’a pas de sens. Je vais essayer de répondre à vos points.

1 ‘ Si vous lancez le kernel <<< 8, 48 >>>, vous obtiendrez 8 blocs contenant chacun 2 chaînes de 32 et 16 threads. Il n’y a aucune garantie que ces 8 blocs seront affectés à différents SM. Si 2 blocs sont alloués à un SM, il est possible que chaque programmateur de chaîne puisse sélectionner une chaîne et exécuter la chaîne. Vous n’utiliserez que 32 des 48 cœurs.

2 ‘ . Il y a une grande différence entre 8 blocs de 48 threads et 64 blocs de 6 threads. Supposons que votre kernel ne présente aucune divergence et que chaque thread exécute 10 instructions.

  • 8 blocs avec 48 fils = 16 chaînes * 10 instructions = 160 instructions
  • 64 blocs avec 6 threads = 64 warps * 10 instructions = 640 instructions

Afin d’obtenir une efficacité optimale, la répartition des tâches devrait être de 32 threads. Le matériel ne fusionne pas les threads des différentes chaînes.

3 ‘ . Un GTX560 peut avoir 8 blocs SM * 8 = 64 blocs à la fois ou 8 SM * 48 warps = 512 warps si le kernel ne maximise pas les registres ou la mémoire partagée. À tout moment, une partie du travail sera active sur les SM. Chaque SM dispose de plusieurs unités d’exécution (plus de cœurs CUDA). Les ressources utilisées à un moment donné dépendent des ordonnanceurs de chaîne et du mélange d’instructions de l’application. Si vous ne faites pas d’opérations TEX, les unités TEX seront inactives. Si vous ne faites pas d’opération spéciale en virgule flottante, les unités SUFU seront inactives.

4 ‘ . Parallels Nsight et le Visual Profiler montrent

une. IPC exécuté

b. IPC délivré

c. chaînes actives par cycle actif

ré. Chaînes éligibles par cycle actif (Nsight uniquement)

e. des raisons de décrochage de la chaîne

F. threads actifs par instruction exécutée

Le profileur n’affiche pas le pourcentage d’utilisation des unités d’exécution. Pour GTX560, une estimation approximative serait IssuedIPC / MaxIPC. Pour MaxIPC, supposons que GF100 (GTX480) soit 2 GF10x (GTX560) soit 4 mais que la cible 3 soit une meilleure cible.

“E. Si une chaîne contient 20 threads, mais qu’il n’ya actuellement que 16 cœurs disponibles, la chaîne ne fonctionnera pas.”

est incorrect. Vous confondez les cœurs dans leur sens habituel (également utilisé dans les processeurs) – le nombre de “multiprocesseurs” dans un GPU, avec des cœurs dans nVIDIA marketing speak (“notre carte a des milliers de cœurs CUDA”).

Un Warp lui-même ne peut être programmé que sur un seul core (= multiprocesseur), et peut exécuter jusqu’à 32 threads simultanément; il ne peut pas utiliser plus d’un seul cœur.

Le nombre “48 warps” est le nombre maximum de warps actifs (chaînes pouvant être programmées pour être programmées pour le cycle suivant, à un cycle donné) par multiprocesseur, sur les GPU nVIDIA avec Compute Capability 2.x; et ce nombre correspond à 1536 = 48 x 32 threads.

Réponse basée sur ce webinary