Lancement CUDA : décryptage du pipeline GPU NVIDIA
L'exécution d'un noyau CUDA, comme un simple ajout de deux vecteurs sur une carte graphique NVIDIA RTX 4090, repose sur un pipeline technique complexe allant du code source jusqu'au matériel. Tout commence par la compilation. Le compilateur nvcc traite le code hôte directement, tandis que le code appareil est d'abord converti en PTX, une architecture virtuelle agnostique, puis en SASS, le langage machine spécifique à l'architecture ciblée. Ces deux versions sont empaquetées dans un fichier fatbin intégré à l'exécutable, permettant une compilation à la volée si le programme s'exécute sur un modèle de GPU différent. Au démarrage, un initialiseur caché inscrit le noyau dans le runtime CUDA. Lorsque le programme host appelle le noyau, un script généré automatiquement regroupe les arguments dans un tampon mémoire aligné sur les adresses de la mémoire constante du GPU. L'appel actif du noyau déclenche le pilote utilisateur, qui ouvre un contexte de communication via des requêtes système standard sur les fichiers du pilote noyau. Le transfert des commandes vers la carte se fait sans appel de fonction traditionnel. Le pilote construit un descripteur de lancement appelé QMD, y incluant la taille de la grille, les dimensions du bloc et l'adresse du code machine. Ce descripteur est écrit dans un buffer de commandes, pointé par un tampon circulaire de coordination, puis la carte est réveillée par l'écriture sur un registre d'ouverture de porte en mémoire mappée. Le processeur central rend alors immédiatement la main à son système d'exploitation. Sur la carte, un distributeur de travail répartit les blocs de threads sur les 128 multiprocesseurs de streaming. Chaque multiprocesseur gère plusieurs groupes de 32 threads via quatre planificateurs indépendants. Contrairement aux processeurs modernes qui détectent les dépendances dynamiquement, le GPU s'appuie sur un planificateur statique intégré directement dans les instructions par le compilateur. Ce dernier y place des compteurs de temporisation, des barrières de dépendance et des indicateurs de priorité pour masquer les latences et maintenir un flux continu. En matière de mémoire, les accès consécutifs des threads sont fusionnés en requêtes groupées, optimisant le transfert vers les caches intermédiaires avant d'atteindre la mémoire vidéo GDDR6X. Les analyses de performance montrent que ce type de calcul est limité par la bande passante mémoire plutôt que par la puissance de calcul, atteignant environ 780 Go/s. Une fois le calcul terminé, le GPU signale sa fin via un sémaphore, déclenchant un transfert inverse vers la mémoire centrale à travers le bus PCIe. Le runtime bloque alors l'hôte jusqu'à réception, et les résultats sont enfin affichés. Cette trajectoire démontre comment une simple instruction parallèle active une orchestration précise entre compilation multi-niveaux, communication hôte-périphérique, planification matérielle optimisée et hiérarchie mémoire, révélant l'architecture complexe qui sous-tend le calcul moderne.
