Déployer des TPU Multislice dans GKE


Cette page présente la configuration des TPU Multislice dans Google Kubernetes Engine (GKE). Avant de configurer Multislice dans GKE, vous devez connaître les concepts suivants :

  1. Présentation de Cloud TPU
  2. Architecture du système Cloud TPU
  3. À propos des TPU dans GKE

Qu'est-ce que TPU Multislice ?

TPU Multislice est l'organisation architecturale de VM TPU où deux tranches Cloud TPU ou plus communiquent sur le réseau de centre de données. Multislice permet un entraînement full stack à grande échelle et économique, avec un scaling presque linéaire jusqu'à plusieurs dizaines de milliers de puces de TPU. Dans une configuration Multislice, GKE déploie une charge de travail Multislice sur plusieurs tranches de TPU. La communication entre les puces d'une tranche s'effectue via des interconnexions entre puces (ICI). La communication entre les tranches s'effectue via le DCN.

Nous vous recommandons d'utiliser Multislice si votre job est trop grand pour tenir sur une seule tranche de TPU.

Disponibilité de Multislice dans GKE

  • La version standard est compatible avec Multislice dans les versions 1.27.4-gke.900 et ultérieures.
  • Autopilot est compatible avec Multislice dans les versions 1.29.2-gke.1521000 et ultérieures.
  • Multislice est compatible avec les frameworks JAX et PyTorch. La version minimale de JAX compatible est la version 2.1.
  • Multislice n'est compatible qu'avec les pools de nœuds de tranches TPU multi-hôtes. Par exemple, vous ne pouvez pas utiliser Multislice avec ct4p-hightpu-4t avec une topologie 2x2x1 ou ct5lp-hightpu-4t avec une topologie 2x2, car il s'agit de pools de nœuds de tranche de pod TPU à hôte unique
  • Multislice n'est compatible qu'avec l'entraînement synchrone multicontrôleur.
  • Les charges de travail Multislice ne peuvent s'exécuter que sur des tranches de TPU partageant le même type, la même taille et la même topologie de TPU.

Avant de commencer

Avant de commencer, effectuez les tâches suivantes :

  • Activez l'API Google Kubernetes Engine.
  • Activer l'API Google Kubernetes Engine
  • Si vous souhaitez utiliser Google Cloud CLI pour cette tâche, installez puis initialisez gcloud CLI. Si vous avez déjà installé gcloud CLI, assurez-vous de disposer de la dernière version en exécutant la commande gcloud components update.

Exécuter une charge de travail sur Multislice

Cette section explique comment exécuter une charge de travail sur Multislice. Si vous utilisez le mode GKE Autopilot, passez à la section Exécuter une charge de travail Multislice. Les clusters Autopilot qui exécutent la version 1.29.2-gke.1521000 ou une version ultérieure activent les TPU par défaut.

Préparer un pool de nœuds en mode Standard

Cette section couvre les étapes suivantes :

  1. Créer trois pools de nœuds de tranche TPU multi-hôtes.
  2. Vérifier l'état du pool de nœuds

Créer le pool de nœuds TPU

Vous pouvez créer plusieurs pools de nœuds TPU multi-hôtes. Pour les besoins de ce guide, créez trois pools de nœuds TPU multi-hôtes pour exécuter une charge de travail Multislice. Vous pouvez créer un pool de nœuds tranche de TPU multi-hôte à l'aide de Google Cloud CLI, de Terraform ou de la console Google Cloud.

gcloud

gcloud container node-pools create POOL_NAME \
    --location=LOCATION \
    --cluster=CLUSTER_NAME \
    --node-locations=NODE_ZONE \
    --machine-type=MACHINE_TYPE \
    --tpu-topology=TPU_TOPOLOGY \
    --num-nodes=NUM_NODES \
    [--spot \]
    [--enable-autoscaling \
      --max-nodes MAX_NODES]
    [--reservation-affinity=specific \
    --reservation=RESERVATION_NAME]

