De nombreux utilisateurs l'ont cité comme raison de passer à Pytorch, mais je n'ai pas encore trouvé de justification / explication pour sacrifier la qualité pratique la plus importante, la vitesse, pour une exécution rapide.
Ci-dessous, les performances de l'analyse comparative du code, TF1 contre TF2 - avec TF1 fonctionnant de 47% à 276% plus rapidement .
Ma question est: qu'est-ce qui, au niveau du graphique ou du matériel, engendre un ralentissement aussi important?
Vous cherchez une réponse détaillée - je connais déjà les concepts généraux. Git pertinent
Spécifications : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070
Résultats de référence :
MISE À JOUR : La désactivation de l'exécution désirée par le code ci-dessous n'aide pas . Le comportement, cependant, est incohérent: parfois, l'exécution en mode graphique aide considérablement, d'autres fois, elle s'exécute plus lentement par rapport à Eager.
Comme les développeurs TF n'apparaissent nulle part, je vais enquêter moi-même sur cette question - je peux suivre les progrès du problème lié à Github.
MISE À JOUR 2 : des tonnes de résultats expérimentaux à partager, accompagnés d'explications; devrait être fait aujourd'hui.
Code de référence :
# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time
batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)
model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y) # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)
K.clear_session() # in my testing, kernel was restarted instead
model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y) # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)
Fonctions utilisées :
def timeit(func, iterations, *args):
t0 = time()
for _ in range(iterations):
func(*args)
print("Time/iter: %.4f sec" % ((time() - t0) / iterations))
def make_small_model(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = Conv1D(128, 400, strides=4, padding='same')(ipt)
x = Flatten()(x)
x = Dropout(0.5)(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_medium_model(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x = LSTM(512, activation='relu', return_sequences=True)(x)
x = Conv1D(128, 400, strides=4, padding='same')(x)
x = Flatten()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_data(batch_shape):
return np.random.randn(*batch_shape), np.random.randint(0, 2, (batch_shape[0], 1))
la source
Réponses:
MISE À JOUR 18/02/2020 : J'ai bancé 2.1 et 2.1 tous les soirs; les résultats sont mitigés. Toutes les configurations sauf une (modèle et taille des données) sont aussi rapides ou beaucoup plus rapides que les meilleures de TF2 et TF1. Celui qui est le plus lent et le plus lent, est Large-Large - esp. dans l'exécution du graphique ( 1,6x à 2,5x plus lent ).
De plus, il existe des différences de reproductibilité extrêmes entre Graph et Eager pour un grand modèle que j'ai testé - un qui ne s'explique pas par le hasard / calcul-parallélisme. Je ne peux pas actuellement présenter de code reproductible pour ces revendications par contraintes de temps, donc je recommande fortement de le tester pour vos propres modèles.
Je n'ai pas encore ouvert de problème Git à ce sujet, mais j'ai commenté l' original - pas encore de réponse. Je mettrai à jour la ou les réponses une fois les progrès réalisés.
VERDICT : ce n'est pas le cas , SI vous savez ce que vous faites. Mais si vous ne le faites pas , cela pourrait vous coûter beaucoup, de quelques mises à niveau de GPU en moyenne et de plusieurs GPU dans le pire des cas.
CETTE RÉPONSE : vise à fournir une description de haut niveau du problème, ainsi que des directives sur la façon de décider de la configuration de la formation spécifique à vos besoins. Pour une description détaillée et de bas niveau, qui inclut tous les résultats d'analyse comparative + code utilisé, voir mon autre réponse.
Je mettrai à jour ma (mes) réponse (s) avec plus d'informations si j'en apprends - je peux mettre en signet / "mettre en vedette" cette question pour référence.
RÉSUMÉ DU PROBLÈME : comme l'a confirmé un développeur TensorFlow, Q. Scott Zhu, TF2 a concentré le développement sur l'exécution désireuse et l'intégration étroite avec Keras, ce qui impliquait des changements radicaux dans la source TF - y compris au niveau du graphique. Avantages: capacités de traitement, de distribution, de débogage et de déploiement considérablement étendues. Le coût de certains d'entre eux, cependant, est la vitesse.
La question est cependant assez complexe. Ce n'est pas seulement TF1 vs TF2 - les facteurs entraînant des différences significatives dans la vitesse du train comprennent:
keras
contre.tf.keras
numpy
vstf.data.Dataset
vs ...train_on_batch()
contre.fit()
model(x)
vsmodel.predict(x)
vs ...Malheureusement, presque rien de ce qui précède n'est indépendant de l'autre, et chacun peut au moins doubler le temps d'exécution par rapport à un autre. Heureusement, vous pouvez déterminer ce qui fonctionnera le mieux systématiquement et avec quelques raccourcis - comme je vais le montrer.
QUE DEVRAIS-JE FAIRE? Actuellement, la seule façon est d'expérimenter pour votre modèle, vos données et votre matériel spécifiques. Aucune configuration unique ne fonctionnera toujours mieux - mais il y a des choses à faire et à ne pas faire pour simplifier votre recherche:
>> À FAIRE:
train_on_batch()
+numpy
+tf.keras
+ TF1 + Désireux / Graphiquetrain_on_batch()
+numpy
+tf.keras
+ TF2 + graphiquefit()
+numpy
+tf.keras
+ TF1 / TF2 + Graphique + grand modèle et données>> NE PAS:
fit()
+numpy
+keras
pour les petits et moyens modèles et donnéesfit()
+numpy
+tf.keras
+ TF1 / TF2 + Désireuxtrain_on_batch()
+numpy
+keras
+ TF1 + Désireux[Major]
tf.python.keras
; il peut fonctionner 10 à 100 fois plus lentement et avec beaucoup de bogues; Plus d'informationslayers
,models
,optimizers
, et services connexes "out-of-box" les importations d'utilisation; les opérations, les utilitaires et les importations «privées» connexes sont correctes - mais pour être sûr, vérifiez les altérations et si elles sont utilisées danstf.keras
Reportez-vous au code au bas de mon autre réponse pour un exemple de configuration d'analyse comparative. La liste ci-dessus est basée principalement sur les tableaux "BENCHMARKS" dans l'autre réponse.
LIMITATIONS des choses à faire et à ne pas faire ci-dessus:
Conv1D
etDense
- pas de RNN, données / cibles clairsemées, entrées 4 / 5D et autres configurationsnumpy
ettf.data.Dataset
, bien que de nombreux autres formats existent; voir autre réponsePourquoi TF2 a-t-il sacrifié la qualité la plus pratique, la vitesse, pour une exécution soignée? Ce n'est pas le cas, clairement - le graphique est toujours disponible. Mais si la question est "pourquoi tant de désir":
.__dict__
. Le graphique, en revanche, nécessite une familiarité avec les fonctions spéciales de backend - compliquant grandement tout le processus de débogage et d'introspection.COMMENT ACTIVER / DÉSACTIVER EAGER?
INFORMATIONS COMPLÉMENTAIRES :
_on_batch()
méthodes dans TF2; selon le développeur TF, ils utilisent toujours une implémentation plus lente, mais pas intentionnellement - c'est-à-dire qu'elle doit être corrigée. Voir l'autre réponse pour plus de détails.DEMANDES DE TENSORFLOW DEVS :
train_on_batch()
et l'aspect performances de l'appelfit()
itératif; les boucles de train personnalisées sont importantes pour beaucoup, surtout pour moi.REMERCIEMENTS : Merci à
MISES À JOUR :
14/11/19 - a trouvé un modèle (dans ma vraie application) qui fonctionne plus lentement sur TF2 pour toutes les configurations * avec les données d'entrée Numpy. Les différences variaient de 13 à 19%, avec une moyenne de 17%. Les différences entre
keras
ettf.keras
, cependant, étaient plus dramatiques: 18-40% , moy. 32% (TF1 & 2). (* - sauf Eager, pour lequel TF2 OOM'd)17/11/19 - devs a mis à jour les
on_batch()
méthodes dans un commit récent , déclarant avoir une vitesse améliorée - à publier dans TF 2.1, ou disponible maintenant soustf-nightly
. Comme je ne parviens pas à faire fonctionner ce dernier, cela retardera le benching jusqu'à 2.1.la source
fit_generator
? ... Je ne veux pratiquement jamaistrain_on_batch
et gérer ma propre boucle d'entraînement sur plusieurs lots est un énorme, énorme anti-modèle à éviter même à grand prix.fit
w / petit supplément de traitement des données. En ce qui concerne les boucles de train, j'ai écrit ma propre boucle personnalisée qui s'est finalement transformée en une sorte d'API;fit_generator
manque d'introspection, de personnalisation et de sauvegarde / chargement - donc un non absolu pour moi. Je publierai ma boucle d'entraînement par la suite, sur Github.fit_generator
votre application est de bien les tester.CETTE RÉPONSE : vise à fournir une description détaillée, au niveau graphique / matériel, du problème, y compris les boucles de train TF2 vs TF1, les processeurs de données d'entrée et les exécutions en mode Désir vs Graphique. Pour un résumé des problèmes et des directives de résolution, consultez mon autre réponse.
VERDICT DE PERFORMANCE : parfois l'un est plus rapide, parfois l'autre, selon la configuration. En ce qui concerne TF2 vs TF1, ils sont à peu près au pair en moyenne, mais des différences importantes basées sur la configuration existent, et TF1 l'emporte sur TF2 plus souvent que l'inverse. Voir "BENCHMARKING" ci-dessous.
EAGER VS. GRAPH : la chair de cette réponse entière pour certains: TF2 impatient est plus lent que TF1, selon mes tests. Détails plus bas.
La différence fondamentale entre les deux est la suivante: Graph met en place un réseau informatique de manière proactive , et s'exécute lorsqu'il lui est «demandé» - tandis que Eager exécute tout lors de la création. Mais l'histoire commence seulement ici:
Désireux n'est PAS dépourvu de graphique , et peut en fait être principalement graphique, contrairement aux attentes. Ce qu'il est en grande partie, est exécuté Graphique - cela inclut les poids du modèle et de l'optimiseur, comprenant une grande partie du graphique.
Désireux reconstruit une partie de son propre graphique à l'exécution ; conséquence directe de la construction incomplète de Graph - voir les résultats du profileur. Cela a une surcharge de calcul.
Désireux est plus lent avec les entrées Numpy ; selon ce commentaire et code Git , les entrées Numpy dans Eager incluent les frais généraux de copie des tenseurs du CPU au GPU. En parcourant le code source, les différences de gestion des données sont claires; Désireux passe directement Numpy, tandis que Graph passe des tenseurs qui évaluent ensuite Numpy; incertain du processus exact, mais ce dernier devrait impliquer des optimisations au niveau du GPU
TF2 Eager est plus lent que TF1 Eager - c'est ... inattendu. Voir les résultats d'analyse comparative ci-dessous. Les différences vont de négligeable à significatif, mais sont cohérentes. Je ne sais pas pourquoi c'est le cas - si un développeur TF clarifie, mettra à jour la réponse.
TF2 vs TF1 : citant les parties pertinentes d'un développement TF, Q. Scott Zhu, réponse - avec un peu de mon accent et reformulation:
Avec la dernière phrase du dernier paragraphe ci-dessus et la dernière clause du paragraphe ci-dessous:
Je ne suis pas d'accord - d'après mes résultats de profilage, qui montrent que le traitement des données d'entrée d'Eager est sensiblement plus lent que celui de Graph. En outre, vous n'êtes pas sûr de cela
tf.data.Dataset
en particulier, mais Eager appelle à plusieurs reprises plusieurs des mêmes méthodes de conversion de données - voir profileur.Enfin, le commit lié du développeur: nombre important de modifications pour prendre en charge les boucles Keras v2 .
Boucles de train : selon (1) Désireux vs Graphique; (2) le format des données d'entrée, la formation procédera à une boucle de train distinct - dans TF2,
_select_training_loop()
, training.py , l' un de:Chacun gère l'allocation des ressources différemment et a des conséquences sur les performances et les capacités.
Boucles de train:
fit
vstrain_on_batch
,keras
vstf.keras
: chacune des quatre utilise des boucles de train différentes, mais peut-être pas dans toutes les combinaisons possibles.keras
'fit
, par exemple, utilise une forme defit_loop
, par exempletraining_arrays.fit_loop()
, et sontrain_on_batch
peut utiliserK.function()
.tf.keras
a une hiérarchie plus sophistiquée décrite en partie dans la section précédente.Train Loops: documentation - documentation source pertinente sur certaines des différentes méthodes d'exécution:
Processeurs de données d'entrée : comme ci-dessus, le processeur est sélectionné au cas par cas, en fonction des indicateurs internes définis en fonction des configurations d'exécution (mode d'exécution, format de données, stratégie de distribution). Le cas le plus simple est avec Eager, qui fonctionne directement avec les tableaux Numpy. Pour quelques exemples spécifiques, voir cette réponse .
TAILLE DU MODÈLE, TAILLE DES DONNÉES:
convert_to_tensor
dans "PROFILER")REPÈRES : la viande hachée. - Document Word - Feuille de calcul Excel
Terminologie :
(1 - longer_time / shorter_time)*100
; justification: nous nous intéressons à quel facteur l' un est plus rapide que l'autre;shorter / longer
est en fait une relation non linéaire, pas utile pour une comparaison directe+
si TF2 est plus rapide+
si Graph est plus rapidePROFILER :
PROFILER - Explication : Spyder 3.3.6 IDE profiler.
Certaines fonctions sont répétées dans les nids des autres; par conséquent, il est difficile de retrouver la séparation exacte entre les fonctions de «traitement des données» et de «formation», il y aura donc un certain chevauchement - comme cela a été prononcé dans le tout dernier résultat.
% chiffres calculés par rapport au temps d'exécution moins le temps de construction
_func = func
-d. Profilera commefunc
), ce qui se mélange dans le temps de construction - d'où la nécessité de l'exclureENVIRONNEMENT D'ESSAI :
MÉTHODOLOGIE :
batch_size
etnum_channels
Conv1D
,Dense
couches « » apprenables; RNN évités par implément de version TF. différenceslayers.Embedding()
) ou de cibles éparses (par exempleSparseCategoricalCrossEntropy()
LIMITATIONS : une réponse «complète» expliquerait chaque boucle de train et itérateur possible, mais cela dépasse certainement mes capacités de temps, mon chèque de paie inexistant ou ma nécessité générale. Les résultats ne sont aussi bons que la méthodologie - interpréter avec un esprit ouvert.
CODE :
la source
model.compile
sansrun_eagerly=True
argument. Si vous êtes en mode impatient, vous pouvez exécuter une partie de votre code en mode graphique à l'aide detf.function
. Par conséquent, je pense que l'implémentation par défaut decompile
est de créer un graphe de calcul au lieu de l'exécuter avec impatience pour des raisons de performances. Notez également que si votre modèle est convolutif, vous ne voyez pas d'accélération en mode graphique car l'interaction python est minimale. Si vous effectuez de nombreuses opérations mathématiques, cela peut faire une grande différence (également dans l'utilisation de la mémoire).model.compile
sansrun_eagerly=True
assure le mode graphique, ou pas?model.compile
oumodel.fit
doit s'assurer que la formation s'exécute en mode graphique en interne.run_eagerly=True
comme paramètre à compiler." (source tensorflow.org/guide/keras/overview ) Par conséquent, si je ne passe pas lerun_eagerly=True
modèle, je peux exécuter en mode graphique. Je ne sais pas quel est le facteur décisif, mais pourquoi ne fonctionnerait-il pas en mode graphique s'il est plus efficace que désireux.