Débogage des problèmes numériques dans les programmes TensorFlow à l'aide de TensorBoard Debugger V2,Débogage des problèmes numériques dans les programmes TensorFlow à l'aide de TensorBoard Debugger V2

Des événements catastrophiques impliquant des NaN peuvent parfois se produire au cours d'un programme TensorFlow, paralysant les processus de formation du modèle. La cause profonde de tels événements est souvent obscure, en particulier pour les modèles de taille et de complexité non triviales. Pour faciliter le débogage de ce type de bogues de modèle, TensorBoard 2.3+ (avec TensorFlow 2.3+) fournit un tableau de bord spécialisé appelé Debugger V2. Ici, nous montrons comment utiliser cet outil en travaillant sur un bogue réel impliquant des NaN dans un réseau de neurones écrit en TensorFlow.

Les techniques illustrées dans ce didacticiel sont applicables à d'autres types d'activités de débogage telles que l'inspection des formes de tenseur d'exécution dans des programmes complexes. Ce didacticiel se concentre sur les NaN en raison de leur fréquence d'occurrence relativement élevée.

Observer le bogue

Le code source du programme TF2 que nous allons déboguer est disponible sur GitHub . L'exemple de programme est également intégré au package tensorflow pip (version 2.3+) et peut être appelé par :

python -m tensorflow.python.debug.examples.v2.debug_mnist_v2

Ce programme TF2 crée une perception multicouche (MLP) et l'entraîne à reconnaître les images MNIST . Cet exemple utilise délibérément l'API de bas niveau de TF2 pour définir des constructions de couche personnalisées, une fonction de perte et une boucle d'entraînement, car la probabilité de bogues NaN est plus élevée lorsque nous utilisons cette API plus flexible mais plus sujette aux erreurs que lorsque nous utilisons la plus simple. des API de haut niveau faciles à utiliser mais légèrement moins flexibles telles que tf.keras .

Le programme imprime un test de précision après chaque étape d'entraînement. Nous pouvons voir dans la console que la précision du test reste bloquée à un niveau proche du hasard (~0,1) après la première étape. Ce n'est certainement pas ainsi que la formation du modèle devrait se comporter : nous nous attendons à ce que la précision approche progressivement de 1,0 (100 %) à mesure que le pas augmente.

Accuracy at step 0: 0.216
Accuracy at step 1: 0.098
Accuracy at step 2: 0.098
Accuracy at step 3: 0.098
...

Une supposition éclairée est que ce problème est causé par une instabilité numérique, telle que NaN ou l'infini. Cependant, comment pouvons-nous confirmer que c'est vraiment le cas et comment trouvons-nous l'opération TensorFlow (op) responsable de la génération de l'instabilité numérique ? Pour répondre à ces questions, instrumentons le programme bogué avec Debugger V2.

Instrumentation du code TensorFlow avec Debugger V2

tf.debugging.experimental.enable_dump_debug_info() est le point d'entrée de l'API de Debugger V2. Il instrumente un programme TF2 avec une seule ligne de code. Par exemple, l'ajout de la ligne suivante vers le début du programme entraînera l'écriture des informations de débogage dans le répertoire des journaux (logdir) à /tmp/tfdbg2_logdir. Les informations de débogage couvrent divers aspects de l'environnement d'exécution TensorFlow. Dans TF2, il inclut l'historique complet de l'exécution hâtive, la construction de graphes effectuée par @tf.function , l'exécution des graphes, les valeurs de tenseur générées par les événements d'exécution, ainsi que l'emplacement du code (traces de pile Python) de ces événements . La richesse des informations de débogage permet aux utilisateurs de se concentrer sur les bogues obscurs.

tf.debugging.experimental.enable_dump_debug_info(
    "/tmp/tfdbg2_logdir",
    tensor_debug_mode="FULL_HEALTH",
    circular_buffer_size=-1)

L'argument tensor_debug_mode contrôle les informations que le débogueur V2 extrait de chaque tenseur impatient ou dans le graphe. "FULL_HEALTH" est un mode qui capture les informations suivantes sur chaque tenseur de type flottant (par exemple, le float32 couramment vu et le dtype bfloat16 moins courant) :

  • DType
  • Rang
  • Nombre total d'éléments
  • Une répartition des éléments de type flottant dans les catégories suivantes : fini négatif ( - ), zéro ( 0 ), fini positif ( + ), infini négatif ( -∞ ), infini positif ( +∞ ) et NaN .

