PEFT documentation
استكشاف الأخطاء وإصلاحها
استكشاف الأخطاء وإصلاحها
إذا واجهت أي مشكلة عند استخدام PEFT، فيرجى التحقق من القائمة التالية للمشاكل الشائعة وحلولها.
لا تعمل الأمثلة
تعتمد الأمثلة غالبًا على أحدث إصدارات الحزم، لذا يرجى التأكد من تحديثها. وبشكل خاص، تحقق من إصدارات الحزم التالية:
pefttransformersacceleratetorch
بشكل عام، يمكنك تحديث إصدار الحزمة من خلال تنفيذ هذا الأمر داخل بيئة بايثون الخاصة بك:
python -m pip install -U <package_name>
يُعدّ تثبيت PEFT من المصدر مفيدًا للبقاء على اطلاع بأحدث التطورات:
python -m pip install git+https://github.com/huggingface/peft
المشكلات المتعلقة بأنواع البيانات
ValueError: محاولة إلغاء توسيع تدرجات FP16
من المحتمل أن يكون هذا الخطأ قد حدث لأن النموذج تم تحميله باستخدام dtype=torch.float16 ثم تم استخدامه في سياق الدقة المختلطة التلقائية (AMP)، على سبيل المثال عن طريق تعيين fp16=True في فئة Trainer من 🤗 Transformers. والسبب في ذلك هو أنه عند استخدام AMP، يجب ألا تستخدم الأوزان القابلة للتدريب fp16 أبداً. لجعل هذا يعمل دون تحميل النموذج بالكامل في fp32، أضف ما يلي إلى كودك:
peft_model = get_peft_model(...)
# add this:
for param in model.parameters():
if param.requires_grad:
param.data = param.data.float()
# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()بديلًا عن ذلك، يمكنك استخدام دالة cast_mixed_precision_params() لصبغ الأوزان بشكل صحيح:
from peft import cast_mixed_precision_params
peft_model = get_peft_model(...)
cast_mixed_precision_params(peft_model, dtype=torch.float16)
# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()بدءًا من إصدار PEFT v0.12.0، يقوم PEFT تلقائيًا بترقية نوع البيانات (dtype) لأوزان المُكيِّف (adapter) من
torch.float16وtorch.bfloat16إلىtorch.float32حيثما كان ذلك مناسبًا. لمنع هذا السلوك، يمكنك تمريرautocast_adapter_dtype=Falseإلى ~get_peft_model()، وإلى from_pretrained()، وإلى load_adapter().
اختيار نوع البيانات (dtype) للمُكيّف
تعتمد معظم طرق PEFT، مثل LoRA، على عملها من خلال إضافة أوزات مُكيِّف قابلة للتدريب. افتراضيًا، تُخزَّن هذه الأوزان في نوع البيانات float32 (fp32)، أي بدقة نسبيًا عالية. لذلك، حتى لو تم تحميل النموذج الأساسي في float16 (fp16) أو bfloat16 (bf16)، فإن أوزان المُكيِّف تبقى من نوع float32. عند حساب نتائج المُكيِّف أثناء المرور الأمامي، يكون المدخل عادةً من نوع بيانات النموذج الأساسي، وبالتالي يتم رفع دقته إلى float32 عند الحاجة، ثم يُعاد تحويله إلى النوع الأصلي.
إذا كنت تفضل أن تكون أوزان المُكيّف (adapter) بدقة أدنى من نموذج الأساس، أي في float16 أو bfloat16، يمكنك تمرير autocast_adapter_dtype=False عند إنشاء النموذج (~get_peft_model()) أو تحميل النموذج (from_pretrained()). هناك بعض المزايا والعيوب لهذا:
مزايا مُكيّف الدقة النصفية:
- حساب أسرع قليلاً
- ذاكرة أقل قليلاً
- حجم ملف نقطة التفتيش الأصغر (نصف الحجم)
عيوب مُكيّف الدقة النصفية:
- فقدان أسوأ قليلاً
- خطر أعلى لحدوث تجاوز أو نقص
لاحظ أنه في معظم حالات الاستخدام، يُحدَّد وقت التشغيل الكلي وتكلفة الذاكرة بحجم النموذج الأساسي وبمجموعة البيانات، بينما لن يكون لنوع البيانات (dtype) الخاص بمكيّف PEFT سوى تأثير طفيف.
تعمل عملية التدريب لكن النموذج لا يتحسن
إذا بدا التدريب سليمًا (ينخفضّ الخسارة، وتُحفظ نقاط التفتيش دون أخطاء) لكن النموذج الناتج لا يؤدي بشكل أفضل من النموذج الأساسي، فتأكد مما إذا كانت المعلمات القابلة للتدريب قد تغيّرت فعليًا أثناء التدريب:
# before training
sample_param = next(p for p in model.parameters() if p.requires_grad)
before = sample_param.detach().clone()
# ... train for at least three optimizer steps ...
after = next(p for p in model.parameters() if p.requires_grad)
print("weights updated:", not torch.allclose(before, after.detach().cpu()))غياب خطافات الأمامية أو الخلفية
إذا لم تتحرك الأوزان، فالسبب المحتمل هو أن مراجع المعاملات تم التقاطها قبل أن تُجسَّد المعاملات على جهازها المستهدف. يمكن أن يحدث ذلك عندما يُغلَّف النموذج الأساسي بواسطة get_peft_model() بينما لم تُنقل معاملاته بعد إلى الجهاز المستهدف، ثم تسجّل مكتبة طرف ثالث خطافات للأمام/الخلف (على سبيل المثال، خطافات التدرجات لكل عينة من إطار عمل الخصوصية التفاضلية) أو يُنشأ مُحسِّن قبل أول استدعاء للأمام. عندما تُجسَّد المعاملات لاحقًا، تشير تلك الخطافات ومراجع المُحسِّن إلى متجهات قديمة، فتصبح كل عملية تحديث عملية صامتة لا تُحدث أي تغيير.
لتجنب ذلك، استخدم الترتيب التالي:
- حمّل النموذج الأساسي وانقله إلى الجهاز المستهدف (مثل
model.to(device)). - قم بتغليفه باستخدام get_peft_model().
- فقط بعد ذلك، سجّل الخطافات أو أنشئ المُحسِّن.
لا تتطابق الوحدات المستهدفة مع النموذج
إذا كانت عملية التدريب تُنفَّذ دون أخطاء لكن الخسارة لا تتغير تقريبًا — أو لم يُظهر النموذج أي تحسّن بعد الضبط الدقيق — فقد لا تتطابق target_modules المُهيّأة (أو target_modules الافتراضية إذا لم يُحدِّد المستخدم خلاف ذلك) مع أسماء الوحدات الفعلية في النموذج. وهذا شائع في البنى الهجينة مثل Mamba وJamba أو NemotronH، التي تستخدم أسماء طبقات مختلفة عن نماذج Transformer القياسية.
يرفع PEFT خطأً فقط عندما لا يتطابق أي من target_modules المُهيأة مع أي وحدة في النموذج. إذا تطابق بعضها ولم يتطابق بعضها الآخر، يتم تجاوز العناصر غير المتطابقة بصمت — دون أي تحذير. وقد تبقى نسبة كبيرة من النموذج مجمّدة دون أي إشارة إلى ذلك.
أول إجراء تشخيصي هو التحقق من عدد المعاملات القابلة للتدريب:
from peft import LoraConfig, get_peft_model
config = LoraConfig(target_modules=["q_proj", "v_proj", "gate_proj"])
model = get_peft_model(base_model, config)
model.print_healthcheck()إذا كان العدد المُبلَّغ عنه أقل بكثير مما هو متوقع، افحص طبقات المُكيِّف (adapter) في النموذج:
print(model.get_layer_status())يعيد هذا الأمر قائمة من إدخالات TunerLayerStatus تعرض اسم كل طبقة مُكيّف، ونوع الوحدة، وحالة التفعيل، والمُكيّفات النشطة. أو، طباعة تمثيل النموذج (print(model)) تمنح نظرة عامة على التسلسل الهرمي الكامل للوحدات، وهو أمر مفيد لتحديد أسماء الطبقات الصحيحة في البنى غير المألوفة.
تُتجاهل بصمت أيّ مدخل في target_modules لا يتوافق مع وحدة في النموذج. حدّث target_modules ليتطابق مع أسماء الوحدات الفعلية.
يؤثر هذا الخطأ على جميع طرق PEFT التي تستخدم
target_modules(مثل LoRA وLoHa وIA³ وغيرها)، وليس على LoRA فقط. خطوات التشخيص هي نفسها بغض النظر عن الطريقة المستخدمة. إذا كنت غير متأكد من أسماء الوحدات التي تستخدمها بنية معينة، فراجع توثيق النموذج أو افحصmodel.named_modules()قبل تكوين المُكيّف (adapter).
نتائج غير جيدة من نموذج PEFT محمّل
قد يكون هناك عدة أسباب للحصول على نتيجة ضعيفة من نموذج PEFT محمّل، وهي مذكورة أدناه. إذا كنت لا تزال غير قادر على استكشاف المشكلة، فتأكد مما إذا كان لدى أي شخص آخر مشكلة مشابهة على GitHub، وإذا لم تجد أي شيء، فافتح مشكلة جديدة.
عند فتح مشكلة، يساعدك كثيرًا تقديم مثال برمجي بسيط يعيد إنتاج المشكلة. كما يُرجى الإبلاغ عما إذا كان النموذج المحمّل يؤدي بنفس مستوى أداء النموذج قبل الضبط الدقيق، أو إذا كان يؤدي بمستوى عشوائي، أو إذا كان أسوأ قليلًا مما هو متوقع. تساعدنا هذه المعلومات في تحديد المشكلة بشكل أسرع.
انحرافات عشوائية
إذا لم تكن مخرجات نموذجك مطابقة تمامًا لمخرجات التشغيلات السابقة، فقد يكون هناك مشكلة في العناصر العشوائية. على سبيل المثال:
- يرجى التأكد من أن النموذج في وضع
.eval()، وهو أمر مهم، على سبيل المثال، إذا كان النموذج يستخدم dropout - إذا كنت تستخدم generate على نموذج لغوي، فقد يحدث أخذ عينات عشوائي، لذا فإن الحصول على نفس النتيجة يتطلب تعيين بذرة عشوائية
- إذا استخدمتَ التكميم ودمجتَ الأوزان، فإن الانحرافات الطفيفة متوقعة بسبب أخطاء التقريب
نموذج محمّل بشكل غير صحيح
يرجى التأكد من تحميل النموذج بشكل صحيح. خطأ شائع هو محاولة تحميل نموذج مدرب باستخدام get_peft_model() وهو أمر غير صحيح. بدلاً من ذلك، يجب أن يبدو كود التحميل على النحو التالي:
from peft import PeftModel, PeftConfig
base_model = ... # to load the base model, use the same code as when you trained it
config = PeftConfig.from_pretrained(peft_model_id)
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)مسارات تنفيذ مُحسّنة تتجاوز المُكيّفات
تقرأ بعض مسارات التنفيذ المُحسَّنة أوزان الوحدة مباشرةً بدلاً من استدعاء طرق forward الخاصة بها. وقد يؤدي ذلك إلى تجاوز التغييرات التي أجراها المُكيِّفون غير المدمجين دون أي تحذير، حتى لو كان المُكيِّف نشطًا. وقد يؤثر هذا المشكلة على وحدات مختلفة وطرق PEFT.
على سبيل المثال، يمكن لمسار torch.nn.TransformerEncoder الأصلي في PyTorch استخدام مسار مدمج أثناء التقييم مع torch.no_grad() أو torch.inference_mode(). يمكن لهذا المسار تجاوز المُكيِّفات (adapters) في وحدات TransformerEncoderLayer الفرعية، مثل مُكيِّفات LoRA على linear1 وlinear2. قد يعمل التدريب بشكل صحيح بينما يتجاهل التقييم تغييرات المُكيِّف. راجع المشكلة #3976 للحصول على طريقة لإعادة إنتاج المشكلة.
إذا كنتَ تشكّ في وجود هذه المشكلة، فتأكد أولاً من أن المُكيّف مُفعّل ونشط باستخدام get_layer_status(). قارن المخرجات على المدخلات نفسها في وضع التقييم مع وجود المُكيّف وبدونه، ثم مع تعطيل التحسين. استخدم مُكيّفًا مُدرَّبًا يُغيّر المخرجات، لأن مُكيّفات LoRA المُهيّأة حديثًا عادةً لا يكون لها أي تأثير. الفروق العددية الصغيرة أمر طبيعي.
لأداة ملاءمة غير مدمجة على مُشفِّر أصلي في الوضع العاجل، قارن المخرجات لإدخال متجه x نفسه:
import torch
model.eval()
previous = torch.backends.mha.get_fastpath_enabled()
try:
with torch.inference_mode():
torch.backends.mha.set_fastpath_enabled(True)
fast_output = model(x)
torch.backends.mha.set_fastpath_enabled(False)
reference_output = model(x)
finally:
torch.backends.mha.set_fastpath_enabled(previous)
print((fast_output - reference_output).abs().max().item())لا يعني تفعيل علم الخلفية (backend) بمفرده أن المسار المدمج (fused path) يعمل؛ إذ يعتمد استخدامه أيضاً على النموذج والمدخلات وشروط وقت التشغيل. افحص تنفيذ forward للوحدة المحيطة (enclosing module) بحثاً عن وصول مباشر إلى الأوزان أو استدعاءات لمُشغِّلات مدمجة (fused operators). كن حذراً عند التصحيح باستخدام خطافات الأمامية (forward hooks): فقد يؤدي تسجيل خطاف إلى تعطيل تحسين معين وتغيير السلوك الذي تقوم بتحقيقه.
بالنسبة للترميز الأصلي في الوضع المتحمس، يُعد torch.backends.mha.set_fastpath_enabled(False) حلاً بديلاً يحافظ على عدم دمج الملاءمات. هذا الإعداد يشمل العملية بأكملها، وقد يؤثر على نماذج أخرى والأداء، ولا ينبغي تبديله بشكل مستقل من قبل الطلبات المتزامنة. بالنسبة لمسارات التحسين الأخرى، عطّل التحسين المتأثر أو استخدم تنفيذاً يستدعي الوحدات المُلاءمة. إذا سمح ذلك أسلوب PEFT وإعداد الكمّية، يمكنك أيضاً دمج الملاءمات باستخدام merge_and_unload() واستخدام النموذج المُعاد للاستدلال. في هذه الحالة، تُدرج تغييرات الملاءمة في الأوزان الأساسية. يؤدي الدمج إلى إزالة أغلفة الملاءمة، لذا احتفظ بنقاط الفحص الأصلية إذا كنت بحاجة إلى تبديل الملاءمات أو استئناف التدريب.
طبقات مهيأة عشوائياً
في بعض المهام، من المهم ضبط modules_to_save بشكل صحيح في الإعدادات لمراعاة الطبقات المُهيأة عشوائياً.
على سبيل المثال، يكون هذا ضروريًا إذا كنت تستخدم LoRA لضبط نموذج لغة بشكل دقيق لتصنيف التسلسلات، لأن 🤗 Transformers يضيف رأس تصنيف مهيأً عشوائيًا فوق النموذج. إذا لم تضف هذا الطبقة إلى modules_to_save، فلن يتم حفظ رأس التصنيف. في المرة التالية التي تحمّل فيها النموذج، ستحصل على رأس تصنيف مهيأً عشوائيًا مختلفًا، مما يؤدي إلى نتائج مختلفة تمامًا.
يحاول PEFT التخمين الصحيح لـ modules_to_save إذا قمت بتوفير معامل task_type في الإعدادات. يجب أن يعمل هذا مع نماذج transformers التي تتبع مخطط التسمية القياسي. ومع ذلك، من الجيد دائمًا إعادة التحقق، لأننا لا يمكننا ضمان أن جميع النماذج تتبع مخطط التسمية.
عند تحميل نموذج transformers يحتوي على طبقات مُهيأة عشوائياً، يجب أن ترى تحذيراً على النحو التالي:
Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.يجب إضافة الطبقات المذكورة إلى modules_to_save في الإعدادات لتجنب المشكلة الموصوفة.
يقوم healthcheck() بإصدار تحذير TASK_HEAD_NOT_SAVED لأكثر أشكال هذه الخطأ شيوعاً: وهو رأس تصنيف تسلسل أو تصنيف رموز أو إجابة أسئلة تم تهيئته حديثاً عند تحميل النموذج الأساسي لكنه غير مدرج في modules_to_save، على سبيل المثال لأن task_type لم يتم ضبطه. يتم تجاوز هذا الفحص إذا لم يذكر النموذج الأساسي معمارياته في الإعدادات.
كمثال، عند تحميل نموذج يستخدم معمارية DeBERTa لتصنيف التسلسلات، ستظهر لك تحذير بأن الأوزان التالية تم تهيئتها حديثاً:
['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']. ومن ذلك يتبع أن طبقتيclassifierوpoolerيجب إضافتهما إلى:modules_to_save=["classifier", "pooler"].
توسيع المفردات
في العديد من مهام الضبط الدقيق للغة، يكون توسيع مفردات النموذج ضروريًا نظرًا لإدخال رموز جديدة. ويتطلب ذلك توسيع طبقة التضمين (embedding layer) لاستيعاب الرموز الجديدة، بالإضافة إلى تخزين طبقة التضمين إلى جانب أوزان المحوّل (adapter) عند حفظ المحوّل، وذلك اعتمادًا على طريقة الضبط الدقيق. هناك عدة طرق لتحقيق ذلك مرتبة حسب فعالية المعلمات:
- الرموز القابلة للتدريب، تدريب الرموز المحددة فقط، مع إمكانية تخزين القيم المحدثة فقط
- تدريب مُكيّف على مصفوفة التضمين، مع خيار تخزين القيم المحدّثة فقط
- التدريب الكامل لطبقة التضمين
استخدام الرموز القابلة للتدريب
لنبدأ بالرموز القابلة للتدريب، وفي هذه الحالة هي تكامل LoRA. إذا كنت مهتمًا بتدريب التضمينات الجديدة فقط دون أي شيء آخر، فراجع التوثيق المستقل.
لتفعيل التدريب الانتقائي للرموز في طبقة التضمين، يجب عليك تقديم معرفات الرموز (token ids) للرموز التي أضفتها حديثاً عبر معامل trainable_token_indices. بشكل اختياري، يمكنك تحديد الطبقة المستهدفة إذا كانت هناك أكثر من طبقة تضمين. بالنسبة لنموذج Mistral، قد يبدو الأمر على النحو التالي:
new_tokens = ['<think>', '</think>']
tokenizer.add_tokens(new_tokens)
base_model.resize_token_embeddings(len(tokenizer))
lora_config = LoraConfig(
...,
trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
)إذا كان نموذجك يستخدم أوزانًا مرتبطة (مثل lm_head)، فستحاول الرموز القابلة للتدريب حل تلك الأوزان وإبقائها محدّثة أيضًا، لذا في تلك الحالة لا ينبغي أن يكون هناك حاجة لإضافة modules_to_save=["lm_head"]. يعمل هذا فقط إذا كان النموذج يستخدم اتفاقية Transformers لربط الأوزان.
قد يؤدي حفظ النموذج باستخدام model.save_pretrained إلى حفظ مصفوفة التضمين الكاملة بدلاً من الفرق فقط، كإجراء احترازي لأن مصفوفة التضمين تم تغيير حجمها. ولتوفير المساحة، يمكنك تعطيل هذا السلوك عن طريق تعيين save_embedding_layers=False عند استدعاء save_pretrained. وهذا إجراء آمن ما لم تقم بتعديل مصفوفة التضمين عبر وسائل أخرى أيضاً، لأن مثل هذه التغييرات لن يتم تتبعها بواسطة الرموز القابلة للتدريب.
استخدام مُكيّف، مثل LoRA
جهّز طبقة التضمين (embedding layer) بإضافتها إلى target_modules في إعدادات المحوّل (adapter config) الخاص بك. على سبيل المثال، قد يبدو إعداد Mistral على النحو التالي:
config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])بمجرد إضافتها إلى target_modules، يقوم PEFT تلقائيًا بتخزين طبقة التضمين عند حفظ المُكيّف إذا كان النموذج يحتوي على get_input_embeddings وget_output_embeddings. وهذا هو الحال بشكل عام لنماذج Transformers.
إذا لم يتبع طبقة التضمين في النموذج مخطط تسمية Transformer ومع ذلك ينفذ get_input_embeddings، يمكنك ما زال حفظها عن طريق تمرير save_embedding_layers=True يدويًا عند حفظ المحوّل:
model = get_peft_model(...)
# train the model
model.save_pretrained("my_adapter", save_embedding_layers=True)لأغراض الاستدلال، حمّل النموذج الأساسي أولاً ثم أعد تحجيمه بالطريقة نفسها التي اتبعتها قبل تدريب النموذج. بعد إعادة تحجيم النموذج الأساسي، يمكنك تحميل نقطة الفحص الخاصة بـ PEFT.
للاطلاع على مثال كامل، يرجى الاطلاع على هذا الدفتر.
الضبط الدقيق الكامل
تُعدّ الضبط الكامل (Full fine-tuning) أكثر تكلفة من حيث ذاكرة الفيديو (VRAM) أو مساحة التخزين، ولكن إذا فشلت جميع الخيارات الأخرى، يمكنك اللجوء إلى هذه الطريقة ومعرفة ما إذا كانت مناسبة لك. يمكنك تحقيق ذلك بإضافة اسم طبقة التضمين (embedding layer) إلى modules_to_save. لاحظ أنك بحاجة إلى إضافة الطبقات المرتبطة (tied layers) أيضًا، مثل lm_head. مثال لنموذج Mistral مع LoRA:
config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])استلام تحذير حول “عدم تهيئة الأوزان من نقطة الحفظ للنموذج”
عند تحميل نموذج PEFT الخاص بك الذي تم تدريبه على مهمة (على سبيل المثال، التصنيف)، قد تظهر لك تحذيرًا مماثلًا للآتي:
لم يتم تهيئة بعض الأوزان في LlamaForSequenceClassification من نقطة التفتيش النموذجية في meta-llama/Llama-3.2-1B وتمت تهيئتها حديثًا: [‘score.weight’]. ينبغي عليك على الأرجح تدريب هذا النموذج على مهمة فرعية حتى تتمكن من استخدامه للتنبؤ والاستدلال.
على الرغم من أن هذا يبدو مخيفًا، فمن المرجح أنه لا يستدعي القلق. ينبع هذا التحذير من Transformers وليس تحذيرًا خاصًا بـ PEFT. يُعلمك هذا التحذير أن رأس تصنيف مُهيأ عشوائيًا (score) موصول بالنموذج الأساسي، ويجب تدريب هذا الرأس لإنتاج تنبؤات معقولة.
عند ظهور هذا التحذير قبل تدريب النموذج، يتولى PEFT تلقائيًا جعل رأس التصنيف قابلًا للتدريب إذا قمت بتمرير معامل task_type بشكل صحيح إلى إعدادات PEFT.
from peft import LoraConfig, TaskType
lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)إذا لم يتبع رأس التصنيف الخاص بك تسميات التسمية المعتادة من Transformers (وهو أمر نادر)، عليك أن تُخبر PEFT صراحةً باسم الرأس في modules_to_save.
lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])للتحقق من اسم رأس التصنيف، اطبع النموذج ويجب أن يكون آخر وحدة.
إذا ظهرت لك هذه التحذيرات من كود الاستدلال الخاص بك، أي بعد تدريب النموذج، عند تحميل نموذج PEFT، فيجب عليك دائمًا تحميل نموذج Transformers أولاً. وبما أن Transformers لا يعلم أنك ستحمّل أوزان PEFT لاحقًا، فإنه ما يزال يعرض التحذير.
كما هو الحال دائمًا، يُعدّ إجراء بعض عمليات التحقق على النموذج للتأكد من عمله بشكل صحيح أثناء الاستدلال ممارسةً مثلى.
تشغيل فحص صحة التدريب
قبل التدريب، استخدم print_healthcheck() للحصول على ملخص مختصر وقابل للقراءة البشرية لحالة نموذج PEFT. وهو مشتق من واجهات برمجة التطبيقات الخاصة بحالة النموذج والطبقات (انظر القسم التالي)، ويحدد الحالات المشبوهة مثل عدم اتساق المُكيِّفات (adapters)، أو المُكيِّفات المدمجة، أو عدد غير معتاد من المعاملات القابلة للتدريب.
peft_model.print_healthcheck()
انتبهوا بشكل خاص إلى وجود قسم “النتائج” في نهاية التقرير، والذي يُدرج إذا تم العثور على أي شذوذ.
إذا كنت تفضل إخراجًا على شكل قاموس، وهو قابل للتسلسل بصيغة JSON، فاستخدم healthcheck() بدلاً من ذلك:
healthcheck = peft_model.healthcheck()
print(healthcheck.get("findings", "all good")تفحص هذه الفحصية النموذج ومُكيّلاته فقط. ولا تتحقق من حلقة التدريب أو المُحسِّن أو مجموعة البيانات الخاصة بك.
تحقق من حالة الطبقة والنموذج
قد ينتهي نموذج PEFT أحياناً في حالة غير سليمة، خاصة عند التعامل مع عدة محوِّلات (adapters). قد تنشأ بعض اللبس حول المحوِّلات الموجودة، وأيها نشط، وأيها مدمج، وما إلى ذلك. لمساعدتك في التحقيق في هذه المشكلة، استدعِ طريقتي get_layer_status() وget_model_status().
توفر لك الطريقة get_layer_status() نظرة تفصيلية على المُكيّفات النشطة والمدمجة والمتاحة لكل طبقة مستهدفة.
>>> from transformers import AutoModel
>>> from peft import get_peft_model, LoraConfig
>>> model_id = "google/flan-t5-small"
>>> model = AutoModel.from_pretrained(model_id)
>>> model = get_peft_model(model, LoraConfig())
>>> model.get_layer_status()
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
module_type='lora.Linear',
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default']),
TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
module_type='lora.Linear',
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default']),
...]
>>> model.get_model_status()
TunerModelStatus(
base_model_type='T5Model',
adapter_model_type='LoraModel',
peft_types={'default': 'LORA'},
trainable_params=344064,
total_params=60855680,
num_adapter_layers=48,
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default'],
)في مخرجات حالة النموذج، يجب أن تنتبه إلى الإدخالات التي تقول "irregular". هذا يعني أن PEFT رصد حالة غير متسقة في النموذج. على سبيل المثال، إذا كان merged_adapters="irregular"، فهذا يعني أنه بالنسبة لمُكيّف واحد على الأقل، تم دمجه في بعض الوحدات المستهدفة وليس في وحدات أخرى. على الأرجح ستكون نتائج الاستدلال غير صحيحة نتيجة لذلك.
أفضل طريقة لحل هذه المشكلة هي إعادة تحميل نقطة الفحص الكاملة للنموذج ونقاط فحص المُكيِّفات. تأكد من عدم تنفيذ أي عمليات غير صحيحة على النموذج، مثل دمج المُكيِّفات يدويًا على بعض الوحدات دون غيرها.
قم بتحويل حالة الطبقة إلى DataFrame من pandas لتسهيل الفحص البصري.
from dataclasses import asdict
import pandas as pd
df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())من الممكن الحصول على هذه المعلومات لنماذج غير PEFT إذا كانت تستخدم طبقات PEFT في الخلفية، لكن بعض المعلومات مثل base_model_type أو peft_types لا يمكن تحديدها في تلك الحالة. كمثال، يمكنك استدعاء هذا على نموذج diffusers على النحو التالي:
>>> import torch
>>> from diffusers import StableDiffusionPipeline
>>> from peft import get_model_status, get_layer_status
>>> path = "runwayml/stable-diffusion-v1-5"
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
>>> pipe = StableDiffusionPipeline.from_pretrained(path, dtype=torch.float16)
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
>>> pipe.set_lora_device(["adapter-2"], "cuda")
>>> get_layer_status(pipe.text_encoder)
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
module_type='lora.Linear',
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
available_adapters=['adapter-1', 'adapter-2'],
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
module_type='lora.Linear',
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
...]
>>> get_model_status(pipe.unet)
TunerModelStatus(
base_model_type='other',
adapter_model_type='None',
peft_types={},
trainable_params=797184,
total_params=861115332,
num_adapter_layers=128,
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
available_adapters=['adapter-1', 'adapter-2'],
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
)السرعة
تحميل أوزان المُكيّف بطيء
يجب أن يكون تحميل المكيّفات مثل أوزان LoRA سريعاً بشكل عام مقارنةً بتحميل النموذج الأساسي. ومع ذلك، قد توجد حالات استخدام تكون فيها أوزان المكيّف كبيرة الحجم، أو يحتاج المستخدمون إلى تحميل عدد كبير من المكيّفات — في هذه الحالة قد يتراكم وقت التحميل. والسبب في ذلك أن أوزان المكيّف تُهيَّأ أولاً ثم تُستبدل بالأوزان المحمَّلة، وهو أمر مُهدِر للموارد. لتسريع وقت التحميل، يمكنك تمرير الحجة low_cpu_mem_usage=True إلى from_pretrained() و load_adapter().
إذا كانت هذه الخيارات تعمل بشكل جيد عبر مختلف حالات الاستخدام، فقد تصبح الافتراضية لتحميل المُكيِّفات في المستقبل.
إمكانية إعادة الإنتاج
النماذج التي تستخدم تطبيع الدفعة
عند تحميل نموذج PEFT مدرَّب يستخدم النموذج الأساسي طبقات تطبيع الدفعة (مثل torch.nn.BatchNorm1d أو torch.nn.BatchNorm2d)، قد تلاحظ أنك لا تستطيع إعادة إنتاج المخرجات نفسها تمامًا. والسبب في ذلك أن طبقات تطبيع الدفعة تتتبع الإحصاءات المتحركة أثناء التدريب، لكن هذه الإحصاءات ليست جزءًا من نقطة الحفظ الخاصة بـ PEFT. وبالتالي، عند تحميل نموذج PEFT، سيتم استخدام الإحصاءات المتحركة للنموذج الأساسي (أي تلك الموجودة قبل التدريب باستخدام PEFT).
قد لا يكون هذا الأمر ذا أهمية كبيرة اعتمادًا على حالة الاستخدام الخاصة بك. ومع ذلك، إذا كنت بحاجة إلى أن تكون مخرجاتك قابلة للتكرار بنسبة 100%، يمكنك تحقيق ذلك بإضافة طبقات تطبيع الدفعة إلى modules_to_save. أدناه مثال على ذلك باستخدام resnet وLoRA. لاحظ أننا اضبطنا modules_to_save=["classifier", "normalization"]. نحتاج إلى الحجة "classifier" لأن مهمتنا هي تصنيف الصور، ونضيف الحجة "normalization" للتأكد من حفظ طبقات تطبيع الدفعة في نقطة التحقق الخاصة بـ PEFT.
from transformers import AutoModelForImageClassification
from peft import LoraConfig, get_peft_model
model_id = "microsoft/resnet-18"
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
config = LoraConfig(
target_modules=["convolution"],
modules_to_save=["classifier", "normalization"],
),يعتمد على نوع النموذج الذي تستخدمه، فقد تحمل طبقات تطبيع الدفعة أسماء مختلفة عن "normalization"، لذا يرجى التأكد من تطابق الاسم مع بنية النموذج الخاص بك.
عدم تطابق الإصدار
خطأ أثناء تحميل الإعداد بسبب معامل كلمة مفتاحية غير متوقعة
عندما تواجه خطأً مماثلاً للعرض أدناه، فهذا يعني أن المُكيّف الذي تحاول تحميله تم تدريبه باستخدام إصدار أحدث من PEFT من الإصدار المثبت على نظامك.
TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>أفضل طريقة لحل هذه المشكلة هي تثبيت أحدث إصدار من PEFT:
python -m pip install -U PEFT
إذا تم تدريب المُكيّف من تثبيت PEFT من المصدر (إصدار غير منشور من PEFT)، فستحتاج أيضًا إلى تثبيت PEFT من المصدر.
python -m pip install -U git+https://github.com/huggingface/peft.git
إذا لم يكن من الممكن لك ترقية PEFT، فهناك حل بديل يمكنك تجربته.
افترض أن رسالة الخطأ تشير إلى أن معامل الكلمة المفتاحية المجهول يُسمى foobar. ابحث داخل adapter_config.json الخاص بهذا محوّل PEFT عن إدخال foobar واحذفه من الملف. ثم احفظ الملف وحاول تحميل النموذج مرة أخرى.
يعمل هذا الحل في معظم الأحيان. ما دام أنه القيمة الافتراضية لـfoobar، يمكن تجاهله. ومع ذلك، عند تعيينه إلى قيمة أخرى، ستحصل على نتائج غير صحيحة. يُعد ترقية PEFT هو الحل الموصى به.
معالجة المُكيّفات
استخدام عدة محوِّلات في الوقت نفسه
يتيح لك PEFT إنشاء أكثر من مُكيّف واحد على نفس النموذج. وقد يكون ذلك مفيدًا في العديد من المواقف. على سبيل المثال، عند الاستدلال، قد ترغب في تقديم نموذجين مُكيَّفين من نفس النموذج الأساسي بدلاً من تحميل النموذج الأساسي مرة واحدة لكل نموذج مُكيَّف، وهو ما سيكلف ذاكرة أكثر. ومع ذلك، يمكن تفعيل عدة مُكيّفات في الوقت نفسه. وبهذه الطريقة، قد يستفيد النموذج من التعلم المستمد من جميع تلك المكيّفات في آنٍ واحد. كمثال، إذا كان لديك نموذج انتشار، فقد ترغب في استخدام مُكيّف LoRA واحد لتغيير الأسلوب ومُكيّف مختلف لتغيير الموضوع.
يمكن تفعيل عدة محوِّلات في الوقت نفسه بشكل عام في جميع طرق PEFT (LoRA، LoHa، IA³، إلخ) باستثناء طرق تعلّم المحفز (p-tuning، prefix tuning، إلخ). يوضح المثال التالي كيفية تحقيق ذلك:
from transformers import AutoModelForCausalLM
from peft import PeftModel
model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
model = PeftModel.from_pretrained(base_model, lora_path_0) # default adapter_name is 'default'
model.load_adapter(lora_path_1, adapter_name="other")
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
model.base_model.set_adapter(["default", "other"])في المثال أعلاه، يمكنك ملاحظة أننا بحاجة إلى استدعاء
model.base_model.set_adapter(["default", "other"]). لماذا لا يمكننا استدعاءmodel.set_adapter(["default", "other"])؟ للأسف، هذا غير ممكن لأن بعض طرق PEFT لا تدعم تفعيل أكثر من مُكيّف واحد في الوقت نفسه، كما تم شرحه سابقًا.
من الممكن أيضًا تدريب مُكيّفَين في الوقت نفسه، لكن يجب أن تكون حريصًا على التأكد من أن أوزان كلا المُكيّفَين معروفة لمُحسِّن التدريب. وإلا، فلن يتلقى أحد المُكيّفَين التحديثات فقط.
from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config_0 = LoraConfig(...)
lora_config_1 = LoraConfig(...)
model = get_peft_model(base_model, lora_config_0)
model.add_adapter(adapter_name="other", peft_config=lora_config_1)إذا قمنا الآن باستدعاء:
from transformers import Trainer
trainer = Trainer(model=model, ...)
trainer.train()أو
optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)ثم لن يتم تدريب مُكيّف LoRA الثاني ("other"). والسبب في ذلك أنه غير نشط في هذه اللحظة، مما يعني أن خاصية requires_grad على معاميره مضبوطة على False وسيهملها المُحسِّن. لذلك، تأكد من تفعيل جميع المُكيّفات التي يجب تدريبها قبل تهيئة المُحسِّن:
# activate all adapters
model.base_model.set_adapter(["default", "other"])
trainer = Trainer(model=model, ...)
trainer.train()يتناول هذا القسم استخدام عدة محوِّلات من النوع نفسه على نفس النموذج، على سبيل المثال، استخدام عدة محوِّلات LoRA في الوقت نفسه. ولا ينطبق هذا على استخدام أنواع مختلفة من المحوِّلات على نفس النموذج، على سبيل المثال محوِّل LoRA واحد ومحوِّل LoHa واحد. لهذا الغرض، يرجى الاطلاع على
PeftMixedModel.
دمج بعض المحوِّلات النشطة فقط
عند دمج أوزان المُكيِّف (adapter) في الأوزان الأساسية، لم تعد الطبقات المتأثرة تطبّق أي مُكيِّف أثناء المرور الأمامي (forward pass)، بل تستخدم فقط الأوزان الأساسية المدمجة. لذلك، إذا كان هناك عدة مُكيِّفات نشطة لكن بعضها فقط تم دمجها، فإن المُكيِّفات التي لا تزال نشطة لكن لم يتم دمجها لن تُطبَّق بصمت:
model = get_peft_model(base_model, lora_config_0, adapter_name="default")
model.add_adapter("other", lora_config_1)
model.base_model.set_adapter(["default", "other"])
output_both = model(**inputs)
model.merge_adapter(adapter_names=["default"])
output_merged = model(**inputs) # this is *not* the same as output_bothهنا، output_merged هو مخرج مُكيّف "default" وحده، على الرغم من أن "other" لا يزال مدرجًا كفعّال:
>>> model.get_layer_status()[0].active_adapters
['default', 'other']
>>> model.get_layer_status()[0].merged_adapters
['default']ينطبق الأمر نفسه عند دمج مُكيّف غير نشط: من ذلك الحين، يعيد النموذج مخرجات المُكيّف المدمج ويتجاهل المُكيّف النشط. لا يتم إصدار خطأ أو تحذير في أيٍّ من الحالتين. تبقى أوزان المُكيّف نفسه غير متأثرة، لذا فإن استدعاء unmerge_adapter() يعيد المخرجات المدمجة.
لتجنّب ذلك، إمّا دمج جميع المُكيّفات النشطة، أي استدعاء model.merge_adapter() دون adapter_names، أو فكّ الدمج قبل تغيير المُكيّفات النشطة.
هذا السلوك متسق عبر طرق PEFT التي تدعم الدمج (LoRA، LoHa، LoKr، IA³، OFT، إلخ)، حيث تشترك جميعها في نفس المنطق في مسار الأمامي.
Update on GitHubهذا السلوك محيّر ومن المقرر تغييره في إصدار PEFT v1.0.