Ele foi citado por muitos usuários como o motivo da mudança para o Pytorch, mas ainda não encontrei uma justificativa / explicação para sacrificar a qualidade prática mais importante, a velocidade, para uma execução mais ágil.
Abaixo está o desempenho do benchmarking de código, TF1 vs. TF2 - com o TF1 executando de 47% a 276% mais rápido .
Minha pergunta é: o que é, no nível do gráfico ou do hardware, que gera uma desaceleração tão significativa?
Procurando uma resposta detalhada - já estou familiarizado com conceitos amplos. Git Relevante
Especificações : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070
Resultados de referência :
ATUALIZAÇÃO : Desabilitar a Execução Ansiosa pelo código abaixo não ajuda. O comportamento, no entanto, é inconsistente: algumas vezes, a execução no modo gráfico ajuda consideravelmente, outras vezes, mais devagar em relação ao Eager.
Como os desenvolvedores do TF não aparecem em nenhum lugar, eu mesmo vou investigar esse assunto - posso acompanhar o progresso na questão do Github vinculada.
ATUALIZAÇÃO 2 : toneladas de resultados experimentais para compartilhar, juntamente com explicações; deve ser feito hoje.
Código de referência :
# 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)
Funções utilizadas :
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))
fonte
Respostas:
ATUALIZAÇÃO 18/2/2020 : Eu já paguei 2,1 e 2,1 todas as noites; os resultados são mistos. Todas as configurações, exceto uma (modelo e tamanho dos dados), são tão rápidas quanto ou muito mais rápidas que as melhores do TF2 e TF1. O que é mais lento e mais dramaticamente, é Grande-Grande - esp. na execução do gráfico ( 1,6x a 2,5x mais lento ).
Além disso, existem extrema diferenças reprodutibilidade entre o Graph e o Eager para um modelo grande que eu testei - um que não pode ser explicado por meio de aleatoriedade / paralelismo da computação. No momento, não posso apresentar código reproduzível para essas reivindicações por restrições de tempo; portanto, recomendo fortemente testá-lo para seus próprios modelos.
Ainda não abri um problema do Git sobre eles, mas comentei o original - ainda não há resposta. Atualizarei a (s) resposta (s) assim que o progresso for feito.
VERDITO : não é , se você sabe o que está fazendo. Mas se você fizer , isso poderá custar muito - com algumas atualizações de GPU, em média, e com o pior caso de várias GPUs.
ESTA RESPOSTA : tem como objetivo fornecer uma descrição de alto nível do problema, bem como diretrizes sobre como decidir sobre a configuração de treinamento específica para suas necessidades. Para uma descrição detalhada e de baixo nível, que inclui todos os resultados de benchmarking + código usado, consulte minha outra resposta.
Estarei atualizando minhas respostas com mais informações, se descobrir alguma - pode marcar / "marcar com estrela" esta pergunta para referência.
RESUMO DA QUESTÃO : as DO confirmado por um desenvolvedor do TensorFlow, Q. Scott Zhu, o TF2 focou o desenvolvimento na execução ansiosa e forte integração com o Keras, que envolveu mudanças radicais na fonte do TF - inclusive no nível do gráfico. Benefícios: recursos de processamento, distribuição, depuração e implantação amplamente expandidos. O custo de alguns deles, no entanto, é a velocidade.
O assunto, no entanto, é bastante mais complexo. Não se trata apenas de TF1 vs. TF2 - os fatores que geram diferenças significativas na velocidade do trem incluem:
keras
vs.tf.keras
numpy
vs.tf.data.Dataset
vs.train_on_batch()
vs.fit()
model(x)
vs.model.predict(x)
vs.Infelizmente, quase nenhuma das opções acima é independente da outra, e cada uma pode pelo menos dobrar o tempo de execução em relação à outra. Felizmente, você pode determinar o que funcionará melhor sistematicamente e com alguns atalhos - como mostrarei.
O QUE DEVO FAZER? Atualmente, a única maneira é experimentar o seu modelo, dados e hardware específicos. Nenhuma configuração única será sempre funciona melhor - mas não são fizeram e não é para simplificar a sua pesquisa:
>> FAÇA:
train_on_batch()
+numpy
+tf.keras
+ TF1 + Ansioso / Gráficotrain_on_batch()
+numpy
+tf.keras
+ TF2 + Gráficofit()
+numpy
+tf.keras
+ TF1 / TF2 + Gráfico + modelo grande e dados>> NÃO:
fit()
+numpy
+keras
para modelos e dados pequenos e médiosfit()
+numpy
+tf.keras
+ TF1 / TF2 + Ansiosotrain_on_batch()
+numpy
+keras
+ TF1 + Ansioso[Major]
tf.python.keras
; ele pode ser executado 10-100x mais lento e com muitos bugs; mais informaçõeslayers
,models
,optimizers
, e relacionado "out-of-box" importações de uso; ops, utils e importações 'privadas' relacionadas são boas - mas para ter certeza, verifique se há alterações e se elas são usadas emtf.keras
Consulte o código na parte inferior da minha outra resposta para um exemplo de configuração de benchmarking. A lista acima é baseada principalmente nas tabelas "BENCHMARKS" na outra resposta.
LIMITAÇÕES dos DOs e NÃOs acima:
Conv1D
eDense
- sem RNNs, dados / destinos esparsos, entradas 4 / 5D e outras configuraçõesnumpy
etf.data.Dataset
, embora existam muitos outros formatos; veja outra respostaPor que o TF2 sacrificou a qualidade mais prática, a velocidade, para uma execução mais ágil? Claramente não está - o gráfico ainda está disponível. Mas se a pergunta é "por que ficar ansiosa":
.__dict__
. O gráfico, ao contrário, requer familiaridade com funções especiais de back-end - complicando bastante todo o processo de depuração e introspecção.COMO ATIVAR / DESATIVAR O EAGER?
INFORMAÇÕES ADICIONAIS :
_on_batch()
métodos no TF2; de acordo com o desenvolvedor do TF, eles ainda usam uma implementação mais lenta, mas não intencionalmente - isto é, deve ser corrigido. Veja outra resposta para detalhes.PEDIDOS PARA DEVS DE FLUXO DE TENSOR :
train_on_batch()
e o aspecto de desempenho da chamadafit()
iterativa; loops de trem personalizados são importantes para muitos, especialmente para mim.AGRADECIMENTOS : Graças a
ATUALIZAÇÕES :
14/11/19 - encontrei um modelo (no meu aplicativo real) que é executado mais lentamente no TF2 para todas as configurações * com dados de entrada Numpy. As diferenças variaram de 13 a 19%, com média de 17%. As diferenças entre
keras
etf.keras
, no entanto, foram mais dramáticas: 18-40% , média. 32% (ambos TF1 e 2). (* - exceto Eager, para o qual TF2 OOM'd)17/11/19 - devs atualizou
on_batch()
métodos em uma consolidação recente , afirmando ter uma velocidade aprimorada - a ser lançado no TF 2.1 ou disponível agora comotf-nightly
. Como não consigo rodar mais tarde, o atraso será atrasado até o 2.1.fonte
fit_generator
? ... Eu praticamente nunca querotrain_on_batch
e gerenciar meu próprio ciclo de treinamento em lotes é um enorme antipadrão a ser evitado, mesmo a um grande custo.fit
pequena sobrecarga adicional de processamento de dados. Quanto aos loops de trem, escrevi meu próprio personalizado que acabou se transformando em uma espécie de API;fit_generator
carece de introspecção, capacidade de personalização e economia / carregamento - portanto, não é absolutamente para mim. Eventualmente, publicarei meu ciclo de treinamento no Github.fit_generator
sua aplicação é testá-lo.ESTA RESPOSTA : tem como objetivo fornecer uma descrição detalhada do problema em nível de gráfico / hardware - incluindo loops de trem TF2 vs. TF1, processadores de dados de entrada e execuções no modo Ansioso vs. Gráfico. Para um resumo do problema e diretrizes de resolução, consulte minha outra resposta.
VERDITO DE DESEMPENHO : às vezes um é mais rápido, às vezes o outro, dependendo da configuração. No que diz respeito ao TF2 vs TF1, eles estão em pé de igualdade, em média, mas existem diferenças significativas baseadas na configuração, e o TF1 supera o TF2 com mais frequência do que vice-versa. Veja "BENCHMARKING" abaixo.
EAGER VS. GRÁFICO : a carne de toda essa resposta para alguns: o desejo do TF2 é mais lento que o do TF1, de acordo com meus testes. Detalhes mais abaixo.
A diferença fundamental entre os dois é: o Graph configura uma rede computacional de forma proativa e é executado quando 'solicitado' - enquanto o Eager executa tudo na criação. Mas a história só começa aqui:
Ansioso NÃO é desprovido de Graph , e pode de fato ser principalmente Graph, contrário à expectativa. O que é em grande parte, é executado Graph - isso inclui pesos de modelo e otimizador, compreendendo grande parte do gráfico.
Ansioso reconstrói parte do próprio gráfico na execução ; conseqüência direta do Graph não estar totalmente construído - veja os resultados do criador de perfil. Isso tem uma sobrecarga computacional.
Ansioso é mais lento com entradas Numpy ; de acordo com esse comentário e código do Git , as entradas do Numpy no Eager incluem o custo adicional de copiar tensores da CPU para a GPU. Percorrendo o código fonte, as diferenças de manipulação de dados são claras; Ansioso passa diretamente o Numpy, enquanto o Graph passa os tensores que são avaliados para o Numpy; incerto do processo exato, mas este último deve envolver otimizações no nível da GPU
TF2 Eager é mais lento que TF1 Eager - isso é ... inesperado. Veja os resultados do benchmarking abaixo. As diferenças variam de insignificante a significante, mas são consistentes. Não sei por que é esse o caso - se um desenvolvedor do TF esclarecer, atualizará a resposta.
TF2 vs. TF1 : citando partes relevantes da resposta de um desenvolvedor de TF, Q. Scott Zhu, com um pouco da minha ênfase e reformulação:
Com a última frase do último parágrafo acima e a última cláusula do parágrafo abaixo:
Discordo - de acordo com meus resultados de criação de perfil, que mostram que o processamento de dados de entrada do Eager é substancialmente mais lento que o do Graph. Além disso, não tem certeza sobre isso
tf.data.Dataset
em particular, mas o Eager chama repetidamente vários dos mesmos métodos de conversão de dados - consulte o profiler.Por fim, o commit vinculado do desenvolvedor: número significativo de alterações para dar suporte aos loops do Keras v2 .
Loops de trem : dependendo de (1) Ansioso vs. Gráfico; (2) formato de dados de entrada, a formação em prosseguirá com um laço trem distintas - em TF2,
_select_training_loop()
, training.py , um dos seguintes:Cada um deles lida com a alocação de recursos de maneira diferente e tem conseqüências no desempenho e na capacidade.
Loops de trem:
fit
vstrain_on_batch
,keras
vstf.keras
.: cada um dos quatro usa loops de trem diferentes, embora talvez não em todas as combinações possíveis.keras
'fit
, por exemplo, usa uma forma defit_loop
, por exemplotraining_arrays.fit_loop()
, etrain_on_batch
pode ser usadaK.function()
.tf.keras
possui uma hierarquia mais sofisticada descrita em parte na seção anterior.Loops de treinamento: documentação - documentação relevante da fonte sobre alguns dos diferentes métodos de execução:
Processadores de dados de entrada : semelhante ao acima, o processador é selecionado caso a caso, dependendo dos sinalizadores internos definidos de acordo com as configurações de tempo de execução (modo de execução, formato de dados, estratégia de distribuição). O caso mais simples é o Eager, que funciona diretamente com matrizes Numpy. Para alguns exemplos específicos, consulte esta resposta .
TAMANHO DO MODELO, TAMANHO DOS DADOS:
convert_to_tensor
"PROFILER")REFERÊNCIAS : a carne moída. - Documento do Word - Planilha do Excel
Terminologia :
(1 - longer_time / shorter_time)*100
; lógica: estamos interessados em qual fator um é mais rápido que o outro;shorter / longer
é na verdade uma relação não linear, não é útil para comparação direta+
se o TF2 for mais rápido+
se o Gráfico for mais rápidoPROFILER :
PROFILER - Explicação : Spyder 3.3.6 IDE profiler.
Algumas funções são repetidas em ninhos de outras; portanto, é difícil rastrear a separação exata entre as funções "processamento de dados" e "treinamento", para que haja alguma sobreposição - conforme pronunciado no último resultado.
% de números calculados em tempo de execução wrt menos tempo de compilação
_func = func
terão o perfil comofunc
), o que se mistura no tempo de construção - daí a necessidade de excluí-loAMBIENTE DE TESTE :
METODOLOGIA :
batch_size
enum_channels
Conv1D
,Dense
camadas 'learnable'; RNNs evitados por implemento da versão TF. diferençaslayers.Embedding()
) ou destinos esparsos (por exemploSparseCategoricalCrossEntropy()
LIMITAÇÕES : uma resposta "completa" explicaria todos os circuitos e iteradores de trem possíveis, mas isso certamente está além da minha capacidade de tempo, salário inexistente ou necessidade geral. Os resultados são tão bons quanto a metodologia - interpretem com a mente aberta.
CÓDIGO :
fonte
model.compile
semrun_eagerly=True
argumento. Se estiver no modo rápido, você pode executar parte do seu código no modo gráfico usandotf.function
. Portanto, acho que a implementação padrão decompile
é criar gráfico computacional em vez de executá-lo ansiosamente por razões de desempenho. Observe também que, se seu modelo for convolucional, você não verá a aceleração no modo gráfico, pois a interação do python é mínima. Se você faz muitas operações matemáticas, isso pode fazer uma grande diferença (também na utilização da memória).model.compile
semrun_eagerly=True
garante o modo gráfico, ou não?model.compile
oumodel.fit
deve garantir que o treinamento seja executado internamente no modo gráfico.run_eagerly=True
como um parâmetro para compilar." (fonte tensorflow.org/guide/keras/overview ) Portanto, se você não passa orun_eagerly=True
modelo, PODE executar no modo gráfico. Não tenho certeza qual é o fator decisivo, mas por que ele não funcionaria no modo gráfico se é mais eficiente do que ansioso?