3 points par GN⁺ 2023-11-13 | 1 commentaires | Partager sur WhatsApp
  • Le développement IA ne peut plus se contenter d’une exécution séquentielle de type CPU ; il faut comprendre le modèle de traitement massivement parallèle des GPU pour bien maîtriser les performances d’entraînement et d’inférence
  • Un CPU dispose généralement de 2 à 16 cœurs en usage grand public, et excelle dans les tâches monothread et à branchements conditionnels, tandis qu’un GPU, avec ses milliers de petits cœurs, est mieux adapté aux calculs matriciels, au traitement d’images et au deep learning
  • AWS propose des environnements d’exécution GPU comme P3/P4, P5/Inf1, G4 et Amazon SageMaker ; p3.2xlarge coûte 3,06 $/h, p5.48xlarge 98,32 $/h, et g4dn.xlarge environ 0,526 $/h
  • CUDA de NVIDIA permet aux développeurs de gérer directement le flux d’exécution parallèle, de l’allocation mémoire GPU à la copie de données, au lancement de kernels et à la compilation
  • Les exemples d’addition de tableaux, de génération de Mandelbrot et de CNN de classification chat/chien montrent comment découper une boucle séquentielle en threads GPU ; Mandelbrot passe ainsi de 4,07 s sur CPU à 0,0046 s sur GPU

Pourquoi les connaissances CPU ne suffisent plus

  • Beaucoup de développeurs ont appris à programmer et à résoudre des problèmes selon une approche centrée sur le CPU, mais le CPU repose fondamentalement sur une architecture séquentielle
  • Le CPU traditionnel exécute les instructions de façon linéaire et optimise un petit nombre de cœurs puissants pour les performances en thread unique
  • Lorsqu’il faut traiter plusieurs tâches en même temps, le coût du traitement séquentiel augmente, car chaque tâche doit être exécutée à son tour
  • Le multithreading peut améliorer les performances, mais la philosophie de conception du CPU reste globalement proche de l’exécution séquentielle

Modèles d’IA et traitement parallèle

  • Les architectures IA modernes comme les Transformer exploitent le traitement parallèle pour améliorer les performances d’entraînement
  • Les RNN fonctionnent de manière séquentielle, mais les Transformer comme GPT peuvent traiter plusieurs mots en même temps, ce qui améliore l’efficacité d’apprentissage et les capacités du modèle
  • L’apprentissage parallèle permet d’entraîner des modèles plus grands, et des modèles plus grands peuvent produire de meilleurs résultats
  • Le parallélisme ne s’applique pas seulement au traitement du langage naturel, mais aussi à la reconnaissance d’images
    • AlexNet en est un exemple : différentes parties d’une image sont traitées en parallèle pour identifier des motifs
  • En raison de sa conception axée sur les performances monothread, le CPU a du mal à répartir et exécuter efficacement les calculs massivement parallèles nécessaires aux modèles d’IA complexes

Comment les GPU réduisent les goulets d’étranglement

  • Le GPU est conçu avec un grand nombre de petits cœurs spécialisés plutôt qu’avec quelques gros cœurs puissants comme le CPU
  • Le parallélisme du GPU s’exprime particulièrement bien dans les charges de travail où le même type d’opération doit être répété à grande échelle, comme le rendu graphique ou les calculs mathématiques complexes
  • Les frameworks de deep learning comme TensorFlow sont optimisés pour exploiter la puissance des GPU et accélérer l’entraînement comme l’inférence
  • L’entraînement des réseaux de neurones implique beaucoup de calculs matriciels, et le GPU excelle dans leur parallélisation grâce à son grand nombre de cœurs

