alsubari commited on
Commit
ae130ae
·
verified ·
1 Parent(s): 966b203

Sync from GitHub via hub-sync

Browse files
Files changed (2) hide show
  1. src/dytr/training/trainer.py +3 -2
  2. tests/test_dytr.py +489 -0
src/dytr/training/trainer.py CHANGED
@@ -67,13 +67,14 @@ class Trainer:
67
  self.device = config.device
68
  self.exp_dir = Path(exp_dir)
69
  self.exp_dir.mkdir(parents=True, exist_ok=True)
70
- self.best_val_loss = model.best_val_loss
71
  self.best_metrics = {}
72
  self.patience_counter = 0
73
  self.loss_history = []
74
  self.num_labels_per_task={}
75
  self.val_loss_history = []
76
  self.metrics_history = []
 
77
  self.logger.info("Trainer initialized")
78
  #self.logger.debug(f"Experiment directory: {exp_dir}")
79
  self.logger.debug("Device: %s ",self.config.device)
@@ -467,7 +468,7 @@ class Trainer:
467
  for task_name in train_datasets.keys():
468
  if task_name in self.model.ewc_penalties: continue
469
  ewc = EWC(self.model, task_name, lambda_param=self.config.ewc_lambda)
470
- fisher_loader = DataLoader(train_datasets[task_name][0], batch_size= 8, shuffle=True, collate_fn=collate_fn)
471
  ewc.compute_fisher(fisher_loader, self.config.device)
472
  self.model.ewc_penalties[task_name] = ewc
473
  return self.model
 
67
  self.device = config.device
68
  self.exp_dir = Path(exp_dir)
69
  self.exp_dir.mkdir(parents=True, exist_ok=True)
70
+ self.best_val_loss = float('inf')
71
  self.best_metrics = {}
72
  self.patience_counter = 0
73
  self.loss_history = []
74
  self.num_labels_per_task={}
75
  self.val_loss_history = []
76
  self.metrics_history = []
77
+ self.fisher_batch_size=8
78
  self.logger.info("Trainer initialized")
79
  #self.logger.debug(f"Experiment directory: {exp_dir}")
80
  self.logger.debug("Device: %s ",self.config.device)
 
468
  for task_name in train_datasets.keys():
469
  if task_name in self.model.ewc_penalties: continue
470
  ewc = EWC(self.model, task_name, lambda_param=self.config.ewc_lambda)
471
+ fisher_loader = DataLoader(train_datasets[task_name][0], batch_size= self.fisher_batch_size, shuffle=True, collate_fn=collate_fn)
472
  ewc.compute_fisher(fisher_loader, self.config.device)
473
  self.model.ewc_penalties[task_name] = ewc
474
  return self.model
