Vertex AI: entraînement multi-nœuds et apprentissage par transfert avec TensorFlow

1. Aperçu

Dans cet atelier, vous allez utiliser Vertex AI pour exécuter une tâche d'entraînement multi-nœud pour un modèle TensorFlow.

Objectifs

Vous apprendrez à effectuer les tâches suivantes :

  • Modifier le code de l'application d'entraînement pour l'entraînement multi-nœud
  • Configurer et lancer une tâche d'entraînement multi-nœud depuis l'UI Vertex AI
  • Configurer et lancer une tâche d'entraînement multi-nœud avec le SDK Vertex

Le coût total d'exécution de cet atelier sur Google Cloud est d'environ 5$.

2. Présentation de Vertex AI

Cet atelier utilise la solution d'IA la plus récente de Google Cloud. Vertex AI intègre toutes les offres de ML de Google Cloud pour créer une expérience de développement fluide. Auparavant, les modèles entraînés avec AutoML et les modèles personnalisés étaient accessibles via des services distincts. La nouvelle offre regroupe ces deux types de modèles en une seule API, ainsi que d'autres nouveaux produits. Vous pouvez également migrer des projets existants vers Vertex AI. Pour envoyer des commentaires, accédez à la page d'assistance.

Vertex AI comprend de nombreux produits différents qui permettent de gérer les workflows de ML de bout en bout. Cet atelier porte sur les produits présentés ci-dessous: Training (Formation) et Workbench (Workbench).

Présentation des produits Vertex

3. Présentation du cas d'utilisation

Dans cet atelier, vous allez utiliser l'apprentissage par transfert pour entraîner un modèle de classification d'images sur l'ensemble de données de cassava à partir de ensembles de données TensorFlow. L'architecture que vous allez utiliser est un modèle ResNet50 provenant de la bibliothèque tf.keras.applications pré-entraînée sur l'ensemble de données Imagenet.

Pourquoi l'entraînement distribué ?

Si vous disposez d'un seul GPU, TensorFlow utilisera cet accélérateur pour accélérer l'entraînement du modèle, sans qu'aucune tâche supplémentaire ne soit nécessaire de votre part. Toutefois, si vous souhaitez profiter d'un avantage supplémentaire en utilisant plusieurs GPU sur une seule machine ou plusieurs machines (chacune avec plusieurs GPU), vous devez utiliser tf.distribute, la bibliothèque de TensorFlow permettant d'exécuter des calculs entre plusieurs appareils. Un appareil fait référence à un processeur ou à un accélérateur, tel que des GPU ou des TPU, sur une machine sur laquelle TensorFlow peut exécuter des opérations.

Le moyen le plus simple de démarrer avec un entraînement distribué consiste à utiliser une machine unique avec plusieurs appareils GPU. Une stratégie de distribution TensorFlow du module tf.distribute gérera la coordination de la distribution des données et des mises à jour des gradients sur tous les GPU. Si vous maîtrisez l'entraînement sur un seul hôte et souhaitez continuer à évoluer, l'ajout de plusieurs machines à votre cluster peut vous permettre d'améliorer vos performances. Vous pouvez utiliser un cluster de machines reposant uniquement sur le processeur, ou chacune d'elles avec un ou plusieurs GPU. Dans cet atelier, nous aborderons le dernier cas et l'utilisation de MultiWorkerMirroredStrategy pour répartir l'entraînement d'un modèle TensorFlow entre plusieurs machines sur Vertex AI.

MultiWorkerMirroredStrategy est une stratégie de parallélisme de données synchrone que vous pouvez utiliser en seulement quelques modifications de code. Une copie du modèle est créée sur chaque appareil de votre cluster. Les mises à jour de gradient suivantes s'effectueront de manière synchrone. Autrement dit, chaque nœud de calcul calcule la propagation avant et arrière du modèle sur une autre tranche des données d'entrée. Les gradients calculés de chacune de ces tranches sont ensuite agrégés sur tous les appareils d'une machine et de toutes les machines du cluster, puis réduits (généralement via une moyenne) dans un processus connu sous le nom de "All-reduced". L'optimiseur effectue ensuite les mises à jour des paramètres avec ces gradients réduits de sorte que les appareils restent synchronisés. Pour en savoir plus sur l'entraînement distribué avec TensorFlow, regardez la vidéo ci-dessous:

