Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Blok tuzilishi
- 2.2. Pre-LN va post-LN
- 2.3. Residual ulanish va gradient oqimi
- 2.4. FFN roli
- 2.5. Dropout joylari
- 2.6. Parametrlar formulasi va FLOP
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Blok noldan, parametr formulasi va tayyor modul bilan moslik
- Misol 2 — Residual siz: gradient so'nishi va tokenlar qo'shilib ketishi
- Misol 3 — Pre-LN va post-LN: chuqur stekda barqarorlik
- Misol 4 — FFN roli: attention yig'adi, FFN hisoblaydi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
24.5-dars: Transformer bloki
24-QISM — TRANSFORMERLAR · 5-dars
1. Kirish va motivatsiya
Oldingi darslarda Transformerning "g'ishtlarini" alohida-alohida qurdik: self-attention, multi-head attention va pozitsion kodlash. Endi ularni bitta blokka yig'amiz. Transformer bloki — butun arxitekturaning takrorlanadigan birligi: BERT-base da 12 ta, GPT-3 da 96 ta bir xil tuzilishdagi blok ketma-ket turadi. Blokni tushunsangiz — butun modelni tushunasiz.
Blok to'rt qismdan iborat: attention (pozitsiyalar orasida ma'lumot almashish), FFN (har pozitsiyada alohida ishlaydigan ikki qatlamli tarmoq), residual ulanish (x + f(x)) va LayerNorm. Birinchi ikkitasi "nima hisoblanadi" ni belgilaydi, oxirgi ikkitasi esa "chuqur stekni o'rgatish mumkinmi" degan savolga javob beradi. Aynan shu yerda amaliy tuzoqlar yashiringan: LayerNorm qayerga qo'yiladi — residual dan oldinmi yoki keyinmi? Residual ulanishni olib tashlasa nima bo'ladi? FFN nima uchun kerak, attention o'zi yetmaydimi?
Bu savollarga intuitsiya bilan emas, o'lchov bilan javob beramiz. Bundan tashqari blokning parametrlar sonini formula bilan hisoblaymiz va o'z blokimiz PyTorch dagi tayyor nn.TransformerEncoderLayer bilan aynan bir xil natija berishini tekshiramiz — bu "tayyor modul ichida nima bor" degan savolga eng ishonchli javob.
Real vaziyat. Jamoa asl Transformer maqolasidagi sxemani (post-LN) 12 blok bilan qurdi va o'rgatishni boshladi. Loss birinchi qadamlardanoq ln(V) atrofida qotib qoldi va hech qachon tushmadi. Bir hafta "ma'lumotda xato bor" deb qidirildi. Aslida muammo arxitekturada edi: post-LN chuqur stekda katta o'rganish tezligi va warmup siz o'rganmay qoladi. LayerNorm ni blok kirishiga ko'chirish (pre-LN) muammoni bir qatorda hal qildi. Bu darsning 3-misoli aynan shu holatni takrorlaydi.
Bu darsda Transformer blokini noldan yig'amiz va uning har bir qismi nima uchun kerakligini raqam bilan ko'rsatamiz.
Bu darsda:
- Blok tuzilishi: attention + FFN + residual + LayerNorm
- Pre-LN va post-LN
- Residual ulanish va gradient oqimi
- FFN roli
- Dropout joylari
- Parametrlar formulasi va FLOP
- Tuzoqlar
ℹ Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Blok tuzilishi
POST-LN (asl maqola, 2017):
x = LN(x + Dropout(Attention(x)))
x = LN(x + Dropout(FFN(x)))
PRE-LN (GPT-2 va ko'p zamonaviy modellar):
x = x + Dropout(Attention(LN(x)))
x = x + Dropout(FFN(LN(x)))
... oxirgi blokdan keyin yana bitta LN
FFN (har pozitsiyaga ALOHIDA, bir xil og'irliklar bilan):
FFN(x) = W_2 * aktivatsiya(W_1 x + b_1) + b_2
W_1: (d_ff, d), W_2: (d, d_ff), odatda d_ff = 4d
aktivatsiya: ReLU (asl), GELU (BERT, GPT)
SHAKLLAR:
kirish (B, L, d) -> chiqish (B, L, d) - o'lcham o'zgarmaydi
shuning uchun bloklarni istalgancha ketma-ket qo'yish mumkin
PYTORCH:
nn.TransformerEncoderLayer(d, h, d_ff, dropout, activation,
batch_first=True, norm_first=False)
norm_first=False -> post-LN, norm_first=True -> pre-LN
1-misol: og'irliklarni ko'chirsak, o'z blokimiz bilan farq 0.0e+00 Blok = ikki "qism-blok": har biri f(x) ni hisoblaydi, residual bilan qo'shadi va LayerNorm bilan normallaydi; farq faqat LN qayerda turganida.
2.2. Pre-LN va post-LN
POST-LN: x_{l+1} = LN(x_l + f(x_l))
residual yo'l har blokda LN dan O'TADI
-> "toza" to'g'ri yo'l yo'q: gradient har LN da qayta masshtablanadi
-> chiqishga yaqin bloklarda gradient kattaroq (1-misol: 12/1 nisbati 1.16)
-> katta lr da warmup kerak; chuqur stekda beqaror
PRE-LN: x_{l+1} = x_l + f(LN(x_l))
residual yo'l LN siz: x_L = x_0 + yig'indi f_l(...)
-> gradient chiqishdan kirishgacha to'g'ridan-to'g'ri o'tadi
-> bloklar gradienti bir tekis (12/1 nisbati 0.86)
-> katta lr va warmup siz ham o'rganadi
-> oxirida qo'shimcha LN kerak (x_L normallanmagan)
3-MISOL (lr = 0.006, warmup yo'q, 100 qadam, 3 seed):
2 blok 12 blok
pre-LN 1.000 1.000
post-LN 1.000 0.060 (tasodif 1/16 = 0.0625)
12 blokda post-LN loss 2.774 da qotib qoldi (ln 16 = 2.773)
NARX:
post-LN yaxshi sozlanganda ba'zan sal yaxshiroq natija beradi,
lekin sozlash qiyin; amaliyotda pre-LN - xavfsiz tanlovChuqur stekda pre-LN barqaror — sayoz stekda farq ko'rinmasligi mumkin, shuning uchun 2 blokda sinab "farqi yo'q" deb xulosa qilmang.
2.3. Residual ulanish va gradient oqimi
RESIDUAL SIZ: x_{l+1} = f(x_l)
zanjir qoidasi: dL/dx_0 = J_1^T J_2^T ... J_L^T dL/dx_L
12 ta Yakobian ko'paytmasi - har biri kichraytirsa, gradient so'nadi
RESIDUAL BILAN: x_{l+1} = x_l + f(x_l)
dx_{l+1}/dx_l = I + J_l <- "I" - to'g'ri yo'l
gradient hech bo'lmaganda birlik matritsa orqali o'tadi
2-MISOL (12 blok, boshlang'ich holat, blok kirishidagi gradient):
1-blok 12-blok nisbat
residual bilan 1.24e-02 1.12e-02 1.11
residual siz 2.71e-03 2.54e-02 0.11
YANA BIR MUAMMO - TOKENLAR "QO'SHILIB KETADI":
attention - vaznli o'rtacha; o'rtachaning o'rtachasi ... -> hamma bir xil
2-misol: tokenlar orasidagi o'rtacha cos
residual siz: 1-blokdan keyin 0.924, 2-blokdan keyin 1.000
residual bilan: 12-blokdan keyin ham 0.284
residual har blokda token o'z vektorini "eslab qolishini" ta'minlaydi
O'RGATISH (2-misol): residual bilan 1.000, residual siz 0.061 (tasodif 0.0625)Residual — ikki narsa uchun to'g'ri yo'l: gradient uchun (so'nmaydi) va ma'lumot uchun (tokenlar farqi saqlanadi).
2.4. FFN roli
ATTENTION NIMA QILADI:
boshqa pozitsiyalardagi vektorlarning vaznli o'rtachasini olib keladi
"ma'lumotni tashish" - lekin qiymatlar ustida chiziqli (w V)
FFN NIMA QILADI:
har pozitsiyada alohida, keng (d_ff = 4d) nochiziqli almashtirish
"yig'ilgan ma'lumot ustida hisoblash"
talqinlardan biri: kalit-qiymat xotirasi (W_1 qatorlari - naqshlar,
W_2 ustunlari - ularga mos chiqishlar)
4-MISOL: y_t = (x_t + x_{t-1}) mod 10
1 attention: 0.736 (5386 parametr)
3 attention: 0.714 (13962 parametr; seedlar 0.245 ... 0.987)
1 attention + FFN: 1.000 (13802 parametr; 3 seedda ham)
-> parametr soni emas, FFN ning nochiziqli hisobi hal qildi
PARAMETR VA HISOB ULUSHI (d_ff = 4d):
parametrlarning ~2/3 qismi FFN da (4-misol: 66.7%)
FLOP: L < 4d bo'lsa FFN ko'proq; L = 4d da skor qismi FFN ga tenglashadiAttention "kimdan olish" ni, FFN "olinganini qanday qayta ishlash" ni hal qiladi; blok parametrlarining uchdan ikki qismi FFN da.
2.5. Dropout joylari
nn.TransformerEncoderLayer ichida (1-misol):
self_attn.dropout - attention OG'IRLIKLARIGA (softmax dan keyin)
dropout1 - attention chiqishiga, residual qo'shishdan OLDIN
dropout - FFN ichida, aktivatsiyadan keyin
dropout2 - FFN chiqishiga, residual qo'shishdan OLDIN
QOIDA:
dropout f(x) ga qo'llanadi, residual yo'lga EMAS
x + Dropout(f(x)) - to'g'ri
Dropout(x + f(x)) - to'g'ri yo'lni ham "teshadi"
TRAIN VA EVAL:
train: ikki chaqiruv farqi 0.796 (1-misol) - har safar boshqa niqob
eval: 0.0e+00 - dropout o'chadi
baholash va attention tahlilidan oldin model.eval() shartDropout — faqat qism-blok chiqishida va attention og'irliklarida; residual yo'l doim toza qoladi.
2.6. Parametrlar formulasi va FLOP
BITTA BLOK (biaslar bilan):
attention: 4 d^2 + 4 d (W_q, W_k, W_v, W_o)
FFN: 2 d d_ff + d_ff + d
LayerNorm: 2 * 2d = 4d (gamma, beta)
jami: 4 d^2 + 2 d d_ff + 9d + d_ff
bosh soni h parametrga TA'SIR QILMAYDI (d = h * d_h)
1-MISOL (d = 64, d_ff = 256):
16640 + 33088 + 256 = 49984 = sum(p.numel()) = nn.TransformerEncoderLayer
d_ff = 4d da: ~12 d^2
BERT-base (d = 768): bir blok 7 087 872, 12 blok 85.1 mln
BERT-large (d = 1024): bir blok 12 596 224, 24 blok 302.3 mln
(qolgani - token va pozitsiya embeddinglari)
FLOP (bir token, bitta blok, ko'paytirish + qo'shish = 2 FLOP):
proyeksiyalar 8 d^2
Q K^T va w V 4 L d <- L ga chiziqli (butun ketma-ketlik uchun L^2)
FFN 4 d d_ff = 16 d^2
4-misol, d = 768: L = 512 da FFN ulushi 60.0%, L = 4096 da 35.3% Parametrlarni formula bilan hisoblang va sum(p.numel()) bilan tekshiring; farq chiqsa — arxitekturani noto'g'ri tushungansiz.
2.7. Tuzoqlar
Asosiy tuzoqlar: pre-LN modelda oxirgi LayerNorm ni unutish; residual ni LN dan keyingi qiymatga qo'shish (x = LN(x); x = x + f(x) — bu na pre-LN, na post-LN); chuqur post-LN modelni katta lr va warmup siz o'rgatib "ma'lumot yomon" deb xulosa qilish; nn.TransformerEncoderLayer da batch_first=True ni unutish (standart — (L, B, d)); dropout ni residual yig'indiga qo'llash; baholashda model.eval() ni unutish; bosh sonini oshirsa parametr ko'payadi deb o'ylash; FFN ni "keraksiz qo'shimcha" deb olib tashlash; qatlamlar bo'yicha arxitekturani faqat sayoz stekda sinash.
3. Tez ma'lumotnoma
import torch
import torch.nn as nn
import torch.nn.functional as F
class Blok(nn.Module):
def __init__(self, d, h, d_ff, p=0.1):
super().__init__()
self.att = nn.MultiheadAttention(d, h, dropout=p, batch_first=True)
self.ffn = nn.Sequential(nn.Linear(d, d_ff), nn.GELU(),
nn.Dropout(p), nn.Linear(d_ff, d))
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.drop1, self.drop2 = nn.Dropout(p), nn.Dropout(p)
def forward(self, x): # pre-LN
y = self.ln1(x)
x = x + self.drop1(self.att(y, y, y, need_weights=False)[0])
return x + self.drop2(self.ffn(self.ln2(x)))
# tayyor modul
qatlam = nn.TransformerEncoderLayer(d, h, d_ff, dropout=0.1, activation="gelu",
batch_first=True, norm_first=True)
stek = nn.TransformerEncoder(qatlam, num_layers=6, enable_nested_tensor=False)
# parametrlar: formula va tekshiruv
jami = 4 * d * d + 2 * d * d_ff + 9 * d + d_ff
assert jami == sum(p.numel() for p in qatlam.parameters())
# bloklar bo'yicha gradient normasi
for i, b in enumerate(model.bloklar):
print(i, torch.cat([p.grad.flatten() for p in b.parameters()]).norm())Transformer bloki xulosasi
blok = attention + FFN, har biri residual + LayerNorm bilan
pre-LN: x + f(LN(x)) - chuqur stekda barqaror, oxirida LN
residual - gradient va token ma'lumoti uchun to'g'ri yo'l
FFN - har pozitsiyada nochiziqli hisob, parametrlarning ~2/3
parametr: 4d^2 + 2 d d_ff + 9d + d_ff (d_ff = 4d da ~12 d^2)4. Batafsil misollar
Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
Misol 1 — Blok noldan, parametr formulasi va tayyor modul bilan moslik
"""Transformer bloki noldan: parametr formulasi va nn.TransformerEncoderLayer bilan moslik."""
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class Blok(nn.Module):
"""attention + FFN + residual + LayerNorm; pre_ln=True -> pre-LN, aks holda post-LN."""
def __init__(self, d, h, d_ff, dropout=0.1, pre_ln=False):
super().__init__()
self.h, self.pre_ln, self.p = h, pre_ln, dropout
self.W_qkv = nn.Linear(d, 3 * d) # W_q, W_k, W_v bitta matritsada
self.W_o = nn.Linear(d, d)
self.ff1, self.ff2 = nn.Linear(d, d_ff), nn.Linear(d_ff, d)
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
def attention(self, x):
B, L, d = x.shape
q, k, v = self.W_qkv(x).reshape(B, L, 3, self.h, d // self.h).permute(2, 0, 3, 1, 4)
w = torch.softmax(q @ k.transpose(-1, -2) / math.sqrt(d // self.h), dim=-1)
w = F.dropout(w, self.p, self.training) # 1: og'irliklarda
a = (w @ v).transpose(1, 2).reshape(B, L, d)
return F.dropout(self.W_o(a), self.p, self.training) # 2: residual dan oldin
def ffn(self, x):
z = F.dropout(F.relu(self.ff1(x)), self.p, self.training) # 3: FFN ichida
return F.dropout(self.ff2(z), self.p, self.training) # 4: residual dan oldin
def forward(self, x):
if self.pre_ln:
x = x + self.attention(self.ln1(x))
return x + self.ffn(self.ln2(x))
x = self.ln1(x + self.attention(x))
return self.ln2(x + self.ffn(x))
def formula(d, d_ff):
attention = 4 * d * d + 4 * d # W_q, W_k, W_v, W_o va biaslar
ffn = 2 * d * d_ff + d_ff + d # ikki Linear
ln = 2 * 2 * d # ikki LayerNorm: gamma va beta
return attention, ffn, ln
def main() -> None:
torch.manual_seed(0)
d, h, d_ff = 64, 4, 256
print("=== 1. Parametrlar: formula va sum(p.numel()) ===")
blok = Blok(d, h, d_ff)
a, f, n = formula(d, d_ff)
print(f" attention 4d^2 + 4d = {a:>7}")
print(f" FFN 2*d*d_ff + d_ff + d = {f:>7}")
print(f" LayerNorm 4d = {n:>7}")
print(f" jami (formula) = {a + f + n:>7}")
print(f" jami (o'z blokimiz) = {sum(p.numel() for p in blok.parameters()):>7}")
tel = nn.TransformerEncoderLayer(d, h, d_ff, dropout=0.1, batch_first=True)
print(f" jami (nn.TransformerEncoderLayer) = {sum(p.numel() for p in tel.parameters())}")
print(f" ulushlar: attention {a / (a + f + n):.1%}, FFN {f / (a + f + n):.1%}, "
f"LayerNorm {n / (a + f + n):.1%}")
print("\n=== 2. Katta o'lchamlar (d_ff = 4d) ===")
for nom, dd, hh, qatlam in [("BERT-base", 768, 12, 12), ("BERT-large", 1024, 16, 24)]:
a, f, n = formula(dd, 4 * dd)
haqiqiy = sum(p.numel() for p in
nn.TransformerEncoderLayer(dd, hh, 4 * dd, batch_first=True).parameters())
print(f" {nom:<11} d={dd:<5} bir blok {a + f + n:>9} (numel {haqiqiy:>9}),"
f" {qatlam} blok {qatlam * (a + f + n) / 1e6:.1f} mln")
print("\n=== 3. Og'irliklarni ko'chirib natijani solishtirish (eval) ===")
X = torch.randn(4, 10, d)
for pre_ln in (False, True):
torch.manual_seed(1)
mening = Blok(d, h, d_ff, pre_ln=pre_ln).eval()
tayyor = nn.TransformerEncoderLayer(d, h, d_ff, dropout=0.1, batch_first=True,
norm_first=pre_ln).eval()
with torch.no_grad():
oldin = (mening(X) - tayyor(X)).abs().max().item()
tayyor.self_attn.in_proj_weight.copy_(mening.W_qkv.weight)
tayyor.self_attn.in_proj_bias.copy_(mening.W_qkv.bias)
tayyor.self_attn.out_proj.weight.copy_(mening.W_o.weight)
tayyor.self_attn.out_proj.bias.copy_(mening.W_o.bias)
for a_, b_ in [(tayyor.linear1, mening.ff1), (tayyor.linear2, mening.ff2),
(tayyor.norm1, mening.ln1), (tayyor.norm2, mening.ln2)]:
a_.weight.copy_(b_.weight)
a_.bias.copy_(b_.bias)
farq = (mening(X) - tayyor(X)).abs().max().item()
nom = "pre-LN (norm_first=True) " if pre_ln else "post-LN (norm_first=False)"
print(f" {nom}: ko'chirishdan oldin {oldin:.3f}, keyin {farq:.1e} "
f"{'MOS' if farq < 1e-4 else 'MOS EMAS'}")
print("\n=== 4. Dropout joylari: train va eval ===")
print(" nn.TransformerEncoderLayer ichidagi dropoutlar:")
for nom, mod in tel.named_modules():
if isinstance(mod, nn.Dropout):
print(f" {nom:<9} p = {mod.p}")
print(f" self_attn.dropout (og'irliklarda) p = {tel.self_attn.dropout}")
blok = Blok(d, h, d_ff, dropout=0.1)
torch.manual_seed(2)
blok.train()
y1, y2 = blok(X), blok(X)
blok.eval()
y3, y4 = blok(X), blok(X)
print(f" train: ikki chaqiruv farqi {(y1 - y2).abs().max().item():.3f}")
print(f" eval: ikki chaqiruv farqi {(y3 - y4).abs().max().item():.1e}")
print(" ⭐ Blok = attention + FFN, har biri residual va LayerNorm bilan o'ralgan")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Parametrlar: formula va sum(p.numel()) ===
attention 4d^2 + 4d = 16640
FFN 2*d*d_ff + d_ff + d = 33088
LayerNorm 4d = 256
jami (formula) = 49984
jami (o'z blokimiz) = 49984
jami (nn.TransformerEncoderLayer) = 49984
ulushlar: attention 33.3%, FFN 66.2%, LayerNorm 0.5%
=== 2. Katta o'lchamlar (d_ff = 4d) ===
BERT-base d=768 bir blok 7087872 (numel 7087872), 12 blok 85.1 mln
BERT-large d=1024 bir blok 12596224 (numel 12596224), 24 blok 302.3 mln
=== 3. Og'irliklarni ko'chirib natijani solishtirish (eval) ===
post-LN (norm_first=False): ko'chirishdan oldin 1.618, keyin 0.0e+00 MOS
pre-LN (norm_first=True) : ko'chirishdan oldin 1.637, keyin 0.0e+00 MOS
=== 4. Dropout joylari: train va eval ===
nn.TransformerEncoderLayer ichidagi dropoutlar:
dropout p = 0.1
dropout1 p = 0.1
dropout2 p = 0.1
self_attn.dropout (og'irliklarda) p = 0.1
train: ikki chaqiruv farqi 0.796
eval: ikki chaqiruv farqi 0.0e+00
⭐ Blok = attention + FFN, har biri residual va LayerNorm bilan o'ralganNatija tahlili. 1-bo'limda formula uchta mustaqil hisob bilan bir xil chiqdi: 49984 — o'z blokimiz ham, nn.TransformerEncoderLayer ham. Parametrlarning 66.2% i FFN da, 33.3% i attention da, LayerNorm esa atigi 0.5%. 2-bo'limda xuddi shu formula BERT o'lchamlari uchun ham numel bilan to'liq mos: bitta blok 7 087 872, 12 blok 85.1 mln — BERT-base ning taxminan 110 mln parametridan qolgani embeddinglar. 3-bo'lim eng muhim tekshiruv: og'irliklar ko'chirilmaguncha ikki modul natijasi 1.6 atrofida farq qiladi, ko'chirilgandan keyin esa farq aynan 0.0e+00 — ham post-LN, ham pre-LN uchun. Demak nn.TransformerEncoderLayer ichida biz yozgan formuladan boshqa hech narsa yo'q, in_proj_weight esa W_q, W_k, W_v ning ustma-ust qo'yilgani. 4-bo'lim to'rtta dropout joyini ko'rsatadi va train rejimida bir xil kirishga ikki chaqiruv 0.796 gacha farq qilishini, eval da esa farq yo'qligini tasdiqlaydi.
Nima ko'rsatdi: 2.1, 2.5, 2.6-bo'limlar.
Misol 2 — Residual siz: gradient so'nishi va tokenlar qo'shilib ketishi
"""Residual ulanishsiz chuqur stek: qatlamlar bo'yicha gradient normasi va tokenlar 'qo'shilib ketishi'."""
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
V, SILJISH, L, N = 16, 3, 16, 12
class Blok(nn.Module):
"""Pre-LN blok; residual=False bo'lsa x + f(x) o'rniga faqat f(x)."""
def __init__(self, d, h, residual):
super().__init__()
self.h, self.residual = h, residual
self.qkv, self.o = nn.Linear(d, 3 * d), nn.Linear(d, d)
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
def attention(self, x):
B, L_, d = x.shape
q, k, v = self.qkv(x).reshape(B, L_, 3, self.h, -1).permute(2, 0, 3, 1, 4)
a = F.scaled_dot_product_attention(q, k, v)
return self.o(a.transpose(1, 2).reshape(B, L_, d))
def forward(self, x):
if self.residual:
x = x + self.attention(self.ln1(x))
return x + self.ffn(self.ln2(x))
x = self.attention(self.ln1(x))
return self.ffn(self.ln2(x))
class Model(nn.Module):
def __init__(self, residual, n=N, d=32, h=2):
super().__init__()
self.emb, self.pos = nn.Embedding(V, d), nn.Embedding(L, d)
self.bloklar = nn.ModuleList([Blok(d, h, residual) for _ in range(n)])
self.ln, self.chiq = nn.LayerNorm(d), nn.Linear(d, V)
def forward(self, X, holatlar=None):
x = self.emb(X) + self.pos(torch.arange(X.shape[1]))
for b in self.bloklar:
if holatlar is not None:
x.retain_grad()
holatlar.append(x)
x = b(x)
if holatlar is not None:
holatlar.append(x)
return self.chiq(self.ln(x))
def batch(n, g):
X = torch.randint(0, V, (n, L), generator=g)
Y = torch.full((n, L), -100)
Y[:, SILJISH:] = X[:, :-SILJISH]
return X, Y
def tashxis(residual):
"""Boshlang'ich holatda: har blok KIRISHIGA kelgan gradient va tokenlar o'xshashligi."""
torch.manual_seed(0)
m = Model(residual)
X, Y = batch(64, torch.Generator().manual_seed(5))
holatlar = []
F.cross_entropy(m(X, holatlar).reshape(-1, V), Y.reshape(-1)).backward()
grad = [h.grad.norm().item() for h in holatlar[:-1]]
kos = []
for h in holatlar:
z = F.normalize(h.detach(), dim=-1)
kos.append((z @ z.transpose(1, 2)).mean().item()) # tokenlar orasidagi o'rtacha cos
return grad, kos
def orgat(residual, seed, qadam=120):
torch.manual_seed(seed)
m = Model(residual)
opt = torch.optim.Adam(m.parameters(), lr=0.003)
g = torch.Generator().manual_seed(seed)
for _ in range(qadam):
X, Y = batch(32, g)
loss = F.cross_entropy(m(X).reshape(-1, V), Y.reshape(-1))
opt.zero_grad()
loss.backward()
opt.step()
m.eval()
X, Y = batch(500, torch.Generator().manual_seed(99))
with torch.no_grad():
p = m(X).argmax(-1)
return (p[Y >= 0] == Y[Y >= 0]).float().mean().item()
def main() -> None:
torch.set_num_threads(1)
print(f"=== 1. Gradient normasi blok kirishida ({N} blok, boshlang'ich holat) ===")
g_r, k_r = tashxis(True)
g_y, k_y = tashxis(False)
print(f" {'blok':>5} {'residual bilan':>15} {'residual siz':>13}")
for i in (0, 2, 4, 6, 8, 10, 11):
print(f" {i + 1:>5} {g_r[i]:>15.2e} {g_y[i]:>13.2e}")
print(f" 1-blok / 12-blok nisbati: residual bilan {g_r[0] / g_r[-1]:.2f}, "
f"residual siz {g_y[0] / g_y[-1]:.2f}")
print("\n=== 2. Tokenlar bir-biriga o'xshab ketadimi (o'rtacha cos) ===")
print(f" {'qatlam':>7} {'residual bilan':>15} {'residual siz':>13}")
for i in (0, 1, 2, 4, 8, 12):
nom = "kirish" if i == 0 else f"{i}-blok"
print(f" {nom:>7} {k_r[i]:>15.3f} {k_y[i]:>13.3f}")
if k_y[-1] > 0.99:
print(" residual siz: hamma pozitsiya deyarli BIR XIL vektorga aylandi")
print(" -> qaysi token qayerda turgani haqidagi ma'lumot yo'qoldi")
print("\n=== 3. O'rgatish: 3 qadam oldingi tokenni topish (120 qadam, 3 seed) ===")
nat = {True: [], False: []}
for s in range(3):
for r in (True, False):
nat[r].append(orgat(r, s))
for r in (True, False):
nom = "residual bilan" if r else "residual siz"
print(f" {nom:<15} " + " ".join(f"{a:.3f}" for a in nat[r])
+ f" o'rtacha {np.mean(nat[r]):.3f}")
f = np.array(nat[True]) - np.array(nat[False])
se = f.std(ddof=1) / np.sqrt(len(f))
print(f" farq {f.mean():+.4f}, SE {se:.4f}, sezilarli: {abs(f.mean()) > 2 * se}")
print(f" tasodif darajasi 1/{V} = {1 / V:.3f}")
print(" ⭐ Residual - gradient va ma'lumot uchun 'to'g'ri yo'l'; usiz chuqur stek o'rganmaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Gradient normasi blok kirishida (12 blok, boshlang'ich holat) ===
blok residual bilan residual siz
1 1.24e-02 2.71e-03
3 1.20e-02 7.65e-03
5 1.16e-02 4.04e-03
7 1.16e-02 6.15e-03
9 1.13e-02 9.40e-03
11 1.12e-02 1.33e-02
12 1.12e-02 2.54e-02
1-blok / 12-blok nisbati: residual bilan 1.11, residual siz 0.11
=== 2. Tokenlar bir-biriga o'xshab ketadimi (o'rtacha cos) ===
qatlam residual bilan residual siz
kirish 0.079 0.079
1-blok 0.095 0.924
2-blok 0.105 1.000
4-blok 0.126 1.000
8-blok 0.219 1.000
12-blok 0.284 1.000
residual siz: hamma pozitsiya deyarli BIR XIL vektorga aylandi
-> qaysi token qayerda turgani haqidagi ma'lumot yo'qoldi
=== 3. O'rgatish: 3 qadam oldingi tokenni topish (120 qadam, 3 seed) ===
residual bilan 1.000 1.000 1.000 o'rtacha 1.000
residual siz 0.059 0.065 0.058 o'rtacha 0.061
farq +0.9391, SE 0.0023, sezilarli: True
tasodif darajasi 1/16 = 0.062
⭐ Residual - gradient va ma'lumot uchun 'to'g'ri yo'l'; usiz chuqur stek o'rganmaydiNatija tahlili. 1-bo'limda residual bilan har blok kirishidagi gradient deyarli bir xil (1.24e-02 dan 1.12e-02 gacha, nisbat 1.11). Residual siz esa gradient kirishga yaqinlashgan sari kichrayadi: 12-blokda 2.54e-02, 1-blokda 2.71e-03 — taxminan 9 barobar kam (nisbat 0.11). Pasayish bir tekis emas (3-blokda 7.65e-03, 5-blokda 4.04e-03), lekin umumiy yo'nalish aniq. 2-bo'lim bundan ham jiddiyroq muammoni ko'rsatadi: residual siz birinchi blokdan keyinoq tokenlar orasidagi o'rtacha kosinus 0.924 ga, ikkinchisidan keyin 1.000 ga yetdi — hamma pozitsiya bir xil vektorga aylandi. Attention vaznli o'rtacha oladi, o'rtachaning o'rtachasi esa tezda bir nuqtaga yig'iladi. Residual bilan 12 blokdan keyin ham kosinus 0.284. 3-bo'limda natija: residual bilan uch seedda ham 1.000, residual siz 0.061 — tasodif darajasi (1/16). Farq +0.9391, SE 0.0023. Gradient 9 barobar kichik bo'lgani o'zi Adam uchun halokatli emas; asosiy sabab — kerakli ma'lumot (qaysi token qayerda) birinchi bloklardayoq yo'qolgan.
Nima ko'rsatdi: 2.3-bo'lim.
Misol 3 — Pre-LN va post-LN: chuqur stekda barqarorlik
"""Pre-LN va post-LN: sayoz va chuqur stekda o'rgatish barqarorligi."""
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
V, SILJISH, L, LR = 16, 3, 16, 0.006
class Blok(nn.Module):
def __init__(self, d, h, tur):
super().__init__()
self.h, self.tur = h, tur
self.qkv, self.o = nn.Linear(d, 3 * d), nn.Linear(d, d)
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
def attention(self, x):
B, L_, d = x.shape
q, k, v = self.qkv(x).reshape(B, L_, 3, self.h, -1).permute(2, 0, 3, 1, 4)
a = F.scaled_dot_product_attention(q, k, v)
return self.o(a.transpose(1, 2).reshape(B, L_, d))
def forward(self, x):
if self.tur == "pre": # x + f(LN(x))
x = x + self.attention(self.ln1(x))
return x + self.ffn(self.ln2(x))
x = self.ln1(x + self.attention(x)) # LN(x + f(x))
return self.ln2(x + self.ffn(x))
class Model(nn.Module):
def __init__(self, tur, n, d=32, h=2):
super().__init__()
self.emb, self.pos = nn.Embedding(V, d), nn.Embedding(L, d)
self.bloklar = nn.ModuleList([Blok(d, h, tur) for _ in range(n)])
self.ln = nn.LayerNorm(d) if tur == "pre" else nn.Identity() # pre-LN: yakuniy LN
self.chiq = nn.Linear(d, V)
def forward(self, X):
x = self.emb(X) + self.pos(torch.arange(X.shape[1]))
for b in self.bloklar:
x = b(x)
return self.chiq(self.ln(x))
def batch(n, g):
X = torch.randint(0, V, (n, L), generator=g)
Y = torch.full((n, L), -100)
Y[:, SILJISH:] = X[:, :-SILJISH]
return X, Y
def orgat(tur, n, seed, qadam=100):
torch.manual_seed(seed)
m = Model(tur, n)
opt = torch.optim.Adam(m.parameters(), lr=LR)
g = torch.Generator().manual_seed(seed)
tarix, normalar = [], []
for _ in range(qadam):
X, Y = batch(32, g)
loss = F.cross_entropy(m(X).reshape(-1, V), Y.reshape(-1))
opt.zero_grad()
loss.backward()
normalar.append(torch.cat([p.grad.flatten() for p in m.parameters()]).norm().item())
opt.step()
tarix.append(loss.item())
m.eval()
X, Y = batch(500, torch.Generator().manual_seed(99))
with torch.no_grad():
p = m(X).argmax(-1)
return (p[Y >= 0] == Y[Y >= 0]).float().mean().item(), tarix, normalar
def blok_gradlari(tur, n=12):
torch.manual_seed(0)
m = Model(tur, n)
X, Y = batch(64, torch.Generator().manual_seed(5))
F.cross_entropy(m(X).reshape(-1, V), Y.reshape(-1)).backward()
return [torch.cat([p.grad.flatten() for p in b.parameters()]).norm().item()
for b in m.bloklar]
def main() -> None:
torch.set_num_threads(1)
print("=== 1. Boshlang'ich holatda blok parametrlari gradienti (12 blok) ===")
gp, gq = blok_gradlari("pre"), blok_gradlari("post")
print(f" {'blok':>5} {'pre-LN':>9} {'post-LN':>9}")
for i in (0, 3, 7, 11):
print(f" {i + 1:>5} {gp[i]:>9.3f} {gq[i]:>9.3f}")
print(f" 12-blok / 1-blok: pre-LN {gp[-1] / gp[0]:.2f}, post-LN {gq[-1] / gq[0]:.2f}")
print(f"\n=== 2. O'rgatish: lr = {LR}, warmup yo'q, 100 qadam, 3 seed ===")
nat = {}
for n in (2, 12):
for tur in ("pre", "post"):
nat[(n, tur)] = [orgat(tur, n, s) for s in range(3)]
print(f" {'bloklar':>7} {'tur':<5} {'seedlar':<20} o'rtacha")
for (n, tur), r in nat.items():
a = [x[0] for x in r]
print(f" {n:>7} {tur:<5} {' '.join(f'{v:.3f}' for v in a):<20} {np.mean(a):>9.3f}")
for n in (2, 12):
f = np.array([x[0] for x in nat[(n, "pre")]]) - np.array([x[0] for x in nat[(n, "post")]])
se = f.std(ddof=1) / np.sqrt(len(f))
print(f" {n:>2} blok: pre - post = {f.mean():+.4f}, SE {se:.4f}, "
f"sezilarli: {abs(f.mean()) > 2 * se}")
print("\n=== 3. 12 blok: loss va gradient normasi jarayonda (seed 0) ===")
print(f" {'qadam':>6} {'pre loss':>9} {'post loss':>10} {'pre |g|':>8} {'post |g|':>9}")
_, tp, np_ = nat[(12, "pre")][0]
_, tq, nq = nat[(12, "post")][0]
for q in (1, 10, 20, 40, 70, 100):
print(f" {q:>6} {tp[q - 1]:>9.3f} {tq[q - 1]:>10.3f} {np_[q - 1]:>8.2f} {nq[q - 1]:>9.2f}")
print(f" ln({V}) = {np.log(V):.3f} - 'hech narsa o'rganmagan' loss")
if tq[-1] > np.log(V) - 0.1 > tp[-1]:
print(" post-LN loss tasodif darajasida qotib qoldi, pre-LN esa nolga yaqin")
print(" ⭐ Chuqur stekda pre-LN katta lr va warmup siz ham barqaror o'rganadi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Boshlang'ich holatda blok parametrlari gradienti (12 blok) ===
blok pre-LN post-LN
1 0.127 0.240
4 0.110 0.219
8 0.115 0.266
12 0.108 0.278
12-blok / 1-blok: pre-LN 0.86, post-LN 1.16
=== 2. O'rgatish: lr = 0.006, warmup yo'q, 100 qadam, 3 seed ===
bloklar tur seedlar o'rtacha
2 pre 1.000 1.000 1.000 1.000
2 post 1.000 1.000 1.000 1.000
12 pre 1.000 1.000 1.000 1.000
12 post 0.058 0.058 0.063 0.060
2 blok: pre - post = +0.0000, SE 0.0000, sezilarli: False
12 blok: pre - post = +0.9403, SE 0.0018, sezilarli: True
=== 3. 12 blok: loss va gradient normasi jarayonda (seed 0) ===
qadam pre loss post loss pre |g| post |g|
1 2.932 2.899 0.56 0.88
10 2.793 2.801 0.46 0.49
20 2.753 2.776 0.28 0.25
40 2.297 2.779 0.48 0.35
70 0.062 2.776 0.11 0.31
100 0.006 2.774 0.01 0.24
ln(16) = 2.773 - 'hech narsa o'rganmagan' loss
post-LN loss tasodif darajasida qotib qoldi, pre-LN esa nolga yaqin
⭐ Chuqur stekda pre-LN katta lr va warmup siz ham barqaror o'rganadiNatija tahlili. 1-bo'limda boshlang'ich gradientlar: post-LN da hamma blokning gradienti pre-LN dagidan taxminan ikki baravar katta va chiqishga yaqin bloklarda o'sadi (12-blok / 1-blok = 1.16), pre-LN da esa bir tekis, hatto biroz kamayadi (0.86). Farq kichik ko'rinadi — asosiy farq o'rgatish jarayonida ochiladi. 2-bo'lim: 2 blokda ikkala variant ham uch seedda 1.000 — sayoz stekda farq umuman yo'q (farq +0.0000). 12 blokda esa pre-LN yana 1.000, post-LN 0.060 — tasodif darajasi; farq +0.9403, SE 0.0018. 3-bo'limda seed 0 dagi jarayon: ikkala model ham dastlab ln(16) = 2.773 atrofida turadi. Pre-LN 40-qadamda 2.297 ga, 70-qadamda 0.062 ga tushdi. Post-LN esa 100 qadam davomida 2.774 da qotib qoldi — portlash (NaN) yo'q, gradient normasi ham 0.24 atrofida, ya'ni "hech narsa sodir bo'lmayotgandek" ko'rinadi. Bu eng xavfli holat: xato xabari yo'q, faqat loss tushmaydi. Kichikroq lr yoki warmup post-LN ga yordam beradi, lekin bu qo'shimcha sozlash — pre-LN esa xuddi shu sozlamada ishladi.
Nima ko'rsatdi: 2.2-bo'lim.
Misol 4 — FFN roli: attention yig'adi, FFN hisoblaydi
"""FFN roli: attention yig'adi, FFN qayta ishlaydi; parametr va FLOP ulushi."""
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
V, L = 10, 12
class Attention(nn.Module):
def __init__(self, d, h):
super().__init__()
self.h = h
self.qkv, self.o = nn.Linear(d, 3 * d), nn.Linear(d, d)
def forward(self, x):
B, L_, d = x.shape
q, k, v = self.qkv(x).reshape(B, L_, 3, self.h, -1).permute(2, 0, 3, 1, 4)
a = F.scaled_dot_product_attention(q, k, v)
return self.o(a.transpose(1, 2).reshape(B, L_, d))
class Model(nn.Module):
"""n_att ta pre-LN attention qatlami, ixtiyoriy bitta FFN qatlami."""
def __init__(self, n_att, ffn, d=32, h=2):
super().__init__()
self.emb, self.pos = nn.Embedding(V, d), nn.Embedding(L, d)
self.lnlar = nn.ModuleList([nn.LayerNorm(d) for _ in range(n_att)])
self.attlar = nn.ModuleList([Attention(d, h) for _ in range(n_att)])
self.ffn = None
if ffn:
self.ln_f = nn.LayerNorm(d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
self.ln, self.chiq = nn.LayerNorm(d), nn.Linear(d, V)
def forward(self, X):
x = self.emb(X) + self.pos(torch.arange(X.shape[1]))
for ln, att in zip(self.lnlar, self.attlar):
x = x + att(ln(x))
if self.ffn is not None:
x = x + self.ffn(self.ln_f(x))
return self.chiq(self.ln(x))
def batch(n, g):
"""Nishon: (joriy token + oldingi token) mod 10 - ikki tokenni 'birlashtirish'."""
X = torch.randint(0, V, (n, L), generator=g)
Y = torch.full((n, L), -100)
Y[:, 1:] = (X[:, 1:] + X[:, :-1]) % V
return X, Y
def orgat(n_att, ffn, seed, qadam=400):
torch.manual_seed(seed)
m = Model(n_att, ffn)
opt = torch.optim.Adam(m.parameters(), lr=0.003)
g = torch.Generator().manual_seed(seed)
for _ in range(qadam):
X, Y = batch(64, g)
loss = F.cross_entropy(m(X).reshape(-1, V), Y.reshape(-1))
opt.zero_grad()
loss.backward()
opt.step()
m.eval()
X, Y = batch(1000, torch.Generator().manual_seed(99))
with torch.no_grad():
p = m(X).argmax(-1)
return (p[Y >= 0] == Y[Y >= 0]).float().mean().item(), sum(p.numel() for p in m.parameters())
def floplar(d, L_, d_ff):
"""Bir token uchun ko'paytirish-qo'shish x2 (FLOP), bitta blok."""
proyeksiya = 2 * 4 * d * d # W_q, W_k, W_v, W_o
skor = 2 * 2 * L_ * d # Q K^T va w V
ffn = 2 * 2 * d * d_ff
return proyeksiya, skor, ffn
def main() -> None:
torch.set_num_threads(1)
print("=== 1. Vazifa: y_t = (x_t + x_{t-1}) mod 10 ===")
X, Y = batch(1, torch.Generator().manual_seed(0))
print(f" x: {X[0].tolist()}")
print(f" y: {['-' if t < 0 else t for t in Y[0].tolist()]}")
print(" attention x_{t-1} ni olib keladi; 'mod 10 yig'indi' esa har token ichidagi hisob")
variantlar = [("1 attention", 1, False), ("3 attention", 3, False),
("1 attention + FFN", 1, True)]
nat = {}
for nom, n_att, ffn in variantlar:
nat[nom] = [orgat(n_att, ffn, s) for s in range(3)]
print("\n=== 2. Aniqlik (400 qadam, 3 seed) ===")
print(f" {'model':<19} {'parametr':>8} {'seedlar':<20} o'rtacha")
for nom, r in nat.items():
a = [x[0] for x in r]
print(f" {nom:<19} {r[0][1]:>8} {' '.join(f'{v:.3f}' for v in a):<20} "
f"{np.mean(a):>8.3f}")
print(f" tasodif 1/{V} = {1 / V:.3f}")
ffn_a = np.array([x[0] for x in nat["1 attention + FFN"]])
for nom in ["1 attention", "3 attention"]:
f = ffn_a - np.array([x[0] for x in nat[nom]])
se = f.std(ddof=1) / np.sqrt(len(f))
print(f" FFN - ({nom}): {f.mean():+.4f}, SE {se:.4f}, "
f"sezilarli: {abs(f.mean()) > 2 * se}")
print("\n=== 3. Parametr ulushi (d_ff = 4d) ===")
for d in (32, 768):
att, ffn = 4 * d * d + 4 * d, 2 * d * 4 * d + 4 * d + d
print(f" d = {d:>4}: attention {att:>9}, FFN {ffn:>9}, FFN ulushi {ffn / (att + ffn):.1%}")
print("\n=== 4. Bir token uchun FLOP, bitta blok (d = 768, d_ff = 3072) ===")
print(f" {'L':>6} {'proyeksiya':>11} {'QK^T, wV':>10} {'FFN':>10} {'FFN ulushi':>11}")
for L_ in (128, 512, 1536, 4096):
p, s, f = floplar(768, L_, 3072)
print(f" {L_:>6} {p / 1e6:>10.1f}M {s / 1e6:>9.1f}M {f / 1e6:>9.1f}M "
f"{f / (p + s + f):>11.1%}")
print(" skor qismi L ga proporsional; L = 4d = 3072 da FFN bilan tenglashadi")
print(" ⭐ Attention ma'lumotni pozitsiyalar orasida tashiydi, FFN har pozitsiyada hisoblaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Vazifa: y_t = (x_t + x_{t-1}) mod 10 ===
x: [4, 9, 3, 0, 3, 9, 7, 3, 7, 3, 1, 6]
y: ['-', 3, 2, 3, 3, 2, 6, 0, 0, 0, 4, 7]
attention x_{t-1} ni olib keladi; 'mod 10 yig'indi' esa har token ichidagi hisob
=== 2. Aniqlik (400 qadam, 3 seed) ===
model parametr seedlar o'rtacha
1 attention 5386 0.837 0.764 0.607 0.736
3 attention 13962 0.245 0.987 0.910 0.714
1 attention + FFN 13802 1.000 1.000 1.000 1.000
tasodif 1/10 = 0.100
FFN - (1 attention): +0.2639, SE 0.0678, sezilarli: True
FFN - (3 attention): +0.2861, SE 0.2355, sezilarli: False
=== 3. Parametr ulushi (d_ff = 4d) ===
d = 32: attention 4224, FFN 8352, FFN ulushi 66.4%
d = 768: attention 2362368, FFN 4722432, FFN ulushi 66.7%
=== 4. Bir token uchun FLOP, bitta blok (d = 768, d_ff = 3072) ===
L proyeksiya QK^T, wV FFN FFN ulushi
128 4.7M 0.4M 9.4M 64.9%
512 4.7M 1.6M 9.4M 60.0%
1536 4.7M 4.7M 9.4M 50.0%
4096 4.7M 12.6M 9.4M 35.3%
skor qismi L ga proporsional; L = 4d = 3072 da FFN bilan tenglashadi
⭐ Attention ma'lumotni pozitsiyalar orasida tashiydi, FFN har pozitsiyada hisoblaydiNatija tahlili. Vazifada har pozitsiyada ikki qo'shni tokenning 10 ga bo'lgandagi qoldiq yig'indisini topish kerak. Attention x_{t-1} ni joriy pozitsiyaga olib kela oladi, lekin "mod 10 yig'indi" — 100 ta holatli jadval, uni chiziqli o'qish qatlami bilan ifodalab bo'lmaydi. Natija: bitta attention qatlami 0.736 (seedlar 0.607–0.837) — tasodifdan (0.100) ancha yuqori, chunki softmax va LayerNorm ham biroz nochiziqlilik beradi. FFN qo'shilganda uch seedda ham 1.000; farq +0.2639, SE 0.0678 — sezilarli. Parametr sonini tenglashtirish uchun 3 ta attention qatlami (13962 parametr, FFN li modelda 13802) ham sinaldi: o'rtacha 0.714, lekin seedlar orasida 0.245 dan 0.987 gacha sochilgan. FFN bilan farq +0.2861, lekin SE 0.2355 — 3 seed bilan 2 × SE qoidasi bo'yicha sezilarli emas. Halol xulosa: attention-only stek ba'zan vazifani deyarli yechadi, lekin ishonchsiz; FFN esa har safar mukammal natija berdi. 3- va 4-bo'limlar FFN ning narxini ko'rsatadi: parametrlarning 66.7% i, d = 768 va L = 512 da hisobning 60.0% i. Faqat juda uzun ketma-ketlikda (L = 4096) skor qismi ustun keladi va FFN ulushi 35.3% ga tushadi.
Nima ko'rsatdi: 2.4, 2.6-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"nn.TransformerEncoderLayer — qora quti" |
Og'irliklarni ko'chirsak, o'z blokimiz bilan farq 0.0e+00 |
| "Bosh sonini oshirsa parametr ko'payadi" | d = h * d_h; parametr h ga bog'liq emas |
| "Parametrlarning ko'pi attention da" | ~2/3 qismi FFN da (66.7%) |
| "Pre-LN va post-LN — ta'm masalasi" | 12 blokda post-LN 0.060, pre-LN 1.000 (bir xil lr) |
| "2 blokda farq yo'q — demak chuqurda ham yo'q" | 2 blokda ikkalasi 1.000, farq faqat chuqurlikda ochiladi |
| "Residual — faqat gradient uchun" | Usiz tokenlar bir xil vektorga aylanadi (cos 1.000) |
| "Attention o'zi yetadi, FFN ixtiyoriy" | 4-misolda FFN siz ishonchsiz (0.245–0.987), FFN bilan 1.000 |
| "Loss tushmasa — ma'lumotda xato" | Post-LN chuqur stekda xabarsiz qotib qoladi |
6. Keng tarqalgan xatolar va yechimlari
1. Pre-LN da oxirgi LayerNorm yo'q
return self.chiq(x) # ⚠️
return self.chiq(self.ln_oxirgi(x)) # ✅2. Residual noto'g'ri joyda
x = self.ln1(x); x = x + self.att(x) # asl x yo'qoldi # ⚠️
x = x + self.att(self.ln1(x)) # pre-LN # ✅3. Dropout residual yig'indiga
x = self.drop(x + self.att(self.ln1(x))) # ⚠️
x = x + self.drop(self.att(self.ln1(x))) # ✅4. batch_first unutilgan
nn.TransformerEncoderLayer(d, h, d_ff) # kutadi (L, B, d) # ⚠️
nn.TransformerEncoderLayer(d, h, d_ff, batch_first=True) # ✅5. Chuqur post-LN, katta lr, warmup siz
nn.TransformerEncoderLayer(d, h, d_ff, norm_first=False) # 12 qatlam, lr 6e-3 # ⚠️
nn.TransformerEncoderLayer(d, h, d_ff, norm_first=True) # yoki warmup # ✅6. Baholashda dropout yoqilgan
with torch.no_grad(): p = model(X) # train rejimida # ⚠️
model.eval()
with torch.no_grad(): p = model(X) # ✅7. Parametrlarni tekshirmaslik
print("model tayyor") # ⚠️
assert sum(p.numel() for p in blok.parameters()) == formula(d, d_ff) # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 21-qism (o'tilgan):
nn.Module, LayerNorm, dropout,model.eval() - 24.2–24.4-darslar (o'tilgan): self-attention, multi-head attention va pozitsion kodlash — blokning qismlari
- 18-qism (o'tilgan): bir necha seed, juftlashgan farq va SE
- Keyingi darslar: encoder da bloklar stek qilinadi va pooling qo'shiladi; decoder blokida kauzal niqob va cross-attention qo'shiladi; BERT va GPT — shu blokning turli stekkalari
- Katta til modellari qismida: RMSNorm, SwiGLU kabi FFN variantlari va yuzlab bloklik modellarni o'rgatish
8. Eng yaxshi amaliyotlar
Standart tanlov — pre-LN va oxirgi LayerNorm.
Parametrlarni formula bilan hisoblang va
sum(p.numel())bilan tekshiring.O'z blokingizni tayyor modul bilan og'irlik ko'chirib solishtiring.
Arxitektura o'zgarishini sayoz va chuqur stekda alohida sinang.
O'rgatish boshida bloklar bo'yicha gradient normasini chop eting.
Loss
ln(V)da qotib qolsa — avval arxitektura (LN joyi, lr, warmup), keyin ma'lumot.Dropout ni faqat qism-blok chiqishiga va attention og'irliklariga qo'llang.
Taqqoslashlarda bir necha seed va juftlashgan farq + SE.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # post-LN qism-blok formulasi?
2. # pre-LN qism-blok formulasi?
3. # pre-LN da oxirida nima qo'shiladi?
4. # FFN formulasi va odatdagi d_ff?
5. # d = 64, d_ff = 256 da blok parametrlari?
6. # h = 4 dan h = 8 ga o'tsa parametrlar?
7. # d_ff = 4d da bitta blok taxminan necha d^2?
8. # residual siz 12 blokdan keyin tokenlar o'xshashligi?
9. # dropout nechta joyda?
10. # dx_{l+1}/dx_l residual bilan?
11. # bir token uchun FFN FLOP?
12. # qaysi L da skor qismi FFN ga tenglashadi?Javoblar
x = LN(x + f(x))x = x + f(LN(x))- Yakuniy LayerNorm
W_2 act(W_1 x + b_1) + b_2,d_ff = 4d4*64^2 + 2*64*256 + 9*64 + 256 = 49984- O'zgarmaydi
- ~`12 d^2`
- Deyarli bir xil (2-misolda cos
1.000) - To'rtta: attention og'irliklari, attention chiqishi, FFN ichi, FFN chiqishi
I + J_l4 d d_ff = 16 d^2L = 4d(d_ff = 4d bo'lsa)
Vazifa 2: Xatolarni tuzating
1. def forward(self, x): # pre-LN bo'lishi kerak edi
x = self.ln1(x)
x = x + self.att(x)
x = self.ln2(x)
return x + self.ffn(x)
2. qatlam = nn.TransformerEncoderLayer(64, 4, 256)
y = qatlam(torch.randn(8, 20, 64)) # (B, L, d)
3. x = self.drop(x + self.ffn(self.ln2(x)))
4. stek = nn.TransformerEncoder(nn.TransformerEncoderLayer(
64, 4, 256, batch_first=True, norm_first=True), 6)
y = self.chiq(stek(x))
5. p = model(X_test).argmax(-1) # o'rgatishdan keyin darholJavoblar
1. def forward(self, x):
x = x + self.att(self.ln1(x))
return x + self.ffn(self.ln2(x))
2. qatlam = nn.TransformerEncoderLayer(64, 4, 256, batch_first=True)
3. x = x + self.drop(self.ffn(self.ln2(x)))
4. stek = nn.TransformerEncoder(nn.TransformerEncoderLayer(
64, 4, 256, batch_first=True, norm_first=True), 6,
norm=nn.LayerNorm(64), enable_nested_tensor=False)
5. model.eval()
with torch.no_grad():
p = model(X_test).argmax(-1)Vazifa 3: Blok noldan
Modellang:
- Attention, FFN, residual, LayerNorm
- Pre-LN va post-LN rejimi
nn.TransformerEncoderLayerga og'irlik ko'chirish- Parametr formulasi
Vazifa 4: Residual
Modellang:
residualbayrog'i- Blok kirishidagi gradient
- Tokenlar kosinusi
- O'rgatish natijasi
Vazifa 5: LN joyi
Modellang:
- 2 va 12 blok
- Pre-LN va post-LN, bir xil lr
- Loss tarixi
- Warmup qo'shib post-LN ni qayta sinash
Vazifa 6: FFN
Modellang:
- Mod yig'indi vazifasi
- Attention-only va FFN li model
- Parametrlarni tenglashtirish
- FLOP jadvali
Vazifa 7: O'ylash
Hamkasbingiz aytdi: "Asl maqoladagi post-LN ni 2 blokda sinadim — pre-LN bilan natija bir xil, 1.000 ga 1.000. Demak farq yo'q, asl maqolaga sodiq qolib 24 blokli modelni post-LN bilan quramiz. Bundan tashqari, parametrlarni tejash uchun FFN ni olib tashlab, o'rniga yana attention qatlamlari qo'yamiz — Transformer baribir 'attention is all you need' ku." Siz nima deysiz?
Javob
Qisqa javob: ikkala xulosa ham bu darsdagi o'lchovlarga zid. Sayoz stekdagi tenglik chuqur stekka o'tmaydi, FFN esa "ortiqcha" emas.
1. Chuqurlik. 3-misolda 2 blokda ham pre-LN, ham post-LN 1.000 berdi — hamkasbingiz ko'rgan narsa shu. Lekin xuddi shu lr va qadamlarda 12 blokda post-LN 0.060 (tasodif) da qoldi, pre-LN esa 1.000. Loss 2.774 da qotib qoldi — xato xabarisiz. 24 blokda bu xavf faqat oshadi.
2. Post-LN ni qutqarish mumkin, lekin qo'shimcha narx bilan: kichikroq lr, uzun warmup, ehtiyotkor init. Pre-LN bu sozlashlarsiz ishladi.
3. FFN. 4-misolda FFN siz, parametr soni teng bo'lgan 3 ta attention qatlami o'rtacha 0.714 berdi, seedlar orasida 0.245 dan 0.987 gacha. FFN li model uch seedda ham 1.000. "Attention is all you need" — maqola nomi, arxitektura esa FFN siz emas: blok parametrlarining ~2/3 qismi aynan FFN da.
4. Nima qilish kerak:
# 1. Arxitekturani MAQSADLI chuqurlikda sinash (24 blok), 2 blokda emas
# 2. pre-LN va post-LN (+ warmup) - bir necha seed, juftlashgan farq
# 3. Bloklar bo'yicha gradient normasi va loss tarixini kuzatish
# 4. FFN ni olib tashlash g'oyasini ham shu tarzda o'lchashHamkasbga javob: "2 blokda farq yo'qligi 24 blok haqida hech narsa demaydi — bizning tajribada 12 blokdayoq post-LN o'rganmay qoldi. Pre-LN dan boshlaymiz. FFN ni esa qoldiramiz: usiz model ishonchsiz bo'ldi, parametrlar tejalmadi ham."
Nimani mustahkamlaydi: 2.2, 2.3, 2.4-bo'limlar.
Xulosa
Bu darsda Transformer blokini noldan yig'dik, uni tayyor modul bilan solishtirdik va har bir qismining rolini o'lchadik.
Eng muhim uch fikr:
Blok — oddiy formula, qora quti emas. 1-misolda parametrlar formulasi
4d^2 + 2 d d_ff + 9d + d_ffo'z blokimiz vann.TransformerEncoderLayeruchun aynan bir xil49984ni berdi, BERT-base o'lchamida esa bir blok7 087 872va 12 blok85.1mln. Og'irliklarni ko'chirgandan keyin o'z blokimiz tayyor modul bilan ham post-LN, ham pre-LN rejimida0.0e+00farq berdi. Parametrlarning66.2%i FFN da, LayerNorm da atigi0.5%.Residual va LN joyi — chuqur stekni o'rgatish sharti. 2-misolda residual siz 12 blokli stekda birinchi blok kirishidagi gradient oxirgisidan 9 barobar kichik bo'ldi (nisbat
0.11, residual bilan1.11), tokenlar esa ikkinchi blokdan keyinoq bir xil vektorga aylandi (cos1.000); natija0.061— tasodif. 3-misolda lr0.006va warmup siz 2 blokda pre-LN ham, post-LN ham1.000berdi, 12 blokda esa post-LN0.060da qoldi (farq+0.9403, SE0.0018) — loss2.774da xabarsiz qotib qoldi.FFN — har pozitsiyadagi hisob. 4-misolda "(joriy + oldingi) mod 10" vazifasida bitta attention qatlami
0.736berdi, FFN qo'shilganda uch seedda ham1.000(farq+0.2639, SE0.0678). Parametrlari teng 3 qatlamli attention-only model o'rtacha0.714va0.245–0.987oralig'ida sochildi — farq2 × SEichida bo'lsa ham, u ishonchsiz. FFN parametrlarning66.7%ini vaL = 512da hisobning60.0%ini egallaydi.
Keyingi darsda Transformer encoder: embedding, pozitsiya va bir necha blokni to'liq modelga yig'amiz, [CLS] va o'rtacha pooling ni solishtiramiz, padding niqobini to'g'ri qo'llaymiz va encoder ni o'zbekcha sharhlar klassifikatsiyasida TF-IDF va EmbeddingBag bazaviylari bilan halol taqqoslaymiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!