Le mode "FULL_HEALTH" convient au débogage des bogues impliquant NaN et l'infini. Voir ci-dessous pour les autres tensor_debug_mode pris en charge.

L'argument circular_buffer_size contrôle le nombre d'événements tensoriels enregistrés dans le logdir. Sa valeur par défaut est 1000, ce qui entraîne la sauvegarde sur disque des 1000 derniers tenseurs avant la fin du programme TF2 instrumenté. Ce comportement par défaut réduit la surcharge du débogueur en sacrifiant l'exhaustivité des données de débogage. Si l'exhaustivité est préférée, comme dans ce cas, nous pouvons désactiver le tampon circulaire en définissant l'argument sur une valeur négative (par exemple, -1 ici).

L'exemple debug_mnist_v2 appelle enable_dump_debug_info() en lui transmettant des indicateurs de ligne de commande. Pour exécuter à nouveau notre programme TF2 problématique avec cette instrumentation de débogage activée, faites :

python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
    --dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH

Démarrage de l'interface graphique du débogueur V2 dans TensorBoard

L'exécution du programme avec l'instrumentation du débogueur crée un logdir dans /tmp/tfdbg2_logdir. Nous pouvons démarrer TensorBoard et le pointer vers le logdir avec :

tensorboard --logdir /tmp/tfdbg2_logdir

Dans le navigateur Web, accédez à la page de TensorBoard à l'adresse http://localhost:6006. Le plugin "Debugger V2" sera inactif par défaut, alors sélectionnez-le dans le menu "Plugins inactifs" en haut à droite. Une fois sélectionné, il devrait ressembler à ceci :

Capture d'écran de la vue complète du débogueur V2

Utilisation de l'interface graphique du débogueur V2 pour trouver la cause première des NaN

