thefinalboss commited on
Commit
a4ddff3
·
verified ·
1 Parent(s): 5beb051

Upload scripts/train_progressive.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. scripts/train_progressive.py +252 -0
scripts/train_progressive.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Progressive growth training: build small → train → grow → train → ... → large.
3
+
4
+ Instead of training a large model from scratch (impossible on CPU), this script
5
+ grows the model palier by palier. Each palier inherits the previous weights via
6
+ zero-padding, then trains briefly. The model never starts from random.
7
+
8
+ Usage:
9
+ # Default: 4 paliers on CPU, then stop (palier 5 = 1B needs GPU).
10
+ python scripts/train_progressive.py
11
+
12
+ # Custom: specify paliers to run.
13
+ python scripts/train_progressive.py --paliers 0,1
14
+
15
+ # Tiny smoke test.
16
+ python scripts/train_progressive.py --paliers 0 --tokens-per-palier 5000
17
+ """
18
+ import argparse, os, sys, time, math
19
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
20
+ import torch
21
+ import torch.nn.functional as F
22
+ from fractus.continuous_engine import ContinuousThoughtEngine
23
+ from fractus.train.online import OnlineTrainer
24
+ from fractus.grow import grow_cte
25
+
26
+ CORPUS = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
27
+ "data", "quality_corpus.pt")
28
+
29
+ # The growth ladder. Each palier is bigger than the last.
30
+ # d_model grows ~2x, n_heads keeps d_head=64, experts grow, rank grows.
31
+ PALIERS = [
32
+ # (d_model, n_heads, n_experts, siren_rank, expert_d_ff, tokens, lr)
33
+ dict(d_model=128, n_heads=2, n_experts=4, siren_rank=32, expert_d_ff=128, tokens=2_000_000, lr=1e-3),
34
+ dict(d_model=256, n_heads=4, n_experts=8, siren_rank=32, expert_d_ff=256, tokens=1_500_000, lr=5e-4),
35
+ dict(d_model=512, n_heads=8, n_experts=16, siren_rank=64, expert_d_ff=512, tokens=1_000_000, lr=3e-4),
36
+ dict(d_model=768, n_heads=12, n_experts=32, siren_rank=64, expert_d_ff=768, tokens=500_000, lr=2e-4),
37
+ dict(d_model=1280, n_heads=20, n_experts=128, siren_rank=64, expert_d_ff=2048, tokens=0, lr=1e-4), # GPU only
38
+ ]
39
+
40
+
41
+ def train_palier(engine, tokens, n_tokens, lr, palier_name, accumulation_steps=8):
42
+ """Train one palier using the fast OnlineTrainer (chunked, head-last).
43
+
44
+ Trains in SEGMENTS with periodic logging so we see progress.
45
+ """
46
+ print(f"\n{'='*60}", flush=True)
47
+ print(f"TRAINING {palier_name}", flush=True)
48
+ print(f" d_model={engine.d_model}, experts={engine.blocks[0].moe.n_experts}, "
49
+ f"rank={engine.blocks[0].moe.expert_rank}, "
50
+ f"params={sum(p.numel() for p in engine.parameters()):,}", flush=True)
51
+ print(f" tokens={n_tokens:,}, lr={lr}", flush=True)
52
+ print(f"{'='*60}", flush=True)
53
+
54
+ if n_tokens == 0:
55
+ print(" (skipped — 0 tokens, GPU only)", flush=True)
56
+ return engine
57
+
58
+ torch.set_num_threads(os.cpu_count() or 6)
59
+ chunk_len = 32
60
+ trainer = OnlineTrainer(engine, lr=lr, accumulation_steps=accumulation_steps)
61
+
62
+ # Train in ONE pass over n_tokens, with inline logging every ~50k tokens.
63
+ train_tokens = tokens[:n_tokens]
64
+ log_interval = max(50_000 // chunk_len, 1) # log every ~50k tokens worth of chunks
65
+
66
+ t0 = time.time()
67
+ import torch.nn.functional as F
68
+ total_loss = 0.0
69
+ total_correct = 0
70
+ total_n = 0
71
+ chunk_idx = 0
72
+ accum = accumulation_steps
73
+ trainer.optimizer.zero_grad()
74
+
75
+ for start in range(0, len(train_tokens) - chunk_len - 1, chunk_len):
76
+ chunk = train_tokens[start:start + chunk_len].unsqueeze(0)
77
+ target = train_tokens[start + chunk_len]
78
+ last_logits = engine.tick_chunk_train(chunk)
79
+ loss = F.cross_entropy(last_logits, target.unsqueeze(0)) / accum
80
+ loss.backward()
81
+
82
+ total_loss += loss.item() * accum
83
+ pred = last_logits.argmax(dim=-1)
84
+ total_correct += (pred == target.unsqueeze(0)).sum().item()
85
+ total_n += 1
86
+ chunk_idx += 1
87
+
88
+ if chunk_idx % accum == 0:
89
+ torch.nn.utils.clip_grad_norm_(engine.parameters(), 1.0)
90
+ trainer.optimizer.step()
91
+ trainer.optimizer.zero_grad()
92
+ trainer.step_count += 1
93
+
94
+ if chunk_idx % log_interval == 0:
95
+ processed = chunk_idx * chunk_len
96
+ elapsed = time.time() - t0
97
+ rate = processed / max(elapsed, 1)
98
+ avg = total_loss / max(total_n, 1)
99
+ acc = total_correct / max(total_n, 1)
100
+ ppl = math.exp(min(avg, 20))
101
+ print(f" {processed:>8,}/{n_tokens:,} loss={avg:.3f} "
102
+ f"ppl={ppl:.1f} acc={acc:.3f} {rate:.0f} tok/s", flush=True)
103
+
104
+ # Final remainder step.
105
+ if chunk_idx % accum != 0:
106
+ torch.nn.utils.clip_grad_norm_(engine.parameters(), 1.0)
107
+ trainer.optimizer.step()
108
+ trainer.optimizer.zero_grad()
109
+
110
+ elapsed = time.time() - t0
111
+ avg_loss = total_loss / max(total_n, 1)
112
+ ppl = math.exp(min(avg_loss, 20))
113
+ print(f"\n {palier_name} DONE: loss={avg_loss:.3f} ppl={ppl:.1f} "
114
+ f"({chunk_idx * chunk_len:,} tokens in {elapsed/60:.1f}min, "
115
+ f"{chunk_idx * chunk_len / max(elapsed,1):.0f} tok/s)", flush=True)
116
+
117
+ return engine
118
+
119
+
120
+ def main():
121
+ ap = argparse.ArgumentParser()
122
+ ap.add_argument("--paliers", type=str, default="0,1,2,3",
123
+ help="comma-separated palier indices to run (default: 0,1,2,3)")
124
+ ap.add_argument("--tokens-per-palier", type=int, default=None,
125
+ help="override tokens per palier (for quick tests)")
126
+ ap.add_argument("--seed", type=int, default=42)
127
+ ap.add_argument("--accumulation-steps", type=int, default=8,
128
+ help="gradient accumulation steps (fewer optimizer steps = faster)")
129
+ ap.add_argument("--compile", action="store_true",
130
+ help="enable torch.compile on tick_chunk_train (reduce-overhead mode)")
131
+ args = ap.parse_args()
132
+
133
+ palier_indices = [int(x) for x in args.paliers.split(",")]
134
+ torch.manual_seed(args.seed)
135
+
136
+ print("=== Fractus Progressive Growth ===", flush=True)
137
+ print(f"Paliers: {palier_indices}", flush=True)
138
+ print(f"Seed: {args.seed}", flush=True)
139
+ print(f"Accumulation steps: {args.accumulation_steps}", flush=True)
140
+ print(f"Compile: {args.compile}", flush=True)
141
+
142
+ # Load corpus.
143
+ tokens = torch.load(CORPUS, weights_only=False).to(torch.int64)
144
+ print(f"Corpus: {len(tokens):,} tokens", flush=True)
145
+
146
+ engine = None
147
+ for idx in palier_indices:
148
+ config = PALIERS[idx]
149
+ palier_name = f"Palier {idx}"
150
+
151
+ # Check for a checkpoint from the PREVIOUS palier.
152
+ prev_ckpt = os.path.join(
153
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
154
+ "checkpoints", f"fractus_palier{idx - 1}.pt")
155
+
156
+ if engine is None and os.path.exists(prev_ckpt):
157
+ # Resume from previous palier checkpoint.
158
+ print(f"\n--- Loading Palier {idx - 1} checkpoint: {prev_ckpt} ---", flush=True)
159
+ ckpt = torch.load(prev_ckpt, weights_only=False, map_location="cpu")
160
+ prev_config = ckpt.get("config", {})
161
+ engine = ContinuousThoughtEngine(
162
+ vocab_size=50257,
163
+ d_model=prev_config.get("d_model", 128),
164
+ n_heads=prev_config.get("n_heads", 2),
165
+ d_head=prev_config.get("d_head", 64),
166
+ n_levels=2, n_oscillators=8, coupling_rank=4,
167
+ n_experts=prev_config.get("n_experts", 4),
168
+ top_k=2,
169
+ expert_d_ff=prev_config.get("expert_d_ff", 128),
170
+ siren_rank=prev_config.get("siren_rank", 32))
171
+ # Load weights, ignoring buffer size mismatches (kuramoto_phases etc).
172
+ model_sd = ckpt["model_state"]
173
+ own_sd = engine.state_dict()
174
+ for key, val in model_sd.items():
175
+ if key in own_sd and own_sd[key].shape == val.shape:
176
+ own_sd[key] = val
177
+ engine.load_state_dict(own_sd)
178
+ print(f" Loaded: d={engine.d_model}, E={engine.blocks[0].moe.n_experts}, "
179
+ f"params={sum(p.numel() for p in engine.parameters()):,}", flush=True)
180
+
181
+ if engine is None:
182
+ # Palier 0: build from scratch (no checkpoint found).
183
+ print(f"\n--- Building {palier_name} from scratch ---", flush=True)
184
+ engine = ContinuousThoughtEngine(
185
+ vocab_size=50257, d_model=config["d_model"],
186
+ n_heads=config["n_heads"], d_head=64, n_levels=2,
187
+ n_oscillators=8, coupling_rank=4,
188
+ n_experts=config["n_experts"], top_k=2,
189
+ expert_d_ff=config["expert_d_ff"],
190
+ siren_rank=config["siren_rank"])
191
+ elif engine.d_model < config["d_model"] or engine.blocks[0].moe.n_experts < config["n_experts"]:
192
+ # Grow from previous palier (in-memory or just-loaded checkpoint).
193
+ print(f"\n--- Growing to {palier_name} ---", flush=True)
194
+ grow_config = dict(
195
+ d_model=config["d_model"],
196
+ n_heads=config["n_heads"],
197
+ d_head=64,
198
+ n_experts=config["n_experts"],
199
+ siren_rank=config["siren_rank"],
200
+ expert_d_ff=config["expert_d_ff"],
201
+ )
202
+ engine = grow_cte(engine, grow_config)
203
+ print(f" Grown: d={engine.d_model}, E={engine.blocks[0].moe.n_experts}, "
204
+ f"params={sum(p.numel() for p in engine.parameters()):,}", flush=True)
205
+ else:
206
+ print(f"\n--- {palier_name} already at target config, training in place ---", flush=True)
207
+ print(f" Grown: d={engine.d_model}, E={engine.blocks[0].moe.n_experts}, "
208
+ f"params={sum(p.numel() for p in engine.parameters()):,}", flush=True)
209
+
210
+ # torch.compile (optional — reduces Python overhead on repeated calls).
211
+ if args.compile:
212
+ print(f" Compiling tick_chunk_train (reduce-overhead)...", flush=True)
213
+ engine.tick_chunk_train = torch.compile(
214
+ engine.tick_chunk_train, mode="reduce-overhead")
215
+
216
+ # Train this palier.
217
+ n_tokens = args.tokens_per_palier or config["tokens"]
218
+ engine = train_palier(engine, tokens, n_tokens, config["lr"], palier_name,
219
+ accumulation_steps=args.accumulation_steps)
220
+
221
+ # Save checkpoint.
222
+ ckpt_path = os.path.join(
223
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
224
+ "checkpoints", f"fractus_palier{idx}.pt")
225
+ os.makedirs(os.path.dirname(ckpt_path), exist_ok=True)
226
+ torch.save({
227
+ "model_state": engine.state_dict(),
228
+ "config": {
229
+ "d_model": engine.d_model,
230
+ "n_experts": engine.blocks[0].moe.n_experts,
231
+ "siren_rank": engine.blocks[0].moe.expert_rank,
232
+ "expert_d_ff": engine.blocks[0].moe.d_ff,
233
+ "n_heads": engine.blocks[0].attn.n_heads,
234
+ "d_head": engine.blocks[0].attn.d_head,
235
+ "palier": idx,
236
+ },
237
+ "params": sum(p.numel() for p in engine.parameters()),
238
+ }, ckpt_path)
239
+ print(f" Saved: {ckpt_path} ({os.path.getsize(ckpt_path)/1e6:.0f}MB)", flush=True)
240
+
241
+ print(f"\n{'='*60}", flush=True)
242
+ print(f"Progressive growth complete.", flush=True)
243
+ print(f"Final model: d={engine.d_model}, E={engine.blocks[0].moe.n_experts}, "
244
+ f"params={sum(p.numel() for p in engine.parameters()):,}", flush=True)
245
+ if engine.d_model < 1280:
246
+ print(f"Next: run palier {max(palier_indices)+1} to continue growing.", flush=True)
247
+ else:
248
+ print(f"Reached 1B target! Ready for GPU fine-tuning.", flush=True)
249
+
250
+
251
+ if __name__ == "__main__":
252
+ main()