# Métodos de paralelismo

Las configuraciones de varios GPU son efectivas para acelerar el entrenamiento y para cargar en memoria modelos grandes que de otra forma no cabrían en un solo GPU. Se basa en paralelizar la carga de trabajo entre los GPU. Existen varios tipos de paralelismo, como el paralelismo de datos, el paralelismo de tensores, el paralelismo de pipeline y el paralelismo de modelo. Cada tipo de paralelismo divide la carga de trabajo de manera diferente, ya sea los datos o el modelo.

Esta guía abordará los distintos métodos de paralelismo, su combinación y la elección de una estrategia adecuada para su configuración. Para más detalles sobre el entrenamiento distribuido, consulte la documentación de [Accelerate](https://hf.co/docs/accelerate/index).

Para una guía completa sobre el escalado de modelos de lenguaje de gran tamaño, consulta el [Ultrascale Playbook](https://huggingface.co/spaces/nanotron/ultrascale-playbook), que ofrece estrategias detalladas y mejores prácticas para el entrenamiento a gran escala.

## Estrategia de escalabilidad

Utilice la [Model Memory Calculator](https://huggingface.co/spaces/hf-accelerate/model-memory-usage) para calcular la cantidad de memoria que requiere un modelo. A continuación, consulte la tabla inferior para seleccionar una estrategia según su configuración.

| configuración | escenario | estrategia |
|---|---|---|
| nodo único/multi-GPU | cabe en una sola GPU | DistributedDataParallel o ZeRO |
|  | no cabe en una sola GPU | PipelineParallel, ZeRO o TensorParallel |
|  | la capa del modelo más grande no cabe | TensorParallel o ZeRO |
| multi-nodo/multi-GPU | conectividad rápida entre nodos (NVLink o NVSwitch) | ZeRO o paralelismo 3D (PipelineParallel, TensorParallel, DataParallel) |
|  | conectividad lenta entre nodos | ZeRO o paralelismo 3D (PipelineParallel, TensorParallel, DataParallel) |

## Paralelismo de datos

El paralelismo de datos distribuye uniformemente los datos entre varias GPU. Cada GPU mantiene una copia del modelo y procesa de forma concurrente su porción de los datos. Al final, los resultados de cada GPU se sincronizan y combinan.

La paralelización de datos reduce significativamente el tiempo de entrenamiento al procesar los datos en paralelo, y es escalable según el número de GPUs disponibles. Sin embargo, sincronizar los resultados de cada GPU puede añadir sobrecarga.

Existen dos tipos de paralelismo de datos: DataParallel (DP) y DistributedDataParallel (DDP).

### DataParallel

[DataParallel](https://pytorch.org/docs/stable/generated/torch.nn.DataParallel.html) admite el entrenamiento distribuido en una *sola máquina* con varias GPU.

1. La GPU predeterminada, `GPU 0`, lee un lote de datos y envía un mini lote de estos a las demás GPUs.
2. Un modelo actualizado se replica desde `GPU 0` a las demás GPUs.
3. Se realiza una pasada `forward` en cada GPU y sus salidas se envían a `GPU 0` para calcular la pérdida.
4. La pérdida se distribuye desde `GPU 0` a las demás GPUs para la pasada de `backward`.
5. Los gradientes de cada GPU se envían de vuelta a `GPU 0` y se promedian.

### DistributedDataParallel

[DistributedDataParallel](https://pytorch.org/docs/main/notes/ddp.html) admite el entrenamiento distribuido en *múltiples máquinas* con varias GPU.

1. El proceso principal replica el modelo desde la GPU predeterminada, `GPU 0`, en cada GPU.
2. Cada GPU procesa directamente un mini lote de datos.
3. Los gradientes locales se promedian entre todas las GPU durante la pasada de `backward`.

Se recomienda DDP porque reduce la sobrecarga de comunicación entre GPUs, aprovecha de manera eficiente cada GPU y escala a más de una máquina.

### Paralelismo de datos ZeRO

[Zero Redundancy Optimizer](https://www.deepspeed.ai/tutorials/zero/) es un tipo de paralelismo de datos más eficiente en memoria. Mejora significativamente la eficiencia de memoria al particionar parámetros, gradientes y estados del optimizador entre los procesos de paralelismo de datos para reducir el uso de memoria. Hay tres etapas de ZeRO:

- La etapa 1 particiona los estados del optimizador
- La etapa 2 particiona los estados del optimizador y del gradiente
- La etapa 3 particiona el optimizador, los gradientes y los parámetros

     

## Paralelismo de modelos

La paralelización de modelos distribuye un modelo en varias GPU. Existen varias formas de dividir un modelo, pero el método típico distribuye las capas del modelo entre las GPU. En la pasada de `forward`, la primera GPU procesa un lote de datos y lo pasa al siguiente grupo de capas en la GPU siguiente. Para la pasada de `backward`, los datos se envían hacia atrás desde la capa final hasta la primera capa.

El paralelismo de modelos es una estrategia útil para entrenar modelos que son demasiado grandes para caber en la memoria de una sola GPU. Sin embargo, la utilización de las GPUs no está equilibrada, ya que solo una GPU está activa a la vez. Además, pasar los resultados entre GPUs añade sobrecarga de comunicación y puede convertirse en un cuello de botella.

## Paralelismo de pipeline

La paralelización por pipeline es conceptualmente muy similar a la paralelización de modelo, pero es más eficiente porque reduce la cantidad de tiempo de GPU inactivo. En lugar de esperar a que cada GPU termine de procesar un lote de datos, la paralelización por pipeline crea *micro-lotes* de datos. En cuanto se termina un micro-lote, se pasa a la siguiente GPU. De esta manera, cada GPU puede procesar parte de los datos de forma concurrente sin esperar a que la otra GPU termine por completo el procesamiento de un mini lote de datos.

El paralelismo de pipeline comparte las mismas ventajas que el paralelismo de modelo, pero optimiza la utilización de la GPU y reduce el tiempo inactivo. Sin embargo, el paralelismo de pipeline puede ser más complejo, ya que los modelos pueden necesitar reescribirse como una secuencia de módulos [nn.Sequential](https://pytorch.org/docs/stable/generated/torch.nn.Sequential.html), y tampoco es posible reducir por completo el tiempo inactivo, porque la última pasada de `forward` también debe esperar a que termine la pasada de `backward`.

## Paralelismo de tensores

El paralelismo de tensores distribuye los cálculos de tensores grandes entre varias GPU. Los tensores se dividen horizontal o verticalmente y cada porción se procesa en una GPU independiente. Cada GPU realiza sus cálculos sobre su porción de tensor y los resultados se sincronizan al final para reconstruir el resultado final.

La paralelización de tensores es eficaz para entrenar modelos grandes que no caben en la memoria de una sola GPU. Además, es más rápida y eficiente, ya que cada GPU puede procesar su porción de tensor en paralelo, y puede combinarse con otros métodos de paralelización. Sin embargo, al igual que otros métodos de paralelización, la paralelización de tensores añade una sobrecarga de comunicación entre las GPUs.

Consulte la guía de [Tensor parallelism](./perf_infer_gpu_multi) para aprender a usarla para inferencia.

## Paralelismo híbrido

Los métodos de paralelismo pueden combinarse para lograr un mayor ahorro de memoria y entrenar modelos con miles de millones de parámetros de manera más eficiente.

### Paralelismo de datos y paralelismo de pipeline

El paralelismo de datos y de pipeline distribuye los datos entre las GPU y divide cada mini lote de datos en micro lotes para lograr el paralelismo de pipeline.

Cada rango de paralelismo de datos trata el proceso como si solo hubiera una GPU en lugar de dos, pero las GPUs 0 y 1 pueden descargar micro-lotes de datos a las GPUs 2 y 3 y reducir el tiempo de inactividad.

Este enfoque optimiza el procesamiento de datos en paralelo al reducir la utilización inactiva de la GPU.

     

### Paralelismo de datos ZeRO, paralelismo de pipeline y paralelismo de modelo (paralelismo 3D)

La combinación de paralelismo de datos, de pipeline y de modelo forma el [paralelismo 3D](https://www.microsoft.com/en-us/research/blog/deepspeed-extreme-scale-model-training-for-everyone/) para optimizar la eficiencia de memoria y de cómputo.

La eficiencia de memoria se logra dividiendo el modelo entre varias GPUs y, además, segmentándolo en etapas para crear una pipeline. Esto permite que las GPUs trabajen en paralelo sobre micro-lotes de datos, reduciendo el uso de memoria del modelo, el optimizador y las activaciones.

La eficiencia de cómputo se habilita mediante la paralelización de datos ZeRO, donde cada GPU solo almacena una porción del modelo, el optimizador y las activaciones. Esto permite una mayor banda ancha de comunicación entre los nodos de paralelismo de datos, ya que la comunicación puede ocurrir de forma independiente o en paralelo con las otras etapas de la pipeline.

Este enfoque es escalable a modelos extremadamente grandes con billones de parámetros.

     

