PEFT documentation

التوازي الكامل للبيانات المجزأة

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

and get access to the augmented documentation experience

to get started

التوازي الكامل للبيانات المجزأة

يُطوَّر التوازي الكامل للبيانات المجزأة (FSDP) للتدريب الموزّع للنماذج المُسبقة التدريب الكبيرة التي تصل إلى 1T من المعاملات. يحقق FSDP ذلك من خلال تجزئة معاملات النموذج والتدرجات وحالات المُحسِّن عبر عمليات التوازي البياناتي، كما يمكنه أيضًا تفريغ معاملات النموذج المجزأة إلى وحدة المعالجة المركزية (CPU). تتيح الكفاءة في استخدام الذاكرة التي يوفرها FSDP توسيع نطاق التدريب إلى أحجام دفعات أو أحجام نماذج أكبر.

تدعم كلتا هاتين الميزتين في 🤗 Accelerate، ويمكنك استخدامهما مع 🤗 PEFT.

استخدام PEFT و FSDP

يساعدك هذا القسم من الدليل على تعلّم كيفية استخدام سكربت التدريب الخاص بنا من DeepSpeed لإجراء SFT. ستقوم بإعداد السكربت لتنفيذ SFT (التدريب الدقيق الإشرافي) لنموذج Llama-70B باستخدام LoRA وFSDP على 8 وحدات GPU من نوع H100 بسعة 80GB على جهاز واحد. يمكنك ضبطه ليتوسع ليشمل عدة أجهزة من خلال تغيير accelerate config.

الإعداد

ابدأ بتشغيل الأمر التالي لإنشاء ملف تكوين FSDP باستخدام 🤗 Accelerate. يتيح لك علم --config_file حفظ ملف التكوين في موقع محدد، وإلا فسيتم حفظه كملف default_config.yaml في ذاكرة التخزين المؤقت لـ 🤗 Accelerate.

يُستخدم ملف الإعدادات لضبط الخيارات الافتراضية عند تشغيل سكربت التدريب.

accelerate config --config_file fsdp_config.yaml

سيُطلب منك الإجابة على بعض الأسئلة المتعلقة بإعدادك، وتكوين الحُجج التالية. في هذا المثال، ستُجيب عن الاستبيان كما هو موضح في الصورة أدناه.

Creating Accelerate's config to use FSDP

بمجرد الانتهاء من ذلك، يجب أن يبدو الإعداد المقابل كما هو موضح أدناه، ويمكنك العثور عليه في مجلد الإعدادات في fsdp_config.yaml:

compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
fsdp_config:
  fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
  fsdp_backward_prefetch: BACKWARD_PRE
  fsdp_cpu_ram_efficient_loading: true
  fsdp_forward_prefetch: false
  fsdp_offload_params: false
  fsdp_sharding_strategy: FULL_SHARD
  fsdp_state_dict_type: SHARDED_STATE_DICT
  fsdp_sync_module_states: true
  fsdp_use_orig_params: false
machine_rank: 0
main_training_function: main
mixed_precision: bf16
num_machines: 1
num_processes: 8
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false

أمر الإقلاع

يتوفر أمر التشغيل في run_peft_fsdp.sh وهو معروض أدناه أيضاً:

accelerate launch --config_file "configs/fsdp_config.yaml"  train.py \
--seed 100 \
--model_name_or_path "meta-llama/Llama-2-70b-hf" \
--dataset_name "smangrul/ultrachat-10k-chatml" \
--chat_template_format "chatml" \
--add_special_tokens False \
--append_concat_token False \
--splits "train,test" \
--max_seq_len 2048 \
--num_train_epochs 1 \
--logging_steps 5 \
--log_level "info" \
--logging_strategy "steps" \
--eval_strategy "epoch" \
--save_strategy "epoch" \
--push_to_hub \
--hub_private_repo True \
--hub_strategy "every_save" \
--bf16 True \
--packing True \
--learning_rate 1e-4 \
--lr_scheduler_type "cosine" \
--weight_decay 1e-4 \
--warmup_steps 0 \
--max_grad_norm 1.0 \
--output_dir "llama-sft-lora-fsdp" \
--per_device_train_batch_size 8 \
--per_device_eval_batch_size 8 \
--gradient_accumulation_steps 4 \
--gradient_checkpointing True \
--use_reentrant False \
--dataset_text_field "content" \
--use_flash_attn True \
--use_peft_lora True \
--lora_r 8 \
--lora_alpha 16 \
--lora_dropout 0.1 \
--lora_target_modules "all-linear" \
--use_4bit_quantization False

