| """ |
| Frox AI Morph 1.1 — Multimodal Fusion Tests |
| Run with: pytest tests/test_multimodal.py -v |
| """ |
| from __future__ import annotations |
|
|
| import pytest |
| import torch |
|
|
| from multimodal.fusion.morph_multimodal import MorphMultimodalModel |
|
|
|
|
| class TestImageTokenInjection: |
| def test_single_image_expands_sequence_correctly(self, tiny_config, device): |
| model = MorphMultimodalModel(tiny_config).to(device).eval() |
|
|
| num_image_tokens = (tiny_config.vision.image_size // tiny_config.vision.patch_size) ** 2 |
| text_len = 6 |
|
|
| input_ids = torch.randint( |
| 0, tiny_config.text.vocab_size, (1, text_len), device=device |
| ) |
| input_ids[0, 3] = model.image_token_id |
|
|
| pixel_values = torch.randn( |
| 1, 3, tiny_config.vision.image_size, tiny_config.vision.image_size, device=device |
| ) |
|
|
| with torch.no_grad(): |
| inputs_embeds, mask = model.prepare_multimodal_inputs(input_ids, pixel_values) |
|
|
| expected_len = (text_len - 1) + num_image_tokens |
| assert inputs_embeds.shape[1] == expected_len |
| assert inputs_embeds.shape[2] == tiny_config.text.hidden_size |
|
|
| def test_no_image_falls_back_to_text_only(self, tiny_config, device): |
| model = MorphMultimodalModel(tiny_config).to(device).eval() |
| input_ids = torch.randint(0, tiny_config.text.total_vocab_size, (1, 8), device=device) |
|
|
| with torch.no_grad(): |
| embeds, mask = model.prepare_multimodal_inputs(input_ids, pixel_values=None) |
|
|
| assert embeds.shape[1] == 8 |
| assert mask is None |
|
|
| def test_forward_pass_with_image_produces_finite_logits(self, tiny_config, device): |
| model = MorphMultimodalModel(tiny_config).to(device).eval() |
|
|
| input_ids = torch.randint(0, tiny_config.text.vocab_size, (1, 6), device=device) |
| input_ids[0, 2] = model.image_token_id |
| pixel_values = torch.randn( |
| 1, 3, tiny_config.vision.image_size, tiny_config.vision.image_size, device=device |
| ) |
|
|
| with torch.no_grad(): |
| out = model(input_ids=input_ids, pixel_values=pixel_values) |
|
|
| assert not torch.isnan(out.logits).any() |
| assert not torch.isinf(out.logits).any() |
|
|
|
|
| class TestParamCount: |
| def test_param_count_breakdown_sums_correctly(self, tiny_config, device): |
| model = MorphMultimodalModel(tiny_config).to(device) |
| counts = model.param_count() |
| |
| |
| assert counts["total_billions"] >= counts["lm_billions"] + counts["vision_billions"] - 1e-6 |
|
|