J'utilise CUDA depuis quelques semaines, mais j'ai quelques doutes sur l'allocation de blocs/warps/thread. J'étudie l'architecture d'un point de vue didactique (projet universitaire), atteindre des performances optimales n'est donc pas ma préoccupation.
Tout d’abord, j'aimerais comprendre si j’ai bien compris ces faits:
Le programmeur écrit un noyau et organise son exécution dans une grille de blocs de thread.
Chaque bloc est affecté à un multiprocesseur de streaming (SM). Une fois attribué, il ne peut pas migrer vers un autre SM.
Chaque SM divise ses propres blocs en Warps (avec actuellement une taille maximale de 32 threads). Tous les threads d'une chaîne s'exécutent simultanément sur les ressources du SM.
L'exécution réelle d'un thread est effectuée par les CUDA Cores contenus dans le SM. Il n'y a pas de mappage spécifique entre les threads et les cœurs.
Si une chaîne contient 20 unités d'exécution mais qu'il n'y a actuellement que 16 cœurs disponibles, elle ne s'exécutera pas.
Par contre, si un bloc contient 48 threads, il sera divisé en 2 warps et ils s'exécuteront en parallèle, à condition que suffisamment de mémoire soit disponible.
Si un thread démarre sur un coeur, alors qu'il est bloqué pour un accès à la mémoire ou pour une longue opération à virgule flottante, son exécution pourrait reprendre sur un autre coeur.
Sont-ils corrects?
Maintenant, j'ai une GeForce 560 Ti donc, selon les spécifications, elle est équipée de 8 SM, chacune contenant 48 cœurs CUDA (384 cœurs au total).
Mon objectif est de veiller à ce que chaque cœur de l’architecture exécute les mêmes instructions. En supposant que mon code ne nécessite pas plus de registre que ceux disponibles dans chaque SM, j'ai imaginé différentes approches:
Je crée 8 blocs de 48 threads chacun, de sorte que chaque SM ait 1 bloc à exécuter. Dans ce cas, les 48 threads seront-ils exécutés en parallèle dans le SM (exploitant tous les 48 cœurs disponibles pour eux)?
Y at-il une différence si je lance 64 blocs de 6 threads? (En supposant qu'ils seront cartographiés de manière égale entre les SM)
Si je "submerge" le GPU dans un travail planifié (en créant 1024 blocs de 1024 threads 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 décroche jamais)?
Est-il possible de vérifier ces situations à l'aide du profileur?
Y at-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
Je vais essayer de répondre à chacune de vos questions.
Le programmeur divise le travail en threads, les threads en blocs de threads et les blocs de threads en grilles. Le distributeur de travail de calcul alloue des blocs de threads aux multiprocesseurs de streaming (SM). Une fois qu'un bloc de thread est distribué à un SM, les ressources du bloc de thread sont allouées (warps et mémoire partagée) et les threads sont divisés en groupes de 32 threads appelés warps. Une fois qu'un warp est attribué, on l'appelle un warp actif. Les deux planificateurs de chaîne sélectionnent deux chaînes actives par cycle et les distribuent aux unités d'exécution. Pour plus de détails sur les unités d'exécution et l'envoi des instructions, voir 1 p.7-10 et 2 .
4 '. Il existe un mappage entre laneid (index de threads dans une chaîne) et un noyau.
5 '. Si une chaîne contient moins de 32 threads, elle sera généralement exécutée comme si elle avait 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 n'ont pas emprunté le chemin actuel soient marqués comme inactifs ou qu'un thread du warp soit quitté.
6 '. Un bloc de thread sera divisé en WarpsPerBlock = (ThreadsPerBlock + WarpSize - 1)/WarpSize Il n’est pas nécessaire que les planificateurs de warp sélectionnent deux warp dans le même bloc de thread.
7 '. Une unité d'exécution ne bloquera pas lors d'une opération en mémoire. Si une ressource n'est pas disponible lorsqu'une instruction est prête à être envoyée, l'instruction sera à nouveau envoyée à l'avenir lorsque la ressource sera disponible. Les warp peuvent caler sur des barrières, sur des opérations de mémoire, sur des textures, sur des dépendances de données, ... Une warp bloquée ne peut pas être sélectionnée par le planificateur de warp. Sur Fermi, il est utile d’avoir au moins 2 chaînes éligibles par cycle afin que le planificateur de chaînes puisse émettre une instruction.
Voir la référence 2 pour connaître 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 objectif n’a pas de sens. Je vais essayer de répondre à vos points.
1 '. Si vous lancez le noyau <<< 8, 48 >>>, vous obtiendrez 8 blocs avec chacun 2 chaînes de 32 et 16 fils. Rien ne garantit que ces 8 blocs seront affectés à différents MS. Si 2 blocs sont alloués à un SM, il est possible que chaque planificateur de chaîne puisse sélectionner une chaîne et exécuter la chaîne. Vous n'utiliserez que 32 des 48 noyaux.
2 '. Il y a une grande différence entre 8 blocs de 48 fils et 64 blocs de 6 fils. Supposons que votre noyau n'a pas de divergence et que chaque thread exécute 10 instructions.
Afin d'obtenir une efficacité optimale, la division du travail doit être un multiple de 32 fils. Le matériel ne fusionnera pas les threads de différentes chaînes.
3 '. Une GTX560 peut avoir 8 SM * 8 blocs = 64 blocs à la fois ou 8 SM * 48 warps = 512 warps si le noyau n'effectue pas le maximum de registres ou de mémoire partagée. À tout moment, une partie du travail sera active sur les MS. Chaque SM a plusieurs unités d’exécution (plus que des cœurs CUDA). Les ressources utilisées à un moment donné dépendent des ordonnanceurs de warp et de la combinaison d'instructions de l'application. Si vous n'effectuez 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 '. Parallel Nsight et le Visual Profiler montrent
une. CIP exécuté
b. IPC délivré
c. chaînes actives par cycle actif
ré. warps éligibles par cycle actif (Nsight uniquement)
e. raisons de décrochage de la chaîne (Nsight uniquement)
f. threads actifs par instruction exécutée
Le profileur n’affiche pas le pourcentage d’utilisation des unités d’exécution. Pour la GTX560, une estimation approximative serait Publiée IPC/MaxIPC. Pour MaxIPC, supposons que GF100 (GTX480) est égal à 2 GF10x (GTX560) est égal à 4 mais que la cible 3 est 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és dans les processeurs) - le nombre de "multi-processeurs" dans un GPU, avec des cœurs dans nVIDIA marketing (notre carte contient des milliers de cœurs CUDA ").
Un warp lui-même ne peut être programmé que sur un seul cœur (= multiprocesseur) et peut exécuter jusqu'à 32 threads en même temps; il ne peut pas utiliser plus d'un noyau.
Le nombre "48 chaînes" est le nombre maximal de chaînes actives (les chaînes pouvant être choisies pour être programmées pour le prochain cycle, à un cycle donné) par multiprocesseur, sur les GPU nVIDIA avec Compute Capability 2.x; et ce nombre correspond à 1536 = 48 x 32 fils.
Réponse basée sur ce webinaire