Saltar al contenido

Estrategias de paralelización para el aprendizaje profundo

Ya no necesitas indagar más por otras páginas porque llegaste al sitio correcto, tenemos la respuesta que buscas sin problemas.

Solución:

Capacitación

En general, existen dos estrategias para paralelizar el entrenamiento del modelo: el paralelismo de datos y el paralelismo del modelo.

1. Paralelismo de datos

Esta estrategia divide los datos de entrenamiento en N particiones, cada una de las cuales se entrenará en diferentes “dispositivos” (diferentes núcleos de CPU, GPU o incluso máquinas). A diferencia del entrenamiento sin paralelismo de datos que produce un gradiente por minibatch, ahora tenemos N gradientes para cada paso del minibatch. La siguiente pregunta es cómo debemos combinar estos N gradientes.

Una forma de hacerlo es promediando todos los N gradientes y luego actualizando los parámetros del modelo una vez basado en el promedio. Esta técnica se llama SGD distribuido síncrono. Al hacer el promedio, tenemos un gradiente más preciso, pero con el costo de esperar que todos los dispositivos terminen de calcular su propio gradiente local.

Otra forma es no combinar los degradados; en su lugar, cada degradado se utilizará para actualizar los parámetros del modelo de forma independiente. Por lo tanto, habrá N actualizaciones de parámetros para cada paso de minibatch, en contraste con solo una para la técnica anterior. Esta técnica se llama SGD distribuido asincrónico. Debido a que no tiene que esperar a que otros dispositivos terminen, el enfoque asincrónico tomará menos tiempo para completar un paso de minibatch que el enfoque de sincronización. Sin embargo, el enfoque asincrónico producirá un gradiente más ruidoso, por lo que es posible que deba completar más pasos de minibatch para ponerse al día con el rendimiento (en términos de pérdida) del enfoque de sincronización.

Hay muchos artículos que proponen algunas mejoras y optimizaciones en cualquiera de los enfoques, pero la idea principal es generalmente la misma que se describe anteriormente.

En la literatura ha habido cierto desacuerdo sobre qué técnica es mejor en la práctica. Al final, la mayoría de la gente se decide ahora por el enfoque sincrónico.

Paralelismo de datos en PyTorch

Para hacer SGD sincrónico, podemos envolver nuestro modelo con torch.nn.parallel.DistributedDataParallel:

from torch.nn.parallel import DistributedDataParallel as DDP

# `model` is the model we previously initialized
model = ...

# `rank` is a device number starting from 0
model = model.to(rank)
ddp_model = DDP(model, device_ids=[rank])

Entonces podemos entrenarlo de manera similar. Para obtener más detalles, puede consultar el tutorial oficial.

Para hacer SGD asincrónico en PyTorch, necesitamos implementarlo de forma más manual ya que no hay un contenedor similar a DistributedDataParallel para ello.

Paralelismo de datos en TensorFlow / Keras

Para SGD sincrónico, podemos usar tf.distribute.MirroredStrategy para envolver la inicialización del modelo:

import tensorflow as tf

strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
    model = Model(...)
    model.compile(...)

Entonces podemos entrenarlo como de costumbre. Para obtener más detalles, puede consultar las guías oficiales en el sitio web de Keras y el sitio web de TensorFlow.

Para SGD asincrónico, podemos usar tf.distribute.experimental.ParameterServerStrategy similar.

2. Modelo de paralelismo

Esta estrategia divide el modelo en N partes, cada una de las cuales se calculará en diferentes dispositivos. Una forma común de dividir el modelo se basa en capas: diferentes conjuntos de capas se colocan en diferentes dispositivos. Pero también podemos dividirlo de manera más intrincada dependiendo de la arquitectura del modelo.

Modelar el paralelismo en TensorFlow y PyTorch

Para implementar el paralelismo del modelo en TensorFlow o PyTorch, la idea es la misma: mover algunos parámetros del modelo a un dispositivo diferente.

