| """ |
| Faz 2 — Smoke test (eğitim döngüsü) + Colab-hazır ana iskelet. |
| |
| İki mod: |
| --synthetic : rastgele token batch'leriyle döngüyü doğrula (yerel CPU, network YOK, |
| Faz 1 koşusunu rahatsız etmez). Model+loop+WSD+grad sağlıklı mı. |
| (varsayılan): gerçek on-the-fly mixed streaming (EN/TR/kod/math + decontam) — Colab GPU. |
| |
| Colab (full) örneği: |
| python kod/faz2_smoke.py --d_model 768 --n_layer 20 --seq_len 2048 --steps 200 --device cuda |
| Yerel loop testi: |
| python kod/faz2_smoke.py --synthetic --d_model 256 --n_layer 6 --seq_len 256 --steps 8 --device cpu |
| |
| NOT: minimal (saf-PyTorch) hibrit kullanır → her yerde çalışır. Faz 3 tam-hız için |
| mamba-og fork (Triton SISO) ile aynı mimari; bu script veri+döngü+mimariyi doğrular. |
| """ |
| import os, sys, time, math, argparse, random |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| import torch |
| import torch.nn.functional as F |
|
|
|
|
| |
| def wsd_lr(step, total, peak, floor, warmup_frac=0.02, decay_frac=0.25): |
| warm = max(1, int(total * warmup_frac)) |
| dec_start = int(total * (1 - decay_frac)) |
| if step < warm: |
| return peak * (step + 1) / warm |
| if step < dec_start: |
| return peak |
| t = (step - dec_start) / max(1, total - dec_start) |
| return peak - (peak - floor) * t |
|
|
|
|
| |
| SOURCES = { |
| "en_fineweb_edu": ("HuggingFaceFW/fineweb-edu", "sample-10BT", 0.55), |
| "tr_fineweb2_hq": ("epfml/FineWeb2-HQ", "tur_Latn", 0.22), |
| "code_codeparrot": ("codeparrot/codeparrot-clean", None, 0.13), |
| "math_openwebmath": ("open-web-math/open-web-math", None, 0.10), |
| } |
| TEXT_KEYS = ("text", "content", "code") |
|
|
|
|
| def _text(rec): |
| for k in TEXT_KEYS: |
| v = rec.get(k) |
| if isinstance(v, str) and v: |
| return v |
| for v in rec.values(): |
| if isinstance(v, str) and v: |
| return v |
| return "" |
|
|
|
|
| class MixedStream: |
| """Kaynakları oranlarına göre karıştırıp 2048'lik diziler üretir (decontam'lı).""" |
| def __init__(self, tok, seq_len, decontam_path=None): |
| os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" |
| from datasets import load_dataset |
| self.tok, self.seq_len = tok, seq_len |
| self.dec = None |
| if decontam_path and os.path.exists(decontam_path): |
| from decontam import Decontaminator |
| self.dec = Decontaminator.load(decontam_path) |
| self.names = list(SOURCES) |
| self.weights = [SOURCES[n][2] for n in self.names] |
| self.iters, self.bufs = {}, {n: [] for n in self.names} |
| for n in self.names: |
| repo, cfg, _ = SOURCES[n] |
| self.iters[n] = iter(load_dataset(repo, name=cfg, split="train", streaming=True)) |
|
|
| def _reopen(self, n): |
| from datasets import load_dataset |
| repo, cfg, _ = SOURCES[n] |
| self.iters[n] = iter(load_dataset(repo, name=cfg, split="train", streaming=True)) |
|
|
| def _fill(self, n): |
| while len(self.bufs[n]) < self.seq_len: |
| try: |
| rec = next(self.iters[n]) |
| except StopIteration: |
| self._reopen(n); rec = next(self.iters[n]) |
| txt = _text(rec) |
| if self.dec is not None and self.dec.is_contaminated(txt): |
| continue |
| self.bufs[n].extend(self.tok.encode(txt, add_eos=True)) |
|
|
| def next_seq(self): |
| n = random.choices(self.names, weights=self.weights, k=1)[0] |
| self._fill(n) |
| seq = self.bufs[n][:self.seq_len]; self.bufs[n] = self.bufs[n][self.seq_len:] |
| return seq |
|
|
| def batch(self, bsz): |
| return torch.tensor([self.next_seq() for _ in range(bsz)], dtype=torch.long) |
|
|
|
|
| def synthetic_batch(bsz, seq_len, vocab): |
| return torch.randint(0, vocab, (bsz, seq_len), dtype=torch.long) |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--synthetic", action="store_true") |
| ap.add_argument("--d_model", type=int, default=256) |
| ap.add_argument("--n_layer", type=int, default=6) |
| ap.add_argument("--seq_len", type=int, default=256) |
| ap.add_argument("--micro_batch", type=int, default=4) |
| ap.add_argument("--steps", type=int, default=8) |
| ap.add_argument("--peak_lr", type=float, default=5e-4) |
| ap.add_argument("--device", default="cpu") |
| ap.add_argument("--bf16", action="store_true", |
| help="bf16 autocast (cuda). Minimal saf-PyTorch SSD'de test edilmedi → " |
| "smoke'u önce fp32 koş; bf16 asıl fork (Faz 3) içindir.") |
| ap.add_argument("--tokenizer", default="kod/tokenizer/tokenizer.model") |
| ap.add_argument("--decontam", default="kod/data/decontam_13gram.pkl.gz") |
| args = ap.parse_args() |
|
|
| dev = torch.device(args.device if (args.device != "cuda" or torch.cuda.is_available()) else "cpu") |
| torch.manual_seed(0); random.seed(0) |
|
|
| |
| from sc_tokenizer import SCTokenizer |
| tok = SCTokenizer(args.tokenizer) |
| vocab = tok.vocab_size |
|
|
| |
| from hybrid_mamba3 import make_config, build_hybrid, init_weights, n_params |
| cfg = make_config(d_model=args.d_model, n_layer=args.n_layer, vocab=vocab, |
| d_mlp_inner=1500 if args.d_model >= 768 else args.d_model * 2, |
| chunk_size=min(64, args.seq_len)) |
| attn_every = 6 if args.n_layer >= 8 else 3 |
| model, attn_idx = build_hybrid(cfg, attn_every=attn_every, n_heads=max(2, args.d_model // 64), |
| n_kv_heads=max(1, args.d_model // 256), device=dev) |
| model.to(dev); model.train() |
| print(f"model: d={args.d_model} L={args.n_layer} ({args.n_layer-len(attn_idx)} Mamba + " |
| f"{len(attn_idx)} GQA) vocab={vocab} | params={n_params(model)/1e6:.1f}M | dev={dev}") |
|
|
| |
| decay = [p for p in model.parameters() if p.ndim >= 2] |
| nodecay = [p for p in model.parameters() if p.ndim < 2] |
| opt = torch.optim.AdamW([{"params": decay, "weight_decay": 0.1}, |
| {"params": nodecay, "weight_decay": 0.0}], |
| lr=args.peak_lr, betas=(0.9, 0.95), eps=1e-8) |
|
|
| stream = None |
| if not args.synthetic: |
| print("on-the-fly mixed streaming kuruluyor (EN/TR/kod/math + decontam)...") |
| stream = MixedStream(tok, args.seq_len, args.decontam) |
|
|
| use_bf16 = args.bf16 and dev.type == "cuda" |
| ln_v = math.log(vocab) |
| print(f"smoke başlıyor | {args.steps} adım | başlangıç loss ~ln({vocab})={ln_v:.2f}") |
| t0 = time.perf_counter(); seen = 0 |
| for step in range(args.steps): |
| batch = (synthetic_batch(args.micro_batch, args.seq_len, vocab) if args.synthetic |
| else stream.batch(args.micro_batch)).to(dev) |
| for g in opt.param_groups: |
| g["lr"] = wsd_lr(step, args.steps, args.peak_lr, args.peak_lr * 0.1) |
| opt.zero_grad(set_to_none=True) |
| ctx = torch.autocast(device_type="cuda", dtype=torch.bfloat16) if use_bf16 else _null() |
| with ctx: |
| logits, _ = model(batch) |
| loss = F.cross_entropy(logits[:, :-1].reshape(-1, logits.size(-1)).float(), |
| batch[:, 1:].reshape(-1)) |
| loss.backward() |
| gnorm = torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) |
| opt.step() |
| seen += batch.numel() |
| tok_s = seen / (time.perf_counter() - t0) |
| print(f" step {step:3d} | loss {loss.item():6.3f} | grad_norm {gnorm:5.2f} | " |
| f"lr {opt.param_groups[0]['lr']:.2e} | {tok_s/1e3:.1f}k tok/s") |
|
|
| print(f"\nsmoke BİTTİ. NaN yok: {not math.isnan(loss.item())} | " |
| f"throughput {seen/(time.perf_counter()-t0)/1e3:.1f}k tok/s") |
|
|
|
|
| class _null: |
| def __enter__(self): return self |
| def __exit__(self, *a): return False |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|