Buckets:
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 üzerindehf_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_groupiç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
glooarka 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
Noneolmayan dikkat maskeleri desteklenmez.attn_maskNoneolmalı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_configile tensor-parallel yükleme, henüzdevice_map,quantization_config,low_cpu_mem_usage=False,use_flashpack=Trueveya 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_degreedeğ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ıylatransformer_qwenimage.py,transformer_flux.pyvetransformer_flux2.pydosyaları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_mapdaha 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
meshargü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.