لاحظ أننا نستخدم LoRA بـ rank=8 و alpha=16 مع استهداف جميع الطبقات الخطية. نحن نمرر ملف إعدادات FSDP ونقوم بضبط نموذج Llama بحجم 70B على مجموعة فرعية من مجموعة بيانات ultrachat.

الأجزاء المهمة

لنغوص قليلاً في تفاصيل السكربت حتى تتمكن من رؤية ما يحدث، وفهم كيفية عمله.

أول ما يجب معرفته هو أن السكربت يستخدم FSDP للتدريب الموزّع نظراً لأن إعداد FSDP قد تم تمريره. يتولى كلاس SFTTrainer كل العمل الشاق لإنشاء نموذج PEFT باستخدام إعداد peft المُمرَّر. بعد ذلك، عند استدعاء trainer.train()، يستخدم Trainer داخلياً 🤗 Accelerate لتحضير النموذج والمُحسِّن والمدرب باستخدام إعداد FSDP لإنشاء نموذج مغلف بـ FSDP والذي يتم تدريبه بعد ذلك. مقتطف الكود الرئيسي أدناه:

# trainer
trainer = SFTTrainer(
    model=model,
    processing_class=tokenizer,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    peft_config=peft_config,
)
trainer.accelerator.print(f"{trainer.model}")
if model_args.use_peft_lora:
    # handle PEFT+FSDP case
    trainer.model.print_healthcheck()
    if getattr(trainer.accelerator.state, "fsdp_plugin", None):
        from peft.utils.other import fsdp_auto_wrap_policy

        fsdp_plugin = trainer.accelerator.state.fsdp_plugin
        fsdp_plugin.auto_wrap_policy = fsdp_auto_wrap_policy(trainer.model)

# train
checkpoint = None
if training_args.resume_from_checkpoint is not None:
    checkpoint = training_args.resume_from_checkpoint
trainer.train(resume_from_checkpoint=checkpoint)

# saving final model
if trainer.is_fsdp_enabled:
    trainer.accelerator.state.fsdp_plugin.set_state_dict_type("FULL_STATE_DICT")
trainer.save_model()

هنا، من أهم الأمور التي يجب ملاحظتها حاليًا عند استخدام FSDP مع PEFT هو أن use_orig_params يجب أن يكون False لتحقيق توفير في ذاكرة GPU. وبسبب use_orig_params=False، يجب تغيير سياسة التفاف التلقائي لـ FSDP بحيث يتم التفاف المعاملات القابلة للتدريب وغير القابلة للتدريب بشكل منفصل. يتم ذلك بواسطة مقتطف الكود أدناه الذي يستخدم دالة الأداة fsdp_auto_wrap_policy من PEFT:

if getattr(trainer.accelerator.state, "fsdp_plugin", None):
    from peft.utils.other import fsdp_auto_wrap_policy

    fsdp_plugin = trainer.accelerator.state.fsdp_plugin
    fsdp_plugin.auto_wrap_policy = fsdp_auto_wrap_policy(trainer.model)

استهلاك الذاكرة

في المثال أعلاه، الذاكرة المستهلكة لكل وحدة معالجة رسومية هي 72-80 جيجابايت (90-98%) كما هو موضح في لقطة الشاشة أدناه. الزيادة الطفيفة في ذاكرة وحدة معالجة الرسومية في النهاية تحدث عند حفظ النموذج باستخدام نوع FULL_STATE_DICT بدلاً من SHARDED_STATE_DICT بحيث يمتلك النموذج أوزان مُكيِّف يمكن تحميلها بشكل طبيعي باستخدام طريقة from_pretrained أثناء الاستدلال:

GPU memory usage for the training run

استخدم PEFT QLoRA و FSDP لضبط نماذج كبيرة على عدة وحدات معالجة رسومية

في هذا القسم، سنستعرض كيفية استخدام QLoRA و FSDP لضبط نموذج llama بحجم 70B على بطاقتي GPU بسعة 24GB لكل منهما. قام Answer.AI بالتعاون مع bitsandbytes و Hugging Face 🤗 بإتاحة الكود مفتوح المصدر الذي يتيح استخدام FSDP+QLoRA، وشرحوا العملية بأكملها في مدونتهم القيّمة You can now train a 70b language model at home. وقد تم دمج ذلك الآن في منظومة Hugging Face.

لذلك، نحتاج أولاً إلى bitsandbytes>=0.43.3، وaccelerate>=1.0.1، وtransformers>4.44.2، وtrl>0.11.4، وpeft>0.13.0. يجب علينا ضبط fsdp_cpu_ram_efficient_loading=true، وfsdp_use_orig_params=false، وfsdp_offload_params=true (إزاحة المعاملات إلى وحدة المعالجة المركزية) عند استخدام إعداد Accelerate. عند عدم استخدام مُشغّل accelerate، يمكنك بدلاً من ذلك ضبط متغير البيئة export FSDP_CPU_RAM_EFFICIENT_LOADING=true. هنا، سنستخدم إعداد accelerate، والإعداد أدناه يمكن العثور عليه في fsdp_config_qlora.yaml:

compute_environment: LOCAL_MACHINE                                                                                                                                           
debug: false                                                                                                                                                                 
distributed_type: FSDP
downcast_bf16: 'no'
fsdp_config:
  fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
  fsdp_backward_prefetch: BACKWARD_PRE
  fsdp_cpu_ram_efficient_loading: true
  fsdp_forward_prefetch: false
  fsdp_offload_params: true
  fsdp_sharding_strategy: FULL_SHARD
  fsdp_state_dict_type: SHARDED_STATE_DICT
  fsdp_sync_module_states: true
  fsdp_use_orig_params: false
machine_rank: 0
main_training_function: main
mixed_precision: 'no'
num_machines: 1
num_processes: 2
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false

أدناه الأمر الخاص بالإطلاق المتاح في run_peft_qlora_fsdp.sh:

accelerate launch --config_file "configs/fsdp_config_qlora.yaml"  train.py \
--seed 100 \
--model_name_or_path "meta-llama/Llama-2-70b-hf" \
--dataset_name "smangrul/ultrachat-10k-chatml" \
--chat_template_format "chatml" \
--add_special_tokens False \
--append_concat_token False \
--splits "train,test" \
--max_seq_len 2048 \
--num_train_epochs 1 \
--logging_steps 5 \
--log_level "info" \
--logging_strategy "steps" \
--eval_strategy "epoch" \
--save_strategy "epoch" \
--push_to_hub \
--hub_private_repo True \
--hub_strategy "every_save" \
--bf16 True \
--packing True \
--learning_rate 1e-4 \
--lr_scheduler_type "cosine" \
--weight_decay 1e-4 \
--warmup_steps 0 \
--max_grad_norm 1.0 \
--output_dir "llama-sft-qlora-fsdp" \
--per_device_train_batch_size 2 \
--per_device_eval_batch_size 2 \
--gradient_accumulation_steps 2 \
--gradient_checkpointing True \
--use_reentrant True \
--dataset_text_field "content" \
--use_flash_attn True \
--use_peft_lora True \
--lora_r 8 \
--lora_alpha 16 \
--lora_dropout 0.1 \
--lora_target_modules "all-linear" \
--use_4bit_quantization True \
--use_nested_quant True \
--bnb_4bit_compute_dtype "bfloat16" \
--bnb_4bit_quant_storage_dtype "bfloat16"

لاحظ الحجة الجديدة المُمرَّرة، bnb_4bit_quant_storage_dtype، والتي تحدد نوع البيانات لتعبئة المعاملات ذات 4 بت. على سبيل المثال، عند ضبطها على bfloat16، يتم تجميع 16/4 = 4 معاملًا من معاملات 4 بت معًا بعد التكميم. عند استخدام التدريب بدقة مختلطة مع bfloat16، يمكن أن تكون bnb_4bit_quant_storage_dtype إما bfloat16 للضبط الدقيق النقي بـ bfloat16، أو float32 للدقة المختلطة التلقائية (وهذا يستهلك ذاكرة GPU أكثر). عند استخدام التدريب بدقة مختلطة مع float16، يجب ضبط bnb_4bit_quant_storage_dtype على float32 لضمان تدريب مستقر بالدقة المختلطة التلقائية.

من حيث كود التدريب، التغييرات المهمة في الكود هي:

...

bnb_config = BitsAndBytesConfig(
    load_in_4bit=args.use_4bit_quantization,
    bnb_4bit_quant_type=args.bnb_4bit_quant_type,
    bnb_4bit_compute_dtype=compute_dtype,
    bnb_4bit_use_double_quant=args.use_nested_quant,
+   bnb_4bit_quant_storage=quant_storage_dtype,
)

...

