Instructions to use diffusers-modular/krea2-edit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use diffusers-modular/krea2-edit with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("diffusers-modular/krea2-edit", torch_dtype=torch.bfloat16, device_map="cuda") prompt = "Turn this cat into a dog" input_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat.png") image = pipe(image=input_image, prompt=prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Krea 2 edit modular blocks (remote code)
Browse files- README.md +62 -0
- before_denoise.py +635 -0
- block.py +12 -0
- config.json +5 -0
- decoders.py +265 -0
- denoise.py +407 -0
- encoders.py +844 -0
- inputs.py +406 -0
- modular_blocks_krea2.py +879 -0
- modular_blocks_krea2_edit.py +97 -0
- modular_pipeline.py +135 -0
- transformer_krea2.py +566 -0
README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: diffusers
|
| 3 |
+
tags:
|
| 4 |
+
- modular-diffusers
|
| 5 |
+
- krea
|
| 6 |
+
- image-to-image
|
| 7 |
+
- image-editing
|
| 8 |
+
base_model: krea/Krea-2-Turbo
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# Krea 2 reference-image edit — Modular Diffusers blocks
|
| 12 |
+
|
| 13 |
+
Custom [Modular Diffusers](https://huggingface.co/docs/diffusers/main/en/modular_diffusers/overview)
|
| 14 |
+
blocks that reproduce the [`ostris/Krea2OstrisEdit`](https://huggingface.co/ostris/Krea2OstrisEdit)
|
| 15 |
+
reference-image ("edit") workflow for **Krea 2**, loadable as remote code on top of stock `diffusers`.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import torch
|
| 19 |
+
from transformers import Qwen3VLProcessor
|
| 20 |
+
from diffusers import ClassifierFreeGuidance
|
| 21 |
+
from diffusers.modular_pipelines import ModularPipelineBlocks
|
| 22 |
+
|
| 23 |
+
blocks = ModularPipelineBlocks.from_pretrained("diffusers-modular/krea2-edit", trust_remote_code=True)
|
| 24 |
+
pipe = blocks.init_pipeline("krea/Krea-2-Turbo") # weights from the base repo
|
| 25 |
+
pipe.load_components(torch_dtype=torch.bfloat16)
|
| 26 |
+
pipe.update_components(processor=Qwen3VLProcessor.from_pretrained("Qwen/Qwen3-VL-4B-Instruct"))
|
| 27 |
+
pipe.update_components(guider=ClassifierFreeGuidance(guidance_scale=0.0, use_original_formulation=True))
|
| 28 |
+
pipe.to("cuda")
|
| 29 |
+
|
| 30 |
+
from PIL import Image
|
| 31 |
+
image = pipe(
|
| 32 |
+
prompt="a white yeti with horns reading a book",
|
| 33 |
+
image=Image.open("reference.png"), # one or more reference images
|
| 34 |
+
num_inference_steps=8, mu=1.15, output="images",
|
| 35 |
+
)[0]
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## What it does
|
| 39 |
+
|
| 40 |
+
Reference images condition generation two ways (matching how the Ostris AI-Toolkit edit LoRAs train):
|
| 41 |
+
|
| 42 |
+
1. **Qwen3-VL prompt embedding** — a coarse view of each reference is embedded into the text
|
| 43 |
+
conditioning through the vision tower.
|
| 44 |
+
2. **Clean VAE latents at flow time t=0** — each reference is VAE-encoded and appended to the
|
| 45 |
+
transformer sequence as clean tokens on its own rotary frame axis, so the noisy image tokens
|
| 46 |
+
attend to it at every block.
|
| 47 |
+
|
| 48 |
+
The bundled `Krea2Transformer2DModel` adds a small, backward-compatible `ref_seq_len` argument to the
|
| 49 |
+
Krea 2 transformer forward (t=0 modulation of the reference span; those tokens are excluded from the
|
| 50 |
+
predicted velocity). With `ref_seq_len=0` it is numerically identical to plain Krea 2 text-to-image.
|
| 51 |
+
|
| 52 |
+
## Files
|
| 53 |
+
|
| 54 |
+
- `block.py` — entry point (`Krea2EditBlocks`), referenced by `config.json`'s `auto_map`.
|
| 55 |
+
- `transformer_krea2.py` — the Krea 2 transformer (with the `ref_seq_len` edit path).
|
| 56 |
+
- `modular_blocks_krea2*.py`, `encoders.py`, `before_denoise.py`, `denoise.py`, `decoders.py`,
|
| 57 |
+
`inputs.py`, `modular_pipeline.py` — the modular blocks.
|
| 58 |
+
|
| 59 |
+
Part of the [`diffusers-modular`](https://huggingface.co/diffusers-modular) effort to replicate
|
| 60 |
+
popular ComfyUI / community workflows with Modular Diffusers.
|
| 61 |
+
|
| 62 |
+
> Requires access to the gated `krea/Krea-2-Turbo` weights.
|
before_denoise.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import inspect
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
|
| 21 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 22 |
+
from diffusers.modular_pipelines.modular_pipeline import ModularPipelineBlocks, PipelineState
|
| 23 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, InputParam, OutputParam
|
| 24 |
+
from .modular_pipeline import Krea2ModularPipeline, Krea2Pachifier
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Copied from diffusers.pipelines.flux.pipeline_flux.calculate_shift
|
| 28 |
+
def calculate_shift(
|
| 29 |
+
image_seq_len,
|
| 30 |
+
base_seq_len: int = 256,
|
| 31 |
+
max_seq_len: int = 4096,
|
| 32 |
+
base_shift: float = 0.5,
|
| 33 |
+
max_shift: float = 1.15,
|
| 34 |
+
):
|
| 35 |
+
m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
|
| 36 |
+
b = base_shift - m * base_seq_len
|
| 37 |
+
mu = image_seq_len * m + b
|
| 38 |
+
return mu
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
|
| 42 |
+
def retrieve_timesteps(
|
| 43 |
+
scheduler,
|
| 44 |
+
num_inference_steps: int | None = None,
|
| 45 |
+
device: str | torch.device | None = None,
|
| 46 |
+
timesteps: list[int] | None = None,
|
| 47 |
+
sigmas: list[float] | None = None,
|
| 48 |
+
**kwargs,
|
| 49 |
+
):
|
| 50 |
+
r"""
|
| 51 |
+
Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
|
| 52 |
+
custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
scheduler (`SchedulerMixin`):
|
| 56 |
+
The scheduler to get timesteps from.
|
| 57 |
+
num_inference_steps (`int`):
|
| 58 |
+
The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
|
| 59 |
+
must be `None`.
|
| 60 |
+
device (`str` or `torch.device`, *optional*):
|
| 61 |
+
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
|
| 62 |
+
timesteps (`list[int]`, *optional*):
|
| 63 |
+
Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
|
| 64 |
+
`num_inference_steps` and `sigmas` must be `None`.
|
| 65 |
+
sigmas (`list[float]`, *optional*):
|
| 66 |
+
Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
|
| 67 |
+
`num_inference_steps` and `timesteps` must be `None`.
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
`tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
|
| 71 |
+
second element is the number of inference steps.
|
| 72 |
+
"""
|
| 73 |
+
if timesteps is not None and sigmas is not None:
|
| 74 |
+
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
|
| 75 |
+
if timesteps is not None:
|
| 76 |
+
accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
|
| 77 |
+
if not accepts_timesteps:
|
| 78 |
+
raise ValueError(
|
| 79 |
+
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
|
| 80 |
+
f" timestep schedules. Please check whether you are using the correct scheduler."
|
| 81 |
+
)
|
| 82 |
+
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
|
| 83 |
+
timesteps = scheduler.timesteps
|
| 84 |
+
num_inference_steps = len(timesteps)
|
| 85 |
+
elif sigmas is not None:
|
| 86 |
+
accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
|
| 87 |
+
if not accept_sigmas:
|
| 88 |
+
raise ValueError(
|
| 89 |
+
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
|
| 90 |
+
f" sigmas schedules. Please check whether you are using the correct scheduler."
|
| 91 |
+
)
|
| 92 |
+
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
|
| 93 |
+
timesteps = scheduler.timesteps
|
| 94 |
+
num_inference_steps = len(timesteps)
|
| 95 |
+
else:
|
| 96 |
+
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
|
| 97 |
+
timesteps = scheduler.timesteps
|
| 98 |
+
return timesteps, num_inference_steps
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# Copied from diffusers.modular_pipelines.qwenimage.before_denoise.get_timesteps
|
| 102 |
+
def get_timesteps(scheduler, num_inference_steps, strength):
|
| 103 |
+
# get the original timestep using init_timestep
|
| 104 |
+
init_timestep = min(num_inference_steps * strength, num_inference_steps)
|
| 105 |
+
|
| 106 |
+
t_start = int(max(num_inference_steps - init_timestep, 0))
|
| 107 |
+
timesteps = scheduler.timesteps[t_start * scheduler.order :]
|
| 108 |
+
if hasattr(scheduler, "set_begin_index"):
|
| 109 |
+
scheduler.set_begin_index(t_start * scheduler.order)
|
| 110 |
+
|
| 111 |
+
return timesteps, num_inference_steps - t_start
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# ====================
|
| 115 |
+
# 1. PREPARE LATENTS
|
| 116 |
+
# ====================
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class Krea2PrepareLatentsStep(ModularPipelineBlocks):
|
| 120 |
+
model_name = "krea2"
|
| 121 |
+
|
| 122 |
+
@property
|
| 123 |
+
def description(self) -> str:
|
| 124 |
+
return "Prepare initial random noise for the generation process"
|
| 125 |
+
|
| 126 |
+
@property
|
| 127 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 128 |
+
return [
|
| 129 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 130 |
+
]
|
| 131 |
+
|
| 132 |
+
@property
|
| 133 |
+
def inputs(self) -> list[InputParam]:
|
| 134 |
+
return [
|
| 135 |
+
InputParam.template("latents"),
|
| 136 |
+
InputParam.template("height"),
|
| 137 |
+
InputParam.template("width"),
|
| 138 |
+
InputParam.template("num_images_per_prompt"),
|
| 139 |
+
InputParam.template("generator"),
|
| 140 |
+
InputParam.template("batch_size"),
|
| 141 |
+
InputParam.template("dtype"),
|
| 142 |
+
]
|
| 143 |
+
|
| 144 |
+
@property
|
| 145 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 146 |
+
return [
|
| 147 |
+
OutputParam(name="height", type_hint=int, description="if not set, updated to default value"),
|
| 148 |
+
OutputParam(name="width", type_hint=int, description="if not set, updated to default value"),
|
| 149 |
+
OutputParam(
|
| 150 |
+
name="latents",
|
| 151 |
+
type_hint=torch.Tensor,
|
| 152 |
+
description="The initial latents to use for the denoising process",
|
| 153 |
+
),
|
| 154 |
+
]
|
| 155 |
+
|
| 156 |
+
@staticmethod
|
| 157 |
+
def check_inputs(height, width, vae_scale_factor):
|
| 158 |
+
if height is not None and height % (vae_scale_factor * 2) != 0:
|
| 159 |
+
raise ValueError(f"Height must be divisible by {vae_scale_factor * 2} but is {height}")
|
| 160 |
+
|
| 161 |
+
if width is not None and width % (vae_scale_factor * 2) != 0:
|
| 162 |
+
raise ValueError(f"Width must be divisible by {vae_scale_factor * 2} but is {width}")
|
| 163 |
+
|
| 164 |
+
@torch.no_grad()
|
| 165 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 166 |
+
block_state = self.get_block_state(state)
|
| 167 |
+
|
| 168 |
+
self.check_inputs(
|
| 169 |
+
height=block_state.height,
|
| 170 |
+
width=block_state.width,
|
| 171 |
+
vae_scale_factor=components.vae_scale_factor,
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
device = components._execution_device
|
| 175 |
+
batch_size = block_state.batch_size * block_state.num_images_per_prompt
|
| 176 |
+
|
| 177 |
+
# we can update the height and width here since it's used to generate the initial
|
| 178 |
+
block_state.height = block_state.height or components.default_height
|
| 179 |
+
block_state.width = block_state.width or components.default_width
|
| 180 |
+
|
| 181 |
+
# VAE applies 8x compression on images but we must also account for packing which requires
|
| 182 |
+
# latent height and width to be divisible by 2.
|
| 183 |
+
latent_height = 2 * (int(block_state.height) // (components.vae_scale_factor * 2))
|
| 184 |
+
latent_width = 2 * (int(block_state.width) // (components.vae_scale_factor * 2))
|
| 185 |
+
|
| 186 |
+
shape = (batch_size, components.num_channels_latents, 1, latent_height, latent_width)
|
| 187 |
+
if isinstance(block_state.generator, list) and len(block_state.generator) != batch_size:
|
| 188 |
+
raise ValueError(
|
| 189 |
+
f"You have passed a list of generators of length {len(block_state.generator)}, but requested an effective batch"
|
| 190 |
+
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
| 191 |
+
)
|
| 192 |
+
if block_state.latents is None:
|
| 193 |
+
block_state.latents = randn_tensor(
|
| 194 |
+
shape, generator=block_state.generator, device=device, dtype=block_state.dtype
|
| 195 |
+
)
|
| 196 |
+
block_state.latents = components.pachifier.pack_latents(block_state.latents)
|
| 197 |
+
|
| 198 |
+
self.set_block_state(state, block_state)
|
| 199 |
+
return components, state
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
class Krea2PrepareLatentsWithStrengthStep(ModularPipelineBlocks):
|
| 203 |
+
model_name = "krea2"
|
| 204 |
+
|
| 205 |
+
@property
|
| 206 |
+
def description(self) -> str:
|
| 207 |
+
return "Step that adds noise to image latents for image-to-image/inpainting. Should be run after set_timesteps, prepare_latents. Both noise and image latents should already be patchified."
|
| 208 |
+
|
| 209 |
+
@property
|
| 210 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 211 |
+
return [
|
| 212 |
+
ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler),
|
| 213 |
+
]
|
| 214 |
+
|
| 215 |
+
@property
|
| 216 |
+
def inputs(self) -> list[InputParam]:
|
| 217 |
+
return [
|
| 218 |
+
InputParam(
|
| 219 |
+
name="latents",
|
| 220 |
+
required=True,
|
| 221 |
+
type_hint=torch.Tensor,
|
| 222 |
+
description="The initial random noised, can be generated in prepare latent step.",
|
| 223 |
+
),
|
| 224 |
+
InputParam.template("image_latents", note="Can be generated from vae encoder and updated in input step."),
|
| 225 |
+
InputParam(
|
| 226 |
+
name="timesteps",
|
| 227 |
+
required=True,
|
| 228 |
+
type_hint=torch.Tensor,
|
| 229 |
+
description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.",
|
| 230 |
+
),
|
| 231 |
+
]
|
| 232 |
+
|
| 233 |
+
@property
|
| 234 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 235 |
+
return [
|
| 236 |
+
OutputParam(
|
| 237 |
+
name="initial_noise",
|
| 238 |
+
type_hint=torch.Tensor,
|
| 239 |
+
description="The initial random noised used for inpainting denoising.",
|
| 240 |
+
),
|
| 241 |
+
OutputParam(
|
| 242 |
+
name="latents",
|
| 243 |
+
type_hint=torch.Tensor,
|
| 244 |
+
description="The scaled noisy latents to use for inpainting/image-to-image denoising.",
|
| 245 |
+
),
|
| 246 |
+
]
|
| 247 |
+
|
| 248 |
+
@staticmethod
|
| 249 |
+
def check_inputs(image_latents, latents):
|
| 250 |
+
if image_latents.shape[0] != latents.shape[0]:
|
| 251 |
+
raise ValueError(
|
| 252 |
+
f"`image_latents` must have have same batch size as `latents`, but got {image_latents.shape[0]} and {latents.shape[0]}"
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
if image_latents.ndim != 3:
|
| 256 |
+
raise ValueError(f"`image_latents` must have 3 dimensions (patchified), but got {image_latents.ndim}")
|
| 257 |
+
|
| 258 |
+
@torch.no_grad()
|
| 259 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 260 |
+
block_state = self.get_block_state(state)
|
| 261 |
+
|
| 262 |
+
self.check_inputs(
|
| 263 |
+
image_latents=block_state.image_latents,
|
| 264 |
+
latents=block_state.latents,
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
# prepare latent timestep
|
| 268 |
+
latent_timestep = block_state.timesteps[:1].repeat(block_state.latents.shape[0])
|
| 269 |
+
|
| 270 |
+
# make copy of initial_noise
|
| 271 |
+
block_state.initial_noise = block_state.latents
|
| 272 |
+
|
| 273 |
+
# scale noise
|
| 274 |
+
block_state.latents = components.scheduler.scale_noise(
|
| 275 |
+
block_state.image_latents, latent_timestep, block_state.latents
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
self.set_block_state(state, block_state)
|
| 279 |
+
|
| 280 |
+
return components, state
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class Krea2CreateMaskLatentsStep(ModularPipelineBlocks):
|
| 284 |
+
model_name = "krea2"
|
| 285 |
+
|
| 286 |
+
@property
|
| 287 |
+
def description(self) -> str:
|
| 288 |
+
return "Step that creates mask latents from preprocessed mask_image by interpolating to latent space."
|
| 289 |
+
|
| 290 |
+
@property
|
| 291 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 292 |
+
return [
|
| 293 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 294 |
+
]
|
| 295 |
+
|
| 296 |
+
@property
|
| 297 |
+
def inputs(self) -> list[InputParam]:
|
| 298 |
+
return [
|
| 299 |
+
InputParam(
|
| 300 |
+
name="processed_mask_image",
|
| 301 |
+
required=True,
|
| 302 |
+
type_hint=torch.Tensor,
|
| 303 |
+
description="The processed mask to use for the inpainting process.",
|
| 304 |
+
),
|
| 305 |
+
InputParam.template("height", required=True),
|
| 306 |
+
InputParam.template("width", required=True),
|
| 307 |
+
InputParam.template("dtype"),
|
| 308 |
+
]
|
| 309 |
+
|
| 310 |
+
@property
|
| 311 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 312 |
+
return [
|
| 313 |
+
OutputParam(
|
| 314 |
+
name="mask", type_hint=torch.Tensor, description="The mask to use for the inpainting process."
|
| 315 |
+
),
|
| 316 |
+
]
|
| 317 |
+
|
| 318 |
+
@torch.no_grad()
|
| 319 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 320 |
+
block_state = self.get_block_state(state)
|
| 321 |
+
|
| 322 |
+
device = components._execution_device
|
| 323 |
+
|
| 324 |
+
# VAE applies 8x compression on images but we must also account for packing which requires
|
| 325 |
+
# latent height and width to be divisible by 2.
|
| 326 |
+
|
| 327 |
+
height_latents = 2 * (int(block_state.height) // (components.vae_scale_factor * 2))
|
| 328 |
+
width_latents = 2 * (int(block_state.width) // (components.vae_scale_factor * 2))
|
| 329 |
+
|
| 330 |
+
block_state.mask = torch.nn.functional.interpolate(
|
| 331 |
+
block_state.processed_mask_image,
|
| 332 |
+
size=(height_latents, width_latents),
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
block_state.mask = block_state.mask.unsqueeze(2)
|
| 336 |
+
block_state.mask = block_state.mask.repeat(1, components.num_channels_latents, 1, 1, 1)
|
| 337 |
+
block_state.mask = block_state.mask.to(device=device, dtype=block_state.dtype)
|
| 338 |
+
|
| 339 |
+
block_state.mask = components.pachifier.pack_latents(block_state.mask)
|
| 340 |
+
|
| 341 |
+
self.set_block_state(state, block_state)
|
| 342 |
+
|
| 343 |
+
return components, state
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
# ====================
|
| 347 |
+
# 2. SET TIMESTEPS
|
| 348 |
+
# ====================
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
class Krea2SetTimestepsStep(ModularPipelineBlocks):
|
| 352 |
+
model_name = "krea2"
|
| 353 |
+
|
| 354 |
+
@property
|
| 355 |
+
def description(self) -> str:
|
| 356 |
+
return "Step that sets the scheduler's timesteps for text-to-image generation. Should be run after prepare latents step."
|
| 357 |
+
|
| 358 |
+
@property
|
| 359 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 360 |
+
return [
|
| 361 |
+
ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler),
|
| 362 |
+
]
|
| 363 |
+
|
| 364 |
+
@property
|
| 365 |
+
def inputs(self) -> list[InputParam]:
|
| 366 |
+
return [
|
| 367 |
+
InputParam.template("num_inference_steps", default=28),
|
| 368 |
+
InputParam.template("sigmas"),
|
| 369 |
+
InputParam(
|
| 370 |
+
name="mu",
|
| 371 |
+
type_hint=float,
|
| 372 |
+
description="Fixed timestep shift for the scheduler. Pass `1.15` for the few-step distilled (TDM/turbo) checkpoint; if not provided, computed from the image sequence length (base checkpoint behavior).",
|
| 373 |
+
),
|
| 374 |
+
InputParam(
|
| 375 |
+
name="latents",
|
| 376 |
+
required=True,
|
| 377 |
+
type_hint=torch.Tensor,
|
| 378 |
+
description="The initial random noised latents for the denoising process. Can be generated in prepare latents step.",
|
| 379 |
+
),
|
| 380 |
+
]
|
| 381 |
+
|
| 382 |
+
@property
|
| 383 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 384 |
+
return [
|
| 385 |
+
OutputParam(
|
| 386 |
+
name="timesteps", type_hint=torch.Tensor, description="The timesteps to use for the denoising process"
|
| 387 |
+
),
|
| 388 |
+
]
|
| 389 |
+
|
| 390 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 391 |
+
block_state = self.get_block_state(state)
|
| 392 |
+
|
| 393 |
+
device = components._execution_device
|
| 394 |
+
sigmas = (
|
| 395 |
+
np.linspace(1.0, 1 / block_state.num_inference_steps, block_state.num_inference_steps)
|
| 396 |
+
if block_state.sigmas is None
|
| 397 |
+
else block_state.sigmas
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
mu = block_state.mu
|
| 401 |
+
if mu is None:
|
| 402 |
+
mu = calculate_shift(
|
| 403 |
+
image_seq_len=block_state.latents.shape[1],
|
| 404 |
+
base_seq_len=components.scheduler.config.get("base_image_seq_len", 256),
|
| 405 |
+
max_seq_len=components.scheduler.config.get("max_image_seq_len", 6400),
|
| 406 |
+
base_shift=components.scheduler.config.get("base_shift", 0.5),
|
| 407 |
+
max_shift=components.scheduler.config.get("max_shift", 1.15),
|
| 408 |
+
)
|
| 409 |
+
block_state.timesteps, block_state.num_inference_steps = retrieve_timesteps(
|
| 410 |
+
scheduler=components.scheduler,
|
| 411 |
+
num_inference_steps=block_state.num_inference_steps,
|
| 412 |
+
device=device,
|
| 413 |
+
sigmas=sigmas,
|
| 414 |
+
mu=mu,
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
components.scheduler.set_begin_index(0)
|
| 418 |
+
|
| 419 |
+
self.set_block_state(state, block_state)
|
| 420 |
+
|
| 421 |
+
return components, state
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
class Krea2SetTimestepsWithStrengthStep(ModularPipelineBlocks):
|
| 425 |
+
model_name = "krea2"
|
| 426 |
+
|
| 427 |
+
@property
|
| 428 |
+
def description(self) -> str:
|
| 429 |
+
return "Step that sets the scheduler's timesteps for image-to-image generation, and inpainting. Should be run after prepare latents step."
|
| 430 |
+
|
| 431 |
+
@property
|
| 432 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 433 |
+
return [
|
| 434 |
+
ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler),
|
| 435 |
+
]
|
| 436 |
+
|
| 437 |
+
@property
|
| 438 |
+
def inputs(self) -> list[InputParam]:
|
| 439 |
+
return [
|
| 440 |
+
InputParam.template("num_inference_steps", default=28),
|
| 441 |
+
InputParam.template("sigmas"),
|
| 442 |
+
InputParam(
|
| 443 |
+
name="mu",
|
| 444 |
+
type_hint=float,
|
| 445 |
+
description="Fixed timestep shift for the scheduler. Pass `1.15` for the few-step distilled (TDM/turbo) checkpoint; if not provided, computed from the image sequence length (base checkpoint behavior).",
|
| 446 |
+
),
|
| 447 |
+
InputParam(
|
| 448 |
+
"latents",
|
| 449 |
+
required=True,
|
| 450 |
+
type_hint=torch.Tensor,
|
| 451 |
+
description="The latents to use for the denoising process. Can be generated in prepare latents step.",
|
| 452 |
+
),
|
| 453 |
+
InputParam.template("strength"),
|
| 454 |
+
]
|
| 455 |
+
|
| 456 |
+
@property
|
| 457 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 458 |
+
return [
|
| 459 |
+
OutputParam(
|
| 460 |
+
name="timesteps",
|
| 461 |
+
type_hint=torch.Tensor,
|
| 462 |
+
description="The timesteps to use for the denoising process.",
|
| 463 |
+
),
|
| 464 |
+
OutputParam(
|
| 465 |
+
name="num_inference_steps",
|
| 466 |
+
type_hint=int,
|
| 467 |
+
description="The number of denoising steps to perform at inference time. Updated based on strength.",
|
| 468 |
+
),
|
| 469 |
+
]
|
| 470 |
+
|
| 471 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 472 |
+
block_state = self.get_block_state(state)
|
| 473 |
+
|
| 474 |
+
device = components._execution_device
|
| 475 |
+
sigmas = (
|
| 476 |
+
np.linspace(1.0, 1 / block_state.num_inference_steps, block_state.num_inference_steps)
|
| 477 |
+
if block_state.sigmas is None
|
| 478 |
+
else block_state.sigmas
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
mu = block_state.mu
|
| 482 |
+
if mu is None:
|
| 483 |
+
mu = calculate_shift(
|
| 484 |
+
image_seq_len=block_state.latents.shape[1],
|
| 485 |
+
base_seq_len=components.scheduler.config.get("base_image_seq_len", 256),
|
| 486 |
+
max_seq_len=components.scheduler.config.get("max_image_seq_len", 6400),
|
| 487 |
+
base_shift=components.scheduler.config.get("base_shift", 0.5),
|
| 488 |
+
max_shift=components.scheduler.config.get("max_shift", 1.15),
|
| 489 |
+
)
|
| 490 |
+
block_state.timesteps, block_state.num_inference_steps = retrieve_timesteps(
|
| 491 |
+
scheduler=components.scheduler,
|
| 492 |
+
num_inference_steps=block_state.num_inference_steps,
|
| 493 |
+
device=device,
|
| 494 |
+
sigmas=sigmas,
|
| 495 |
+
mu=mu,
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
block_state.timesteps, block_state.num_inference_steps = get_timesteps(
|
| 499 |
+
scheduler=components.scheduler,
|
| 500 |
+
num_inference_steps=block_state.num_inference_steps,
|
| 501 |
+
strength=block_state.strength,
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
self.set_block_state(state, block_state)
|
| 505 |
+
|
| 506 |
+
return components, state
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
# ====================
|
| 510 |
+
# 3. OTHER INPUTS FOR DENOISER
|
| 511 |
+
# ====================
|
| 512 |
+
|
| 513 |
+
## RoPE inputs for denoiser
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
class Krea2RoPEInputsStep(ModularPipelineBlocks):
|
| 517 |
+
model_name = "krea2"
|
| 518 |
+
|
| 519 |
+
@property
|
| 520 |
+
def description(self) -> str:
|
| 521 |
+
return (
|
| 522 |
+
"Step that prepares the rotary position ids for the denoising process. Text tokens sit at the origin, "
|
| 523 |
+
"image tokens carry their `(0, h, w)` latent-grid coordinates. Should be placed after prepare_latents step."
|
| 524 |
+
)
|
| 525 |
+
|
| 526 |
+
@property
|
| 527 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 528 |
+
return [
|
| 529 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 530 |
+
]
|
| 531 |
+
|
| 532 |
+
@property
|
| 533 |
+
def inputs(self) -> list[InputParam]:
|
| 534 |
+
return [
|
| 535 |
+
InputParam.template("height", required=True),
|
| 536 |
+
InputParam.template("width", required=True),
|
| 537 |
+
InputParam.template("prompt_embeds_mask"),
|
| 538 |
+
]
|
| 539 |
+
|
| 540 |
+
@property
|
| 541 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 542 |
+
return [
|
| 543 |
+
OutputParam(
|
| 544 |
+
name="position_ids",
|
| 545 |
+
kwargs_type="denoiser_input_fields",
|
| 546 |
+
type_hint=torch.Tensor,
|
| 547 |
+
description="The rotary coordinates of shape (text_seq_len + grid_height * grid_width, 3) for the combined text-image sequence.",
|
| 548 |
+
),
|
| 549 |
+
]
|
| 550 |
+
|
| 551 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 552 |
+
block_state = self.get_block_state(state)
|
| 553 |
+
|
| 554 |
+
device = components._execution_device
|
| 555 |
+
patch_size = components.pachifier.config.patch_size
|
| 556 |
+
|
| 557 |
+
text_seq_len = block_state.prompt_embeds_mask.shape[1]
|
| 558 |
+
grid_height = block_state.height // (components.vae_scale_factor * patch_size)
|
| 559 |
+
grid_width = block_state.width // (components.vae_scale_factor * patch_size)
|
| 560 |
+
|
| 561 |
+
text_ids = torch.zeros(text_seq_len, 3, device=device)
|
| 562 |
+
image_ids = torch.zeros(grid_height, grid_width, 3, device=device)
|
| 563 |
+
image_ids[..., 1] = torch.arange(grid_height, device=device)[:, None]
|
| 564 |
+
image_ids[..., 2] = torch.arange(grid_width, device=device)[None, :]
|
| 565 |
+
image_ids = image_ids.reshape(grid_height * grid_width, 3)
|
| 566 |
+
block_state.position_ids = torch.cat([text_ids, image_ids], dim=0)
|
| 567 |
+
|
| 568 |
+
self.set_block_state(state, block_state)
|
| 569 |
+
|
| 570 |
+
return components, state
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
class Krea2EditRoPEInputsStep(ModularPipelineBlocks):
|
| 574 |
+
model_name = "krea2"
|
| 575 |
+
|
| 576 |
+
@property
|
| 577 |
+
def description(self) -> str:
|
| 578 |
+
return (
|
| 579 |
+
"Step that prepares the rotary position ids for the edit task: the `[text, image]` coordinates followed "
|
| 580 |
+
"by the reference tokens' coordinates (each reference on its own frame axis). Should be placed after "
|
| 581 |
+
"prepare_latents and the reference latents step."
|
| 582 |
+
)
|
| 583 |
+
|
| 584 |
+
@property
|
| 585 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 586 |
+
return [
|
| 587 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 588 |
+
]
|
| 589 |
+
|
| 590 |
+
@property
|
| 591 |
+
def inputs(self) -> list[InputParam]:
|
| 592 |
+
return [
|
| 593 |
+
InputParam.template("height", required=True),
|
| 594 |
+
InputParam.template("width", required=True),
|
| 595 |
+
InputParam.template("prompt_embeds_mask"),
|
| 596 |
+
InputParam(
|
| 597 |
+
"reference_position_ids",
|
| 598 |
+
required=True,
|
| 599 |
+
type_hint=torch.Tensor,
|
| 600 |
+
description="Rotary coordinates for the reference tokens. Can be generated in the reference latents step.",
|
| 601 |
+
),
|
| 602 |
+
]
|
| 603 |
+
|
| 604 |
+
@property
|
| 605 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 606 |
+
return [
|
| 607 |
+
OutputParam(
|
| 608 |
+
name="position_ids",
|
| 609 |
+
kwargs_type="denoiser_input_fields",
|
| 610 |
+
type_hint=torch.Tensor,
|
| 611 |
+
description="The rotary coordinates of shape (text_seq_len + grid_height * grid_width + ref_seq_len, 3) "
|
| 612 |
+
"for the combined text-image-reference sequence.",
|
| 613 |
+
),
|
| 614 |
+
]
|
| 615 |
+
|
| 616 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 617 |
+
block_state = self.get_block_state(state)
|
| 618 |
+
|
| 619 |
+
device = components._execution_device
|
| 620 |
+
patch_size = components.pachifier.config.patch_size
|
| 621 |
+
|
| 622 |
+
text_seq_len = block_state.prompt_embeds_mask.shape[1]
|
| 623 |
+
grid_height = block_state.height // (components.vae_scale_factor * patch_size)
|
| 624 |
+
grid_width = block_state.width // (components.vae_scale_factor * patch_size)
|
| 625 |
+
|
| 626 |
+
text_ids = torch.zeros(text_seq_len, 3, device=device)
|
| 627 |
+
image_ids = torch.zeros(grid_height, grid_width, 3, device=device)
|
| 628 |
+
image_ids[..., 1] = torch.arange(grid_height, device=device)[:, None]
|
| 629 |
+
image_ids[..., 2] = torch.arange(grid_width, device=device)[None, :]
|
| 630 |
+
image_ids = image_ids.reshape(grid_height * grid_width, 3)
|
| 631 |
+
block_state.position_ids = torch.cat([text_ids, image_ids, block_state.reference_position_ids], dim=0)
|
| 632 |
+
|
| 633 |
+
self.set_block_state(state, block_state)
|
| 634 |
+
|
| 635 |
+
return components, state
|
block.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Entry point for the Krea 2 reference-image edit modular blocks (loaded via `trust_remote_code`).
|
| 2 |
+
|
| 3 |
+
Importing this module also registers `Krea2Transformer2DModel` into the `diffusers` namespace so that
|
| 4 |
+
`ModularPipeline` component loading can resolve the transformer referenced as
|
| 5 |
+
`["diffusers", "Krea2Transformer2DModel"]` in the base `krea/Krea-2-Turbo` `model_index.json`.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from .transformer_krea2 import Krea2Transformer2DModel # noqa: F401 (registers into diffusers namespace)
|
| 9 |
+
from .modular_blocks_krea2_edit import Krea2EditBlocks
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
__all__ = ["Krea2EditBlocks", "Krea2Transformer2DModel"]
|
config.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"ModularPipelineBlocks": "block.Krea2EditBlocks"
|
| 4 |
+
}
|
| 5 |
+
}
|
decoders.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
from typing import Any
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from diffusers.configuration_utils import FrozenDict
|
| 21 |
+
from diffusers.image_processor import InpaintProcessor, VaeImageProcessor
|
| 22 |
+
from diffusers.models import AutoencoderKLQwenImage
|
| 23 |
+
from diffusers.utils import logging
|
| 24 |
+
from diffusers.modular_pipelines.modular_pipeline import ModularPipelineBlocks, PipelineState
|
| 25 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, InputParam, OutputParam
|
| 26 |
+
from .modular_pipeline import Krea2ModularPipeline, Krea2Pachifier
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
logger = logging.get_logger(__name__)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# after denoising loop (unpack latents)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Krea2AfterDenoiseStep(ModularPipelineBlocks):
|
| 36 |
+
model_name = "krea2"
|
| 37 |
+
|
| 38 |
+
@property
|
| 39 |
+
def description(self) -> str:
|
| 40 |
+
return "Step that unpack the latents from 3D tensor (batch_size, sequence_length, channels) into 5D tensor (batch_size, channels, 1, height, width)"
|
| 41 |
+
|
| 42 |
+
@property
|
| 43 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 44 |
+
components = [
|
| 45 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
return components
|
| 49 |
+
|
| 50 |
+
@property
|
| 51 |
+
def inputs(self) -> list[InputParam]:
|
| 52 |
+
return [
|
| 53 |
+
InputParam.template("height", required=True),
|
| 54 |
+
InputParam.template("width", required=True),
|
| 55 |
+
InputParam(
|
| 56 |
+
name="latents",
|
| 57 |
+
required=True,
|
| 58 |
+
type_hint=torch.Tensor,
|
| 59 |
+
description="The latents to decode, can be generated in the denoise step.",
|
| 60 |
+
),
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
@property
|
| 64 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 65 |
+
return [
|
| 66 |
+
OutputParam(
|
| 67 |
+
name="latents", type_hint=torch.Tensor, description="The denoised latents unpacked to B, C, 1, H, W"
|
| 68 |
+
),
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
@torch.no_grad()
|
| 72 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 73 |
+
block_state = self.get_block_state(state)
|
| 74 |
+
|
| 75 |
+
vae_scale_factor = components.vae_scale_factor
|
| 76 |
+
block_state.latents = components.pachifier.unpack_latents(
|
| 77 |
+
block_state.latents, block_state.height, block_state.width, vae_scale_factor=vae_scale_factor
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
self.set_block_state(state, block_state)
|
| 81 |
+
return components, state
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# decode step
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class Krea2DecoderStep(ModularPipelineBlocks):
|
| 88 |
+
model_name = "krea2"
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def description(self) -> str:
|
| 92 |
+
return "Step that decodes the latents to images"
|
| 93 |
+
|
| 94 |
+
@property
|
| 95 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 96 |
+
components = [
|
| 97 |
+
ComponentSpec("vae", AutoencoderKLQwenImage),
|
| 98 |
+
]
|
| 99 |
+
|
| 100 |
+
return components
|
| 101 |
+
|
| 102 |
+
@property
|
| 103 |
+
def inputs(self) -> list[InputParam]:
|
| 104 |
+
return [
|
| 105 |
+
InputParam(
|
| 106 |
+
name="latents",
|
| 107 |
+
required=True,
|
| 108 |
+
type_hint=torch.Tensor,
|
| 109 |
+
description="The denoised latents to decode, can be generated in the denoise step and unpacked in the after denoise step.",
|
| 110 |
+
),
|
| 111 |
+
]
|
| 112 |
+
|
| 113 |
+
@property
|
| 114 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 115 |
+
return [OutputParam.template("images", note="tensor output of the vae decoder.")]
|
| 116 |
+
|
| 117 |
+
@torch.no_grad()
|
| 118 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 119 |
+
block_state = self.get_block_state(state)
|
| 120 |
+
|
| 121 |
+
if block_state.latents.ndim == 4:
|
| 122 |
+
block_state.latents = block_state.latents.unsqueeze(dim=1)
|
| 123 |
+
elif block_state.latents.ndim != 5:
|
| 124 |
+
raise ValueError(
|
| 125 |
+
f"expect latents to be a 4D or 5D tensor but got: {block_state.latents.shape}. Please make sure the latents are unpacked before decode step."
|
| 126 |
+
)
|
| 127 |
+
block_state.latents = block_state.latents.to(components.vae.dtype)
|
| 128 |
+
|
| 129 |
+
latents_mean = (
|
| 130 |
+
torch.tensor(components.vae.config.latents_mean)
|
| 131 |
+
.view(1, components.vae.config.z_dim, 1, 1, 1)
|
| 132 |
+
.to(block_state.latents.device, block_state.latents.dtype)
|
| 133 |
+
)
|
| 134 |
+
latents_std = 1.0 / torch.tensor(components.vae.config.latents_std).view(
|
| 135 |
+
1, components.vae.config.z_dim, 1, 1, 1
|
| 136 |
+
).to(block_state.latents.device, block_state.latents.dtype)
|
| 137 |
+
block_state.latents = block_state.latents / latents_std + latents_mean
|
| 138 |
+
block_state.images = components.vae.decode(block_state.latents, return_dict=False)[0][:, :, 0]
|
| 139 |
+
|
| 140 |
+
self.set_block_state(state, block_state)
|
| 141 |
+
return components, state
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# postprocess the decoded images
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
class Krea2ProcessImagesOutputStep(ModularPipelineBlocks):
|
| 148 |
+
model_name = "krea2"
|
| 149 |
+
|
| 150 |
+
@property
|
| 151 |
+
def description(self) -> str:
|
| 152 |
+
return "postprocess the generated image"
|
| 153 |
+
|
| 154 |
+
@property
|
| 155 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 156 |
+
return [
|
| 157 |
+
ComponentSpec(
|
| 158 |
+
"image_processor",
|
| 159 |
+
VaeImageProcessor,
|
| 160 |
+
config=FrozenDict({"vae_scale_factor": 16}),
|
| 161 |
+
default_creation_method="from_config",
|
| 162 |
+
),
|
| 163 |
+
]
|
| 164 |
+
|
| 165 |
+
@property
|
| 166 |
+
def inputs(self) -> list[InputParam]:
|
| 167 |
+
return [
|
| 168 |
+
InputParam(
|
| 169 |
+
name="images",
|
| 170 |
+
required=True,
|
| 171 |
+
type_hint=torch.Tensor,
|
| 172 |
+
description="the generated image tensor from decoders step",
|
| 173 |
+
),
|
| 174 |
+
InputParam.template("output_type"),
|
| 175 |
+
]
|
| 176 |
+
|
| 177 |
+
@property
|
| 178 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 179 |
+
return [OutputParam.template("images")]
|
| 180 |
+
|
| 181 |
+
@staticmethod
|
| 182 |
+
def check_inputs(output_type):
|
| 183 |
+
if output_type not in ["pil", "np", "pt"]:
|
| 184 |
+
raise ValueError(f"Invalid output_type: {output_type}")
|
| 185 |
+
|
| 186 |
+
@torch.no_grad()
|
| 187 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState):
|
| 188 |
+
block_state = self.get_block_state(state)
|
| 189 |
+
|
| 190 |
+
self.check_inputs(block_state.output_type)
|
| 191 |
+
|
| 192 |
+
block_state.images = components.image_processor.postprocess(
|
| 193 |
+
image=block_state.images,
|
| 194 |
+
output_type=block_state.output_type,
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
self.set_block_state(state, block_state)
|
| 198 |
+
return components, state
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class Krea2InpaintProcessImagesOutputStep(ModularPipelineBlocks):
|
| 202 |
+
model_name = "krea2"
|
| 203 |
+
|
| 204 |
+
@property
|
| 205 |
+
def description(self) -> str:
|
| 206 |
+
return "postprocess the generated image, optionally apply the mask overlay to the original image."
|
| 207 |
+
|
| 208 |
+
@property
|
| 209 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 210 |
+
return [
|
| 211 |
+
ComponentSpec(
|
| 212 |
+
"image_mask_processor",
|
| 213 |
+
InpaintProcessor,
|
| 214 |
+
config=FrozenDict({"vae_scale_factor": 16}),
|
| 215 |
+
default_creation_method="from_config",
|
| 216 |
+
),
|
| 217 |
+
]
|
| 218 |
+
|
| 219 |
+
@property
|
| 220 |
+
def inputs(self) -> list[InputParam]:
|
| 221 |
+
return [
|
| 222 |
+
InputParam(
|
| 223 |
+
name="images",
|
| 224 |
+
required=True,
|
| 225 |
+
type_hint=torch.Tensor,
|
| 226 |
+
description="the generated image tensor from decoders step",
|
| 227 |
+
),
|
| 228 |
+
InputParam.template("output_type"),
|
| 229 |
+
InputParam(
|
| 230 |
+
name="mask_overlay_kwargs",
|
| 231 |
+
type_hint=dict[str, Any],
|
| 232 |
+
description="The kwargs for the postprocess step to apply the mask overlay. generated in Krea2InpaintProcessImagesInputStep.",
|
| 233 |
+
),
|
| 234 |
+
]
|
| 235 |
+
|
| 236 |
+
@property
|
| 237 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 238 |
+
return [OutputParam.template("images")]
|
| 239 |
+
|
| 240 |
+
@staticmethod
|
| 241 |
+
def check_inputs(output_type, mask_overlay_kwargs):
|
| 242 |
+
if output_type not in ["pil", "np", "pt"]:
|
| 243 |
+
raise ValueError(f"Invalid output_type: {output_type}")
|
| 244 |
+
|
| 245 |
+
if mask_overlay_kwargs and output_type != "pil":
|
| 246 |
+
raise ValueError("only support output_type 'pil' for mask overlay")
|
| 247 |
+
|
| 248 |
+
@torch.no_grad()
|
| 249 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState):
|
| 250 |
+
block_state = self.get_block_state(state)
|
| 251 |
+
|
| 252 |
+
self.check_inputs(block_state.output_type, block_state.mask_overlay_kwargs)
|
| 253 |
+
|
| 254 |
+
if block_state.mask_overlay_kwargs is None:
|
| 255 |
+
mask_overlay_kwargs = {}
|
| 256 |
+
else:
|
| 257 |
+
mask_overlay_kwargs = block_state.mask_overlay_kwargs
|
| 258 |
+
|
| 259 |
+
block_state.images = components.image_mask_processor.postprocess(
|
| 260 |
+
image=block_state.images,
|
| 261 |
+
**mask_overlay_kwargs,
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
self.set_block_state(state, block_state)
|
| 265 |
+
return components, state
|
denoise.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import inspect
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
from diffusers.configuration_utils import FrozenDict
|
| 20 |
+
from diffusers.guiders import ClassifierFreeGuidance
|
| 21 |
+
from .transformer_krea2 import Krea2Transformer2DModel
|
| 22 |
+
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
|
| 23 |
+
from diffusers.utils import logging
|
| 24 |
+
from diffusers.modular_pipelines.modular_pipeline import BlockState, LoopSequentialPipelineBlocks, ModularPipelineBlocks, PipelineState
|
| 25 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, InputParam, OutputParam
|
| 26 |
+
from .modular_pipeline import Krea2ModularPipeline
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
logger = logging.get_logger(__name__)
|
| 30 |
+
|
| 31 |
+
# ====================
|
| 32 |
+
# 1. LOOP STEPS (run at each denoising step)
|
| 33 |
+
# ====================
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# loop step:before denoiser
|
| 37 |
+
class Krea2LoopBeforeDenoiser(ModularPipelineBlocks):
|
| 38 |
+
model_name = "krea2"
|
| 39 |
+
|
| 40 |
+
@property
|
| 41 |
+
def description(self) -> str:
|
| 42 |
+
return (
|
| 43 |
+
"step within the denoising loop that prepares the latent input for the denoiser. "
|
| 44 |
+
"This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` "
|
| 45 |
+
"object (e.g. `Krea2DenoiseLoopWrapper`)"
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
@property
|
| 49 |
+
def inputs(self) -> list[InputParam]:
|
| 50 |
+
return [
|
| 51 |
+
InputParam(
|
| 52 |
+
name="latents",
|
| 53 |
+
required=True,
|
| 54 |
+
type_hint=torch.Tensor,
|
| 55 |
+
description="The initial latents to use for the denoising process. Can be generated in prepare_latent step.",
|
| 56 |
+
),
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
@torch.no_grad()
|
| 60 |
+
def __call__(self, components: Krea2ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
|
| 61 |
+
# one timestep
|
| 62 |
+
block_state.timestep = t.expand(block_state.latents.shape[0]).to(block_state.latents.dtype)
|
| 63 |
+
block_state.latent_model_input = block_state.latents
|
| 64 |
+
return components, block_state
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# loop step:before denoiser (edit) -- appends the clean reference tokens to the denoiser input each step
|
| 68 |
+
class Krea2EditLoopBeforeDenoiser(ModularPipelineBlocks):
|
| 69 |
+
model_name = "krea2"
|
| 70 |
+
|
| 71 |
+
@property
|
| 72 |
+
def description(self) -> str:
|
| 73 |
+
return (
|
| 74 |
+
"step within the denoising loop that prepares the latent input for the edit denoiser: it appends the "
|
| 75 |
+
"packed clean reference tokens after the noisy image tokens. This block should be used to compose the "
|
| 76 |
+
"`sub_blocks` attribute of a `LoopSequentialPipelineBlocks` object (e.g. `Krea2EditDenoiseStep`)."
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
@property
|
| 80 |
+
def inputs(self) -> list[InputParam]:
|
| 81 |
+
return [
|
| 82 |
+
InputParam(
|
| 83 |
+
name="latents",
|
| 84 |
+
required=True,
|
| 85 |
+
type_hint=torch.Tensor,
|
| 86 |
+
description="The initial latents to use for the denoising process. Can be generated in prepare_latent step.",
|
| 87 |
+
),
|
| 88 |
+
InputParam(
|
| 89 |
+
name="reference_latents",
|
| 90 |
+
required=True,
|
| 91 |
+
type_hint=torch.Tensor,
|
| 92 |
+
description="Packed clean reference tokens to append to the denoiser sequence. Can be generated in the reference latents step.",
|
| 93 |
+
),
|
| 94 |
+
]
|
| 95 |
+
|
| 96 |
+
@torch.no_grad()
|
| 97 |
+
def __call__(self, components: Krea2ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
|
| 98 |
+
block_state.timestep = t.expand(block_state.latents.shape[0]).to(block_state.latents.dtype)
|
| 99 |
+
# Reference tokens are shared across the batch; expand and append them after the noisy image tokens.
|
| 100 |
+
reference_latents = block_state.reference_latents.expand(block_state.latents.shape[0], -1, -1)
|
| 101 |
+
block_state.latent_model_input = torch.cat(
|
| 102 |
+
[block_state.latents, reference_latents.to(block_state.latents.dtype)], dim=1
|
| 103 |
+
)
|
| 104 |
+
return components, block_state
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# loop step:denoiser
|
| 108 |
+
class Krea2LoopDenoiser(ModularPipelineBlocks):
|
| 109 |
+
model_name = "krea2"
|
| 110 |
+
|
| 111 |
+
@property
|
| 112 |
+
def description(self) -> str:
|
| 113 |
+
return (
|
| 114 |
+
"step within the denoising loop that denoise the latent input for the denoiser. "
|
| 115 |
+
"This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` "
|
| 116 |
+
"object (e.g. `Krea2DenoiseLoopWrapper`)"
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
@property
|
| 120 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 121 |
+
return [
|
| 122 |
+
ComponentSpec(
|
| 123 |
+
"guider",
|
| 124 |
+
ClassifierFreeGuidance,
|
| 125 |
+
config=FrozenDict({"guidance_scale": 4.5, "use_original_formulation": True}),
|
| 126 |
+
default_creation_method="from_config",
|
| 127 |
+
),
|
| 128 |
+
ComponentSpec("transformer", Krea2Transformer2DModel),
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
@property
|
| 132 |
+
def inputs(self) -> list[InputParam]:
|
| 133 |
+
return [
|
| 134 |
+
InputParam.template("denoiser_input_fields"),
|
| 135 |
+
InputParam(
|
| 136 |
+
"position_ids",
|
| 137 |
+
required=True,
|
| 138 |
+
type_hint=torch.Tensor,
|
| 139 |
+
description="The rotary coordinates for the combined text-image sequence. Can be generated in prepare_rope_inputs step.",
|
| 140 |
+
),
|
| 141 |
+
]
|
| 142 |
+
|
| 143 |
+
@torch.no_grad()
|
| 144 |
+
def __call__(self, components: Krea2ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
|
| 145 |
+
guider_inputs = {
|
| 146 |
+
"encoder_hidden_states": (
|
| 147 |
+
getattr(block_state, "prompt_embeds", None),
|
| 148 |
+
getattr(block_state, "negative_prompt_embeds", None),
|
| 149 |
+
),
|
| 150 |
+
"encoder_attention_mask": (
|
| 151 |
+
getattr(block_state, "prompt_embeds_mask", None),
|
| 152 |
+
getattr(block_state, "negative_prompt_embeds_mask", None),
|
| 153 |
+
),
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
transformer_args = set(inspect.signature(components.transformer.forward).parameters.keys())
|
| 157 |
+
additional_cond_kwargs = {}
|
| 158 |
+
for field_name, field_value in block_state.denoiser_input_fields.items():
|
| 159 |
+
if field_name in transformer_args and field_name not in guider_inputs:
|
| 160 |
+
additional_cond_kwargs[field_name] = field_value
|
| 161 |
+
block_state.additional_cond_kwargs.update(additional_cond_kwargs)
|
| 162 |
+
|
| 163 |
+
components.guider.set_state(step=i, num_inference_steps=block_state.num_inference_steps, timestep=t)
|
| 164 |
+
guider_state = components.guider.prepare_inputs(guider_inputs)
|
| 165 |
+
|
| 166 |
+
for guider_state_batch in guider_state:
|
| 167 |
+
components.guider.prepare_models(components.transformer)
|
| 168 |
+
cond_kwargs = {input_name: getattr(guider_state_batch, input_name) for input_name in guider_inputs.keys()}
|
| 169 |
+
|
| 170 |
+
guider_state_batch.noise_pred = components.transformer(
|
| 171 |
+
hidden_states=block_state.latent_model_input,
|
| 172 |
+
timestep=block_state.timestep / 1000,
|
| 173 |
+
return_dict=False,
|
| 174 |
+
**cond_kwargs,
|
| 175 |
+
**block_state.additional_cond_kwargs,
|
| 176 |
+
)[0]
|
| 177 |
+
|
| 178 |
+
components.guider.cleanup_models(components.transformer)
|
| 179 |
+
|
| 180 |
+
guider_output = components.guider(guider_state)
|
| 181 |
+
block_state.noise_pred = guider_output.pred
|
| 182 |
+
|
| 183 |
+
return components, block_state
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# loop step:after denoiser
|
| 187 |
+
class Krea2LoopAfterDenoiser(ModularPipelineBlocks):
|
| 188 |
+
model_name = "krea2"
|
| 189 |
+
|
| 190 |
+
@property
|
| 191 |
+
def description(self) -> str:
|
| 192 |
+
return (
|
| 193 |
+
"step within the denoising loop that updates the latents. "
|
| 194 |
+
"This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` "
|
| 195 |
+
"object (e.g. `Krea2DenoiseLoopWrapper`)"
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
@property
|
| 199 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 200 |
+
return [
|
| 201 |
+
ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler),
|
| 202 |
+
]
|
| 203 |
+
|
| 204 |
+
@property
|
| 205 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 206 |
+
return [
|
| 207 |
+
OutputParam.template("latents"),
|
| 208 |
+
]
|
| 209 |
+
|
| 210 |
+
@torch.no_grad()
|
| 211 |
+
def __call__(self, components: Krea2ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
|
| 212 |
+
latents_dtype = block_state.latents.dtype
|
| 213 |
+
block_state.latents = components.scheduler.step(
|
| 214 |
+
block_state.noise_pred,
|
| 215 |
+
t,
|
| 216 |
+
block_state.latents,
|
| 217 |
+
return_dict=False,
|
| 218 |
+
)[0]
|
| 219 |
+
|
| 220 |
+
if block_state.latents.dtype != latents_dtype:
|
| 221 |
+
if torch.backends.mps.is_available():
|
| 222 |
+
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
|
| 223 |
+
block_state.latents = block_state.latents.to(latents_dtype)
|
| 224 |
+
|
| 225 |
+
return components, block_state
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class Krea2LoopAfterDenoiserInpaint(ModularPipelineBlocks):
|
| 229 |
+
model_name = "krea2"
|
| 230 |
+
|
| 231 |
+
@property
|
| 232 |
+
def description(self) -> str:
|
| 233 |
+
return (
|
| 234 |
+
"step within the denoising loop that updates the latents using mask and image_latents for inpainting. "
|
| 235 |
+
"This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` "
|
| 236 |
+
"object (e.g. `Krea2DenoiseLoopWrapper`)"
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
@property
|
| 240 |
+
def inputs(self) -> list[InputParam]:
|
| 241 |
+
return [
|
| 242 |
+
InputParam(
|
| 243 |
+
"mask",
|
| 244 |
+
required=True,
|
| 245 |
+
type_hint=torch.Tensor,
|
| 246 |
+
description="The mask to use for the inpainting process. Can be generated in inpaint prepare latents step.",
|
| 247 |
+
),
|
| 248 |
+
InputParam.template("image_latents"),
|
| 249 |
+
InputParam(
|
| 250 |
+
"initial_noise",
|
| 251 |
+
required=True,
|
| 252 |
+
type_hint=torch.Tensor,
|
| 253 |
+
description="The initial noise to use for the inpainting process. Can be generated in inpaint prepare latents step.",
|
| 254 |
+
),
|
| 255 |
+
]
|
| 256 |
+
|
| 257 |
+
@property
|
| 258 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 259 |
+
return [
|
| 260 |
+
OutputParam.template("latents"),
|
| 261 |
+
]
|
| 262 |
+
|
| 263 |
+
@torch.no_grad()
|
| 264 |
+
def __call__(self, components: Krea2ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
|
| 265 |
+
block_state.init_latents_proper = block_state.image_latents
|
| 266 |
+
if i < len(block_state.timesteps) - 1:
|
| 267 |
+
block_state.noise_timestep = block_state.timesteps[i + 1]
|
| 268 |
+
block_state.init_latents_proper = components.scheduler.scale_noise(
|
| 269 |
+
block_state.init_latents_proper, torch.tensor([block_state.noise_timestep]), block_state.initial_noise
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
block_state.latents = (
|
| 273 |
+
1 - block_state.mask
|
| 274 |
+
) * block_state.init_latents_proper + block_state.mask * block_state.latents
|
| 275 |
+
|
| 276 |
+
return components, block_state
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
# ====================
|
| 280 |
+
# 2. DENOISE LOOP WRAPPER: define the denoising loop logic
|
| 281 |
+
# ====================
|
| 282 |
+
class Krea2DenoiseLoopWrapper(LoopSequentialPipelineBlocks):
|
| 283 |
+
model_name = "krea2"
|
| 284 |
+
|
| 285 |
+
@property
|
| 286 |
+
def description(self) -> str:
|
| 287 |
+
return (
|
| 288 |
+
"Pipeline block that iteratively denoise the latents over `timesteps`. "
|
| 289 |
+
"The specific steps with each iteration can be customized with `sub_blocks` attributes"
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
@property
|
| 293 |
+
def loop_expected_components(self) -> list[ComponentSpec]:
|
| 294 |
+
return [
|
| 295 |
+
ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler),
|
| 296 |
+
]
|
| 297 |
+
|
| 298 |
+
@property
|
| 299 |
+
def loop_inputs(self) -> list[InputParam]:
|
| 300 |
+
return [
|
| 301 |
+
InputParam(
|
| 302 |
+
name="timesteps",
|
| 303 |
+
required=True,
|
| 304 |
+
type_hint=torch.Tensor,
|
| 305 |
+
description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.",
|
| 306 |
+
),
|
| 307 |
+
InputParam.template("num_inference_steps", required=True),
|
| 308 |
+
]
|
| 309 |
+
|
| 310 |
+
@torch.no_grad()
|
| 311 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 312 |
+
block_state = self.get_block_state(state)
|
| 313 |
+
|
| 314 |
+
block_state.num_warmup_steps = max(
|
| 315 |
+
len(block_state.timesteps) - block_state.num_inference_steps * components.scheduler.order, 0
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
block_state.additional_cond_kwargs = {}
|
| 319 |
+
|
| 320 |
+
with self.progress_bar(total=block_state.num_inference_steps) as progress_bar:
|
| 321 |
+
for i, t in enumerate(block_state.timesteps):
|
| 322 |
+
components, block_state = self.loop_step(components, block_state, i=i, t=t)
|
| 323 |
+
if i == len(block_state.timesteps) - 1 or (
|
| 324 |
+
(i + 1) > block_state.num_warmup_steps and (i + 1) % components.scheduler.order == 0
|
| 325 |
+
):
|
| 326 |
+
progress_bar.update()
|
| 327 |
+
|
| 328 |
+
self.set_block_state(state, block_state)
|
| 329 |
+
|
| 330 |
+
return components, state
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
# ====================
|
| 334 |
+
# 3. DENOISE STEPS: compose the denoising loop with loop wrapper + loop steps
|
| 335 |
+
# ====================
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
# Krea 2 (text2image, image2image)
|
| 339 |
+
class Krea2DenoiseStep(Krea2DenoiseLoopWrapper):
|
| 340 |
+
model_name = "krea2"
|
| 341 |
+
|
| 342 |
+
block_classes = [
|
| 343 |
+
Krea2LoopBeforeDenoiser,
|
| 344 |
+
Krea2LoopDenoiser,
|
| 345 |
+
Krea2LoopAfterDenoiser,
|
| 346 |
+
]
|
| 347 |
+
block_names = ["before_denoiser", "denoiser", "after_denoiser"]
|
| 348 |
+
|
| 349 |
+
@property
|
| 350 |
+
def description(self) -> str:
|
| 351 |
+
return (
|
| 352 |
+
"Denoise step that iteratively denoise the latents.\n"
|
| 353 |
+
"Its loop logic is defined in `Krea2DenoiseLoopWrapper.__call__` method\n"
|
| 354 |
+
"At each iteration, it runs blocks defined in `sub_blocks` sequencially:\n"
|
| 355 |
+
" - `Krea2LoopBeforeDenoiser`\n"
|
| 356 |
+
" - `Krea2LoopDenoiser`\n"
|
| 357 |
+
" - `Krea2LoopAfterDenoiser`\n"
|
| 358 |
+
"This block supports text2image and image2image tasks for Krea 2."
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
# Krea 2 (inpainting)
|
| 363 |
+
class Krea2InpaintDenoiseStep(Krea2DenoiseLoopWrapper):
|
| 364 |
+
model_name = "krea2"
|
| 365 |
+
block_classes = [
|
| 366 |
+
Krea2LoopBeforeDenoiser,
|
| 367 |
+
Krea2LoopDenoiser,
|
| 368 |
+
Krea2LoopAfterDenoiser,
|
| 369 |
+
Krea2LoopAfterDenoiserInpaint,
|
| 370 |
+
]
|
| 371 |
+
block_names = ["before_denoiser", "denoiser", "after_denoiser", "after_denoiser_inpaint"]
|
| 372 |
+
|
| 373 |
+
@property
|
| 374 |
+
def description(self) -> str:
|
| 375 |
+
return (
|
| 376 |
+
"Denoise step that iteratively denoise the latents. \n"
|
| 377 |
+
"Its loop logic is defined in `Krea2DenoiseLoopWrapper.__call__` method \n"
|
| 378 |
+
"At each iteration, it runs blocks defined in `sub_blocks` sequencially:\n"
|
| 379 |
+
" - `Krea2LoopBeforeDenoiser`\n"
|
| 380 |
+
" - `Krea2LoopDenoiser`\n"
|
| 381 |
+
" - `Krea2LoopAfterDenoiser`\n"
|
| 382 |
+
" - `Krea2LoopAfterDenoiserInpaint`\n"
|
| 383 |
+
"This block supports inpainting tasks for Krea 2."
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
# Krea 2 (reference-image edit)
|
| 388 |
+
class Krea2EditDenoiseStep(Krea2DenoiseLoopWrapper):
|
| 389 |
+
model_name = "krea2"
|
| 390 |
+
block_classes = [
|
| 391 |
+
Krea2EditLoopBeforeDenoiser,
|
| 392 |
+
Krea2LoopDenoiser,
|
| 393 |
+
Krea2LoopAfterDenoiser,
|
| 394 |
+
]
|
| 395 |
+
block_names = ["before_denoiser", "denoiser", "after_denoiser"]
|
| 396 |
+
|
| 397 |
+
@property
|
| 398 |
+
def description(self) -> str:
|
| 399 |
+
return (
|
| 400 |
+
"Denoise step that iteratively denoise the latents for the reference-image edit task.\n"
|
| 401 |
+
"Its loop logic is defined in `Krea2DenoiseLoopWrapper.__call__` method\n"
|
| 402 |
+
"At each iteration, it runs blocks defined in `sub_blocks` sequencially:\n"
|
| 403 |
+
" - `Krea2EditLoopBeforeDenoiser` (appends the clean reference tokens)\n"
|
| 404 |
+
" - `Krea2LoopDenoiser`\n"
|
| 405 |
+
" - `Krea2LoopAfterDenoiser`\n"
|
| 406 |
+
"This block supports reference-image (edit) generation for Krea 2."
|
| 407 |
+
)
|
encoders.py
ADDED
|
@@ -0,0 +1,844 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Text and VAE encoder blocks for Krea 2 pipelines.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import math
|
| 20 |
+
|
| 21 |
+
import numpy as np
|
| 22 |
+
import PIL.Image
|
| 23 |
+
import torch
|
| 24 |
+
import torch.nn.functional as F
|
| 25 |
+
from transformers import Qwen2Tokenizer, Qwen3VLModel, Qwen3VLProcessor
|
| 26 |
+
|
| 27 |
+
from diffusers.configuration_utils import FrozenDict
|
| 28 |
+
from diffusers.guiders import ClassifierFreeGuidance
|
| 29 |
+
from diffusers.image_processor import InpaintProcessor, VaeImageProcessor
|
| 30 |
+
from diffusers.models import AutoencoderKLQwenImage
|
| 31 |
+
from diffusers.utils import logging
|
| 32 |
+
from diffusers.modular_pipelines.modular_pipeline import ModularPipelineBlocks, PipelineState
|
| 33 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, InputParam, OutputParam
|
| 34 |
+
from .modular_pipeline import Krea2ModularPipeline, Krea2Pachifier
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
logger = logging.get_logger(__name__)
|
| 38 |
+
|
| 39 |
+
# Text conditioning uses the Qwen-Image chat template, tokenized as a fixed-length block: the prompt is padded to a
|
| 40 |
+
# fixed length first and the assistant suffix is appended after the padding (matching how the model was sampled at
|
| 41 |
+
# training time). The first `KREA2_PROMPT_TEMPLATE_START_IDX` (system prefix) tokens are dropped from the encoder
|
| 42 |
+
# outputs.
|
| 43 |
+
KREA2_PROMPT_TEMPLATE_PREFIX = (
|
| 44 |
+
"<|im_start|>system\nDescribe the image by detailing the color, shape, size, texture, quantity, text, "
|
| 45 |
+
"spatial relationships of the objects and background:<|im_end|>\n<|im_start|>user\n"
|
| 46 |
+
)
|
| 47 |
+
KREA2_PROMPT_TEMPLATE_SUFFIX = "<|im_end|>\n<|im_start|>assistant\n"
|
| 48 |
+
KREA2_PROMPT_TEMPLATE_START_IDX = 34
|
| 49 |
+
KREA2_PROMPT_TEMPLATE_NUM_SUFFIX_TOKENS = 5
|
| 50 |
+
|
| 51 |
+
# Indices into the text encoder's `hidden_states` tuple (0 is the embedding output) whose states are stacked per token
|
| 52 |
+
# and fed to the transformer's text fusion stage. These are the Krea 2 (Qwen3-VL-4B) taps; must have
|
| 53 |
+
# `transformer.config.num_text_layers` entries.
|
| 54 |
+
KREA2_TEXT_ENCODER_SELECT_LAYERS = (2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def get_krea2_prompt_embeds(
|
| 58 |
+
text_encoder,
|
| 59 |
+
tokenizer,
|
| 60 |
+
prompt: str | list[str],
|
| 61 |
+
text_encoder_select_layers: tuple[int, ...] = KREA2_TEXT_ENCODER_SELECT_LAYERS,
|
| 62 |
+
prompt_template_prefix: str = KREA2_PROMPT_TEMPLATE_PREFIX,
|
| 63 |
+
prompt_template_suffix: str = KREA2_PROMPT_TEMPLATE_SUFFIX,
|
| 64 |
+
prompt_template_start_idx: int = KREA2_PROMPT_TEMPLATE_START_IDX,
|
| 65 |
+
prompt_template_num_suffix_tokens: int = KREA2_PROMPT_TEMPLATE_NUM_SUFFIX_TOKENS,
|
| 66 |
+
max_sequence_length: int = 512,
|
| 67 |
+
device: torch.device | None = None,
|
| 68 |
+
):
|
| 69 |
+
"""Tokenize `prompt` into the fixed-length Krea 2 layout and tap the selected encoder hidden states.
|
| 70 |
+
|
| 71 |
+
Returns a `(prompt_embeds, prompt_embeds_mask)` tuple of shapes `(batch_size, text_seq_len, num_text_layers,
|
| 72 |
+
text_hidden_dim)` and `(batch_size, text_seq_len)` (bool).
|
| 73 |
+
"""
|
| 74 |
+
prompt = [prompt] if isinstance(prompt, str) else prompt
|
| 75 |
+
prefix_idx = prompt_template_start_idx
|
| 76 |
+
text = [prompt_template_prefix + e for e in prompt]
|
| 77 |
+
text_tokens = tokenizer(
|
| 78 |
+
text,
|
| 79 |
+
truncation=True,
|
| 80 |
+
padding="max_length",
|
| 81 |
+
max_length=max_sequence_length + prefix_idx - prompt_template_num_suffix_tokens,
|
| 82 |
+
return_tensors="pt",
|
| 83 |
+
).to(device)
|
| 84 |
+
suffix_tokens = tokenizer([prompt_template_suffix] * len(text), return_tensors="pt").to(device)
|
| 85 |
+
|
| 86 |
+
input_ids = torch.cat([text_tokens.input_ids, suffix_tokens.input_ids], dim=1)
|
| 87 |
+
attention_mask = torch.cat([text_tokens.attention_mask, suffix_tokens.attention_mask], dim=1).bool()
|
| 88 |
+
|
| 89 |
+
# Krea 2 pads in the middle of the template (`[prefix | prompt | PAD | suffix]`), so the suffix tokens sit
|
| 90 |
+
# downstream of the padding. The text features must use positions that count only real tokens (padding does
|
| 91 |
+
# not consume a position) to match how the model was trained; otherwise the suffix gets a shifted mRoPE phase.
|
| 92 |
+
# `Qwen3VLModel`'s default raw-index positions would place the suffix at ~max_length instead. Build the
|
| 93 |
+
# cumulative-valid-token positions explicitly and broadcast across the 3 mRoPE axes (T/H/W are equal for text).
|
| 94 |
+
position_ids = (attention_mask.long().cumsum(dim=-1) - 1).clamp(min=0)
|
| 95 |
+
position_ids = position_ids.unsqueeze(0).expand(3, -1, -1)
|
| 96 |
+
|
| 97 |
+
outputs = text_encoder(
|
| 98 |
+
input_ids=input_ids,
|
| 99 |
+
attention_mask=attention_mask,
|
| 100 |
+
position_ids=position_ids,
|
| 101 |
+
output_hidden_states=True,
|
| 102 |
+
)
|
| 103 |
+
hidden_states = torch.stack([outputs.hidden_states[i] for i in text_encoder_select_layers], dim=2)
|
| 104 |
+
|
| 105 |
+
prompt_embeds = hidden_states[:, prefix_idx:]
|
| 106 |
+
prompt_embeds_mask = attention_mask[:, prefix_idx:]
|
| 107 |
+
return prompt_embeds, prompt_embeds_mask
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
# Reference images ride in the user message ahead of the prompt through named vision placeholders; the Qwen3-VL
|
| 111 |
+
# processor expands each `<|image_pad|>` into the image's token grid so the text conditioning "sees" the references.
|
| 112 |
+
KREA2_EDIT_IMAGE_PLACEHOLDER = "Picture {}: <|vision_start|><|image_pad|><|vision_end|>"
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def to_chw_tensor(image) -> torch.Tensor:
|
| 116 |
+
"""Convert a PIL image / numpy array / CHW tensor to a float CHW tensor in [0, 1]."""
|
| 117 |
+
if isinstance(image, torch.Tensor):
|
| 118 |
+
t = image.squeeze(0) if image.ndim == 4 else image
|
| 119 |
+
t = t.float()
|
| 120 |
+
if t.min() < 0: # assume [-1, 1]
|
| 121 |
+
t = (t + 1.0) / 2.0
|
| 122 |
+
return t.clamp(0, 1)
|
| 123 |
+
if isinstance(image, np.ndarray):
|
| 124 |
+
image = PIL.Image.fromarray(image)
|
| 125 |
+
image = image.convert("RGB")
|
| 126 |
+
arr = np.asarray(image).astype(np.float32) / 255.0
|
| 127 |
+
return torch.from_numpy(arr).permute(2, 0, 1)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def prep_vl_images(images: list[torch.Tensor], max_pixels: int) -> list[torch.Tensor]:
|
| 131 |
+
"""Resize reference images for the Qwen3-VL pass: aspect-preserving downscale (never upscaled) to fit
|
| 132 |
+
`max_pixels` total area. The MLLM only needs a coarse view of the references; high-res detail flows through the
|
| 133 |
+
VAE reference latents."""
|
| 134 |
+
prepped = []
|
| 135 |
+
for img in images:
|
| 136 |
+
h, w = img.shape[1], img.shape[2]
|
| 137 |
+
scale = min(1.0, math.sqrt(max_pixels / (h * w)))
|
| 138 |
+
nh, nw = max(round(h * scale), 28), max(round(w * scale), 28)
|
| 139 |
+
if (nh, nw) != (h, w):
|
| 140 |
+
img = F.interpolate(img.unsqueeze(0).float(), size=(nh, nw), mode="bicubic", antialias=True).squeeze(0)
|
| 141 |
+
img = img.clamp(0, 1)
|
| 142 |
+
prepped.append(img.float())
|
| 143 |
+
return prepped
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def get_krea2_edit_prompt_embeds(
|
| 147 |
+
text_encoder,
|
| 148 |
+
tokenizer,
|
| 149 |
+
processor,
|
| 150 |
+
prompt: str | list[str],
|
| 151 |
+
images: list[torch.Tensor] | None = None,
|
| 152 |
+
text_encoder_select_layers: tuple[int, ...] = KREA2_TEXT_ENCODER_SELECT_LAYERS,
|
| 153 |
+
prompt_template_prefix: str = KREA2_PROMPT_TEMPLATE_PREFIX,
|
| 154 |
+
prompt_template_suffix: str = KREA2_PROMPT_TEMPLATE_SUFFIX,
|
| 155 |
+
prompt_template_start_idx: int = KREA2_PROMPT_TEMPLATE_START_IDX,
|
| 156 |
+
max_sequence_length: int = 512,
|
| 157 |
+
device: torch.device | None = None,
|
| 158 |
+
):
|
| 159 |
+
"""Encode prompts for the edit task, embedding reference images (a coarse VL view of each) into the text
|
| 160 |
+
conditioning through the Qwen3-VL vision tower.
|
| 161 |
+
|
| 162 |
+
Unlike `get_krea2_prompt_embeds`, prompts are tokenized at their natural (unpadded) length: the processor
|
| 163 |
+
expands each `<|image_pad|>` placeholder into a run of vision tokens that must stay intact, so no truncation or
|
| 164 |
+
fixed padding is applied before encoding. All prompts share the same `images`. Returns a
|
| 165 |
+
`(prompt_embeds, prompt_embeds_mask)` tuple of shapes `(batch_size, text_seq_len, num_text_layers,
|
| 166 |
+
text_hidden_dim)` and `(batch_size, text_seq_len)` (bool), right-padded across the batch.
|
| 167 |
+
"""
|
| 168 |
+
prompt = [prompt] if isinstance(prompt, str) else prompt
|
| 169 |
+
prefix_idx = prompt_template_start_idx
|
| 170 |
+
|
| 171 |
+
# The suffix is tokenized separately so it lands after the (image +) prompt tokens.
|
| 172 |
+
suffix_inputs = tokenizer([prompt_template_suffix], return_tensors="pt").to(device)
|
| 173 |
+
suffix_ids = suffix_inputs["input_ids"]
|
| 174 |
+
suffix_mask = suffix_inputs["attention_mask"].bool()
|
| 175 |
+
|
| 176 |
+
image_prompt = ""
|
| 177 |
+
if images:
|
| 178 |
+
image_prompt = "".join(KREA2_EDIT_IMAGE_PLACEHOLDER.format(i + 1) for i in range(len(images)))
|
| 179 |
+
|
| 180 |
+
features = []
|
| 181 |
+
for p in prompt:
|
| 182 |
+
text = prompt_template_prefix + image_prompt + p
|
| 183 |
+
extra_inputs = {}
|
| 184 |
+
if images:
|
| 185 |
+
# No truncation: the expanded image-pad runs must stay intact.
|
| 186 |
+
inputs = processor(text=[text], images=list(images), return_tensors="pt", do_rescale=False).to(device)
|
| 187 |
+
for k, v in inputs.items():
|
| 188 |
+
if k in ("input_ids", "attention_mask"):
|
| 189 |
+
continue
|
| 190 |
+
if isinstance(v, torch.Tensor) and v.is_floating_point():
|
| 191 |
+
v = v.to(text_encoder.dtype)
|
| 192 |
+
extra_inputs[k] = v
|
| 193 |
+
else:
|
| 194 |
+
inputs = tokenizer(
|
| 195 |
+
[text], truncation=True, max_length=max_sequence_length + prefix_idx, return_tensors="pt"
|
| 196 |
+
).to(device)
|
| 197 |
+
|
| 198 |
+
input_ids = torch.cat([inputs["input_ids"], suffix_ids], dim=1)
|
| 199 |
+
attention_mask = torch.cat([inputs["attention_mask"].bool(), suffix_mask], dim=1)
|
| 200 |
+
|
| 201 |
+
# mm_token_type_ids (used for M-RoPE) must cover the appended suffix tokens too; they are plain text -> type 0.
|
| 202 |
+
if "mm_token_type_ids" in extra_inputs:
|
| 203 |
+
tt = extra_inputs["mm_token_type_ids"]
|
| 204 |
+
extra_inputs["mm_token_type_ids"] = torch.cat([tt, torch.zeros_like(suffix_ids, dtype=tt.dtype)], dim=1)
|
| 205 |
+
|
| 206 |
+
outputs = text_encoder(
|
| 207 |
+
input_ids=input_ids, attention_mask=attention_mask, output_hidden_states=True, **extra_inputs
|
| 208 |
+
)
|
| 209 |
+
hidden_states = torch.stack([outputs.hidden_states[i] for i in text_encoder_select_layers], dim=2)
|
| 210 |
+
# Drop the system-prefix tokens; what remains is (image +) prompt + suffix.
|
| 211 |
+
features.append(hidden_states[0, prefix_idx:])
|
| 212 |
+
|
| 213 |
+
max_len = max(f.shape[0] for f in features)
|
| 214 |
+
prompt_embeds = features[0].new_zeros(len(features), max_len, *features[0].shape[1:])
|
| 215 |
+
prompt_embeds_mask = torch.zeros(len(features), max_len, dtype=torch.bool, device=device)
|
| 216 |
+
for i, f in enumerate(features):
|
| 217 |
+
prompt_embeds[i, : f.shape[0]] = f
|
| 218 |
+
prompt_embeds_mask[i, : f.shape[0]] = True
|
| 219 |
+
|
| 220 |
+
return prompt_embeds, prompt_embeds_mask
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
|
| 224 |
+
def retrieve_latents(
|
| 225 |
+
encoder_output: torch.Tensor, generator: torch.Generator | None = None, sample_mode: str = "sample"
|
| 226 |
+
):
|
| 227 |
+
if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
|
| 228 |
+
return encoder_output.latent_dist.sample(generator)
|
| 229 |
+
elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
|
| 230 |
+
return encoder_output.latent_dist.mode()
|
| 231 |
+
elif hasattr(encoder_output, "latents"):
|
| 232 |
+
return encoder_output.latents
|
| 233 |
+
else:
|
| 234 |
+
raise AttributeError("Could not access latents of provided encoder_output")
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
# Modified from diffusers.modular_pipelines.qwenimage.encoders.encode_vae_image
|
| 238 |
+
def encode_vae_image(
|
| 239 |
+
image: torch.Tensor,
|
| 240 |
+
vae: AutoencoderKLQwenImage,
|
| 241 |
+
generator: torch.Generator,
|
| 242 |
+
device: torch.device,
|
| 243 |
+
dtype: torch.dtype,
|
| 244 |
+
latent_channels: int = 16,
|
| 245 |
+
sample_mode: str = "argmax",
|
| 246 |
+
):
|
| 247 |
+
if not isinstance(image, torch.Tensor):
|
| 248 |
+
raise ValueError(f"Expected image to be a tensor, got {type(image)}.")
|
| 249 |
+
|
| 250 |
+
# preprocessed image should be a 4D tensor: batch_size, num_channels, height, width
|
| 251 |
+
if image.dim() == 4:
|
| 252 |
+
image = image.unsqueeze(2)
|
| 253 |
+
elif image.dim() != 5:
|
| 254 |
+
raise ValueError(f"Expected image dims 4 or 5, got {image.dim()}.")
|
| 255 |
+
|
| 256 |
+
image = image.to(device=device, dtype=dtype)
|
| 257 |
+
|
| 258 |
+
if isinstance(generator, list):
|
| 259 |
+
image_latents = [
|
| 260 |
+
retrieve_latents(vae.encode(image[i : i + 1]), generator=generator[i], sample_mode=sample_mode)
|
| 261 |
+
for i in range(image.shape[0])
|
| 262 |
+
]
|
| 263 |
+
image_latents = torch.cat(image_latents, dim=0)
|
| 264 |
+
else:
|
| 265 |
+
image_latents = retrieve_latents(vae.encode(image), generator=generator, sample_mode=sample_mode)
|
| 266 |
+
latents_mean = (
|
| 267 |
+
torch.tensor(vae.config.latents_mean)
|
| 268 |
+
.view(1, latent_channels, 1, 1, 1)
|
| 269 |
+
.to(image_latents.device, image_latents.dtype)
|
| 270 |
+
)
|
| 271 |
+
latents_std = (
|
| 272 |
+
torch.tensor(vae.config.latents_std)
|
| 273 |
+
.view(1, latent_channels, 1, 1, 1)
|
| 274 |
+
.to(image_latents.device, image_latents.dtype)
|
| 275 |
+
)
|
| 276 |
+
image_latents = (image_latents - latents_mean) / latents_std
|
| 277 |
+
|
| 278 |
+
return image_latents
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def encode_reference_latents(
|
| 282 |
+
images: list[torch.Tensor],
|
| 283 |
+
vae: AutoencoderKLQwenImage,
|
| 284 |
+
max_pixels: int,
|
| 285 |
+
generator: torch.Generator | None,
|
| 286 |
+
device: torch.device,
|
| 287 |
+
vae_scale_factor: int,
|
| 288 |
+
patch_size: int,
|
| 289 |
+
latent_channels: int = 16,
|
| 290 |
+
) -> list[torch.Tensor]:
|
| 291 |
+
"""Encode `[0, 1]` CHW reference images to normalized VAE latents, one `(C, h, w)` tensor per image. Each image
|
| 292 |
+
is downscaled (aspect-preserving, never upscaled) to fit within `max_pixels`, then snapped so the latent grid is
|
| 293 |
+
patchifiable. References keep their own aspect ratio, independent of the generated output size."""
|
| 294 |
+
snap = vae_scale_factor * patch_size
|
| 295 |
+
vae_dtype = vae.dtype
|
| 296 |
+
|
| 297 |
+
latents_mean = torch.tensor(vae.config.latents_mean).view(1, latent_channels, 1, 1, 1)
|
| 298 |
+
latents_std = torch.tensor(vae.config.latents_std).view(1, latent_channels, 1, 1, 1)
|
| 299 |
+
|
| 300 |
+
ref_latents = []
|
| 301 |
+
for img in images:
|
| 302 |
+
img = img.unsqueeze(0).to(device, dtype=vae_dtype)
|
| 303 |
+
h, w = img.shape[2], img.shape[3]
|
| 304 |
+
if h * w > max_pixels:
|
| 305 |
+
ratio = h / w
|
| 306 |
+
new_h, new_w = math.sqrt(max_pixels * ratio), math.sqrt(max_pixels / ratio)
|
| 307 |
+
else:
|
| 308 |
+
new_h, new_w = float(h), float(w)
|
| 309 |
+
new_h = max(snap, int(round(new_h / snap)) * snap)
|
| 310 |
+
new_w = max(snap, int(round(new_w / snap)) * snap)
|
| 311 |
+
if (new_h, new_w) != (h, w):
|
| 312 |
+
img = F.interpolate(img.float(), size=(new_h, new_w), mode="bilinear").to(vae_dtype)
|
| 313 |
+
|
| 314 |
+
img = (img * 2.0 - 1.0).unsqueeze(2) # [0, 1] -> [-1, 1], add frame dim
|
| 315 |
+
latent = retrieve_latents(vae.encode(img), generator=generator, sample_mode="sample")
|
| 316 |
+
latent = (latent - latents_mean.to(latent.device, latent.dtype)) / latents_std.to(latent.device, latent.dtype)
|
| 317 |
+
ref_latents.append(latent[:, :, 0][0]) # drop frame + batch dims -> (C, h, w)
|
| 318 |
+
return ref_latents
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def pack_reference_latents(
|
| 322 |
+
ref_latents: list[torch.Tensor],
|
| 323 |
+
pachifier: Krea2Pachifier,
|
| 324 |
+
device: torch.device,
|
| 325 |
+
dtype: torch.dtype,
|
| 326 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 327 |
+
"""Patchify reference latents into `(1, ref_seq_len, C * p * p)` tokens and build their `(ref_seq_len, 3)` rotary
|
| 328 |
+
coordinates. The i-th reference sits on frame axis `i + 1` with its own y/x grid starting at 0 (the Kontext-style
|
| 329 |
+
"index" placement that marks each reference as a distinct image rather than more of the canvas)."""
|
| 330 |
+
p = pachifier.config.patch_size
|
| 331 |
+
tokens, position_ids = [], []
|
| 332 |
+
for i, ref in enumerate(ref_latents):
|
| 333 |
+
ref = ref.unsqueeze(0).to(device, dtype)
|
| 334 |
+
tokens.append(pachifier.pack_latents(ref))
|
| 335 |
+
_, _, h, w = ref.shape
|
| 336 |
+
ids = torch.zeros(h // p, w // p, 3, device=device)
|
| 337 |
+
ids[..., 0] = i + 1
|
| 338 |
+
ids[..., 1] = torch.arange(h // p, device=device)[:, None]
|
| 339 |
+
ids[..., 2] = torch.arange(w // p, device=device)[None, :]
|
| 340 |
+
position_ids.append(ids.reshape(-1, 3))
|
| 341 |
+
return torch.cat(tokens, dim=1), torch.cat(position_ids, dim=0)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# ====================
|
| 345 |
+
# 1. TEXT ENCODER
|
| 346 |
+
# ====================
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
class Krea2TextEncoderStep(ModularPipelineBlocks):
|
| 350 |
+
model_name = "krea2"
|
| 351 |
+
|
| 352 |
+
def __init__(self, text_encoder_select_layers: tuple[int, ...] | None = None):
|
| 353 |
+
"""Text encoder step for Krea 2.
|
| 354 |
+
|
| 355 |
+
Args:
|
| 356 |
+
text_encoder_select_layers (`tuple[int, ...]`, *optional*):
|
| 357 |
+
Indices into the text encoder's `hidden_states` tuple (0 is the embedding output) whose states are
|
| 358 |
+
stacked per token as the transformer's text conditioning. Must have
|
| 359 |
+
`transformer.config.num_text_layers` entries. Defaults to the Krea 2 (Qwen3-VL-4B) taps.
|
| 360 |
+
"""
|
| 361 |
+
if text_encoder_select_layers is None:
|
| 362 |
+
text_encoder_select_layers = KREA2_TEXT_ENCODER_SELECT_LAYERS
|
| 363 |
+
self.text_encoder_select_layers = tuple(text_encoder_select_layers)
|
| 364 |
+
super().__init__()
|
| 365 |
+
|
| 366 |
+
@property
|
| 367 |
+
def description(self) -> str:
|
| 368 |
+
return "Text Encoder step that generates text embeddings to guide the image generation."
|
| 369 |
+
|
| 370 |
+
@property
|
| 371 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 372 |
+
return [
|
| 373 |
+
ComponentSpec("text_encoder", Qwen3VLModel, description="The text encoder to use"),
|
| 374 |
+
ComponentSpec("tokenizer", Qwen2Tokenizer, description="The tokenizer to use"),
|
| 375 |
+
ComponentSpec(
|
| 376 |
+
"guider",
|
| 377 |
+
ClassifierFreeGuidance,
|
| 378 |
+
config=FrozenDict({"guidance_scale": 4.5, "use_original_formulation": True}),
|
| 379 |
+
default_creation_method="from_config",
|
| 380 |
+
),
|
| 381 |
+
]
|
| 382 |
+
|
| 383 |
+
@property
|
| 384 |
+
def inputs(self) -> list[InputParam]:
|
| 385 |
+
return [
|
| 386 |
+
InputParam.template("prompt"),
|
| 387 |
+
InputParam.template("negative_prompt"),
|
| 388 |
+
InputParam.template("max_sequence_length"),
|
| 389 |
+
]
|
| 390 |
+
|
| 391 |
+
@property
|
| 392 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 393 |
+
return [
|
| 394 |
+
OutputParam.template("prompt_embeds"),
|
| 395 |
+
OutputParam.template("prompt_embeds_mask"),
|
| 396 |
+
OutputParam.template("negative_prompt_embeds"),
|
| 397 |
+
OutputParam.template("negative_prompt_embeds_mask"),
|
| 398 |
+
]
|
| 399 |
+
|
| 400 |
+
@staticmethod
|
| 401 |
+
def check_inputs(prompt, negative_prompt, max_sequence_length):
|
| 402 |
+
if not isinstance(prompt, str) and not isinstance(prompt, list):
|
| 403 |
+
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
| 404 |
+
|
| 405 |
+
if (
|
| 406 |
+
negative_prompt is not None
|
| 407 |
+
and not isinstance(negative_prompt, str)
|
| 408 |
+
and not isinstance(negative_prompt, list)
|
| 409 |
+
):
|
| 410 |
+
raise ValueError(f"`negative_prompt` has to be of type `str` or `list` but is {type(negative_prompt)}")
|
| 411 |
+
|
| 412 |
+
if max_sequence_length is not None and max_sequence_length <= 0:
|
| 413 |
+
raise ValueError(f"`max_sequence_length` must be a positive integer but is {max_sequence_length}")
|
| 414 |
+
|
| 415 |
+
@torch.no_grad()
|
| 416 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState):
|
| 417 |
+
block_state = self.get_block_state(state)
|
| 418 |
+
|
| 419 |
+
device = components._execution_device
|
| 420 |
+
self.check_inputs(block_state.prompt, block_state.negative_prompt, block_state.max_sequence_length)
|
| 421 |
+
|
| 422 |
+
block_state.prompt_embeds, block_state.prompt_embeds_mask = get_krea2_prompt_embeds(
|
| 423 |
+
components.text_encoder,
|
| 424 |
+
components.tokenizer,
|
| 425 |
+
prompt=block_state.prompt,
|
| 426 |
+
text_encoder_select_layers=self.text_encoder_select_layers,
|
| 427 |
+
max_sequence_length=block_state.max_sequence_length,
|
| 428 |
+
device=device,
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
block_state.negative_prompt_embeds = None
|
| 432 |
+
block_state.negative_prompt_embeds_mask = None
|
| 433 |
+
if components.requires_unconditional_embeds:
|
| 434 |
+
negative_prompt = block_state.negative_prompt or ""
|
| 435 |
+
block_state.negative_prompt_embeds, block_state.negative_prompt_embeds_mask = get_krea2_prompt_embeds(
|
| 436 |
+
components.text_encoder,
|
| 437 |
+
components.tokenizer,
|
| 438 |
+
prompt=negative_prompt,
|
| 439 |
+
text_encoder_select_layers=self.text_encoder_select_layers,
|
| 440 |
+
max_sequence_length=block_state.max_sequence_length,
|
| 441 |
+
device=device,
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
self.set_block_state(state, block_state)
|
| 445 |
+
return components, state
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
class Krea2EditTextEncoderStep(ModularPipelineBlocks):
|
| 449 |
+
model_name = "krea2"
|
| 450 |
+
|
| 451 |
+
def __init__(self, text_encoder_select_layers: tuple[int, ...] | None = None):
|
| 452 |
+
"""Text encoder step for the Krea 2 edit task: encodes the prompt while embedding a coarse view of the
|
| 453 |
+
reference image(s) into the conditioning through the Qwen3-VL vision tower.
|
| 454 |
+
|
| 455 |
+
Args:
|
| 456 |
+
text_encoder_select_layers (`tuple[int, ...]`, *optional*):
|
| 457 |
+
Indices into the text encoder's `hidden_states` tuple whose states are stacked per token as the
|
| 458 |
+
transformer's text conditioning. Defaults to the Krea 2 (Qwen3-VL-4B) taps.
|
| 459 |
+
"""
|
| 460 |
+
if text_encoder_select_layers is None:
|
| 461 |
+
text_encoder_select_layers = KREA2_TEXT_ENCODER_SELECT_LAYERS
|
| 462 |
+
self.text_encoder_select_layers = tuple(text_encoder_select_layers)
|
| 463 |
+
super().__init__()
|
| 464 |
+
|
| 465 |
+
@property
|
| 466 |
+
def description(self) -> str:
|
| 467 |
+
return (
|
| 468 |
+
"Text encoder step for the edit task. Embeds reference image(s) into the text conditioning via the "
|
| 469 |
+
"Qwen3-VL vision tower, matching how the Ostris AI-Toolkit edit LoRAs are trained."
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
@property
|
| 473 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 474 |
+
return [
|
| 475 |
+
ComponentSpec("text_encoder", Qwen3VLModel, description="The text encoder to use"),
|
| 476 |
+
ComponentSpec("tokenizer", Qwen2Tokenizer, description="The tokenizer to use"),
|
| 477 |
+
ComponentSpec("processor", Qwen3VLProcessor, description="The Qwen3-VL processor for reference images"),
|
| 478 |
+
ComponentSpec(
|
| 479 |
+
"guider",
|
| 480 |
+
ClassifierFreeGuidance,
|
| 481 |
+
config=FrozenDict({"guidance_scale": 4.5, "use_original_formulation": True}),
|
| 482 |
+
default_creation_method="from_config",
|
| 483 |
+
),
|
| 484 |
+
]
|
| 485 |
+
|
| 486 |
+
@property
|
| 487 |
+
def inputs(self) -> list[InputParam]:
|
| 488 |
+
return [
|
| 489 |
+
InputParam.template("prompt"),
|
| 490 |
+
InputParam.template("negative_prompt"),
|
| 491 |
+
InputParam.template("image", required=True, note="The reference image(s) for the edit."),
|
| 492 |
+
InputParam.template("max_sequence_length"),
|
| 493 |
+
InputParam(
|
| 494 |
+
"vl_image_max_pixels",
|
| 495 |
+
type_hint=int,
|
| 496 |
+
default=384 * 384,
|
| 497 |
+
description="Pixel budget for the coarse Qwen3-VL view of each reference image.",
|
| 498 |
+
),
|
| 499 |
+
]
|
| 500 |
+
|
| 501 |
+
@property
|
| 502 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 503 |
+
return [
|
| 504 |
+
OutputParam.template("prompt_embeds"),
|
| 505 |
+
OutputParam.template("prompt_embeds_mask"),
|
| 506 |
+
OutputParam.template("negative_prompt_embeds"),
|
| 507 |
+
OutputParam.template("negative_prompt_embeds_mask"),
|
| 508 |
+
]
|
| 509 |
+
|
| 510 |
+
@staticmethod
|
| 511 |
+
def check_inputs(prompt, negative_prompt, max_sequence_length):
|
| 512 |
+
if not isinstance(prompt, str) and not isinstance(prompt, list):
|
| 513 |
+
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
| 514 |
+
if (
|
| 515 |
+
negative_prompt is not None
|
| 516 |
+
and not isinstance(negative_prompt, str)
|
| 517 |
+
and not isinstance(negative_prompt, list)
|
| 518 |
+
):
|
| 519 |
+
raise ValueError(f"`negative_prompt` has to be of type `str` or `list` but is {type(negative_prompt)}")
|
| 520 |
+
if max_sequence_length is not None and max_sequence_length <= 0:
|
| 521 |
+
raise ValueError(f"`max_sequence_length` must be a positive integer but is {max_sequence_length}")
|
| 522 |
+
|
| 523 |
+
@torch.no_grad()
|
| 524 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState):
|
| 525 |
+
block_state = self.get_block_state(state)
|
| 526 |
+
|
| 527 |
+
device = components._execution_device
|
| 528 |
+
self.check_inputs(block_state.prompt, block_state.negative_prompt, block_state.max_sequence_length)
|
| 529 |
+
|
| 530 |
+
image_list = block_state.image if isinstance(block_state.image, (list, tuple)) else [block_state.image]
|
| 531 |
+
ref_images = [to_chw_tensor(img).to(device) for img in image_list]
|
| 532 |
+
vl_images = prep_vl_images(ref_images, block_state.vl_image_max_pixels)
|
| 533 |
+
|
| 534 |
+
block_state.prompt_embeds, block_state.prompt_embeds_mask = get_krea2_edit_prompt_embeds(
|
| 535 |
+
components.text_encoder,
|
| 536 |
+
components.tokenizer,
|
| 537 |
+
components.processor,
|
| 538 |
+
prompt=block_state.prompt,
|
| 539 |
+
images=vl_images,
|
| 540 |
+
text_encoder_select_layers=self.text_encoder_select_layers,
|
| 541 |
+
max_sequence_length=block_state.max_sequence_length,
|
| 542 |
+
device=device,
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
block_state.negative_prompt_embeds = None
|
| 546 |
+
block_state.negative_prompt_embeds_mask = None
|
| 547 |
+
if components.requires_unconditional_embeds:
|
| 548 |
+
negative_prompt = block_state.negative_prompt or ""
|
| 549 |
+
block_state.negative_prompt_embeds, block_state.negative_prompt_embeds_mask = get_krea2_edit_prompt_embeds(
|
| 550 |
+
components.text_encoder,
|
| 551 |
+
components.tokenizer,
|
| 552 |
+
components.processor,
|
| 553 |
+
prompt=negative_prompt,
|
| 554 |
+
images=vl_images,
|
| 555 |
+
text_encoder_select_layers=self.text_encoder_select_layers,
|
| 556 |
+
max_sequence_length=block_state.max_sequence_length,
|
| 557 |
+
device=device,
|
| 558 |
+
)
|
| 559 |
+
|
| 560 |
+
self.set_block_state(state, block_state)
|
| 561 |
+
return components, state
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
# ====================
|
| 565 |
+
# 2. IMAGE PREPROCESS
|
| 566 |
+
# ====================
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
class Krea2InpaintProcessImagesInputStep(ModularPipelineBlocks):
|
| 570 |
+
model_name = "krea2"
|
| 571 |
+
|
| 572 |
+
@property
|
| 573 |
+
def description(self) -> str:
|
| 574 |
+
return "Image Preprocess step for inpainting task. This processes the image and mask inputs together. Images will be resized to the given height and width."
|
| 575 |
+
|
| 576 |
+
@property
|
| 577 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 578 |
+
return [
|
| 579 |
+
ComponentSpec(
|
| 580 |
+
"image_mask_processor",
|
| 581 |
+
InpaintProcessor,
|
| 582 |
+
config=FrozenDict({"vae_scale_factor": 16}),
|
| 583 |
+
default_creation_method="from_config",
|
| 584 |
+
),
|
| 585 |
+
]
|
| 586 |
+
|
| 587 |
+
@property
|
| 588 |
+
def inputs(self) -> list[InputParam]:
|
| 589 |
+
return [
|
| 590 |
+
InputParam.template("mask_image"),
|
| 591 |
+
InputParam.template("image"),
|
| 592 |
+
InputParam.template("height"),
|
| 593 |
+
InputParam.template("width"),
|
| 594 |
+
InputParam.template("padding_mask_crop"),
|
| 595 |
+
]
|
| 596 |
+
|
| 597 |
+
@property
|
| 598 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 599 |
+
return [
|
| 600 |
+
OutputParam(
|
| 601 |
+
name="processed_image",
|
| 602 |
+
type_hint=torch.Tensor,
|
| 603 |
+
description="The processed image",
|
| 604 |
+
),
|
| 605 |
+
OutputParam(
|
| 606 |
+
name="processed_mask_image",
|
| 607 |
+
type_hint=torch.Tensor,
|
| 608 |
+
description="The processed mask image",
|
| 609 |
+
),
|
| 610 |
+
OutputParam(
|
| 611 |
+
name="mask_overlay_kwargs",
|
| 612 |
+
type_hint=dict,
|
| 613 |
+
description="The kwargs for the postprocess step to apply the mask overlay",
|
| 614 |
+
),
|
| 615 |
+
]
|
| 616 |
+
|
| 617 |
+
@staticmethod
|
| 618 |
+
def check_inputs(height, width, vae_scale_factor):
|
| 619 |
+
if height is not None and height % (vae_scale_factor * 2) != 0:
|
| 620 |
+
raise ValueError(f"Height must be divisible by {vae_scale_factor * 2} but is {height}")
|
| 621 |
+
|
| 622 |
+
if width is not None and width % (vae_scale_factor * 2) != 0:
|
| 623 |
+
raise ValueError(f"Width must be divisible by {vae_scale_factor * 2} but is {width}")
|
| 624 |
+
|
| 625 |
+
@torch.no_grad()
|
| 626 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState):
|
| 627 |
+
block_state = self.get_block_state(state)
|
| 628 |
+
|
| 629 |
+
self.check_inputs(
|
| 630 |
+
height=block_state.height, width=block_state.width, vae_scale_factor=components.vae_scale_factor
|
| 631 |
+
)
|
| 632 |
+
height = block_state.height or components.default_height
|
| 633 |
+
width = block_state.width or components.default_width
|
| 634 |
+
|
| 635 |
+
block_state.processed_image, block_state.processed_mask_image, block_state.mask_overlay_kwargs = (
|
| 636 |
+
components.image_mask_processor.preprocess(
|
| 637 |
+
image=block_state.image,
|
| 638 |
+
mask=block_state.mask_image,
|
| 639 |
+
height=height,
|
| 640 |
+
width=width,
|
| 641 |
+
padding_mask_crop=block_state.padding_mask_crop,
|
| 642 |
+
)
|
| 643 |
+
)
|
| 644 |
+
|
| 645 |
+
self.set_block_state(state, block_state)
|
| 646 |
+
return components, state
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
class Krea2ProcessImagesInputStep(ModularPipelineBlocks):
|
| 650 |
+
model_name = "krea2"
|
| 651 |
+
|
| 652 |
+
@property
|
| 653 |
+
def description(self) -> str:
|
| 654 |
+
return "Image Preprocess step. will resize the image to the given height and width."
|
| 655 |
+
|
| 656 |
+
@property
|
| 657 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 658 |
+
return [
|
| 659 |
+
ComponentSpec(
|
| 660 |
+
"image_processor",
|
| 661 |
+
VaeImageProcessor,
|
| 662 |
+
config=FrozenDict({"vae_scale_factor": 16}),
|
| 663 |
+
default_creation_method="from_config",
|
| 664 |
+
),
|
| 665 |
+
]
|
| 666 |
+
|
| 667 |
+
@property
|
| 668 |
+
def inputs(self) -> list[InputParam]:
|
| 669 |
+
return [
|
| 670 |
+
InputParam.template("image"),
|
| 671 |
+
InputParam.template("height"),
|
| 672 |
+
InputParam.template("width"),
|
| 673 |
+
]
|
| 674 |
+
|
| 675 |
+
@property
|
| 676 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 677 |
+
return [
|
| 678 |
+
OutputParam(
|
| 679 |
+
name="processed_image",
|
| 680 |
+
type_hint=torch.Tensor,
|
| 681 |
+
description="The processed image",
|
| 682 |
+
)
|
| 683 |
+
]
|
| 684 |
+
|
| 685 |
+
@staticmethod
|
| 686 |
+
def check_inputs(height, width, vae_scale_factor):
|
| 687 |
+
if height is not None and height % (vae_scale_factor * 2) != 0:
|
| 688 |
+
raise ValueError(f"Height must be divisible by {vae_scale_factor * 2} but is {height}")
|
| 689 |
+
|
| 690 |
+
if width is not None and width % (vae_scale_factor * 2) != 0:
|
| 691 |
+
raise ValueError(f"Width must be divisible by {vae_scale_factor * 2} but is {width}")
|
| 692 |
+
|
| 693 |
+
@torch.no_grad()
|
| 694 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState):
|
| 695 |
+
block_state = self.get_block_state(state)
|
| 696 |
+
|
| 697 |
+
self.check_inputs(
|
| 698 |
+
height=block_state.height, width=block_state.width, vae_scale_factor=components.vae_scale_factor
|
| 699 |
+
)
|
| 700 |
+
height = block_state.height or components.default_height
|
| 701 |
+
width = block_state.width or components.default_width
|
| 702 |
+
|
| 703 |
+
block_state.processed_image = components.image_processor.preprocess(
|
| 704 |
+
image=block_state.image,
|
| 705 |
+
height=height,
|
| 706 |
+
width=width,
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
self.set_block_state(state, block_state)
|
| 710 |
+
return components, state
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
# ====================
|
| 714 |
+
# 3. VAE ENCODER
|
| 715 |
+
# ====================
|
| 716 |
+
|
| 717 |
+
|
| 718 |
+
class Krea2VaeEncoderStep(ModularPipelineBlocks):
|
| 719 |
+
model_name = "krea2"
|
| 720 |
+
|
| 721 |
+
@property
|
| 722 |
+
def description(self) -> str:
|
| 723 |
+
return "VAE Encoder step that converts processed_image into latent representations image_latents."
|
| 724 |
+
|
| 725 |
+
@property
|
| 726 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 727 |
+
return [ComponentSpec("vae", AutoencoderKLQwenImage)]
|
| 728 |
+
|
| 729 |
+
@property
|
| 730 |
+
def inputs(self) -> list[InputParam]:
|
| 731 |
+
return [
|
| 732 |
+
InputParam(
|
| 733 |
+
name="processed_image", required=True, type_hint=torch.Tensor, description="The image tensor to encode"
|
| 734 |
+
),
|
| 735 |
+
InputParam.template("generator"),
|
| 736 |
+
]
|
| 737 |
+
|
| 738 |
+
@property
|
| 739 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 740 |
+
return [OutputParam.template("image_latents")]
|
| 741 |
+
|
| 742 |
+
@torch.no_grad()
|
| 743 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 744 |
+
block_state = self.get_block_state(state)
|
| 745 |
+
|
| 746 |
+
device = components._execution_device
|
| 747 |
+
dtype = components.vae.dtype
|
| 748 |
+
|
| 749 |
+
block_state.image_latents = encode_vae_image(
|
| 750 |
+
image=block_state.processed_image,
|
| 751 |
+
vae=components.vae,
|
| 752 |
+
generator=block_state.generator,
|
| 753 |
+
device=device,
|
| 754 |
+
dtype=dtype,
|
| 755 |
+
latent_channels=components.num_channels_latents,
|
| 756 |
+
)
|
| 757 |
+
|
| 758 |
+
self.set_block_state(state, block_state)
|
| 759 |
+
|
| 760 |
+
return components, state
|
| 761 |
+
|
| 762 |
+
|
| 763 |
+
class Krea2EditReferenceLatentsStep(ModularPipelineBlocks):
|
| 764 |
+
model_name = "krea2"
|
| 765 |
+
|
| 766 |
+
@property
|
| 767 |
+
def description(self) -> str:
|
| 768 |
+
return (
|
| 769 |
+
"Reference (edit) VAE encoder step. Encodes reference image(s) to clean, normalized VAE latents and packs "
|
| 770 |
+
"them into transformer tokens with their frame-axis rotary coordinates. These tokens are appended to the "
|
| 771 |
+
"sequence at flow time t=0 to condition the generation on the references."
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
+
@property
|
| 775 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 776 |
+
return [
|
| 777 |
+
ComponentSpec("vae", AutoencoderKLQwenImage),
|
| 778 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 779 |
+
]
|
| 780 |
+
|
| 781 |
+
@property
|
| 782 |
+
def inputs(self) -> list[InputParam]:
|
| 783 |
+
return [
|
| 784 |
+
InputParam.template("image", required=True, note="The reference image(s) for the edit."),
|
| 785 |
+
InputParam.template("generator"),
|
| 786 |
+
InputParam(
|
| 787 |
+
"reference_max_pixels",
|
| 788 |
+
type_hint=int,
|
| 789 |
+
default=1024 * 1024,
|
| 790 |
+
description="Pixel budget each reference image is downscaled to fit before VAE encoding.",
|
| 791 |
+
),
|
| 792 |
+
]
|
| 793 |
+
|
| 794 |
+
@property
|
| 795 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 796 |
+
return [
|
| 797 |
+
OutputParam(
|
| 798 |
+
name="reference_latents",
|
| 799 |
+
type_hint=torch.Tensor,
|
| 800 |
+
description="Packed clean reference tokens of shape (1, ref_seq_len, C * p * p), appended to the "
|
| 801 |
+
"denoiser sequence at t=0.",
|
| 802 |
+
),
|
| 803 |
+
OutputParam(
|
| 804 |
+
name="reference_position_ids",
|
| 805 |
+
type_hint=torch.Tensor,
|
| 806 |
+
description="Rotary coordinates (ref_seq_len, 3) for the reference tokens; the i-th reference sits on "
|
| 807 |
+
"frame axis i + 1.",
|
| 808 |
+
),
|
| 809 |
+
OutputParam(
|
| 810 |
+
name="ref_seq_len",
|
| 811 |
+
kwargs_type="denoiser_input_fields",
|
| 812 |
+
type_hint=int,
|
| 813 |
+
description="Number of reference tokens appended to the denoiser sequence.",
|
| 814 |
+
),
|
| 815 |
+
]
|
| 816 |
+
|
| 817 |
+
@torch.no_grad()
|
| 818 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 819 |
+
block_state = self.get_block_state(state)
|
| 820 |
+
|
| 821 |
+
device = components._execution_device
|
| 822 |
+
patch_size = components.pachifier.config.patch_size
|
| 823 |
+
|
| 824 |
+
image_list = block_state.image if isinstance(block_state.image, (list, tuple)) else [block_state.image]
|
| 825 |
+
ref_images = [to_chw_tensor(img) for img in image_list]
|
| 826 |
+
|
| 827 |
+
ref_latents = encode_reference_latents(
|
| 828 |
+
images=ref_images,
|
| 829 |
+
vae=components.vae,
|
| 830 |
+
max_pixels=block_state.reference_max_pixels,
|
| 831 |
+
generator=block_state.generator,
|
| 832 |
+
device=device,
|
| 833 |
+
vae_scale_factor=components.vae_scale_factor,
|
| 834 |
+
patch_size=patch_size,
|
| 835 |
+
latent_channels=components.num_channels_latents,
|
| 836 |
+
)
|
| 837 |
+
block_state.reference_latents, block_state.reference_position_ids = pack_reference_latents(
|
| 838 |
+
ref_latents, components.pachifier, device, components.vae.dtype
|
| 839 |
+
)
|
| 840 |
+
block_state.ref_seq_len = block_state.reference_latents.shape[1]
|
| 841 |
+
|
| 842 |
+
self.set_block_state(state, block_state)
|
| 843 |
+
|
| 844 |
+
return components, state
|
inputs.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
from diffusers.modular_pipelines.modular_pipeline import ModularPipelineBlocks, PipelineState
|
| 19 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, InputParam, OutputParam
|
| 20 |
+
from .modular_pipeline import Krea2ModularPipeline, Krea2Pachifier
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# Copied from diffusers.modular_pipelines.qwenimage.inputs.repeat_tensor_to_batch_size
|
| 24 |
+
def repeat_tensor_to_batch_size(
|
| 25 |
+
input_name: str,
|
| 26 |
+
input_tensor: torch.Tensor,
|
| 27 |
+
batch_size: int,
|
| 28 |
+
num_images_per_prompt: int = 1,
|
| 29 |
+
) -> torch.Tensor:
|
| 30 |
+
"""Repeat tensor elements to match the final batch size.
|
| 31 |
+
|
| 32 |
+
This function expands a tensor's batch dimension to match the final batch size (batch_size * num_images_per_prompt)
|
| 33 |
+
by repeating each element along dimension 0.
|
| 34 |
+
|
| 35 |
+
The input tensor must have batch size 1 or batch_size. The function will:
|
| 36 |
+
- If batch size is 1: repeat each element (batch_size * num_images_per_prompt) times
|
| 37 |
+
- If batch size equals batch_size: repeat each element num_images_per_prompt times
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
input_name (str): Name of the input tensor (used for error messages)
|
| 41 |
+
input_tensor (torch.Tensor): The tensor to repeat. Must have batch size 1 or batch_size.
|
| 42 |
+
batch_size (int): The base batch size (number of prompts)
|
| 43 |
+
num_images_per_prompt (int, optional): Number of images to generate per prompt. Defaults to 1.
|
| 44 |
+
|
| 45 |
+
Returns:
|
| 46 |
+
torch.Tensor: The repeated tensor with final batch size (batch_size * num_images_per_prompt)
|
| 47 |
+
|
| 48 |
+
Raises:
|
| 49 |
+
ValueError: If input_tensor is not a torch.Tensor or has invalid batch size
|
| 50 |
+
|
| 51 |
+
Examples:
|
| 52 |
+
tensor = torch.tensor([[1, 2, 3]]) # shape: [1, 3] repeated = repeat_tensor_to_batch_size("image", tensor,
|
| 53 |
+
batch_size=2, num_images_per_prompt=2) repeated # tensor([[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]) - shape:
|
| 54 |
+
[4, 3]
|
| 55 |
+
|
| 56 |
+
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]]) # shape: [2, 3] repeated = repeat_tensor_to_batch_size("image",
|
| 57 |
+
tensor, batch_size=2, num_images_per_prompt=2) repeated # tensor([[1, 2, 3], [1, 2, 3], [4, 5, 6], [4, 5, 6]])
|
| 58 |
+
- shape: [4, 3]
|
| 59 |
+
"""
|
| 60 |
+
# make sure input is a tensor
|
| 61 |
+
if not isinstance(input_tensor, torch.Tensor):
|
| 62 |
+
raise ValueError(f"`{input_name}` must be a tensor")
|
| 63 |
+
|
| 64 |
+
# make sure input tensor e.g. image_latents has batch size 1 or batch_size same as prompts
|
| 65 |
+
if input_tensor.shape[0] == 1:
|
| 66 |
+
repeat_by = batch_size * num_images_per_prompt
|
| 67 |
+
elif input_tensor.shape[0] == batch_size:
|
| 68 |
+
repeat_by = num_images_per_prompt
|
| 69 |
+
else:
|
| 70 |
+
raise ValueError(
|
| 71 |
+
f"`{input_name}` must have have batch size 1 or {batch_size}, but got {input_tensor.shape[0]}"
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
# expand the tensor to match the batch_size * num_images_per_prompt
|
| 75 |
+
input_tensor = input_tensor.repeat_interleave(repeat_by, dim=0)
|
| 76 |
+
|
| 77 |
+
return input_tensor
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# Copied from diffusers.modular_pipelines.qwenimage.inputs.calculate_dimension_from_latents
|
| 81 |
+
def calculate_dimension_from_latents(latents: torch.Tensor, vae_scale_factor: int) -> tuple[int, int]:
|
| 82 |
+
"""Calculate image dimensions from latent tensor dimensions.
|
| 83 |
+
|
| 84 |
+
This function converts latent space dimensions to image space dimensions by multiplying the latent height and width
|
| 85 |
+
by the VAE scale factor.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
latents (torch.Tensor): The latent tensor. Must have 4 or 5 dimensions.
|
| 89 |
+
Expected shapes: [batch, channels, height, width] or [batch, channels, frames, height, width]
|
| 90 |
+
vae_scale_factor (int): The scale factor used by the VAE to compress images.
|
| 91 |
+
Typically 8 for most VAEs (image is 8x larger than latents in each dimension)
|
| 92 |
+
|
| 93 |
+
Returns:
|
| 94 |
+
tuple[int, int]: The calculated image dimensions as (height, width)
|
| 95 |
+
|
| 96 |
+
Raises:
|
| 97 |
+
ValueError: If latents tensor doesn't have 4 or 5 dimensions
|
| 98 |
+
|
| 99 |
+
"""
|
| 100 |
+
# make sure the latents are not packed
|
| 101 |
+
if latents.ndim != 4 and latents.ndim != 5:
|
| 102 |
+
raise ValueError(f"unpacked latents must have 4 or 5 dimensions, but got {latents.ndim}")
|
| 103 |
+
|
| 104 |
+
latent_height, latent_width = latents.shape[-2:]
|
| 105 |
+
|
| 106 |
+
height = latent_height * vae_scale_factor
|
| 107 |
+
width = latent_width * vae_scale_factor
|
| 108 |
+
|
| 109 |
+
return height, width
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class Krea2TextInputsStep(ModularPipelineBlocks):
|
| 113 |
+
model_name = "krea2"
|
| 114 |
+
|
| 115 |
+
@property
|
| 116 |
+
def description(self) -> str:
|
| 117 |
+
summary_section = (
|
| 118 |
+
"Text input processing step that standardizes text embeddings for the pipeline.\n"
|
| 119 |
+
"This step:\n"
|
| 120 |
+
" 1. Determines `batch_size` and `dtype` based on `prompt_embeds`\n"
|
| 121 |
+
" 2. Ensures all text embeddings have consistent batch sizes (batch_size * num_images_per_prompt)"
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
# Placement guidance
|
| 125 |
+
placement_section = "\n\nThis block should be placed after all encoder steps to process the text embeddings before they are used in subsequent pipeline steps."
|
| 126 |
+
|
| 127 |
+
return summary_section + placement_section
|
| 128 |
+
|
| 129 |
+
@property
|
| 130 |
+
def inputs(self) -> list[InputParam]:
|
| 131 |
+
return [
|
| 132 |
+
InputParam.template("num_images_per_prompt"),
|
| 133 |
+
InputParam.template("prompt_embeds"),
|
| 134 |
+
InputParam.template("prompt_embeds_mask"),
|
| 135 |
+
InputParam.template("negative_prompt_embeds"),
|
| 136 |
+
InputParam.template("negative_prompt_embeds_mask"),
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
@property
|
| 140 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 141 |
+
return [
|
| 142 |
+
OutputParam(name="batch_size", type_hint=int, description="The batch size of the prompt embeddings"),
|
| 143 |
+
OutputParam(name="dtype", type_hint=torch.dtype, description="The data type of the prompt embeddings"),
|
| 144 |
+
OutputParam.template("prompt_embeds", note="batch-expanded"),
|
| 145 |
+
OutputParam.template("prompt_embeds_mask", note="batch-expanded"),
|
| 146 |
+
OutputParam.template("negative_prompt_embeds", note="batch-expanded"),
|
| 147 |
+
OutputParam.template("negative_prompt_embeds_mask", note="batch-expanded"),
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
@staticmethod
|
| 151 |
+
def check_inputs(
|
| 152 |
+
prompt_embeds,
|
| 153 |
+
prompt_embeds_mask,
|
| 154 |
+
negative_prompt_embeds,
|
| 155 |
+
negative_prompt_embeds_mask,
|
| 156 |
+
):
|
| 157 |
+
if prompt_embeds.ndim != 4:
|
| 158 |
+
raise ValueError(
|
| 159 |
+
f"`prompt_embeds` must have 4 dimensions (batch_size, text_seq_len, num_text_layers, text_hidden_dim), but got {prompt_embeds.ndim}"
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
if negative_prompt_embeds is not None and negative_prompt_embeds_mask is None:
|
| 163 |
+
raise ValueError("`negative_prompt_embeds_mask` is required when `negative_prompt_embeds` is not None")
|
| 164 |
+
|
| 165 |
+
if negative_prompt_embeds is None and negative_prompt_embeds_mask is not None:
|
| 166 |
+
raise ValueError("cannot pass `negative_prompt_embeds_mask` without `negative_prompt_embeds`")
|
| 167 |
+
|
| 168 |
+
if prompt_embeds_mask.shape[0] != prompt_embeds.shape[0]:
|
| 169 |
+
raise ValueError("`prompt_embeds_mask` must have the same batch size as `prompt_embeds`")
|
| 170 |
+
|
| 171 |
+
elif negative_prompt_embeds is not None and negative_prompt_embeds.shape[0] != prompt_embeds.shape[0]:
|
| 172 |
+
raise ValueError("`negative_prompt_embeds` must have the same batch size as `prompt_embeds`")
|
| 173 |
+
|
| 174 |
+
elif (
|
| 175 |
+
negative_prompt_embeds_mask is not None and negative_prompt_embeds_mask.shape[0] != prompt_embeds.shape[0]
|
| 176 |
+
):
|
| 177 |
+
raise ValueError("`negative_prompt_embeds_mask` must have the same batch size as `prompt_embeds`")
|
| 178 |
+
|
| 179 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 180 |
+
block_state = self.get_block_state(state)
|
| 181 |
+
|
| 182 |
+
self.check_inputs(
|
| 183 |
+
prompt_embeds=block_state.prompt_embeds,
|
| 184 |
+
prompt_embeds_mask=block_state.prompt_embeds_mask,
|
| 185 |
+
negative_prompt_embeds=block_state.negative_prompt_embeds,
|
| 186 |
+
negative_prompt_embeds_mask=block_state.negative_prompt_embeds_mask,
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
block_state.batch_size = block_state.prompt_embeds.shape[0]
|
| 190 |
+
block_state.dtype = block_state.prompt_embeds.dtype
|
| 191 |
+
|
| 192 |
+
# Krea 2 prompt embeddings are 4D: (batch_size, text_seq_len, num_text_layers, text_hidden_dim)
|
| 193 |
+
_, seq_len, num_text_layers, dim = block_state.prompt_embeds.shape
|
| 194 |
+
|
| 195 |
+
block_state.prompt_embeds = block_state.prompt_embeds.repeat(1, block_state.num_images_per_prompt, 1, 1)
|
| 196 |
+
block_state.prompt_embeds = block_state.prompt_embeds.view(
|
| 197 |
+
block_state.batch_size * block_state.num_images_per_prompt, seq_len, num_text_layers, dim
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
block_state.prompt_embeds_mask = block_state.prompt_embeds_mask.repeat(1, block_state.num_images_per_prompt)
|
| 201 |
+
block_state.prompt_embeds_mask = block_state.prompt_embeds_mask.view(
|
| 202 |
+
block_state.batch_size * block_state.num_images_per_prompt, seq_len
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
if block_state.negative_prompt_embeds is not None:
|
| 206 |
+
_, seq_len, num_text_layers, dim = block_state.negative_prompt_embeds.shape
|
| 207 |
+
block_state.negative_prompt_embeds = block_state.negative_prompt_embeds.repeat(
|
| 208 |
+
1, block_state.num_images_per_prompt, 1, 1
|
| 209 |
+
)
|
| 210 |
+
block_state.negative_prompt_embeds = block_state.negative_prompt_embeds.view(
|
| 211 |
+
block_state.batch_size * block_state.num_images_per_prompt, seq_len, num_text_layers, dim
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
block_state.negative_prompt_embeds_mask = block_state.negative_prompt_embeds_mask.repeat(
|
| 215 |
+
1, block_state.num_images_per_prompt
|
| 216 |
+
)
|
| 217 |
+
block_state.negative_prompt_embeds_mask = block_state.negative_prompt_embeds_mask.view(
|
| 218 |
+
block_state.batch_size * block_state.num_images_per_prompt, seq_len
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
self.set_block_state(state, block_state)
|
| 222 |
+
|
| 223 |
+
return components, state
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
class Krea2AdditionalInputsStep(ModularPipelineBlocks):
|
| 227 |
+
model_name = "krea2"
|
| 228 |
+
|
| 229 |
+
def __init__(
|
| 230 |
+
self,
|
| 231 |
+
image_latent_inputs: list[InputParam] | None = None,
|
| 232 |
+
additional_batch_inputs: list[InputParam] | None = None,
|
| 233 |
+
):
|
| 234 |
+
"""Input processing step for additional (non-text) inputs.
|
| 235 |
+
|
| 236 |
+
For image latent inputs: updates height/width if None, patchifies, and expands batch size. For additional batch
|
| 237 |
+
inputs: expands batch dimensions to match the final batch size.
|
| 238 |
+
|
| 239 |
+
Args:
|
| 240 |
+
image_latent_inputs (list[InputParam], optional): Image latent inputs to process. Defaults to
|
| 241 |
+
`[InputParam.template("image_latents")]`.
|
| 242 |
+
additional_batch_inputs (list[InputParam], optional): Additional tensor inputs that only need batch
|
| 243 |
+
expansion. Defaults to `[]`.
|
| 244 |
+
"""
|
| 245 |
+
# by default, process `image_latents`
|
| 246 |
+
if image_latent_inputs is None:
|
| 247 |
+
image_latent_inputs = [InputParam.template("image_latents")]
|
| 248 |
+
if additional_batch_inputs is None:
|
| 249 |
+
additional_batch_inputs = []
|
| 250 |
+
|
| 251 |
+
if not isinstance(image_latent_inputs, list):
|
| 252 |
+
raise ValueError(f"image_latent_inputs must be a list, but got {type(image_latent_inputs)}")
|
| 253 |
+
else:
|
| 254 |
+
for input_param in image_latent_inputs:
|
| 255 |
+
if not isinstance(input_param, InputParam):
|
| 256 |
+
raise ValueError(f"image_latent_inputs must be a list of InputParam, but got {type(input_param)}")
|
| 257 |
+
|
| 258 |
+
if not isinstance(additional_batch_inputs, list):
|
| 259 |
+
raise ValueError(f"additional_batch_inputs must be a list, but got {type(additional_batch_inputs)}")
|
| 260 |
+
else:
|
| 261 |
+
for input_param in additional_batch_inputs:
|
| 262 |
+
if not isinstance(input_param, InputParam):
|
| 263 |
+
raise ValueError(
|
| 264 |
+
f"additional_batch_inputs must be a list of InputParam, but got {type(input_param)}"
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
self._image_latent_inputs = image_latent_inputs
|
| 268 |
+
self._additional_batch_inputs = additional_batch_inputs
|
| 269 |
+
super().__init__()
|
| 270 |
+
|
| 271 |
+
@property
|
| 272 |
+
def description(self) -> str:
|
| 273 |
+
summary_section = (
|
| 274 |
+
"Input processing step that:\n"
|
| 275 |
+
" 1. For image latent inputs: Updates height/width if None, patchifies, and expands batch size\n"
|
| 276 |
+
" 2. For additional batch inputs: Expands batch dimensions to match final batch size"
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
inputs_info = ""
|
| 280 |
+
if self._image_latent_inputs or self._additional_batch_inputs:
|
| 281 |
+
inputs_info = "\n\nConfigured inputs:"
|
| 282 |
+
if self._image_latent_inputs:
|
| 283 |
+
inputs_info += f"\n - Image latent inputs: {[p.name for p in self._image_latent_inputs]}"
|
| 284 |
+
if self._additional_batch_inputs:
|
| 285 |
+
inputs_info += f"\n - Additional batch inputs: {[p.name for p in self._additional_batch_inputs]}"
|
| 286 |
+
|
| 287 |
+
placement_section = "\n\nThis block should be placed after the encoder steps and the text input step."
|
| 288 |
+
|
| 289 |
+
return summary_section + inputs_info + placement_section
|
| 290 |
+
|
| 291 |
+
@property
|
| 292 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 293 |
+
return [
|
| 294 |
+
ComponentSpec("pachifier", Krea2Pachifier, default_creation_method="from_config"),
|
| 295 |
+
]
|
| 296 |
+
|
| 297 |
+
@property
|
| 298 |
+
def inputs(self) -> list[InputParam]:
|
| 299 |
+
inputs = [
|
| 300 |
+
InputParam.template("num_images_per_prompt"),
|
| 301 |
+
InputParam.template("batch_size"),
|
| 302 |
+
InputParam.template("height"),
|
| 303 |
+
InputParam.template("width"),
|
| 304 |
+
]
|
| 305 |
+
# default is `image_latents`
|
| 306 |
+
inputs += self._image_latent_inputs + self._additional_batch_inputs
|
| 307 |
+
|
| 308 |
+
return inputs
|
| 309 |
+
|
| 310 |
+
@property
|
| 311 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 312 |
+
outputs = [
|
| 313 |
+
OutputParam(
|
| 314 |
+
name="image_height",
|
| 315 |
+
type_hint=int,
|
| 316 |
+
description="The image height calculated from the image latents dimension",
|
| 317 |
+
),
|
| 318 |
+
OutputParam(
|
| 319 |
+
name="image_width",
|
| 320 |
+
type_hint=int,
|
| 321 |
+
description="The image width calculated from the image latents dimension",
|
| 322 |
+
),
|
| 323 |
+
]
|
| 324 |
+
|
| 325 |
+
# `height`/`width` are not new outputs, but they will be updated if any image latent inputs are provided
|
| 326 |
+
if len(self._image_latent_inputs) > 0:
|
| 327 |
+
outputs.append(
|
| 328 |
+
OutputParam(name="height", type_hint=int, description="if not provided, updated to image height")
|
| 329 |
+
)
|
| 330 |
+
outputs.append(
|
| 331 |
+
OutputParam(name="width", type_hint=int, description="if not provided, updated to image width")
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
# image latent inputs are modified in place (patchified and batch-expanded)
|
| 335 |
+
for input_param in self._image_latent_inputs:
|
| 336 |
+
outputs.append(
|
| 337 |
+
OutputParam(
|
| 338 |
+
name=input_param.name,
|
| 339 |
+
type_hint=input_param.type_hint,
|
| 340 |
+
description=input_param.description + " (patchified and batch-expanded)",
|
| 341 |
+
)
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
# additional batch inputs (batch-expanded only)
|
| 345 |
+
for input_param in self._additional_batch_inputs:
|
| 346 |
+
outputs.append(
|
| 347 |
+
OutputParam(
|
| 348 |
+
name=input_param.name,
|
| 349 |
+
type_hint=input_param.type_hint,
|
| 350 |
+
description=input_param.description + " (batch-expanded)",
|
| 351 |
+
)
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
return outputs
|
| 355 |
+
|
| 356 |
+
def __call__(self, components: Krea2ModularPipeline, state: PipelineState) -> PipelineState:
|
| 357 |
+
block_state = self.get_block_state(state)
|
| 358 |
+
|
| 359 |
+
# Process image latent inputs
|
| 360 |
+
for input_param in self._image_latent_inputs:
|
| 361 |
+
image_latent_input_name = input_param.name
|
| 362 |
+
image_latent_tensor = getattr(block_state, image_latent_input_name)
|
| 363 |
+
if image_latent_tensor is None:
|
| 364 |
+
continue
|
| 365 |
+
|
| 366 |
+
# 1. Calculate height/width from latents and update if not provided
|
| 367 |
+
height, width = calculate_dimension_from_latents(image_latent_tensor, components.vae_scale_factor)
|
| 368 |
+
block_state.height = block_state.height or height
|
| 369 |
+
block_state.width = block_state.width or width
|
| 370 |
+
|
| 371 |
+
if not hasattr(block_state, "image_height"):
|
| 372 |
+
block_state.image_height = height
|
| 373 |
+
if not hasattr(block_state, "image_width"):
|
| 374 |
+
block_state.image_width = width
|
| 375 |
+
|
| 376 |
+
# 2. Patchify
|
| 377 |
+
image_latent_tensor = components.pachifier.pack_latents(image_latent_tensor)
|
| 378 |
+
|
| 379 |
+
# 3. Expand batch size
|
| 380 |
+
image_latent_tensor = repeat_tensor_to_batch_size(
|
| 381 |
+
input_name=image_latent_input_name,
|
| 382 |
+
input_tensor=image_latent_tensor,
|
| 383 |
+
num_images_per_prompt=block_state.num_images_per_prompt,
|
| 384 |
+
batch_size=block_state.batch_size,
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
setattr(block_state, image_latent_input_name, image_latent_tensor)
|
| 388 |
+
|
| 389 |
+
# Process additional batch inputs (only batch expansion)
|
| 390 |
+
for input_param in self._additional_batch_inputs:
|
| 391 |
+
input_name = input_param.name
|
| 392 |
+
input_tensor = getattr(block_state, input_name)
|
| 393 |
+
if input_tensor is None:
|
| 394 |
+
continue
|
| 395 |
+
|
| 396 |
+
input_tensor = repeat_tensor_to_batch_size(
|
| 397 |
+
input_name=input_name,
|
| 398 |
+
input_tensor=input_tensor,
|
| 399 |
+
num_images_per_prompt=block_state.num_images_per_prompt,
|
| 400 |
+
batch_size=block_state.batch_size,
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
setattr(block_state, input_name, input_tensor)
|
| 404 |
+
|
| 405 |
+
self.set_block_state(state, block_state)
|
| 406 |
+
return components, state
|
modular_blocks_krea2.py
ADDED
|
@@ -0,0 +1,879 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
from diffusers.utils import logging
|
| 18 |
+
from diffusers.modular_pipelines.modular_pipeline import AutoPipelineBlocks, SequentialPipelineBlocks
|
| 19 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import InputParam, InsertableDict, OutputParam
|
| 20 |
+
from .before_denoise import (
|
| 21 |
+
Krea2CreateMaskLatentsStep,
|
| 22 |
+
Krea2PrepareLatentsStep,
|
| 23 |
+
Krea2PrepareLatentsWithStrengthStep,
|
| 24 |
+
Krea2RoPEInputsStep,
|
| 25 |
+
Krea2SetTimestepsStep,
|
| 26 |
+
Krea2SetTimestepsWithStrengthStep,
|
| 27 |
+
)
|
| 28 |
+
from .decoders import (
|
| 29 |
+
Krea2AfterDenoiseStep,
|
| 30 |
+
Krea2DecoderStep,
|
| 31 |
+
Krea2InpaintProcessImagesOutputStep,
|
| 32 |
+
Krea2ProcessImagesOutputStep,
|
| 33 |
+
)
|
| 34 |
+
from .denoise import (
|
| 35 |
+
Krea2DenoiseStep,
|
| 36 |
+
Krea2InpaintDenoiseStep,
|
| 37 |
+
)
|
| 38 |
+
from .encoders import (
|
| 39 |
+
Krea2InpaintProcessImagesInputStep,
|
| 40 |
+
Krea2ProcessImagesInputStep,
|
| 41 |
+
Krea2TextEncoderStep,
|
| 42 |
+
Krea2VaeEncoderStep,
|
| 43 |
+
)
|
| 44 |
+
from .inputs import (
|
| 45 |
+
Krea2AdditionalInputsStep,
|
| 46 |
+
Krea2TextInputsStep,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
logger = logging.get_logger(__name__)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ====================
|
| 54 |
+
# 1. TEXT ENCODER
|
| 55 |
+
# ====================
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# auto_docstring
|
| 59 |
+
class Krea2AutoTextEncoderStep(AutoPipelineBlocks):
|
| 60 |
+
"""
|
| 61 |
+
Text encoder step that encodes the text prompt into a text embedding. This is an auto pipeline block.
|
| 62 |
+
- `Krea2TextEncoderStep` (text_encoder) is used when `prompt` is provided.
|
| 63 |
+
- if `prompt` is not provided, step will be skipped.
|
| 64 |
+
|
| 65 |
+
Components:
|
| 66 |
+
text_encoder (`Qwen3VLModel`): The text encoder to use tokenizer (`Qwen2Tokenizer`): The tokenizer to use
|
| 67 |
+
guider (`ClassifierFreeGuidance`)
|
| 68 |
+
|
| 69 |
+
Inputs:
|
| 70 |
+
prompt (`str`, *optional*):
|
| 71 |
+
The prompt or prompts to guide image generation.
|
| 72 |
+
negative_prompt (`str`, *optional*):
|
| 73 |
+
The prompt or prompts not to guide the image generation.
|
| 74 |
+
max_sequence_length (`int`, *optional*, defaults to 512):
|
| 75 |
+
Maximum sequence length for prompt encoding.
|
| 76 |
+
|
| 77 |
+
Outputs:
|
| 78 |
+
prompt_embeds (`Tensor`):
|
| 79 |
+
The prompt embeddings.
|
| 80 |
+
prompt_embeds_mask (`Tensor`):
|
| 81 |
+
The encoder attention mask.
|
| 82 |
+
negative_prompt_embeds (`Tensor`):
|
| 83 |
+
The negative prompt embeddings.
|
| 84 |
+
negative_prompt_embeds_mask (`Tensor`):
|
| 85 |
+
The negative prompt embeddings mask.
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
model_name = "krea2"
|
| 89 |
+
block_classes = [Krea2TextEncoderStep()]
|
| 90 |
+
block_names = ["text_encoder"]
|
| 91 |
+
block_trigger_inputs = ["prompt"]
|
| 92 |
+
|
| 93 |
+
@property
|
| 94 |
+
def description(self) -> str:
|
| 95 |
+
return (
|
| 96 |
+
"Text encoder step that encodes the text prompt into a text embedding. This is an auto pipeline block.\n"
|
| 97 |
+
" - `Krea2TextEncoderStep` (text_encoder) is used when `prompt` is provided.\n"
|
| 98 |
+
" - if `prompt` is not provided, step will be skipped."
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# ====================
|
| 103 |
+
# 2. VAE ENCODER
|
| 104 |
+
# ====================
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# auto_docstring
|
| 108 |
+
class Krea2InpaintVaeEncoderStep(SequentialPipelineBlocks):
|
| 109 |
+
"""
|
| 110 |
+
This step is used for processing image and mask inputs for inpainting tasks. It:
|
| 111 |
+
- Resizes the image to the target size, based on `height` and `width`.
|
| 112 |
+
- Processes and updates `image` and `mask_image`.
|
| 113 |
+
- Creates `image_latents`.
|
| 114 |
+
|
| 115 |
+
Components:
|
| 116 |
+
image_mask_processor (`InpaintProcessor`) vae (`AutoencoderKLQwenImage`)
|
| 117 |
+
|
| 118 |
+
Inputs:
|
| 119 |
+
mask_image (`Image`):
|
| 120 |
+
Mask image for inpainting.
|
| 121 |
+
image (`Image | list`):
|
| 122 |
+
Reference image(s) for denoising. Can be a single image or list of images.
|
| 123 |
+
height (`int`, *optional*):
|
| 124 |
+
The height in pixels of the generated image.
|
| 125 |
+
width (`int`, *optional*):
|
| 126 |
+
The width in pixels of the generated image.
|
| 127 |
+
padding_mask_crop (`int`, *optional*):
|
| 128 |
+
Padding for mask cropping in inpainting.
|
| 129 |
+
generator (`Generator`, *optional*):
|
| 130 |
+
Torch generator for deterministic generation.
|
| 131 |
+
|
| 132 |
+
Outputs:
|
| 133 |
+
processed_image (`Tensor`):
|
| 134 |
+
The processed image
|
| 135 |
+
processed_mask_image (`Tensor`):
|
| 136 |
+
The processed mask image
|
| 137 |
+
mask_overlay_kwargs (`dict`):
|
| 138 |
+
The kwargs for the postprocess step to apply the mask overlay
|
| 139 |
+
image_latents (`Tensor`):
|
| 140 |
+
The latent representation of the input image.
|
| 141 |
+
"""
|
| 142 |
+
|
| 143 |
+
model_name = "krea2"
|
| 144 |
+
block_classes = [Krea2InpaintProcessImagesInputStep(), Krea2VaeEncoderStep()]
|
| 145 |
+
block_names = ["preprocess", "encode"]
|
| 146 |
+
|
| 147 |
+
@property
|
| 148 |
+
def description(self) -> str:
|
| 149 |
+
return (
|
| 150 |
+
"This step is used for processing image and mask inputs for inpainting tasks. It:\n"
|
| 151 |
+
" - Resizes the image to the target size, based on `height` and `width`.\n"
|
| 152 |
+
" - Processes and updates `image` and `mask_image`.\n"
|
| 153 |
+
" - Creates `image_latents`."
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# auto_docstring
|
| 158 |
+
class Krea2Img2ImgVaeEncoderStep(SequentialPipelineBlocks):
|
| 159 |
+
"""
|
| 160 |
+
Vae encoder step that preprocess and encode the image inputs into their latent representations.
|
| 161 |
+
|
| 162 |
+
Components:
|
| 163 |
+
image_processor (`VaeImageProcessor`) vae (`AutoencoderKLQwenImage`)
|
| 164 |
+
|
| 165 |
+
Inputs:
|
| 166 |
+
image (`Image | list`):
|
| 167 |
+
Reference image(s) for denoising. Can be a single image or list of images.
|
| 168 |
+
height (`int`, *optional*):
|
| 169 |
+
The height in pixels of the generated image.
|
| 170 |
+
width (`int`, *optional*):
|
| 171 |
+
The width in pixels of the generated image.
|
| 172 |
+
generator (`Generator`, *optional*):
|
| 173 |
+
Torch generator for deterministic generation.
|
| 174 |
+
|
| 175 |
+
Outputs:
|
| 176 |
+
processed_image (`Tensor`):
|
| 177 |
+
The processed image
|
| 178 |
+
image_latents (`Tensor`):
|
| 179 |
+
The latent representation of the input image.
|
| 180 |
+
"""
|
| 181 |
+
|
| 182 |
+
model_name = "krea2"
|
| 183 |
+
|
| 184 |
+
block_classes = [Krea2ProcessImagesInputStep(), Krea2VaeEncoderStep()]
|
| 185 |
+
block_names = ["preprocess", "encode"]
|
| 186 |
+
|
| 187 |
+
@property
|
| 188 |
+
def description(self) -> str:
|
| 189 |
+
return "Vae encoder step that preprocess and encode the image inputs into their latent representations."
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class Krea2AutoVaeEncoderStep(AutoPipelineBlocks):
|
| 193 |
+
model_name = "krea2"
|
| 194 |
+
block_classes = [Krea2InpaintVaeEncoderStep, Krea2Img2ImgVaeEncoderStep]
|
| 195 |
+
block_names = ["inpaint", "img2img"]
|
| 196 |
+
block_trigger_inputs = ["mask_image", "image"]
|
| 197 |
+
|
| 198 |
+
@property
|
| 199 |
+
def description(self):
|
| 200 |
+
return (
|
| 201 |
+
"Vae encoder step that encode the image inputs into their latent representations.\n"
|
| 202 |
+
+ "This is an auto pipeline block.\n"
|
| 203 |
+
+ " - `Krea2InpaintVaeEncoderStep` (inpaint) is used when `mask_image` is provided.\n"
|
| 204 |
+
+ " - `Krea2Img2ImgVaeEncoderStep` (img2img) is used when `image` is provided.\n"
|
| 205 |
+
+ " - if `mask_image` or `image` is not provided, step will be skipped."
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# ====================
|
| 210 |
+
# 3. DENOISE (input -> prepare_latents -> set_timesteps -> prepare_rope_inputs -> denoise -> after_denoise)
|
| 211 |
+
# ====================
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
# assemble input steps
|
| 215 |
+
# auto_docstring
|
| 216 |
+
class Krea2Img2ImgInputStep(SequentialPipelineBlocks):
|
| 217 |
+
"""
|
| 218 |
+
Input step that prepares the inputs for the img2img denoising step. It:
|
| 219 |
+
- make sure the text embeddings have consistent batch size as well as the additional inputs (`image_latents`).
|
| 220 |
+
- update height/width based `image_latents`, patchify `image_latents`.
|
| 221 |
+
|
| 222 |
+
Components:
|
| 223 |
+
pachifier (`Krea2Pachifier`)
|
| 224 |
+
|
| 225 |
+
Inputs:
|
| 226 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
| 227 |
+
The number of images to generate per prompt.
|
| 228 |
+
prompt_embeds (`Tensor`):
|
| 229 |
+
text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 230 |
+
prompt_embeds_mask (`Tensor`):
|
| 231 |
+
mask for the text embeddings. Can be generated from text_encoder step.
|
| 232 |
+
negative_prompt_embeds (`Tensor`, *optional*):
|
| 233 |
+
negative text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 234 |
+
negative_prompt_embeds_mask (`Tensor`, *optional*):
|
| 235 |
+
mask for the negative text embeddings. Can be generated from text_encoder step.
|
| 236 |
+
height (`int`, *optional*):
|
| 237 |
+
The height in pixels of the generated image.
|
| 238 |
+
width (`int`, *optional*):
|
| 239 |
+
The width in pixels of the generated image.
|
| 240 |
+
image_latents (`Tensor`):
|
| 241 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step.
|
| 242 |
+
|
| 243 |
+
Outputs:
|
| 244 |
+
batch_size (`int`):
|
| 245 |
+
The batch size of the prompt embeddings
|
| 246 |
+
dtype (`dtype`):
|
| 247 |
+
The data type of the prompt embeddings
|
| 248 |
+
prompt_embeds (`Tensor`):
|
| 249 |
+
The prompt embeddings. (batch-expanded)
|
| 250 |
+
prompt_embeds_mask (`Tensor`):
|
| 251 |
+
The encoder attention mask. (batch-expanded)
|
| 252 |
+
negative_prompt_embeds (`Tensor`):
|
| 253 |
+
The negative prompt embeddings. (batch-expanded)
|
| 254 |
+
negative_prompt_embeds_mask (`Tensor`):
|
| 255 |
+
The negative prompt embeddings mask. (batch-expanded)
|
| 256 |
+
image_height (`int`):
|
| 257 |
+
The image height calculated from the image latents dimension
|
| 258 |
+
image_width (`int`):
|
| 259 |
+
The image width calculated from the image latents dimension
|
| 260 |
+
height (`int`):
|
| 261 |
+
if not provided, updated to image height
|
| 262 |
+
width (`int`):
|
| 263 |
+
if not provided, updated to image width
|
| 264 |
+
image_latents (`Tensor`):
|
| 265 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step. (patchified and
|
| 266 |
+
batch-expanded)
|
| 267 |
+
"""
|
| 268 |
+
|
| 269 |
+
model_name = "krea2"
|
| 270 |
+
block_classes = [Krea2TextInputsStep(), Krea2AdditionalInputsStep()]
|
| 271 |
+
block_names = ["text_inputs", "additional_inputs"]
|
| 272 |
+
|
| 273 |
+
@property
|
| 274 |
+
def description(self):
|
| 275 |
+
return (
|
| 276 |
+
"Input step that prepares the inputs for the img2img denoising step. It:\n"
|
| 277 |
+
" - make sure the text embeddings have consistent batch size as well as the additional inputs (`image_latents`).\n"
|
| 278 |
+
" - update height/width based `image_latents`, patchify `image_latents`."
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
# auto_docstring
|
| 283 |
+
class Krea2InpaintInputStep(SequentialPipelineBlocks):
|
| 284 |
+
"""
|
| 285 |
+
Input step that prepares the inputs for the inpainting denoising step. It:
|
| 286 |
+
- make sure the text embeddings have consistent batch size as well as the additional inputs (`image_latents` and
|
| 287 |
+
`processed_mask_image`).
|
| 288 |
+
- update height/width based `image_latents`, patchify `image_latents`.
|
| 289 |
+
|
| 290 |
+
Components:
|
| 291 |
+
pachifier (`Krea2Pachifier`)
|
| 292 |
+
|
| 293 |
+
Inputs:
|
| 294 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
| 295 |
+
The number of images to generate per prompt.
|
| 296 |
+
prompt_embeds (`Tensor`):
|
| 297 |
+
text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 298 |
+
prompt_embeds_mask (`Tensor`):
|
| 299 |
+
mask for the text embeddings. Can be generated from text_encoder step.
|
| 300 |
+
negative_prompt_embeds (`Tensor`, *optional*):
|
| 301 |
+
negative text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 302 |
+
negative_prompt_embeds_mask (`Tensor`, *optional*):
|
| 303 |
+
mask for the negative text embeddings. Can be generated from text_encoder step.
|
| 304 |
+
height (`int`, *optional*):
|
| 305 |
+
The height in pixels of the generated image.
|
| 306 |
+
width (`int`, *optional*):
|
| 307 |
+
The width in pixels of the generated image.
|
| 308 |
+
image_latents (`Tensor`, *optional*):
|
| 309 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step.
|
| 310 |
+
processed_mask_image (`Tensor`, *optional*):
|
| 311 |
+
The processed mask image
|
| 312 |
+
|
| 313 |
+
Outputs:
|
| 314 |
+
batch_size (`int`):
|
| 315 |
+
The batch size of the prompt embeddings
|
| 316 |
+
dtype (`dtype`):
|
| 317 |
+
The data type of the prompt embeddings
|
| 318 |
+
prompt_embeds (`Tensor`):
|
| 319 |
+
The prompt embeddings. (batch-expanded)
|
| 320 |
+
prompt_embeds_mask (`Tensor`):
|
| 321 |
+
The encoder attention mask. (batch-expanded)
|
| 322 |
+
negative_prompt_embeds (`Tensor`):
|
| 323 |
+
The negative prompt embeddings. (batch-expanded)
|
| 324 |
+
negative_prompt_embeds_mask (`Tensor`):
|
| 325 |
+
The negative prompt embeddings mask. (batch-expanded)
|
| 326 |
+
image_height (`int`):
|
| 327 |
+
The image height calculated from the image latents dimension
|
| 328 |
+
image_width (`int`):
|
| 329 |
+
The image width calculated from the image latents dimension
|
| 330 |
+
height (`int`):
|
| 331 |
+
if not provided, updated to image height
|
| 332 |
+
width (`int`):
|
| 333 |
+
if not provided, updated to image width
|
| 334 |
+
image_latents (`Tensor`):
|
| 335 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step. (patchified and
|
| 336 |
+
batch-expanded)
|
| 337 |
+
processed_mask_image (`Tensor`):
|
| 338 |
+
The processed mask image (batch-expanded)
|
| 339 |
+
"""
|
| 340 |
+
|
| 341 |
+
model_name = "krea2"
|
| 342 |
+
block_classes = [
|
| 343 |
+
Krea2TextInputsStep(),
|
| 344 |
+
Krea2AdditionalInputsStep(
|
| 345 |
+
additional_batch_inputs=[
|
| 346 |
+
InputParam(name="processed_mask_image", type_hint=torch.Tensor, description="The processed mask image")
|
| 347 |
+
]
|
| 348 |
+
),
|
| 349 |
+
]
|
| 350 |
+
block_names = ["text_inputs", "additional_inputs"]
|
| 351 |
+
|
| 352 |
+
@property
|
| 353 |
+
def description(self):
|
| 354 |
+
return (
|
| 355 |
+
"Input step that prepares the inputs for the inpainting denoising step. It:\n"
|
| 356 |
+
" - make sure the text embeddings have consistent batch size as well as the additional inputs (`image_latents` and `processed_mask_image`).\n"
|
| 357 |
+
" - update height/width based `image_latents`, patchify `image_latents`."
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
# assemble prepare latents steps
|
| 362 |
+
# auto_docstring
|
| 363 |
+
class Krea2InpaintPrepareLatentsStep(SequentialPipelineBlocks):
|
| 364 |
+
"""
|
| 365 |
+
This step prepares the latents/image_latents and mask inputs for the inpainting denoising step. It:
|
| 366 |
+
- Add noise to the image latents to create the latents input for the denoiser.
|
| 367 |
+
- Create the patchified latents `mask` based on the processed mask image.
|
| 368 |
+
|
| 369 |
+
Components:
|
| 370 |
+
scheduler (`FlowMatchEulerDiscreteScheduler`) pachifier (`Krea2Pachifier`)
|
| 371 |
+
|
| 372 |
+
Inputs:
|
| 373 |
+
latents (`Tensor`):
|
| 374 |
+
The initial random noised, can be generated in prepare latent step.
|
| 375 |
+
image_latents (`Tensor`):
|
| 376 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step. (Can be
|
| 377 |
+
generated from vae encoder and updated in input step.)
|
| 378 |
+
timesteps (`Tensor`):
|
| 379 |
+
The timesteps to use for the denoising process. Can be generated in set_timesteps step.
|
| 380 |
+
processed_mask_image (`Tensor`):
|
| 381 |
+
The processed mask to use for the inpainting process.
|
| 382 |
+
height (`int`):
|
| 383 |
+
The height in pixels of the generated image.
|
| 384 |
+
width (`int`):
|
| 385 |
+
The width in pixels of the generated image.
|
| 386 |
+
dtype (`dtype`, *optional*, defaults to torch.float32):
|
| 387 |
+
The dtype of the model inputs, can be generated in input step.
|
| 388 |
+
|
| 389 |
+
Outputs:
|
| 390 |
+
initial_noise (`Tensor`):
|
| 391 |
+
The initial random noised used for inpainting denoising.
|
| 392 |
+
latents (`Tensor`):
|
| 393 |
+
The scaled noisy latents to use for inpainting/image-to-image denoising.
|
| 394 |
+
mask (`Tensor`):
|
| 395 |
+
The mask to use for the inpainting process.
|
| 396 |
+
"""
|
| 397 |
+
|
| 398 |
+
model_name = "krea2"
|
| 399 |
+
block_classes = [Krea2PrepareLatentsWithStrengthStep(), Krea2CreateMaskLatentsStep()]
|
| 400 |
+
block_names = ["add_noise_to_latents", "create_mask_latents"]
|
| 401 |
+
|
| 402 |
+
@property
|
| 403 |
+
def description(self) -> str:
|
| 404 |
+
return (
|
| 405 |
+
"This step prepares the latents/image_latents and mask inputs for the inpainting denoising step. It:\n"
|
| 406 |
+
" - Add noise to the image latents to create the latents input for the denoiser.\n"
|
| 407 |
+
" - Create the patchified latents `mask` based on the processed mask image.\n"
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
# assemble denoising steps
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
# Krea 2 (text2image)
|
| 415 |
+
# auto_docstring
|
| 416 |
+
class Krea2CoreDenoiseStep(SequentialPipelineBlocks):
|
| 417 |
+
"""
|
| 418 |
+
step that denoise noise into image for text2image task. It includes the denoise loop, as well as prepare the inputs
|
| 419 |
+
(timesteps, latents, rope inputs etc.).
|
| 420 |
+
|
| 421 |
+
Components:
|
| 422 |
+
pachifier (`Krea2Pachifier`) scheduler (`FlowMatchEulerDiscreteScheduler`) guider (`ClassifierFreeGuidance`)
|
| 423 |
+
transformer (`Krea2Transformer2DModel`)
|
| 424 |
+
|
| 425 |
+
Inputs:
|
| 426 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
| 427 |
+
The number of images to generate per prompt.
|
| 428 |
+
prompt_embeds (`Tensor`):
|
| 429 |
+
text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 430 |
+
prompt_embeds_mask (`Tensor`):
|
| 431 |
+
mask for the text embeddings. Can be generated from text_encoder step.
|
| 432 |
+
negative_prompt_embeds (`Tensor`, *optional*):
|
| 433 |
+
negative text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 434 |
+
negative_prompt_embeds_mask (`Tensor`, *optional*):
|
| 435 |
+
mask for the negative text embeddings. Can be generated from text_encoder step.
|
| 436 |
+
latents (`Tensor`, *optional*):
|
| 437 |
+
Pre-generated noisy latents for image generation.
|
| 438 |
+
height (`int`, *optional*):
|
| 439 |
+
The height in pixels of the generated image.
|
| 440 |
+
width (`int`, *optional*):
|
| 441 |
+
The width in pixels of the generated image.
|
| 442 |
+
generator (`Generator`, *optional*):
|
| 443 |
+
Torch generator for deterministic generation.
|
| 444 |
+
num_inference_steps (`int`, *optional*, defaults to 28):
|
| 445 |
+
The number of denoising steps.
|
| 446 |
+
sigmas (`list`, *optional*):
|
| 447 |
+
Custom sigmas for the denoising process.
|
| 448 |
+
mu (`float`, *optional*):
|
| 449 |
+
Fixed timestep shift for the scheduler. Pass `1.15` for the few-step distilled (TDM/turbo) checkpoint; if
|
| 450 |
+
not provided, computed from the image sequence length (base checkpoint behavior).
|
| 451 |
+
**denoiser_input_fields (`None`, *optional*):
|
| 452 |
+
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
|
| 453 |
+
|
| 454 |
+
Outputs:
|
| 455 |
+
latents (`Tensor`):
|
| 456 |
+
Denoised latents.
|
| 457 |
+
"""
|
| 458 |
+
|
| 459 |
+
model_name = "krea2"
|
| 460 |
+
block_classes = [
|
| 461 |
+
Krea2TextInputsStep(),
|
| 462 |
+
Krea2PrepareLatentsStep(),
|
| 463 |
+
Krea2SetTimestepsStep(),
|
| 464 |
+
Krea2RoPEInputsStep(),
|
| 465 |
+
Krea2DenoiseStep(),
|
| 466 |
+
Krea2AfterDenoiseStep(),
|
| 467 |
+
]
|
| 468 |
+
block_names = [
|
| 469 |
+
"input",
|
| 470 |
+
"prepare_latents",
|
| 471 |
+
"set_timesteps",
|
| 472 |
+
"prepare_rope_inputs",
|
| 473 |
+
"denoise",
|
| 474 |
+
"after_denoise",
|
| 475 |
+
]
|
| 476 |
+
|
| 477 |
+
@property
|
| 478 |
+
def description(self):
|
| 479 |
+
return "step that denoise noise into image for text2image task. It includes the denoise loop, as well as prepare the inputs (timesteps, latents, rope inputs etc.)."
|
| 480 |
+
|
| 481 |
+
@property
|
| 482 |
+
def outputs(self):
|
| 483 |
+
return [
|
| 484 |
+
OutputParam.template("latents"),
|
| 485 |
+
]
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
# Krea 2 (inpainting)
|
| 489 |
+
# auto_docstring
|
| 490 |
+
class Krea2InpaintCoreDenoiseStep(SequentialPipelineBlocks):
|
| 491 |
+
"""
|
| 492 |
+
step that denoise noise into image for inpaint task. It includes the denoise loop, as well as prepare the inputs
|
| 493 |
+
(timesteps, latents, rope inputs etc.).
|
| 494 |
+
|
| 495 |
+
Components:
|
| 496 |
+
pachifier (`Krea2Pachifier`) scheduler (`FlowMatchEulerDiscreteScheduler`) guider (`ClassifierFreeGuidance`)
|
| 497 |
+
transformer (`Krea2Transformer2DModel`)
|
| 498 |
+
|
| 499 |
+
Inputs:
|
| 500 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
| 501 |
+
The number of images to generate per prompt.
|
| 502 |
+
prompt_embeds (`Tensor`):
|
| 503 |
+
text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 504 |
+
prompt_embeds_mask (`Tensor`):
|
| 505 |
+
mask for the text embeddings. Can be generated from text_encoder step.
|
| 506 |
+
negative_prompt_embeds (`Tensor`, *optional*):
|
| 507 |
+
negative text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 508 |
+
negative_prompt_embeds_mask (`Tensor`, *optional*):
|
| 509 |
+
mask for the negative text embeddings. Can be generated from text_encoder step.
|
| 510 |
+
height (`int`, *optional*):
|
| 511 |
+
The height in pixels of the generated image.
|
| 512 |
+
width (`int`, *optional*):
|
| 513 |
+
The width in pixels of the generated image.
|
| 514 |
+
image_latents (`Tensor`, *optional*):
|
| 515 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step.
|
| 516 |
+
processed_mask_image (`Tensor`, *optional*):
|
| 517 |
+
The processed mask image
|
| 518 |
+
latents (`Tensor`, *optional*):
|
| 519 |
+
Pre-generated noisy latents for image generation.
|
| 520 |
+
generator (`Generator`, *optional*):
|
| 521 |
+
Torch generator for deterministic generation.
|
| 522 |
+
num_inference_steps (`int`, *optional*, defaults to 28):
|
| 523 |
+
The number of denoising steps.
|
| 524 |
+
sigmas (`list`, *optional*):
|
| 525 |
+
Custom sigmas for the denoising process.
|
| 526 |
+
mu (`float`, *optional*):
|
| 527 |
+
Fixed timestep shift for the scheduler. Pass `1.15` for the few-step distilled (TDM/turbo) checkpoint; if
|
| 528 |
+
not provided, computed from the image sequence length (base checkpoint behavior).
|
| 529 |
+
strength (`float`, *optional*, defaults to 0.9):
|
| 530 |
+
Strength for img2img/inpainting.
|
| 531 |
+
**denoiser_input_fields (`None`, *optional*):
|
| 532 |
+
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
|
| 533 |
+
|
| 534 |
+
Outputs:
|
| 535 |
+
latents (`Tensor`):
|
| 536 |
+
Denoised latents.
|
| 537 |
+
"""
|
| 538 |
+
|
| 539 |
+
model_name = "krea2"
|
| 540 |
+
block_classes = [
|
| 541 |
+
Krea2InpaintInputStep(),
|
| 542 |
+
Krea2PrepareLatentsStep(),
|
| 543 |
+
Krea2SetTimestepsWithStrengthStep(),
|
| 544 |
+
Krea2InpaintPrepareLatentsStep(),
|
| 545 |
+
Krea2RoPEInputsStep(),
|
| 546 |
+
Krea2InpaintDenoiseStep(),
|
| 547 |
+
Krea2AfterDenoiseStep(),
|
| 548 |
+
]
|
| 549 |
+
block_names = [
|
| 550 |
+
"input",
|
| 551 |
+
"prepare_latents",
|
| 552 |
+
"set_timesteps",
|
| 553 |
+
"prepare_inpaint_latents",
|
| 554 |
+
"prepare_rope_inputs",
|
| 555 |
+
"denoise",
|
| 556 |
+
"after_denoise",
|
| 557 |
+
]
|
| 558 |
+
|
| 559 |
+
@property
|
| 560 |
+
def description(self):
|
| 561 |
+
return "step that denoise noise into image for inpaint task. It includes the denoise loop, as well as prepare the inputs (timesteps, latents, rope inputs etc.)."
|
| 562 |
+
|
| 563 |
+
@property
|
| 564 |
+
def outputs(self):
|
| 565 |
+
return [
|
| 566 |
+
OutputParam.template("latents"),
|
| 567 |
+
]
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
# Krea 2 (image2image)
|
| 571 |
+
# auto_docstring
|
| 572 |
+
class Krea2Img2ImgCoreDenoiseStep(SequentialPipelineBlocks):
|
| 573 |
+
"""
|
| 574 |
+
step that denoise noise into image for img2img task. It includes the denoise loop, as well as prepare the inputs
|
| 575 |
+
(timesteps, latents, rope inputs etc.).
|
| 576 |
+
|
| 577 |
+
Components:
|
| 578 |
+
pachifier (`Krea2Pachifier`) scheduler (`FlowMatchEulerDiscreteScheduler`) guider (`ClassifierFreeGuidance`)
|
| 579 |
+
transformer (`Krea2Transformer2DModel`)
|
| 580 |
+
|
| 581 |
+
Inputs:
|
| 582 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
| 583 |
+
The number of images to generate per prompt.
|
| 584 |
+
prompt_embeds (`Tensor`):
|
| 585 |
+
text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 586 |
+
prompt_embeds_mask (`Tensor`):
|
| 587 |
+
mask for the text embeddings. Can be generated from text_encoder step.
|
| 588 |
+
negative_prompt_embeds (`Tensor`, *optional*):
|
| 589 |
+
negative text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 590 |
+
negative_prompt_embeds_mask (`Tensor`, *optional*):
|
| 591 |
+
mask for the negative text embeddings. Can be generated from text_encoder step.
|
| 592 |
+
height (`int`, *optional*):
|
| 593 |
+
The height in pixels of the generated image.
|
| 594 |
+
width (`int`, *optional*):
|
| 595 |
+
The width in pixels of the generated image.
|
| 596 |
+
image_latents (`Tensor`):
|
| 597 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step.
|
| 598 |
+
latents (`Tensor`, *optional*):
|
| 599 |
+
Pre-generated noisy latents for image generation.
|
| 600 |
+
generator (`Generator`, *optional*):
|
| 601 |
+
Torch generator for deterministic generation.
|
| 602 |
+
num_inference_steps (`int`, *optional*, defaults to 28):
|
| 603 |
+
The number of denoising steps.
|
| 604 |
+
sigmas (`list`, *optional*):
|
| 605 |
+
Custom sigmas for the denoising process.
|
| 606 |
+
mu (`float`, *optional*):
|
| 607 |
+
Fixed timestep shift for the scheduler. Pass `1.15` for the few-step distilled (TDM/turbo) checkpoint; if
|
| 608 |
+
not provided, computed from the image sequence length (base checkpoint behavior).
|
| 609 |
+
strength (`float`, *optional*, defaults to 0.9):
|
| 610 |
+
Strength for img2img/inpainting.
|
| 611 |
+
**denoiser_input_fields (`None`, *optional*):
|
| 612 |
+
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
|
| 613 |
+
|
| 614 |
+
Outputs:
|
| 615 |
+
latents (`Tensor`):
|
| 616 |
+
Denoised latents.
|
| 617 |
+
"""
|
| 618 |
+
|
| 619 |
+
model_name = "krea2"
|
| 620 |
+
block_classes = [
|
| 621 |
+
Krea2Img2ImgInputStep(),
|
| 622 |
+
Krea2PrepareLatentsStep(),
|
| 623 |
+
Krea2SetTimestepsWithStrengthStep(),
|
| 624 |
+
Krea2PrepareLatentsWithStrengthStep(),
|
| 625 |
+
Krea2RoPEInputsStep(),
|
| 626 |
+
Krea2DenoiseStep(),
|
| 627 |
+
Krea2AfterDenoiseStep(),
|
| 628 |
+
]
|
| 629 |
+
block_names = [
|
| 630 |
+
"input",
|
| 631 |
+
"prepare_latents",
|
| 632 |
+
"set_timesteps",
|
| 633 |
+
"prepare_img2img_latents",
|
| 634 |
+
"prepare_rope_inputs",
|
| 635 |
+
"denoise",
|
| 636 |
+
"after_denoise",
|
| 637 |
+
]
|
| 638 |
+
|
| 639 |
+
@property
|
| 640 |
+
def description(self):
|
| 641 |
+
return "step that denoise noise into image for img2img task. It includes the denoise loop, as well as prepare the inputs (timesteps, latents, rope inputs etc.)."
|
| 642 |
+
|
| 643 |
+
@property
|
| 644 |
+
def outputs(self):
|
| 645 |
+
return [
|
| 646 |
+
OutputParam.template("latents"),
|
| 647 |
+
]
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
# Auto denoise step for Krea 2
|
| 651 |
+
class Krea2AutoCoreDenoiseStep(AutoPipelineBlocks):
|
| 652 |
+
model_name = "krea2"
|
| 653 |
+
block_classes = [
|
| 654 |
+
Krea2InpaintCoreDenoiseStep,
|
| 655 |
+
Krea2Img2ImgCoreDenoiseStep,
|
| 656 |
+
Krea2CoreDenoiseStep,
|
| 657 |
+
]
|
| 658 |
+
block_names = [
|
| 659 |
+
"inpaint",
|
| 660 |
+
"img2img",
|
| 661 |
+
"text2image",
|
| 662 |
+
]
|
| 663 |
+
block_trigger_inputs = ["processed_mask_image", "image_latents", None]
|
| 664 |
+
|
| 665 |
+
@property
|
| 666 |
+
def description(self):
|
| 667 |
+
return (
|
| 668 |
+
"Core step that performs the denoising process. \n"
|
| 669 |
+
+ " - `Krea2InpaintCoreDenoiseStep` (inpaint) is used when `processed_mask_image` is provided.\n"
|
| 670 |
+
+ " - `Krea2Img2ImgCoreDenoiseStep` (img2img) is used when `image_latents` is provided.\n"
|
| 671 |
+
+ " - `Krea2CoreDenoiseStep` (text2image) is used otherwise.\n"
|
| 672 |
+
+ "This step support text-to-image, image-to-image, and inpainting tasks for Krea 2:\n"
|
| 673 |
+
+ " - for image-to-image generation, you need to provide `image_latents`\n"
|
| 674 |
+
+ " - for inpainting, you need to provide `processed_mask_image` and `image_latents`\n"
|
| 675 |
+
+ " - for text-to-image generation, all you need to provide is prompt embeddings"
|
| 676 |
+
)
|
| 677 |
+
|
| 678 |
+
@property
|
| 679 |
+
def outputs(self):
|
| 680 |
+
return [
|
| 681 |
+
OutputParam.template("latents"),
|
| 682 |
+
]
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
# ====================
|
| 686 |
+
# 4. DECODE
|
| 687 |
+
# ====================
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
# standard decode step works for most tasks except for inpaint
|
| 691 |
+
# auto_docstring
|
| 692 |
+
class Krea2DecodeStep(SequentialPipelineBlocks):
|
| 693 |
+
"""
|
| 694 |
+
Decode step that decodes the latents to images and postprocess the generated image.
|
| 695 |
+
|
| 696 |
+
Components:
|
| 697 |
+
vae (`AutoencoderKLQwenImage`) image_processor (`VaeImageProcessor`)
|
| 698 |
+
|
| 699 |
+
Inputs:
|
| 700 |
+
latents (`Tensor`):
|
| 701 |
+
The denoised latents to decode, can be generated in the denoise step and unpacked in the after denoise
|
| 702 |
+
step.
|
| 703 |
+
output_type (`str`, *optional*, defaults to pil):
|
| 704 |
+
Output format: 'pil', 'np', 'pt'.
|
| 705 |
+
|
| 706 |
+
Outputs:
|
| 707 |
+
images (`list`):
|
| 708 |
+
Generated images. (tensor output of the vae decoder.)
|
| 709 |
+
"""
|
| 710 |
+
|
| 711 |
+
model_name = "krea2"
|
| 712 |
+
block_classes = [Krea2DecoderStep(), Krea2ProcessImagesOutputStep()]
|
| 713 |
+
block_names = ["decode", "postprocess"]
|
| 714 |
+
|
| 715 |
+
@property
|
| 716 |
+
def description(self):
|
| 717 |
+
return "Decode step that decodes the latents to images and postprocess the generated image."
|
| 718 |
+
|
| 719 |
+
|
| 720 |
+
# Inpaint decode step
|
| 721 |
+
# auto_docstring
|
| 722 |
+
class Krea2InpaintDecodeStep(SequentialPipelineBlocks):
|
| 723 |
+
"""
|
| 724 |
+
Decode step that decodes the latents to images and postprocess the generated image, optionally apply the mask
|
| 725 |
+
overlay to the original image.
|
| 726 |
+
|
| 727 |
+
Components:
|
| 728 |
+
vae (`AutoencoderKLQwenImage`) image_mask_processor (`InpaintProcessor`)
|
| 729 |
+
|
| 730 |
+
Inputs:
|
| 731 |
+
latents (`Tensor`):
|
| 732 |
+
The denoised latents to decode, can be generated in the denoise step and unpacked in the after denoise
|
| 733 |
+
step.
|
| 734 |
+
output_type (`str`, *optional*, defaults to pil):
|
| 735 |
+
Output format: 'pil', 'np', 'pt'.
|
| 736 |
+
mask_overlay_kwargs (`dict`, *optional*):
|
| 737 |
+
The kwargs for the postprocess step to apply the mask overlay. generated in
|
| 738 |
+
Krea2InpaintProcessImagesInputStep.
|
| 739 |
+
|
| 740 |
+
Outputs:
|
| 741 |
+
images (`list`):
|
| 742 |
+
Generated images. (tensor output of the vae decoder.)
|
| 743 |
+
"""
|
| 744 |
+
|
| 745 |
+
model_name = "krea2"
|
| 746 |
+
block_classes = [Krea2DecoderStep(), Krea2InpaintProcessImagesOutputStep()]
|
| 747 |
+
block_names = ["decode", "postprocess"]
|
| 748 |
+
|
| 749 |
+
@property
|
| 750 |
+
def description(self):
|
| 751 |
+
return "Decode step that decodes the latents to images and postprocess the generated image, optionally apply the mask overlay to the original image."
|
| 752 |
+
|
| 753 |
+
|
| 754 |
+
# Auto decode step for Krea 2
|
| 755 |
+
class Krea2AutoDecodeStep(AutoPipelineBlocks):
|
| 756 |
+
model_name = "krea2"
|
| 757 |
+
block_classes = [Krea2InpaintDecodeStep, Krea2DecodeStep]
|
| 758 |
+
block_names = ["inpaint_decode", "decode"]
|
| 759 |
+
block_trigger_inputs = ["mask", None]
|
| 760 |
+
|
| 761 |
+
@property
|
| 762 |
+
def description(self):
|
| 763 |
+
return (
|
| 764 |
+
"Decode step that decode the latents into images. \n"
|
| 765 |
+
" This is an auto pipeline block that works for inpaint/text2image/img2img tasks.\n"
|
| 766 |
+
+ " - `Krea2InpaintDecodeStep` (inpaint_decode) is used when `mask` is provided.\n"
|
| 767 |
+
+ " - `Krea2DecodeStep` (decode) is used when `mask` is not provided.\n"
|
| 768 |
+
)
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
# ====================
|
| 772 |
+
# 5. AUTO BLOCKS & PRESETS
|
| 773 |
+
# ====================
|
| 774 |
+
AUTO_BLOCKS = InsertableDict(
|
| 775 |
+
[
|
| 776 |
+
("text_encoder", Krea2AutoTextEncoderStep()),
|
| 777 |
+
("vae_encoder", Krea2AutoVaeEncoderStep()),
|
| 778 |
+
("denoise", Krea2AutoCoreDenoiseStep()),
|
| 779 |
+
("decode", Krea2AutoDecodeStep()),
|
| 780 |
+
]
|
| 781 |
+
)
|
| 782 |
+
|
| 783 |
+
|
| 784 |
+
# auto_docstring
|
| 785 |
+
class Krea2AutoBlocks(SequentialPipelineBlocks):
|
| 786 |
+
"""
|
| 787 |
+
Auto Modular pipeline for text-to-image, image-to-image, and inpainting tasks using Krea 2.
|
| 788 |
+
|
| 789 |
+
Supported workflows:
|
| 790 |
+
- `text2image`: requires `prompt`
|
| 791 |
+
- `image2image`: requires `prompt`, `image`
|
| 792 |
+
- `inpainting`: requires `prompt`, `mask_image`, `image`
|
| 793 |
+
|
| 794 |
+
Components:
|
| 795 |
+
text_encoder (`Qwen3VLModel`): The text encoder to use tokenizer (`Qwen2Tokenizer`): The tokenizer to use
|
| 796 |
+
guider (`ClassifierFreeGuidance`) image_mask_processor (`InpaintProcessor`) vae (`AutoencoderKLQwenImage`)
|
| 797 |
+
image_processor (`VaeImageProcessor`) pachifier (`Krea2Pachifier`) scheduler
|
| 798 |
+
(`FlowMatchEulerDiscreteScheduler`) transformer (`Krea2Transformer2DModel`)
|
| 799 |
+
|
| 800 |
+
Inputs:
|
| 801 |
+
prompt (`str`, *optional*):
|
| 802 |
+
The prompt or prompts to guide image generation.
|
| 803 |
+
negative_prompt (`str`, *optional*):
|
| 804 |
+
The prompt or prompts not to guide the image generation.
|
| 805 |
+
max_sequence_length (`int`, *optional*, defaults to 512):
|
| 806 |
+
Maximum sequence length for prompt encoding.
|
| 807 |
+
mask_image (`Image`, *optional*):
|
| 808 |
+
Mask image for inpainting.
|
| 809 |
+
image (`Image | list`, *optional*):
|
| 810 |
+
Reference image(s) for denoising. Can be a single image or list of images.
|
| 811 |
+
height (`int`, *optional*):
|
| 812 |
+
The height in pixels of the generated image.
|
| 813 |
+
width (`int`, *optional*):
|
| 814 |
+
The width in pixels of the generated image.
|
| 815 |
+
padding_mask_crop (`int`, *optional*):
|
| 816 |
+
Padding for mask cropping in inpainting.
|
| 817 |
+
generator (`Generator`, *optional*):
|
| 818 |
+
Torch generator for deterministic generation.
|
| 819 |
+
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
| 820 |
+
The number of images to generate per prompt.
|
| 821 |
+
prompt_embeds (`Tensor`):
|
| 822 |
+
text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 823 |
+
prompt_embeds_mask (`Tensor`):
|
| 824 |
+
mask for the text embeddings. Can be generated from text_encoder step.
|
| 825 |
+
negative_prompt_embeds (`Tensor`, *optional*):
|
| 826 |
+
negative text embeddings used to guide the image generation. Can be generated from text_encoder step.
|
| 827 |
+
negative_prompt_embeds_mask (`Tensor`, *optional*):
|
| 828 |
+
mask for the negative text embeddings. Can be generated from text_encoder step.
|
| 829 |
+
image_latents (`Tensor`, *optional*):
|
| 830 |
+
image latents used to guide the image generation. Can be generated from vae_encoder step.
|
| 831 |
+
processed_mask_image (`Tensor`, *optional*):
|
| 832 |
+
The processed mask image
|
| 833 |
+
latents (`Tensor`):
|
| 834 |
+
Pre-generated noisy latents for image generation.
|
| 835 |
+
num_inference_steps (`int`):
|
| 836 |
+
The number of denoising steps.
|
| 837 |
+
sigmas (`list`, *optional*):
|
| 838 |
+
Custom sigmas for the denoising process.
|
| 839 |
+
mu (`float`, *optional*):
|
| 840 |
+
Fixed timestep shift for the scheduler. Pass `1.15` for the few-step distilled (TDM/turbo) checkpoint; if
|
| 841 |
+
not provided, computed from the image sequence length (base checkpoint behavior).
|
| 842 |
+
strength (`float`, *optional*, defaults to 0.9):
|
| 843 |
+
Strength for img2img/inpainting.
|
| 844 |
+
**denoiser_input_fields (`None`, *optional*):
|
| 845 |
+
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
|
| 846 |
+
output_type (`str`, *optional*, defaults to pil):
|
| 847 |
+
Output format: 'pil', 'np', 'pt'.
|
| 848 |
+
mask_overlay_kwargs (`dict`, *optional*):
|
| 849 |
+
The kwargs for the postprocess step to apply the mask overlay. generated in
|
| 850 |
+
Krea2InpaintProcessImagesInputStep.
|
| 851 |
+
|
| 852 |
+
Outputs:
|
| 853 |
+
images (`list`):
|
| 854 |
+
Generated images.
|
| 855 |
+
"""
|
| 856 |
+
|
| 857 |
+
model_name = "krea2"
|
| 858 |
+
|
| 859 |
+
block_classes = AUTO_BLOCKS.values()
|
| 860 |
+
block_names = AUTO_BLOCKS.keys()
|
| 861 |
+
|
| 862 |
+
# Workflow map defines the trigger conditions for each workflow.
|
| 863 |
+
# How to define:
|
| 864 |
+
# - Only include required inputs and trigger inputs (inputs that determine which blocks run)
|
| 865 |
+
# - currently, only supports `True` means the workflow triggers when the input is not None
|
| 866 |
+
|
| 867 |
+
_workflow_map = {
|
| 868 |
+
"text2image": {"prompt": True},
|
| 869 |
+
"image2image": {"prompt": True, "image": True},
|
| 870 |
+
"inpainting": {"prompt": True, "mask_image": True, "image": True},
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
@property
|
| 874 |
+
def description(self):
|
| 875 |
+
return "Auto Modular pipeline for text-to-image, image-to-image, and inpainting tasks using Krea 2."
|
| 876 |
+
|
| 877 |
+
@property
|
| 878 |
+
def outputs(self):
|
| 879 |
+
return [OutputParam.template("images")]
|
modular_blocks_krea2_edit.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from diffusers.utils import logging
|
| 16 |
+
from diffusers.modular_pipelines.modular_pipeline import SequentialPipelineBlocks
|
| 17 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import InsertableDict, OutputParam
|
| 18 |
+
from .before_denoise import (
|
| 19 |
+
Krea2EditRoPEInputsStep,
|
| 20 |
+
Krea2PrepareLatentsStep,
|
| 21 |
+
Krea2SetTimestepsStep,
|
| 22 |
+
)
|
| 23 |
+
from .decoders import Krea2AfterDenoiseStep
|
| 24 |
+
from .denoise import Krea2EditDenoiseStep
|
| 25 |
+
from .encoders import (
|
| 26 |
+
Krea2EditReferenceLatentsStep,
|
| 27 |
+
Krea2EditTextEncoderStep,
|
| 28 |
+
)
|
| 29 |
+
from .inputs import Krea2TextInputsStep
|
| 30 |
+
from .modular_blocks_krea2 import Krea2DecodeStep
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
logger = logging.get_logger(__name__)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Krea 2 (reference-image edit)
|
| 37 |
+
class Krea2EditCoreDenoiseStep(SequentialPipelineBlocks):
|
| 38 |
+
model_name = "krea2"
|
| 39 |
+
block_classes = [
|
| 40 |
+
Krea2TextInputsStep(),
|
| 41 |
+
Krea2PrepareLatentsStep(),
|
| 42 |
+
Krea2SetTimestepsStep(),
|
| 43 |
+
Krea2EditRoPEInputsStep(),
|
| 44 |
+
Krea2EditDenoiseStep(),
|
| 45 |
+
Krea2AfterDenoiseStep(),
|
| 46 |
+
]
|
| 47 |
+
block_names = [
|
| 48 |
+
"input",
|
| 49 |
+
"prepare_latents",
|
| 50 |
+
"set_timesteps",
|
| 51 |
+
"prepare_rope_inputs",
|
| 52 |
+
"denoise",
|
| 53 |
+
"after_denoise",
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
@property
|
| 57 |
+
def description(self):
|
| 58 |
+
return (
|
| 59 |
+
"step that denoises noise into an image for the reference-image edit task. It includes the denoise loop "
|
| 60 |
+
"(with the clean reference tokens appended each step), as well as preparing the inputs (timesteps, "
|
| 61 |
+
"latents, rope inputs etc.)."
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
@property
|
| 65 |
+
def outputs(self):
|
| 66 |
+
return [
|
| 67 |
+
OutputParam.template("latents"),
|
| 68 |
+
]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# Preset: reference-image edit. `image` carries the reference image(s); without it, use the text2image preset.
|
| 72 |
+
EDIT_BLOCKS = InsertableDict(
|
| 73 |
+
[
|
| 74 |
+
("text_encoder", Krea2EditTextEncoderStep()),
|
| 75 |
+
("reference_latents", Krea2EditReferenceLatentsStep()),
|
| 76 |
+
("denoise", Krea2EditCoreDenoiseStep()),
|
| 77 |
+
("decode", Krea2DecodeStep()),
|
| 78 |
+
]
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class Krea2EditBlocks(SequentialPipelineBlocks):
|
| 83 |
+
"""
|
| 84 |
+
Modular pipeline for the Krea 2 reference-image edit task.
|
| 85 |
+
|
| 86 |
+
Pass one or more reference images as `image`; they condition generation both through the Qwen3-VL text encoder
|
| 87 |
+
and as clean VAE latents appended to the transformer sequence at flow time t=0 (matching the Ostris AI-Toolkit
|
| 88 |
+
edit LoRAs). Load an edit/style LoRA into the transformer to control the effect.
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
model_name = "krea2"
|
| 92 |
+
block_classes = EDIT_BLOCKS.values()
|
| 93 |
+
block_names = EDIT_BLOCKS.keys()
|
| 94 |
+
|
| 95 |
+
@property
|
| 96 |
+
def description(self):
|
| 97 |
+
return "Auto Modular pipeline for the reference-image edit task using Krea 2. Requires `prompt` and `image`."
|
modular_pipeline.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 17 |
+
from diffusers.modular_pipelines.modular_pipeline import ModularPipeline
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Copied from diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImagePachifier with QwenImage->Krea2
|
| 21 |
+
class Krea2Pachifier(ConfigMixin):
|
| 22 |
+
"""
|
| 23 |
+
A class to pack and unpack latents for Krea2.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
config_name = "config.json"
|
| 27 |
+
|
| 28 |
+
@register_to_config
|
| 29 |
+
def __init__(self, patch_size: int = 2):
|
| 30 |
+
super().__init__()
|
| 31 |
+
|
| 32 |
+
def pack_latents(self, latents):
|
| 33 |
+
if latents.ndim != 4 and latents.ndim != 5:
|
| 34 |
+
raise ValueError(f"Latents must have 4 or 5 dimensions, but got {latents.ndim}")
|
| 35 |
+
|
| 36 |
+
if latents.ndim == 4:
|
| 37 |
+
latents = latents.unsqueeze(2)
|
| 38 |
+
|
| 39 |
+
batch_size, num_channels_latents, num_latent_frames, latent_height, latent_width = latents.shape
|
| 40 |
+
patch_size = self.config.patch_size
|
| 41 |
+
|
| 42 |
+
if latent_height % patch_size != 0 or latent_width % patch_size != 0:
|
| 43 |
+
raise ValueError(
|
| 44 |
+
f"Latent height and width must be divisible by {patch_size}, but got {latent_height} and {latent_width}"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
latents = latents.view(
|
| 48 |
+
batch_size,
|
| 49 |
+
num_channels_latents,
|
| 50 |
+
latent_height // patch_size,
|
| 51 |
+
patch_size,
|
| 52 |
+
latent_width // patch_size,
|
| 53 |
+
patch_size,
|
| 54 |
+
)
|
| 55 |
+
latents = latents.permute(
|
| 56 |
+
0, 2, 4, 1, 3, 5
|
| 57 |
+
) # Batch_size, num_patches_height, num_patches_width, num_channels_latents, patch_size, patch_size
|
| 58 |
+
latents = latents.reshape(
|
| 59 |
+
batch_size,
|
| 60 |
+
(latent_height // patch_size) * (latent_width // patch_size),
|
| 61 |
+
num_channels_latents * patch_size * patch_size,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
return latents
|
| 65 |
+
|
| 66 |
+
def unpack_latents(self, latents, height, width, vae_scale_factor=8):
|
| 67 |
+
if latents.ndim != 3:
|
| 68 |
+
raise ValueError(f"Latents must have 3 dimensions, but got {latents.ndim}")
|
| 69 |
+
|
| 70 |
+
batch_size, num_patches, channels = latents.shape
|
| 71 |
+
patch_size = self.config.patch_size
|
| 72 |
+
|
| 73 |
+
# VAE applies 8x compression on images but we must also account for packing which requires
|
| 74 |
+
# latent height and width to be divisible by 2.
|
| 75 |
+
height = patch_size * (int(height) // (vae_scale_factor * patch_size))
|
| 76 |
+
width = patch_size * (int(width) // (vae_scale_factor * patch_size))
|
| 77 |
+
|
| 78 |
+
latents = latents.view(
|
| 79 |
+
batch_size,
|
| 80 |
+
height // patch_size,
|
| 81 |
+
width // patch_size,
|
| 82 |
+
channels // (patch_size * patch_size),
|
| 83 |
+
patch_size,
|
| 84 |
+
patch_size,
|
| 85 |
+
)
|
| 86 |
+
latents = latents.permute(0, 3, 1, 4, 2, 5)
|
| 87 |
+
|
| 88 |
+
latents = latents.reshape(batch_size, channels // (patch_size * patch_size), 1, height, width)
|
| 89 |
+
|
| 90 |
+
return latents
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class Krea2ModularPipeline(ModularPipeline):
|
| 94 |
+
"""
|
| 95 |
+
A ModularPipeline for Krea 2.
|
| 96 |
+
|
| 97 |
+
> [!WARNING] > This is an experimental feature and is likely to change in the future.
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
default_blocks_name = "Krea2AutoBlocks"
|
| 101 |
+
|
| 102 |
+
@property
|
| 103 |
+
def default_height(self):
|
| 104 |
+
return self.default_sample_size * self.vae_scale_factor
|
| 105 |
+
|
| 106 |
+
@property
|
| 107 |
+
def default_width(self):
|
| 108 |
+
return self.default_sample_size * self.vae_scale_factor
|
| 109 |
+
|
| 110 |
+
@property
|
| 111 |
+
def default_sample_size(self):
|
| 112 |
+
return 128
|
| 113 |
+
|
| 114 |
+
@property
|
| 115 |
+
def vae_scale_factor(self):
|
| 116 |
+
vae_scale_factor = 8
|
| 117 |
+
if hasattr(self, "vae") and self.vae is not None:
|
| 118 |
+
vae_scale_factor = 2 ** len(self.vae.temperal_downsample)
|
| 119 |
+
return vae_scale_factor
|
| 120 |
+
|
| 121 |
+
@property
|
| 122 |
+
def num_channels_latents(self):
|
| 123 |
+
num_channels_latents = 16
|
| 124 |
+
if hasattr(self, "transformer") and self.transformer is not None:
|
| 125 |
+
num_channels_latents = self.transformer.config.in_channels // 4
|
| 126 |
+
return num_channels_latents
|
| 127 |
+
|
| 128 |
+
@property
|
| 129 |
+
def requires_unconditional_embeds(self):
|
| 130 |
+
requires_unconditional_embeds = False
|
| 131 |
+
|
| 132 |
+
if hasattr(self, "guider") and self.guider is not None:
|
| 133 |
+
requires_unconditional_embeds = self.guider._enabled and self.guider.num_conditions > 1
|
| 134 |
+
|
| 135 |
+
return requires_unconditional_embeds
|
transformer_krea2.py
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Krea AI and The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import inspect
|
| 16 |
+
import math
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn as nn
|
| 20 |
+
import torch.nn.functional as F
|
| 21 |
+
|
| 22 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 23 |
+
from diffusers.loaders import PeftAdapterMixin
|
| 24 |
+
from diffusers.utils import logging
|
| 25 |
+
from diffusers.utils.torch_utils import maybe_adjust_dtype_for_device
|
| 26 |
+
from diffusers.models.attention import AttentionMixin, AttentionModuleMixin
|
| 27 |
+
from diffusers.models.attention_dispatch import dispatch_attention_fn
|
| 28 |
+
from diffusers.models.embeddings import apply_rotary_emb, get_1d_rotary_pos_embed
|
| 29 |
+
from diffusers.models.modeling_outputs import Transformer2DModelOutput
|
| 30 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class Krea2RMSNorm(nn.Module):
|
| 37 |
+
"""RMSNorm with a zero-centered scale: the effective multiplier is `1 + weight`, matching the Krea 2 checkpoint
|
| 38 |
+
format. The activations are upcast so the normalization runs in float32; the scale weight is kept in float32 by
|
| 39 |
+
the model's `_keep_in_fp32_modules`."""
|
| 40 |
+
|
| 41 |
+
def __init__(self, dim: int, eps: float = 1e-5) -> None:
|
| 42 |
+
super().__init__()
|
| 43 |
+
self.dim = dim
|
| 44 |
+
self.eps = eps
|
| 45 |
+
self.weight = nn.Parameter(torch.zeros(dim))
|
| 46 |
+
|
| 47 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 48 |
+
dtype = hidden_states.dtype
|
| 49 |
+
hidden_states = F.rms_norm(hidden_states.float(), (self.dim,), weight=self.weight + 1.0, eps=self.eps)
|
| 50 |
+
return hidden_states.to(dtype)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class Krea2AttnProcessor:
|
| 54 |
+
_attention_backend = None
|
| 55 |
+
_parallel_config = None
|
| 56 |
+
|
| 57 |
+
def __call__(
|
| 58 |
+
self,
|
| 59 |
+
attn: "Krea2Attention",
|
| 60 |
+
hidden_states: torch.Tensor,
|
| 61 |
+
attention_mask: torch.Tensor | None = None,
|
| 62 |
+
image_rotary_emb: tuple[torch.Tensor, torch.Tensor] | None = None,
|
| 63 |
+
) -> torch.Tensor:
|
| 64 |
+
query = attn.to_q(hidden_states).unflatten(-1, (attn.num_heads, attn.head_dim))
|
| 65 |
+
key = attn.to_k(hidden_states).unflatten(-1, (attn.num_kv_heads, attn.head_dim))
|
| 66 |
+
value = attn.to_v(hidden_states).unflatten(-1, (attn.num_kv_heads, attn.head_dim))
|
| 67 |
+
gate = attn.to_gate(hidden_states)
|
| 68 |
+
|
| 69 |
+
query = attn.norm_q(query)
|
| 70 |
+
key = attn.norm_k(key)
|
| 71 |
+
|
| 72 |
+
if image_rotary_emb is not None:
|
| 73 |
+
query = apply_rotary_emb(query, image_rotary_emb, sequence_dim=1)
|
| 74 |
+
key = apply_rotary_emb(key, image_rotary_emb, sequence_dim=1)
|
| 75 |
+
|
| 76 |
+
hidden_states = dispatch_attention_fn(
|
| 77 |
+
query,
|
| 78 |
+
key,
|
| 79 |
+
value,
|
| 80 |
+
attn_mask=attention_mask,
|
| 81 |
+
enable_gqa=attn.num_heads != attn.num_kv_heads,
|
| 82 |
+
backend=self._attention_backend,
|
| 83 |
+
parallel_config=self._parallel_config,
|
| 84 |
+
)
|
| 85 |
+
hidden_states = hidden_states.flatten(2, 3)
|
| 86 |
+
hidden_states = hidden_states * torch.sigmoid(gate)
|
| 87 |
+
return attn.to_out[0](hidden_states)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class Krea2Attention(nn.Module, AttentionModuleMixin):
|
| 91 |
+
"""Self-attention with grouped-query projections, q/k RMSNorm, rotary embeddings and a sigmoid output gate."""
|
| 92 |
+
|
| 93 |
+
_default_processor_cls = Krea2AttnProcessor
|
| 94 |
+
_available_processors = [Krea2AttnProcessor]
|
| 95 |
+
|
| 96 |
+
def __init__(
|
| 97 |
+
self, hidden_size: int, num_heads: int, num_kv_heads: int | None = None, eps: float = 1e-5, processor=None
|
| 98 |
+
) -> None:
|
| 99 |
+
super().__init__()
|
| 100 |
+
if hidden_size % num_heads != 0:
|
| 101 |
+
raise ValueError(f"hidden_size={hidden_size} must be divisible by num_heads={num_heads}")
|
| 102 |
+
self.hidden_size = hidden_size
|
| 103 |
+
self.num_heads = num_heads
|
| 104 |
+
self.num_kv_heads = num_kv_heads if num_kv_heads is not None else num_heads
|
| 105 |
+
self.head_dim = hidden_size // num_heads
|
| 106 |
+
self.use_bias = False
|
| 107 |
+
|
| 108 |
+
self.to_q = nn.Linear(hidden_size, self.head_dim * self.num_heads, bias=False)
|
| 109 |
+
self.to_k = nn.Linear(hidden_size, self.head_dim * self.num_kv_heads, bias=False)
|
| 110 |
+
self.to_v = nn.Linear(hidden_size, self.head_dim * self.num_kv_heads, bias=False)
|
| 111 |
+
self.to_gate = nn.Linear(hidden_size, hidden_size, bias=False)
|
| 112 |
+
self.norm_q = Krea2RMSNorm(self.head_dim, eps=eps)
|
| 113 |
+
self.norm_k = Krea2RMSNorm(self.head_dim, eps=eps)
|
| 114 |
+
self.to_out = nn.ModuleList([nn.Linear(hidden_size, hidden_size, bias=False), nn.Dropout(0.0)])
|
| 115 |
+
|
| 116 |
+
if processor is None:
|
| 117 |
+
processor = self._default_processor_cls()
|
| 118 |
+
self.set_processor(processor)
|
| 119 |
+
|
| 120 |
+
def forward(
|
| 121 |
+
self,
|
| 122 |
+
hidden_states: torch.Tensor,
|
| 123 |
+
attention_mask: torch.Tensor | None = None,
|
| 124 |
+
image_rotary_emb: tuple[torch.Tensor, torch.Tensor] | None = None,
|
| 125 |
+
**kwargs,
|
| 126 |
+
) -> torch.Tensor:
|
| 127 |
+
attn_parameters = set(inspect.signature(self.processor.__call__).parameters.keys())
|
| 128 |
+
unused_kwargs = [k for k in kwargs if k not in attn_parameters]
|
| 129 |
+
if len(unused_kwargs) > 0:
|
| 130 |
+
logger.warning(
|
| 131 |
+
f"attention_kwargs {unused_kwargs} are not expected by {self.processor.__class__.__name__} and will be ignored."
|
| 132 |
+
)
|
| 133 |
+
kwargs = {k: w for k, w in kwargs.items() if k in attn_parameters}
|
| 134 |
+
return self.processor(self, hidden_states, attention_mask, image_rotary_emb, **kwargs)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class Krea2SwiGLU(nn.Module):
|
| 138 |
+
"""SwiGLU feed-forward network."""
|
| 139 |
+
|
| 140 |
+
def __init__(self, dim: int, hidden_dim: int) -> None:
|
| 141 |
+
super().__init__()
|
| 142 |
+
self.gate = nn.Linear(dim, hidden_dim, bias=False)
|
| 143 |
+
self.up = nn.Linear(dim, hidden_dim, bias=False)
|
| 144 |
+
self.down = nn.Linear(hidden_dim, dim, bias=False)
|
| 145 |
+
|
| 146 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 147 |
+
return self.down(F.silu(self.gate(hidden_states)) * self.up(hidden_states))
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
class Krea2TextFusionBlock(nn.Module):
|
| 151 |
+
"""Pre-norm transformer block (no rotary embeddings, no time modulation) used by the text fusion stage."""
|
| 152 |
+
|
| 153 |
+
def __init__(self, dim: int, num_heads: int, num_kv_heads: int, intermediate_size: int, eps: float) -> None:
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.norm1 = Krea2RMSNorm(dim, eps=eps)
|
| 156 |
+
self.norm2 = Krea2RMSNorm(dim, eps=eps)
|
| 157 |
+
self.attn = Krea2Attention(dim, num_heads, num_kv_heads, eps=eps)
|
| 158 |
+
self.ff = Krea2SwiGLU(dim, intermediate_size)
|
| 159 |
+
|
| 160 |
+
def forward(self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None) -> torch.Tensor:
|
| 161 |
+
hidden_states = hidden_states + self.attn(self.norm1(hidden_states), attention_mask=attention_mask)
|
| 162 |
+
hidden_states = hidden_states + self.ff(self.norm2(hidden_states))
|
| 163 |
+
return hidden_states
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
class Krea2TextFusion(nn.Module):
|
| 167 |
+
"""Fuses the stack of tapped text-encoder hidden states into a single sequence of text features.
|
| 168 |
+
|
| 169 |
+
Two `layerwise_blocks` attend across the `num_text_layers` axis independently for every token, a linear
|
| 170 |
+
`projector` collapses that axis, and two `refiner_blocks` attend across the token sequence.
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
def __init__(
|
| 174 |
+
self,
|
| 175 |
+
num_text_layers: int,
|
| 176 |
+
dim: int,
|
| 177 |
+
num_heads: int,
|
| 178 |
+
num_kv_heads: int,
|
| 179 |
+
intermediate_size: int,
|
| 180 |
+
num_layerwise_blocks: int,
|
| 181 |
+
num_refiner_blocks: int,
|
| 182 |
+
eps: float,
|
| 183 |
+
) -> None:
|
| 184 |
+
super().__init__()
|
| 185 |
+
self.layerwise_blocks = nn.ModuleList(
|
| 186 |
+
[
|
| 187 |
+
Krea2TextFusionBlock(dim, num_heads, num_kv_heads, intermediate_size, eps)
|
| 188 |
+
for _ in range(num_layerwise_blocks)
|
| 189 |
+
]
|
| 190 |
+
)
|
| 191 |
+
self.projector = nn.Linear(num_text_layers, 1, bias=False)
|
| 192 |
+
self.refiner_blocks = nn.ModuleList(
|
| 193 |
+
[
|
| 194 |
+
Krea2TextFusionBlock(dim, num_heads, num_kv_heads, intermediate_size, eps)
|
| 195 |
+
for _ in range(num_refiner_blocks)
|
| 196 |
+
]
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
def forward(self, encoder_hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None) -> torch.Tensor:
|
| 200 |
+
batch_size, seq_len, num_text_layers, dim = encoder_hidden_states.shape
|
| 201 |
+
|
| 202 |
+
hidden_states = encoder_hidden_states.reshape(batch_size * seq_len, num_text_layers, dim)
|
| 203 |
+
for block in self.layerwise_blocks:
|
| 204 |
+
hidden_states = block(hidden_states.contiguous())
|
| 205 |
+
|
| 206 |
+
hidden_states = hidden_states.reshape(batch_size, seq_len, num_text_layers, dim).permute(0, 1, 3, 2)
|
| 207 |
+
hidden_states = self.projector(hidden_states).squeeze(-1)
|
| 208 |
+
|
| 209 |
+
for block in self.refiner_blocks:
|
| 210 |
+
hidden_states = block(hidden_states, attention_mask=attention_mask)
|
| 211 |
+
|
| 212 |
+
return hidden_states
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class Krea2TransformerBlock(nn.Module):
|
| 216 |
+
def __init__(
|
| 217 |
+
self, hidden_size: int, intermediate_size: int, num_heads: int, num_kv_heads: int, norm_eps: float
|
| 218 |
+
) -> None:
|
| 219 |
+
super().__init__()
|
| 220 |
+
self.scale_shift_table = nn.Parameter(torch.zeros(6, hidden_size))
|
| 221 |
+
self.norm1 = Krea2RMSNorm(hidden_size, eps=norm_eps)
|
| 222 |
+
self.norm2 = Krea2RMSNorm(hidden_size, eps=norm_eps)
|
| 223 |
+
self.attn = Krea2Attention(hidden_size, num_heads, num_kv_heads, eps=norm_eps)
|
| 224 |
+
self.ff = Krea2SwiGLU(hidden_size, intermediate_size)
|
| 225 |
+
|
| 226 |
+
def forward(
|
| 227 |
+
self,
|
| 228 |
+
hidden_states: torch.Tensor,
|
| 229 |
+
temb: torch.Tensor | tuple[torch.Tensor, torch.Tensor, int],
|
| 230 |
+
image_rotary_emb: tuple[torch.Tensor, torch.Tensor],
|
| 231 |
+
attention_mask: torch.Tensor | None = None,
|
| 232 |
+
) -> torch.Tensor:
|
| 233 |
+
# temb: (B, 1, 6 * hidden_size), shared across all blocks; each block only learns an additive table.
|
| 234 |
+
# For reference-image ("edit") conditioning, temb is instead a tuple (temb, ref_temb, split): the leading
|
| 235 |
+
# `split` tokens (text + noisy image) are modulated with the real timestep while the trailing tokens (clean
|
| 236 |
+
# reference tokens) use the t=0 embedding `ref_temb`.
|
| 237 |
+
if isinstance(temb, tuple):
|
| 238 |
+
temb, ref_temb, split = temb
|
| 239 |
+
m = (temb.unflatten(-1, (6, -1)) + self.scale_shift_table).unbind(-2)
|
| 240 |
+
r = (ref_temb.unflatten(-1, (6, -1)) + self.scale_shift_table).unbind(-2)
|
| 241 |
+
|
| 242 |
+
def modulate(h, scale_idx, shift_idx):
|
| 243 |
+
return torch.cat(
|
| 244 |
+
(
|
| 245 |
+
(1.0 + m[scale_idx]) * h[:, :split] + m[shift_idx],
|
| 246 |
+
(1.0 + r[scale_idx]) * h[:, split:] + r[shift_idx],
|
| 247 |
+
),
|
| 248 |
+
dim=1,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
def gate(h, gate_idx):
|
| 252 |
+
return torch.cat((m[gate_idx] * h[:, :split], r[gate_idx] * h[:, split:]), dim=1)
|
| 253 |
+
|
| 254 |
+
attn_out = self.attn(
|
| 255 |
+
modulate(self.norm1(hidden_states), 0, 1),
|
| 256 |
+
attention_mask=attention_mask,
|
| 257 |
+
image_rotary_emb=image_rotary_emb,
|
| 258 |
+
)
|
| 259 |
+
hidden_states = hidden_states + gate(attn_out, 2)
|
| 260 |
+
ff_out = self.ff(modulate(self.norm2(hidden_states), 3, 4))
|
| 261 |
+
hidden_states = hidden_states + gate(ff_out, 5)
|
| 262 |
+
return hidden_states
|
| 263 |
+
|
| 264 |
+
modulation = temb.unflatten(-1, (6, -1)) + self.scale_shift_table
|
| 265 |
+
prescale, preshift, pregate, postscale, postshift, postgate = modulation.unbind(-2)
|
| 266 |
+
|
| 267 |
+
attn_out = self.attn(
|
| 268 |
+
(1.0 + prescale) * self.norm1(hidden_states) + preshift,
|
| 269 |
+
attention_mask=attention_mask,
|
| 270 |
+
image_rotary_emb=image_rotary_emb,
|
| 271 |
+
)
|
| 272 |
+
hidden_states = hidden_states + pregate * attn_out
|
| 273 |
+
ff_out = self.ff((1.0 + postscale) * self.norm2(hidden_states) + postshift)
|
| 274 |
+
hidden_states = hidden_states + postgate * ff_out
|
| 275 |
+
return hidden_states
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
class Krea2TimestepEmbedding(nn.Module):
|
| 279 |
+
"""Sinusoidal flow-time embedding (cos-first, input scaled by 1000) followed by a two-layer MLP.
|
| 280 |
+
|
| 281 |
+
Keeps the sequence dimension at size 1 so the per-block modulations broadcast over tokens.
|
| 282 |
+
"""
|
| 283 |
+
|
| 284 |
+
def __init__(self, embed_dim: int, hidden_size: int) -> None:
|
| 285 |
+
super().__init__()
|
| 286 |
+
self.embed_dim = embed_dim
|
| 287 |
+
self.linear_1 = nn.Linear(embed_dim, hidden_size, bias=True)
|
| 288 |
+
self.linear_2 = nn.Linear(hidden_size, hidden_size, bias=True)
|
| 289 |
+
|
| 290 |
+
def forward(self, timestep: torch.Tensor, dtype: torch.dtype) -> torch.Tensor:
|
| 291 |
+
half = self.embed_dim // 2
|
| 292 |
+
freqs = torch.exp(-math.log(1e4) * torch.arange(half, dtype=torch.float32, device=timestep.device) / half)
|
| 293 |
+
args = (timestep.float() * 1e3)[:, None, None] * freqs
|
| 294 |
+
emb = torch.cat([torch.cos(args), torch.sin(args)], dim=-1).to(dtype)
|
| 295 |
+
return self.linear_2(F.gelu(self.linear_1(emb), approximate="tanh"))
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
class Krea2TextProjection(nn.Module):
|
| 299 |
+
"""Projects the fused text features into the transformer width."""
|
| 300 |
+
|
| 301 |
+
def __init__(self, text_dim: int, hidden_size: int, eps: float) -> None:
|
| 302 |
+
super().__init__()
|
| 303 |
+
self.norm = Krea2RMSNorm(text_dim, eps=eps)
|
| 304 |
+
self.linear_1 = nn.Linear(text_dim, hidden_size, bias=True)
|
| 305 |
+
self.linear_2 = nn.Linear(hidden_size, hidden_size, bias=True)
|
| 306 |
+
|
| 307 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 308 |
+
hidden_states = self.linear_1(self.norm(hidden_states))
|
| 309 |
+
return self.linear_2(F.gelu(hidden_states, approximate="tanh"))
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
class Krea2FinalLayer(nn.Module):
|
| 313 |
+
"""Final adaptive RMSNorm and output projection. Kept as one module (and in `_no_split_modules`) so the learned
|
| 314 |
+
modulation table, norm and projection stay co-located under device-mapped inference."""
|
| 315 |
+
|
| 316 |
+
def __init__(self, hidden_size: int, out_channels: int, eps: float) -> None:
|
| 317 |
+
super().__init__()
|
| 318 |
+
self.scale_shift_table = nn.Parameter(torch.zeros(2, hidden_size))
|
| 319 |
+
self.norm = Krea2RMSNorm(hidden_size, eps=eps)
|
| 320 |
+
self.linear = nn.Linear(hidden_size, out_channels, bias=True)
|
| 321 |
+
|
| 322 |
+
def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor) -> torch.Tensor:
|
| 323 |
+
modulation = temb + self.scale_shift_table
|
| 324 |
+
scale, shift = modulation.chunk(2, dim=1)
|
| 325 |
+
hidden_states = (1.0 + scale) * self.norm(hidden_states) + shift
|
| 326 |
+
return self.linear(hidden_states)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Copied from diffusers.models.transformers.transformer_flux.FluxPosEmbed with FluxPosEmbed->Krea2RotaryPosEmbed
|
| 330 |
+
class Krea2RotaryPosEmbed(nn.Module):
|
| 331 |
+
# modified from https://github.com/black-forest-labs/flux/blob/c00d7c60b085fce8058b9df845e036090873f2ce/src/flux/modules/layers.py#L11
|
| 332 |
+
def __init__(self, theta: int, axes_dim: list[int]):
|
| 333 |
+
super().__init__()
|
| 334 |
+
self.theta = theta
|
| 335 |
+
self.axes_dim = axes_dim
|
| 336 |
+
|
| 337 |
+
def forward(self, ids: torch.Tensor) -> torch.Tensor:
|
| 338 |
+
n_axes = ids.shape[-1]
|
| 339 |
+
cos_out = []
|
| 340 |
+
sin_out = []
|
| 341 |
+
pos = ids.float()
|
| 342 |
+
freqs_dtype = maybe_adjust_dtype_for_device(torch.float64, ids.device)
|
| 343 |
+
for i in range(n_axes):
|
| 344 |
+
cos, sin = get_1d_rotary_pos_embed(
|
| 345 |
+
self.axes_dim[i],
|
| 346 |
+
pos[:, i],
|
| 347 |
+
theta=self.theta,
|
| 348 |
+
repeat_interleave_real=True,
|
| 349 |
+
use_real=True,
|
| 350 |
+
freqs_dtype=freqs_dtype,
|
| 351 |
+
)
|
| 352 |
+
cos_out.append(cos)
|
| 353 |
+
sin_out.append(sin)
|
| 354 |
+
freqs_cos = torch.cat(cos_out, dim=-1).to(ids.device)
|
| 355 |
+
freqs_sin = torch.cat(sin_out, dim=-1).to(ids.device)
|
| 356 |
+
return freqs_cos, freqs_sin
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class Krea2Transformer2DModel(ModelMixin, ConfigMixin, AttentionMixin, PeftAdapterMixin):
|
| 360 |
+
r"""
|
| 361 |
+
The single-stream MMDiT flow-matching backbone used by the Krea 2 pipeline.
|
| 362 |
+
|
| 363 |
+
Text conditioning enters as a stack of hidden states tapped from several layers of a multimodal text encoder. A
|
| 364 |
+
small text-fusion transformer collapses the layer axis and refines the token sequence; the result is concatenated
|
| 365 |
+
with the patchified image latents into a single `[text, image]` sequence processed by the transformer blocks. The
|
| 366 |
+
timestep conditions every block through one shared modulation vector plus per-block learned tables.
|
| 367 |
+
|
| 368 |
+
Args:
|
| 369 |
+
in_channels (`int`, defaults to 64):
|
| 370 |
+
Latent channel count after patchification (`vae_channels * patch_size ** 2`).
|
| 371 |
+
num_layers (`int`, defaults to 28):
|
| 372 |
+
Number of transformer blocks.
|
| 373 |
+
attention_head_dim (`int`, defaults to 128):
|
| 374 |
+
Dimension of each attention head; the total hidden size is `attention_head_dim * num_attention_heads`.
|
| 375 |
+
num_attention_heads (`int`, defaults to 48):
|
| 376 |
+
Number of query heads.
|
| 377 |
+
num_key_value_heads (`int`, defaults to 12):
|
| 378 |
+
Number of key/value heads for grouped-query attention.
|
| 379 |
+
intermediate_size (`int`, defaults to 16384):
|
| 380 |
+
Feed-forward hidden size of the SwiGLU MLP inside each block.
|
| 381 |
+
timestep_embed_dim (`int`, defaults to 256):
|
| 382 |
+
Width of the sinusoidal timestep embedding before its MLP.
|
| 383 |
+
text_hidden_dim (`int`, defaults to 2560):
|
| 384 |
+
Hidden size of the text encoder whose hidden states are consumed.
|
| 385 |
+
num_text_layers (`int`, defaults to 12):
|
| 386 |
+
Number of tapped text-encoder hidden states stacked per token.
|
| 387 |
+
text_num_attention_heads (`int`, defaults to 20):
|
| 388 |
+
Number of query heads in the text fusion blocks.
|
| 389 |
+
text_num_key_value_heads (`int`, defaults to 20):
|
| 390 |
+
Number of key/value heads in the text fusion blocks.
|
| 391 |
+
text_intermediate_size (`int`, defaults to 6912):
|
| 392 |
+
Feed-forward hidden size of the SwiGLU MLP inside the text fusion blocks.
|
| 393 |
+
num_layerwise_text_blocks (`int`, defaults to 2):
|
| 394 |
+
Number of text fusion blocks applied across the tapped-layer axis (per token).
|
| 395 |
+
num_refiner_text_blocks (`int`, defaults to 2):
|
| 396 |
+
Number of text fusion blocks applied across the token sequence.
|
| 397 |
+
axes_dims_rope (`tuple[int, int, int]`, defaults to `(32, 48, 48)`):
|
| 398 |
+
Head-dim split across the (t, h, w) rotary position axes.
|
| 399 |
+
rope_theta (`float`, defaults to 1000.0):
|
| 400 |
+
Base used by the rotary position embedding.
|
| 401 |
+
norm_eps (`float`, defaults to 1e-5):
|
| 402 |
+
Epsilon used by all RMSNorm modules.
|
| 403 |
+
"""
|
| 404 |
+
|
| 405 |
+
_supports_gradient_checkpointing = True
|
| 406 |
+
_no_split_modules = ["Krea2TransformerBlock", "Krea2TextFusionBlock", "Krea2FinalLayer"]
|
| 407 |
+
_repeated_blocks = ["Krea2TransformerBlock"]
|
| 408 |
+
_keep_in_fp32_modules = ["norm", "norm1", "norm2", "norm_q", "norm_k"]
|
| 409 |
+
_skip_layerwise_casting_patterns = ["time_embed", "norm"]
|
| 410 |
+
|
| 411 |
+
@register_to_config
|
| 412 |
+
def __init__(
|
| 413 |
+
self,
|
| 414 |
+
in_channels: int = 64,
|
| 415 |
+
num_layers: int = 28,
|
| 416 |
+
attention_head_dim: int = 128,
|
| 417 |
+
num_attention_heads: int = 48,
|
| 418 |
+
num_key_value_heads: int = 12,
|
| 419 |
+
intermediate_size: int = 16384,
|
| 420 |
+
timestep_embed_dim: int = 256,
|
| 421 |
+
text_hidden_dim: int = 2560,
|
| 422 |
+
num_text_layers: int = 12,
|
| 423 |
+
text_num_attention_heads: int = 20,
|
| 424 |
+
text_num_key_value_heads: int = 20,
|
| 425 |
+
text_intermediate_size: int = 6912,
|
| 426 |
+
num_layerwise_text_blocks: int = 2,
|
| 427 |
+
num_refiner_text_blocks: int = 2,
|
| 428 |
+
axes_dims_rope: tuple[int, int, int] = (32, 48, 48),
|
| 429 |
+
rope_theta: float = 1000.0,
|
| 430 |
+
norm_eps: float = 1e-5,
|
| 431 |
+
) -> None:
|
| 432 |
+
super().__init__()
|
| 433 |
+
|
| 434 |
+
hidden_size = attention_head_dim * num_attention_heads
|
| 435 |
+
if sum(axes_dims_rope) != attention_head_dim:
|
| 436 |
+
raise ValueError(
|
| 437 |
+
f"sum(axes_dims_rope)={sum(axes_dims_rope)} must equal attention_head_dim={attention_head_dim}"
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
self.in_channels = in_channels
|
| 441 |
+
self.out_channels = in_channels
|
| 442 |
+
self.hidden_size = hidden_size
|
| 443 |
+
self.gradient_checkpointing = False
|
| 444 |
+
|
| 445 |
+
self.img_in = nn.Linear(in_channels, hidden_size, bias=True)
|
| 446 |
+
self.time_embed = Krea2TimestepEmbedding(timestep_embed_dim, hidden_size)
|
| 447 |
+
self.time_mod_proj = nn.Linear(hidden_size, 6 * hidden_size, bias=True)
|
| 448 |
+
self.text_fusion = Krea2TextFusion(
|
| 449 |
+
num_text_layers=num_text_layers,
|
| 450 |
+
dim=text_hidden_dim,
|
| 451 |
+
num_heads=text_num_attention_heads,
|
| 452 |
+
num_kv_heads=text_num_key_value_heads,
|
| 453 |
+
intermediate_size=text_intermediate_size,
|
| 454 |
+
num_layerwise_blocks=num_layerwise_text_blocks,
|
| 455 |
+
num_refiner_blocks=num_refiner_text_blocks,
|
| 456 |
+
eps=norm_eps,
|
| 457 |
+
)
|
| 458 |
+
self.txt_in = Krea2TextProjection(text_hidden_dim, hidden_size, eps=norm_eps)
|
| 459 |
+
self.rotary_emb = Krea2RotaryPosEmbed(theta=rope_theta, axes_dim=list(axes_dims_rope))
|
| 460 |
+
|
| 461 |
+
self.transformer_blocks = nn.ModuleList(
|
| 462 |
+
[
|
| 463 |
+
Krea2TransformerBlock(
|
| 464 |
+
hidden_size=hidden_size,
|
| 465 |
+
intermediate_size=intermediate_size,
|
| 466 |
+
num_heads=num_attention_heads,
|
| 467 |
+
num_kv_heads=num_key_value_heads,
|
| 468 |
+
norm_eps=norm_eps,
|
| 469 |
+
)
|
| 470 |
+
for _ in range(num_layers)
|
| 471 |
+
]
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
self.final_layer = Krea2FinalLayer(hidden_size, out_channels=in_channels, eps=norm_eps)
|
| 475 |
+
|
| 476 |
+
def forward(
|
| 477 |
+
self,
|
| 478 |
+
hidden_states: torch.Tensor,
|
| 479 |
+
encoder_hidden_states: torch.Tensor,
|
| 480 |
+
timestep: torch.Tensor,
|
| 481 |
+
position_ids: torch.Tensor,
|
| 482 |
+
encoder_attention_mask: torch.Tensor | None = None,
|
| 483 |
+
ref_seq_len: int = 0,
|
| 484 |
+
return_dict: bool = True,
|
| 485 |
+
) -> Transformer2DModelOutput | tuple[torch.Tensor]:
|
| 486 |
+
r"""
|
| 487 |
+
Predict the flow-matching velocity for the (noisy) image tokens.
|
| 488 |
+
|
| 489 |
+
Args:
|
| 490 |
+
hidden_states (`torch.Tensor` of shape `(batch_size, image_seq_len + ref_seq_len, in_channels)`):
|
| 491 |
+
Packed (patchified) noisy image latents, with any packed clean reference latents appended at the end.
|
| 492 |
+
encoder_hidden_states (`torch.Tensor` of shape `(batch_size, text_seq_len, num_text_layers, text_hidden_dim)`):
|
| 493 |
+
Stack of tapped text-encoder hidden states per token.
|
| 494 |
+
timestep (`torch.Tensor` of shape `(batch_size,)`):
|
| 495 |
+
Flow-matching time in `[0, 1]` (1 is pure noise, 0 is clean data).
|
| 496 |
+
position_ids (`torch.Tensor` of shape `(text_seq_len + image_seq_len + ref_seq_len, 3)`):
|
| 497 |
+
`(t, h, w)` rotary coordinates for the combined sequence. Text rows are all-zero; image rows hold the
|
| 498 |
+
latent-grid coordinates; the i-th reference image sits on frame axis `i + 1` with its own grid.
|
| 499 |
+
encoder_attention_mask (`torch.Tensor` of shape `(batch_size, text_seq_len)`, *optional*):
|
| 500 |
+
Boolean mask marking valid text tokens. Pass `None` when every text token is valid.
|
| 501 |
+
ref_seq_len (`int`, *optional*, defaults to 0):
|
| 502 |
+
Number of trailing reference (edit) tokens in `hidden_states`. They are modulated with the t=0
|
| 503 |
+
embedding and excluded from the returned velocity.
|
| 504 |
+
return_dict (`bool`, *optional*, defaults to `True`):
|
| 505 |
+
Whether to return a [`~models.modeling_outputs.Transformer2DModelOutput`] instead of a plain tuple.
|
| 506 |
+
|
| 507 |
+
Returns:
|
| 508 |
+
[`~models.modeling_outputs.Transformer2DModelOutput`] or a `tuple` whose first element is the velocity
|
| 509 |
+
tensor of shape `(batch_size, image_seq_len, in_channels)`.
|
| 510 |
+
"""
|
| 511 |
+
if position_ids.ndim != 2 or position_ids.shape[-1] != 3:
|
| 512 |
+
raise ValueError(f"`position_ids` must have shape (sequence_length, 3), got {tuple(position_ids.shape)}.")
|
| 513 |
+
|
| 514 |
+
batch_size, image_seq_len, _ = hidden_states.shape # image_seq_len includes any trailing reference tokens
|
| 515 |
+
text_seq_len = encoder_hidden_states.shape[1]
|
| 516 |
+
|
| 517 |
+
temb = self.time_embed(timestep, dtype=hidden_states.dtype)
|
| 518 |
+
temb_mod = self.time_mod_proj(F.gelu(temb, approximate="tanh"))
|
| 519 |
+
|
| 520 |
+
# Clean reference tokens are conditioned at flow time t=0; the text + noisy image tokens keep the real
|
| 521 |
+
# timestep. The blocks then receive a (temb, ref_temb, split) tuple that modulates the two spans separately.
|
| 522 |
+
block_temb = temb_mod
|
| 523 |
+
if ref_seq_len > 0:
|
| 524 |
+
temb_zero = self.time_embed(torch.zeros_like(timestep), dtype=hidden_states.dtype)
|
| 525 |
+
ref_temb_mod = self.time_mod_proj(F.gelu(temb_zero, approximate="tanh"))
|
| 526 |
+
block_temb = (temb_mod, ref_temb_mod, text_seq_len + image_seq_len - ref_seq_len)
|
| 527 |
+
|
| 528 |
+
text_attention_mask = None
|
| 529 |
+
attention_mask = None
|
| 530 |
+
if encoder_attention_mask is not None:
|
| 531 |
+
# Key-padding masks of shape (B, 1, 1, L): padded text tokens are excluded as attention keys everywhere;
|
| 532 |
+
# their own (garbage) lanes are never read back and are dropped at the output slice.
|
| 533 |
+
text_attention_mask = encoder_attention_mask[:, None, None, :]
|
| 534 |
+
image_mask = encoder_attention_mask.new_ones((batch_size, image_seq_len))
|
| 535 |
+
attention_mask = torch.cat([encoder_attention_mask, image_mask], dim=1)[:, None, None, :]
|
| 536 |
+
|
| 537 |
+
encoder_hidden_states = self.text_fusion(encoder_hidden_states, attention_mask=text_attention_mask)
|
| 538 |
+
encoder_hidden_states = self.txt_in(encoder_hidden_states)
|
| 539 |
+
|
| 540 |
+
hidden_states = self.img_in(hidden_states)
|
| 541 |
+
hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1)
|
| 542 |
+
|
| 543 |
+
image_rotary_emb = self.rotary_emb(position_ids)
|
| 544 |
+
|
| 545 |
+
for block in self.transformer_blocks:
|
| 546 |
+
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 547 |
+
hidden_states = self._gradient_checkpointing_func(
|
| 548 |
+
block, hidden_states, block_temb, image_rotary_emb, attention_mask
|
| 549 |
+
)
|
| 550 |
+
else:
|
| 551 |
+
hidden_states = block(hidden_states, block_temb, image_rotary_emb, attention_mask)
|
| 552 |
+
|
| 553 |
+
# Keep only the noisy image tokens: drop the leading text tokens and any trailing reference (edit) tokens.
|
| 554 |
+
hidden_states = hidden_states[:, text_seq_len : text_seq_len + image_seq_len - ref_seq_len]
|
| 555 |
+
output = self.final_layer(hidden_states, temb)
|
| 556 |
+
|
| 557 |
+
if not return_dict:
|
| 558 |
+
return (output,)
|
| 559 |
+
return Transformer2DModelOutput(sample=output)
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
# Register into the diffusers namespace so `ModularPipeline` component loading can resolve the
|
| 563 |
+
# transformer referenced as ["diffusers", "Krea2Transformer2DModel"] in the base repo's model_index.json.
|
| 564 |
+
import diffusers as _diffusers # noqa: E402
|
| 565 |
+
|
| 566 |
+
_diffusers.Krea2Transformer2DModel = Krea2Transformer2DModel
|