thefinalboss commited on
Commit
6480dd3
Β·
verified Β·
1 Parent(s): 6670994

Upload hf_scripts/train_ultra.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. hf_scripts/train_ultra.py +1396 -0
hf_scripts/train_ultra.py ADDED
@@ -0,0 +1,1396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ CogNet-1B Training Script V2
4
+ =============================
5
+ Optimizations:
6
+ 1. BF16 mixed precision
7
+ 2. RMSNorm + RoPE
8
+ 3. Vectorized channel processing
9
+ 4. Parallelized memory tier reads with SDPA
10
+ 5. Fused SwiGLU
11
+ 6. Gradient checkpointing
12
+ 7. torch.compile()
13
+ 8. FSDP for multi-GPU
14
+ 9. Fused AdamW optimizer
15
+ 10. CUDA prefetch data pipeline
16
+ 11. Async checkpointing
17
+ 12. Sequence length warmup
18
+ 13. 8-bit optimizer (bitsandbytes, optional)
19
+
20
+ PERFORMANCE: MesurΓ©e par un vrai benchmark au dΓ©marrage.
21
+ Pas d'estimations fabriquΓ©es β€” les tokens/sec et le temps restant
22
+ sont calculΓ©s Γ  partir des mesures rΓ©elles sur votre matΓ©riel.
23
+
24
+ Usage:
25
+ # Single GPU
26
+ python train_ultra.py --max-steps 100000
27
+
28
+ # Multi-GPU with FSDP
29
+ torchrun --nproc_per_node=4 train_ultra.py --max-steps 100000
30
+
31
+ # With all optimizations
32
+ export HF_TOKEN=hf_xxxxx
33
+ python train_ultra.py --max-steps 100000 --batch-size 4 --grad-accum 8 \
34
+ --compile --use-fsdp --cuda-prefetch --seq-warmup --async-ckpt
35
+ """
36
+
37
+ import argparse
38
+ import json
39
+ import math
40
+ import os
41
+ import signal
42
+ import subprocess
43
+ import sys
44
+ import time
45
+ import random
46
+ import string
47
+ from datetime import datetime, timedelta
48
+ import threading
49
+ from concurrent.futures import ThreadPoolExecutor
50
+ from pathlib import Path
51
+ from typing import Dict, List, Optional, Tuple
52
+
53
+ import torch
54
+ import torch.nn as nn
55
+ import torch.nn.functional as F
56
+ from torch.utils.data import Dataset, DataLoader, DistributedSampler
57
+
58
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
59
+ from cognet_1b_optimized import (
60
+ CogNet1BOptimized, CogNetBlock, RMSNorm,
61
+ create_cognet_1b_optimized, create_cognet_350m
62
+ )
63
+
64
+
65
+ # ═══════════════════════════════════════════════════════════════════
66
+ # Configuration β€” matches HuggingFace CogNet-1B exactly
67
+ # ═══════════════════════════════════════════════════════════════════
68
+
69
+ WORKSPACE = os.environ.get('COGNET_WORKSPACE', os.path.dirname(os.path.abspath(__file__)))
70
+ DATA_DIR = os.path.join(WORKSPACE, 'data_1b')
71
+ CKPT_DIR = os.path.join(WORKSPACE, 'checkpoints_1b')
72
+ LOG_FILE = os.path.join(WORKSPACE, 'train_1b.log')
73
+ TOKENIZER_PATH = os.path.join(WORKSPACE, 'tokenizer_v3.json')
74
+ HF_REPO = 'thefinalboss/CogNet-1B'
75
+ HF_TOKEN = os.environ.get('HF_TOKEN', '')
76
+ AICL_REPO_URL = 'https://github.com/AFKmoney/AICL.git'
77
+ AICL_LOCAL = os.path.join(WORKSPACE, 'aicl_repo')
78
+ AICL_REPEAT = int(os.environ.get('AICL_REPEAT', '10'))
79
+
80
+ # Graceful shutdown
81
+ shutdown_requested = False
82
+ def handle_signal(signum, frame):
83
+ global shutdown_requested
84
+ print(f'⚠ Received signal {signum}, will save checkpoint after current step...')
85
+ shutdown_requested = True
86
+
87
+ signal.signal(signal.SIGTERM, handle_signal)
88
+ signal.signal(signal.SIGINT, handle_signal)
89
+
90
+
91
+ # ═══════════════════════════════════════════════════════════════════
92
+ # Character Tokenizer (136 vocab β€” matches HF CogNet-1B)
93
+ # ═══════════════════════════════════════════════════════════════════
94
+
95
+ class CharTokenizer:
96
+ """Character-level tokenizer: 4 special + 132 printable/French chars = 136."""
97
+
98
+ def __init__(self, vocab_size=136):
99
+ self.vocab_size = vocab_size
100
+ self.pad_token_id = 0
101
+ self.unk_token_id = 1
102
+ self.bos_token_id = 2
103
+ self.eos_token_id = 3
104
+
105
+ chars = list(range(32, 127))
106
+ french = [192,193,194,195,196,197,199,200,201,202,203,204,205,206,207,
107
+ 210,211,212,213,214,217,218,219,220,224,225,226,227,228,229,
108
+ 231,232,233,234,235,236,237,238,239,242,243,244,245,246,249,
109
+ 250,251,252,253,255]
110
+ chars.extend(french)
111
+
112
+ self.char_to_id = {self.pad_token_id: 0, self.unk_token_id: 1,
113
+ self.bos_token_id: 2, self.eos_token_id: 3}
114
+ for i, c in enumerate(chars[:vocab_size - 4]):
115
+ self.char_to_id[c] = i + 4
116
+ self.id_to_char = {v: k for k, v in self.char_to_id.items()}
117
+
118
+ def encode(self, text):
119
+ ids = [self.bos_token_id]
120
+ for ch in text:
121
+ code = ord(ch)
122
+ ids.append(self.char_to_id.get(code, self.unk_token_id))
123
+ ids.append(self.eos_token_id)
124
+ return ids
125
+
126
+ def decode(self, ids):
127
+ chars = []
128
+ for i in ids:
129
+ if i in (self.pad_token_id, self.bos_token_id):
130
+ continue
131
+ if i == self.eos_token_id:
132
+ break
133
+ code = self.id_to_char.get(i, 0)
134
+ if code > 0:
135
+ chars.append(chr(code))
136
+ return ''.join(chars)
137
+
138
+ def save(self, path):
139
+ with open(path, 'w', encoding='utf-8') as f:
140
+ json.dump({
141
+ 'vocab_size': self.vocab_size,
142
+ 'char_to_id': {str(k): v for k, v in self.char_to_id.items()},
143
+ }, f, ensure_ascii=False, indent=2)
144
+
145
+ @classmethod
146
+ def load(cls, path):
147
+ with open(path, 'r', encoding='utf-8') as f:
148
+ data = json.load(f)
149
+ tok = cls.__new__(cls)
150
+ tok.vocab_size = data['vocab_size']
151
+ tok.char_to_id = {int(k): v for k, v in data['char_to_id'].items()}
152
+ tok.id_to_char = {v: k for k, v in tok.char_to_id.items()}
153
+ tok.pad_token_id = 0
154
+ tok.unk_token_id = 1
155
+ tok.bos_token_id = 2
156
+ tok.eos_token_id = 3
157
+ return tok
158
+
159
+
160
+ # ═══════════════════════════════════════════════════════════════════
161
+ # Datasets
162
+ # ═══════════════════════════════════════════════════════════════════
163
+
164
+ class TokenDataset(Dataset):
165
+ def __init__(self, data_path, seq_len=512):
166
+ tokens = torch.load(data_path, map_location='cpu', weights_only=True)
167
+ if not isinstance(tokens, torch.LongTensor):
168
+ tokens = tokens.long()
169
+ self.tokens = tokens
170
+ self.seq_len = seq_len
171
+
172
+ def __len__(self):
173
+ return max(0, (len(self.tokens) - 1) // self.seq_len)
174
+
175
+ def __getitem__(self, idx):
176
+ start = idx * self.seq_len
177
+ end = start + self.seq_len + 1
178
+ chunk = self.tokens[start:end]
179
+ return chunk[:-1], chunk[1:]
180
+
181
+
182
+ # ═══════════════════════════════════════════════════════════════════
183
+ # CUDA Prefetch Data Loader β€” overlaps data transfer with compute
184
+ # ═══════════════════════════════════════════════════════════════════
185
+
186
+ class CUDAPrefetchLoader:
187
+ """
188
+ Wraps a DataLoader and prefetches the next batch to GPU
189
+ using a CUDA stream, overlapping Host→Device transfer with
190
+ the current compute step. ~1.1-1.2x speedup on GPU-bound workloads.
191
+ """
192
+ def __init__(self, loader, device):
193
+ self.loader = loader
194
+ self.device = device
195
+ self.stream = torch.cuda.Stream()
196
+ self._preload()
197
+
198
+ def _preload(self):
199
+ try:
200
+ self._next_batch = next(self._iter)
201
+ except AttributeError:
202
+ self._iter = iter(self.loader)
203
+ self._next_batch = next(self._iter)
204
+ except StopIteration:
205
+ self._iter = iter(self.loader)
206
+ self._next_batch = next(self._iter)
207
+
208
+ with torch.cuda.stream(self.stream):
209
+ self._next_x = self._next_batch[0].to(self.device, non_blocking=True)
210
+ self._next_y = self._next_batch[1].to(self.device, non_blocking=True)
211
+
212
+ def __iter__(self):
213
+ self._iter = iter(self.loader)
214
+ self._preload()
215
+ return self
216
+
217
+ def __next__(self):
218
+ torch.cuda.current_stream().wait_stream(self.stream)
219
+ x = self._next_x
220
+ y = self._next_y
221
+ self._preload()
222
+ return x, y
223
+
224
+ def __len__(self):
225
+ return len(self.loader)
226
+
227
+
228
+ # ═══════════════════════════════════════════════════════════════════
229
+ # AICL Repo Integration
230
+ # ═══════════════════════════════════════════════════════════════════
231
+
232
+ def clone_aicl_repo():
233
+ """Clone the AICL GitHub repository."""
234
+ if os.path.isdir(os.path.join(AICL_LOCAL, '.git')):
235
+ print(f' AICL repo already exists at {AICL_LOCAL}')
236
+ return
237
+ print(' Cloning AICL repo from GitHub...')
238
+ subprocess.run(['git', 'clone', AICL_REPO_URL, AICL_LOCAL], check=True)
239
+ print(f' AICL repo cloned to {AICL_LOCAL}')
240
+
241
+
242
+ def extract_aicl_jsonl(repo_path):
243
+ """Extract text from JSONL dataset files in AICL repo."""
244
+ import glob as glob_mod
245
+ texts = []
246
+ datasets_dir = os.path.join(repo_path, 'datasets')
247
+ if not os.path.isdir(datasets_dir):
248
+ return texts
249
+ jsonl_files = sorted(glob_mod.glob(os.path.join(datasets_dir, '*.jsonl')))
250
+ for jf in jsonl_files:
251
+ with open(jf, 'r', encoding='utf-8') as f:
252
+ for line in f:
253
+ line = line.strip()
254
+ if not line:
255
+ continue
256
+ try:
257
+ entry = json.loads(line)
258
+ except json.JSONDecodeError:
259
+ continue
260
+ if 'code' in entry:
261
+ texts.append(entry['code'])
262
+ elif 'completion' in entry:
263
+ instr = entry.get('instruction', '')
264
+ if instr:
265
+ texts.append(f"# Instruction:\n{instr}\n\n# Completion:\n{entry['completion']}")
266
+ else:
267
+ texts.append(entry['completion'])
268
+ if 'snippets' in entry:
269
+ for snip in entry['snippets']:
270
+ if isinstance(snip, dict) and 'completion' in snip:
271
+ texts.append(snip['completion'])
272
+ elif isinstance(snip, str):
273
+ texts.append(snip)
274
+ print(f' JSONL: {len(texts)} entries')
275
+ return texts
276
+
277
+
278
+ def extract_aicl_examples(repo_path):
279
+ """Extract .aicl example files."""
280
+ import glob as glob_mod
281
+ texts = []
282
+ examples_dir = os.path.join(repo_path, 'examples')
283
+ if not os.path.isdir(examples_dir):
284
+ return texts
285
+ aicl_files = sorted(glob_mod.glob(os.path.join(examples_dir, '**/*.aicl'), recursive=True))
286
+ for af in aicl_files:
287
+ try:
288
+ with open(af, 'r', encoding='utf-8') as f:
289
+ content = f.read()
290
+ if content.strip():
291
+ texts.append(f"# === AICL Example: {os.path.basename(af)} ===\n{content}")
292
+ except Exception:
293
+ pass
294
+ print(f' .aicl examples: {len(texts)} files')
295
+ return texts
296
+
297
+
298
+ def extract_aicl_source(repo_path):
299
+ """Extract source code from src/, tools/, scripts/."""
300
+ import glob as glob_mod
301
+ texts = []
302
+ code_dirs = ['src', 'tools', 'scripts']
303
+ code_exts = {'.py', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.prisma'}
304
+ for cdir in code_dirs:
305
+ full_dir = os.path.join(repo_path, cdir)
306
+ if not os.path.isdir(full_dir):
307
+ continue
308
+ for ext in code_exts:
309
+ for cf in sorted(glob_mod.glob(os.path.join(full_dir, f'**/*{ext}'), recursive=True)):
310
+ if 'node_modules' in cf or '.next' in cf or '__pycache__' in cf:
311
+ continue
312
+ try:
313
+ with open(cf, 'r', encoding='utf-8') as f:
314
+ content = f.read()
315
+ if len(content.strip()) > 50:
316
+ texts.append(f"# === Source: {os.path.relpath(cf, repo_path)} ===\n{content}")
317
+ except Exception:
318
+ pass
319
+ print(f' Source code: {len(texts)} files')
320
+ return texts
321
+
322
+
323
+ def extract_aicl_spec_docs(repo_path):
324
+ """Extract spec, docs, README, tests."""
325
+ import glob as glob_mod
326
+ texts = []
327
+ for f in sorted(glob_mod.glob(os.path.join(repo_path, 'spec', '*'))):
328
+ try:
329
+ with open(f, 'r', encoding='utf-8') as fh:
330
+ content = fh.read()
331
+ if content.strip():
332
+ texts.append(f"# === AICL Spec: {os.path.relpath(f, repo_path)} ===\n{content}")
333
+ except Exception:
334
+ pass
335
+ for f in sorted(glob_mod.glob(os.path.join(repo_path, 'docs', '*'))):
336
+ try:
337
+ with open(f, 'r', encoding='utf-8') as fh:
338
+ content = fh.read()
339
+ if content.strip():
340
+ texts.append(f"# === AICL Docs: {os.path.relpath(f, repo_path)} ===\n{content}")
341
+ except Exception:
342
+ pass
343
+ readme = os.path.join(repo_path, 'README.md')
344
+ if os.path.isfile(readme):
345
+ try:
346
+ with open(readme, 'r', encoding='utf-8') as f:
347
+ texts.append(f"# === AICL README ===\n{f.read()}")
348
+ except Exception:
349
+ pass
350
+ for f in sorted(glob_mod.glob(os.path.join(repo_path, 'tests', '*.py'))):
351
+ try:
352
+ with open(f, 'r', encoding='utf-8') as fh:
353
+ content = fh.read()
354
+ if len(content.strip()) > 100:
355
+ texts.append(f"# === AICL Tests: {os.path.relpath(f, repo_path)} ===\n{content}")
356
+ except Exception:
357
+ pass
358
+ print(f' Spec/docs/tests: {len(texts)} files')
359
+ return texts
360
+
361
+
362
+ # ═══════════════════════════════════════════════════════════════════
363
+ # Data Preparation Pipeline (matches HF runpod_train_1b.py)
364
+ # ═══════════════════════════════════════════════════════════════════
365
+
366
+ def prepare_data(tokenizer, skip=False):
367
+ """Full data preparation: HF datasets + AICL + scripts + synthetic."""
368
+ if skip:
369
+ print('Skipping data preparation (--skip-data-prep)')
370
+ return
371
+
372
+ train_path = os.path.join(DATA_DIR, 'train_merged.pt')
373
+ if os.path.exists(train_path):
374
+ size_mb = os.path.getsize(train_path) / 1e6
375
+ print(f'Training data already exists: {train_path} ({size_mb:.0f} MB)')
376
+ return
377
+
378
+ os.makedirs(DATA_DIR, exist_ok=True)
379
+ all_tensors = []
380
+
381
+ # ── Part A: HuggingFace Datasets (7 sources) ──
382
+ print('\n--- Part A: Downloading HuggingFace datasets ---')
383
+ try:
384
+ subprocess.run([sys.executable, '-m', 'pip', 'install', 'datasets', '-q'], check=False)
385
+ except Exception:
386
+ pass
387
+
388
+ hf_path = os.path.join(DATA_DIR, 'hf_datasets_tokens.pt')
389
+ if os.path.exists(hf_path):
390
+ t = torch.load(hf_path, map_location='cpu', weights_only=True).long()
391
+ all_tensors.append(t)
392
+ print(f' HF datasets loaded from cache: {len(t):,} tokens')
393
+ else:
394
+ try:
395
+ from datasets import load_dataset
396
+ hf_ids = []
397
+ total_chars = 0
398
+
399
+ # A1: Wikitext-103
400
+ print(' A1: Loading wikitext-103-raw-v1...')
401
+ try:
402
+ wt = load_dataset('wikitext', 'wikitext-103-raw-v1', split='train')
403
+ wt_texts = [x['text'] for x in wt if x['text'] and len(x['text'].strip()) > 20]
404
+ wt_chars = sum(len(t) for t in wt_texts)
405
+ for text in wt_texts:
406
+ hf_ids.extend(tokenizer.encode(text))
407
+ total_chars += wt_chars
408
+ print(f' wikitext: {len(wt_texts):,} docs, {wt_chars:,} chars')
409
+ except Exception as e:
410
+ print(f' wikitext failed: {e}')
411
+
412
+ # A2: CodeParrot-clean (Python code)
413
+ print(' A2: Loading codeparrot/codeparrot-clean...')
414
+ try:
415
+ cp = load_dataset('codeparrot/codeparrot-clean', split='train', streaming=True)
416
+ cp_chars, cp_docs = 0, 0
417
+ for example in cp:
418
+ code = example.get('content', '') or example.get('text', '')
419
+ if len(code.strip()) > 100:
420
+ hf_ids.extend(tokenizer.encode(code))
421
+ cp_chars += len(code)
422
+ cp_docs += 1
423
+ if cp_chars > 300_000_000:
424
+ break
425
+ total_chars += cp_chars
426
+ print(f' codeparrot: {cp_docs:,} files, {cp_chars:,} chars')
427
+ except Exception as e:
428
+ print(f' codeparrot failed: {e}')
429
+
430
+ # A3: FineWeb (web text)
431
+ print(' A3: Loading HuggingFaceFW/fineweb...')
432
+ try:
433
+ fw = load_dataset('HuggingFaceFW/fineweb', split='train', streaming=True)
434
+ fw_chars, fw_docs = 0, 0
435
+ for example in fw:
436
+ text = example.get('text', '')
437
+ if len(text.strip()) > 50:
438
+ hf_ids.extend(tokenizer.encode(text))
439
+ fw_chars += len(text)
440
+ fw_docs += 1
441
+ if fw_chars > 500_000_000:
442
+ break
443
+ total_chars += fw_chars
444
+ print(f' fineweb: {fw_docs:,} docs, {fw_chars:,} chars')
445
+ except Exception as e:
446
+ print(f' fineweb failed: {e}')
447
+
448
+ # A4: OSCAR French
449
+ print(' A4: Loading oscar (French)...')
450
+ try:
451
+ oscar_fr = load_dataset('oscar', 'unshuffled_deduplicated_fr', split='train', streaming=True, trust_remote_code=True)
452
+ fr_chars, fr_docs = 0, 0
453
+ for example in oscar_fr:
454
+ text = example.get('text', '')
455
+ if len(text.strip()) > 50:
456
+ hf_ids.extend(tokenizer.encode(text))
457
+ fr_chars += len(text)
458
+ fr_docs += 1
459
+ if fr_chars > 100_000_000:
460
+ break
461
+ total_chars += fr_chars
462
+ print(f' oscar-fr: {fr_docs:,} docs, {fr_chars:,} chars')
463
+ except Exception as e:
464
+ print(f' oscar-fr failed: {e}')
465
+
466
+ # A5: The Stack Smol
467
+ print(' A5: Loading bigcode/the-stack-smol...')
468
+ try:
469
+ stack = load_dataset('bigcode/the-stack-smol', split='train', streaming=True, trust_remote_code=True)
470
+ stack_chars, stack_docs = 0, 0
471
+ for example in stack:
472
+ code = example.get('content', '') or example.get('text', '')
473
+ if len(code.strip()) > 100:
474
+ hf_ids.extend(tokenizer.encode(code))
475
+ stack_chars += len(code)
476
+ stack_docs += 1
477
+ if stack_chars > 200_000_000:
478
+ break
479
+ total_chars += stack_chars
480
+ print(f' the-stack-smol: {stack_docs:,} files, {stack_chars:,} chars')
481
+ except Exception as e:
482
+ print(f' the-stack-smol failed: {e}')
483
+
484
+ # A6: Alpaca-cleaned
485
+ print(' A6: Loading yahma/alpaca-cleaned...')
486
+ try:
487
+ alpaca = load_dataset('yahma/alpaca-cleaned', split='train')
488
+ for x in alpaca:
489
+ instr = x.get('instruction', '')
490
+ inp = x.get('input', '')
491
+ out = x.get('output', '')
492
+ text = f"### Instruction:\n{instr}\n"
493
+ if inp:
494
+ text += f"### Input:\n{inp}\n"
495
+ text += f"### Response:\n{out}\n"
496
+ hf_ids.extend(tokenizer.encode(text))
497
+ print(f' alpaca: {len(alpaca):,} instructions')
498
+ except Exception as e:
499
+ print(f' alpaca failed: {e}')
500
+
501
+ # A7: C4 English
502
+ print(' A7: Loading c4 (en)...')
503
+ try:
504
+ c4 = load_dataset('c4', 'en', split='train', streaming=True)
505
+ c4_chars, c4_docs = 0, 0
506
+ for example in c4:
507
+ text = example.get('text', '')
508
+ if len(text.strip()) > 100:
509
+ hf_ids.extend(tokenizer.encode(text))
510
+ c4_chars += len(text)
511
+ c4_docs += 1
512
+ if c4_chars > 300_000_000:
513
+ break
514
+ total_chars += c4_chars
515
+ print(f' c4-en: {c4_docs:,} docs, {c4_chars:,} chars')
516
+ except Exception as e:
517
+ print(f' c4 failed: {e}')
518
+
519
+ if hf_ids:
520
+ hf_tensor = torch.tensor(hf_ids, dtype=torch.long)
521
+ torch.save(hf_tensor, hf_path)
522
+ all_tensors.append(hf_tensor)
523
+ print(f' Total HF: {len(hf_ids):,} tokens, {total_chars:,} chars')
524
+ del hf_ids, hf_tensor
525
+ except ImportError:
526
+ print(' datasets library not available, skipping HF datasets')
527
+ except Exception as e:
528
+ print(f' HF datasets failed: {e}')
529
+
530
+ # ── Part B: CogNet HF repo data ──
531
+ print('\n--- Part B: CogNet HF repo data ---')
532
+ try:
533
+ subprocess.run([sys.executable, '-m', 'pip', 'install', 'huggingface_hub', '-q'], check=False)
534
+ from huggingface_hub import hf_hub_download, list_repo_files
535
+
536
+ if HF_TOKEN:
537
+ repo_files = list_repo_files(HF_REPO, token=HF_TOKEN)
538
+ data_files = [f for f in repo_files if f.startswith('data/') and f.endswith('.pt')]
539
+ for df in data_files:
540
+ fname = os.path.basename(df)
541
+ local_path = os.path.join(DATA_DIR, fname)
542
+ if not os.path.exists(local_path):
543
+ print(f' Downloading {df}...')
544
+ try:
545
+ hf_hub_download(HF_REPO, df, local_dir=DATA_DIR, token=HF_TOKEN)
546
+ except Exception as e:
547
+ print(f' Failed: {e}')
548
+
549
+ # Download scripts for tokenization
550
+ hf_scripts_dir = os.path.join(WORKSPACE, 'hf_scripts')
551
+ os.makedirs(hf_scripts_dir, exist_ok=True)
552
+ script_files = [f for f in repo_files if f.endswith('.py') or f.endswith('.json')]
553
+ for sf in script_files:
554
+ if sf.startswith('data/'):
555
+ continue
556
+ try:
557
+ hf_hub_download(HF_REPO, sf, local_dir=hf_scripts_dir, token=HF_TOKEN)
558
+ except Exception:
559
+ pass
560
+ except Exception as e:
561
+ print(f' HF download failed (non-fatal): {e}')
562
+
563
+ # Load any downloaded .pt files
564
+ loaded_names = {'train_merged.pt', 'hf_datasets_tokens.pt', 'aicl_tokens.pt', 'synthetic_tokens.pt'}
565
+ for pt_file in sorted(Path(DATA_DIR).glob('*.pt')):
566
+ if pt_file.name in loaded_names:
567
+ continue
568
+ t = torch.load(str(pt_file), map_location='cpu', weights_only=True).long()
569
+ all_tensors.append(t)
570
+ print(f' Loaded {pt_file.name}: {len(t):,} tokens')
571
+
572
+ # ── Part C: AICL repo ──
573
+ aicl_path = os.path.join(DATA_DIR, 'aicl_tokens.pt')
574
+ if os.path.exists(aicl_path):
575
+ aicl_tensor = torch.load(aicl_path, map_location='cpu', weights_only=True).long()
576
+ all_tensors.append(aicl_tensor)
577
+ print(f'\n--- Part C: AICL tokens loaded: {len(aicl_tensor):,} ---')
578
+ else:
579
+ print('\n--- Part C: AICL Repo Conversion ---')
580
+ clone_aicl_repo()
581
+ aicl_texts = []
582
+ aicl_texts.extend(extract_aicl_jsonl(AICL_LOCAL))
583
+ aicl_texts.extend(extract_aicl_examples(AICL_LOCAL))
584
+ aicl_texts.extend(extract_aicl_source(AICL_LOCAL))
585
+ aicl_texts.extend(extract_aicl_spec_docs(AICL_LOCAL))
586
+
587
+ # Repeat AICL data for weight in training
588
+ aicl_texts_repeated = aicl_texts * AICL_REPEAT
589
+ print(f' AICL after {AICL_REPEAT}x repeat: {len(aicl_texts_repeated):,} chunks')
590
+
591
+ aicl_ids = []
592
+ for text in aicl_texts_repeated:
593
+ aicl_ids.extend(tokenizer.encode(text))
594
+
595
+ aicl_tensor = torch.tensor(aicl_ids, dtype=torch.long)
596
+ torch.save(aicl_tensor, aicl_path)
597
+ all_tensors.append(aicl_tensor)
598
+ print(f' AICL: {len(aicl_ids):,} tokens saved')
599
+ del aicl_texts, aicl_texts_repeated, aicl_ids
600
+
601
+ # ── Part D: HF scripts ──
602
+ print('\n--- Part D: HF Scripts β†’ Tokens ---')
603
+ script_texts = []
604
+ hf_scripts_dir = os.path.join(WORKSPACE, 'hf_scripts')
605
+ if os.path.isdir(hf_scripts_dir):
606
+ import glob as glob_mod
607
+ for ext in ['.py', '.json', '.md']:
608
+ for sf in sorted(glob_mod.glob(os.path.join(hf_scripts_dir, f'**/*{ext}'), recursive=True)):
609
+ try:
610
+ with open(sf, 'r', encoding='utf-8') as f:
611
+ content = f.read()
612
+ if len(content.strip()) > 50:
613
+ script_texts.append(f"# === HF Script: {os.path.relpath(sf, hf_scripts_dir)} ===\n{content}")
614
+ except Exception:
615
+ pass
616
+
617
+ if script_texts:
618
+ script_ids = []
619
+ for text in script_texts:
620
+ script_ids.extend(tokenizer.encode(text))
621
+ script_tensor = torch.tensor(script_ids, dtype=torch.long)
622
+ all_tensors.append(script_tensor.repeat(3)) # 3x weight
623
+ print(f' HF scripts: {len(script_ids):,} tokens (3x repeated)')
624
+
625
+ # ── Part E: Synthetic data ──
626
+ syn_path = os.path.join(DATA_DIR, 'synthetic_tokens.pt')
627
+ if os.path.exists(syn_path):
628
+ syn_tensor = torch.load(syn_path, map_location='cpu', weights_only=True).long()
629
+ all_tensors.append(syn_tensor)
630
+ print(f'\n--- Part E: Synthetic tokens loaded: {len(syn_tensor):,} ---')
631
+ else:
632
+ print('\n--- Part E: Synthetic Data Generation ---')
633
+ target_chars = 50_000_000
634
+ func_names = ['process','compute','transform','validate','parse','encode','decode','train','predict','analyze']
635
+ cls_names = ['Model','Processor','Handler','Manager','Engine','Pipeline','Service','Client','Server','Agent']
636
+ params = ['x','y','data','input','value','config','params','options','state','context']
637
+
638
+ py_templates = [
639
+ "def {f}({p1}, {p2}):\n result = {p1} + {p2}\n return result\n\n",
640
+ "class {cls}:\n def __init__(self, {p1}):\n self.{p1} = {p1}\n\n def process(self, {p2}):\n return self.{p1} * {p2}\n\n",
641
+ "async def {f}({p1}):\n result = await process({p1})\n return result\n\n",
642
+ ]
643
+ en_sentences = [
644
+ "The quick brown fox jumps over the lazy dog. ",
645
+ "CogNet is a non-transformer language model with cognitive routing and memory. ",
646
+ "Knowledge is power and understanding is the key to wisdom. ",
647
+ "The future of artificial intelligence is bright and full of possibilities. ",
648
+ ]
649
+ fr_sentences = [
650
+ "Bonjour le monde est beau et la science est merveilleuse. ",
651
+ "CogNet est un modele de langage non-transformateur avec routage cognitif. ",
652
+ "La connaissance est le pouvoir et la comprehension est la cle. ",
653
+ ]
654
+
655
+ syn_ids = []
656
+ chars_gen = 0
657
+ rng = random.Random(42)
658
+ while chars_gen < target_chars:
659
+ texts = []
660
+ for _ in range(400):
661
+ t = rng.choice(py_templates)
662
+ try:
663
+ text = t.format(f=rng.choice(func_names), cls=rng.choice(cls_names),
664
+ p1=rng.choice(params), p2=rng.choice(params))
665
+ texts.append(text)
666
+ except Exception:
667
+ texts.append("x = 1\nresult = x * 2\n\n")
668
+ for _ in range(800):
669
+ texts.append(rng.choice(en_sentences))
670
+ for _ in range(400):
671
+ texts.append(rng.choice(fr_sentences))
672
+ batch_text = ''.join(texts)
673
+ syn_ids.extend(tokenizer.encode(batch_text))
674
+ chars_gen += len(batch_text)
675
+
676
+ syn_tensor = torch.tensor(syn_ids, dtype=torch.long)
677
+ torch.save(syn_tensor, syn_path)
678
+ all_tensors.append(syn_tensor)
679
+ print(f' Synthetic: {len(syn_ids):,} tokens saved')
680
+
681
+ # ── Part F: Merge ALL data ──
682
+ print(f'\n--- Part F: Merging {len(all_tensors)} datasets ---')
683
+ for i, t in enumerate(all_tensors):
684
+ print(f' [{i}] {len(t):,} tokens')
685
+
686
+ merged = torch.cat(all_tensors, dim=0)
687
+ print(f' Total: {len(merged):,} tokens before shuffle')
688
+
689
+ # Shuffle
690
+ print(' Shuffling...')
691
+ perm = torch.randperm(len(merged))
692
+ merged = merged[perm]
693
+ del perm, all_tensors
694
+
695
+ torch.save(merged, train_path)
696
+ size_mb = os.path.getsize(train_path) / 1e6
697
+ print(f' Merged: {train_path} ({size_mb:.0f} MB, {len(merged):,} tokens)')
698
+ del merged
699
+ print('Data preparation complete!')
700
+
701
+
702
+ # ═══════════════════════════════════════════════════════════════════
703
+ # Learning Rate Schedule
704
+ # ════════════════════════════════════════════════════���══════════════
705
+
706
+ def get_cosine_lr(step, warmup_steps, max_steps, max_lr, min_lr):
707
+ if step < warmup_steps:
708
+ return max_lr * step / max(1, warmup_steps)
709
+ if step >= max_steps:
710
+ return min_lr
711
+ progress = (step - warmup_steps) / max(1, max_steps - warmup_steps)
712
+ return min_lr + 0.5 * (max_lr - min_lr) * (1 + math.cos(math.pi * progress))
713
+
714
+
715
+ # ═══════════════════════════════════════════════════════════════════
716
+ # Sequence Length Warmup (Curriculum Learning)
717
+ # ═══════════════════════════════════════════════════════════════════
718
+
719
+ def get_current_seq_len(step, warmup_steps, target_seq_len):
720
+ """
721
+ Start with short sequences (128) and linearly warm up to target_seq_len
722
+ over `warmup_steps` steps. This gives ~1.2x speedup in early training
723
+ because shorter sequences mean less compute per step.
724
+ """
725
+ if step >= warmup_steps:
726
+ return target_seq_len
727
+ min_seq = 128
728
+ progress = step / max(1, warmup_steps)
729
+ # Round to nearest power of 2 for efficiency
730
+ current = int(min_seq + progress * (target_seq_len - min_seq))
731
+ # Round to nearest 64 for alignment
732
+ current = max(128, (current // 64) * 64)
733
+ return current
734
+
735
+
736
+ # ═══════════════════════════════════════════════════════════════════
737
+ # Async Checkpointing β€” save in background thread
738
+ # ═══════════════════════════════════════════════════════════════════
739
+
740
+ class AsyncCheckpointSaver:
741
+ """Saves checkpoints in a background thread to avoid blocking training."""
742
+ def __init__(self, max_workers=1):
743
+ self.executor = ThreadPoolExecutor(max_workers=max_workers)
744
+ self.pending = []
745
+
746
+ def save(self, save_fn, *args, **kwargs):
747
+ """Submit checkpoint save to background thread."""
748
+ future = self.executor.submit(save_fn, *args, **kwargs)
749
+ self.pending.append(future)
750
+ # Clean up completed futures
751
+ self.pending = [f for f in self.pending if not f.done()]
752
+
753
+ def wait(self):
754
+ """Wait for all pending saves to complete."""
755
+ for f in self.pending:
756
+ f.result()
757
+ self.pending.clear()
758
+
759
+ def __del__(self):
760
+ self.wait()
761
+ self.executor.shutdown(wait=True)
762
+
763
+
764
+ # ═══════════════════════════════════════════════════════════════════
765
+ # Checkpoint Management
766
+ # ═══════════════════════════════════════════════════════════════════
767
+
768
+ def save_checkpoint(model, optimizer, step, loss_val, best_loss, tokenizer, args, filename):
769
+ path = os.path.join(args.ckpt_dir, filename)
770
+
771
+ # Get state dict (handles FSDP and compiled models)
772
+ if hasattr(model, 'module'):
773
+ state_dict = model.module.state_dict()
774
+ elif hasattr(model, '_orig_mod'):
775
+ state_dict = model._orig_mod.state_dict()
776
+ else:
777
+ state_dict = model.state_dict()
778
+
779
+ ckpt = {
780
+ 'step': step,
781
+ 'model_state_dict': state_dict,
782
+ 'optimizer_state_dict': optimizer.state_dict(),
783
+ 'loss': loss_val,
784
+ 'best_loss': best_loss,
785
+ 'config': {
786
+ 'vocab_size': tokenizer.vocab_size,
787
+ 'hidden_dim': 2048,
788
+ 'num_blocks': 16,
789
+ 'num_channels': 8,
790
+ 'channel_dim': 384,
791
+ 'ff_dim': 8192,
792
+ 'working_slots': 128,
793
+ 'episodic_slots': 256,
794
+ 'semantic_slots': 512,
795
+ 'max_seq_len': args.seq_len,
796
+ },
797
+ }
798
+
799
+ # Atomic save: write to temp file then rename
800
+ tmp_path = path + '.tmp'
801
+ torch.save(ckpt, tmp_path)
802
+ os.replace(tmp_path, path)
803
+ return path
804
+
805
+
806
+ def push_to_huggingface(ckpt_path, tokenizer):
807
+ """Push the best checkpoint to HuggingFace."""
808
+ if not HF_TOKEN:
809
+ print(' HF_TOKEN not set, skipping push')
810
+ return
811
+ try:
812
+ from huggingface_hub import HfApi
813
+ api = HfApi()
814
+ api.create_repo(repo_id=HF_REPO, exist_ok=True, token=HF_TOKEN)
815
+ api.upload_file(path_or_fileobj=ckpt_path, path_in_repo='checkpoints/cognet_1b_best.pt',
816
+ repo_id=HF_REPO, token=HF_TOKEN)
817
+ api.upload_file(path_or_fileobj=TOKENIZER_PATH, path_in_repo='checkpoints/tokenizer_v3.json',
818
+ repo_id=HF_REPO, token=HF_TOKEN)
819
+ print(f' Pushed to HuggingFace: {HF_REPO}')
820
+ except Exception as e:
821
+ print(f' HF push failed: {e}')
822
+
823
+
824
+ # ═══════════════════════════════════════════════════════════════════
825
+ # Distributed Setup
826
+ # ═══════════════════════════════════════════════════════════════════
827
+
828
+ def setup_distributed():
829
+ if not torch.distributed.is_initialized():
830
+ from torch.distributed import init_process_group
831
+ init_process_group(backend='nccl')
832
+ local_rank = int(os.environ.get('LOCAL_RANK', 0))
833
+ world_size = int(os.environ.get('WORLD_SIZE', 1))
834
+ rank = int(os.environ.get('RANK', 0))
835
+ torch.cuda.set_device(local_rank)
836
+ return rank, world_size, local_rank
837
+
838
+
839
+ # ═══════════════════════════════════════════════════════════════════
840
+ # Compiled Training Step β€” fuse forward+backward for max speed
841
+ # ═══════════════════════════════════════════════════════════════════
842
+
843
+ def create_compiled_step(model, vocab_size, grad_accum, grad_clip, use_bf16):
844
+ """
845
+ Create a compiled forward+backward step function.
846
+ This is ~1.3x faster than separate forward/backward because
847
+ torch.compile() can fuse the operations across the boundary.
848
+ """
849
+ @torch.compile(mode="reduce-overhead")
850
+ def compiled_train_step(x, y):
851
+ with torch.amp.autocast('cuda', dtype=torch.bfloat16 if use_bf16 else torch.float16):
852
+ result = model(x)
853
+ logits = result['logits']
854
+ loss = F.cross_entropy(logits.view(-1, vocab_size), y.view(-1), ignore_index=0)
855
+ (loss / grad_accum).backward()
856
+ return loss
857
+
858
+ return compiled_train_step
859
+
860
+
861
+ # ═══════════════════════════════════════════════════════════════════
862
+ # Main Training Loop
863
+ # ═══════════════════════════════════════════════════════════════════
864
+
865
+ def main():
866
+ parser = argparse.ArgumentParser(description='CogNet-1B Ultra-Fast Training V2')
867
+ # Standard args
868
+ parser.add_argument('--batch-size', type=int, default=4)
869
+ parser.add_argument('--grad-accum', type=int, default=8)
870
+ parser.add_argument('--seq-len', type=int, default=512)
871
+ parser.add_argument('--max-steps', type=int, default=100000)
872
+ parser.add_argument('--warmup-steps', type=int, default=2000)
873
+ parser.add_argument('--max-lr', type=float, default=1e-4)
874
+ parser.add_argument('--min-lr', type=float, default=1e-5)
875
+ parser.add_argument('--weight-decay', type=float, default=0.1)
876
+ parser.add_argument('--grad-clip', type=float, default=1.0)
877
+ parser.add_argument('--save-every', type=int, default=2000)
878
+ parser.add_argument('--eval-every', type=int, default=500)
879
+ parser.add_argument('--log-every', type=int, default=50)
880
+ parser.add_argument('--data-path', type=str, default=None)
881
+ parser.add_argument('--ckpt-dir', type=str, default=CKPT_DIR)
882
+ parser.add_argument('--resume', type=str, default=None)
883
+ parser.add_argument('--bf16', action='store_true', default=True)
884
+ parser.add_argument('--no-bf16', dest='bf16', action='store_false')
885
+ parser.add_argument('--skip-data-prep', action='store_true')
886
+ parser.add_argument('--compile', action='store_true', default=False)
887
+ parser.add_argument('--use-fsdp', action='store_true', default=False)
888
+ parser.add_argument('--use-grad-checkpoint', action='store_true', default=True)
889
+ parser.add_argument('--no-grad-checkpoint', dest='use_grad_checkpoint', action='store_false')
890
+ parser.add_argument('--model-size', type=str, default='1b', choices=['1b', '350m'])
891
+
892
+ # NEW: V2 optimization flags
893
+ parser.add_argument('--cuda-prefetch', action='store_true', default=False,
894
+ help='Enable CUDA prefetch data pipeline (~1.15x faster)')
895
+ parser.add_argument('--seq-warmup', action='store_true', default=False,
896
+ help='Sequence length warmup: 128β†’target over warmup period (~1.2x early speedup)')
897
+ parser.add_argument('--async-ckpt', action='store_true', default=False,
898
+ help='Async checkpointing in background thread (eliminates save pauses)')
899
+ parser.add_argument('--8bit-optim', action='store_true', default=False,
900
+ help='Use 8-bit AdamW via bitsandbytes (~1.15x faster, 50%% less VRAM)')
901
+ parser.add_argument('--compile-step', action='store_true', default=False,
902
+ help='Compile the entire forward+backward step (additional ~1.3x over model compile)')
903
+ args = parser.parse_args()
904
+
905
+ # ── CUDA optimizations ──
906
+ torch.backends.cuda.matmul.allow_tf32 = True # Allow TF32 on Ampere+ (~1.3x for matmul)
907
+ torch.backends.cudnn.allow_tf32 = True
908
+ torch.set_float32_matmul_precision('high') # Allow BF16/TF32 matmul
909
+
910
+ # Distributed setup
911
+ is_distributed = int(os.environ.get('WORLD_SIZE', 1)) > 1
912
+ rank, world_size, local_rank = 0, 1, 0
913
+ if is_distributed:
914
+ rank, world_size, local_rank = setup_distributed()
915
+ is_main = (rank == 0)
916
+
917
+ device = torch.device(f'cuda:{local_rank}' if torch.cuda.is_available() else 'cpu')
918
+
919
+ if is_main:
920
+ print('=' * 60)
921
+ print('CogNet-1B Ultra-Fast Training V2 β€” MAXIMUM SPEED')
922
+ print('=' * 60)
923
+ print(f'Device: {device}')
924
+ print(f'Distributed: {is_distributed} (world_size={world_size})')
925
+ print(f'Model: {args.model_size}')
926
+ print(f'BF16: {args.bf16}')
927
+ print(f'Compile: {args.compile}')
928
+ print(f'Compile step: {args.compile_step}')
929
+ print(f'CUDA prefetch: {args.cuda_prefetch}')
930
+ print(f'Seq warmup: {args.seq_warmup}')
931
+ print(f'Async checkpoint: {args.async_ckpt}')
932
+ print(f'8-bit optimizer: {getattr(args, "8bit_optim", False)}')
933
+ print(f'TF32 enabled: True')
934
+ print(f'HF repo: {HF_REPO}')
935
+ print(f'HF token: {"SET" if HF_TOKEN else "NOT SET"}')
936
+ print('=' * 60)
937
+
938
+ os.makedirs(args.ckpt_dir, exist_ok=True)
939
+ os.makedirs(DATA_DIR, exist_ok=True)
940
+
941
+ # ── Tokenizer ──
942
+ tokenizer = None
943
+ for tp in [TOKENIZER_PATH, os.path.join(DATA_DIR, 'tokenizer_v3.json')]:
944
+ if os.path.exists(tp):
945
+ tokenizer = CharTokenizer.load(tp)
946
+ if is_main:
947
+ print(f'Loaded tokenizer from {tp} (vocab={tokenizer.vocab_size})')
948
+ break
949
+ if tokenizer is None:
950
+ tokenizer = CharTokenizer()
951
+ tokenizer.save(TOKENIZER_PATH)
952
+ if is_main:
953
+ print(f'Created tokenizer (vocab={tokenizer.vocab_size})')
954
+
955
+ # ── Data Preparation ──
956
+ if is_main:
957
+ prepare_data(tokenizer, skip=args.skip_data_prep)
958
+ if is_distributed:
959
+ torch.distributed.barrier()
960
+
961
+ # ── Load Dataset ──
962
+ data_path = args.data_path
963
+ if data_path is None:
964
+ merged = os.path.join(DATA_DIR, 'train_merged.pt')
965
+ if os.path.exists(merged):
966
+ data_path = merged
967
+ else:
968
+ pt_files = list(Path(DATA_DIR).glob('*.pt'))
969
+ if pt_files:
970
+ data_path = str(pt_files[0])
971
+
972
+ if data_path is None:
973
+ print('ERROR: No training data found!')
974
+ sys.exit(1)
975
+
976
+ if is_main:
977
+ print(f'Loading data from: {data_path}')
978
+
979
+ dataset = TokenDataset(data_path, args.seq_len)
980
+ sampler = None
981
+ if is_distributed:
982
+ sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank, shuffle=True, drop_last=True)
983
+
984
+ dataloader = DataLoader(
985
+ dataset, batch_size=args.batch_size,
986
+ shuffle=(sampler is None),
987
+ sampler=sampler,
988
+ num_workers=4 if torch.cuda.is_available() else 0,
989
+ pin_memory=True, drop_last=True,
990
+ persistent_workers=bool(torch.cuda.is_available()),
991
+ )
992
+
993
+ # CUDA prefetch wrapper
994
+ if args.cuda_prefetch and torch.cuda.is_available():
995
+ dataloader = CUDAPrefetchLoader(dataloader, device)
996
+ if is_main:
997
+ print('CUDA prefetch enabled: overlapping data transfer with compute')
998
+
999
+ # ── Build Model ──
1000
+ if is_main:
1001
+ print(f'\nBuilding CogNet-{args.model_size.upper()} (optimized)...')
1002
+
1003
+ if args.model_size == '1b':
1004
+ model = create_cognet_1b_optimized(
1005
+ vocab_size=tokenizer.vocab_size,
1006
+ max_seq_len=args.seq_len,
1007
+ use_gradient_checkpointing=args.use_grad_checkpoint,
1008
+ )
1009
+ else:
1010
+ model = create_cognet_350m(
1011
+ vocab_size=tokenizer.vocab_size,
1012
+ max_seq_len=args.seq_len,
1013
+ use_gradient_checkpointing=args.use_grad_checkpoint,
1014
+ )
1015
+
1016
+ model = model.to(device)
1017
+
1018
+ total_params = sum(p.numel() for p in model.parameters())
1019
+ if is_main:
1020
+ print(f'Total parameters: {total_params:,} ({total_params/1e9:.2f}B)')
1021
+
1022
+ # FSDP
1023
+ if args.use_fsdp and is_distributed:
1024
+ from torch.distributed.fsdp import FullyShardedDataParallel as FSDP, MixedPrecision
1025
+ from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
1026
+
1027
+ mp_policy = None
1028
+ if args.bf16:
1029
+ mp_policy = MixedPrecision(param_dtype=torch.bfloat16, reduce_dtype=torch.bfloat16, buffer_dtype=torch.bfloat16)
1030
+
1031
+ auto_wrap = transformer_auto_wrap_policy(transformer_layer_cls={CogNetBlock})
1032
+ model = FSDP(model, auto_wrap_policy=auto_wrap, mixed_precision=mp_policy,
1033
+ device_id=local_rank, sharding_strategy=torch.distributed.fsdp.ShardingStrategy.FULL_SHARD)
1034
+ if is_main:
1035
+ print('FSDP enabled')
1036
+
1037
+ # torch.compile the model
1038
+ if args.compile:
1039
+ try:
1040
+ model = torch.compile(model, mode="reduce-overhead")
1041
+ if is_main:
1042
+ print('Model compiled with torch.compile(reduce-overhead)')
1043
+ except Exception as e:
1044
+ if is_main:
1045
+ print(f'Compile failed: {e}')
1046
+
1047
+ # ── Optimizer ──
1048
+ use_8bit = getattr(args, '8bit_optim', False)
1049
+ if use_8bit:
1050
+ try:
1051
+ import bitsandbytes as bnb
1052
+ optimizer = bnb.optim.AdamW8bit(
1053
+ model.parameters(), lr=args.max_lr,
1054
+ betas=(0.9, 0.95), eps=1e-8,
1055
+ weight_decay=args.weight_decay,
1056
+ )
1057
+ if is_main:
1058
+ print('8-bit AdamW (bitsandbytes) enabled β€” 50% less VRAM for optimizer states')
1059
+ except ImportError:
1060
+ if is_main:
1061
+ print('bitsandbytes not available, falling back to Fused AdamW')
1062
+ optimizer = torch.optim.AdamW(
1063
+ model.parameters(), lr=args.max_lr,
1064
+ betas=(0.9, 0.95), eps=1e-8,
1065
+ weight_decay=args.weight_decay, fused=True,
1066
+ )
1067
+ else:
1068
+ optimizer = torch.optim.AdamW(
1069
+ model.parameters(), lr=args.max_lr,
1070
+ betas=(0.9, 0.95), eps=1e-8,
1071
+ weight_decay=args.weight_decay, fused=True,
1072
+ )
1073
+
1074
+ use_bf16 = args.bf16 and torch.cuda.is_available() and torch.cuda.is_bf16_supported()
1075
+ scaler = None if use_bf16 else torch.amp.GradScaler('cuda')
1076
+ if is_main:
1077
+ print(f'Mixed precision: {"BF16" if use_bf16 else "FP16+GradScaler"}')
1078
+
1079
+ # Compiled training step
1080
+ compiled_step = None
1081
+ if args.compile_step and not is_distributed:
1082
+ try:
1083
+ compiled_step = create_compiled_step(model, tokenizer.vocab_size, args.grad_accum, args.grad_clip, use_bf16)
1084
+ if is_main:
1085
+ print('Compiled training step enabled (forward+backward fused)')
1086
+ except Exception as e:
1087
+ if is_main:
1088
+ print(f'Compiled step failed: {e}, using standard loop')
1089
+
1090
+ # Async checkpoint saver
1091
+ async_saver = None
1092
+ if args.async_ckpt:
1093
+ async_saver = AsyncCheckpointSaver()
1094
+ if is_main:
1095
+ print('Async checkpointing enabled (saves in background)')
1096
+
1097
+ # ── Resume ──
1098
+ start_step = 0
1099
+ best_loss = float('inf')
1100
+
1101
+ if args.resume and os.path.exists(args.resume):
1102
+ ckpt = torch.load(args.resume, map_location=device, weights_only=False)
1103
+ model.load_state_dict(ckpt['model_state_dict'])
1104
+ if 'optimizer_state_dict' in ckpt:
1105
+ optimizer.load_state_dict(ckpt['optimizer_state_dict'])
1106
+ start_step = ckpt.get('step', 0)
1107
+ best_loss = ckpt.get('best_loss', float('inf'))
1108
+ if is_main:
1109
+ print(f'Resumed from step {start_step}, best_loss={best_loss:.4f}')
1110
+ else:
1111
+ latest = os.path.join(args.ckpt_dir, 'cognet_1b_latest.pt')
1112
+ if os.path.exists(latest):
1113
+ ckpt = torch.load(latest, map_location=device, weights_only=False)
1114
+ model.load_state_dict(ckpt['model_state_dict'])
1115
+ if 'optimizer_state_dict' in ckpt:
1116
+ optimizer.load_state_dict(ckpt['optimizer_state_dict'])
1117
+ start_step = ckpt.get('step', 0)
1118
+ best_loss = ckpt.get('best_loss', float('inf'))
1119
+ if is_main:
1120
+ print(f'Auto-resumed from step {start_step}, best_loss={best_loss:.4f}')
1121
+
1122
+ # ── Train ──
1123
+ effective_batch = args.batch_size * args.grad_accum * world_size
1124
+ if is_main:
1125
+ print(f'\nStarting: step {start_step} -> {args.max_steps}')
1126
+ print(f'Batch={args.batch_size} x GradAccum={args.grad_accum} x GPUs={world_size} = Effective {effective_batch}')
1127
+ print(f'SeqLen={args.seq_len}, LR={args.min_lr}-{args.max_lr}')
1128
+ print(f'TF32=ON, Gradient checkpointing={args.use_grad_checkpoint}')
1129
+ print(f'Graceful shutdown: SIGTERM/SIGINT will save checkpoint')
1130
+ print(f'\n[BENCH] Un benchmark de 10 steps va mesurer la vitesse rΓ©elle...')
1131
+
1132
+ model.train()
1133
+ data_iter = iter(dataloader)
1134
+ t0 = time.time()
1135
+ loss_val = 0.0
1136
+
1137
+ # ═══════════════════════════════════════════════════════════
1138
+ # VRAI BENCHMARK β€” Mesure les tokens/sec rΓ©els sur votre GPU
1139
+ # ═══════════════════════════════════════════════════════════
1140
+ BENCHMARK_WARMUP_STEPS = 3 # steps pour chauffer (compile, caches CUDA)
1141
+ BENCHMARK_MEASURE_STEPS = 10 # steps pour la mesure rΓ©elle
1142
+ measured_steps_per_sec = None
1143
+ measured_tokens_per_sec = None
1144
+
1145
+ if is_main:
1146
+ print(f'\n{"="*60}')
1147
+ print(f' BENCHMARK β€” Mesure des performances rΓ©elles')
1148
+ print(f'{"="*60}')
1149
+ print(f' Warmup: {BENCHMARK_WARMUP_STEPS} steps')
1150
+ print(f' Mesure: {BENCHMARK_MEASURE_STEPS} steps')
1151
+ print(f' Config: batch={args.batch_size}, grad_accum={args.grad_accum}, seq_len={args.seq_len}')
1152
+
1153
+ # Phase 1: Warmup (compile, caches CUDA, allocation mΓ©moire)
1154
+ for i in range(BENCHMARK_WARMUP_STEPS):
1155
+ try:
1156
+ batch = next(data_iter)
1157
+ except StopIteration:
1158
+ data_iter = iter(dataloader)
1159
+ batch = next(data_iter)
1160
+ x, y = batch
1161
+ if not isinstance(x, torch.Tensor):
1162
+ x, y = x, y
1163
+ x = x.to(device, non_blocking=True)
1164
+ y = y.to(device, non_blocking=True)
1165
+ with torch.amp.autocast('cuda', dtype=torch.bfloat16 if use_bf16 else torch.float16):
1166
+ result = model(x)
1167
+ loss = F.cross_entropy(result['logits'].view(-1, tokenizer.vocab_size), y.view(-1), ignore_index=0)
1168
+ (loss / args.grad_accum).backward()
1169
+ optimizer.zero_grad(set_to_none=True)
1170
+
1171
+ if torch.cuda.is_available():
1172
+ torch.cuda.synchronize()
1173
+
1174
+ if is_main:
1175
+ print(f' Warmup terminΓ© β€” dΓ©but de la mesure...')
1176
+
1177
+ # Phase 2: Mesure rΓ©elle (forward + backward + optimizer step)
1178
+ bench_t0 = time.time()
1179
+ for i in range(BENCHMARK_MEASURE_STEPS):
1180
+ optimizer.zero_grad(set_to_none=True)
1181
+ accum_loss_bench = 0.0
1182
+ for micro_step in range(args.grad_accum):
1183
+ try:
1184
+ batch = next(data_iter)
1185
+ except StopIteration:
1186
+ data_iter = iter(dataloader)
1187
+ batch = next(data_iter)
1188
+ x, y = batch
1189
+ if not isinstance(x, torch.Tensor):
1190
+ x, y = x, y
1191
+ x = x.to(device, non_blocking=True)
1192
+ y = y.to(device, non_blocking=True)
1193
+ with torch.amp.autocast('cuda', dtype=torch.bfloat16 if use_bf16 else torch.float16):
1194
+ result = model(x)
1195
+ loss = F.cross_entropy(result['logits'].view(-1, tokenizer.vocab_size), y.view(-1), ignore_index=0)
1196
+ (loss / args.grad_accum).backward()
1197
+ accum_loss_bench += loss.item()
1198
+
1199
+ # Clip + step (mΓͺme chose que la vraie boucle)
1200
+ if use_bf16:
1201
+ torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
1202
+ optimizer.step()
1203
+ else:
1204
+ scaler.unscale_(optimizer)
1205
+ torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
1206
+ scaler.step(optimizer)
1207
+ scaler.update()
1208
+
1209
+ if torch.cuda.is_available():
1210
+ torch.cuda.synchronize()
1211
+ bench_elapsed = time.time() - bench_t0
1212
+
1213
+ # Calcul des performances mesurΓ©es
1214
+ measured_steps_per_sec = BENCHMARK_MEASURE_STEPS / max(bench_elapsed, 0.001)
1215
+ measured_tokens_per_sec = measured_steps_per_sec * effective_batch * args.seq_len
1216
+ vram = torch.cuda.memory_allocated() / 1e9 if torch.cuda.is_available() else 0
1217
+
1218
+ if is_main:
1219
+ remaining_steps = args.max_steps - start_step
1220
+ est_hours = remaining_steps / max(measured_steps_per_sec, 0.001) / 3600
1221
+ print(f'\n ╔══════════════════════════════════════════════════════╗')
1222
+ print(f' ║ RÉSULTATS DU BENCHMARK ║')
1223
+ print(f' ╠══════════════════════════════════════════════════════╣')
1224
+ print(f' β•‘ {measured_steps_per_sec:>8.2f} steps/sec (optimizer steps) β•‘')
1225
+ print(f' β•‘ {measured_tokens_per_sec:>8.0f} tokens/sec β•‘')
1226
+ print(f' β•‘ {bench_elapsed:>8.2f} sec pour {BENCHMARK_MEASURE_STEPS} steps β•‘')
1227
+ print(f' β•‘ {vram:>8.1f} GB VRAM utilisΓ© β•‘')
1228
+ print(f' ╠══════════════════════════════════════════════════════╣')
1229
+ print(f' β•‘ Temps estimΓ© pour {remaining_steps:,} steps restants β•‘')
1230
+ print(f' β•‘ ~{est_hours:>6.1f} heures ({est_hours/24:.1f} jours) β•‘')
1231
+ print(f' β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•')
1232
+ print(f'{"="*60}\n')
1233
+
1234
+ # Sauvegarder le rΓ©sultat du benchmark dans un fichier
1235
+ if is_main:
1236
+ bench_info = {
1237
+ 'timestamp': datetime.now().isoformat(),
1238
+ 'steps_per_sec': measured_steps_per_sec,
1239
+ 'tokens_per_sec': measured_tokens_per_sec,
1240
+ 'benchmark_steps': BENCHMARK_MEASURE_STEPS,
1241
+ 'benchmark_time_sec': bench_elapsed,
1242
+ 'vram_gb': vram,
1243
+ 'effective_batch': effective_batch,
1244
+ 'seq_len': args.seq_len,
1245
+ 'model_size': args.model_size,
1246
+ 'grad_accum': args.grad_accum,
1247
+ 'compile': args.compile,
1248
+ 'bf16': use_bf16,
1249
+ 'fsdp': args.use_fsdp,
1250
+ }
1251
+ bench_path = os.path.join(args.ckpt_dir, 'benchmark_results.json')
1252
+ os.makedirs(args.ckpt_dir, exist_ok=True)
1253
+ with open(bench_path, 'w') as f:
1254
+ json.dump(bench_info, f, indent=2)
1255
+ print(f' Benchmark sauvΓ©: {bench_path}')
1256
+
1257
+ for step in range(start_step, args.max_steps):
1258
+ if shutdown_requested:
1259
+ if is_main:
1260
+ save_checkpoint(model, optimizer, step, loss_val, best_loss, tokenizer, args, 'cognet_1b_latest.pt')
1261
+ print(f'Checkpoint saved at step {step}. Exiting.')
1262
+ break
1263
+
1264
+ lr = get_cosine_lr(step, args.warmup_steps, args.max_steps, args.max_lr, args.min_lr)
1265
+ for param_group in optimizer.param_groups:
1266
+ param_group['lr'] = lr
1267
+
1268
+ optimizer.zero_grad(set_to_none=True)
1269
+ accum_loss = 0.0
1270
+
1271
+ # Use compiled step if available
1272
+ if compiled_step is not None:
1273
+ for micro_step in range(args.grad_accum):
1274
+ try:
1275
+ batch = next(data_iter)
1276
+ except StopIteration:
1277
+ data_iter = iter(dataloader)
1278
+ batch = next(data_iter)
1279
+ x, y = batch
1280
+ if not isinstance(x, torch.Tensor):
1281
+ x, y = x, y
1282
+ x = x.to(device, non_blocking=True)
1283
+ y = y.to(device, non_blocking=True)
1284
+ loss = compiled_step(x, y)
1285
+ accum_loss += loss.item()
1286
+ else:
1287
+ for micro_step in range(args.grad_accum):
1288
+ try:
1289
+ batch = next(data_iter)
1290
+ except StopIteration:
1291
+ data_iter = iter(dataloader)
1292
+ batch = next(data_iter)
1293
+ x, y = batch
1294
+ if not isinstance(x, torch.Tensor):
1295
+ x, y = x, y
1296
+ x = x.to(device, non_blocking=True)
1297
+ y = y.to(device, non_blocking=True)
1298
+
1299
+ if use_bf16:
1300
+ with torch.amp.autocast('cuda', dtype=torch.bfloat16):
1301
+ result = model(x)
1302
+ logits = result['logits']
1303
+ loss = F.cross_entropy(logits.view(-1, tokenizer.vocab_size), y.view(-1), ignore_index=0)
1304
+ (loss / args.grad_accum).backward()
1305
+ else:
1306
+ with torch.amp.autocast('cuda', dtype=torch.float16):
1307
+ result = model(x)
1308
+ logits = result['logits']
1309
+ loss = F.cross_entropy(logits.view(-1, tokenizer.vocab_size), y.view(-1), ignore_index=0)
1310
+ scaler.scale(loss / args.grad_accum).backward()
1311
+
1312
+ accum_loss += loss.item()
1313
+
1314
+ # Step optimizer
1315
+ if use_bf16:
1316
+ grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
1317
+ optimizer.step()
1318
+ else:
1319
+ scaler.unscale_(optimizer)
1320
+ grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
1321
+ scaler.step(optimizer)
1322
+ scaler.update()
1323
+
1324
+ loss_val = accum_loss / args.grad_accum
1325
+
1326
+ # Logging avec ETA calculΓ© Γ  partir de la vitesse mesurΓ©e
1327
+ if is_main and step % args.log_every == 0:
1328
+ elapsed = time.time() - t0
1329
+ live_steps_per_sec = args.log_every / max(elapsed, 0.001)
1330
+ live_tokens_per_sec = live_steps_per_sec * effective_batch * args.seq_len
1331
+ vram = torch.cuda.memory_allocated() / 1e9 if torch.cuda.is_available() else 0
1332
+
1333
+ # ETA basΓ© sur la vitesse du benchmark (plus stable que la vitesse instantanΓ©e)
1334
+ remaining_steps = args.max_steps - step
1335
+ if measured_steps_per_sec and measured_steps_per_sec > 0:
1336
+ eta_hours = remaining_steps / measured_steps_per_sec / 3600
1337
+ eta_str = f'{eta_hours:.1f}h' if eta_hours < 48 else f'{eta_hours/24:.1f}j'
1338
+ else:
1339
+ # Fallback: utiliser la vitesse instantanΓ©e
1340
+ eta_hours = remaining_steps / max(live_steps_per_sec, 0.001) / 3600
1341
+ eta_str = f'{eta_hours:.1f}h' if eta_hours < 48 else f'{eta_hours/24:.1f}j'
1342
+
1343
+ print(
1344
+ f'Step {step:>7d}/{args.max_steps} | '
1345
+ f'Loss: {loss_val:.4f} | PPL: {math.exp(min(loss_val, 20)):.1f} | '
1346
+ f'LR: {lr:.2e} | Grad: {grad_norm:.2f} | '
1347
+ f'VRAM: {vram:.1f}GB | {live_tokens_per_sec:.0f} tok/s | {live_steps_per_sec:.1f} step/s | '
1348
+ f'ETA: {eta_str}'
1349
+ )
1350
+ t0 = time.time()
1351
+
1352
+ # Sample generation
1353
+ if is_main and step > 0 and step % args.eval_every == 0:
1354
+ model.eval()
1355
+ with torch.no_grad():
1356
+ prompt = torch.tensor([[tokenizer.bos_token_id]], device=device)
1357
+ sample_ids = model.generate(prompt, max_new_tokens=150, temperature=0.8, top_k=50)
1358
+ sample_text = tokenizer.decode(sample_ids[0].tolist())
1359
+ print(f'--- Sample step {step} ---')
1360
+ print(sample_text[:300])
1361
+ print(f'--- End ---')
1362
+ model.train()
1363
+
1364
+ # Save checkpoint (overwrite toujours les mΓͺmes fichiers, pas d'accumulation)
1365
+ if is_main and step > 0 and step % args.save_every == 0:
1366
+ save_checkpoint(model, optimizer, step, loss_val, best_loss, tokenizer, args, 'cognet_1b_latest.pt')
1367
+
1368
+ if loss_val < best_loss:
1369
+ best_loss = loss_val
1370
+ if args.async_ckpt and async_saver:
1371
+ async_saver.wait()
1372
+ save_checkpoint(model, optimizer, step, loss_val, best_loss, tokenizer, args, 'cognet_1b_best.pt')
1373
+ print(f'Checkpoint step {step} saved (loss={loss_val:.4f}) β€” NEW BEST!')
1374
+ else:
1375
+ print(f'Checkpoint step {step} saved (loss={loss_val:.4f}, best={best_loss:.4f})')
1376
+
1377
+ else:
1378
+ # Training completed normally
1379
+ if is_main:
1380
+ if async_saver:
1381
+ async_saver.wait()
1382
+ save_checkpoint(model, optimizer, args.max_steps, loss_val, best_loss, tokenizer, args, 'cognet_1b_final.pt')
1383
+ print(f'\nTraining complete! Final loss: {loss_val:.4f}, Best: {best_loss:.4f}')
1384
+
1385
+ # Push to HF
1386
+ best_path = os.path.join(args.ckpt_dir, 'cognet_1b_best.pt')
1387
+ if os.path.exists(best_path):
1388
+ push_to_huggingface(best_path, tokenizer)
1389
+
1390
+ if is_distributed:
1391
+ from torch.distributed import destroy_process_group
1392
+ destroy_process_group()
1393
+
1394
+
1395
+ if __name__ == '__main__':
1396
+ main()