kdirgul commited on
Commit
14ee509
·
verified ·
1 Parent(s): 0e5c273

faz7_rag: RAG pipeline (retrieve+compress+inject, demo corpus)

Browse files
Files changed (1) hide show
  1. code/kod/faz7_rag.py +296 -0
code/kod/faz7_rag.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Faz 7 — RAG (retrieval-augmented generation) — SmartCore V1. (Basamak 2)
3
+
4
+ Pipeline: doküman → chunk → embed → index | soru → embed → top-k ara →
5
+ bağlamı SFT şablonunun '### Girdi:' alanına enjekte → v1-instruct cevaplar.
6
+ Amaç: 177M'in olgusal halüsinasyonunu retrieval ile çöz (ezberden değil, getirerek).
7
+
8
+ Generator: HybridLM gömülü (model-bağımsız). Embedding: sentence-transformers (çok dilli TR+EN).
9
+ Index: numpy cosine (prototip; ölçek için FAISS/turbovec sonra). 2048 bağlam → bütçe-kırpma
10
+ (REFRAG/headroom sıkıştırma = sonraki sürüm).
11
+
12
+ Ortam: Colab GPU + mamba-og fork (wheel) + `pip install sentence-transformers`.
13
+ Kullanım:
14
+ HF_TOKEN=hf_xxx python faz7_rag.py --demo --query "Türkiye'nin başkenti neresi?"
15
+ HF_TOKEN=hf_xxx python faz7_rag.py --docs /content/dokumanlar # interaktif
16
+ """
17
+ import os, sys, glob, argparse
18
+ import torch, torch.nn as nn, torch.nn.functional as F
19
+ from functools import partial
20
+
21
+ try:
22
+ from mamba_ssm.modules.block import Block
23
+ from mamba_ssm.modules.mamba3 import Mamba3
24
+ from mamba_ssm.modules.mlp import GatedMLP
25
+ from mamba_ssm.ops.triton.layer_norm import RMSNorm
26
+ except Exception as e:
27
+ sys.exit(f"[hata] mamba-og fork yok ({e!r}). Önce wheel kurulum hücresini çalıştır (CUDA).")
28
+
29
+
30
+ # ───────────── model (faz3_train ile birebir) ─────────────
31
+ def _rms(x, w, eps=1e-5):
32
+ return (x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + eps)) * w
33
+
34
+
35
+ def _rot_half(x):
36
+ a, b = x.chunk(2, -1)
37
+ return torch.cat((-b, a), -1)
38
+
39
+
40
+ class GQAMixer(nn.Module):
41
+ def __init__(self, dim, n_heads=12, n_kv=3, base=10000.0, layer_idx=None, device=None, dtype=None):
42
+ super().__init__()
43
+ self.nh, self.nkv, self.hd = n_heads, n_kv, dim // n_heads
44
+ self.rep = n_heads // n_kv
45
+ fk = {"device": device, "dtype": dtype}
46
+ self.q_proj = nn.Linear(dim, n_heads * self.hd, bias=False, **fk)
47
+ self.k_proj = nn.Linear(dim, n_kv * self.hd, bias=False, **fk)
48
+ self.v_proj = nn.Linear(dim, n_kv * self.hd, bias=False, **fk)
49
+ self.out_proj = nn.Linear(n_heads * self.hd, dim, bias=False, **fk)
50
+ self.qn = nn.Parameter(torch.ones(self.hd, **fk))
51
+ self.kn = nn.Parameter(torch.ones(self.hd, **fk))
52
+ self.register_buffer(
53
+ "inv", 1.0 / (base ** (torch.arange(0, self.hd, 2, device=device).float() / self.hd)),
54
+ persistent=False)
55
+
56
+ def _rope(self, x, T):
57
+ f = torch.outer(torch.arange(T, device=x.device, dtype=torch.float32), self.inv)
58
+ e = torch.cat((f, f), -1)
59
+ return (x * e.cos()[None, None] + _rot_half(x) * e.sin()[None, None]).to(x.dtype)
60
+
61
+ def forward(self, x, **kw):
62
+ B, T, _ = x.shape
63
+ q = self.q_proj(x).view(B, T, self.nh, self.hd).transpose(1, 2)
64
+ k = self.k_proj(x).view(B, T, self.nkv, self.hd).transpose(1, 2)
65
+ v = self.v_proj(x).view(B, T, self.nkv, self.hd).transpose(1, 2)
66
+ q = _rms(q.float(), self.qn.float()).to(x.dtype)
67
+ k = _rms(k.float(), self.kn.float()).to(x.dtype)
68
+ q, k = self._rope(q, T), self._rope(k, T)
69
+ k = k.repeat_interleave(self.rep, 1)
70
+ v = v.repeat_interleave(self.rep, 1)
71
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
72
+ return self.out_proj(y.transpose(1, 2).contiguous().view(B, T, -1))
73
+
74
+
75
+ class HybridLM(nn.Module):
76
+ def __init__(self, cfg, device=None, dtype=None):
77
+ super().__init__()
78
+ self.cfg = cfg
79
+ self.vocab = cfg["vocab_size"]
80
+ self.scaled_embed = cfg.get("scaled_embed", False)
81
+ d = cfg["d_model"]
82
+ self.embedding = nn.Embedding(self.vocab, d, device=device, dtype=dtype)
83
+ self.layers = nn.ModuleList()
84
+ for i in range(cfg["n_layers"]):
85
+ is_attn = ((i + 1) % cfg["attn_every"] == 0) and i != 0 and i != cfg["n_layers"] - 1
86
+ fk = {"device": device, "dtype": dtype}
87
+ if is_attn:
88
+ mixer_cls = partial(GQAMixer, n_heads=cfg["n_heads"], n_kv=cfg["n_kv_heads"], layer_idx=i, **fk)
89
+ else:
90
+ ssm = dict(d_state=cfg["d_state"], expand=cfg["expand"], headdim=cfg["head_dim"],
91
+ ngroups=cfg["ngroups"], rope_fraction=cfg["rope_fraction"],
92
+ is_outproj_norm=False, is_mimo=cfg["is_mimo"], mimo_rank=cfg["mimo_rank"],
93
+ chunk_size=cfg["chunk_size"])
94
+ mixer_cls = partial(Mamba3, layer_idx=i, **ssm, **fk)
95
+ blk = Block(d, mixer_cls,
96
+ partial(GatedMLP, hidden_features=cfg["d_intermediate"], out_features=d, **fk),
97
+ norm_cls=partial(RMSNorm, eps=1e-5, **fk), fused_add_norm=True, residual_in_fp32=True)
98
+ blk.layer_idx = i
99
+ self.layers.append(blk)
100
+ self.norm_f = RMSNorm(d, eps=1e-5, device=device, dtype=dtype)
101
+ self.lm_head = nn.Linear(d, self.vocab, bias=False, device=device, dtype=dtype)
102
+ self.lm_head.weight = self.embedding.weight
103
+
104
+ def forward(self, ids):
105
+ h = self.embedding(ids)
106
+ if self.scaled_embed:
107
+ h = h * (self.cfg["d_model"] ** 0.5)
108
+ res = None
109
+ for l in self.layers:
110
+ h, res = l(h, res)
111
+ h = self.norm_f((h + res) if res is not None else h)
112
+ return self.lm_head(h.to(self.lm_head.weight.dtype))
113
+
114
+
115
+ # ───────────── tokenizer + checkpoint ─────────────
116
+ def load_tok(token):
117
+ import sentencepiece as spm
118
+ from huggingface_hub import hf_hub_download
119
+ p = hf_hub_download("kdirgul/smartcore-v1", "tokenizer/tokenizer.model", repo_type="model", token=token)
120
+ return spm.SentencePieceProcessor(model_file=p)
121
+
122
+
123
+ def resolve_ckpt(spec, token):
124
+ if os.path.exists(spec):
125
+ return spec
126
+ from huggingface_hub import hf_hub_download
127
+ print(f"[ckpt] HF: {spec}", flush=True)
128
+ return hf_hub_download("kdirgul/smartcore-v1", spec, repo_type="model", token=token)
129
+
130
+
131
+ @torch.no_grad()
132
+ def generate(model, sp, prompt, max_new=160, temperature=0.3, top_k=40, top_p=0.9, rep_penalty=1.2):
133
+ eos = sp.eos_id()
134
+ ids = sp.encode(prompt, out_type=int)
135
+ x = torch.tensor([ids], device="cuda"); out = list(ids)
136
+ for _ in range(max_new):
137
+ with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
138
+ logits = model(x)[0, -1].float()
139
+ if rep_penalty != 1.0:
140
+ for t in set(out):
141
+ logits[t] = logits[t] / rep_penalty if logits[t] > 0 else logits[t] * rep_penalty
142
+ if temperature <= 0:
143
+ nxt = int(logits.argmax())
144
+ else:
145
+ logits = logits / temperature
146
+ if top_k:
147
+ kth = torch.topk(logits, min(top_k, logits.numel())).values[-1]; logits[logits < kth] = -float("inf")
148
+ probs = F.softmax(logits, -1)
149
+ if top_p < 1.0:
150
+ s, si = torch.sort(probs, descending=True); cut = torch.cumsum(s, -1) > top_p
151
+ cut[1:] = cut[:-1].clone(); cut[0] = False; s[cut] = 0
152
+ probs = torch.zeros_like(probs).scatter_(0, si, s); probs /= probs.sum()
153
+ nxt = int(torch.multinomial(probs, 1))
154
+ if nxt == eos:
155
+ break
156
+ out.append(nxt); x = torch.cat([x, torch.tensor([[nxt]], device="cuda")], 1)
157
+ if x.shape[1] >= 2048:
158
+ x = x[:, -2048:]
159
+ return sp.decode([t for t in out[len(ids):] if t != eos])
160
+
161
+
162
+ # ───────────── RAG (saf-python: yerelde test edilebilir) ─────────────
163
+ DEMO_DOCS = [
164
+ "Türkiye'nin başkenti Ankara'dır. Ankara ülkenin ikinci en kalabalık şehridir ve yaklaşık 5,7 milyon nüfusa sahiptir.",
165
+ "İstanbul, Türkiye'nin en kalabalık şehridir; nüfusu yaklaşık 15,5 milyondur. İstanbul iki kıtaya yayılır.",
166
+ "Fotosentez, bitkilerin güneş ışığı, su ve karbondioksiti kullanarak glikoz ve oksijen ürettiği biyokimyasal süreçtir.",
167
+ "SmartCore V1, sıfırdan eğitilmiş 177 milyon parametreli, Mamba-3 ve GQA hibrit mimarili, Türkçe ve İngilizce bir dil modelidir.",
168
+ "The capital of France is Paris, located on the river Seine. Paris is the most populous city in France.",
169
+ "Water boils at 100 degrees Celsius at sea level under one atmosphere of pressure.",
170
+ "The speed of light in vacuum is approximately 299,792 kilometers per second.",
171
+ "Mount Everest is the highest mountain on Earth, with its peak at 8,849 meters above sea level.",
172
+ "Mamba is a state space model (SSM) architecture for sequence modeling, proposed as a linear-time alternative to the Transformer.",
173
+ "Mimar Sinan, Osmanlı İmparatorluğu'nun baş mimarıdır; Süleymaniye ve Selimiye camilerini tasarlamıştır.",
174
+ ]
175
+
176
+
177
+ def chunk_text(text, size=600, overlap=100):
178
+ text = " ".join(text.split())
179
+ out, i = [], 0
180
+ step = max(1, size - overlap)
181
+ while i < len(text):
182
+ seg = text[i:i + size].strip()
183
+ if seg:
184
+ out.append(seg)
185
+ i += step
186
+ return out
187
+
188
+
189
+ def build_rag_prompt(question, hits, max_ctx_chars=2400):
190
+ ctx = ""
191
+ for c, _ in hits:
192
+ if len(ctx) + len(c) + 3 > max_ctx_chars:
193
+ break
194
+ ctx += f"- {c}\n"
195
+ return (f"### Talimat:\nAşağıdaki bağlamı kullanarak soruyu yanıtla. Bağlamda yoksa "
196
+ f"\"bilmiyorum\" de.\n\n### Girdi:\n{ctx.strip()}\n\n### Soru:\n{question}\n\n### Yanıt:\n")
197
+
198
+
199
+ def load_docs(path):
200
+ docs = []
201
+ files = ([path] if os.path.isfile(path)
202
+ else glob.glob(os.path.join(path, "**", "*.txt"), recursive=True)
203
+ + glob.glob(os.path.join(path, "**", "*.md"), recursive=True))
204
+ for f in files:
205
+ with open(f, encoding="utf-8", errors="ignore") as fh:
206
+ docs.append(fh.read())
207
+ return docs
208
+
209
+
210
+ # ───────────── retrieval (embedder gerekir) ─────────────
211
+ def build_index(embedder, chunks):
212
+ return embedder.encode(chunks, normalize_embeddings=True, convert_to_numpy=True,
213
+ batch_size=64, show_progress_bar=False)
214
+
215
+
216
+ def retrieve(embedder, index, chunks, query, k):
217
+ q = embedder.encode([query], normalize_embeddings=True, convert_to_numpy=True)[0]
218
+ sims = index @ q
219
+ top = sims.argsort()[::-1][:k]
220
+ return [(chunks[i], float(sims[i])) for i in top]
221
+
222
+
223
+ def main():
224
+ ap = argparse.ArgumentParser()
225
+ ap.add_argument("--docs", default=None, help="doküman dosyası/dizini (.txt/.md)")
226
+ ap.add_argument("--demo", action="store_true", help="gömülü TR+EN demo korpusu")
227
+ ap.add_argument("--query", default=None, help="boşsa interaktif")
228
+ ap.add_argument("--ckpt", default="sft/epoch_2/ckpt.pt", help="HF yolu | yerel .pt")
229
+ ap.add_argument("--embed_model", default="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
230
+ ap.add_argument("--top_k", type=int, default=3)
231
+ ap.add_argument("--chunk_size", type=int, default=600)
232
+ ap.add_argument("--overlap", type=int, default=100)
233
+ ap.add_argument("--max_ctx_chars", type=int, default=2400)
234
+ ap.add_argument("--temperature", type=float, default=0.3)
235
+ ap.add_argument("--no_rag", action="store_true", help="RAG'sız (kıyas için: bağlam enjekte etme)")
236
+ args = ap.parse_args()
237
+
238
+ assert torch.cuda.is_available(), "CUDA yok (Colab GPU gerekir)."
239
+ torch.set_float32_matmul_precision("high")
240
+ token = os.environ.get("HF_TOKEN")
241
+ try:
242
+ from huggingface_hub import get_token
243
+ token = token or get_token()
244
+ except Exception:
245
+ pass
246
+
247
+ # 1) korpus → chunk → index
248
+ docs = DEMO_DOCS if args.demo else (load_docs(args.docs) if args.docs else None)
249
+ assert docs, "doküman yok: --demo ya da --docs ver."
250
+ chunks = []
251
+ for d in docs:
252
+ chunks += chunk_text(d, args.chunk_size, args.overlap)
253
+ print(f"[rag] {len(docs)} doküman → {len(chunks)} chunk | embed: {args.embed_model}", flush=True)
254
+ from sentence_transformers import SentenceTransformer
255
+ embedder = SentenceTransformer(args.embed_model, device="cuda")
256
+ index = build_index(embedder, chunks)
257
+
258
+ # 2) generator
259
+ sp = load_tok(token)
260
+ st = torch.load(resolve_ckpt(args.ckpt, token), map_location="cpu")
261
+ model = HybridLM(st["cfg"], device="cuda", dtype=torch.bfloat16)
262
+ model.load_state_dict(st["model"], strict=False); model.eval()
263
+ tag = f"sft epoch={st.get('epoch')}" if st.get("sft") else f"base step={st.get('step','?')}"
264
+ print(f"[model] {tag} | {'MIMO' if st['cfg'].get('is_mimo') else 'SISO'}\n", flush=True)
265
+
266
+ def answer(q):
267
+ hits = retrieve(embedder, index, chunks, q, args.top_k)
268
+ prompt = (f"### Talimat:\n{q}\n\n### Yanıt:\n" if args.no_rag
269
+ else build_rag_prompt(q, hits, args.max_ctx_chars))
270
+ ans = generate(model, sp, prompt, temperature=args.temperature)
271
+ return hits, ans
272
+
273
+ def show(q):
274
+ hits, ans = answer(q)
275
+ if not args.no_rag:
276
+ print("[getirilen]")
277
+ for c, s in hits:
278
+ print(f" ({s:.2f}) {c[:90]}")
279
+ print(f"CEVAP: {ans}\n")
280
+
281
+ if args.query:
282
+ show(args.query)
283
+ else:
284
+ print("İnteraktif RAG — soru yaz (boş/çık = quit).")
285
+ while True:
286
+ try:
287
+ q = input("\n> ").strip()
288
+ except (EOFError, KeyboardInterrupt):
289
+ break
290
+ if not q or q.lower() in ("quit", "exit", "çık"):
291
+ break
292
+ show(q)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()