userkuku commited on
Commit
7bb2a87
·
verified ·
1 Parent(s): ecc111a

Upload training.py

Browse files
Files changed (1) hide show
  1. training.py +519 -0
training.py ADDED
@@ -0,0 +1,519 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Training utilities for LMCODE (Language Model with Memory CODE).
3
+
4
+ Implements memory-aware training with:
5
+ - Experience replay from long-term memory
6
+ - Memory consolidation
7
+ - Gradient clipping for memory stability
8
+ """
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ import torch.optim as optim
13
+ from torch.utils.data import Dataset, DataLoader
14
+ import numpy as np
15
+ from typing import Optional, Dict, List, Tuple
16
+ from model_architecture import LMCODE, LMCODEConfig
17
+ import math
18
+
19
+
20
+ class MemoryDataset(Dataset):
21
+ """
22
+ Dataset that can sample from both current data and long-term memory.
23
+
24
+ Implements experience replay by mixing current training examples
25
+ with retrieved memories from the model's long-term memory.
26
+ """
27
+
28
+ def __init__(self, data: List[Dict], memory_sample_ratio: float = 0.2):
29
+ """
30
+ Args:
31
+ data: List of training examples (dicts with 'input_ids', 'labels')
32
+ memory_sample_ratio: Fraction of batch to sample from memory
33
+ """
34
+ self.data = data
35
+ self.memory_sample_ratio = memory_sample_ratio
36
+
37
+ def __len__(self) -> int:
38
+ return len(self.data)
39
+
40
+ def __getitem__(self, idx: int) -> Dict:
41
+ return self.data[idx]
42
+
43
+ def sample_with_memory(self, model: LMCODE, batch_size: int) -> Dict[str, torch.Tensor]:
44
+ """
45
+ Sample a batch mixing current data and memory samples.
46
+
47
+ Args:
48
+ model: LMCODE model to query memory from
49
+ batch_size: Total batch size
50
+
51
+ Returns:
52
+ Batch dictionary with mixed data
53
+ """
54
+ # Sample from current data
55
+ memory_batch_size = int(batch_size * self.memory_sample_ratio)
56
+ current_batch_size = batch_size - memory_batch_size
57
+
58
+ # Sample current data
59
+ current_indices = torch.randint(0, len(self.data), (current_batch_size,))
60
+ current_batch = [self.data[i] for i in current_indices.tolist()]
61
+
62
+ # Pad sequences
63
+ current_batch_padded = self._pad_batch(current_batch)
64
+
65
+ # Sample from long-term memory (if available)
66
+ memory_batch_padded = None
67
+ if memory_batch_size > 0 and hasattr(model, 'long_term_memory_size'):
68
+ # In practice, retrieve from model's long-term memory
69
+ # For now, return None
70
+ pass
71
+
72
+ return current_batch_padded
73
+
74
+ def _pad_batch(self, batch: List[Dict]) -> Dict[str, torch.Tensor]:
75
+ """Pad a batch of sequences to the same length."""
76
+ max_len = max(item['input_ids'].shape[-1] for item in batch)
77
+
78
+ padded_inputs = []
79
+ padded_labels = []
80
+
81
+ for item in batch:
82
+ input_ids = item['input_ids'].squeeze(0)
83
+ labels = item.get('labels', input_ids.clone())
84
+
85
+ # Pad input
86
+ pad_len = max_len - input_ids.shape[-1]
87
+ if pad_len > 0:
88
+ input_ids = torch.cat([input_ids, torch.zeros(pad_len, dtype=input_ids.dtype)])
89
+
90
+ # Pad labels
91
+ if labels.shape[-1] < max_len:
92
+ pad_len = max_len - labels.shape[-1]
93
+ labels = torch.cat([labels, torch.full((pad_len,), -100, dtype=labels.dtype)])
94
+
95
+ padded_inputs.append(input_ids)
96
+ padded_labels.append(labels)
97
+
98
+ return {
99
+ 'input_ids': torch.stack(padded_inputs),
100
+ 'labels': torch.stack(padded_labels)
101
+ }
102
+
103
+
104
+ class MemoryAwareTrainer:
105
+ """
106
+ Trainer for LMCODE with memory-aware training.
107
+
108
+ Features:
109
+ - Memory consolidation scheduling
110
+ - Gradient clipping for memory parameters
111
+ - Experience replay
112
+ - Memory importance updates
113
+ """
114
+
115
+ def __init__(self, model: LMCODE, config: Dict):
116
+ """
117
+ Initialize trainer.
118
+
119
+ Args:
120
+ model: LMCODE model to train
121
+ config: Training configuration dictionary
122
+ """
123
+ self.model = model
124
+ self.config = config
125
+
126
+ # Training parameters
127
+ self.lr = config.get('learning_rate', 1e-4)
128
+ self.weight_decay = config.get('weight_decay', 0.01)
129
+ self.gradient_clip = config.get('gradient_clip', 1.0)
130
+ self.memory_consolidation_interval = config.get('memory_consolidation_interval', 1000)
131
+ self.warmup_steps = config.get('warmup_steps', 1000)
132
+
133
+ # Optimizer with separate learning rates for memory parameters
134
+ self.optimizer = self._create_optimizer()
135
+
136
+ # Learning rate scheduler
137
+ self.scheduler = self._create_scheduler()
138
+
139
+ # Training state
140
+ self.global_step = 0
141
+ self.best_loss = float('inf')
142
+
143
+ # Loss tracking
144
+ self.loss_history = []
145
+ self.memory_stats = []
146
+
147
+ def _create_optimizer(self) -> optim.Optimizer:
148
+ """Create optimizer with parameter groups."""
149
+ # Separate memory parameters from model parameters
150
+ memory_params = []
151
+ model_params = []
152
+
153
+ for name, param in self.model.named_parameters():
154
+ if 'memory' in name:
155
+ memory_params.append(param)
156
+ else:
157
+ model_params.append(param)
158
+
159
+ # Higher learning rate for memory parameters
160
+ param_groups = [
161
+ {'params': model_params, 'lr': self.lr, 'weight_decay': self.weight_decay},
162
+ {'params': memory_params, 'lr': self.lr * 2, 'weight_decay': 0.0} # No weight decay for memory
163
+ ]
164
+
165
+ return optim.AdamW(param_groups)
166
+
167
+ def _create_scheduler(self):
168
+ """Create learning rate scheduler with warmup."""
169
+ def lr_lambda(current_step):
170
+ if current_step < self.warmup_steps:
171
+ return float(current_step) / float(max(1, self.warmup_steps))
172
+ return max(
173
+ 0.0,
174
+ float(self.config.get('total_steps', 10000) - current_step) /
175
+ float(max(1, self.config.get('total_steps', 10000) - self.warmup_steps))
176
+ )
177
+
178
+ return optim.lr_scheduler.LambdaLR(self.optimizer, lr_lambda)
179
+
180
+ def train_step(self, batch: Dict[str, torch.Tensor]) -> Dict[str, float]:
181
+ """
182
+ Perform a single training step.
183
+
184
+ Args:
185
+ batch: Batch of training data
186
+
187
+ Returns:
188
+ Dictionary with loss and memory statistics
189
+ """
190
+ self.model.train()
191
+
192
+ # Move batch to device
193
+ device = next(self.model.parameters()).device
194
+ input_ids = batch['input_ids'].to(device)
195
+ labels = batch['labels'].to(device)
196
+
197
+ # Determine whether to store in long-term memory
198
+ # Store periodically (e.g., every 10 steps)
199
+ store_long_term = (self.global_step % 10 == 0)
200
+
201
+ # Forward pass
202
+ outputs = self.model(
203
+ input_ids=input_ids,
204
+ labels=labels,
205
+ use_long_term_memory=True,
206
+ store_long_term=store_long_term
207
+ )
208
+
209
+ loss = outputs['loss']
210
+
211
+ # Backward pass
212
+ self.optimizer.zero_grad()
213
+ loss.backward()
214
+
215
+ # Gradient clipping
216
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.gradient_clip)
217
+
218
+ # Memory-specific gradient clipping
219
+ self._clip_memory_gradients()
220
+
221
+ # Optimizer step
222
+ self.optimizer.step()
223
+ self.scheduler.step()
224
+
225
+ # Update memory importance
226
+ if self.global_step % 50 == 0:
227
+ self._update_memory_importance(outputs)
228
+
229
+ # Consolidate memories periodically
230
+ if self.global_step % self.memory_consolidation_interval == 0:
231
+ self._consolidate_memories()
232
+
233
+ # Track statistics
234
+ stats = {
235
+ 'loss': loss.item(),
236
+ 'learning_rate': self.scheduler.get_last_lr()[0],
237
+ 'global_step': self.global_step,
238
+ 'store_long_term': store_long_term
239
+ }
240
+
241
+ # Add memory statistics
242
+ memory_stats = self._get_memory_stats()
243
+ stats.update(memory_stats)
244
+
245
+ self.loss_history.append(loss.item())
246
+ self.memory_stats.append(memory_stats)
247
+ self.global_step += 1
248
+
249
+ return stats
250
+
251
+ def _clip_memory_gradients(self):
252
+ """Apply special gradient clipping for memory parameters."""
253
+ for name, param in self.model.named_parameters():
254
+ if 'memory' in name and param.grad is not None:
255
+ # More aggressive clipping for memory parameters
256
+ torch.nn.utils.clip_grad_norm_([param], max_norm=0.5)
257
+
258
+ def _update_memory_importance(self, outputs: Dict):
259
+ """
260
+ Update memory importance based on usage in forward pass.
261
+
262
+ Importance increases when memories are retrieved with high weight.
263
+ """
264
+ # Iterate through layers
265
+ for layer_output in outputs.get('long_term_outputs', []):
266
+ if layer_output is None:
267
+ continue
268
+
269
+ # Get retrieval weights
270
+ retrieval_weights = layer_output.get('retrieval_weights')
271
+ if retrieval_weights is not None:
272
+ # Update importance based on average retrieval weight
273
+ # This is a simplified version - in practice, you'd need
274
+ # to track which specific memories were retrieved
275
+ pass
276
+
277
+ def _consolidate_memories(self):
278
+ """Consolidate long-term memories across all layers."""
279
+ for layer in self.model.layers:
280
+ layer.long_term_memory.consolidate_memories()
281
+
282
+ def _get_memory_stats(self) -> Dict[str, float]:
283
+ """Get statistics about memory usage."""
284
+ stats = {}
285
+
286
+ for i, layer in enumerate(self.model.layers):
287
+ # Short-term memory statistics
288
+ st_memory = layer.short_term_memory.memory
289
+ stats[f'layer_{i}_st_memory_mean'] = st_memory.mean().item()
290
+ stats[f'layer_{i}_st_memory_std'] = st_memory.std().item()
291
+
292
+ # Long-term memory statistics
293
+ lt_keys = layer.long_term_memory.memory_keys
294
+ lt_values = layer.long_term_memory.memory_values
295
+ lt_importance = layer.long_term_memory.memory_importance
296
+
297
+ stats[f'layer_{i}_lt_keys_mean'] = lt_keys.mean().item()
298
+ stats[f'layer_{i}_lt_importance_mean'] = torch.sigmoid(lt_importance).mean().item()
299
+
300
+ # Count active memories
301
+ active_count = (torch.sigmoid(lt_importance) > 0.1).sum().item()
302
+ stats[f'layer_{i}_lt_active_count'] = active_count
303
+
304
+ return stats
305
+
306
+ def train(self, train_dataset: MemoryDataset,
307
+ num_epochs: int,
308
+ batch_size: int = 32,
309
+ eval_dataset: Optional[MemoryDataset] = None) -> Dict:
310
+ """
311
+ Train the model.
312
+
313
+ Args:
314
+ train_dataset: Training dataset
315
+ num_epochs: Number of training epochs
316
+ batch_size: Batch size
317
+ eval_dataset: Optional evaluation dataset
318
+
319
+ Returns:
320
+ Training history
321
+ """
322
+ history = {
323
+ 'train_loss': [],
324
+ 'eval_loss': [],
325
+ 'memory_stats': []
326
+ }
327
+
328
+ for epoch in range(num_epochs):
329
+ self.model.train()
330
+ epoch_loss = 0
331
+ num_batches = 0
332
+
333
+ # Create data loader
334
+ dataloader = DataLoader(
335
+ train_dataset,
336
+ batch_size=batch_size,
337
+ shuffle=True
338
+ )
339
+
340
+ for batch_idx, batch in enumerate(dataloader):
341
+ # Perform training step
342
+ stats = self.train_step(batch)
343
+
344
+ epoch_loss += stats['loss']
345
+ num_batches += 1
346
+
347
+ # Log progress
348
+ if batch_idx % 100 == 0:
349
+ print(f"Epoch {epoch+1}/{num_epochs}, "
350
+ f"Batch {batch_idx}/{len(dataloader)}, "
351
+ f"Loss: {stats['loss']:.4f}")
352
+
353
+ # Average epoch loss
354
+ avg_epoch_loss = epoch_loss / num_batches
355
+ history['train_loss'].append(avg_epoch_loss)
356
+
357
+ # Evaluate
358
+ if eval_dataset is not None:
359
+ eval_loss = self.evaluate(eval_dataset)
360
+ history['eval_loss'].append(eval_loss)
361
+ print(f"Epoch {epoch+1} - Train Loss: {avg_epoch_loss:.4f}, "
362
+ f"Eval Loss: {eval_loss:.4f}")
363
+ else:
364
+ print(f"Epoch {epoch+1} - Train Loss: {avg_epoch_loss:.4f}")
365
+
366
+ # Save best model
367
+ if avg_epoch_loss < self.best_loss:
368
+ self.best_loss = avg_epoch_loss
369
+ self.save_checkpoint('best_model.pt')
370
+
371
+ return history
372
+
373
+ def evaluate(self, dataset: MemoryDataset) -> float:
374
+ """
375
+ Evaluate the model on a dataset.
376
+
377
+ Args:
378
+ dataset: Evaluation dataset
379
+
380
+ Returns:
381
+ Average loss
382
+ """
383
+ self.model.eval()
384
+ total_loss = 0
385
+ num_batches = 0
386
+
387
+ dataloader = DataLoader(dataset, batch_size=32, shuffle=False)
388
+
389
+ with torch.no_grad():
390
+ for batch in dataloader:
391
+ # Move to device
392
+ device = next(self.model.parameters()).device
393
+ input_ids = batch['input_ids'].to(device)
394
+ labels = batch['labels'].to(device)
395
+
396
+ # Forward pass (no memory storage during eval)
397
+ outputs = self.model(
398
+ input_ids=input_ids,
399
+ labels=labels,
400
+ use_long_term_memory=True,
401
+ store_long_term=False
402
+ )
403
+
404
+ total_loss += outputs['loss'].item()
405
+ num_batches += 1
406
+
407
+ return total_loss / num_batches
408
+
409
+ def save_checkpoint(self, path: str):
410
+ """
411
+ Save model checkpoint.
412
+
413
+ Args:
414
+ path: Path to save checkpoint
415
+ """
416
+ checkpoint = {
417
+ 'model_state_dict': self.model.state_dict(),
418
+ 'optimizer_state_dict': self.optimizer.state_dict(),
419
+ 'scheduler_state_dict': self.scheduler.state_dict(),
420
+ 'global_step': self.global_step,
421
+ 'best_loss': self.best_loss,
422
+ 'config': self.config,
423
+ 'loss_history': self.loss_history
424
+ }
425
+
426
+ torch.save(checkpoint, path)
427
+ print(f"Checkpoint saved to {path}")
428
+
429
+ def load_checkpoint(self, path: str):
430
+ """
431
+ Load model checkpoint.
432
+
433
+ Args:
434
+ path: Path to checkpoint file
435
+ """
436
+ checkpoint = torch.load(path, map_location='cpu')
437
+
438
+ self.model.load_state_dict(checkpoint['model_state_dict'])
439
+ self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
440
+ self.scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
441
+ self.global_step = checkpoint['global_step']
442
+ self.best_loss = checkpoint['best_loss']
443
+ self.loss_history = checkpoint.get('loss_history', [])
444
+
445
+ print(f"Checkpoint loaded from {path}")
446
+
447
+
448
+ def create_synthetic_dataset(num_samples: int = 1000,
449
+ seq_len: int = 50,
450
+ vocab_size: int = 50257) -> List[Dict]:
451
+ """
452
+ Create a synthetic dataset for testing.
453
+
454
+ Args:
455
+ num_samples: Number of samples
456
+ seq_len: Sequence length
457
+ vocab_size: Vocabulary size
458
+
459
+ Returns:
460
+ List of training examples
461
+ """
462
+ dataset = []
463
+
464
+ for _ in range(num_samples):
465
+ # Generate random sequence
466
+ input_ids = torch.randint(0, vocab_size, (1, seq_len))
467
+
468
+ # Create labels (shifted by 1 for next-token prediction)
469
+ labels = torch.cat([
470
+ input_ids[:, 1:],
471
+ torch.zeros(1, 1, dtype=input_ids.dtype)
472
+ ], dim=1)
473
+
474
+ dataset.append({
475
+ 'input_ids': input_ids,
476
+ 'labels': labels
477
+ })
478
+
479
+ return dataset
480
+
481
+
482
+ if __name__ == '__main__':
483
+ # Create model
484
+ config = LMCODEConfig(
485
+ vocab_size=50257,
486
+ hidden_size=256, # Smaller for testing
487
+ num_layers=4,
488
+ num_heads=4,
489
+ short_term_memory_size=256,
490
+ long_term_memory_slots=1000
491
+ )
492
+
493
+ model = LMCODE(config)
494
+
495
+ # Create synthetic dataset
496
+ train_data = create_synthetic_dataset(num_samples=100, seq_len=32)
497
+ train_dataset = MemoryDataset(train_data, memory_sample_ratio=0.2)
498
+
499
+ # Create trainer
500
+ trainer_config = {
501
+ 'learning_rate': 1e-4,
502
+ 'weight_decay': 0.01,
503
+ 'gradient_clip': 1.0,
504
+ 'memory_consolidation_interval': 50,
505
+ 'warmup_steps': 10,
506
+ 'total_steps': 1000
507
+ }
508
+
509
+ trainer = MemoryAwareTrainer(model, trainer_config)
510
+
511
+ # Train for 2 epochs
512
+ print("Starting training...")
513
+ history = trainer.train(train_dataset, num_epochs=2, batch_size=8)
514
+
515
+ # Save model
516
+ trainer.save_checkpoint('lm_memory_model.pt')
517
+
518
+ print("Training complete!")
519
+ print(f"Final loss: {history['train_loss'][-1]:.4f}")