thefinalboss commited on
Commit
da2ec70
·
verified ·
1 Parent(s): d0aff6e

Upload train_1b_v3.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. train_1b_v3.py +427 -0
train_1b_v3.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ CogNet-1B Training Script — Full 1B on 16GB VRAM
4
+ ===================================================
5
+ Strategy: CPU optimizer offloading + gradient checkpointing
6
+ - Model FP16 on GPU: ~2GB
7
+ - Gradients on GPU: ~2GB
8
+ - Activations (checkpointed): ~2-3GB
9
+ - Optimizer states on CPU: ~8GB RAM
10
+ - Peak GPU: ~7-8GB → fits easily in 16GB
11
+ """
12
+
13
+ import os, sys, time, math, json, torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ from torch.utils.data import Dataset, DataLoader
17
+
18
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
19
+ from cognet_1b import CogNet1B
20
+
21
+ # ─── Full 1B Config ─────────────────────────────────────────────
22
+ VOCAB_SIZE = 136
23
+ HIDDEN_DIM = 2048
24
+ NUM_BLOCKS = 13
25
+ NUM_CHANNELS = 8
26
+ CHANNEL_DIM = 256
27
+ FF_DIM = 4096
28
+ MAX_SEQ_LEN = 256
29
+ WORKING_SLOTS = 64
30
+ EPISODIC_SLOTS = 128
31
+ SEMANTIC_SLOTS = 256
32
+
33
+ BATCH_SIZE = 2
34
+ GRAD_ACCUM = 32 # Effective BS = 64
35
+ MAX_STEPS = 100000
36
+ LR = 2e-4
37
+ WEIGHT_DECAY = 0.1
38
+ WARMUP_STEPS = 2000
39
+ MIN_LR = 1e-5
40
+ SEQ_LEN = MAX_SEQ_LEN
41
+
42
+ EVAL_EVERY = 2000
43
+ SAVE_EVERY = 5000
44
+ LOG_EVERY = 10
45
+
46
+ # ─── Char Tokenizer ─────────────────────────────────────────────
47
+ class CharTokenizer:
48
+ def __init__(self):
49
+ self.chars = sorted(set(
50
+ [chr(i) for i in range(32, 127)]
51
+ + list('\u00e0\u00e2\u00e4\u00e9\u00e8\u00ea\u00eb\u00ef\u00ee\u00f4\u00f9\u00fb\u00fc\u00ff\u00e7\u0153\u00e6'
52
+ '\u00c0\u00c2\u00c4\u00c9\u00c8\u00ca\u00cb\u00cf\u00ce\u00d4\u00d9\u00db\u00dc\u0178\u00c7\u0152\u00c6')
53
+ + list('\u00eb\u00df\u00f1\u00bf\u00ab\u00bb') + ['\t', '\n']
54
+ ))
55
+ self.char_to_id = {c: i for i, c in enumerate(self.chars)}
56
+ self.id_to_char = {i: c for i, c in enumerate(self.chars)}
57
+ self.vocab_size = len(self.chars)
58
+
59
+ def encode(self, text):
60
+ return [self.char_to_id.get(c, self.char_to_id.get(' ', 0)) for c in text]
61
+
62
+ def decode(self, ids):
63
+ return ''.join(self.id_to_char.get(i, ' ') for i in ids)
64
+
65
+ @classmethod
66
+ def load(cls, path):
67
+ tok = cls.__new__(cls)
68
+ with open(path, 'r', encoding='utf-8') as f:
69
+ data = json.load(f)
70
+ tok.chars = data['chars']
71
+ tok.char_to_id = {c: i for i, c in enumerate(tok.chars)}
72
+ tok.id_to_char = {i: c for i, c in enumerate(tok.chars)}
73
+ tok.vocab_size = data['vocab_size']
74
+ return tok
75
+
76
+
77
+ # ─── Dataset ─────────────────────────────────────────────────────
78
+ class TokenDataset(Dataset):
79
+ def __init__(self, tokens, seq_len):
80
+ self.tokens = tokens
81
+ self.seq_len = seq_len
82
+
83
+ def __len__(self):
84
+ return max(0, (len(self.tokens) - 1) // self.seq_len)
85
+
86
+ def __getitem__(self, idx):
87
+ start = idx * self.seq_len
88
+ end = start + self.seq_len + 1
89
+ chunk = self.tokens[start:end]
90
+ x = chunk[:-1].clone().detach().long()
91
+ y = chunk[1:].clone().detach().long()
92
+ return x, y
93
+
94
+
95
+ def load_combined_data(data_dir, tokenizer):
96
+ all_tokens = []
97
+
98
+ aicl_10x = os.path.join(data_dir, 'aicl_10x.pt')
99
+ if os.path.exists(aicl_10x):
100
+ t = torch.load(aicl_10x, map_location='cpu', weights_only=True)
101
+ t = t.long().clamp(0, tokenizer.vocab_size - 1)
102
+ all_tokens.append(t)
103
+ print(f" AICL 10x: {len(t):,} tokens")
104
+
105
+ for p in [os.path.join(data_dir, '..', 'checkpoints', 'train_ids.pt'),
106
+ os.path.join(data_dir, 'train_ids.pt'),
107
+ '/root/CogNet/checkpoints/train_ids.pt']:
108
+ if os.path.exists(p):
109
+ t = torch.load(p, map_location='cpu', weights_only=True)
110
+ t = t.long().clamp(0, tokenizer.vocab_size - 1)
111
+ all_tokens.append(t)
112
+ print(f" Train data: {len(t):,} tokens")
113
+ break
114
+
115
+ aicl_raw = os.path.join(data_dir, 'aicl_all.pt')
116
+ if os.path.exists(aicl_raw):
117
+ t = torch.load(aicl_raw, map_location='cpu', weights_only=True)
118
+ t = t.long().clamp(0, tokenizer.vocab_size - 1)
119
+ all_tokens.append(t)
120
+ print(f" AICL raw: {len(t):,} tokens")
121
+
122
+ if not all_tokens:
123
+ raise FileNotFoundError("No data found!")
124
+
125
+ combined = torch.cat(all_tokens, dim=0)
126
+ print(f" COMBINED: {len(combined):,} tokens")
127
+ return combined
128
+
129
+
130
+ # ─── CPU-Offloaded Optimizer ────────────────────────────────────
131
+ class CPUAdamW:
132
+ """AdamW with optimizer states on CPU. Vectorized for speed."""
133
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.95), eps=1e-8, weight_decay=0.1):
134
+ self.params = list(params)
135
+ self.lr = lr
136
+ self.beta1, self.beta2 = betas
137
+ self.eps = eps
138
+ self.wd = weight_decay
139
+ self.step_count = 0
140
+
141
+ # Flat buffers for vectorized ops on CPU
142
+ # Group all params into one flat tensor for fast batch update
143
+ self.group_sizes = [p.numel() for p in self.params]
144
+ self.group_offsets = []
145
+ off = 0
146
+ for s in self.group_sizes:
147
+ self.group_offsets.append(off)
148
+ off += s
149
+ total = off
150
+
151
+ self.m_flat = torch.zeros(total, dtype=torch.float32, device='cpu')
152
+ self.v_flat = torch.zeros(total, dtype=torch.float32, device='cpu')
153
+
154
+ def zero_grad(self):
155
+ for p in self.params:
156
+ if p.grad is not None:
157
+ p.grad.zero_()
158
+
159
+ @torch.no_grad()
160
+ def step(self):
161
+ self.step_count += 1
162
+ bc1 = 1 - self.beta1 ** self.step_count
163
+ bc2 = 1 - self.beta2 ** self.step_count
164
+
165
+ # Gather all grads and params into flat CPU tensors
166
+ g_parts = []
167
+ p_parts = []
168
+ for p in self.params:
169
+ if p.grad is not None:
170
+ g_parts.append(p.grad.detach().reshape(-1).float().cpu())
171
+ p_parts.append(p.data.reshape(-1).float().cpu())
172
+ else:
173
+ g_parts.append(torch.zeros(p.numel(), dtype=torch.float32, device='cpu'))
174
+ p_parts.append(p.data.reshape(-1).float().cpu())
175
+
176
+ g_flat = torch.cat(g_parts)
177
+ p_flat = torch.cat(p_parts)
178
+
179
+ # Vectorized AdamW update
180
+ self.m_flat.mul_(self.beta1).add_(g_flat, alpha=1 - self.beta1)
181
+ self.v_flat.mul_(self.beta2).addcmul_(g_flat, g_flat, value=1 - self.beta2)
182
+
183
+ m_hat = self.m_flat / bc1
184
+ v_hat = self.v_flat / bc2
185
+
186
+ update = m_hat / (v_hat.sqrt() + self.eps)
187
+ if self.wd > 0:
188
+ update.add_(p_flat, alpha=self.wd)
189
+
190
+ p_flat.add_(update, alpha=-self.lr)
191
+
192
+ # Scatter back to GPU params
193
+ offset = 0
194
+ for p in self.params:
195
+ sz = p.numel()
196
+ p.data.copy_(p_flat[offset:offset+sz].reshape(p.shape).to(p.device, dtype=p.dtype))
197
+ offset += sz
198
+
199
+
200
+ def get_lr(step, warmup, max_steps, lr, min_lr):
201
+ if step < warmup:
202
+ return lr * (step + 1) / warmup
203
+ decay = max(0.0, (max_steps - step) / (max_steps - warmup))
204
+ return min_lr + (lr - min_lr) * decay
205
+
206
+
207
+ # ─── Main ────────────────────────────────────────────────────────
208
+ def main():
209
+ print("=" * 60)
210
+ print(" CogNet-1B Training (CPU Offload)")
211
+ print("=" * 60)
212
+
213
+ device = torch.device('cuda')
214
+ print(f" Device: {device}")
215
+ print(f" GPU: {torch.cuda.get_device_name(0)}")
216
+ print(f" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
217
+
218
+ torch.backends.cuda.matmul.allow_tf32 = True
219
+ torch.backends.cudnn.allow_tf32 = True
220
+
221
+ # Tokenizer
222
+ tok_path = '/root/CogNet/tokenizer_v3.json'
223
+ if not os.path.exists(tok_path):
224
+ tok_path = '/root/CogNet/data_1b/tokenizer_v3.json'
225
+ tokenizer = CharTokenizer.load(tok_path)
226
+ print(f" Tokenizer: {tokenizer.vocab_size} chars")
227
+
228
+ # Data
229
+ print("\n Loading data...")
230
+ combined = load_combined_data('/root/CogNet/data_1b', tokenizer)
231
+ n = len(combined)
232
+ split = int(n * 0.95)
233
+ train_tokens = combined[:split]
234
+ val_tokens = combined[split:]
235
+ print(f" Train: {len(train_tokens):,} | Val: {len(val_tokens):,}")
236
+
237
+ train_ds = TokenDataset(train_tokens, SEQ_LEN)
238
+ val_ds = TokenDataset(val_tokens, SEQ_LEN)
239
+ train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True,
240
+ num_workers=2, pin_memory=True, drop_last=True)
241
+ val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False,
242
+ num_workers=1, pin_memory=True, drop_last=True)
243
+
244
+ # Build 1B model
245
+ print("\n Building CogNet-1B...")
246
+ model = CogNet1B(
247
+ vocab_size=tokenizer.vocab_size,
248
+ hidden_dim=HIDDEN_DIM,
249
+ num_blocks=NUM_BLOCKS,
250
+ num_channels=NUM_CHANNELS,
251
+ channel_dim=CHANNEL_DIM,
252
+ ff_dim=FF_DIM,
253
+ max_seq_len=MAX_SEQ_LEN,
254
+ working_slots=WORKING_SLOTS,
255
+ episodic_slots=EPISODIC_SLOTS,
256
+ semantic_slots=SEMANTIC_SLOTS,
257
+ )
258
+
259
+ total_params = sum(p.numel() for p in model.parameters())
260
+ print(f" Parameters: {total_params:,} ({total_params/1e9:.2f}B)")
261
+
262
+ model.to(device)
263
+ print(f" Model VRAM: {torch.cuda.memory_allocated()/1e9:.2f} GB")
264
+
265
+ # CPU-offloaded optimizer — saves 8GB VRAM!
266
+ optimizer = CPUAdamW(model.parameters(), lr=LR,
267
+ betas=(0.9, 0.95), weight_decay=WEIGHT_DECAY)
268
+ print(" Optimizer: CPU-offloaded AdamW")
269
+
270
+ # Checkpoint dir
271
+ ckpt_dir = '/root/CogNet/checkpoints_1b'
272
+ os.makedirs(ckpt_dir, exist_ok=True)
273
+
274
+ # Resume
275
+ start_step = 0
276
+ best_val = float('inf')
277
+ ckpt_path = os.path.join(ckpt_dir, 'cognet_1b_best.pt')
278
+ if os.path.exists(ckpt_path):
279
+ print(f" Resuming from checkpoint...")
280
+ ckpt = torch.load(ckpt_path, map_location=device, weights_only=False)
281
+ model.load_state_dict(ckpt['model_state_dict'])
282
+ start_step = ckpt.get('step', 0) + 1
283
+ best_val = ckpt.get('val_loss', float('inf'))
284
+ # Re-init optimizer with new params
285
+ optimizer = CPUAdamW(model.parameters(), lr=LR,
286
+ betas=(0.9, 0.95), weight_decay=WEIGHT_DECAY)
287
+ print(f" Resumed step {start_step}, val_loss={best_val:.4f}")
288
+
289
+ print(f"\n Effective BS: {BATCH_SIZE * GRAD_ACCUM}")
290
+ print(f" Max steps: {MAX_STEPS}")
291
+ print("=" * 60)
292
+
293
+ model.train()
294
+ train_iter = iter(train_loader)
295
+ global_step = start_step
296
+ t0 = time.time()
297
+ log_loss = 0.0
298
+ log_count = 0
299
+
300
+ while global_step < MAX_STEPS:
301
+ optimizer.zero_grad()
302
+ accum_loss = 0.0
303
+
304
+ for micro in range(GRAD_ACCUM):
305
+ try:
306
+ x, y = next(train_iter)
307
+ except StopIteration:
308
+ train_iter = iter(train_loader)
309
+ x, y = next(train_iter)
310
+
311
+ x = x.to(device, non_blocking=True)
312
+ y = y.to(device, non_blocking=True)
313
+
314
+ with torch.amp.autocast('cuda', dtype=torch.float16):
315
+ result = model(x)
316
+ loss = F.cross_entropy(
317
+ result['logits'].view(-1, tokenizer.vocab_size),
318
+ y.view(-1)
319
+ ) / GRAD_ACCUM
320
+
321
+ loss.backward()
322
+ accum_loss += loss.item()
323
+
324
+ # Clip gradients
325
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
326
+
327
+ # Update LR
328
+ lr = get_lr(global_step, WARMUP_STEPS, MAX_STEPS, LR, MIN_LR)
329
+ optimizer.lr = lr
330
+
331
+ # Step (optimizer on CPU)
332
+ optimizer.step()
333
+
334
+ global_step += 1
335
+ log_loss += accum_loss
336
+ log_count += 1
337
+
338
+ if global_step % LOG_EVERY == 0:
339
+ avg = log_loss / log_count
340
+ ppl = math.exp(min(avg, 10))
341
+ elapsed = time.time() - t0
342
+ spm = LOG_EVERY / (elapsed / 60) if elapsed > 0 else 0
343
+ vram = torch.cuda.max_memory_allocated() / 1e9
344
+
345
+ print(f"Step {global_step:>6d} | Loss: {avg:.4f} | PPL: {ppl:.2f} | "
346
+ f"LR: {lr:.6f} | {spm:.0f} spm | VRAM: {vram:.1f}GB")
347
+
348
+ log_loss = 0.0
349
+ log_count = 0
350
+ t0 = time.time()
351
+
352
+ if global_step % EVAL_EVERY == 0:
353
+ model.eval()
354
+ val_loss = 0.0
355
+ vc = 0
356
+ with torch.no_grad():
357
+ for vx, vy in val_loader:
358
+ vx = vx.to(device, non_blocking=True)
359
+ vy = vy.to(device, non_blocking=True)
360
+ with torch.amp.autocast('cuda', dtype=torch.float16):
361
+ vr = model(vx)
362
+ vl = F.cross_entropy(vr['logits'].view(-1, tokenizer.vocab_size), vy.view(-1))
363
+ val_loss += vl.item()
364
+ vc += 1
365
+ if vc >= 50:
366
+ break
367
+
368
+ avg_val = val_loss / max(vc, 1)
369
+ val_ppl = math.exp(min(avg_val, 10))
370
+ print(f" [EVAL] step={global_step} val_loss={avg_val:.4f} val_ppl={val_ppl:.2f}")
371
+
372
+ # Sample generation
373
+ prompts = ["The ", "Once upon a time", "Bonjour ", "def ", "# AICL\nGoal:\n"]
374
+ for p in prompts:
375
+ ids = tokenizer.encode(p)
376
+ inp = torch.tensor([ids], dtype=torch.long, device=device)
377
+ with torch.no_grad(), torch.amp.autocast('cuda', dtype=torch.float16):
378
+ out = model(inp)
379
+ probs = F.softmax(out['logits'][0, -1] / 0.7, dim=-1)
380
+ top5 = torch.topk(probs, 5)
381
+ chars = [tokenizer.decode([t.item()]) for t in top5.indices]
382
+ print(f' "{p}" -> {chars}')
383
+
384
+ if avg_val < best_val:
385
+ best_val = avg_val
386
+
387
+ model.train()
388
+ t0 = time.time()
389
+
390
+ if global_step % SAVE_EVERY == 0:
391
+ ckpt = {
392
+ 'model_state_dict': model.state_dict(),
393
+ 'step': global_step,
394
+ 'val_loss': best_val,
395
+ 'hidden_dim': HIDDEN_DIM,
396
+ 'num_blocks': NUM_BLOCKS,
397
+ 'num_channels': NUM_CHANNELS,
398
+ 'channel_dim': CHANNEL_DIM,
399
+ 'ff_dim': FF_DIM,
400
+ 'max_seq_len': MAX_SEQ_LEN,
401
+ 'working_slots': WORKING_SLOTS,
402
+ 'episodic_slots': EPISODIC_SLOTS,
403
+ 'semantic_slots': SEMANTIC_SLOTS,
404
+ 'vocab_size': tokenizer.vocab_size,
405
+ }
406
+ torch.save(ckpt, os.path.join(ckpt_dir, 'cognet_1b_best.pt'))
407
+
408
+ # FP16 checkpoint
409
+ fp16 = {k: v.half() for k, v in model.state_dict().items()}
410
+ torch.save({
411
+ 'model_state_dict': fp16,
412
+ 'step': global_step, 'val_loss': best_val,
413
+ 'hidden_dim': HIDDEN_DIM, 'num_blocks': NUM_BLOCKS,
414
+ 'num_channels': NUM_CHANNELS, 'channel_dim': CHANNEL_DIM,
415
+ 'ff_dim': FF_DIM, 'max_seq_len': MAX_SEQ_LEN,
416
+ 'working_slots': WORKING_SLOTS, 'episodic_slots': EPISODIC_SLOTS,
417
+ 'semantic_slots': SEMANTIC_SLOTS, 'vocab_size': tokenizer.vocab_size,
418
+ }, os.path.join(ckpt_dir, 'cognet_1b_fp16.pt'))
419
+
420
+ sz = os.path.getsize(os.path.join(ckpt_dir, 'cognet_1b_best.pt')) / 1e9
421
+ print(f" [CKPT] step={global_step} val_loss={best_val:.4f} size={sz:.1f}GB")
422
+
423
+ print(f"\nDONE! Step {global_step}, best val_loss: {best_val:.4f}")
424
+
425
+
426
+ if __name__ == "__main__":
427
+ main()