# Paralelisme konteks

[Context parallelism](https://huggingface.co/spaces/nanotron/ultrascale-playbook?section=context_parallelism) membagi urutan input di antara GPU, dan setiap GPU memproses bagiannya sendiri. Hal ini menurunkan memori per GPU, yang paling membantu untuk urutan panjang seperti gambar resolusi tinggi dan video. Untuk membandingkannya dengan strategi lain, lihat [Distributed inference](../training/distributed_inference).

Context parallelism berjalan di dalam attention backend, dan hanya beberapa backend yang mendukungnya. Jika backend Anda tidak mendukungnya, `enable_parallelism` akan memicu error yang mencantumkan backend yang kompatibel. Ganti backend dengan [set_attention_backend()](/docs/diffusers/v0.43.0/id/api/models/overview#diffusers.ModelMixin.set_attention_backend), dan lihat [backend yang tersedia](./attention_backends#available-backends) untuk daftar lengkap.

Konfigurasikan setiap varian di bawah dengan [ContextParallelConfig](/docs/diffusers/v0.43.0/id/api/parallel#diffusers.ContextParallelConfig).

| Varian | Konfigurasi | Persyaratan | Gunakan saat |
|---|---|---|---|
| [Ring Attention](#ring-attention) | `ring_degree > 1` | panjang sekuens harus dapat dibagi habis oleh `ring_degree` | urutan panjang dan bandwidth interkoneksi terbatas |
| [Ulysses Attention](#ulysses-attention) | `ulysses_degree > 1` | panjang sekuens dan jumlah kepala perhatian harus dapat dibagi habis oleh `ulysses_degree` | GPU Anda memiliki interkoneksi yang cepat |
| [Atensi Terpadu](#unified-attention) | `ring_degree > 1` dan `ulysses_degree > 1` | minimal 4 GPU | Anda memiliki lebih banyak perangkat daripada yang dapat digunakan Ulysses Attention secara mandiri |
| [Ulysses Anything Attention](#ulysses-anything-attention) | `ulysses_degree > 1`, `ulysses_anything=True` | `ring_degree` harus 1 | panjang sekuens atau jumlah head tidak terbagi rata |
| [Ring Anything Attention](#ring-anything-attention) | `ring_degree > 1`, `ring_anything=True` | `ulysses_degree` harus 1, hanya untuk inferensi, `attn_mask` harus `None` | panjang sekuens tidak terbagi rata |

## Ring Attention

[Ring Attention](https://huggingface.co/papers/2310.01889) meneruskan blok key (K) dan value (V) antar-GPU yang disusun dalam bentuk cincin. Setiap GPU menghitung perhatian terhadap blok K/V yang dipegangnya, lalu meneruskan blok tersebut ke GPU berikutnya. Setelah satu putaran penuh, setiap GPU telah melihat K/V dari setiap token, dan tidak ada GPU yang pernah memegang seluruh rangkaian.

Set `ring_degree` ke jumlah GPU, dan lewati config ke metode `enable_parallelism()` transformer.

```py
import os
import torch
from torch import distributed as dist
from diffusers import DiffusionPipeline, ContextParallelConfig

def setup_distributed():
    if not dist.is_initialized():
        dist.init_process_group(backend="nccl")
    # 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)
    return device

def main():
    device = setup_distributed()
    world_size = dist.get_world_size()

    pipeline = DiffusionPipeline.from_pretrained(
        "black-forest-labs/FLUX.1-dev", dtype=torch.bfloat16
    ).to(device)
    pipeline.transformer.set_attention_backend("_native_cudnn")

    cp_config = ContextParallelConfig(ring_degree=world_size)
    pipeline.transformer.enable_parallelism(config=cp_config)

    prompt = """
    cinematic film still of a cat sipping a margarita in a pool in Palm Springs, California
    highly detailed, high budget hollywood movie, cinemascope, moody, epic, gorgeous, film grain
    """

    # Must specify generator so all ranks start with same latents (or pass your own)
    generator = torch.Generator().manual_seed(42)
    image = pipeline(
        prompt,
        guidance_scale=3.5,
        num_inference_steps=50,
        generator=generator,
    ).images[0]

    if dist.get_rank() == 0:
        image.save("output.png")

    if dist.is_initialized():
        dist.destroy_process_group()

if __name__ == "__main__":
    main()
```

Simpan skrip sebagai `ring_attention.py` dan jalankan dengan [torchrun](https://docs.pytorch.org/docs/stable/elastic/run.html) atau launcher lain yang kompatibel dengan PyTorch. Atur `--nproc-per-node` ke jumlah GPU.

```bash
torchrun --nproc-per-node 2 ring_attention.py
```

Untuk mencoba varian lainnya, ganti panggilan `enable_parallelism` di `ring_attention.py` dengan yang ada di bagian mereka.

## Perhatian Ulysses

[Ulysses Attention](https://huggingface.co/papers/2309.14509) menggunakan komunikasi *all-to-all*, di mana setiap GPU bertukar data dengan setiap GPU lainnya. all-to-all pertama mengelompokkan ulang data sehingga setiap GPU memegang setiap token untuk subset dari attention heads. Setiap GPU menghitung attention untuk heads-nya, dan all-to-all kedua mengelompokkan ulang hasil berdasarkan token untuk layer berikutnya.

Set `ulysses_degree` ke jumlah GPU, dan lewati config ke `enable_parallelism()`.

```py
pipeline.transformer.enable_parallelism(config=ContextParallelConfig(ulysses_degree=2))
```

## Perhatian Terpadu

[Unified Sequence Parallelism](https://huggingface.co/papers/2405.07719) menggabungkan Ring Attention dan Ulysses Attention. Proses ini menjalankan all-to-all untuk mendistribusikan ulang heads dan tokens, kemudian menjalankan Ring Attention pada hasilnya, lalu membalikkan all-to-all untuk memulihkan tata letak asli. Ulysses melakukan paralelisasi di antara attention heads, sedangkan Ring melakukan paralelisasi di sepanjang sequence, sehingga secara bersama-sama keduanya dapat menggunakan lebih banyak GPU dibandingkan Ulysses saja.

Gunakan Unified Attention ketika Anda memiliki setidaknya 4 GPU. Atur `ulysses_degree` dan `ring_degree` keduanya ke nilai lebih dari 1. GPU membentuk grid 2D dari `ulysses_degree * ring_degree` perangkat, dan setiap GPU termasuk dalam satu grup Ulysses dan satu grup Ring.

```py
pipeline.transformer.enable_parallelism(config=ContextParallelConfig(ulysses_degree=2, ring_degree=2))
```

Benchmark di bawah ini membandingkan Ulysses, Ring, dan Unified Attention pada 4 GPU H100 dengan [skrip ini](https://github.com/huggingface/diffusers/pull/12693#issuecomment-3694727532). Setiap gambar menggunakan 50 langkah inferensi.

| Backend CP         | Waktu / Gambar (ms) | Langkah / Detik | Memori Puncak (GB) |
|--------------------|------------------|-------------|------------------|
| ulysses            | 6670.789         | 7.50        | 33.85            |
| ring               | 13076.492        | 3.82        | 56.02            |
| unified_balanced   | 11068.705        | 4.52        | 33.85            |

Ulysses memiliki throughput tertinggi, tetapi tidak dapat menggunakan lebih banyak GPU daripada jumlah kepala perhatian (attention heads) pada model. Unified Attention menghilangkan batasan tersebut.

## Ulysses Anything Attention

Ulysses Attention membutuhkan panjang sekuens dan jumlah kepala perhatian yang dapat dibagi rata oleh `ulysses_degree`. [Ulysses Anything Attention](https://github.com/huggingface/diffusers/pull/12996) menghilangkan kedua persyaratan tersebut. Anda tidak dapat menggabungkannya dengan Unified Attention, sehingga `ring_degree` harus 1.

Set `ulysses_degree` ke lebih dari 1 dan `ulysses_anything=True`.

```py
pipeline.transformer.enable_parallelism(config=ContextParallelConfig(ulysses_degree=2, ulysses_anything=True))
```

> [!TIP]
> Tambahkan backend `gloo` untuk tensor CPU ke [init_process_group](https://docs.pytorch.org/docs/stable/distributed.html#torch.distributed.init_process_group). Ini menghindari sinkronisasi CUDA yang dipaksakan akibat transfer dari host ke perangkat dan dari perangkat ke host.

```py
import torch.distributed as dist

dist.init_process_group(backend="cpu:gloo,cuda:nccl")
```

Benchmark di bawah ini menjalankan FLUX.1-dev pada 4 GPU L20 dengan [skrip ini](https://github.com/huggingface/diffusers/pull/12996#issuecomment-3797695999).

| Backend CP         | Waktu / Iterasi (ms) | Langkah / Detik | Memori Puncak (GB) | Bentuk (HxW)|
|--------------------|------------------|-------------|------------------|------------|
| ulysses            |   281.07         |    3.56     |     37.11        | 1024x1024  |
| ring               |   351.34         |    2.85     |     37.01        | 1024x1024  |
| unified_balanced   |   324.37         |    3.08     |     37.16        | 1024x1024  |
| ulysses_anything   |   280.94         |    3.56     |     37.11        | 1024x1024  |
| ulysses            |   gagal         |    gagal   |     gagal       | 1008x1008  |
| ring               |   gagal         |    gagal   |     gagal       | 1008x1008  |
| unified_balanced   |   gagal         |    gagal   |     gagal       | 1008x1008  |
| ulysses_anything   |   278.40         |    3.59     |     36.99        | 1008x1008  |

Pada 1008x1008, di mana panjang sekuens tidak terbagi rata, hanya Ulysses Anything Attention yang berjalan. Pada 1024x1024, kinerjanya sama dengan Ulysses Attention standar.

## Ring Anything Attention

Ring Attention membutuhkan panjang sekuens yang dapat dibagi rata oleh `ring_degree`. [Ring Anything Attention](https://github.com/huggingface/diffusers/pull/13545#issuecomment-4302195582) mendukung panjang sekuens apa pun. Ia melakukan padding pada K/V lokal di setiap GPU hingga panjang lokal terpanjang, melakukan all-gather pada K/V yang sudah di-padding, dan memotong kembali setiap blok ke panjang aslinya sebelum menghitung attention.

Anda tidak dapat menggabungkannya dengan Unified Attention, sehingga `ulysses_degree` harus 1. Atur `ring_degree` ke lebih dari 1 dan `ring_anything=True`.

```py
pipeline.transformer.enable_parallelism(config=ContextParallelConfig(ring_degree=2, ring_anything=True))
```

Ring Anything Attention juga mendapat manfaat dari backend `gloo` yang ditampilkan di [Ulysses Anything Attention](#ulysses-anything-attention).

> [!NOTE]
> Ring Anything Attention hanya mendukung inferensi, dan `attn_mask` harus `None`.

Benchmarks di bawah ini menjalankan FLUX.1-dev pada 4 GPU RTX 4090 (48GB).

| Backend CP         | Waktu / Iterasi (ms) | Langkah / Detik | Memori Puncak (GB) | Bentuk (HxW)|
|--------------------|------------------|-------------|------------------|------------|
| ulysses            |   259.07         |    3.86     |     33.83        | 1024x1024  |
| ring               |   338.98         |    2.95     |     33.83        | 1024x1024  |
| unified_balanced   |   321.54         |    3.11     |     33.83        | 1024x1024  |
| ulysses_anything   |   259.07         |    3.86     |     33.83        | 1024x1024  |
| ring_anything      |   340.14         |    2.94     |     33.83        | 1024x1024  |
| ulysses            |   gagal         |    gagal   |     gagal       | 1008x1008  |
| ring               |   gagal         |    gagal   |     gagal       | 1008x1008  |
| unified_balanced   |   gagal         |    gagal   |     gagal       | 1008x1008  |
| ulysses_anything   |   253.16         |    3.95     |     33.75        | 1008x1008  |
| ring_anything      |   335.57         |    2.98     |     33.75        | 1008x1008  |

Ring Anything Attention berjalan pada 1008x1008 dan kinerjanya kurang lebih sama dengan Ring Attention standar. Untuk motivasi dan trade-off-nya, lihat [komentar ini](https://github.com/huggingface/diffusers/pull/13545#issuecomment-4304104462).

## Aktifkan paralelisme konteks saat memuat

Alih-alih memanggil `enable_parallelism` setelah memuat, teruskan [ContextParallelConfig](/docs/diffusers/v0.43.0/id/api/parallel#diffusers.ContextParallelConfig) ke argumen `parallel_config` dari [from_pretrained()](/docs/diffusers/v0.43.0/id/api/models/overview#diffusers.ModelMixin.from_pretrained). Inisialisasi grup proses terlebih dahulu, karena `from_pretrained` akan melempar error jika `torch.distributed` belum diinisialisasi. Jalankan skrip dengan `torchrun`, seperti pada contoh [Ring Attention](#ring-attention).

```py
import os
import torch
from torch import distributed as dist
from diffusers import AutoModel, ContextParallelConfig, DiffusionPipeline

CKPT_ID = "black-forest-labs/FLUX.1-dev"

dist.init_process_group(backend="nccl")
local_rank = int(os.environ["LOCAL_RANK"])
device = torch.device(f"cuda:{local_rank}")
torch.cuda.set_device(device)

cp_config = ContextParallelConfig(ring_degree=dist.get_world_size())
transformer = AutoModel.from_pretrained(
    CKPT_ID,
    subfolder="transformer",
    dtype=torch.bfloat16,
    parallel_config=cp_config
)

pipeline = DiffusionPipeline.from_pretrained(
    CKPT_ID, transformer=transformer, dtype=torch.bfloat16,
).to(device)
```