Différences de rôle entre CPU et GPU

  • CPU

    • Le CPU est conçu avant tout pour le traitement séquentiel, et convient bien aux tâches qui exécutent un flux d’instructions linéaire
    • Il est adapté au calcul généraliste, aux tâches système et aux algorithmes complexes avec branchements conditionnels
    • Un CPU grand public compte généralement un nombre relativement limité de cœurs, souvent de 2 à 16
    • Chaque cœur peut traiter indépendamment son propre jeu d’instructions
  • GPU

    • Le GPU est conçu selon une architecture parallèle, efficace pour traiter simultanément un grand nombre de sous-tâches
    • Il est particulièrement adapté au rendu graphique, aux calculs mathématiques complexes et à l’exécution d’algorithmes parallélisables
    • Il découpe les tâches en unités parallèles plus petites pour exécuter plusieurs opérations en même temps
    • Les cœurs GPU se comptent souvent par milliers et sont organisés en streaming multiprocessors (SMs) ou structures similaires
    • Il convient aux traitements manipulant simultanément de grands volumes de données, comme l’image, la vidéo, le deep learning ou les simulations scientifiques

Environnements GPU disponibles sur AWS

  • AWS propose plusieurs instances GPU pour des charges de travail comme le machine learning
  • Instances GPU généralistes

    • P3 et P4 sont des instances GPU généralistes adaptées à divers workloads
    • Elles peuvent servir à l’entraînement et à l’inférence ML, au traitement d’images et à l’encodage vidéo
    • p3.2xlarge coûte 3,06 $/h et fournit un GPU NVIDIA Tesla V100 avec 16 Go de mémoire GPU
  • Instances optimisées pour l’inférence

    • L’inférence consiste à injecter des données en temps réel dans un modèle IA entraîné afin d’obtenir une prédiction ou de résoudre une tâche
    • P5 et Inf1 sont conçues pour l’inférence machine learning, où la faible latence et le coût sont essentiels
    • p5.48xlarge coûte 98,32 $/h et fournit 8 GPU NVIDIA H100 avec 80 Go chacun, soit 640 Go de mémoire vidéo au total
  • Instances optimisées pour le graphisme

    • Les G4 instances sont conçues pour gérer des tâches intensives en graphisme
    • Les développeurs de jeux vidéo peuvent utiliser des instances G4 pour rendre des graphismes 3D de jeu
    • g4dn.xlarge coûte 0,526 $/h et utilise un GPU NVIDIA T4 avec 16 Go de mémoire
  • Service managé de machine learning

    • Amazon SageMaker est un service managé de machine learning qui donne accès à des instances GPU comme P3, P4 et P5
    • SageMaker convient aux organisations qui veulent se lancer dans le machine learning sans gérer elles-mêmes l’infrastructure sous-jacente
    • Une page distincte est disponible pour les tarifs Amazon SageMaker

Premiers pas avec NVIDIA CUDA

  • CUDA est une plateforme de calcul parallèle et un modèle de programmation développés par NVIDIA, qui permettent d’accélérer l’exécution des applications grâce aux GPU
  • L’exemple montre le flux de développement CUDA : allocation de mémoire GPU, copie des données, exécution du kernel et récupération du résultat
  • Installation

    • Téléchargez le base installer et le driver installer depuis CUDA
    • Ajoutez les variables d’environnement suivantes dans le .bashrc du dossier personnel
      • export PATH="/usr/local/cuda-12.3/bin:$PATH"
      • export LD_LIBRARY_PATH="/usr/local/cuda-12.3/lib64:$LD_LIBRARY_PATH"
    • Exécutez les commandes suivantes
      • sudo apt-get install cuda-toolkit
      • sudo apt-get install nvidia-gds
    • Redémarrez le système pour appliquer les changements
  • Commandes de vérification utiles

    • lspci | grep VGA : identifie et liste les GPU du système
    • nvidia-smi : fournit des informations détaillées sur l’utilisation, la température et la mémoire des GPU NVIDIA
    • sudo lshw -C display : affiche des informations sur les contrôleurs d’affichage, dont les cartes graphiques
    • inxi -G : montre les informations du sous-système graphique, y compris GPU et affichage
    • sudo hwinfo --gfxcard : permet d’obtenir des informations détaillées sur les cartes graphiques du système

