Lineer Cebir Refresher: Vektör, Matris, Broadcasting, Einsum — LLM Mühendisinin Matematiksel Dili
Vektör/matris/tensor sezgisi, broadcasting kuralları, dot product, matris çarpımının üç farklı bakış açısı, einsum notasyonu, norm aileleri, attention'da Q@K^T çarpımının matematiksel anatomisi.
Şükrü Yusuf KAYA
38 dakikalık okuma
Başlangıç📐 Modül 1'e hoş geldin
Sonraki 10 ders boyunca bir LLM mühendisinin günde defalarca kullandığı matematiği inşa edeceğiz. Bu ders refresher değil, inşa. Lineer cebiri 'biliyordum' diyenler bile attention mekanizmasını gerçekten matematiksel olarak yapabildiklerinde 'hımm, demek böyleymiş' diyor. 38 dakika sonra Q @ K^T çarpımının her bit'inin nereden geldiğini bileceksin.
Neden Lineer Cebir? Bir LLM'in İç Bakışı#
Llama 3.1 8B modelinin tek bir forward pass'inde tahminen şu işlem yapılır:
- ~3 trilyon floating-point operation (3 TFLOPs)
- Bunun %95'i matris çarpımı (GEMM)
- Bunun %4'ü element-wise activation (SwiGLU, RMSNorm)
- Bunun %1'i softmax, embedding lookup, residual ekleme
Yani: LLM'in tamamı, doğru sırada yapılan matris çarpımlarıdır. Lineer cebir bilmeden LLM mühendisliği yapmak, müzik teorisi bilmeden orkestra şefliği yapmak gibi.
Bu derste şu soruları cevaplayacağız:
- Tensor nedir? PyTorch 'i ile geometrik anlamı arasındaki köprü.
shape - Broadcasting nasıl çalışır? "Shape mismatch" hatalarının kökü.
- Matris çarpımının üç bakışı: satır-sütun, doğrusal dönüşüm, lineer kombinasyon.
- Einsum: Tek satırda istediğin her tensor işlemini ifade etme.
- Norm aileleri: L1, L2, Lp, Frobenius — LoRA, weight decay, gradient clipping hep burada.
- Attention'da : her boyutun ne ifade ettiği.
softmax(QK^T / √d) V
1. Skalerler, Vektörler, Matrisler, Tensorlar#
Skaler (rank-0 tensor): Tek bir sayı. , , .
Vektör (rank-1 tensor): Sıralı sayı dizisi. .
Matris (rank-2 tensor): Sayıların 2D tablosu. .
Tensor (rank-n tensor): n boyutlu sayı dizisi. LLM'lerde sıkça rank-3 ve rank-4 tensorlarla çalışırsın.
5-0.3π[1, 2, 3][[1,2],[3,4]]LLM'de tipik shape'ler#
| Tensor | Shape | Anlamı |
|---|---|---|
| Embedding tablosu | (vocab_size, d_model) | Her token için bir vektör |
| Bir batch input id | (batch, seq_len) | Batch'teki her cümle için token id'leri |
| Hidden states | (batch, seq_len, d_model) | Her batch × token × özellik |
| Attention scores | (batch, heads, seq_len, seq_len) | Her batch × kafa × token-token |
| LoRA matrisi A | (rank, d_in) | Düşük-rank açma |
| LoRA matrisi B | (d_out, rank) | Düşük-rank kapatma |
d_model = 4096heads = 32python
import torch # Skalers = torch.tensor(3.14)print(s.shape) # torch.Size([]) — boş # Vektör (rank-1)v = torch.tensor([1.0, 2.0, 3.0])print(v.shape) # torch.Size([3]) # Matris (rank-2)M = torch.tensor([[1., 2.], [3., 4.], [5., 6.]])print(M.shape) # torch.Size([3, 2]) # Rank-3 tensor — bir mini-batch hidden stateB, T, d = 4, 128, 4096h = torch.randn(B, T, d)print(h.shape) # torch.Size([4, 128, 4096]) # Bellek boyutuprint(f"{h.numel() * 4 / 1024**2:.2f} MB (fp32)") # ~8 MBprint(f"{h.numel() * 2 / 1024**2:.2f} MB (bf16)") # ~4 MBPyTorch tensor'larının shape ve bellek hesabı.
🧠 Shape disiplini — Karpathy ipucu
Bir bug'ı %80 hızlandıran tek alışkanlık: kod yazarken yorumda shape belirt. veya . PyTorch'un 'silent broadcasting' hatalarının çoğunu bu önler. İlerde ile bunu daha da güçlendireceğiz.
# (B, T, d)# x: [B, T, d] -> [B, T, vocab]einops.rearrange2. Broadcasting — "Shape Mismatch" Hatalarının Anatomisi#
PyTorch ve NumPy, farklı shape'li tensorları otomatik genişletip element-wise işlem yapmana izin verir. Buna broadcasting denir.
Broadcasting kuralları (sağdan başlayarak hizala)#
İki shape'i karşılaştır:
- Sağdan eşle her boyutu.
- Boyutlar eşit ise → OK.
- Boyutlardan biri 1 ise → 1 olan diğer tarafa "genişler" (kopyalanmadan, view).
- Eksik boyut varsa → 1 ile doldurulmuş kabul edilir.
- Hiçbiri eşleşmiyorsa → .
RuntimeError
Örnek 1: → ✓
(3, 4) + (4,)A: (3, 4) B: (4) → (1, 4) → (3, 4)
Örnek 2: → ✗ (sağdan eşle: 4 ≠ 3)
Çözüm: → → broadcast .
(3, 4) + (3,)B.unsqueeze(1)(3, 1)(3, 4)Örnek 3: → ✓ (örn. bias eklemek)
(B, T, d) + (d,)A: (B, T, d) B: (d) → (1, 1, d) → (B, T, d)
python
import torch # (B, T, d) + (d,) — RoPE veya bias eklemeB, T, d = 4, 8, 16hidden = torch.randn(B, T, d)bias = torch.randn(d)out = hidden + bias # otomatik broadcast (1,1,d) -> (B,T,d)print(out.shape) # (4, 8, 16) # (B, T, d) + (T, d) — position embedding (her batch için aynı pos enc)pos = torch.randn(T, d)out2 = hidden + pos # (1,T,d) -> (B,T,d)print(out2.shape) # (4, 8, 16) # YANLIŞ: (B, T, d) + (B,) → broadcast başarısıztry: bad = hidden + torch.randn(B)except RuntimeError as e: print("HATA:", e)# RuntimeError: The size of tensor a (16) must match the size of tensor b (4) # DOĞRU: explicit unsqueezebatch_scalar = torch.randn(B)ok = hidden + batch_scalar.view(B, 1, 1) # (B,1,1) -> (B,T,d)print(ok.shape) # (4, 8, 16)Broadcasting'in başarılı ve başarısız örnekleri.
⚠️ Sessiz broadcasting bug'ı
PyTorch (1, T, d) ve (T, 1, d) shape'lerini sessizce (T, T, d)'ye broadcast eder — yani 64 element bekleyen yerde 4096 element gelir. Bu bug çoğu mühendisin defalarca yakalandığı tuzak. Çözüm: shape comment'i + assert + birim test.
3. Dot Product (İç Çarpım) — Üç Bakış#
İki vektörün dot product'ı (iç çarpımı) tek bir sayıdır:
Bu yalın formülün üç önemli yorumu var:
Yorum 1: Cebirsel#
Element-wise çarp, topla.
Yorum 2: Geometrik#
İki vektör arasındaki açıyı ölçer. Aynı yöne bakıyorlarsa pozitif, dik iseler 0, ters yönde iseler negatif. Cosine similarity burada başlar (embedding benzerliği bunun normalize edilmiş hali).
Yorum 3: Projeksiyon#
abbLLM'de niye önemli?#
- Embedding similarity: cosine similarity = dot product (normalize edilmiş)
- Attention score: — sorgu ve anahtar vektörlerin uyumu
score_{ij} = q_i · k_j - Logit: son katmanda — gizli state'in vocab vektörüne projeksiyonu
logit_v = h · W_v
python
import torchimport torch.nn.functional as F a = torch.tensor([1.0, 2.0, 3.0])b = torch.tensor([4.0, -1.0, 2.0]) # Üç eşdeğer yold1 = torch.dot(a, b) # cebirsel — sadece 1Dd2 = (a * b).sum() # element-wise * sonra sumd3 = a @ b # @ operatörü (PyTorch 1D-1D için dot)print(d1, d2, d3) # tensor(8.) hepsi aynı # Geometrik kontrolcos_theta = F.cosine_similarity(a.unsqueeze(0), b.unsqueeze(0))mag_product = a.norm() * b.norm()print(f"Geometric: {cos_theta.item() * mag_product.item():.4f}") # 8.0000 # Attention'da:# Q: (B, T, d), K: (B, T, d) -> scores: (B, T, T)B, T, d = 2, 4, 8Q = torch.randn(B, T, d)K = torch.randn(B, T, d)scores = Q @ K.transpose(-2, -1) # (B, T, T)print(scores.shape) # torch.Size([2, 4, 4])# Her score[b, i, j] = Q[b, i, :] · K[b, j, :] (dot product)Dot product'ın üç yolu + attention bağlamı.
4. Matris Çarpımının Üç Bakışı#
C = A @ BKlasik tanım: = i. satır A × j. sütun B'nin dot product'ı.
C[i, j]Sezgi: Her çıktı elementi, bir input satır ve bir weight sütununun ne kadar "uyduğunu" söyler.
LLM örneği: Embedding lookup'tan sonraki ilk linear: (x: (T, d_in), W: (d_out, d_in)). Her çıktı nörone (j) her token'ın (i) ne kadar ağırlık verdiği.
h = x @ W^Tpython
import torch # Shape uyumu: (m, k) @ (k, n) = (m, n)A = torch.tensor([[1., 2., 3.], [4., 5., 6.]]) # (2, 3)B = torch.tensor([[7., 8.], [9., 10.], [11., 12.]]) # (3, 2)C = A @ B # (2, 2)print(C)# tensor([[ 58., 64.],# [139., 154.]]) # Manuel doğrulama: C[0,0] = 1*7 + 2*9 + 3*11 = 58 ✓ # Batched matmul — LLM'in günlük işiB_size, T, d_in, d_out = 4, 16, 64, 128X = torch.randn(B_size, T, d_in)W = torch.randn(d_out, d_in) # Linear layer: y = X @ W^T (PyTorch nn.Linear böyle yapar)y = X @ W.T # (B_size, T, d_out)print(y.shape) # torch.Size([4, 16, 128]) # FLOP hesabı: 2 * B * T * d_in * d_outflops = 2 * B_size * T * d_in * d_outprint(f"FLOPs: {flops/1e9:.3f} GFLOPs") # 0.001 GFLOPs (küçük) # Llama 8B'de bir tek FFN katmanı için karşılaştır:B_, T_, d_, ff_ = 1, 4096, 4096, 14336flops_real = 2 * B_ * T_ * d_ * ff_print(f"Llama FFN: {flops_real/1e9:.1f} GFLOPs") # 481 GFLOPsMatris çarpımı + LLM'deki gerçek FLOP boyutları.
⏱️ Matris çarpımının zaman karmaşıklığı
Naif algoritma: O(m·n·k). 4096×4096 matris × 4096×4096 matris ≈ 137 milyar çarpma — ama H100 GPU bunu ~3 milisaniyede yapıyor (60+ TFLOPS BF16). LLM yavaşlığı çarpımın kendisinden değil; bellek transferi'nden geliyor (Modül 33'te detaylı). Strassen, Coppersmith-Winograd gibi sub-cubic algoritmalar teoride hızlı ama GPU'da pratik değil — GEMM tile-friendly olduğu için naif sıralı O(n^3) bile çok hızlı.
5. Einsum — Tensor Cebrinin İsviçre Çakısı#
torch.einsumNotasyon kuralları#
Format:
"input1,input2,...->output"- Aynı harf birden çok input'ta → o boyut toplanır (Einstein convention)
- Yalnızca output'ta olmayan harf → o boyut kaldırılır (sum out)
- Output'ta da olan harf → o boyut korunur
Klasik örnekler#
| İşlem | Einsum | Eşdeğer |
|---|---|---|
| Dot product | "i,i->" | (a * b).sum() |
| Outer product | "i,j->ij" | a[:, None] * b[None, :] |
| Matris çarpımı | "ik,kj->ij" | A @ B |
| Transpose | "ij->ji" | A.T |
| Trace (köşegen toplamı) | "ii->" | torch.trace(A) |
| Batched matmul | "bik,bkj->bij" | A @ B |
| Attention scores | "bhid,bhjd->bhij" | Q @ K.transpose(-2,-1) |
| Attention output | "bhij,bhjd->bhid" | attn @ V |
python
import torch # 1) Dot producta = torch.tensor([1., 2., 3.])b = torch.tensor([4., 5., 6.])print(torch.einsum("i,i->", a, b)) # tensor(32.) # 2) Outer productout = torch.einsum("i,j->ij", a, b)print(out)# tensor([[ 4., 5., 6.],# [ 8., 10., 12.],# [12., 15., 18.]]) # 3) Matris çarpımıA = torch.randn(3, 4)B = torch.randn(4, 5)C1 = torch.einsum("ik,kj->ij", A, B)C2 = A @ Bprint(torch.allclose(C1, C2)) # True # 4) Attention scores: (B, H, T, d) Q ile KB, H, T, d = 2, 8, 16, 64Q = torch.randn(B, H, T, d)K = torch.randn(B, H, T, d)V = torch.randn(B, H, T, d) # Scores: Q @ K^T -> (B, H, T, T)scores = torch.einsum("bhid,bhjd->bhij", Q, K) / (d ** 0.5) # Softmax over last dim (key dim)attn = torch.softmax(scores, dim=-1) # Attention output: attn @ V -> (B, H, T, d)out = torch.einsum("bhij,bhjd->bhid", attn, V)print(out.shape) # torch.Size([2, 8, 16, 64]) # Bu üç satır = scaled_dot_product_attention'ın kalbi!Einsum ile attention mekanizmasını tek satırda kurmak.
💡 Einsum performansı
Einsum syntax güzel ama her zaman en hızlı değil. PyTorch arka planda veya 'e mapler ama bazı karmaşık desenler optimal değildir. Production'da kritik path'lerde profile et; (, ) çoğu zaman daha okunaklı ve aynı hızlı. Einsum'u öğrenmek için kullan, prod'da gerekirse ya da 'a dönüştür.
@bmmeinopsrearrangereduce@torch.compile6. Norm Aileleri — Vektör Büyüklüğünün Çeşitleri#
Bir vektörün norm'u, o vektörün "büyüklüğü"dür. Tek bir tanım yok; farklı normlar farklı geometrileri verir.
Önemli normlar#
L1 norm (Manhattan / taxicab):
Mutlak değer toplamı. Köşeli, "L-şeklinde" mesafe.
L2 norm (Euclidean):
Pisagor uzaklığı. En yaygın.
Lp norm (genel):
L∞ norm (maks):
En büyük element.
Frobenius norm (matris için):
Matrisi vektör gibi düşünüp L2 al. LLM'de gradient clipping burada kullanılır.
LLM'de niye önemli?#
- Weight decay (regularization): kayıp fonksiyonuna eklenir → büyük weight'ler cezalandırılır
λ ||W||²₂ - Gradient clipping: belli bir eşiği aşarsa scale et → eğitim stabilitesi
||grad||₂ - Embedding normalization: cosine similarity için L2 normalize
- LoRA: küçük olduğu varsayımı düşük-rank decomposition'ı haklı kılar
||ΔW||_F - Layer normalization vs RMSNorm: ikisi de L2-based
python
import torch x = torch.tensor([3., -4., 0., 5.]) print(torch.norm(x, p=1)) # 12.0 — L1print(torch.norm(x, p=2)) # 7.0710 — L2 (sqrt(9+16+0+25))print(torch.norm(x, p=float('inf'))) # 5.0 — L_inf # Matris için FrobeniusW = torch.randn(4, 4)print(torch.norm(W, p='fro')) # Frobenius = sqrt(sum of squares) # Pratik 1: Gradient clippingimport torch.nn as nnmodel = nn.Linear(10, 5)# ... loss.backward() ...torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)# Tüm gradient'lerin L2 toplam normu 1'i geçerse scale et # Pratik 2: Embedding L2 normalize (cosine similarity için)emb = torch.randn(100, 128) # 100 vektör, 128 boyutemb_norm = emb / emb.norm(dim=-1, keepdim=True)# Şimdi her satırın L2 normu 1print(emb_norm.norm(dim=-1)[:5]) # ~1.0Norm hesaplama + LLM'de pratik kullanım.
7. Bellek Düzeni — Stride, Contiguous, Transpose'un Maliyeti#
Bir tensor sadece "şekil"den ibaret değildir; bellekte nasıl yerleştiği önemli.
Strides (adımlar)#
Bir matris bellekte aslında tek bir 1D array — . ona "şu boyutta ilerlemek için kaç element atla" der.
(3, 4)[m00, m01, m02, m03, m10, m11, ...]stride- contiguous tensor → stride
(3, 4). (Satırlar arası 4, kolonlar arası 1.)(4, 1) - (transpose) → stride
.T. Aynı veri, farklı yorumlama.(1, 4)
Neden önemli?#
Bazı operasyonlar contiguous bellek istiyor. Eğer transpose ettiysen, çağırmak gerekir (yeni bir kopya çıkarır). Bu, maliyetli: O(n²) bellek + zaman.
.contiguous().view().reshape()LLM'de pratik#
- → strides değişir, kopya yok (hızlı)
x.transpose(-2, -1) - → kopya çıkar (yavaş)
x.transpose(-2, -1).contiguous() - → multi-head attention'da head boyutunu öne çekme (genelde kopya gerekli)
x.permute(0, 2, 1, 3)
python
import torch A = torch.arange(12).reshape(3, 4)print(A)print(A.stride()) # (4, 1) — satırlar arası 4, kolonlar 1print(A.is_contiguous()) # True # Transpose: shape değişti ama bellek aynıB = A.Tprint(B.stride()) # (1, 4) — şimdi tam tersiprint(B.is_contiguous()) # False — bellekte hâlâ row-major # view() çalışmaztry: B.view(-1)except RuntimeError as e: print("HATA:", e)# RuntimeError: view size is not compatible with input tensor's size and stride # reshape() veya contiguous() ile düzeltC = B.contiguous().view(-1)print(C.stride()) # (1,) # Multi-head attention'da tipik permuteB, T, H, dh = 2, 8, 4, 16x = torch.randn(B, T, H, dh)# Attention için (B, H, T, dh) lazımx_perm = x.permute(0, 2, 1, 3)print(x_perm.shape, x_perm.is_contiguous()) # (2, 4, 8, 16), False# Sonra .contiguous() veya direkt einsum kullanStride, contiguous, transpose'un bellek anatomisi.
8. Bütün Bunlar Attention'da Nereye Bağlanıyor?#
Şimdi öğrendiğimizi birleştirelim. Tek bir attention katmanının matematiği:
Adım adım:
- (B, T, d) → linear projeksiyon →
X(B, T, d) ← 3 matris çarpımıQ, K, V - →
Q @ K^T(B, T, T) ← batched matrix multiplication (dot product geometric anlamı)scores - → element-wise division (broadcasting)
/√d_k - →
softmax(scores, dim=-1)(B, T, T) ← element-wise exp, sum (broadcast), divideattn - →
attn @ V(B, T, d) ← batched matmul (lineer kombinasyon bakışı)output
Türkçe köşesi: Türkçe bir cümlede her token sözcükten daha küçük (Modül 6 detayında); bu da T'yi büyütür. T büyüdükçe attention'ın bellek tüketimi T² büyür — uzun Türkçe metinlerde RAM patlamasının kökü bu.
python
import torchimport torch.nn.functional as F def scaled_dot_product_attention(Q, K, V, mask=None): """ Q, K, V: (B, H, T, d_head) output: (B, H, T, d_head) """ d_k = Q.size(-1) scores = torch.einsum("bhid,bhjd->bhij", Q, K) / (d_k ** 0.5) if mask is not None: scores = scores.masked_fill(mask == 0, float('-inf')) attn = F.softmax(scores, dim=-1) out = torch.einsum("bhij,bhjd->bhid", attn, V) return out, attn # TestB, H, T, d = 2, 4, 8, 16Q = torch.randn(B, H, T, d)K = torch.randn(B, H, T, d)V = torch.randn(B, H, T, d)out, attn = scaled_dot_product_attention(Q, K, V)print(out.shape, attn.shape) # (2,4,8,16) (2,4,8,8) # attn'in rows softmax → her satır toplamı 1print(attn.sum(dim=-1)[0, 0]) # ~tensor([1., 1., ..., 1.]) # PyTorch'un kendi implementasyonuyla karşılaştırout_torch = F.scaled_dot_product_attention(Q, K, V)print(torch.allclose(out, out_torch, atol=1e-5)) # TrueLineer cebir bilgisi ile attention'ı sıfırdan yazma.
🎯 Bu kavramları tek bir kursta birleştirdiğin ders bu
Vektör → matris → tensor → broadcasting → einsum → norm → attention. 30 dakika önce 'matris çarpımı' soyut bir kavramdı; şimdi senin için Q ile K'nin uyumunu ölçen geometrik bir işlem. Modül 8'de attention'ı daha derin işleyeceğiz; bu temel olmadan oraya gidemezdik.
9. Mini Egzersizler (yapmadan geçme)#
-
Shape detective:= (4, 8, 16, 16),
x= (16, 32).Wçıktısının shape'i nedir?x @ W -
Einsum çevirisi:ne yapıyor? (İpucu: rank-3 input'lar, rank-4 output, hiçbir boyut toplanmıyor.)
torch.einsum("bid,bje->bije", a, b) -
Broadcasting hatası:neden olmuyor? Hangi unsqueeze düzeltir?
(B, T, d) * (B, d) -
LoRA shape: Llama 8B'ninmatrisi (4096, 4096). Rank-16 LoRA için A ve B matrislerinin shape'i ne? Parametre sayısında ne kadar tasarruf?
q_proj -
Frobenius vs L2: Bir (1024, 1024) matrisin Frobenius normunu hesaplamak, onu vektör olarak L2 normunu hesaplamakla aynı mı? Neden?
(Çözümleri aşağıdaki accordion'da.)
Bu Derste Neler Öğrendik?#
✓ Tensor hiyerarşisi: scalar (0) → vector (1) → matrix (2) → tensor (n)
✓ Broadcasting kuralları ve "shape mismatch" tuzakları
✓ Dot product'ın üç bakışı: cebirsel, geometrik (cos θ), projeksiyon
✓ Matris çarpımının üç yorumu: satır-sütun, lineer dönüşüm, lineer kombinasyon
✓ Einsum notasyonu — tensor cebrinin tek satırlık dili
✓ Norm aileleri: L1, L2, Lp, L∞, Frobenius — LoRA, weight decay, grad clipping
✓ Bellek düzeni: stride, contiguous, transpose maliyeti
✓ Attention mekanizması'nın lineer cebir aktarımı
Sıradaki Ders#
1.2 — Matris Ayrıştırmaları: SVD, Eigendecomposition, PCA, LoRA Bağlantısı
Bir matrisin "DNA'sını" öğreneceğiz. SVD'nin LoRA'da nasıl saklı olduğunu görecek, PCA'yı SVD ile sıfırdan kuracağız. Embedding compression ve rank truncation pratikleri.
Sık Sorulan Sorular
Hayır, fonksiyonel olarak aynı. `A @ B` Python operatörü ve PyTorch onu doğrudan `torch.matmul(A, B)`'e çevirir. Tek farkı: `matmul`'ün ek `out=` parametresi var (in-place yazma). Pratikte `@` tercih edilir çünkü kod kısalır. NumPy de aynı pattern'i takip ediyor.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 0: Kurs Çerçevesi ve Atölye Kurulumu
LLM Engineer Kimdir? Junior'dan Staff'a Yapay Zekâ Mühendisliği Kariyer Haritası
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu
Kurs Felsefesi: Neden Bu Yol, Neden Bu Sıra — 8 Aylık Müfredatın İskeleti
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu