Accelerate documentation

Gradyan senkronizasyonu

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

and get access to the augmented documentation experience

to get started

Gradyan senkronizasyonu

PyTorch’un dağıtık modülü, sistemdeki tüm GPU’lar arasında gidip gelerek iletişim kurarak çalışır. Bu iletişim zaman alır ve ddp modülü kullanılırken tüm süreçlerin birbirlerinin durumlarını bilmesini sağlamak, belirli tetikleme noktalarında gerçekleşir.

Bu tetikleme noktaları, özellikle forward() ve backward() yöntemleri, PyTorch modeline eklenir. Bu, model DistributedDataParallel ile sarıldığında gerçekleşir:

import torch.nn as nn
from torch.nn.parallel import DistributedDataParallel

model = nn.Linear(10, 10)
ddp_model = DistributedDataParallel(model)

Accelerate'ta bu dönüşüm, prepare() çağrılırken modelinizi iletmeniz durumunda otomatik olarak gerçekleşir.

+ from accelerate import Accelerator
+ accelerator = Accelerator()
  import torch.nn as nn
- from torch.nn.parallel import DistributedDataParallel

  model = nn.Linear(10,10)
+ model = accelerator.prepare(model)

Gradyan biriktirmedeki yavaşlama

Artık PyTorch'un, dağıtık bir yapılandırma içinde eğitim sırasında PyTorch modelinizin forward ve backward yöntemlerine kancalar (hook) eklediğini anlıyorsunuz. Peki bu, kodunuzun yavaşlamasına nasıl yol açabilir?

DDP (distributed data parallel) içinde, işlemlerin gerçekleştirilme ve çalıştırılma sırasının belirli noktalarda belirli bir şekilde olması beklenir ve bunların devam edebilmek için yaklaşık aynı anda gerçekleşmesi gerekir.

En doğrudan örnek, model parametrelerini optimizer.step() üzerinden güncellediğinizde ortaya çıkar. Gradyan biriktirme yapılmadığında, modelin tüm örneklerinin, bir sonraki veri grubuna geçmeden önce güncellenmiş gradyanlarının hesaplanması, toplanması ve güncellenmesi gerekir. Gradyan biriktirme yapılırken, n kayıp gradyanı biriktirilir ve n gruba ulaşılana kadar optimizer.step() atlanır. Tüm eğitim süreçlerinin yalnızca optimizer.step() çağrıldığında senkronize olması gerektiğinden, eğitim adımınızda herhangi bir değişiklik yapmadan, bu gereksiz süreçler arası iletişim önemli bir yavaşlamaya neden olabilir.

Bu ek yükten nasıl kaçınabilirsiniz?

Yavaşlama sorununu çözme

Bu topluluklarda eğitim sırasında model parametre güncellemelerini atladığınız için, optimizer.step() gerçekten çağrılana kadar gradyanlarının senkronize edilmesine gerek yoktur. PyTorch, bunun ne zaman yapılması gerektiğini otomatik olarak anlayamaz, ancak DDP'ye dönüştürdükten sonra modelinize eklenen no_sync bağlam yöneticisi aracılığıyla bu konuda yardımcı olacak bir araç sunar.

Bu bağlam yöneticisi altında, .backward() çağrıldığında PyTorch gradyanları senkronize etmeyi atlar ve bu bağlam yöneticisinin dışındaki ilk .backward() çağrısı senkronizasyonu tetikler. Aşağıda bir örnek bulunuyor:

ddp_model, dataloader, optimizer = accelerator.prepare(model, dataloader, optimizer)

for index, batch in enumerate(dataloader):
    inputs, targets = batch
    # Trigger gradient synchronization on the last batch
    if index != (len(dataloader) - 1):
        with ddp_model.no_sync():
            # Gradients only accumulate
            outputs = ddp_model(inputs)
            loss = loss_func(outputs)
            accelerator.backward(loss)
    else:
        # Gradients finally sync
        outputs = ddp_model(inputs)
        loss = loss_func(outputs)
        accelerator.backward(loss)
        optimizer.step()

Accelerate'ta bunu, eğitim cihazı ne olursa olsun çağrılabilen bir API haline getirmek için (ancak dağıtık bir sistemde değilseniz hiçbir şey yapmayabilir!), ddp_model.no_sync yerine no_sync() kullanılır ve aynı şekilde çalışır:

  ddp_model, dataloader, optimizer = accelerator.prepare(model, dataloader, optimizer)

  for index, batch in enumerate(dataloader):
      inputs, targets = batch
      # Trigger gradient synchronization on the last batch
      if index != (len(dataloader)-1):
-         with ddp_model.no_sync():
+         with accelerator.no_sync(model):
              # Gradients only accumulate
              outputs = ddp_model(inputs)
              loss = loss_func(outputs, targets)
              accelerator.backward(loss)
      else:
          # Gradients finally sync
          outputs = ddp_model(inputs)
          loss = loss_func(outputs)
          accelerator.backward(loss)
          optimizer.step()
          optimizer.zero_grad()

Bekleyeceğiniz üzere, accumulate() fonksiyonu, mevcut parti numarasını takip ederek bu koşullu kontrolü sarar ve size nihai gradyan biriktirme API'sini sunar:

ddp_model, dataloader, optimizer = accelerator.prepare(model, dataloader, optimizer)

for batch in dataloader:
    with accelerator.accumulate(model):
        optimizer.zero_grad()
        inputs, targets = batch
        outputs = model(inputs)
        loss = loss_function(outputs, targets)
        accelerator.backward(loss)
        optimizer.step()
        optimizer.zero_grad()

Sonuç olarak, API seçimi söz konusu olduğunda accelerator.accumulate veya accelerator.no_sync kullanmanız gerekir.

Tam olarak ne kadar bir yavaşlama var ve yapabileceğiniz basit hatalar

Gerçekçi bir örnek oluşturmak için aşağıdaki kurulumu göz önünde bulundurun:

  • İki tek GPU'lu T4 düğümü ve iki GPU'lu bir düğüm
  • Her GPU bir T4'tür ve GCP üzerinde barındırılır
  • Kullanılan betik, NLP Example betiğinin bir değişikliğidir
  • GPU başına batch boyutu 16'dır ve gradyanlar her 4 adımda birikimlendirilir

Tüm betikler bu depoda mevcuttur.

Gradyan senkronizasyonu ve GPU iletişimine dikkat edilmezse, bu GPU'ların gereksiz dönemlerde birbirleriyle iletişim kurması sırasında çok fazla zaman harcanabilir.

Ne kadar?

Referans:

  • Çizgi: Burada tartışılan senkronizasyon uygulamalarından hiçbiri kullanılmaz
  • no_sync yanlış kullanımı: no_sync yalnızca backward çağrısının etrafında, forward çağrısının etrafında değil
  • no_sync: no_sync desenini doğru şekilde kullanma
  • accumulate: accumulate() fonksiyonunun doğru kullanımı

Aşağıda, tek düğüm ve çift düğüm kurulumlarında her bir yapılandırma için 29 veri grubu üzerinde ortalama saniye/batch değerleri verilmiştir:

Tabanno_sync yanlışno_syncaccumulate
Çok Düğümlü2±0.01s2.13±0.08s0.91±0.11s0.91±0.11s
Tek Düğüm0.50±0.01s0.50±0.01s0.41±0.015s0.41±0.015s

Gördüğünüz gibi, gradyan senkronizasyonunu nasıl kurduğunuz konusunda dikkatli olmazsanız, eğitim sırasında 2 katından daha fazla yavaşlamayla karşılaşabilirsiniz!

Her şeyin düzgün yapıldığından emin olmak konusunda endişeliyseniz, Accelerator nesnesine gradient_accumulation_steps veya gradient_accumulation_plugin parametresini ileterek accumulate() fonksiyonunu kullanmanızı ve bu işlemin Accelerate tarafından sizin için halledilmesini şiddetle öneririz.

no_sync, FSDP kullanıldığında ek GPU belleği gerektirir

Gradyanların senkronize edilmemesinin FSDP eğitimi sırasında olumsuz etkileri olabileceğini unutmayın. torch içinde uyarıldığı gibi, FSDP için no_sync bağlam yöneticisi ek bellek gerektirecektir.

Dolayısıyla FSDP kullanılırken bellek yoğun durumlarda, no_sync işlevini devre dışı bırakmak için GradientAccumulationPlugin içinde sync_each_batch değerini True olarak ayarlamayı öneririz.

Aşağıdaki örnekte, Mixtral’ı (47B parametre) 8 A100-80GB GPU üzerinde ince ayarlıyoruz. no_sync etkinleştirildiğinde, mütevazı bir gradient_accumulation_steps=2 değeriyle bile hızla bellek yetersizliği (OOM) ile karşılaştığımızı görüyoruz. Bu durum yine, FSDP’nin no_sync özelliğinden kaynaklanan ek bellek yüklerinden kaynaklanmaktadır. Ancak no_sync, sync_each_batch=True ile devre dışı bırakıldığında, gradient_accumulation_steps=16 için bellek tüketimi, gradient_accumulation_steps=1 için olan seviyeye geri döner.

Modelno_sync (accum=1)no_sync (accum=2)no_sync devre dışı (accum=16)
mixtral 8x7B69GOOM69G

no_sync'in devre dışı bırakılması, bu kılavuzun önceki bölümlerinde açıklandığı gibi, ek veri senkronizasyonları nedeniyle yavaşlamaya yol açar.

Update on GitHub