4. Configurer votre environnement

Pour exécuter cet atelier de programmation, vous aurez besoin d'un projet Google Cloud Platform dans lequel la facturation sera activée. Pour créer un projet, suivez ces instructions.

Étape 1: Activez l'API Compute Engine

Accédez à Compute Engine et sélectionnez Enable (Activer) si ce n'est pas déjà fait. Vous en aurez besoin pour créer votre instance de notebook.

Étape 2: Activez l'API Container Registry

Accédez à Container Registry et sélectionnez Enable (Activer) si ce n'est pas déjà fait. Cette clé vous permettra de créer un conteneur pour votre tâche d'entraînement personnalisée.

Étape 3: Activez l'API Vertex AI

Accédez à la section Vertex AI de Cloud Console, puis cliquez sur Activer l'API Vertex AI.

Tableau de bord Vertex AI

Étape 4: Créez une instance Vertex AI Workbench

Dans la section Vertex AI de Cloud Console, cliquez sur Workbench:

Menu Vertex AI

Activez l'API Notebooks si elle ne l'est pas encore.

API pour notebooks

Une fois l'option activée, cliquez sur NOTEG Notebooks:

Interface utilisateur de notebook

Sélectionnez ensuite NOUVEAU NOTEBOOK.

new_notebook

Attribuez un nom à votre notebook, puis cliquez sur Advanced Settings (Paramètres avancés).

créer_notebook

Sous "Paramètres avancés", activez l'arrêt en cas d'inactivité et définissez le nombre de minutes sur 60. Cela signifie que votre notebook s'arrête automatiquement lorsqu'il n'est pas utilisé. Vous ne payez donc pas de frais inutiles.

idle_timeout

Sous Sécurité, sélectionnez "Activer le terminal" si elle n'est pas déjà activée.

activer_terminal

Vous pouvez conserver tous les autres paramètres avancés tels quels.

Cliquez ensuite sur Create (Créer). Le provisionnement de l'instance prend quelques minutes.

Une fois l'instance créée, sélectionnez Ouvrir JupyterLab.

open_jupyterlab.

La première fois que vous utilisez une nouvelle instance, vous êtes invité à vous authentifier. Pour ce faire, suivez les étapes indiquées dans l'interface utilisateur.

authentifier

5. Conteneuriser le code de l'application d'entraînement

Vous allez envoyer cette tâche d'entraînement à Vertex en plaçant le code de votre application d'entraînement dans un conteneur Docker et en le transmettant à Google Container Registry. Cette approche vous permet d'entraîner un modèle conçu avec n'importe quel framework.

Pour commencer, à partir du menu du Lanceur d'applications, ouvrez une fenêtre Terminal dans votre instance de notebook:

Ouvrir le terminal dans le notebook

Créez un répertoire appelé cassava et ajoutez-y le code de commande suivant:

mkdir cassava
cd cassava

Étape 1: Créez un Dockerfile

La première étape pour conteneuriser votre code consiste à créer un Dockerfile. Dans le fichier Dockerfile, vous allez inclure toutes les commandes nécessaires à l'exécution de l'image. Il installe toutes les bibliothèques nécessaires et configure le point d'entrée du code d'entraînement.

À partir de votre terminal, créez un Dockerfile vide:

touch Dockerfile

Ouvrez le Dockerfile et copiez-y les éléments suivants:

FROM gcr.io/deeplearning-platform-release/tf2-gpu.2-7

WORKDIR /

# Copies the trainer code to the docker image.
COPY trainer /trainer

# Sets up the entry point to invoke the trainer.
ENTRYPOINT ["python", "-m", "trainer.task"]