Remplacez les éléments suivants :

  • POOL_NAME : nom du nouveau pool de nœuds.
  • LOCATION : nom de la zone basé sur la version de TPU que vous souhaitez utiliser :

    • Pour les TPU v4, utilisez us-central2-b.
    • Les types de machines TPU v5e commençant par ct5l- ne sont jamais à hôtes multiples.
    • Pour les types de machines TPU v5e commençant par ct5lp-, utilisez us-west1-c, us-west4-a, us-west4-b, us-central1-a, us-east1-c, us-east5-b ou europe-west4-a.
    • Pour les types de machines TPU v5p commençant par ct5p-, utilisez us-east1-d, us-east5-a ou us-east5-c.

    Pour en savoir plus, consultez la section Disponibilité des TPU dans GKE.

  • CLUSTER_NAME : nom du cluster.

  • NODE_ZONE : liste d'une ou de plusieurs zones, séparées par une virgule, dans lesquelles GKE crée le pool de nœuds.

  • MACHINE_TYPE : type de machine à utiliser pour les nœuds. Pour en savoir plus sur les types de machines disponibles, consultez la section Mappage de la configuration TPU.

  • TPU_TOPOLOGY : Topologie physique de la tranche de TPU. Le format de la topologie dépend de la version du TPU :

    • TPU v4 ou v5p : définissez la topologie à trois tuples ({A}x{B}x{C}), par exemple 4x4x4.
    • TPU v5e : définissez la topologie à deux tuples ({A}x{B}), par exemple 2x2.

    Pour en savoir plus, consultez la section Topologie.

  • NUM_NODES : Nombre de nœuds dans le pool de nœuds. Doit être égal à zéro ou au produit des valeurs définies dans TPU_TOPOLOGY ({A}x{B}x{C}) divisé par le nombre de puces dans chaque VM. Pour les TPU multi-hôtes v4 et v5e, le nombre de puces dans chaque VM est de quatre. Par conséquent, si votre TPU_TOPOLOGY est 2x4x4 (TPU v4 avec quatre puces dans chaque VM), la valeur de NUM_NODES est 32/4, ce qui correspond à 8.

Vous pouvez également utiliser les options suivantes :

  • RESERVATION_NAME : nom de la réservation utilisée par GKE lors de la création du pool de nœuds. Si vous omettez cette option, GKE utilise les pools de nœuds TPU disponibles. Pour en savoir plus sur les réservations TPU, consultez la page Réservations TPU.
  • --spot : définit le pool de nœuds de sorte qu'il utilise des VM Spot pour les nœuds TPU. Ce paramètre ne peut pas être modifié après la création du pool de nœuds. Pour en savoir plus, consultez la page VM Spot.
  • --enable-autoscaling : Ajouter un pool de nœuds avec l'autoscaling activé. Lorsque GKE met à l'échelle un pool de nœuds de tranche TPU multi-hôte, il effectue un scaling de façon atomique du pool de nœuds de zéro à la taille maximale.
    • MAX_NODES : taille maximale du pool de nœuds. L'option --max-nodes est obligatoire si --enable-autoscaling est fourni. Elle doit être égale au produit des valeurs définies dans TPU_TOPOLOGY ({A}x{B}x{C}) divisée par le nombre de puces dans pour chaque VM.

