Warum ist TensorFlow 2 viel langsamer als TensorFlow 1?

137

Es wurde von vielen Benutzern als Grund für den Wechsel zu Pytorch angeführt, aber ich habe noch keine Rechtfertigung / Erklärung dafür gefunden, die wichtigste praktische Qualität, die Geschwindigkeit, für die eifrige Ausführung zu opfern.

Nachfolgend finden Sie die Code-Benchmarking-Leistung TF1 vs. TF2 - wobei TF1 zwischen 47% und 276% schneller läuft .

Meine Frage ist: Was führt auf Grafik- oder Hardwareebene zu einer so deutlichen Verlangsamung?


Auf der Suche nach einer detaillierten Antwort - bin bereits mit umfassenden Konzepten vertraut. Relevante Git

Specs : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, 10 Windows 1070 GTX


Benchmark-Ergebnisse :


UPDATE : Das Deaktivieren der Eager-Ausführung gemäß dem folgenden Code hilft nicht . Das Verhalten ist jedoch inkonsistent: Manchmal hilft das Ausführen im Grafikmodus erheblich, manchmal läuft es im Vergleich zu Eager langsamer .

Da TF-Entwickler nirgendwo auftauchen, werde ich diese Angelegenheit selbst untersuchen - kann den Fortschritt in der verknüpften Github-Ausgabe verfolgen.

UPDATE 2 : Tonnen von experimentellen Ergebnissen, die zusammen mit Erklärungen geteilt werden müssen; sollte heute gemacht werden.


Benchmark-Code :

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

Verwendete Funktionen :

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))
OverLordGoldDragon
quelle
Haben Sie jemals ein solches Tool von cProfile verwendet, um zu analysieren, welches Teil sie so unterschiedlich macht?
Zihaozhihao
@zihaozhihao habe ich , wenn auch nicht speziell dafür; Gemäß dem vorherigen Link und dem Schreiben eines benutzerdefinierten Optimierers bin ich bereits mit Unterschieden bei Anrufen vertraut, verstehe aber nicht , warum einer langsamer ist als der andere - und kein Nicht-TF-Experte kann ihn von der Quelle aus verstehen, was zusätzlich dazu ist ein Wirrwarr, dokumentiert keine relativen Leistungen. Es sind
Informationen auf Grafik-
Ist die Numpy-Version in beiden Tests gleich?
Chabir
Autsch ... Wenn der alte Keras allein schon deutlich langsamer war als PyTorch, stellen Sie sich das jetzt vor.
Daniel Möller
Skaliert das Problem mit der Modellgröße? Haben Sie auch versucht, denselben Benchmark auf einem anderen Betriebssystem auszuführen?
Okawo

Antworten:

76

UPDATE 18.02.2020 : Ich habe 2.1 und 2.1 pro Nacht auf die Bank gesetzt . Die Ergebnisse sind gemischt. Alle bis auf eine Konfiguration (Modell- und Datengröße) sind so schnell oder viel schneller als die besten von TF2 und TF1. Derjenige, der langsamer und dramatisch langsamer ist, ist Large-Large - insb. in der Grafikausführung ( 1,6x bis 2,5x langsamer ).

Darüber hinaus gibt es extreme Unterschiede in der Reproduzierbarkeit zwischen Graph und Eager für ein großes Modell, das ich getestet habe - eines, das nicht durch Zufälligkeit / Rechenparallelität erklärt werden kann. Ich kann derzeit keinen reproduzierbaren Code für diese Ansprüche pro Zeitbeschränkung präsentieren. Daher empfehle ich dringend, diesen für Ihre eigenen Modelle zu testen.

Ich habe noch keine Git-Ausgabe zu diesen Themen geöffnet, aber ich habe das Original kommentiert - noch keine Antwort. Ich werde die Antwort (en) aktualisieren, sobald Fortschritte erzielt wurden.


