Le rôle des Warps dans le traitement parallèle
Introduction
Les GPU sont décrits comme des processeurs parallèles en raison de leur capacité à exécuter des tâches en parallèle. Les tâches sont divisées en sous-tâches plus petites, exécutées simultanément par plusieurs unités de traitement et combinées pour produire le résultat final. Ces unités de traitement (threads, warps, blocs de threads, cœurs, multiprocesseurs) partagent des ressources, telles que la mémoire, facilitant la collaboration entre elles et améliorant l'efficacité globale du GPU.
Une unité en particulier, les chaînes, constitue la pierre angulaire du traitement parallèle. En regroupant les threads en une seule unité d'exécution, les warps permettent de simplifier la gestion des threads, le partage des données et des ressources entre les threads, ainsi que le masquage de la latence de la mémoire avec une planification efficace.
« Le terme chaîne vient du tissage, la première technologie à fils parallèles »
Conditions préalables
Il peut être utile de lire ce « Rappel de CUDA » avant de continuer**
Dans cet article, nous expliquerons comment les warps sont utiles pour optimiser les performances des applications accélérées par GPU. En construisant une intuition autour des distorsions, les développeurs peuvent réaliser des gains significatifs en termes de vitesse et d’efficacité de calcul.
Chaînes démêlées
Les blocs de threads sont divisés en chaînes composées de 32 threads chacune. Tous les threads d’une chaîne s’exécutent sur le même multiprocesseur de streaming. Figure tirée d'une présentation NVIDIA sur les TENDANCES GPGPU ET ACCÉLÉRATEURS
Lorsqu'un multiprocesseur de streaming (SM) se voit attribuer des blocs de threads pour l'exécution, il subdivise les threads en chaînes. Les architectures GPU modernes ont généralement une taille de chaîne de 32 threads.
Le nombre de chaînes dans un bloc de thread dépend de la taille du bloc de thread configurée par le programmeur CUDA. Par exemple, si la taille du bloc de fil est de 96 fils et la taille de la chaîne est de 32 fils, le nombre de chaînes par bloc de fil serait : 96 fils/32 fils par chaîne=3 chaînes par bloc de fil.
Sur cette figure, 3 blocs de threads sont affectés au SM. Les blocs de fils sont composés chacun de 3 chaînes. Une chaîne contient 32 threads consécutifs. Figure de l'article Medium
Notez comment, sur la figure, les threads sont indexés, en commençant à 0 et en continuant entre les chaînes dans le bloc de thread. La première chaîne est constituée des 32 premiers fils (0-31), la chaîne suivante comporte les 32 fils suivants (32-63), et ainsi de suite.
Maintenant que nous avons défini les warps, prenons du recul et examinons la taxonomie de Flynn, en nous concentrant sur la manière dont ce schéma de catégorisation s'applique aux GPU et à la gestion des threads au niveau des warp.
GPU : SIMD ou SIMT ?
La taxonomie de Flynn est un système de classification basé sur le nombre d'instructions et de flux de données d'une architecture informatique. Il existe 4 classes : SISD (Données uniques à instruction unique), SIMD (Données multiples à instruction unique), MISD (données uniques à instructions multiples), MIMD (données multiples à instructions multiples). Figure tirée de l’atelier PEP root6 du CERN
La taxonomie de Flynn est un système de classification basé sur le nombre d'instructions et de flux de données d'une architecture informatique. Les GPU sont souvent décrits comme des Single Instruction Multiple Data (SIMD), ce qui signifie qu'ils effectuent simultanément la même opération sur plusieurs opérandes de données. Single Instruction Multiple Thread (SIMT), terme inventé par NVIDIA, s'étend sur la taxonomie de Flynn pour mieux décrire le parallélisme au niveau des threads présenté par les GPU NVIDIA. Dans une architecture SIMT, plusieurs threads émettent les mêmes instructions sur les données. L'effort combiné du compilateur CUDA et du GPU permet aux threads d'une chaîne de se synchroniser et d'exécuter des instructions identiques à l'unisson aussi souvent que possible, optimisant ainsi les performances.
Bien que SIMD et SIMT exploitent tous deux le parallélisme au niveau des données, leur approche est différenciée. SIMD excelle dans le traitement uniforme des données, tandis que SIMT offre une flexibilité accrue grâce à sa gestion dynamique des threads et à son exécution conditionnelle.
La planification Warp masque la latence
Dans le contexte des warps, la latence est le nombre de cycles d'horloge nécessaires à un warp pour terminer l'exécution d'une instruction et devenir disponible pour traiter la suivante.
W désigne la chaîne et T désigne le fil. Les GPU exploitent la planification Warp pour masquer la latence, tandis que les processeurs s'exécutent de manière séquentielle avec changement de contexte. Figure de la leçon 6 du CS179 de CalTech
L'utilisation maximale est atteinte lorsque tous les planificateurs de distorsion ont toujours des instructions à émettre à chaque cycle d'horloge. Ainsi, le nombre de warps résidents, c'est-à-dire les warps qui sont exécutés sur le SM à un instant donné, affecte directement l'utilisation. En d’autres termes, il doit y avoir des chaînes pour lesquelles les planificateurs de chaînes peuvent émettre des instructions. Plusieurs distorsions résidentes permettent au SM de basculer entre elles, masquant la latence et maximisant le débit.
Compteurs de programme
Les compteurs de programme incrémentent chaque cycle d'instruction pour récupérer la séquence de programme de la mémoire, guidant ainsi le flux d'exécution du programme. Notamment, bien que les threads d'une chaîne partagent une adresse de programme de démarrage commune, ils maintiennent des compteurs de programme séparés, permettant une exécution autonome et un branchement des threads individuels.
Les GPU pré-Volta avaient un seul compteur de programme pour une déformation à 32 threads. Suite à l'introduction de la micro-architecture Volta, chaque thread possède son propre compteur de programme. Comme le dit Stephen Jones lors de sa conférence GTC' 17 : "donc maintenant, tous ces fils sont totalement indépendants - ils fonctionnent toujours mieux si vous les regroupez… mais vous n'êtes plus mort dans l'eau si vous les divisez. ."Figure des GPU Inside Volta (GTC'17).
Ramification
Des compteurs de programme séparés permettent le branchement, une structure de programmation if-then-else, dans laquelle les instructions ne sont traitées que si les threads sont actifs. Étant donné que les performances optimales sont atteintes lorsque les 32 threads d’une chaîne convergent vers une instruction, il est conseillé aux programmeurs d’écrire du code qui minimise les cas où les threads d’une chaîne empruntent un chemin divergent.
Conclusion : attacher les fils lâches
Les Warps jouent un rôle important dans la programmation GPU. Cette unité à 32 threads exploite SIMT pour augmenter l'efficacité du traitement parallèle. Une planification Warp efficace masque la latence et maximise le débit, permettant une exécution rationalisée de charges de travail complexes. De plus, les compteurs de programmes et les branchements facilitent une gestion flexible des threads. Malgré cette flexibilité, il est conseillé aux programmeurs d'éviter les longues séquences d'exécution divergentes pour les threads d'une même chaîne.
Références supplémentaires
Primitives de niveau Warp CUDA
Guide de programmation CUDA C++