Ce Dockerfile utilise l'image Docker TensorFlow Enterprise 2.7 Deep Learning. Les conteneurs de deep learning sur Google Cloud sont préinstallés sur de nombreux frameworks de ML et de science des données courants. Après avoir téléchargé cette image, ce Dockerfile configure le point d'entrée du code d'entraînement. Vous n'avez pas encore créé ces fichiers. À l'étape suivante, vous ajouterez le code pour l'entraînement et le réglage du modèle.

Étape 2: Créez un bucket Cloud Storage

Dans cette tâche d'entraînement, vous allez exporter le modèle TensorFlow entraîné vers un bucket Cloud Storage. Depuis le terminal, exécutez la commande suivante pour définir une variable d'environnement pour votre projet, en veillant à remplacer your-cloud-project par l'ID de votre projet:

PROJECT_ID='your-cloud-project'

Exécutez ensuite la commande suivante dans votre terminal pour créer un bucket dans votre projet.

BUCKET="gs://${PROJECT_ID}-bucket"
gsutil mb -l us-central1 $BUCKET

Étape 3: Ajouter le code d'entraînement du modèle

Depuis le terminal, exécutez la commande suivante afin de créer un répertoire pour le code d'entraînement et un fichier Python dans lequel vous ajouterez le code:

mkdir trainer
touch trainer/task.py

Votre répertoire cassava/ doit maintenant contenir le code suivant:

+ Dockerfile
+ trainer/
    + task.py

Ensuite, ouvrez le fichier task.py que vous venez de créer et copiez le code ci-dessous. Vous devez remplacer {your-gcs-bucket} par le nom du bucket Cloud Storage que vous venez de créer.

import tensorflow as tf
import tensorflow_datasets as tfds
import os

PER_REPLICA_BATCH_SIZE = 64
EPOCHS = 2

# TODO: replace {your-gcs-bucket} with the name of the Storage bucket you created earlier
BUCKET = 'gs://{your-gcs-bucket}/mwms'

def preprocess_data(image, label):
  '''Resizes and scales images.'''

  image = tf.image.resize(image, (300,300))
  return tf.cast(image, tf.float32) / 255., label

def create_dataset(batch_size):
  '''Loads Cassava dataset and preprocesses data.'''

  data, info = tfds.load(name='cassava', as_supervised=True, with_info=True)
  number_of_classes = info.features['label'].num_classes
  train_data = data['train'].map(preprocess_data,
                                 num_parallel_calls=tf.data.experimental.AUTOTUNE)
  train_data  = train_data.shuffle(1000)
  train_data  = train_data.batch(batch_size)
  train_data  = train_data.prefetch(tf.data.experimental.AUTOTUNE)

  # Set AutoShardPolicy
  options = tf.data.Options()
  options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA
  train_data = train_data.with_options(options)

  return train_data, number_of_classes

def create_model(number_of_classes):
  '''Creates and compiles pretrained ResNet50 model.'''

  base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False)
  x = base_model.output
  x = tf.keras.layers.GlobalAveragePooling2D()(x)
  x = tf.keras.layers.Dense(1016, activation='relu')(x)
  predictions = tf.keras.layers.Dense(number_of_classes, activation='softmax')(x)
  model = tf.keras.Model(inputs=base_model.input, outputs=predictions)

  model.compile(
      loss='sparse_categorical_crossentropy',
      optimizer=tf.keras.optimizers.Adam(0.0001),
      metrics=['accuracy'])

  return model

def _is_chief(task_type, task_id):
  '''Helper function. Determines if machine is chief.'''

  return task_type == 'chief'

def _get_temp_dir(dirpath, task_id):
  '''Helper function. Gets temporary directory for saving model.'''

  base_dirpath = 'workertemp_' + str(task_id)
  temp_dir = os.path.join(dirpath, base_dirpath)
  tf.io.gfile.makedirs(temp_dir)
  return temp_dir

def write_filepath(filepath, task_type, task_id):
  '''Helper function. Gets filepath to save model.'''

  dirpath = os.path.dirname(filepath)
  base = os.path.basename(filepath)
  if not _is_chief(task_type, task_id):
    dirpath = _get_temp_dir(dirpath, task_id)
  return os.path.join(dirpath, base)

def main():
  # Create strategy
  strategy = tf.distribute.MultiWorkerMirroredStrategy()

  # Get data
  global_batch_size = PER_REPLICA_BATCH_SIZE * strategy.num_replicas_in_sync
  train_data, number_of_classes = create_dataset(global_batch_size)

  # Wrap variable creation within strategy scope
  with strategy.scope():
    model = create_model(number_of_classes)

  model.fit(train_data, epochs=EPOCHS)

  # Determine type and task of the machine from
  # the strategy cluster resolver
  task_type, task_id = (strategy.cluster_resolver.task_type,
                        strategy.cluster_resolver.task_id)

  # Based on the type and task, write to the desired model path
  write_model_path = write_filepath(BUCKET, task_type, task_id)
  model.save(write_model_path)

if __name__ == "__main__":
    main()

Avant de créer le conteneur, examinons de plus près le code qui utilise MultiWorkerMirroredStrategy à partir de l'API tf.distribute.Strategy.

Quelques éléments du code sont nécessaires pour que celui-ci fonctionne avec MultiWorkerMirroredStrategy.

  1. Les données doivent être segmentées, ce qui signifie que chaque nœud de calcul se voit attribuer un sous-ensemble de l'ensemble de données complet. Par conséquent, à chaque étape, une taille globale de lot d'éléments d'ensemble de données sans chevauchement est traitée par chaque nœud de calcul. Ce partitionnement s'effectue automatiquement avec tf.data.experimental.AutoShardPolicy, qui peut être défini sur FILE ou DATA. Dans cet exemple, la fonction create_dataset() définit AutoShardPolicy sur DATA, car l'ensemble de données du manioc n'est pas téléchargé sous la forme de plusieurs fichiers. Toutefois, si vous ne définissez pas la règle sur DATA, la règle AUTO par défaut est appliquée et le résultat final reste identique. En savoir plus sur la segmentation d'ensembles de données avec MultiWorkerMirroredStrategy
  2. Dans la fonction main(), l'objet MultiWorkerMirroredStrategy est créé. Vous encapsulez ensuite la création des variables de modèle dans le champ d'application de la stratégie. Cette étape essentielle indique à TensorFlow les variables à mettre en miroir sur les instances dupliquées.
  3. La taille du lot est mise à l'échelle par la valeur num_replicas_in_sync. Cela garantit que chaque instance dupliquée traite le même nombre d'exemples à chaque étape. La mise à l'échelle de la taille de lot est une bonne pratique lorsque vous utilisez des stratégies de parallélisme de données synchrones dans TensorFlow.
  4. L'enregistrement du modèle est un peu plus complexe dans le cas d'un nœud de calcul multi-nœud, car la destination doit être différente pour chacun des nœuds de calcul. Le nœud de calcul principal enregistre le modèle dans le répertoire souhaité, tandis que les autres nœuds de calcul enregistrent le modèle dans des répertoires temporaires. Il est important que ces répertoires temporaires soient uniques pour éviter que plusieurs nœuds de calcul n'écrivent au même emplacement. L'enregistrement peut contenir des opérations collectives, ce qui signifie que tous les travailleurs doivent sauver, et pas seulement le chef. Les fonctions _is_chief(), _get_temp_dir(), write_filepath() et main() incluent toutes du code récurrent qui permet d'enregistrer le modèle.

Notez que si vous avez utilisé MultiWorkerMirroredStrategy dans un autre environnement, vous avez peut-être configuré la variable d'environnement TF_CONFIG. Vertex AI définit automatiquement TF_CONFIG pour vous. Vous n'avez donc pas besoin de définir cette variable sur chaque machine de votre cluster.

Étape 4: Créez le conteneur

Depuis le terminal, exécutez la commande suivante pour définir une variable d'environnement pour votre projet, en veillant à remplacer your-cloud-project par l'ID de votre projet:

PROJECT_ID='your-cloud-project'

Définissez une variable avec l'URI de votre image de conteneur dans Google Container Registry:

IMAGE_URI="gcr.io/$PROJECT_ID/multiworker:cassava"

Ensuite, créez le conteneur en exécutant la commande suivante à partir de la racine de votre répertoire cassava:

docker build ./ -t $IMAGE_URI

Enfin, transférez-le vers Google Container Registry:

docker push $IMAGE_URI

Maintenant que le conteneur est transféré dans Container Registry, vous êtes prêt à lancer une tâche d'entraînement.

6. Exécuter une tâche d'entraînement multi-nœud sur Vertex AI

Cet atelier utilise un entraînement personnalisé via un conteneur personnalisé sur Google Container Registry, mais vous pouvez également exécuter une tâche d'entraînement avec les conteneurs prédéfinis.

Pour commencer, accédez à la section Entraînement dans la section "Vertex" de Cloud Console:

Menu uCAIP

Étape 1: Configurez la tâche d'entraînement

Cliquez sur Create (Créer) pour saisir les paramètres de votre tâche d'entraînement.

  • Sous Ensemble de données, sélectionnez Aucun ensemble de données géré.
  • Sélectionnez ensuite Custom training (advanced) (Entraînement personnalisé) comme méthode d'entraînement, puis cliquez sur Continue (Continuer).
  • Saisissez multiworker-cassava (ou le nom que vous souhaitez attribuer à votre modèle) dans le champ Nom du modèle
  • Cliquez sur Continuer.

À l'étape "Paramètres du conteneur", sélectionnez Conteneur personnalisé:

Option de conteneur personnalisé

Dans la première zone (Container image), saisissez la valeur de votre variable IMAGE_URI dans la section précédente. Il devrait s'agir de gcr.io/your-cloud-project/multiworker:cassava, avec votre propre ID de projet. Laissez les autres champs vides et cliquez sur Continue (Continuer).

Ignorez l'étape "Hhyperparamètres" (Hhyperparamètres) en cliquant à nouveau sur Continue (Continuer).

Étape 2: Configurez le cluster de calcul

Vertex AI fournit quatre pools de nœuds de calcul pour couvrir les différents types de tâches de la machine.

Le pool de nœuds de calcul 0 configure le nœud principal, le principal, le programmeur ou le maître. Dans MultiWorkerMirroredStrategy, toutes les machines sont désignées comme des nœuds de calcul, ce qui correspond aux machines physiques sur lesquelles le calcul dupliqué est exécuté. Chaque machine est considérée comme un nœud de calcul. Toutefois, un nœud de calcul doit effectuer un travail supplémentaire, comme enregistrer des points de contrôle et écrire des fichiers récapitulatifs dans TensorBoard. On appelle cette machine le chef. Il n'y a qu'un seul nœud de calcul principal. Le nombre de nœuds de calcul pour le pool de nœuds de calcul 0 est donc toujours 1.

Dans Calcul et tarification, laissez la région sélectionnée telle quelle et configurez le pool de nœuds de calcul 0 comme suit:

Pool de nœuds de calcul_0

Le pool de nœuds de calcul 1 vous permet de configurer les nœuds de calcul de votre cluster.

Configurez le pool de nœuds de calcul 1 comme suit:

Pool de nœuds de calcul_1

Le cluster est maintenant configuré pour avoir deux machines réservées au processeur. Lorsque le code de l'application d'entraînement est exécuté, MultiWorkerMirroredStrategy répartit l'entraînement sur les deux machines.

MultiWorkerMirroredStrategy ne dispose que des types de tâche principale et de nœud de calcul. Il n'est donc pas nécessaire de configurer les pools de nœuds de calcul supplémentaires. Toutefois, si vous utilisez la méthode ParameterServerStrategy de TensorFlow, vous devez configurer vos serveurs de paramètres dans le pool de nœuds de calcul 2. Si vous souhaitez ajouter un évaluateur à votre cluster, vous devez configurer cette machine dans le pool de nœuds de calcul 3.

