25.3 C
Colombia
lunes, julio 7, 2025

Primeros experimentos con entrenamiento de precisión mixta de TensorFlow


A partir de su lanzamiento 2.1 muy reciente, TensorFlow admite lo que se llama entrenamiento de precisión mixta (En el siguiente: MPT) para Keras. En esta publicación, experimentamos con MPT y proporcionamos algunos antecedentes. Declarado por adelantado: en una GPU TESLA V100, nuestro experimento basado en CNN no reveló reducciones sustanciales en el tiempo de ejecución. En un caso como este, es difícil decidir si realmente escribir una publicación o no. Podrías argumentar que al igual que en la ciencia, nulo Los resultados son resultados. O, más prácticamente: abren una discusión que puede conducir al descubrimiento de errores, la aclaración de las instrucciones de uso y una mayor experimentación, entre otros.

Además, el tema en sí es lo suficientemente interesante como para merecer algunas explicaciones de antecedentes, incluso si los resultados no están ahí todavía.

Entonces, para comenzar, escuchemos un contexto en MPT.

Esto no se trata solo de guardar memoria

Una forma de describir MPT en TensorFlow podría ir así: MPT le permite entrenar modelos donde el pesas son de tipo float32 o float64como de costumbre (por razones de estabilidad numérica), pero el datos – Los tensores empujados entre operaciones – tienen menor precisión, a saber, 16 bits (float16).

Esta oración probablemente funcionaría bien como un TLDR;
para el nuevo Página de documentación de MPTtambién disponible para R en el TensorFlow para el sitio net de R. Y según esta oración, es posible que te lleve a pensar “Oh, claro, así que se trata de guardar la memoria”. Menos uso de la memoria implicaría que podría ejecutar tamaños de lotes más grandes sin obtener errores fuera de la memoria.

Por supuesto, esto es correcto, y verá que sucede en los resultados de la experimentación. Pero es solo una parte de la historia. La otra parte está relacionada con la arquitectura de GPU y la computación paralela (no solo en la GPU paralela, como veremos).

AVX & CO.

Las GPU tienen que ver con la paralelización. Pero también para las CPU, los últimos diez años han visto desarrollos importantes en arquitectura e conjuntos de instrucciones. SIMD (datos múltiples de instrucción única) Las operaciones realizan una instrucción sobre un montón de datos a la vez. Por ejemplo, dos operandos de 128 bits podrían contener dos enteros de 64 bits cada uno, y estos podrían agregarse por pares. Conceptualmente, esto recuerda la adición de vectores en R (¡es solo un análogo!):

# image these as 64-bit integers
c(1, 2) + c(3, 4)

O, esos operandos podrían contener cuatro enteros de 32 bits cada uno, en cuyo caso podríamos escribir simbólicamente

# image these as 32-bit integers
c(1, 2, 3, 4) + c(5, 6, 7, 8)

Con enteros de 16 bits, podríamos volver a duplicar el número de elementos operados:

# image these as 16-bit integers
c(1, 2, 3, 4, 5, 6, 7, 8) + c(9, 10, 11, 12, 13, 14, 15, 16)

Durante la última década, las principales extensiones de lenguaje de ensamblaje X-86 relacionadas con SIMD han sido AVX (Extensiones vectoriales avanzadas), AVX2, AVX-512 y FMA (más sobre FMA pronto). ¿Alguno de estos suena a una campana?

Your CPU helps directions that this TensorFlow binary was not compiled to make use of:
AVX2 FMA

Esta es una línea que es possible que vea si está utilizando un binario tensorflow previo a la construcción, en lugar de la compilación de la fuente. (Más tarde, al informar los resultados de la experimentación, también indicaremos los tiempos de ejecución de la CPU, para proporcionar algún contexto para los tiempos de ejecución de GPU que nos interesan, y solo por diversión, también haremos un- muy Superficial: comparación entre un binario tensorflow instalado de PYPI y uno que se compiló manualmente).

