Muchos usuarios lo han citado como la razón para cambiar a Pytorch, pero aún no he encontrado una justificación / explicación para sacrificar la calidad práctica más importante, la velocidad, para una ejecución ansiosa.
A continuación se muestra el rendimiento de la evaluación comparativa de código, TF1 frente a TF2, con TF1 ejecutándose en cualquier lugar del 47% al 276% más rápido .
Mi pregunta es: ¿qué es, a nivel gráfico o de hardware, lo que produce una desaceleración tan significativa?
Buscando una respuesta detallada, ya estoy familiarizado con los conceptos generales. Git relevante
Especificaciones : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070
Resultados de referencia :
ACTUALIZACIÓN : Deshabilitar la Ejecución ansiosa por el siguiente código no ayuda. Sin embargo, el comportamiento es inconsistente: a veces, ejecutar en modo gráfico ayuda considerablemente, otras veces se ejecuta más lentamente en relación con Eager.
Como los desarrolladores de TF no aparecen en ningún lado, investigaré este asunto yo mismo, puedo seguir el progreso en el problema de Github vinculado.
ACTUALIZACIÓN 2 : toneladas de resultados experimentales para compartir, junto con explicaciones; debe hacerse hoy.
Código de referencia :
# 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)
Funciones 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))
fuente
Respuestas:
ACTUALIZACIÓN 18/02/2020 : he puesto en la banca 2.1 y 2.1 por noche; Los resultados son mixtos. Todas las configuraciones menos una (modelo y tamaño de datos) son tan rápidas o mucho más rápidas que las mejores TF2 y TF1. El que es más lento, y más lento dramáticamente, es Large-Large - esp. en la ejecución del gráfico ( 1.6x a 2.5x más lento ).
Además, existen diferencias extremas de reproducibilidad entre Graph y Eager para un modelo grande que probé, uno no explicable a través de aleatoriedad / paralelismo de cómputo. Actualmente no puedo presentar código reproducible para estas notificaciones por restricciones de tiempo, por lo que recomiendo probar esto para sus propios modelos.
Todavía no he abierto un problema de Git sobre estos, pero hice un comentario sobre el original , aún no hay respuesta. Actualizaré las respuestas una vez que se haya avanzado.
VEREDICTO : no lo es , SI sabes lo que estás haciendo. Pero si no lo hace , podría costarle mucho, por un par de actualizaciones de GPU en promedio, y por múltiples GPU en el peor de los casos.
ESTA RESPUESTA : tiene como objetivo proporcionar una descripción de alto nivel del problema, así como pautas sobre cómo decidir sobre la configuración de capacitación específica para sus necesidades. Para obtener una descripción detallada de bajo nivel, que incluye todos los resultados de evaluación comparativa + código utilizado, consulte mi otra respuesta.
Actualizaré mi (s) respuesta (s) con más información si descubro que puede marcar / "marcar" esta pregunta como referencia.
RESUMEN DEL PROBLEMA : según lo confirmado por un desarrollador de TensorFlow, Q. Scott Zhu, TF2 centró el desarrollo en la ejecución Eager y la estrecha integración con Keras, lo que implicó cambios radicales en la fuente de TF, incluso a nivel de gráfico. Beneficios: capacidades ampliadas de procesamiento, distribución, depuración e implementación. Sin embargo, el costo de algunos de estos es la velocidad.
El asunto, sin embargo, es bastante más complejo. No se trata solo de TF1 frente a TF2: los factores que producen diferencias significativas en la velocidad del tren incluyen:
keras
vs.tf.keras
numpy
vs.tf.data.Dataset
vs.train_on_batch()
vs.fit()
model(x)
vs.model.predict(x)
vs.Desafortunadamente, casi ninguno de los anteriores es independiente del otro, y cada uno puede al menos duplicar el tiempo de ejecución en relación con el otro. Afortunadamente, puede determinar qué funcionará mejor de manera sistemática y con algunos atajos, como mostraré.
¿QUÉ TENGO QUE HACER? Actualmente, la única forma es: experimente con su modelo, datos y hardware específicos. Sin configuración única siempre funcionará mejor - pero no se qué hacer y qué no lo es para simplificar su búsqueda:
>> HACER:
train_on_batch()
+numpy
+tf.keras
+ TF1 + Eager / Graphtrain_on_batch()
+numpy
+tf.keras
+ TF2 + Gráficofit()
+numpy
+tf.keras
+ TF1 / TF2 + Gráfico + modelo grande y datos>> NO:
fit()
+numpy
+keras
para modelos y datos pequeños y medianosfit()
+numpy
+tf.keras
+ TF1 / TF2 + Ansiosotrain_on_batch()
+numpy
+keras
+ TF1 + Ansioso[Mayor]
tf.python.keras
; puede correr 10-100x más lento y con muchos errores; más informaciónlayers
,models
,optimizers
, y relaciona "fuera de la caja" importaciones de uso; las operaciones, las utilidades y las importaciones 'privadas' relacionadas están bien, pero para estar seguro, verifique si hay alternativas y si se usan entf.keras
Consulte el código en la parte inferior de mi otra respuesta para un ejemplo de configuración de evaluación comparativa. La lista anterior se basa principalmente en las tablas "BENCHMARKS" en la otra respuesta.
LIMITACIONES de lo que HACER y NO HACER:
Conv1D
yDense
sin RNN, datos / objetivos dispersos, entradas 4 / 5D y otras configuracionesnumpy
ytf.data.Dataset
, aunque existen muchos otros formatos; ver otra respuesta¿Por qué TF2 sacrificó la calidad más práctica, la velocidad, para una ejecución ansiosa? Claramente no lo ha hecho: el gráfico todavía está disponible. Pero si la pregunta es "¿por qué ansioso?":
.__dict__
. Graph, por el contrario, requiere familiaridad con funciones especiales de back-end, lo que complica enormemente todo el proceso de depuración e introspección.¿CÓMO HABILITAR / DESACTIVAR EAGER?
INFORMACIÓN ADICIONAL :
_on_batch()
métodos en TF2; de acuerdo con el desarrollador de TF, todavía usan una implementación más lenta, pero no intencionalmente , es decir, se debe solucionar. Ver otra respuesta para más detalles.SOLICITUDES DE DISPOSITIVOS DE FLUJO TENSOR :
train_on_batch()
, corrija , y el aspecto de rendimiento de llamar de formafit()
iterativa; los bucles de trenes personalizados son importantes para muchos, especialmente para mí.AGRADECIMIENTOS : Gracias a
ACTUALIZACIONES :
14/11/19 : encontré un modelo (en mi aplicación real) que se ejecuta más lentamente en TF2 para todas las configuraciones * con datos de entrada Numpy. Las diferencias oscilaron entre el 13 y el 19%, con un promedio del 17%. Las diferencias entre
keras
ytf.keras
, sin embargo, fueron más dramáticas: 18-40% , promedio. 32% (ambos TF1 y 2). (* - excepto Eager, para el cual TF2 OOM'd)17/11/19 : los desarrolladores actualizaron los
on_batch()
métodos en una confirmación reciente , indicando que han mejorado la velocidad, que se lanzará en TF 2.1 o estará disponible ahora comotf-nightly
. Como no puedo ejecutarlo más tarde, retrasaré el banco hasta 2.1.fuente
fit_generator
? ... Prácticamente nunca quierotrain_on_batch
y administrar mi propio ciclo de entrenamiento en lotes es un gran antipatrón enorme que se debe evitar incluso a un gran costo.fit
con una pequeña sobrecarga de procesamiento de datos adicional. En cuanto a los bucles de trenes, escribí mi propio personalizado que finalmente se convirtió en una especie de API;fit_generator
carece de introspección, personalización y guardar / cargar, por lo que es un nop absoluto para mí Eventualmente publicaré mi ciclo de entrenamiento, en Github.fit_generator
su aplicación es, bueno, probarlo.ESTA RESPUESTA : tiene como objetivo proporcionar una descripción detallada del problema a nivel gráfico / hardware, incluidos los bucles de tren TF2 vs. TF1, los procesadores de datos de entrada y las ejecuciones en modo Eager vs. Graph. Para un resumen del problema y pautas de resolución, vea mi otra respuesta.
VEREDICTO DE DESEMPEÑO : a veces uno es más rápido, a veces el otro, dependiendo de la configuración. En cuanto a TF2 vs TF1, están a la par en promedio, pero existen diferencias significativas basadas en la configuración, y TF1 supera a TF2 con más frecuencia que viceversa. Ver "BENCHMARKING" a continuación.
EAGER VS. GRÁFICO : la carne de esta respuesta completa para algunos: el deseo de TF2 es más lento que el de TF1, según mis pruebas. Detalles más abajo.
La diferencia fundamental entre los dos es: Graph configura una red computacional de manera proactiva , y se ejecuta cuando se le 'dice', mientras que Eager ejecuta todo al momento de la creación. Pero la historia solo comienza aquí:
Eager no está desprovisto de Graph , y de hecho puede ser principalmente Graph, al contrario de lo esperado. Lo que es en gran parte es el gráfico ejecutado : esto incluye pesos de modelo y optimizador, que comprenden una gran parte del gráfico.
Eager reconstruye parte del propio gráfico en la ejecución ; consecuencia directa de que Graph no se construyó completamente - ver resultados del generador de perfiles. Esto tiene una sobrecarga computacional.
Ansioso es más lento con entradas Numpy ; según este comentario y código de Git , las entradas de Numpy en Eager incluyen el costo general de copiar tensores de la CPU a la GPU. Al recorrer el código fuente, las diferencias en el manejo de datos son claras; Eager pasa directamente a Numpy, mientras que Graph pasa a los tensores que luego evalúan a Numpy; incierto del proceso exacto, pero este último debe incluir optimizaciones a nivel de GPU
TF2 Eager es más lento que TF1 Eager : esto es ... inesperado. Vea los resultados de la evaluación comparativa a continuación. Las diferencias abarcan desde insignificante a significativo, pero son consistentes. No estoy seguro de por qué es así: si un desarrollador de TF aclara, actualizará la respuesta.
TF2 vs. TF1 : citando porciones relevantes de la respuesta de un desarrollador de TF, Q. Scott Zhu, con un poco de mi énfasis y reformulación:
Con la última oración del último párrafo anterior y la última cláusula del párrafo siguiente:
No estoy de acuerdo, según mis resultados de perfil, que muestran que el procesamiento de datos de entrada de Eager es sustancialmente más lento que el de Graph. Además, no estoy seguro acerca de esto
tf.data.Dataset
en particular, pero Eager llama repetidamente a varios de los mismos métodos de conversión de datos; consulte el generador de perfiles.Por último, el compromiso vinculado del desarrollador: número significativo de cambios para admitir los bucles Keras v2 .
Train Loops : dependiendo de (1) Eager vs. Graph; (2) formato de datos de entrada, el entrenamiento continuará con un ciclo de entrenamiento distinto: en TF2
_select_training_loop()
, training.py , uno de:Cada uno maneja la asignación de recursos de manera diferente y tiene consecuencias en el rendimiento y la capacidad.
Train Loops:
fit
vstrain_on_batch
,keras
vstf.keras
.: cada uno de los cuatro usa diferentes bucles de tren, aunque quizás no en todas las combinaciones posibles.keras
'fit
, por ejemplo, usa una forma defit_loop
, por ejemplotraining_arrays.fit_loop()
, ytrain_on_batch
puede usarK.function()
.tf.keras
tiene una jerarquía más sofisticada descrita en parte en la sección anterior.Train Loops: documentación - cadena de documentación fuente relevante en algunos de los diferentes métodos de ejecución:
Procesadores de datos de entrada : similar al anterior, el procesador se selecciona caso por caso, dependiendo de los indicadores internos establecidos de acuerdo con las configuraciones de tiempo de ejecución (modo de ejecución, formato de datos, estrategia de distribución). El caso más simple es con Eager, que funciona directamente con matrices Numpy. Para algunos ejemplos específicos, vea esta respuesta .
TAMAÑO DEL MODELO, TAMAÑO DE LOS DATOS:
convert_to_tensor
en "PERFIL")BENCHMARKS : la carne picada. - Documento de Word - Hoja de cálculo de Excel
Terminología :
(1 - longer_time / shorter_time)*100
; justificación: nos interesa qué factor es uno más rápido que el otro;shorter / longer
en realidad es una relación no lineal, no es útil para la comparación directa+
si TF2 es más rápido+
si Graph es más rápidoPERFIL :
PERFIL - Explicación : Spyder 3.3.6 IDE profiler.
Algunas funciones se repiten en nidos de otros; por lo tanto, es difícil rastrear la separación exacta entre las funciones de "procesamiento de datos" y "entrenamiento", por lo que habrá cierta superposición, como se manifestó en el último resultado.
% de cifras calculadas en tiempo de ejecución wrt menos tiempo de construcción
_func = func
se perfilarán comofunc
), lo que se mezcla en el tiempo de compilación, de ahí la necesidad de excluirloENTORNO DE PRUEBA :
METODOLOGÍA :
batch_size
ynum_channels
Conv1D
,Dense
capas '' se pueden aprender; RNNs evitados por implem versión TF. diferenciaslayers.Embedding()
. Ej. ) U objetivos dispersos (p. Ej.SparseCategoricalCrossEntropy()
LIMITACIONES : una respuesta "completa" explicaría todos los posibles bucles e iteradores de trenes, pero eso seguramente está más allá de mi capacidad de tiempo, cheque de pago inexistente o necesidad general. Los resultados son tan buenos como la metodología: interprete con una mente abierta.
CÓDIGO :
fuente
model.compile
sinrun_eagerly=True
argumento. Si está en modo ansioso, puede ejecutar parte de su código en modo gráfico usandotf.function
. Por lo tanto, creo que la implementación predeterminada decompile
es crear un gráfico computacional en lugar de ejecutarlo ansiosamente por razones de rendimiento. También tenga en cuenta que si su modelo es convolucional, entonces no verá la aceleración en modo gráfico ya que la interacción con Python es mínima. Si realiza muchas operaciones matemáticas, puede hacer una gran diferencia (también en la utilización de la memoria).model.compile
norun_eagerly=True
garantiza el modo gráfico, ¿o no?model.compile
omodel.fit
debe garantizar que la capacitación se ejecute en modo gráfico internamente.run_eagerly=True
como parámetro para compilar". (fuente tensorflow.org/guide/keras/overview ) Por lo tanto, si no pasa elrun_eagerly=True
modelo, PUEDO ejecutarlo en modo gráfico. No estoy seguro de cuál es el factor decisivo, pero ¿por qué no funcionaría en modo gráfico si es más eficiente que ansioso?