Paralléliser une addition de tableaux avec CUDA

  • L’addition de tableaux est un bon problème pour expliquer la parallélisation sur GPU
  • Les tableaux d’exemple sont A = [1,2,3,4,5,6], B = [7,8,9,10,11,12], et le résultat est C = [8,10,12,14,16,18]
  • L’approche CPU parcourt les éléments du tableau un par un pour effectuer l’addition
  • À mesure que le volume de données augmente, le temps d’exécution séquentiel s’allonge, tandis que le GPU peut effectuer simultanément des opérations comme 1+7, 2+8, 3+9
  • L’exemple CUDA utilise un fichier de kernel .cu
    • __global__ désigne une fonction kernel appelée sur le GPU
    • vectorAdd reçoit trois pointeurs entiers a, b et c et effectue l’addition vectorielle
    • threadIdx.x récupère l’indice du thread courant
    • Chaque thread stocke la somme de l’élément correspondant dans c[i]
  • La fonction main suit l’ordre allocation mémoire GPU, copie des données, exécution du kernel, copie du résultat
    • cudaMalloc alloue cudaA, cudaB et cudaC dans la mémoire GPU
    • cudaMemcpy copie a et b de l’hôte vers le GPU
    • vectorAdd <<<1, sizeof(a) / sizeof(a[0])>>> lance le kernel
    • Le vecteur résultat cudaC est recopié du GPU vers l’hôte
  • La compilation et l’exécution se font avec la commande nvcc
  • Le code complet est fourni

Utiliser le GPU pour générer des images en Python

  • La génération de l’ensemble de Mandelbrot consiste à produire des motifs visuels complexes à partir du comportement de nombres dans une équation donnée, et c’est une tâche gourmande en ressources
  • Dans l’exemple Python sur CPU, chaque pixel est parcouru pour calculer la valeur de Mandelbrot, et la génération d’une image 1024×1536 prend 4,07 secondes
  • La version accélérée par GPU utilise la Numba library
    • Le décorateur @jit effectue une compilation Just-In-Time qui transforme le code Python en code machine
    • cuda.jit sert à créer mandel_gpu, avec device=True pour indiquer une exécution sur GPU
    • mandel_kernel s’exécute sur le GPU CUDA et répartit la génération de Mandelbrot entre les threads GPU
  • create_fractal_gpu gère l’allocation mémoire GPU, la configuration des threads et blocs, l’exécution du kernel, la synchronisation et la copie du résultat
    • threadsperblock = (16, 16) est utilisé
    • cuda.synchronize() attend la fin du travail GPU
    • d_image.copy_to_host(image) copie le résultat vers le CPU
  • Le temps d’exécution sur GPU est de 0,0046 seconde, bien plus rapide que le code basé sur CPU
  • Le code complet est fourni

Entraîner un réseau de neurones de classification chat/chien avec un GPU

  • Un exemple de réseau de neurones distinguant chats et chiens est utilisé pour montrer comment les GPU servent en IA
  • Les prérequis sont CUDA et TensorFlow
    • TensorFlow peut être installé avec pip install tensorflow[and-cuda]
    • Le jeu de données utilisé est Kaggle Dogs vs. Cats
    • Après téléchargement, les images de chats et de chiens sont organisées dans des sous-dossiers distincts du dossier d’entraînement
  • Le modèle utilise un réseau de neurones convolutif (CNN)
    • pandas et numpy servent à la manipulation des données
    • Sequential permet d’empiler linéairement les couches du réseau
    • Convolution2D, MaxPooling2D, Dense, Flatten sont les couches qui composent le CNN
    • ImageDataGenerator est utilisé pour l’augmentation de données en temps réel pendant l’entraînement
  • Les données d’entraînement sont chargées avec ImageDataGenerator
    • On applique rescale=1./255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True
    • Les images d’entrée sont configurées en taille (64, 64), batch size 32, mode de classification binaire
  • L’architecture du CNN est composée de couches de convolution, max pooling, flatten, Dense et d’une sortie sigmoid
  • Le modèle est compilé avec l’optimiseur adam, la loss binary_crossentropy et la métrique accuracy
  • L’entraînement s’exécute avec epochs=25, validation_steps=2000, puis le modèle est enregistré dans un fichier .h5 via classifier.save('trained_model.h5')
  • Le code d’inférence charge trained_model.h5, convertit l’image en (64, 64), puis affiche dog si la prédiction est supérieure ou égale à 0.5, sinon cat
  • Le code complet est fourni

