Comprensión popular de Megatron-DeepSpeed: la tecnología detrás del modelo de 100 mil millones de parámetros BLOOM

prefacio

Este artículo puede considerarse como la tecnología detrás de "BLOOM, modelo grande de código abierto de 100 mil millones de parámetros , este es el texto original en inglés " y las notas de estudio de artículos relacionados, pero se han corregido algunos detalles y errores, y se han realizado una gran cantidad de explicaciones. agregado para que sea más fácil de leer Claro y fácil de entender

La primera parte de BLOOM y el Megatron-DeepSpeed ​​detrás

1.1 Detalles del entrenamiento de BLOOM: hardware/puntos de control/conjunto de datos

La arquitectura del modelo de BLOOM   es muy similar a GPT3, pero se han agregado algunas mejoras. El entrenamiento del modelo 176B BLOOM tardará aproximadamente 3,5 meses en completarse (aproximadamente 1 millón de horas de cálculo) de marzo a julio de 2022. El siguiente es su entrenamiento . Algunos detalles

hardware de entrenamiento

  • GPU: 384 GPU NVIDIA A100 de 80 GB (48 nodos) + 32 GPU de repuesto
  • 8 GPU por nodo, 4 interconexiones entre tarjetas NVLink, 4 enlaces OmniPath
  • CPU: Procesador AMD EPYC 7543 de 32 núcleos
  • Memoria de la CPU: 512 GB por nodo
  • Memoria GPU: 640 GB por nodo
  • Conexión entre nodos: se utiliza una tarjeta de red Omni-Path Architecture (OPA) y la topología de la red es un árbol grueso sin bloqueo
  • NCCL - Red de Comunicaciones: una subred totalmente dedicada
  • Red Disk IO: GPFS compartido con otros nodos y usuarios

Puntos de control

  • principales puntos de control
  • Cada punto de control contiene un estado optimizador con una precisión de fp32 y un peso con una precisión de bf16 + fp32, y ocupa un espacio de almacenamiento de 2,3 TB. Si sólo se ahorra el peso del bf16, sólo se ocuparán 329 GB de espacio de almacenamiento.

conjunto de datos

1.2 Megatron-DeepSpeed:

 El modelo 176B BLOOM se entrena utilizando  Megatron-DeepSpeed , que combina dos técnicas principales:

El equipo de DeepSpeed ​​​​combinó el primer elemento a continuación con los tres últimos

  • Paralelismo tensorial en Megatron-LM (Paralelismo tensorial, que puede entenderse como un tipo de paralelismo modelo),
    cada tensor se divide en varios bloques, por lo que cada porción del tensor se ubica en su GPU designada, en lugar de dejar residir todo el tensor. en una sola GPU. Durante el procesamiento, cada fragmento se procesa por separado y en paralelo en una GPU diferente, y los resultados se sincronizan al final del paso. Esto se llama paralelismo horizontal, porque la división horizontal
  • El Optimizador de Redundancia Cero (ZeRO para abreviar, que es el núcleo de la biblioteca Microsoft DeepSpeed)
    también realiza una fragmentación del tensor similar a TP, pero todo el tensor se reconstruirá a tiempo para cálculos directos o inversos, por lo que no hay modelo. necesita ser modificado. También admite varias técnicas de descarga para compensar la memoria limitada de la GPU.
  • Paralelismo de datos (Paralelismo de datos)
    Las mismas configuraciones y modelos se replican en múltiples copias, y cada copia recibe una copia diferente de datos cada vez. El procesamiento se realiza en paralelo, con todos los recursos compartidos sincronizados al final de cada paso de capacitación.
  • Los modelos de paralelismo de canalización (también conocido como paralelismo de canalización)
    se dividen verticalmente (es decir, por capa) en varias GPU, de modo que solo una o más capas del modelo se colocan en una sola GPU. Cada GPU procesa diferentes etapas del proceso en paralelo y procesa una parte del lote.

Desarrollé una implementación paralela 3D, Megatron-Deepspeed, que hace que el entrenamiento distribuido de modelos de lenguaje a gran escala con más de 100 mil millones de parámetros, como BLOOM, sea más fácil, eficiente y efectivo.

Tenga en cuenta que la versión BigScience de Megatron-DeepSpeed ​​​​del equipo BLOOM  se basa en el código base original  de Megatron-DeepSpeed  , pero agrega bastantes códigos además.

La siguiente tabla enumera qué componentes de cada uno de los dos marcos utilizamos para entrenar BLOOM.

componentes Velocidad profunda Megatrón-LM
Paralelo de datos ZeRO
tensorial paralelo
Tubería paralela
Optimizador BF16
Función del núcleo de fusión CUDA
Cargador de datos