L'interface graphique du débogueur V2 dans TensorBoard est organisée en six sections :

  • Alertes : cette section en haut à gauche contient une liste des événements "d'alerte" détectés par le débogueur dans les données de débogage du programme TensorFlow instrumenté. Chaque alerte indique une certaine anomalie qui mérite votre attention. Dans notre cas, cette section met en évidence 499 événements NaN/∞ avec une couleur rose-rouge saillante. Cela confirme notre suspicion que le modèle ne parvient pas à apprendre en raison de la présence de NaN et/ou d'infinis dans ses valeurs de tenseur interne. Nous nous pencherons sur ces alertes sous peu.
  • Chronologie d'exécution de Python : il s'agit de la moitié supérieure de la section centrale supérieure. Il présente l'historique complet de l'exécution impatiente des opérations et des graphiques. Chaque case de la chronologie est marquée par la lettre initiale du nom de l'op ou du graphe (par exemple, "T" pour l'op "TensorSliceDataset", "m" pour le "model" tf.function ). Nous pouvons naviguer dans cette chronologie en utilisant les boutons de navigation et la barre de défilement au-dessus de la chronologie.
  • Exécution du graphe : située dans le coin supérieur droit de l'interface graphique, cette section sera au cœur de notre tâche de débogage. Il contient un historique de tous les tenseurs de type flottant calculés à l'intérieur des graphes (c'est-à-dire compilés par @tf-function s).
  • La structure du graphique (moitié inférieure de la section centrale supérieure), le code source (section inférieure gauche) et Stack Trace (section inférieure droite) sont initialement vides. Leur contenu sera rempli lorsque nous interagissons avec l'interface graphique. Ces trois sections joueront également un rôle important dans notre tâche de débogage.

Après nous être orientés vers l'organisation de l'interface utilisateur, suivons les étapes suivantes pour comprendre pourquoi les NaN sont apparus. Tout d'abord, cliquez sur l'alerte NaN/∞ dans la section Alertes. Cela fait défiler automatiquement la liste des 600 tenseurs de graphe dans la section Exécution de graphe et se concentre sur le #88, qui est un tenseur nommé Log:0 généré par une Log (logarithme naturel). Une couleur rose-rouge saillante met en évidence un élément -∞ parmi les 1000 éléments du tenseur 2D float32. Il s'agit du premier tenseur de l'historique d'exécution du programme TF2 qui contenait du NaN ou de l'infini : les tenseurs calculés avant lui ne contiennent ni NaN ni ∞ ; de nombreux (en fait, la plupart) tenseurs calculés par la suite contiennent des NaN. Nous pouvons le confirmer en faisant défiler de haut en bas la liste d'exécution de graphe. Cette observation fournit une forte indication que l'op Log est la source de l'instabilité numérique dans ce programme TF2.

Débogueur V2 : Alertes Nan/Infini et liste d'exécution des graphes

Pourquoi ce Log op crache-t-il un -∞ ? Répondre à cette question nécessite d'examiner l'entrée de l'op. Cliquer sur le nom du tenseur ( Log:0 ) fait apparaître une visualisation simple mais informative du voisinage de l'op Log dans son graphique TensorFlow dans la section Structure du graphique. Notez la direction de haut en bas du flux d'informations. L'op lui-même est indiqué en gras au milieu. Immédiatement au-dessus, nous pouvons voir qu'une opération Placeholder fournit la seule et unique entrée à l' Log . Où est le tenseur généré par ce probs Placeholder dans la liste Graph Execution ? En utilisant la couleur d'arrière-plan jaune comme aide visuelle, nous pouvons voir que le tenseur probs:0 est trois lignes au-dessus du tenseur Log:0 , c'est-à-dire à la ligne 85.

Débogueur V2 : Vue de la structure du graphe et traçage vers le tenseur d'entrée

Un examen plus attentif de la décomposition numérique du tenseur probs:0 de la ligne 85 révèle pourquoi son consommateur Log:0 produit un -∞ : parmi les 1000 éléments de probs:0 , un élément a une valeur de 0. Le -∞ est résultat du calcul du logarithme naturel de 0 ! Si nous pouvons nous assurer d'une manière ou d'une autre que l'op Log n'est exposé qu'aux entrées positives, nous pourrons empêcher le NaN/∞ de se produire. Ceci peut être réalisé en appliquant un découpage (par exemple, en utilisant tf.clip_by_value() ) sur le tenseur Placeholder probs .

Nous nous rapprochons de la résolution du bogue, mais ce n'est pas encore tout à fait terminé. Afin d'appliquer le correctif, nous devons savoir d'où proviennent l'op Log et son entrée Placeholder dans le code source Python. Le débogueur V2 fournit une prise en charge de première classe pour tracer les opérations graphiques et les événements d'exécution jusqu'à leur source. Lorsque nous avons cliqué sur le tenseur Log:0 dans Graph Executions, la section Stack Trace a été remplie avec la trace de pile d'origine de la création de l'op Log . La trace de la pile est quelque peu volumineuse car elle inclut de nombreuses images du code interne de TensorFlow (par exemple, gen_math_ops.py et dumping_callback.py), que nous pouvons ignorer en toute sécurité pour la plupart des tâches de débogage. Le cadre qui nous intéresse est la ligne 216 de debug_mnist_v2.py (c'est-à-dire le fichier Python que nous essayons actuellement de déboguer). Cliquer sur "Ligne 216" fait apparaître une vue de la ligne de code correspondante dans la section Code source.

Débogueur V2 : code source et trace de la pile

Cela nous amène enfin au code source qui a créé l'opération Log problématique à partir de son entrée probs . Il s'agit de notre fonction personnalisée de perte d'entropie croisée catégorique décorée avec @tf.function et donc convertie en un graphique TensorFlow. L'espace réservé op probs correspond au premier argument d'entrée de la fonction de perte. L'op Log est créé avec l'appel d'API tf.math.log().

Le correctif de valeur de ce bogue ressemblera à :

  diff = -(labels *
           tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))

Cela résoudra l'instabilité numérique de ce programme TF2 et permettra au MLP de s'entraîner avec succès. Une autre approche possible pour corriger l'instabilité numérique consiste à utiliser tf.keras.losses.CategoricalCrossentropy .

Ceci conclut notre voyage de l'observation d'un bogue du modèle TF2 à la proposition d'un changement de code qui corrige le bogue, aidé par l'outil Debugger V2, qui offre une visibilité complète sur l'historique d'exécution rapide et graphique du programme TF2 instrumenté, y compris les résumés numériques des valeurs de tenseur et association entre les ops, les tenseurs et leur code source d'origine.

Compatibilité matérielle du débogueur V2

Debugger V2 prend en charge le matériel de formation grand public, y compris le CPU et le GPU. La formation multi-GPU avec tf.distributed.MirroredStrategy est également prise en charge. La prise en charge de TPU est encore à un stade précoce et nécessite d'appeler

tf.config.set_soft_device_placement(True)

avant d'appeler enable_dump_debug_info() . Il peut également avoir d'autres limitations sur les TPU. Si vous rencontrez des problèmes lors de l'utilisation de Debugger V2, veuillez signaler les bogues sur notre page de problèmes GitHub .

Compatibilité API du débogueur V2

Debugger V2 est implémenté à un niveau relativement bas de la pile logicielle de TensorFlow et est donc compatible avec tf.keras , tf.data et d'autres API construites au-dessus des niveaux inférieurs de TensorFlow. Le débogueur V2 est également rétrocompatible avec TF1, bien que la chronologie d'exécution rapide soit vide pour les journaux de débogage générés par les programmes TF1.

Conseils d'utilisation de l'API

Une question fréquemment posée à propos de cette API de débogage est de savoir où, dans le code TensorFlow, il faut insérer l'appel à enable_dump_debug_info() . En règle générale, l'API doit être appelée le plus tôt possible dans votre programme TF2, de préférence après les lignes d'importation Python et avant le début de la construction et de l'exécution du graphe. Cela garantira une couverture complète de toutes les opérations et graphiques qui alimentent votre modèle et sa formation.

Les tensor_debug_modes actuellement pris en charge sont : NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH et SHAPE . Ils varient dans la quantité d'informations extraites de chaque tenseur et la surcharge de performances du programme débogué. Veuillez vous référer à la section args de la documentation de enable_dump_debug_info() .

Surcoût de performances

L'API de débogage introduit une surcharge de performances pour le programme instrumenté TensorFlow. La surcharge varie selon tensor_debug_mode , le type de matériel et la nature du programme TensorFlow instrumenté. Comme point de référence, sur un GPU, le mode NO_TENSOR ajoute une surcharge de 15 % lors de la formation d'un modèle Transformer sous une taille de lot de 64. Le pourcentage de surcharge pour les autres tensor_debug_modes est plus élevé : environ 50 % pour CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH et SHAPE modes. Sur les processeurs, la surcharge est légèrement inférieure. Sur les TPU, les frais généraux sont actuellement plus élevés.

Relation avec d'autres API de débogage TensorFlow

Notez que TensorFlow propose d'autres outils et API pour le débogage. Vous pouvez parcourir ces API sous l'espace de noms tf.debugging.* sur la page de documentation de l'API. Parmi ces API, la plus fréquemment utilisée est tf.print() . Quand faut-il utiliser Debugger V2 et quand faut-il tf.print() à la place ? tf.print() est pratique dans le cas où

  1. on sait exactement quels tenseurs imprimer,
  2. nous savons où exactement dans le code source insérer ces tf.print() ,
  3. le nombre de tels tenseurs n'est pas trop grand.

Pour d'autres cas (par exemple, examen de nombreuses valeurs de tenseur, examen des valeurs de tenseur générées par le code interne de TensorFlow et recherche de l'origine de l'instabilité numérique comme nous l'avons montré ci-dessus), Debugger V2 fournit un moyen plus rapide de débogage. De plus, Debugger V2 fournit une approche unifiée pour inspecter les tenseurs impatients et graphiques. Il fournit en outre des informations sur la structure du graphique et les emplacements de code, qui dépassent les capacités de tf.print() .

