Diffusers documentation

ParaAttention

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.43.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

ParaAttention

Los modelos de generación de imágenes y videos de gran tamaño, como FLUX.1-dev y HunyuanVideo, pueden suponer un reto de inferencia para aplicaciones en tiempo real y despliegue debido a su tamaño.

ParaAttention es una biblioteca que implementa paralelismo de contexto y cache del primer bloque, y puede combinarse con otras técnicas (torch.compile, cuantización dinámica fp8) para acelerar la inferencia.

Esta guía le mostrará cómo aplicar ParaAttention a FLUX.1-dev y HunyuanVideo en GPUs NVIDIA L20. No se aplican optimizaciones para nuestro benchmark de línea base, excepto para HunyuanVideo, para evitar errores de memoria insuficiente.

Nuestro benchmark de referencia muestra que FLUX.1-dev es capaz de generar una imagen de resolución 1024x1024 en 28 pasos en 26.36 segundos, y HunyuanVideo es capaz de generar 129 fotogramas a resolución 720p en 30 pasos en 3675.71 segundos.

Para una inferencia aún más rápida con paralelismo de contexto, intente usar GPUs NVIDIA A100 o H100 (si están disponibles) con soporte para NVLink, especialmente cuando hay un gran número de GPUs.

Caché del primer bloque

Almacenar en caché la salida de los bloques de transformers del modelo y reutilizarla en los pasos de inferencia posteriores reduce el costo de cómputo y acelera la inferencia.

Sin embargo, es difícil decidir cuándo reutilizar la caché para garantizar la calidad de las imágenes o videos generados. ParaAttention utiliza directamente la diferencia residual de la salida del primer bloque de transformer para aproximar la diferencia entre las salidas del modelo. Cuando la diferencia es lo suficientemente pequeña, se reutiliza la diferencia residual de los pasos de inferencia anteriores. En otras palabras, se omite el paso de desruido.

Esto logra una aceleración de 2x en la inferencia de FLUX.1-dev y HunyuanVideo con una calidad muy buena.

Cache in Diffusion Transformer
Cómo funciona AdaCache, First Block Cache es una variante de este
FLUX-1.dev
HunyuanVideo

Para aplicar la caché del primer bloque en FLUX.1-dev, llame a apply_cache_on_pipe como se muestra a continuación. 0.08 es el valor de diferencia residual predeterminado para los modelos FLUX.

import time
import torch
from diffusers import FluxPipeline

pipe = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev",
    dtype=torch.bfloat16,
).to("cuda")  # or "mps", "xpu", "cpu"

from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe

apply_cache_on_pipe(pipe, residual_diff_threshold=0.08)

# Enable memory savings
# pipe.enable_model_cpu_offload()
# pipe.enable_sequential_cpu_offload()

begin = time.time()
image = pipe(
    "A cat holding a sign that says hello world",
    num_inference_steps=28,
).images[0]
end = time.time()
print(f"Time: {end - begin:.2f}s")

print("Saving image to flux.png")
image.save("flux.png")
OptimizacionesOriginalFBCache rdt=0.06FBCache rdt=0.08FBCache rdt=0.10FBCache rdt=0.12
Vista previaOriginalFBCache rdt=0.06FBCache rdt=0.08FBCache rdt=0.10FBCache rdt=0.12
Tiempo real (s)26.3621.8317.0116.0013.78

First Block Cache redujo la velocidad de inferencia a 17,01 segundos en comparación con la línea base, es decir, 1,55 veces más rápido, mientras mantenía una pérdida de calidad casi nula.

cuantización fp8

fp8 con cuantización dinámica acelera aún más la inferencia y reduce el uso de memoria. Tanto las activaciones como los pesos deben estar cuantizados para poder utilizar los NVIDIA Tensor Cores de 8 bits.

Use float8_weight_only y float8_dynamic_activation_float8_weight para cuantizar el codificador de texto y el modelo transformer.

El método de cuantización predeterminado es la cuantización por tensor, pero si tu GPU admite la cuantización por fila, también puedes probarla para obtener una mayor precisión.

Instala torchao con el siguiente comando.

pip3 install -U torch torchao

torch.compile con mode="max-autotune-no-cudagraphs" o mode="max-autotune" selecciona el mejor kernel para el rendimiento. La compilación puede tardar mucho si es la primera vez que se llama al modelo, pero vale la pena una vez que el modelo ha sido compilado.

Este ejemplo solo cuantiza el modelo transformer, pero también puedes cuantizar el codificador de texto para reducir aún más el uso de memoria.

La cuantización dinámica puede cambiar significativamente la distribución de la salida del modelo, por lo que es necesario cambiar residual_diff_threshold a un valor mayor para que surta efecto.

FLUX-1.dev
HunyuanVideo
import time
import torch
from diffusers import FluxPipeline

pipe = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev",
    dtype=torch.bfloat16,
).to("cuda")  # or "mps", "xpu", "cpu"

from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe

apply_cache_on_pipe(
    pipe,
    residual_diff_threshold=0.12,  # Use a larger value to make the cache take effect
)

from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight, float8_weight_only

quantize_(pipe.text_encoder, float8_weight_only())
quantize_(pipe.transformer, float8_dynamic_activation_float8_weight())
pipe.transformer = torch.compile(
   pipe.transformer, mode="max-autotune-no-cudagraphs",
)

# Enable memory savings
# pipe.enable_model_cpu_offload()
# pipe.enable_sequential_cpu_offload()

for i in range(2):
    begin = time.time()
    image = pipe(
        "A cat holding a sign that says hello world",
        num_inference_steps=28,
    ).images[0]
    end = time.time()
    if i == 0:
        print(f"Warm up time: {end - begin:.2f}s")
    else:
        print(f"Time: {end - begin:.2f}s")

print("Saving image to flux.png")
image.save("flux.png")

La cuantización dinámica fp8 y torch.compile redujeron la velocidad de inferencia a 7,56 segundos en comparación con la línea base, es decir, 3,48 veces más rápido.

Paralelismo de contexto

El paralelismo de contexto (Context Parallelism) paraleliza la inferencia y escala con múltiples GPUs. El diseño composicional de ParaAttention le permite combinar el paralelismo de contexto con la caché del primer bloque (First Block Cache) y la cuantización dinámica.

Consulte el repositorio ParaAttention para obtener instrucciones detalladas y ejemplos sobre cómo escalar la inferencia con varias GPU.

Si el proceso de inferencia debe ser persistente y servible, se sugiere usar torch.multiprocessing para escribir su propio procesador de inferencia. Esto puede eliminar la sobrecarga de iniciar el proceso y cargar y recompilar el modelo.

FLUX-1.dev
HunyuanVideo

La muestra de código siguiente combina First Block Cache, cuantización dinámica fp8, torch.compile y Context Parallelism para la velocidad de inferencia más rápida.

import time
import torch
import torch.distributed as dist
from diffusers import FluxPipeline

dist.init_process_group()

torch.cuda.set_device(dist.get_rank())

pipe = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev",
    dtype=torch.bfloat16,
).to("cuda")  # or "mps", "xpu", "cpu"

from para_attn.context_parallel import init_context_parallel_mesh
from para_attn.context_parallel.diffusers_adapters import parallelize_pipe
from para_attn.parallel_vae.diffusers_adapters import parallelize_vae

mesh = init_context_parallel_mesh(
    pipe.device.type,
    max_ring_dim_size=2,
)
parallelize_pipe(
    pipe,
    mesh=mesh,
)
parallelize_vae(pipe.vae, mesh=mesh._flatten())

from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe

apply_cache_on_pipe(
    pipe,
    residual_diff_threshold=0.12,  # Use a larger value to make the cache take effect
)

from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight, float8_weight_only

quantize_(pipe.text_encoder, float8_weight_only())
quantize_(pipe.transformer, float8_dynamic_activation_float8_weight())
torch._inductor.config.reorder_for_compute_comm_overlap = True
pipe.transformer = torch.compile(
   pipe.transformer, mode="max-autotune-no-cudagraphs",
)

# Enable memory savings
# pipe.enable_model_cpu_offload(gpu_id=dist.get_rank())
# pipe.enable_sequential_cpu_offload(gpu_id=dist.get_rank())

for i in range(2):
    begin = time.time()
    image = pipe(
        "A cat holding a sign that says hello world",
        num_inference_steps=28,
        output_type="pil" if dist.get_rank() == 0 else "pt",
    ).images[0]
    end = time.time()
    if dist.get_rank() == 0:
        if i == 0:
            print(f"Warm up time: {end - begin:.2f}s")
        else:
            print(f"Time: {end - begin:.2f}s")

if dist.get_rank() == 0:
    print("Saving image to flux.png")
    image.save("flux.png")

dist.destroy_process_group()

Guárdalo en run_flux.py y ejecútalo con torchrun.

# Use --nproc_per_node to specify the number of GPUs
torchrun --nproc_per_node=2 run_flux.py

La velocidad de inferencia se reduce a 8,20 segundos en comparación con la línea base, es decir, es 3,21 veces más rápida, con 2 GPU NVIDIA L20. En 4 L20, la velocidad de inferencia es de 3,90 segundos, o 6,75 veces más rápida.

Benchmarks

FLUX-1.dev
HunyuanVideo
Tipo de GPUNúmero de GPUsOptimizacionesTiempo real (s)Aceleración
NVIDIA L201Línea base26.361.00x
NVIDIA L201FBCache (rdt=0.08)17.011.55x
NVIDIA L201FP8 DQ13.401.96x
NVIDIA L201FBCache (rdt=0.12) + FP8 DQ7.563.48x
NVIDIA L202FBCache (rdt=0.12) + FP8 DQ + CP4.925.35x
NVIDIA L204FBCache (rdt=0.12) + FP8 DQ + CP3.906.75x
Update on GitHub