Terraform

  1. Assurez-vous d'utiliser la version 4.84.0 ou ultérieure du fournisseur google.
  2. Ajoutez le bloc suivant à votre configuration Terraform :

    resource "google_container_node_pool" "NODE_POOL_RESOURCE_NAME" {
      provider           = google
      project            = PROJECT_ID
      cluster            = CLUSTER_NAME
      name               = POOL_NAME
      location           = CLUSTER_LOCATION
      node_locations     = [NODE_ZONES]
      initial_node_count = NUM_NODES
    
      autoscaling {
        max_node_count = MAX_NODES
        location_policy      = "ANY"
      }
      node_config {
        machine_type = MACHINE_TYPE
        reservation_affinity {
          consume_reservation_type = "SPECIFIC_RESERVATION"
          key = "compute.googleapis.com/reservation-name"
          values = [RESERVATION_LABEL_VALUES]
        }
        spot = true
      }
    
      placement_policy {
        type = "COMPACT"
        tpu_topology = TPU_TOPOLOGY
      }
    }
    

    Remplacez les éléments suivants :

    • NODE_POOL_RESOURCE_NAME : nom de la ressource de pool de nœuds dans le modèle Terraform.
    • PROJECT_ID : ID de votre projet
    • CLUSTER_NAME : nom du cluster existant auquel ajouter le pool de nœuds.
    • POOL_NAME : nom du pool de nœuds à créer
    • CLUSTER_LOCATION : emplacement de calcul du cluster. Nous vous recommandons de disposer d'un cluster régional pour améliorer la fiabilité du plan de contrôle Kubernetes. Vous pouvez également utiliser un cluster zonal. Pour en savoir plus, consultez la section Sélectionner une version et une topologie de TPU.
    • NODE_ZONES : liste d'une ou de plusieurs zones, séparées par une virgule, dans lesquelles GKE crée le pool de nœuds.
    • NUM_NODES : Nombre de nœuds dans le pool de nœuds. Doit être égal à zéro ou au produit du nombre de puces TPU divisé par quatre, car dans les tranches de TPU à hôtes multiples, chaque nœud TPU comporte quatre puces. Par exemple, si TPU_TOPOLOGY est défini sur 4x8, il y a 32 puces, ce qui signifie que NUM_NODES doit être égal à 8. Pour en savoir plus sur les topologies TPU, utilisez le tableau de la section Mappage de la configuration TPU.
    • TPU_TOPOLOGY : indique la topologie physique souhaitée pour la tranche de TPU. Le format de la topologie dépend de la version de TPU que vous utilisez :
      • Pour les TPU v4 : définissez la topologie à trois tuples ({A}x{B}x{C}), par exemple 4x4x4.
      • TPU v5e : définissez la topologie à deux tuples ({A}x{B}), par exemple 2x2.

    Vous pouvez également utiliser les variables suivantes :

    • RESERVATION_NAME : si vous utilisez la réservation TPU, il s'agit de la liste des libellés des ressources de réservation à utiliser lors de la création du pool de nœuds. Pour en savoir plus sur la spécification de RESERVATION_LABEL_VALUES dans le champ reservation_affinity, consultez la page Fournisseur Terraform.
    • autoscaling : Ajouter un pool de nœuds avec l'autoscaling activé. Lorsque GKE met à l'échelle un pool de nœuds de tranche TPU multi-hôte, il effectue un scaling de façon atomique du pool de nœuds de zéro à la taille maximale.
      • MAX_NODES : taille maximale du pool de nœuds. Elle doit être égale au produit des valeurs définies dans TPU_TOPOLOGY ({A}x{B}x{C}) divisée par le nombre de puces dans chaque VM.
    • spot : permet au pool de nœuds d'utiliser des VM Spot pour les nœuds TPU. Ce paramètre ne peut pas être modifié après la création du pool de nœuds. Pour en savoir plus, consultez la page VM Spot.

Console

Pour créer un pool de nœuds à l'aide de TPU, exécutez la commande suivante :

  1. Accédez à la page Google Kubernetes Engine dans Google Cloud Console.

    Accéder à Google Kubernetes Engine

  2. Dans la liste des clusters, cliquez sur le nom du cluster que vous souhaitez modifier.

  3. Cliquez sur Ajouter un pool de nœuds.

  4. Dans la section Détails du pool de nœuds, cochez la case Spécifier les emplacements de nœud.

  5. Sélectionnez la zone en fonction de la version de TPU que vous souhaitez utiliser :

    • Pour les TPU v4, utilisez us-central2-b.
    • Les types de machines TPU v5e commençant par ct5l- ne sont jamais à hôtes multiples.
    • Pour les types de machines TPU v5e commençant par ct5lp-, utilisez us-west1-c, us-west4-a, us-west4-b, us-central1-a, us-east1-c, us-east5-b ou europe-west4-a.
    • Pour les types de machines TPU v5p commençant par ct5p-, utilisez us-east1-d, us-east5-a ou us-east5-c.
  6. Dans le volet de navigation, cliquez sur Nœuds.

  7. Dans la section Configuration de la machine, sélectionnez TPU.

  8. Dans le menu déroulant Série, sélectionnez l'une des options suivantes :

    • CT4P : pour les TPU v4.
    • CT5LP : pour les TPU v5e
  9. Dans le menu déroulant Type de machine, sélectionnez le nom de la machine à utiliser pour les nœuds. Utilisez le tableau Mappage des configurations TPU pour apprendre à définir le type de machine et la topologie TPU qui créent un pool de nœuds TPU multi-hôtes.

  10. Dans le menu déroulant Topologie TPU, sélectionnez la topologie physique de la tranche de TPU.

  11. Dans la boîte de dialogue Modifications requises, cliquez sur Apporter des modifications.

  12. Assurez-vous d'avoir défini le champ Type de disque de démarrage sur Disque persistant standard ou Disque persistant SSD.

  13. Vous pouvez éventuellement cocher la case Activer les nœuds sur les VM Spot afin d'utiliser des VM Spot pour les nœuds du pool de nœuds.

  14. Cliquez sur Créer.

