Bonnes pratiques de test TensorFlow

Voici les pratiques recommandées pour tester le code dans le référentiel TensorFlow .

Avant de commencer

Avant de contribuer au code source d'un projet TensorFlow, veuillez consulter le fichier CONTRIBUTING.md dans le dépôt GitHub du projet. (Par exemple, consultez le fichier CONTRIBUTING.md pour le référentiel TensorFlow principal .) Tous les contributeurs de code doivent signer un contrat de licence de contributeur (CLA).

Principes généraux

Dépendez uniquement de ce que vous utilisez dans vos règles de CONSTRUCTION

TensorFlow est une grande bibliothèque et dépendre du package complet lors de l'écriture d'un test unitaire pour ses sous-modules est une pratique courante. Cependant, cela désactive l'analyse basée sur les dépendances bazel . Cela signifie que les systèmes d'intégration continue ne peuvent pas éliminer intelligemment les tests non liés pour les exécutions avant/après la soumission. Si vous dépendez uniquement des sous-modules que vous testez dans votre fichier BUILD , vous ferez gagner du temps à tous les développeurs TensorFlow et une grande puissance de calcul précieuse.

Cependant, modifier votre dépendance de build pour omettre les cibles TF complètes entraîne certaines limitations quant à ce que vous pouvez importer dans votre code Python. Vous ne pourrez plus utiliser l'instruction import tensorflow as tf dans vos tests unitaires. Mais c'est un compromis intéressant car cela évite à tous les développeurs d'exécuter des milliers de tests inutiles.

Tout le code doit avoir des tests unitaires

Pour tout code que vous écrivez, vous devez également écrire ses tests unitaires. Si vous écrivez un nouveau fichier foo.py , vous devez placer ses tests unitaires dans foo_test.py et le soumettre dans le même changement. Visez une couverture de test incrémentielle > 90 % pour tout votre code.

Évitez d'utiliser des règles de test Bazel natives dans TF

TF a beaucoup de subtilités lors de l'exécution de tests. Nous avons travaillé pour masquer toutes ces complexités dans nos macros Bazel. Pour éviter d'avoir à les gérer, utilisez ce qui suit à la place des règles de test natives. Notez que tous ces éléments sont définis dans tensorflow/tensorflow.bzl Pour les tests CC, utilisez tf_cc_test , tf_gpu_cc_test , tf_gpu_only_cc_test . Pour les tests Python, utilisez tf_py_test ou gpu_py_test . Si vous avez besoin de quelque chose de vraiment proche de la règle native py_test , veuillez plutôt utiliser celle définie dans tensorflow.bzl. Il vous suffit d'ajouter la ligne suivante en haut du fichier BUILD : load(“tensorflow/tensorflow.bzl”, “py_test”)

Sachez où le test s'exécute

Lorsque vous écrivez un test, notre infrastructure de test peut se charger d'exécuter vos tests sur le CPU, le GPU et les accélérateurs si vous les écrivez en conséquence. Nous avons des tests automatisés qui s'exécutent sous Linux, Macos, Windows, et qui ont des systèmes avec ou sans GPU. Il vous suffit de choisir l'une des macros répertoriées ci-dessus, puis d'utiliser des balises pour limiter l'endroit où elles sont exécutées.

  • La balise manual empêchera votre test de s'exécuter n'importe où. Cela inclut les exécutions de tests manuels qui utilisent des modèles tels que bazel test tensorflow/…

  • no_oss exclura votre test de l'exécution dans l'infrastructure de test officielle TF OSS.

  • Les balises no_mac ou no_windows peuvent être utilisées pour exclure votre test des suites de tests du système d'exploitation pertinentes.

  • La balise no_gpu peut être utilisée pour exclure votre test de l'exécution dans les suites de tests GPU.

Vérifier les tests exécutés dans les suites de tests attendues

TF dispose de plusieurs suites de tests. Parfois, leur mise en place peut être déroutante. Il peut y avoir différents problèmes qui entraînent l'omission de vos tests dans les builds continues. Ainsi, vous devez vérifier que vos tests s'exécutent comme prévu. Pour faire ça:

  • Attendez que vos présoumissions sur votre Pull Request (PR) soient terminées.
  • Faites défiler vers le bas de votre PR pour voir les vérifications de statut.
  • Cliquez sur le lien « Détails » sur le côté droit de n'importe quel chèque Kokoro.
  • Consultez la liste « Cibles » pour trouver vos cibles nouvellement ajoutées.

Chaque classe/unité doit avoir son propre fichier de test unitaire

Des classes de test séparées nous aident à mieux isoler les pannes et les ressources. Ils conduisent à des fichiers de test beaucoup plus courts et plus faciles à lire. Par conséquent, tous vos fichiers Python doivent avoir au moins un fichier de test correspondant (pour chaque foo.py , il doit avoir foo_test.py ). Pour des tests plus élaborés, tels que des tests d'intégration qui nécessitent des configurations différentes, il est possible d'ajouter davantage de fichiers de test.

Vitesse et temps de course

Le partage doit être utilisé le moins possible

Au lieu de fragmenter, pensez à :

  • Rendre vos tests plus petits
  • Si ce qui précède n'est pas possible, divisez les tests

Le partage permet de réduire la latence globale d'un test, mais la même chose peut être obtenue en divisant les tests en cibles plus petites. Le fractionnement des tests nous donne un niveau de contrôle plus fin sur chaque test, minimisant les exécutions préalables inutiles et réduisant la perte de couverture due à un buildcop désactivant une cible entière en raison d'un scénario de test qui se comporte mal. De plus, le partitionnement entraîne des coûts cachés qui ne sont pas si évidents, comme l'exécution de tout le code d'initialisation des tests pour tous les fragments. Ce problème nous a été signalé par les équipes infra en tant que source générant une charge supplémentaire.

Des tests plus petits sont meilleurs

Plus vos tests seront exécutés rapidement, plus les utilisateurs seront susceptibles de les exécuter. Une seconde supplémentaire pour votre test peut équivaloir à des heures supplémentaires consacrées à l'exécution de votre test par les développeurs et notre infrastructure. Essayez de faire en sorte que vos tests s'exécutent en moins de 30 secondes (en mode non opt !) et réduisez-les. Marquez vos tests comme moyens uniquement en dernier recours. L'infra n'exécute pas de tests volumineux en tant que pré-soumissions ou post-soumissions ! Par conséquent, n’écrivez un test volumineux que si vous souhaitez déterminer où il va s’exécuter. Quelques conseils pour accélérer l’exécution des tests :

  • Exécutez moins d’itérations d’entraînement dans votre test
  • Envisagez d'utiliser l'injection de dépendances pour remplacer les dépendances lourdes du système testé par de simples contrefaçons.
  • Envisagez d'utiliser des données d'entrée plus petites dans les tests unitaires
  • Si rien d'autre ne fonctionne, essayez de diviser votre fichier de test.

Les temps de test doivent viser la moitié du délai d’attente de la taille du test pour éviter les flocons

Avec les cibles de test bazel , les petits tests ont un délai d'attente d'une minute. Les délais d’attente moyens pour les tests sont de 5 minutes. Les tests volumineux ne sont tout simplement pas exécutés par l'infra de test TensorFlow. Cependant, de nombreux tests ne sont pas déterministes quant au temps qu’ils prennent. Pour diverses raisons, vos tests peuvent prendre plus de temps de temps en temps. Et, si vous marquez un test qui s'exécute en moyenne pendant 50 secondes comme étant petit, votre test échouera s'il est planifié sur une machine dotée d'un ancien processeur. Par conséquent, visez une durée moyenne d’exécution de 30 secondes pour les petits tests. Visez 2 minutes 30 secondes de durée moyenne d’exécution pour les tests moyens.

Réduire le nombre d’échantillons et augmenter les tolérances pour la formation

Les tests lents dissuadent les contributeurs. L’entraînement aux tests peut être très lent. Préférez des tolérances plus élevées pour pouvoir utiliser moins d'échantillons dans vos tests afin de garder vos tests suffisamment rapides (2,5 minutes max).

Éliminer le non-déterminisme et les flocons

Écrire des tests déterministes

Les tests unitaires doivent toujours être déterministes. Tous les tests exécutés sur TAP et guitar doivent s'exécuter de la même manière à chaque fois, si aucun changement de code ne les affecte. Pour garantir cela, voici quelques points à considérer.

Toujours semer toute source de stochasticité

Tout générateur de nombres aléatoires ou toute autre source de stochasticité peut provoquer une desquamation. Par conséquent, chacun d’eux doit être ensemencé. En plus de rendre les tests moins flous, cela rend tous les tests reproductibles. Différentes manières de définir certaines valeurs de départ que vous devrez peut-être définir dans les tests TF sont :

# Python RNG
import random
random.seed(42)

# Numpy RNG
import numpy as np
np.random.seed(42)

# TF RNG
from tensorflow.python.framework import random_seed
random_seed.set_seed(42)

Évitez d'utiliser sleep dans les tests multithreads

L’utilisation de la fonction sleep lors des tests peut être une cause majeure de desquamation. Surtout lorsque vous utilisez plusieurs threads, utiliser sleep pour attendre un autre thread ne sera jamais déterministe. Cela est dû au fait que le système n'est pas en mesure de garantir un ordre d'exécution des différents threads ou processus. Préférez donc les constructions de synchronisation déterministes telles que les mutex.

Vérifiez si le test est floconneux

Les flocons font perdre de nombreuses heures aux buildcops et aux développeurs. Ils sont difficiles à détecter et difficiles à déboguer. Même s'il existe des systèmes automatisés pour détecter la desquamation, ils doivent accumuler des centaines de tests avant de pouvoir refuser avec précision les tests. Même lorsqu’ils détectent, ils refusent vos tests et la couverture des tests est perdue. Par conséquent, les auteurs de tests doivent vérifier si leurs tests sont irréguliers lors de la rédaction des tests. Cela peut être facilement fait en exécutant votre test avec l'indicateur : --runs_per_test=1000

Utiliser TensorFlowTestCase

TensorFlowTestCase prend les précautions nécessaires, telles que l'amorçage de tous les générateurs de nombres aléatoires utilisés pour réduire autant que possible la desquamation. Au fur et à mesure que nous découvrirons et corrigerons davantage de sources de floconnement, celles-ci seront toutes ajoutées à TensorFlowTestCase. Par conséquent, vous devez utiliser TensorFlowTestCase lors de l'écriture de tests pour Tensorflow. TensorFlowTestCase est défini ici : tensorflow/python/framework/test_util.py

Écrire des tests hermétiques

Les tests hermétiques ne nécessitent aucune ressource extérieure. Ils regorgent de tout ce dont ils ont besoin et lancent simplement tous les faux services dont ils pourraient avoir besoin. Tous les services autres que vos tests sont des sources de non-déterminisme. Même avec une disponibilité de 99 % des autres services, le réseau peut s'effondrer, la réponse rpc peut être retardée et vous pourriez vous retrouver avec un message d'erreur inexplicable. Les services externes peuvent être, sans toutefois s'y limiter, GCS, S3 ou tout autre site Web.