VERDICT : Ist es nicht , wenn Sie wissen, was Sie tun. Wenn Sie dies nicht tun , könnte es Sie viel kosten - im Durchschnitt durch ein paar GPU-Upgrades und im schlimmsten Fall durch mehrere GPUs.


DIESE ANTWORT : Ziel ist es, eine allgemeine Beschreibung des Problems sowie Richtlinien für die Entscheidung über die für Ihre Anforderungen spezifische Schulungskonfiguration bereitzustellen. Eine detaillierte Beschreibung auf niedriger Ebene, die alle Benchmarking-Ergebnisse + den verwendeten Code enthält, finden Sie in meiner anderen Antwort.

Ich werde meine Antwort (en) mit weiteren Informationen aktualisieren, wenn ich etwas erfahre - kann diese Frage als Referenz mit einem Lesezeichen versehen / "markieren".


ZUSAMMENFASSUNG DER AUSGABE : Wie von einem TensorFlow-Entwickler, Q. Scott Zhu, bestätigt , konzentrierte sich TF2 auf die eifrige Ausführung und enge Integration mit Keras, was umfassende Änderungen der TF-Quelle beinhaltete - auch auf Grafikebene. Vorteile: Stark erweiterte Verarbeitungs-, Verteilungs-, Debug- und Bereitstellungsfunktionen. Die Kosten für einige davon sind jedoch Geschwindigkeit.

Die Angelegenheit ist jedoch ziemlich komplex. Es ist nicht nur TF1 vs. TF2 - Faktoren, die zu signifikanten Unterschieden in der Zuggeschwindigkeit führen, sind:

  1. TF2 gegen TF1
  2. Eifriger vs. Grafikmodus
  3. keras vs. tf.keras
  4. numpyvs. tf.data.Datasetvs. ...
  5. train_on_batch() vs. fit()
  6. GPU vs. CPU
  7. model(x)vs. model.predict(x)vs. ...

Leider ist fast keiner der oben genannten Punkte unabhängig vom anderen, und jeder kann die Ausführungszeit im Vergleich zum anderen mindestens verdoppeln. Glücklicherweise können Sie systematisch und mit ein paar Verknüpfungen bestimmen, was am besten funktioniert - wie ich zeigen werde.


WAS SOLLTE ICH TUN? Derzeit ist der einzige Weg - Experimentieren Sie für Ihr spezifisches Modell, Ihre Daten und Ihre Hardware. Keine einzelne Konfiguration funktioniert immer am besten - aber es gibt Vor- und Nachteile, um Ihre Suche zu vereinfachen:

>> DO:

  • train_on_batch()+ numpy+ tf.keras+ TF1 + Eifrig / Grafik
  • train_on_batch()+ numpy+ tf.keras+ TF2 + Graph
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + Grafik + großes Modell & Daten

>> NICHT:

  • fit()+ numpy+ kerasFür kleine und mittlere Modelle und Daten
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + Eifrig
  • train_on_batch()+ numpy+ keras+ TF1 + Eifrig

  • [Major] tf.python.keras ; es kann 10-100x langsamer laufen und mit vielen Fehlern; Mehr Info

    • Dazu gehört auch layers, models, optimizers, und in Verbindung stehenden "out-of-box" usage Einfuhren; Ops, Utils und verwandte "private" Importe sind in Ordnung - aber um sicherzugehen, prüfen Sie, ob Alts vorhanden sind und ob sie in verwendet werdentf.keras

Ein Beispiel für ein Benchmarking-Setup finden Sie im Code unten in meiner anderen Antwort. Die obige Liste basiert hauptsächlich auf den "BENCHMARKS" -Tabellen in der anderen Antwort.