Tenga en cuenta que tanto Megatron-LM como DeepSpeed ​​​​tienen implementaciones de paralelismo canalizado y optimizador BF16, pero usamos la implementación de DeepSpeed ​​porque están integradas en ZeRO.


La segunda parte es Paralelismo tensorial (Paralelismo tensorial, un tipo de paralelismo modelo)

En el paralelismo tensorial (TP), cada GPU procesa solo una parte de un tensor y las operaciones de agregación se activan solo cuando ciertos operadores requieren el tensor completo.

Como todos sabemos, hay dos módulos principales de Transformer: 一个自注意力层 + 残差连接,una capa completamente conectada MLP + conexión residual

En 2019, NVIDIA aprobó  el  documento Megatron-LM ( Entrenamiento eficiente de modelos de lenguaje a gran escala en clústeres de GPU ), y su parte del producto escalar se puede escribir como Y = GeLU(XA), donde  X y  Y son vectores de entrada y salida, y  A son matrices de peso.

Es fácil ver cómo la multiplicación de matrices se puede dividir en varias GPU si se representa en forma de matriz, como se muestra en el siguiente diagrama (indicado como Figura 1 ):

2.1 Paralelización de MLP: la matriz A de peso se corta verticalmente y la matriz B se corta horizontalmente y finalmente FUSIONA

