Accelerate documentation

التوازي التسلسلي في 🤗 accelerate

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

التوازي التسلسلي في 🤗 accelerate

ستغطي هذه الدليل أساسيات استخدام توازي التسلسل في 🤗accelerate.

انظر أيضًا إلى التوازي السياقي ذي الصلة الوثيقة.

لماذا التوازي التسلسلي؟

مع ظهور نماذج اللغة الكبيرة، ونماذج الاستدلال مؤخراً، شهدت طول التسلسل نمواً سريعاً. وقد أدى هذا، مقترناً بالتعقيد الذاكري التربيعي للانتباه، إلى الحاجة إلى طرق أكثر كفاءة لتدريب النماذج ذات التسلسلات الطويلة. عند طول تسلسل يبلغ 128k، فإن المتطلب الذاكري لمصفوفة الانتباه هو 128k * 128k * 2 bytes * num_heads = ~32 GB * num_heads لدقة bf16، بافتراض استخدام تطبيق الانتباه القياسي. ومنحاً، فإن استخدام flash attention أو SDPA، والتي لا تُجسّد أوزان الانتباه هذه، يُقلّص هذا المتطلب بشكل حاد، لكن النمو في المتطلبات الذاكية لا يزال يُعدّ كبيراً.

يتيح لنا توازي التسلسل في Ulysses تقسيم مدخلات حساب الانتباه على طول بُعد التسلسل وحساب الانتباه بشكل طبيعي، ولكن باستخدام جزء فقط من رؤوس الانتباه على كل وحدة معالجة رسومية. وبفضل ذلك، يمكننا تدريب نماذج ذات تسلسلات طويلة، مع بعض الأدوات الإضافية، والتوسع حتى طول تسلسل يتجاوز 15 مليون رمز. للاطلاع على كيفية تعزيز Ulysses SP باستخدام TiledMLP وLiger-Kernel وإزاحة نقاط التفتيش للتفعيل إلى وحدة المعالجة المركزية وبعض الحيل الأخرى، يُرجى الرجوع إلى الورقة البحثية: Arctic Long Sequence Training: Scalable And Efficient Training For Multi-Million Token Sequences.

كيف يختلف Ulysses SP عن FSDP CP

في المستند Context Parallelism يمكنك التعرف على نشر تقنية أخرى تُسمى Context Parallelism، والتي تقطع أيضاً على بُعد التسلسل لكنها تستخدم Ring Attention بدلاً من التقطيع على بُعد الرأس.

تتناول المقالات التالية شرحاً مفصلاً للغاية للفروق بين التقنيتين:

ملخص سريع مقتبس من إحدى المقالات:

  • يتميز Ulysses SP بعبء اتصال منخفض نسبيًا، لكنه مقيد بعدد رؤوس الانتباه (Attention Heads)، وبالتالي فإن له متطلبات معينة لبنية الشبكة (يجب أن يكون عدد رؤوس الانتباه قابلاً للقسمة على عدد وحدات معالجة الرسومات المشاركة في نسخة واحدة). اتصال All-to-all حساس للزمن المستغرق (Latency) ويتطلب DeepSpeed.
  • لا تخضع اتصالات الحلقة P2P في FSDP CP Ring-Attention لمتطلبات القسمة المذكورة أعلاه، لكنها تتطلب حجم اتصال أعلى.

وأخيرًا، ينبغي أن يكون من الممكن الجمع بين SP و CP كما هو موضح في الورقة USP: A Unified Sequence Parallelism Approach for Long Context Generative AI لدعم طول تسلسل أطول حتى، على الرغم من أن هذا لم يتم دمجه بعد في 🤗accelerate.

واجهات الخلفية المدعومة للتوازي التسلسلي

حاليًا، الخلفية الوحيدة المتاحة لتوازي التسلسل هي deepspeed، وهي مستمدة من Ulysses SP الحديثة التي تُعد جزءًا من تقنية Arctic Long Sequence Training. كما يتوفر دليل تدريبي في حال رغبتَ في دمجها مباشرةً في كودك الخاص.

كيف تستخدم توازي التسلسل؟

from accelerate.utils import ParallelismConfig, DeepSpeedSequenceParallelConfig