model = AutoModelForCausalLM.from_pretrained(
    args.model_name_or_path,
    quantization_config=bnb_config,
    trust_remote_code=True,
    attn_implementation="flash_attention_2" if args.use_flash_attn else "eager",
+   dtype=quant_storage_dtype or torch.float32,
)

لاحظ أن dtype الخاص بـ AutoModelForCausalLM هو نفسه نوع البيانات bnb_4bit_quant_storage. هذا كل شيء. كل ما عدا ذلك يتكفل به Trainer وTRL.

استهلاك الذاكرة

في المثال أعلاه، الذاكرة المستهلكة لكل وحدة معالجة رسومية هي 19.6 GB بينما استهلاك ذاكرة الوصول العشوائي للمعالج المركزي حوالي 107 GB. عند تعطيل نقل المهام إلى المعالج المركزي، يصبح استهلاك ذاكرة وحدة معالجة الرسومية 35.6 GB/ GPU. وبالتالي، ما كان يتطلب 16 وحدة معالجة رسومية بسعة 80GB للضبط الكامل، و8 وحدات معالجة رسومية بسعة 80GB مع FSDP+LoRA، وعدة وحدات معالجة رسومية بسعة 80GB مع DDP+QLoRA، أصبح الآن يتطلب وحدتي معالجة رسومية بسعة 24GB. وهذا يجعل ضبط النماذج الكبيرة أكثر قابلية للوصول.

الموارد الإضافية

يمكنك أيضاً الرجوع إلى مستودع llama-recipes ودليل Getting started with Llama لمعرفة كيفية الضبط الدقيق باستخدام FSDP وPEFT.

ملاحظات

  1. دمج النماذج عند استخدام PEFT و FSDP غير مدعوم حاليًا وسيؤدي إلى ظهور خطأ.
  2. لم يتم اختبار تمرير معامل الإعداد modules_to_save حتى الآن.
  3. لم يتم اختبار توفير ذاكرة وحدة معالجة الرسوميات (GPU) عند استخدام نقل المعالجة إلى وحدة المعالجة المركزية (CPU) حتى الآن.
  4. عند استخدام FSDP+QLoRA، يؤدي paged_adamw_8bit حاليًا إلى خطأ عند حفظ نقطة التفتيش.
  5. يجب أن يعمل تدريب DoRA مع FSDP (على الرغم من أن سرعته أقل من LoRA). عند دمجه مع bitsandbytes (QDoRA)، يجب أن تعمل التكميم بدقة 4 بت أيضاً، لكن التكميم بدقة 8 بت يعاني من مشكلات معروفة ولا يُنصح به.
  6. مع FSDP2، تعيد set_adapter() وset_requires_grad() تقسيم وحدات FSDP2 داخل PeftModel، بما في ذلك PeftModel نفسه، بحيث تصل تغييرات requires_grad إلى المعاملات المقسمة. لا يحدث ذلك عند الخروج من disable_adapter() إلا إذا استبدل FSDP2 كائنات المعاملات المسجلة داخله، كما يحدث مع المعاملات المقسمة في تمريرة أمامية، أو إذا أدى إعادة التفعيل إلى تغيير requires_grad عن قيمته عند الدخول. لا تُعاد تقسيم وحدات FSDP2 التي تقسم معاملات طبقة مُدمجة، لذا يجب إلغاء الدمج قبل استدعاء هذه الطرق. مع reshard_after_forward=False، أو بالنسبة للوحدة الجذرية مع القيمة الافتراضية None، ستقوم التمريرة الأمامية التالية بجمع كل المعاملات مرة أخرى. لا تُعاد تقسيم وحدات FSDP2 التي تغلف PeftModel؛ استدعِ reshard() عليها قبل هذه الطرق. نظرًا لأن هذه الاستدعاءات قد تُبقي الأوزان الأساسية مقسمة، فإن الدمج مباشرةً بعدها لا يعمل بشكل صحيح (انظر 1). لا تغطي طرق تعلم الموجهات والموجه التكيفي بعد.
  7. عند استخدام FSDP2/HSDP، استدعِ get_peft_model_state_dict() أو save_pretrained() على كل رتبة مشاركة: فجمع متجهات المُكيِّف الموزَّعة هو عملية جماعية. فيما يتعلق بـsave_pretrained()، مرِّر is_main_process=True فقط على الرتبة المسؤولة عن الكتابة. يتم جمع إدخالات المُكيِّف المحددة فقط (بما في ذلك التضمينات المطلوبة صراحةً أو modules_to_save)؛ بينما تبقى أوزان النموذج الأساسي غير المحددة مجزأة.
Update on GitHub