tests/test_dytr.py ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Complete test suite for dytr library.
3
+ Tests all major components: model initialization, task configuration, training, and inference.
4
+ """
5
+
6
+ import pytest
7
+ import torch
8
+ import pandas as pd
9
+
10
+
11
+
12
+
13
+
14
+ # Import dytr components
15
+ from dytr import (
16
+ DynamicTransformer,
17
+ ModelConfig,
18
+ TaskConfig,
19
+ TrainingStrategy,
20
+ Trainer,
21
+ SingleDatasetProcessing,
22
+ )
23
+
24
+
25
+
26
+
27
+ @pytest.fixture(scope="session")
28
+ def model_config():
29
+ """Create a small model configuration for testing."""
30
+ return ModelConfig(
31
+ embed_dim=64,
32
+ num_layers=2,
33
+ num_heads=4,
34
+ head_dim=16,
35
+ ff_mult=2,
36
+ tokenizer_name='prajjwal1/bert-tiny',
37
+ max_seq_len=64,
38
+ dropout=0.1,
39
+ batch_size=4,
40
+ learning_rate=3e-4,
41
+ num_train_epochs=1,
42
+ use_rotary_embedding=True,
43
+ use_task_adapters=True,
44
+ adapter_bottleneck=16,
45
+ use_ewc=False,
46
+ use_replay=False,
47
+ device='cpu'
48
+ )
49
+
50
+
51
+ @pytest.fixture(scope="session")
52
+ def model(model_config):
53
+ """Create a single model instance for all tests."""
54
+ model = DynamicTransformer(model_config)
55
+ return model
56
+
57
+
58
+ @pytest.fixture
59
+ def sample_classification_data():
60
+ """Create sample data for classification task."""
61
+ df = pd.DataFrame({
62
+ 'text': [
63
+ 'This product is amazing!',
64
+ 'Terrible quality, very disappointed.',
65
+ 'Good value for money.',
66
+ 'Worst purchase ever.',
67
+ 'Excellent service!'
68
+ ],
69
+ 'label': [1, 0, 1, 0, 1]
70
+ })
71
+ return df
72
+
73
+
74
+ @pytest.fixture
75
+ def sample_token_data():
76
+ """Create sample data for token classification."""
77
+ df = pd.DataFrame({
78
+ 'text': [
79
+ 'Apple Inc. is in California',
80
+ 'Google was founded in Mountain View',
81
+ 'Microsoft has offices in Seattle'
82
+ ],
83
+ 'tags': [
84
+ '1 0 0 2 0',
85
+ '1 0 0 0 2 0',
86
+ '1 0 0 0 2'
87
+ ]
88
+ })
89
+ return df
90
+
91
+
92
+ @pytest.fixture
93
+ def sample_seq2seq_data():
94
+ """Create sample data for seq2seq task."""
95
+ df = pd.DataFrame({
96
+ 'source': [
97
+ 'Hello world',
98
+ 'How are you',
99
+ 'Good morning'
100
+ ],
101
+ 'target': [
102
+ 'مرحبا بالعالم',
103
+ 'كيف حالك',
104
+ 'صباح الخير'
105
+ ]
106
+ })
107
+ return df
108
+
109
+
110
+ @pytest.fixture
111
+ def sample_causal_data():
112
+ """Create sample data for causal LM."""
113
+ df = pd.DataFrame({
114
+ 'text': [
115
+ 'The sun rises in the east.',
116
+ 'Cats are adorable animals.',
117
+ 'Machine learning is fascinating.',
118
+ 'Python is a great language.'
119
+ ]
120
+ })
121
+ return df
122
+
123
+
124
+
125
+ def test_import_and_version():
126
+ """Test that dytr imports correctly and has version."""
127
+ import dytr
128
+ assert dytr.__version__ is not None
129
+ assert isinstance(dytr.__version__, str)
130
+ print(f"✅ dytr version {dytr.__version__} imported successfully")
131
+
132
+
133
+
134
+
135
+ def test_model_initialization(model_config):
136
+ """Test that model initializes correctly."""
137
+ model = DynamicTransformer(model_config)
138
+ assert model is not None
139
+ assert hasattr(model, 'encoder')
140
+ assert hasattr(model, 'tokenizer')
141
+ assert hasattr(model, 'shared_embedding')
142
+ assert len(model.tokenizer) > 0
143
+ print(f"✅ Model initialized with {sum(p.numel() for p in model.parameters()):,} parameters")
144
+
145
+
146
+ def test_model_config_parameters(model_config):
147
+ """Test model configuration parameters."""
148
+ assert model_config.embed_dim == 64
149
+ assert model_config.num_layers == 2
150
+ assert model_config.num_heads == 4
151
+ assert model_config.max_seq_len == 64
152
+ assert model_config.device == 'cpu'
153
+ print("✅ Model configuration verified")
154
+
155
+
156
+
157
+
158
+ def test_task_config_creation():
159
+ """Test creating task configurations for all strategies."""
160
+
161
+ # Classification task
162
+ class_task = TaskConfig(
163
+ task_name="test_classification",
164
+ training_strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
165
+ num_labels=2,
166
+ text_column="text",
167
+ label_column="label",
168
+ max_length=32
169
+ )
170
+ assert class_task.task_name == "test_classification"
171
+ assert class_task.num_labels == 2
172
+
173
+ # Token classification task
174
+ token_task = TaskConfig(
175
+ task_name="test_token",
176
+ training_strategy=TrainingStrategy.TOKEN_CLASSIFICATION,
177
+ num_labels=5,
178
+ text_column="text",
179
+ label_column="tags",
180
+ max_length=64
181
+ )
182
+ assert token_task.training_strategy == TrainingStrategy.TOKEN_CLASSIFICATION
183
+
184
+ # Seq2Seq task
185
+ seq2seq_task = TaskConfig(
186
+ task_name="test_seq2seq",
187
+ training_strategy=TrainingStrategy.SEQ2SEQ,
188
+ source_column="source",
189
+ target_column="target",
190
+ max_length=32
191
+ )
192
+ assert seq2seq_task.training_strategy == TrainingStrategy.SEQ2SEQ
193
+
194
+ # Causal LM task
195
+ causal_task = TaskConfig(
196
+ task_name="test_causal",
197
+ training_strategy=TrainingStrategy.CAUSAL_LM,
198
+ text_column="text",
199
+ max_length=32
200
+ )
201
+ assert causal_task.training_strategy == TrainingStrategy.CAUSAL_LM
202
+
203
+ print("✅ All task configurations created successfully")
204
+
205
+
206
+
207
+
208
+ def test_add_tasks_to_model(model, sample_classification_data, sample_token_data):
209
+ """Test adding multiple tasks to the model."""
210
+
211
+ # Create and add classification task
212
+ class_task = TaskConfig(
213
+ task_name="test_classification",
214
+ training_strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
215
+ num_labels=2,
216
+ text_column="text",
217
+ label_column="label",
218
+ max_length=32
219
+ )
220
+ model.add_task(class_task)
221
+
222
+ # Create and add token classification task
223
+ token_task = TaskConfig(
224
+ task_name="test_token",
225
+ training_strategy=TrainingStrategy.TOKEN_CLASSIFICATION,
226
+ num_labels=5,
227
+ text_column="text",
228
+ label_column="tags",
229
+ max_length=64
230
+ )
231
+ model.add_task(token_task)
232
+
233
+ # Verify tasks were added
234
+ assert "test_classification" in model.current_tasks
235
+ assert "test_token" in model.current_tasks
236
+ assert len(model.current_tasks) >= 2
237
+
238
+ # Verify task heads exist
239
+ assert "test_classification" in model.task_heads
240
+ assert "test_token" in model.task_heads
241
+
242
+ print(f"✅ Added {len(model.current_tasks)} tasks to model")
243
+
244
+
245
+
246
+
247
+ def test_dataset_processing(model, sample_classification_data):
248
+ """Test SingleDatasetProcessing for classification."""
249
+
250
+ dataset = SingleDatasetProcessing(
251
+ df=sample_classification_data,
252
+ tokenizer=model.tokenizer,
253
+ max_len=32,
254
+ task_name="test_classification",
255
+ strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
256
+ num_labels=2,
257
+ text_column="text",
258
+ label_column="label",cache_dir="./",
259
+ )
260
+
261
+ assert len(dataset) == len(sample_classification_data)
262
+
263
+ # Get a sample
264
+ sample = dataset[0]
265
+ assert "input_ids" in sample
266
+ assert "attention_mask" in sample
267
+ assert "labels" in sample
268
+ assert sample["task_name"] == "test_classification"
269
+ assert isinstance(sample["input_ids"], torch.Tensor)
270
+
271
+ print(f"✅ Dataset processing works, {len(dataset)} samples")
272
+
273
+
274
+ def test_token_dataset_processing(model, sample_token_data):
275
+ """Test SingleDatasetProcessing for token classification."""
276
+
277
+ dataset = SingleDatasetProcessing(
278
+ df=sample_token_data,
279
+ tokenizer=model.tokenizer,
280
+ max_len=64,
281
+ task_name="test_token",
282
+ strategy=TrainingStrategy.TOKEN_CLASSIFICATION,
283
+ num_labels=5,
284
+ text_column="text",
285
+ tags_column="tags",cache_dir="./",
286
+ )
287
+
288
+ assert len(dataset) > 0
289
+ sample = dataset[0]
290
+ assert "labels" in sample
291
+ assert sample["labels"].dim() == 1 # 1D tensor for token labels
292
+
293
+ print(f"✅ Token dataset processing works")
294
+
295
+
296
+
297
+
298
+ def test_forward_pass(model, sample_classification_data):
299
+ """Test forward pass through the model."""
300
+
301
+ # Create dataset and get a sample
302
+ dataset = SingleDatasetProcessing(
303
+ df=sample_classification_data,
304
+ tokenizer=model.tokenizer,
305
+ max_len=32,
306
+ task_name="test_classification",
307
+ strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
308
+ num_labels=2,
309
+ text_column="text",
310
+ label_column="label",cache_dir="./",
311
+ )
312
+
313
+ sample = dataset[0]
314
+ input_ids = sample["input_ids"].unsqueeze(0)
315
+ attention_mask = sample["attention_mask"].unsqueeze(0)
316
+ #labels = sample["labels"]
317
+ labels = sample["labels"][0].unsqueeze(0)
318
+
319
+ # Forward pass
320
+ outputs = model.forward(
321
+ input_ids=input_ids,
322
+ attention_mask=attention_mask,
323
+ task_name="test_classification",
324
+ labels=labels
325
+ )
326
+
327
+ assert "logits" in outputs
328
+ assert "loss" in outputs
329
+ assert outputs["logits"].shape[-1] == 2 # 2 classes
330
+
331
+ print(f"✅ Forward pass successful, loss: {outputs['loss'].item():.4f}")
332
+
333
+
334
+ def test_forward_without_labels(model, sample_classification_data):
335
+ """Test forward pass without labels (inference mode)."""
336
+
337
+ dataset = SingleDatasetProcessing(
338
+ df=sample_classification_data,
339
+ tokenizer=model.tokenizer,
340
+ max_len=32,
341
+ task_name="test_classification",
342
+ strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
343
+ num_labels=2,
344
+ text_column="text",
345
+ label_column="label",cache_dir="./",
346
+ )
347
+
348
+ sample = dataset[0]
349
+ input_ids = sample["input_ids"].unsqueeze(0)
350
+ attention_mask = sample["attention_mask"].unsqueeze(0)
351
+
352
+ outputs = model.forward(
353
+ input_ids=input_ids,
354
+ attention_mask=attention_mask,
355
+ task_name="test_classification"
356
+ )
357
+
358
+ assert "logits" in outputs
359
+ assert "loss" not in outputs
360
+
361
+ print("✅ Inference forward pass successful")
362
+
363
+
364
+
365
+
366
+ def test_generate_classification(model):
367
+ """Test generate method for classification."""
368
+
369
+ result = model.generate(
370
+ "This is a test sentence for classification.",
371
+ task_name="test_classification"
372
+ )
373
+
374
+ assert "prediction" in result
375
+ assert "probabilities" in result
376
+ assert isinstance(result["prediction"], int)
377
+ assert len(result["probabilities"]) == 2
378
+
379
+ print(f"✅ Classification generation successful, prediction: {result['prediction']}")
380
+
381
+
382
+ def test_generate_token_classification(model, sample_token_data):
383
+ """Test generate method for token classification."""
384
+
385
+ # Create and add token task if not exists
386
+ if "test_token_generate" not in model.current_tasks:
387
+ token_task = TaskConfig(
388
+ task_name="test_token_generate",
389
+ training_strategy=TrainingStrategy.TOKEN_CLASSIFICATION,
390
+ num_labels=5,
391
+ text_column="text",
392
+ label_column="tags",
393
+ max_length=64
394
+ )
395
+ model.add_task(token_task)
396
+
397
+
398
+
399
+ result = model.generate(
400
+ "Apple Inc. is a technology company.",
401
+ task_name="test_token_generate"
402
+ )
403
+
404
+ assert "tokens" in result
405
+ assert "predictions" in result
406
+ assert "pairs" in result
407
+ assert len(result["tokens"]) == len(result["predictions"])
408
+
409
+ print("✅ Token classification generation successful")
410
+
411
+
412
+
413
+ def test_training(model, sample_classification_data):
414
+ """Test training loop (single epoch)."""
415
+
416
+ # Create dataset
417
+ dataset = SingleDatasetProcessing(
418
+ df=sample_classification_data,
419
+ tokenizer=model.tokenizer,
420
+ max_len=32,
421
+ task_name="test_classification",
422
+ strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
423
+ num_labels=2,
424
+ text_column="text",
425
+ label_column="label",cache_dir="./",
426
+ )
427
+
428
+ # Create trainer
429
+ trainer = Trainer(model, model.config, exp_dir="./")
430
+
431
+ # Create train datasets dict
432
+ train_datasets = {
433
+ "test_classification": (dataset, TrainingStrategy.SENTENCE_CLASSIFICATION)
434
+ }
435
+
436
+ # Get task config
437
+ task_config = TaskConfig(
438
+ task_name="test_classification",
439
+ training_strategy=TrainingStrategy.SENTENCE_CLASSIFICATION,
440
+ num_labels=2,
441
+ text_column="text",
442
+ label_column="label"
443
+ )
444
+
445
+ # Quick training (1 epoch)
446
+ try:
447
+ trained_model = trainer.train([task_config], train_datasets, {})
448
+ assert trained_model is not None
449
+ print("✅ Training completed successfully")
450
+ except Exception as e:
451
+ print(f"⚠️ Training test note: {e}")
452
+ # Training might fail without proper data, but that's expected
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+ def test_multi_task_with_strategies(model, sample_classification_data, sample_causal_data):
461
+ """Test model with multiple task types."""
462
+
463
+ # Add causal LM task
464
+ if "test_causal_multi" not in model.current_tasks:
465
+ causal_task = TaskConfig(
466
+ task_name="test_causal_multi",
467
+ training_strategy=TrainingStrategy.CAUSAL_LM,
468
+ text_column="text",
469
+ max_length=32
470
+ )
471
+ model.add_task(causal_task)
472
+
473
+
474
+ # Test both tasks
475
+ class_result = model.generate("Test classification", task_name="test_classification")
476
+ assert "prediction" in class_result
477
+
478
+ try:
479
+ causal_result = model.generate("The future of", task_name="test_causal_multi", max_new_tokens=10)
480
+ assert causal_result is not None
481
+ print("✅ Multi-task with different strategies works")
482
+ except Exception as e:
483
+ print(f"⚠️ Causal LM generation note: {e}")
484
+
485
+
486
+ if __name__ == "__main__":
487
+
488
+ pytest.main(["-v", "--tb=short", __file__])#,"-s"])
489
+