+# Example: 4 GPUs with sp_size=4, dp_shard_size=1
+# Ensure: dp_replicate_size × dp_shard_size × sp_size = 1 × 1 × 4 = 4 GPUs
parallelism_config = ParallelismConfig(
+     sp_backend="deepspeed",
+     sp_size=4,
+     dp_shard_size=1,  # Explicit: no data parallelism
+     sp_handler=DeepSpeedSequenceParallelConfig(
+         sp_seq_length_is_variable: true,
+         sp_attn_implementation="sdpa",
+     ),
+ )

accelerator = Accelerator(
    ...,
    parallelism_config=parallelism_config,
)

كما هو الحال مع أي ميزة أخرى في 🤗accelerate، يمكنك تفعيل توازي التسلسل أيضًا عن طريق تمرير الأعلام المقابلة إلى accelerate launch. وفي هذه الحالة، لا يوجد أي فرق:

accelerate launch --parallelism-config-sp-size 8  ...

يمكنك أيضًا ضبط sp_size وإعدادات أخرى في أمر accelerate config، حيث سيتم حفظها في ملف إعدادات accelerate الخاص بك، فلا يضطر إلى تمريرها في كل مرة تطلق فيها سكربت.

يتكامل توازي التسلسل مع توازي البيانات. ولا يتطلب وحدات معالجة رسومية إضافية. لذا إذا كان لديك 8 وحدات معالجة رسومية يمكنك تنفيذ: --parallelism-config-dp-shard-size 8 --parallelism-config-sp-size 8. أو يمكنك استخدام فئة ParallelismConfig لضبطها برمجياً.

مهم: يجب عليك التأكد من أن dp_replicate_size × dp_shard_size × sp_size = num_processes. على سبيل المثال، مع 8 وحدات معالجة رسومية وsp_size=8، تحتاج إلى dp_shard_size=1 (لأن 1 × 1 × 8 = 8). مع 4 وحدات معالجة رسومية وsp_size=2، يمكنك استخدام dp_shard_size=2 (لأن 1 × 2 × 2 = 4) لتحقيق التوازي ثنائي الأبعاد.

ALST/Ulysses SP backend configuration

تنفّذ ALST/UlyssesSP توازي التسلسل باستخدام توازي رؤوس الانتباه، كما هو موضح في هذا الورقة. وبسبب البساطة، نعيد استخدام مفهوم وإعداد توازي التسلسل، والذي، من منظور المستخدم، هو نفسه: تُستخدم وحدات معالجة رسومية متعددة لمعالجة دفعة واحدة.

لتوضيح ما أتاحه ALST - فقد مكّننا من التدريب باستخدام bf16 مع 500 ألف رمز على وحدة معالجة رسومية H100 واحدة، و3.7 مليون رمز على عقدة واحدة، و15 مليون رمز على Llama-8B باستخدام أربع عقد فقط. تتيح هذه الميزة في HF Accelerate مكوّنًا واحدًا فقط من بين مكوّنات ALST الثلاثة، لذا فإن طول التسلسل القابل للتحقيق سيكون أقصر. ستحتاج إلى تفعيل TiledMLP، وإفراغ نقاط التفتيش للتفعيل إلى وحدة المعالجة المركزية، وبعض الأشياء الأخرى للحصول على القوة الكاملة لـ ALST. للتفاصيل، يُرجى الرجوع إلى هذا الشرح التعليمي.

لإعداد الخلفية deepspeed:

# Example: 4 GPUs with sp_size=4, dp_shard_size=1
# Ensure: dp_replicate_size × dp_shard_size × sp_size = 1 × 1 × 4 = 4 GPUs
parallelism_config = ParallelismConfig(
    sp_backend="deepspeed",
    sp_size=4,
    dp_shard_size=1,  # Explicit: no data parallelism
    sp_handler=DeepSpeedSequenceParallelConfig(
        sp_seq_length=256,
        sp_seq_length_is_variable=True,
        sp_attn_implementation="sdpa",
    ),
)
accelerator = Accelerator(
    ...,
    parallelism_config=parallelism_config,
)
  • sp_backend: اضبطه على deepspeed هنا
  • sp_size هو درجة توازي التسلسل - في المثال أعلاه قيمته 4، وبالتالي سيتم استخدام 4 وحدات معالجة رسومية لمعالجة دفعة واحدة (مع إجراء DP=4 على نفس وحدات المعالجة الرسومية)
  • تُستخدم sp_seq_length وsp_seq_length_is_variable للتعامل مع أطوال التسلسلات. إذا كان sp_seq_length_is_variable=True، فسيعمل الخلفية مع طول تسلسل قد يتغير بين الدفعات، وفي هذه الحالة يمكن تعيين قيمة sp_seq_length إلى أي قيمة قابلة للقسمة على درجة التوازي التسلسلي أو عدم تعيينها على الإطلاق. في هذه الحالة، سيتم اشتقاق متغيرات التسلسل من المدخلات في كل عملية forward. أما إذا كانت False، فيجب أن تتطابق seq_length مع بُعد طول التسلسل الخاص بالدفعة، مما سيتطلب حشوها (padding) لتبقى دائمًا بنفس الطول. القيمة الافتراضية هي True.
  • sp_attn_implementation هو أحد sdpa أو flash_attention_2 أو flash_attention_3. يستخدم هذا التنفيذ المتوازي للتسلسل position_ids بدلاً من attention_mask، وبالتالي لا يمكن لـ eager العمل هنا حتى يدعم التعامل مع position_ids. كما يُرجى ملاحظة أن sdpa لا يتعامل بشكل صحيح مع العينات المتعددة المدمجة في عينة واحدة؛ إذ سيقوم بالانتباه إلى العينة بأكملها كوحدة واحدة. أما إذا لم تكن العينات مدمجة، فسيعمل sdpa بشكل صحيح. لذلك، يُعد Flash Attention الخيار المثالي لأنه يعمل دائمًا.

بدلاً من تعيين هذه القيم في كائن DeepSpeedSequenceParallelConfig، يمكنك أيضًا استخدام متغيرات البيئة لتحقيق نفس النتيجة - وهي هنا مقابلة لنهاية القائمة أعلاه.

  • PARALLELISM_CONFIG_SP_BACKEND
  • PARALLELISM_CONFIG_SP_SEQ_LENGTH
  • PARALLELISM_CONFIG_SP_SEQ_LENGTH_IS_VARIABLE
  • PARALLELISM_CONFIG_SP_ATTN_IMPLEMENTATION

إذا لم يُمرَّر في الشيفرة، يمكن تعيين sp_size عبر حجة سطر الأوامر --parallelism_config_sp_size. وكذلك الأمر بالنسبة للحجج الأخرى. يمكنك أيضًا استخدام أسلوب تكوين ملف accelerate config، على سبيل المثال، لجهازَي GPU:

distributed_type: DEEPSPEED
deepspeed_config:
  deepspeed_config_file: path/to/ds_config.json
machine_rank: 0
num_machines: 1
num_processes: 2
parallelism_config:
  parallelism_config_dp_replicate_size: 1
  parallelism_config_dp_shard_size: 1  # Must satisfy: 1 × 1 × 2 = 2 num_processes
  parallelism_config_sp_size: 2
  parallelism_config_sp_backend: deepspeed
  parallelism_config_sp_seq_length_is_variable: true
  parallelism_config_sp_attn_implementation: sdpa

كما ذُكر سابقًا، يُطبَّق عادةً توازي تسلسل Ulysses فوق توازي البيانات - حيث تُستخدم نفس الرتب لتغذية تدفقات بيانات فريدة وأداء توازي تسلسل Ulysses في آنٍ واحد. لكن يمكنك أيضًا إنشاء نسخ على النحو التالي:

# Example: 4 GPUs with 2D parallelism (SP=2, DP=2)
# Ensure: dp_replicate_size × dp_shard_size × sp_size = 2 × 1 × 2 = 4 GPUs
parallelism_config = ParallelismConfig(
    dp_replicate_size=2,
    dp_shard_size=1,  # Explicit: no sharding within replicas
    sp_size=2,
    sp_backend="deepspeed",
    sp_handler=DeepSpeedSequenceParallelConfig(...),
)

هنا نستخدم 4 وحدات معالجة رسومية (GPU)، مع نسختين من توازٍ التسلسل (sequence parallelism). أما ما يقود توازٍ البيانات هنا فهو Deepspeed-ZeRO.