Vérifier l'état du pool de nœuds

  1. Obtenez des identifiants afin de pouvoir utiliser kubectl pour accéder au cluster :

    gcloud container clusters get-credentials CLUSTER_NAME \
        --project=PROJECT_ID
    

    Remplacez les éléments suivants :

    • CLUSTER_NAME : nom du cluster.
    • PROJECT_ID : ID de votre projet
  2. Utilisez kubectl dans Cloud Shell pour afficher vos nœuds TPU :

    kubectl get nodes -l cloud.google.com/gke-tpu-accelerator=TPU_ACCELERATOR \
       -l cloud.google.com/gke-tpu-topology=TPU_TOPOLOGY
    

    Remplacez les éléments suivants :

    • TPU_ACCELERATOR : type d'accélérateur TPU que vous avez utilisé lors de la création des pools de nœuds. Par exemple, tpu-v4-podslice, tpu-v5-lite-device ou tpu-v5-lite-podslice.
    • TPU_TOPOLOGY : Topologie physique de la tranche de TPU.

    Le résultat ressemble à ce qui suit :

     NAME                                    STATUS   ROLES    AGE    VERSION
     gke-tpu-20ee2cce-5tv6                   Ready    <none>   34h     v1.28.1-gke.1066000
    

Exécuter une charge de travail Multislice

Dans cette section, vous exécutez une charge de travail JAX qui affiche le nombre global de puces TPU dans la tranche TPU, puis se ferme.

