هل تختار ضبط النموذج على بياناتك أم تعتمد على براعة الـ prompting؟ مقارنة تقنية عميقة تكشف ما يحدث خلف الكواليس في الذاكرة والمعالج، مع أمثلة واقعية من مشاريع الإنتاج.
الساعة الثالثة صباحاً، السيرفر بيعلق، و الـ GPU utilization عند ٩٨٪. أنت جالس تحاول تشغيل نموذجك بعد جلسة fine-tuning استمرت ١٢ ساعة، بينما زميل لك في الفريق ينفذ نفس المهمة باستخدام few-shot prompting على نموذج جاهز في دقائق معدودة. السؤال الذي يطن في رأسك: هل الاستثمار في ضبط النموذج يستحق العناء حقاً، أم أن الـ prompting الذكي هو المستقبل؟ الحقيقة هي أن الاختيار ليس بهذه البساطة، وكل خيار يأتي بتكاليفه الخفية التي لا تظهر إلا في الإنتاج.
في هذا المقال، سنفكك المعركة بين fine-tuning و few-shot prompting من منظور مهندس برمجيات لا يهتم بالنظريات بقدر ما يهتم بما يحدث في الذاكرة والمعالج. سنرى كيف يؤثر كل نهج على الـ latency، الـ throughput، واستهلاك الموارد، مع أمثلة واقعية من مشاريع حقيقية مثل توصية المنتجات في سوق إلكتروني كبير. لن نتوقف عند تعريفات سطحية، بل سنغوص في تفاصيل مثل كيفية تأثير حجم الـ batch على استقرار التدريب، وكيف يمكن للـ tokenization السيئ أن يدمر أداء الـ prompting حتى مع أمثلة مثالية.
عندما نتحدث عن fine-tuning، فإننا نتحدث عن عملية تتطلب موارد هائلة. النموذج الذي يحتوي على مليارات الـ parameters يحتاج إلى ذاكرة كافية لتحميل الـ weights بالإضافة إلى الـ gradients أثناء التدريب. على سبيل المثال، نموذج مثل Llama-2-7B يحتاج إلى حوالي ١٤ جيجابايت من VRAM لمجرد تحميله في وضع التقييم، بينما يتطلب التدريب الفعلي ضعف هذه الذاكرة على الأقل بسبب الـ gradients و الـ optimizer states. هذا يعني أنك ستحتاج إلى بطاقة مثل A100 أو H100 لتشغيل جلسة تدريب كاملة دون الوقوع في مشاكل الـ out-of-memory.
أما في حالة few-shot prompting، فإنك تعتمد على نموذج جاهز يتم تشغيله في وضع الاستدلال فقط. هنا، لا تحتاج إلى ذاكرة إضافية للـ gradients، ولكنك ستواجه تحديات أخرى مثل الـ context window limitations. معظم النماذج الحديثة تدعم نوافذ سياق تصل إلى ٣٢ ألف token، ولكن هذا يأتي بتكلفة: كلما زاد عدد الـ tokens في الـ prompt، زاد الوقت الذي يستغرقه النموذج في المعالجة. في أحد مشاريعي السابقة مع منصة تعليمية، وجدنا أن زيادة عدد الأمثلة في الـ few-shot prompt من ٣ إلى ٥ أدى إلى زيادة الـ latency من ٤٠٠ مللي ثانية إلى ١.٢ ثانية، وهو فارق كبير عندما تتعامل مع آلاف الطلبات في الثانية.
# مثال على جلسة fine-tuning مع LoRA لتوفير الذاكرة
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model
import torch
model_name = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16, device_map="auto")
# تطبيق LoRA لتقليل عدد الـ parameters القابلة للتعديل
lora_c LoraConfig(
r=8,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# إعداد بيانات التدريب (مثال بسيط)
train_data = [
{"text": "سؤال: ما هي عاصمة فرنسا؟\nإجابة: باريس"},
{"text": "سؤال: ما هي عاصمة ألمانيا؟\nإجابة: برلين"}
]
training_args = TrainingArguments(
output_dir="./results",
per_device_train_batch_size=4,
num_train_epochs=3,
save_steps=10_000,
logging_steps=100,
optim="paged_adamw_8bit"
)
# هنا يحدث السحر: التدريب الفعلي يستهلك الذاكرة والمعالج
# لاحظ استخدام mixed precision و LoRA لتقليل الحمل
from trl import SFTTrainer
trainer = SFTTrainer(
model=model,
train_dataset=train_data,
args=training_args,
peft_config=lora_config,
)
trainer.train()في عالم الـ few-shot prompting، كل شيء يعتمد على جودة الأمثلة التي تقدمها للنموذج. الفكرة بسيطة: بدلاً من تعديل الـ weights الداخلية للنموذج، تقدم له أمثلة توضيحية في الـ prompt نفسه، مما يسمح له بتعلم المهمة من السياق. لكن هذه البساطة تخفي تحديات كبيرة. أولاً، هناك حد لحجم الـ context window، وهو ما يعني أنك لا تستطيع تقديم عدد لا نهائي من الأمثلة. ثانياً، جودة الأمثلة تلعب دوراً حاسماً - مثال واحد سيئ يمكن أن يفسد الأداء بالكامل.
في أحد المشاريع التي عملت عليها مع شركة للتجارة الإلكترونية، كنا نحاول تحسين نظام توصية المنتجات باستخدام few-shot prompting. وجدنا أن تقديم ثلاثة أمثلة فقط في الـ prompt كان كافياً لتحقيق دقة ٨٢٪، ولكن عندما أضفنا مثالاً رابعاً غير متجانس، انخفض الأداء إلى ٦٥٪. السبب؟ النموذج بدأ في الخلط بين الأنماط المختلفة في الأمثلة. الحل كان في استخدام تقنية تسمى "dynamic few-shot" حيث نختار الأمثلة الأكثر صلة بالمستخدم الحالي من قاعدة بيانات كبيرة، بدلاً من استخدام أمثلة ثابتة.
# مثال على few-shot prompting مع اختيار ديناميكي للأمثلة
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
# نموذج لتوليد الـ embeddings
embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
# قاعدة بيانات للأمثلة مع embeddings مسبقة الحساب
examples_db = [
{"question": "ما هي عاصمة فرنسا؟", "answer": "باريس", "embedding": None},
{"question": "ما هي عاصمة ألمانيا؟", "answer": "برلين", "embedding": None},
{"question": "ما هي عملة اليابان؟", "answer": "الين", "embedding": None}
]
# حساب embeddings مسبقاً
for example in examples_db:
example["embedding"] = embedding_model.encode(example["question"])
def get_relevant_examples(user_question, k=3):
# حساب embedding للسؤال الحالي
questi embedding_model.encode(user_question)
# حساب التشابه مع جميع الأمثلة
similarities = []
for example in examples_db:
sim = cosine_similarity(
[question_embedding],
[example["embedding"]]
)[0][0]
similarities.append(sim)
# اختيار الأمثلة الأكثر تشابهاً
top_indices = np.argsort(similarities)[-k:]
return [examples_db[i] for i in top_indices]
def generate_prompt(user_question):
# الحصول على الأمثلة الأكثر صلة
relevant_examples = get_relevant_examples(user_question)
# بناء الـ prompt
prompt = "أنت مساعد ذكي يجيب على الأسئلة بدقة:\n"
for example in relevant_examples:
prompt += f"سؤال: {example['question']}\nإجابة: {example['answer']}\n\n"
prompt += f"سؤال: {user_question}\nإجابة:"
return prompt
# استخدام النموذج مع الـ prompt المولد
user_question = "ما هي عاصمة إيطاليا؟"
prompt = generate_prompt(user_question)
print(prompt)عندما تتخذ قراراً بين fine-tuning و few-shot prompting، فإنك تتخذ قراراً بشأن ميزانية كاملة، وليس مجرد اختيار تقني. دعنا نكسر التكاليف الحقيقية لكل نهج. في حالة fine-tuning، التكاليف تبدأ من شراء أو استئجار الـ hardware المناسب. بطاقة A100 بسعة ٤٠ جيجابايت تكلف حوالي ١٠ آلاف دولار، وإذا كنت تريد تدريب نموذج كبير، فقد تحتاج إلى عدة بطاقات. أضف إلى ذلك تكلفة الكهرباء - تدريب نموذج متوسط الحجم يمكن أن يستهلك ما يعادل استهلاك منزل صغير لمدة أسبوع.
أما في حالة few-shot prompting، فإن التكاليف تبدو أقل في البداية، لكنها تتراكم بسرعة مع زيادة عدد الطلبات. معظم مقدمي خدمات النماذج مثل OpenAI أو Anthropic يفرضون رسوماً بناءً على عدد الـ tokens المستخدمة. على سبيل المثال، إذا كنت تعالج ١٠ آلاف طلب يومياً، وكل طلب يستخدم ٢٠٠٠ token في المتوسط، فإنك ستدفع حوالي ٤٠٠ دولار شهرياً فقط على الـ tokens، دون احتساب التكاليف الإضافية مثل الـ API latency والتخزين المؤقت للنتائج. في أحد المشاريع مع شركة ناشئة، وجدنا أن تكلفة استخدام API للنموذج كانت أعلى من تكلفة تشغيل نموذج fine-tuned على سيرفراتنا الخاصة بعد ستة أشهر فقط من التشغيل.
في مرحلة التطوير، كل شيء يبدو جميلاً في الـ Jupyter notebook. دقة النموذج جيدة، الـ latency مقبول، والـ throughput يبدو واعداً. لكن عندما تنتقل إلى الإنتاج، تبدأ المشاكل الحقيقية في الظهور. مع fine-tuning، قد تواجه تحديات مثل الـ model drift، حيث يتغير توزيع البيانات بمرور الوقت، مما يتطلب إعادة تدريب دورية للنموذج. في أحد المشاريع مع منصة للتوظيف، وجدنا أن نموذجنا لتصنيف السير الذاتية بدأ يفقد دقته بعد ثلاثة أشهر بسبب تغير اتجاهات سوق العمل، مما اضطرنا إلى إعادة تدريبه كل شهرين للحفاظ على الأداء.
أما مع few-shot prompting، فإن التحدي الأكبر هو الـ latency المتغير. النماذج الكبيرة مثل GPT-4 يمكن أن تستغرق عدة ثوانٍ للرد على طلب واحد، خاصة إذا كان الـ prompt طويلاً. في بيئة إنتاجية، هذا يعني أنك ستحتاج إلى تنفيذ استراتيجيات مثل الـ caching، الـ request batching، و الـ fallback mechanisms. في مشروع مع منصة للدعم الفني، وجدنا أن استخدام few-shot prompting مع نموذج خارجي أدى إلى زيادة متوسط وقت الاستجابة من ٣٠٠ مللي ثانية إلى ١.٨ ثانية، مما أثر سلباً على رضا العملاء. الحل كان في استخدام نموذج أصغر fine-tuned داخلياً للمهام الشائعة، والاحتفاظ بالنموذج الكبير فقط للحالات المعقدة.
# مثال على تنفيذ caching و fallback في نظام يعتمد على few-shot prompting
import time
import hashlib
from functools import lru_cache
import requests
# تخزين مؤقت للنتائج
@lru_cache(maxsize=1000)
def cached_prompt(prompt_hash):
# في الواقع، هذا سيتصل بـ API خارجي
# هنا نستخدم mock للحصول على النتيجة
time.sleep(1) # محاكاة تأخير الشبكة
return f"إجابة على: {prompt_hash}"
def generate_prompt_hash(prompt):
# إنشاء hash فريد للـ prompt لتخزينه مؤقتاً
return hashlib.md5(prompt.encode()).hexdigest()
def get_answer(prompt, max_retries=2):
prompt_hash = generate_prompt_hash(prompt)
# محاولة الحصول على النتيجة من التخزين المؤقت
if prompt_hash in [k for k, _ in cached_prompt.cache_info().hits]:
return cached_prompt(prompt_hash)
# محاولة الاتصال بالنموذج الخارجي
for attempt in range(max_retries):
try:
# في الواقع، هذا سيتصل بـ API مثل OpenAI
answer = cached_prompt(prompt_hash)
return answer
except Exception as e:
if attempt == max_retries - 1:
# fallback إلى نموذج داخلي أصغر
return fallback_model(prompt)
time.sleep(1 * (attempt + 1))
def fallback_model(prompt):
# نموذج داخلي أصغر fine-tuned للمهام الشائعة
# هذا مجرد مثال بسيط
comm {
"ما هي عاصمة فرنسا؟": "باريس",
"ما هي عاصمة ألمانيا؟": "برلين"
}
return common_answers.get(prompt, "عذراً، لا أعرف الإجابة على هذا السؤال.")
# استخدام النظام
prompt = "ما هي عاصمة فرنسا؟"
answer = get_answer(prompt)
print(answer)بعد كل هذه التفاصيل، كيف تتخذ القرار بين fine-tuning و few-shot prompting؟ الحقيقة هي أنه لا يوجد جواب واحد يناسب الجميع، ولكن هناك خارطة طريق يمكن أن تساعدك في اتخاذ القرار الصحيح لمشروعك. أولاً، اسأل نفسك عن حجم البيانات المتاحة لديك. إذا كان لديك أقل من ١٠٠٠ مثال، فإن few-shot prompting هو الخيار الأفضل. أما إذا كان لديك عشرات الآلاف من الأمثلة، فإن fine-tuning يصبح خياراً مغرياً.
ثانياً، فكر في متطلبات الأداء. إذا كنت بحاجة إلى زمن استجابة أقل من ٢٠٠ مللي ثانية، فإن fine-tuning على hardware محلي قد يكون الخيار الوحيد. أما إذا كان بإمكانك تحمل زمن استجابة يصل إلى ثانية أو أكثر، فإن few-shot prompting قد يكون كافياً. ثالثاً، ضع في اعتبارك ميزانيتك. إذا كنت شركة ناشئة ذات موارد محدودة، فقد يكون من الأفضل البدء بـ few-shot prompting ثم الانتقال إلى fine-tuning عندما يكبر مشروعك.
إذا كان علي تلخيص كل ما تعلمته من تجارب عديدة في سطرين، فسيكونان: ابدأ دائماً بـ few-shot prompting لاختبار الفكرة وتحديد المتطلبات الحقيقية، ثم انتقل إلى fine-tuning فقط عندما تثبت البيانات أن الاستثمار يستحق العناء. ولا تنسَ أبداً أن مراقبة الأداء في الإنتاج هي التي ستحدد نجاحك، وليس الأرقام الجميلة في مرحلة التطوير.