BareGit
import unittest

import torch

from diffusion_cli.text_encoder import TextConditioning
from diffusion_cli.zimage_model import (
    NextDiT,
    ZImageConfig,
    ZImageModel,
    detectZImageConfig,
    normalizeZImageStateDict,
)


class ZImageModelTest(unittest.TestCase):
    def testStateDictNormalizationStripsModelPrefix(self):
        state_dict = normalizeZImageStateDict(
            {
                "model.diffusion_model.x_embedder.weight": "a",
                "diffusion_model.final_layer.linear.weight": "b",
                "layers.0.ffn_norm1.weight": "c",
            }
        )

        self.assertEqual(state_dict["x_embedder.weight"], "a")
        self.assertEqual(state_dict["final_layer.linear.weight"], "b")
        self.assertEqual(state_dict["layers.0.ffn_norm1.weight"], "c")

    def testDetectConfigReadsZImageShapes(self):
        state_dict = {
            "model.diffusion_model.cap_embedder.1.weight": torch.empty(
                3840,
                2560,
            ),
            "model.diffusion_model.x_embedder.weight": torch.empty(3840, 64),
            "model.diffusion_model.cap_pad_token": torch.empty(1, 3840),
            "model.diffusion_model.layers.0.ffn_norm1.weight": torch.empty(
                3840,
            ),
            "model.diffusion_model.layers.1.ffn_norm1.weight": torch.empty(
                3840,
            ),
            "model.diffusion_model.context_refiner.0.ffn_norm1.weight":
                torch.empty(3840),
        }

        config = detectZImageConfig(state_dict)

        self.assertEqual(config.dim, 3840)
        self.assertEqual(config.cap_feat_dim, 2560)
        self.assertEqual(config.n_layers, 2)
        self.assertEqual(config.n_refiner_layers, 1)
        self.assertEqual(config.patch_size, 2)
        self.assertEqual(config.pad_tokens_multiple, 32)

    def testTinyForwardMatchesLatentShapeAndIsFinite(self):
        torch.manual_seed(1)
        config = ZImageConfig(
            dim=258,
            cap_feat_dim=8,
            n_layers=1,
            n_refiner_layers=1,
            n_heads=3,
            n_kv_heads=3,
            patch_size=2,
            in_channels=4,
            axes_dims=(30, 28, 28),
            pad_tokens_multiple=0,
        )
        model = NextDiT(config, dtype=torch.float32)
        latent = torch.randn(1, 4, 4, 4)
        context = torch.randn(1, 3, 8)
        timesteps = torch.tensor([0.5])

        output = model(latent, timesteps, context, 3)

        self.assertEqual(output.shape, latent.shape)
        self.assertTrue(torch.all(torch.isfinite(output)))

    def testWrapperValidatesFiniteOutput(self):
        class IdentityModel(torch.nn.Module):
            def forward(
                self,
                latent,
                timesteps,
                context,
                num_tokens,
                attention_mask,
            ):
                return latent + timesteps.reshape(-1, 1, 1, 1) * 0

        wrapper = ZImageModel(
            model_path=None,
            device=torch.device("cpu"),
            dtype=torch.float32,
            model=IdentityModel(),
        )
        conditioning = TextConditioning(
            hidden_states=torch.zeros(1, 2, 8),
            attention_mask=torch.ones(1, 2),
            token_count=2,
        )
        latent = torch.zeros(1, 4, 4, 4)

        output = wrapper.forward(latent, torch.tensor([0.5]), conditioning)

        self.assertEqual(output.shape, latent.shape)
        self.assertTrue(torch.all(torch.isfinite(output)))

    def testWrapperConvertsFlowOutputToDenoisedPrediction(self):
        class ConstantVelocityModel(torch.nn.Module):
            def forward(
                self,
                latent,
                timesteps,
                context,
                num_tokens,
                attention_mask,
            ):
                del timesteps, context, num_tokens, attention_mask
                return torch.ones_like(latent) * 2.0

        wrapper = ZImageModel(
            model_path=None,
            device=torch.device("cpu"),
            dtype=torch.float32,
            model=ConstantVelocityModel(),
        )
        conditioning = TextConditioning(
            hidden_states=torch.zeros(1, 2, 8),
            attention_mask=torch.ones(1, 2),
            token_count=2,
        )
        latent = torch.ones(1, 4, 2, 2) * 3.0

        output = wrapper.forward(latent, torch.tensor([0.25]), conditioning)

        self.assertTrue(torch.equal(output, torch.ones_like(latent) * 2.5))


if __name__ == "__main__":
    unittest.main()