Si bien todos esos AVXes son (básicamente) sobre una extensión del procesamiento de vectores a tipos de datos cada vez más grandes, FMA es diferente, y es algo interesante saber en sí mismo, para cualquiera que realice un procesamiento de señal o el uso de redes neuronales.

Multiplicy-add fusionado (FMA)

Fusionada multiplicada es un tipo de múltiple acumulado operación. En múltiple acumuladolos operandos se multiplican y luego se agregan al acumulador realizando un seguimiento de la suma en ejecución. Si “se fusionó”, toda la operación multiplicada y luego agregada se realiza con un solo redondeo al ultimate (en lugar de redondear una vez después de la multiplicación, y luego nuevamente después de la adición). Por lo common, esto da como resultado una mayor precisión.

Para las CPU, FMA se introdujo simultáneamente con AVX2. FMA se puede realizar en escalares o en vectores, “empacados” de la manera descrita en el párrafo anterior.

¿Por qué dijimos que esto period tan interesante para los científicos de datos? Bueno, muchas operaciones (productos DOT, multiplicaciones de matriz, convoluciones) implican multiplicaciones seguidas de adiciones. La “multiplicación de matriz” aquí en realidad nos hace dejar el reino de las CPU y saltar a las GPU, porque lo que hace MPT es hacer uso de los nuevos Nvidia Núcleos de tensor que extiende FMA de escalares/vectores a matrices.

Núcleos de tensor

Como documentadoMPT requiere GPU con capacidad de calcular > = 7.0. Las GPU respectivas, además de las habituales Núcleos de cudase han llamado “núcleos de tensor” que realizan FMA en matrices:

La operación tiene lugar en matrices 4×4; Las multiplicaciones ocurren en operandos de 16 bits, mientras que el resultado ultimate podría ser de 16 o 32 bits.

Podemos ver cómo esto es inmediatamente relevante para las operaciones involucradas en el aprendizaje profundo; Los detalles, sin embargo, son no necesariamente claro.

Dejando esas partes internas a los expertos, ahora procedemos al experimento actual.

Experimentos

Conjunto de datos

Con sus imágenes de tamaño 28x28px / 32x32px, ni Mnist ni Cifar parecían particularmente adecuados para desafiar la GPU. En cambio, elegimos Imagenetteel “pequeño imagenet” creado por el ayunar.ai gente, que consta de 10 clases: Tench, Springer inglés, reproductor de cassette, sierra de cadena, iglesia, cuerno francés, camión de basura, bomba de gasoline, pelota de golf, y paracaídas. Aquí hay algunos ejemplos, tomados de la versión de 320px:


Ejemplos de las 10 clases de Imagenette.

Figura 3: Ejemplos de las 10 clases de Imagenette.

Estas imágenes han sido redicionadas, manteniendo la relación de aspecto, de modo que la dimensión más grande tiene una longitud de 320 px. Como parte del preprocesamiento, cambiaremos de tamaño aún más a 256x256px, para trabajar con un buen poder de 2.

El conjunto de datos puede obtenerse convenientemente mediante el uso de TFDSla interfaz R para los conjuntos de datos TensorFlow.

library(keras)
# wants model 2.1
library(tensorflow)
library(tfdatasets)
# accessible from github: devtools::install_github("rstudio/tfds")
library(tfds)

# to make use of TensorFlow Datasets, we'd like the Python backend
# usually, simply use tfds::install_tfds for this
# as of this writing although, we'd like a nightly construct of TensorFlow Datasets
# envname ought to discuss with no matter atmosphere you run TensorFlow in
reticulate::py_install("tfds-nightly", envname = "r-reticulate") 

# on first execution, this downloads the dataset
imagenette <- tfds_load("imagenette/320px")

# extract practice and take a look at components
practice <- imagenette$practice
take a look at <- imagenette$validation

# batch dimension for the preliminary run
batch_size <- 32
# 12895 is the variety of gadgets within the coaching set
buffer_size <- 12895/batch_size