يرجى ملاحظة أن الكثير من العمليات الخفية تتم داخل UlyssesSPDataLoaderAdapter. يُستخدم في الخلفية، حيث يغلّف كائن DataLoader الأصلي الخاص بك، لكن ينبغي لك أن تكون على دراية به في حال واجهت أي مشاكل. كما يقوم تلقائيًا بحقن shift_labels الصحيحة في قاموس الدفعة (batch)، قبل أن يتم تقسيم الدفعة عبر الرتب المشاركة.

الآن، الجزء المتبقي الوحيد لبدء استخدام ALST/UlyssesSP هو تجميع الخسارة عبر الرتب باستخدام all_gather قابل للاشتقاق للحصول على التدرجات الصحيحة. يقوم الكود التالي بذلك، مع استبعاد أي رموز مقنّعة بـ -100 للحصول على المتوسط الصحيح:

sp_size = parallelism_config.sp_size if parallelism_config is not None else 1
if sp_size > 1:
    sp_group = accelerator.torch_device_mesh["sp"].get_group()
    sp_world_size = parallelism_config.sp_size

# Normal training loop
for iter, batch in enumerate(dl):
    optimizer.zero_grad()

    batch = move_to_device(batch, model.device)

    # The model automatically receives shift_labels via **kwargs and uses it for loss computation.
    # Both standard transformers models and Liger-patched models handle this correctly.
    outputs = model(**batch)
    loss = outputs.loss
    shift_labels = batch["shift_labels"]

    if sp_size > 1:
        # differentiable weighted per-shard-loss aggregation across ranks
        losses_per_rank = torch.distributed.nn.functional.all_gather(loss, group=sp_group)
        # special dealing with SFT that has prompt tokens that aren't used in loss computation
        good_tokens = (shift_labels != -100).view(-1).sum()
        good_tokens_per_rank = torch.distributed.nn.functional.all_gather(
            good_tokens, group=sp_group
        )
        # Skip ranks with zero valid tokens to avoid NaN contamination (NaN * 0 = NaN)
        total_loss = sum(
            losses_per_rank[rank] * good_tokens_per_rank[rank]
            for rank in range(sp_world_size)
            if good_tokens_per_rank[rank] > 0
        )
        total_good_tokens = sum(good_tokens_per_rank)
        loss = total_loss / max(total_good_tokens, 1)

    if rank == 0: accelerator.print(f"{iter}: {loss=}")
    accelerator.log(dict(train_loss=loss, step=iter))

    accelerator.backward(loss)
    optimizer.step()

لاحظ أن النماذج تتعامل تلقائيًا مع shift_labels عندما يكون حاضرًا في الدفعة. يستقبل مسار الأمامي للنموذج shift_labels عبر **kwargs ويمرره إلى دالة الخسارة، التي تحسب الخسارة بشكل صحيح للتوازي التسلسلي. إذا استخدمت Liger Kernel، فإنه يتعامل أيضًا مع shift_labels بسلاسة ويحسب الخسارة بطريقة فعالة جدًا من حيث الذاكرة. يُنصح بشدة باستخدام Liger لطول التسلسلات الطويل، حيث يحرر ذاكرة وحدة معالجة الرسوميات (GPU) من خلال استخدام عمليات مدمجة (مثل حساب logit-loss المدمج الذي لا يُنشئ قطار logits الكامل في الذاكرة أبدًا).

إذا كنت ترغب في الاطلاع على ما قام به HF Accelerate في الخلفية، فيرجى قراءة هذا الشرح التكاملي الكامل.

لمثال على حلقة تدريب باستخدام Accelerate مع تفعيل ALST/UlyssesSP، راجع examples/alst_ulysses_sequence_parallelism.

[!تحذير]

هذا الواجهة البرمجية (API) جديدة إلى حد كبير ولا تزال في مرحلتها التجريبية. وعلى الرغم من أننا نسعى جاهدين لتقديم واجهة برمجية مستقرة، إلا أن بعض الأجزاء الصغيرة من الواجهة البرمجية العامة قد تتغير في المستقبل.

بما أن هذا هو عمق Deepspeed، فإن إعداد Deepspeed المعتاد ينطبق، لذا يمكنك دمج توازي التسلسل مع تفريغ حالات المُحسِّن و/أو الأوزان أيضًا لتحرير المزيد من ذاكرة وحدة معالجة الرسوميات وتمكين طول تسلسل أطول حتى. وقد تم اختبار هذه التقنية لتعمل مع DeepSpeed ZeRO المرحلة 2 و 3.

Update on GitHub