Buckets:

hf-doc-build/doc / diffusers /v0.43.0 /es /optimization /context_parallelism.md
mishig's picture
|
download
raw
12.8 kB

Paralelismo de contexto

El paralelismo de contexto divide la secuencia de entrada entre las GPU y cada GPU procesa su propia porción. Esto reduce la memoria por GPU, lo cual resulta más útil para secuencias largas, como imágenes de alta resolución y vídeo. Para compararlo con las demás estrategias, consulte Inferencia distribuida.

El paralelismo de contexto se ejecuta dentro del backend de atención, y solo algunos backends lo admiten. Si el suyo no lo hace, enable_parallelism lanza un error que enumera los backends compatibles. Cambie de backend con set_attention_backend() y consulte los backends disponibles para ver la lista completa.

Configure cada variante de abajo con ContextParallelConfig.

Variante Config Requisitos Uso
Ring Attention ring_degree > 1 la longitud de la secuencia debe ser divisible exactamente por ring_degree las secuencias son largas y el ancho de banda de interconexión es limitado
Atención Ulysses ulysses_degree > 1 la longitud de la secuencia y el número de cabezas de atención deben ser divisibles exactamente por ulysses_degree sus GPUs tienen un interconexión rápida
Atención unificada ring_degree > 1 y ulysses_degree > 1 al menos 4 GPUs tienes más dispositivos de los que Ulysses Attention puede usar por sí solo
Ulysses Anything Attention ulysses_degree > 1, ulysses_anything=True ring_degree debe ser 1 la longitud de la secuencia o el número de cabezas no se divide de forma uniforme
Ring Anything Attention ring_degree > 1, ring_anything=True ulysses_degree debe ser 1, solo inferencia, attn_mask debe ser None la longitud de la secuencia no se divide exactamente

Ring Attention

Ring Attention transmite los bloques de claves (K) y valores (V) entre las GPU dispuestas en un anillo. Cada GPU calcula la atención contra el bloque K/V que posee y luego pasa ese bloque a la GPU siguiente. Después de un ciclo completo, cada GPU ha visto los K/V de cada token y ninguna GPU almacena la secuencia completa.

Establezca ring_degree en el número de GPUs y pase la configuración al método enable_parallelism() del transformer.

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

Guarde el script como ring_attention.py y ejecútelo con torchrun o cualquier otro lanzador compatible con PyTorch. Establezca --nproc-per-node en el número de GPUs.

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

Para probar las otras variantes, reemplace la llamada a enable_parallelism en ring_attention.py con la de su sección.

Ulysses Attention

Ulysses Attention utiliza comunicación all-to-all, donde cada GPU intercambia datos con todas las demás GPUs. El primer all-to-all reagrupa los datos para que cada GPU contenga todos los tokens de un subconjunto de cabezas de atención. Cada GPU calcula la atención para sus cabezas, y un segundo all-to-all reagrupa los resultados por token para la siguiente capa.

Establezca ulysses_degree en el número de GPUs y pase la configuración a enable_parallelism().

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

Atención unificada

Unified Sequence Parallelism combina Ring Attention y Ulysses Attention. Ejecuta un all-to-all para redistribuir las cabezas y los tokens, ejecuta Ring Attention sobre el resultado y luego invierte el all-to-all para restaurar el diseño original. Ulysses paraleliza entre las cabezas de atención y Ring paraleliza a lo largo de la secuencia, por lo que, juntos, pueden utilizar más GPUs que Ulysses por sí solo.

Use Unified Attention cuando tenga al menos 4 GPUs. Establezca tanto ulysses_degree como ring_degree en un valor mayor que 1. Las GPUs forman una cuadrícula 2D de ulysses_degree * ring_degree dispositivos, y cada GPU pertenece a un grupo Ulysses y a un grupo Ring.

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

El siguiente benchmark compara Ulysses, Ring y Unified Attention en 4 GPUs H100 con este script. Cada imagen utiliza 50 pasos de inferencia.

Backend de CP Tiempo / Imagen (ms) Pasos / Seg Memoria máxima (GB)
ulysses 6670.789 7.50 33.85
ring 13076.492 3.82 56.02
unified_balanced 11068.705 4.52 33.85

Ulysses tiene el mayor rendimiento, pero no puede usar más GPUs que las cabezas de atención que tiene el modelo. Unified Attention elimina ese límite.

Ulysses Anything Attention

Ulysses Attention requiere que la longitud de la secuencia y el número de cabezas de atención sean divisibles exactamente por ulysses_degree. Ulysses Anything Attention elimina ambos requisitos. No se puede combinar con Unified Attention, por lo que ring_degree debe ser 1.

Establezca ulysses_degree en un valor mayor que 1 y ulysses_anything=True.

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

Agregue un backend gloo para tensores de CPU a init_process_group. Esto evita las sincronizaciones forzadas de CUDA que provocan las transferencias de host a dispositivo y de dispositivo a host.

import torch.distributed as dist

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

El benchmark siguiente ejecuta FLUX.1-dev en 4 GPUs L20 con este script.

Backend de CP Tiempo / Iteración (ms) Pasos / Seg Memoria máxima (GB) Forma (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 fallido fallido fallido 1008x1008
ring fallido fallido fallido 1008x1008
unified_balanced fallido fallido fallido 1008x1008
ulysses_anything 278.40 3.59 36.99 1008x1008

En 1008x1008, donde la longitud de la secuencia no se divide exactamente, solo se ejecuta Ulysses Anything Attention. En 1024x1024, su rendimiento es el mismo que el de Ulysses Attention estándar.

Ring Anything Attention

Ring Attention requiere que la longitud de la secuencia sea divisible exactamente por ring_degree. Ring Anything Attention admite cualquier longitud de secuencia. Rellena los K/V locales de cada GPU hasta la longitud local más larga, realiza un all-gather de los K/V rellenos y recorta cada bloque a su longitud real antes de calcular la atención.

No se puede combinar con Unified Attention, por lo que ulysses_degree debe ser 1. Establezca ring_degree en un valor mayor que 1 y ring_anything=True.

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

Ring Anything Attention también se beneficia del backend gloo mostrado en Ulysses Anything Attention.

Ring Anything Attention solo admite inferencia y attn_mask debe ser None.

El benchmark siguiente ejecuta FLUX.1-dev en 4 GPUs RTX 4090 (48GB).

Backend de CP Tiempo / Iteración (ms) Pasos / Seg Memoria máxima (GB) Forma (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 fallido fallido fallido 1008x1008
ring fallido fallido fallido 1008x1008
unified_balanced fallido fallido fallido 1008x1008
ulysses_anything 253.16 3.95 33.75 1008x1008
ring_anything 335.57 2.98 33.75 1008x1008

Ring Anything Attention se ejecuta a 1008x1008 y rinde aproximadamente igual que el Ring Attention estándar. Para conocer la motivación y las compensaciones, consulte este comentario.

Activar el paralelismo de contexto durante la carga

En lugar de llamar a enable_parallelism después de cargar, pase el ContextParallelConfig al argumento parallel_config de from_pretrained(). Inicialice primero el grupo de procesos, ya que from_pretrained genera un error si torch.distributed no está inicializado. Ejecute el script con torchrun, como en el ejemplo de Ring Attention.

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)

Xet Storage Details

Size:
12.8 kB
·
Xet hash:
2b7032bc519a4c967f7d4c99066493caaed1eb0af8d5b628c589fdea9ba74438

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.