EINSCHRÄNKUNGEN der oben genannten DO's & DON'T's:

  • Diese Frage trägt den Titel "Warum ist TF2 viel langsamer als TF1?", Und obwohl sein Körper das Training explizit betrifft, ist die Angelegenheit nicht darauf beschränkt. Auch die Inferenz unterliegt großen Geschwindigkeitsunterschieden, selbst innerhalb derselben TF-Version, des Imports, des Datenformats usw. - siehe diese Antwort .
  • RNNs ändern wahrscheinlich das Datenraster in der anderen Antwort erheblich, da sie in TF2 verbessert wurden
  • Hauptsächlich verwendete Modelle Conv1Dund Dense- keine RNNs, spärliche Daten / Ziele, 4 / 5D-Eingänge und andere Konfigurationen
  • Eingabedaten beschränkt auf numpyund tf.data.Dataset, während viele andere Formate existieren; siehe andere Antwort
  • GPU wurde verwendet; Ergebnisse werden auf einer CPU unterscheiden. Als ich die Frage stellte, war mein CUDA nicht richtig konfiguriert, und einige der Ergebnisse waren CPU-basiert.

Warum hat TF2 die praktischste Qualität, die Geschwindigkeit, für eine eifrige Ausführung geopfert? Es ist nicht klar - Grafik ist noch verfügbar. Aber wenn die Frage ist "warum überhaupt eifrig":

  • Überlegenes Debugging : Sie sind wahrscheinlich auf eine Vielzahl von Fragen gestoßen, die lauten : "Wie erhalte ich Zwischenschichtausgaben?" Oder "Wie überprüfe ich Gewichte?". mit eifrig ist es (fast) so einfach wie .__dict__. Im Gegensatz dazu erfordert Graph die Kenntnis spezieller Backend-Funktionen, was den gesamten Prozess des Debuggens und der Selbstbeobachtung erheblich verkompliziert.
  • Schnelleres Prototyping : nach ähnlichen Ideen wie oben; schnelleres Verständnis = mehr Zeit für die eigentliche DL.

WIE AKTIVIEREN / DEAKTIVIEREN SIE EAGER?

tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
tf.compat.v1.disable_eager_execution() # TF2; above holds

ZUSÄTZLICHE INFO :

  • Vorsicht mit _on_batch()Methoden in TF2; Laut TF-Entwickler verwenden sie immer noch eine langsamere Implementierung, aber nicht absichtlich - dh sie muss behoben werden. Siehe andere Antwort für Details.

ANFRAGEN AN TENSORFLOW DEVS :

  1. Bitte korrigieren train_on_batch()und den Leistungsaspekt des fit()iterativen Aufrufs ; Kundenspezifische Zugschleifen sind für viele wichtig, besonders für mich.
  2. Fügen Sie eine Dokumentation / Dokumentation hinzu, in der diese Leistungsunterschiede für das Wissen der Benutzer erwähnt werden.
  3. Verbessern Sie die allgemeine Ausführungsgeschwindigkeit, um zu verhindern, dass Peeps zu Pytorch springen.

Danksagung : Danke an