# coaching dataset is resized, scaled to between 0 and 1,
# cached, shuffled, and divided into batches
train_dataset <- practice %>%
  dataset_map(perform(report) {
    report$picture <- report$picture %>%
      tf$picture$resize(dimension = c(256L, 256L)) %>%
      tf$truediv(255)
    report
  }) %>%
  dataset_cache() %>%
  dataset_shuffle(buffer_size) %>%
  dataset_batch(batch_size) %>%
  dataset_map(unname)

# take a look at dataset is resized, scaled to between 0 and 1, and divided into batches
test_dataset <- take a look at %>% 
  dataset_map(perform(report) {
    report$picture <- report$picture %>% 
      tf$picture$resize(dimension = c(256L, 256L)) %>%
      tf$truediv(255)
    report}) %>%
  dataset_batch(batch_size) %>% 
  dataset_map(unname)

En el código anterior, almacenamos en caché el conjunto de datos después de las operaciones de cambio de tamaño y escala, ya que queremos minimizar el tiempo de preprocesamiento dedicado a la CPU.

Configuración de MPT

Nuestro experimento usa keras match – A diferencia de un bucle de entrenamiento personalizado, y dadas estas condiciones previas, ejecutar MPT es principalmente una cuestión de agregar tres líneas de código. (Hay un pequeño cambio en el modelo, como veremos en un momento).

Le decimos a Keras que use el mixed_float16 Coveragey verifique que los tensores tengan tipo float16 mientras el Variables (pesas) todavía son de tipo float32:

# in case you learn this at a later time and get an error right here,
# take a look at whether or not the placement within the codebase has modified
mixed_precision <- tf$keras$mixed_precision$experimental

coverage <- mixed_precision$Coverage('mixed_float16')
mixed_precision$set_policy(coverage)

# float16
coverage$compute_dtype
# float32
coverage$variable_dtype

El modelo es una convnet directa, con un número de filtros que son múltiplos de 8, como se especifica en el documentación. Sin embargo, hay una cosa a tener en cuenta: por razones de estabilidad numérica, el tensor de salida actual del modelo debe ser de tipo float32.

mannequin <- keras_model_sequential() %>% 
  layer_conv_2d(filters = 32, kernel_size = 5, strides = 2, padding = "identical", input_shape = c(256, 256, 3), activation = "relu") %>%
  layer_batch_normalization() %>%
  layer_conv_2d(filters = 64, kernel_size = 7, strides = 2, padding = "identical", activation = "relu") %>%
  layer_batch_normalization() %>%
  layer_conv_2d(filters = 128, kernel_size = 11, strides = 2, padding = "identical", activation = "relu") %>%
  layer_batch_normalization() %>%
  layer_global_average_pooling_2d() %>%
  # separate logits from activations so precise outputs could be float32
  layer_dense(models = 10) %>%
  layer_activation("softmax", dtype = "float32")

mannequin %>% compile(
  loss = "sparse_categorical_crossentropy",
  optimizer = "adam",
  metrics = "accuracy")

mannequin %>% 
  match(train_dataset, validation_data = test_dataset, epochs = 20)

Resultados

El experimento principal se realizó en un Tesla V100 con 16 g de memoria. Solo por curiosidad, ejecutamos ese mismo modelo en otras cuatro condiciones, ninguna de las cuales cumple el requisito previo de tener un capacidad de calcular igual a al menos 7.0. Mencionaremos rápidamente los que hay después de los resultados principales.

Con el modelo anterior, la precisión ultimate (ultimate como en: después de 20 épocas) fluctuó aproximadamente 0.78:

Epoch 16/20
403/403 [==============================] - 12s 29ms/step - loss: 0.3365 -
accuracy: 0.8982 - val_loss: 0.7325 - val_accuracy: 0.8060
Epoch 17/20
403/403 [==============================] - 12s 29ms/step - loss: 0.3051 -
accuracy: 0.9084 - val_loss: 0.6683 - val_accuracy: 0.7820
Epoch 18/20
403/403 [==============================] - 11s 28ms/step - loss: 0.2693 -
accuracy: 0.9208 - val_loss: 0.8588 - val_accuracy: 0.7840
Epoch 19/20
403/403 [==============================] - 11s 28ms/step - loss: 0.2274 -
accuracy: 0.9358 - val_loss: 0.8692 - val_accuracy: 0.7700
Epoch 20/20
403/403 [==============================] - 11s 28ms/step - loss: 0.2082 -
accuracy: 0.9410 - val_loss: 0.8473 - val_accuracy: 0.7460

Los números informados a continuación son milisegundos por paso, paso Ser un pase sobre un solo lote. Por lo tanto, en common, duplicando el tamaño del lote, esperaríamos que el tiempo de ejecución también se duplique.

Aquí hay tiempos de ejecución, tomados de la época 20, para cinco tamaños de lotes diferentes, comparando MPT con un valor predeterminado Coverage que usa float32 a lo largo de. (Deberíamos agregar eso, aparte de la primera época, los tiempos de ejecución por paso fluctuan como máximo un milisegundo en cada condición).

32 28 30
64 52 56
128 97 106
256 188 206
512 377 415

Consistentemente, MPT fue más rápido, lo que indica que se usó la ruta del código prevista. Pero la aceleración no es tan grande.

También vimos la utilización de GPU durante las carreras. Estos variaron de alrededor del 72% para batch_size 32 más de ~ 78% para batch_size 128 a valores fluctuantes con altura, alcanzando repetidamente el 100%, para batch_size 512.

Como se aludió anteriormente, solo para anclar estos valores, ejecutamos el mismo modelo en otras cuatro condiciones, donde no se esperaba aceleración. A pesar de que estos tiempos de ejecución no son estrictamente parte de los experimentos, los informamos, en caso de que el lector sea tan curioso sobre algún contexto como nosotros.

En primer lugar, aquí está la tabla equivalente para un Titan XP con 12 g de memoria y capacidad de calcular 6.1.

32 44 38
64 70 70
128 142 136
256 270 270
512 518 539

Como se esperaba, no existe una superioridad consistente de MPT; Como aparte, mirando los valores en common (¡especialmente en comparación con los tiempos de ejecución de la CPU!), ¡podría concluir que afortunadamente, uno no siempre necesita la última y mejor GPU para entrenar redes neuronales!

A continuación, damos un paso más por la escalera de {hardware}. Aquí hay tiempos de ejecución de un Quadro M2200 (4G, capacidad de calcular 5.2). (Las tres carreras que no tienen un número bloqueado con fuera de memoria.)

32 186 197
64 352 375
128 687 746
256 1000
512

Esta vez, en realidad vemos cómo el aspecto puro de uso de memoria juega un papel: con MPT, podemos ejecutar lotes de tamaño 256; Sin, obtenemos un error fuera de memoria.

Ahora, también comparamos con el tiempo de ejecución en la CPU (Intel Core i7, velocidad del reloj 2.9GHz). Para ser honesto, nos detuvimos después de una sola época. Con un batch_size de 32 y que ejecuta una instalación estándar preconstruida de TensorFlow, un solo paso ahora tomó 321, no milisegundos, sino segundos. Solo por diversión, nos comparamos con un flujo tensor de construcción guide que puede hacer uso de Avx2 y FMA Instrucciones (este tema podría merecer un experimento dedicado): el tiempo de ejecución por paso se redujo a 304 segundos/paso.

Conclusión

En resumen, nuestro experimento no mostró reducciones importantes en los tiempos de ejecución, por razones aún no claras. ¡Estaremos encantados de alentar una discusión en los comentarios!

A pesar de los resultados experimentales, esperamos que haya disfrutado obtener información de fondo sobre un tema que no es demasiado discutido. ¡Gracias por leer!

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles