topoexploit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env python3
2
+ """Vanilla transformer control for TopoGPT3 Hodge-CM ablation.
3
+ Matched size: D=384, L=8, H=8, SwiGLU FFN, RoPE, RMSNorm, tied embeddings.
4
+ Target: ~33M to compare against TopoGPT3 37.4M (which includes TorusBrain+MoE+spectral).
5
+ Same vocab (50257), full curriculum interface (codealpaca, code_feedback,
6
+ magicoder_evol, tiny_the_stack) with train/val/holdout disjuntos — nada
7
+ recortado. Usa tokens reales del tier cuando existen; sintetico solo con
8
+ --allow-synthetic explícito.
9
+ """
10
+ import argparse
11
+ import math
12
+ from pathlib import Path
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ from dataclasses import dataclass
17
+
18
+
19
+ TIER_NAMES = ("codealpaca", "code_feedback", "magicoder_evol", "tiny_the_stack")
20
+
21
+
22
+ def resolve_device(req: str) -> torch.device:
23
+ if req == "cuda":
24
+ if not torch.cuda.is_available():
25
+ print("WARNING: --device cuda pedido pero torch.cuda.is_available()=False. "
26
+ "RTX 2060 detectada pero este torch +cu130 exige driver >=580 "
27
+ "(tienes 550/CUDA 12.4). Reinstala cu124: pip install torch "
28
+ "--index-url https://download.pytorch.org/whl/cu124. Fallo a cpu.")
29
+ return torch.device("cpu")
30
+ return torch.device("cuda")
31
+ if req == "cpu":
32
+ return torch.device("cpu")
33
+ if torch.cuda.is_available():
34
+ return torch.device("cuda")
35
+ print("NOTA: cuda no disponible para este build torch. Uso cpu. "
36
+ "Para tu RTX 2060: pip install torch --index-url "
37
+ "https://download.pytorch.org/whl/cu124")
38
+ return torch.device("cpu")
39
+
40
+
41
+ def find_tier_tokens(tier: str):
42
+ """Busca train real tokenizado del tier. Devuelve lista de ints o None.
43
+ Nada silencioso: si no hay, el llamador decide (error o sintetico explícito)."""
44
+ repo = Path(__file__).resolve().parents[1]
45
+ cands = [
46
+ repo / "data/tokenized" / tier / "train.bin",
47
+ repo / "data/tokenized" / tier / "train.npy",
48
+ repo / "data" / tier / "train.bin",
49
+ repo / "data" / tier / "train.npy",
50
+ ]
51
+ for p in cands:
52
+ if p.exists():
53
+ import numpy as np
54
+ if p.suffix == ".npy":
55
+ return np.load(str(p)).astype("int64").tolist()
56
+ return np.fromfile(str(p), dtype=np.uint16).astype("int64").tolist()
57
+ return None
58
+
59
+
60
+ @dataclass
61
+ class VanillaConfig:
62
+ VOCAB_SIZE: int = 50257
63
+ D_MODEL: int = 384
64
+ N_HEADS: int = 8
65
+ N_LAYERS: int = 8
66
+ MAX_SEQ_LEN: int = 512
67
+ DROPOUT: float = 0.1
68
+ LR: float = 3e-4
69
+ WEIGHT_DECAY: float = 0.1
70
+
71
+
72
+ class RMSNorm(nn.Module):
73
+ def __init__(self, d: int, eps: float = 1e-6):
74
+ super().__init__()
75
+ self.eps = eps
76
+ self.weight = nn.Parameter(torch.ones(d))
77
+
78
+ def forward(self, x):
79
+ rms = x.pow(2).mean(-1, keepdim=True).add(self.eps).sqrt()
80
+ return x / rms * self.weight
81
+
82
+
83
+ class RotaryEmbedding(nn.Module):
84
+ def __init__(self, d_head: int, max_seq_len: int = 2048, base: int = 10000):
85
+ super().__init__()
86
+ inv_freq = 1.0 / (base ** (torch.arange(0, d_head, 2).float() / d_head))
87
+ self.register_buffer("inv_freq", inv_freq)
88
+ self._build_cache(max_seq_len)
89
+
90
+ def _build_cache(self, seq_len: int):
91
+ t = torch.arange(seq_len, device=self.inv_freq.device).float()
92
+ freqs = torch.outer(t, self.inv_freq)
93
+ emb = torch.cat([freqs, freqs], dim=-1)
94
+ self.register_buffer("_cos", emb.cos(), persistent=False)
95
+ self.register_buffer("_sin", emb.sin(), persistent=False)
96
+
97
+ @staticmethod
98
+ def _rot_half(x):
99
+ x1, x2 = x[..., : x.shape[-1] // 2], x[..., x.shape[-1] // 2:]
100
+ return torch.cat([-x2, x1], dim=-1)
101
+
102
+ def forward(self, q, k):
103
+ sq, sk = q.shape[2], k.shape[2]
104
+ if max(sq, sk) > self._cos.shape[0]:
105
+ self._build_cache(max(sq, sk) * 2)
106
+ cq = self._cos[:sq].unsqueeze(0).unsqueeze(0)
107
+ sq_ = self._sin[:sq].unsqueeze(0).unsqueeze(0)
108
+ ck = self._cos[:sk].unsqueeze(0).unsqueeze(0)
109
+ sk_ = self._sin[:sk].unsqueeze(0).unsqueeze(0)
110
+ return q * cq + self._rot_half(q) * sq_, k * ck + self._rot_half(k) * sk_
111
+
112
+
113
+ class CausalAttention(nn.Module):
114
+ def __init__(self, cfg: VanillaConfig):
115
+ super().__init__()
116
+ assert cfg.D_MODEL % cfg.N_HEADS == 0
117
+ self.n_heads = cfg.N_HEADS
118
+ self.d_head = cfg.D_MODEL // cfg.N_HEADS
119
+ self.qkv = nn.Linear(cfg.D_MODEL, 3 * cfg.D_MODEL, bias=False)
120
+ self.out = nn.Linear(cfg.D_MODEL, cfg.D_MODEL, bias=False)
121
+ self.rope = RotaryEmbedding(self.d_head, cfg.MAX_SEQ_LEN)
122
+ self.drop = cfg.DROPOUT
123
+
124
+ def forward(self, x):
125
+ B, S, D = x.shape
126
+ qkv = self.qkv(x).view(B, S, 3, self.n_heads, self.d_head)
127
+ q, k, v = qkv[:, :, 0].transpose(1, 2), qkv[:, :, 1].transpose(1, 2), qkv[:, :, 2].transpose(1, 2)
128
+ q, k = self.rope(q, k)
129
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True,
130
+ dropout_p=self.drop if self.training else 0.0)
131
+ return self.out(y.transpose(1, 2).contiguous().view(B, S, D))
132
+
133
+
134
+ class SwiGLU(nn.Module):
135
+ def __init__(self, d: int):
136
+ super().__init__()
137
+ inner = (int(d * 8 / 3) + 7) // 8 * 8
138
+ self.gate = nn.Linear(d, inner, bias=False)
139
+ self.up = nn.Linear(d, inner, bias=False)
140
+ self.down = nn.Linear(inner, d, bias=False)
141
+
142
+ def forward(self, x):
143
+ return self.down(F.silu(self.gate(x)) * self.up(x))
144
+
145
+
146
+ class VanillaLayer(nn.Module):
147
+ def __init__(self, cfg: VanillaConfig):
148
+ super().__init__()
149
+ self.n1 = RMSNorm(cfg.D_MODEL)
150
+ self.n2 = RMSNorm(cfg.D_MODEL)
151
+ self.attn = CausalAttention(cfg)
152
+ self.ffn = SwiGLU(cfg.D_MODEL)
153
+ self.drop = nn.Dropout(cfg.DROPOUT)
154
+
155
+ def forward(self, x):
156
+ x = x + self.drop(self.attn(self.n1(x)))
157
+ x = x + self.drop(self.ffn(self.n2(x)))
158
+ return x
159
+
160
+
161
+ class VanillaTransformer(nn.Module):
162
+ """Tied-embedding decoder-only transformer. Same training interface as TopoGPT2."""
163
+
164
+ def __init__(self, cfg: VanillaConfig = VanillaConfig()):
165
+ super().__init__()
166
+ self.cfg = cfg
167
+ self.embed = nn.Embedding(cfg.VOCAB_SIZE, cfg.D_MODEL)
168
+ nn.init.normal_(self.embed.weight, std=0.02)
169
+ self.layers = nn.ModuleList([VanillaLayer(cfg) for _ in range(cfg.N_LAYERS)])
170
+ self.norm = RMSNorm(cfg.D_MODEL)
171
+ self.lm_head = nn.Linear(cfg.D_MODEL, cfg.VOCAB_SIZE, bias=False)
172
+ self.lm_head.weight = self.embed.weight
173
+ self.apply(self._init)
174
+
175
+ @staticmethod
176
+ def _init(m):
177
+ if isinstance(m, nn.Linear) and not hasattr(m, "_tied"):
178
+ nn.init.normal_(m.weight, std=0.02)
179
+
180
+ def forward(self, ids):
181
+ x = self.embed(ids)
182
+ for layer in self.layers:
183
+ x = layer(x)
184
+ return self.lm_head(self.norm(x))
185
+
186
+ def count_params(self):
187
+ total = sum(p.numel() for p in self.parameters())
188
+ return {"total": total, "trainable": total}
189
+
190
+
191
+ def build_optimizer(model: VanillaTransformer, cfg: VanillaConfig):
192
+ return torch.optim.AdamW(model.parameters(), lr=cfg.LR, weight_decay=cfg.WEIGHT_DECAY)
193
+
194
+
195
+ if __name__ == "__main__":
196
+ ap = argparse.ArgumentParser()
197
+ ap.add_argument("--device", default="auto", choices=["auto", "cuda", "cpu"])
198
+ ap.add_argument("--tier", default="codealpaca", choices=list(TIER_NAMES),
199
+ help="tier curriculum de donde tomar tokens reales")
200
+ ap.add_argument("--seq-len", type=int, default=32)
201
+ ap.add_argument("--batch", type=int, default=2)
202
+ ap.add_argument("--allow-synthetic", action="store_true",
203
+ help="permite tokens randint solo como smoke test; sin esto, falla si no hay data real")
204
+ ap.add_argument("--train-step", action="store_true",
205
+ help="corre 1 step AdamW + grad norm (smoke de entrenamiento en GPU si disponible)")
206
+ args = ap.parse_args()
207
+ device = resolve_device(args.device)
208
+ print(f"device={device} cuda_avail={torch.cuda.is_available()} "
209
+ f"{torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'sin-gpu-para-este-build'}")
210
+ cfg = VanillaConfig()
211
+ m = VanillaTransformer(cfg).to(device)
212
+ print(m.count_params())
213
+ toks = find_tier_tokens(args.tier)
214
+ if toks is not None and len(toks) >= args.batch * args.seq_len:
215
+ import torch as _t
216
+ ids = _t.tensor(toks[:args.batch * args.seq_len], dtype=_t.long).view(args.batch, args.seq_len).to(device)
217
+ print(f"data=tier-real:{args.tier} ntok={len(toks)}")
218
+ elif args.allow_synthetic:
219
+ print(f"WARNING: sin tokens reales de {args.tier} "
220
+ f"(corre topogpt3/train.py --prepare-data para los 4 tiers). Uso sintetico SOLO smoke.")
221
+ ids = torch.randint(0, cfg.VOCAB_SIZE, (args.batch, args.seq_len), device=device)
222
+ else:
223
+ raise SystemExit(
224
+ f"Sin tokens reales de tier {args.tier}. Corre: python3 -m topogpt3.train --prepare-data "
225
+ f"(descarga CodeAlpaca + CodeFeedback + Magicoder + Stack, splits train/val/holdout). "
226
+ f"O repite con --allow-synthetic para smoke. No uso sintetico silencioso.")
227
+ use_amp = device.type == "cuda"
228
+ with torch.autocast(device_type="cuda", enabled=use_amp):
229
+ logits = m(ids)
230
+ print("forward:", tuple(logits.shape))
231
+ if args.train_step:
232
+ opt = build_optimizer(m, cfg)
233
+ tgt = torch.roll(ids, -1, dims=1)
234
+ with torch.autocast(device_type="cuda", enabled=use_amp):
235
+ loss = F.cross_entropy(logits[:, :-1].reshape(-1, cfg.VOCAB_SIZE), tgt[:, :-1].reshape(-1))
236
+ opt.zero_grad()
237
+ loss.backward()
238
+ gn = torch.nn.utils.clip_grad_norm_(m.parameters(), 1.0)
239
+ opt.step()
240
+ print(f"train-step loss={loss.item():.4f} grad_norm={gn:.4f}")