From 48fad8524e60a4b3139373c615a606c28eb2ffb1 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Tue, 23 Jun 2026 01:03:06 +0100 Subject: [PATCH] feat(krea2): add Krea 2 (K2) image model support Krea 2 is a 12.9B single-stream flow-matching DiT trained from scratch, using a Qwen3-VL-4B text encoder and the Qwen-Image VAE. The transformer is vendored as a diffusers ModelMixin whose module tree mirrors the checkpoint, so weights load with no key conversion; the pipeline ports the reference encode, flow-matching denoise, and VAE decode. The text encoder is shared at runtime via the existing dedup registry, so Base and Turbo reuse one Qwen3-VL-4B copy. Covers text-to-image, image-to-image, native LoRA, and the single-file UNET override. Also completes SD.Next's partial Qwen-Image VAE support (5D decode input and TAESD preview mapping) that K2 shares. --- data/reference-distilled.json | 10 + data/reference.json | 9 + ...sFelicitousness--Krea-2-Base-Diffusers.jpg | Bin 0 -> 44527 bytes ...Felicitousness--Krea-2-Turbo-Diffusers.jpg | Bin 0 -> 43975 bytes modules/lora/lora_load.py | 1 + modules/lora/lora_overrides.py | 1 + modules/modeldata.py | 2 + modules/processing_vae.py | 4 +- modules/sd_detect.py | 2 + modules/sd_models.py | 4 + modules/sd_samplers_common.py | 2 +- modules/shared_items.py | 1 + modules/ui_extra_networks_lora.py | 1 + modules/vae/sd_vae_taesd.py | 2 +- pipelines/generic_shared.py | 5 + pipelines/krea2/__init__.py | 8 + pipelines/krea2/krea2_lora.py | 71 ++++ pipelines/krea2/pipeline_krea2.py | 273 ++++++++++++++ pipelines/krea2/transformer_krea2.py | 349 ++++++++++++++++++ pipelines/model_krea2.py | 53 +++ test/test-krea2-transformer.py | 88 +++++ 21 files changed, 882 insertions(+), 4 deletions(-) create mode 100644 models/Reference/CalamitousFelicitousness--Krea-2-Base-Diffusers.jpg create mode 100644 models/Reference/CalamitousFelicitousness--Krea-2-Turbo-Diffusers.jpg create mode 100644 pipelines/krea2/__init__.py create mode 100644 pipelines/krea2/krea2_lora.py create mode 100644 pipelines/krea2/pipeline_krea2.py create mode 100644 pipelines/krea2/transformer_krea2.py create mode 100644 pipelines/model_krea2.py create mode 100644 test/test-krea2-transformer.py diff --git a/data/reference-distilled.json b/data/reference-distilled.json index 7d6c52ee4..5119806b2 100644 --- a/data/reference-distilled.json +++ b/data/reference-distilled.json @@ -9,6 +9,16 @@ "extras": "steps: 4, cfg_scale: 0.0", "size": 20.81 }, + "Krea 2 Turbo": { + "path": "CalamitousFelicitousness/Krea-2-Turbo-Diffusers", + "preview": "CalamitousFelicitousness--Krea-2-Turbo-Diffusers.jpg", + "desc": "Krea 2 (K2) Turbo is the 8-step distilled inference model of the Krea 2 family, trained from scratch by Krea. A 12.9B-parameter single-stream flow-matching DiT that uses a Qwen3-VL-4B vision-language model as its text encoder and the Qwen-Image VAE. Runs without classifier-free guidance; LoRAs trained on Krea 2 Base apply directly.", + "skip": true, + "tags": "distilled", + "extras": "sampler: Default, cfg_scale: 0.0, steps: 8, width: 1024, height: 1024", + "size": 34.0, + "date": "2026 June" + }, "StabilityAI Stable Cascade Lite": { "path": "huggingface/stabilityai/stable-cascade-lite", "skip": true, diff --git a/data/reference.json b/data/reference.json index a8d88322b..7c03f247c 100644 --- a/data/reference.json +++ b/data/reference.json @@ -193,6 +193,15 @@ "size": 53.58, "date": "2026 June" }, + "Krea 2 Base": { + "path": "CalamitousFelicitousness/Krea-2-Base-Diffusers", + "preview": "CalamitousFelicitousness--Krea-2-Base-Diffusers.jpg", + "desc": "Krea 2 (K2) Base is the undistilled foundation model of the Krea 2 family, trained from scratch by Krea. A 12.9B-parameter single-stream flow-matching DiT that uses a Qwen3-VL-4B vision-language model as its text encoder and the Qwen-Image VAE. The base checkpoint is intended for fine-tuning and LoRA training; LoRAs trained on it apply to Krea 2 Turbo.", + "skip": true, + "extras": "sampler: Default, cfg_scale: 3.5, steps: 52, width: 1024, height: 1024", + "size": 34.0, + "date": "2026 June" + }, "Baidu ERNIE-Image": { "path": "baidu/ERNIE-Image", "preview": "baidu--ERNIE-Image.jpg", diff --git a/models/Reference/CalamitousFelicitousness--Krea-2-Base-Diffusers.jpg b/models/Reference/CalamitousFelicitousness--Krea-2-Base-Diffusers.jpg new file mode 100644 index 0000000000000000000000000000000000000000..954ae7962df457b6444ba7c4c7224b09f8fd0227 GIT binary patch literal 44527 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
ujw5X-Z^Ejin44AK0n6y~p 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 literal 0 HcmV?d00001 diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py index f97f6e5fc..604b24342 100644 --- a/modules/lora/lora_load.py +++ b/modules/lora/lora_load.py @@ -27,6 +27,7 @@ _NATIVE_DISPATCH = { 'ernieimage': 'pipelines.ernie.ernie_lora', 'f2': 'pipelines.flux.flux2_lora', 'anima': 'pipelines.anima.anima_lora', + 'krea2': 'pipelines.krea2.krea2_lora', } diff --git a/modules/lora/lora_overrides.py b/modules/lora/lora_overrides.py index efd842c3e..2d27f7b88 100644 --- a/modules/lora/lora_overrides.py +++ b/modules/lora/lora_overrides.py @@ -31,6 +31,7 @@ allow_native = [ 'zimage', 'anima', 'ernieimage', + 'krea2', ] diff --git a/modules/modeldata.py b/modules/modeldata.py index 75162eecd..cc5725dea 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -57,6 +57,8 @@ def get_model_type(pipe): model_type = 'zimage' elif "Ideogram4" in name: model_type = 'ideogram4' + elif "Krea2" in name: + model_type = 'krea2' elif "LuminaDiMOO" in name: model_type = 'luminadimoo' elif "Lumina2" in name: diff --git a/modules/processing_vae.py b/modules/processing_vae.py index a889b1748..602bf6025 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -130,8 +130,8 @@ def full_vae_decode(latents, model): latents = latents + shift_factor # check dims - if model.vae.__class__.__name__ in ['AutoencoderKLWan'] and latents.ndim == 4: - latents = latents.unsqueeze(2) # wan is __nhw + if model.vae.__class__.__name__ in ['AutoencoderKLWan', 'AutoencoderKLQwenImage'] and latents.ndim == 4: + latents = latents.unsqueeze(2) # video VAEs (wan, qwen-image) expect a frame axis # handle quants if getattr(model.vae, "post_quant_conv", None) is not None: diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 045d46a28..3479eba58 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -151,6 +151,8 @@ def guess_by_name(fn, current_guess): new_guess = 'NucleusImage' elif 'z-image' in fn.lower() or 'z_image' in fn.lower(): new_guess = 'ZImage' + elif 'krea-2' in fn.lower() or 'krea2' in fn.lower(): + new_guess = 'Krea2' elif 'ideogram' in fn.lower(): new_guess = 'Ideogram4' elif 'longcat-image' in fn.lower(): diff --git a/modules/sd_models.py b/modules/sd_models.py index 909527d1c..bedf07d67 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -540,6 +540,10 @@ def load_diffuser_force(detected_model_type: str, checkpoint_info: CheckpointInf from pipelines.model_z_image import load_z_image sd_model = load_z_image(checkpoint_info, diffusers_load_config) allow_post_quant = False + elif model_type in ['Krea2']: + from pipelines.model_krea2 import load_krea2 + sd_model = load_krea2(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Ideogram4']: from pipelines.model_ideogram4 import load_ideogram4 sd_model = load_ideogram4(checkpoint_info, diffusers_load_config) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 7bbae7eda..d964a4bf8 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -11,7 +11,7 @@ from modules.image import convert SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) approximation_indexes = { "Simple": 0, "Approximate": 1, "TAESD": 2, "Full VAE": 3 } -flow_models = ['f1', 'f2', 'sd3', 'lumina', 'auraflow', 'sana', 'zimage', 'lumina2', 'cogview4', 'h1', 'cosmos', 'anima', 'chroma', 'omnigen', 'omnigen2', 'longcat', 'ideogram4'] +flow_models = ['f1', 'f2', 'sd3', 'lumina', 'auraflow', 'sana', 'zimage', 'lumina2', 'cogview4', 'h1', 'cosmos', 'anima', 'chroma', 'omnigen', 'omnigen2', 'longcat', 'ideogram4', 'krea2'] warned = False queue_lock = threading.Lock() diff --git a/modules/shared_items.py b/modules/shared_items.py index 753bdaed3..00e4a732e 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -70,6 +70,7 @@ pipelines = { 'HiDreamO1': None, 'HunyuanImage3': None, 'Ideogram4': None, + 'Krea2': None, 'Lens': None, 'LuminaDiMOO': None, 'Meissonic': None, diff --git a/modules/ui_extra_networks_lora.py b/modules/ui_extra_networks_lora.py index 348ff9aac..8a1c11c98 100644 --- a/modules/ui_extra_networks_lora.py +++ b/modules/ui_extra_networks_lora.py @@ -69,6 +69,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): 'f1': 'Flux', 'sd1': 'SD 1.5', 'sd2': 'SD 2', 'xl': 'SDXL', 'sd3': 'SD3', 'sc': 'Cascade', 'hv': 'HunyuanVideo', 'chroma': 'Chroma', 'zimage': 'zImage', 'qwen': 'Qwen', + 'krea2': 'Krea 2', } def cleanup_version(self, dct, lora): diff --git a/modules/vae/sd_vae_taesd.py b/modules/vae/sd_vae_taesd.py index b661205e7..00739f13f 100644 --- a/modules/vae/sd_vae_taesd.py +++ b/modules/vae/sd_vae_taesd.py @@ -76,7 +76,7 @@ def get_model(model_cls, variant=None): variant = 'TAE FLUX.2' elif model_cls in {'sd3'}: variant = 'TAE SD3' - elif model_cls in {'wanai', 'qwen', 'chrono', 'cosmos', 'anima', 'fibo', 'joy'}: + elif model_cls in {'wanai', 'qwen', 'chrono', 'cosmos', 'anima', 'fibo', 'joy', 'krea2'}: variant = 'TAE WanVideo' else: warn_once(f'cls={shared.sd_model.__class__.__name__} type={shared.sd_model_type} unsuppported', variant=variant) diff --git a/pipelines/generic_shared.py b/pipelines/generic_shared.py index f0c153dc0..24d705b66 100644 --- a/pipelines/generic_shared.py +++ b/pipelines/generic_shared.py @@ -98,6 +98,11 @@ shared_te_map = { 'target_subfolder': 'text_encoder', }, + 'Qwen3-VL 4B Base': { # Krea 2 base+turbo share one canonical 4B copy + 'cls': transformers.Qwen3VLModel, + 'identifier': 'krea', + 'target_repo': 'Qwen/Qwen3-VL-4B-Instruct', + }, 'Qwen3-VL 8B SDNQ-UInt4': { 'cls': transformers.Qwen3VLModel, 'identifier': 'uint4', diff --git a/pipelines/krea2/__init__.py b/pipelines/krea2/__init__.py new file mode 100644 index 000000000..bea25eec4 --- /dev/null +++ b/pipelines/krea2/__init__.py @@ -0,0 +1,8 @@ +from pipelines.krea2.transformer_krea2 import Krea2Transformer2DModel +from pipelines.native_transformer import TransformerSpec + + +# Checkpoint keys are bare (`first.`, `blocks.N.`, `txtfusion.`, ...) and the transformer's +# module tree mirrors them exactly, so no state-dict conversion is needed. The model has no +# rope/pos buffers, so the default acceptable-missing set is sufficient. +KREA2_SPEC = TransformerSpec(cls=Krea2Transformer2DModel, converter=None) diff --git a/pipelines/krea2/krea2_lora.py b/pipelines/krea2/krea2_lora.py new file mode 100644 index 000000000..6f93e2f8e --- /dev/null +++ b/pipelines/krea2/krea2_lora.py @@ -0,0 +1,71 @@ +"""Krea 2 native adapter loader. + +Runs when :func:`modules.lora.lora_overrides.get_method` returns ``'native'`` +(``lora_force_diffusers`` off and ``krea2`` in ``allow_native``). + +The transformer module tree mirrors the checkpoint (``blocks.N.attn.{wq,wk,wv,wo,gate}``, +``blocks.N.mlp.{gate,up,down}``, ``txtfusion.*``, ``first``, ``last`` ...), so dotted keys +bind verbatim with no name rewrite and no fused-QKV split. Kohya flat-underscore keys are +reconstructed back to dotted paths, protecting the two compound module names +(``layerwise_blocks``, ``refiner_blocks``). +""" + +from modules.lora import native_adapter + + +KNOWN_PREFIXES = native_adapter.KNOWN_PREFIXES_DEFAULT + +# Top-level module names that a bare diffusers-format LoRA key can start with. +BARE_DIFFUSERS_PREFIXES = ("blocks.", "txtfusion.", "first.", "last.", "tmlp.", "tproj.", "txtmlp.") + + +def resolve_targets(prefix_used, base): + """Return ``[(diffusers_path, None), ...]`` for a parsed group key. + + K2's diffusers module names equal the checkpoint names, so dotted keys map verbatim. + Universal passthrough prefixes are handled upstream by + :func:`native_adapter.resolve_group_targets`. + """ + if prefix_used in (None, "diffusion_model.", "transformer."): + return [(base, None)] + if prefix_used in ("lora_unet_", "lora_transformer_"): + return _underscore_to_dotted(base) + return [] + + +def _underscore_to_dotted(base): + """Rebuild a dotted path from a kohya flat-underscore base, keeping compound names intact.""" + protected = base.replace("layerwise_blocks", "layerwise@blocks").replace("refiner_blocks", "refiner@blocks") + return [(protected.replace("_", ".").replace("@", "_"), None)] + + +_BIND_KWARGS = dict( + resolve_targets=resolve_targets, + prefixes=KNOWN_PREFIXES, + bare_diffusers_prefixes=BARE_DIFFUSERS_PREFIXES, + arch_name="krea2", +) + + +def try_load_lora(name, network_on_disk, lora_scale): + return native_adapter.try_load_lora(name, network_on_disk, lora_scale, **_BIND_KWARGS) + + +def try_load_lokr(name, network_on_disk, lora_scale): + return native_adapter.try_load_lokr(name, network_on_disk, lora_scale, **_BIND_KWARGS) + + +def try_load_loha(name, network_on_disk, lora_scale): + return native_adapter.try_load_loha(name, network_on_disk, lora_scale, **_BIND_KWARGS) + + +def try_load_oft(name, network_on_disk, lora_scale): + return native_adapter.try_load_oft(name, network_on_disk, lora_scale, **_BIND_KWARGS) + + +def try_load(name, network_on_disk, lora_scale): + """Run every Krea 2 family loader, merge any that match.""" + return native_adapter.try_load_chain( + name, network_on_disk, lora_scale, + family_loaders=(try_load_lora, try_load_lokr, try_load_loha, try_load_oft), + ) diff --git a/pipelines/krea2/pipeline_krea2.py b/pipelines/krea2/pipeline_krea2.py new file mode 100644 index 000000000..b713d64f9 --- /dev/null +++ b/pipelines/krea2/pipeline_krea2.py @@ -0,0 +1,273 @@ +"""Krea 2 (K2) text-to-image pipeline. + +A single-stream flow-matching pipeline that conditions a custom DiT on stacked Qwen3-VL +hidden states and decodes with the Qwen-Image VAE. The encode, packing, denoise and decode +steps mirror the reference K2 inference code. This module imports only diffusers/transformers +so the repos can ship it for standalone use; SD.Next-specific wiring lives in the loader. +""" + +import torch +from einops import rearrange, repeat + +from diffusers.image_processor import VaeImageProcessor +from diffusers.loaders import FromSingleFileMixin +from diffusers.pipelines.pipeline_utils import DiffusionPipeline, ImagePipelineOutput +from diffusers.utils.torch_utils import randn_tensor + + +class Krea2Pipeline(DiffusionPipeline, FromSingleFileMixin): + r"""Text-to-image generation with Krea 2. + + Args: + transformer (`Krea2Transformer2DModel`): single-stream flow-matching DiT. + text_encoder (`Qwen3VLModel`): multimodal backbone tapped for stacked hidden states. + tokenizer (`Qwen2Tokenizer`): tokenizer paired with `text_encoder`. + vae (`AutoencoderKLQwenImage`): f8/16-channel latent autoencoder. + scheduler (`FlowMatchEulerDiscreteScheduler`): exponential-shift flow-matching scheduler. + """ + + model_cpu_offload_seq = "text_encoder->transformer->vae" + _callback_tensor_inputs = ["latents"] + + # Conditioning template and layer taps (reference encoder.py / inference.py). + PROMPT_TEMPLATE_PREFIX = ( + "<|im_start|>system\nDescribe the image by detailing the color, shape, size, texture, " + "quantity, text, spatial relationships of the objects and background:<|im_end|>\n" + "<|im_start|>user\n" + ) + PROMPT_TEMPLATE_SUFFIX = "<|im_end|>\n<|im_start|>assistant\n" + PREFIX_TOKENS = 34 + SUFFIX_START = 5 + MAX_LENGTH = 512 + SELECT_LAYERS = (2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35) + MIN_RES = 256 + MAX_RES = 1280 + + def __init__(self, transformer, text_encoder, tokenizer, vae, scheduler): + super().__init__() + self.register_modules( + transformer=transformer, + text_encoder=text_encoder, + tokenizer=tokenizer, + vae=vae, + scheduler=scheduler, + ) + self.patch = int(getattr(transformer.config, "patch", 2)) + self.latent_channels = int(getattr(transformer.config, "channels", 16)) + self.vae_compression = 8 # AutoencoderKLQwenImage is f8 + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_compression) + self._interrupt = False + self._guidance_scale = None + self._num_timesteps = 0 + + # --- text conditioning: port of encoder.py Qwen3VLConditioner.forward --- + def encode_prompt(self, prompts: list[str], device: torch.device) -> tuple[torch.Tensor, torch.Tensor]: + """Return `(hidden, mask)` where hidden is `(B, L, len(SELECT_LAYERS), txtdim)`. + + The user prompt is wrapped in the image-description chat template, padded to a fixed + length, and the assistant suffix appended; the system prefix tokens are then dropped. + """ + texts = [self.PROMPT_TEMPLATE_PREFIX + p for p in prompts] + suffix = self.tokenizer([self.PROMPT_TEMPLATE_SUFFIX] * len(texts), return_tensors="pt").to(device) + main = self.tokenizer( + texts, + truncation=True, + padding="max_length", + max_length=self.MAX_LENGTH + self.PREFIX_TOKENS - self.SUFFIX_START, + return_tensors="pt", + ).to(device) + + input_ids = torch.cat([main.input_ids, suffix.input_ids], dim=1) + mask = torch.cat([main.attention_mask.bool(), suffix.attention_mask.bool()], dim=1) + + out = self.text_encoder(input_ids=input_ids, attention_mask=mask, output_hidden_states=True) + hidden = torch.stack([out.hidden_states[i] for i in self.SELECT_LAYERS], dim=2) + return hidden[:, self.PREFIX_TOKENS:], mask[:, self.PREFIX_TOKENS:] + + # --- packing: port of sampling.prepare --- + def pack_sequence(self, latent: torch.Tensor, text_mask: torch.Tensor): + """Patchify the latent and build the joint text+image `(tokens, position_ids, mask)`.""" + batch, _, height, width = latent.shape + patch = self.patch + grid_h, grid_w = height // patch, width // patch + device = latent.device + + img_ids = torch.zeros(grid_h, grid_w, 3, device=device) + img_ids[..., 1] = torch.arange(grid_h, device=device)[:, None] + img_ids[..., 2] = torch.arange(grid_w, device=device)[None, :] + img_pos = repeat(img_ids, "h w c -> b (h w) c", b=batch) + img_mask = torch.ones(batch, grid_h * grid_w, dtype=torch.bool, device=device) + img_tokens = rearrange(latent, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch, pw=patch) + + txt_pos = torch.zeros(batch, text_mask.shape[1], 3, device=device) + pos = torch.cat([txt_pos, img_pos], dim=1) + mask = torch.cat([text_mask, img_mask], dim=1) + return img_tokens, pos, mask + + def prepare_latents(self, batch_size, height, width, dtype, device, generator, latents=None): + shape = (batch_size, self.latent_channels, height // self.vae_compression, width // self.vae_compression) + if latents is not None: + return latents.to(device=device, dtype=dtype) + return randn_tensor(shape, generator=generator, device=device, dtype=dtype) + + @staticmethod + def calculate_shift(image_seq_len, base_seq_len, max_seq_len, base_shift, max_shift): + slope = (max_shift - base_shift) / (max_seq_len - base_seq_len) + return image_seq_len * slope + (base_shift - slope * base_seq_len) + + def decode_latents(self, latents: torch.Tensor) -> torch.Tensor: + """Denormalize and decode latents with the Qwen-Image VAE (port of autoencoder.decode).""" + cfg = self.vae.config + mean = torch.tensor(cfg.latents_mean, device=latents.device, dtype=latents.dtype).view(1, -1, 1, 1, 1) + std = torch.tensor(cfg.latents_std, device=latents.device, dtype=latents.dtype).view(1, -1, 1, 1, 1) + latents = latents.unsqueeze(2) * std + mean # (B, C, 1, H, W); the VAE treats images as 1-frame video + image = self.vae.decode(latents).sample + return image.squeeze(2) + + def encode_image(self, image, height, width, dtype, device): + """Encode a pixel image to a normalized latent (inverse of decode_latents).""" + pixels = self.image_processor.preprocess(image, height=height, width=width) + pixels = pixels.to(device=device, dtype=self.vae.dtype).unsqueeze(2) # (B, C, 1, H, W) + cfg = self.vae.config + mean = torch.tensor(cfg.latents_mean, device=device, dtype=dtype).view(1, -1, 1, 1, 1) + std = torch.tensor(cfg.latents_std, device=device, dtype=dtype).view(1, -1, 1, 1, 1) + raw = self.vae.encode(pixels).latent_dist.mode() + return ((raw.to(dtype) - mean) / std).squeeze(2) + + @property + def guidance_scale(self): + return self._guidance_scale + + @property + def num_timesteps(self): + return self._num_timesteps + + @property + def interrupt(self): + return self._interrupt + + @torch.no_grad() + def __call__( + self, + prompt: str | list[str] | None = None, + negative_prompt: str | list[str] | None = None, + height: int = 1024, + width: int = 1024, + num_inference_steps: int = 28, + guidance_scale: float = 4.5, + num_images_per_prompt: int = 1, + generator: torch.Generator | list[torch.Generator] | None = None, + latents: torch.Tensor | None = None, + image=None, + strength: float = 0.6, + output_type: str = "pil", + return_dict: bool = True, + attention_kwargs: dict | None = None, + callback_on_step_end=None, + callback_on_step_end_tensor_inputs: list[str] | None = None, + ): + align = self.vae_compression * self.patch + height = (height // align) * align + width = (width // align) * align + + prompts = [prompt] if isinstance(prompt, str) else list(prompt) + device = self._execution_device + dtype = self.transformer.dtype + self._guidance_scale = guidance_scale + self._interrupt = False + + is_distilled = bool(getattr(self.transformer.config, "is_distilled", False)) + do_cfg = guidance_scale is not None and guidance_scale > 0 and not is_distilled + + text, text_mask = self.encode_prompt(prompts, device) + text = text.to(dtype) + if do_cfg: + negatives = [negative_prompt or ""] * len(prompts) if not isinstance(negative_prompt, list) else negative_prompt + uncond, uncond_mask = self.encode_prompt(negatives, device) + uncond = uncond.to(dtype) + + batch = len(prompts) * num_images_per_prompt + text = text.repeat_interleave(num_images_per_prompt, dim=0) + text_mask = text_mask.repeat_interleave(num_images_per_prompt, dim=0) + if do_cfg: + uncond = uncond.repeat_interleave(num_images_per_prompt, dim=0) + uncond_mask = uncond_mask.repeat_interleave(num_images_per_prompt, dim=0) + + cfg = self.scheduler.config + grid_h = height // (self.vae_compression * self.patch) + grid_w = width // (self.vae_compression * self.patch) + mu = self.calculate_shift( + grid_h * grid_w, + cfg.get("base_image_seq_len", 256), + cfg.get("max_image_seq_len", 6400), + cfg.get("base_shift", 0.5), + cfg.get("max_shift", 1.15), + ) + self.scheduler.set_timesteps(num_inference_steps, device=device, mu=mu) + timesteps = self.scheduler.timesteps + + if image is not None: + clean = self.encode_image(image, height, width, dtype, device) + clean = clean.repeat_interleave(num_images_per_prompt, dim=0) + init_steps = min(int(num_inference_steps * strength), num_inference_steps) + t_start = max(num_inference_steps - init_steps, 0) + timesteps = timesteps[t_start:] + noise = self.prepare_latents(batch, height, width, dtype, device, generator) + latents = self.scheduler.scale_noise(clean, timesteps[:1], noise) + else: + latents = self.prepare_latents(batch, height, width, dtype, device, generator, latents) + + img, pos, mask = self.pack_sequence(latents, text_mask) + if do_cfg: + _, uncond_pos, uncond_full_mask = self.pack_sequence(latents, uncond_mask) + self._num_timesteps = len(timesteps) + num_train = cfg.get("num_train_timesteps", 1000) + + with self.progress_bar(total=len(timesteps)) as progress_bar: + for i, t in enumerate(timesteps): + if self.interrupt: + continue + model_t = (t.float() / num_train).reshape(1).expand(batch).to(device=device, dtype=img.dtype) + cond = self.transformer( + hidden_states=img, encoder_hidden_states=text, timestep=model_t, + position_ids=pos, attention_mask=mask, return_dict=False, + )[0] + if do_cfg: + neg = self.transformer( + hidden_states=img, encoder_hidden_states=uncond, timestep=model_t, + position_ids=uncond_pos, attention_mask=uncond_full_mask, return_dict=False, + )[0] + velocity = cond + guidance_scale * (cond - neg) + else: + velocity = cond + img = self.scheduler.step(velocity, t, img, return_dict=False)[0] + + if callback_on_step_end is not None: + # Unpack the packed tokens to a standard [B, C, h, w] latent for the callback's preview. + cb_kwargs = {} + if "latents" in (callback_on_step_end_tensor_inputs or ["latents"]): + cb_kwargs["latents"] = rearrange(img, "b (h w) (c ph pw) -> b c (h ph) (w pw)", h=grid_h, w=grid_w, ph=self.patch, pw=self.patch) + callback_on_step_end(self, i, t, cb_kwargs) + progress_bar.update() + + latent = rearrange(img, "b (h w) (c ph pw) -> b c (h ph) (w pw)", h=grid_h, w=grid_w, ph=self.patch, pw=self.patch) + + if output_type == "latent": + image = latent + else: + image = self.decode_latents(latent.to(dtype)) + image = self.image_processor.postprocess(image, output_type=output_type) + + self.maybe_free_model_hooks() + if not return_dict: + return (image,) + return ImagePipelineOutput(images=image) + + +class Krea2Img2ImgPipeline(Krea2Pipeline): + """Image-to-image task variant. + + The denoise path is identical to the base pipeline (which already accepts `image` + `strength`); + this distinct class exists so the diffusers AUTO maps and `get_diffusers_task` can tell the two + tasks apart, following the per-task-class convention (e.g. ChromaPipeline / ChromaImg2ImgPipeline). + """ diff --git a/pipelines/krea2/transformer_krea2.py b/pipelines/krea2/transformer_krea2.py new file mode 100644 index 000000000..c4805f5e7 --- /dev/null +++ b/pipelines/krea2/transformer_krea2.py @@ -0,0 +1,349 @@ +"""Krea 2 (K2) single-stream DiT, ported to the diffusers ModelMixin contract. + +The module tree mirrors the original `SingleStreamDiT` checkpoint exactly (``first``, +``blocks.N.attn.{wq,wk,wv,gate,wo}``, ``txtfusion.*``, ``last`` ...), so the safetensors +load is an identity map (no key conversion). Architecture specifics: gated attention, +grouped-query attention, per-head QK RMSNorm, 3-axis rotary embedding, a shared+per-block +modulation, and a text-fusion stage that collapses several text-encoder hidden-state layers +into one conditioning stream. +""" + +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +from einops import rearrange + +from diffusers.configuration_utils import ConfigMixin, register_to_config +from diffusers.loaders import FromOriginalModelMixin, PeftAdapterMixin +from diffusers.models.attention_dispatch import dispatch_attention_fn +from diffusers.models.modeling_outputs import Transformer2DModelOutput +from diffusers.models.modeling_utils import ModelMixin + + +def rope(pos: torch.Tensor, dim: int, theta: float = 1e4, ntk: float = 1.0) -> torch.Tensor: + scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim + omega = 1.0 / ((theta * ntk) ** scale) + out = torch.einsum("...n,d->...nd", pos, omega) + out = torch.stack([torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1) + out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2) + return out.float() + + +def rope_apply(xq: torch.Tensor, xk: torch.Tensor, freqs: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: + xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2) + xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2) + freqs = freqs[:, None, :, :, :] + xq_ = freqs[..., 0] * xq_[..., 0] + freqs[..., 1] * xq_[..., 1] + xk_ = freqs[..., 0] * xk_[..., 0] + freqs[..., 1] * xk_[..., 1] + return xq_.reshape(*xq.shape).to(xq.dtype), xk_.reshape(*xk.shape).to(xk.dtype) + + +def time_embed(t: torch.Tensor, dim: int, period: float = 1e4, tfactor: float = 1e3, device=None, dtype=None) -> torch.Tensor: + half = dim // 2 + freqs = torch.exp(-math.log(period) * torch.arange(half, dtype=torch.float32, device=device) / half) + # t: (B,) -> (B, 1, half) so the embedding broadcasts as a per-sample vector. + args = (t.float() * tfactor)[:, None, None] * freqs + return torch.cat((torch.cos(args), torch.sin(args)), dim=-1).to(dtype=dtype) + + +def expand_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor: + """Repeat each KV head ``n_rep`` times so grouped-query attention runs on any SDPA backend.""" + if n_rep == 1: + return x + return x.repeat_interleave(n_rep, dim=1) # x: (B, kvheads, L, D) -> (B, heads, L, D) + + +def segment_mask(mask: torch.Tensor) -> torch.Tensor: + """Expand a (B, L) key-padding mask into a (B, 1, L, L) attention mask.""" + return mask.unsqueeze(1).unsqueeze(2) * mask.unsqueeze(1).unsqueeze(3) + + +class RMSNorm(nn.Module): + """RMSNorm whose stored weight is a zero-init delta applied as ``scale + 1`` and computed in fp32.""" + + def __init__(self, features: int, eps: float = 1e-05): + super().__init__() + self.features = features + self.eps = eps + self.scale = nn.Parameter(torch.zeros(features, dtype=torch.float32)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + dtype = x.dtype + t = F.rms_norm(x.float(), (self.features,), eps=self.eps, weight=self.scale.float() + 1.0) + return t.to(dtype) + + +class QKNorm(nn.Module): + def __init__(self, dim: int): + super().__init__() + self.qnorm = RMSNorm(dim) + self.knorm = RMSNorm(dim) + + def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + return self.qnorm(q), self.knorm(k), v + + +class SimpleModulation(nn.Module): + def __init__(self, dim: int): + super().__init__() + self.lin = nn.Parameter(torch.zeros(2, dim)) + self.multiplier = 2 + + def forward(self, vec: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: + out = vec + rearrange(self.lin, "two d -> 1 two d") + scale, shift = out.chunk(self.multiplier, dim=1) + return scale, shift + + +class DoubleSharedModulation(nn.Module): + def __init__(self, dim: int): + super().__init__() + self.lin = nn.Parameter(torch.zeros(6 * dim)) + + def forward(self, vec: torch.Tensor): + out = vec + self.lin + return out.chunk(6, dim=-1) + + +class PositionalEncoding(nn.Module): + def __init__(self, axdims: list[int], theta: float = 1e2, ntk: float = 1.0): + super().__init__() + self.axdims = axdims # split of the head dimension across the position axes + self.theta = theta + self.ntk = ntk + + def forward(self, pos: torch.Tensor) -> torch.Tensor: + return torch.cat([rope(pos[..., i], d, self.theta, self.ntk) for i, d in enumerate(self.axdims)], dim=-3) + + +class SwiGLU(nn.Module): + def __init__(self, features: int, multiplier: int, bias: bool = False, multiple: int = 128): + super().__init__() + mlpdim = int(2 * features / 3) * multiplier + mlpdim = multiple * ((mlpdim + multiple - 1) // multiple) + self.gate = nn.Linear(features, mlpdim, bias=bias) + self.up = nn.Linear(features, mlpdim, bias=bias) + self.down = nn.Linear(mlpdim, features, bias=bias) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.down(F.silu(self.gate(x)) * self.up(x)) + + +class Attention(nn.Module): + """Gated grouped-query attention with per-head QK RMSNorm and optional 3-axis RoPE.""" + + def __init__(self, dim: int, heads: int, kvheads: int | None = None, bias: bool = False): + super().__init__() + self.heads = heads + self.kvheads = kvheads if kvheads is not None else heads + self.headdim = dim // self.heads + self.n_rep = self.heads // self.kvheads + + self.wq = nn.Linear(dim, self.headdim * self.heads, bias=bias) + self.wk = nn.Linear(dim, self.headdim * self.kvheads, bias=bias) + self.wv = nn.Linear(dim, self.headdim * self.kvheads, bias=bias) + self.gate = nn.Linear(dim, dim, bias=bias) + self.qknorm = QKNorm(self.headdim) + self.wo = nn.Linear(dim, dim, bias=bias) + + def forward(self, x: torch.Tensor, freqs: torch.Tensor | None = None, mask: torch.Tensor | None = None) -> torch.Tensor: + q, k, v, gate = self.wq(x), self.wk(x), self.wv(x), self.gate(x) + q = rearrange(q, "B L (H D) -> B H L D", H=self.heads) + k = rearrange(k, "B L (H D) -> B H L D", H=self.kvheads) + v = rearrange(v, "B L (H D) -> B H L D", H=self.kvheads) + + q, k, v = self.qknorm(q, k, v) + if freqs is not None: + q, k = rope_apply(q, k, freqs) + k, v = expand_kv(k, self.n_rep), expand_kv(v, self.n_rep) + + # dispatch_attention_fn expects (B, L, H, D); the q/k/v math above stays in (B, H, L, D). + out = dispatch_attention_fn(q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), attn_mask=mask) + # A fully-masked query row (padding token) yields NaN on the CUDA SDPA backends; zero it so + # it cannot propagate through the next layer's `0 * NaN`. cuDNN returns 0 here already. + out = torch.nan_to_num(out) + out = rearrange(out, "B L H D -> B L (H D)") + return self.wo(out * F.sigmoid(gate)) + + +class LastLayer(nn.Module): + def __init__(self, features: int, patch: int, channels: int): + super().__init__() + self.norm = RMSNorm(features) + self.linear = nn.Linear(features, patch * patch * channels, bias=True) + self.modulation = SimpleModulation(features) + self.down = nn.Linear(features, features, bias=False) + self.up = nn.Linear(features, features, bias=False) + + def forward(self, x: torch.Tensor, tvec: torch.Tensor) -> torch.Tensor: + scale, shift = self.modulation(tvec) + x = (1 + scale) * self.norm(x) + shift + self.up(self.down(x)) + return self.linear(x) + + +class TextFusionBlock(nn.Module): + def __init__(self, features: int, heads: int, multiplier: int, bias: bool = False, kvheads: int | None = None): + super().__init__() + self.prenorm = RMSNorm(features) + self.postnorm = RMSNorm(features) + self.attn = Attention(dim=features, heads=heads, bias=bias, kvheads=kvheads) + self.mlp = SwiGLU(features, multiplier, bias) + + def forward(self, x: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor: + x = x + self.attn(self.prenorm(x), mask=mask) + x = x + self.mlp(self.postnorm(x)) + return x + + +class TextFusionTransformer(nn.Module): + """Fuse ``num_txt_layers`` stacked text-encoder hidden states into a single conditioning stream. + + ``num_txt_layers`` is the count of selected encoder layers fed in (projected down to 1), + not the transformer depth, which is fixed at 2 layerwise + 2 refiner blocks. + """ + + def __init__(self, num_txt_layers: int, txt_dim: int, heads: int, multiplier: int, bias: bool = False, kvheads: int | None = None): + super().__init__() + self.layerwise_blocks = nn.ModuleList([TextFusionBlock(txt_dim, heads, multiplier, bias, kvheads) for _ in range(2)]) + self.projector = nn.Linear(num_txt_layers, 1, bias=False) + self.refiner_blocks = nn.ModuleList([TextFusionBlock(txt_dim, heads, multiplier, bias, kvheads) for _ in range(2)]) + + def forward(self, x: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor: + b, l, n, d = x.shape + x = x.reshape(b * l, n, d) + for block in self.layerwise_blocks: + x = block(x.contiguous(), mask=None) + x = rearrange(x, "(b l) n d -> b l d n", b=b, l=l) + x = self.projector(x).squeeze(-1) + for block in self.refiner_blocks: + x = block(x, mask=mask) + return x + + +class SingleStreamBlock(nn.Module): + def __init__(self, features: int, heads: int, multiplier: int, bias: bool = False, kvheads: int | None = None): + super().__init__() + self.mod = DoubleSharedModulation(features) + self.prenorm = RMSNorm(features) + self.postnorm = RMSNorm(features) + self.attn = Attention(dim=features, heads=heads, bias=bias, kvheads=kvheads) + self.mlp = SwiGLU(features, multiplier, bias) + + def forward(self, x: torch.Tensor, vec: torch.Tensor, freqs: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor: + prescale, preshift, pregate, postscale, postshift, postgate = self.mod(vec) + x = x + pregate * self.attn((1 + prescale) * self.prenorm(x) + preshift, freqs, mask) + x = x + postgate * self.mlp((1 + postscale) * self.postnorm(x) + postshift) + return x + + +class Krea2Transformer2DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, FromOriginalModelMixin): + r"""Single-stream flow-matching DiT backbone for Krea 2. + + The transformer consumes patchified noisy image tokens plus stacked text-encoder hidden + states, fuses the text layers internally, concatenates text and image tokens into one + stream, and predicts the flow-matching velocity for the image-token positions. + """ + + _supports_gradient_checkpointing = True + _no_split_modules = ["SingleStreamBlock", "TextFusionBlock"] + _repeated_blocks = ["SingleStreamBlock"] + + @register_to_config + def __init__( + self, + features: int = 6144, + tdim: int = 256, + txtdim: int = 2560, + heads: int = 48, + kvheads: int = 12, + multiplier: int = 4, + layers: int = 28, + patch: int = 2, + channels: int = 16, + bias: bool = False, + theta: float = 1e3, + txtlayers: int = 12, + txtheads: int = 20, + txtkvheads: int = 20, + is_distilled: bool = False, + ): + super().__init__() + self.gradient_checkpointing = False + self.tdim = tdim + self.is_distilled = is_distilled + + headdim = features // heads + axes = [headdim - 12 * (headdim // 16), 6 * (headdim // 16), 6 * (headdim // 16)] + assert sum(axes) == headdim, f"sum(axes)={sum(axes)} != headdim={headdim}" + assert all(a % 2 == 0 for a in axes), f"axes={axes}" + + self.posemb = PositionalEncoding(axes, theta=theta, ntk=1.0) + self.first = nn.Linear(channels * patch**2, features, bias=True) + self.blocks = nn.ModuleList( + [SingleStreamBlock(features, heads, multiplier, bias, kvheads) for _ in range(layers)] + ) + self.tmlp = nn.Sequential( + nn.Linear(tdim, features), + nn.GELU(approximate="tanh"), + nn.Linear(features, features), + ) + self.txtfusion = TextFusionTransformer(txtlayers, txtdim, txtheads, multiplier, bias, txtkvheads) + self.txtmlp = nn.Sequential( + RMSNorm(txtdim), + nn.Linear(txtdim, features), + nn.GELU(approximate="tanh"), + nn.Linear(features, features), + ) + self.last = LastLayer(features, patch, channels) + self.tproj = nn.Sequential(nn.GELU(approximate="tanh"), nn.Linear(features, features * 6)) + + def forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor, + timestep: torch.Tensor, + position_ids: torch.Tensor, + attention_mask: torch.Tensor, + return_dict: bool = True, + ): + r""" + Args: + hidden_states: `(B, L_img, channels * patch ** 2)` patchified noisy image tokens. + encoder_hidden_states: `(B, L_txt, txtlayers, txtdim)` stacked text-encoder hidden states. + timestep: `(B,)` flow-matching time in `[0, 1]`. + position_ids: `(B, L_txt + L_img, 3)` `(t, h, w)` coordinates for the 3-axis RoPE. + attention_mask: `(B, L_txt + L_img)` boolean key-padding mask (True = valid token). + """ + img = self.first(hidden_states) + t = self.tmlp(time_embed(timestep, self.tdim, device=img.device, dtype=img.dtype)) + tvec = self.tproj(t) + + txtmask = segment_mask(attention_mask[:, : encoder_hidden_states.shape[1]]) + context = self.txtfusion(encoder_hidden_states, mask=txtmask) + context = self.txtmlp(context) + + txtlen, imglen = context.shape[1], img.shape[1] + combined = torch.cat((context, img), dim=1) + + # Pad the joint sequence to a multiple of 256 to keep compiled attention kernel shapes stable. + padlen = (-combined.shape[1]) % 256 + if padlen > 0: + combined = F.pad(combined, (0, 0, 0, padlen)) + attention_mask = F.pad(attention_mask, (0, padlen), value=False) + position_ids = F.pad(position_ids, (0, 0, 0, padlen)) + + mask = segment_mask(attention_mask) + freqs = self.posemb(position_ids) + + for block in self.blocks: + if torch.is_grad_enabled() and self.gradient_checkpointing: + combined = self._gradient_checkpointing_func(block, combined, tvec, freqs, mask) + else: + combined = block(combined, tvec, freqs, mask) + + output = self.last(combined, t)[:, txtlen : txtlen + imglen, :] + if not return_dict: + return (output,) + return Transformer2DModelOutput(sample=output) diff --git a/pipelines/model_krea2.py b/pipelines/model_krea2.py new file mode 100644 index 000000000..afe3ec5ac --- /dev/null +++ b/pipelines/model_krea2.py @@ -0,0 +1,53 @@ +import diffusers +import transformers +from modules import shared, devices, sd_models, model_quant, sd_hijack_te, sd_hijack_vae +from modules.logger import log +from pipelines import generic + + +def load_krea2(checkpoint_info, diffusers_load_config=None): + if diffusers_load_config is None: + diffusers_load_config = {} + repo_id = sd_models.path_to_repo(checkpoint_info) + sd_models.hf_auth_check(checkpoint_info) + load_args, _ = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) + log.debug(f'Load model: type=Krea2 repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype} args={load_args}') + + from pipelines.krea2.transformer_krea2 import Krea2Transformer2DModel + from pipelines.krea2.pipeline_krea2 import Krea2Pipeline, Krea2Img2ImgPipeline + from pipelines.krea2 import KREA2_SPEC + diffusers.Krea2Transformer2DModel = Krea2Transformer2DModel + diffusers.Krea2Pipeline = Krea2Pipeline + diffusers.Krea2Img2ImgPipeline = Krea2Img2ImgPipeline + generic.set_pipeline('Krea2', Krea2Pipeline) + # One class per task so get_diffusers_task defaults to text2image and set_diffuser_pipe switches + # to the img2img variant cleanly (matches the Chroma/Qwen per-task-class pattern). + from diffusers.pipelines import auto_pipeline + auto_pipeline.AUTO_TEXT2IMAGE_PIPELINES_MAPPING['krea2'] = Krea2Pipeline + auto_pipeline.AUTO_IMAGE2IMAGE_PIPELINES_MAPPING['krea2'] = Krea2Img2ImgPipeline + if repo_id is None or repo_id.lower() == 'none': + return None + + # Keep small/sensitive layers in compute dtype. `first` (in=64) and `txtfusion.projector` + # (in=12) are below the int8 GEMM's minimum K; `last` is the output projection; `tmlp`/`tproj` + # produce the global per-block modulation, too int8-sensitive to quantize (its error compounds + # across blocks and steps). All are tiny next to the 28 blocks, so the memory cost is small. + transformer = generic.load_transformer(repo_id, cls_name=Krea2Transformer2DModel, load_config=diffusers_load_config, native_spec=KREA2_SPEC, modules_to_not_convert=['first', 'last', 'projector', 'tmlp', 'tproj']) + text_encoder = generic.load_text_encoder(repo_id, cls_name=transformers.Qwen3VLModel, load_config=diffusers_load_config) + + pipe = Krea2Pipeline.from_pretrained( + repo_id, + cache_dir=shared.opts.diffusers_dir, + transformer=transformer, + text_encoder=text_encoder, + **load_args, + ) + + generic.load_vae_override(pipe, diffusers_load_config) + + del transformer + del text_encoder + sd_hijack_te.init_hijack(pipe) + sd_hijack_vae.init_hijack(pipe) + devices.torch_gc(force=True, reason='load') + return pipe diff --git a/test/test-krea2-transformer.py b/test/test-krea2-transformer.py new file mode 100644 index 000000000..d62d937ec --- /dev/null +++ b/test/test-krea2-transformer.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +"""Offline parity test: Krea2Transformer2DModel vs the reference SingleStreamDiT. + +Builds both models from one tiny config, copies the reference state dict into the diffusers +port, runs identical inputs, and asserts the forward outputs match. No server, no checkpoint. + +The reference checkpoint repo (mmdit.py) is expected at $KREA2_REF_DIR +(default /home/ohiom/database/watering-hole). +""" + +import importlib.util +import os +import sys +from contextlib import nullcontext + +import torch + +REF_DIR = os.environ.get("KREA2_REF_DIR", "/home/ohiom/database/watering-hole") + + +def load_reference(): + sys.path.insert(0, REF_DIR) + import mmdit + # The reference pins the cuDNN SDPA backend; neutralize it so both models use the same + # default kernel and the test can run on CPU. + mmdit.sdpa_kernel = lambda *a, **k: nullcontext() + return mmdit + + +def load_port(): + path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "pipelines", "krea2", "transformer_krea2.py")) + spec = importlib.util.spec_from_file_location("transformer_krea2", path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +def main(): + mmdit = load_reference() + port = load_port() + + cfg = dict( + features=128, tdim=32, txtdim=64, heads=4, kvheads=2, multiplier=4, + layers=2, patch=2, channels=4, bias=False, theta=1e3, + txtlayers=3, txtheads=2, txtkvheads=2, + ) + + torch.manual_seed(0) + ref = mmdit.SingleStreamDiT(mmdit.SingleMMDiTConfig(**cfg)).float().eval() + mine = port.Krea2Transformer2DModel(**cfg).float().eval() + missing, unexpected = mine.load_state_dict(ref.state_dict(), strict=False) + assert not missing, f"missing keys when loading reference weights: {missing}" + assert not unexpected, f"unexpected keys when loading reference weights: {unexpected}" + + batch, txtlen, imglen = 2, 5, 9 + cdim = cfg["channels"] * cfg["patch"] ** 2 + seq = txtlen + imglen + gen = torch.Generator().manual_seed(1) + img = torch.randn(batch, imglen, cdim, generator=gen) + context = torch.randn(batch, txtlen, cfg["txtlayers"], cfg["txtdim"], generator=gen) + timestep = torch.rand(batch, generator=gen) + pos = torch.randint(0, 16, (batch, seq, 3), generator=gen).float() + mask = torch.ones(batch, seq, dtype=torch.bool) + mask[0, -2:] = False # exercise the key-padding path + + with torch.no_grad(): + out_ref = ref(img, context, timestep, pos, mask) + out_mine = mine( + hidden_states=img, + encoder_hidden_states=context, + timestep=timestep, + position_ids=pos, + attention_mask=mask, + return_dict=False, + )[0] + + assert out_ref.shape == out_mine.shape, f"shape mismatch: {out_ref.shape} vs {out_mine.shape}" + diff = (out_ref - out_mine).abs().max().item() + rel = diff / (out_ref.abs().max().item() + 1e-8) + print(f"output shape: {tuple(out_mine.shape)}") + print(f"max abs diff: {diff:.3e} max rel diff: {rel:.3e}") + tol = 1e-4 + assert diff < tol, f"PARITY FAILED: max abs diff {diff:.3e} >= {tol}" + print("PARITY OK") + + +if __name__ == "__main__": + main()