Quelle est la relation entre un noyau CUDA, un multiprocesseur de streaming et le modèle CUDA de blocs et de threads?
Qu'est-ce qui est mappé à quoi et à quoi est parallélisé et comment? et quoi de plus efficace, maximiser le nombre de blocs ou le nombre de threads?
D'après ce que je comprends actuellement, il y a 8 noyaux cuda par multiprocesseur. et que chaque noyau de cuda sera capable d’exécuter un bloc à la fois. et tous les threads de ce bloc sont exécutés en série dans ce noyau particulier.
Est-ce correct?
La disposition des fils/blocs est décrite en détail dans le Guide de programmation CUDA . Le chapitre 4 indique notamment:
L’architecture CUDA s’articule autour d’un ensemble évolutif de multiprocesseurs de streaming multithread. Lorsqu'un programme CUDA sur la CPU hôte appelle une grille du noyau, les blocs de la grille sont énumérés et distribués aux multiprocesseurs ayant la capacité d'exécution disponible. Les threads d'un bloc de thread s'exécutent simultanément sur un multiprocesseur et plusieurs blocs de thread peuvent s'exécuter simultanément sur un multiprocesseur. À la fin des blocs de thread, de nouveaux blocs sont lancés sur les multiprocesseurs libérés.
Chaque SM contient 8 cœurs CUDA et exécute une seule chaîne de 32 threads à la fois. Il faut donc 4 cycles d'horloge pour émettre une seule instruction pour l'ensemble de la chaîne. Vous pouvez supposer que les threads d'une chaîne donnée s'exécutent de manière verrouillée, mais pour pouvoir se synchroniser d'une chaîne à l'autre, vous devez utiliser __syncthreads()
.
La GTX 970 comprend 13 multiprocesseurs de diffusion (Streaming Multiprocessors) avec 128 cœurs Cuda chacun. Les cœurs Cuda sont également appelés processeurs de flux (SP).
Vous pouvez définir des grilles qui mappent des blocs au GPU.
Vous pouvez définir des blocs qui mappent les threads aux processeurs de flux (les 128 cœurs Cuda par SM).
Une chaîne est toujours formée de 32 fils et tous les fils d’une chaîne sont exécutés simultanément.
Pour utiliser au maximum la puissance d'un GPU, vous avez besoin de beaucoup plus de threads par SM que le SM n'en a. Pour chaque capacité de calcul, il existe un certain nombre de threads pouvant résider dans un SM à la fois. Tous les blocs que vous définissez sont mis en file d'attente et attendent qu'un SM ait les ressources (nombre de SP libres), puis il est chargé. Le SM commence à exécuter Warps. Etant donné qu'un Warp n'a que 32 Threads et qu'un SM a par exemple 128 SP, un SM peut exécuter 4 Warps à un moment donné. Le fait est que si les threads ont accès à la mémoire, le thread bloquera jusqu'à ce que sa demande de mémoire soit satisfaite. En chiffres: un calcul arithmétique sur le SP a une latence de 18 à 22 cycles, tandis qu'un accès à la mémoire globale non mis en cache peut prendre jusqu'à 300 à 400 cycles. Cela signifie que si les threads d'une chaîne attendent des données, seul un sous-ensemble des 128 SP fonctionnera. Par conséquent, le planificateur commute pour exécuter une autre chaîne si elle est disponible. Et si cette chaîne bloque, elle exécute la suivante, etc. Ce concept s'appelle la latence cachée. Le nombre de warps et la taille du bloc déterminent l'occupation (à partir du nombre de warps que le SM peut exécuter). Si le taux d'occupation est élevé, il est plus improbable qu'il n'y ait pas de travail pour les SP.
Votre déclaration selon laquelle chaque cœur de cuda exécutera un bloc à la fois est fausse. Si vous parlez de Streaming Multiprocessors, ils peuvent exécuter des warps à partir de tous les threads résidant dans le SM. Si un bloc a une taille de 256 threads et que votre GPU autorise 2048 threads à résider par SM, chaque SM aura 8 blocs résidants à partir desquels le SM pourra choisir l'exécution de warps. Tous les threads des chaînes exécutées sont exécutés en parallèle.
Vous trouverez les numéros des différentes capacités de calcul et architectures GPU ici: https://en.wikipedia.org/wiki/CUDA#Limitations
Vous pouvez télécharger une feuille de calcul d’occupation auprès de Nvidia Feuille de calcul d’occupation (par Nvidia) .
Le serveur de calcul Compute Work distribuera un bloc de thread (CTA) sur un SM uniquement si celui-ci dispose de ressources suffisantes pour le bloc de thread (mémoire partagée, warps, registres, barrières, ...). Les ressources au niveau des threads, telles que la mémoire partagée, sont allouées. Allocate crée suffisamment de warp pour tous les threads du bloc de threads. Le gestionnaire de ressources alloue les warps à l’aide de round robin aux sous-partitions SM. Chaque sous-partition SM contient un planificateur de warp, un fichier de registre et des unités d'exécution. Une fois qu'un warp est affecté à une sous-partition, il restera sur la sous-partition jusqu'à ce qu'il soit terminé ou préempté par un commutateur de contexte (architecture Pascal). Lors du changement de contexte, le warp sera restauré sur le même SM que le même warp-id.
Lorsque tous les threads de warp ont terminé, le planificateur de warp attend la fin de toutes les instructions en attente émises par le warp, puis le gestionnaire de ressources libère les ressources de niveau warp comprenant le warp-id et le fichier register.
Lorsque toutes les chaînes d'un bloc de thread sont terminées, des ressources de niveau bloc sont libérées et le SM informe le distributeur Compute Work que le blocage est terminé.
Une fois qu'un warp est attribué à une sous-partition et à toutes les ressources, le warp est considéré comme étant actif, ce qui signifie que le planificateur de warp suit activement l'état du warp. À chaque cycle, le planificateur de chaîne détermine les chaînes actives bloquées et celles pouvant émettre une instruction. Le planificateur de chaîne sélectionne la chaîne éligible ayant la priorité la plus élevée et émet une à deux instructions consécutives à partir de la chaîne. Les règles pour la double émission sont spécifiques à chaque architecture. Si un warp génère une charge de mémoire, il peut continuer à exécuter des instructions indépendantes jusqu'à atteindre une instruction dépendante. La chaîne signalera alors bloquée jusqu'à la fin du chargement. Il en va de même pour les instructions mathématiques dépendantes. L'architecture SM est conçue pour masquer à la fois l'ALU et la latence de la mémoire en commutant par cycle les warps.
Cette réponse n'utilise pas le terme noyau CUDA car cela introduit un modèle mental incorrect. Les cœurs CUDA sont des unités d’exécution à nombres flottants/entiers à simple précision et en pipeline. Le taux d'émission et la latence des dépendances sont spécifiques à chaque architecture. Chaque sous-partition SM et chaque SM comporte d'autres unités d'exécution, notamment des unités de chargement/stockage, des unités à virgule flottante double précision, des unités à virgule flottante semi-précision, des unités de branche, etc.
Afin de maximiser les performances, le développeur doit comprendre le compromis entre blocs, warps, registres/threads.
Le terme "occupation" est le rapport entre les chaînes actives et les chaînes maximales sur un MS. L’architecture Kepler - Pascal (sauf le GP100) comporte 4 ordonnanceurs de warp par SM. Le nombre minimal de warp par SM devrait au moins être égal au nombre d'ordonnanceurs de warp. Si l'architecture a une latence d'exécution dépendante de 6 cycles (Maxwell et Pascal), il vous faudra au moins 6 warps par ordonnanceur, soit 24 par SM (24/64 = 37,5% d'occupation) pour couvrir la latence. Si les threads ont un parallélisme de niveau d’instruction, cela pourrait être réduit. Presque tous les noyaux émettent des instructions de latence variable, telles que des chargements de mémoire pouvant durer de 80 à 1000 cycles. Cela nécessite plus de warps actifs par programmateur de warp pour masquer la latence. Pour chaque noyau, il existe un compromis entre le nombre de chaînes et d'autres ressources telles que la mémoire partagée ou les registres; l'optimisation pour une occupation à 100% n'est donc pas conseillée car d'autres sacrifices seront probablement effectués. Le profileur CUDA peut aider à identifier les raisons du taux d’instruction, de l’occupation et du décrochage afin d’aider le développeur à déterminer cet équilibre.
La taille d'un bloc de thread peut avoir une incidence sur les performances. Si le noyau a de gros blocs et utilise des barrières de synchronisation, alors les blocages de barrière peuvent être une raison de blocage. Cela peut être atténué en réduisant le nombre de chaînes par bloc de threads.
Il y a plusieurs multiprocesseurs en streaming sur un seul périphérique.
Un SM peut contenir plusieurs blocs. Chaque bloc peut contenir plusieurs threads.
Un SM a plusieurs noyaux CUDA (en tant que développeur, vous ne devriez pas vous en soucier car il est résumé par warp), qui fonctionnera sur le thread. SM travaille toujours sur une chaîne de fils (toujours 32). Une chaîne ne fonctionnera que sur le fil du même bloc.
SM et le bloc ont tous les deux des limites sur le nombre de threads, le nombre de registres et la mémoire partagée.