Accélérez votre code d'IA
Christian Hudon Christian Hudon
17 novembre 8 min

Accélérez votre code d'IA

Le 26 novembre 2020, j'ai fait une présentation sur l'accélération du code d'IA où j'ai présenté comment profiler et accélérer un modèle PyTorch, en utilisant le modèle N-Beats d'Element AI comme exemple. Cette présentation s'adresse à la fois aux développeurs qui mettent des modèles d'IA en production et aux praticiens qui créent les modèles et qui souhaitent les accélérer. C'est également l'occasion de vous montrer comment on plonge tête première dans les modèles d'IA chez Element AI. Vous aimeriez vous joindre à notre géniale équipe de développeurs? Regardez nos postes ouverts. Ci-dessous, vous trouverez la présentation et les diapositives.

La présentation

Les diapositives

Téléchargez les diapositives (en anglais).

Les choses à faire

Voici la liste des actions à prendre avant et durant votre profilage, comme discuté dans la présentation.

Préparation

  • Utilisez un profileur d'échantillonnage ou deux! (NVIDIA Nsight Systems pour les GPU et l'aperçu du système, PyInstrument / Py-Spy / Scalene pour le code Python.)
  • Simplifiez le problème! Désactivez la recherche d’hyper-paramètres et les parties que vous savez se mettre à l'échelle sans problème.
  • Pour itérer rapidement, implantez une configuration d'entraînement qui comprend un sous-ensemble de vos données d'entraînement. Visez un modèle qui peut être entraîné en 5 minutes. (Il s'agit également du temps maximal supporté par le profileur NVIDIA Nsight Systems.)
  • Assurez-vous de toujours travailler sur la même génération de GPU et de CPU — particulièrement si vous soumettez des entraînements à une grappe de machines — afin que les temps d'entraînements de vos différents essais puissent être comparés.
  • Jetez un coup d’œil à l’erreur de test du modèle. Pas pour valider la qualité de votre modèle (il ne sera pas bon avec une seule époque!), mais pour aider à déterminer si les changements que vous apportez pour accélérer votre code changent en même temps vos résultats — cela ne devrait pas être le cas.
  • Ajoutez des annotations NVTX pour les parties importantes de votre pipeline d'entraînement afin de voir (dans Nsight Systems) comment se distribue votre temps d'entraînement.
  • Créez au moins une image mentale du système complet, avec une idée grossière de la vitesse de chaque partie. Ne sautez pas cette étape! Nous utiliserons ceci dès le premier profil.
  • Sinon, concentrez vos efforts où ils feront une différence. Que se passerait-il si (après un effort infini?) la partie de votre système que vous souhaitez accélérer finissait par rouler en 0 seconde? Quel impact cela aurait-il sur le temps total de votre programme? De façon plus réaliste, que se passerait-il avec le temps total d’exécution si cette partie roulait 10 fois plus vite? Ou seulement 2 fois plus vite? Laissez les résultats de cette réflexion guider où vous investissez vos efforts.

Profilage et amélioration de votre code

  • Comparez votre premier profil d'exécution de code et l'image du système complet créée précédemment! Dans votre cas, est-ce que le chargement de l'ensemble de données est le goulet d’étranglement? Ou est-ce peut-être la création de chaque minibatch? Inutile d'optimiser la partie GPU du code d'entraînement si le goulet se situe au niveau du CPU! C'est la partie la plus importante de cette section. Gardez votre GPU occupé! Le profil d’exécution montré par Nsight Systems vous permettra d’identifier facilement les goulet d’étranglement qui empêchent votre code de s’exécuter plus rapidement.
  • Si le goulet d’étranglement se situe au niveau du code Python, il sera visible dans le profileur Python.
    • Si le code provient d'un DataLoader PyTorch et vous avez des CPU inutilisés, pouvez-vous simplement ajuster les paramètres num_workers de DataLoader à un niveau qui lui permet d'aller à la même vitesse que le GPU? Si oui, c'est la solution la plus simple.
    • Pouvez-vous réécrire cette partie du code de façon vectorisée?
    • Peut-être que cette partie du code est utile seulement dans un cas particulier? Contournez-le alors pour le cas régulier.
  • Si le goulet d’étranglement provient des transferts de données entre CPU et GPU, vous le verrez dans la section « mémoire GPU » de Nsight Systems.
    • Pouvez-vous transférer vos données en morceaux plus gros? Cette option peut être plusieurs ordres de magnitude plus efficace.
    • Pouvez-vous chevaucher le transfert et le traitement des données? Cela s’effectue en utilisant l’option pin_memory de la classe DataLoader de PyTorch, et l’option non_blocking lors des transferts de données via PyTorch.
  • Si le GPU n'est pas assez occupé, cela va être visible dans la section GPU de Nsight Systems.
    • Pouvez-vous utiliser des minibatch plus grosses, sans nuire à votre taux d'erreur de test? Vous devrez confirmer ceci avec un modèle entrainé sur toutes vos données, mais si vous donnez davantage de données à la fois au GPU, il aura moins de difficultés à se garder occupé.
    • Pouvez-vous utiliser CUDA Streams pour réaliser plusieurs calculs indépendants en même temps sur le GPU? (Présentation de NVIDIA sur CUDA Streams.)
  • Toujours pas satisfait?
    • Convertissez votre modèle PyTorch en PyTorch-Lightning. Ensuite, avec un changement d’une ligne de code, vous pouvez obtenir un entraînement multi-GPU, distribué, ou en float16.
    • Sortez Python du chemin critique :
      • Essayez TorchScript (surtout pour l'inférence).
      • Essayez Numba pour compiler votre code Python difficile à vectoriser (avec @numba.jit et @numba.cuda.jit).
    • Faites davantage de vos pré-traitements sur le GPU. Considérez :
      • Rapids.AI : fournit des librairies CUDA compatibles avec (un sous-ensemble des fonctionalités de) Pandas, Scikit-Learn et de multiples autres librairies scientifiques.
      • DALI : librairie d'importation de données de NVIDIA
      • CUVI : la librairie CUDA Vision and Imaging
      • CuPy, si vous avez du code Numpy
      • … et bien d'autres. Consultez la liste CUDA-X. Et bien d'autres, encore, librairies ouvertes; l'internet est (presque) une mine d'or!

Les apprentissages

  • Un profileur d'échantillonnage est un outil génial et a un effet multiplicateur pour ce type de travail. Prenez le temps d'apprendre à en utiliser un. Cela peut transformer la recherche de goulets d’étranglements dans votre code de « Répondre à chaque question est une expédition » à « J'ai pu répondre à 10 questions avant l’heure du lunch! ».
  • Commencez toujours par modéliser votre système! Ensuite, utilisez ce modèle pour poser des questions en regardant les rapports que produisent les profileurs. C'est une étape essentielle, mais souvent implicite. Le meilleur retour sur le temps investi sera lorsque vous passerez d'un modèle de votre système comme un vague ramassis de code à accélérer à un modèle contenant 3 à 5 morceaux, avec leur vitesse relative.
  • Votre priorité devrait être d'éliminer les goulets d'étranglement dans votre code d'entraînement. Après cette étape, concentrez-vous à utiliser au maximum la composante la plus rapide (ici, le GPU pour l'entraînement de modèles).
  • Aussitôt que vous commencez à rouler des entrainements de modèles de façon régulière, cela vaut la peine de jeter un coup d’œil à votre code avec un profileur, même si c'est du code de recherche. Combien de temps gagneriez-vous si votre modèle prenait 50 % moins de temps à entraîner? Ou s'il pouvait être entraîné deux fois, cinq fois, voire dix fois plus rapidement? Vous avez peut-être un goulet d’étranglement très simple à corriger qui pourrait vous donner des gains de cette envergure. La seule façon d'avoir une réponse est de vérifier. Le potentiel de gain devrait vous convaincre de faire des vérifications rapides.
  • Résistez à la tentation d'essayer de deviner où sont vos problèmes de performance. Traitez l'accélération de votre code avec une perspective d’expérimentation scientifique. Formez des hypothèses, et ensuite confirmez ou infirmez-les avec le bon outil (dans ce cas-ci, un profileur d'échantillonnage) avant de commencer à optimiser votre code. sinon, vous risquez d’investir votre temps sans avoir d’effet.
  • Utilisez le plus possible les librairies écrites par experts en fonctionnement d’un CPU ou GPU. Entre autres, on ne vectorise pas en Python le code seulement parce que Python est lent. Étant vectorisé, ce code réutilise alors aussi le travail d’experts mondiaux en calcul numérique, qui ont tenu compte de toutes les versions des CPU et GPU pour aller chercher le maximum de vitesse d’une opération de multiplication matricielle, par exemple. Même si vous programmez en C ou en C++, vous devriez privilégier autant que possible l’utilisation de libraires conçues avec ce niveau d’expertise et d’effort.