Buckets:

hf-doc-build/doc / diffusers /main /tr /training /distributed_inference.md
mishig's picture
|
download
raw
36.2 kB

Dağıtık çıkarım

Dağıtık çıkarım, iş yükünü birden fazla GPU arasında paylaştırır. Daha büyük modellerin belleğe sığdırılması için faydalı bir tekniktir ve daha yüksek bir aktarım hızı için birden çok istemciyi işleyebilir.

Bu kılavuz, dağıtık çıkarım için Accelerate ve PyTorch Distributed kullanmayı gösterecektir.

Accelerate

Accelerate, kurulumu ele alarak kullanıcıların PyTorch kodlarına odaklanmasını sağlamak üzere, birden fazla hızlandırıcıda çıkarım ve eğitimi basitleştirmek için tasarlanmış bir kütüphanedir.

Aşağıdaki komutla Accelerate'i yükleyin.

uv pip install accelerate

Dağıtık bir ortam oluşturmak için bir Python dosyasında accelerate.PartialState sınıfını başlatın. accelerate.PartialState sınıfı, süreç yönetimi, cihaz kontrolü ve dağıtımı ile süreç koordinasyonunu yönetir.

Her bir işlem için bir GPU atamak üzere DiffusionPipeline nesnesini accelerate.PartialState.device konumuna taşıyın.

import torch
from accelerate import PartialState
from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "Qwen/Qwen-Image", dtype=torch.float16
)
distributed_state = PartialState()
pipeline.to(distributed_state.device)

İstemcileri işlem sayısı arasında otomatik olarak dağıtmak için split_between_processes yardımcısını bir bağlam yöneticisi olarak kullanın.

with distributed_state.split_between_processes(["a dog", "a cat"]) as prompt:
    result = pipeline(prompt).images[0]
    result.save(f"result_{distributed_state.process_index}.png")

Betik çalıştırmak için accelerate launch komutunu çağırın ve kullanılacak GPU sayısını belirlemek için --num_processes argümanını kullanın.

accelerate launch run_distributed.py --num_processes=2

Birden fazla GPU üzerinde çıkarım (inference) çalıştırmak için bu minimal script örneğine bakın. Daha fazla bilgi edinmek için Distributed Inference with 🤗 Accelerate kılavuzuna göz atın.

PyTorch Dağıtık

PyTorch DistributedDataParallel, aynı modeli her cihazda çoğaltarak farklı veri topluluklarını paralel olarak işlemek için data parallelism (veri paralelliği) sağlar.

Dağıtık işlem grubunu oluşturmak ve her GPU için çıkarım süreçlerini başlatmak üzere bir Python dosyasına torch.distributed ve torch.multiprocessing içe aktarın.

import torch
import torch.distributed as dist
import torch.multiprocessing as mp

from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "Qwen/Qwen-Image", dtype=torch.float16,
)

init_process_group ile çıkarım için bir fonksiyon oluşturun. Bu yöntem, backend türü, mevcut sürecin rank değeri ve katılan süreç sayısı olan world_size (örneğin, 2 GPU için world_size=2) ile dağıtık bir ortam oluşturur.

Hattı rank'a taşıyın ve her bir sürece bir GPU atamak için get_rank kullanın. Her süreç farklı bir istemciyi işler.

def run_inference(rank, world_size):
    dist.init_process_group("nccl", rank=rank, world_size=world_size)

    pipeline.to(rank)

    if torch.distributed.get_rank() == 0:
        prompt = "a dog"
    elif torch.distributed.get_rank() == 1:
        prompt = "a cat"

    image = sd(prompt).images[0]
    image.save(f"./{'_'.join(prompt)}.png")

world_size içinde tanımlanan süreç sayısını oluşturmak için mp.spawn kullanın.

def main():
    world_size = 2
    mp.spawn(run_inference, args=(world_size,), nprocs=world_size, join=True)

if __name__ == "__main__":
    main()

Çıkarım betiğini çalıştırmak için torchrun çağırın ve kullanılacak GPU sayısını ayarlamak için --nproc_per_node argümanını kullanın.

torchrun --nproc_per_node=2 run_distributed.py

device_map

device_map argümanı, model bileşenlerini ayrı GPU'lara otomatik olarak yerleştirerek dağıtık çıkarımı (inference) etkinleştirir. Bu, bir modelin tek bir GPU'ya sığmadığı durumlarda özellikle faydalıdır. Aşağıdaki örnekte gösterildiği gibi (iki GPU'nun mevcut olduğu varsayılır), belirli bir aşamada gerekli model bileşenlerini seçici olarak yüklemek ve boşaltmak için device_map kullanabilirsiniz.

device_map="balanced" değerini ayarlayarak metin kodlayıcılarını (text encoders) tüm kullanılabilir GPU'lara eşit şekilde dağıtabilirsiniz. Her bir metin kodlayıcı için tahsis edilecek maksimum bellek miktarını belirtmek için max_memory argümanını kullanabilirsiniz. Bellek kullanımını önlemek için başka hiçbir pipeline bileşeni yüklemeyin.

from diffusers import FluxPipeline
import torch

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

pipeline = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev",
    transformer=None,
    vae=None,
    device_map="balanced",
    max_memory={0: "16GB", 1: "16GB"},
    dtype=torch.bfloat16
)
with torch.no_grad():
    print("Encoding prompts.")
    prompt_embeds, pooled_prompt_embeds, text_ids = pipeline.encode_prompt(
        prompt=prompt, prompt_2=None, max_sequence_length=512
    )

Metin gömlemleri hesaplandıktan sonra, difüzyon dönüştürücüsü için yer açmak üzere GPU'dan kaldırın.

import gc 

def flush():
    gc.collect()
    torch.cuda.empty_cache()
    torch.cuda.reset_max_memory_allocated()
    torch.cuda.reset_peak_memory_stats()

del pipeline.text_encoder
del pipeline.text_encoder_2
del pipeline.tokenizer
del pipeline.tokenizer_2
del pipeline

flush()

device_map="auto" değerini ayarlayarak modelin iki GPU üzerinde otomatik olarak dağıtılmasını sağlayın. Bu strateji, modeli önce en hızlı cihaza yerleştirir; gerekirse modeli CPU veya sabit disk gibi daha yavaş bir cihaza yerleştirir. Model parametrelerini daha yavaş cihazlarda saklamanın dezavantajı, daha yavaş çıkarım gecikmesidir.

from diffusers import AutoModel
import torch 

transformer = AutoModel.from_pretrained(
    "black-forest-labs/FLUX.1-dev", 
    subfolder="transformer",
    device_map="auto",
    dtype=torch.bfloat16
)

Farklı modellerin cihazlar arasında nasıl dağıtıldığını görmek için pipeline.hf_device_map çalıştırın. Bu, model cihaz yerleşimini izlemek için kullanışlıdır. Ayrıca, dağılımını görmek için dönüştürücü model üzerinde hf_device_map çağrısını da yapabilirsiniz.

Dönüştürücü modelini hattın (pipeline) içine ekleyin ve latentleri üretmek için output_type="latent" değerini ayarlayın.

pipeline = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev",
    text_encoder=None,
    text_encoder_2=None,
    tokenizer=None,
    tokenizer_2=None,
    vae=None,
    transformer=transformer,
    dtype=torch.bfloat16
)

print("Running denoising.")
height, width = 768, 1360
latents = pipeline(
    prompt_embeds=prompt_embeds,
    pooled_prompt_embeds=pooled_prompt_embeds,
    num_inference_steps=50,
    guidance_scale=3.5,
    height=height,
    width=width,
    output_type="latent",
).images

Hafızadan pipeline ve transformer'ı kaldırın ve latents'leri çözmek için bir VAE yükleyin. VAE genellikle tek bir cihaza yüklenebilecek kadar küçüktür.

import torch
from diffusers import AutoencoderKL
from diffusers.image_processor import VaeImageProcessor

vae = AutoencoderKL.from_pretrained(ckpt_id, subfolder="vae", dtype=torch.bfloat16).to("cuda")  # or "mps", "xpu", "cpu"
vae_scale_factor = 2 ** (len(vae.config.block_out_channels) - 1)
image_processor = VaeImageProcessor(vae_scale_factor=vae_scale_factor)

with torch.no_grad():
    print("Running decoding.")
    latents = FluxPipeline._unpack_latents(latents, height, width, vae_scale_factor)
    latents = (latents / vae.config.scaling_factor) + vae.config.shift_factor

    image = vae.decode(latents, return_dict=False)[0]
    image = image_processor.postprocess(image, output_type="pil")
    image[0].save("split_transformer.png")

Belirli bir aşamada ihtiyaç duyduğunuz modelleri seçici olarak yükleyip boşaltarak ve en büyük modelleri birden çok GPU arasında paylaştırarak, tüketici GPU'larında büyük modellerle çıkarım (inference) çalıştırılabilir.

Bağlam paralelliği

Bağlam paralelliği, bellek kullanımını azaltmak için giriş dizilerini birden çok GPU arasında böler. Her GPU, dizinin kendi dilimini işler.

Daha optimize bir dikkat (attention) arka ucuna geçmek için set_attention_backend() kullanın. Kullanılabilir arka uçların tam listesi için bu tabloya bakın.

Çoğu dikkat (attention) arka ucu, bağlam paralelliğiyle uyumludur. Bir arka uç uyumlu değilse bir issue açın.

Halka Dikkat

Key (K) ve değer (V) temsilleri, cihazlar arasında Ring Attention kullanarak iletişim kurar. Bu, her bölünmenin diğer tüm token'ların K/V'lerini görmesini sağlar. Her GPU, yerel K/V'si için dikkat hesaplamasını yapar ve bunu halkadaki bir sonraki GPU'ya iletir. Tek bir GPU tam diziyi barındırmaz; bu da iletişim gecikmesini azaltır.

Dönüştürücü modelinin parallel_config argümanına bir ContextParallelConfig geçirin. Yapılandırma, Ring Attention için kaç cihazın kullanılacağını belirleyen ring_degree argümanını destekler.

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")
    rank = dist.get_rank()
    device = torch.device(f"cuda:{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(f"output.png")

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

if __name__ == "__main__":
    main()

Yukarıdaki betik, PyTorch ile uyumlu torchrun gibi dağıtık bir başlatıcı ile çalıştırılmalıdır. --nproc-per-node, kullanılabilir GPU sayısına ayarlanır.

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

Ulysses Dikkat

Ulysses Attention, bir diziyi GPU'lar arasında böler ve all-to-all iletişimi gerçekleştirir (her cihaz, veri göndermek ve almak için her diğer cihazla etkileşime girer). Her GPU, yalnızca dikkat başlıklarının bir alt kümesi için tüm token'lara sahip olur. Her GPU, kendi başlığı için tüm token'lar üzerinde dikkati yerel olarak hesaplar, ardından bir sonraki katman için sonuçları token'lara göre yeniden gruplamak üzere başka bir all-to-all işlemi gerçekleştirir.

ContextParallelConfig, ulysses_degree argümanı aracılığıyla Ulysses Attention'ı destekler. Bu, Ulysses Attention için kaç cihazın kullanılacağını belirler.

ContextParallelConfig nesnesini enable_parallelism() fonksiyonuna iletin.

# Depending on the number of GPUs available.
pipeline.transformer.enable_parallelism(config=ContextParallelConfig(ulysses_degree=2))

Birleşik Dikkat

Unified Sequence Parallelism, uzun dizi işleme için verimli bir yaklaşım olarak Ring Attention ve Ulysses Attention'ı tek bir yöntemde birleştirir. Önce başlıkları ve dizi jetonlarını yeniden dağıtmak için Ulysses'in all-to-all iletişimini uygular, ardından yeniden dağıtılan verileri işlemek için Ring Attention kullanır ve son olarak orijinal düzeni geri getirmek için all-to-all işlemini tersine çevirir.

Bu hibrit yaklaşım, her iki yöntemin güçlü yönlerinden faydalanır:

  • Ulysses Attention, dikkat başlıkları arasında verimli bir şekilde paralelleştirme sağlar
  • Ring Attention, çok uzun dizileri minimal bellek maliyetiyle işler
  • Birlikte, hem başlar hem de dizi boyutları boyunca 2B paralelleştirmeyi mümkün kılarlar

ContextParallelConfig, hem ulysses_degree hem de ring_degree parametrelerini belirleyerek Unified Attention'ı destekler. Kullanılan toplam cihaz sayısı ulysses_degree * ring_degree'tür ve Ulysses ile Ring gruplarının dik (keseşmeyen) olduğu 2B bir ızgarada düzenlenir. ulysses_degree ve ring_degree değerlerinin her ikisi de 1'den büyük olacak şekilde ayarlanmış ContextParallelConfig nesnesini enable_parallelism() fonksiyonuna iletin.

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

Unified Attention, 2D bir ızgara oluşturmak için yeterli sayıda cihaz olduğunda (en az 4 cihaz) kullanılmalıdır.

Ulysess, Ring ve Unified Attention için bu betiği kullanarak 4 H100 GPU'lu bir düğümde bir benchmark çalıştırdık. Sonuçlar aşağıda özetlenmiştir:

CP Arka Ucu Zaman / İterasyon (ms) Adım / Saniye Zirve Bellek (GB)
ulysses 6670.789 7.50 33.85
halka 13076.492 3.82 56.02
unified_balanced 11068.705 4.52 33.85

Yukarıdaki tablodan, Ulysses'in daha iyi bir veri akışı sağladığı açıkça görülüyor; ancak kullanabileceği cihaz sayısı, dikkat başlarının sayısıyla sınırlı kalmaya devam ediyor ve bu kısıtlama birleşik dikkat (unified attention) ile çözümleniyor.

Ulysses Anything Dikkat

Varsayılan Ulysses Attention mekanizması, gizli durumların (hidden states) dizi uzunluğunun cihaz sayısına tam bölünmesini gerektirir. Bu durum, Ulysses Attention'ın pratik kullanımına önemli kısıtlamalar getirmektedir. Ulysses Anything Attention, Ulysses Attention'ın bir varyantı olup keyfi dizi uzunluklarını ve keyfi dikkat başı (attention head) sayılarını destekleyerek, Ulysses Attention'ın pratik kullanımdaki çok yönlülüğünü artırır.

ContextParallelConfig, ulysses_degree ve ulysses_anything parametrelerini birlikte belirleyerek Ulysses Anything Attention desteği sunar. Ulysses Anything Attention'ın şu anda Unified Attention tarafından desteklenmediğini lütfen unutmayın. ulysses_degree değerini 1'den büyük olacak şekilde ve ulysses_anything=True olarak ayarlanmış ContextParallelConfig nesnesini enable_parallelism() fonksiyonuna iletin.

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

H2D ve D2H aktarımlarından kaynaklanan birden fazla zorunlu CUDA senkronizasyonunu önlemek için init_process_group içinde gloo arka ucunu ekleyin. Bu, iletişim gecikmesini önemli ölçüde azaltacaktır.

FLUX.1-dev için Ulysses, Ring, Unified Attention ve Ulysses Anything Attention ile bu betiği kullanarak 4 L20 GPU'lu bir düğümde bir benchmark çalıştırdık. Sonuçlar aşağıda özetlenmiştir:

CP Arka Ucu Zaman / İterasyon (ms) Adım / Saniye Zirve Bellek (GB) Biçim (HxW)
ulysses 281.07 3.56 37.11 1024x1024
halka 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 başarısız başarısız başarısız 1008x1008
halka başarısız başarısız başarısız 1008x1008
unified_balanced başarısız başarısız başarısız 1008x1008
ulysses_anything 278.40 3.59 36.99 1008x1008

Yukarıdaki tablodan, Ulysses Anything Attention'ın standart Ulysses Attention ile aynı performansı korurken keyfi dizi uzunluklarıyla daha iyi uyumluluk sunduğu açıkça görülmektedir.

Ring Anything Attention

Varsayılan Ring Attention, gizli durumların (hidden states) dizi uzunluğunun halka derecesine (ring degree) eşit olarak bölünebilmesini gerektirir. Ring Anything Attention, Ring Attention'ın keyfi (eşit olarak bölünemeyen) dizi uzunluklarını destekleyen bir varyantıdır. Her sıranın (rank) yerel KV'sini küresel maksimum dizi uzunluğuna kadar doldurur (padding), doldurulmuş KV tamponunu tüm toplar (all-gather) ve dikkat (attention) çalıştırılmadan önce her sıranın gerçek uzunluğuna geri keser.

Ring Anything Attention, Unified Attention tarafından desteklenmez. Ring Anything Attention'i etkinleştirmek için ring_degree > 1 ve ring_anything=True değerlerini ayarlayın.

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

Birden fazla zorunlu CUDA senkronizasyonunun H2D ve D2H aktarımlarından kaynaklanmasını önlemek için init_process_group fonksiyonuna gloo arka ucunu ekleyin.

import torch.distributed as dist

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

Ring Anything Attention şu anda yalnızca çıkarımı destekler ve None olmayan dikkat maskeleri desteklenmez. attn_mask None olmalıdır.

4 RTX 4090 (48GB) GPU'lu bir düğümde FLUX.1-dev referans değerleri aşağıda görülebilir.

CP Arka Ucu Zaman / İterasyon (ms) Adım / Saniye Zirve Bellek (GB) Biçim (HxW)
ulysses 259.07 3.86 33.83 1024x1024
halka 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 başarısız başarısız başarısız 1008x1008
halka başarısız başarısız başarısız 1008x1008
unified_balanced başarısız başarısız başarısız 1008x1008
ulysses_anything 253.16 3.95 33.75 1008x1008
ring_anything 335.57 2.98 33.75 1008x1008

Yukarıdaki tablodan, Ring Anything Attention'ın standart Ring Attention ile karşılaştırılabilir performansını korurken keyfi dizi uzunluklarıyla uyumlu olduğunu görüyoruz. Ring Anything Attention'ın motivasyonu ve ödünleşimleri hakkında daha fazla ayrıntı için bu yorumu inceleyin.

parallel_config

Model başlatma sırasında bağlam paralelliğini etkinleştirmek için parallel_config parametresini geçirin.

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

cp_config = ContextParallelConfig(ring_degree=2)
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)

Tensor paralelliği

Tensor parallelism, bir modelin ağırlık matrislerini cihazlar arasında parçalar. Her cihaz, her katman için sütun bazlı ("colwise") veya satır bazlı ("rowwise") bir dilim tutar, kısmi bir sonuç hesaplar ve katman sınırındaki AllReduce/AllGather işlemi tam çıktıyı yeniden oluşturur. Bağlam paralelliğinin aksine, cihaz başına ağırlık belleğini azaltır; bu da tek bir cihaza sığmayan modeller için faydalıdır.

Modelin from_pretrained() metodunun parallel_config argümanına bir TensorParallelConfig geçirin. tp_degree, parçalanacak cihaz sayısıdır ve modelin dikkat başı sayısına tam bölünmelidir. Model, bir _tp_plan (modül adı glob'larından "colwise"/"rowwise" stiline düz bir eşleme) tanımlamalıdır.

Bu yöntemle yüklenirken kontrol noktası okunurken parçalanır: her rank, parçalanan her ağırlığın yalnızca kendi dilimini okur ve onu doğrudan kendi cihazına yerleştirir. Tam boyutlu hiçbir şey asla maddi olarak oluşturulmadığından, rank başına bellek tp_degree arttıkça düşer.

enable_parallelism() çağrısı yapılarak tam modeli yüklemeye kıyasla, bu yöntem daha hızlı yüklenir ve rank başına daha az CPU belleği kullanır; tp_degree arttıkça bu fark büyür. Aşağıdaki sayılar, 4x A10G (23GB) üzerinde black-forest-labs/FLUX.2-dev (yalnızca transformer, 32B parametre, bf16) için verilmiştir.

tp_degree yöntem yüklenme süresi zirve CPU/rank
4 from_pretrained(parallel_config=...) 12.5s 6,8 GB
4 from_pretrained + enable_parallelism 30.4s 64,1 GB
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()
    device = torch.device(f"cuda:{rank}")
    torch.cuda.set_device(device)

    # Each rank reads only its own shard of every planned weight, straight onto `cuda:rank`.
    transformer = Flux2Transformer2DModel.from_pretrained(
        "black-forest-labs/FLUX.2-dev",
        subfolder="transformer",
        torch_dtype=torch.bfloat16,
        parallel_config=TensorParallelConfig(tp_degree=world_size),
    )

    pipeline = DiffusionPipeline.from_pretrained(
        "black-forest-labs/FLUX.2-dev", transformer=transformer, torch_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()
torchrun --nproc-per-node 4 tensor_parallel_flux.py

tp_degree, yukarıdaki world_size değerinden alınır; bu nedenle --nproc-per-node 4 komutu, transformer'ı 4 cihaz arasında paylaştırır.

parallel_config ile tensor-parallel yükleme, henüz device_map, quantization_config, low_cpu_mem_usage=False, use_flashpack=True veya safetensors olmayan ağırlıklarla desteklenmiyor; her biri sessizce tam checkpoint'i yüklemeye geri düşmek yerine hata fırlatıyor.

Gergilik paralelliğinin (tensor parallelism) kuantizasyon, offloading veya LoRA adaptörleriyle birleştirilmesi henüz desteklenmediğinden, model nasıl bölünürse (shard) olsun bu durumlar yine de … hatası verir.

Tensor-parallel modelleri bu şekilde yüklemenizi öneririz. Aynı yapılandırmayı kullanarak enable_parallelism() çağrısı yaparak bellekte zaten bulunan bir modeli dağitalayabilirsiniz (shard), ancak bu yöntem her şeyi önce yükleyip yeniden dağıtır, bu da her rank üzerinde tam checkpoint belleği maliyeti oluşturur.

Tensor-parallel bir modelin kaydedilmesi henüz desteklenmiyor ve save_pretrained() bu durumda bir hata fırlatır. Modeli parçalara ayırmadan önce kaydedin.

Bir tensör paralelliği planı yazma

Tensor paralelliği, yalnızca modül ad kümeleme kalıplarını bir bölme stiline eşleyen düz bir sınıf özelliği olan _tp_plan tanımlayan modellerde çalışır. Bir tane yazmak, büyük ölçüde gizli boyutu genişleten her projeksiyonu, onu geri daraltan projeksiyonla eşleştirmekle ilgilidir.

Her anahtar en fazla bir * içerebilir ve bu karakterden önceki önek, tek bir girdinin tüm blokları kapsaması için bir nn.ModuleList olarak çözümlenmelidir. * içermeyen bir anahtar, modelin kendisine uygulanır. Yollar modele göre göreli olarak tanımlanır.

Colwise ve rowwise

Stil Parçalar Her sıra Kullanım alanları
"colwise" çıkış özellikleri (weight boyutu 0) çıkışın bir dilimini hesaplar to_q, to_k, to_v, FFN iç projeksiyonu
"rowwise" girdi özellikleri (weight boyutu 1) kısmi bir toplam hesaplar to_out.0, FFN çıkış projeksiyonu
"rowwise_split_input" girdi özellikleri (weight boyutu 1) girdinin ve ağırlıkların kendi dilimine kullanarak kısmi bir sonuç hesaplar; AllReduce bu sonuçları ekler ve böylece her sıra tüm çıktı özelliklerini alır örneğin zaman adımı gömümünden bir AdaLN modülasyon projeksiyonu gibi, replike bir aktivasyonu okuyan bağımsız bir projeksiyon

Her zaman bu sırayla eşleştirin. "colwise" projeksiyonu çıktısını parçalı (sharded) bırakır, ardından gelen "rowwise" projeksiyonu bu parçayı doğrudan tüketir ve blok sınırındaki tek bir AllReduce işlemi sonucu yeniden oluşturur. Çifti başka bir şekilde parçalamak, ortada bir toplama (gather) işlemi zorunlu kılar ve çok daha fazla iletişim gerektirir.

Dikkat mekanizması için bu, her sıranın (rank) başların (head) bir alt kümesine sahip olduğu anlamına gelir; bu nedenle tp_degree, baş sayısına tam bölünmelidir. Kodlayıcı akış kopyaları (add_q_proj, to_add_out, ff_context), görüntü akışı karşılıklarıyla aynı deseni izler.

Birleştirilmiş projeksiyonlar

Tek bir Linear, parçalanan boyut boyunca birden fazla mantıksal tensörü paketlediğinde, düz "colwise"/"rowwise" dilimleri birleştirmenin üzerinden doğrudan geçer ve parçaları hizasız bırakır. Bunun yerine, her paketlenmiş bloğu bağımsız olarak parçalayan PackedColwiseParallel/PackedRowwiseParallel kullanın.

# in src/diffusers/models/transformers/your_model.py
from ...hooks.tensor_parallel import PackedColwiseParallel, PackedRowwiseParallel

blocks, toplamı paketlenmiş boyutu bölen oransal tam sayılardan oluşan bir listedir — eşit yarımlara sahip bir SwiGLU gate+up projeksiyonu için [1, 1] veya mlp_ratio=3 ile birleşik Q+K+V+gate+up projeksiyonu için [1, 1, 1, 3, 3].

"transformer_blocks.*.ff.linear_in": PackedColwiseParallel([1, 1]),

Blok boyutları yalnızca yapılandırmadan biliniyorsa, argümanı atlayın ve bunun yerine mutlak boyutları __init__ sırasında Linear üzerinde _tp_packed_col_blocks veya _tp_packed_row_blocks olarak saklayın.

# in the attention module's __init__
self.to_out._tp_packed_row_blocks = [self.inner_dim, self.mlp_hidden_dim]
# in the model's _tp_plan
"single_transformer_blocks.*.attn.to_out": PackedRowwiseParallel(),

Her iki biçim de transformer_flux2.py içindeki tek bir planda görünür: çift akışlı SwiGLU çifti blocks değerini olduğu gibi geçirirken, birleştirilmiş tek akışlı QKV+MLP projeksiyonu, boyutları mlp_ratio ve mlp_mult_factor değerlerine bağlı olduğu için bu boyutları Flux2ParallelSelfAttention.__init__ içindeki Linear sınıfında saklar.

Dışarıda bırakılacaklar

Planın dışında kalan her şey, her sırada kopyalanır; bu, normalizasyon katmanları, AdaLN modülasyonu (img_mod/txt_mod), yama ve metin gömme değerleri ile son norm_out/proj_out için doğru seçimdir. Bunlar küçük olduğundan, bunları bölümlere ayırmak az miktarda bellek tasarrufu sağlar ancak iletişim yükü ekler.

Kısıtlamalar ve doğrulama

  • tp_degree, config.num_attention_heads'i tam olarak bölmelidir.
  • Her paketlenmiş blok, yalnızca toplamları değil, tek tek tp_degree değerine bölünebilir olmalıdır.

Her ikisi de herhangi bir ağırlık yüklenmeden veya bölünmeden önce from_pretrained() ve enable_parallelism() tarafından doğrulanır.

Yeni bir planı gözle değil, sayısal olarak doğrulayın: sabit bir tohumla tek bir cihazda üretim yapın, ardından tensor paralelliği altında tekrar üretin ve çıktıları karşılaştırın. Yanlış yerleştirilmiş bir "colwise"/"rowwise" genellikle yine de çalışır ve makul görünen ancak yanlış bir görüntü üretir.

Benzer bir mimariye sahip mevcut bir plandan başlayın. QwenImageTransformer2DModel tamamen füzyon yapılmamış olup her giriş düz "colwise"/"rowwise" şeklindedir, FluxTransformer2DModel tek bir paketli satır içi projeksiyon ekler ve Flux2Transformer2DModel her iki paketli stili de kapsar — sırasıyla transformer_qwenimage.py, transformer_flux.py ve transformer_flux2.py dosyalarına bakın.

Strateji seçimi

Yukarıdaki stratejiler farklı sorunları çözer ve faydalı soru soyut olarak hangisinin en hızlı olduğu değil, neyin tükendiğidir.

Strateji Bölümler Azaltır Tek bir istem için gecikme En iyi olduğu durum
Accelerate / DDP kopyalar arasında istemler hiçbiri — her cihaz tam bir kopya tutar değişmedi model zaten sığıyor ve birçok isteminiz var
device_map cihazlar arası bileşenler ağırlık belleği biraz daha kötü model sığmıyor ve bağlantı yavaş
Bağlam paralelliği giriş dizisi aktivasyon belleği düşük diziler uzun — yüksek çözünürlüklü veya video
Tensor paralelliği ağırlık matrisleri ağırlık belleği düşük bir bileşenin ağırlıkları sığmıyor ve bağlantı hızlı

Bazı pratik yönergeler:

  • Çoklu isteklerde (prompts) yüksek verimlilik, model zaten sığabiliyor. Veri paralelliği kullanın. Modeli değiştirmeden verimliliği doğrusal olarak ölçeklendiren tek strateji budur ve tek istek gecikmesini (latency) etkilemez.
  • Bir bileşenin ağırlıkları sığmıyor. Hem belleği hem de gecikmeyi düşürdüğü için önce tensör paralelliğine yönelin. Her blok sınırında iletişim kurduğu için NVLink gibi hızlı bir bağlantı ister; PCIe üzerinde katman başına bu trafik, kazandırdığı hesaplama gücünü aşabilir ve bu durumda device_map daha iyi bir seçim olur. device_map, bileşenlerin tek başına uygun olmasına rağmen toplu olarak çok büyük olduğu durumu da ele alır.
  • Problem ağırlıklarda değil, aktivasyonlarda. Bu, uzun dizi rejimidir — büyük görüntüler, çok sayıda kare — ve bağlam paralelliği doğrudan cevaptır. Yukarıdaki Ulysses/Ring kıyaslamalarından bir arka uç seçin.
  • Hem ağırlıklar hem de dizi çok büyük. Tensor ve bağlam paralelliğini birleştirin. TensorParallelConfig, her ikisinin de tek bir cihaz ızgarasını paylaşabilmesi için mesh argümanını kabul eder. Bu kombinasyon henüz doğrulanmadığından deneysel olarak ele alınmalıdır.

Xet Storage Details

Size:
36.2 kB
·
Xet hash:
7295fca578d1c55335aab27ac12880817ffcb36fff26641827ddc5888aec24f0

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