UPDATES :

  • 14.11.19 - Ich habe ein Modell (in meiner realen Anwendung) gefunden, das auf TF2 für alle * Konfigurationen mit Numpy-Eingabedaten langsamer läuft . Die Unterschiede lagen zwischen 13 und 19% und im Durchschnitt bei 17%. Die Unterschiede zwischen kerasund tf.keraswaren jedoch dramatischer: 18-40% , Durchschn. 32% (sowohl TF1 als auch 2). (* - außer Eager, für die TF2 OOM'd)

  • 17.11.19 - Entwickler haben die on_batch()Methoden in einem kürzlich durchgeführten Commit aktualisiert und angegeben, dass die Geschwindigkeit verbessert wurde. Sie werden in TF 2.1 veröffentlicht oder sind ab sofort als verfügbar tf-nightly. Da ich letzteres nicht zum Laufen bringen kann, wird das Bänken bis 2.1 verzögert.

  • 20.02.20 - Die Prognoseleistung ist ebenfalls einen Besuch wert. In TF2 können CPU-Vorhersagezeiten beispielsweise periodische Spitzen beinhalten
OverLordGoldDragon
quelle
3
Was ist mit fit_generator? ... Ich möchte so gut wie nie train_on_batchund die Verwaltung meiner eigenen Trainingsschleife über mehrere Chargen hinweg ist ein riesiges Anti-Muster, das selbst bei hohen Kosten vermieden werden muss.
ely
@ely Es muss noch getestet werden, wie in meiner anderen Antwort angegeben - aber wenn überhaupt, sage ich voraus, dass es sich um einen fitkleinen zusätzlichen Datenverarbeitungsaufwand handelt. Was Zugschleifen betrifft, habe ich meine eigene geschrieben, die sich letztendlich in eine Art API verwandelte. fit_generatorEs fehlt an Selbstbeobachtung, Anpassbarkeit und Speichern / Laden - also ein absolutes Nein für mich. Ich werde meine Trainingsschleife irgendwann auf Github veröffentlichen.
OverLordGoldDragon
Das Fehlen von Selbstbeobachtung und Anpassbarkeit ist für mich eine Funktion, kein Fehler. IDK worauf bezieht sich der Kommentar zum Speichern / Laden? Zwischenspeicherung / Laden während einer Schleife, die nicht vom Datengenerator gesteuert wird? (Ich persönlich bin auch glücklich, wenn ich mich nur auf Rückrufe verlasse, und würde eine weitere Anpassbarkeit als Codegeruch sehen, bei dem meine Trainingsschleife falsch ausgelegt ist.)
ely
@ely Es ist nicht einfach, aber für das Training mit komplexen Eingabedaten-Pipelines, Zielfunktionen und Nicht-API-Modellkonfigurationen (z. B. Ensembles) erforderlich. Introspektion ist ein Muss für viele Debugging- und Feature-Engineering-Zwecke. In Ermangelung eines externen Speichern / Laden und Zug Schleife pausability & resumability für rechen teure Modelle - ein Alptraum. Unabhängig davon, hängt letztendlich von Ihren spezifischen Bedürfnissen ab und davon, nicht zum Thema zu gehören. Der sicherste Weg, die Leistung mit fit_generatorIhrer Anwendung zu testen, besteht darin, sie zu testen.
OverLordGoldDragon
47

DIESE ANTWORT : Ziel ist es, eine detaillierte Beschreibung des Problems auf Grafik- / Hardware-Ebene bereitzustellen - einschließlich TF2- / TF1-Zugschleifen, Eingabedatenprozessoren und Eager- / Grafikmodus-Ausführungen. Eine Zusammenfassung der Probleme und Richtlinien zur Lösung von Problemen finden Sie in meiner anderen Antwort.


LEISTUNGSPRÜFUNG : Je nach Konfiguration ist manchmal eine schneller, manchmal die andere. Was TF2 und TF1 angeht, sind sie im Durchschnitt ungefähr gleich, aber es gibt signifikante konfigurationsbasierte Unterschiede, und TF1 übertrumpft TF2 häufiger als umgekehrt. Siehe "BENCHMARKING" weiter unten.


EAGER VS. GRAFIK : Das Fleisch dieser ganzen Antwort für einige: TF2 ist nach meinen Tests langsamer als TF1. Details weiter unten.

Der grundlegende Unterschied zwischen den beiden besteht darin, dass Graph proaktiv ein Computernetzwerk einrichtet und ausgeführt wird, wenn es dazu aufgefordert wird - während Eager alles bei der Erstellung ausführt. Aber die Geschichte beginnt erst hier:

  • Eifrig ist NICHT frei von Graph und kann entgegen der Erwartung tatsächlich meistens Graph sein. Was es größtenteils ist, ist ausgeführtes Diagramm - dies schließt Modell- und Optimierungsgewichte ein, die einen großen Teil des Diagramms ausmachen.

  • Eager erstellt bei der Ausführung einen Teil des eigenen Diagramms neu . direkte Folge davon, dass Graph nicht vollständig erstellt wurde - siehe Profilerergebnisse. Dies hat einen Rechenaufwand.

  • Eifrig ist langsamer mit Numpy-Eingaben ; pro diesem Git Kommentar & Code, in Eager Numpy Eingänge umfassen die Overhead - Kosten Tensoren von CPU zu GPU zu kopieren. Beim Durchlaufen des Quellcodes sind die Unterschiede bei der Datenverarbeitung klar. Eifrig passiert Numpy direkt, während Graph Tensoren passiert, die dann zu Numpy ausgewertet werden. unsicher über den genauen Prozess, aber letztere sollten Optimierungen auf GPU-Ebene beinhalten

  • TF2 Eager ist langsamer als TF1 Eager - das ist ... unerwartet. Siehe Benchmarking-Ergebnisse unten. Die Unterschiede reichen von vernachlässigbar bis signifikant, sind jedoch konsistent. Unsicher, warum dies der Fall ist - wenn ein TF-Entwickler dies klarstellt, wird die Antwort aktualisiert.


TF2 vs. TF1 : Zitieren relevanter Teile der Antwort eines TF-Entwicklers, Q. Scott Zhu - mit ein wenig meiner Betonung und Umformulierung:

In der Eifersucht muss die Laufzeit die Operationen ausführen und den numerischen Wert für jede Zeile Python-Code zurückgeben. Die Art der Einzelschrittausführung führt dazu, dass sie langsam ist .

In TF2 nutzt Keras die Funktion tf.function, um ein Diagramm für Training, Bewertung und Vorhersage zu erstellen. Wir nennen sie "Ausführungsfunktion" für das Modell. In TF1 war die "Ausführungsfunktion" ein FuncGraph, der eine gemeinsame Komponente als TF-Funktion gemeinsam hatte, jedoch eine andere Implementierung aufweist.

Während des Prozesses haben wir irgendwie eine falsche Implementierung für train_on_batch (), test_on_batch () und Predict_on_batch () hinterlassen . Sie sind immer noch numerisch korrekt , aber die Ausführungsfunktion für x_on_batch ist eine reine Python-Funktion und keine mit tf.function umschlossene Python-Funktion. Dies führt zu Langsamkeit

In TF2 konvertieren wir alle Eingabedaten in ein tf.data.Dataset, mit dem wir unsere Ausführungsfunktion vereinheitlichen können, um den einzelnen Typ der Eingaben zu verarbeiten. Die Dataset-Konvertierung kann einen gewissen Overhead verursachen , und ich denke, dies ist nur ein einmaliger Overhead und keine Kosten pro Stapel

Mit dem letzten Satz des letzten Absatzes oben und dem letzten Satz des folgenden Absatzes:

Um die Langsamkeit im eifrigen Modus zu überwinden, haben wir die Funktion @ tf., die eine Python-Funktion in einen Graphen verwandelt. Wenn Sie einen numerischen Wert wie ein np-Array eingeben, wird der Hauptteil der Funktion tf. in ein statisches Diagramm umgewandelt, optimiert und der endgültige Wert zurückgegeben, der schnell ist und eine ähnliche Leistung wie der TF1-Diagrammmodus haben sollte.

Ich bin anderer Meinung - gemäß meinen Profilerstellungsergebnissen, die zeigen, dass die Verarbeitung der Eingabedaten von Eager wesentlich langsamer ist als die von Graph. Auch unsicher, tf.data.Datasetinsbesondere, aber Eager ruft wiederholt mehrere der gleichen Datenkonvertierungsmethoden auf - siehe Profiler.

Zuletzt das verknüpfte Commit von dev: Signifikante Anzahl von Änderungen zur Unterstützung der Keras v2-Schleifen .


Zugschleifen : abhängig von (1) Eifrig gegen Grafik; (2) Eingangsdatenformat, in Ausbildung wird mit einer deutlichen Zuge Schleife ablaufen - in TF2 _select_training_loop(), training.py , von:

training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
              training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
            training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators 
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph

Jeder behandelt die Ressourcenzuweisung unterschiedlich und hat Konsequenzen für Leistung und Leistungsfähigkeit.


Zugschleifen: fitvs train_on_batch, kerasvstf.keras .: Jede der vier verwendet unterschiedliche Zugschleifen, wenn auch möglicherweise nicht in jeder möglichen Kombination. keras'verwendet fitzum Beispiel eine Form von fit_loopz. B. training_arrays.fit_loop()und train_on_batchkann verwendet werden K.function(). tf.kerashat eine komplexere Hierarchie, die teilweise im vorherigen Abschnitt beschrieben wurde.


Train Loops: Dokumentation - relevante Quelldokumentation zu einigen der verschiedenen Ausführungsmethoden:

Im Gegensatz zu anderen TensorFlow-Operationen konvertieren wir keine numerischen Python-Eingaben in Tensoren. Darüber hinaus wird für jeden einzelnen Python-Zahlenwert ein neues Diagramm erstellt

function Instanziiert ein separates Diagramm für jeden eindeutigen Satz von Eingabeformen und Datentypen .

Ein einzelnes tf.function-Objekt muss möglicherweise mehreren Berechnungsgraphen unter der Haube zugeordnet werden. Dies sollte nur als Leistung sichtbar sein (für die Verfolgung von Diagrammen fallen Rechen- und Speicherkosten ungleich Null an ).


Eingabedatenprozessoren : Ähnlich wie oben wird der Prozessor von Fall zu Fall ausgewählt, abhängig von den internen Flags, die gemäß den Laufzeitkonfigurationen (Ausführungsmodus, Datenformat, Verteilungsstrategie) gesetzt wurden. Der einfachste Fall ist mit Eager, das direkt mit Numpy-Arrays funktioniert. Einige spezifische Beispiele finden Sie in dieser Antwort .


MODELLGRÖSSE, DATENGRÖSSE:

  • Ist entscheidend; Keine einzige Konfiguration krönte sich auf allen Modell- und Datengrößen.
  • Die Datengröße im Verhältnis zur Modellgröße ist wichtig. Bei kleinen Daten und Modellen kann der Overhead der Datenübertragung (z. B. von CPU zu GPU) dominieren. Ebenso können kleine Overhead-Prozessoren bei großen Daten pro dominierender Datenkonvertierungszeit langsamer laufen (siehe convert_to_tensorunter "PROFILER").
  • Die Geschwindigkeit ist je nach Zugschleifen und Eingabedatenprozessoren unterschiedlich.

BENCHMARKS : das gemahlene Fleisch. - Word-Dokument - Excel-Tabelle


Terminologie :

  • % -lose Zahlen sind alle Sekunden
  • % berechnet als (1 - longer_time / shorter_time)*100; Begründung: Wir sind daran interessiert, welcher Faktor einer schneller ist als der andere. shorter / longerist eigentlich eine nichtlineare Beziehung, die für einen direkten Vergleich nicht nützlich ist
  • % Vorzeichenbestimmung:
    • TF2 vs TF1: +wenn TF2 schneller ist
    • GvE (Graph vs. Eager): +Wenn Graph schneller ist
  • TF2 = TensorFlow 2.0.0 + Keras 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5

PROFILER :


PROFILER - Erläuterung : Spyder 3.3.6 IDE-Profiler.

  • Einige Funktionen werden in Nestern anderer wiederholt; Daher ist es schwierig, die genaue Trennung zwischen den Funktionen "Datenverarbeitung" und "Training" zu ermitteln, sodass es zu Überschneidungen kommt - wie im allerletzten Ergebnis deutlich.

  • % Zahlen berechnet für Laufzeit minus Build-Zeit

  • Erstellungszeit berechnet durch Summieren aller (eindeutigen) Laufzeiten, die 1 oder 2 Mal aufgerufen wurden
  • Zugzeit berechnet durch Summieren aller (eindeutigen) Laufzeiten, die genauso oft wie die Anzahl der Iterationen aufgerufen wurden, und einiger Laufzeiten ihrer Nester
  • Funktionen werden leider anhand ihrer ursprünglichen Namen profiliert (dh _func = funcwerden als profiliert func), was sich in der Erstellungszeit mischt - daher muss sie ausgeschlossen werden

PRÜFUMGEBUNG :

  • Unten ausgeführter Code mit minimalen Hintergrundaufgaben
  • Die GPU wurde mit einigen Iterationen vor dem Timing der Iterationen "aufgewärmt", wie in diesem Beitrag vorgeschlagen
  • CUDA 10.0.130, cuDNN 7.6.0, TensorFlow 1.14.0 und TensorFlow 2.0.0 aus der Quelle sowie Anaconda
  • Python 3.7.4, Spyder 3.3.6 IDE
  • GTX 1070, Windows 10, 24 GB DDR4 2,4-MHz-RAM, i7-7700HQ 2,8-GHz-CPU

METHODIK :

  • Benchmark "kleine", "mittlere" und "große" Modell- und Datengrößen
  • Festlegen der Anzahl der Parameter für jede Modellgröße, unabhängig von der Größe der Eingabedaten
  • "Größeres" Modell hat mehr Parameter und Ebenen
  • "Größere" Daten haben eine längere Sequenz, aber die gleichen batch_sizeundnum_channels
  • Modelle nur verwenden Conv1D, Dense‚erlernbar‘ Schichten; RNNs werden pro TF-Version vermieden. Unterschiede
  • Es wurde immer eine Zuganpassung außerhalb der Benchmarking-Schleife ausgeführt, um die Erstellung von Modell- und Optimierungsgraphen zu vermeiden
  • Keine spärlichen Daten (z. B. layers.Embedding()) oder spärlichen Ziele (zSparseCategoricalCrossEntropy()

EINSCHRÄNKUNGEN : Eine "vollständige" Antwort würde jede mögliche Zugschleife und jeden möglichen Iterator erklären, aber das geht sicherlich über meine Zeitfähigkeit, meinen nicht vorhandenen Gehaltsscheck oder meine allgemeine Notwendigkeit hinaus. Die Ergebnisse sind nur so gut wie die Methodik - offen interpretieren.


CODE :

import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time

from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model 
#from keras.optimizers import Adam
#import keras.backend as K

#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()

def reset_seeds(reset_graph_with_backend=None, verbose=1):
    if reset_graph_with_backend is not None:
        K = reset_graph_with_backend
        K.clear_session()
        tf.compat.v1.reset_default_graph()
        if verbose:
            print("KERAS AND TENSORFLOW GRAPHS RESET")

    np.random.seed(1)
    random.seed(2)
    if tf.__version__[0] == '2':
        tf.random.set_seed(3)
    else:
        tf.set_random_seed(3)
    if verbose:
        print("RANDOM SEEDS RESET")

print("TF version: {}".format(tf.__version__))
reset_seeds()

def timeit(func, iterations, *args, _verbose=0, **kwargs):
    t0 = time()
    for _ in range(iterations):
        func(*args, **kwargs)
        print(end='.'*int(_verbose))
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_model_small(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 40, strides=4, padding='same')(ipt)
    x     = GlobalAveragePooling1D()(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_model_medium(batch_shape):
    ipt = Input(batch_shape=batch_shape)
    x = ipt
    for filters in [64, 128, 256, 256, 128, 64]:
        x  = Conv1D(filters, 20, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(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_model_large(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(64,  400, strides=4, padding='valid')(ipt)
    x     = Conv1D(128, 200, strides=1, padding='valid')(x)
    for _ in range(40):
        x = Conv1D(256,  12, strides=1, padding='same')(x)
    x     = Conv1D(512,  20, strides=2, padding='valid')(x)
    x     = Conv1D(1028, 10, strides=2, padding='valid')(x)
    x     = Conv1D(256,   1, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(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))

def make_data_tf(batch_shape, n_batches, iters):
    data = np.random.randn(n_batches, *batch_shape),
    trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
    return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)

batch_shape_small  = (32, 140,   30)
batch_shape_medium = (32, 1400,  30)
batch_shape_large  = (32, 14000, 30)

batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data",  "Medium data",  "Large data"]
model_names = ["Small model", "Medium model", "Large model"]

def test_all(fit=False, tf_dataset=False):
    for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
        for batch_shape, shape_name in zip(batch_shapes, shape_names):
            if (model_fn is make_model_large) and (batch_shape is batch_shape_small):
                continue
            reset_seeds(reset_graph_with_backend=K)
            if tf_dataset:
                data = make_data_tf(batch_shape, iters, iters)
            else:
                data = make_data(batch_shape)
            model = model_fn(batch_shape)

            if fit:
                if tf_dataset:
                    model.train_on_batch(data.take(1))
                    t0 = time()
                    model.fit(data, steps_per_epoch=iters)
                    print("Time/iter: %.4f sec" % ((time() - t0) / iters))
                else:
                    model.train_on_batch(*data)
                    timeit(model.fit, iters, *data, _verbose=1, verbose=0)
            else:
                model.train_on_batch(*data)
                timeit(model.train_on_batch, iters, *data, _verbose=1)
            cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
            del model

test_all(fit=True, tf_dataset=False)
OverLordGoldDragon
quelle
Ich bin mir nicht sicher, ob Ihr Code korrekt ist. Ich denke, dass Ihre Modelle immer im Grafikmodus ausgeführt werden, da Sie model.compileohne run_eagerly=TrueArgument aufrufen . Im eifrigen Modus können Sie einen Teil Ihres Codes im Grafikmodus mit ausführen tf.function. Daher denke ich, dass die Standardimplementierung darin compilebesteht, ein Rechendiagramm zu erstellen, anstatt es aus Leistungsgründen eifrig auszuführen. Beachten Sie auch, dass bei einem Faltungsmodell keine Beschleunigung im Diagrammmodus angezeigt wird, da die Python-Interaktion minimal ist. Wenn Sie viele Matheoperationen durchführen, kann dies einen großen Unterschied machen (auch bei der Speichernutzung).
user2781994
@OverLordGoldDragon, aber in TF 2 ist der eifrige Modus standardmäßig, model.compileohne jedoch run_eagerly=Trueden Grafikmodus sicherzustellen , oder nicht?
user2781994
@OverLordGoldDragon Ich stimme zu, dass nicht alle importierten Methoden im Grafikmodus ausgeführt werden, aber ich denke, dass entweder model.compileoder model.fitmuss sichergestellt werden, dass das Training intern im Grafikmodus ausgeführt wird.
user2781994
@OverLordGoldDragon TRUE - "tf.keras.Model.compile verwendet drei wichtige Argumente: ... Um sicherzustellen, dass das Modell eifrig trainiert und auswertet, können Sie außerdem sicherstellen, dass es run_eagerly=Trueals zu kompilierender Parameter übergeben wird." (Quelle tensorflow.org/guide/keras/overview ) Daher kann ich, wenn Sie das run_eagerly=TrueModell nicht bestehen, im Grafikmodus laufen. Ich bin mir nicht sicher, was der entscheidende Faktor ist, aber warum sollte es nicht im Grafikmodus ausgeführt werden, wenn es effizienter als eifrig ist.
user2781994
Willst du mehr Beweise? :) "Standardmäßig werden wir versuchen, Ihr Modell zu einem statischen Diagramm zu kompilieren, um die beste Ausführungsleistung zu erzielen." ( github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/… )
user2781994