Best practice per i test di TensorFlow

Queste sono le pratiche consigliate per testare il codice nel repository TensorFlow .

Prima di iniziare

Prima di contribuire con il codice sorgente a un progetto TensorFlow, esamina il file CONTRIBUTING.md nel repository GitHub del progetto. (Ad esempio, vedere il file CONTRIBUTING.md per il repository TensorFlow principale .) Tutti i contributori del codice sono tenuti a firmare un contratto di licenza con il contributore (CLA).

Principi generali

Dipendi solo da ciò che usi nelle tue regole BUILD

TensorFlow è una libreria di grandi dimensioni e, quando si scrive un test unitario per i suoi sottomoduli, dipendere dal pacchetto completo è stata una pratica comune. Tuttavia, ciò disabilita l'analisi basata sulle dipendenze bazel . Ciò significa che i sistemi di integrazione continua non possono eliminare in modo intelligente i test non correlati per le esecuzioni pre/post invio. Se dipendi solo dai sottomoduli che stai testando nel tuo file BUILD , risparmierai tempo per tutti gli sviluppatori TensorFlow e molta preziosa potenza di calcolo.

Tuttavia, la modifica della dipendenza della build per omettere le destinazioni TF complete comporta alcune limitazioni su ciò che puoi importare nel tuo codice Python. Non sarai più in grado di utilizzare l'istruzione import tensorflow as tf nei test unitari. Ma questo è un compromesso utile poiché evita a tutti gli sviluppatori di eseguire migliaia di test non necessari.

Tutto il codice dovrebbe avere test unitari

Per qualsiasi codice che scrivi, dovresti scrivere anche i relativi test unitari. Se scrivi un nuovo file foo.py , dovresti inserire i suoi test unitari in foo_test.py e inviarlo con la stessa modifica. Punta a una copertura dei test incrementale >90% per tutto il tuo codice.

Evitare di utilizzare regole di test Bazel native in TF

TF ha molte sottigliezze durante l'esecuzione dei test. Abbiamo lavorato per nascondere tutte queste complessità nelle nostre macro bazel. Per evitare di doverli occupare, utilizzare quanto segue invece delle regole di test native. Tieni presente che tutti questi sono definiti in tensorflow/tensorflow.bzl Per i test CC, utilizza tf_cc_test , tf_gpu_cc_test , tf_gpu_only_cc_test . Per i test Python, utilizzare tf_py_test o gpu_py_test . Se hai bisogno di qualcosa di veramente simile alla regola nativa py_test , utilizza invece quella definita in tensorflow.bzl. Devi solo aggiungere la seguente riga all'inizio del file BUILD: load(“tensorflow/tensorflow.bzl”, “py_test”)

Essere consapevoli di dove viene eseguito il test

Quando scrivi un test, la nostra infra test può occuparsi di eseguire i tuoi test su CPU, GPU e acceleratori se li scrivi di conseguenza. Abbiamo test automatizzati che funzionano su Linux, macos, Windows, che hanno sistemi con o senza GPU. Devi semplicemente scegliere una delle macro elencate sopra e quindi utilizzare i tag per limitare dove vengono eseguite.

  • il tag manual escluderà l'esecuzione del test ovunque. Ciò include esecuzioni di test manuali che utilizzano modelli come bazel test tensorflow/…

  • no_oss escluderà il tuo test dall'esecuzione nell'infrastruttura di test TF OSS ufficiale.

  • I tag no_mac o no_windows possono essere utilizzati per escludere il test dalle suite di test del sistema operativo pertinenti.

  • Il tag no_gpu può essere utilizzato per escludere il test dall'esecuzione nelle suite di test GPU.

Verificare che i test vengano eseguiti nelle suite di test previste

TF ha parecchie suite di test. A volte, potrebbero creare confusione da configurare. Potrebbero esserci diversi problemi che causano l'omissione dei test dalle build continue. Pertanto, dovresti verificare che i tuoi test vengano eseguiti come previsto. Per fare ciò:

  • Attendi il completamento dei preinvii sulla tua Pull Request (PR).
  • Scorri fino alla fine del tuo PR per vedere i controlli dello stato.
  • Fai clic sul collegamento "Dettagli" sul lato destro di qualsiasi assegno Kokoro.
  • Controlla l'elenco "Target" per trovare i target appena aggiunti.

Ogni classe/unità dovrebbe avere il proprio file di test unitario

Classi di test separate ci aiutano a isolare meglio guasti e risorse. Conducono a file di test molto più brevi e più facili da leggere. Pertanto, tutti i tuoi file Python dovrebbero avere almeno un file di test corrispondente (per ogni foo.py , dovrebbe avere foo_test.py ). Per test più elaborati, come i test di integrazione che richiedono configurazioni diverse, è possibile aggiungere più file di test.

Velocità e tempi di esecuzione

Lo sharding dovrebbe essere utilizzato il meno possibile

Invece dello sharding, considera:

  • Rimpicciolire i test
  • Se quanto sopra non è possibile, suddividere le prove

Lo sharding aiuta a ridurre la latenza complessiva di un test, ma è possibile ottenere lo stesso risultato suddividendo i test su obiettivi più piccoli. La suddivisione dei test ci offre un livello di controllo più preciso su ciascun test, riducendo al minimo le esecuzioni di preinvio non necessarie e la perdita di copertura derivante dalla disabilitazione di un intero target da parte di un buildcop a causa di un test case dal comportamento anomalo. Inoltre, lo sharding comporta costi nascosti non così evidenti, come l'esecuzione di tutto il codice di inizializzazione dei test per tutti gli sharding. Questo problema ci è stato segnalato dai team infra come fonte che crea carico aggiuntivo.