En PyTorch podemos usar torch.nn.Module.to método para mover un módulo a un dispositivo diferente. Por ejemplo, supongamos que queremos crear dos capas lineales, cada una de las cuales se coloca en una GPU diferente:

import torch.nn as nn

linear1 = nn.Linear(16, 8).to('cuda:0')
linear2 = nn.Linear(8, 4).to('cuda:1')

En TensorFlow podemos usar tf.device para colocar una operación en un dispositivo específico. Para implementar el ejemplo de PyTorch anterior en TensorFlow:

import tensorflow as tf
from tensorflow.keras import layers

with tf.device('/GPU:0'):
    linear1 = layers.Dense(8, input_dim=16)
with tf.device('/GPU:1'):
    linear2 = layers.Dense(4, input_dim=8)

Para obtener más detalles, puede consultar the official PyTorch tutorial; o si usa TensorFlow, incluso puede usar una biblioteca de más alto nivel como mesh.

3. Híbrido: Paralelismo de datos y modelos

Recuerde que el paralelismo de datos solo divide los datos de entrenamiento, mientras que el paralelismo del modelo solo divide las estructuras del modelo. Si tenemos un modelo tan grande que incluso después de usar cualquiera de las estrategias de paralelismo todavía no cabe en la memoria, siempre podemos hacer ambas.

En la práctica, la mayoría de la gente prefiere el paralelismo de datos al paralelismo de modelos, ya que el primero está más desacoplado (de hecho, independiente) de la arquitectura del modelo que el segundo. Es decir, al utilizar el paralelismo de datos, pueden cambiar la arquitectura del modelo a su gusto, sin preocuparse de qué parte del modelo se debe paralelizar.

Modelo de inferencia / servicio

El servicio de modelos en paralelo es más fácil que el entrenamiento de modelos en paralelo, ya que los parámetros del modelo ya están fijos y cada solicitud se puede procesar de forma independiente. De manera similar a escalar un servicio web Python normal, podemos escalar el servicio de modelos generando más procesos (para solucionar el GIL de Python) en una sola máquina, o incluso generando más instancias de máquina.

Sin embargo, cuando usamos una GPU para servir el modelo, necesitamos trabajar más para escalarlo. Debido a que una GPU maneja la simultaneidad de manera diferente en comparación con una CPU, para maximizar el rendimiento, necesitamos realizar solicitudes de inferencia por lotes. La idea es que cuando llega una solicitud, en lugar de procesarla inmediatamente, esperamos un tiempo de espera para que lleguen otras solicitudes. Cuando se agota el tiempo de espera, incluso si el número de solicitudes es solo una, las agrupamos todas para procesarlas en la GPU.

Para minimizar la latencia de solicitud promedio, necesitamos encontrar la duración óptima del tiempo de espera. Para encontrarlo, debemos observar que existe una compensación entre minimizar la duración del tiempo de espera y maximizar la cantidad de tamaño de lote. Si el tiempo de espera es demasiado bajo, el tamaño del lote será pequeño, por lo que la GPU estará infrautilizada. Pero si el tiempo de espera es demasiado alto, las solicitudes que llegan temprano esperarán demasiado antes de ser procesadas. Por lo tanto, la duración óptima del tiempo de espera depende de la complejidad del modelo (por lo tanto, la duración de la inferencia) y el promedio de solicitudes por segundo a recibir.

Implementar un programador para realizar solicitudes de procesamiento por lotes no es una tarea trivial, por lo que en lugar de hacerlo manualmente, será mejor que usemos TensorFlow Serving o PyTorch Serve, que ya lo admiten.


Para obtener más información sobre el aprendizaje paralelo y distribuido, puede leer este artículo de revisión.

Como la pregunta es bastante amplia, intentaré arrojar una luz un poco diferente y tocar temas diferentes a los que se muestran en la respuesta en profundidad de @ Daniel.

