LoRA والضبط الموفّر
درس 3 من 6
3LoRA والضبط الموفّر
الصورة الكبيرةضبط كل أوزان نموذج بسبعة مليارات معامل يحتاج عشرات الغيغابايتات من الذاكرة ونسخة كاملة من النموذج لكل مهمة. LoRA يجمّد الأوزان الأصلية ويدرّب مصفوفتين صغيرتين بجانب كل طبقة، أقل من 1% من المعاملات، ويصل غالبًا لجودة قريبة جدًّا من الضبط الكامل.
ماذا تشاهد في الرسم: الإشارة تمرّ من عرض كامل d إلى عنق ضيّق بعرض الرتبة r ثم تعود. A هي الوصلات الأولى وB الثانية، ولأن العنق ضيّق فعدد المعاملات صغير: أقل من 0.4% من مصفوفة 4096 في 4096 عند الرتبة 8.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
النموذج الكبير مثل كتاب ضخم. بدل أن تعيد كتابة الكتاب كله لتغيّر أسلوبه، تلصق على صفحاته ملاحظات صغيرة شفافة تعدّل المعنى قليلًا. LoRA هذه الملاحظات: تدرّب جزءًا صغيرًا جدًا من الأرقام، فتوفّر الذاكرة والوقت والمال.
LoRA يجمّد الأوزان الأصلية ويضيف لكل مصفوفة مستهدفة حاصل ضرب مصفوفتين صغيرتين B·A برتبة r. المعاملات الثلاثة: الرتبة r، والمعامل alpha، والوحدات المستهدفة target_modules. وQLoRA يضيف تكميم النموذج الأساسي إلى 4 بت لتوفير الذاكرة.
قبل أن تبدأ
🧮الجبر الخطي
تعرف ضرب المصفوفات، وأن ضرب مصفوفة d×r في r×d يعطي مصفوفة d×d.
🧱كتلة المحوّل
تعرف إسقاطات الانتباه q وk وv وo والشبكة الأمامية داخل الكتلة.
الفكرة الجوهرية
LoRA يجمّد الأوزان الأصلية ويدرّب مصفوفتين صغيرتين بجانبها. في هذا الدرس ترى كم معاملًا يوفّر، ولماذا تكفي رتبة صغيرة، وكيف يبدأ من الصفر ويُدمج، ثم كيف تضبطه بمكتبة.
1جمّد W وتعلّم A وB
بدل تغيير W كاملة، نتعلّم تعديلًا ΔW = A × B. المصفوفة A طويلة ونحيفة، وB قصيرة وعريضة، وعرضهما المشترك هو الرتبة r، وغالبًا 8 أو 16.
انظر إلى الرسم: مصفوفة 4096 في 4096 فيها 16.8 مليون معامل، وبالرتبة 8 ندرّب 0.07 مليون فقط. العمود الثاني يكاد لا يُرى.
d = 4096 # one projection matrix in a 7B model is 4096 x 4096
full = d * d
for r in [4, 8, 16, 64]:
lora = d * r + r * d # A is d x r, B is r x d
print(f"rank {r:2}: {lora:>9,} trainable instead of {full:,} ({lora / full:.2%})")
مصفوفة واحدة 4096 في 4096 فيها 16.7 مليون معامل. بالرتبة 8 ندرّب 65 ألفًا فقط، أقل من 0.4%. وفي نموذج كامل يعني هذا عشرات الملايين بدل المليارات، فتكفي ذاكرة بطاقة واحدة متوسطة، ويكون الناتج ملفًّا صغيرًا ببضع عشرات الميغابايتات بدل نسخة كاملة من النموذج.
2لماذا تكفي رتبة صغيرة
النموذج المدرَّب مسبقًا يعرف الكثير، والضبط لا يعلّمه من الصفر. هو يميله في اتجاهات قليلة: هذا الشكل، وهذا الأسلوب، وهذه الفئات. والرتبة هي عدد تلك الاتجاهات.
انظر إلى الرسم: الخطأ ينزل بسرعة حتى الرتبة 6، ثم يكاد يقف عند 0.20. ما بعدها ضجيج فقط.
تعديل «حقيقي» مبنيّ من 6 اتجاهات زائد ضجيج خفيف. نقرّبه برتب مختلفة ونقيس الخطأ.
import numpy as np
rng = np.random.default_rng(0)
d = 256
true_rank = 6
# a "real" fine-tuning update: mostly a few directions, plus a little noise
update = rng.normal(size=(d, true_rank)) @ rng.normal(size=(true_rank, d)) / d
update += rng.normal(scale=0.002, size=(d, d))
u, s, vt = np.linalg.svd(update)
for r in [1, 2, 4, 6, 8, 16]:
approx = (u[:, :r] * s[:r]) @ vt[:r]
err = np.linalg.norm(update - approx) / np.linalg.norm(update)
print(f"rank {r:2}: relative error {err:.3f} parameters {2 * d * r:>6,} of {d * d:,}")
الخطأ ينخفض بسرعة حتى الرتبة 6، ثم يكاد يتوقّف عند 0.20 تقريبًا. هذا الباقي هو الضجيج، والرتبة الأعلى لا تتعلّم إلا إيّاه. في الضبط الحقيقي لا تعرف الرتبة «الحقيقية»، فتجرّب 8 و16 و32 على مجموعة التحقّق وتختار حيث يتوقّف التحسّن.
3البداية من الصفر والدمج
المخرج يصير x W + (α / r) x A B. نبدأ A صغيرة عشوائية وB أصفارًا، فيكون النموذج في أول خطوة هو الأصلي تمامًا.
انظر إلى الرسم: التدريب يغيّر A وB فقط، ثم ندمج W + (α / r) A B في مصفوفة واحدة للاستخدام.
import numpy as np
rng = np.random.default_rng(1)
d, r, alpha = 64, 8, 16
W = rng.normal(size=(d, d)) / np.sqrt(d) # frozen pretrained weight
A = rng.normal(size=(d, r)) * 0.01 # trainable, small random
B = np.zeros((r, d)) # trainable, zeros: the update starts at nothing
scale = alpha / r
x = rng.normal(size=(4, d))
def lora_forward(x):
return x @ W + scale * (x @ A) @ B
print("before training, LoRA output equals the base model:", bool(np.allclose(lora_forward(x), x @ W)))
B = rng.normal(size=(r, d)) * 0.05 # pretend training changed B
merged = W + scale * A @ B # fold the adapter into the weight
print("after merging, one matrix gives the same output:", bool(np.allclose(lora_forward(x), x @ merged)))
print("extra cost at inference after merging: 0 extra matrices")
B تبدأ أصفارًا، فالنموذج في أول خطوة تدريب هو النموذج الأصلي بالضبط، ولا يبدأ التدريب بتخريب ما يعرفه. وبعد التدريب، دمج A B في W يعطي مصفوفة واحدة بنفس المخرجات، فلا يكلّف LoRA شيئًا وقت الاستخدام.
4الرتبة والمقياس والطبقات
الرتبة r عرض المصفوفتين، وα يضبط قوة التعديل. البداية الشائعة r = 16 وα = 32. وتضع المحوّلات على إسقاطات الانتباه q وk وv وo على الأقل، وإضافة الشبكة الأمامية تحسّن غالبًا مقابل معاملات أكثر.
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct", torch_dtype="bfloat16")
config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
task_type="CAUSAL_LM",
)
model = get_peft_model(model, config)
model.print_trainable_parameters() # about 1% of the model is trainable
# after training:
# merged = model.merge_and_unload(); merged.save_pretrained("tuned-merged")
target_modules تحدّد الطبقات، وأسماؤها تختلف بين عائلات النماذج، فاطبع model وانظر إليها. وmerge_and_unload يدمج المحوّل في النموذج لنشره كنموذج عادي.
كيف يعمل، خطوة بخطوة: كيف يدرّب LoRA طبقة واحدة
تُجمَّد W الأصلية فلا تدرّجات لها ولا حالة محسّن.
تُضاف بجانبها A عشوائية صغيرة وB أصفار، فيبدأ النموذج مطابقًا للأصل تمامًا.
المخرج يصير x W + (α / r) x A B، والتدريب يحدّث A وB وحدهما.
بعد التدريب تدمج W + (α / r) A B في مصفوفة واحدة، أو تبقي المحوّل منفصلًا لتبدّله.
أعمق قليلًا
QLoRA يذهب خطوة أبعد: يحمّل النموذج الأساسي المجمّد بدقة 4 بتات بدل 16، فتنخفض ذاكرته إلى الربع، ويدرّب محوّلات LoRA بدقة عادية فوقه. هكذا صار ضبط نموذج بسبعة مليارات معامل ممكنًا على بطاقة واحدة بذاكرة 16 غيغابايت، مع خسارة صغيرة في الجودة تقيسها.
مثال محلول
الفكرة: بدل تعديل مصفوفة الأوزان W كاملة، نتعلّم تعديلًا ΔW = A × B حيث A طويلة ونحيفة وB قصيرة وعريضة. عرضهما المشترك هو «الرتبة» r، وهو صغير: 8 أو 16 عادة.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| رتبة أعلى تعني جودة أعلى. | في المثال توقّف الخطأ عند نحو 0.20 بعد الرتبة 6، والرتب الأعلى تعلّمت الضجيج فقط. ابدأ بـ8 أو 16. |
| LoRA يبطئ النموذج وقت الاستخدام. | بعد الدمج تصير W + (α / r) A B مصفوفة بنفس شكل W، فالحساب هو نفسه تمامًا. |
| LoRA يعلّم النموذج من الصفر بمعاملات أقل. | النموذج يعرف الكثير مسبقًا، والضبط يميله في اتجاهات قليلة: شكل، أو أسلوب، أو فئات. لهذا تكفي رتبة صغيرة. |
جرّب بنفسك
مصفوفة 4096 × 4096 والرتبة 16. كم معاملًا تدرّب LoRA فيها؟
A بحجم 4096 × 16 وB بحجم 16 × 4096: 65,536 + 65,536 = 131,072، أي 0.78% من المصفوفة.
بعد دمج محوّل LoRA في الأوزان، كم تزيد تكلفة الاستخدام؟
W + (α / r) A B مصفوفة بنفس أبعاد W. الحساب نفسه بالضبط كالنموذج الأصلي.
بطاقات مراجعة سريعة
W وتعلّم ΔW = A × B منخفض الرتبة. المخرج x W + (α / r) x A B.r عرض المصفوفتين، وα يضبط قوة التعديل. البداية الشائعة r = 16 وα = 32.q وk وv وo)، وإضافة الشبكة الأمامية يحسّن غالبًا مقابل معاملات أكثر.ΔW مع W بعد التدريب في مصفوفة واحدة، فلا تكلفة إضافية في الاستخدام. أو إبقاؤهما منفصلين لتبديل محوّلات عدّة على نموذج واحد.تحقق من فهمك
لماذا تبدأ B بأصفار لا بأرقام عشوائية مثل A؟
حتى يكون A × B = 0 في البداية، فيبدأ النموذج المضبوط مطابقًا للأصلي تمامًا. لو كانت الاثنتان عشوائيتين، لأضاف التعديل ضجيجًا لكل طبقة قبل أي تعلّم، وخرّب أداء النموذج في الخطوات الأولى. وA عشوائية حتى لا تكون التدرّجات صفرًا للاثنتين.
ما الذي يضيفه QLoRA فوق LoRA، وما ثمنه؟
يحمّل النموذج الأساسي المجمّد بدقة 4 بتات بدل 16 فتنخفض ذاكرته إلى الربع، ويدرّب المحوّلات بدقة عادية فوقه. الثمن خسارة صغيرة في الجودة تقيسها.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.