خلاصة القول
يستغرق بناء نموذج لغوي بسيط من الصفر أقل من 300 سطر من بايثون. تكشف العملية بالضبط كيف تعمل عملية ترميز النص (tokenization)، والانتباه (attention)، والاستدلال (inference)، مما يجعلك مستهلكًا أفضل بكثير لواجهات برمجة التطبيقات (API) عند دمج نماذج اللغة الكبيرة (LLMs) في تطبيقاتك.
مقدمة
يتعامل معظم المطورين مع النماذج اللغوية كصناديق سوداء. ترسل نصًا إليها، وتخرج رموزًا (tokens)، وبين هذا وذاك، يحدث السحر. يعمل هذا النموذج الذهني بشكل جيد حتى تحتاج إلى تصحيح خطأ في دمج واجهة برمجة تطبيقات معطلة، أو ضبط معلمات أخذ العينات، أو معرفة سبب استمرار نموذجك في هلوسة البيانات المهيكلة.
مشروع GuppyLM، الذي تصدر مؤخرًا الصفحة الرئيسية لموقع HackerNews بـ 842 نقطة، يجعل التفاصيل الداخلية مرئية. إنه محول (transformer) يحتوي على 8.7 مليون معلمة مكتوب من الصفر بلغة بايثون. يتدرب في أقل من ساعة على وحدة معالجة رسوميات استهلاكية (GPU). الرمز البرمجي يتناسب مع ملف واحد. الهدف ليس التنافس مع GPT-4؛ بل هو إزالة الغموض عن كيفية عمل نماذج اللغة الكبيرة (LLMs) بالفعل.
تتناول هذه المقالة خطوات بناء نموذج لغوي كبير (LLM) صغير، وفهم كل مكون، وكيفية أن هذا الفهم يحسن تعاملاتك مع واجهات برمجة تطبيقات الذكاء الاصطناعي (AI APIs) بشكل عملي.
💡 إذا كنت تختبر عمليات دمج واجهة برمجة تطبيقات الذكاء الاصطناعي (AI API)، فإن "سيناريوهات الاختبار" (Test Scenarios) في Apidog تتيح لك التحقق من استجابات البث، والتأكيد على بنية الرمز (token structure)، ومحاكاة عمليات الإكمال للحالات الهامشية دون استهلاك أرصدة الإنتاج. المزيد عن ذلك لاحقًا
ما الذي يجعل النموذج اللغوي "صغيرًا"؟
النموذج الإنتاجي مثل GPT-4 يمتلك مئات المليارات من المعلمات. النموذج "الصغير" يكون ضمن 1 إلى 25 مليون معلمة. أمثلة عملية:
- GuppyLM (8.7 مليون)
- nanoGPT (124 مليون)
- MicroLM (1-2 مليون)
النماذج الصغيرة يمكن أن:
- تتدرب على كمبيوتر محمول أو Google Colab
- تعمل بالكامل على الذاكرة العشوائية لوحدة المعالجة المركزية
- يمكن فحصها وتعديلها وتصحيحها على مستوى الأوزان
لكن لا يمكنها:
- التعامل مع الاستدلالات المعقدة
- توليد نصوص طويلة متماسكة دائمًا
- الوصول لجودة الإنتاج
القيمة الأساسية من بناء نموذج صغير هي الفهم العملي وليس النتائج النهائية.
المكونات الأساسية: كيف يعمل النموذج اللغوي (LLM) بالفعل
للبناء من الصفر، ركز على أربعة مكونات رئيسية:
مُرمّز النص (Tokenizer)
يحوّل النص إلى معرفات عددية. مثلًا:
"Hello, world!" → [15496, 11, 995, 0]
كل رقم يرمز لوحدة فرعية من كلمة.
أهمية ذلك في APIs:
عدد الرموز يؤثر مباشرة على التكلفة وزمن الاستجابة. فهمك لطريقة التقطيع يساعدك في كتابة prompts مناسبة وتجنب الاقتطاع المفاجئ.
GuppyLM يستخدم tokenizer بسيط على مستوى الحرف، بينما تستخدم النماذج الإنتاجية BPE مع مفردات ضخمة.
طبقة التضمين (Embedding layer)
تحول معرفات الرموز إلى متجهات كثيفة، مثل 384 بعدًا في GuppyLM. تضمينات الموضع تضاف لمعرفة ترتيب الرموز.
كتل المحول (Transformer blocks)
تشمل:
- الانتباه الذاتي (Self-attention): كل رمز ينظر لجميع الرموز الأخرى ويقرر أيها مهم للتنبؤ بالرمز التالي (GuppyLM: 6 رؤوس × 6 طبقات).
- شبكة التغذية الأمامية (Feed-forward): شبكة MLP طبقتين مع ReLU.
رأس الإخراج (Output head)
طبقة خطية تحول تمثيل كل رمز إلى متجه بحجم المفردات، ثم Softmax، ثم يتم أخذ العينة أو اختيار الاحتمال الأعلى.
بناء نموذج لغوي صغير (LLM) في بايثون
قم بإنشاء نموذج بسيط باستخدام PyTorch. هذا مثال عملي يمكن تجربته مباشرة:
import torch
import torch.nn as nn
import torch.nn.functional as F
# Hyperparameters
VOCAB_SIZE = 256 # character-level: one slot per ASCII char
D_MODEL = 128 # embedding dimension
N_HEADS = 4 # attention heads
N_LAYERS = 3 # transformer blocks
SEQ_LEN = 64 # context window
DROPOUT = 0.1
class SelfAttention(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.n_heads = n_heads
self.head_dim = d_model // n_heads
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
self.proj = nn.Linear(d_model, d_model, bias=False)
self.dropout = nn.Dropout(DROPOUT)
def forward(self, x):
B, T, C = x.shape
qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.head_dim)
q, k, v = qkv.unbind(dim=2)
q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# Causal mask: each token can only attend to previous tokens
scale = self.head_dim ** -0.5
attn = (q @ k.transpose(-2, -1)) * scale
mask = torch.triu(torch.ones(T, T, device=x.device), diagonal=1).bool()
attn = attn.masked_fill(mask, float('-inf'))
attn = F.softmax(attn, dim=-1)
attn = self.dropout(attn)
out = (attn @ v).transpose(1, 2).reshape(B, T, C)
return self.proj(out)
class TransformerBlock(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.attn = SelfAttention(d_model, n_heads)
self.ff = nn.Sequential(
nn.Linear(d_model, 4 * d_model),
nn.ReLU(),
nn.Linear(4 * d_model, d_model),
nn.Dropout(DROPOUT),
)
self.ln1 = nn.LayerNorm(d_model)
self.ln2 = nn.LayerNorm(d_model)
def forward(self, x):
x = x + self.attn(self.ln1(x))
x = x + self.ff(self.ln2(x))
return x
class TinyLLM(nn.Module):
def __init__(self):
super().__init__()
self.embed = nn.Embedding(VOCAB_SIZE, D_MODEL)
self.pos_embed = nn.Embedding(SEQ_LEN, D_MODEL)
self.blocks = nn.ModuleList([
TransformerBlock(D_MODEL, N_HEADS) for _ in range(N_LAYERS)
])
self.ln_f = nn.LayerNorm(D_MODEL)
self.head = nn.Linear(D_MODEL, VOCAB_SIZE, bias=False)
def forward(self, idx):
B, T = idx.shape
tok_emb = self.embed(idx)
pos = torch.arange(T, device=idx.device)
pos_emb = self.pos_embed(pos)
x = tok_emb + pos_emb
for block in self.blocks:
x = block(x)
x = self.ln_f(x)
logits = self.head(x)
return logits
# Initialize and count parameters
model = TinyLLM()
total_params = sum(p.numel() for p in model.parameters())
print(f"Model size: {total_params:,} parameters") # ~1.2M
حلقة التدريب
استخدم دالة التدريب التالية:
import torch.optim as optim
def train(model, data, epochs=100, lr=3e-4):
optimizer = optim.AdamW(model.parameters(), lr=lr)
model.train()
for epoch in range(epochs):
# data: tensor of token IDs, shape [batch, seq_len+1]
x = data[:, :-1] # input: all tokens except last
y = data[:, 1:] # target: all tokens shifted by 1
logits = model(x)
loss = F.cross_entropy(logits.reshape(-1, VOCAB_SIZE), y.reshape(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f"Epoch {epoch}, loss: {loss.item():.4f}")
الاستدلال (توليد النص)
لاختبار النموذج وتوليد نص جديد:
@torch.no_grad()
def generate(model, prompt_ids, max_new_tokens=50, temperature=1.0, top_k=10):
model.eval()
ids = torch.tensor([prompt_ids])
for _ in range(max_new_tokens):
idx_cond = ids[:, -SEQ_LEN:] # crop to context window
logits = model(idx_cond)
logits = logits[:, -1, :] / temperature # last token only
# top-k sampling
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
logits[logits < v[:, [-1]]] = float('-inf')
probs = F.softmax(logits, dim=-1)
next_id = torch.multinomial(probs, num_samples=1)
ids = torch.cat([ids, next_id], dim=1)
return ids[0].tolist()
ما الذي يعلمك إياه هذا عن سلوك واجهات برمجة تطبيقات الذكاء الاصطناعي
بناء النموذج بنفسك يكشف لك سلوك الـ API من الداخل. نقاط عملية:
درجة الحرارة (Temperature) وأخذ العينات (sampling) ميكانيكية وليست سحرية
درجة الحرارة تعدّل توزيع اللوجيتات قبل Softmax:
- أعلى = عشوائية أكثر
- أقل = نتائج أكثر تحديدًا
إذا رأيت نتائج غير متوقعة مع
temperature=0.0في الـ API، غالبًا ما يكون هناك تعديل داخلي طفيف.
نوافذ السياق هي حدود صارمة وليست اقتراحات مرنة
في السطر idx_cond = ids[:, -SEQ_LEN:] النموذج يسقط الرموز القديمة بصمت. إذا افترضت أن النموذج يتذكر كل شيء، أنت مخطئ. عليك التعامل مع هذه الحدود في تصميمك.
الرموز المتدفقة هي مجرد خطوات استدلال مرئية
APIs التي تقدم تدفق الرموز لا تفعل شيئًا معماريًا مختلفًا—فقط تعرض كل رمز فور توليده. في حالة انقطاع البث يجب إعادة التوليد من جديد.
اللوجيتات تشرح لماذا يصعب إنتاج مخرجات مهيكلة
كل رمز يحصل على احتمالية في كل خطوة. توليد JSON صالح أو مخرجات مهيكلة يتطلب أن يفوز الرمز الصحيح في كل موضع. مكتبات مثل Outlines و Guidance تفرض القواعد النحوية عبر التلاعب باللوجيتات أثناء الاستدلال.
كيفية اختبار دمج واجهات برمجة تطبيقات الذكاء الاصطناعي باستخدام Apidog
بعد فهمك لاستدلال النماذج، يمكنك كتابة اختبارات API عملية وفعالة. استخدم سيناريوهات Apidog لسلاسل استدعاءات API والتأكيد على بنية استجابات الذكاء الاصطناعي.
مثال اختبار واجهة دردشة متدفقة:
- أنشئ سيناريو اختبار في Apidog باستخدام endpoint
/v1/chat/completions - اضبط التأكيدات للتحقق من بنية الاستجابة مثل:
response.choices[0].finish_reason == "stop"response.usage.total_tokens < 4096
- أضف خطوة تالية ترسل الاستجابة كسياق للدور التالي لمحاكاة محادثة متعددة الأدوار
- استخدم Smart Mock لمحاكاة حالات مثل:
-
finish_reason: "length"(مخرجات مقتطعة) finish_reason: "content_filter"- مهلة الشبكة أثناء البث
-
بهذا تختبر دمج الذكاء الاصطناعي دون استهلاك أرصدة API في كل تشغيل CI. راجع [internal: api-testing-tutorial] لأساليب اختبار موسعة.
اختبار تأكيدات عدد الرموز
{
"assertions": [
{
"field": "response.usage.completion_tokens",
"operator": "less_than",
"value": 512
},
{
"field": "response.choices[0].finish_reason",
"operator": "equals",
"value": "stop"
},
{
"field": "response.choices[0].message.content",
"operator": "not_empty"
}
]
}
شغل هذه الاختبارات على نماذج متعددة (GPT-4o، Claude 3.5 Sonnet، Gemini 1.5 Pro) في سيناريو واحد للقبض على اختلافات الـ API قبل الإنتاج.
متقدم: التكميم (Quantization) وتحسين الاستدلال (Inference Optimization)
عند امتلاكك لنموذج عامل، هاتان تقنيتان أساسيتان لأي نشر عملي:
التكميم (Quantization)
الأوزان الافتراضية تكون FP32، يمكنك تقليلها لـ INT8 أو حتى INT4 لتوفير الذاكرة. مثال عملي في PyTorch:
# Example: dynamic INT8 quantization in PyTorch
import torch.quantization
quantized_model = torch.quantization.quantize_dynamic(
model, {nn.Linear}, dtype=torch.qint8
)
الـ APIs الإنتاجية غالبًا ما تشغّل نماذج مكممة. اختلاف جودة المخرجات أحيانًا سببه التكميم.
ذاكرة التخزين المؤقت KV (KV cache)
في التوليد، يتم عادةً إعادة حساب الانتباه لكل السياق. أنظمة الإنتاج تستخدم KV cache لتخزين نتائج الرموز السابقة، ما يجعل الرموز التالية أسرع بكثير في التوليد.
نموذج لغوي صغير (Tiny LLM) مقابل واجهة برمجة تطبيقات إنتاجية: متى تستخدم كل منهما
| حالة الاستخدام | نموذج لغوي صغير (Tiny LLM) | واجهة برمجة تطبيقات إنتاجية |
|---|---|---|
| تعلم التفاصيل الداخلية للنموذج | الأفضل لـ | مبالغ فيه |
| إنشاء نموذج أولي لتطبيق جديد | جودة غير كافية | الأفضل لـ |
| بيانات خاصة/حساسة | خيار جيد | يعتمد على المزود |
| النشر دون اتصال/على الحافة | قابل للتطبيق | غير ممكن |
| حساس للتكلفة، حجم كبير | ممكن مع مقايضات | مكلف على نطاق واسع |
| مهام تتطلب استدلالًا كبيرًا | غير قابل للتطبيق | مطلوب |
النصيحة: استخدم واجهة برمجة التطبيقات للإنتاج، لكن جرب بناء وتشغيل نموذج صغير لفهم العمق. كلاهما مكمل للآخر. راجع أيضًا [internal: open-source-coding-assistants-2026] لأدوات "أحضر نموذجك الخاص".
خاتمة
بناء نموذج لغوي صغير (LLM) من الصفر يستغرق عطلة نهاية أسبوع واحدة. لن تحصل على منتج إنتاجي، لكنك ستكتسب فهماً عملياً لكيفية عمل كل LLM من الداخل. هذا الفهم يساعدك في تصحيح الأخطاء، ضبط معلمات أخذ العينات، وتصميم اختبارات أكثر صرامة لواجهات برمجة تطبيقات الذكاء الاصطناعي.
ابدأ بمشروع GuppyLM: استنسخه، دربه على أي مجموعة بيانات نصية، وراجع حلقة الاستدلال. بعدها، عد لاختبارات دمج واجهات الإنتاج وستجد نفسك أكثر سيطرة.
جرّب سيناريوهات اختبار Apidog لإضفاء نفس الصرامة على اختبار واجهات الذكاء الاصطناعي التي تطبقها على الأنظمة الأخرى.
الأسئلة الشائعة
كم عدد المعلمات التي يحتاجها نموذج لغوي "صغير" لتوليد نص متماسك؟
عادةً بين 10 إلى 50 مليون معلمة مع بيانات تدريب جيدة. أقل من مليون ينتج نصاً غير مفهوم غالبًا. GuppyLM بـ 8.7 مليون كافٍ لمحادثات قصيرة في مجال تدريبه.
هل يمكنني تشغيل نموذج لغوي صغير (LLM) بدون GPU؟
نعم، النماذج الأقل من 100 مليون معلمة تعمل جيدًا على CPU، فقط الاستدلال أبطأ. النموذج أعلاه (1.2 مليون) ينتج رموزًا في أجزاء من الثانية على كمبيوتر محمول.
ما هي مجموعة البيانات التي يجب أن أتدرب عليها؟
النماذج على مستوى الحرف تعمل مع نصوص Project Gutenberg، أو ويكيبيديا، أو أي نص حر. GuppyLM يستخدم مجموعة دردشة 60 ألف إدخال على HuggingFace (arman-bd/guppylm-60k-generic). لتوليد الكود استخدم The Stack أو CodeParrot.
ما الفرق بين درجة الحرارة (temperature) وtop-k؟
درجة الحرارة تضبط توزيع اللوجيتات (عشوائية عامة)، top-k يقتصر الاحتمال على k رموز فقط. غالبًا يتم استخدامهما معًا.
لماذا يكرر نموذج LLM نفسه أحيانًا؟
التكرار يحدث عندما يعطي النموذج احتمالية عالية للرموز التي أنتجها للتو. APIs الإنتاجية تطبق عقوبات تكرار (repetition_penalty) لتقليل ذلك. استخدم مثلًا: repetition_penalty=1.1.
كم يستغرق تدريب نموذج LLM صغير؟
النموذج أعلاه ينتج مخرجات متماسكة في أقل من ساعتين على GPU واحدة (مثل RTX 3060). GuppyLM يتدرب في Colab في نفس الوقت تقريبًا. النماذج الأكبر تتطلب عدة GPUs وأيامًا من التدريب.
ما هي أسرع طريقة للانتقال من نموذج LLM صغير إلى نقطة نهاية API حقيقية؟
صدر النموذج إلى GGUF باستخدام نص تحويل llama.cpp، ثم قدمه عبر llama-server للحصول على endpoint متوافق مع OpenAI يعمل محليًا. يمكنك بعدها اختبار ذلك عبر Apidog.
كيف تتعامل نماذج LLM الإنتاجية مع السياقات الأطول من نافذة تدريبها؟
تقنيات مثل RoPE، والانتباه المنزلق، والتوليد المدعوم بالاسترجاع توسع السياق بشكل عملي. البنية الأساسية للمحول لا تتغير، فقط طريقة ترميز الموضع ونوافذ الانتباه يتم تعديلها.
Top comments (0)