# Paralelismo de especialistas

[Paralelismo de especialistas](https://huggingface.co/spaces/nanotron/ultrascale-playbook?section=expert_parallelism) é uma estratégia de paralelismo para [modelos mixture-of-experts (MoE)](https://huggingface.co/blog/moe). A camada feedforward de cada especialista reside em um acelerador de hardware diferente. Um roteador despacha os tokens para os especialistas apropriados e reúne os resultados. Essa abordagem escala os modelos para contagens de parâmetros muito maiores sem aumentar o custo de computação, pois cada token ativa apenas alguns especialistas.

## DistributedConfig

Ative o paralelismo de especialistas com a classe [DistributedConfig](/docs/transformers/main/pt/expert_parallelism#transformers.DistributedConfig) e o argumento `ep_size`. A maioria dos modelos MoE usa [token dispatch](#token-dispatch) por padrão, portanto `ep_size` pode ser definido independentemente de `tp_size`. Alguns, como Llama 4 e Gemma 4, ainda usam masking e all-reduce por padrão (`"ep_router"` e `"moe_tp_experts"` em `model.ep_plan`). O masking também está disponível em qualquer modelo com um override de `ep_plan` e exige `ep_size=tp_size` para que cada rank em um grupo de especialistas receba os mesmos 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 dois planos em sua configuração: `base_model_tp_plan` para os módulos densos e `base_model_ep_plan` para os especialistas. Eles são expostos no modelo carregado como `model.tp_plan` e `model.ep_plan`. Com `tp_size > 1` e `ep_size > 1`, ambos se aplicam: o plano de [paralelismo de tensor](./perf_infer_gpu_multi) faz o sharding da atenção e dos MLPs densos, e o plano de paralelismo de especialistas faz o sharding dos especialistas. As regras de EP têm precedência sobre as regras de TP para os mesmos módulos, de modo que os pesos dos especialistas são shardados uma única vez, pelo plano de EP. No plano de EP, o estilo `GroupedGemmParallel` divide os pesos dos especialistas ao longo da dimensão de especialista, de modo que cada rank carrega apenas seus especialistas locais, e o `ep_router` mascara os especialistas que estão em outros ranks antes de um all-reduce combinar as saídas dos especialistas.

`tp_plan` é aplicado apenas quando `tp_size > 1`, e `ep_plan` apenas quando `ep_size > 1`. Com o TP habilitado e o EP desabilitado, o plano TP completo é aplicado, incluindo as regras de especialista.

A regra de encaminhamento de especialistas em `ep_plan` seleciona como os tokens chegam aos especialistas:

| regra | mecanismo | layout |
| :--- | :--- | :--- |
| `"moe_tp_experts"` com `"ep_router"` no roteador | mascaramento e all-reduce: cada rank executa seus especialistas locais em todo o lote, o router mascara os outros e um all-reduce combina as saídas | `ep_size=tp_size` |
| `"ep_dispatch_experts"` | [token dispatch](#token-dispatch): cada rank mantém seus próprios tokens e apenas troca os pares roteados (token, expert) com duas coletivas all-to-all | `ep_size` um múltiplo de `tp_size` que divide `fsdp_size * tp_size` |

> [!TIP]
> `enable_expert_parallel=True` sem `ep_size` está obsoleto e emite um `FutureWarning`. Ele faz o sharding apenas dos experts, como em `ep_size=tp_size, tp_plan={}`.

Inicie seu script de inferência com [torchrun](https://pytorch.org/docs/stable/elastic/run.html). O número de processos deve ser igual a `tp_size * fsdp_size * pp_size`, e `ep_size` deve dividir uniformemente o número de especialistas.

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

### Sobrescrevendo os planos

Passe `tp_plan={...}` ou `ep_plan={...}` para [DistributedConfig](/docs/transformers/main/pt/expert_parallelism#transformers.DistributedConfig) para sobrescrever regras individuais dos planos predefinidos. As regras não especificadas são mantidas e os planos mesclados são armazenados no modelo. Cada chave deve corresponder a um módulo, a um parâmetro ou a uma entrada existente do plano; caso contrário, o carregamento levanta um `ValueError` antes que qualquer coisa seja fragmentada. Use o caminho completo conforme visto a partir do modelo carregado, ou seja, `model.layers.*` para um LM causal e `layers.*` para o modelo base correspondente.

```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"},
)
```

Fornecer um plano não infere os tamanhos de paralelismo: defina `tp_size` e `ep_size` explicitamente.

A maioria dos modelos MoE usa `"ep_dispatch_experts"` por padrão. Para usar mascaramento e all-reduce em vez disso, defina `ep_size=tp_size` e substitua tanto as regras de forward do roteador quanto as dos especialistas (o nome do módulo do roteador depende do modelo, por exemplo, `mlp.router` no 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 outro lado, substitua a regra de forward dos especialistas de um modelo cujo plano usa mascaramento por `"ep_dispatch_experts"` para usar o despacho de tokens. A regra do roteador é então ignorada, pois o despacho precisa dos ids globais dos especialistas para encontrar o proprietário de cada especialista.

## Despacho de tokens

Com o token dispatch, cada rank treina em sua própria parte do lote. Em cada camada MoE, um rank roteia seus tokens, envia cada par (token, especialista) para o rank que possui o especialista por meio de um all-to-all, executa seus especialistas locais no que recebe, obtém os resultados de volta com um segundo all-to-all e os combina com os pesos de roteamento. Apenas as ativações roteadas e as saídas dos especialistas trafegam, e nenhum rank calcula especialistas para tokens que não possui.

```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)
```

Com `tp_size=1`, `ep_size` deve dividir `fsdp_size` e o número de especialistas, e a atenção não é limitada por `num_key_value_heads`. Para o restante do modelo:

- Os parâmetros fora dos especialistas são fragmentados com [FSDP2](./fsdp) em `fsdp`, o que reduz seus gradientes.
- Os especialistas são fragmentados em `ep` e, quando `efsdp_size = fsdp_size * tp_size // ep_size` é maior que um, também são fragmentados via FSDP em `efsdp`. Eles sempre são envolvidos por FSDP, portanto `fsdp_mixed_precision` e `fsdp_cpu_offload` também se aplicam a eles e [save_pretrained()](/docs/transformers/main/pt/main_classes/model#transformers.PreTrainedModel.save_pretrained) os agrega como qualquer outro parâmetro.
- Um grupo de paralelismo de especialistas mantém `ep_size / tp_size` lotes, de modo que o gradiente de um especialista é a soma sobre esse número de lotes. A redução `efsdp` divide por `fsdp_size` em vez do tamanho do seu grupo, o que resulta na mesma média por lote que o FSDP2 utiliza para os módulos densos.
- Cada especialista local também processa uma linha de zero pad por camada. Um rank cujos especialistas não receberam tokens ainda participa do reverse all-to-all e da redução de gradiente dos especialistas.

### Como os tamanhos se combinam

- `tp_size * fsdp_size` é o número de processos. `ep_size` não adiciona nenhum: ele apenas reagrupa os mesmos ranks para os pesos dos especialistas.
- `ep_size` divide a lista de especialistas em `ep_size` blocos. Cada rank calcula `num_experts / ep_size` especialistas, e `ep_size` ranks consecutivos mantêm um conjunto completo. Esse conjunto de ranks é o grupo em que o all-to-all é executado.
- `efsdp_size = fsdp_size * tp_size / ep_size` é o número de cópias completas do conjunto de especialistas. Os ranks na mesma posição em cópias diferentes fazem o sharding desses especialistas para memória e fazem a média de seus gradientes, assim como o FSDP faz para os módulos densos.
- O lote que um rank mantém depende apenas de `fsdp`. Ranks consecutivos formam um grupo de TP e recebem o mesmo lote; os grupos `fsdp_size` recebem lotes diferentes.

Oito processos, `tp_size=2, fsdp_size=4`, oito especialistas:

```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
```

Dois números decorrem da imagem:

- Dentro de um grupo de EP, cada token existe `tp_size` vezes, uma vez por rank do par que detém seu lote. Isso não depende de `ep_size`.
- Um grupo de EP mantém `ep_size / tp_size` lotes diferentes. Esse é o número sobre o qual a gradiente de um especialista é somada.

### Com paralelismo de tensor

Defina `tp_size > 1` para dividir os módulos densos com o plano de TP, enquanto os especialistas usam dispatch. Em oito processos:

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

Cada par de ranks de TP recebe o mesmo lote, pois a paralelização de tensores replica as ativações dentro do par. Se ambos os ranks despachassem todos os seus tokens, o rank que possui um especialista receberia cada token duas vezes, o calcularia duas vezes e o gradiente do seu peso seria duplicado. Como os especialistas estão inteiros em um único rank, a duplicata não pode ser dividida pelos pesos, e como o proprietário geralmente é outro rank, ela não pode ser resolvida pela propriedade, como a máscara faz. Portanto, o par divide as linhas: cada rank de TP despacha uma fração `1 / tp_size` disjunta dos tokens, os resultados retornam ao rank que os enviou e um all-reduce sobre o par das metades com preenchimento zero restaura a saída replicada que a próxima camada espera. A divisão é por `tp_size`, não por `ep_size`, pois apenas os ranks que possuem um lote podem enviá-lo. Os grupos de especialistas abrangem quatro ranks e cada especialista é fragmentado por FSDP em `efsdp_size = 2` ranks, enquanto o grupo FSDP do tronco abrange quatro ranks. As restrições de TP habituais do modelo, como a divisibilidade das cabeças de atenção, ainda se aplicam aos módulos densos. As fatias de tokens podem ser desiguais ou vazias, inclusive durante a decodificação de um único token.

O despacho de tokens ainda não pode ser combinado com paralelismo de pipeline; use `pp_size=1` (ainda não testado). Cada uma dessas configurações usa oito GPUs:

| Configuração | Resultado |
| :--- | :--- |
| `DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4)` | Despache com grupos de TP de quatro, cada um fatiando seu lote em quatro (plano padrão); a menos que você especifique um ep_plan para usar o EP mascarado legado |
| `DistributedConfig(tp_size=1, fsdp_size=8, ep_size=4)` | Despache com um lote independente em cada rank, sem fatiamento, e com especialistas FSDP-particionados entre pares de ranks. |
| `DistributedConfig(tp_size=2, fsdp_size=4, ep_size=4)` | Despache com um par de TP por lote, onde cada par divide seu lote em dois; dois lotes por grupo de especialistas. |
| `DistributedConfig(tp_size=8, ep_size=8)` | Despache com cada rank compartilhando um único lote, dividido em oito, ou com mascaramento e all-reduce para um plano mascarado. |

> [!WARNING]
> O [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer) ainda não considera o despacho de tokens: a contagem de lotes e de tokens pressupõe o layout de all-reduce, em que os ranks de um grupo de TP compartilham um lote e existem `fsdp_size` shards de paralelismo de dados. O suporte do Trainer para despacho será implementado em uma atualização posterior.

## Combinando com FSDP2

O paralelismo de tensor e de especialistas divide os pesos entre `tp`, mas o estado do otimizador e os módulos sem uma regra ainda são replicados em cada rank do grupo, o que limita o tamanho do modelo que você pode treinar. Adicione [FSDP2](./fsdp) em uma segunda dimensão de malha com `fsdp_size`. Com mascaramento e all-reduce, mantenha `ep_size=tp_size` e passe `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)
```

Carregue o modelo como de costume e, em seguida, treine com [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer). Ele calcula a norma do gradiente em ambas as malhas e atribui a cada malha seu próprio grupo de parâmetros do otimizador. [save_model()](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer.save_model) reúne os pesos fragmentados em um checkpoint regular. Isso requer `accelerate>=1.12` para que o `Trainer` possa espelhar `tp_size` e `fsdp_size` em `~Accelerate.ParallelismConfig`.

A tabela abaixo compara o treinamento apenas com EP e o 2D EP+FSDP2 em 8 GPUs H100. A carga de trabalho é o fine-tuning completo do Qwen3-30B-A3B em bf16, com comprimento de sequência 2048. Mais shards do FSDP reduzem a memória de pico, e os tokens/s caem um pouco porque o FSDP2 faz all-gathers e reduce-scatters dos experts através do `fsdp`.

| configuração | tokens/s/GPU | memória de pico/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]
> Retomar a partir de um checkpoint ainda não é suportado para modelos fragmentados no momento do carregamento, portanto, o [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer) aceita apenas `save_only_model=True` ou `save_strategy="no"` para eles.

## Referência da 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)
```

[Origem](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 paralelismo de tensor. Se `None` e `tp_plan` estiver definido, o valor padrão é `WORLD_SIZE // (other_parallel_size)`. Se `None` e nenhum `tp_plan` estiver definido, o valor padrão é 1.

tp_plan (`dict[str, str]` ou `"auto"`, *opcional*) : Plano de sharding de paralelismo de tensor. Passe `"auto"`, ou deixe como `None` quando `tp_size` estiver definido, para usar o `base_model_tp_plan` predefinido do modelo. Passe um dicionário para sobrescrever regras individuais desse plano; as regras não especificadas são mantidas. Um dicionário vazio não aplica nenhuma regra de paralelismo de tensor.

enable_sequence_parallel (`bool`, *opcional*, padrão `False`) : Reservado para paralelismo de sequência. Ainda não implementado.

enable_expert_parallel (`bool`, *opcional*, padrão `False`) : Obsoleto, removido na v5.21. Sem `ep_size`, é o mesmo que `ep_size=tp_size, tp_plan={}`: apenas o plano de paralelismo de especialistas entre `tp_size` ranks. Um `ep_size` explícito tem precedência. Esta flag não altera `tp_size` ou `fsdp_size`.

fsdp_size (`int`, *opcional*) : Número de dispositivos para FSDP (paralelismo de dados). Se `None` e `tp_size` estiver definido, o valor padrão é 1.

dp_replicate_size (`int`, *opcional*) : Número de réplicas de FSDP (HSDP). Os parâmetros são fragmentados em `fsdp_size` dispositivos e replicados `dp_replicate_size` vezes: os gradientes são reduzidos e dispersados (reduce-scattered) dentro de uma réplica e, em seguida, reduzidos (all-reduced) entre as réplicas. O valor padrão é 1. Recomendamos seu uso para treinamento em múltiplos nós: o FSDP puro faz all-gather dos parâmetros e reduce-scatter dos gradientes entre todos os dispositivos a cada passo, de modo que esse tráfego passa pelos links inter-nós mais lentos. Defina `dp_replicate_size` como o número de nós e `fsdp_size * tp_size` como o número de dispositivos por nó: assim, o tráfego de fragmentação permanece nos links intra-nós mais rápidos, e apenas o all-reduce dos gradientes cruza entre nós. O custo é mais memória por dispositivo, pois os parâmetros são fragmentados dentro de um nó.

fsdp_plan (`dict[str, str]`, *opcional*) : Plano de sharding do FSDP2. Deixe como `None` para usar o `base_model_fsdp_plan` predefinido do modelo e o `_fsdp_plan` de nível de classe. Passe um dicionário para sobrescrever regras individuais; as regras não especificadas são mantidas. Aplicado apenas quando `dp_replicate_size * fsdp_size > 1`.

fsdp_cpu_offload (`bool`, *opcional*, padrão `False`) : Se deve habilitar o offloading de CPU para FSDP2.

fsdp_mixed_precision (`bool`, *opcional*, padrão `False`) : Se deve habilitar a precisão mista para o FSDP2.

pp_size (`int`, *opcional*) : Número de dispositivos para paralelismo de pipeline. Se `None` e outro modo de paralelismo estiver definido, o valor padrão é 1.

ep_size (`int`, *opcional*) : Número de dispositivos que possuem shards de especialistas distintos. O valor padrão é 1. Defina-o explicitamente para habilitar o EP. Deve ser um múltiplo de `tp_size` e dividir `fsdp_size * tp_size`. Os planos de all-reduce de especialistas exigem `ep_size=tp_size`; o despacho de tokens (`"ep_dispatch_experts"`) também permite `ep_size > tp_size`.

ep_plan (`dict[str, str]`, *opcional*) : Plano de sharding de paralelismo de especialistas. Deixe como `None` para usar o `base_model_ep_plan` predefinido do modelo. Passe um dicionário para sobrescrever regras individuais desse plano; as regras não especificadas são mantidas. Aplicado apenas quando `ep_size > 1`, e suas regras têm precedência sobre as regras de `tp_plan` para os mesmos módulos. Uma regra `"ep_dispatch_experts"` seleciona o despacho de tokens all-to-all em vez de mascaramento de roteador e all-reduce.

Configuração para inferência e treinamento distribuídos nativos com paralelismo de tensor, pipeline ou FSDP2.

