Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Attention narxi: FLOP va xotira
- 2.2. scaled_dot_product_attention: flash va memory-efficient g'oyasi
- 2.3. KV-cache
- 2.4. Parametr soni: 12·L·d^2
- 2.5. Masshtablash qonunlari — o'z tajribamizda
- 2.6. Aralash aniqlik
- 2.7. Gradient checkpointing
- 2.8. LoRA — past rangli adapterlar
- 2.9. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Attention narxi: formula, FLOP sanagich va SDPA xotirasi
- Misol 2 — KV-cache noldan
- Misol 3 — Parametr soni va masshtablash
- Misol 4 — Aralash aniqlik, gradient checkpointing va LoRA
- 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.11-dars: Samaradorlik va masshtab
24-QISM — TRANSFORMERLAR · 11-dars
1. Kirish va motivatsiya
Transformerning kuchi — har pozitsiya boshqa har pozitsiyaga qaray oladi. Uning narxi ham aynan shu yerda: n ta token uchun n × n ta skor. 512 tokenli jumlada bu 262 ming skor, 32 ming tokenli hujjatda — bir milliarddan ko'p, va har qatlam, har bosh uchun alohida. Katta modellarni o'rgatish va ishlatish bo'yicha zamonaviy muhandislikning katta qismi — shu narxni boshqarish.
Bu darsda "qancha turadi?" savoliga raqam bilan javob beramiz — soniyalar bilan emas (ular mashinaga bog'liq), balki FLOP, bayt va parametrlar soni bilan. Attention ning O(n^2) narxini formulada chiqarib, uni PyTorch ning FLOP sanagichi bilan tekshiramiz. F.scaled_dot_product_attention n × n matritsani xotirada saqlamasligini o'lchaymiz. Generatsiyani KV-cache bilan noldan tezlashtirib, natija aynan bir xil qolishini va bajarilgan hisob necha barobar kamayishini sanaymiz. Parametr soni formulasini (12·L·d^2) tekshiramiz va kichik modellar oilasida masshtablash qonunini o'z tajribamizda sinaymiz — halol, natija qanday chiqsa shunday. Oxirida xotirani tejashning uch usuli: aralash aniqlik, gradient checkpointing va LoRA.
Real vaziyat. Jamoa o'z chat-botini 7 milliard parametrli model ustiga qurdi. Test serverida bitta foydalanuvchi bilan hammasi yaxshi ishladi. Ishga tushirilgach, 20 ta parallel suhbatda server xotira yetishmasligidan to'xtadi — vaznlar 14 GB edi va GPU da ulardan keyin 30 GB bo'sh joy qolgan edi. Hech kim KV-cache ni hisoblamagan edi: har suhbat 4096 token kontekst bilan float16 da taxminan 2 GB kesh talab qiladi (2-misoldagi formula), 20 ta suhbat — 40 GB. Muammo modelda emas, hisob-kitob yo'qligida edi.
Bu darsda transformerning hisob va xotira narxini formulalar bilan chiqaramiz va har formulani kod bilan tekshiramiz.
Bu darsda:
- Attention narxi: O(n^2) FLOP va xotira
F.scaled_dot_product_attention: flash va memory-efficient g'oyasi- KV-cache noldan
- Parametr soni:
12·L·d^2 - Masshtablash qonunlari — o'z tajribamizda
- Aralash aniqlik: float16 va bfloat16
- Gradient checkpointing
- LoRA noldan
- Tuzoqlar
ℹ Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Attention narxi: FLOP va xotira
BITTA BLOK FORWARD I, n token, d kenglik (B = 1), matmul FLOP = 2 * m * k * n:
Q, K, V proyeksiyasi 2 * n * d * 3d = 6 n d^2
chiqish proyeksiyasi 2 * n * d * d = 2 n d^2
FFN (d -> 4d -> d) 2 * 2 * n * d * 4d = 16 n d^2
-------------------------------------------- chiziqli qism: 24 n d^2
skorlar Q K^T 2 * n * n * d = 2 n^2 d
A @ V 2 * n * n * d = 2 n^2 d
-------------------------------------------- attention: 4 n^2 d
TENG BO'LADIGAN NUQTA: 4 n^2 d = 24 n d^2 -> n = 6d
d = 768 (BERT-base) -> n = 4608 token
n < 6d: chiziqli qatlamlar qimmatroq; n > 6d: attention ustunlik qiladi
XOTIRA (skor matritsasi, har qatlam): B * h * n^2 * 4 bayt (float32)
1-misol, d=768, h=12: n=512 -> 12.0 MB, n=8192 -> 3.0 GB, n=32768 -> 48.0 GB1-misoldagi jadval buni aniq ko'rsatadi: BERT-base qatlamida n=512 da attention umumiy FLOP ning atigi 10.0% i, n=8192 da 64.0%, n=32768 da 87.7%. n ni 4 barobar oshirsak, chiziqli qism 4 barobar, attention esa 16 barobar qimmatlashadi. Formulani PyTorch ning FlopCounterMode sanagichi bilan tekshirdik — uchala uzunlikda ham sanalgan FLOP formulaga aynan teng chiqdi (masalan n=512 da 117440512).
Qisqa matnlarda (sharhlar, murojaatlar — 20-100 token) attention narxi ahamiyatsiz: hisobning deyarli hammasi chiziqli qatlamlarda. O(n^2) muammo uzun hujjatlar, kod va uzun suhbatlarda boshlanadi.
Transformer blokining narxi 24nd^2 + 4n^2d: qisqa matnda chiziqli qism, uzun matnda (n > 6d) attention hal qiladi.
2.2. scaled_dot_product_attention: flash va memory-efficient g'oyasi
ODDIY (qo'lda) ATTENTION:
S = Q K^T / sqrt(d_h) (B, h, n, n) <- xotirada TO'LIQ
P = softmax(S) (B, h, n, n) <- backward uchun SAQLANADI
O = P V
backward uchun xotira: O(n^2)
FLASH / MEMORY-EFFICIENT G'OYASI:
Q, K, V ni bloklarga bo'lib, softmax ni "onlayn" hisoblash:
har K-blok uchun: joriy maksimum va yig'indini yangilash
(softmax(x) = exp(x - max) / sum - bo'laklab hisoblasa bo'ladi)
n x n matritsa HECH QACHON to'liq yaratilmaydi
backward uchun faqat O va logsumexp (har qator uchun 1 son) saqlanadi
backward da kerakli bloklar QAYTA hisoblanadi
FLOP bir xil (yoki biroz ko'p), xotira O(n), GPU da xotiraga murojaat kam -> tez
PYTORCH DA: F.scaled_dot_product_attention(q, k, v, attn_mask=..., is_causal=...)
mavjud yadrolardan (flash, memory-efficient, oddiy) mosini o'zi tanlaydi
CPU da ham flash uslubidagi yadro bor - 1-misol
1-MISOL (backward uchun saqlangan baytlar, h=4, d_h=16):
n qo'lda SDPA nisbat
128 608.0 KB 130.0 KB 4.7
2048 129.5 MB 2.0 MB 63.8
n 2x oshganda: qo'lda 3.95x, SDPA 2.00xSDPA — alohida "yangi attention" emas, xuddi shu matematik amalning boshqacha tashkil etilgan hisobi. 1-misolda uning chiqishi va gradientlari qo'lda yozilgan attention bilan float32 yaxlitlash darajasida mos keldi (chiqishda 5.4e-07, kauzal niqob bilan gradientda 4.8e-06). Shuning uchun amaliy qoida oddiy: o'z modelingizda softmax(q @ k.T) o'rniga F.scaled_dot_product_attention ishlating — 24.10-darsdagi encoder bloklari shunday qilgan. Bu darsdagi GPT bloki esa ataylab qo'lda yozilgan attention bilan: FLOP sanagichi flash yadroni sanamaydi, bizga esa hisobni ko'rsatish kerak.
Flash attention FLOP ni emas, xotirani O(n^2) dan O(n) ga tushiradi — n × n matritsani bloklab, saqlamasdan hisoblaydi.
2.3. KV-cache
KESHSIZ GENERATSIYA (24.9-darsdagi sodda sikl):
qadam t: model(X[:, :k+t]) - BUTUN prefiks qaytadan
har qadamda eski tokenlarning K, V si QAYTA hisoblanadi (natija o'sha-o'sha!)
modeldan o'tgan pozitsiyalar: k + (k+1) + ... + (k+n-1) ~ O(n^2)
KUZATUV: kauzal niqob tufayli eski tokenning K, V si yangi token qo'shilganda
O'ZGARMAYDI -> ularni saqlab qo'ysa bo'ladi
KESHLI GENERATSIYA:
1. prefiks bir marta: K, V -> kesh (har blok uchun)
2. har qadam: faqat YANGI token -> q, k, v
k, v keshga qo'shiladi: K = cat(K_kesh, k), V = cat(V_kesh, v)
attention: yangi q x butun K -> (1 x t) skor, (t x t) emas
pozitsiya kodlash: boshi = joriy uzunlik - 1 (yangi token pozitsiyasi!)
modeldan o'tgan pozitsiyalar: k + (n - 1) ~ O(n)
2-MISOL (prefiks k=10, 40 belgi):
pozitsiyalar: keshsiz 1180, keshli 49; matmul FLOP 24.7 barobar kam
tokenlar aynan bir xil, logitlar farqi 7.6e-06
NARXI - XOTIRA: 2 (K va V) * L * n * d * bayt (har ketma-ketlik uchun)
GPT-2 small (L=12, d=768, n=1024, float16): 36 MB
7B model (L=32, d=4096, n=4096, float16): 2048 MBKV-cache — generatsiyadagi eng muhim optimallashtirish: u hisobni kvadratdan chiziqliga tushiradi va natijani o'zgartirmaydi. 2-misolda keshsiz va keshli greedy generatsiya bir xil 40 ta belgini chiqardi (tokenlar aynan bir xil: True), logitlar esa faqat float32 yaxlitlash darajasida farq qildi. Bajarilgan matmul FLOP 148970880 dan 6033696 ga — 24.7 barobar kamaydi.
Lekin kesh bepul emas: u xotirani egallaydi va n bilan chiziqli o'sadi. Kirishdagi real vaziyat aynan shu — 7B model uchun bitta 4096 tokenli suhbat keshi float16 da 2048 MB. Shuning uchun katta modellarni ishlatishda kesh hajmini kamaytiruvchi usullar (multi-query va grouped-query attention — K, V boshlarini bo'lishish, kesh kvantlash, sahifalangan kesh) alohida muhandislik sohasi. Ular haqida Katta til modellari qismida gaplashamiz.
KV-cache: hisob O(n^2) → O(n), xotira esa + 2·L·n·d; natija keshsizniki bilan aynan bir xil bo'lishi shart — buni tekshiring.
2.4. Parametr soni: 12·L·d^2
BITTA BLOK (d kenglik, FFN 4d):
qkv: 3d^2 + 3d proj: d^2 + d
ffn1: 4d^2 + 4d ffn2: 4d^2 + d 2 x LayerNorm: 4d
jami: 12 d^2 + 13 d ~ 12 d^2
BUTUN MODEL: L * (12 d^2 + 13 d) + V * d (token emb) + T * d (pozitsiya)
+ 2d (oxirgi LN) + d * V + V (chiqish; bog'langan bo'lsa yo'q)
3-MISOL (sanalgan == formula, uchala konfiguratsiyada):
V=27, d=48, L=2: bloklar 56544, 12*L*d^2 = 55296 (xato 2.2%)
V=1000, d=128, L=6: bloklar 1189632, 12*L*d^2 = 1179648 (xato 0.8%)
MA'LUM MODELLAR (bloklar + token embedding):
BERT-base L=12 d=768: 84.9M + 23.4M = 108.4M (e'lon qilingan ~110M)
GPT-2 small L=12 d=768: 84.9M + 38.6M = 123.5M (e'lon qilingan ~124M)
GPT-2 XL L=48 d=1600: 1474.6M + 80.4M = 1555.0M (e'lon qilingan ~1.5B)Formula katta modellar uchun hayratlanarli aniq: BERT-base va GPT-2 ning e'lon qilingan hajmlaridan farq faqat pozitsiya embeddinglari, bias va LayerNorm lar hisobiga. 13d qo'shimchasi d o'sgan sari ahamiyatsizlashadi — d=48 da 2.2%, d=128 da 0.8%.
Formula ikki amaliy savolga tez javob beradi. Xotira: float32 da har parametr 4 bayt, AdamW esa har parametrga yana 2 ta holat (m, v) saqlaydi — o'rgatishda taxminan 16 bayt × N (vazn, gradient, m, v). 124M model uchun bu ~2 GB, 7B model uchun ~112 GB — aktivatsiyalarsiz. Hisob: o'rgatishda har token uchun taxminan 6N FLOP (forward 2N, backward 4N) — masshtablash qonunlari aynan shu bilan ishlaydi.
N ≈ 12·L·d^2 + embeddinglar — model hajmini, xotirani va o'rgatish narxini bir qatorda baholash imkonini beradi.
2.5. Masshtablash qonunlari — o'z tajribamizda
DA'VO (katta tadqiqotlar, N 10^6 dan 10^10 gacha):
loss(N) ~ (N_c / N)^alfa yetarli ma'lumot va hisob bo'lsa
log-log grafikda TO'G'RI CHIZIQ, alfa ~ 0.07-0.1 (til modellari)
shuningdek ma'lumot (D) va hisob (C ~ 6ND) bo'yicha ham
amaliy xulosa: kichik modellarda o'lchab, kattasining loss ini bashorat qilish
BIZNING TAJRIBA (3-misol):
belgi darajasidagi GPT, L=2, d = 8, 16, 24, 32, 48
bir xil matn, bir xil 250 qadam, bir xil lr
d N (bloklar) val loss
8 1744 1.9800
16 6560 1.6611
24 14448 1.1540
32 25408 0.8357
48 56544 0.4737
umumiy qiyalik -0.407 (SE 0.077)
mahalliy qiyaliklar: -0.133, -0.461, -0.572, -0.709 <- TO'G'RI CHIZIQ EMASNatija "katta model — kichik loss" degan umumiy yo'nalishni tasdiqladi, lekin toza darajali qonun chiqmadi: log-log grafikda chiziq egilgan, mahalliy qiyalik -0.133 dan -0.709 gacha o'zgardi. Buni yashirish yoki "alfa = 0.407" deb e'lon qilish noto'g'ri bo'lardi. Sabablar ma'lum:
- Diapazon kichik. N atigi 1.5 tartibga o'zgardi (1744 dan 56544 gacha); qonunlar 6-7 tartibda o'lchanadi.
- Qat'iy qadamlar. Hamma model 250 qadam o'rgatildi. Qonun "har model o'z hajmiga mos hisob bilan" degan shartda ishlaydi; bu yerda katta modellar kam o'rgatilgan, kichiklari esa sig'im chegarasida — bu ikki rejim aralashgan.
- Juda kichik modellar. d=8 va 4 bosh — har boshga 2 o'lcham; bunday model tilni emas, eng oddiy belgi statistikasini o'rganadi.
- Bitta urug'. Har nuqta bitta o'rgatish natijasi; qiyalikning
SEsi (0.077) faqat to'g'ri chiziqdan chetlanishni aks ettiradi.
Masshtablash qonunlari real va juda foydali — lekin ular ma'lum shartlarda o'lchangan empirik kuzatuvlar. Kichik tajribada ularni "tasdiqladim" deyish uchun diapazon, hisob byudjeti va takrorlar kerak.
Masshtablash qonuni — keng diapazon va to'g'ri hisob byudjetida o'lchangan empirik chiziq; kichik tajribada egri chiqsa, buni halol ayting.
2.6. Aralash aniqlik
bit maks qiymat eps (1 dan keyingi qadam)
float32 32 3.4e38 1.2e-07
float16 16 65504 9.8e-04
bfloat16 16 3.4e38 7.8e-03
4-MISOL: 70000 -> float16: inf, bfloat16: 70144.0
1 + 0.001 -> float16: 1.0009766, bfloat16: 1.0000000
ARALASH ANIQLIK (torch.autocast):
parametrlar va optimizator holati - float32 ("master" nusxa)
matmul va konvolyutsiyalar - float16 / bfloat16 (tez, aktivatsiyalar 2x kichik)
softmax, loss, normlashtirish - float32 da qoladi (autocast o'zi hal qiladi)
float16 + GradScaler: loss ni katta songa ko'paytirib gradient "yo'qolishini" oldini olish
bfloat16: diapazon float32 niki -> scaler kerak emas; aniqlik pastligi o'rgatishda
odatda muammo emas
4-MISOL (4 blokli model, bfloat16 autocast):
loss 3.5084 (float32: 3.5082), saqlangan aktivatsiyalar 55% (baytlarda)Aralash aniqlik: hisob va aktivatsiyalar 16 bitda, vaznlar 32 bitda; float16 ga GradScaler kerak, bfloat16 ga — yo'q.
2.7. Gradient checkpointing
ODDIY BACKWARD: forward dagi har oraliq tenzor (aktivatsiya) saqlanadi
xotira ~ L * (blokdagi aktivatsiyalar)
CHECKPOINT: blok ichidagi aktivatsiyalar SAQLANMAYDI, faqat blok KIRISHI
backward da blok forward i QAYTA bajariladi, keyin gradient
torch.utils.checkpoint.checkpoint(blok, x, use_reentrant=False)
4-MISOL (4 blok, B=8, T=64):
saqlangan tenzor bayt FLOP (forward + backward)
oddiy 113 11310212 419364864
checkpoint 17 723076 520028160
+ blok kirishlari 393216
xotira 9.9%, hisob 1.24x, gradientlar farqi 0.0Checkpointing — sof almashuv: xotirani hisob evaziga sotib olamiz. 4-misolda gradientlar bitma-bit bir xil chiqdi (0.0) — bu approksimatsiya emas. Xotira 9.9% gacha tushdi (blok kirishlari bilan birga), hisob esa 1.24 barobar oshdi: har blok forward i ikki marta bajariladi, chiqish qatlami va loss esa checkpoint dan tashqarida. Bitta GPU ga sig'maydigan batch yoki uzun kontekst bilan o'rgatishda bu ko'pincha yagona yo'l.
Gradient checkpointing: bir xil gradient, ancha kam xotira, taxminan +1 forward hisob.
2.8. LoRA — past rangli adapterlar
G'OYA: fine-tuning dagi vazn o'zgarishi dW "past rangli" deb faraz qilinadi
W' = W + dW, dW = (alfa / r) * B A, B: (d_chiq x r), A: (r x d_kir), r << d
W muzlatilgan; faqat A va B o'rgatiladi
B = 0 dan boshlanadi -> boshida W' = W (model aynan pretrained holatda)
PARAMETRLAR (d x d qatlam uchun): d^2 o'rniga 2 r d
bizning model (d=48, r=4, qkv + proj + ffn da): 6144 / 62428 = 9.8%
katta modelda (d=4096, r=8): bir qatlamda 16.8M o'rniga 65536 - 0.4%
AFZALLIKLAR:
optimizator holati faqat A, B uchun -> xotira keskin kam
asl model o'zgarmaydi -> katastrofik unutish yo'q 24.10-bob, bitta asos + ko'p adapter
inference: W' = W + k B A ni BIRLASHTIRISH -> qo'shimcha hisob yo'q
4-MISOL (sharhlar modeli -> klinika sharhlari, 100 qadam, 3 urug'):
usul o'rgatiladi ulush val loss - to'liq 2*SE
faqat bosh 1372 2.2% 1.3857 +0.9880 0.0100
LoRA r=4 6144 9.8% 0.5700 +0.1723 0.0106
to'liq FT 62428 100.0% 0.3977
moslashdan oldin: 3.2853; birlashtirishdan keyin chiqish farqi 8.1e-064-misolning natijasi ikki tomonlama. LoRA faqat boshni o'rgatishdan ancha yaxshi (0.5700 va 1.3857) va to'liq modelning 9.8% parametrini o'rgatib, moslashdan oldingi 3.2853 loss ning katta qismini yopdi. Lekin to'liq fine-tuning (0.3977) dan sezilarli yomon: farq +0.1723, 2*SE = 0.0106. Bizning 62 ming parametrli modelda r=4 ham d=48 ning sezilarli ulushi, va 100 qadam kam. Katta modellarda (milliardlab parametr) LoRA odatda to'liq fine-tuning ga ancha yaqin keladi — lekin buni har vazifada o'lchash kerak, xuddi shu jadval bilan.
LoRA ning eng katta amaliy afzalligi sifatda emas, tashkiliy: bitta asos model va har mijoz yoki vazifa uchun alohida kichik adapter. Birlashtirilgandan keyin chiqish farqi 8.1e-06 — ya'ni inference da LoRA modeli oddiy model kabi ishlaydi.
LoRA: W + (alfa/r)·B·A, W muzlatilgan, B nol bilan boshlanadi; kam parametr, unutish yo'q, birlashtirsa inference bepul — sifatni esa to'liq fine-tuning bilan juftlashgan taqqoslashda tekshiring.
2.9. Tuzoqlar
Asosiy tuzoqlar: tezlikni soniyalar bilan bitta mashinada o'lchab umumiy xulosa chiqarish (FLOP va baytlarni hisoblang); qisqa matnlarda attention ni optimallashtirishga urinish (hisobning ko'pi chiziqli qatlamlarda); KV-cache da yangi token pozitsiyasini noto'g'ri berish (boshi doim 0 — model har tokenni birinchi pozitsiyada deb o'ylaydi); keshli va keshsiz natijani solishtirmaslik; kesh xotirasini rejalashtirmaslik; 12·L·d^2 ga embeddinglarni qo'shishni unutish (kichik modelda ular ko'pchilik bo'lishi mumkin); AdamW holatini xotira hisobiga kiritmaslik; masshtablash qonunini 1-2 tartibli diapazonda va qat'iy qadamlar bilan "tasdiqlash"; float16 da GradScaler siz o'rgatish; softmax va loss ni qo'lda float16 ga o'tkazish; torch.utils.checkpoint da use_reentrant ni ko'rsatmaslik (ogohlantirish va eski xatti-harakat); dropout li blokni checkpoint qilganda tasodifiy holatni hisobga olmaslik; LoRA da B ni tasodifiy boshlash (model boshidanoq buziladi); LoRA da asl vaznlarni muzlatishni unutish; LoRA uchun to'liq fine-tuning dagi lr ni ishlatish (odatda kattaroq kerak).
3. Tez ma'lumotnoma
import torch
import torch.nn.functional as F
from torch.utils.checkpoint import checkpoint
from torch.utils.flop_counter import FlopCounterMode
# FLOP ni sanash (vaqt emas)
fc = FlopCounterMode(display=False)
with fc:
model(X)
fc.get_total_flops() # blok: 24 n d^2 + 4 n^2 d
# samarali attention (flash / memory-efficient)
o = F.scaled_dot_product_attention(q, k, v, attn_mask=maska, is_causal=True)
# KV-cache: yangi tokenning k, v si keshga qo'shiladi
k = torch.cat([kesh["k"], k], 2); v = torch.cat([kesh["v"], v], 2)
lg = model(t, keshlar, boshi=X.shape[1] - 1) # faqat yangi token
# parametrlar
N = sum(p.numel() for p in model.parameters()) # ~ 12 L d^2 + V d + ...
# aralash aniqlik (CPU: bfloat16; GPU: float16 + torch.amp.GradScaler)
with torch.autocast("cpu", dtype=torch.bfloat16):
loss = loss_fn()
# gradient checkpointing
x = checkpoint(blok, x, use_reentrant=False)
# LoRA
class LoRA(nn.Module): # W x + (alfa/r) B A x
...
yangi.weight += k * B @ A # birlashtirishSamaradorlik va masshtab xulosasi
blok narxi: 24nd^2 (chiziqli) + 4n^2d (attention); teng nuqta n = 6d
SDPA: xotira O(n^2) -> O(n), natija bir xil
KV-cache: hisob O(n^2) -> O(n), xotira 2*L*n*d; natija aynan bir xil
N ~ 12*L*d^2 + embeddinglar; o'rgatish ~ 16 bayt/parametr, 6N FLOP/token
masshtablash qonuni: keng diapazon va mos byudjet kerak; kichik tajribada egri
bfloat16 autocast, checkpoint (bir xil grad), LoRA (kam parametr, unutishsiz)4. Batafsil misollar
Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
Misol 1 — Attention narxi: formula, FLOP sanagich va SDPA xotirasi
"""Attention narxi: O(n^2) FLOP va xotira, formulani sanagich bilan tekshirish, SDPA."""
import math
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.checkpoint import checkpoint
from torch.utils.flop_counter import FlopCounterMode
MAVZU = ["telefon", "kitob", "kafe", "mehmonxona", "avtobus", "dastur", "kurs", "film",
"restoran", "kamera", "taksi", "televizor"]
IJOBIY = ["a'lo", "ajoyib", "zo'r", "yaxshi", "qulay", "chiroyli", "tez", "sifatli",
"mazali", "toza", "arzon", "ishonchli"]
SALBIY = ["yomon", "sekin", "qimmat", "iflos", "noqulay", "xunuk", "sifatsiz", "bemaza",
"eski", "zerikarli", "ishonchsiz", "buzuq"]
DUM = {1: ["hammaga tavsiya qilaman", "yana kelaman", "juda mamnunman"],
0: ["hech kimga tavsiya qilmayman", "boshqa kelmayman", "vaqtim behuda ketdi"]}
def sharhlar(seed, n, mavzu=MAVZU, dum=DUM):
"""Sintetik sharhlar matni (belgi darajasida til modeli uchun)."""
rng = np.random.default_rng(seed)
qatorlar = []
for _ in range(n):
q = int(rng.integers(2))
s = [mavzu[int(rng.integers(len(mavzu)))], ["juda", "ancha", ""][int(rng.integers(3))],
(IJOBIY if q else SALBIY)[int(rng.integers(12))], ["ekan", "edi", ""][int(rng.integers(3))]]
if rng.random() < 0.6:
s.append(dum[q][int(rng.integers(3))])
qatorlar.append(" ".join(" ".join(s).split()))
return " . ".join(qatorlar) + " . "
def qolda_attention(q, k, v, kauzal=False):
"""Oddiy attention: (B, h, n, n) skor matritsasi xotirada TO'LIQ yaratiladi."""
skor = q @ k.transpose(-1, -2) / math.sqrt(q.shape[-1])
if kauzal:
n, m = skor.shape[-2:]
maska = torch.ones(n, m, dtype=torch.bool).tril(m - n)
skor = skor.masked_fill(~maska, float("-inf"))
return torch.softmax(skor, -1) @ v
class Blok(nn.Module):
"""Pre-LN decoder bloki; ixtiyoriy KV-cache (kesh lug'ati)."""
def __init__(self, d, h):
super().__init__()
self.h = h
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.qkv, self.proj = nn.Linear(d, 3 * d), nn.Linear(d, d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
def forward(self, x, kesh=None):
B, T, d = x.shape
q, k, v = self.qkv(self.ln1(x)).view(B, T, 3, self.h, d // self.h).permute(2, 0, 3, 1, 4)
if kesh is not None:
if "k" in kesh: # oldingi tokenlarning K, V si
k = torch.cat([kesh["k"], k], 2)
v = torch.cat([kesh["v"], v], 2)
kesh["k"], kesh["v"] = k, v
a = qolda_attention(q, k, v, kauzal=True)
x = x + self.proj(a.transpose(1, 2).reshape(B, T, d))
return x + self.ffn(self.ln2(x))
class GPT(nn.Module):
def __init__(self, V, d=48, L=2, h=4, T=64):
super().__init__()
self.emb, self.poz = nn.Embedding(V, d), nn.Embedding(T, d)
self.bloklar = nn.ModuleList([Blok(d, h) for _ in range(L)])
self.ln = nn.LayerNorm(d)
self.chiqish = nn.Linear(d, V)
def forward(self, X, keshlar=None, boshi=0, ckpt=False):
x = self.emb(X) + self.poz(torch.arange(boshi, boshi + X.shape[1]))
for i, b in enumerate(self.bloklar):
if ckpt:
x = checkpoint(b, x, use_reentrant=False)
else:
x = b(x, None if keshlar is None else keshlar[i])
return self.chiqish(self.ln(x))
def batch(data, g, B=32, T=32):
i = torch.randint(0, len(data) - T - 1, (B,), generator=g).tolist()
return (torch.stack([data[j:j + T] for j in i]),
torch.stack([data[j + 1:j + T + 1] for j in i]))
def orgat(model, data, qadam, lr=3e-3, seed=0, params=None):
params = [p for p in model.parameters() if p.requires_grad] if params is None else params
opt = torch.optim.AdamW(params, lr=lr, weight_decay=0.01)
g = torch.Generator().manual_seed(seed)
V = model.chiqish.out_features
for _ in range(qadam):
xb, yb = batch(data, g)
loss = F.cross_entropy(model(xb).reshape(-1, V), yb.reshape(-1))
opt.zero_grad()
loss.backward()
opt.step()
model.eval()
return model
@torch.no_grad()
def val_loss(model, X, Y):
return F.cross_entropy(model(X).reshape(-1, model.chiqish.out_features), Y.reshape(-1)).item()
def saqlangan_baytlar(fn):
"""Backward uchun saqlangan tenzorlar: (soni, baytlar)."""
hajm = []
def pack(t):
hajm.append(t.numel() * t.element_size())
return t
with torch.autograd.graph.saved_tensors_hooks(pack, lambda t: t):
natija = fn()
return natija, len(hajm), sum(hajm)
def blok_flop(n, d):
"""Bitta blok forward i (B=1): chiziqli qismlar va attention matmullari."""
chiziqli = 2 * n * d * 3 * d + 2 * n * d * d + 2 * (2 * n * d * 4 * d) # qkv, proj, ffn
attention = 2 * n * n * d + 2 * n * n * d # QK^T va A@V
return chiziqli, attention
def hajm(b):
for birlik, k in [("GB", 2 ** 30), ("MB", 2 ** 20), ("KB", 2 ** 10)]:
if b >= k:
return f"{b / k:.1f} {birlik}"
return f"{b} B"
def main() -> None:
torch.set_num_threads(1)
print("=== 1. Formula: BERT-base o'lchamidagi bitta qatlam (d=768, h=12, B=1) ===")
d, h = 768, 12
print(f" {'n':>6} {'chiziqli GFLOP':>15} {'attention GFLOP':>16} {'attention ulushi':>17} "
f"{'skor matritsasi':>16}")
for n in [128, 512, 2048, 8192, 32768]:
c, a = blok_flop(n, d)
print(f" {n:>6} {c / 1e9:>15.1f} {a / 1e9:>16.1f} {a / (a + c):>17.1%} "
f"{hajm(h * n * n * 4):>16}")
print(f" attention = chiziqli bo'ladigan nuqta: 4n^2 d = 24 n d^2 -> n = 6d = {6 * d}")
print(" n 4 barobar oshsa: chiziqli qism 4x, attention 16x, skor matritsasi 16x")
print("\n=== 2. Formulani FLOP sanagich bilan tekshirish (d=64, h=4) ===")
torch.manual_seed(0)
blok = Blok(64, 4)
print(f" {'n':>5} {'sanagich':>12} {'formula':>12} {'mos':>5}")
for n in [32, 128, 512]:
x = torch.randn(1, n, 64)
fc = FlopCounterMode(display=False)
with fc, torch.no_grad():
blok(x)
c, a = blok_flop(n, 64)
print(f" {n:>5} {fc.get_total_flops():>12} {c + a:>12} "
f"{str(fc.get_total_flops() == c + a):>5}")
print("\n=== 3. Backward uchun saqlanadigan xotira: qo'lda va SDPA (h=4, d_h=16) ===")
print(f" {'n':>5} {'qo`lda':>10} {'SDPA':>10} {'nisbat':>7}".replace("`", "'"))
oldingi = None
for n in [128, 256, 512, 1024, 2048]:
q, k, v = [torch.randn(1, 4, n, 16, requires_grad=True) for _ in range(3)]
_, _, b1 = saqlangan_baytlar(lambda: qolda_attention(q, k, v))
_, _, b2 = saqlangan_baytlar(lambda: F.scaled_dot_product_attention(q, k, v))
print(f" {n:>5} {hajm(b1):>10} {hajm(b2):>10} {b1 / b2:>7.1f}")
if oldingi is not None and n == 2048:
print(f" n 2x oshganda: qo'lda {b1 / oldingi[0]:.2f}x, SDPA {b2 / oldingi[1]:.2f}x")
oldingi = (b1, b2)
print(" SDPA (flash/memory-efficient yadro) n x n matritsani saqlamaydi:")
print(" bloklab hisoblaydi va backward da qayta hisoblaydi")
print("\n=== 4. Natija bir xilmi? ===")
torch.manual_seed(1)
q, k, v = [torch.randn(2, 4, 256, 16, requires_grad=True) for _ in range(3)]
for kauzal in [False, True]:
a1 = qolda_attention(q, k, v, kauzal)
a2 = F.scaled_dot_product_attention(q, k, v, is_causal=kauzal)
g1 = torch.autograd.grad(a1.square().sum(), (q, k, v))
g2 = torch.autograd.grad(a2.square().sum(), (q, k, v))
fg = max((x - y).abs().max().item() for x, y in zip(g1, g2))
print(f" kauzal={str(kauzal):<5} chiqish farqi {(a1 - a2).abs().max().item():.1e} "
f"gradient farqi {fg:.1e} allclose: {torch.allclose(a1, a2, atol=1e-5)}")
print(" farq faqat float32 yaxlitlash darajasida - matematik jihatdan bir xil amal")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Formula: BERT-base o'lchamidagi bitta qatlam (d=768, h=12, B=1) ===
n chiziqli GFLOP attention GFLOP attention ulushi skor matritsasi
128 1.8 0.1 2.7% 768.0 KB
512 7.2 0.8 10.0% 12.0 MB
2048 29.0 12.9 30.8% 192.0 MB
8192 116.0 206.2 64.0% 3.0 GB
32768 463.9 3298.5 87.7% 48.0 GB
attention = chiziqli bo'ladigan nuqta: 4n^2 d = 24 n d^2 -> n = 6d = 4608
n 4 barobar oshsa: chiziqli qism 4x, attention 16x, skor matritsasi 16x
=== 2. Formulani FLOP sanagich bilan tekshirish (d=64, h=4) ===
n sanagich formula mos
32 3407872 3407872 True
128 16777216 16777216 True
512 117440512 117440512 True
=== 3. Backward uchun saqlanadigan xotira: qo'lda va SDPA (h=4, d_h=16) ===
n qo'lda SDPA nisbat
128 608.0 KB 130.0 KB 4.7
256 2.2 MB 260.0 KB 8.6
512 8.4 MB 520.0 KB 16.5
1024 32.8 MB 1.0 MB 32.2
2048 129.5 MB 2.0 MB 63.8
n 2x oshganda: qo'lda 3.95x, SDPA 2.00x
SDPA (flash/memory-efficient yadro) n x n matritsani saqlamaydi:
bloklab hisoblaydi va backward da qayta hisoblaydi
=== 4. Natija bir xilmi? ===
kauzal=False chiqish farqi 5.4e-07 gradient farqi 6.0e-07 allclose: True
kauzal=True chiqish farqi 4.8e-07 gradient farqi 4.8e-06 allclose: True
farq faqat float32 yaxlitlash darajasida - matematik jihatdan bir xil amalNima ko'rsatdi: 2.1, 2.2-bo'limlar. 24nd^2 + 4n^2d formulasi uchala uzunlikda FLOP sanagichi bilan aynan mos keldi. BERT-base qatlamida attention ulushi n=128 da 2.7%, n=32768 da 87.7%. Backward uchun saqlanadigan xotira qo'lda yozilgan attention da n ikki barobar oshganda 3.95 barobar, SDPA da 2.00 barobar o'sdi; n=2048 da farq 63.8 barobar. Natija va gradientlar esa bir xil (farqlar 1e-06 darajasida).
Misol 2 — KV-cache noldan
"""KV-cache noldan: keshsiz va keshli generatsiya - natija bir xil, hisob keskin kam."""
import math
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.checkpoint import checkpoint
from torch.utils.flop_counter import FlopCounterMode
MAVZU = ["telefon", "kitob", "kafe", "mehmonxona", "avtobus", "dastur", "kurs", "film",
"restoran", "kamera", "taksi", "televizor"]
IJOBIY = ["a'lo", "ajoyib", "zo'r", "yaxshi", "qulay", "chiroyli", "tez", "sifatli",
"mazali", "toza", "arzon", "ishonchli"]
SALBIY = ["yomon", "sekin", "qimmat", "iflos", "noqulay", "xunuk", "sifatsiz", "bemaza",
"eski", "zerikarli", "ishonchsiz", "buzuq"]
DUM = {1: ["hammaga tavsiya qilaman", "yana kelaman", "juda mamnunman"],
0: ["hech kimga tavsiya qilmayman", "boshqa kelmayman", "vaqtim behuda ketdi"]}
def sharhlar(seed, n, mavzu=MAVZU, dum=DUM):
"""Sintetik sharhlar matni (belgi darajasida til modeli uchun)."""
rng = np.random.default_rng(seed)
qatorlar = []
for _ in range(n):
q = int(rng.integers(2))
s = [mavzu[int(rng.integers(len(mavzu)))], ["juda", "ancha", ""][int(rng.integers(3))],
(IJOBIY if q else SALBIY)[int(rng.integers(12))], ["ekan", "edi", ""][int(rng.integers(3))]]
if rng.random() < 0.6:
s.append(dum[q][int(rng.integers(3))])
qatorlar.append(" ".join(" ".join(s).split()))
return " . ".join(qatorlar) + " . "
def qolda_attention(q, k, v, kauzal=False):
"""Oddiy attention: (B, h, n, n) skor matritsasi xotirada TO'LIQ yaratiladi."""
skor = q @ k.transpose(-1, -2) / math.sqrt(q.shape[-1])
if kauzal:
n, m = skor.shape[-2:]
maska = torch.ones(n, m, dtype=torch.bool).tril(m - n)
skor = skor.masked_fill(~maska, float("-inf"))
return torch.softmax(skor, -1) @ v
class Blok(nn.Module):
"""Pre-LN decoder bloki; ixtiyoriy KV-cache (kesh lug'ati)."""
def __init__(self, d, h):
super().__init__()
self.h = h
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.qkv, self.proj = nn.Linear(d, 3 * d), nn.Linear(d, d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
def forward(self, x, kesh=None):
B, T, d = x.shape
q, k, v = self.qkv(self.ln1(x)).view(B, T, 3, self.h, d // self.h).permute(2, 0, 3, 1, 4)
if kesh is not None:
if "k" in kesh: # oldingi tokenlarning K, V si
k = torch.cat([kesh["k"], k], 2)
v = torch.cat([kesh["v"], v], 2)
kesh["k"], kesh["v"] = k, v
a = qolda_attention(q, k, v, kauzal=True)
x = x + self.proj(a.transpose(1, 2).reshape(B, T, d))
return x + self.ffn(self.ln2(x))
class GPT(nn.Module):
def __init__(self, V, d=48, L=2, h=4, T=64):
super().__init__()
self.emb, self.poz = nn.Embedding(V, d), nn.Embedding(T, d)
self.bloklar = nn.ModuleList([Blok(d, h) for _ in range(L)])
self.ln = nn.LayerNorm(d)
self.chiqish = nn.Linear(d, V)
def forward(self, X, keshlar=None, boshi=0, ckpt=False):
x = self.emb(X) + self.poz(torch.arange(boshi, boshi + X.shape[1]))
for i, b in enumerate(self.bloklar):
if ckpt:
x = checkpoint(b, x, use_reentrant=False)
else:
x = b(x, None if keshlar is None else keshlar[i])
return self.chiqish(self.ln(x))
def batch(data, g, B=32, T=32):
i = torch.randint(0, len(data) - T - 1, (B,), generator=g).tolist()
return (torch.stack([data[j:j + T] for j in i]),
torch.stack([data[j + 1:j + T + 1] for j in i]))
def orgat(model, data, qadam, lr=3e-3, seed=0, params=None):
params = [p for p in model.parameters() if p.requires_grad] if params is None else params
opt = torch.optim.AdamW(params, lr=lr, weight_decay=0.01)
g = torch.Generator().manual_seed(seed)
V = model.chiqish.out_features
for _ in range(qadam):
xb, yb = batch(data, g)
loss = F.cross_entropy(model(xb).reshape(-1, V), yb.reshape(-1))
opt.zero_grad()
loss.backward()
opt.step()
model.eval()
return model
@torch.no_grad()
def val_loss(model, X, Y):
return F.cross_entropy(model(X).reshape(-1, model.chiqish.out_features), Y.reshape(-1)).item()
def saqlangan_baytlar(fn):
"""Backward uchun saqlangan tenzorlar: (soni, baytlar)."""
hajm = []
def pack(t):
hajm.append(t.numel() * t.element_size())
return t
with torch.autograd.graph.saved_tensors_hooks(pack, lambda t: t):
natija = fn()
return natija, len(hajm), sum(hajm)
@torch.no_grad()
def keshsiz(model, prefiks, n):
"""Har qadamda BUTUN ketma-ketlik qaytadan o'tkaziladi."""
X, logitlar, pozitsiyalar = prefiks.clone(), [], 0
for _ in range(n):
pozitsiyalar += X.shape[1]
lg = model(X)[:, -1]
logitlar.append(lg)
X = torch.cat([X, lg.argmax(-1, keepdim=True)], 1)
return X, torch.stack(logitlar, 1), pozitsiyalar
@torch.no_grad()
def keshli(model, prefiks, n):
"""Prefiks bir marta; keyin har qadamda faqat YANGI token, K va V keshdan."""
keshlar = [{} for _ in model.bloklar]
lg = model(prefiks, keshlar)[:, -1]
X, logitlar, pozitsiyalar = prefiks.clone(), [], prefiks.shape[1]
for _ in range(n):
logitlar.append(lg)
t = lg.argmax(-1, keepdim=True)
X = torch.cat([X, t], 1)
if len(logitlar) < n:
pozitsiyalar += 1
lg = model(t, keshlar, boshi=X.shape[1] - 1)[:, -1]
return X, torch.stack(logitlar, 1), pozitsiyalar, keshlar
def main() -> None:
torch.set_num_threads(1)
matn = sharhlar(0, 4000)
belgilar = sorted(set(matn))
s2i = {c: i for i, c in enumerate(belgilar)}
data = torch.tensor([s2i[c] for c in matn])
print("=== 1. Kichik GPT (belgi darajasida) ===")
torch.manual_seed(0)
model = orgat(GPT(len(belgilar), d=48, L=2, h=4, T=64), data, 400)
print(f" matn {len(data)} belgi, lug'at {len(belgilar)}, "
f"parametrlar {sum(p.numel() for p in model.parameters())}")
print("\n=== 2. Generatsiya: keshsiz va keshli (greedy, 40 belgi) ===")
prefiks = torch.tensor([[s2i[c] for c in "kafe juda "]])
n = 40
X1, L1, p1 = keshsiz(model, prefiks, n)
X2, L2, p2, keshlar = keshli(model, prefiks, n)
print(f" keshsiz: {''.join(belgilar[i] for i in X1[0].tolist())!r}")
print(f" keshli: {''.join(belgilar[i] for i in X2[0].tolist())!r}")
print(f" tokenlar aynan bir xil: {torch.equal(X1, X2)}")
print(f" logitlar max farqi: {(L1 - L2).abs().max().item():.1e}")
print("\n=== 3. Qancha hisob bajarildi ===")
natija = {}
for nom, fn in [("keshsiz", keshsiz), ("keshli", keshli)]:
fc = FlopCounterMode(display=False)
with fc:
fn(model, prefiks, n)
natija[nom] = fc.get_total_flops()
print(f" {'':<9} {'modeldan o`tgan pozitsiyalar':>29} {'matmul FLOP':>13}".replace("`", "'"))
print(f" {'keshsiz':<9} {p1:>29} {natija['keshsiz']:>13}")
print(f" {'keshli':<9} {p2:>29} {natija['keshli']:>13}")
print(f" tejash: {natija['keshsiz'] / natija['keshli']:.1f} barobar kam FLOP")
k = prefiks.shape[1]
print(f" formula: keshsiz sum(k..k+n-1) = {sum(range(k, k + n))}, "
f"keshli k + n - 1 = {k + n - 1}")
print("\n=== 4. Keshning o'zi qancha xotira oladi ===")
kv = sum(t.numel() * t.element_size() for c in keshlar for t in c.values())
print(f" bizning model: {len(keshlar)} blok x (K, V) x {tuple(keshlar[0]['k'].shape)} "
f"= {kv} bayt")
print(f" formula 2 * L * n * d * 4 bayt = {2 * 2 * keshlar[0]['k'].shape[2] * 48 * 4}")
for nom, L, dm, n_ in [("GPT-2 small", 12, 768, 1024), ("7B model", 32, 4096, 4096)]:
b = 2 * L * n_ * dm * 2
print(f" {nom:<12} L={L}, d={dm}, n={n_}, float16: {b / 2 ** 20:.0f} MB "
"bitta ketma-ketlik uchun")
print(" kesh FLOP ni kamaytiradi, lekin xotirani n ga chiziqli oshiradi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Kichik GPT (belgi darajasida) ===
matn 138379 belgi, lug'at 27, parametrlar 62331
=== 2. Generatsiya: keshsiz va keshli (greedy, 40 belgi) ===
keshsiz: "kafe juda a'lo edi . kamera ancha jules a a a ajud"
keshli: "kafe juda a'lo edi . kamera ancha jules a a a ajud"
tokenlar aynan bir xil: True
logitlar max farqi: 7.6e-06
=== 3. Qancha hisob bajarildi ===
modeldan o'tgan pozitsiyalar matmul FLOP
keshsiz 1180 148970880
keshli 49 6033696
tejash: 24.7 barobar kam FLOP
formula: keshsiz sum(k..k+n-1) = 1180, keshli k + n - 1 = 49
=== 4. Keshning o'zi qancha xotira oladi ===
bizning model: 2 blok x (K, V) x (1, 4, 49, 12) = 37632 bayt
formula 2 * L * n * d * 4 bayt = 37632
GPT-2 small L=12, d=768, n=1024, float16: 36 MB bitta ketma-ketlik uchun
7B model L=32, d=4096, n=4096, float16: 2048 MB bitta ketma-ketlik uchun
kesh FLOP ni kamaytiradi, lekin xotirani n ga chiziqli oshiradiNima ko'rsatdi: 2.3-bo'lim. Ikkala usul aynan bir xil 40 belgini chiqardi; keshli usulda modeldan 49 pozitsiya o'tdi (keshsizda 1180), matmul FLOP 24.7 barobar kam. Model kichik va kam o'rgatilgan — "kamera ancha jules" kabi davomlar shundan; bu misolning maqsadi matn sifati emas, tenglik va hisob. Kesh hajmi formulaga aynan mos (37632 bayt).
Misol 3 — Parametr soni va masshtablash
"""Parametr soni formulasi (12*L*d^2) va kichik modellar oilasida masshtablash."""
import math
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.checkpoint import checkpoint
from torch.utils.flop_counter import FlopCounterMode
MAVZU = ["telefon", "kitob", "kafe", "mehmonxona", "avtobus", "dastur", "kurs", "film",
"restoran", "kamera", "taksi", "televizor"]
IJOBIY = ["a'lo", "ajoyib", "zo'r", "yaxshi", "qulay", "chiroyli", "tez", "sifatli",
"mazali", "toza", "arzon", "ishonchli"]
SALBIY = ["yomon", "sekin", "qimmat", "iflos", "noqulay", "xunuk", "sifatsiz", "bemaza",
"eski", "zerikarli", "ishonchsiz", "buzuq"]
DUM = {1: ["hammaga tavsiya qilaman", "yana kelaman", "juda mamnunman"],
0: ["hech kimga tavsiya qilmayman", "boshqa kelmayman", "vaqtim behuda ketdi"]}
def sharhlar(seed, n, mavzu=MAVZU, dum=DUM):
"""Sintetik sharhlar matni (belgi darajasida til modeli uchun)."""
rng = np.random.default_rng(seed)
qatorlar = []
for _ in range(n):
q = int(rng.integers(2))
s = [mavzu[int(rng.integers(len(mavzu)))], ["juda", "ancha", ""][int(rng.integers(3))],
(IJOBIY if q else SALBIY)[int(rng.integers(12))], ["ekan", "edi", ""][int(rng.integers(3))]]
if rng.random() < 0.6:
s.append(dum[q][int(rng.integers(3))])
qatorlar.append(" ".join(" ".join(s).split()))
return " . ".join(qatorlar) + " . "
def qolda_attention(q, k, v, kauzal=False):
"""Oddiy attention: (B, h, n, n) skor matritsasi xotirada TO'LIQ yaratiladi."""
skor = q @ k.transpose(-1, -2) / math.sqrt(q.shape[-1])
if kauzal:
n, m = skor.shape[-2:]
maska = torch.ones(n, m, dtype=torch.bool).tril(m - n)
skor = skor.masked_fill(~maska, float("-inf"))
return torch.softmax(skor, -1) @ v
class Blok(nn.Module):
"""Pre-LN decoder bloki; ixtiyoriy KV-cache (kesh lug'ati)."""
def __init__(self, d, h):
super().__init__()
self.h = h
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.qkv, self.proj = nn.Linear(d, 3 * d), nn.Linear(d, d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
def forward(self, x, kesh=None):
B, T, d = x.shape
q, k, v = self.qkv(self.ln1(x)).view(B, T, 3, self.h, d // self.h).permute(2, 0, 3, 1, 4)
if kesh is not None:
if "k" in kesh: # oldingi tokenlarning K, V si
k = torch.cat([kesh["k"], k], 2)
v = torch.cat([kesh["v"], v], 2)
kesh["k"], kesh["v"] = k, v
a = qolda_attention(q, k, v, kauzal=True)
x = x + self.proj(a.transpose(1, 2).reshape(B, T, d))
return x + self.ffn(self.ln2(x))
class GPT(nn.Module):
def __init__(self, V, d=48, L=2, h=4, T=64):
super().__init__()
self.emb, self.poz = nn.Embedding(V, d), nn.Embedding(T, d)
self.bloklar = nn.ModuleList([Blok(d, h) for _ in range(L)])
self.ln = nn.LayerNorm(d)
self.chiqish = nn.Linear(d, V)
def forward(self, X, keshlar=None, boshi=0, ckpt=False):
x = self.emb(X) + self.poz(torch.arange(boshi, boshi + X.shape[1]))
for i, b in enumerate(self.bloklar):
if ckpt:
x = checkpoint(b, x, use_reentrant=False)
else:
x = b(x, None if keshlar is None else keshlar[i])
return self.chiqish(self.ln(x))
def batch(data, g, B=32, T=32):
i = torch.randint(0, len(data) - T - 1, (B,), generator=g).tolist()
return (torch.stack([data[j:j + T] for j in i]),
torch.stack([data[j + 1:j + T + 1] for j in i]))
def orgat(model, data, qadam, lr=3e-3, seed=0, params=None):
params = [p for p in model.parameters() if p.requires_grad] if params is None else params
opt = torch.optim.AdamW(params, lr=lr, weight_decay=0.01)
g = torch.Generator().manual_seed(seed)
V = model.chiqish.out_features
for _ in range(qadam):
xb, yb = batch(data, g)
loss = F.cross_entropy(model(xb).reshape(-1, V), yb.reshape(-1))
opt.zero_grad()
loss.backward()
opt.step()
model.eval()
return model
@torch.no_grad()
def val_loss(model, X, Y):
return F.cross_entropy(model(X).reshape(-1, model.chiqish.out_features), Y.reshape(-1)).item()
def saqlangan_baytlar(fn):
"""Backward uchun saqlangan tenzorlar: (soni, baytlar)."""
hajm = []
def pack(t):
hajm.append(t.numel() * t.element_size())
return t
with torch.autograd.graph.saved_tensors_hooks(pack, lambda t: t):
natija = fn()
return natija, len(hajm), sum(hajm)
def formula(V, d, L, T):
"""Aniq hisob: bloklar + embeddinglar + oxirgi LayerNorm + chiqish qatlami."""
blok = (3 * d * d + 3 * d) + (d * d + d) + (4 * d * d + 4 * d) + (4 * d * d + d) + 4 * d
return L * blok + V * d + T * d + 2 * d + (d * V + V), L * blok
def main() -> None:
torch.set_num_threads(1)
print("=== 1. Parametr soni: sanash va formula ===")
print(f" {'V':>4} {'d':>4} {'L':>3} {'sanalgan':>10} {'formula':>10} {'bloklar':>9} "
f"{'12*L*d^2':>9} {'xato':>6}")
for V, d, L in [(27, 48, 2), (27, 96, 4), (1000, 128, 6)]:
m = GPT(V, d=d, L=L, h=4, T=64)
jami, blok = formula(V, d, L, 64)
sanalgan = sum(p.numel() for p in m.parameters())
print(f" {V:>4} {d:>4} {L:>3} {sanalgan:>10} {jami:>10} {blok:>9} {12 * L * d * d:>9} "
f"{(blok - 12 * L * d * d) / blok:>6.1%}")
print(" bitta blok: attention 4d^2 (Q, K, V, O) + FFN 8d^2 (d -> 4d -> d) = 12d^2")
print("\n Ma'lum modellar (bloklar 12*L*d^2 + token embedding V*d):")
for nom, L, d, V in [("BERT-base", 12, 768, 30522), ("GPT-2 small", 12, 768, 50257),
("GPT-2 XL", 48, 1600, 50257)]:
b = 12 * L * d * d
print(f" {nom:<12} L={L:>2} d={d:>4}: bloklar {b / 1e6:>6.1f}M + embedding "
f"{V * d / 1e6:>5.1f}M = {(b + V * d) / 1e6:>6.1f}M")
print("\n=== 2. Masshtablash: bir xil ma'lumot va qadamlar, d o'zgaradi (L=2) ===")
matn = sharhlar(0, 6000)
belgilar = sorted(set(matn))
s2i = {c: i for i, c in enumerate(belgilar)}
data = torch.tensor([s2i[c] for c in matn])
n = int(len(data) * 0.9)
oquv, val = data[:n], data[n:]
Xv, Yv = batch(val, torch.Generator().manual_seed(123), B=256)
N, loss = [], []
print(f" {'d':>4} {'N (bloklar)':>12} {'val loss':>9}")
for d in [8, 16, 24, 32, 48]:
torch.manual_seed(0)
m = orgat(GPT(len(belgilar), d=d, L=2, h=4, T=64), oquv, 250)
N.append(formula(len(belgilar), d, 2, 64)[1])
loss.append(val_loss(m, Xv, Yv))
print(f" {d:>4} {N[-1]:>12} {loss[-1]:>9.4f}")
print("\n=== 3. Log-log qiyalik: loss ~ N^(-alfa) ===")
lx, ly = np.log(N), np.log(loss)
(k, b), cov = np.polyfit(lx, ly, 1, cov=True)
print(f" umumiy qiyalik: {k:.3f} (SE {np.sqrt(cov[0, 0]):.3f}) -> alfa = {-k:.3f}")
qiya = np.diff(ly) / np.diff(lx)
for i in range(len(qiya)):
print(f" N {N[i]:>6} -> {N[i + 1]:>6}: mahalliy qiyalik {qiya[i]:+.3f}")
qoldiq = ly - (k * lx + b)
print(f" to'g'ri chiziqdan eng katta chetlanish: {np.abs(qoldiq).max():.3f} (log birlikda)")
if np.ptp(qiya) > 0.2:
print(" mahalliy qiyaliklar bir-biridan ancha farq qiladi - bu diapazonda")
print(" toza darajali qonun yo'q: kichik modellar va qat'iy qadamlar soni")
print(" eslatma: katta tadqiqotlarda N 6-7 tartibga o'zgaradi, bu yerda 1.5 tartib")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Parametr soni: sanash va formula ===
V d L sanalgan formula bloklar 12*L*d^2 xato
27 48 2 62331 62331 56544 55296 2.2%
27 96 4 458907 458907 447360 442368 1.1%
1000 128 6 1455080 1455080 1189632 1179648 0.8%
bitta blok: attention 4d^2 (Q, K, V, O) + FFN 8d^2 (d -> 4d -> d) = 12d^2
Ma'lum modellar (bloklar 12*L*d^2 + token embedding V*d):
BERT-base L=12 d= 768: bloklar 84.9M + embedding 23.4M = 108.4M
GPT-2 small L=12 d= 768: bloklar 84.9M + embedding 38.6M = 123.5M
GPT-2 XL L=48 d=1600: bloklar 1474.6M + embedding 80.4M = 1555.0M
=== 2. Masshtablash: bir xil ma'lumot va qadamlar, d o'zgaradi (L=2) ===
d N (bloklar) val loss
8 1744 1.9800
16 6560 1.6611
24 14448 1.1540
32 25408 0.8357
48 56544 0.4737
=== 3. Log-log qiyalik: loss ~ N^(-alfa) ===
umumiy qiyalik: -0.407 (SE 0.077) -> alfa = 0.407
N 1744 -> 6560: mahalliy qiyalik -0.133
N 6560 -> 14448: mahalliy qiyalik -0.461
N 14448 -> 25408: mahalliy qiyalik -0.572
N 25408 -> 56544: mahalliy qiyalik -0.709
to'g'ri chiziqdan eng katta chetlanish: 0.193 (log birlikda)
mahalliy qiyaliklar bir-biridan ancha farq qiladi - bu diapazonda
toza darajali qonun yo'q: kichik modellar va qat'iy qadamlar soni
eslatma: katta tadqiqotlarda N 6-7 tartibga o'zgaradi, bu yerda 1.5 tartibNima ko'rsatdi: 2.4, 2.5-bo'limlar. Parametr formulasi uchala konfiguratsiyada sanalgan son bilan aynan mos; 12·L·d^2 taxmini bloklar sonidan atigi 0.8-2.2% kam. Masshtablash tajribasida loss d bilan monoton kamaydi (1.9800 → 0.4737), lekin log-log da to'g'ri chiziq chiqmadi — mahalliy qiyaliklar -0.133 dan -0.709 gacha. Kod bu xulosani o'zi chiqardi va sabablarini 2.5-bo'lim tushuntiradi.
Misol 4 — Aralash aniqlik, gradient checkpointing va LoRA
"""Aralash aniqlik, gradient checkpointing va LoRA noldan."""
import copy
import math
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.checkpoint import checkpoint
from torch.utils.flop_counter import FlopCounterMode
MAVZU = ["telefon", "kitob", "kafe", "mehmonxona", "avtobus", "dastur", "kurs", "film",
"restoran", "kamera", "taksi", "televizor"]
IJOBIY = ["a'lo", "ajoyib", "zo'r", "yaxshi", "qulay", "chiroyli", "tez", "sifatli",
"mazali", "toza", "arzon", "ishonchli"]
SALBIY = ["yomon", "sekin", "qimmat", "iflos", "noqulay", "xunuk", "sifatsiz", "bemaza",
"eski", "zerikarli", "ishonchsiz", "buzuq"]
DUM = {1: ["hammaga tavsiya qilaman", "yana kelaman", "juda mamnunman"],
0: ["hech kimga tavsiya qilmayman", "boshqa kelmayman", "vaqtim behuda ketdi"]}
def sharhlar(seed, n, mavzu=MAVZU, dum=DUM):
"""Sintetik sharhlar matni (belgi darajasida til modeli uchun)."""
rng = np.random.default_rng(seed)
qatorlar = []
for _ in range(n):
q = int(rng.integers(2))
s = [mavzu[int(rng.integers(len(mavzu)))], ["juda", "ancha", ""][int(rng.integers(3))],
(IJOBIY if q else SALBIY)[int(rng.integers(12))], ["ekan", "edi", ""][int(rng.integers(3))]]
if rng.random() < 0.6:
s.append(dum[q][int(rng.integers(3))])
qatorlar.append(" ".join(" ".join(s).split()))
return " . ".join(qatorlar) + " . "
def qolda_attention(q, k, v, kauzal=False):
"""Oddiy attention: (B, h, n, n) skor matritsasi xotirada TO'LIQ yaratiladi."""
skor = q @ k.transpose(-1, -2) / math.sqrt(q.shape[-1])
if kauzal:
n, m = skor.shape[-2:]
maska = torch.ones(n, m, dtype=torch.bool).tril(m - n)
skor = skor.masked_fill(~maska, float("-inf"))
return torch.softmax(skor, -1) @ v
class Blok(nn.Module):
"""Pre-LN decoder bloki; ixtiyoriy KV-cache (kesh lug'ati)."""
def __init__(self, d, h):
super().__init__()
self.h = h
self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.qkv, self.proj = nn.Linear(d, 3 * d), nn.Linear(d, d)
self.ffn = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
def forward(self, x, kesh=None):
B, T, d = x.shape
q, k, v = self.qkv(self.ln1(x)).view(B, T, 3, self.h, d // self.h).permute(2, 0, 3, 1, 4)
if kesh is not None:
if "k" in kesh: # oldingi tokenlarning K, V si
k = torch.cat([kesh["k"], k], 2)
v = torch.cat([kesh["v"], v], 2)
kesh["k"], kesh["v"] = k, v
a = qolda_attention(q, k, v, kauzal=True)
x = x + self.proj(a.transpose(1, 2).reshape(B, T, d))
return x + self.ffn(self.ln2(x))
class GPT(nn.Module):
def __init__(self, V, d=48, L=2, h=4, T=64):
super().__init__()
self.emb, self.poz = nn.Embedding(V, d), nn.Embedding(T, d)
self.bloklar = nn.ModuleList([Blok(d, h) for _ in range(L)])
self.ln = nn.LayerNorm(d)
self.chiqish = nn.Linear(d, V)
def forward(self, X, keshlar=None, boshi=0, ckpt=False):
x = self.emb(X) + self.poz(torch.arange(boshi, boshi + X.shape[1]))
for i, b in enumerate(self.bloklar):
if ckpt:
x = checkpoint(b, x, use_reentrant=False)
else:
x = b(x, None if keshlar is None else keshlar[i])
return self.chiqish(self.ln(x))
def batch(data, g, B=32, T=32):
i = torch.randint(0, len(data) - T - 1, (B,), generator=g).tolist()
return (torch.stack([data[j:j + T] for j in i]),
torch.stack([data[j + 1:j + T + 1] for j in i]))
def orgat(model, data, qadam, lr=3e-3, seed=0, params=None):
params = [p for p in model.parameters() if p.requires_grad] if params is None else params
opt = torch.optim.AdamW(params, lr=lr, weight_decay=0.01)
g = torch.Generator().manual_seed(seed)
V = model.chiqish.out_features
for _ in range(qadam):
xb, yb = batch(data, g)
loss = F.cross_entropy(model(xb).reshape(-1, V), yb.reshape(-1))
opt.zero_grad()
loss.backward()
opt.step()
model.eval()
return model
@torch.no_grad()
def val_loss(model, X, Y):
return F.cross_entropy(model(X).reshape(-1, model.chiqish.out_features), Y.reshape(-1)).item()
def saqlangan_baytlar(fn):
"""Backward uchun saqlangan tenzorlar: (soni, baytlar)."""
hajm = []
def pack(t):
hajm.append(t.numel() * t.element_size())
return t
with torch.autograd.graph.saved_tensors_hooks(pack, lambda t: t):
natija = fn()
return natija, len(hajm), sum(hajm)
class LoRA(nn.Module):
"""W x + (alfa/r) * B A x; W muzlatilgan, A va B o'rgatiladi, B = 0 dan boshlanadi."""
def __init__(self, asl, r=4, alfa=8):
super().__init__()
self.asl = asl
for p in self.asl.parameters():
p.requires_grad_(False)
self.A = nn.Parameter(torch.randn(r, asl.in_features) / math.sqrt(asl.in_features))
self.B = nn.Parameter(torch.zeros(asl.out_features, r))
self.k = alfa / r
def forward(self, x):
return self.asl(x) + (x @ self.A.T @ self.B.T) * self.k
def birlashtir(self):
"""Inference uchun: W' = W + k * B A - qo'shimcha hisobsiz oddiy Linear."""
yangi = copy.deepcopy(self.asl)
with torch.no_grad():
yangi.weight += self.k * self.B @ self.A
return yangi
def lora_qosh(model, r=4):
for p in model.parameters():
p.requires_grad_(False)
for b in model.bloklar:
b.qkv, b.proj = LoRA(b.qkv, r), LoRA(b.proj, r)
b.ffn[0], b.ffn[2] = LoRA(b.ffn[0], r), LoRA(b.ffn[2], r)
return model
def main() -> None:
torch.set_num_threads(1)
print("=== 1. Aralash aniqlik: float16 va bfloat16 ===")
for nom, dt in [("float32", torch.float32), ("float16", torch.float16),
("bfloat16", torch.bfloat16)]:
katta = torch.tensor(70000.0).to(dt).item()
kichik = (torch.tensor(1.0, dtype=dt) + torch.tensor(1e-3, dtype=dt)).item()
print(f" {nom:<9} {torch.finfo(dt).bits:>2} bit 70000 -> {katta:<8} "
f"1 + 0.001 = {kichik:<10.7f} eps {torch.finfo(dt).eps:.1e}")
print(" float16: diapazon tor (maks 65504) -> gradient scaling kerak")
print(" bfloat16: diapazon float32 niki, aniqlik past -> scaling kerak emas")
matn = sharhlar(0, 5000)
shifo = ["shifoxona", "poliklinika", "dorixona", "shifokor", "hamshira", "laboratoriya"]
shifo_dum = {1: ["davolanishdan mamnunman", "tez tuzaldim", "rahmat shifokorlarga"],
0: ["davolanish foyda bermadi", "soatlab kutdim", "boshqa bormayman"]}
yangi_matn = sharhlar(1, 3000, shifo, shifo_dum)
belgilar = sorted(set(matn + yangi_matn))
s2i = {c: i for i, c in enumerate(belgilar)}
A = torch.tensor([s2i[c] for c in matn])
B_ = torch.tensor([s2i[c] for c in yangi_matn])
V = len(belgilar)
torch.manual_seed(0)
model = GPT(V, d=48, L=4, h=4, T=64)
xb, yb = batch(A, torch.Generator().manual_seed(0), B=8, T=64)
def loss_fn(ckpt=False):
return F.cross_entropy(model(xb, ckpt=ckpt).reshape(-1, V), yb.reshape(-1))
l32, _, b32 = saqlangan_baytlar(loss_fn)
with torch.autocast("cpu", dtype=torch.bfloat16):
l16, _, b16 = saqlangan_baytlar(loss_fn)
print(f" autocast(bfloat16): loss {l16.item():.4f} (float32: {l32.item():.4f}), "
f"saqlangan aktivatsiyalar {b16 / b32:.0%} (baytlarda)")
print(f" parametrlar turi autocast ichida ham: {next(model.parameters()).dtype}")
print("\n=== 2. Gradient checkpointing (4 blok, B=8, T=64) ===")
natija = {}
for ckpt in [False, True]:
model.zero_grad()
fc = FlopCounterMode(display=False)
with fc:
loss, soni, bayt = saqlangan_baytlar(lambda: loss_fn(ckpt))
loss.backward()
natija[ckpt] = (loss.item(), soni, bayt, fc.get_total_flops(),
[p.grad.clone() for p in model.parameters()])
print(f" {'':<14} {'loss':>8} {'saqlangan tenzor':>17} {'bayt':>10} {'FLOP (f+b)':>12}")
for ckpt, nom in [(False, "oddiy"), (True, "checkpoint")]:
l, s, b, f, _ = natija[ckpt]
print(f" {nom:<14} {l:>8.4f} {s:>17} {b:>10} {f:>12}")
farq = max((a - b).abs().max().item() for a, b in zip(natija[False][4], natija[True][4]))
print(f" gradientlar max farqi: {farq}")
kirishlar = len(model.bloklar) * xb.numel() * 48 * 4 # har blok kirishi (B, T, d)
jami = natija[True][2] + kirishlar
print(f" checkpoint har blok KIRISHINI ham ushlab turadi: {kirishlar} bayt")
print(f" xotira {jami} / {natija[False][2]} = {jami / natija[False][2]:.1%}, "
f"hisob {natija[True][3] / natija[False][3]:.2f}x (forward qayta hisoblanadi)")
print("\n=== 3. LoRA: 'sharhlar' da o'rgatilgan modelni 'klinika sharhlari' ga moslash ===")
torch.manual_seed(0)
asos = orgat(GPT(V, d=48, L=2, h=4, T=64), A, 300)
n = int(len(B_) * 0.8)
Xv, Yv = batch(B_[n:], torch.Generator().manual_seed(7), B=256)
print(f" moslashdan oldin val loss (yangi domen): {val_loss(asos, Xv, Yv):.4f}")
usullar = [("faqat bosh", 3e-3), ("LoRA r=4", 1e-2), ("to'liq FT", 3e-3)]
natija = {}
for s in range(3):
for nom, lr in usullar:
torch.manual_seed(s)
m = copy.deepcopy(asos)
if nom == "LoRA r=4":
lora_qosh(m)
elif nom == "faqat bosh":
for p in m.parameters():
p.requires_grad_(False)
for p in m.chiqish.parameters():
p.requires_grad_(True)
orgat(m, B_[:n], 100, lr=lr, seed=s)
natija[(nom, s)] = val_loss(m, Xv, Yv)
if s == 0:
natija[nom] = (sum(p.numel() for p in m.parameters() if p.requires_grad),
sum(p.numel() for p in asos.parameters()), m)
toliq = np.array([natija[("to'liq FT", s)] for s in range(3)])
print(f" {'usul':<11} {'o`rgatiladi':>11} {'ulush':>7} {'val loss':>9} {'- to`liq':>9} "
f"{'2*SE':>7}".replace("`", "'"))
for nom, _ in usullar:
a = np.array([natija[(nom, s)] for s in range(3)])
d = a - toliq
se = d.std(ddof=1) / np.sqrt(3)
o, j, _ = natija[nom]
qator = f" {nom:<11} {o:>11} {o / j:>7.1%} {a.mean():>9.4f}"
if nom != "to'liq FT":
qator += f" {d.mean():>+9.4f} {2 * se:>7.4f}"
print(qator)
lora = np.array([natija[("LoRA r=4", s)] for s in range(3)])
bosh = np.array([natija[("faqat bosh", s)] for s in range(3)])
if (bosh - lora).mean() > 2 * (bosh - lora).std(ddof=1) / np.sqrt(3):
print(" LoRA faqat boshdan sezilarli yaxshi")
if (lora - toliq).mean() > 2 * (lora - toliq).std(ddof=1) / np.sqrt(3):
print(" lekin to'liq fine-tuning ga yetmadi (bu kichik modelda, 100 qadamda)")
m = natija["LoRA r=4"][2]
with torch.no_grad():
oldin = m(Xv[:4])
for b in m.bloklar:
b.qkv, b.proj = b.qkv.birlashtir(), b.proj.birlashtir()
b.ffn[0], b.ffn[2] = b.ffn[0].birlashtir(), b.ffn[2].birlashtir()
keyin = m(Xv[:4])
print(f" birlashtirilgandan keyin parametrlar {sum(p.numel() for p in m.parameters())}, "
f"chiqish farqi {(oldin - keyin).abs().max().item():.1e}")
print(" asl model vaznlari o'zgarmadi: har domen uchun faqat A, B ni saqlash yetadi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Aralash aniqlik: float16 va bfloat16 ===
float32 32 bit 70000 -> 70000.0 1 + 0.001 = 1.0010000 eps 1.2e-07
float16 16 bit 70000 -> inf 1 + 0.001 = 1.0009766 eps 9.8e-04
bfloat16 16 bit 70000 -> 70144.0 1 + 0.001 = 1.0000000 eps 7.8e-03
float16: diapazon tor (maks 65504) -> gradient scaling kerak
bfloat16: diapazon float32 niki, aniqlik past -> scaling kerak emas
autocast(bfloat16): loss 3.5084 (float32: 3.5082), saqlangan aktivatsiyalar 55% (baytlarda)
parametrlar turi autocast ichida ham: torch.float32
=== 2. Gradient checkpointing (4 blok, B=8, T=64) ===
loss saqlangan tenzor bayt FLOP (f+b)
oddiy 3.5082 113 11310212 419364864
checkpoint 3.5082 17 723076 520028160
gradientlar max farqi: 0.0
checkpoint har blok KIRISHINI ham ushlab turadi: 393216 bayt
xotira 1116292 / 11310212 = 9.9%, hisob 1.24x (forward qayta hisoblanadi)
=== 3. LoRA: 'sharhlar' da o'rgatilgan modelni 'klinika sharhlari' ga moslash ===
moslashdan oldin val loss (yangi domen): 3.2853
usul o'rgatiladi ulush val loss - to'liq 2*SE
faqat bosh 1372 2.2% 1.3857 +0.9880 0.0100
LoRA r=4 6144 9.8% 0.5700 +0.1723 0.0106
to'liq FT 62428 100.0% 0.3977
LoRA faqat boshdan sezilarli yaxshi
lekin to'liq fine-tuning ga yetmadi (bu kichik modelda, 100 qadamda)
birlashtirilgandan keyin parametrlar 62428, chiqish farqi 8.1e-06
asl model vaznlari o'zgarmadi: har domen uchun faqat A, B ni saqlash yetadiNima ko'rsatdi: 2.6, 2.7, 2.8-bo'limlar. bfloat16 autocast loss ni 3.5082 dan 3.5084 ga o'zgartirdi va saqlangan aktivatsiyalarni 55% ga tushirdi. Checkpointing gradientlarni bitma-bit saqlab, xotirani 9.9% ga tushirdi, hisobni 1.24 barobar oshirdi. LoRA 9.8% parametr bilan faqat boshdan sezilarli yaxshi, to'liq fine-tuning dan esa sezilarli yomon chiqdi; birlashtirilgan model LoRA modeli bilan bir xil ishlaydi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Transformer hisobining asosiy qismi — attention" | n < 6d da chiziqli qatlamlar; BERT-base n=512 da attention 10.0% |
| "Flash attention FLOP ni kamaytiradi" | Xotirani O(n^2) → O(n) ga; FLOP deyarli bir xil |
| "KV-cache — taxminiy tezlashtirish" | Natija aynan bir xil (2-misol: tokenlar True) |
| "KV-cache bepul" | Xotira 2·L·n·d; 7B model, 4096 token — 2048 MB |
| "Parametrlar = vaznlar xotirasi" | O'rgatishda gradient va AdamW holati bilan ~16 bayt/parametr |
| "Masshtablash qonuni har tajribada to'g'ri chiziq beradi" | 3-misolda qiyalik -0.133 dan -0.709 gacha egildi |
| "Checkpointing gradientni taxminiy qiladi" | Gradientlar farqi 0.0; faqat hisob 1.24x |
| "LoRA to'liq fine-tuning bilan bir xil sifat beradi" | Bu yerda +0.1723 loss ga yomon; o'lchash kerak |
6. Keng tarqalgan xatolar va yechimlari
1. Tezlikni soniyada o'lchab xulosa
t0 = time.time(); model(X); print(time.time() - t0) # ⚠️ mashinaga bog'liq
fc = FlopCounterMode(display=False) # ✅ FLOP, bayt, parametr
with fc: model(X)2. KV-cache da pozitsiya
lg = model(t, keshlar) # boshi=0 # ⚠️ har token 0-pozitsiyada
lg = model(t, keshlar, boshi=X.shape[1] - 1) # ✅3. Keshli natijani tekshirmaslik
X2 = keshli(model, prefiks, n) # "tezroq ishlayapti" # ⚠️
assert torch.equal(keshsiz(model, prefiks, n)[0], X2[0]) # ✅4. Qo'lda attention katta n da
w = torch.softmax(q @ k.transpose(-1, -2) / d_h ** 0.5, -1); o = w @ v # ⚠️ O(n^2) xotira
o = F.scaled_dot_product_attention(q, k, v, is_causal=True) # ✅5. float16 da scaler siz
with torch.autocast("cuda", dtype=torch.float16): loss = f() # ⚠️ kichik gradientlar 0 ga
loss.backward()
scaler = torch.amp.GradScaler(); scaler.scale(loss).backward() # ✅ (yoki bfloat16)
scaler.step(opt); scaler.update()6. Checkpoint parametrlarisiz
x = checkpoint(blok, x) # ⚠️ use_reentrant ogohlantirishi
x = checkpoint(blok, x, use_reentrant=False) # ✅7. LoRA da B tasodifiy
self.B = nn.Parameter(torch.randn(d_chiq, r)) # ⚠️ boshidan W' != W
self.B = nn.Parameter(torch.zeros(d_chiq, r)) # ✅ boshida aynan pretrained7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 21.6-dars (o'tilgan): Qurilma va aniqlik —
autocastva turlar - 24.2-24.5-darslar (o'tilgan): Self-attention va blok — bu darsdagi formulalarning manbasi
- 24.9-dars (o'tilgan): Generatsiya sikli — KV-cache uni tezlashtiradi
- 24.10-dars (o'tilgan): Katastrofik unutish — LoRA unga javob
- 24.12-dars: Loyihada kichik transformer — parametr soni va hisob byudjeti bazaviylar bilan taqqoslashda hisobga olinadi
- Katta til modellari qismida: Grouped-query attention, kesh kvantlash, uzun kontekst, LoRA va QLoRA bilan katta modellarni moslash
- MLOps va deploy qismida: Inference xarajati, batching, xotira rejalash, adapterlarni boshqarish
8. Eng yaxshi amaliyotlar
Narxni FLOP, bayt va parametrda hisoblang; soniyani faqat o'z mashinangiz uchun ishlating.
Matn uzunliklari taqsimotini bilib oling — n < 6d bo'lsa attention ni emas, chiziqli qatlamlarni o'ylang.
Attention da doim
F.scaled_dot_product_attentionishlating.Generatsiyada KV-cache — va uning natijasini keshsiz bilan solishtiruvchi test.
Xotirani oldindan hisoblang: vaznlar + gradient + optimizator + aktivatsiyalar + kesh.
Masshtablash tajribasida diapazonni keng, byudjetni mos va takrorlarni bir necha qiling; egri chiqsa — yozing.
Xotira yetmasa tartib: bfloat16 autocast → gradient checkpointing → LoRA.
LoRA ni to'liq fine-tuning va faqat bosh bilan juftlashgan taqqoslashda baholang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # bitta blokning chiziqli qismi FLOP formulasi?
2. # attention qismi FLOP formulasi?
3. # d = 1024 da attention qaysi n dan ustun keladi?
4. # n 2 barobar oshsa, skor matritsasi xotirasi necha barobar?
5. # SDPA backward uchun n x n o'rniga nima saqlaydi?
6. # keshsiz generatsiyada k=10, n=40 bo'lsa modeldan nechta pozitsiya o'tadi?
7. # keshli generatsiyada-chi?
8. # L=24, d=1024, n=2048, float16 da KV-cache hajmi?
9. # d=1024, L=24 modelning bloklardagi parametrlari taxminan?
10. # 1B parametrli modelni AdamW bilan float32 da o'rgatish uchun taxminiy xotira?
11. # gradient checkpointing gradientni o'zgartiradimi?
12. # d_kir = d_chiq = 1024, r = 8 LoRA qatlamida nechta o'rgatiladigan parametr?Javoblar
24 n d^2(qkv6nd^2, proj2nd^2, FFN16nd^2)4 n^2 dn > 6d = 6144- 4 barobar
- Chiqish va har qator uchun logsumexp (O(n)); backward da bloklarni qayta hisoblaydi
10 + 11 + ... + 49 = 118010 + 39 = 492 · 24 · 2048 · 1024 · 2 = 201326592bayt = 192 MB12 · 24 · 1024^2 ≈ 302M- ~16 GB (vazn 4 + gradient 4 + AdamW m, v 8 bayt), aktivatsiyalarsiz
- Yo'q — 4-misolda farq
0.0 2 · 8 · 1024 = 16384(to'liq qatlamda1048576)
Vazifa 2: Xatolarni tuzating
1. for _ in range(n):
lg = model(t, keshlar)[:, -1]
t = lg.argmax(-1, keepdim=True)
2. self.B = nn.Parameter(torch.randn(asl.out_features, r) * 0.01)
3. N = 12 * L * d * d
print(f"model {N / 1e6:.1f}M parametr") # V=50000, d=256, L=4
4. x = checkpoint(blok, x)
5. with torch.autocast("cpu", dtype=torch.bfloat16):
model = model.to(torch.bfloat16)
loss = loss_fn()Javoblar
1. for _ in range(n): # pozitsiya har qadamda oshadi
lg = model(t, keshlar, boshi=X.shape[1] - 1)[:, -1]
t = lg.argmax(-1, keepdim=True)
X = torch.cat([X, t], 1)
2. self.B = nn.Parameter(torch.zeros(asl.out_features, r)) # boshida W' = W
3. N = 12 * L * d * d + V * d # 3.1M blok + 12.8M embedding!
# kichik model + katta lug'at: parametrlarning ko'pi embeddingda
4. x = checkpoint(blok, x, use_reentrant=False)
5. with torch.autocast("cpu", dtype=torch.bfloat16): # vaznlar float32 da qoladi
loss = loss_fn()Vazifa 3: Attention narxi
Modellang:
blok_flopni d=64 da n = 64, 128, ..., 1024 uchun hisoblang va attention ulushi grafigini chizing- Multi-head da h ni o'zgartiring — FLOP o'zgaradimi? Skor matritsasi xotirasi-chi?
- Kauzal niqob bilan qo'lda va SDPA — saqlangan baytlar
- FFN kengligi 4d o'rniga 2d — teng bo'lish nuqtasi qanday siljiydi?
Yechim yo'nalishi
h o'zgarganda matmul FLOP o'zgarmaydi (h · n^2 · d_h = n^2 · d), skor matritsasi esa h ga proporsional (h · n^2). FFN 2d bo'lsa chiziqli qism 16nd^2 bo'ladi va teng nuqta 4n^2d = 16nd^2 → n = 4d. 1-misoldagi saqlangan_baytlar ni qolda_attention(q, k, v, kauzal=True) va is_causal=True bilan chaqiring.
Vazifa 4: KV-cache
Modellang:
- Batch bilan generatsiya (B=4 prefiks) — kesh shakli
- Kesh uzunligini cheklash (oxirgi 32 token, "sirpanuvchi oyna") — natija o'zgaradimi?
- Grouped-query g'oyasi: K, V uchun 1 bosh, Q uchun 4 bosh — kesh necha barobar kichrayadi?
- Generatsiya uzunligi 10, 20, 40 da FLOP nisbati
Yechim yo'nalishi
Kesh shakli (B, h, t, d_h). Sirpanuvchi oynada kesh["k"] = k[:, :, -32:] — bu boshqa model (u oynadan tashqarini ko'rmaydi), shuning uchun keshsiz natija bilan farq qilishi kutiladi; uni o'rgatishda ham shunday niqob kerak. Grouped-query da K, V boshlari soni 4 dan 1 ga tushsa, kesh 4 barobar kichrayadi. FLOP nisbati generatsiya uzunligi bilan o'sadi, chunki keshsiz narx kvadratik.
Vazifa 5: Masshtablash
Modellang:
- 3-misolni 3 urug' bilan takrorlang — har d uchun o'rtacha va SE
- Qadamlar sonini N ga proporsional qiling (hisob ~ 6ND) — egri to'g'rilanadimi?
- d o'rniga L ni o'zgartiring (d=32, L=1..6)
loss = L_inf + a * N^(-alfa)niscipy.optimize.curve_fitbilan moslang
Yechim yo'nalishi
Urug'lar bilan har nuqtaning SE sini hisoblang va qiyalikni qayta moslang. Hisob byudjetini tenglashtirish uchun qadam = int(C / (6 * N * B * T)) ko'rinishida tanlang. curve_fit da boshlang'ich qiymatlarni bering (p0=[0.3, 10, 0.5]); L_inf — matnning o'zidagi noaniqlik (sintetik sharhlarda sifat tanlovi tasodifiy, shuning uchun loss noldan yuqorida to'xtaydi). Natija qanday bo'lsa — shunday yozing.
Vazifa 6: Xotirani tejash
Modellang:
- bfloat16 autocast bilan 200 qadam o'rgatish — float32 bilan val loss farqi (3 urug')
- Faqat har ikkinchi blokni checkpoint qilish — xotira va hisob
- LoRA r = 1, 2, 4, 8, 16 — o'rgatiladigan ulush va val loss
- LoRA faqat qkv da va hamma qatlamda
Yechim yo'nalishi
orgat ichida forward va loss ni torch.autocast("cpu", dtype=torch.bfloat16) bilan o'rang. Checkpoint uchun GPT.forward da ckpt and i % 2 == 0. LoRA r bo'yicha jadvalda to'liq fine-tuning bilan juftlashgan farq va 2*SE ni ko'rsating — r oshganda farq kamayishi kutiladi, lekin qanchaga — o'lchang.
Vazifa 7: O'ylash
Menejer so'radi: "Bizda 7B model bor. Mijozlarimiz uzun hujjatlar yuboradi — 32 ming tokengacha. GPU da 80 GB xotira. Bitta GPU da nechta so'rovni parallel xizmat qila olamiz va nimani o'zgartirsak ko'proq bo'ladi?" Siz qanday hisoblaysiz?
Javob
Qisqa javob: hisob ikki qismdan iborat — doimiy (vaznlar) va har so'rovga (KV-cache). Uzun kontekstda cheklov — kesh.
1. Vaznlar. 7B parametr × 2 bayt (float16) = 14 GB. Qoladi: 80 - 14 = 66 GB (aktivatsiyalar va zaxira uchun yana biroz ajratish kerak).
2. Bitta so'rovning keshi. Formula 2 · L · n · d · bayt. 7B model uchun odatda L=32, d=4096: 2 · 32 · 32768 · 4096 · 2 = 16 GB bitta 32 ming tokenli hujjat uchun. Ya'ni bir vaqtda atigi 4 ta shunday so'rov (66 / 16). 4096 tokenli so'rovlar uchun esa 2 GB — 30 dan ortiq.
3. Attention hisobi. n = 32768 da n > 6d = 24576 — prefiks (hujjatni "o'qish") bosqichida attention chiziqli qatlamlardan qimmat. Bu yerda flash attention majburiy: n × n matritsa har bosh uchun 4 GB (float32) bo'lardi.
Nima qilish mumkin:
- Grouped-query attention (agar model shunday o'rgatilgan bo'lsa): 32 ta K, V boshi o'rniga 8 ta — kesh 4 barobar kichik, 16 ta so'rov.
- Kesh kvantlash (8 bit): yana 2 barobar.
- Sahifalangan kesh: har so'rovga maksimal uzunlik uchun emas, haqiqiy uzunlik uchun xotira — o'rtacha hujjat qisqaroq bo'lsa, parallellik ancha oshadi.
- Mahsulot qarori: 32 ming token haqiqatan kerakmi? Hujjatni bo'laklab, faqat tegishli qismlarni berish (qidiruv bilan) — ko'pincha arzonroq va sifatli.
L, d, bayt = 32, 4096, 2
kesh = lambda n: 2 * L * n * d * bayt / 2 ** 30 # GB
bosh_joy = 80 - 14
for n in [4096, 16384, 32768]:
print(n, kesh(n), int(bosh_joy // kesh(n))) # 2.0 GB -> 33, 8.0 -> 8, 16.0 -> 4Menejerga javob: "32 ming tokenli hujjatlar bilan bitta GPU da taxminan 4 ta parallel so'rov — cheklov model emas, har so'rov uchun 16 GB lik kesh. Grouped-query attention li model, kesh kvantlash va hujjatni qidiruv bilan qisqartirish bu sonni bir necha barobar oshiradi. Aniq sonni yuklama testi bilan tasdiqlaymiz."
Nimani mustahkamlaydi: 2.1, 2.3, 2.4, 2.8-bo'limlar.
Xulosa
Bu darsda transformerning hisob va xotira narxini formulalar bilan chiqardik va har birini kod bilan tekshirdik.
Eng muhim uch fikr:
Narx formulasi —
24nd^2 + 4n^2dvaN ≈ 12·L·d^2. 1-misolda blok formulasi FLOP sanagichi bilan aynan mos keldi (n=512 da117440512); BERT-base qatlamida attention ulushi n=512 da10.0%, n=32768 da87.7%— teng bo'lish nuqtasin = 6d = 4608.F.scaled_dot_product_attentionnatijani o'zgartirmay (farq1e-06darajasida) backward xotirasini n=2048 da63.8barobar kamaytirdi. 3-misolda parametr formulasi sanalgan son bilan aynan mos,12·L·d^2esa bloklardan atigi0.8-2.2%farq qildi; GPT-2 small uchun123.5M— e'lon qilingan 124M ga yaqin.KV-cache hisobni kvadratdan chiziqliga tushiradi — natijani o'zgartirmasdan, lekin xotira evaziga. 2-misolda keshli generatsiya keshsiz bilan aynan bir xil tokenlarni chiqardi, modeldan o'tgan pozitsiyalar
1180dan49ga, matmul FLOP24.7barobar kamaydi. Kesh hajmi2·L·n·d·bayt: 7B model va 4096 token uchun float16 da2048 MB— ko'p foydalanuvchili tizimda aynan shu cheklov bo'ladi.Masshtab va tejash — o'lchab, halol. 3-misolda loss model hajmi bilan monoton kamaydi (
1.9800→0.4737), lekin toza darajali qonun chiqmadi — mahalliy qiyaliklar-0.133dan-0.709gacha; buning sabablari (tor diapazon, qat'iy qadamlar) natijaning bir qismi. 4-misolda bfloat16 aktivatsiyalarni55%ga, checkpointing xotirani9.9%ga tushirdi (gradientlar farqi0.0, hisob1.24x). LoRA9.8%parametr bilan faqat boshdan ancha yaxshi (0.5700va1.3857), lekin to'liq fine-tuning dan (0.3977) sezilarli yomon chiqdi; birlashtirilgandan keyin u oddiy model kabi ishlaydi.
Keyingi darsda amaliyot: 24-qismning yakuniy loyihasi — o'zbekcha sharhlarning tonalligini inkor, uzoq bog'lanish va kontrast kabi qiyin holatlar bilan aniqlash; TF-IDF bazaviylari, noldan o'rgatilgan va pretrained kichik transformer bir xil foldlarda juftlashgan taqqoslanadi, test esa faqat oxirida bir marta ochiladi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!