پیش از آموزش یک GPT کوچک (124M پارامتر)
Type: Build
Languages: Python (with numpy)
Prerequisites: Phase 10, Lessons 01-03 (Tokenizers, Building a Tokenizer, Data Pipelines)
Time: ~120 minutes
اهداف یادگیری
- پیاده سازی معماری کامل GPT-2 (124M پارامتر) از ابتدا: گنجانده شدن توکن ها، گنجانده شدن موقعیت ها، بلوک های ترانسفورماتور و سر مدل زبان
- تمرین یک مدل GPT در یک متن با استفاده از پیش بینی توکن بعدی با از دست دادن آنترپی متقاطع
- پیاده سازی تولید متن خودکشی با نمونه گیری دمای و فیلتر کردن top-k/top-p
- منحنیات از دست دادن آموزش را نظارت کنید و تأیید کنید که مدل الگوهای منسجم زبان را یاد می گیرد
مشکل
شما می دانید که یک ترانسفورماتور چیست. شما نمودارها را خوانده اید. شما می توانید "انتباه تنها چیزی است که شما نیاز دارید" را بخوانید و جعبه هایی را که برچسب "انتباه چند سر" را روی یک صفحه سفید نشان می دهید.
هیچ چیز از این بدان معنی نیست که شما می دانید وقتی یک مدل متن تولید می کند چه اتفاقی می افتد.
در GPT-2 Small (با وزن متصل) 124,438,272 پارامتر وجود دارد. هر یک از آنها با اجرا یک حلقه آموزش تنظیم شد: عبور جلو، از دست دادن محاسبه، عبور عقب، وزنهای تازه. دوازده بلوک ترانسفورماتور 12 تا سر توجه در هر بلوک یک فضای 768 بعدی يک لغت از 50257 توکن هر بار که مدل یک توکن تولید کند، تمام 124 میلیون پارامتر در یک زنجیره ضرب ماتریکس واحد شرکت می کنند که یک ردیف از شناسه های توکن را می گیرد و توزیع احتمال را در توکن بعدی تولید می کند.
اگر شما هرگز این را خودتان ساخته اید، شما با یک جعبه سیاه کار می کنید. شما می توانید از API استفاده کنید. شما می توانید تنظیمات را خوب کنید. اما وقتی چیزی اشتباه می شود - وقتی مدل توهم می کند، وقتی خود را تکرار می کند، وقتی از پیروی از دستورالعمل ها انکار می کند - شما هیچ مدل ذهنی برای چرا ندارید.
این درس GPT-2 را از نو ساخت. نه در PyTorch. در numpy. هر ضرب ماتریک قابل مشاهده است. هر گرادینت توسط کد شما محاسبه می شود. شما دقیقا خواهید دید که چگونه 124 میلیون عدد برای پیش بینی کلمه بعدی سازش می کنند.
مفهوم
معماری GPT
GPT یک مدل زبان autoregressive است. "autoregressive" به معنای تولید یک توکن در یک زمان است، هر یک از آنها بر روی تمام توکن های قبلی مشروط است. معماری یک دسته از بلوک های دیکودر ترانسفورماتور است.
این گراف کامل محاسبه از شناسه های توکن تا احتمال های توکن بعدی است:
- نماد شناسه ها وارد می شوند. شکل: (بچ_سائز، seq_len).
- جستجوی سمبول. هر شناسه نقشه به یک ویکتور 768 بعدی. شکل: (بچ_size, seq_len, 768).
- هر موقعیت (0,1,2, ...) نقشه به یک ویکتور 768 بعدی. شکل مشابه.
- اضافه کردن رمزگذاری + موقعیت گذاری
- از 12 بلوک ترانسفورماتور عبور کن
- طبقه آخر نرمال شدن
- طرح خطی به اندازه لغت. شکل: (بچ_size، seq_len، vocab_size).
- نرم تا احتمالات رو بدست بيار
این کل مدل است. هیچ پیچ و جوی و تکرار نیست. فقط گنجانده شدن، توجه، شبکه های بازتاب و استاندارد های لایه 12 بار جمع شده است.
graph TD
A["Token IDs\n(batch, seq_len)"] --> B["Token Embeddings\n(batch, seq_len, 768)"]
A --> C["Position Embeddings\n(batch, seq_len, 768)"]
B --> D["Add"]
C --> D
D --> E["Transformer Block 1"]
E --> F["Transformer Block 2"]
F --> G["..."]
G --> H["Transformer Block 12"]
H --> I["Layer Norm"]
I --> J["Linear Head\n(768 -> 50257)"]
J --> K["Softmax\nNext-token probabilities"]
style A fill:#1a1a2e,stroke:#e94560,color:#fff
style B fill:#1a1a2e,stroke:#0f3460,color:#fff
style C fill:#1a1a2e,stroke:#0f3460,color:#fff
style D fill:#1a1a2e,stroke:#16213e,color:#fff
style E fill:#1a1a2e,stroke:#e94560,color:#fff
style F fill:#1a1a2e,stroke:#e94560,color:#fff
style H fill:#1a1a2e,stroke:#e94560,color:#fff
style I fill:#1a1a2e,stroke:#16213e,color:#fff
style J fill:#1a1a2e,stroke:#0f3460,color:#fff
style K fill:#1a1a2e,stroke:#51cf66,color:#fffبلوک ترانسفورماتور
هر یک از 12 بلوک از الگوی مشابه پیروی می کند. معماری قبل از استاندارد (GPT-2 از قبل از استاندارد استفاده می کند، نه پس از استاندارد مانند ترانسفورماتور اصلی):
- لایه نورم
- توجه به خود چند سر
- اتصال باقیمانده (به ورودی باز اضافه کنید)
- لایه نورم
- شبکه ارسال اطلاعات (MLP)
- اتصال باقیمانده (به ورودی باز اضافه کنید)
اتصال های باقیمانده حیاتی هستند. بدون آنها، گرادینت ها در زمان رسیدن به بلوک 1 در طول گسترش عقب ناپدید می شوند. با آنها، گرادینت ها می توانند مستقیما از ضایع به هر لایه از طریق مسیر "پریدن" جریان یابند. به همین دلیل می توانید بلوک های 12، 32 یا حتی 96 را جمع کنید (GPT-4 به طور شایعه ای از 120 استفاده می کند).
توجه: مکانیسم اصلی
توجه به خود اجازه می دهد که هر نشانه به هر نشانه قبلی نگاه کند و تصمیم بگیرد که چقدر به هر یک توجه کند.
برای هر موقعیت رمزنگاری، سه متری را از ورودی محاسبه کنید:
- Query (Q)"من دنبال چي ميگردم؟"
- Key (K)"من چه چيزي رو در خود دارم؟"
- Value (V): "چه اطلاعاتي دارم؟"
Q = input @ W_q (768 -> 768)
K = input @ W_k (768 -> 768)
V = input @ W_v (768 -> 768)
attention_scores = Q @ K^T / sqrt(d_k)
attention_scores = mask(attention_scores) # causal mask: -inf for future positions
attention_weights = softmax(attention_scores)
output = attention_weights @ Vماسک علت گرایی باعث می شود که GPT خودکشی کند. موقعیت 5 می تواند به موقعیت های 0-5 توجه کند اما نه 6, 7, 8 و غیره. این مانع از "خداطوری" مدل از طریق نگاه کردن به توکن های آینده در طول آموزش می شود.
Multi-head attentionدر این بخش، فضای 768 بعدی را به 12 سر با هر یک از 64 ابعاد تقسیم می کند. هر سر الگوی توجه متفاوتی را یاد می گیرد. یک سر ممکن است روابط نحوی را ردیابی کند (اتفاق موضوع و فعل). یکی دیگر ممکن است شباهت معنوی را ردیابی کند (سینونیم ها). یکی دیگر ممکن است نزدیکی موقعیت را ردیابی کند (کلمات نزدیک). نتایج از هر 12 سر به هم متصل شده و به 768 ابعاد بازتاب داده می شود.
graph LR
subgraph MultiHead["Multi-Head Attention (12 heads)"]
direction TB
I["Input (768)"] --> S1["Split into 12 heads"]
S1 --> H1["Head 1\n(64 dims)"]
S1 --> H2["Head 2\n(64 dims)"]
S1 --> H3["..."]
S1 --> H12["Head 12\n(64 dims)"]
H1 --> C["Concat (768)"]
H2 --> C
H3 --> C
H12 --> C
C --> O["Output Projection\n(768 -> 768)"]
end
subgraph SingleHead["Each Head Computes"]
direction TB
Q["Q = X @ W_q"] --> A["scores = Q @ K^T / 8"]
K["K = X @ W_k"] --> A
A --> M["Apply causal mask"]
M --> SM["Softmax"]
SM --> MUL["weights @ V"]
V["V = X @ W_v"] --> MUL
end
style I fill:#1a1a2e,stroke:#e94560,color:#fff
style O fill:#1a1a2e,stroke:#e94560,color:#fff
style Q fill:#1a1a2e,stroke:#0f3460,color:#fff
style K fill:#1a1a2e,stroke:#0f3460,color:#fff
style V fill:#1a1a2e,stroke:#0f3460,color:#fffتقسیم با sqrt(d_k) -- sqrt(64) = 8 -- مقیاس است. بدون آن، محصولات نقطه برای متریهای ابعاد بالا بزرگ می شوند، و نرمترین را به مناطقی که گرادیانت تقریبا صفر است، فشار می دهند. این یکی از بینش های کلیدی در مقاله اصلی "اهتمام تنها چیزی است که شما نیاز دارید" بود.
KV Cache: چرا نتیجه گیری سریع است
در طول آموزش، شما تمام تکه را به یک بار پردازش می کنید. در طول نتیجه گیری، شما یک توکن را در یک زمان تولید می کنید. بدون بهینه سازی، تولید توکن N نیاز به محاسبه مجدد توجه برای تمام توکن های قبلی N-1 دارد. این O(N^2) برای هر توکن تولید شده است، یا O(N^3) کل برای یک تکه طول N.
KV Cache اينو حل ميکنه بعد از محاسبه K و V برای هر توکن، آنها را ذخیره کنید. وقتی توکن N+1 تولید می کنید، فقط باید Q را برای توکن جدید محاسبه کنید و K و V را از تمام توکن های قبلی جستجو کنید. این هزینه هر توکن را از O(N) به O(1) برای محاسبه K و V کاهش می دهد. محاسبه امتیاز توجه هنوز O(N) است زیرا شما به تمام موقعیت های قبلی توجه می کنید، اما از ضربات ماتریس اضافی در ورودی جلوگیری می کنید.
برای GPT-2 با 12 لایه و 12 سر، کیش KV 2 (K + V) x 12 لایه x 12 سر x 64 dims = 18,432 ارزش در هر توکن ذخیره می کند. برای یک ردیابی 1024 توکن، که حدود 75MB در FP32 است. برای Llama 3 405B با 128 لایه، کیش KV برای یک ردیابی می تواند بیش از 10GB باشد. به همین دلیل است که نتیجه گیری زمینه طولانی به حافظه محدود است.
پیش پر کردن در مقابل رمزگذاری: دو مرحله از فرض
وقتی به یک مدرک لیسانس می فرستید، نتیجه گیری در دو مرحله متفاوت اتفاق می افتد.
Prefillتمام توکن ها شناخته شده اند، بنابراین مدل می تواند توجه را برای تمام موقعیت ها به طور همزمان محاسبه کند. این مرحله محدود به محاسبه است - GPU ضربات ماتریکس را با تولید کامل انجام می دهد. برای یک توکن 1000 در A100، پیش پر کردن حدود 20-50ms است.
Decodeتوکن ها را یک به یک تولید می کند. هر توکن جدید به تمام توکن های قبلی بستگی داره. این مرحله مربوط به حافظه است - گلو شکنی خواندن وزن مدل و KV از حافظه GPU است، نه خود ریاضی ماتریکس. هسته های محاسباتی GPU بیشتر بیکار می نشینند و منتظر خواندن حافظه هستند. برای GPT-2، هر مرحله رمزگذاری تقریباً زمان مشابهی را می گیرد، صرف نظر از اینکه مامل ها چه تعداد FLOP نیاز دارند، زیرا عرض باند حافظه محدودیت است.
این تفاوت برای سیستم های تولید مهم است. مقیاس های تولید را با محاسبه GPU (FLOPS بیشتر = prefill سریعتر) پر کنید. مقیاس های تولید را با عرض باند حافظه (memory bandwidth) (memory bandwidth = faster decode) پر کنید. به همین دلیل H100 NVIDIA بر بهبود عرض باند حافظه نسبت به A100 تمرکز کرد - به طور مستقیم تولید توکن را تسریع می کند.
graph LR
subgraph Prefill["Phase 1: Prefill"]
direction TB
P1["Full prompt\n(all tokens known)"]
P2["Parallel computation\n(compute-bound)"]
P3["Builds KV Cache"]
P1 --> P2 --> P3
end
subgraph Decode["Phase 2: Decode"]
direction TB
D1["Generate token N"]
D2["Read KV Cache\n(memory-bound)"]
D3["Append to KV Cache"]
D4["Generate token N+1"]
D1 --> D2 --> D3 --> D4
D4 -.->|repeat| D1
end
Prefill --> Decode
style P1 fill:#1a1a2e,stroke:#51cf66,color:#fff
style P2 fill:#1a1a2e,stroke:#51cf66,color:#fff
style P3 fill:#1a1a2e,stroke:#51cf66,color:#fff
style D1 fill:#1a1a2e,stroke:#e94560,color:#fff
style D2 fill:#1a1a2e,stroke:#e94560,color:#fff
style D3 fill:#1a1a2e,stroke:#e94560,color:#fff
style D4 fill:#1a1a2e,stroke:#e94560,color:#fffچرخه آموزش
آموزش یک LLM پیش بینی بعدی است. به عنوان توکن [0, 1, 2, ..., N-1]، توکن های پیش بینی [1, 2, 3, ..., N] را ارائه دهید. عملکرد از دست دادن بین توزیع احتمال پیش بینی شده مدل و توکن بعدی واقعی است.
يک مرحله آموزش:
- Forward pass: دسته را در تمام 12 بلوک اجرا کنید. برای هر موقعیت، امتیازات (نمای های قبل از نرم) را بدست آورید.
- Compute loss: انترپی متقاطع بین logits و توکن های هدف (دخل با یک موقعیت تغییر یافته است).
- Backward pass: gradients برای تمام پارامترهای 124M با استفاده از backpropagation محاسبه کنید.
- Optimizer stepGPT-2 از آدم استفاده ميکنه تا سرعت گرم شدن و زوال کوسينو رو به دست بياره
برنامه ی سرعت یادگیری مهم تر از آنچه ممکن است انتظار داشته باشید است. GPT-2 از 0 تا نرخ یادگیری اوج در طول ۲۰۰۰ مرحله اول گرم می شود، سپس به دنبال منحنی کوسین تجزیه می شود. شروع با نرخ یادگیری بالا باعث انحراف مدل می شود. حفظ نرخ بالا ثابت باعث نوسان در آموزش های بعدی می شود. الگوی گرم شدن پس از تجزیه توسط هر LLM بزرگ استفاده می شود.
GPT-2 کوچک: اعداد
| Component | Shape | Parameters |
|---|---|---|
| Token embeddings | (50257, 768) | 38,597,376 |
| Position embeddings | (1024, 768) | 786,432 |
| Per-block attention (W_q, W_k, W_v, W_out) | 4 x (768, 768) | 2,359,296 |
| Per-block FFN (up + down) | (768, 3072) + (3072, 768) | 4,718,592 |
| Per-block LayerNorms (2x) | 2 x 768 x 2 | 3,072 |
| Final LayerNorm | 768 x 2 | 1,536 |
| Total per block | 7,080,960 | |
| Total (12 blocks) | 85,054,464 + 39,383,808 = 124,438,272 |
پروژکتور خروجی (سر Logits) وزنهایی را با ماتریس گنجانیدن توکن به اشتراک می گذارد. این به عنوان وزن پیوند نامیده می شود - این تعداد پارامتر را با 38M کاهش می دهد و عملکرد را بهبود می بخشد زیرا مدل را مجبور می کند از همان فضای نمایش برای ورودی و خروجی استفاده کند.
آن را بسازید
مرحله اول: قرار دادن لایه
گنجانده شدن توکن هر یک از 50257 توکن احتمالی را به یک ویکتور 768 بعدی نقشه می زند. گنجانده شدن موقعیت اطلاعات مربوط به جایی که هر توکن در ردیف قرار دارد را اضافه می کند. دو مورد به طور مجموع جمع می شوند.
pythonimport numpy as np
class Embedding:
def __init__(self, vocab_size, embed_dim, max_seq_len):
self.token_embed = np.random.randn(vocab_size, embed_dim) * 0.02
self.pos_embed = np.random.randn(max_seq_len, embed_dim) * 0.02
def forward(self, token_ids):
seq_len = token_ids.shape[-1]
tok_emb = self.token_embed[token_ids]
pos_emb = self.pos_embed[:seq_len]
return tok_emb + pos_embانحراف استاندارد 0.02 برای ابتدایی از کاغذ GPT-2 است. بیش از حد بزرگ و گذر اولیه پیش تولید می کند ارزش های شدید است که بی ثبات آموزش. بیش از حد کوچک و خروجی اولیه تقریبا یکسان برای همه ورودی است، به طوری که سیگنال های گرادینت اولیه بی فایده است.
مرحله دوم: خودبهرشی با ماسک علت
توجه یک سر اول. ماسک علتي موقعیت های آینده را قبل از نرمترین حد به بی نهایت منفی می اندازد، اطمینان حاصل می کند که هر موقعیت فقط می تواند به خود و موقعیت های قبلی توجه کند.
pythondef attention(Q, K, V, mask=None):
d_k = Q.shape[-1]
scores = Q @ K.transpose(0, -1, -2 if Q.ndim == 4 else 1) / np.sqrt(d_k)
if mask is not None:
scores = scores + mask
weights = np.exp(scores - scores.max(axis=-1, keepdims=True))
weights = weights / weights.sum(axis=-1, keepdims=True)
return weights @ Vپیاده سازی softmax حداکثر را قبل از نمادگذاری از دست می دهد. بدون این، exp(large_number) به بی نهایت جریان می یابد. این یک ترفند ثبات عددی است که تولید را تغییر نمی دهد زیرا softmax(x - c) = softmax(x) برای هر ثابت c.
مرحله سوم: توجه چند سر
ورودی 768 بعدی را به 12 سر با هر 64 ابعاد تقسیم کنید. هر سر توجه را به طور مستقل محاسبه می کند. نتایج را به 768 ابعاد بازگردانید.
pythonclass MultiHeadAttention:
def __init__(self, embed_dim, num_heads):
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.W_q = np.random.randn(embed_dim, embed_dim) * 0.02
self.W_k = np.random.randn(embed_dim, embed_dim) * 0.02
self.W_v = np.random.randn(embed_dim, embed_dim) * 0.02
self.W_out = np.random.randn(embed_dim, embed_dim) * 0.02
def forward(self, x, mask=None):
batch, seq_len, d = x.shape
Q = (x @ self.W_q).reshape(batch, seq_len, self.num_heads, self.head_dim).transpose(0, 2, 1, 3)
K = (x @ self.W_k).reshape(batch, seq_len, self.num_heads, self.head_dim).transpose(0, 2, 1, 3)
V = (x @ self.W_v).reshape(batch, seq_len, self.num_heads, self.head_dim).transpose(0, 2, 1, 3)
scores = Q @ K.transpose(0, 1, 3, 2) / np.sqrt(self.head_dim)
if mask is not None:
scores = scores + mask
weights = np.exp(scores - scores.max(axis=-1, keepdims=True))
weights = weights / weights.sum(axis=-1, keepdims=True)
attn_out = weights @ V
attn_out = attn_out.transpose(0, 2, 1, 3).reshape(batch, seq_len, d)
return attn_out @ self.W_outرقص تغییر شکل-تضحیک-تضحیک بیشتر بخش گیج کننده ی توجه چند سر است. این اتفاق می افتد: تنسور (بچ، seq_len، 768) تبدیل به (بچ، seq_len، 12, 64) می شود، سپس (بچ، 12, seq_len، 64). حالا هر یک از 12 سر دارای ماتریک خود (seq_len، 64) برای هدایت توجه است. پس از توجه، ما روند را معکوس می کنیم: (بچ، 12، seq_len، 64) می شود (بچ، seq_len، 12, 64) می شود (بچ، seq_len، 768).
مرحله چهارم: بلاک ترانسفورماتور
یک بلوک کامل ترانسفورماتور: LayerNorm، توجه چند سر با باقی مانده، LayerNorm، feedforward با باقی مانده.
pythonclass LayerNorm:
def __init__(self, dim, eps=1e-5):
self.gamma = np.ones(dim)
self.beta = np.zeros(dim)
self.eps = eps
def forward(self, x):
mean = x.mean(axis=-1, keepdims=True)
var = x.var(axis=-1, keepdims=True)
return self.gamma * (x - mean) / np.sqrt(var + self.eps) + self.beta
class FeedForward:
def __init__(self, embed_dim, ff_dim):
self.W1 = np.random.randn(embed_dim, ff_dim) * 0.02
self.b1 = np.zeros(ff_dim)
self.W2 = np.random.randn(ff_dim, embed_dim) * 0.02
self.b2 = np.zeros(embed_dim)
def forward(self, x):
h = x @ self.W1 + self.b1
h = np.maximum(0, h) # GELU approximation: ReLU for simplicity
return h @ self.W2 + self.b2
class TransformerBlock:
def __init__(self, embed_dim, num_heads, ff_dim):
self.ln1 = LayerNorm(embed_dim)
self.attn = MultiHeadAttention(embed_dim, num_heads)
self.ln2 = LayerNorm(embed_dim)
self.ffn = FeedForward(embed_dim, ff_dim)
def forward(self, x, mask=None):
x = x + self.attn.forward(self.ln1.forward(x), mask)
x = x + self.ffn.forward(self.ln2.forward(x))
return xشبکه پیشرسانی ورودی 768 را به 3.072 ابعاد (4x) گسترش می دهد، یک غیر خطی را اعمال می کند، سپس به 768 باز می گردد. این الگوی انقباض و گسترش به مدل نمایش داخلی "بسیاری" برای کار در هر موقعیت می دهد. GPT-2 از فعال سازی GELU استفاده می کند، اما ما برای سادگی اینجا از ReLU استفاده می کنیم - تفاوت برای درک معماری جزئی است.
مرحله 5: مدل کامل GPT
12 بلوک ترانسفورماتور را جمع کنید. لایه ی گنجانده شده را در جلو و پروژکتور خروجی را در پشت اضافه کنید.
pythonclass MiniGPT:
def __init__(self, vocab_size=50257, embed_dim=768, num_heads=12,
num_layers=12, max_seq_len=1024, ff_dim=3072):
self.embedding = Embedding(vocab_size, embed_dim, max_seq_len)
self.blocks = [
TransformerBlock(embed_dim, num_heads, ff_dim)
for _ in range(num_layers)
]
self.ln_f = LayerNorm(embed_dim)
self.vocab_size = vocab_size
self.embed_dim = embed_dim
def forward(self, token_ids):
seq_len = token_ids.shape[-1]
mask = np.triu(np.full((seq_len, seq_len), -1e9), k=1)
x = self.embedding.forward(token_ids)
for block in self.blocks:
x = block.forward(x, mask)
x = self.ln_f.forward(x)
logits = x @ self.embedding.token_embed.T
return logits
def count_parameters(self):
total = 0
total += self.embedding.token_embed.size
total += self.embedding.pos_embed.size
for block in self.blocks:
total += block.attn.W_q.size + block.attn.W_k.size
total += block.attn.W_v.size + block.attn.W_out.size
total += block.ffn.W1.size + block.ffn.b1.size
total += block.ffn.W2.size + block.ffn.b2.size
total += block.ln1.gamma.size + block.ln1.beta.size
total += block.ln2.gamma.size + block.ln2.beta.size
total += self.ln_f.gamma.size + self.ln_f.beta.size
return totalتوجه به وزن بسته شدن:logits = x @ self.embedding.token_embed.T. پروژکتور خروجی از ماتریس گنجانده شدن توکن (ترانسپوس) استفاده مجدد می کند. این فقط یک ترفند ذخیره سازی پارامتر نیست. این بدان معنی است که مدل برای درک توکن ها (توابع) و پیش بینی آنها (خروجی) از همان فضای ویکتور استفاده می کند.
مرحله ۶: چرخه آموزش
برای یک تمرین واقعی در پارامتر 124M، شما نیاز به یک GPU و PyTorch دارید. این حلقه آموزشی مکانیک یک مدل کوچک را نشان می دهد که در حالت نمپی خالص اجرا می شود. ما از یک مدل کوچک (4 لایه، 4 سر، 128 dims) برای انجام آن استفاده می کنیم.
pythondef cross_entropy_loss(logits, targets):
batch, seq_len, vocab_size = logits.shape
logits_flat = logits.reshape(-1, vocab_size)
targets_flat = targets.reshape(-1)
max_logits = logits_flat.max(axis=-1, keepdims=True)
log_softmax = logits_flat - max_logits - np.log(
np.exp(logits_flat - max_logits).sum(axis=-1, keepdims=True)
)
loss = -log_softmax[np.arange(len(targets_flat)), targets_flat].mean()
return loss
def train_mini_gpt(text, vocab_size=256, embed_dim=128, num_heads=4,
num_layers=4, seq_len=64, num_steps=200, lr=3e-4):
tokens = np.array(list(text.encode("utf-8")[:2048]))
model = MiniGPT(
vocab_size=vocab_size, embed_dim=embed_dim, num_heads=num_heads,
num_layers=num_layers, max_seq_len=seq_len, ff_dim=embed_dim * 4
)
print(f"Model parameters: {model.count_parameters():,}")
print(f"Training tokens: {len(tokens):,}")
print(f"Config: {num_layers} layers, {num_heads} heads, {embed_dim} dims")
print()
for step in range(num_steps):
start_idx = np.random.randint(0, max(1, len(tokens) - seq_len - 1))
batch_tokens = tokens[start_idx:start_idx + seq_len + 1]
input_ids = batch_tokens[:-1].reshape(1, -1)
target_ids = batch_tokens[1:].reshape(1, -1)
logits = model.forward(input_ids)
loss = cross_entropy_loss(logits, target_ids)
if step % 20 == 0:
print(f"Step {step:4d} | Loss: {loss:.4f}")
return modelاین خسارت در نزدیکی ln(vocab_size) شروع می شود - برای یک لغت باط سطح 256-توکن، یعنی ln(256) = 5.55. یک مدل تصادفی احتمال برابر را به هر توکن اختصاص می دهد. به عنوان آموزش پیشرفت می کند، خسارت کاهش می یابد زیرا مدل یاد می گیرد تا الگوهای رایج را پیش بینی کند: "th" پس از "t"، فضا پس از یک دوره و غیره.
در تولید، شما از بهینه سازی آدم با تراکم گرادینت، گرم شدن سرعت یادگیری و برش گرادینت استفاده می کنید. حلقه پیشرفت-خسارت-بازدید-تازهکاری یکسان است. بهینه سازی پیچیده تر است.
مرحله هفتم: تولید متن
نسل از مدل آموزش دیده برای پیش بینی یک توکن در یک زمان استفاده می کند. هر پیش بینی از توزیع خروجی نمونه گیری می شود (یا به طمع به عنوان argmax گرفته می شود).
pythondef generate(model, prompt_tokens, max_new_tokens=100, temperature=0.8):
tokens = list(prompt_tokens)
seq_len = model.embedding.pos_embed.shape[0]
for _ in range(max_new_tokens):
context = np.array(tokens[-seq_len:]).reshape(1, -1)
logits = model.forward(context)
next_logits = logits[0, -1, :]
next_logits = next_logits / temperature
probs = np.exp(next_logits - next_logits.max())
probs = probs / probs.sum()
next_token = np.random.choice(len(probs), p=probs)
tokens.append(next_token)
return tokensدمای 1.0 توزیع خام را استفاده می کند. دمای 0.5 آن را تیز می کند (مطمئن تر - مدل انتخاب های برتر خود را بیشتر انتخاب می کند). دمای 1.5 آن را صاف می کند (مطمئن تر - توکن های احتمال کم شانس بیشتری دارند). دمای 0.0 رمزگذاری طمع است (همیشه بالاترین توکن احتمال را انتخاب کنید).
.tokens[-seq_len:]پنجره ضروری است زیرا مدل دارای حداکثر طول زمینه است (1024 برای GPT-2). هنگامی که شما آن را فراتر می روید، باید قدیمی ترین توکن ها را رها کنید. این "تنها زمینه" است که همه در مورد آن صحبت می کنند.
ازش استفاده کن
آموزش کامل و نمایش نسل
pythoncorpus = """The transformer architecture has revolutionized natural language processing.
Attention mechanisms allow the model to focus on relevant parts of the input.
Self-attention computes relationships between all pairs of positions in a sequence.
Multi-head attention splits the representation into multiple subspaces.
Each attention head can learn different types of relationships.
The feedforward network provides nonlinear transformations at each position.
Residual connections enable gradient flow through deep networks.
Layer normalization stabilizes training by normalizing activations.
Position embeddings give the model information about token ordering.
The causal mask ensures autoregressive generation during training.
Pre-training on large text corpora teaches the model general language understanding.
Fine-tuning adapts the pre-trained model to specific downstream tasks."""
model = train_mini_gpt(corpus, num_steps=200)
prompt = list("The transformer".encode("utf-8"))
output_tokens = generate(model, prompt, max_new_tokens=100, temperature=0.8)
generated_text = bytes(output_tokens).decode("utf-8", errors="replace")
print(f"\nGenerated: {generated_text}")در یک کورپوس کوچک با یک مدل کوچک، متن تولید شده در بهترین حالت نیمه منسجم خواهد بود. این دستگاه از متن آموزش برخی الگوهای بائته را یاد می گیرد اما نمی تواند روش GPT-2 را با 40GB داده های آموزش و معماری پارامتر کامل 124M عمومی کند. نکته کیفیت محصول نیست. نکته این است که شما می توانید هر مرحله را ردیابی کنید: جستجوی داخلی، محاسبه توجه، تحول پیشروی، پروژکتور منطق، نرم ماکس و نمونه گیری. هر عمليات رو مي تونيم ببينيم
-باده
این درس به ما کمک می کندoutputs/prompt-gpt-architecture-analyzer.md-- یک پیامک که انتخاب معماری را در هر مدل سبک GPT تجزیه و تحلیل می کند. یک کارت مدل یا گزارش فنی را به آن می دهد و پارامتر های اختصاصی، طراحی توجه و تصمیمات مقیاس بندی را تجزیه می کند.
تمرینات
- مدل را تغییر دهید تا به جای 12/12 از 24 لایه و 16 سر استفاده شود. پارامترها را بشمارید. چگونه دو برابر کردن عمق با دو برابر کردن عرض (بعضیات ادغام) مقایسه می شود؟
- عملکرد فعال سازی GELU (GELU(x) = x 0.5 (1 + erf(x / sqrt(2)))) را پیاده سازی کنید و ReLU را در شبکه feedforward جایگزین کنید. تمرین برای 500 مرحله با هر فعال سازی انجام دهید و از دست دادن نهایی را مقایسه کنید.
- یک حافظه پیشگیری KV را به تابع تولید اضافه کنید. تنسورهای K و V را برای هر لایه پس از اولین عبور به جلو ذخیره کنید و برای توکن های بعدی از آنها استفاده کنید. سرعت را اندازه گیری کنید: 200 توکن با و بدون حافظه پیشگیری ایجاد کنید و زمان ساعت دیواری را مقایسه کنید.
- نمونه گیری top-k را اجرا کنید (تنها از نمرات k با احتمال بالا) و نمونه گیری top-p را اجرا کنید (نمایش هسته ای: کوچکترین مجموعه از نمرات را که احتمال تجمعی آن از p بیشتر است، در نظر بگیرید). کیفیت خروجی را در دمای 0.8 با top-k=50 در مقایسه با top-p=0.95 مقایسه کنید.
- یک نقشه کشی منحنی از دست دادن آموزش بسازید. مدل را برای 1000 مرحله و نقشه از دست دادن در مقابل مرحله آموزش دهید. سه مرحله را شناسایی کنید: کاهش اولیه سریع (تعلّم بائتهای مشترک) ، مرحله متوسط آهسته تر (تعلّم بائتهای الگوی) و سطح بالا (تضمین بر روی کورپوس کوچک). شکل این منحنی یکسان است که آیا شما در حال آموزش یک مدل 128 بعدی یا GPT-4 هستید.
اصطلاحات کلیدی
| Term | What people say | What it actually means |
|---|---|---|
| Autoregressive | "It generates one word at a time" | Each output token is conditioned on all previous tokens -- the model predicts P(token_n | token_0, ..., token_{n-1}) |
| Causal mask | "It can't see the future" | An upper-triangular matrix of -infinity values that prevents attention to future positions during training |
| Multi-head attention | "Multiple attention patterns" | Splitting Q, K, V into parallel heads (e.g., 12 heads of 64 dims each for GPT-2) so each head can learn different relationship types |
| KV Cache | "Caching for speed" | Storing computed Key and Value tensors from previous tokens to avoid redundant computation during autoregressive generation |
| Prefill | "Processing the prompt" | The first inference phase where all prompt tokens are processed in parallel -- compute-bound on GPU FLOPS |
| Decode | "Generating tokens" | The second inference phase where tokens are generated one at a time -- memory-bound on GPU bandwidth |
| Weight tying | "Sharing embeddings" | Using the same matrix for input token embeddings and the output projection head -- saves 38M params in GPT-2 |
| Residual connection | "Skip connection" | Adding the input directly to the output of a sublayer (x + sublayer(x)) -- enables gradient flow in deep networks |
| Layer normalization | "Normalizing activations" | Normalizing across the feature dimension to mean 0 and variance 1, with learnable scale and bias parameters |
| Cross-entropy loss | "How wrong the predictions are" | -log(probability assigned to the correct next token), averaged over all positions -- the standard LLM training objective |
خواندن بیشتر
- Radford et al., 2019 -- "Language Models are Unsupervised Multitask Learners" (GPT-2)-- کاغذ GPT-2 که خانواده پارامتر 124M تا 1.5B را معرفی کرد
- Vaswani et al., 2017 -- "Attention Is All You Need"-- کاغذ اصلی ترانسفورماتور با توجه به نقطه محصول و توجه چند سر
- Llama 3 Technical Report-- چطور ميتا معماری GPT را به پارامترهای 405B با GPU های 16K مقیاس زد
- Pope et al., 2022 -- "Efficiently Scaling Transformer Inference"-- مقاله اي که پيش از پر کردن و رمزگشایی و تجزیه و تحلیل کش KV را رسمي ميکنه
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.