Une autre API qui peut être utilisée pour déboguer les problèmes impliquant ∞ et NaN est tf.debugging.enable_check_numerics() . Contrairement à enable_dump_debug_info() , enable_check_numerics() n'enregistre pas les informations de débogage sur le disque. Au lieu de cela, il surveille simplement ∞ et NaN pendant l'exécution de TensorFlow et se trompe avec l'emplacement du code d'origine dès qu'une opération génère de telles mauvaises valeurs numériques. Il a une surcharge de performances inférieure à celle de enable_dump_debug_info() , mais ne permet pas une trace complète de l'historique d'exécution du programme et ne vient pas avec une interface utilisateur graphique comme Debugger V2.

,

Des événements catastrophiques impliquant des NaN peuvent parfois se produire au cours d'un programme TensorFlow, paralysant les processus de formation du modèle. La cause profonde de tels événements est souvent obscure, en particulier pour les modèles de taille et de complexité non triviales. Pour faciliter le débogage de ce type de bogues de modèle, TensorBoard 2.3+ (avec TensorFlow 2.3+) fournit un tableau de bord spécialisé appelé Debugger V2. Ici, nous montrons comment utiliser cet outil en travaillant sur un bogue réel impliquant des NaN dans un réseau de neurones écrit en TensorFlow.

