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

نماذج مخصصة

بعض تقنيات الضبط الدقيق، مثل ضبط الموجه (prompt tuning)، مخصصة لنماذج اللغة. وهذا يعني أنه في 🤗 PEFT يُفترض استخدام نموذج من 🤗 Transformers. ومع ذلك، لا تقتصر تقنيات الضبط الدقيق الأخرى - مثل LoRA - على أنواع محددة من النماذج.

في هذا الدليل، سنرى كيف يمكن تطبيق LoRA على محدد متعدد الطبقات، أو نموذج رؤية حاسوبية من مكتبة timm، أو بنية جديدة من 🤗 Transformers.

شبكة عصبية متعددة الطبقات

لنفترض أننا نريد ضبط نموذج محسّن (fine-tune) لشبكة عصبية متعددة الطبقات (multilayer perceptron) باستخدام LoRA. فيما يلي التعريف:

from torch import nn


class MLP(nn.Module):
    def __init__(self, num_units_hidden=2000):
        super().__init__()
        self.seq = nn.Sequential(
            nn.Linear(20, num_units_hidden),
            nn.ReLU(),
            nn.Linear(num_units_hidden, num_units_hidden),
            nn.ReLU(),
            nn.Linear(num_units_hidden, 2),
            nn.LogSoftmax(dim=-1),
        )

    def forward(self, X):
        return self.seq(X)

هذا مُدرك متعدد الطبقات بسيط يتكوّن من طبقة إدخال وطبقة خفية وطبقة إخراج.

في هذا المثال التوضيحي، اخترنا عددًا كبيرًا بشكل مفرط من الوحدات المخفية لإبراز المكاسب في الكفاءة الناتجة عن PEFT، لكن هذه المكاسب تتوافق مع أمثلة أكثر واقعية.

توجد بعض الطبقات الخطية في هذا النموذج يمكن ضبطها باستخدام LoRA. عند العمل مع نماذج 🤗 Transformers الشائعة، يعرف PEFT الطبقات التي يجب تطبيق LoRA عليها، لكن في هذه الحالة، يتعين علينا كمستخدمين اختيار الطبقات. لتحديد أسماء الطبقات المراد ضبطها:

print([(n, type(m)) for n, m in MLP().named_modules()])

يجب أن يطبع هذا:

[('', __main__.MLP),
 ('seq', torch.nn.modules.container.Sequential),
 ('seq.0', torch.nn.modules.linear.Linear),
 ('seq.1', torch.nn.modules.activation.ReLU),
 ('seq.2', torch.nn.modules.linear.Linear),
 ('seq.3', torch.nn.modules.activation.ReLU),
 ('seq.4', torch.nn.modules.linear.Linear),
 ('seq.5', torch.nn.modules.activation.LogSoftmax)]

لنفترض أننا نريد تطبيق LoRA على طبقة الإدخال وطبقة الخفاء، وهما 'seq.0' و'seq.2'. علاوة على ذلك، لنفترض أننا نريد تحديث طبقة الإخراج دون استخدام LoRA، وهي 'seq.4'. الإعداد المقابل لذلك هو:

from peft import LoraConfig

config = LoraConfig(
    target_modules=["seq.0", "seq.2"],
    modules_to_save=["seq.4"],
)

وبذلك، يمكننا إنشاء نموذج PEFT الخاص بنا والتحقق من نسبة المعاملات المُدرَّبة:

from peft import get_peft_model

model = MLP()
peft_model = get_peft_model(model, config)
peft_model.print_healthcheck()

أخيرًا، يمكننا استخدام أي إطار عمل للتدريب نرغب فيه، أو كتابة حلقة ملاءمة خاصة بنا، لتدريب peft_model.

لمثال كامل، راجع هذا الدفتر.

نماذج timm

تحتوي مكتبة timm على عدد كبير من نماذج الرؤية الحاسوبية المُسبقة التدريب. ويمكن أيضًا ضبط هذه النماذج بدقة باستخدام PEFT. دعونا نطلع على كيفية عمل ذلك عمليًا.

للبداية، تأكد من تثبيت timm في بيئة Python:

python -m pip install -U timm

في الخطوة التالية، نحمّل نموذج timm لمهمة تصنيف الصور:

import timm

num_classes = ...
model_id = "timm/poolformer_m36.sail_in1k"
model = timm.create_model(model_id, pretrained=True, num_classes=num_classes)

