Mejores prácticas de prueba de TensorFlow

Estas son las prácticas recomendadas para probar código en el repositorio de TensorFlow .

Antes de empezar

Antes de contribuir con código fuente a un proyecto de TensorFlow, revise el archivo CONTRIBUTING.md en el repositorio de GitHub del proyecto. (Por ejemplo, consulte el archivo CONTRIBUTING.md para el repositorio principal de TensorFlow ). Todos los contribuyentes de código deben firmar un Acuerdo de licencia de colaborador (CLA).

Principios generales

Solo depende de lo que uses en tus reglas BUILD

TensorFlow es una biblioteca grande y depender del paquete completo al escribir una prueba unitaria para sus submódulos ha sido una práctica común. Sin embargo, esto deshabilita el análisis basado en dependencias bazel . Esto significa que los sistemas de integración continua no pueden eliminar de forma inteligente las pruebas no relacionadas para las ejecuciones previas y posteriores al envío. Si solo depende de los submódulos que está probando en su archivo BUILD , ahorrará tiempo a todos los desarrolladores de TensorFlow y una gran cantidad de poder de cálculo valioso.

Sin embargo, modificar su dependencia de compilación para omitir los objetivos TF completos trae algunas limitaciones sobre lo que puede importar en su código Python. Ya no podrá utilizar el import tensorflow as tf en sus pruebas unitarias. Pero esta es una compensación que vale la pena, ya que evita que todos los desarrolladores ejecuten miles de pruebas innecesarias.

Todo el código debe tener pruebas unitarias.

Para cualquier código que escriba, también debe escribir sus pruebas unitarias. Si escribe un archivo nuevo foo.py , debe colocar sus pruebas unitarias en foo_test.py y enviarlo dentro del mismo cambio. Apunte a una cobertura de prueba incremental >90 % para todo su código.

Evite el uso de reglas de prueba nativas de Bazel en TF

TF tiene muchas sutilezas al ejecutar pruebas. Hemos trabajado para ocultar todas esas complejidades en nuestras macros de bazel. Para evitar tener que lidiar con ellos, utilice lo siguiente en lugar de las reglas de prueba nativas. Tenga en cuenta que todos estos están definidos en tensorflow/tensorflow.bzl Para pruebas CC, use tf_cc_test , tf_gpu_cc_test , tf_gpu_only_cc_test . Para pruebas de Python, utilice tf_py_test o gpu_py_test . Si necesita algo realmente parecido a la regla nativa py_test , utilice la definida en tensorflow.bzl. Solo necesita agregar la siguiente línea en la parte superior del archivo BUILD: load(“tensorflow/tensorflow.bzl”, “py_test”)

Tenga en cuenta dónde se ejecuta la prueba

Cuando escribe una prueba, nuestra infraestructura de prueba puede encargarse de ejecutar sus pruebas en CPU, GPU y aceleradores si las escribe en consecuencia. Contamos con pruebas automatizadas que se ejecutan en Linux, macos, windows, que tengan sistemas con o sin GPU. Simplemente necesita elegir una de las macros enumeradas anteriormente y luego usar etiquetas para limitar dónde se ejecutan.

  • La etiqueta manual excluirá la ejecución de su prueba en cualquier lugar. Esto incluye ejecuciones de pruebas manuales que utilizan patrones como bazel test tensorflow/…

  • no_oss excluirá la ejecución de su prueba en la infraestructura de prueba oficial de TF OSS.

  • Se pueden utilizar etiquetas no_mac o no_windows para excluir su prueba de los conjuntos de pruebas relevantes del sistema operativo.

  • La etiqueta no_gpu se puede utilizar para excluir la ejecución de su prueba en conjuntos de pruebas de GPU.

Verificar las pruebas ejecutadas en los conjuntos de pruebas esperados

TF tiene bastantes conjuntos de pruebas. A veces, puede resultar confuso configurarlos. Puede haber diferentes problemas que hagan que sus pruebas se omitan en las compilaciones continuas. Por lo tanto, debe verificar que sus pruebas se estén ejecutando como se esperaba. Para hacer esto:

  • Espere a que se completen los envíos previos de su solicitud de extracción (PR).
  • Desplácese hasta la parte inferior de su PR para ver las verificaciones de estado.
  • Haga clic en el enlace "Detalles" en el lado derecho de cualquier cheque de Kokoro.
  • Consulte la lista "Objetivos" para encontrar los objetivos recién agregados.

Cada clase/unidad debe tener su propio archivo de prueba unitaria

Las clases de prueba separadas nos ayudan a aislar mejor las fallas y los recursos. Conducen a archivos de prueba mucho más cortos y fáciles de leer. Por lo tanto, todos sus archivos Python deben tener al menos un archivo de prueba correspondiente (para cada foo.py , debe tener foo_test.py ). Para pruebas más elaboradas, como pruebas de integración que requieren configuraciones diferentes, está bien agregar más archivos de prueba.

Velocidad y tiempos de ejecución.

La fragmentación debe usarse lo menos posible

En lugar de fragmentar, considere:

  • Hacer sus pruebas más pequeñas
  • Si lo anterior no es posible, divida las pruebas

La fragmentación ayuda a reducir la latencia general de una prueba, pero se puede lograr lo mismo dividiendo las pruebas en objetivos más pequeños. La división de pruebas nos brinda un mayor nivel de control en cada prueba, minimizando ejecuciones innecesarias de envío previo y reduciendo la pérdida de cobertura debido a que un buildcop deshabilita un objetivo completo debido a un caso de prueba que se comporta mal. Además, la fragmentación conlleva costos ocultos que no son tan obvios, como ejecutar todo el código de inicialización de prueba para todos los fragmentos. Los equipos de infraestructura nos han remitido este problema como una fuente que crea una carga adicional.

