Apache MXNet - Entrenamiento distribuido
Este capítulo trata sobre el entrenamiento distribuido en Apache MXNet. Comencemos por comprender cuáles son los modos de cálculo en MXNet.
Modos de computación
MXNet, una biblioteca ML en varios idiomas, ofrece a sus usuarios los siguientes dos modos de cálculo:
Modo imperativo
Este modo de cálculo expone una interfaz como NumPy API. Por ejemplo, en MXNet, use el siguiente código imperativo para construir un tensor de ceros tanto en la CPU como en la GPU:
import mxnet as mx
tensor_cpu = mx.nd.zeros((100,), ctx=mx.cpu())
tensor_gpu= mx.nd.zeros((100,), ctx=mx.gpu(0))
Como vemos en el código anterior, MXNets especifica la ubicación donde mantener el tensor, ya sea en la CPU o en el dispositivo GPU. En el ejemplo anterior, está en la ubicación 0. MXNet logra una utilización increíble del dispositivo, porque todos los cálculos se realizan de forma perezosa en lugar de instantáneamente.
Modo simbólico
Aunque el modo imperativo es bastante útil, uno de los inconvenientes de este modo es su rigidez, es decir, todos los cálculos deben conocerse de antemano junto con las estructuras de datos predefinidas.
Por otro lado, el modo simbólico expone un gráfico de cálculo como TensorFlow. Elimina el inconveniente de la API imperativa al permitir que MXNet trabaje con símbolos o variables en lugar de estructuras de datos fijas / predefinidas. Posteriormente, los símbolos se pueden interpretar como un conjunto de operaciones de la siguiente manera:
import mxnet as mx
x = mx.sym.Variable(“X”)
y = mx.sym.Variable(“Y”)
z = (x+y)
m = z/100
Tipos de paralelismo
Apache MXNet admite entrenamiento distribuido. Nos permite aprovechar múltiples máquinas para un entrenamiento más rápido y efectivo.
A continuación, se muestran las dos formas en que podemos distribuir la carga de trabajo de entrenar una NN en varios dispositivos, CPU o dispositivo GPU:
Paralelismo de datos
En este tipo de paralelismo, cada dispositivo almacena una copia completa del modelo y trabaja con una parte diferente del conjunto de datos. Los dispositivos también actualizan un modelo compartido de forma colectiva. Podemos ubicar todos los dispositivos en una sola máquina o en varias máquinas.
Paralelismo de modelos
Es otro tipo de paralelismo, que resulta útil cuando los modelos son tan grandes que no caben en la memoria del dispositivo. En el paralelismo del modelo, a diferentes dispositivos se les asigna la tarea de aprender diferentes partes del modelo. El punto importante a tener en cuenta es que actualmente Apache MXNet admite el paralelismo de modelos en una sola máquina.
Trabajo de formación distribuida
Los conceptos que se dan a continuación son la clave para comprender el funcionamiento del entrenamiento distribuido en Apache MXNet:
Tipos de procesos
Los procesos se comunican entre sí para lograr el entrenamiento de un modelo. Apache MXNet tiene los siguientes tres procesos:
Obrero
El trabajo del nodo trabajador es realizar entrenamiento en un lote de muestras de entrenamiento. Los nodos de trabajo extraerán pesos del servidor antes de procesar cada lote. Los nodos trabajadores enviarán gradientes al servidor, una vez que se procese el lote.
Servidor
MXNet puede tener varios servidores para almacenar los parámetros del modelo y comunicarse con los nodos trabajadores.
Programador
La función del planificador es configurar el clúster, lo que incluye esperar los mensajes que ha aparecido cada nodo y qué puerto está escuchando. Después de configurar el clúster, el programador permite que todos los procesos conozcan todos los demás nodos del clúster. Es porque los procesos pueden comunicarse entre sí. Solo hay un programador.
Tienda KV
KV stores significa Key-ValueTienda. Es un componente crítico que se utiliza para el entrenamiento con múltiples dispositivos. Es importante porque la comunicación de parámetros a través de dispositivos en una o varias máquinas se transmite a través de uno o más servidores con un KVStore para los parámetros. Entendamos el funcionamiento de KVStore con la ayuda de los siguientes puntos:
Cada valor en KVStore está representado por un key y un value.
A cada matriz de parámetros de la red se le asigna un key y los pesos de esa matriz de parámetros se refieren a value.
Después de eso, los nodos trabajadores pushgradientes después de procesar un lote. Ellos tambiénpull pesos actualizados antes de procesar un nuevo lote.
La noción de servidor KVStore existe solo durante el entrenamiento distribuido y el modo distribuido se habilita llamando mxnet.kvstore.create función con un argumento de cadena que contiene la palabra dist -
kv = mxnet.kvstore.create(‘dist_sync’)
Distribución de claves
No es necesario que todos los servidores almacenen toda la matriz de parámetros o claves, sino que se distribuyen en diferentes servidores. KVStore gestiona de forma transparente dicha distribución de claves en diferentes servidores y la decisión de qué servidor almacena una clave específica se toma al azar.
KVStore, como se mencionó anteriormente, asegura que cada vez que se extrae la clave, su solicitud se envía a ese servidor, que tiene el valor correspondiente. ¿Qué pasa si el valor de alguna clave es grande? En ese caso, se puede compartir entre diferentes servidores.
Datos de entrenamiento divididos
Como usuarios, queremos que cada máquina trabaje en diferentes partes del conjunto de datos, especialmente cuando se ejecuta entrenamiento distribuido en modo paralelo de datos. Sabemos que, para dividir un lote de muestras proporcionadas por el iterador de datos para el entrenamiento de datos en paralelo en un solo trabajador, podemos usarmxnet.gluon.utils.split_and_load y luego, cargue cada parte del lote en el dispositivo que lo procesará más.
Por otro lado, en el caso del entrenamiento distribuido, al principio necesitamos dividir el conjunto de datos en ndiferentes partes para que cada trabajador tenga una parte diferente. Una vez obtenido, cada trabajador puede usarsplit_and_loadpara volver a dividir esa parte del conjunto de datos en diferentes dispositivos en una sola máquina. Todo esto sucede a través del iterador de datos.mxnet.io.MNISTIterator y mxnet.io.ImageRecordIter Hay dos iteradores de este tipo en MXNet que admiten esta función.
Actualización de pesos
Para actualizar los pesos, KVStore admite los siguientes dos modos:
El primer método agrega los gradientes y actualiza los pesos mediante el uso de esos gradientes.
En el segundo método, el servidor solo agrega gradientes.
Si está utilizando Gluon, hay una opción para elegir entre los métodos indicados anteriormente pasando update_on_kvstorevariable. Vamos a entenderlo creando eltrainer objeto de la siguiente manera -
trainer = gluon.Trainer(net.collect_params(), optimizer='sgd',
optimizer_params={'learning_rate': opt.lr,
'wd': opt.wd,
'momentum': opt.momentum,
'multi_precision': True},
kvstore=kv,
update_on_kvstore=True)
Modos de entrenamiento distribuido
Si la cadena de creación de KVStore contiene la palabra dist, significa que el entrenamiento distribuido está habilitado. A continuación se muestran diferentes modos de entrenamiento distribuido que se pueden habilitar usando diferentes tipos de KVStore:
dist_sync
Como su nombre lo indica, denota entrenamiento distribuido sincrónico. En esto, todos los trabajadores utilizan el mismo conjunto sincronizado de parámetros del modelo al inicio de cada lote.
El inconveniente de este modo es que, después de cada lote, el servidor debe esperar para recibir gradientes de cada trabajador antes de actualizar los parámetros del modelo. Esto significa que si un trabajador choca, detendría el progreso de todos los trabajadores.
dist_async
Como su nombre lo indica, denota entrenamiento distribuido sincrónico. En esto, el servidor recibe gradientes de un trabajador y actualiza inmediatamente su tienda. El servidor utiliza la tienda actualizada para responder a cualquier otro tirón.
La ventaja, en comparación con dist_sync mode, es que un trabajador que termina de procesar un lote puede extraer los parámetros actuales del servidor e iniciar el siguiente lote. El trabajador puede hacerlo, incluso si el otro trabajador aún no ha terminado de procesar el lote anterior. También es más rápido que el modo dist_sync porque puede tardar más épocas en converger sin ningún costo de sincronización.
dist_sync_device
Este modo es el mismo que dist_syncmodo. La única diferencia es que, cuando se utilizan varias GPU en cada nododist_sync_device agrega gradientes y actualiza pesos en GPU mientras que, dist_sync agrega gradientes y actualiza los pesos en la memoria de la CPU.
Reduce la costosa comunicación entre GPU y CPU. Por eso es más rápido quedist_sync. El inconveniente es que aumenta el uso de memoria en la GPU.
dist_async_device
Este modo funciona igual que dist_sync_device modo, pero en modo asincrónico.