مرة أخرى، نحتاج إلى اتخاذ قرار بشأن الطبقات التي سنطبق عليها LoRA. بما أن LoRA يدعم طبقات الالتفاف ثنائية الأبعاد (2D conv layers)، وبما أن هذه الطبقات تُعدّ عنصراً أساسياً في هذا النموذج، ينبغي علينا تطبيق LoRA على طبقات الالتفاف ثنائية الأبعاد. لتحديد أسماء هذه الطبقات، دعونا نطلع على جميع أسماء الطبقات:

print([(n, type(m)) for n, m in model.named_modules()])

ستُطبع هذه الجملة قائمة طويلة جداً، وسنعرض هنا أول بضع عناصر فقط:

[('', timm.models.metaformer.MetaFormer),
 ('stem', timm.models.metaformer.Stem),
 ('stem.conv', torch.nn.modules.conv.Conv2d),
 ('stem.norm', torch.nn.modules.linear.Identity),
 ('stages', torch.nn.modules.container.Sequential),
 ('stages.0', timm.models.metaformer.MetaFormerStage),
 ('stages.0.downsample', torch.nn.modules.linear.Identity),
 ('stages.0.blocks', torch.nn.modules.container.Sequential),
 ('stages.0.blocks.0', timm.models.metaformer.MetaFormerBlock),
 ('stages.0.blocks.0.norm1', timm.layers.norm.GroupNorm1),
 ('stages.0.blocks.0.token_mixer', timm.models.metaformer.Pooling),
 ('stages.0.blocks.0.token_mixer.pool', torch.nn.modules.pooling.AvgPool2d),
 ('stages.0.blocks.0.drop_path1', torch.nn.modules.linear.Identity),
 ('stages.0.blocks.0.layer_scale1', timm.models.metaformer.Scale),
 ('stages.0.blocks.0.res_scale1', torch.nn.modules.linear.Identity),
 ('stages.0.blocks.0.norm2', timm.layers.norm.GroupNorm1),
 ('stages.0.blocks.0.mlp', timm.layers.mlp.Mlp),
 ('stages.0.blocks.0.mlp.fc1', torch.nn.modules.conv.Conv2d),
 ('stages.0.blocks.0.mlp.act', torch.nn.modules.activation.GELU),
 ('stages.0.blocks.0.mlp.drop1', torch.nn.modules.dropout.Dropout),
 ('stages.0.blocks.0.mlp.norm', torch.nn.modules.linear.Identity),
 ('stages.0.blocks.0.mlp.fc2', torch.nn.modules.conv.Conv2d),
 ('stages.0.blocks.0.mlp.drop2', torch.nn.modules.dropout.Dropout),
 ('stages.0.blocks.0.drop_path2', torch.nn.modules.linear.Identity),
 ('stages.0.blocks.0.layer_scale2', timm.models.metaformer.Scale),
 ('stages.0.blocks.0.res_scale2', torch.nn.modules.linear.Identity),
 ('stages.0.blocks.1', timm.models.metaformer.MetaFormerBlock),
 ('stages.0.blocks.1.norm1', timm.layers.norm.GroupNorm1),
 ('stages.0.blocks.1.token_mixer', timm.models.metaformer.Pooling),
 ('stages.0.blocks.1.token_mixer.pool', torch.nn.modules.pooling.AvgPool2d),
 ...
 ('head.global_pool.flatten', torch.nn.modules.linear.Identity),
 ('head.norm', timm.layers.norm.LayerNorm2d),
 ('head.flatten', torch.nn.modules.flatten.Flatten),
 ('head.drop', torch.nn.modules.linear.Identity),
 ('head.fc', torch.nn.modules.linear.Linear)]
 ]

عند الفحص الدقيق، نلاحظ أن طبقات الالتفاف ثنائية الأبعاد تحمل أسماء مثل "stages.0.blocks.0.mlp.fc1" و"stages.0.blocks.0.mlp.fc2". كيف يمكننا مطابقة أسماء تلك الطبقات تحديداً؟ يمكنك كتابة تعبيرات منتظمة لمطابقة أسماء الطبقات. في حالتنا، يجب أن يؤدي التعبير المنتظم r".*\.mlp\.fc\d" المطلوب.

علاوة على ذلك، وكما في المثال الأول، يجب أن نتأكد من تحديث طبقة الإخراج، وفي هذه الحالة رأس التصنيف، أيضاً. بالنظر إلى نهاية القائمة المطبوعة أعلاه، يمكننا ملاحظة أن اسمها 'head.fc'. مع وضع ذلك في الاعتبار، إليك إعداد LoRA الخاص بنا:

config = LoraConfig(target_modules=r".*\.mlp\.fc\d", modules_to_save=["head.fc"])

ثم نحتاج فقط إلى إنشاء نموذج PEFT عن طريق تمرير النموذج الأساسي والإعداد إلى get_peft_model:

peft_model = get_peft_model(model, config)
peft_model.print_healthcheck()

يُظهر لنا هذا أنه لا نحتاج إلى تدريب أقل من 2% من جميع المعاملات، وهو ما يمثل مكسبًا هائلًا في الكفاءة.

لمثال كامل، راجع هذا الدفتر.

معماريات Transformers الجديدة

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

كخطوة أولى، يُعدّ من الجيد الاطلاع على النماذج الموجودة لاستلهام الأفكار. يمكنك العثور عليها داخل constants.py في مستودع PEFT. وغالباً ما ستجد بنية معمارية مشابهة تستخدم نفس الأسماء. على سبيل المثال، إذا كانت البنية المعمارية للنموذج الجديد عبارة عن نسخة معدّلة من نموذج “mistral” وتريد تطبيق LoRA، يمكنك ملاحظة أن الإدخال الخاص بـ “mistral” في TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING يحتوي على ["q_proj", "v_proj"]. وهذا يخبرك أنه بالنسبة لنماذج “mistral”, يجب أن تكون target_modules الخاصة بـ LoRA هي ["q_proj", "v_proj"]:

from peft import LoraConfig, get_peft_model

my_mistral_model = ...
config = LoraConfig(
    target_modules=["q_proj", "v_proj"],
    ...,  # other LoRA arguments
)
peft_model = get_peft_model(my_mistral_model, config)

إذا لم يُجدِ ذلك نفعًا، فتأكد من الوحدات الموجودة في بنية النموذج الخاص بك باستخدام طريقة named_modules وحاول تحديد طبقات الانتباه، لا سيما طبقات المفتاح والاستعلام والقيمة. غالبًا ما تحمل هذه الطبقات أسماء مثل c_attn وquery وq_proj وغيرها. لا يتم تكييف طبقة المفتاح دائمًا، ومن المثالي أن تتحقق مما إذا كان تضمينها يؤدي إلى أداء أفضل.

علاوة على ذلك، تُعدّ الطبقات الخطية أهدافًا شائعة للتكييف (على سبيل المثال، في ورقة QLoRA، يقترح المؤلفون تكييفها أيضًا). وغالبًا ما تحتوي أسماء هذه الطبقات على السلاسل fc أو dense.

إذا كنت ترغب في إضافة نموذج جديد إلى PEFT، يرجى إنشاء إدخال في constants.py وفتح طلب سحب على المستودع. لا تنسَ تحديث README أيضاً.

التحقق من المعاملات والطبقات

يمكنك التحقق من أنك طبّقت طريقة PEFT بشكل صحيح على نموذجك بعدة طرق.

  • شغّل print_healthcheck() لفحص نسبة المعاملات القابلة للتدريب وحالة المُكيّف. إذا كانت نسبة المعاملات أقل أو أعلى من المتوقع، فافحص تمثيل النموذج repr عن طريق طباعة النموذج. يُظهر هذا أسماء جميع أنواع الطبقات في النموذج. تأكد من أن الطبقات المستهدفة المقصودة فقط هي التي تم استبدالها بطبقات المُكيّف. على سبيل المثال، إذا تم تطبيق LoRA على طبقات nn.Linear، فيجب أن ترى فقط طبقات lora.Linear قيد الاستخدام.
peft_model.print_healthcheck()
  • طريقة أخرى يمكنك من خلالها عرض الطبقات المُكيَّفة هي استخدام خاصية targeted_module_names لإدراج اسم كل وحدة تم تكييفها.
print(peft_model.targeted_module_names)

أنواع الوحدات غير المدعومة

لا تعمل طرق مثل LoRA إلا إذا كانت الوحدات المستهدفة مدعومة من قبل PEFT. على سبيل المثال، يمكن تطبيق LoRA على طبقات nn.Linear وnn.Conv2d، لكن لا يمكن تطبيقها، على سبيل المثال، على nn.LSTM. إذا وجدت فئة طبقة تريد تطبيق PEFT عليها غير مدعومة، يمكنك:

  • تعريف خرائط مخصصة لتوجيه وحدات مخصصة ديناميكيًا في LoRA
  • افتح قضية واطلب الميزة، حيث سيقوم المسؤولون بتنفيذها أو إرشادك حول كيفية تنفيذها بنفسك إذا كان الطلب على هذا النوع من الوحدات كافياً

