Powołany przez wielu użytkowników jako powód przejścia na Pytorch, ale jeszcze nie znalazłem uzasadnienia / wyjaśnienia dla poświęcenia najważniejszej praktycznej jakości, szybkości, dla szybkiego wykonania.
Poniżej przedstawiono wyniki testów porównawczych kodu, TF1 vs. TF2 - z TF1 działającym gdziekolwiek od 47% do 276% szybciej .
Moje pytanie brzmi: co to jest, na poziomie graficznym lub sprzętowym, które powoduje tak znaczne spowolnienie?
Poszukuję szczegółowej odpowiedzi - już znam szerokie koncepcje. Odpowiedni Git
Specyfikacja : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070
Wyniki testu porównawczego :
AKTUALIZACJA : Wyłączenie funkcji Eager Execution według poniższego kodu nie pomaga. Zachowanie jest jednak niespójne: czasem praca w trybie graficznym znacznie pomaga, innym razem działa wolniej w stosunku do Chętnego.
Ponieważ deweloperzy TF nigdzie się nie pojawiają, sam będę badał tę sprawę - mogę śledzić postępy w powiązanej kwestii Github.
AKTUALIZACJA 2 : mnóstwo wyników eksperymentalnych do udostępnienia wraz z objaśnieniami; powinno być zrobione dzisiaj.
Kod testu porównawczego :
# 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)
Zastosowane funkcje :
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))
źródło
Odpowiedzi:
AKTUALIZACJA 2/18/2020 : Korzystałem z usług 2.1 i 2.1 na noc; wyniki są mieszane. Wszystkie konfiguracje oprócz jednego (model i rozmiar danych) są tak szybkie lub znacznie szybsze niż najlepsze z TF2 i TF1. Ten, który jest wolniejszy i znacznie wolniejszy, to Large-Large - szczególnie. w wykonaniu graficznym ( 1,6x do 2,5x wolniej ).
Co więcej, istnieją ekstremalne różnice w odtwarzalności między Graph a Eager dla dużego modelu, który przetestowałem - jednego, którego nie można wytłumaczyć losowością / równoległością obliczeniową. Nie mogę obecnie przedstawić odtwarzalnego kodu dla tych oświadczeń w odniesieniu do ograniczeń czasowych, dlatego zdecydowanie zalecam przetestowanie tego dla własnych modeli.
Nie otworzyłem jeszcze na nich problemu z Gitem, ale skomentowałem oryginał - jeszcze nie ma odpowiedzi. Zaktualizuję odpowiedź (odpowiedzi) po osiągnięciu postępu.
OCENA : nie jest , JEŚLI wiesz, co robisz. Ale jeśli tego nie zrobisz , może cię to kosztować wiele - średnio o kilka aktualizacji GPU i przez najgorszy przypadek wielu GPU.
TA ODPOWIEDŹ : ma na celu przedstawienie wysokiego poziomu opisu problemu, a także wskazówek, jak zdecydować o konfiguracji szkolenia specyficznej dla Twoich potrzeb. Szczegółowy opis niskiego poziomu, który obejmuje wszystkie wyniki testów porównawczych + użyty kod, zobacz moją drugą odpowiedź.
Będę aktualizować moje odpowiedzi w / więcej informacji, jeśli się czegoś nauczę - mogę dodać zakładkę / „gwiazdkę” do tego pytania w celach informacyjnych.
STRESZCZENIE : jak potwierdził twórca TensorFlow, Q. Scott Zhu, TF2 skupił się na szybkim wykonywaniu i ścisłej integracji z Keras, co wiązało się z dużymi zmianami w źródle TF - w tym na poziomie graficznym. Korzyści: znacznie rozszerzone możliwości przetwarzania, dystrybucji, debugowania i wdrażania. Koszt niektórych z nich to jednak szybkość.
Sprawa jest jednak bardziej złożona. Nie chodzi tylko o TF1 vs. TF2 - czynniki powodujące znaczne różnice w prędkości pociągów obejmują:
keras
vs.tf.keras
numpy
vs.tf.data.Dataset
vs. ...train_on_batch()
vs.fit()
model(x)
vs.model.predict(x)
vs. ...Niestety, prawie żadna z powyższych nie jest niezależna od siebie i każda z nich może przynajmniej podwoić czas wykonania w stosunku do drugiej. Na szczęście możesz ustalić, co będzie działało najlepiej systematycznie i za pomocą kilku skrótów - jak pokażę.
CO POWINIENEM ZROBIĆ? Obecnie jedynym sposobem jest - eksperymentowanie z określonym modelem, danymi i sprzętem. Żadna pojedyncza konfiguracja nie zawsze będzie działać najlepiej - są jednak polecenia i nie upraszczają wyszukiwania:
>> ZRÓB:
train_on_batch()
+numpy
+tf.keras
+ TF1 + Eager / Graphtrain_on_batch()
+numpy
+tf.keras
+ TF2 + Wykresfit()
+numpy
+tf.keras
+ TF1 / TF2 + Wykres + duży model i dane>> NIE:
fit()
+numpy
+keras
dla małych i średnich modeli i danychfit()
+numpy
+tf.keras
+ TF1 / TF2 + chętnytrain_on_batch()
+numpy
+keras
+ TF1 + chętny[Major]
tf.python.keras
; może działać 10-100x wolniej, z dużą ilością błędów; więcej informacjilayers
,models
,optimizers
, i związane z "out-of-box" importu użytkowania; operacje, narzędzia i powiązane „prywatne” importy są w porządku - ale dla pewności sprawdź, czy są alty i czy są używane wtf.keras
Odwołaj się do kodu na dole mojej innej odpowiedzi, aby zobaczyć przykład konfiguracji testu porównawczego. Powyższa lista opiera się głównie na tabelach „BENCHMARKS” w drugiej odpowiedzi.
OGRANICZENIA powyższych DO & DON'T:
Conv1D
iDense
- brak RNN, rzadkie dane / cele, wejścia 4 / 5D i inne konfiguracjenumpy
itf.data.Dataset
, podczas gdy istnieje wiele innych formatów; zobacz inną odpowiedźDlaczego TF2 poświęcił najbardziej praktyczną jakość, szybkość, dla chętnego wykonania? Oczywiście nie ma - wykres jest nadal dostępny. Ale jeśli pytanie brzmi „dlaczego w ogóle chętny”:
.__dict__
. W przeciwieństwie do tego wykres wymaga znajomości specjalnych funkcji zaplecza - co znacznie komplikuje cały proces debugowania i introspekcji.JAK WŁĄCZYĆ / WYŁĄCZYĆ EAGERA?
INFORMACJE DODATKOWE :
_on_batch()
metodami w TF2; według twórcy TF nadal używają wolniejszej implementacji, ale nie celowo - tj. należy to naprawić. Zobacz inne odpowiedzi, aby uzyskać szczegółowe informacje.WNIOSKI DO URZĄDZEŃ TENSORFLOW :
train_on_batch()
, a aspekt wydajnościfit()
iteracyjnego wywoływania ; niestandardowe pętle pociągów są ważne dla wielu, szczególnie dla mnie.PODZIĘKOWANIA : Dzięki
AKTUALIZACJE :
14.11.19 - znalazłem model (w mojej prawdziwej aplikacji), który działa wolniej na TF2 dla wszystkich * konfiguracji z danymi wejściowymi Numpy. Różnice wynosiły 13–19%, średnio 17%. Różnice
keras
itf.keras
były jednak bardziej dramatyczne: 18-40% , śr. 32% (zarówno TF1, jak i 2). (* - z wyjątkiem chętnych, dla których TF2 OOM'd)17.11.19 - deweloperzy zaktualizowali
on_batch()
metody w ostatnim zatwierdzeniu , stwierdzając, że poprawili prędkość - zostaną wydane w TF 2.1 lub będą dostępne teraz jakotf-nightly
. Ponieważ nie jestem w stanie uruchomić tego ostatniego, opóźni testowanie do 2.1.źródło
fit_generator
? ... Praktycznie nigdy nie chcę,train_on_batch
a zarządzanie własną pętlą treningową pomiędzy partiami jest ogromnym, ogromnym anty-wzorem, którego należy unikać nawet przy ogromnych kosztach.fit
w / małe dodatkowe obciążenie przetwarzania danych. Jeśli chodzi o pętle kolejowe, napisałem własną, niestandardową, która ostatecznie przekształciła się w rodzaj API;fit_generator
brakuje introspekcji, możliwości dostosowania i zapisywania / ładowania - więc dla mnie absolutnie nie. W końcu opublikuję moją pętlę treningową na Github.fit_generator
aplikacji jest przetestowanie jej.TA ODPOWIEDŹ : ma na celu przedstawienie szczegółowego opisu problemu na poziomie wykresu / sprzętu - w tym pętli pociągów TF2 vs. TF1, procesorów danych wejściowych oraz wykonywania w trybie Eager vs. Graph. Aby uzyskać podsumowanie problemu i wytyczne dotyczące rozwiązywania problemów, zobacz moją drugą odpowiedź.
OCENA WYDAJNOŚCI : czasami jedno jest szybsze, a czasem inne, w zależności od konfiguracji. Jeśli chodzi o TF2 vs TF1, są one na średnim poziomie, ale istnieją znaczące różnice w konfiguracji, a TF1 przebija TF2 częściej niż na odwrót. Zobacz „BENCHMARKING” poniżej.
EAGER VS. WYKRES : sedno całej tej odpowiedzi dla niektórych: według moich testów chętny TF2 jest wolniejszy niż TF1. Szczegóły poniżej.
Podstawowa różnica między nimi polega na tym, że: Graph tworzy sieć obliczeniową proaktywnie i wykonuje się, gdy „polecono” - podczas gdy Eager wykonuje wszystko po stworzeniu. Ale historia zaczyna się dopiero tutaj:
Chętny NIE jest pozbawiony Grafu i może w rzeczywistości być w większości Grafem, wbrew oczekiwaniom. W dużej mierze jest wykonywany Wykres - obejmuje to wagi modelu i optymalizatora, które stanowią dużą część wykresu.
Chętnie odbudowuje część własnego wykresu podczas wykonywania ; bezpośrednia konsekwencja nieukończenia programu Graph - patrz wyniki profilera. Ma to narzut obliczeniowy.
Chętny jest wolniejszy z wejściami Numpy ; zgodnie z tym komentarzem i kodem Gita dane wejściowe Numpy w programie Eager obejmują koszty ogólne kopiowania tensorów z procesora na procesor graficzny. Przechodząc przez kod źródłowy, różnice w przetwarzaniu danych są wyraźne; Chętnie przekazuje Numpy bezpośrednio, a Graph przekazuje tensory, które następnie oceniają na Numpy; niepewny dokładnego procesu, ale ten ostatni powinien obejmować optymalizacje na poziomie GPU
TF2 Eager jest wolniejszy niż TF1 Eager - to ... niespodziewane. Zobacz wyniki testów porównawczych poniżej. Różnice rozciągają się od nieznacznych do znaczących, ale są spójne. Nie jestem pewien, dlaczego tak jest - jeśli programista TF wyjaśni, zaktualizuje odpowiedź.
TF2 vs. TF1 : cytując odpowiednie części twórcy TF, Q. Scott Zhu, odpowiedź - w / trochę mojego nacisku i przeredagowania:
Z ostatnim zdaniem ostatniego akapitu powyżej i ostatnią klauzulą poniższego akapitu:
Nie zgadzam się - na podstawie moich wyników profilowania, które pokazują, że przetwarzanie danych wejściowych Eagera jest znacznie wolniejsze niż wykresu. Nie jestem pewien
tf.data.Dataset
w szczególności, ale Eager wielokrotnie wywołuje wiele takich samych metod konwersji danych - patrz profiler.Na koniec zatwierdzone przez dev zatwierdzenie: znaczna liczba zmian w celu obsługi pętli Keras v2 .
Pętle pociągowe : w zależności od (1) chętnych vs. wykresów; (2) w formacie danych wejściowych trenuje dokona odrębnego obiegu kolejowego - w TF2,
_select_training_loop()
, training.py , jeden spośród:Każda z nich traktuje alokację zasobów inaczej i ma wpływ na wydajność i możliwości.
Pętle pociągowe:
fit
vstrain_on_batch
,keras
vstf.keras
.: każda z czterech używa różnych pętli pociągowych, choć być może nie w każdej możliwej kombinacji.keras
„fit
na przykład używa formyfit_loop
np.training_arrays.fit_loop()
itrain_on_batch
może z niej korzystaćK.function()
.tf.keras
ma bardziej wyrafinowaną hierarchię opisaną częściowo w poprzedniej sekcji.Pętle kolejowe: dokumentacja - odpowiednie dokumentowanie źródłowe dotyczące niektórych różnych metod wykonywania:
Procesory danych wejściowych : podobnie jak powyżej, procesor jest wybierany indywidualnie, zależnie od wewnętrznych flag ustawionych zgodnie z konfiguracjami środowiska wykonawczego (tryb wykonywania, format danych, strategia dystrybucji). Najprostszy przypadek to Eager, który działa bezpośrednio z tablicami Numpy. Aby zapoznać się z niektórymi konkretnymi przykładami, zobacz tę odpowiedź .
ROZMIAR MODELU, ROZMIAR DANYCH:
convert_to_tensor
„PROFILER”)BENCHMARKS : zmielone mięso. - Dokument programu Word - Arkusz kalkulacyjny Excel
Terminologia :
(1 - longer_time / shorter_time)*100
; uzasadnienie: interesuje nas , który czynnik jest szybszy od drugiego;shorter / longer
jest w rzeczywistości relacją nieliniową, nieprzydatną do bezpośredniego porównania+
jeśli TF2 jest szybszy+
jeśli Graph jest szybszyPROFILER :
PROFILER - Objaśnienie : Profiler IDE Spyder 3.3.6.
Niektóre funkcje są powtarzane w gniazdach innych; dlatego trudno jest wyśledzić dokładny rozdział między funkcjami „przetwarzania danych” a „szkoleniem”, więc pewne nakładanie się będzie na siebie widoczne - jak zaznaczono w ostatnim wyniku.
% liczby obliczone w czasie wykonywania minus czas kompilacji
_func = func
Będą się profilować jakofunc
), co miesza się w czasie kompilacji - stąd konieczność wykluczeniaTESTOWANIE ŚRODOWISKA :
METODOLOGIA :
batch_size
inum_channels
Conv1D
,Dense
„nauczenia” warstw; Unikano numerów RNN na implem w wersji TF. różnicelayers.Embedding()
) Lub rzadkich celów (npSparseCategoricalCrossEntropy()
OGRANICZENIA : „kompletna” odpowiedź wyjaśniałaby każdą możliwą pętlę pociągu i iterator, ale z pewnością jest to poza moją zdolnością czasową, nieistniejącą wypłatą lub ogólną koniecznością. Wyniki są tak dobre, jak metodologia - interpretuj z otwartym umysłem.
KOD :
źródło
model.compile
bezrun_eagerly=True
argumentów. W trybie chętnym możesz uruchomić część kodu w trybie graficznym za pomocątf.function
. Dlatego myślę, że domyślną implementacjącompile
jest tworzenie grafu obliczeniowego zamiast uruchamiania go chętnie ze względu na wydajność. Zauważ też, że jeśli twój model jest splotowy, to nie widzisz przyspieszenia w trybie graficznym, ponieważ interakcja Pythona jest minimalna. Jeśli wykonasz wiele operacji matematycznych, może to mieć duże znaczenie (również w zakresie wykorzystania pamięci).model.compile
bezrun_eagerly=True
zapewnia tryb wykresu, czy nie?model.compile
albomodel.fit
zapewnić, że szkolenie działa wewnętrznie w trybie graficznym.run_eagerly=True
jako parametr do kompilacji.” (źródło tensorflow.org/guide/keras/overview ) Dlatego jeśli nie przejdzieszrun_eagerly=True
modelu, MOŻNA uruchomić w trybie graficznym. Nie jestem pewien, co jest decydującym czynnikiem, ale dlaczego nie miałby działać w trybie graficznym, jeśli jest bardziej wydajny niż chętny.