هذا المقال يتطلب مستوى متوسط. الافتراض إنك تعرف يعني إيه fine-tuning، وشغّلت نموذجًا من Hugging Face قبل كده، وعارف أساسيات المصفوفات.
LoRA وQLoRA: الضبط الدقيق الفعّال للنماذج اللغوية الكبيرة
تقدر تعمل fine-tuning لنموذج بـ7 مليار باراميتر على كارت شاشة واحد، وتدرّب أقل من 1% من أوزانه، بجودة قريبة جدًا من التدريب الكامل. ده اللي بيعمله LoRA. المقال يوريك بالظبط إزاي، وإمتى الطريقة دي تنفع وإمتى لأ.
المشكلة باختصار
الضبط الدقيق الكامل (full fine-tuning) بيحدّث كل وزن في النموذج. المشكلة مش في الأوزان نفسها، المشكلة في الذاكرة اللي حواليها. القاعدة التقريبية: التدريب الكامل بمُحسِّن Adam بياخد حوالي 16 بايت لكل باراميتر (الوزن + التدرّج + حالتَي Adam بدقة fp32).
يعني نموذج 7B محتاج قرابة 112 جيجابايت ذاكرة GPU علشان تدرّبه بالكامل. ده مش كارت واحد، دي عدة كروت A100. ولو عايز تدرّب 10 نسخ لـ10 مهام، هتخزّن 10 نسخ كاملة من النموذج. مكلف وبطيء وبيتكرّر بلا داعي.
الفكرة بمثال بسيط الأول
تخيّل عندك كتاب مرجعي 500 صفحة، وعايز تظبطه لقارئ معيّن. الطريقة المكلفة إنك تعيد طباعة الكتاب كله من أول وجديد عشان تغيّر شوية جمل. الطريقة الذكية إنك تسيب الكتاب زي ما هو، وتحط ورقات ملاحظات صغيرة (sticky notes) جنب الصفحات المهمة.
القارئ بيقرأ الكتاب الأصلي + الملاحظات مع بعض، فيوصله المعنى المعدّل. الكتاب الأصلي هو النموذج المجمّد اللي ما بنلمسوش. الملاحظات الصغيرة هي محوّل LoRA اللي بندرّبه. رخيص، سريع، وتقدر تشيل الملاحظات وتحط غيرها لمهمة تانية في ثواني.
المفهوم علميًا: تفكيك منخفض الرتبة
في كل طبقة، النموذج عنده مصفوفة أوزان W. LoRA بيجمّد W بالكامل، وبيضيف تحديثًا صغيرًا جنبها اسمه ΔW. الحيلة إن ΔW مش مصفوفة كاملة، لكنها حاصل ضرب مصفوفتين رفيعتين: ΔW = B · A، حيث الرتبة r صغيرة جدًا (زي 8 أو 16).
فبدل ما تدرّب مصفوفة d×d، بتدرّب B بحجم d×r وA بحجم r×d. عند الاستدلال، المخرج بيبقى h = Wx + (B · A)x. وبنهيّئ B بأصفار في البداية، فالنموذج يبدأ التدريب من نفس سلوك النموذج الأصلي بالظبط.
الأرقام بتوضّح المكسب. لو d = 4096 وr = 8: المصفوفة الكاملة فيها 16.7 مليون باراميتر، بينما B + A فيهم 65,536 فقط. ده 256 ضعف أقل في الباراميترات القابلة للتدريب، في كل طبقة. الافتراض هنا إن التعديل المطلوب على النموذج "منخفض الرتبة" فعلًا، وده صحيح لأغلب مهام التكييف الأسلوبي والمجالي.
QLoRA: نضيف التكميم فوق LoRA
LoRA بيقلّل الباراميترات القابلة للتدريب، لكن النموذج الأساسي المجمّد نفسه لسه بياخد ذاكرة (14 جيجا لـ7B بدقة fp16). QLoRA بيحل ده: بيحمّل النموذج الأساسي مكمّمًا لـ4 بت بصيغة NF4، ويدرّب محوّل LoRA فوقه. النتيجة من الورقة الأصلية: fine-tuning لنموذج 65B على كارت واحد بـ48 جيجا، بجودة تعادل التدريب الكامل بدقة 16 بت.
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
# QLoRA: حمّل النموذج الأساسي بدقة 4-bit (NF4)
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype="bfloat16",
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf", quantization_config=bnb, device_map="auto"
)
# محوّل LoRA: ندرّب مصفوفتي الإسقاط في طبقات الانتباه فقط
config = LoraConfig(
r=8, lora_alpha=16, lora_dropout=0.05,
target_modules=["q_proj", "v_proj"], task_type="CAUSAL_LM",
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.062
لاحظ السطر الأخير: 0.062% فقط من الأوزان بتتدرّب. وبعد التدريب، بتدمج المحوّل في الأوزان الأصلية بسطر واحد علشان تشيل أي زمن إضافي وقت الاستدلال:
merged = model.merge_and_unload() # يدمج B·A داخل W، فالاستدلال بسرعة النموذج الأصلي
merged.save_pretrained("llama2-7b-my-task")
الأرقام اللي بتفرق
- ورقة LoRA الأصلية على GPT-3 بـ175B: تقليل الباراميترات القابلة للتدريب حتى 10,000 ضعف، وذاكرة الـGPU المطلوبة 3 أضعاف أقل، بدون زيادة في زمن الاستدلال بعد الدمج.
- QLoRA: نموذج Guanaco المبني عليها وصل لـ99.3% من أداء ChatGPT على اختبار Vicuna، بتدريب 24 ساعة على كارت واحد.
- حجم المحوّل الناتج عادةً عشرات الميجابايت، مقابل نموذج كامل بحجم عدة جيجابايت لكل مهمة.
الـ trade-offs وما يجب الانتباه له
كل مكسب له ثمن. خلّي دي قدامك:
- الرتبة r: كل ما زوّدتها، زادت سعة المحوّل ودقّته المحتملة، لكن زادت الذاكرة وزمن التدريب. تبدأ بـ8 أو 16 لأغلب المهام، وترفعها لو المهمة بعيدة عن توزيع النموذج الأصلي.
- QLoRA: بتكسب توفيرًا هائلًا في الذاكرة، بتخسر حوالي 30% في سرعة التدريب بسبب فك التكميم عند كل تمريرة، مع فرق دقة بسيط في الغالب.
- الدمج مقابل المرونة: دمج المحوّل بيلغي الزمن الإضافي، لكنه بيلغي قدرتك على تبديل المهام بسرعة. لو محتاج تبدّل بين 10 مهام على نفس النموذج، سيب المحوّلات منفصلة.
متى لا تستخدم هذه الطريقة
LoRA مش الحل لكل حاجة. لو محتاج تحقن معرفة أو حقائق جديدة كبيرة في النموذج (زي مستندات شركتك)، الأنسب هو RAG مش fine-tuning أصلًا. ولو المهمة بعيدة جدًا عن قدرات النموذج الأساسي وعندك موارد كافية، التدريب الكامل ممكن يديك دقة أعلى. وفي المهام الحسّاسة اللي بيفرق فيها آخر 1% دقة، اقيس الفرق بينهم بنفسك قبل ما تقرّر.
الخطوة التالية
افتح دفتر Colab بكارت T4 مجاني، حمّل meta-llama/Llama-2-7b-hf بإعداد QLoRA اللي فوق، وشغّل print_trainable_parameters(). لو النسبة طلعت تحت 0.1% والنموذج اتحمّل في حدود 6 جيجا ذاكرة، يبقى الإعداد شغّال صح. ابعتلي نسبة الباراميترات اللي طلعتلك.
المصادر
- Hu et al., "LoRA: Low-Rank Adaptation of Large Language Models", 2021 — arXiv:2106.09685
- Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs", 2023 — arXiv:2305.14314
- توثيق مكتبة Hugging Face PEFT — huggingface.co/docs/peft
- مكتبة bitsandbytes للتكميم 4-bit — github.com/bitsandbytes-foundation/bitsandbytes