الوحدة 01 · الأساسيات

من البيرسبترون إلى Transformer:
الأساس الذي تقوم عليه نماذج LLM الحديثة

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

1. الشبكات العصبية الاصطناعية: من الخلايا العصبية البيولوجية إلى الدوال الرياضية

1.1 أصغر وحدة في الشبكة العصبية

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

output = activation(w · x + b)

هنا x هو متجه الإدخال، w هو الوزن، b هو الانحياز، وactivation هي دالة غير خطية (شائعة: Sigmoid أو ReLU أو GELU). رصّ آلافًا من هذه «الخلايا العصبية» في طبقات ووصّلها ببعضها وستحصل على أساس التعلم العميق الحديث.

1.2 العناصر الثلاثة للتدريب

💡 حدس رئيسي: تدريب الشبكة العصبية هو في جوهره «البحث في فضاء معاملات بأبعاد المليارات عن تشكيل أوزان يقلل الخسارة». لهذا تحتاج نماذج LLM إلى بيانات ضخمة وقوة حوسبة هائلة — فضاء المعاملات ضخم جدًا.

2. Transformer: حجر الزاوية لنماذج LLM الحديثة

في عام 2017، نشرت Google ورقة Attention Is All You Need، مقدّمة بنية Transformer ومغيرة مسار الذكاء الاصطناعي. ابتكارها الأساسي: التخلي تمامًا عن التكرار والاعتماد كليًا على آلية الانتباه لمعالجة التسلسلات.

2.1 لماذا هيمن Transformer على معالجة اللغة الطبيعية

الخاصيةRNN / LSTMTransformer
التبعيات بعيدة المدىتميل إلى نسيان السياق المبكراتصال مباشر بين أي موضعين
الحوسبة المتوازيةمتتابعة بشكل صارميعالج جميع المواقع دفعة واحدة
سرعة التدريببطيئةسريعة (استفادة كاملة من GPU)
القابلية للتوسعصعب تجاوز 10 مليار معاملدعم أصلي لـ 100 مليار+ معامل

2.2 الانتباه الذاتي (Self-attention)

الفكرة الأساسية: لكل كلمة في تسلسل، «انظر» إلى كل كلمة أخرى وقرر مدى أهمية كل واحدة لها.

خذ جملة المثال هذه — «ذهب Xiaoming في رحلة عمل إلى بكين أمس، وعاد هو اليوم»:

  1. تُنتج كل كلمة ثلاثة متجهات: الاستعلام (Q)، المفتاح (K)، القيمة (V).
  2. بالنسبة لـ «هو»، يُحسب الجداء النقطي لاستعلامها مع مفتاح كل كلمة أخرى للحصول على درجة الصلة.
  3. يُطبَّع Softmax للحصول على أوزان الانتباه (مثلًا: 0.8 لـ «Xiaoming»، 0.1 لـ «بكين»).
  4. تُستخدم هذه الأوزان لأخذ مجموع مرجَّح لقيم V لكل كلمة، منتجةً تمثيلًا جديدًا غنيًا بالسياق لكلمة «هو».

بالصيغة الرياضية:

Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V

حيث d_k هو بُعد متجه Key؛ القسمة على جذره التربيعي تمنع الجداء النقطي من إفساد تدرج Softmax.

2.3 الانتباه متعدد الرؤوس (Multi-head attention)

الانتباه متعدد الرؤوس هو انتباه ذاتي على المنشطات. يُنفذ عمليات انتباه ذاتي متعددة بالتوازي عبر «فضاءات تمثيل» مختلفة ويسلسل النتائج. هذا يمكّن النموذج من التقاط أنواع متعددة من العلاقات في وقت واحد:

2.4 الترميز الموضعي (Positional encoding)

يعالج Transformer كل كلمة بالتوازي، لذا يفقد افتراضيًا معلومات «الترتيب». «الكلب يعض الرجل» و«الرجل يعض الكلب» لهما رموز متطابقة — يختلفان فقط في الترتيب. يضيف الترميز الموضعي «متجه موضع» فريد إلى تمثيل كل كلمة لكي يعرف النموذج مكانها في التسلسل.

استخدم Transformer الأصلي ترميزات موضعية جيبية/جيب تمام ثابتة. أما النماذج اللاحقة (LLaMA، Qwen) فتستخدم RoPE (Rotary Position Embeddings)، التي تتعامل مع تسلسلات أطول بكثير.

3. البنية الكاملة لـ Transformer

كتلة Transformer القياسية لها نصفان:

input → [LayerNorm → Multi-Head Attention → Residual]
     → [LayerNorm → Feed-Forward Network (FFN) → Residual]
     → output

عائلة GPT (ChatGPT وDeepSeek وQwen) هي ببساطة عشرات إلى مئات من هذه الكتل مكدسة في Transformer يعمل بفك الترميز فقط (Decoder-only).

4. التدريب المسبق، SFT، RLHF: وصفة التدريب من ثلاث خطوات

4.1 التدريب المسبق (Pre-training)

التدريب على مجموعة ضخمة من نصوص الإنترنت (تريليونات الرموز) لكي يتعلم النموذج «التنبؤ بالرمز التالي». هذه هي أطول خطوة وأكثرها تكلفة — استخدم DeepSeek-V3 بيانات تدريب قدرها 14.8 تريليون رمز وتكلف حوالي 5.57 مليون دولار (إيجار GPU).

4.2 الضبط الدقيق الخاضع للإشراف (SFT)

التدريب على مجموعة صغيرة من أزواج «التعليمات-الاستجابة» عالية الجودة والمنتقاة بشريًا (عشرات الآلاف) لكي يتعلم النموذج «فهم الطلب والاستجابة وفقًا لذلك». الحجم أصغر بكثير من التدريب المسبق، لكن متطلبات الجودة قصوى.

4.3 التعلم المعزز من التغذية الراجعة البشرية (RLHF)

يُصنّف البشر استجابات النموذج المختلفة؛ يُدرَّب نموذج مكافأة على هذه التصنيفات، ثم يُستخدم التعلم المعزز (عادة PPO أو DPO) لدفع النموذج نحو الإجابات الأعلى تصنيفًا.

5. لماذا نحتاج إلى عناقيد GPU؟

تدريب نموذج بمئة مليار معامل — تتطلب عملية أمامية + خلفية واحدة:

لا شيء من هذا يتسع في وحدة GPU واحدة (80GB)، لذلك نحتاج إلى استراتيجيات متوازية متعددة:

التوازيالفكرةإطار العمل التمثيلي
البيانات المتوازيةتقسيم البيانات عبر وحدات GPU؛ كل واحدة تشغل النموذج الكاملPyTorch DDP
النموذج المتوازيوضع طبقات مختلفة على وحدات GPU مختلفةMegatron-LM
خط الأنابيب المتوازيتقسيم النموذج إلى مراحل؛ كل GPU تعالج دفعة صغيرة مختلفةGPipe, PipeDream
التنسور المتوازيتقسيم عملية مصفوفية واحدة عبر وحدات GPUMegatron-LM

6. من نماذج LLM إلى الوكلاء: قفزة في القدرات

بمجرد أن أصبحت نماذج LLM قوية بما فيه الكفاية، بدأ الناس في إضافة «إضافات»: لم يعد النموذج «يتكلم» فحسب، بل «يفعل». هذه هي نقطة الانطلاق لـ AI Agent:

7. ملخص الفصل

📚 الفصل التالي: تطور الـ Agent — من LLM إلى Agentic Agent

8. تطبيقي: ابنِ Mini Transformer من الصفر

بعد تغطية النظرية، يرشدك هذا القسم خلال بناء Transformer بـ 6 طبقات من نوع Decoder-only في PyTorch من الصفر، مدرب على مجموعة نصية صغيرة. يبلغ طول الكود الكامل حوالي 250 سطرًا ويعمل على CPU أو GPU — الهدف هو جعل كل ترس في Transformer مرئيًا لك.

8.1 الأهداف والبيئة

إعداد البيئة (سطران):

pip install torch numpy
python -c "import torch; print('torch', torch.__version__)"

8.2 إعداد البيانات

سنستخدم قطعة صغيرة من بيانات شبيهة بشعر تانغ (يمكنك استبدالها بـ input.txt خاصتك):

cat > data/input.txt << 'EOF'
Quiet night thoughts
Before my bed a pool of light,
Can it be frost upon the ground?
Eyes up, I find the moon so bright,
Head down, I think of home I've found.
Spring dawn
Spring slumber knows no dawn so soon,
The birds' song fills the air with tune.
Last night the wind and rain did blow,
How many flowers have let go?
EOF
wc -c data/input.txt

نموذج على مستوى الأحرف يقرأ ببساطة الملف بالكامل كسلسلة طويلة — لا حاجة إلى tokenizer.