I test più piccoli sono migliori

Più velocemente vengono eseguiti i test, più è probabile che le persone li eseguano. Un secondo in più per il tuo test può accumularsi in ore di tempo extra dedicate all'esecuzione del test da parte degli sviluppatori e della nostra infrastruttura. Prova a eseguire i test in meno di 30 secondi (in modalità non opt!) e rendili piccoli. Contrassegna i tuoi test come medi solo come ultima risorsa. L'infrastruttura non esegue test di grandi dimensioni come pre-inoltri o post-invii! Pertanto, scrivi un test di grandi dimensioni solo se intendi decidere dove verrà eseguito. Alcuni suggerimenti per velocizzare l'esecuzione dei test:

  • Esegui meno iterazioni di allenamento nel test
  • Prendi in considerazione l'utilizzo dell'iniezione di dipendenza per sostituire le pesanti dipendenze del sistema sotto test con semplici falsi.
  • Prendi in considerazione l'utilizzo di dati di input più piccoli nei test unitari
  • Se non funziona nient'altro, prova a dividere il file di test.

I tempi di test dovrebbero essere pari alla metà del timeout della dimensione del test per evitare scaglie

Con gli obiettivi dei test bazel , i test piccoli hanno timeout di 1 minuto. I timeout medi del test sono 5 minuti. I test di grandi dimensioni non vengono eseguiti dall'infrastruttura di test TensorFlow. Tuttavia, molti test non sono deterministici in termini di tempo impiegato. Per vari motivi i tuoi test potrebbero richiedere più tempo di tanto in tanto. Inoltre, se contrassegni un test che viene eseguito in media per 50 secondi come piccolo, il test fallirà se viene pianificato su una macchina con una vecchia CPU. Pertanto, per i test di piccole dimensioni, puntare a un tempo di esecuzione medio di 30 secondi. Obiettivo per 2 minuti e 30 secondi di tempo di esecuzione medio per i test medi.

Ridurre il numero di campioni e aumentare le tolleranze per la formazione

I test lenti scoraggiano i contributori. L'esecuzione dell'addestramento nei test può essere molto lenta. Preferire tolleranze più elevate per poter utilizzare meno campioni nei test e mantenere i test sufficientemente veloci (2,5 minuti massimo).

Elimina il non determinismo e le scaglie

Scrivere test deterministici

I test unitari dovrebbero sempre essere deterministici. Tutti i test eseguiti su TAP e chitarra dovrebbero essere eseguiti allo stesso modo ogni volta, a meno che non vi siano modifiche al codice che li influenzino. Per garantire ciò, di seguito sono riportati alcuni punti da considerare.

Semina sempre qualsiasi fonte di stocasticità

Qualsiasi generatore di numeri casuali o qualsiasi altra fonte di stocasticità può causare instabilità. Pertanto, ognuno di questi deve essere seminato. Oltre a rendere i test meno instabili, questo rende tutti i test riproducibili. Diversi modi per impostare alcuni seed che potresti dover impostare nei test TF sono:

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

Evitare di utilizzare sleep nei test multithread

L'utilizzo della funzione sleep nei test può essere una delle principali cause di sfaldamento. Soprattutto quando si utilizzano più thread, l'utilizzo della sospensione per attendere un altro thread non sarà mai deterministico. Ciò è dovuto al fatto che il sistema non è in grado di garantire alcun ordine di esecuzione di thread o processi diversi. Pertanto, preferire costrutti di sincronizzazione deterministici come i mutex.

Controlla se il test è instabile

I fiocchi fanno perdere molte ore ai buildcops e agli sviluppatori. Sono difficili da rilevare ed è difficile eseguire il debug. Anche se esistono sistemi automatizzati per rilevare la debolezza, è necessario accumulare centinaia di esecuzioni di test prima di poter inserire accuratamente i test nell'elenco negato. Anche quando rilevano, negano i tuoi test e la copertura del test viene persa. Pertanto, gli autori dei test dovrebbero verificare se i loro test sono instabili quando scrivono i test. Questo può essere fatto facilmente eseguendo il test con il flag: --runs_per_test=1000

Utilizza TensorFlowTestCase

TensorFlowTestCase prende le precauzioni necessarie come il seeding di tutti i generatori di numeri casuali utilizzati per ridurre il più possibile la fragilità. Man mano che scopriamo e risolviamo ulteriori fonti di instabilità, tutte queste verranno aggiunte a TensorFlowTestCase. Pertanto, dovresti utilizzare TensorFlowTestCase quando scrivi test per tensorflow. TensorFlowTestCase è definito qui: tensorflow/python/framework/test_util.py

Scrivi test ermetici

I test ermetici non necessitano di risorse esterne. Sono pieni di tutto ciò di cui hanno bisogno e avviano semplicemente tutti i servizi falsi di cui potrebbero aver bisogno. Tutti i servizi diversi dai test sono fonti di non determinismo. Anche con una disponibilità del 99% di altri servizi, la rete può rompersi, la risposta rpc può essere ritardata e potresti ritrovarti con un messaggio di errore inspiegabile. I servizi esterni possono essere, ma non limitati a, GCS, S3 o qualsiasi sito web.