| """ |
| Complete test suite for dytr library. |
| Tests all major components: model initialization, task configuration, training, and inference. |
| """ |
|
|
| import pytest |
| import torch |
| import pandas as pd |
|
|
|
|
|
|
|
|
|
|
| |
| from dytr import ( |
| DynamicTransformer, |
| ModelConfig, |
| TaskConfig, |
| TrainingStrategy, |
| Trainer, |
| SingleDatasetProcessing, |
| ) |
|
|
|
|
|
|
|
|
| @pytest.fixture(scope="session") |
| def model_config(): |
| """Create a small model configuration for testing.""" |
| return ModelConfig( |
| embed_dim=64, |
| num_layers=2, |
| num_heads=4, |
| head_dim=16, |
| ff_mult=2, |
| tokenizer_name='prajjwal1/bert-tiny', |
| max_seq_len=64, |
| dropout=0.1, |
| batch_size=4, |
| learning_rate=3e-4, |
| num_train_epochs=1, |
| use_rotary_embedding=True, |
| use_task_adapters=True, |
| adapter_bottleneck=16, |
| use_ewc=False, |
| use_replay=False, |
| device='cpu' |
| ) |
|
|
|
|
| @pytest.fixture(scope="session") |
| def model(model_config): |
| """Create a single model instance for all tests.""" |
| model = DynamicTransformer(model_config) |
| return model |
|
|
|
|
| @pytest.fixture |
| def sample_classification_data(): |
| """Create sample data for classification task.""" |
| df = pd.DataFrame({ |
| 'text': [ |
| 'This product is amazing!', |
| 'Terrible quality, very disappointed.', |
| 'Good value for money.', |
| 'Worst purchase ever.', |
| 'Excellent service!' |
| ], |
| 'label': [1, 0, 1, 0, 1] |
| }) |
| return df |
|
|
|
|
| @pytest.fixture |
| def sample_token_data(): |
| """Create sample data for token classification.""" |
| df = pd.DataFrame({ |
| 'text': [ |
| 'Apple Inc. is in California', |
| 'Google was founded in Mountain View', |
| 'Microsoft has offices in Seattle' |
| ], |
| 'tags': [ |
| '1 0 0 2 0', |
| '1 0 0 0 2 0', |
| '1 0 0 0 2' |
| ] |
| }) |
| return df |
|
|
|
|
| @pytest.fixture |
| def sample_seq2seq_data(): |
| """Create sample data for seq2seq task.""" |
| df = pd.DataFrame({ |
| 'source': [ |
| 'Hello world', |
| 'How are you', |
| 'Good morning' |
| ], |
| 'target': [ |
| 'مرحبا بالعالم', |
| 'كيف حالك', |
| 'صباح الخير' |
| ] |
| }) |
| return df |
|
|
|
|
| @pytest.fixture |
| def sample_causal_data(): |
| """Create sample data for causal LM.""" |
| df = pd.DataFrame({ |
| 'text': [ |
| 'The sun rises in the east.', |
| 'Cats are adorable animals.', |
| 'Machine learning is fascinating.', |
| 'Python is a great language.' |
| ] |
| }) |
| return df |
|
|
|
|
|
|
| def test_import_and_version(): |
| """Test that dytr imports correctly and has version.""" |
| import dytr |
| assert dytr.__version__ is not None |
| assert isinstance(dytr.__version__, str) |
| print(f"✅ dytr version {dytr.__version__} imported successfully") |
|
|
|
|
|
|
|
|
| def test_model_initialization(model_config): |
| """Test that model initializes correctly.""" |
| model = DynamicTransformer(model_config) |
| assert model is not None |
| assert hasattr(model, 'encoder') |
| assert hasattr(model, 'tokenizer') |
| assert hasattr(model, 'shared_embedding') |
| assert len(model.tokenizer) > 0 |
| print(f"✅ Model initialized with {sum(p.numel() for p in model.parameters()):,} parameters") |
|
|
|
|
| def test_model_config_parameters(model_config): |
| """Test model configuration parameters.""" |
| assert model_config.embed_dim == 64 |
| assert model_config.num_layers == 2 |
| assert model_config.num_heads == 4 |
| assert model_config.max_seq_len == 64 |
| assert model_config.device == 'cpu' |
| print("✅ Model configuration verified") |
|
|
|
|
|
|
|
|
| def test_task_config_creation(): |
| """Test creating task configurations for all strategies.""" |
| |
| |
| class_task = TaskConfig( |
| task_name="test_classification", |
| training_strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label", |
| max_length=32 |
| ) |
| assert class_task.task_name == "test_classification" |
| assert class_task.num_labels == 2 |
| |
| |
| token_task = TaskConfig( |
| task_name="test_token", |
| training_strategy=TrainingStrategy.TOKEN_CLASSIFICATION, |
| num_labels=5, |
| text_column="text", |
| label_column="tags", |
| max_length=64 |
| ) |
| assert token_task.training_strategy == TrainingStrategy.TOKEN_CLASSIFICATION |
| |
| |
| seq2seq_task = TaskConfig( |
| task_name="test_seq2seq", |
| training_strategy=TrainingStrategy.SEQ2SEQ, |
| source_column="source", |
| target_column="target", |
| max_length=32 |
| ) |
| assert seq2seq_task.training_strategy == TrainingStrategy.SEQ2SEQ |
| |
| |
| causal_task = TaskConfig( |
| task_name="test_causal", |
| training_strategy=TrainingStrategy.CAUSAL_LM, |
| text_column="text", |
| max_length=32 |
| ) |
| assert causal_task.training_strategy == TrainingStrategy.CAUSAL_LM |
| |
| print("✅ All task configurations created successfully") |
|
|
|
|
|
|
|
|
| def test_add_tasks_to_model(model, sample_classification_data, sample_token_data): |
| """Test adding multiple tasks to the model.""" |
| |
| |
| class_task = TaskConfig( |
| task_name="test_classification", |
| training_strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label", |
| max_length=32 |
| ) |
| model.add_task(class_task) |
| |
| |
| token_task = TaskConfig( |
| task_name="test_token", |
| training_strategy=TrainingStrategy.TOKEN_CLASSIFICATION, |
| num_labels=5, |
| text_column="text", |
| label_column="tags", |
| max_length=64 |
| ) |
| model.add_task(token_task) |
| |
| |
| assert "test_classification" in model.current_tasks |
| assert "test_token" in model.current_tasks |
| assert len(model.current_tasks) >= 2 |
| |
| |
| assert "test_classification" in model.task_heads |
| assert "test_token" in model.task_heads |
| |
| print(f"✅ Added {len(model.current_tasks)} tasks to model") |
|
|
|
|
|
|
|
|
| def test_dataset_processing(model, sample_classification_data): |
| """Test SingleDatasetProcessing for classification.""" |
| |
| dataset = SingleDatasetProcessing( |
| df=sample_classification_data, |
| tokenizer=model.tokenizer, |
| max_len=32, |
| task_name="test_classification", |
| strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label",cache_dir="./", |
| ) |
| |
| assert len(dataset) == len(sample_classification_data) |
| |
| |
| sample = dataset[0] |
| assert "input_ids" in sample |
| assert "attention_mask" in sample |
| assert "labels" in sample |
| assert sample["task_name"] == "test_classification" |
| assert isinstance(sample["input_ids"], torch.Tensor) |
| |
| print(f"✅ Dataset processing works, {len(dataset)} samples") |
|
|
|
|
| def test_token_dataset_processing(model, sample_token_data): |
| """Test SingleDatasetProcessing for token classification.""" |
| |
| dataset = SingleDatasetProcessing( |
| df=sample_token_data, |
| tokenizer=model.tokenizer, |
| max_len=64, |
| task_name="test_token", |
| strategy=TrainingStrategy.TOKEN_CLASSIFICATION, |
| num_labels=5, |
| text_column="text", |
| tags_column="tags",cache_dir="./", |
| ) |
| |
| assert len(dataset) > 0 |
| sample = dataset[0] |
| assert "labels" in sample |
| assert sample["labels"].dim() == 1 |
| |
| print(f"✅ Token dataset processing works") |
|
|
|
|
|
|
|
|
| def test_forward_pass(model, sample_classification_data): |
| """Test forward pass through the model.""" |
| |
| |
| dataset = SingleDatasetProcessing( |
| df=sample_classification_data, |
| tokenizer=model.tokenizer, |
| max_len=32, |
| task_name="test_classification", |
| strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label",cache_dir="./", |
| ) |
| |
| sample = dataset[0] |
| input_ids = sample["input_ids"].unsqueeze(0) |
| attention_mask = sample["attention_mask"].unsqueeze(0) |
| |
| labels = sample["labels"][0].unsqueeze(0) |
| |
| |
| outputs = model.forward( |
| input_ids=input_ids, |
| attention_mask=attention_mask, |
| task_name="test_classification", |
| labels=labels |
| ) |
| |
| assert "logits" in outputs |
| assert "loss" in outputs |
| assert outputs["logits"].shape[-1] == 2 |
| |
| print(f"✅ Forward pass successful, loss: {outputs['loss'].item():.4f}") |
|
|
|
|
| def test_forward_without_labels(model, sample_classification_data): |
| """Test forward pass without labels (inference mode).""" |
| |
| dataset = SingleDatasetProcessing( |
| df=sample_classification_data, |
| tokenizer=model.tokenizer, |
| max_len=32, |
| task_name="test_classification", |
| strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label",cache_dir="./", |
| ) |
| |
| sample = dataset[0] |
| input_ids = sample["input_ids"].unsqueeze(0) |
| attention_mask = sample["attention_mask"].unsqueeze(0) |
| |
| outputs = model.forward( |
| input_ids=input_ids, |
| attention_mask=attention_mask, |
| task_name="test_classification" |
| ) |
| |
| assert "logits" in outputs |
| assert "loss" not in outputs |
| |
| print("✅ Inference forward pass successful") |
|
|
|
|
|
|
|
|
| def test_generate_classification(model): |
| """Test generate method for classification.""" |
| |
| result = model.generate( |
| "This is a test sentence for classification.", |
| task_name="test_classification" |
| ) |
| |
| assert "prediction" in result |
| assert "probabilities" in result |
| assert isinstance(result["prediction"], int) |
| assert len(result["probabilities"]) == 2 |
| |
| print(f"✅ Classification generation successful, prediction: {result['prediction']}") |
|
|
|
|
| def test_generate_token_classification(model, sample_token_data): |
| """Test generate method for token classification.""" |
| |
| |
| if "test_token_generate" not in model.current_tasks: |
| token_task = TaskConfig( |
| task_name="test_token_generate", |
| training_strategy=TrainingStrategy.TOKEN_CLASSIFICATION, |
| num_labels=5, |
| text_column="text", |
| label_column="tags", |
| max_length=64 |
| ) |
| model.add_task(token_task) |
| |
| |
| |
| result = model.generate( |
| "Apple Inc. is a technology company.", |
| task_name="test_token_generate" |
| ) |
| |
| assert "tokens" in result |
| assert "predictions" in result |
| assert "pairs" in result |
| assert len(result["tokens"]) == len(result["predictions"]) |
| |
| print("✅ Token classification generation successful") |
|
|
|
|
|
|
| def test_training(model, sample_classification_data): |
| """Test training loop (single epoch).""" |
| |
| |
| dataset = SingleDatasetProcessing( |
| df=sample_classification_data, |
| tokenizer=model.tokenizer, |
| max_len=32, |
| task_name="test_classification", |
| strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label",cache_dir="./", |
| ) |
| |
| |
| trainer = Trainer(model, model.config, exp_dir="./") |
| |
| |
| train_datasets = { |
| "test_classification": (dataset, TrainingStrategy.SENTENCE_CLASSIFICATION) |
| } |
| |
| |
| task_config = TaskConfig( |
| task_name="test_classification", |
| training_strategy=TrainingStrategy.SENTENCE_CLASSIFICATION, |
| num_labels=2, |
| text_column="text", |
| label_column="label" |
| ) |
| |
| |
| try: |
| trained_model = trainer.train([task_config], train_datasets, {}) |
| assert trained_model is not None |
| print("✅ Training completed successfully") |
| except Exception as e: |
| print(f"⚠️ Training test note: {e}") |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def test_multi_task_with_strategies(model, sample_classification_data, sample_causal_data): |
| """Test model with multiple task types.""" |
| |
| |
| if "test_causal_multi" not in model.current_tasks: |
| causal_task = TaskConfig( |
| task_name="test_causal_multi", |
| training_strategy=TrainingStrategy.CAUSAL_LM, |
| text_column="text", |
| max_length=32 |
| ) |
| model.add_task(causal_task) |
| |
| |
| |
| class_result = model.generate("Test classification", task_name="test_classification") |
| assert "prediction" in class_result |
| |
| try: |
| causal_result = model.generate("The future of", task_name="test_causal_multi", max_new_tokens=10) |
| assert causal_result is not None |
| print("✅ Multi-task with different strategies works") |
| except Exception as e: |
| print(f"⚠️ Causal LM generation note: {e}") |
|
|
|
|
| if __name__ == "__main__": |
| |
| pytest.main(["-v", "--tb=short", __file__]) |
|
|
|
|