8.3 الكود الكامل: train.py

"""Mini Transformer: 6 طبقات Decoder-only، نموذج لغوي على مستوى الأحرف.

الاستخدام:
    python train.py
"""
import math
import time
from pathlib import Path

import torch
import torch.nn as nn
import torch.nn.functional as F


# ---------- 1. محمّل البيانات ----------
class CharDataset:
    """يقطّع ملف نصي إلى عينات تدريب بطول (block_size)."""

    def __init__(self, path: str, block_size: int = 64):
        self.text = Path(path).read_text(encoding="utf-8")
        self.chars = sorted(set(self.text))
        self.vocab_size = len(self.chars)
        self.stoi = {c: i for i, c in enumerate(self.chars)}
        self.itos = {i: c for i, c in enumerate(self.chars)}
        self.block_size = block_size
        self.data = torch.tensor([self.stoi[c] for c in self.text], dtype=torch.long)
        print(f"[data] chars={self.vocab_size} len={len(self.data)}")

    def get_batch(self, batch_size: int = 32):
        ix = torch.randint(0, len(self.data) - self.block_size - 1, (batch_size,))
        x = torch.stack([self.data[i : i + self.block_size] for i in ix])
        y = torch.stack([self.data[i + 1 : i + 1 + self.block_size] for i in ix])
        return x, y


# ---------- 2. مكونات النموذج ----------
class PositionalEncoding(nn.Module):
    """ترميز موضعي جيبي، مجمّد."""

    def __init__(self, d_model: int, max_len: int = 512):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        self.register_buffer("pe", pe.unsqueeze(0))  # (1, max_len, d_model)

    def forward(self, x):
        return x + self.pe[:, : x.size(1)]


class CausalSelfAttention(nn.Module):
    """انتباه ذاتي سببي متعدد الرؤوس (مستخدم في Decoder)."""

    def __init__(self, d_model: int, n_heads: int, max_len: int = 512, dropout: float = 0.1):
        super().__init__()
        assert d_model % n_heads == 0
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads
        self.qkv = nn.Linear(d_model, 3 * d_model)
        self.proj = nn.Linear(d_model, d_model)
        self.dropout = nn.Dropout(dropout)
        # قناع سببي: المثلث العلوي (باستثناء القطر) هو -inf
        mask = torch.triu(torch.ones(max_len, max_len), diagonal=1).bool()
        self.register_buffer("mask", mask)

    def forward(self, x):
        B, T, C = x.shape
        qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.head_dim).permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0], qkv[1], qkv[2]  # (B, h, T, head_dim)
        att = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
        att = att.masked_fill(self.mask[:T, :T], float("-inf"))
        att = F.softmax(att, dim=-1)
        att = self.dropout(att)
        out = (att @ v).transpose(1, 2).reshape(B, T, C)
        return self.proj(out)


class FeedForward(nn.Module):
    """FFN الخاص بـ Transformer: GELU + Linear + Dropout."""

    def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.GELU(),
            nn.Linear(d_ff, d_model),
            nn.Dropout(dropout),
        )

    def forward(self, x):
        return self.net(x)


class Block(nn.Module):
    """كتلة Decoder بنمط Pre-LN: Attention -> FFN، كل واحدة مع اتصال متبقي."""

    def __init__(self, d_model: int, n_heads: int, d_ff: int, max_len: int, dropout: float):
        super().__init__()
        self.ln1 = nn.LayerNorm(d_model)
        self.attn = CausalSelfAttention(d_model, n_heads, max_len, dropout)
        self.ln2 = nn.LayerNorm(d_model)
        self.ffn = FeedForward(d_model, d_ff, dropout)

    def forward(self, x):
        x = x + self.attn(self.ln1(x))
        x = x + self.ffn(self.ln2(x))
        return x


