# Paralelismo de expertos

[Paralelismo de expertos](https://huggingface.co/spaces/nanotron/ultrascale-playbook?section=expert_parallelism) es una estrategia de paralelismo para [modelos de mezcla de expertos (MoE)](https://huggingface.co/blog/moe). La capa de avance de cada experto reside en un acelerador de hardware diferente. Un router envía los tokens a los expertos correspondientes y recopila los resultados. Este enfoque permite escalar los modelos a un número de parámetros mucho mayor sin aumentar el costo de cómputo, ya que cada token activa solo unos pocos expertos.

## DistributedConfig

Active el paralelismo de expertos con la clase [DistributedConfig](/docs/transformers/main/es/expert_parallelism#transformers.DistributedConfig) y el argumento `ep_size`. La mayoría de los modelos MoE usan [token dispatch](#token-dispatch) de forma predeterminada, por lo que `ep_size` se puede establecer de forma independiente de `tp_size`. Algunos, como Llama 4 y Gemma 4, siguen usando enmascarado y all-reduce de forma predeterminada (`"ep_router"` y `"moe_tp_experts"` en `model.ep_plan`). El enmascarado también está disponible en cualquier modelo con una sobrescritura de `ep_plan` y requiere `ep_size=tp_size` para que cada rank de un grupo de expertos reciba los mismos tokens.

```py
import os

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.distributed.configuration_utils import DistributedConfig

distributed_config = DistributedConfig(
    tp_size=int(os.environ["WORLD_SIZE"]),
    ep_size=int(os.environ["WORLD_SIZE"]),
)

model = AutoModelForCausalLM.from_pretrained(
    "openai/gpt-oss-120b",
    distributed_config=distributed_config,
)
```

Cada modelo MoE define dos planes en su configuración: `base_model_tp_plan` para los módulos densos y `base_model_ep_plan` para los expertos. Se exponen en el modelo cargado como `model.tp_plan` y `model.ep_plan`. Con `tp_size > 1` y `ep_size > 1`, se aplican ambos: el plan de [paralelismo de tensor](./perf_infer_gpu_multi) divide la atención y los MLP densos, y el plan de paralelismo de expertos divide los expertos. Las reglas de EP tienen prioridad sobre las reglas de TP para los mismos módulos, por lo que los pesos de los expertos se dividen una sola vez, mediante el plan de EP. En el plan de EP, el estilo `GroupedGemmParallel` divide los pesos de los expertos a lo largo de la dimensión de expertos para que cada rank cargue solo sus expertos locales, y `ep_router` enmascara los expertos que residen en otros ranks antes de que un all-reduce combine las salidas de los expertos.

`tp_plan` se aplica solo cuando `tp_size > 1`, y `ep_plan` solo cuando `ep_size > 1`. Con TP habilitado y EP deshabilitado, se aplica el plan de TP completo, incluidas las reglas de expertos.

La regla de avance de expertos en `ep_plan` selecciona cómo llegan los tokens a los expertos:

| regla | mecanismo | layout |
| :--- | :--- | :--- |
| `"moe_tp_experts"` con `"ep_router"` en el router | enmascarado y all-reduce: cada rank ejecuta sus expertos locales en todo el lote, el router enmascara a los demás y un all-reduce combina las salidas | `ep_size=tp_size` |
| `"ep_dispatch_experts"` | [token dispatch](#token-dispatch): cada rank conserva sus propios tokens y solo intercambia las parejas (token, expert) enrutadas mediante dos colectivas all-to-all | `ep_size` un múltiplo de `tp_size` que divide a `fsdp_size * tp_size` |

> [!TIP]
> `enable_expert_parallel=True` sin `ep_size` está en desuso y emite una `FutureWarning`. Solo fragmenta los expertos, como `ep_size=tp_size, tp_plan={}`.

Lance su script de inferencia con [torchrun](https://pytorch.org/docs/stable/elastic/run.html). El número de procesos debe ser igual a `tp_size * fsdp_size * pp_size`, y `ep_size` debe dividir exactamente el número de expertos.

```zsh
torchrun --nproc-per-node 8 your_script.py
```

### Sobrescribir los planes

Pase `tp_plan={...}` o `ep_plan={...}` a [DistributedConfig](/docs/transformers/main/es/expert_parallelism#transformers.DistributedConfig) para sobrescribir reglas individuales de los planes predefinidos. Las reglas no especificadas se conservan y los planes fusionados se almacenan en el modelo. Cada clave debe coincidir con un módulo, un parámetro o una entrada existente del plan; de lo contrario, la carga genera un `ValueError` antes de que se fragmente nada. Use la ruta completa tal como se ve desde el modelo cargado, por lo tanto `model.layers.*` para un LM causal y `layers.*` para su modelo base.

```py
distributed_config = DistributedConfig(
    tp_size=4,
    ep_size=4,
    tp_plan={"model.layers.*.self_attn.q_proj": "colwise_rep"},
    ep_plan={"model.layers.*.mlp.experts.down_proj": "grouped_gemm"},
)
```

Proporcionar un plan no infiere los tamaños de paralelismo: establezca `tp_size` y `ep_size` explícitamente.

La mayoría de los modelos MoE usan `"ep_dispatch_experts"` por defecto. Para usar enmascarado y all-reduce en su lugar, establezca `ep_size=tp_size` y anule tanto las reglas de forward del router como las de los expertos (el nombre del módulo del router depende del modelo, p. ej. `mlp.router` en gpt-oss):

```py
distributed_config = DistributedConfig(
    tp_size=4,
    ep_size=4,
    ep_plan={
        "model.layers.*.mlp.gate": "ep_router",
        "model.layers.*.mlp.experts": "moe_tp_experts",
    },
)
```

Por el contrario, sobrescriba la regla de avance de los expertos de un modelo cuyo plan utiliza enmascarado con `"ep_dispatch_experts"` para usar el despacho de tokens. En ese caso, la regla del enrutador se ignora, ya que el despacho necesita los ids globales de los expertos para encontrar el propietario de cada experto.

## Despacho de tokens

Con el despacho de tokens, cada rango entrena con su propia parte del lote. En cada capa MoE, un rango enruta sus tokens, envía cada par (token, experto) al rango que posee el experto mediante un all-to-all, ejecuta sus expertos locales sobre lo que recibe, obtiene los resultados de vuelta con un segundo all-to-all y los combina con los pesos de enrutamiento. Solo viajan las activaciones enrutadas y las salidas de los expertos, y ningún rango calcula expertos para tokens que no posee.

```py
from transformers import AutoModelForCausalLM
from transformers.distributed import DistributedConfig

distributed_config = DistributedConfig(
    tp_size=1,
    fsdp_size=8,
    ep_size=4,
)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-30B-A3B", distributed_config=distributed_config)
```

Con `tp_size=1`, `ep_size` debe dividir a `fsdp_size` y al número de expertos, y la atención no está limitada por `num_key_value_heads`. Para el resto del modelo:

- Los parámetros que están fuera de los expertos se fragmentan con [FSDP2](./fsdp) a través de `fsdp`, lo que reduce sus gradientes.
- Los expertos se fragmentan en `ep` y, cuando `efsdp_size = fsdp_size * tp_size // ep_size` es mayor que uno, además se fragmentan con FSDP en `efsdp`. Siempre están envueltos con FSDP, por lo que `fsdp_mixed_precision` y `fsdp_cpu_offload` también se aplican a ellos y [save_pretrained()](/docs/transformers/main/es/main_classes/model#transformers.PreTrainedModel.save_pretrained) los reúne como cualquier otro parámetro.
- Un grupo de paralelismo de expertos contiene `ep_size / tp_size` lotes, por lo que el gradiente de un experto es la suma sobre esa cantidad de lotes. La reducción de `efsdp` divide por `fsdp_size` en lugar de por el tamaño de su grupo, lo que produce la misma media por lote que FSDP2 utiliza para los módulos densos.
- Cada experto local también procesa una fila de relleno de ceros por capa. Un rango cuyos expertos no recibieron tokens aún participa en el reverse all-to-all y en la reducción de gradientes de expertos.

### Cómo se combinan los tamaños

- `tp_size * fsdp_size` es el número de procesos. `ep_size` no añade ninguno: agrupa los mismos rangos solo para los pesos de los expertos.
- `ep_size` divide la lista de expertos en `ep_size` bloques. Cada rank calcula `num_experts / ep_size` expertos, y `ep_size` ranks consecutivos mantienen un conjunto completo. Ese conjunto de ranks es el grupo en el que se ejecuta el all-to-all.
- `efsdp_size = fsdp_size * tp_size / ep_size` es el número de copias completas del conjunto de expertos. Los rangos en la misma posición en copias diferentes fragmentan esos expertos para la memoria y promedian sus gradientes, como FSDP lo hace para los módulos densos.
- El lote que mantiene un rango depende únicamente de `fsdp`. Los rangos consecutivos forman un grupo de TP y reciben el mismo lote; los grupos `fsdp_size` reciben lotes diferentes.

Ocho procesos, `tp_size=2, fsdp_size=4`, ocho expertos:

```text
rank         0      1       2      3       4      5       6      7
batch       [====B0====]   [====B1====]   [====B2====]   [====B3====]     one batch per TP pair
tp           0      1       0      1       0      1       0      1

ep_size=2   E0-3   E4-7    E0-3   E4-7    E0-3   E4-7    E0-3   E4-7     group = a TP pair,   efsdp_size=4
ep_size=4   E0E1   E2E3    E4E5   E6E7    E0E1   E2E3    E4E5   E6E7     group = two pairs,   efsdp_size=2
ep_size=8   E0     E1      E2     E3      E4     E5      E6     E7       group = all ranks,   efsdp_size=1
```

De la imagen se desprenden dos números:

- Dentro de un grupo de EP, cada token existe `tp_size` veces, una por cada rango del par que mantiene su lote. Esto no depende de `ep_size`.
- Un grupo EP mantiene `ep_size / tp_size` lotes diferentes. Esta es la cantidad sobre la que se suma el gradiente de un experto.

### Con paralelismo de tensores

Establezca `tp_size > 1` para fragmentar los módulos densos con el plan de TP mientras que los expertos usan dispatch. En ocho procesos:

```py
distributed_config = DistributedConfig(
    tp_size=2,
    fsdp_size=4,
    ep_size=4,
)
```

Cada par de rangos de TP recibe el mismo lote, porque la paralelización de tensores replica las activaciones dentro del par. Si ambos rangos despacharan todos sus tokens, el rango propietario de un experto recibiría cada token dos veces, lo calcularía dos veces y su gradiente de pesos se duplicaría. Los expertos están completos en un solo rango, por lo que el duplicado no puede dividirse por pesos, y el propietario suele ser otro rango, por lo que no puede resolverse por propiedad, como lo hace el enmascaramiento. Por lo tanto, el par divide las filas: cada rango de TP despacha una fracción disjunta de `1 / tp_size` de los tokens, los resultados vuelven al rango que los envió, y un all-reduce sobre el par de las mitades rellenas con ceros restaura la salida replicada que la siguiente capa espera. La división es por `tp_size`, no por `ep_size`, ya que solo los rangos que poseen un lote pueden enviarlo. Los grupos de expertos abarcan cuatro rangos y cada experto está fragmentado con FSDP en `efsdp_size = 2` rangos, mientras que el grupo de FSDP del tronco abarca cuatro rangos. Las restricciones habituales de TP del modelo, como la divisibilidad de las cabezas de atención, siguen aplicándose a los módulos densos. Las porciones de tokens pueden ser desiguales o vacías, incluso durante la decodificación de un solo token.

El despacho de tokens aún no puede combinarse con la paralelización por pipeline; use `pp_size=1` (aún no probado). Cada una de estas configuraciones utiliza ocho GPUs:

| Configuración | Resultado |
| :--- | :--- |
| `DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4)` | Despacho con grupos de TP de cuatro, cada uno dividiendo su lote en cuatro (plan predeterminado); a menos que especifique un ep_plan para usar el EP enmascarado heredado |
| `DistributedConfig(tp_size=1, fsdp_size=8, ep_size=4)` | Despachar con un lote independiente en cada rango, sin segmentación, y con expertos fragmentados con FSDP entre pares de rangos. |
| `DistributedConfig(tp_size=2, fsdp_size=4, ep_size=4)` | Despachar con un par de TP por lote, donde cada par divide su lote en dos; dos lotes por grupo de expertos. |
| `DistributedConfig(tp_size=8, ep_size=8)` | Despachar con cada rango compartiendo un solo lote, dividido en ocho, o aplicar enmascarado y all-reduce para un plan enmascarado. |

> [!WARNING]
> El [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer) aún no tiene en cuenta el despacho de tokens: el conteo de lotes y de tokens asume el diseño de all-reduce, donde los rangos de un grupo de TP comparten un lote y existen `fsdp_size` fragmentos de paralelismo de datos. El soporte del Trainer para el despacho llegará en una actualización posterior.

## Combinación con FSDP2

El paralelismo de tensores y de expertos fragmenta los pesos a lo largo de `tp`, pero el estado del optimizador y los módulos sin una regla siguen replicados en cada rank del grupo, lo que limita el tamaño del modelo que se puede entrenar. Añade [FSDP2](./fsdp) en una segunda dimensión de malla con `fsdp_size`. Con enmascarado y all-reduce, mantén `ep_size=tp_size` y pasa `ep_plan={"layers.*.mlp.gate": "ep_router", "layers.*.mlp.experts": "moe_tp_experts"}``.

```py
from transformers import AutoModelForCausalLM
from transformers.distributed import DistributedConfig

distributed_config = DistributedConfig(
    tp_size=4,
    ep_size=4,  # expert parallel size, must match tp_size with masking and all-reduce
    fsdp_size=2,  # data parallel shards
    ep_plan={
        "model.layers.*.mlp.gate": "ep_router",
        "model.layers.*.mlp.experts": "moe_tp_experts",
    },
)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-30B-A3B", distributed_config=distributed_config)
```

Cargue el modelo como de costumbre y luego entrene con [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer). Toma la norma del gradiente en ambas mallas y asigna a cada malla su propio grupo de parámetros del optimizador. [save_model()](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer.save_model) reúne los pesos fragmentados en un punto de control regular. Esto requiere `accelerate>=1.12` para que `Trainer` pueda reflejar `tp_size` y `fsdp_size` en `~Accelerate.ParallelismConfig`.

La tabla siguiente compara el entrenamiento solo con EP con el entrenamiento 2D EP+FSDP2 en 8xH100 GPUs. La carga de trabajo es el ajuste completo de Qwen3-30B-A3B en bf16 con una longitud de secuencia de 2048. Más fragmentos de FSDP reducen la memoria pico, y los tokens/s disminuyen un poco porque FSDP2 realiza all-gathers y reduce-scatters de los expertos a través de `fsdp`.

| configuración | tokens/s/GPU | memoria máxima/GPU |
|---|---|---|
| `tp_size=8` | 3485 | 38.6 GB |
| `tp_size=4, fsdp_size=2` | 2900 | 34.2 GB |
| `tp_size=2, fsdp_size=4` | 2830 | 32.3 GB |

> [!WARNING]
> Reanudar desde un punto de control aún no se admite para modelos fragmentados en el momento de la carga, por lo que el [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer) solo acepta `save_only_model=True` o `save_strategy="no"` para ellos.

## Referencia de la API[[transformers.DistributedConfig]]

#### transformers.DistributedConfig[[transformers.DistributedConfig]]

```python
transformers.DistributedConfig(tp_size: int | None = None, tp_plan: typing.Union[dict[str, str], typing.Literal['auto'], NoneType] = None, enable_sequence_parallel: bool = False, enable_expert_parallel: bool = False, fsdp_size: int | None = None, dp_replicate_size: int | None = None, fsdp_plan: dict[str, str] | None = None, fsdp_cpu_offload: bool = False, fsdp_mixed_precision: bool = False, pp_size: int | None = None, ep_size: int | None = None, ep_plan: dict[str, str] | None = None)
```

[Fuente](https://github.com/huggingface/transformers/blob/main/src/transformers/distributed/configuration_utils.py#L26)

**Parámetros:**

tp_size (`int`, *opcional*) : Número de dispositivos para la paralelización de tensores. Si `None` y `tp_plan` está establecido, el valor predeterminado es `WORLD_SIZE // (other_parallel_size)`. Si `None` y no se establece `tp_plan`, el valor predeterminado es 1.

tp_plan (`dict[str, str]` o `"auto"`, *opcional*) : Plan de particionamiento de paralelismo de tensores. Pasa `"auto"`, o déjalo como `None` cuando `tp_size` esté establecido, para usar el `base_model_tp_plan` predefinido del modelo. Pasa un diccionario para sobrescribir reglas individuales de ese plan; las reglas no especificadas se conservan. Un diccionario vacío no aplica ninguna regla de paralelismo de tensores.

enable_sequence_parallel (`bool`, *opcional*, valor predeterminado `False`) : Reservado para el paralelismo de secuencias. Aún no está implementado.

enable_expert_parallel (`bool`, *opcional*, valor predeterminado `False`) : Obsoleto, eliminado en v5.21. Sin `ep_size`, es lo mismo que `ep_size=tp_size, tp_plan={}`: el plan de paralelismo de expertos por sí solo a través de `tp_size` rangos. Un `ep_size` explícito tiene prioridad. Esta bandera no modifica `tp_size` ni `fsdp_size`.

fsdp_size (`int`, *opcional*) : Número de dispositivos para FSDP (paralelismo de datos). Si `None` y `tp_size` está definido, el valor predeterminado es 1.

dp_replicate_size (`int`, *opcional*) : Número de réplicas de FSDP (HSDP). Los parámetros se fragmentan en `fsdp_size` dispositivos y se replican `dp_replicate_size` veces: los gradientes se reduce-scatter dentro de una réplica y luego se all-reduce entre réplicas. El valor predeterminado es 1. Lo sugerimos para el entrenamiento multinodo: FSDP simple realiza all-gather de parámetros y reduce-scatter de gradientes entre todos los dispositivos en cada paso, por lo que este tráfico pasa por los enlaces inter-nodo más lentos. Establezca `dp_replicate_size` al número de nodos y `fsdp_size * tp_size` al número de dispositivos por nodo: el tráfico de fragmentación se mantendrá entonces en los enlaces intra-nodo rápidos, y solo el all-reduce de gradientes cruzará nodos. El costo es más memoria por dispositivo, ya que los parámetros se fragmentan dentro de un nodo.

fsdp_plan (`dict[str, str]`, *opcional*) : Plan de sharding de FSDP2. Déjalo como `None` para usar el `base_model_fsdp_plan` predefinido del modelo y el `_fsdp_plan` a nivel de clase. Pasa un diccionario para sobrescribir reglas individuales; las reglas no especificadas se mantienen. Se aplica solo cuando `dp_replicate_size * fsdp_size > 1`.

fsdp_cpu_offload (`bool`, *opcional*, valor predeterminado `False`) : Si se habilita el offloading de CPU para FSDP2.

fsdp_mixed_precision (`bool`, *opcional*, valor predeterminado `False`) : Si se habilita la precisión mixta para FSDP2.

pp_size (`int`, *opcional*) : Número de dispositivos para la paralelización por pipeline. Si es `None` y se establece otro modo de paralelismo, el valor predeterminado es 1.

ep_size (`int`, *opcional*) : Número de dispositivos que poseen fragmentos de expertos distintos. El valor predeterminado es 1. Establézcalo explícitamente para habilitar EP. Debe ser un múltiplo de `tp_size` y dividir `fsdp_size * tp_size`. Los planes de expertos con all-reduce requieren `ep_size=tp_size`; el despacho de tokens (`"ep_dispatch_experts"`) también permite `ep_size > tp_size`.

ep_plan (`dict[str, str]`, *opcional*) : Plan de particionado de paralelismo de expertos. Déjalo como `None` para usar el `base_model_ep_plan` predefinido del modelo. Pasa un diccionario para sobrescribir reglas individuales de ese plan; las reglas no especificadas se conservan. Se aplica solo cuando `ep_size > 1`, y sus reglas tienen prioridad sobre las reglas de `tp_plan` para los mismos módulos. Una regla `"ep_dispatch_experts"` selecciona el despacho de tokens all-to-all en lugar de la enmascaración del router y el all-reduce.

Configuración para inferencia y entrenamiento distribuidos nativos con paralelismo de tensor, pipeline o FSDP2.