Les techniques illustrées dans ce didacticiel sont applicables à d'autres types d'activités de débogage telles que l'inspection des formes de tenseur d'exécution dans des programmes complexes. Ce didacticiel se concentre sur les NaN en raison de leur fréquence d'occurrence relativement élevée.

Observer le bogue

Le code source du programme TF2 que nous allons déboguer est disponible sur GitHub . L'exemple de programme est également intégré au package tensorflow pip (version 2.3+) et peut être appelé par :

python -m tensorflow.python.debug.examples.v2.debug_mnist_v2

Ce programme TF2 crée une perception multicouche (MLP) et l'entraîne à reconnaître les images MNIST . Cet exemple utilise délibérément l'API de bas niveau de TF2 pour définir des constructions de couche personnalisées, une fonction de perte et une boucle d'entraînement, car la probabilité de bogues NaN est plus élevée lorsque nous utilisons cette API plus flexible mais plus sujette aux erreurs que lorsque nous utilisons la plus simple. des API de haut niveau faciles à utiliser mais légèrement moins flexibles telles que tf.keras .

Le programme imprime un test de précision après chaque étape d'entraînement. Nous pouvons voir dans la console que la précision du test reste bloquée à un niveau proche du hasard (~0,1) après la première étape. Ce n'est certainement pas ainsi que la formation du modèle devrait se comporter : nous nous attendons à ce que la précision approche progressivement de 1,0 (100 %) à mesure que le pas augmente.

Accuracy at step 0: 0.216
Accuracy at step 1: 0.098
Accuracy at step 2: 0.098
Accuracy at step 3: 0.098
...

Une supposition éclairée est que ce problème est causé par une instabilité numérique, telle que NaN ou l'infini. Cependant, comment pouvons-nous confirmer que c'est vraiment le cas et comment trouvons-nous l'opération TensorFlow (op) responsable de la génération de l'instabilité numérique ? Pour répondre à ces questions, instrumentons le programme bogué avec Debugger V2.

Instrumentation du code TensorFlow avec Debugger V2

tf.debugging.experimental.enable_dump_debug_info() est le point d'entrée de l'API de Debugger V2. Il instrumente un programme TF2 avec une seule ligne de code. Par exemple, l'ajout de la ligne suivante vers le début du programme entraînera l'écriture des informations de débogage dans le répertoire des journaux (logdir) à /tmp/tfdbg2_logdir. Les informations de débogage couvrent divers aspects de l'environnement d'exécution TensorFlow. Dans TF2, il inclut l'historique complet de l'exécution hâtive, la construction de graphes effectuée par @tf.function , l'exécution des graphes, les valeurs de tenseur générées par les événements d'exécution, ainsi que l'emplacement du code (traces de pile Python) de ces événements . La richesse des informations de débogage permet aux utilisateurs de se concentrer sur les bogues obscurs.

tf.debugging.experimental.enable_dump_debug_info(
    "/tmp/tfdbg2_logdir",
    tensor_debug_mode="FULL_HEALTH",
    circular_buffer_size=-1)

L'argument tensor_debug_mode contrôle les informations que le débogueur V2 extrait de chaque tenseur impatient ou dans le graphe. "FULL_HEALTH" est un mode qui capture les informations suivantes sur chaque tenseur de type flottant (par exemple, le float32 couramment vu et le dtype bfloat16 moins courant) :

  • DType
  • Rang
  • Nombre total d'éléments
  • Une répartition des éléments de type flottant dans les catégories suivantes : fini négatif ( - ), zéro ( 0 ), fini positif ( + ), infini négatif ( -∞ ), infini positif ( +∞ ) et NaN .

Le mode "FULL_HEALTH" convient au débogage des bogues impliquant NaN et l'infini. Voir ci-dessous pour les autres tensor_debug_mode pris en charge.

L'argument circular_buffer_size contrôle le nombre d'événements tensoriels enregistrés dans le logdir. Sa valeur par défaut est 1000, ce qui entraîne la sauvegarde sur disque des 1000 derniers tenseurs avant la fin du programme TF2 instrumenté. Ce comportement par défaut réduit la surcharge du débogueur en sacrifiant l'exhaustivité des données de débogage. Si l'exhaustivité est préférée, comme dans ce cas, nous pouvons désactiver le tampon circulaire en définissant l'argument sur une valeur négative (par exemple, -1 ici).

L'exemple debug_mnist_v2 appelle enable_dump_debug_info() en lui transmettant des indicateurs de ligne de commande. Pour exécuter à nouveau notre programme TF2 problématique avec cette instrumentation de débogage activée, faites :

python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
    --dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH

Démarrage de l'interface graphique du débogueur V2 dans TensorBoard

L'exécution du programme avec l'instrumentation du débogueur crée un logdir dans /tmp/tfdbg2_logdir. Nous pouvons démarrer TensorBoard et le pointer vers le logdir avec :

tensorboard --logdir /tmp/tfdbg2_logdir

Dans le navigateur Web, accédez à la page de TensorBoard à l'adresse http://localhost:6006. Le plugin "Debugger V2" sera inactif par défaut, alors sélectionnez-le dans le menu "Plugins inactifs" en haut à droite. Une fois sélectionné, il devrait ressembler à ceci :

Capture d'écran de la vue complète du débogueur V2

Utilisation de l'interface graphique du débogueur V2 pour trouver la cause première des NaN

L'interface graphique du débogueur V2 dans TensorBoard est organisée en six sections :

  • Alertes : cette section en haut à gauche contient une liste des événements "d'alerte" détectés par le débogueur dans les données de débogage du programme TensorFlow instrumenté. Chaque alerte indique une certaine anomalie qui mérite votre attention. Dans notre cas, cette section met en évidence 499 événements NaN/∞ avec une couleur rose-rouge saillante. Cela confirme notre suspicion que le modèle ne parvient pas à apprendre en raison de la présence de NaN et/ou d'infinis dans ses valeurs de tenseur interne. Nous nous pencherons sur ces alertes sous peu.
  • Chronologie d'exécution de Python : il s'agit de la moitié supérieure de la section centrale supérieure. Il présente l'historique complet de l'exécution impatiente des opérations et des graphiques. Chaque case de la chronologie est marquée par la lettre initiale du nom de l'op ou du graphe (par exemple, "T" pour l'op "TensorSliceDataset", "m" pour le "model" tf.function ). Nous pouvons naviguer dans cette chronologie en utilisant les boutons de navigation et la barre de défilement au-dessus de la chronologie.
  • Exécution du graphe : située dans le coin supérieur droit de l'interface graphique, cette section sera au cœur de notre tâche de débogage. Il contient un historique de tous les tenseurs de type flottant calculés à l'intérieur des graphes (c'est-à-dire compilés par @tf-function s).
  • La structure du graphique (moitié inférieure de la section centrale supérieure), le code source (section inférieure gauche) et Stack Trace (section inférieure droite) sont initialement vides. Leur contenu sera rempli lorsque nous interagissons avec l'interface graphique. Ces trois sections joueront également un rôle important dans notre tâche de débogage.

Après nous être orientés vers l'organisation de l'interface utilisateur, suivons les étapes suivantes pour comprendre pourquoi les NaN sont apparus. Tout d'abord, cliquez sur l'alerte NaN/∞ dans la section Alertes. Cela fait défiler automatiquement la liste des 600 tenseurs de graphe dans la section Exécution de graphe et se concentre sur le #88, qui est un tenseur nommé Log:0 généré par une Log (logarithme naturel). Une couleur rose-rouge saillante met en évidence un élément -∞ parmi les 1000 éléments du tenseur 2D float32. Il s'agit du premier tenseur de l'historique d'exécution du programme TF2 qui contenait du NaN ou de l'infini : les tenseurs calculés avant lui ne contiennent ni NaN ni ∞ ; de nombreux (en fait, la plupart) tenseurs calculés par la suite contiennent des NaN. Nous pouvons le confirmer en faisant défiler de haut en bas la liste d'exécution de graphe. Cette observation fournit une forte indication que l'op Log est la source de l'instabilité numérique dans ce programme TF2.

Débogueur V2 : Alertes Nan/Infini et liste d'exécution des graphes

Pourquoi ce Log op crache-t-il un -∞ ? Répondre à cette question nécessite d'examiner l'entrée de l'op. Cliquer sur le nom du tenseur ( Log:0 ) fait apparaître une visualisation simple mais informative du voisinage de l'op Log dans son graphique TensorFlow dans la section Structure du graphique. Notez la direction de haut en bas du flux d'informations. L'op lui-même est indiqué en gras au milieu. Immédiatement au-dessus, nous pouvons voir qu'une opération Placeholder fournit la seule et unique entrée à l' Log . Où est le tenseur généré par ce probs Placeholder dans la liste Graph Execution ? En utilisant la couleur d'arrière-plan jaune comme aide visuelle, nous pouvons voir que le tenseur probs:0 est trois lignes au-dessus du tenseur Log:0 , c'est-à-dire à la ligne 85.

Débogueur V2 : Vue de la structure du graphe et traçage vers le tenseur d'entrée

Un examen plus attentif de la décomposition numérique du tenseur probs:0 de la ligne 85 révèle pourquoi son consommateur Log:0 produit un -∞ : parmi les 1000 éléments de probs:0 , un élément a une valeur de 0. Le -∞ est résultat du calcul du logarithme naturel de 0 ! Si nous pouvons nous assurer d'une manière ou d'une autre que l'op Log n'est exposé qu'aux entrées positives, nous pourrons empêcher le NaN/∞ de se produire. Ceci peut être réalisé en appliquant un découpage (par exemple, en utilisant tf.clip_by_value() ) sur le tenseur Placeholder probs .

Nous nous rapprochons de la résolution du bogue, mais ce n'est pas encore tout à fait terminé. Afin d'appliquer le correctif, nous devons savoir d'où proviennent l'op Log et son entrée Placeholder dans le code source Python. Le débogueur V2 fournit une prise en charge de première classe pour tracer les opérations graphiques et les événements d'exécution jusqu'à leur source. Lorsque nous avons cliqué sur le tenseur Log:0 dans Graph Executions, la section Stack Trace a été remplie avec la trace de pile d'origine de la création de l'op Log . La trace de la pile est quelque peu volumineuse car elle inclut de nombreuses images du code interne de TensorFlow (par exemple, gen_math_ops.py et dumping_callback.py), que nous pouvons ignorer en toute sécurité pour la plupart des tâches de débogage. Le cadre qui nous intéresse est la ligne 216 de debug_mnist_v2.py (c'est-à-dire le fichier Python que nous essayons actuellement de déboguer). Cliquer sur "Ligne 216" fait apparaître une vue de la ligne de code correspondante dans la section Code source.

Débogueur V2 : code source et trace de la pile

Cela nous amène enfin au code source qui a créé l'opération Log problématique à partir de son entrée probs . Il s'agit de notre fonction personnalisée de perte d'entropie croisée catégorique décorée avec @tf.function et donc convertie en un graphique TensorFlow. L'espace réservé op probs correspond au premier argument d'entrée de la fonction de perte. L'op Log est créé avec l'appel d'API tf.math.log().

Le correctif de valeur de ce bogue ressemblera à :

  diff = -(labels *
           tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))

Cela résoudra l'instabilité numérique de ce programme TF2 et permettra au MLP de s'entraîner avec succès. Une autre approche possible pour corriger l'instabilité numérique consiste à utiliser tf.keras.losses.CategoricalCrossentropy .

Ceci conclut notre voyage de l'observation d'un bogue du modèle TF2 à la proposition d'un changement de code qui corrige le bogue, aidé par l'outil Debugger V2, qui offre une visibilité complète sur l'historique d'exécution rapide et graphique du programme TF2 instrumenté, y compris les résumés numériques des valeurs de tenseur et association entre les ops, les tenseurs et leur code source d'origine.

Compatibilité matérielle du débogueur V2

Debugger V2 prend en charge le matériel de formation grand public, y compris le CPU et le GPU. La formation multi-GPU avec tf.distributed.MirroredStrategy est également prise en charge. La prise en charge de TPU est encore à un stade précoce et nécessite d'appeler

tf.config.set_soft_device_placement(True)

avant d'appeler enable_dump_debug_info() . Il peut également avoir d'autres limitations sur les TPU. Si vous rencontrez des problèmes lors de l'utilisation de Debugger V2, veuillez signaler les bogues sur notre page de problèmes GitHub .

Compatibilité API du débogueur V2

Debugger V2 est implémenté à un niveau relativement bas de la pile logicielle de TensorFlow et est donc compatible avec tf.keras , tf.data et d'autres API construites au-dessus des niveaux inférieurs de TensorFlow. Le débogueur V2 est également rétrocompatible avec TF1, bien que la chronologie d'exécution rapide soit vide pour les journaux de débogage générés par les programmes TF1.

Conseils d'utilisation de l'API

Une question fréquemment posée à propos de cette API de débogage est de savoir où, dans le code TensorFlow, il faut insérer l'appel à enable_dump_debug_info() . En règle générale, l'API doit être appelée le plus tôt possible dans votre programme TF2, de préférence après les lignes d'importation Python et avant le début de la construction et de l'exécution du graphe. Cela garantira une couverture complète de toutes les opérations et graphiques qui alimentent votre modèle et sa formation.

Les tensor_debug_modes actuellement pris en charge sont : NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH et SHAPE . Ils varient dans la quantité d'informations extraites de chaque tenseur et la surcharge de performances du programme débogué. Veuillez vous référer à la section args de la documentation de enable_dump_debug_info() .

Surcoût de performances

L'API de débogage introduit une surcharge de performances pour le programme instrumenté TensorFlow. La surcharge varie selon tensor_debug_mode , le type de matériel et la nature du programme TensorFlow instrumenté. Comme point de référence, sur un GPU, le mode NO_TENSOR ajoute une surcharge de 15 % lors de la formation d'un modèle Transformer sous une taille de lot de 64. Le pourcentage de surcharge pour les autres tensor_debug_modes est plus élevé : environ 50 % pour CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH et SHAPE modes. Sur les processeurs, la surcharge est légèrement inférieure. Sur les TPU, les frais généraux sont actuellement plus élevés.

Relation avec d'autres API de débogage TensorFlow

Notez que TensorFlow propose d'autres outils et API pour le débogage. Vous pouvez parcourir ces API sous l'espace de noms tf.debugging.* sur la page de documentation de l'API. Parmi ces API, la plus fréquemment utilisée est tf.print() . Quand faut-il utiliser Debugger V2 et quand faut-il tf.print() à la place ? tf.print() est pratique dans le cas où

  1. on sait exactement quels tenseurs imprimer,
  2. nous savons où exactement dans le code source insérer ces tf.print() ,
  3. le nombre de tels tenseurs n'est pas trop grand.

Pour d'autres cas (par exemple, examen de nombreuses valeurs de tenseur, examen des valeurs de tenseur générées par le code interne de TensorFlow et recherche de l'origine de l'instabilité numérique comme nous l'avons montré ci-dessus), Debugger V2 fournit un moyen plus rapide de débogage. De plus, Debugger V2 fournit une approche unifiée pour inspecter les tenseurs impatients et graphiques. Il fournit en outre des informations sur la structure du graphique et les emplacements de code, qui dépassent les capacités de tf.print() .

Une autre API qui peut être utilisée pour déboguer les problèmes impliquant ∞ et NaN est tf.debugging.enable_check_numerics() . Contrairement à enable_dump_debug_info() , enable_check_numerics() n'enregistre pas les informations de débogage sur le disque. Au lieu de cela, il surveille simplement ∞ et NaN pendant l'exécution de TensorFlow et se trompe avec l'emplacement du code d'origine dès qu'une opération génère de telles mauvaises valeurs numériques. Il a une surcharge de performances inférieure à celle de enable_dump_debug_info() , mais ne permet pas une trace complète de l'historique d'exécution du programme et ne vient pas avec une interface utilisateur graphique comme Debugger V2.