L’étendue des usages du GPU

  • À l’ère de l’IA, il devient difficile d’ignorer les capacités des GPU, et les développeurs doivent mieux comprendre leur potentiel
  • En passant d’algorithmes séquentiels à des algorithmes parallélisés, le GPU devient un outil d’accélération des calculs complexes
  • Les capacités de traitement parallèle du GPU sont particulièrement utiles pour les grands jeux de données et les architectures de réseaux de neurones complexes en IA et en machine learning
  • Les GPU sont utilisés au-delà du machine learning traditionnel, notamment dans la recherche scientifique, la simulation et les traitements intensifs en données
  • Leur capacité de traitement parallèle est mise à profit pour résoudre des problèmes dans des domaines variés comme la découverte de médicaments, la modélisation climatique ou les simulations financières

1 commentaires

 
GN⁺ 2023-11-13
Avis sur Hacker News
  • Le code de cet article est faux. Aucun kernel CUDA n’est appelé : https://github.com/RijulTP/GPUToolkit/blob/f17fec12e008d0d37...
    90 % du temps passé à « calculer » l’ensemble de Mandelbrot avec le code compilé en JIT ne sert pas au calcul réel, mais à la compilation de la fonction
    Si vous voulez vraiment apprendre CUDA, implémenter une multiplication de matrices est un bon exercice ; des tutoriels utiles sont https://cnugteren.github.io/tutorial/pages/page1.html et https://siboehm.com/articles/22/CUDA-MMM

    • Il y a aussi SAXPY, qu’on appelle le « Hello World » du code mathématique parallèle en CUDA. SAXPY signifie « Single-Precision A·X Plus Y » ; c’est une fonction BLAS standard et une opération très simple combinant multiplication par un scalaire et addition vectorielle
      Elle prend deux vecteurs X et Y en virgule flottante 32 bits, ainsi qu’un scalaire A, multiplie chaque X[i] par A puis l’ajoute à Y[i] : https://developer.nvidia.com/blog/six-ways-saxpy/
    • Après que le problème a été signalé, le code a été corrigé et le billet de blog mis à jour
  • L’article affirme que « tous les développeurs devraient savoir » cela, mais en réalité il traite plutôt de la manière dont les GPU sont utilisés en IA. La plupart des développeurs ne sont pas des développeurs IA, et n’interagissent pas directement avec l’IA ni n’utilisent directement des GPU
    En plus, il aborde à peine la 3D, qui est pourtant la raison fondamentale de l’existence des GPU

    • Connaître les bases de domaines voisins est utile. C’est particulièrement vrai pour un domaine aussi largement applicable que le machine learning : le projet dont vous serez chargé le mois prochain pourrait en avoir besoin, et cela aide aussi à collaborer avec les collègues qui s’en occupent
      Avec des notions de base, on comprend aussi mieux les discours « IA » vendus aux managers
      L’attitude consistant à dire que « les domaines adjacents ne servent à rien » est quelque chose que j’ai souvent vu à l’école. Côté administration système, mes camarades disaient qu’ils n’avaient pas besoin de connaître la programmation, mais il fallait faire du scripting ; dans une école de développement logiciel, on disait qu’il n’était pas nécessaire de connaître les réseaux, mais quelques années plus tard DevOps apparaissait partout dans les offres d’emploi
      Si l’article fait environ 1 500 mots, même en le lisant comme un support d’étude cela prend une douzaine de minutes, et passer deux heures à exécuter les exemples de code n’est pas un gros investissement. Bien sûr, cela suppose que l’article soit une bonne introduction
    • Quand je suis passé d’une entreprise embedded traditionnelle à une startup, je me souviens qu’un collègue m’avait gentiment taquiné parce que je ne savais pas envoyer une requête JSON avec curl. Je suis toujours développeur embedded, mais depuis j’ai beaucoup appris en backend, frontend et infrastructure, et il me semble très probable qu’une situation similaire se produise autour de l’IA dans l’ensemble du secteur au cours des prochaines années
    • Même l’exemple de rendu de l’ensemble de Mandelbrot n’obtient qu’une accélération par 10, alors que c’est l’exemple typique d’un calcul presque entièrement limité par le volume d’opérations en virgule flottante. Personnellement, je trouve l’article médiocre
    • Il repose sur beaucoup d’hypothèses inexactes. Je suis d’accord pour dire que la plupart des développeurs ne sont pas des développeurs IA, et l’auteur de l’article original semble soit assez déconnecté de la population générale des développeurs, soit en train de généraliser à partir de son propre environnement
    • Chaque fois que je vois un article affirmer que « tous les développeurs devraient savoir » quelque chose, cette affirmation s’est généralement révélée fausse. Il peut exister des articles contenant des informations que tout le monde devrait vraiment connaître, mais ceux que l’on croise sont surtout du clickbait
  • Je pense que la raison pour laquelle Python domine en IA est que la relation Python-C ressemble à la relation CPU-GPU
    Les GPU sont très performants mais difficiles à programmer directement ; les gens les utilisent donc via des appels à des API de haut niveau comme PyTorch
    C est aussi performant mais difficile à programmer ; on utilise donc Python comme couche d’abstraction au-dessus de C
    Il n’est pas évident que les gens doivent comprendre les GPU aussi en profondeur. C’est encore plus vrai s’ils ne sont pas plongés dans l’entraînement ou l’exploitation de modèles d’IA ; et si la loi de Moore prend fin et que le multithreading devient le principal moyen d’augmenter les performances, il y a de fortes chances que de nouveaux langages adaptés au paradigme de la programmation parallèle apparaissent. Mojo ressemble à un point de départ

    • Je me suis demandé s’il y avait une place pour un nouveau langage qui maximise les performances de façon invisible, quel que soit le matériel sur lequel il s’exécute
      L’idée serait que, dès de simples calculs répétitifs, chaque instruction exploite intelligemment en arrière-plan tous les cœurs CPU en parallèle, et que les tâches qui le permettent soient transférées au GPU
      Je me demande si ce genre de tentative a déjà existé, ou même si c’est possible au départ
    • Il est difficile de dire que la loi de Moore est déjà terminée, et le multithreading n’est pas non plus la réponse. En revanche, la première phrase est juste
    • La programmation GPU n’est pas si difficile. CUDA est assez intuitif pour beaucoup de tâches, et il arrive souvent qu’on obtienne une accélération par 100 avec moins de 100 lignes de code
    • Avec un langage plus moderne, on peut assez facilement obtenir des performances de niveau C tout en gardant une expressivité à la Python. À mon avis, C manque plutôt d’abstraction, ce qui rend plus attrayant du code lent mais simple
    • C est un mode de vie. Les personnes qui utilisent presque exclusivement C ont du mal à accepter le concept d’« indentation significative » de Python
  • Dire que « lorsqu’un CPU rencontre plusieurs tâches, il alloue ses ressources pour traiter chaque tâche une par une » est beaucoup trop simpliste. J’en viendrais presque à souhaiter que les CPU soient encore aussi simples
    Il est légitime que l’article se concentre sur le modèle de programmation, mais du point de vue des performances, dire qu’un « CPU exécute les instructions séquentiellement » est fondamentalement faux. Les pipelines exécutent des instructions en parallèle, il y a aussi le SIMD, et plusieurs cœurs peuvent travailler ensemble sur le même problème

    • L’article semble viser à côté. Un CPU avec AVX-512 dispose lui aussi d’un parallélisme de données massif, et un CPU peut également exécuter beaucoup d’instructions simultanément
      La grande différence est que le CPU consacre beaucoup de silicium et d’énergie à la gestion du flux de contrôle pour exécuter efficacement un seul thread, tandis que le GPU consacre ces ressources à davantage d’unités de calcul et exécute un très grand nombre de threads pour masquer les latences liées au flux de contrôle et à la mémoire
    • Les CPU exécutent eux aussi plusieurs instructions SIMD en même temps
  • Dire que les CPU sont adaptés au code sériel et les GPU au code parallèle est vrai dans une certaine mesure, mais c’est une approximation assez grossière. À budget énergétique comparable, de l’ordre de quelques centaines de watts, un CPU compte environ 100 « cœurs » qui exécutent des tâches indépendantes une par une, hyperthreading compris, et masquent la latence mémoire grâce à la prédiction de branchement et au pipelining
    Un GPU compte environ 100 « unités de calcul », et chaque unité entrelace l’exécution d’environ 80 tâches indépendantes, masquant la latence mémoire en exécutant l’instruction suivante d’une autre tâche
    La terminologie est assez déroutante, et il est très probable qu’un CPU dispose d’unités vectorielles de 256 bits de large, tandis qu’un GPU en ait de 2048 bits, mais avec un peu de recul les deux architectures paraissent assez similaires

    • Les GPU offrent une bande passante mémoire environ 10 fois supérieure à celle des CPU, et cette différence devient importante pour les LLM. Avec un traitement par lots optimal, produire un seul token de sortie exige en pratique de lire toute la mémoire, car cette mémoire est utilisée pour les poids ou le cache KV
    • Je me suis toujours étonné qu’il y ait si peu d’efforts pour combiner quelques cœurs à faible latence avec de nombreux cœurs à haut débit. Il suffirait d’entourer un cœur Intel P de plusieurs cœurs E, puis d’ajouter à ces cœurs E beaucoup de cœurs iGPU ou d’unités AVX-512
      On pourrait appeler ça Xeon Chi
  • La plupart des langages de programmation étant conçus pour un traitement séquentiel façon CPU, alors qu’Erlang/Elixir ont été conçus pour le parallélisme façon GPU, je me demande si Nx / Axon va décoller : https://github.com/elixir-nx/

    • Erlang n’a pas été conçu pour du traitement parallèle intensif en calcul, mais pour des systèmes distribués à forte concurrence
    • Je suis vraiment curieux de voir à quel point Elixir et Nx fonctionnent bien pour des charges de calcul intensives sur des clusters HPC. Structurellement, ce n’est pas très différent de MPI, souvent utilisé dans ce domaine, et cela pourrait être bien plus accessible, comme numpy et l’écosystème Python scientifique
    • J’examine si la combinaison Elixir et Nx/Axon convient bien à des architectures mêlant CPU et GPU, comme NVIDIA Grace Hopper
    • Je me demande si ça s’exécute sur GPU. À l’avenir, je pense qu’il faudra les deux. La programmation séquentielle reste la meilleure abstraction pour la plupart des tâches qui n’exigent pas une exécution massivement parallèle
  • Il faudrait un guide d’achat. J’aimerais savoir quel est le minimum à dépenser et quels sont les meilleurs choix par tranche de budget. Le problème, c’est que ces informations changent de temps en temps, et je ne sais pas s’il existe une ressource maintenue à jour en continu

  • On est revenus aux articles putaclic du genre « ce que tout développeur devrait savoir » ?

    • Ce format d’article me semble voué à être remplacé par ChatGPT, mais quand c’est bien écrit, il peut en réalité avoir pas mal de valeur
      J’aime les textes qui abordent la complexité de front, et comme je connais un peu à la fois les méthodes quantitatives et les détails qualitatifs de domaines comme le matériel informatique, j’apprécie les articles qui expliquent vraiment les détails d’un sujet
      Par exemple, que tous les programmeurs doivent ou non connaître « What every programmer should know about memory » est une autre question, mais un bon programmeur devrait avoir une idée de la façon dont un ordinateur fonctionne réellement. Le point essentiel à retenir de cet article, la localité, apparaît souvent naturellement dans du bon code rapide, facile à suivre et bien adapté au problème
    • On dirait bien. Il faut prendre les affirmations de cet article avec du recul
  • Bon article, mais les instances AWS P5, avec les P4d et P4de, sont clairement orientées entraînement plutôt qu’inférence. Les types d’instances plus adaptés à l’inférence sont G4dn et G5, qui utilisent respectivement des GPU T4 et A10G

    • L’article d’origine a oublié G5
  • Je débute presque totalement en programmation GPU, mais j’ai trouvé cet article intéressant. C’est impressionnant de voir à quel point les choses ont progressé, au point qu’on puisse entraîner aussi facilement un simple réseau neuronal « chien ou chat »