Pour exécuter une charge de travail JAX, procédez comme suit :

  1. Créez le fichier manifeste tpu-multislice.yaml suivant :

    Autopilot

    apiVersion: jobset.x-k8s.io/v1alpha2
    kind: JobSet
    metadata:
      name: multislice-job
      annotations:
        alpha.jobset.sigs.k8s.io/exclusive-topology: cloud.google.com/gke-nodepool
    spec:
      failurePolicy:
        maxRestarts: 4
      replicatedJobs:
        - name: slice
          replicas: NUM_SLICES
          template:
            spec:
              parallelism: NUM_NODES
              completions: NUM_NODES
              backoffLimit: 0
              template:
                spec:
                  nodeSelector:
                    cloud.google.com/gke-tpu-accelerator: ACCELERATOR_TYPE
                    cloud.google.com/gke-tpu-topology: TPU_TOPOLOGY
                  containers:
                  - name: jax-tpu
                    image: python:3.8
                    ports:
                    - containerPort: 8471
                    - containerPort: 8080
                    - containerPort: 8431
                    command:
                    - bash
                    - -c
                    - |
                      pip install "jax[tpu]" -f https://storage.googleapis.com/jax-releases/libtpu_releases.html
                      python -c 'import jax; print("Global device count:", jax.device_count())'
                      sleep 60
                    resources:
                     limits:
                        google.com/tpu: NUM_CHIPS
    

    Standard

    apiVersion: jobset.x-k8s.io/v1alpha2
    kind: JobSet
    metadata:
      name: multislice-job
      annotations:
        alpha.jobset.sigs.k8s.io/exclusive-topology: cloud.google.com/gke-nodepool
    spec:
      failurePolicy:
        maxRestarts: 4
      replicatedJobs:
        - name: slice
          replicas: NUM_SLICES
          template:
            spec:
              parallelism: NUM_NODES
              completions: NUM_NODES
              backoffLimit: 0
              template:
                spec:
                  hostNetwork: true
                  dnsPolicy: ClusterFirstWithHostNet
                  nodeSelector:
                    cloud.google.com/gke-tpu-accelerator: ACCELERATOR_TYPE
                    cloud.google.com/gke-tpu-topology: TPU_TOPOLOGY
                  containers:
                  - name: jax-tpu
                    image: python:3.8
                    ports:
                    - containerPort: 8471
                    - containerPort: 8080
                    - containerPort: 8431
                    securityContext:
                      privileged: true
                    command:
                    - bash
                    - -c
                    - |
                      pip install "jax[tpu]" -f https://storage.googleapis.com/jax-releases/libtpu_releases.html
                      python -c 'import jax; print("Global device count:", jax.device_count())'
                      sleep 60
                    resources:
                      limits:
                       google.com/tpu: NUM_CHIPS
    

    Remplacez les éléments suivants :

    • NUM_SLICES : nombre de pools de nœuds TPU. Dans ce cas, NUM_SLICES est égal à 3.
    • ACCELERATOR_TYPE : type d'accélérateur TPU que vous avez utilisé lors de la création des pools de nœuds. Par exemple, tpu-v4-podslice, tpu-v5-lite-device ou tpu-v5-lite-podslice.
    • TPU_TOPOLOGY : Topologie physique de la tranche de TPU. Par exemple, 4x4x4 ou 2x2 selon la version du TPU.
    • NUM_NODES : Nombre de nœuds dans le pool de nœuds. Doit être égal à zéro ou au produit des valeurs définies dans TPU_TOPOLOGY ({A}x{B}x{C}) divisé par le nombre de puces dans chaque VM. Pour les TPU multi-hôtes v4, le nombre de puces dans chaque VM est de quatre. Pour les TPU multi-hôtes v5e, le nombre de puces dans chaque VM est de un, quatre ou huit. Par conséquent, si votre TPU_TOPOLOGY est 2x4x4 (TPU v4 avec quatre puces dans chaque VM), la valeur de NUM_NODES est 32/4, ce qui correspond à 8.
    • NUM_CHIPS : pour les TPU multi-hôtes v4, le nombre de puces dans chaque VM est de quatre. Pour les TPU multi-hôtes v5e, le nombre de puces dans chaque VM est de un, quatre ou huit. Pour en savoir plus, consultez la section puces TPU sur la VM TPU.

    Dans le fichier manifeste :

    • JobSet est un service headless portant le même nom que le nom JobSet. Dans le cas présent, il s'agit de multislice-job.
    • La valeur maxRestarts: 4 indique le nombre maximal de redémarrages de GKE sur une ressource JobSet en cas d'échec d'un job enfant. Si le redémarrage de JobSet atteint la valeur maximale définie, la ressource JobSet est marquée comme ayant échoué.
    • Les champs parallelism et completions correspondent au nombre de nœuds dans chaque pool de nœuds.
    • Le paramètre backoff est égal à 0, car Multislice n'accepte que l'entraînement synchrone multicontrôleurs. Doit être défini sur 0. Faire échouer le job en cas de défaillance d'un pod.
    • Les valeurs de la section d'affinité permettent de s'assurer qu'une seule charge de travail TPU Multislice s'exécute dans un groupe de segments multiples.
    • containerPort: 8080 est le port du coordinateur MXLA.
    • containerPort: 8431 est le port permettant d'exporter les métriques d'utilisation du TPU.
    • L'élément securityContext: privileged: true indique que le mode privilégié est activé pour les nœuds pour l'accès aux TPU. Les nœuds de GKE version 1.28 ou ultérieure n'ont pas besoin d'activer le mode privilégié pour accéder aux TPU. Pour en savoir plus, consultez la page Exécuter des conteneurs sans mode privilégié.
  2. Appliquez le fichier manifeste :

    kubectl apply -f tpu-multislice.yaml
    
  3. Confirmez que la charge de travail est acceptée :

    kubectl get jobsets
    

    Le résultat ressemble à ce qui suit :

    NAME            RESTARTS   COMPLETED   AGE
    multislice-job                         3s
    
  4. Surveillez l'état des pods provisionnés :

    kubectl get pods
    

    Le résultat ressemble à ce qui suit :

     NAME                                READY   STATUS      RESTARTS   AGE
     multislice-job-slice-0-0-wzq9t      0/1     Completed   0          2m31s
     multislice-job-slice-0-1-zf4dp      0/1     Completed   0          2m30s
     multislice-job-slice-1-0-hbfn5      0/1     Completed   0          2m31s
     multislice-job-slice-1-1-45fgl      0/1     Completed   0          2m30s
     multislice-job-slice-2-0-wjbp4      0/1     Completed   0          2m30s
     multislice-job-slice-2-1-lwnvs      0/1     Completed   0          2m30s
    

