Buckets:
| # Paralelismo de tensor | |
| [Tensor parallelism](https://huggingface.co/spaces/nanotron/ultrascale-playbook?section=tensor_parallelism) distribui as matrizes de pesos de um modelo entre os dispositivos. Cada dispositivo mantém uma fatia por coluna (`"colwise"`) ou por linha (`"rowwise"`) de cada camada e calcula um resultado parcial. Um `AllReduce` ou `AllGather` na fronteira da camada combina os resultados parciais para formar a saída completa. Diferentemente do [context parallelism](./context_parallelism), ele reduz a memória de *weight* por dispositivo, o que ajuda quando um modelo não cabe em um único dispositivo. Consulte [Distributed inference](../training/distributed_inference) para compará-lo com as outras estratégias. | |
| ## Carregar um modelo fragmentado | |
| Passe um [TensorParallelConfig](/docs/diffusers/main/pt/api/parallel#diffusers.TensorParallelConfig) para o argumento `parallel_config` do [from_pretrained()](/docs/diffusers/main/pt/api/models/overview#diffusers.ModelMixin.from_pretrained) do modelo. `tp_degree` é o número de dispositivos para os quais o modelo será dividido e deve dividir o número de cabeças de atenção do modelo. A classe do modelo deve definir um `_tp_plan`, que mapeia nomes de módulos para um estilo de divisão. Para verificar se um modelo suporta paralelismo de tensor, procure por esse atributo em sua classe, como [Flux2Transformer2DModel](/docs/diffusers/main/pt/api/models/flux2_transformer#diffusers.Flux2Transformer2DModel). | |
| Para adicionar um plano a outro modelo, consulte [Adicionar paralelismo de tensor a um modelo](#add-tensor-parallelism-to-a-model). | |
| Carregar dessa forma faz o checkpoint ser fragmentado conforme é lido. Cada rank lê apenas sua própria fatia de cada peso e a coloca diretamente em sua GPU, de modo que nenhum rank mantém um peso de tamanho completo. A memória por rank diminui à medida que `tp_degree` aumenta. | |
| ```py | |
| import os | |
| import torch | |
| from torch import distributed as dist | |
| from diffusers import DiffusionPipeline, Flux2Transformer2DModel, TensorParallelConfig | |
| def main(): | |
| dist.init_process_group(backend="nccl") | |
| rank, world_size = dist.get_rank(), dist.get_world_size() | |
| # torchrun sets LOCAL_RANK to this process's GPU index on its node | |
| local_rank = int(os.environ["LOCAL_RANK"]) | |
| device = torch.device(f"cuda:{local_rank}") | |
| torch.cuda.set_device(device) | |
| # Each rank reads only its own shard of every planned weight, straight onto its own GPU. | |
| transformer = Flux2Transformer2DModel.from_pretrained( | |
| "black-forest-labs/FLUX.2-dev", | |
| subfolder="transformer", | |
| dtype=torch.bfloat16, | |
| parallel_config=TensorParallelConfig(tp_degree=world_size), | |
| ) | |
| pipeline = DiffusionPipeline.from_pretrained( | |
| "black-forest-labs/FLUX.2-dev", transformer=transformer, dtype=torch.bfloat16 | |
| ) | |
| # The transformer is already on its device; move the remaining components individually. Do not call | |
| # `pipeline.to(device)` — that would move every rank's shards onto the same device. | |
| pipeline.text_encoder.to(device) | |
| pipeline.vae.to(device) | |
| generator = torch.Generator().manual_seed(42) | |
| image = pipeline(prompt="a cat holding a sign that says hello", generator=generator).images[0] | |
| if rank == 0: | |
| image.save("output.png") | |
| dist.destroy_process_group() | |
| if __name__ == "__main__": | |
| main() | |
| ``` | |
| Salve o script como `tensor_parallel_flux2.py` e execute-o com `torchrun`. | |
| ```shell | |
| torchrun --nproc-per-node 4 tensor_parallel_flux2.py | |
| ``` | |
| O script define `tp_degree` como `world_size`, portanto `--nproc-per-node 4` distribui o transformer em 4 GPUs. | |
| Apenas o transformer é fragmentado. Cada rank mantém uma cópia completa do codificador de texto, portanto, cada GPU precisa de memória suficiente para o codificador de texto mais sua parte do transformer. | |
| ## Desempenho de carregamento | |
| Carregar com `parallel_config` é mais rápido e usa menos memória de CPU por rank do que carregar o modelo completo e, em seguida, chamar `enable_parallelism()`. A tabela abaixo mostra os resultados para [black-forest-labs/FLUX.2-dev](https://huggingface.co/black-forest-labs/FLUX.2-dev) (somente o transformer, 32B de parâmetros, bf16) em 4x A10G (23GB). | |
| | tp_degree | método | tempo de carregamento | CPU de pico/por rank | | |
| |---|---|---|---| | |
| | 4 | `from_pretrained(parallel_config=...)` | 12.5s | 6.8GB | | |
| | 4 | `from_pretrained` + `enable_parallelism` | 30.4s | 64.1GB | | |
| ## Limitações | |
| O paralelismo de tensor não funciona com algumas opções e recursos de carregamento, e não é possível salvar um modelo fragmentado. | |
| - Carregar com um `parallel_config` de paralelismo de tensor não funciona com `device_map`, `quantization_config`, `low_cpu_mem_usage=False`, `use_flashpack=True` ou pesos que não sejam safetensors. Cada um deles gera um erro em vez de carregar silenciosamente o checkpoint completo. | |
| - A combinação de paralelismo de tensor com quantização, offloading ou adaptadores LoRA também gera um erro, independentemente de como o modelo é particionado. | |
| - Para dividir um modelo que já está na memória, chame `enable_parallelism()` com a mesma configuração. Ele carrega o modelo completo primeiro e depois o divide, então cada rank precisa de memória para o checkpoint completo. Carregar com `parallel_config` evita esse custo. | |
| - Salvar um modelo paralelo de tensor gera um erro em [save_pretrained()](/docs/diffusers/main/pt/api/models/overview#diffusers.ModelMixin.save_pretrained). Salve o modelo antes de fragmentá-lo. | |
| ## Adicionar paralelismo de tensor a um modelo | |
| Esta seção é destinada a contribuidores que desejam adicionar suporte a paralelismo de tensor a um modelo do Diffusers. | |
| O paralelismo de tensores funciona apenas em modelos que definem um `_tp_plan`, um atributo de classe que mapeia nomes de módulos para um estilo de particionamento. Escrever um geralmente significa associar cada projeção que *expande* a dimensão oculta com a projeção que a *contrai* de volta. | |
| Cada chave pode conter no máximo um `*`, e a parte antes do `*` deve resolver para um [`nn.ModuleList`](https://pytorch.org/docs/stable/generated/torch.nn.ModuleList.html), de modo que uma entrada cubra todos os blocos. Uma chave sem `*` se aplica ao próprio modelo. Os caminhos são relativos ao modelo. | |
| ### Colwise e rowwise | |
| | Estilo | Shards | Cada rank | Usar para | | |
| |---|---|---|---| | |
| | `"colwise"` | características de saída (dim 0 de `weight`) | calcula uma fatia da saída | `to_q`, `to_k`, `to_v`, in-projection do FFN | | |
| | `"rowwise"` | recursos de entrada (dim 1 de `weight`) | calcula uma soma parcial | `to_out.0`, projeção de saída do FFN | | |
| | `"rowwise_split_input"` | recursos de entrada (dim 1 de `weight`) | calcula uma soma parcial a partir de sua fatia da entrada replicada | uma projeção independente cuja entrada é replicada, como uma projeção de modulação AdaLN que lê o embedding do timestep | | |
| Sempre combine `"colwise"` e `"rowwise"` nessa ordem. Uma projeção `"colwise"` deixa sua saída fragmentada, a projeção `"rowwise"` subsequente consome esse fragmento diretamente e um único `AllReduce` na fronteira do bloco reconstrói o resultado. Fragmentar o par de qualquer outra forma força um gather no meio e comunica muito mais. | |
| Na atenção, as projeções de Q, K e V por coluna atribuem a cada rank um subconjunto de cabeças, portanto `tp_degree` deve dividir o número de cabeças. Os duplicados do stream do codificador (`add_q_proj`, `to_add_out`, `ff_context`) seguem o mesmo padrão de seus equivalentes no stream de imagem. | |
| ### Projeções fundidas | |
| Quando um `Linear` empacota vários tensores lógicos ao longo da dimensão que está sendo fragmentada, fatias simples de `"colwise"`/`"rowwise"` atravessam a concatenação e desalinharam as peças. Use `PackedColwiseParallel`/`PackedRowwiseParallel` em vez disso, que fragmenta cada bloco empacotado de forma independente. | |
| ```py | |
| # in src/diffusers/models/transformers/your_model.py | |
| from ...hooks.tensor_parallel import PackedColwiseParallel, PackedRowwiseParallel | |
| ``` | |
| Pass `blocks`, uma lista de inteiros proporcionais cuja soma divide a dimensão empacotada. Por exemplo, use `[1, 1]` para uma projeção de gate e up do SwiGLU com metades iguais, ou `[1, 1, 1, 3, 3]` para uma projeção fundida de Q, K, V, gate e up com `mlp_ratio=3`. | |
| ```py | |
| "transformer_blocks.*.ff.linear_in": PackedColwiseParallel([1, 1]), | |
| ``` | |
| Quando os tamanhos dos blocos são conhecidos apenas a partir da configuração, omita `blocks` e armazene os tamanhos absolutos no `Linear` durante o `__init__`, como `_tp_packed_col_blocks` ou `_tp_packed_row_blocks`. | |
| ```py | |
| # in the attention module's __init__ | |
| self.to_out._tp_packed_row_blocks = [self.inner_dim, self.mlp_hidden_dim] | |
| ``` | |
| ```py | |
| # in the model's _tp_plan | |
| "single_transformer_blocks.*.attn.to_out": PackedRowwiseParallel(), | |
| ``` | |
| O plano em [`transformer_flux2.py`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_flux2.py) usa ambas as formas. O par SwiGLU de duplo fluxo passa `blocks` diretamente. A projeção QKV e MLP de fluxo único fundida armazena seus tamanhos no `Linear` em `Flux2ParallelSelfAttention.__init__`, porque eles dependem de `mlp_ratio` e `mlp_mult_factor`. | |
| ### O que deixar de fora | |
| Módulos excluídos do plano são replicados em cada rank. Exclua as camadas de normalização, as incorporações de patch e de texto, e o `norm_out` e `proj_out` finais. Eles são pequenos, portanto, fragmentá-los poupa pouco memória e adiciona comunicação. A modulação AdaLN (`img_mod` e `txt_mod`) geralmente é pequena o suficiente para também ser excluída. Quando é grande, como o `adaln_proj.linear` do MiniMax-H3, fragmente-a com `"rowwise_split_input"`. | |
| ### Restrições e verificação | |
| - `tp_degree` deve dividir `config.num_attention_heads`. | |
| - Cada bloco empacotado deve ser *individualmente* divisível por `tp_degree`, e não apenas sua soma. | |
| [from_pretrained()](/docs/diffusers/main/pt/api/models/overview#diffusers.ModelMixin.from_pretrained) e `enable_parallelism()` verificam ambos antes de carregar ou dividir qualquer peso. | |
| Valide um novo plano numericamente, em vez de a olho nu: gere com uma semente fixa em um único dispositivo e, em seguida, novamente sob paralelismo de tensor, e compare as saídas. Um `"colwise"`/`"rowwise"` mal posicionado geralmente ainda executa e produz uma imagem plausível, porém incorreta. | |
| > [!TIP] | |
| > Comece a partir de um plano existente para uma arquitetura semelhante. | |
| > | |
| > - [`transformer_qwenimage.py`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_qwenimage.py) não possui projeções fundidas, portanto cada entrada é apenas `"colwise"` ou `"rowwise"`. - [`transformer_flux.py`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_flux.py) adiciona uma projeção row-wise empacotada. - [`transformer_flux2.py`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_flux2.py) usa ambos os estilos empacotados. | |
Xet Storage Details
- Size:
- 11.2 kB
- Xet hash:
- e8f36e375b0e02689e16e594dc87b5c4b1f6a0c04534a0fb865c72b2ddc6150f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.