Capacitación

Paralelización de datos vs paralelización de modelos

Como lo menciona @Daniel, el paralelismo de datos se usa con mucha más frecuencia y es más fácil de hacer correctamente. La principal advertencia del paralelismo del modelo es la necesidad de esperar parte de la red neuronal y la sincronización entre ellos.

Digamos que tiene un feedforward simple 5 capa de red neuronal repartida por 5 diferentes GPU, cada capa para un dispositivo. En este caso, durante cada paso hacia adelante, cada dispositivo tiene que esperar los cálculos de las capas anteriores. En este caso simplista, la copia de datos entre dispositivos y la sincronización llevaría mucho más tiempo y no reportaría beneficios.

Por otro lado, hay modelos más adecuados para la paralelización de modelos como las redes Inception, vea la imagen a continuación:

bloque de inicio

Aquí puedes ver 4 caminos independientes de la capa anterior que podrían ir en paralelo y solo 2 puntos de sincronizaciónFilter concatenation y Previous Layer).

Preguntas

Por ejemplo, la retropropagación a través del gráfico en sí se puede paralelizar, por ejemplo, al tener diferentes capas alojadas en diferentes máquinas, ya que (¿creo?) El gráfico de autodif es siempre un DAG.

No es así de fácil. Los degradados se calculan en función del valor de pérdida (normalmente) y es necesario conocer los degradados de las capas más profundas para calcular los degradados de las más superficiales. Como se indicó anteriormente, si tiene rutas independientes, es más fácil y puede ayudar, pero es mucho más fácil en un solo dispositivo.

Creo que esto también se llama acumulación de gradiente (?)

No, en realidad es una reducción en varios dispositivos. Puedes ver algo de eso en el tutorial de PyTorch. La acumulación de gradiente es cuando ejecuta su pase hacia adelante (ya sea en uno o varios dispositivos) N tiempos y retropropagación (el gradiente se mantiene en el gráfico y los valores se agregan durante cada paso) y el optimizador solo realiza un solo paso para cambiar los pesos de la red neuronal (y borra el gradiente). En este caso, la pérdida se suele dividir por el número de pasos sin optimizador. Esto se usa para una estimación de gradiente más confiable, generalmente cuando no puede usar lotes grandes.

La reducción en todos los dispositivos se ve así:

reducción

Esto es todo: reducción en la paralelización de datos, cada dispositivo calcula los valores que se envían a todos los demás dispositivos y se propagan hacia atrás allí.

¿Cuándo es mejor cada estrategia para qué tipo de problema o red neuronal?

Descrito anteriormente, el paralelo de datos casi siempre está bien si tiene suficientes datos y las muestras son grandes (hasta 8k muestras o más se pueden hacer a la vez sin muy gran lucha).

¿Qué modos son compatibles con las bibliotecas modernas?

tensorflow y pytorch Ambos son compatibles, la mayoría de las bibliotecas modernas y mantenidas tienen esas funcionalidades implementadas de una forma u otra

¿Se pueden combinar las cuatro (2×2) estrategias?

Sí, puede paralelizar tanto el modelo como los datos entre máquinas y dentro de ellas.

sincrónico vs asincrónico

asincrónico

Descrito brevemente por @Daniel, pero vale la pena mencionar que las actualizaciones no están totalmente separadas. Eso tendría poco sentido, ya que esencialmente entrenaríamos N diferentes modelos en función de sus lotes.

En su lugar, hay un espacio de parámetros global, donde se supone que cada réplica comparte las actualizaciones calculadas de forma asincrónica (por lo tanto, avance, retroceda, calcule la actualización con el optimizador y comparta esta actualización con los parámetros globales).

Sin embargo, este enfoque tiene un problema: no hay garantía de que cuando un trabajador calcula el pase hacia adelante, otro trabajador actualiza los parámetros, por lo que se calcula la actualización. con respecto al antiguo conjunto de parámetros y esto se llama gradientes obsoletos. Debido a esto, la convergencia podría verse perjudicada.