No subestimes el diagrama esquemático anterior. De hecho, contiene muchos detalles que vale la pena reflexionar una y otra vez. Los detalles se muestran en la siguiente figura (indicada como Figura 2 ) .

  1. Para la entrada X, su número de filas es el btamaño por la longitud de la secuencia yo, y el número de columnas es el ancho de la capa oculta, es decir, k
    el módulo de su capa oculta son en realidad dos capas completamente conectadas.
  2. Suponiendo que el peso de la primera capa oculta es A( el número de filas es K, el número de columnas es K', K' es generalmente 4 veces K ), primero realice la multiplicación de matrices X\cdot Ay luego conecte una función de activación \sigmacomo GELU ( GELU es similar a El punto de inflexión de ReLU desciende suavemente )
  3. Suponiendo que el peso de la segunda capa oculta es B( número de filas K', número de columnas K ), el resultado final\sigma (X\cdot A)B = Y
  4. A continuación, veamos cómo dividir. Es mejor usar varias GPU para el paralelismo.
    Si los datos de entrada son relativamente grandes, elija hacer el paralelismo de datos primero, es decir, dividir la entrada. XSi
    el modelo en sí es relativamente grande, entonces Elija hacerlo primero. El modelo es paralelo, es decir, la matriz Ase divide y el método de división se divide en dos
    \flecha correcta  tipos. El primero ( correspondiente a la parte inferior de la Figura 1 arriba ): la matriz Aes dividir horizontalmente por fila ( Xluego la columna correspondiente se divide verticalmente, lo que resulta en El resultado es que se requiere comunicación entre las dos GPU )
    \flecha correcta  El segundo tipo ( correspondiente a la parte superior de la Figura 1 arriba ): la matriz Ase desmonta verticalmente por columna, como se muestra en la Figura 2 anterior, una columna es azul y la otra es verde ( luego Xse divide en filas correspondientemente y horizontalmente, o Xdebe haber una copia en ambas GPU y no se requiere comunicación adicional en este momento )
  5. Después de confirmar el segundo método de división ( es decir, la matriz Ase divide verticalmente por columna ), multiplique Xpara obtener una matriz grande X\cdot Ay luego Bcorte la matriz por fila ( como se muestra en la Figura 2 anterior, la segunda matriz es la matriz B, el azul se coloca en la GPU 0 y el verde se coloca en la GPU 1 ), y la CHAcolumna y la fila de la matriz finales Bmultiplican la matriz , y el tamaño obtenido es consistente con el tamaño de, pero el resultado es solo ( refiriéndose al número de GPU ) , en otras palabras, al realizar la multiplicación de matrices para obtener primero  y luego obtener   un vector de salida   , se pueden ingresar de forma independiente en GeLU y finalmente fusionar los resultados del paso 5 anterior para obtener uno completo . Con las operaciones anteriores, podemos actualizar el MLP de cualquier profundidad, solo en cada secuencia Sincronice la GPU. El autor del artículo de Megatron-LM proporcionó una bonita ilustración de esto (indicado en la Figura 3):Y1/Nnorte
    XA_1XA_nnorteY_1 Y_2 \cdots Y_n\left[Y_{1}, Y_{2}\right]=\left[\operatorname{GeLU}\left(X A_{1}\right), \operatorname{GeLU}\left(X A_{2}\right)\right]
    Y

    拆列-拆行

    Aquí  F está el operador de identidad en el pase hacia adelante y todos reducen en el pase hacia atrás, gramo pero todos reducen en el pase hacia adelante y la identidad en el pase hacia atrás.

2.2 Paralelización de la capa de atención de múltiples cabezales: cada cabezal se calcula por separado

¡Paralelizar capas de atención de múltiples cabezas es aún más simple ya que son inherentemente paralelas debido a múltiples cabezas independientes! Como se muestra en la siguiente figura (marcada como Figura 4)

  1. Para la Xmatriz de entrada, el número de filas sigue siendo el btamaño por la longitud de la secuencia yo(suponiendo que el tamaño del lote es 1), y el número de columnas es k, en el mecanismo de autoatención ( si olvida qué mecanismo de autoatención Es decir, lea este artículo para obtener más detalles. La tercera parte de Transformer Notes ), la entrada Xse copiará en tres copias, correspondientes a: matriz Xvectorial q k V(similar a tres clones)
  2. En cuanto a la atención de múltiples cabezas, la dimensión de la cabeza es k/h, asumiendo que, después de eso, para cada vector de palabra h=2en la matriz de entrada de cada cabeza , hará un producto escalar escalar con el vector del contexto respectivo, luego hará softmax para obtenga un puntaje o peso de atención, y luego hágalo con Suma ponderada para obtener un resultado y finalmente multiplíquelo por una proyección para obtener un resultadoXqkVl \veces k/h
    k\veces kl \veces k
  3. El proceso de cálculo del segundo cabezal es similar
    . Encontrará que el cálculo de cada cabezal es independiente y paralelo sin afectarse entre sí, lo que significa que un cabezal se puede colocar en GPU 0 (indicado en azul ) y el otro cabezal puede se coloca en la GPU 1 ( indicada en verde

    Todo el proceso se muestra en la siguiente figura (indicada como Figura 5)

Es necesario prestar especial atención a:

  1. Dado que hay dos reducciones totales por capa en la propagación hacia adelante y hacia atrás, TP requiere una interconexión muy rápida entre dispositivos. Por lo tanto, a menos que tenga una red muy rápida, no se recomienda realizar TP en varios nodos. En nuestra configuración de hardware para entrenar BLOOM, la velocidad entre nodos es mucho más lenta que PCIe. De hecho, si el nodo tiene 4 GPU, es mejor un grado de TP máximo de 4. Si necesita un grado de TP de 8, debe utilizar un nodo con al menos 8 GPU
  2. Este componente es implementado por Megatron-LM. Megatron-LM ha ampliado recientemente la capacidad de tensor paralelo y ha agregado una capacidad de secuencia paralela para operadores que son difíciles de usar el algoritmo de segmentación antes mencionado. Por ejemplo, el documento LayerNorm Reduction Activation Recomputation in Large Transformer Models  proporciona información detallada sobre esta tecnología. El paralelismo de secuencia se desarrolló después del entrenamiento de BLOOM, por lo que el entrenamiento de BLOOM no utiliza esta técnica.

2.3 Paralelización para entrada y salida

A continuación, veamos la paralelización de entrada y salida, como se muestra en la siguiente figura (indicada como Figura 6).

  1. Para la entrada, Xes una bmatriz yopor el tamaño del lote y la longitud de la secuencia, que almacena oraciones línea por línea, y la capa de incrustación es un vocabulario con filas de vocabulario (equivalente a un vocabulario) y columnas de K. todo el diccionario se puede
    cortar horizontalmente (por ejemplo, coloque la parte superior en la GPU 0 con una marca azul y coloque la mitad inferior en la GPU 1 con una marca verde) y obtenga un resultado buscando en la b\veces l \veces ktabla
  2. Para la salida, el número de filas es y el número de columnas es K. Después de pasar por el vocabulario, se obtiene b\veces luna salida, la mitad izquierda de la cual se puede colocar en la GPU 0 y la mitad derecha se puede colocar en la GPU 1. Cada línea de salida se puede sumar horizontalmente, pero V b\veces l \veces vpuede ser relativamente grande, como decenas de miles, por supuesto, cada GPU puede contar su propia parte.
    b\veces l \veces v

Parte III Datos paralelos y ZeRO

La mayoría de los usuarios con solo unas pocas GPU probablemente estén familiarizados con  (DDP), que es la documentaciónDistributedDataParallel correspondiente  de PyTorch . En este enfoque, los modelos se replican completamente en cada GPU y luego todos los modelos sincronizan sus estados entre sí después de cada iteración. Este método puede acelerar el entrenamiento y resolver problemas invirtiendo más recursos de GPU. Pero tiene la limitación de que sólo funciona si el modelo cabe en una única GPU.

3.1 Paralelismo de datos ZeRO

3.1.1 Cero 1

En 2020, el equipo de Microsoft DeepSpeed ​​​​propuso el Optimizador de redundancia cero (ZeRO para abreviar) a través del artículo " Zero: optimizaciones de memoria para entrenar modelos de billones de parámetros ", pero la optimización es un tema eterno, por lo que el equipo de DeepSpeed ​​​​ha publicado tres Artículos relacionados con ZeRO en los últimos años (el método propuesto en el último artículo se denomina ZeRO 3 ), que propone métodos como eliminar parámetros redundantes, introducir CPU y memoria, e introducir NVMe, etc., con un objetivo de De principio a fin: para llevar a cabo la optimización de la memoria de video hasta el final.

La Figura 7 a continuación es una buena descripción del paralelismo de datos de ZeRO (de esta  publicación de blog )

Parece ser relativamente alto, lo que puede dificultarle concentrarse en la comprensión, pero de hecho, el concepto es muy simple. Este es solo el DDP habitual, excepto que en lugar de que cada GPU replique los parámetros completos del modelo, los gradientes y el estado del optimizador, cada GPU almacena solo una parte de ellos. Durante las ejecuciones posteriores, cuando se requieren los parámetros completos de una capa determinada, todas las GPU se sincronizan para proporcionarse entre sí las piezas que faltan, nada más.

3.1.2 Cero 2

// Para actualizarse

3.1.3 Cero 3

Para actualizarse..


Lo siguiente se cambiará , 8.25 a las 4 p.m.

La cuarta parte del oleoducto es paralela.

El paralelismo de canalización ingenuo (PP ingenuo) consiste en distribuir capas de modelo en grupos en múltiples GPU y simplemente mover datos de una GPU a otra como si fuera una GPU compuesta grande. El mecanismo es relativamente simple: vincula el  .to() método de capa deseado al dispositivo correspondiente y ahora, cada vez que los datos ingresan o salen de estas capas, las capas cambiarán los datos al mismo dispositivo que la capa y el resto seguirá igual.

En realidad, esto es paralelismo de modelo vertical, porque si recuerda cómo dibujamos la topología de la mayoría de los modelos, en realidad dividimos las capas del modelo verticalmente. Por ejemplo, si la siguiente imagen muestra un modelo de 8 capas:

===================  ===================
|  0 | 1 | 2 | 3  |  |  4 | 5 | 6 | 7  |
===================  ===================
        GPU0                 GPU1

Lo cortamos verticalmente en 2 partes, colocando las capas 0-3 en GPU0 y las capas 4-7 en GPU1.

Ahora, cuando los datos se pasan de la capa 0 a la capa 1, de la capa 1 a la capa 2 y de la capa 2 a la capa 3, es como un paso hacia adelante normal en una sola GPU. Pero cuando los datos necesitan pasar de la capa 3 a la capa 4, deben transferirse de GPU0 a GPU1, lo que introduce una sobrecarga de comunicación. Si las GPU participantes están en el mismo nodo de computación (por ejemplo, la misma máquina física), la transferencia es muy rápida, pero si las GPU están en diferentes nodos de computación (por ejemplo, varias máquinas), la sobrecarga de comunicación puede ser mucho mayor.

Luego, las capas 4 a 5 a 6 a 7 vuelven a ser como modelos normales, y cuando la capa 7 está terminada, generalmente necesitamos enviar datos a la capa 0 donde están las etiquetas (o enviar las etiquetas a la última capa). Ahora se puede calcular la pérdida y se puede utilizar el optimizador para actualizar los parámetros.

pregunta:

  • ¿Por qué este método se llama paralelismo ingenuo de canalización y cuáles son sus defectos? Principalmente porque el esquema tiene todas las GPU inactivas menos una en un momento dado. Entonces, si usas 4 GPU, casi cuadriplicarás la cantidad de memoria en una sola GPU, y otros recursos (como la computación) son prácticamente inútiles. Agregue la sobrecarga de copiar datos entre dispositivos. Por lo tanto, 4 tarjetas de 6 GB en paralelo utilizando una canalización ingenua podrán contener el modelo del mismo tamaño que 1 tarjeta de 24 GB, que se entrena más rápido porque no tiene gastos generales de transferencia de datos. Pero, por ejemplo, si tiene una tarjeta de 40 GB, pero necesita ejecutar un modelo de 45 GB, puede usar 4 tarjetas de 40 GB (lo cual es suficiente, porque también hay gradientes y estados optimizadores que requieren memoria de video).

  • Compartir incrustaciones puede requerir copiar de un lado a otro entre GPU. El paralelismo canalizado (PP) que utilizamos es casi el mismo que el ingenuo PP anterior, pero resuelve el problema de inactividad de la GPU al dividir los lotes entrantes en microlotes y crear artificialmente canalizaciones que permiten que diferentes GPU participen en el proceso de cálculo simultáneamente.

La siguiente figura es del  documento GPipe . La parte superior representa el esquema PP ingenuo y la parte inferior es el método PP:

mp-pp

En la mitad inferior de la figura es fácil ver que el PP tiene menos zona muerta (lo que significa que la GPU está inactiva), es decir, menos "burbujas".

El grado de paralelismo de los dos esquemas en la figura es 4, es decir, la tubería se compone de 4 GPU. Entonces, hay cuatro caminos directos de F0, F1, F2 y F3, y luego el camino inverso de B3, B2, B1 y B0.

PP introduce un nuevo hiperparámetro para sintonizar, llamado  块 (chunks). Define cuántos bloques de datos se envían secuencialmente a través del mismo nivel de tubería. Por ejemplo, en la mitad inferior de la figura, puedes ver  chunks = 4. GPU0 ejecuta la misma ruta de avance en los fragmentos 0, 1, 2 y 3 (F0,0, F0,1, F0,2, F0,3) y luego espera hasta que las otras GPU terminen su trabajo antes de que GPU0 comience a funcionar nuevamente, ejecute la ruta de retroceso para los bloques 3, 2, 1 y 0 (B0,3, B0,2, B0,1, B0,0).

Tenga en cuenta que esto es conceptualmente lo mismo que los pasos de acumulación de gradiente (GAS). PyTorch lo llama  y DeepSpeed ​​lo llama  GAS.

Porque  PP introduce el concepto de microlotes (MBS). DP divide el tamaño del lote global en lotes pequeños, por lo que si el grado de DP es 4, el tamaño del lote global 1024 se dividirá en 4 tamaños de lote pequeños y cada tamaño de lote pequeño será 256 (1024/4). Y si   el número (o GAS) es 32, terminamos con un tamaño de micro lote de 8 (256/32). Cada etapa del tubo procesa un microlote a la vez.

La fórmula para calcular el tamaño de lote global para la configuración DP + PP es:  mbs*chunks*dp_degree ( 8*32*4=1024).

Volvamos atrás y miremos la imagen nuevamente.

Usar  chunks=1 lo que se obtiene es PP ingenuo, lo cual es muy ineficiente. Y con números muy grandes   , se termina con microlotes pequeños, que probablemente tampoco sean muy eficientes. Por lo tanto, hay que experimentar para encontrar   el número que haga el uso más eficiente de la GPU.

El gráfico muestra que hay burbujas de tiempo "muerto" que no se pueden paralelizar porque la última  forward etapa tiene que esperar a  backward que termine el oleoducto. Luego, el problema de encontrar el   número óptimo para que todas las GPU participantes puedan lograr una alta utilización simultánea se transforma en realidad en minimizar el número de burbujas.

Este mecanismo de programación se llama  全前全后. Algunas otras opciones son  el tándem  y  el tándem escalonado .

Si bien tanto Megatron-LM como DeepSpeed ​​​​tienen sus propias implementaciones del protocolo PP, Megatron-DeepSpeed ​​​​utiliza la implementación de DeepSpeed ​​porque está integrada con otras características de DeepSpeed.

Otra cuestión importante aquí es el tamaño de la matriz de incrustación de palabras. Si bien generalmente las matrices de incrustación de palabras requieren menos memoria que los bloques transformadores, en el caso de BLOOM con un vocabulario de 250k, la capa de incrustación requiere 7,2 GB para pesos bf16, en comparación con solo 4,9 GB para el bloque transformador. Por lo tanto, tuvimos que hacer que Megatron-Deepspeed tratara la capa de incrustación como un bloque transformador. Entonces tenemos una tubería de 72 etapas, 2 de las cuales están dedicadas a la integración (la primera y la última). Esto nos permite equilibrar el consumo de memoria de la GPU. Si no hiciéramos esto, la primera y la última etapa consumirían mucha memoria de la GPU y el 95% del uso de la memoria de la GPU sería muy pequeño, por lo que el entrenamiento sería muy ineficiente.

DP+PP

Hay un diagrama en el Tutorial paralelo de DeepSpeed ​​​​Pipeline   que demuestra cómo combinar DP y PP, como se muestra a continuación.

dp-pp-2d

Lo importante que hay que entender aquí es que el rango DP 0 no puede ver GPU2 y el rango DP 1 no puede ver GPU3. Para DP, solo hay GPU 0 y 1, y se les envían datos. GPU0 usa PP para descargar "en secreto" parte de su carga a GPU2. Asimismo, GPU1 también recibirá ayuda de GPU3.

Dado que se requieren al menos 2 GPU para cada dimensión, aquí se requieren al menos 4 GPU.

DP+PP+TP

Para un entrenamiento más eficiente, se pueden combinar PP, TP y DP, lo que se denomina paralelismo 3D, como se muestra en la siguiente figura.

dp-pp-tp-3d

Esta figura es de la publicación del blog " Paralelismo 3D: escalado a billones de modelos de parámetros "), que también es un buen artículo.

Dado que necesita al menos 2 GPU por dimensión, aquí necesita al menos 8 GPU para un paralelismo 3D completo.

Cero DP+PP+TP

Una de las características principales de DeepSpeed ​​es ZeRO, que es una versión mejorada súper escalable de DP, que hemos   discutido en la sección Paralelismo de datos de ZeRO . Suele ser una función independiente y no requiere PP ni TP. Pero también se puede combinar con PP, TP.

Cuando ZeRO-DP se combina con PP (y por lo tanto con TP), normalmente solo habilita la fase 1 de ZeRO, que solo fragmenta el estado del optimizador. La etapa 2 de ZeRO también fragmenta los gradientes y la etapa 3 también fragmenta los pesos del modelo.

Si bien es teóricamente posible utilizar ZeRO etapa 2 con paralelismo de canalización, puede tener un impacto negativo en el rendimiento. Cada microlote requiere una comunicación adicional de reducción y dispersión para agregar gradientes antes de la fragmentación, lo que agrega una sobrecarga de comunicación potencialmente significativa. De acuerdo con la naturaleza paralela de la tubería, utilizaremos pequeños microlotes y nos centraremos en el equilibrio entre la intensidad aritmética (tamaño del microlote) y la minimización de las burbujas de la tubería (número de microlotes). Por lo tanto, el aumento de los gastos generales de comunicación perjudica el paralelismo de la canalización.

Además, debido al PP, el número de capas ya es menor de lo normal, por lo que no ahorra mucha memoria. PP ha reducido el tamaño del gradiente  1/PP, por lo que el corte de gradiente sobre esta base no ahorra mucha memoria en comparación con DP puro.

ZeRO stage 3 también se puede utilizar para entrenar modelos de este tamaño, sin embargo, requiere más comunicación que DeepSpeed ​​3D en paralelo. Hace un año, después de una cuidadosa evaluación de nuestro entorno, descubrimos que el paralelismo 3D Megatron-DeepSpeed ​​​​funcionaba mejor. El rendimiento de ZeRO Phase 3 ha mejorado significativamente desde entonces, y si tuviéramos que reevaluarlo hoy, tal vez elegiríamos la Fase 3.

BF16Optimizador

Entrenar enormes modelos LLM con FP16 es un no-no.

 Lo hemos demostrado nosotros mismos  al pasar meses  entrenando el modelo 104B , que, como puede ver en Tensorboard  , fue un fracaso total. En el proceso de luchar contra la siempre divergente pérdida de películas, aprendimos mucho:

104B-falla

También recibimos la misma sugerencia de los equipos Megatron-LM y DeepSpeed ​​después de que entrenaron  el modelo 530B  . El OPT-175B recientemente lanzado   también informó que entrenaron muy duro en el FP16.

En enero supimos que íbamos a entrenar en el A100, que admite el formato BF16. Olatunji Ruwase desarrolló uno para entrenar a BLOOM  BF16Optimizer.

Si no estás familiarizado con este formato de datos, echa un vistazo a su  disposición de bits . La clave del formato BF16 es que tiene el mismo número de exponentes que FP32, por lo que no se desbordará, ¡pero FP16 a menudo se desborda! FP16 tiene un rango de valores máximo de 64k, solo puedes multiplicar números más pequeños. Por ejemplo puedes hacerlo  250*250=62500, pero si lo intentas  255*255=65025, te desbordarás, que es la principal causa de problemas con el entrenamiento. Esto significa que sus pesos deben mantenerse pequeños. Una técnica llamada escala de pérdida ayuda a aliviar este problema, pero el pequeño alcance del FP16 aún puede ser un problema cuando los modelos crecen mucho.

El BF16 no tiene este problema, puedes hacerlo fácilmente  10_000*10_000=100_000_000, sin ningún problema.

Por supuesto, dado que BF16 y FP16 tienen el mismo tamaño, 2 bytes, no hay nada gratis, y la desventaja de usar BF16 es que tiene una precisión muy pobre. Sin embargo, debes recordar que el método de descenso de gradiente estocástico y sus variantes que usamos en el entrenamiento, este método es un poco asombroso, si no encuentras la dirección perfecta en este paso, está bien, lo corregirás en el siguiente. paso Propio.

Ya sea que use BF16 o FP16, hay una copia de los pesos que siempre está en FP32; esto es lo que actualiza el optimizador. Entonces, el formato de 16 bits solo se usa para cálculos, el optimizador actualiza los pesos FP32 con total precisión y luego los convierte al formato de 16 bits para la siguiente iteración.

Todos los componentes de PyTorch se han actualizado para garantizar que realicen cualquier acumulación en FP32, por lo que no se produce pérdida de precisión.

Una cuestión clave es la acumulación de gradientes, que es una de las principales características del paralelismo de tuberías, ya que se acumulan los gradientes procesados ​​por cada microlote. Implementar la acumulación de gradiente en FP32 para la precisión del entrenamiento es fundamental, y esto es exactamente  BF16Optimizer lo que se hizo.

Entre otras mejoras, creemos que el uso del entrenamiento de precisión mixta BF16 convirtió una pesadilla potencial en un proceso relativamente fluido, como se puede ver en el siguiente gráfico de pérdida de película:

176B - Pérdida

Función del núcleo de fusión CUDA

La GPU hace principalmente dos cosas. Puede escribir y leer datos de la memoria de video y realizar cálculos sobre esos datos. Cuando la GPU está ocupada leyendo y escribiendo datos, las unidades informáticas de la GPU están inactivas. Si queremos utilizar la GPU de manera eficiente, queremos mantener el tiempo de inactividad al mínimo.

Una función del kernel es un conjunto de instrucciones que implementan una operación específica de PyTorch. Por ejemplo, cuando lo llamas  torch.add , pasa por un  programador de PyTorch , que decide qué código debe ejecutar en función de los valores de los tensores de entrada y otras variables, y finalmente lo ejecuta. Los kernels CUDA utilizan CUDA para implementar estos códigos y, por lo tanto, solo se ejecutan en GPU NVIDIA.

Ahora, cuando se computa con la GPU  c = torch.add (a, b); e = torch.max ([c,d]) , normalmente lo que hará PyTorch es lanzar dos núcleos separados, uno que suma  a la  b suma  c y  el otro que toma d el máximo de los dos. En este caso, la GPU obtiene la suma de su memoria de video  a ,  brealiza la suma y luego escribe el resultado en la memoria de video. Luego toma la suma  c y  d realiza una operación máxima, luego escribe el resultado nuevamente en la memoria de video.

Si fusionáramos estas dos operaciones, es decir, las pusiéramos en una "función de kernel fusionada" y luego lanzáramos ese kernel, en lugar de escribir el resultado intermedio  c en la memoria de video, lo mantendríamos en los registros de la GPU y solo necesitaríamos Get  d para hacerlo. el cálculo final. Esto ahorra muchos gastos generales y evita que la GPU esté inactiva, por lo que toda la operación es mucho más eficiente.

La función del kernel de fusión hace precisamente eso. Principalmente reemplazan múltiples cálculos discretos y movimiento de datos hacia y desde la memoria de video con cálculos fusionados con muy poco movimiento de datos. Además, algunos núcleos de fusión transforman matemáticamente las operaciones para que ciertas combinaciones de cálculos se puedan realizar más rápido.

Para entrenar BLOOM de forma rápida y eficiente, es necesario utilizar varias funciones de kernel fusionadas CUDA personalizadas proporcionadas por Megatron-LM. En particular, existe un núcleo de fusión LayerNorm y núcleos para varias combinaciones de operaciones de escalado de fusión, enmascaramiento y softmax. Bias Add también está integrado con GeLU a través de la función JIT de PyTorch. Todas estas operaciones están vinculadas a la memoria, por lo que es importante fusionarlas para maximizar la cantidad de cálculo después de cada lectura de la memoria de video. Entonces, por ejemplo, ejecutar Bias Add mientras se ejecuta una operación GeLU cuyo cuello de botella está en la memoria no aumentará el tiempo de ejecución. Estas funciones del kernel se pueden encontrar en  la base del código del repositorio Megatron-LM  .

conjunto de datos

Otra característica importante de Megatron-LM es el eficiente cargador de datos. Antes de que comience el primer entrenamiento, cada muestra de cada conjunto de datos se divide en muestras de longitud de secuencia fija (BLOOM es 2048) y se crea un índice para numerar cada muestra. Con base en los hiperparámetros de entrenamiento, determinaremos la cantidad de épocas en las que cada conjunto de datos debe participar y, en base a esto, crearemos una lista ordenada de índices de muestra y luego la mezclaremos. Por ejemplo, si un conjunto de datos tiene 10 muestras que deben entrenarse para 2 épocas, el sistema primero ordena los  [0, ..., 9, 0, ..., 9] índices de las muestras y luego baraja el orden para crear el orden global final para el conjunto de datos. Tenga en cuenta que esto significa que el entrenamiento no simplemente iterará sobre todo el conjunto de datos y se repetirá; es posible que vea la misma muestra dos veces antes de ver otra, pero al final del entrenamiento el modelo solo verá cada muestra dos veces. Esto ayuda a garantizar una curva de entrenamiento suave durante todo el entrenamiento. Estos índices, incluido el desplazamiento de cada muestra en el conjunto de datos original, se guardan en un archivo para evitar volver a calcularlos cada vez que se inicia el entrenamiento. Finalmente, varios de estos conjuntos de datos se pueden combinar con diferentes pesos en los datos finales utilizados para el entrenamiento.

Incrustar norma de capa

En nuestros esfuerzos por evitar la divergencia del modelo 104B, descubrimos que agregar un LayerNorm adicional después de la primera capa de incrustación de palabras hacía que el entrenamiento fuera más estable.

Esta idea proviene de experimentos con bitsandbytes , que  tiene una  StableEmbedding operación que es una incrustación normal con un LayerNorm inicializado con una función Xavier uniforme.

código de localización

Con base en el artículo  Train Short, Test Long: Attention with Linear Biases Enables Input length Extrapolation , también reemplazamos las incrustaciones posicionales normales con AliBi, que permite la extrapolación de secuencias de entrada más largas que las secuencias de entrada utilizadas para entrenar el modelo. Por lo tanto, aunque entrenemos con secuencias de longitud 2048, el modelo puede manejar secuencias más largas durante la inferencia.

dificultades en el entrenamiento

Con la arquitectura, el hardware y el software implementados, pudimos comenzar la capacitación a principios de marzo de 2022. Desde entonces, sin embargo, las cosas no han ido sobre ruedas. En esta sección, analizamos algunos de los principales obstáculos que encontramos.

Antes de que comience el entrenamiento, hay muchas preguntas que resolver. En particular, encontramos varios problemas que solo aparecieron después de que comenzamos a entrenar en 48 nodos, no a pequeña escala. Por ejemplo, para  CUDA_LAUNCH_BLOCKING=1 evitar que el marco se bloquee, debemos dividir el grupo optimizador en grupos más pequeños; de lo contrario, el marco se bloqueará nuevamente. Puedes   leer más sobre estos en la crónica previa al entrenamiento .

El principal tipo de problemas que se encuentran durante la formación son los fallos de hardware. Dado que se trata de un clúster nuevo con aproximadamente 400 GPU, en promedio experimentamos entre 1 y 2 fallas de GPU por semana. Guardamos un punto de control cada 3 horas (100 iteraciones). Como resultado, perdemos un promedio de 1,5 horas de entrenamiento por semana debido a fallas del hardware. El administrador del sistema Jean Zay luego reemplazará la GPU defectuosa y restaurará el nodo. Mientras tanto, tenemos nodos de repuesto disponibles.

También hemos tenido varios otros problemas que resultaron en un tiempo de inactividad de 5 a 10 horas varias veces, algunos relacionados con errores de interbloqueo en PyTorch, otros debido a espacio insuficiente en el disco. Consulta  las crónicas de entrenamiento si estás interesado en detalles específicos .

Todo este tiempo de inactividad se planificó en el análisis de viabilidad del entrenamiento de este modelo y, en consecuencia, elegimos el tamaño de modelo adecuado y la cantidad de datos que queríamos que consumiera el modelo. Entonces, incluso con estos problemas de tiempo de inactividad, logramos completar la capacitación dentro del tiempo estimado. Como se mencionó anteriormente, se necesitan alrededor de 1 millón de horas de cálculo para completarlo.

Otro problema es que SLURM no fue diseñado para ser utilizado por un grupo de personas. Los trabajos SLURM pertenecen a un único usuario y, si no están presentes, los demás miembros del grupo no pueden hacer nada con el trabajo en ejecución. Contamos con un esquema de terminación que permite a otros usuarios del grupo finalizar el proceso actual sin la presencia del usuario que inició el proceso. Esto funciona muy bien en el 90% de los problemas. Si los diseñadores de SLURM leen esto, agreguen el concepto de grupo Unix para que un trabajo de SLURM pueda ser propiedad de un grupo.

Dado que la capacitación se realiza las 24 horas del día, los 7 días de la semana, necesitamos a alguien de guardia, pero como tenemos gente en Europa y la costa oeste de Canadá, no es necesario que alguien lleve un buscapersonas y somos bastante buenos apoyándonos mutuamente. Por supuesto, hay que vigilar los entrenamientos del fin de semana. Automatizamos la mayoría de las cosas, incluida la recuperación automática de fallas de hardware, pero a veces aún se requiere la intervención humana.


enlace importante

Artículos y artículos

Es imposible para nosotros explicar todo en detalle en este artículo, por lo que si las técnicas presentadas aquí despiertan su curiosidad y le hacen querer aprender más, lea los siguientes artículos:

Megatrón-LM:

Velocidad profunda:

Megatron-LM y Deepspeedeed combinados:

Coartada:

BitsNBytes:

  • Optimizadores de 8 bits mediante cuantificación por bloques  (utilizamos LaynerNorm incorporado en este documento, pero otras partes del documento y sus técnicas también son muy buenas, la única razón por la que no usamos optimizadores de 8 bits es que ya usamos DeepSpeed-ZeRO para ahorrar memoria del optimizador).

Supongo que te gusta

Origin blog.csdn.net/v_JULY_v/article/details/132462452
Recomendado
Clasificación