Las pruebas más pequeñas son mejores

Cuanto más rápido se ejecuten las pruebas, más probabilidades habrá de que las personas las ejecuten. Un segundo adicional para su prueba puede acumular horas de tiempo adicional dedicado a ejecutar su prueba por parte de los desarrolladores y nuestra infraestructura. Intente que sus pruebas se ejecuten en menos de 30 segundos (¡en modo no opcional!) y hágalas pequeñas. Marque sus pruebas como medias únicamente como último recurso. ¡Infra no ejecuta pruebas importantes como envíos previos o posteriores! Por lo tanto, sólo escriba una prueba grande si va a organizar dónde se ejecutará. Algunos consejos para que las pruebas se ejecuten más rápido:

  • Ejecute menos iteraciones de entrenamiento en su prueba
  • Considere utilizar la inyección de dependencias para reemplazar las grandes dependencias del sistema bajo prueba con simples falsificaciones.
  • Considere el uso de datos de entrada más pequeños en pruebas unitarias
  • Si nada más funciona, intente dividir su archivo de prueba.

Los tiempos de prueba deben apuntar a la mitad del tiempo de espera del tamaño de la prueba para evitar escamas

Con los objetivos de prueba bazel , las pruebas pequeñas tienen tiempos de espera de 1 minuto. Los tiempos de espera de prueba medios son de 5 minutos. Las pruebas grandes simplemente no se ejecutan mediante la infraestructura de prueba de TensorFlow. Sin embargo, muchas pruebas no son deterministas en cuanto a la cantidad de tiempo que toman. Por diversas razones, sus pruebas pueden llevar más tiempo de vez en cuando. Y, si marca una prueba que se ejecuta durante 50 segundos en promedio como pequeña, su prueba fallará si se programa en una máquina con una CPU antigua. Por lo tanto, intente lograr un tiempo de ejecución promedio de 30 segundos para pruebas pequeñas. Apunta a 2 minutos y 30 segundos de tiempo de carrera promedio para pruebas medias.

Reducir el número de muestras y aumentar las tolerancias para el entrenamiento.

Las pruebas de ejecución lenta disuaden a los contribuyentes. El entrenamiento de carrera en las pruebas puede resultar muy lento. Prefiera tolerancias más altas para poder utilizar menos muestras en sus pruebas y mantenerlas lo suficientemente rápidas (2,5 minutos como máximo).

Eliminar el no determinismo y las copos

Escribir pruebas deterministas

Las pruebas unitarias siempre deben ser deterministas. Todas las pruebas que se ejecutan en TAP y Guitar deben ejecutarse de la misma manera cada vez, si no hay ningún cambio de código que las afecte. Para garantizar esto, a continuación se presentan algunos puntos a considerar.

Siembre siempre cualquier fuente de estocasticidad.

Cualquier generador de números aleatorios o cualquier otra fuente de estocasticidad puede provocar debilidad. Por lo tanto, cada uno de estos debe ser sembrado. Además de hacer que las pruebas sean menos complicadas, esto hace que todas las pruebas sean reproducibles. Las diferentes formas de establecer algunas semillas que puede necesitar en las pruebas de TF son:

# 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)

Evite utilizar sleep en pruebas multiproceso.

El uso de la función sleep en las pruebas puede ser una de las principales causas de descamación. Especialmente cuando se utilizan varios subprocesos, utilizar la suspensión para esperar otro subproceso nunca será determinista. Esto se debe a que el sistema no puede garantizar ningún orden de ejecución de diferentes subprocesos o procesos. Por lo tanto, prefiera construcciones de sincronización deterministas como mutex.

Compruebe si la prueba está escamosa.

Los copos hacen que los constructores y los desarrolladores pierdan muchas horas. Son difíciles de detectar y difíciles de depurar. Aunque existen sistemas automatizados para detectar debilidades, necesitan acumular cientos de ejecuciones de pruebas antes de poder incluir pruebas en la lista de denegaciones con precisión. Incluso cuando lo detectan, rechazan sus pruebas y se pierde la cobertura de la prueba. Por lo tanto, los autores de pruebas deben verificar si sus pruebas son deficientes al escribirlas. Esto se puede hacer fácilmente ejecutando la prueba con la bandera: --runs_per_test=1000

Utilice TensorFlowTestCase

TensorFlowTestCase toma las precauciones necesarias, como inicializar todos los generadores de números aleatorios utilizados para reducir la descamación tanto como sea posible. A medida que descubramos y solucionemos más fuentes de descamación, todas ellas se agregarán a TensorFlowTestCase. Por lo tanto, debes usar TensorFlowTestCase al escribir pruebas para tensorflow. TensorFlowTestCase se define aquí: tensorflow/python/framework/test_util.py

Escribe pruebas herméticas.

Las pruebas herméticas no necesitan recursos externos. Están repletos de todo lo que necesitan y simplemente inician cualquier servicio falso que puedan necesitar. Cualquier servicio distinto de sus pruebas es fuente de no determinismo. Incluso con una disponibilidad del 99 % de otros servicios, la red puede fallar, la respuesta de rpc puede retrasarse y podría terminar con un mensaje de error inexplicable. Los servicios externos pueden ser, entre otros, GCS, S3 o cualquier sitio web.