Otro enfoque es calcular N pasos y actualizaciones para cada trabajador y sincronizarlos después, aunque no se usa con tanta frecuencia.

Esta parte se basó en una excelente publicación de blog y definitivamente debería leerla si está interesado (hay más sobre obsolescencia y algunas soluciones).

sincrónico

En su mayoría descritos anteriormente, existen diferentes enfoques, pero PyTorch recopila la salida de la red y se propaga hacia atrás en ellos (torch.nn.parallel.DistributedDataParallel)[https://pytorch.org/docs/stable/nn.html#torch.nn.parallel.DistributedDataParallel]. POR CIERTO. Solo deberías esto (no torch.nn.DataParallel) ya que supera Problema de GIL de Python.

Para llevar

  • La paralelización de datos siempre se usa casi siempre cuando se busca acelerar, ya que “solo” tiene que replicar la red neuronal en cada dispositivo (ya sea a través de la red o dentro de una sola máquina), ejecutar parte del lote en cada uno durante el pase directo, concatenarlos en un lote único (sincronización) en un dispositivo y propagación inversa en dicho.
  • Hay varias formas de hacer la paralelización de datos, ya introducidas por @Daniel
  • La paralelización del modelo se realiza cuando el modelo es demasiado grande para caber en una sola máquina (el GPT-3 de OpenAI sería un caso extremo) o cuando la arquitectura es adecuada para esta tarea, pero ambos raramente son el caso AFAIK.
  • Cuantas más y más largas rutas paralelas tenga el modelo (puntos de sincronización), mejor será adecuado para la paralelización del modelo
  • Es importante iniciar a los trabajadores en momentos similares con cargas similares para no dar paso a los procesos de sincronización en un enfoque sincrónico o no obtener gradientes obsoletos en modo asincrónico (aunque en el último caso no es suficiente).

Servicio

Modelos pequeños

Como buscas modelos grandes, no profundizaré en las opciones para los más pequeños, solo una breve mención.

Si desea brindar servicios a varios usuarios a través de la red, necesita alguna forma de escalar su arquitectura (generalmente en la nube, como GCP o AWS). Puede hacer eso usando Kubernetes y sus POD o preasignar algunos servidores para manejar solicitudes, pero ese enfoque sería ineficiente (una pequeña cantidad de usuarios y servidores en ejecución generaría costos inútiles, mientras que una gran cantidad de usuarios puede detener la infraestructura y tomar demasiado mucho tiempo para procesar las solicitudes).

Otra forma es utilizar el ajuste de escala automático basado en un enfoque sin servidor. Los recursos se proporcionarán en función de cada solicitud, por lo que tiene grandes capacidades de escala + no paga cuando el tráfico es bajo. Puede ver Azure Functions ya que están en el camino para mejorarlo para tareas de ML / DL, o torchlambda para PyTorch (descargo de responsabilidad, soy el autor) para modelos más pequeños.

Modelos grandes

Como se mencionó anteriormente, puede usar Kubernetes con su código personalizado o herramientas listas para usar.

En el primer caso, puede difundir el modelo de la misma manera que para el entrenamiento, pero solo forward aprobar. De esta manera, incluso los modelos gigantes se pueden poner en la red (una vez más, GPT-3 con parámetros 175B), pero requiere mucho trabajo.

En el segundo, @Daniel brindó dos posibilidades. Otros que vale la pena mencionar podrían ser (lea los documentos respectivos, ya que tienen muchas funcionalidades):

  • KubeFlow: múltiples marcos, basados ​​en Kubernetes (por lo tanto, escalado automático, múltiples nodos), entrenamiento, servicio y lo que no, se conecta con otras cosas como MLFlow a continuación
  • AWS SageMaker: entrenamiento y servicio con la API de Python, compatible con Amazon
  • MLFlow: múltiples marcos, para el manejo y servicio de experimentos
  • BentoML: múltiples marcos, entrenamiento y servicio

Para PyTorch, puede leer más aquí, mientras que tensorflow tiene muchas funciones de servicio listas para usar a través de Tensorflow EXtended (TFX).

Preguntas del comentario de OP

¿Hay alguna forma de paralelismo que sea mejor dentro de una máquina que entre máquinas?

Lo mejor para el paralelismo probablemente sería dentro de una computadora gigante para minimizar la transferencia entre dispositivos.

Además, hay diferentes backends (al menos en PyTorch) entre los que se puede elegir (mpi, gloo, nccl) y no todos admiten el envío directo, la recepción, la reducción, etc. de datos entre dispositivos (algunos pueden admitir CPU a CPU, otros GPU a GPU). Si no hay un enlace directo entre los dispositivos, primero deben copiarse a otro dispositivo y copiarse nuevamente al dispositivo de destino (por ejemplo, GPU en otra máquina -> CPU en el host -> GPU en el host). Consulte la información de Pytorch.

Cuantos más datos y mayor sea la red, más rentable debería ser paralelizar los cálculos. Si todo el conjunto de datos puede caber en un solo dispositivo, no hay necesidad de paralelización. Además, se deben tener en cuenta aspectos como la velocidad de transferencia de Internet, la confiabilidad de la red, etc. Esos costos pueden superar los beneficios.

En general, opte por la paralelización de datos si tiene muchos datos (digamos ImageNet con 1.000.000 imágenes) o muestras grandes (digamos imágenes 2000x2000). Si es posible, dentro de una sola máquina para minimizar la transferencia entre máquinas. Distribuya el modelo solo si no hay forma de evitarlo (por ejemplo, no cabe en la GPU). No lo haga de otra manera (hay poco o ningún punto para paralelizar al entrenar MNIST ya que todo el conjunto de datos cabe fácilmente en la RAM y la lectura será más rápida).

¿Por qué molestarse en construir hardware personalizado específico de ML, como TPU?

Las CPU no son las más adecuadas para cálculos altamente paralelos (por ejemplo, multiplicación de matrices) + la CPU puede estar ocupada con muchas otras tareas (como la carga de datos), por lo que tiene sentido usar GPU.

Como la GPU se creó teniendo en cuenta los gráficos (es decir, la transformación algebraica), puede tomar algunas tareas de la CPU y puede especializarse (muchos más núcleos en comparación con la CPU, pero más simples, consulte V100, por ejemplo).

Ahora, las TPU están diseñadas específicamente para los cálculos de tensores (por lo tanto, el aprendizaje profundo principalmente) y se originaron en Google, todavía WIP en comparación con las GPU. Aquellos son adecuados para ciertos tipos de modelos (principalmente redes neuronales convolucionales) y pueden traer aceleraciones en este caso. Además, se deben usar los lotes más grandes con este dispositivo (ver aquí), lo mejor es que sea divisible por 128. Puede comparar eso con la tecnología Tensor Cores (GPU) de NVidia, donde está bien con lotes (o tamaños de capa) divisibles por 16 o 8 (float16 precisión y int8 respectivamente) para una buena utilización (aunque cuanto más, mejor y depende del número de núcleos, la tarjeta gráfica exacta y muchas otras cosas, consulte algunas pautas aquí).

Por otro lado, el soporte de TPU aún no es el mejor, aunque dos marcos principales lo admiten (tensorflow oficialmente, mientras que PyTorch con torch_xla paquete).

En general, la GPU es una buena opción predeterminada en el aprendizaje profundo en este momento, las TPU para arquitecturas pesadas de convolución, aunque pueden dar algunos dolores de cabeza por cierto. Además (una vez más gracias a @Daniel), las TPU son más energéticamente efectivas, por lo que deberían ser más baratas al comparar el costo de operación de un solo punto flotante.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *