چیت‌شیت

DDP، FSDP، ZeRO و موازی‌سازی پایپ‌لاین

DDP, FSDP, ZeRO and Pipeline Parallel

انتخاب روش موازی‌سازی، اندازه‌گیری حافظه، سیم‌کشی اعمال اشتراکی و رفع قفل‌شدگی‌ها. با PyTorch gloo روی CPU.

  • ۱۳ بخش
  • ۷ درس مرتبط
  • کد و دستورها عیناً از دوره‌ی اصلی
فهرست مطالب
  1. مقایسه‌ی رو در روی چهار استراتژی
  2. موارد استفاده
  3. مراحل ZeRO
  4. ارقام حافظه
  5. زمان‌بندی‌های پایپ‌لاین
  6. اعمال اشتراکی
  7. آل‌ردیوس حلقه‌ای روی یک مش صف
  8. حلقه‌ی ورکر DDP
  9. فراخوانی‌های اعمال اشتراکی
  10. اجرای درس‌ها
  11. پیش‌فرض‌ها در کد
  12. عیب‌یابی
  13. پرسش و پاسخ

مقایسه‌ی رو در روی چهار استراتژی

مقایسه
فاکتورDDPFSDP یا ZeRO-3Pipelineتنسور موازی
بخش‌بندیدسته (batch)پارامترها، گرادیان‌ها، بهینه‌سازمدل بر اساس لایه‌هاهر ماتریس وزن
حافظه به‌ازای هر GPUنسخه‌ی کامل، بدون کاهش1/N از هر وضعیتیک گروه از لایه‌هایک برش از هر لایه
ترافیکall-reduce روی گرادیان‌هاall-gather به‌ازای لایه، reduce-scatterاکتیویشن‌ها در مرزهای لایه‌هاall-reduce پس از هر matmul
اتصال (Link)همپوشانی با محاسباتبیشتر از DP معمولیپهنای باند کم، بین نودهاNVLink درون یک نود
هزینه‌ی اصلیحافظهگردآوری در هر گذر رو به جلوحباب پایپ‌لاینتاخیر پس از هر لایه

موارد استفاده

مفهوم‌ها
DDP
مدل روی یک دستگاه جا می‌شود، اما دیتاست جا نمی‌شود.
FSDP
مدل روی یک دستگاه هم جا نمی‌شود.
ZeRO-1
حالت پیش‌فرض. پهنای باند مشابه DDP، کاهش خطی حافظه نسبت به N.
ZeRO-2 یا ZeRO-3
حافظه‌ی شارد پارامترها هم یک مشکل است.
تنسور موازی
درون یک نود. به اتصالات سریع بین GPUها نیاز دارد.
پایپ‌لاین
بین نودها. نیاز به پهنای باند کم است.
DualPipe
مدل‌های MoE با بار شدید EP در مقیاس چند هزار GPU.
DualPipeV
مدل‌های چگال (dense) یا با بار سبک EP. تک‌نسخه از پارامتر.

مراحل ZeRO

مقایسه
فاکتورDDPZeRO-1ZeRO-2ZeRO-3
شارد شدههیچ‌کداموضعیت بهینه‌سازبهینه‌ساز + گرادیان‌هابهینه‌ساز + گرادیان‌ها + پارامترها
حافظه به‌ازای هر رتبهپارامترها + گرادیان‌ها + بهینه‌سازپارامترها + گرادیان‌ها + بهینه‌ساز/Nپارامترها + گرادیان‌ها/N + بهینه‌ساز/Nپارامترها/N + گرادیان‌ها/N + بهینه‌ساز/N
ارتباطات به‌ازای هر گام1 بار allreduce1 بار reduce_scatter + 1 بار allgather1 بار reduce_scatter + 1 بار allgatherallgather و reduce_scatter به‌ازای هر لایه
صرفه‌جوییبدون کاهش~4 برابر کاهش~8 برابر کاهش~N برابر کاهش
نام در PyTorch FSDPدر لیست نیستدر لیست نیستShardingStrategy.SHARD_GRAD_OPFULL_SHARD

ارقام حافظه

مفهوم‌ها
وضعیت‌های Adam
مقادیر m و v در FP32. برای 70B x 4 بایت x 2 = 560GB.
ساده (Vanilla)، دقت ترکیبی
16P بایت به‌ازای هر رتبه.
ZeRO-1
معادل 4P + 12P/N بایت. در N=8 می‌شود 5.5P، یعنی 65% کاهش.
FSDP برای 70B روی 8 تا GPU
معادل 105 گیگابایت به‌ازای هر GPU. همچنان بالای 80GB.
دقت ترکیبی، 7B
کاهش از 112 گیگابایت به 84 گیگابایت. بهینه‌ساز در FP32 می‌ماند.
دسته‌ی مؤثر
per_gpu_batch_size x N.

زمان‌بندی‌های پایپ‌لاین

مقایسه
فاکتور1F1BZero BubbleDualPipeDualPipeV
نسخه‌های پارامتر1121
حباب در برابر ریزدسته‌هارشد می‌کندرشد می‌کندثابترشد جزئی
همپوشانیحداقلجزئیکاملجزئی
شبیه‌سازbubble_1f1bbubble_zero_bubblebubble_dualpipebubble_dualpipev

اعمال اشتراکی

مقایسه
فاکتورRing allreduceTree allreduceAllgatherReduce_scatter
بایت به‌ازای هر رتبه2T(N-1)/NT log2(N)T(N-1)/NT(N-1)/N
گام‌ها2(N-1)2 log2(N)N-1N-1
کاربردT بزرگT کوچک، تاخیر زیادخروج از شارد ZeROشارد گرادیان ZeRO
اندازه‌ی پیامبیشتر از ~1 مگابایتکمتر از ~1 مگابایتهر اندازه‌ایهر اندازه‌ای

آل‌ردیوس حلقه‌ای روی یک مش صف

معماری
صف 0 به 1صف 1 به 2صف 2 به 3صف 3 به 0رتبه 0مالک چانک 0رتبه 1مالک چانک 1رتبه 2مالک چانک 2رتبه 3مالک چانک 3

حلقه‌ی ورکر DDP

کد
python
dist.init_process_group(
    backend="gloo",
    init_method=f"file://{init_file}",                       # راندوو مبتنی بر فایل
    rank=rank,
    world_size=world_size,
)
torch.manual_seed(SEED)                                      # مقداردهی اولیه‌ی یکسان در هر رتبه
ddp = DistributedDataParallel(model, world_size=world_size)  # همه‌پخشی پارامترها
for step in range(steps):
    offset = step * world_size * batch + rank * batch        # برش اختصاصی
    optim.zero_grad(set_to_none=True)
    loss.backward()
    ddp.sync_grads()                                         # all_reduce SUM، سپس div_ بر world_size
    optim.step()

فراخوانی‌های اعمال اشتراکی

کار و کد
وظیفهکد
تنظیم پورت راندووos.environ["MASTER_PORT"] = str(port)
شروع گروه پردازشی CPUdist.init_process_group(backend="gloo", rank=rank, world_size=world_size)
پارامترهای یکسان روی تمام رتبه‌هاdist.broadcast(p.data, src=0)
جمع گرادیان‌هاdist.all_reduce(p.grad.data, op=dist.ReduceOp.SUM)
میانگین گرادیان‌هاp.grad.data.div_(world_size)
شارد گرادیان ZeRO-1dist.reduce_scatter(local_grad, chunks, op=dist.ReduceOp.SUM)
بازگردانی پارامترهای ZeRO-1dist.all_gather(gathered, self.master_shard)
حباب GPipebubble_fraction(num_stages, num_microbatches)

اجرای درس‌ها

دستورها
دستورعملکرد
python3 code/main.pyدرس 48: دو رتبه‌ی gloo، نوشتن در outputs/ddp-demo.json
--world-size 4درس 48: چهار رتبه. پراکندگی پارامترها زیر 1e-3 می‌ماند
--no-writeدرس 48: صرف‌نظر از فایل JSON
python3 code/main.pyدرس 76: مش صف در مقایسه با gloo، شمارنده‌ی بایت
python3 code/main.pyدرس 77: تک فرایند در مقایسه با DDP روی 4 رتبه
python3 code/main.pyدرس 78: تابع زیان و جدول حافظه‌ی ZeRO-1
python3 code/main.pyدرس 79: نمودار گانت و درصد حباب پایپ‌لاین

پیش‌فرض‌ها در کد

پیش‌فرض‌ها
تنظیمپیش‌فرضنکته
--world-size2رتبه‌های درس 48
--backendglooروی GPU از nccl استفاده کنید
--num-steps6گام‌های آموزش درس 48
--batch-size8به‌ازای هر رتبه
RECV_TIMEOUT_S30.0مهلت زمانی دریافت مش صف
DDP bucket size25 MBیک allreduce به‌ازای هر باکت
NUM_STAGES4پایپ‌لاین درس 79
NUM_MICROBATCHES8پایپ‌لاین درس 79

عیب‌یابی

عیب‌یابی
  • واگرایی رتبه‌ها از گام اول

    علت
    هر رتبه از مقداردهی اولیه‌ی خود استفاده می‌کند
    راه‌حل
    استفاده از dist.broadcast(tensor, src=0) در زمان ساخت
  • تمام رتبه‌ها دسته‌های یکسانی را می‌بینند

    علت
    یک سید مشترک برای به‌هم‌ریختن (shuffling)
    راه‌حل
    استفاده از torch.manual_seed(seed + rank) برای به‌هم‌ریختن
  • بن‌بست (deadlock) در Allreduce

    علت
    پارامترهای نادیده‌گرفته‌شده گرادیان ندارند
    راه‌حل
    find_unused_parameters=True
  • انباشت (accumulation) کند است

    علت
    اجرای Allreduce روی تک‌تک ریزدسته‌ها
    راه‌حل
    ریزدسته‌ها را درون no_sync() قرار دهید
  • با تغییر N، نرخ یادگیری (LR) به‌هم می‌ریزد

    علت
    انجام SUM بدون عمل تقسیم
    راه‌حل
    تقسیم بر world_size
  • پایپ‌لاین در شبکه قفل می‌کند

    علت
    هر مرحله قبل از دریافت، اقدام به ارسال می‌کند
    راه‌حل
    رتبه‌های زوج اول ارسال کنند، رتبه‌های فرد اول دریافت کنند
  • یک مرحله تمام چرخه‌ها را مسدود می‌کند

    علت
    تقسیم مراحل بر اساس تعداد پارامتر
    راه‌حل
    یکسان‌سازی FLOPs به‌ازای هر مرحله
  • حافظه FSDP برای 70B بیش از 80GB روی 8 کارت

    علت
    105 گیگابایت به‌ازای هر GPU
    راه‌حل
    16 تا GPU یا چک‌پوینت‌گذاری اکتیویشن

پرسش و پاسخ

پرسش و پاسخ
چرا میانگین و نه جمع؟
تا LR تنظیم‌شده روی یک رتبه، روی چهار رتبه هم کار کند.
آیا دقت ترکیبی حافظه را نصف می‌کند؟
خیر. حدود 25% صرفه‌جویی می‌کند.
حباب GPipe در M=8 و N=4؟
27%. در M=64 و N=4 برابر 4.5% است.
مدل GPipe یا 1F1B؟
حباب پایپ‌لاین یکسان است. 1F1B حافظه‌ی اکتیویشن را محدود می‌کند.
چرا در ZeRO از reduce_scatter استفاده می‌شود؟
هر رتبه فقط جمع مربوط به شارد خود را دریافت می‌کند.
آیا ZeRO-3 همان FSDP است؟
بله. سازوکار دقیقاً یکی است.
استفاده از DualPipe زیر 1000 تا GPU؟
افراطی است. حباب‌ها بخش کوچکی از هزینه را تشکیل می‌دهند.