thefinalboss commited on
Commit
388fa4d
Β·
verified Β·
1 Parent(s): ce094de

Upload hf_scripts/prepare_data.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. hf_scripts/prepare_data.py +429 -0
hf_scripts/prepare_data.py ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CogNet Data Preparation Script
3
+ ===============================
4
+ Prepares and tokenizes multiple datasets for training:
5
+ - Wikipedia (multilingual)
6
+ - Code datasets (The Stack, CodeParrot)
7
+ - Books (BookCorpus)
8
+ - Common Crawl subsets
9
+ - Custom local files
10
+
11
+ Outputs pre-tokenized .pt files for maximum training throughput.
12
+
13
+ Usage:
14
+ python prepare_data.py --output-dir ./data_cache --vocab-size 32000
15
+ python prepare_data.py --output-dir ./data_cache --datasets wiki code books
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import sys
22
+ import time
23
+ from pathlib import Path
24
+ from typing import Dict, List, Optional
25
+
26
+ # ─── Dataset Configs ─────────────────────────────────────────────────────────
27
+
28
+ DATASET_CONFIGS = {
29
+ 'wiki': {
30
+ 'path': 'wikimedia/wikipedia',
31
+ 'subset': '20231101.en',
32
+ 'split': 'train',
33
+ 'text_field': 'text',
34
+ 'max_docs': None,
35
+ 'max_chars': 5_000_000_000, # 5B chars
36
+ 'description': 'Wikipedia English',
37
+ },
38
+ 'wiki_fr': {
39
+ 'path': 'wikimedia/wikipedia',
40
+ 'subset': '20231101.fr',
41
+ 'split': 'train',
42
+ 'text_field': 'text',
43
+ 'max_docs': None,
44
+ 'max_chars': 2_000_000_000,
45
+ 'description': 'Wikipedia French',
46
+ },
47
+ 'code': {
48
+ 'path': 'bigcode/the-stack',
49
+ 'subset': 'data',
50
+ 'split': 'train',
51
+ 'text_field': 'content',
52
+ 'max_docs': None,
53
+ 'max_chars': 5_000_000_000,
54
+ 'description': 'The Stack (multi-language code)',
55
+ 'languages': ['python', 'javascript', 'java', 'cpp', 'c', 'rust', 'go', 'typescript'],
56
+ },
57
+ 'code_python': {
58
+ 'path': 'bigcode/the-stack',
59
+ 'subset': 'data',
60
+ 'split': 'train',
61
+ 'text_field': 'content',
62
+ 'max_docs': None,
63
+ 'max_chars': 3_000_000_000,
64
+ 'description': 'Python code from The Stack',
65
+ 'languages': ['python'],
66
+ },
67
+ 'books': {
68
+ 'path': 'bookcorpus/bookcorpus',
69
+ 'subset': None,
70
+ 'split': 'train',
71
+ 'text_field': 'text',
72
+ 'max_docs': None,
73
+ 'max_chars': 3_000_000_000,
74
+ 'description': 'BookCorpus',
75
+ },
76
+ 'c4': {
77
+ 'path': 'allenai/c4',
78
+ 'subset': 'en',
79
+ 'split': 'train',
80
+ 'text_field': 'text',
81
+ 'max_docs': None,
82
+ 'max_chars': 10_000_000_000,
83
+ 'description': 'C4 (Colossal Clean Crawled Corpus)',
84
+ },
85
+ 'openwebtext': {
86
+ 'path': 'openwebtext',
87
+ 'subset': None,
88
+ 'split': 'train',
89
+ 'text_field': 'text',
90
+ 'max_docs': None,
91
+ 'max_chars': 5_000_000_000,
92
+ 'description': 'OpenWebText',
93
+ },
94
+ 'alpaca': {
95
+ 'path': 'tatsu-lab/alpaca',
96
+ 'subset': None,
97
+ 'split': 'train',
98
+ 'text_field': 'text',
99
+ 'max_docs': None,
100
+ 'max_chars': 500_000_000,
101
+ 'description': 'Alpaca instruction data',
102
+ 'format_fn': 'alpaca_format',
103
+ },
104
+ 'redpajama': {
105
+ 'path': 'togethercomputer/RedPajama-Data-1T',
106
+ 'subset': None,
107
+ 'split': 'train',
108
+ 'text_field': 'text',
109
+ 'max_docs': None,
110
+ 'max_chars': 10_000_000_000,
111
+ 'description': 'RedPajama 1T',
112
+ },
113
+ }
114
+
115
+
116
+ def alpaca_format(example: Dict) -> str:
117
+ """Format Alpaca data into text."""
118
+ instruction = example.get('instruction', '')
119
+ input_text = example.get('input', '')
120
+ output = example.get('output', '')
121
+ if input_text:
122
+ return f"### Instruction:\n{instruction}\n\n### Input:\n{input_text}\n\n### Response:\n{output}"
123
+ return f"### Instruction:\n{instruction}\n\n### Response:\n{output}"
124
+
125
+
126
+ # ─── Tokenizer Training ──────────────────────────────────────────────────────
127
+
128
+ def train_bpe_tokenizer(output_dir: str, vocab_size: int = 32000,
129
+ sample_files: Optional[List[str]] = None) -> str:
130
+ """
131
+ Train a BPE tokenizer on sample text data.
132
+ Returns the path to the saved tokenizer.
133
+ """
134
+ try:
135
+ from tokenizers import Tokenizer
136
+ from tokenizers.models import BPE
137
+ from tokenizers.trainers import BpeTrainer
138
+ from tokenizers.pre_tokenizers import Metaspace, ByteLevel
139
+ from tokenizers.decoders import ByteLevel as ByteLevelDecoder
140
+ except ImportError:
141
+ print("ERROR: 'tokenizers' library not installed.")
142
+ print("Install with: pip install tokenizers")
143
+ sys.exit(1)
144
+
145
+ tokenizer_path = os.path.join(output_dir, f"bpe_tokenizer_{vocab_size}.json")
146
+ if os.path.exists(tokenizer_path):
147
+ print(f"Tokenizer already exists at {tokenizer_path}")
148
+ return tokenizer_path
149
+
150
+ print(f"\nTraining BPE tokenizer (vocab_size={vocab_size})...")
151
+
152
+ tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
153
+ tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=False)
154
+ tokenizer.decoder = ByteLevelDecoder()
155
+
156
+ trainer = BpeTrainer(
157
+ vocab_size=vocab_size,
158
+ special_tokens=[
159
+ "[PAD]", # 0
160
+ "[UNK]", # 1
161
+ "[BOS]", # 2
162
+ "[EOS]", # 3
163
+ ],
164
+ show_progress=True,
165
+ initial_alphabet=ByteLevel.alphabet(),
166
+ )
167
+
168
+ if sample_files and len(sample_files) > 0:
169
+ print(f"Training on {len(sample_files)} files...")
170
+ tokenizer.train(sample_files, trainer)
171
+ else:
172
+ print("No sample files provided. Training on built-in data...")
173
+ # Generate diverse sample text for tokenizer training
174
+ sample_texts = []
175
+ # English
176
+ sample_texts.extend([
177
+ "The quick brown fox jumps over the lazy dog. " * 500,
178
+ "Science and technology have transformed our understanding of the universe. " * 500,
179
+ "In the field of artificial intelligence, neural networks learn from data. " * 500,
180
+ ])
181
+ # French
182
+ sample_texts.extend([
183
+ "Le renard brun rapide saute par-dessus le chien paresseux. " * 500,
184
+ "La science et la technologie ont transforme notre comprehension de l'univers. " * 500,
185
+ ])
186
+ # Code
187
+ sample_texts.extend([
188
+ "def hello_world():\n print('Hello, World!')\n return True\n" * 500,
189
+ "class NeuralNetwork:\n def __init__(self, layers):\n self.layers = layers\n" * 500,
190
+ "import torch\nimport torch.nn as nn\nmodel = nn.Sequential(nn.Linear(768, 768))\n" * 500,
191
+ "function fibonacci(n) {\n if (n <= 1) return n;\n return fibonacci(n-1) + fibonacci(n-2);\n}\n" * 500,
192
+ ])
193
+ tokenizer.train_from_iterator(sample_texts, trainer)
194
+
195
+ os.makedirs(output_dir, exist_ok=True)
196
+ tokenizer.save(tokenizer_path)
197
+ print(f"Saved tokenizer to {tokenizer_path}")
198
+ print(f"Vocabulary size: {tokenizer.get_vocab_size()}")
199
+
200
+ return tokenizer_path
201
+
202
+
203
+ # ─── Data Processing ─────────────────────────────────────────────────────────
204
+
205
+ def process_dataset(name: str, config: Dict, tokenizer, output_dir: str,
206
+ seq_len: int = 4096) -> Optional[str]:
207
+ """
208
+ Process a single dataset and save as pre-tokenized .pt file.
209
+ Returns the output path or None if failed.
210
+ """
211
+ print(f"\n{'='*60}")
212
+ print(f"Processing: {name} β€” {config.get('description', '')}")
213
+ print(f"{'='*60}")
214
+
215
+ output_path = os.path.join(output_dir, f"{name}_packed_seq{seq_len}.pt")
216
+ if os.path.exists(output_path):
217
+ print(f"Already exists: {output_path}")
218
+ return output_path
219
+
220
+ try:
221
+ from datasets import load_dataset
222
+ except ImportError:
223
+ print("ERROR: 'datasets' library not installed.")
224
+ print("Install with: pip install datasets")
225
+ return None
226
+
227
+ # Load dataset
228
+ print(f"Loading {config['path']}...")
229
+ try:
230
+ if config.get('subset'):
231
+ ds = load_dataset(
232
+ config['path'],
233
+ config['subset'],
234
+ split=config['split'],
235
+ streaming=True,
236
+ trust_remote_code=True,
237
+ )
238
+ else:
239
+ ds = load_dataset(
240
+ config['path'],
241
+ split=config['split'],
242
+ streaming=True,
243
+ trust_remote_code=True,
244
+ )
245
+ except Exception as e:
246
+ print(f"Failed to load dataset: {e}")
247
+ return None
248
+
249
+ # Filter by language if specified (for code datasets)
250
+ if config.get('languages'):
251
+ languages = set(config['languages'])
252
+ def lang_filter(example):
253
+ return example.get('language', '') in languages
254
+ ds = ds.filter(lang_filter)
255
+
256
+ # Tokenize
257
+ all_ids = []
258
+ doc_count = 0
259
+ total_chars = 0
260
+ max_chars = config.get('max_chars', 5_000_000_000)
261
+ text_field = config.get('text_field', 'text')
262
+ format_fn_name = config.get('format_fn')
263
+
264
+ t0 = time.time()
265
+
266
+ for example in ds:
267
+ # Get text
268
+ if format_fn_name == 'alpaca_format':
269
+ text = alpaca_format(example)
270
+ else:
271
+ text = example.get(text_field, '')
272
+
273
+ if not text or len(text.strip()) < 20:
274
+ continue
275
+
276
+ # Tokenize
277
+ ids = tokenizer.encode(text)
278
+ if isinstance(ids, list):
279
+ all_ids.extend(ids)
280
+ elif hasattr(ids, 'ids'):
281
+ all_ids.extend(ids.ids)
282
+ else:
283
+ all_ids.extend(list(ids))
284
+
285
+ # Add EOS between documents
286
+ all_ids.append(3) # [EOS] token id
287
+
288
+ doc_count += 1
289
+ total_chars += len(text)
290
+
291
+ if doc_count % 10000 == 0:
292
+ elapsed = time.time() - t0
293
+ print(f" {doc_count:,} docs | {len(all_ids):,} tokens | "
294
+ f"{total_chars/1e9:.2f}B chars | {elapsed:.0f}s")
295
+
296
+ if total_chars >= max_chars:
297
+ print(f" Reached char limit ({max_chars/1e9:.1f}B)")
298
+ break
299
+
300
+ if config.get('max_docs') and doc_count >= config['max_docs']:
301
+ print(f" Reached doc limit ({config['max_docs']:,})")
302
+ break
303
+
304
+ if len(all_ids) == 0:
305
+ print(" No tokens collected!")
306
+ return None
307
+
308
+ # Save
309
+ elapsed = time.time() - t0
310
+ print(f"\n Final: {doc_count:,} docs, {len(all_ids):,} tokens, {total_chars/1e9:.2f}B chars")
311
+ print(f" Time: {elapsed:.0f}s ({doc_count/max(elapsed,1):,.0f} docs/s)")
312
+
313
+ # Pack into sequences and save
314
+ import torch
315
+ tensor_data = torch.tensor(all_ids, dtype=torch.long)
316
+ torch.save(tensor_data, output_path)
317
+ size_gb = os.path.getsize(output_path) / 1e9
318
+ print(f" Saved to {output_path} ({size_gb:.2f} GB)")
319
+
320
+ return output_path
321
+
322
+
323
+ # ─── Merge Datasets ──────────────────────────────────────────────────────────
324
+
325
+ def merge_datasets(paths: List[str], output_path: str):
326
+ """Merge multiple pre-tokenized datasets into one."""
327
+ print(f"\nMerging {len(paths)} datasets...")
328
+ all_data = []
329
+
330
+ for path in paths:
331
+ if not os.path.exists(path):
332
+ print(f" Skipping (not found): {path}")
333
+ continue
334
+ data = torch.load(path, map_location='cpu', weights_only=True)
335
+ all_data.append(data)
336
+ print(f" {path}: {len(data):,} tokens")
337
+
338
+ if not all_data:
339
+ print(" No data to merge!")
340
+ return
341
+
342
+ merged = torch.cat(all_data, dim=0)
343
+ print(f" Total: {len(merged):,} tokens")
344
+
345
+ torch.save(merged, output_path)
346
+ size_gb = os.path.getsize(output_path) / 1e9
347
+ print(f" Saved to {output_path} ({size_gb:.2f} GB)")
348
+
349
+
350
+ # ─── Main ────────────────────────────────────────────────────────────────────
351
+
352
+ def main():
353
+ parser = argparse.ArgumentParser(description='CogNet Data Preparation')
354
+ parser.add_argument('--output-dir', type=str, default='./data_cache',
355
+ help='Output directory for processed data')
356
+ parser.add_argument('--vocab-size', type=int, default=32000,
357
+ help='BPE vocabulary size')
358
+ parser.add_argument('--seq-len', type=int, default=4096,
359
+ help='Sequence length for packing')
360
+ parser.add_argument('--datasets', nargs='+',
361
+ default=['wiki', 'code'],
362
+ choices=list(DATASET_CONFIGS.keys()) + ['all'],
363
+ help='Datasets to process')
364
+ parser.add_argument('--merge', action='store_true',
365
+ help='Merge all datasets into one file')
366
+ parser.add_argument('--local-data', type=str, default=None,
367
+ help='Path to local data directory with .txt/.py files')
368
+ args = parser.parse_args()
369
+
370
+ os.makedirs(args.output_dir, exist_ok=True)
371
+
372
+ # Train tokenizer
373
+ tokenizer_path = train_bpe_tokenizer(args.output_dir, args.vocab_size)
374
+
375
+ # Load tokenizer
376
+ from tokenizers import Tokenizer
377
+ tokenizer = Tokenizer.from_file(tokenizer_path)
378
+ print(f"\nTokenizer loaded: {tokenizer.get_vocab_size()} vocab")
379
+
380
+ # Process datasets
381
+ if 'all' in args.datasets:
382
+ datasets_to_process = list(DATASET_CONFIGS.keys())
383
+ else:
384
+ datasets_to_process = args.datasets
385
+
386
+ output_paths = []
387
+ for name in datasets_to_process:
388
+ config = DATASET_CONFIGS[name]
389
+ path = process_dataset(name, config, tokenizer, args.output_dir, args.seq_len)
390
+ if path:
391
+ output_paths.append(path)
392
+
393
+ # Process local data
394
+ if args.local_data and os.path.exists(args.local_data):
395
+ print(f"\nProcessing local data from {args.local_data}...")
396
+ local_ids = []
397
+ for ext in ['*.txt', '*.md', '*.py', '*.js', '*.java', '*.c', '*.cpp', '*.rs', '*.go']:
398
+ for fpath in Path(args.local_data).rglob(ext):
399
+ try:
400
+ with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
401
+ text = f.read()
402
+ ids = tokenizer.encode(text)
403
+ if isinstance(ids, list):
404
+ local_ids.extend(ids)
405
+ elif hasattr(ids, 'ids'):
406
+ local_ids.extend(ids.ids)
407
+ local_ids.append(3) # EOS
408
+ except Exception as e:
409
+ print(f" Skipping {fpath}: {e}")
410
+
411
+ if local_ids:
412
+ local_path = os.path.join(args.output_dir, "local_packed_seq{args.seq_len}.pt")
413
+ torch.save(torch.tensor(local_ids, dtype=torch.long), local_path)
414
+ output_paths.append(local_path)
415
+ print(f" Local data: {len(local_ids):,} tokens")
416
+
417
+ # Merge
418
+ if args.merge and len(output_paths) > 1:
419
+ merge_path = os.path.join(args.output_dir, f"train_packed_seq{args.seq_len}.pt")
420
+ merge_datasets(output_paths, merge_path)
421
+
422
+ print("\n" + "=" * 60)
423
+ print("Data preparation complete!")
424
+ print(f"Output directory: {args.output_dir}")
425
+ print("=" * 60)
426
+
427
+
428
+ if __name__ == '__main__':
429
+ main()