smartcore-v1 / code /kod /hybrid_mamba3.py
kdirgul's picture
kod (data hariç) Colab için
9aed7c4 verified
Raw
History Blame Contribute Delete
6.22 kB
"""
SmartCore V1 — Mamba-3 (SISO) + GQA 5:1 hibrit, 48K EN+TR hedefi.
Referans iskelet: referans_kod/mamba3_minimal.py (saf-PyTorch Mamba-3, Triton'suz).
Bu dosya o modelin üstüne GQA attention mixer'ı ekler ve 5:1 hibrit kurar:
her (attn_every)'inci katmanın SSM mixer'ı yerine GQAttention konur; ilk ve son
katman Mamba kalır (induction head'ler derinlik ortasında oluşsun diye).
Forward arayüzü minimal modelle birebir: mixer(x, h) -> (y, h). GQA, h'i yok sayar
(eğitim/forward yolu); KV-cache'li decode bu dosyada gerekli değil (param + smoke).
CPU'da çalışır (Ryzen 5500). Amaç: hibrit öğreniyor mu + kesin ~180M param-sayımı.
"""
import sys, time, gc
import torch
import torch.nn as nn
import torch.nn.functional as F
sys.path.insert(0, "referans_kod")
import mamba3_minimal as m3
from mamba3_minimal import Mamba3Config, Mamba3LMHeadModel, RMSNorm
torch.manual_seed(0)
torch.set_num_threads(6)
DEV = torch.device("cpu")
def rotate_half(x):
x1, x2 = x.chunk(2, dim=-1)
return torch.cat((-x2, x1), dim=-1)
class GQAttention(nn.Module):
"""Grouped-Query Attention mixer (QK-norm + RoPE, causal). minimal-Mamba3 ile
aynı (y, h) arayüzü. n_heads q-head, n_kv_heads kv-head (GQA sıkıştırma)."""
def __init__(self, d_model, n_heads=12, n_kv_heads=3, rope_base=10000.0, device=None):
super().__init__()
assert d_model % n_heads == 0, "d_model n_heads'e bölünmeli"
assert n_heads % n_kv_heads == 0, "n_heads n_kv_heads'e bölünmeli (GQA)"
self.n_heads, self.n_kv, self.hd = n_heads, n_kv_heads, d_model // n_heads
self.rep = n_heads // n_kv_heads
self.q_proj = nn.Linear(d_model, n_heads * self.hd, bias=False, device=device)
self.k_proj = nn.Linear(d_model, n_kv_heads * self.hd, bias=False, device=device)
self.v_proj = nn.Linear(d_model, n_kv_heads * self.hd, bias=False, device=device)
self.o_proj = nn.Linear(n_heads * self.hd, d_model, bias=False, device=device)
self.q_norm = RMSNorm(self.hd, device=device) # QK-norm (RoPE'den önce)
self.k_norm = RMSNorm(self.hd, device=device)
inv_freq = 1.0 / (rope_base ** (torch.arange(0, self.hd, 2, device=device).float() / self.hd))
self.register_buffer("inv_freq", inv_freq, persistent=False)
def _rope(self, x, T): # x: (B, nh, T, hd)
t = torch.arange(T, device=x.device, dtype=torch.float32)
freqs = torch.outer(t, self.inv_freq) # (T, hd/2)
emb = torch.cat((freqs, freqs), dim=-1) # (T, hd)
cos, sin = emb.cos()[None, None], emb.sin()[None, None]
return (x * cos + rotate_half(x) * sin).to(x.dtype)
def forward(self, x, h=None):
B, T, _ = x.shape
q = self.q_proj(x).view(B, T, self.n_heads, self.hd).transpose(1, 2)
k = self.k_proj(x).view(B, T, self.n_kv, self.hd).transpose(1, 2)
v = self.v_proj(x).view(B, T, self.n_kv, self.hd).transpose(1, 2)
q, k = self.q_norm(q), self.k_norm(k) # QK-norm
q, k = self._rope(q, T), self._rope(k, T)
k = k.repeat_interleave(self.rep, dim=1) # GQA: kv-head'leri çoğalt (CPU-güvenli)
v = v.repeat_interleave(self.rep, dim=1)
y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
y = y.transpose(1, 2).contiguous().view(B, T, -1)
return self.o_proj(y), h
def init_weights(model):
"""SSM-güvenli init (minimal demo.py'den): A_log, D, dt_bias kritik —
constructor bunları set etmiyor; atlanırsa SSM NaN üretir. GQA projeksiyonları
(2D) std=0.02, RMSNorm ağırlıkları (1D) dokunulmaz (default 1.0)."""
for name, p in model.named_parameters():
if "A_log" in name:
torch.nn.init.uniform_(p, -4, -1)
elif "D" in name and p.dim() == 1:
torch.nn.init.ones_(p)
elif "dt_bias" in name:
torch.nn.init.uniform_(p, 0.001, 0.1)
elif p.dim() >= 2:
torch.nn.init.normal_(p, std=0.02)
def build_hybrid(args, attn_every=6, n_heads=12, n_kv_heads=3, device=None):
"""minimal Mamba3LMHeadModel kur, sonra her attn_every'inci katmanın mixer'ını
GQAttention ile değiştir (ilk/son katman Mamba kalır)."""
model = Mamba3LMHeadModel(args, device=device)
attn_idx = []
for i in range(args.n_layer):
if (i + 1) % attn_every == 0 and i != 0 and i != args.n_layer - 1:
model.backbone.layers[i].mixer = GQAttention(
args.d_model, n_heads, n_kv_heads, device=device)
attn_idx.append(i)
init_weights(model) # GQA swap'ten SONRA — tüm parametreler güvenli init'lensin
return model, attn_idx
def n_params(model):
seen, tot = set(), 0
for p in model.parameters():
if id(p) in seen:
continue
seen.add(id(p)); tot += p.numel()
return tot
def make_config(d_model, n_layer, vocab, d_mlp_inner=1500, d_state=128, headdim=64, chunk_size=64):
c = Mamba3Config(d_model=d_model, n_layer=n_layer, d_state=d_state,
headdim=headdim, chunk_size=chunk_size, vocab_size=vocab)
c.d_mlp_inner = d_mlp_inner # referans eğitim reçetesi (1500), minimal'in 2048'i yerine
return c
def param_sweep():
"""180M hedef için 48K vocab'ta katman sweep'i (import edilmez, sadece doğrudan çalıştırınca)."""
print(f" {'n_layer':>7} {'Mamba':>6} {'GQA':>4} {'embed(M)':>9} {'TOPLAM(M)':>10}")
for nlayer in [18, 19, 20, 21, 22]:
c = make_config(d_model=768, n_layer=nlayer, vocab=48000, d_mlp_inner=1500)
mdl, ai = build_hybrid(c, attn_every=6, n_heads=12, n_kv_heads=3, device=DEV)
tot = n_params(mdl) / 1e6
embed_m = c.vocab_size * c.d_model / 1e6
mark = " <== 180M bandi" if 175 <= tot <= 185 else ""
print(f" {nlayer:>7} {nlayer-len(ai):>6} {len(ai):>4} {embed_m:>9.1f} {tot:>10.1f}{mark}")
del mdl; gc.collect()
if __name__ == "__main__":
# Doğrulanan sonuç: n_layer=20 -> 175.5M (17 Mamba+3 GQA), n_layer=21 -> 182.8M.
param_sweep()