Cliquez sur Start training (Démarrer l'entraînement) pour lancer la tâche de réglage des hyperparamètres. Dans la section "Training" (Entraînement) de la console, sousPIPELINES POUR LA FORMATION onglet s'affiche la nouvelle tâche:

Tâches d'entraînement

🎉 Félicitations ! 🎉

Vous avez appris à utiliser Vertex AI pour:

  • Lancer une tâche d'entraînement multi-nœud pour le code d'entraînement fourni dans un conteneur personnalisé Dans cet exemple, vous avez utilisé un modèle TensorFlow. Vous pouvez toutefois entraîner un modèle avec n'importe quel framework à l'aide de conteneurs personnalisés ou intégrés.

Pour en savoir plus sur les différentes parties de Vertex, consultez la documentation.

7. [Facultatif] Utiliser le SDK Vertex

La section précédente explique comment lancer la tâche d'entraînement via l'UI. Dans cette section, vous disposez d'une autre façon d'envoyer la tâche d'entraînement à l'aide de l'API Vertex Python.

Revenez à votre instance de notebook et créez un notebook TensorFlow 2 à partir du Lanceur d'applications:

new_notebook

Importez le SDK Vertex AI.

from google.cloud import aiplatform

Pour lancer la tâche d'entraînement multi-nœud, vous devez d'abord définir la spécification du pool de nœuds de calcul. Notez que l'utilisation des GPU dans la spécification est entièrement facultative. Vous pouvez supprimer accelerator_type et accelerator_count si vous souhaitez disposer d'un cluster à processeur uniquement, comme indiqué dans la section précédente.

# The spec of the worker pools including machine type and Docker image
# Be sure to replace {YOUR-PROJECT-ID} with your project ID.
worker_pool_specs=[
     {
        "replica_count": 1,
        "machine_spec": {
          "machine_type": "n1-standard-8", "accelerator_type": "NVIDIA_TESLA_V100", "accelerator_count": 1
        },
        "container_spec": {"image_uri": "gcr.io/{YOUR-PROJECT-ID}/multiworker:cassava"}
      },
      {
        "replica_count": 1,
        "machine_spec": {
          "machine_type": "n1-standard-8", "accelerator_type": "NVIDIA_TESLA_V100", "accelerator_count": 1
        },
        "container_spec": {"image_uri": "gcr.io/{YOUR-PROJECT-ID}/multiworker:cassava"}
      }
]

Ensuite, créez et exécutez un CustomJob. Vous devez remplacer {YOUR_BUCKET} par un bucket dans votre projet pour la préproduction. Vous pouvez utiliser le bucket que vous avez créé précédemment.

# Replace YOUR_BUCKET
my_multiworker_job = aiplatform.CustomJob(display_name='multiworker-cassava-sdk',
                              worker_pool_specs=worker_pool_specs,
                              staging_bucket='gs://{YOUR_BUCKET}')

my_multiworker_job.run()

Dans la section "Training" (Entraînement) de la console, sous l'onglet CUSTOM JOBS (Tâches personnalisées), vous verrez votre tâche d'entraînement:

Tâches personnalisées

8. Nettoyage

Comme nous avons configuré le notebook de sorte qu'il expire au bout de 60 minutes inactives, nous n'avons pas besoin de nous arrêter pour arrêter l'instance. Si vous souhaitez arrêter manuellement l'instance, cliquez sur le bouton "Stop" (Arrêter) dans la section "Vertex AI Workbench" de la console. Si vous souhaitez supprimer entièrement le notebook, cliquez sur le bouton "Supprimer".

Arrêter l'instance

Pour supprimer le bucket de stockage, utilisez le menu de navigation de Cloud Console, accédez à Storage et sélectionnez votre bucket, puis cliquez sur "Delete" (Supprimer) :

Supprimer l'espace de stockage