JobSet multislice-job planifie, crée, puis exécute les pods jusqu'à la fin. Les noms des pods sont au format <jobsetName>-<jobName>-<jobReplicaIndex>-<randomSuffix>. Le préfixe jobsetName détermine le JobJob auquel appartient le pod.

Configurations supplémentaires

Les sections suivantes décrivent les configurations supplémentaires que vous pouvez appliquer à votre objet Multislice.

Activer hostNetwork sur vos pods GKE Standard

Pour améliorer les performances réseau entre les tranches de TPU, nous vous recommandons d'activer hostNetworking. Utilisez hostNetwork: true dans la spécification de pod pour ignorer toute la pile réseau Kubernetes et laisser vos pods Kubernetes utiliser directement le réseau hôte pour une communication de VM à VM.

Pour activer hostNetworking, supprimez les deux lignes suivantes de la spécification de pod :

hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet

Pour continuer à utiliser podHostnames pour la découverte de nœuds de calcul avec hostNetwork, définissez dnsPolicy: ClusterFirstWithHostNet. Cela est important lorsque vous exécutez des tâches de reprise automatique et que vous devez disposer des mêmes noms pour actualiser les mêmes points de contrôle.

Journalisation

Les journaux émis par les conteneurs s'exécutant sur des nœuds GKE, y compris les VM TPU, sont visibles dans l'explorateur de journaux, si vous avez activé la journalisation système GKE dans votre cluster.

Vous pouvez afficher vos journaux à partir de GKE à l'aide de l'explorateur de journaux avec le filtre suivant pour afficher les journaux de conteneur de votre charge de travail :

resource.type="k8s_container"
resource.labels.cluster_name=CLUSTER_NAME
labels."k8s-pod/jobset_sigs_k8s_io/jobset-name"=JOBSET_NAME

Utilisez le filtre suivant pour les tranches et les nœuds de calcul TPU :

resource.type="k8s_container"
resource.labels.cluster_name=CLUSTER_NAME
labels."k8s-pod/jobset_sigs_k8s_io/jobset-name"=JOBSET_NAME
resource.labels.pod_name:<jobSetName>-<replicateJobName>-<job-index>-<worker-index>

Observabilité et métriques

En plus des métriques TPU générales, il existe quatre métriques d'exécution TPU multislice spécifiques. Ces métriques sont disponibles dans GKE 1.29.1-gke.1016000 ou version ultérieure. La charge de travail TPU doit utiliser la version JAX 0.4.24

Voici les métriques multislice disponibles :

  • Latences de transfert DCN (réseau de centres de données) : distribution des latences de transfert réseau pour le trafic multislice.
  • Latences collectives : répartition de la latence collective de bout en bout pour le trafic multislice.
  • Latences de transfert hôte à appareil : répartition de la latence de transfert hôte à appareil pour chaque fragment de données pour le trafic multislice.
  • Latences de transfert d'appareil à hôte : répartition de la latence de transfert entre l'appareil et l'hôte, pour chaque fragment de données pour le trafic multislice.

Ces métriques se trouvent dans le schéma du conteneur Kubernetes (k8s_container) :

  • kubernetes.io/container/multislice/network/dcn_transfer_latencies
  • kubernetes.io/container/multislice/network/collective_end_to_end_latencies
  • kubernetes.io/container/multislice/accelerator/host_to_device_transfer_latencies
  • kubernetes.io/container/multislice/accelerator/device_to_host_transfer_latencies

Tranche de pod TPU ou Multislice

Le tableau suivant différencie l'organisation architecturale d'une tranche de TPU et Multislice :

Tranche de pod TPU Multicouche
Interconnectivité La charge de travail s'exécute sur une seule tranche de TPU. Toutes les puces TPU d'une tranche sont connectées à ICI. La charge de travail s'exécute sur plusieurs tranches de TPU. La communication au sein d'une tranche s'effectue via ICI. La communication entre les tranches s'effectue via DCN.
Pools de nœuds compatibles Tranche de TPU à hôte unique et tranche de TPU à multi-hôte Groupes de tranches de TPU multi-hôte
Type de charge de travail recommandé IndexedJob ou JobSet JobSet

Étapes suivantes