Dépannage

Ce guide propose une aide au dépannage pour les utilisateurs souhaitant exécuter leurs propres modèles TensorFlow sur Cloud TPU. Pour une introduction plus générale à Cloud TPU, consultez le guide de démarrage rapide ou le tutoriel MNIST.

Aperçu

Les problèmes courants rencontrés avec les Cloud TPU appartiennent aux catégories suivantes :

  1. Le script d'entraînement ne parvient pas à se connecter au serveur TPU.

  2. Le TPU renvoie une erreur lors d'une tentative d'exécution du modèle.

  3. Le modèle ne tient pas dans la mémoire du TPU.

  4. Le modèle peut s'exécuter sur TPU, mais la vitesse d'entraînement n'est pas aussi rapide qu'espéré.

  5. Le modèle peut s'exécuter sur le TPU, mais la justesse du modèle entraîné sur le TPU est inférieure à celle d'une référence obtenue par entraînement sur processeur/GPU.

Pour une aide plus spécialisée sur le portage de certains types de réseaux de neurones vers l'architecture TPU, consultez les tutoriels Cloud TPU.

Problèmes de connexion au serveur TPU

Lorsque vous entraînez un modèle sur un TPU, vous devez transmettre une URL de serveur TPU distant avec le paramètre master dans RunConfig. En coulisses, TensorFlow crée une session distante tf.Session sur ce serveur. Cette section fournit des solutions de dépannage pour les cas où TensorFlow cesse de répondre ou renvoie une erreur lors de la connexion au serveur TPU. L'étape de compilation du graphe TPU peut prendre beaucoup de temps pour les modèles volumineux. Laissez le script s'exécuter pendant au moins cinq minutes avant de conclure qu'il a cessé de répondre.

La première étape consiste à vérifier si le problème concerne le serveur lui-même ou votre pipeline d'entraînement TensorFlow. Pour ce faire, exécutez le tutoriel MNIST en utilisant l’URL de votre serveur TPU et vérifiez qu’il fonctionne correctement. S'il y a toujours des problèmes de connexion avec le tutoriel MNIST, cela confirme que le problème est lié au serveur TPU. Dans ce cas :

  1. Exécutez la commande suivante pour répertorier les TPU disponibles :

    (vm)$ gcloud compute tpus list

    Vous devrez peut-être aussi définir les paramètres zone et project, comme indiqué dans le tutoriel MNIST. Cela permet d'afficher des messages de sortie tels que :

    NAME       ZONE           ACCELERATOR_TYPE  NETWORK_ENDPOINT   NETWORK  RANGE          STATUS
    demo-tpu   us-central1-b  v2-8              10.240.1.2:8470    default  10.240.1.0  READY

  2. Vérifiez que vous transmettez la valeur correcte à --tpu (demo-tpu dans l'exemple ci-dessus) et que ce TPU est classé en tant que READY. Assurez-vous également que vos paramètres zone et project sont définis sur les valeurs suivantes :

    (vm)$ gcloud config set project your-project-name
    (vm)$ gcloud config set compute/zone us-central1-b
  3. Si votre TPU n'est pas répertorié comme étant READY ou si vous avez encore des difficultés de connexion, redémarrez le serveur manuellement à l'aide de cette commande :

    (vm)$ gcloud compute tpus stop $TPU_SERVER_NAME ∓& gcloud compute tpus start $TPU_SERVER_NAME

    Dans l'exemple ci-dessus, $TPU_NAMEcorrespond à demo-tpu. Cette opération peut prendre plusieurs minutes.

  4. Exécutez à nouveau la commande ... tpus list ci-dessus et attendez que le TPU soit dans un état READY. Cela peut prendre quelques minutes.

  5. Essayez de relancer le tutoriel MNIST.

  6. Si vous ne parvenez toujours pas à exécuter le tutoriel MNIST, demandez de l'aide via l'un des mécanismes décrits dans la section Obtenir de l'aide.

Déboguer les erreurs courantes

Impossible d'utiliser le système de fichiers local

Message d'erreur

InvalidArgumentError: Unimplemented: File system scheme '[local]' not implemented

Détails

Tous les fichiers d'entrée et le répertoire de modèles doivent utiliser un chemin d'accès au bucket Cloud Storage (gs://bucket-name/...). Ce bucket doit être accessible depuis le serveur TPU. Notez que l'intégralité du traitement des données et de la gestion des points de contrôle du modèle sont réalisés sur le serveur TPU et non sur la machine locale. Pour plus d'informations sur la configuration adéquate de Cloud Storage pour une utilisation avec Cloud TPU, reportez-vous au guide Se connecter aux buckets Cloud Storage.

La fonction tf.data.Dataset.cache() ne permet pas de mettre en cache vers le système de fichiers local

Message d'erreur

tensorflow.python.framework.errors_impl.UnimplementedError: File system scheme '[local]' not implemented (file: '[filename].lockfile')

Détails

Il est possible de mettre en cache un fichier tf.data.Dataset. L'appel .cache() présente deux mises en œuvre :

  1. en mémoire, si aucun argument n'est transmis ;

  2. vers un système de fichiers, si un chemin de fichier est transmis en argument.

Sur Cloud TPU, (1) fonctionne (tant que l'objet tient dans la mémoire disponible), mais (2) ne fonctionne pas lorsque l'on tente d'enregistrer l'objet vers le système de fichiers local, ce qui génère alors l'erreur ci-dessus.

Les extraits de code suivants illustrent chacune de ces situations :

(1)
 import tensorflow as tf

def main():
  print('Hello world!')
  ds = tf.data.Dataset.range(10)
  ds = ds.cache()

runs to completion.

(2)
 import tensorflow as tf

def main():
  print('Hello world!')
  ds = tf.data.Dataset.range(10)
  ds = ds.cache('/tmp/foo')

generates the error.

Le guide de l'API contient des informations plus détaillées sur tf.data.Dataset.cache().

Type de données non compatible

Message d'erreur

TypeError: DataType is not a supported TPU infeed type.

Détails

Actuellement, seuls les types de données tf.float32, tf.int32, tf.bfloat16 et tf.bool sont acceptés sur le TPU. Les autres types de données courants, tels que tf.uint8, tf.string et tf.int64 doivent être convertis dans l'un des types compatibles lors du prétraitement des données.

Consultez le tutoriel MNIST pour en voir un autre exemple. À titre d'illustration, l'extrait de code ci-dessous, provenant de MNIST, convertit un Tensor image stocké sous forme d'une séquence d'octets tf.uint8 en un Tensor tf.float32 :

image = tf.decode_raw(image, tf.uint8)
image = tf.cast(image, tf.float32)
image = tf.reshape(image, [784])

Cet extrait convertit un Tensor label stocké en tant que tf.int64 en un Tensor tf.int32 :

label = tf.cast(label, tf.int32)

Formes dynamiques non compatibles

Message d'erreur

ValueError: shape [Shape] must have a fixed size for dimension d that is known at graph construction time.

Détails

Pour exécuter un modèle sur un TPU, TensorFlow le compile à l’aide du framework XLA. Bien que cette étape de compilation améliore considérablement la vitesse d'entraînement et l'utilisation de la mémoire, les formes (tailles des dimensions) de tous les Tensors du graphe doivent être statiques, c'est-à-dire que leurs valeurs doivent être connues au moment de la compilation du graphe. Si la moindre forme ne peut pas être déterminée au moment de la compilation, la compilation TPU échoue avec le message d'erreur ci-dessus.

dataset.batch(batch_size) est une opération courante qui renvoie une forme dynamique, car le nombre d'échantillons restant dans un flux peut être inférieur à la taille de lot. Par conséquent, lors de l'entraînement sur le TPU, utilisez la fonction tf.contrib.data.batch_and_drop_remainder(batch_size). Cette fonction supprime, le cas échéant, les derniers échantillons d'un fichier pour garantir que chaque lot possède une forme statique de taille batch_size. Exemple :

dataset = ...
dataset = dataset.apply(tf.contrib.data.batch_and_drop_remainder(batch_size))

Opération TensorFlow non disponible

Message d'erreur

NotFoundError: No registered 'OpName' OpKernel for XLA_TPU_JIT devices compatible with node

Détails

Le modèle utilise une opération TensorFlow qui n’est pas disponible pour le moment sur TPU.

Pour obtenir la liste des opérations disponibles sur TPU, ainsi que les opérations dont la compatibilité est prévue à l'avenir et les solutions de contournement, veuillez consulter le guide des opérations TensorFlow disponibles.

Message d'erreur de mémoire insuffisante

Message d'erreur

ResourceExhaustedError: Ran out of memory in memory space hbm; used: YYY; limit: 7.48G.

Détails

Chaque TPU est composé de huit cœurs de TPU, chacun disposant de 8 Go de mémoire RAM (ou HBM, mémoire à haut débit). Cette mémoire est utilisée pour stocker les Tensors des pondérations (variables), ainsi que les Tensors des résultats intermédiaires nécessaires aux calculs de gradients. Si le modèle est trop volumineux pour tenir dans la mémoire RAM du TPU, l'initialisation échoue et le message d'erreur ci-dessus est affiché. Consultez la section Réduire l'utilisation de la mémoire pour obtenir de l'aide.

Fonctionnalité CrossShardOptimizer non utilisée

Message d'erreur

ValueError: CrossShardOptimizer must be used for model training on TPUs.

Détails

Lorsque vous définissez un modèle à l'aide de l'API TensorFlow pour Python, la plus grande partie du code écrit par l'utilisateur n'a pas besoin d'être spécialisée pour le TPU. L'exception la plus significative est l'optimiseur, qui doit être encapsulé dans tf.contrib.tpu.CrossShardOptimizer(), comme illustré ci-dessous :

optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
if FLAGS.use_tpu:
  optimizer = tf.contrib.tpu.CrossShardOptimizer(optimizer)
train_op=optimizer.minimize(loss, tf.train.get_global_step())

Chaque TPU est composé de huit cœurs de TPU, qui sont des unités de traitement indépendantes. Pour chaque pas de l'entraînement (mise à jour des pondérations), chaque cœur de TPU exécute le calcul de la propagation avant et du gradient sur un mini-lot de données indépendant, puis tous les cœurs échangent leurs gradients entre eux. Dans la plupart des cas, c'est mathématiquement équivalent au calcul des gradients sur un lot volumineux, bien qu'il existe certaines mises en garde expliquées à la section Comprendre la segmentation des données.

CrossShardOptimizer est l'opération responsable de cet échange de gradients. Par défaut, CrossShardOptimizer calcule les gradients de la perte mean à travers les cœurs, mais il est possible de le configurer pour calculer la perte sum en transmettant reduction=losses.Reduction.SUM.

Impossible de se connecter au serveur TPU

Message d'erreur

An error was raised while a session was being created. This may be due to a preemption of a connected worker or parameter server. A new session is created.

Détails

Cette erreur est affichée lorsque TensorFlow ne peut pas se connecter à l'URL du serveur TPU transmise à master. Pour obtenir de l'aide, reportez-vous à la section sur les problèmes de connexion au serveur TPU.

Erreurs en cours d'entraînement

Si un modèle ne peut pas être exécuté avec succès sur TPU, toute erreur liée est conçue pour être interceptée lors de l'initialisation. Par conséquent, il est rare qu'un modèle échoue en cours d'entraînement. Si cela se produit, la cause la plus probable est un problème dans la fonction de prétraitement des données. Par exemple, lorsque vous utilisez l'API Dataset, vous devez généralement faire un appel à dataset = dataset.repeat(). Dans le cas contraire, l'entraînement échoue après un seul passage sur les données. Les opérations d'exécution dynamiques telles que tf.while_loop() ne peuvent également échouer que d'une manière qui dépend des données d'entrée. Il existe également la possibilité rare de pannes parasites au niveau du matériel ou du réseau.

Problèmes d'arrêt de l'exécution

Si TensorFlow rencontre une erreur lors de l'exécution sur TPU, le script semble parfois cesser de répondre plutôt que quitter l'interface système. Si cela se produit, appuyez sur la combinaison de touches CTRL+\ sur le clavier pour déclencher un SIGQUIT qui force la fermeture immédiate de Python.

De la même façon, la commande CTRL+C pendant l'exécution TPU ne met pas fin immédiatement à TensorFlow, mais attend la fin de la boucle d'itération en cours pour s'arrêter proprement. Si vous appuyez sur CTRL+\, Python se ferme immédiatement.

Si, après une fermeture de ce type, vous rencontrez de nouvelles erreurs telles que DeadlineExceededError lorsque vous vous reconnectez au serveur TPU, réinitialisez manuellement le serveur TPU à l'aide de la commande gcloud compute tpus stop $TPU_SERVER_NAME && gcloud compute tpus start $TPU_SERVER_NAME, où la valeur de $TPU_SERVER_NAME provient de la première colonne du résultat de la commande gcloud compute tpus list.

Réduire l'utilisation de la mémoire

Si vous rencontrez une erreur de mémoire insuffisante lors de l'exécution de votre modèle sur TPU, vous devez prendre des mesures pour réduire l'utilisation de la mémoire par le modèle. Cette section décrit plusieurs causes possibles des problèmes de mémoire et fournit des consignes pour les résoudre.

Trop de pondérations dans le modèle

Cause possible du problème de mémoire

Chaque pondération de modèle float32 nécessite quatre octets. Ces pondérations sont répliquées sur chaque cœur de TPU. Par conséquent, un modèle comportant des centaines de millions de pondérations sera probablement trop volumineux pour tenir sur le TPU.

Réduire l'utilisation de la mémoire

  1. Certains optimiseurs requièrent davantage de mémoire par pondération afin de stocker les statistiques de mise à jour. C'est par exemple le cas de AdamOptimizer et AdadeltaOptimizer qui requièrent tous deux huit octets supplémentaires par pondération. AdagradOptimizer et MomentumOptimizer nécessitent quatre octets de plus par pondération. L'optimiseur GradientDescentOptimizer standard ne nécessite pas d'espace de stockage supplémentaire, bien qu'il soit moins performant que les autres optimiseurs en termes de précision du modèle final. L'optimiseur AdafactorOptimizer expérimental n'exige quasiment pas de mémoire supplémentaire et fonctionne aussi bien que l'optimiseur de base Adam lors de l'entraînement de modèles Transformer.
  2. Si la majorité des pondérations sont des représentations vectorielles continues de mots, il a été démontré que des techniques telles que WordPiece réduisent considérablement la taille du vocabulaire tout en augmentant la justesse sur un large éventail de tâches.
  3. Une version prochaine de TensorFlow propose une compatibilité expérimentale pour les pondérations et les gradients en virgule flottante de 16 bits, afin de réduire de moitié les besoins en mémoire.

Remplissage excessif du tenseur

Cause possible du problème de mémoire

Les Tensors stockés dans la mémoire du TPU sont remplis, c'est-à-dire que le TPU arrondit les tailles des Tensors stockés en mémoire pour effectuer les calculs plus efficacement. Ce remplissage se produit de manière transparente au niveau du matériel et n'affecte pas les résultats. Toutefois, dans certains cas, le remplissage peut entraîner une augmentation significative de l'utilisation de la mémoire et du temps d'exécution.

Réduire l'utilisation de la mémoire

La couche logicielle TPU tente de disposer les Tensors en mémoire afin de maximiser l’efficacité des calculs et de minimiser le remplissage. Ce processus d’agencement de la mémoire est complexe, mais pour optimiser les résultats, le modèle doit respecter la règle empirique suivante. Pour minimiser la surcharge de mémoire et optimiser l'efficacité de calcul, l'une des conditions suivantes doit être remplie :

  • La taille totale du lot doit être un multiple de 64 (8 par cœur de TPU) et les dimensions des caractéristiques doivent être un multiple de 128.

    ou

  • La taille totale du lot doit être un multiple de 1 024 (128 par cœur de TPU), et les dimensions des caractéristiques doivent être un multiple de 8.

L'efficacité maximale est atteinte avec une taille de lot de 1 024 et des dimensions de caractéristiques multiples de 128, bien que cela ne soit pas toujours possible pour tous les modèles. Par souci de clarté, "dimension de caractéristique" désigne la taille cachée d'une couche entièrement connectée ou le nombre de canaux de sortie dans une convolution. Certaines couches ne peuvent pas se conformer à cette règle, en particulier les première et dernière couches du réseau. Ce n'est pas un problème et il est attendu que la plupart des modèles nécessitent un certain remplissage.

Taille de lot trop importante

Cause possible du problème de mémoire

Lors de l'entraînement d'un réseau de neurones sur un processeur, un GPU ou un TPU, l'utilisation de la mémoire provient de deux sources :

  1. Le stockage des pondérations, des gradients de pondérations et des statistiques spécifiques à l'optimiseur telles que le Momentum. L'utilisation de la mémoire est directement proportionnelle au nombre de pondérations dans le modèle, mais pas à la taille de lot.
  2. Le stockage des activations intermédiaires provenant du calcul de propagation avant et utilisées pour le calcul de la rétropropagation. L'utilisation de la mémoire est directement proportionnelle à la taille du lot, à la taille et au nombre de couches.

Par conséquent, la mémoire requise par un modèle dépend largement de la taille de lot.

Réduire l'utilisation de la mémoire

Essayez de réduire progressivement la taille de lot jusqu'à ce qu'un lot tienne en mémoire, en vous assurant que la taille totale de lot est un multiple de 64 (la taille de lot par cœur doit être un multiple de 8). N'oubliez pas que des tailles de lot plus importantes sont plus efficaces sur TPU. Une taille de lot totale de 1 024 (128 par cœur) est généralement un bon point de départ.

Modèle trop volumineux

Cause possible du problème de mémoire

La mémoire requise par un modèle dépend fortement du nombre d'opérateurs dans le graphe (c'est-à-dire des couches du réseau). Cette exigence de stockage est distincte du nombre de pondérations. Par exemple, le calcul du gradient d'un opérateur tel que tf.nn.conv2d() peut augmenter l'utilisation de la mémoire, en plus de la mémoire utilisée pour stocker les pondérations.

Le moteur de TPU tente de recalculer stratégiquement certains opérateurs pour ajuster le modèle en mémoire (appelé rematérialisation, semblable à la gestion des points de contrôle de gradient), mais il n'est pas toujours en mesure de le faire.

Réduire l'utilisation de la mémoire

Si le modèle ne peut pas être exécuté sur TPU même avec une taille de lot faible (par exemple, 64), essayez de réduire le nombre ou la taille des couches. Une version prochaine de TensorFlow prendra en charge le "parallélisme de modèle" sur TPU, ce qui permettra d'exécuter des modèles beaucoup plus volumineux sur Cloud TPU en exécutant différentes parties du modèle sur différents cœurs de TPU.

Améliorer la vitesse d'entraînement

Si votre modèle est capable de fonctionner correctement sur TPU, mais que la vitesse d’entraînement n'est pas aussi rapide qu'espéré, cette section décrit plusieurs moyens potentiels d’améliorer la vitesse.

Trop peu d'itérations par boucle

Description du problème de performances

Le paramètre iterations_per_loop de TPUConfig contrôle le nombre de lots de données envoyés au TPU dans une seule "boucle d'entraînement". Chaque boucle d'entraînement nécessite une communication importante entre la machine locale et le serveur TPU. Par conséquent, si iterations_per_loop est trop faible, cela peut considérablement ralentir l'entraînement.

Comment savoir si votre modèle est affecté

Si le message de journalisation Enqueue next (X) batch(es) of data to infeed s'affiche très fréquemment, toutes les trois secondes par exemple, votre entraînement peut présenter une surcharge importante liée à la boucle d'entraînement.

Minimiser le problème

Définissez iterations_per_loop sur une valeur plus élevée. Dans le tutoriel MNIST, ceci est contrôlé par l'option --iterations. Tant que le message Enqueue next (X) batch(es) of data to infeed n'apparaît pas plus de quelques fois par minute, la valeur actuelle doit être suffisante. Notez que iterations_per_loop peut être défini sur une valeur très élevée. Le seul inconvénient est que les messages de journalisation et de création de points de contrôle ne peuvent être générés qu'à la fin d'une boucle.

Goulot d'étranglement au niveau du traitement des entrées

Description du problème de performances

Pendant que le TPU effectue l'entraînement sur un bloc de données particulier, la fonction de traitement des entrées utilise le processeur pour préparer le bloc de données suivant. Ainsi, si la fonction d'entrée prend moins de temps que la fonction du modèle, le coût du traitement des entrées est effectivement nul. Cependant, une fonction d'entrée plus lente que la fonction du modèle crée un goulot d'étranglement.

Comment savoir si votre modèle est affecté

Suivez les instructions de la documentation Outils Cloud TPU : analyseur du pipeline d'entrées pour afficher l’analyse du pipeline d’entrées dans TensorBoard :

image

La page d'analyse du pipeline d'entrées présente un résumé clair indiquant si votre modèle présente un goulot d'étranglement au niveau du traitement des entrées. La même page affiche également le temps d'exécution par opération, ce qui vous permet d'identifier les opérations problématiques.

Minimiser le problème

Il existe plusieurs solutions au niveau du chargement de données grâce à l'API Dataset :

  1. Stockez vos données sous forme de collection de structures tf.train.Example dans des fichiers TFRecord, et chargez-les à l'aide de TFRecordDataset. Consultez le tutoriel sur l'API Dataset ou le tutoriel ResNet pour voir des exemples.
  2. Utilisez dataset.cache() et/ou dataset.prefetch() pour mettre en mémoire tampon les données d'entrée. Cela évite que les ralentissements sporadiques au niveau de l'accès aux fichiers ne créent un goulot d'étranglement.
  3. Spécifiez le paramètre num_parallel_calls de la fonction dataset.map() pour activer les opérations multithread map().
  4. Effectuez le prétraitement coûteux des données hors ligne comme une dépense unique, plutôt que de subir les frais occasionnés à chaque époque de chaque entraînement.

L'intégralité du traitement des entrées est effectué sur les processeurs situés sur le serveur TPU et non sur la machine locale. La vitesse de la machine locale n'est donc pas un facteur.

Trop d'opérations de multiplications non matricielles

Description du problème de performances

Cloud TPU peut effectuer des multiplications et des convolutions de matrices à des vitesses incroyablement élevées. La plupart des autres opérations TensorFlow ont des mises en œuvre efficaces sur TPU, mais elles ne représentent pas le point fort des TPU par rapport à un autre type de matériel. Par conséquent, pour tirer pleinement parti du TPU, un modèle doit reposer essentiellement sur des multiplications ou convolutions de matrices.

Comment savoir si votre modèle est affecté

Le guide Outils Cloud TPU : profil d'opérations décrit comment générer un profil de performances pour votre modèle, divisé par type d'opération. En général, la grande majorité des architectures de réseaux de neurones modernes sont dominées par des multiplications et des convolutions de matrices.

Minimiser le problème

Si l'absence de multiplications matricielles dans votre modèle était principalement motivée par des problèmes de vitesse d'entraînement sur d'autres types de matériel, nous vous invitons à réévaluer ces modèles sur le TPU pour améliorer les performances en matière de vitesse. Si l'absence de multiplications matricielles est une propriété fondamentale du modèle, le TPU n'est peut-être pas le choix de matériel optimal.

Remplissage excessif du Tensor

Description du problème de performances

Le TPU remplit les tenseurs en mémoire de manière à pouvoir utiliser efficacement ses unités de calcul. Le remplissage peut augmenter l'utilisation de la mémoire, ainsi que de la bande passante mémoire. Consultez la section sur le remplissage de Tensor pour comprendre et résoudre les problèmes de remplissage de Tensor.

Taille de lot trop faible

Description du problème de performances

En règle générale, l'utilisation de lots de grande taille accélère l'apprentissage sur TPU, en termes d'échantillons/seconde.

Comment savoir si votre modèle est affecté

La taille de lot de tout modèle doit toujours être d'au moins 64 (huit par cœur de TPU), car le TPU remplit systématiquement les Tensors pour atteindre cette taille. La taille de lot idéale pour l'apprentissage sur TPU est de 1 024 (128 par cœur de TPU), car cela élimine les inefficacités liées au transfert de mémoire et au remplissage.

Minimiser le problème

Il est recommandé d’utiliser la taille de lot la plus grande qui tienne en mémoire et soit un multiple de 64. La méthode la plus simple consiste à démarrer à 1 024. Si cela provoque une erreur de mémoire insuffisante, essayez de réduire la taille de lot jusqu'à ce que le modèle s'exécute correctement. Modifier la taille de lot d'un modèle peut nécessiter l'ajustement d'autres hyperparamètres, tels que le taux d'apprentissage, pour obtenir la même justesse de modèle, mais cela doit être évalué au cas par cas.

La taille des couches est trop faible

Description du problème de performances

Même lorsqu'un modèle est dominé par des multiplications ou convolutions de matrices, le TPU peut ne pas fonctionner à pleine efficacité si les Tensors d'entrée sont petits. Par rapport à d'autres types de matériel, le TPU fonctionne plus efficacement lorsque les lots et les couches sont de grande taille (par exemple, de dimension >= 512).

Comment savoir si votre modèle est affecté

En règle générale, les tailles de couche inférieures à 128 n'atteignent qu'une efficacité médiocre sur TPU, car 128 est la dimension native de l'unité de multiplication matricielle TPU. Pour les couches entièrement connectées, une taille cachée minimale de 512 est recommandée pour obtenir une efficacité élevée. Notez que les couches convolutives n'ont généralement pas besoin d'être aussi grandes que des couches entièrement connectées pour atteindre un niveau d'efficacité équivalent. Par exemple, une convolution 3 × 3 de taille 256 atteint une efficacité similaire (élevée) à celle d'une couche entièrement connectée de taille 2 048, puisque 3 × 3 × 256 = 2 304.

Minimiser le problème

Si la principale motivation pour avoir des couches de petite taille dans votre modèle est la vitesse d'entraînement, nous vous invitons à effectuer une nouvelle analyse comparative de vos modèles avec des couches plus grandes sur TPU. Par exemple, passer la taille de sortie d'une couche de 256 à 512 n'accroît le temps d'apprentissage que de 20 %, même si le modèle effectue deux fois plus de calculs.

Profilage de modèle au niveau des opérations

Il est souvent utile de mesurer le temps d'exécution et l'utilisation de la mémoire au niveau des opérations afin d'identifier les goulots d'étranglement des performances. Pour plus d'informations sur ce point,
consultez le guide Outils Cloud TPU : Lecteur de traces.

Le débogage dégrade la justesse du modèle

L’un des objectifs de l’écosystème Cloud TPU est que tout modèle actuellement entraîné sur un processeur ou un GPU atteigne une précision très similaire lorsqu’il est entraîné sur un TPU, avec éventuellement des ajustements mineurs au niveau d'hyperparamètres tels que la taille de lot et le taux d’apprentissage. Cependant, les utilisateurs peuvent parfois observer une dégradation de la justesse de certains modèles après entraînement sur un TPU. Déboguer de tels problèmes peut être extrêmement frustrant en raison de la nature aléatoire de l'entraînement des réseaux de neurones. Cette section explique comment identifier la cause première de toute dégradation de la justesse d'un modèle lors du transfert sur TPU.

Comprendre la segmentation de données (parallélisme des données)

L'un des principaux objectifs de TensorFlow est de faire en sorte que chaque opération produise des résultats quasi identiques, qu'elle soit exécutée sur processeur, GPU ou TPU. Il existe certaines exceptions à cette règle, telles que les opérations aléatoires. En général, si vous constatez une différence significative entre les résultats d'opérations non aléatoires sur TPU et sur processeur, signalez-le comme un bug.

Cependant, au niveau du pipeline d'entraînement dans son ensemble, il existe une différence significative entre l'entraînement sur le processeur/GPU et celui sur le TPU. Lors de l'entraînement sur un TPU, TensorFlow exécute la segmentation de données, aussi connue sous le nom de parallélisme de données avec SGD synchrone. Chaque Cloud TPU est composé de huit cœurs de TPU fonctionnant comme des unités de traitement indépendantes. Pour chaque pas de l'entraînement, chaque cœur de TPU reçoit un lot de données, calcule les gradients des pondérations, les échange avec les autres cœurs, puis calcule la mise à jour de la pondération. Par défaut, c'est la perte moyenne sur l'ensemble des cœurs qui est calculée, mais on peut aussi calculer la somme en modifiant le paramètre de CrossShardOptimizer.

Si la perte totale du modèle peut être calculée comme la moyenne (ou la somme) de pertes indépendantes par échantillon, cette procédure est alors mathématiquement équivalente à un entraînement sur un seul lot de grande taille.

L'opération la plus courante qui n'est pas indépendante par échantillon est la normalisation de lot, qui s'exécute sur chaque "lot par cœur" séparément. Par exemple, si la taille de lot totale est de 128, la taille de lot par cœur est de 16 et chacun des huit cœurs réalise la normalisation du lot sur ses 16 échantillons propres. Dans certains cas, une dégradation de la justesse a été observée avec la normalisation par lot sur de petits lots (moins de 32, par exemple). Dans un scénario idéal, la taille totale des lots doit être importante (par exemple, de 256 à 1 024). Pour les lots trop volumineux pour tenir en mémoire, l’effet de la segmentation doit être évalué au cas par cas.

En raison des complexités introduites par la segmentation, la première étape du débogage pour une situation où la justesse est médiocre consiste à réaliser un entraînement TPU déterministe et monocœur, et à le comparer à un modèle entraîné sur processeur/GPU. En règle générale, cela peut être fait rapidement, car il n’est pas nécessaire d'entraîner un modèle jusqu'à convergence.

Entraînement déterministe

Une des raisons pour lesquelles il est difficile de déboguer les différences de justesse d'un modèle tient au fait que TensorFlow utilise une initialisation des pondérations et un brassage des données différents chaque fois qu'un modèle est entraîné. Il est avantageux de modifier la procédure d’entraînement pour qu’elle devienne déterministe, de sorte que plusieurs exécutions produisent des modèles quasiment identiques. Cette section montre comment exécuter le tutoriel MNIST de manière déterministe :

  1. Générez un fichier de point de contrôle initial en exécutant un seul pas sur le processeur. Ce pas permet d’initialiser les pondérations de manière déterministe. Il peut également être obtenu en injectant des données dans les initialiseurs de variables, mais c'est plus difficile.
# Run training for 1 step to create an initial checkpoint.
python mnist_tpu.py \
  --use_tpu=False \
  --data_dir=${STORAGE_BUCKET}/data/ \
  --model_dir=${STORAGE_BUCKET}/init_output \
  --random_seed=12345 \
  --iterations=1
  --train_steps=1
  1. Modifiez les fonctions de brassage des données de votre fonction d'entrée pour utiliser une valeur de départ aléatoire. C'est déjà le cas dans le tutoriel MNIST. Cela fonctionne pour les opérations de traitement des données en entrée, car celles-ci sont toujours exécutées sur le processeur. Les opérations aléatoires dans la fonction du modèle peuvent ne pas être déterministes entre le TPU et le processeur. Exemple :
# In the flag definitions
tf.flags.DEFINE_integer("batch_size", None, "Random seed for training")

# In the input_fn
if FLAGS.random_seed is not None:
dataset = dataset.shuffle(seed=FLAGS.random_seed)
  1. Exécutez le même modèle deux fois sur le processeur pour vérifier que l'entraînement est déterministe. Notez que l'entraînement doit être exécuté pour un nombre raisonnable de pas (par exemple, 1 000), mais pas nécessairement jusqu'à convergence, car cela peut être très lent sur un processeur.

    Comme l'entraînement sur processeur est comparé à l'entraînement sur un TPU monocœur, utilisez une taille de lot pouvant tenir sur un seul cœur de TPU (généralement, la taille de lot complète divisée par huit). TensorFlow ne garantit pas un déterminisme bit par bit entre les exécutions, mais la perte doit être très proche :
# Copy the initial weights
gsutil mkdir ${STORAGE_BUCKET}/cpu_output_1
gsutil cp -f ${STORAGE_BUCKET}/init_output/* ${STORAGE_BUCKET}/cpu_output_1
gsutil mkdir ${STORAGE_BUCKET}/cpu_output_2
gsutil cp -f ${STORAGE_BUCKET}/init_output/* ${STORAGE_BUCKET}/cpu_output_2

# Run 1
python mnist_tpu.py \
  --use_tpu=False \
  --data_dir=${STORAGE_BUCKET}/data/ \
  --model_dir=${STORAGE_BUCKET}/cpu_output_1 \
  --batch_size=128 \
  --random_seed=12345 \
  --train_steps=2000 \
  --eval_steps=10

# Output 1
accuracy = 0.9910644, global_step = 1000, loss = 0.025323588

# Run 2
python mnist_tpu.py \
  --use_tpu=False \
  --data_dir=${STORAGE_BUCKET}/data/ \
  --model_dir=${STORAGE_BUCKET}/cpu_output_1 \
  --batch_size=128 \
  --random_seed=12345 \
  --train_steps=2000 \
  --eval_steps=10

# Output 2
accuracy = 0.9910644, global_step = 1000, loss = 0.025323414

Entraînement TPU monocœur

Une fois que vous pouvez exécuter le tutoriel MNIST de manière déterministe, l'étape suivante consiste à répliquer sur TPU les résultats entraînés sur le processeur, en utilisant un seul cœur de TPU pour déterminer si le problème est lié à la segmentation des données ou au moteur d'exécution du TPU lui-même.

Voici comment exécuter un entraînement et une évaluation monocœur avec le tutoriel MNIST :

# Use the same weight initialization as the CPU
gsutil cp -f ${STORAGE_BUCKET}/init_output/* ${STORAGE_BUCKET}/tpu_output

# Run training for 1000 steps
python mnist.py \
    --use_tpu=True \
    --master=$GRPC_SERVER \
    --train_file=${STORAGE_BUCKET}/data/train.tfrecords \
    --model_dir=${STORAGE_BUCKET}/tpu_output \
    --random_seed=12345 \
    --batch_size=128 \
    --train_steps=1000 \
    --eval_steps=10

  accuracy = 0.9910644, global_step = 1000, loss = 0.02514153

La perte ne correspondra pas exactement au modèle entraîné sur processeur, mais elle devrait être proche. Si ce n'est pas le cas pour votre modèle, cela peut indiquer que vous avez trouvé un bug dans le moteur d'exécution du TPU. Avant de soumettre un rapport de bug, vérifiez les points suivants :

  1. Vous transmettez num_shards=1 à TPUConfig.

  2. Vous ne disposez d'aucune opération aléatoire dans votre fonction de modèle et toutes les opérations aléatoires de votre fonction d'entrée sont correctement initialisées.

  3. Vous utilisez le même fichier de point de contrôle initial pour l'entraînement sur processeur et sur TPU.

Déboguer l'entraînement TPU multicœur

Si votre modèle atteint effectivement la même perte sur processeur et sur TPU monocœur, le problème est probablement l'un des éléments suivants :

(a) La dégradation est due à la variance aléatoire naturelle survenant lorsque l'on entraîne des modèles neuronaux avec différentes initialisations.

(b) La dégradation est due à un problème lié à la segmentation des données sur le TPU.

Pour déterminer si votre problème relève du cas (a), entraînez de nouveau le modèle complet sur processeur/GPU et sur TPU multicœur en utilisant la même initialisation des pondérations, comme ci-dessus.

Si vous êtes certain que la dégradation de la justesse est statistiquement significative, les problèmes les plus probables liés à la segmentation des données sont les suivants :

  1. Si votre modèle calcule la perte comme la somme des erreurs par échantillon, transmettez reduction=losses.Reduction.SUM à CrossShardOptimizer. Par défaut, CrossShardOptimizer calcule la moyenne des pertes, plutôt que leur somme.
  2. Si votre modèle utilise la normalisation par lot, une taille de lot totale inférieure à 256 (par exemple, inférieure à 32 par cœur) peut réduire la justesse.
  3. Les fonctions de perte par lot sont affectées par la segmentation. Ces fonctions de perte sont généralement assez spécialisées. Par exemple Karras et al. 2017 utilisent un discriminateur par lot lors de l'entraînement d'un réseau antagoniste génératif.