دعم تجريبي للتوجيه الديناميكي للوحدات المخصصة في LoRA

هذه الميزة تجريبية وقابلة للتغيير، وذلك تبعًا لاستقبال المجتمع لها. سنقدم واجهة برمجة تطبيقات عامة ومستقرة إذا وُجد طلب كبير عليها.

يدعم PEFT واجهة برمجة تجريبية لأنواع وحدات مخصصة لـ LoRA. لنفترض أن لديك تطبيقًا لـ LoRA مخصصًا لشبكات LSTM. في العادة، لن تتمكن من إخبار PEFT باستخدامه، حتى لو كان من الناحية النظرية متوافقًا مع PEFT. ومع ذلك، يصبح هذا ممكنًا عبر التوجيه الديناميكي للطبقات المخصصة.

يبدو الواجهة البرمجية التجريبية حاليًا على النحو التالي:

class MyLoraLSTMLayer:
    ...

base_model = ...  # load the base model that uses LSTMs

# add the LSTM layer names to target_modules
config = LoraConfig(..., target_modules=["lstm"])
# define a mapping from base layer type to LoRA layer type
custom_module_mapping = {nn.LSTM: MyLoraLSTMLayer}
# register the new mapping
config._register_custom_module(custom_module_mapping)
# after registration, create the PEFT model
peft_model = get_peft_model(base_model, config)
# do training

عند استدعاء get_peft_model()، ستظهر لك تحذير لأن PEFT لا يتعرف على نوع الوحدة المستهدفة. في هذه الحالة، يمكنك تجاهل هذا التحذير.

عند توفير تعيين مخصص، يتحقق PEFT أولاً من طبقات النموذج الأساسي مقابل التعيين المخصص، ثم يوجّه إلى نوع طبقة LoRA المخصصة إذا وُجد تطابق. وإذا لم يوجد تطابق، يتحقق PEFT من أنواع طبقات LoRA المدمجة بحثاً عن تطابق.

لذلك، يمكن استخدام هذه الميزة أيضًا لتجاوز منطق التوجيه (dispatch) الحالي، على سبيل المثال إذا كنت ترغب في استخدام طبقة LoRA الخاصة بك لـ nn.Linear بدلاً من استخدام تلك التي يوفرها PEFT.

عند إنشاء وحدة LoRA المخصصة، يرجى اتباع نفس القواعد المطبقة على وحدات LoRA الموجودة. بعض القيود المهمة التي يجب مراعاتها:

  • يجب أن يرث الوحدة المخصصة من nn.Module وpeft.tuners.lora.layer.LoraLayer.
  • يجب أن تحتوي طريقة __init__ في الوحدة المخصصة على الحجج الموضعية base_layer وadapter_name. بعد ذلك، توجد حجة **kwargs إضافية يمكنك استخدامها أو تجاهلها بحرية.
  • يجب تخزين المعاملات القابلة للتعلم في nn.ModuleDict أو nn.ParameterDict، حيث يتوافق المفتاح مع اسم المحوّل (adapter) المحدد (تذكر أن النموذج يمكن أن يحتوي على أكثر من محوّل في وقت واحد).
  • يجب أن تبدأ أسماء هذه الخصائص المعاملات القابلة للتعلم بـ "lora_"، على سبيل المثال self.lora_new_param = ....
  • بعض الطرق اختيارية، على سبيل المثال، لا تحتاج إلى تنفيذ merge وunmerge إلا إذا كنت ترغب في دعم دمج الأوزان.

حاليًا، لا يتم الحفاظ على المعلومات المتعلقة بالوحدة المخصصة عند حفظ النموذج. عند تحميل النموذج، يجب عليك تسجيل الوحدات المخصصة مرة أخرى.

# saving works as always and includes the parameters of the custom modules
peft_model.save_pretrained(<model-path>)

# loading the model later:
base_model = ...
# load the LoRA config that you saved earlier
config = LoraConfig.from_pretrained(<model-path>)
# register the custom module again, the same way as the first time
custom_module_mapping = {nn.LSTM: MyLoraLSTMLayer}
config._register_custom_module(custom_module_mapping)
# pass the config instance to from_pretrained:
peft_model = PeftModel.from_pretrained(model, tmp_path / "lora-custom-module", config=config)

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

Update on GitHub