class MiniTransformer(nn.Module):
    """Transformer بـ 6 طبقات Decoder-only، نموذج لغوي على مستوى الأحرف."""

    def __init__(
        self,
        vocab_size: int,
        d_model: int = 128,
        n_heads: int = 4,
        d_ff: int = 512,
        n_layers: int = 6,
        max_len: int = 256,
        dropout: float = 0.1,
    ):
        super().__init__()
        self.tok_emb = nn.Embedding(vocab_size, d_model)
        self.pos_enc = PositionalEncoding(d_model, max_len)
        self.blocks = nn.ModuleList(
            [Block(d_model, n_heads, d_ff, max_len, dropout) for _ in range(n_layers)]
        )
        self.ln_f = nn.LayerNorm(d_model)
        self.head = nn.Linear(d_model, vocab_size, bias=False)
        # ربط الأوزان: تمثيل الإدخال يتشارك الأوزان مع رأس الإخراج
        self.head.weight = self.tok_emb.weight
        self.apply(self._init_weights)

    def _init_weights(self, m):
        if isinstance(m, nn.Linear):
            nn.init.normal_(m.weight, std=0.02)
            if m.bias is not None:
                nn.init.zeros_(m.bias)
        elif isinstance(m, nn.Embedding):
            nn.init.normal_(m.weight, std=0.02)

    def forward(self, idx, targets=None):
        x = self.tok_emb(idx)
        x = self.pos_enc(x)
        for blk in self.blocks:
            x = blk(x)
        x = self.ln_f(x)
        logits = self.head(x)
        loss = None
        if targets is not None:
            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))
        return logits, loss

    @torch.no_grad()
    def generate(self, idx, max_new_tokens: int = 100, temperature: float = 1.0, top_k: int = 20):
        """توليد autoregressif مع درجة الحرارة + أخذ عينات top-k."""
        for _ in range(max_new_tokens):
            idx_cond = idx[:, -self.pos_enc.pe.size(1) :]
            logits, _ = self(idx_cond)
            logits = logits[:, -1, :] / temperature
            if top_k:
                v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
                logits[logits < v[:, [-1]]] = float("-inf")
            probs = F.softmax(logits, dim=-1)
            nxt = torch.multinomial(probs, num_samples=1)
            idx = torch.cat([idx, nxt], dim=1)
        return idx


# ---------- 3. حلقة التدريب ----------
def main():
    torch.manual_seed(0)
    ds = CharDataset("data/input.txt", block_size=64)
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = MiniTransformer(vocab_size=ds.vocab_size).to(device)
    print(f"[model] params={sum(p.numel() for p in model.parameters()) / 1e6:.2f}M device={device}")

    opt = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.1)
    steps = 1000
    batch_size = 32
    t0 = time.time()
    for step in range(steps):
        x, y = ds.get_batch(batch_size)
        x, y = x.to(device), y.to(device)
        _, loss = model(x, y)
        opt.zero_grad()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        opt.step()
        if step % 100 == 0 or step == steps - 1:
            dt = time.time() - t0
            print(f"step {step:4d} loss={loss.item():.4f} elapsed={dt:.1f}s")
    torch.save({"model": model.state_dict(), "stoi": ds.stoi, "itos": ds.itos}, "ckpt.pt")
    print("[done] saved ckpt.pt")


if __name__ == "__main__":
    main()

8.4 نص الاستدلال: sample.py

"""يُولّد نصًا من ckpt.pt المدرّب."""
import torch
from train import MiniTransformer

ckpt = torch.load("ckpt.pt", map_location="cpu")
stoi, itos = ckpt["stoi"], ckpt["itos"]
model = MiniTransformer(vocab_size=len(itos))
model.load_state_dict(ckpt["model"])
model.eval()

prompt = "Quiet night thoughts\n"
idx = torch.tensor([[stoi[c] for c in prompt]], dtype=torch.long)
out = model.generate(idx, max_new_tokens=120, temperature=0.8, top_k=10)[0].tolist()
print("=== generated ===")
print("".join(itos[i] for i in out))

8.5 شغّله والمخرجات المتوقعة

mkdir -p data
# (اكتب input.txt من القسم 8.2)
python train.py

المخرجات المتوقعة (CPU ~30-60 ثانية، MPS/GPU بضع ثوانٍ):

[data] chars=28 len=87
[model] params=1.21M device=cpu
step    0 loss=3.3321 elapsed=0.0s
step  100 loss=2.7102 elapsed=2.1s
step  200 loss=2.3504 elapsed=4.0s
...
step  900 loss=1.8421 elapsed=32.5s
step  999 loss=1.7903 elapsed=36.0s
[done] saved ckpt.pt

python sample.py
=== generated ===
Quiet night thoughts
Before my bed a pool of light,
Can it be frost upon the ground?
Eyes up, I find the moon so bright,
Head down, I think of home I've found.

8.6 نصائح الضبط

8.7 إلى أين بعد ذلك؟

📚 السابق: 7. ملخص الفصل · الفصل التالي: تطور الـ Agent — من LLM إلى Agentic Agent