diff --git a/OmniConsistency/README.md b/OmniConsistency/README.md
new file mode 100644
index 0000000..5a4a5da
--- /dev/null
+++ b/OmniConsistency/README.md
@@ -0,0 +1,164 @@
+# OmniConsistency
+
+> **OmniConsistency: Learning Style-Agnostic
+Consistency from Paired Stylization Data**
+>
+> [Yiren Song](https://scholar.google.com.hk/citations?user=L2YS0jgAAAAJ),
+> [Cheng Liu](https://scholar.google.com.hk/citations?hl=zh-CN&user=TvdVuAYAAAAJ),
+> and
+> [Mike Zheng Shou](https://sites.google.com/view/showlab)
+>
+> [Show Lab](https://sites.google.com/view/showlab), National University of Singapore
+>
+
+
+
+
+
+
+
+
+
+
+## News
+- **2025‑06‑01**: 🚀 Released the **OmniConsistency Generator** [ComfyUI node](https://github.com/lc03lc/Comfyui_OmniConsistency) – one‑click FLUX + OmniConsistency (with any LoRA) inside ComfyUI.
+
+
+## Installation
+
+We recommend using Python 3.10 and PyTorch with CUDA support. To set up the environment:
+
+```bash
+# Create a new conda environment
+conda create -n omniconsistency python=3.10
+conda activate omniconsistency
+
+# Install other dependencies
+pip install -r requirements.txt
+```
+
+## Download
+
+You can download the OmniConsistency model and trained LoRAs directly from [Hugging Face](https://huggingface.co/showlab/OmniConsistency).
+Or download using Python script:
+
+### OmniConsistency Model
+
+```python
+from huggingface_hub import hf_hub_download
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/3D_Chibi_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/American_Cartoon_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Chinese_Ink_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Clay_Toy_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Fabric_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Ghibli_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Irasutoya_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Jojo_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/LEGO_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Line_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Macaron_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Oil_Painting_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Origami_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Paper_Cutting_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Picasso_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Pixel_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Poly_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Pop_Art_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Rick_Morty_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Snoopy_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Van_Gogh_rank128_bf16.safetensors", local_dir="./LoRAs")
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="LoRAs/Vector_rank128_bf16.safetensors", local_dir="./LoRAs")
+```
+### Trained LoRAs
+```python
+from huggingface_hub import hf_hub_download
+hf_hub_download(repo_id="showlab/OmniConsistency", filename="OmniConsistency.safetensors", local_dir="./Model")
+```
+
+## Usage
+Here's a basic example of using OmniConsistency:
+
+### Model Initialization
+```python
+import time
+import torch
+from PIL import Image
+from src_inference.pipeline import FluxPipeline
+from src_inference.lora_helper import set_single_lora
+
+def clear_cache(transformer):
+ for name, attn_processor in transformer.attn_processors.items():
+ attn_processor.bank_kv.clear()
+
+# Initialize model
+device = "cuda"
+base_path = "/path/to/black-forest-labs/FLUX.1-dev"
+pipe = FluxPipeline.from_pretrained(base_path, torch_dtype=torch.bfloat16).to("cuda")
+
+# Load OmniConsistency model
+set_single_lora(pipe.transformer,
+ "/path/to/OmniConsistency.safetensors",
+ lora_weights=[1], cond_size=512)
+
+# Load external LoRA
+pipe.unload_lora_weights()
+pipe.load_lora_weights("/path/to/lora_folder",
+ weight_name="lora_name.safetensors")
+```
+
+### Style Inference
+```python
+image_path1 = "figure/test.png"
+prompt = "3D Chibi style, Three individuals standing together in the office."
+
+subject_images = []
+spatial_image = [Image.open(image_path1).convert("RGB")]
+
+width, height = 1024, 1024
+
+start_time = time.time()
+
+image = pipe(
+ prompt,
+ height=height,
+ width=width,
+ guidance_scale=3.5,
+ num_inference_steps=25,
+ max_sequence_length=512,
+ generator=torch.Generator("cpu").manual_seed(5),
+ spatial_images=spatial_image,
+ subject_images=subject_images,
+ cond_size=512,
+).images[0]
+
+end_time = time.time()
+elapsed_time = end_time - start_time
+print(f"code running time: {elapsed_time} s")
+
+# Clear cache after generation
+clear_cache(pipe.transformer)
+
+image.save("results/output.png")
+```
+
+## Datasets
+Our datasets have been uploaded to the [Hugging Face](https://huggingface.co/datasets/showlab/OmniConsistency). and is available for direct use via the datasets library.
+
+You can easily load any of the 22 style subsets like this:
+```python
+from datasets import load_dataset
+
+# Load a single style (e.g., Ghibli)
+ds = load_dataset("showlab/OmniConsistency", split="Ghibli")
+print(ds[0])
+```
+
+## Citation
+```
+@inproceedings{Song2025OmniConsistencyLS,
+ title={OmniConsistency: Learning Style-Agnostic Consistency from Paired Stylization Data},
+ author={Yiren Song and Cheng Liu and Mike Zheng Shou},
+ year={2025},
+ url={https://api.semanticscholar.org/CorpusID:278905729}
+}
+```
diff --git a/OmniConsistency/__pycache__/infer.cpython-311.pyc b/OmniConsistency/__pycache__/infer.cpython-311.pyc
new file mode 100644
index 0000000..3930ffd
Binary files /dev/null and b/OmniConsistency/__pycache__/infer.cpython-311.pyc differ
diff --git a/OmniConsistency/app.py b/OmniConsistency/app.py
new file mode 100644
index 0000000..937391b
--- /dev/null
+++ b/OmniConsistency/app.py
@@ -0,0 +1,210 @@
+# import spaces
+import os
+import time
+import torch
+# import gradio as gr
+from PIL import Image
+from huggingface_hub import hf_hub_download, list_repo_files
+from src_inference.pipeline import FluxPipeline
+from src_inference.lora_helper import set_single_lora
+
+BASE_PATH = "black-forest-labs/FLUX.1-dev"
+LOCAL_LORA_DIR = "./LoRAs"
+CUSTOM_LORA_DIR = "./Custom_LoRAs"
+os.makedirs(LOCAL_LORA_DIR, exist_ok=True)
+os.makedirs(CUSTOM_LORA_DIR, exist_ok=True)
+
+print("downloading OmniConsistency base LoRA …")
+omni_consistency_path = hf_hub_download(
+ repo_id="showlab/OmniConsistency",
+ filename="OmniConsistency.safetensors",
+ local_dir="./Model"
+)
+
+print("loading base pipeline …")
+pipe = FluxPipeline.from_pretrained(
+ BASE_PATH, torch_dtype=torch.bfloat16
+).to("cuda")
+set_single_lora(pipe.transformer, omni_consistency_path,
+ lora_weights=[1], cond_size=512)
+
+def download_all_loras():
+ lora_names = [
+ "3D_Chibi", "American_Cartoon", "Chinese_Ink", "Clay_Toy",
+ "Fabric", "Ghibli", "Irasutoya", "Jojo", "LEGO", "Line",
+ "Macaron", "Oil_Painting", "Origami", "Paper_Cutting",
+ "Picasso", "Pixel", "Poly", "Pop_Art", "Rick_Morty",
+ "Snoopy", "Van_Gogh", "Vector"
+ ]
+ for name in lora_names:
+ hf_hub_download(
+ repo_id="showlab/OmniConsistency",
+ filename=f"LoRAs/{name}_rank128_bf16.safetensors",
+ local_dir=LOCAL_LORA_DIR,
+ )
+download_all_loras()
+
+def clear_cache(transformer):
+ for _, attn_processor in transformer.attn_processors.items():
+ attn_processor.bank_kv.clear()
+
+# @spaces.GPU()
+def generate_image(
+ lora_name,
+ custom_repo_id,
+ prompt,
+ uploaded_image,
+ width, height,
+ guidance_scale,
+ num_inference_steps,
+ seed
+):
+ width, height = int(width), int(height)
+ generator = torch.Generator("cpu").manual_seed(seed)
+
+ if custom_repo_id and custom_repo_id.strip():
+ repo_id = custom_repo_id.strip()
+ try:
+ files = list_repo_files(repo_id)
+ print("using custom LoRA from:", repo_id)
+ safetensors_files = [f for f in files if f.endswith(".safetensors")]
+ print("found safetensors files:", safetensors_files)
+ if not safetensors_files:
+ raise ValueError("No .safetensors files were found in this repo")
+ fname = safetensors_files[0]
+ lora_path = hf_hub_download(
+ repo_id=repo_id,
+ filename=fname,
+ local_dir=CUSTOM_LORA_DIR,
+ )
+ except Exception as e:
+ raise gr.Error(f"Load custom LoRA failed: {e}")
+ else:
+ lora_path = os.path.join(
+ f"{LOCAL_LORA_DIR}/LoRAs", f"{lora_name}_rank128_bf16.safetensors"
+ )
+
+ pipe.unload_lora_weights()
+ try:
+ pipe.load_lora_weights(
+ os.path.dirname(lora_path),
+ weight_name=os.path.basename(lora_path)
+ )
+ except Exception as e:
+ raise gr.Error(f"Load LoRA failed: {e}")
+
+ spatial_image = [uploaded_image.convert("RGB")]
+ subject_images = []
+ start = time.time()
+ out_img = pipe(
+ prompt,
+ height=(height // 8) * 8,
+ width=(width // 8) * 8,
+ guidance_scale=guidance_scale,
+ num_inference_steps=num_inference_steps,
+ max_sequence_length=512,
+ generator=generator,
+ spatial_images=spatial_image,
+ subject_images=subject_images,
+ cond_size=512,
+ ).images[0]
+ print(f"inference time: {time.time()-start:.2f}s")
+
+ clear_cache(pipe.transformer)
+ return uploaded_image, out_img
+
+# =============== Gradio UI ===============
+def create_interface():
+ demo_lora_names = [
+ "3D_Chibi", "American_Cartoon", "Chinese_Ink", "Clay_Toy",
+ "Fabric", "Ghibli", "Irasutoya", "Jojo", "LEGO", "Line",
+ "Macaron", "Oil_Painting", "Origami", "Paper_Cutting",
+ "Picasso", "Pixel", "Poly", "Pop_Art", "Rick_Morty",
+ "Snoopy", "Van_Gogh", "Vector"
+ ]
+
+ def update_trigger_word(lora_name, prompt):
+ for name in demo_lora_names:
+ trigger = " ".join(name.split("_")) + " style,"
+ prompt = prompt.replace(trigger, "")
+ new_trigger = " ".join(lora_name.split("_"))+ " style,"
+ return new_trigger + prompt
+
+ # Example data
+ examples = [
+ ["3D_Chibi", "", "3D Chibi style, Two smiling colleagues enthusiastically high-five in front of a whiteboard filled with technical notes about multimodal learning, reflecting a moment of success and collaboration at OpenAI.",
+ Image.open("./test_imgs/00.png"), 680, 1024, 3.5, 24, 42],
+ ["Clay_Toy", "", "Clay Toy style, Three team members from OpenAI are gathered around a laptop in a cozy, festive setting, with holiday decorations in the background; one waves cheerfully while the others engage in light conversation, reflecting a relaxed and collaborative atmosphere.",
+ Image.open("./test_imgs/01.png"), 560, 1024, 3.5, 24, 42],
+ ["American_Cartoon", "", "American Cartoon style, In a dramatic and comedic moment from a classic Chinese film, an intense elder with a white beard and red hat grips a younger man, declaring something with fervor, while the subtitle at the bottom reads 'I want them all' — capturing both tension and humor.",
+ Image.open("./test_imgs/02.png"), 568, 1024, 3.5, 24, 42],
+ ["Origami", "", "Origami style, A thrilled fan wearing a Portugal football kit poses energetically with a smiling Cristiano Ronaldo, who gives a thumbs-up, as they stand side by side in a casual, cheerful moment—capturing the excitement of meeting a football legend.",
+ Image.open("./test_imgs/03.png"), 768, 672, 3.5, 24, 42],
+ ["Vector", "", "Vector style, A man glances admiringly at a passing woman, while his girlfriend looks at him in disbelief, perfectly capturing the theme of shifting attention and misplaced priorities in a humorous, relatable way.",
+ Image.open("./test_imgs/04.png"), 512, 1024, 3.5, 24, 42]
+ ]
+
+ header = """
+
+"""
+
+ with gr.Blocks() as demo:
+ gr.Markdown("# OmniConsistency LoRA Image Generation")
+ gr.Markdown("Select a LoRA, enter a prompt, and upload an image to generate a new image with OmniConsistency.")
+ gr.HTML(header)
+
+ with gr.Row():
+ with gr.Column(scale=1):
+ image_input = gr.Image(type="pil", label="Upload Image")
+ prompt_box = gr.Textbox(label="Prompt",
+ value="3D Chibi style,",
+ info="Remember to include the necessary trigger words if you're using a custom LoRA."
+ )
+ lora_dropdown = gr.Dropdown(
+ demo_lora_names, label="Select built-in LoRA")
+ custom_repo_box = gr.Textbox(
+ label="Enter Custom LoRA",
+ placeholder="LoRA Hugging Face path (e.g., 'username/repo_name')",
+ info="If you want to use a custom LoRA, enter its Hugging Face repo ID here and built-in LoRA will be Overridden. Leave empty to use built-in LoRAs. [Check the list of FLUX LoRAs](https://huggingface.co/models?other=base_model:adapter:black-forest-labs/FLUX.1-dev)"
+ )
+ gen_btn = gr.Button("Generate")
+ with gr.Column(scale=1):
+ output_image = gr.ImageSlider(label="Generated Image")
+ with gr.Accordion("Advanced Options", open=False):
+ height_box = gr.Textbox(value="1024", label="Height")
+ width_box = gr.Textbox(value="1024", label="Width")
+ guidance_slider = gr.Slider(
+ 0.1, 20, value=3.5, step=0.1, label="Guidance Scale")
+ steps_slider = gr.Slider(
+ 1, 50, value=25, step=1, label="Inference Steps")
+ seed_slider = gr.Slider(
+ 1, 2_147_483_647, value=42, step=1, label="Seed")
+
+ lora_dropdown.select(fn=update_trigger_word, inputs=[lora_dropdown,prompt_box],
+ outputs=prompt_box)
+
+ gr.Examples(
+ examples=examples,
+ inputs=[lora_dropdown, custom_repo_box, prompt_box, image_input,
+ height_box, width_box, guidance_slider, steps_slider, seed_slider],
+ outputs=output_image,
+ fn=generate_image,
+ cache_examples=False,
+ label="Examples"
+ )
+
+ gen_btn.click(
+ fn=generate_image,
+ inputs=[lora_dropdown, custom_repo_box, prompt_box, image_input,
+ width_box, height_box, guidance_slider, steps_slider, seed_slider],
+ outputs=output_image
+ )
+ return demo
+
+if __name__ == "__main__":
+ demo = create_interface()
+ demo.launch(ssr_mode=False)
\ No newline at end of file
diff --git a/OmniConsistency/figure/teaser.png b/OmniConsistency/figure/teaser.png
new file mode 100644
index 0000000..382ea77
Binary files /dev/null and b/OmniConsistency/figure/teaser.png differ
diff --git a/OmniConsistency/figure/test.png b/OmniConsistency/figure/test.png
new file mode 100644
index 0000000..053bc90
Binary files /dev/null and b/OmniConsistency/figure/test.png differ
diff --git a/OmniConsistency/infer.py b/OmniConsistency/infer.py
new file mode 100644
index 0000000..d4b5e63
--- /dev/null
+++ b/OmniConsistency/infer.py
@@ -0,0 +1,54 @@
+import time
+import torch
+from PIL import Image
+from .src_inference.pipeline import FluxPipeline
+from .src_inference.lora_helper import set_single_lora
+
+# torch.cuda.set_device(0)
+
+def clear_cache(transformer):
+ for name, attn_processor in transformer.attn_processors.items():
+ attn_processor.bank_kv.clear()
+
+# base_path = "black-forest-labs/FLUX.1-dev"
+# pipe = FluxPipeline.from_pretrained(base_path, torch_dtype=torch.bfloat16).to("cuda")
+
+# set_single_lora(pipe.transformer,
+# "/path/to/OmniConsistency.safetensors",
+# lora_weights=[1], cond_size=512)
+
+# pipe.unload_lora_weights()
+# pipe.load_lora_weights("/path/to/lora_folder",
+# weight_name="lora_name.safetensors")
+
+# image_path1 = "figure/test.png"
+# prompt = "3D Chibi style, Three individuals standing together in the office."
+
+# subject_images = []
+# spatial_image = [Image.open(image_path1).convert("RGB")]
+
+# width, height = 1024, 1024
+
+# start_time = time.time()
+
+# image = pipe(
+# prompt,
+# height=height,
+# width=width,
+# guidance_scale=3.5,
+# num_inference_steps=25,
+# max_sequence_length=512,
+# generator=torch.Generator("cpu").manual_seed(5),
+# spatial_images=spatial_image,
+# subject_images=subject_images,
+# cond_size=512,
+# ).images[0]
+
+# end_time = time.time()
+# elapsed_time = end_time - start_time
+# print(f"code running time: {elapsed_time} s")
+
+# # Clear cache after generation
+# clear_cache(pipe.transformer)
+
+# image.save("results/output.png")
diff --git a/OmniConsistency/requirements.txt b/OmniConsistency/requirements.txt
new file mode 100644
index 0000000..7785d7c
--- /dev/null
+++ b/OmniConsistency/requirements.txt
@@ -0,0 +1,17 @@
+--extra-index-url https://download.pytorch.org/whl/cu124
+torch
+torchvision
+torchaudio==2.3.1
+diffusers==0.32.2
+easydict==1.13
+einops==0.8.1
+peft==0.14.0
+pillow==11.0.0
+protobuf==5.29.3
+requests==2.32.3
+safetensors==0.5.2
+sentencepiece==0.2.0
+spaces==0.34.1
+transformers==4.49.0
+datasets
+wandb
diff --git a/OmniConsistency/src_inference/__init__.py b/OmniConsistency/src_inference/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/OmniConsistency/src_inference/__pycache__/__init__.cpython-311.pyc b/OmniConsistency/src_inference/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000..643ef76
Binary files /dev/null and b/OmniConsistency/src_inference/__pycache__/__init__.cpython-311.pyc differ
diff --git a/OmniConsistency/src_inference/__pycache__/layers_cache.cpython-311.pyc b/OmniConsistency/src_inference/__pycache__/layers_cache.cpython-311.pyc
new file mode 100644
index 0000000..494584c
Binary files /dev/null and b/OmniConsistency/src_inference/__pycache__/layers_cache.cpython-311.pyc differ
diff --git a/OmniConsistency/src_inference/__pycache__/lora_helper.cpython-311.pyc b/OmniConsistency/src_inference/__pycache__/lora_helper.cpython-311.pyc
new file mode 100644
index 0000000..920ab8f
Binary files /dev/null and b/OmniConsistency/src_inference/__pycache__/lora_helper.cpython-311.pyc differ
diff --git a/OmniConsistency/src_inference/__pycache__/pipeline.cpython-311.pyc b/OmniConsistency/src_inference/__pycache__/pipeline.cpython-311.pyc
new file mode 100644
index 0000000..8f2a770
Binary files /dev/null and b/OmniConsistency/src_inference/__pycache__/pipeline.cpython-311.pyc differ
diff --git a/OmniConsistency/src_inference/layers_cache.py b/OmniConsistency/src_inference/layers_cache.py
new file mode 100644
index 0000000..e4245a8
--- /dev/null
+++ b/OmniConsistency/src_inference/layers_cache.py
@@ -0,0 +1,366 @@
+import inspect
+import math
+from typing import Callable, List, Optional, Tuple, Union
+from einops import rearrange
+import torch
+from torch import nn
+import torch.nn.functional as F
+from torch import Tensor
+from diffusers.models.attention_processor import Attention
+
+class LoRALinearLayer(nn.Module):
+ def __init__(
+ self,
+ in_features: int,
+ out_features: int,
+ rank: int = 4,
+ network_alpha: Optional[float] = None,
+ device: Optional[Union[torch.device, str]] = None,
+ dtype: Optional[torch.dtype] = None,
+ cond_width=512,
+ cond_height=512,
+ number=0,
+ n_loras=1
+ ):
+ super().__init__()
+ self.down = nn.Linear(in_features, rank, bias=False, device=device, dtype=dtype)
+ self.up = nn.Linear(rank, out_features, bias=False, device=device, dtype=dtype)
+ # This value has the same meaning as the `--network_alpha` option in the kohya-ss trainer script.
+ # See https://github.com/darkstorm2150/sd-scripts/blob/main/docs/train_network_README-en.md#execute-learning
+ self.network_alpha = network_alpha
+ self.rank = rank
+ self.out_features = out_features
+ self.in_features = in_features
+
+ nn.init.normal_(self.down.weight, std=1 / rank)
+ nn.init.zeros_(self.up.weight)
+
+ self.cond_height = cond_height
+ self.cond_width = cond_width
+ self.number = number
+ self.n_loras = n_loras
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ orig_dtype = hidden_states.dtype
+ dtype = self.down.weight.dtype
+
+ ####
+ batch_size = hidden_states.shape[0]
+ cond_size = self.cond_width // 8 * self.cond_height // 8 * 16 // 64
+ block_size = hidden_states.shape[1] - cond_size * self.n_loras
+ shape = (batch_size, hidden_states.shape[1], 3072)
+ mask = torch.ones(shape, device=hidden_states.device, dtype=dtype)
+ mask[:, :block_size+self.number*cond_size, :] = 0
+ mask[:, block_size+(self.number+1)*cond_size:, :] = 0
+ hidden_states = mask * hidden_states
+ ####
+
+ down_hidden_states = self.down(hidden_states.to(dtype))
+ up_hidden_states = self.up(down_hidden_states)
+
+ if self.network_alpha is not None:
+ up_hidden_states *= self.network_alpha / self.rank
+
+ return up_hidden_states.to(orig_dtype)
+
+
+class MultiSingleStreamBlockLoraProcessor(nn.Module):
+ def __init__(self, dim: int, ranks=[], lora_weights=[], network_alphas=[], device=None, dtype=None, cond_width=512, cond_height=512, n_loras=1):
+ super().__init__()
+ # Initialize a list to store the LoRA layers
+ self.n_loras = n_loras
+ self.cond_width = cond_width
+ self.cond_height = cond_height
+
+ self.q_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.k_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.v_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.lora_weights = lora_weights
+ self.bank_attn = None
+ self.bank_kv = []
+
+
+ def __call__(self,
+ attn: Attention,
+ hidden_states: torch.FloatTensor,
+ encoder_hidden_states: torch.FloatTensor = None,
+ attention_mask: Optional[torch.FloatTensor] = None,
+ image_rotary_emb: Optional[torch.Tensor] = None,
+ use_cond = False,
+ image_emb: torch.FloatTensor = None
+ ) -> torch.FloatTensor:
+
+ scaled_cond_size = self.cond_width // 8 * self.cond_height // 8 * 16 // 64
+ batch_size, _, _ = hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
+ scaled_seq_len = hidden_states.shape[1]
+ block_size = scaled_seq_len - scaled_cond_size * self.n_loras
+
+ if len(self.bank_kv)== 0:
+ cache = True
+ else:
+ cache = False
+
+ if cache:
+ query = attn.to_q(hidden_states)
+ key = attn.to_k(hidden_states)
+ value = attn.to_v(hidden_states)
+ for i in range(self.n_loras):
+ query = query + self.lora_weights[i] * self.q_loras[i](hidden_states)
+ key = key + self.lora_weights[i] * self.k_loras[i](hidden_states)
+ value = value + self.lora_weights[i] * self.v_loras[i](hidden_states)
+
+ inner_dim = key.shape[-1]
+ head_dim = inner_dim // attn.heads
+
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+
+
+ self.bank_kv.append(key[:, :, block_size:, :])
+ self.bank_kv.append(value[:, :, block_size:, :])
+
+ if attn.norm_q is not None:
+ query = attn.norm_q(query)
+ if attn.norm_k is not None:
+ key = attn.norm_k(key)
+
+ if image_rotary_emb is not None:
+ from diffusers.models.embeddings import apply_rotary_emb
+ query = apply_rotary_emb(query, image_rotary_emb)
+ key = apply_rotary_emb(key, image_rotary_emb)
+
+ num_cond_blocks = self.n_loras
+ mask = torch.ones((scaled_seq_len, scaled_seq_len), device=hidden_states.device)
+ mask[ :block_size, :] = 0 # First block_size row
+ for i in range(num_cond_blocks):
+ start = i * scaled_cond_size + block_size
+ end = (i + 1) * scaled_cond_size + block_size
+ mask[start:end, start:end] = 0 # Diagonal blocks
+ mask = mask * -1e20
+ mask = mask.to(query.dtype)
+
+ hidden_states = F.scaled_dot_product_attention(query, key, value, dropout_p=0.0, is_causal=False, attn_mask=mask)
+ else:
+ query = attn.to_q(hidden_states)
+ key = attn.to_k(hidden_states)
+ value = attn.to_v(hidden_states)
+
+ inner_dim = query.shape[-1]
+ head_dim = inner_dim // attn.heads
+
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+
+ zero_pad = torch.zeros_like(self.bank_kv[0], dtype=query.dtype, device=query.device)
+
+
+ key = torch.concat([key[:, :, :scaled_seq_len, :], self.bank_kv[0]], dim=-2)
+ value = torch.concat([value[:, :, :scaled_seq_len, :], self.bank_kv[1]], dim=-2)
+
+ if attn.norm_q is not None:
+ query = attn.norm_q(query)
+ if attn.norm_k is not None:
+ key = attn.norm_k(key)
+
+ query = torch.concat([query[:, :, :scaled_seq_len, :], zero_pad], dim=-2)
+
+ if image_rotary_emb is not None:
+ from diffusers.models.embeddings import apply_rotary_emb
+ query = apply_rotary_emb(query, image_rotary_emb)
+ key = apply_rotary_emb(key, image_rotary_emb)
+
+ query = query[:, :, :scaled_seq_len, :]
+
+ hidden_states = F.scaled_dot_product_attention(query, key, value, dropout_p=0.0, is_causal=False, attn_mask=None)
+
+ hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
+ hidden_states = hidden_states.to(query.dtype)
+
+ hidden_states = hidden_states[:, : scaled_seq_len,:]
+
+ return hidden_states
+
+
+class MultiDoubleStreamBlockLoraProcessor(nn.Module):
+ def __init__(self, dim: int, ranks=[], lora_weights=[], network_alphas=[], device=None, dtype=None, cond_width=512, cond_height=512, n_loras=1):
+ super().__init__()
+
+ # Initialize a list to store the LoRA layers
+ self.n_loras = n_loras
+ self.cond_width = cond_width
+ self.cond_height = cond_height
+ self.q_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.k_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.v_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.proj_loras = nn.ModuleList([
+ LoRALinearLayer(dim, dim, ranks[i],network_alphas[i], device=device, dtype=dtype, cond_width=cond_width, cond_height=cond_height, number=i, n_loras=n_loras)
+ for i in range(n_loras)
+ ])
+ self.lora_weights = lora_weights
+ self.bank_attn = None
+ self.bank_kv = []
+
+
+ def __call__(self,
+ attn: Attention,
+ hidden_states: torch.FloatTensor,
+ encoder_hidden_states: torch.FloatTensor = None,
+ attention_mask: Optional[torch.FloatTensor] = None,
+ image_rotary_emb: Optional[torch.Tensor] = None,
+ use_cond=False,
+ image_emb: torch.FloatTensor = None
+ ) -> torch.FloatTensor:
+
+ scaled_cond_size = self.cond_width // 8 * self.cond_height // 8 * 16 // 64
+ batch_size, _, _ = hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
+ block_size = hidden_states.shape[1]
+ scaled_seq_len = encoder_hidden_states.shape[1] + hidden_states.shape[1]
+ scaled_block_size = scaled_seq_len
+
+ # `context` projections.
+ inner_dim = 3072
+ head_dim = inner_dim // attn.heads
+ encoder_hidden_states_query_proj = attn.add_q_proj(encoder_hidden_states)
+ encoder_hidden_states_key_proj = attn.add_k_proj(encoder_hidden_states)
+ encoder_hidden_states_value_proj = attn.add_v_proj(encoder_hidden_states)
+
+ encoder_hidden_states_query_proj = encoder_hidden_states_query_proj.view(
+ batch_size, -1, attn.heads, head_dim
+ ).transpose(1, 2)
+ encoder_hidden_states_key_proj = encoder_hidden_states_key_proj.view(
+ batch_size, -1, attn.heads, head_dim
+ ).transpose(1, 2)
+ encoder_hidden_states_value_proj = encoder_hidden_states_value_proj.view(
+ batch_size, -1, attn.heads, head_dim
+ ).transpose(1, 2)
+
+ if attn.norm_added_q is not None:
+ encoder_hidden_states_query_proj = attn.norm_added_q(encoder_hidden_states_query_proj)
+ if attn.norm_added_k is not None:
+ encoder_hidden_states_key_proj = attn.norm_added_k(encoder_hidden_states_key_proj)
+
+ if len(self.bank_kv)== 0:
+ cache = True
+ else:
+ cache = False
+
+ if cache:
+
+ query = attn.to_q(hidden_states)
+ key = attn.to_k(hidden_states)
+ value = attn.to_v(hidden_states)
+ for i in range(self.n_loras):
+ query = query + self.lora_weights[i] * self.q_loras[i](hidden_states)
+ key = key + self.lora_weights[i] * self.k_loras[i](hidden_states)
+ value = value + self.lora_weights[i] * self.v_loras[i](hidden_states)
+
+ inner_dim = key.shape[-1]
+ head_dim = inner_dim // attn.heads
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+
+
+ self.bank_kv.append(key)
+ self.bank_kv.append(value)
+
+ if attn.norm_q is not None:
+ query = attn.norm_q(query)
+ if attn.norm_k is not None:
+ key = attn.norm_k(key)
+
+ # attention
+ query = torch.cat([encoder_hidden_states_query_proj, query], dim=2)
+ key = torch.cat([encoder_hidden_states_key_proj, key], dim=2)
+ value = torch.cat([encoder_hidden_states_value_proj, value], dim=2)
+
+ if image_rotary_emb is not None:
+ from diffusers.models.embeddings import apply_rotary_emb
+ query = apply_rotary_emb(query, image_rotary_emb)
+ key = apply_rotary_emb(key, image_rotary_emb)
+
+ num_cond_blocks = self.n_loras
+ mask = torch.ones((scaled_seq_len, scaled_seq_len), device=hidden_states.device)
+ mask[ :scaled_block_size-block_size, :] = 0 # First block_size row
+ for i in range(num_cond_blocks):
+ start = i * scaled_cond_size + scaled_block_size-block_size
+ end = (i + 1) * scaled_cond_size + scaled_block_size-block_size
+ mask[start:end, start:end] = 0 # Diagonal blocks
+ mask = mask * -1e20
+ mask = mask.to(query.dtype)
+
+ hidden_states = F.scaled_dot_product_attention(query, key, value, dropout_p=0.0, is_causal=False, attn_mask=mask)
+
+ else:
+ query = attn.to_q(hidden_states)
+ key = attn.to_k(hidden_states)
+ value = attn.to_v(hidden_states)
+
+ inner_dim = query.shape[-1]
+ head_dim = inner_dim // attn.heads
+
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+
+ zero_pad = torch.zeros_like(self.bank_kv[0], dtype=query.dtype, device=query.device)
+
+ key = torch.concat([key[:, :, :block_size, :], self.bank_kv[0]], dim=-2)
+ value = torch.concat([value[:, :, :block_size, :], self.bank_kv[1]], dim=-2)
+
+ if attn.norm_q is not None:
+ query = attn.norm_q(query)
+ if attn.norm_k is not None:
+ key = attn.norm_k(key)
+
+ query = torch.concat([query[:, :, :block_size, :], zero_pad], dim=-2)
+
+ # attention
+ query = torch.cat([encoder_hidden_states_query_proj, query], dim=2)
+ key = torch.cat([encoder_hidden_states_key_proj, key], dim=2)
+ value = torch.cat([encoder_hidden_states_value_proj, value], dim=2)
+
+ if image_rotary_emb is not None:
+ from diffusers.models.embeddings import apply_rotary_emb
+ query = apply_rotary_emb(query, image_rotary_emb)
+ key = apply_rotary_emb(key, image_rotary_emb)
+
+ query = query[:, :, :scaled_block_size, :]
+
+ hidden_states = F.scaled_dot_product_attention(query, key, value, dropout_p=0.0, is_causal=False, attn_mask=None)
+
+ hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
+ hidden_states = hidden_states.to(query.dtype)
+
+ encoder_hidden_states, hidden_states = (
+ hidden_states[:, : encoder_hidden_states.shape[1]],
+ hidden_states[:, encoder_hidden_states.shape[1] :],
+ )
+
+ # Linear projection (with LoRA weight applied to each proj layer)
+ hidden_states = attn.to_out[0](hidden_states)
+ encoder_hidden_states = attn.to_add_out(encoder_hidden_states)
+
+ hidden_states = hidden_states[:, :block_size,:]
+
+ return hidden_states, encoder_hidden_states
\ No newline at end of file
diff --git a/OmniConsistency/src_inference/lora_helper.py b/OmniConsistency/src_inference/lora_helper.py
new file mode 100644
index 0000000..aca1f21
--- /dev/null
+++ b/OmniConsistency/src_inference/lora_helper.py
@@ -0,0 +1,194 @@
+from diffusers.models.attention_processor import FluxAttnProcessor2_0
+from safetensors import safe_open
+import re
+import torch
+from .layers_cache import MultiDoubleStreamBlockLoraProcessor, MultiSingleStreamBlockLoraProcessor
+
+device = "cuda"
+
+def load_safetensors(path):
+ tensors = {}
+ with safe_open(path, framework="pt", device="cpu") as f:
+ for key in f.keys():
+ tensors[key] = f.get_tensor(key)
+ return tensors
+
+def get_lora_rank(checkpoint):
+ for k in checkpoint.keys():
+ if k.endswith(".down.weight"):
+ return checkpoint[k].shape[0]
+
+def load_checkpoint(local_path):
+ if local_path is not None:
+ if '.safetensors' in local_path:
+ print(f"Loading .safetensors checkpoint from {local_path}")
+ checkpoint = load_safetensors(local_path)
+ else:
+ print(f"Loading checkpoint from {local_path}")
+ checkpoint = torch.load(local_path, map_location='cpu')
+ return checkpoint
+
+def update_model_with_lora(checkpoint, lora_weights, transformer, cond_size):
+ number = len(lora_weights)
+ ranks = [get_lora_rank(checkpoint) for _ in range(number)]
+ lora_attn_procs = {}
+ double_blocks_idx = list(range(19))
+ single_blocks_idx = list(range(38))
+ for name, attn_processor in transformer.attn_processors.items():
+ match = re.search(r'\.(\d+)\.', name)
+ if match:
+ layer_index = int(match.group(1))
+
+ if name.startswith("transformer_blocks") and layer_index in double_blocks_idx:
+
+ lora_state_dicts = {}
+ for key, value in checkpoint.items():
+ # Match based on the layer index in the key (assuming the key contains layer index)
+ if re.search(r'\.(\d+)\.', key):
+ checkpoint_layer_index = int(re.search(r'\.(\d+)\.', key).group(1))
+ if checkpoint_layer_index == layer_index and key.startswith("transformer_blocks"):
+ lora_state_dicts[key] = value
+
+ lora_attn_procs[name] = MultiDoubleStreamBlockLoraProcessor(
+ dim=3072, ranks=ranks, network_alphas=ranks, lora_weights=lora_weights, device=device, dtype=torch.bfloat16, cond_width=cond_size, cond_height=cond_size, n_loras=number
+ )
+
+ # Load the weights from the checkpoint dictionary into the corresponding layers
+ for n in range(number):
+ lora_attn_procs[name].q_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.q_loras.{n}.down.weight', None)
+ lora_attn_procs[name].q_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.q_loras.{n}.up.weight', None)
+ lora_attn_procs[name].k_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.k_loras.{n}.down.weight', None)
+ lora_attn_procs[name].k_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.k_loras.{n}.up.weight', None)
+ lora_attn_procs[name].v_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.v_loras.{n}.down.weight', None)
+ lora_attn_procs[name].v_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.v_loras.{n}.up.weight', None)
+ lora_attn_procs[name].proj_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.proj_loras.{n}.down.weight', None)
+ lora_attn_procs[name].proj_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.proj_loras.{n}.up.weight', None)
+ lora_attn_procs[name].to(device)
+
+ elif name.startswith("single_transformer_blocks") and layer_index in single_blocks_idx:
+
+ lora_state_dicts = {}
+ for key, value in checkpoint.items():
+ if re.search(r'\.(\d+)\.', key):
+ checkpoint_layer_index = int(re.search(r'\.(\d+)\.', key).group(1))
+ if checkpoint_layer_index == layer_index and key.startswith("single_transformer_blocks"):
+ lora_state_dicts[key] = value
+
+ lora_attn_procs[name] = MultiSingleStreamBlockLoraProcessor(
+ dim=3072, ranks=ranks, network_alphas=ranks, lora_weights=lora_weights, device=device, dtype=torch.bfloat16, cond_width=cond_size, cond_height=cond_size, n_loras=number
+ )
+ for n in range(number):
+ lora_attn_procs[name].q_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.q_loras.{n}.down.weight', None)
+ lora_attn_procs[name].q_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.q_loras.{n}.up.weight', None)
+ lora_attn_procs[name].k_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.k_loras.{n}.down.weight', None)
+ lora_attn_procs[name].k_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.k_loras.{n}.up.weight', None)
+ lora_attn_procs[name].v_loras[n].down.weight.data = lora_state_dicts.get(f'{name}.v_loras.{n}.down.weight', None)
+ lora_attn_procs[name].v_loras[n].up.weight.data = lora_state_dicts.get(f'{name}.v_loras.{n}.up.weight', None)
+ lora_attn_procs[name].to(device)
+ else:
+ lora_attn_procs[name] = FluxAttnProcessor2_0()
+
+ transformer.set_attn_processor(lora_attn_procs)
+
+
+def update_model_with_multi_lora(checkpoints, lora_weights, transformer, cond_size):
+ ck_number = len(checkpoints)
+ cond_lora_number = [len(ls) for ls in lora_weights]
+ cond_number = sum(cond_lora_number)
+ ranks = [get_lora_rank(checkpoint) for checkpoint in checkpoints]
+ multi_lora_weight = []
+ for ls in lora_weights:
+ for n in ls:
+ multi_lora_weight.append(n)
+
+ lora_attn_procs = {}
+ double_blocks_idx = list(range(19))
+ single_blocks_idx = list(range(38))
+ for name, attn_processor in transformer.attn_processors.items():
+ match = re.search(r'\.(\d+)\.', name)
+ if match:
+ layer_index = int(match.group(1))
+
+ if name.startswith("transformer_blocks") and layer_index in double_blocks_idx:
+ lora_state_dicts = [{} for _ in range(ck_number)]
+ for idx, checkpoint in enumerate(checkpoints):
+ for key, value in checkpoint.items():
+ # Match based on the layer index in the key (assuming the key contains layer index)
+ if re.search(r'\.(\d+)\.', key):
+ checkpoint_layer_index = int(re.search(r'\.(\d+)\.', key).group(1))
+ if checkpoint_layer_index == layer_index and key.startswith("transformer_blocks"):
+ lora_state_dicts[idx][key] = value
+
+ lora_attn_procs[name] = MultiDoubleStreamBlockLoraProcessor(
+ dim=3072, ranks=ranks, network_alphas=ranks, lora_weights=multi_lora_weight, device=device, dtype=torch.bfloat16, cond_width=cond_size, cond_height=cond_size, n_loras=cond_number
+ )
+
+ # Load the weights from the checkpoint dictionary into the corresponding layers
+ num = 0
+ for idx in range(ck_number):
+ for n in range(cond_lora_number[idx]):
+ lora_attn_procs[name].q_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.q_loras.{n}.down.weight', None)
+ lora_attn_procs[name].q_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.q_loras.{n}.up.weight', None)
+ lora_attn_procs[name].k_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.k_loras.{n}.down.weight', None)
+ lora_attn_procs[name].k_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.k_loras.{n}.up.weight', None)
+ lora_attn_procs[name].v_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.v_loras.{n}.down.weight', None)
+ lora_attn_procs[name].v_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.v_loras.{n}.up.weight', None)
+ lora_attn_procs[name].proj_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.proj_loras.{n}.down.weight', None)
+ lora_attn_procs[name].proj_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.proj_loras.{n}.up.weight', None)
+ lora_attn_procs[name].to(device)
+ num += 1
+
+ elif name.startswith("single_transformer_blocks") and layer_index in single_blocks_idx:
+
+ lora_state_dicts = [{} for _ in range(ck_number)]
+ for idx, checkpoint in enumerate(checkpoints):
+ for key, value in checkpoint.items():
+ # Match based on the layer index in the key (assuming the key contains layer index)
+ if re.search(r'\.(\d+)\.', key):
+ checkpoint_layer_index = int(re.search(r'\.(\d+)\.', key).group(1))
+ if checkpoint_layer_index == layer_index and key.startswith("single_transformer_blocks"):
+ lora_state_dicts[idx][key] = value
+
+ lora_attn_procs[name] = MultiSingleStreamBlockLoraProcessor(
+ dim=3072, ranks=ranks, network_alphas=ranks, lora_weights=multi_lora_weight, device=device, dtype=torch.bfloat16, cond_width=cond_size, cond_height=cond_size, n_loras=cond_number
+ )
+ # Load the weights from the checkpoint dictionary into the corresponding layers
+ num = 0
+ for idx in range(ck_number):
+ for n in range(cond_lora_number[idx]):
+ lora_attn_procs[name].q_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.q_loras.{n}.down.weight', None)
+ lora_attn_procs[name].q_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.q_loras.{n}.up.weight', None)
+ lora_attn_procs[name].k_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.k_loras.{n}.down.weight', None)
+ lora_attn_procs[name].k_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.k_loras.{n}.up.weight', None)
+ lora_attn_procs[name].v_loras[num].down.weight.data = lora_state_dicts[idx].get(f'{name}.v_loras.{n}.down.weight', None)
+ lora_attn_procs[name].v_loras[num].up.weight.data = lora_state_dicts[idx].get(f'{name}.v_loras.{n}.up.weight', None)
+ lora_attn_procs[name].to(device)
+ num += 1
+
+ else:
+ lora_attn_procs[name] = FluxAttnProcessor2_0()
+
+ transformer.set_attn_processor(lora_attn_procs)
+
+
+def set_single_lora(transformer, local_path, lora_weights=[], cond_size=512):
+ checkpoint = load_checkpoint(local_path)
+ update_model_with_lora(checkpoint, lora_weights, transformer, cond_size)
+
+def set_multi_lora(transformer, local_paths, lora_weights=[[]], cond_size=512):
+ checkpoints = [load_checkpoint(local_path) for local_path in local_paths]
+ update_model_with_multi_lora(checkpoints, lora_weights, transformer, cond_size)
+
+def unset_lora(transformer):
+ lora_attn_procs = {}
+ for name, attn_processor in transformer.attn_processors.items():
+ lora_attn_procs[name] = FluxAttnProcessor2_0()
+ transformer.set_attn_processor(lora_attn_procs)
+
+
+'''
+unset_lora(pipe.transformer)
+lora_path = "./lora.safetensors"
+lora_weights = [1, 1]
+set_lora(pipe.transformer, local_path=lora_path, lora_weights=lora_weights, cond_size=512)
+'''
\ No newline at end of file
diff --git a/OmniConsistency/src_inference/pipeline.py b/OmniConsistency/src_inference/pipeline.py
new file mode 100644
index 0000000..9c5e79b
--- /dev/null
+++ b/OmniConsistency/src_inference/pipeline.py
@@ -0,0 +1,749 @@
+import inspect
+from typing import Any, Callable, Dict, List, Optional, Union
+
+import numpy as np
+import torch
+from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
+
+from diffusers.image_processor import (VaeImageProcessor)
+from diffusers.loaders import FluxLoraLoaderMixin, FromSingleFileMixin
+from diffusers.models.autoencoders import AutoencoderKL
+from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
+from diffusers.utils import (
+ USE_PEFT_BACKEND,
+ is_torch_xla_available,
+ logging,
+ scale_lora_layers,
+ unscale_lora_layers,
+)
+from diffusers.utils.torch_utils import randn_tensor
+from diffusers.pipelines.pipeline_utils import DiffusionPipeline
+from diffusers.pipelines.flux.pipeline_output import FluxPipelineOutput
+from torchvision.transforms.functional import pad
+from diffusers import FluxTransformer2DModel
+
+if is_torch_xla_available():
+ import torch_xla.core.xla_model as xm
+
+ XLA_AVAILABLE = True
+else:
+ XLA_AVAILABLE = False
+
+logger = logging.get_logger(__name__) # pylint: disable=invalid-name
+
+def calculate_shift(
+ image_seq_len,
+ base_seq_len: int = 256,
+ max_seq_len: int = 4096,
+ base_shift: float = 0.5,
+ max_shift: float = 1.16,
+):
+ m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
+ b = base_shift - m * base_seq_len
+ mu = image_seq_len * m + b
+ return mu
+
+def prepare_latent_image_ids_(height, width, device, dtype):
+ latent_image_ids = torch.zeros(height//2, width//2, 3, device=device, dtype=dtype)
+ latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height//2, device=device)[:, None] # y
+ latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width//2, device=device)[None, :] # x
+ return latent_image_ids
+
+def prepare_latent_subject_ids(height, width, device, dtype):
+ latent_image_ids = torch.zeros(height // 2, width // 2, 3, device=device, dtype=dtype)
+ latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height // 2, device=device)[:, None]
+ latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width // 2, device=device)[None, :]
+ latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
+ latent_image_ids = latent_image_ids.reshape(
+ latent_image_id_height * latent_image_id_width, latent_image_id_channels
+ )
+ return latent_image_ids.to(device=device, dtype=dtype)
+
+def resize_position_encoding(batch_size, original_height, original_width, target_height, target_width, device, dtype):
+ latent_image_ids = prepare_latent_image_ids_(original_height, original_width, device, dtype)
+ latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
+ latent_image_ids = latent_image_ids.reshape(
+ latent_image_id_height * latent_image_id_width, latent_image_id_channels
+ )
+
+ scale_h = original_height / target_height
+ scale_w = original_width / target_width
+ latent_image_ids_resized = torch.zeros(target_height//2, target_width//2, 3, device=device, dtype=dtype)
+ latent_image_ids_resized[..., 1] = latent_image_ids_resized[..., 1] + torch.arange(target_height//2, device=device)[:, None] * scale_h
+ latent_image_ids_resized[..., 2] = latent_image_ids_resized[..., 2] + torch.arange(target_width//2, device=device)[None, :] * scale_w
+
+ cond_latent_image_id_height, cond_latent_image_id_width, cond_latent_image_id_channels = latent_image_ids_resized.shape
+ cond_latent_image_ids = latent_image_ids_resized.reshape(
+ cond_latent_image_id_height * cond_latent_image_id_width, cond_latent_image_id_channels
+ )
+ return latent_image_ids, cond_latent_image_ids
+
+# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
+def retrieve_latents(
+ encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"
+):
+ if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
+ return encoder_output.latent_dist.sample(generator)
+ elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
+ return encoder_output.latent_dist.mode()
+ elif hasattr(encoder_output, "latents"):
+ return encoder_output.latents
+ else:
+ raise AttributeError("Could not access latents of provided encoder_output")
+
+
+# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
+def retrieve_timesteps(
+ scheduler,
+ num_inference_steps: Optional[int] = None,
+ device: Optional[Union[str, torch.device]] = None,
+ timesteps: Optional[List[int]] = None,
+ sigmas: Optional[List[float]] = None,
+ **kwargs,
+):
+ if timesteps is not None and sigmas is not None:
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
+ if timesteps is not None:
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
+ if not accepts_timesteps:
+ raise ValueError(
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
+ f" timestep schedules. Please check whether you are using the correct scheduler."
+ )
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
+ timesteps = scheduler.timesteps
+ num_inference_steps = len(timesteps)
+ elif sigmas is not None:
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
+ if not accept_sigmas:
+ raise ValueError(
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
+ )
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
+ timesteps = scheduler.timesteps
+ num_inference_steps = len(timesteps)
+ else:
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
+ timesteps = scheduler.timesteps
+ return timesteps, num_inference_steps
+
+
+class FluxPipeline(DiffusionPipeline, FluxLoraLoaderMixin, FromSingleFileMixin):
+ model_cpu_offload_seq = "text_encoder->text_encoder_2->transformer->vae"
+ def __init__(
+ self,
+ scheduler: FlowMatchEulerDiscreteScheduler,
+ vae: AutoencoderKL,
+ text_encoder: CLIPTextModel,
+ tokenizer: CLIPTokenizer,
+ text_encoder_2: T5EncoderModel,
+ tokenizer_2: T5TokenizerFast,
+ transformer: FluxTransformer2DModel,
+ ):
+ super().__init__()
+
+ self.register_modules(
+ vae=vae,
+ text_encoder=text_encoder,
+ text_encoder_2=text_encoder_2,
+ tokenizer=tokenizer,
+ tokenizer_2=tokenizer_2,
+ transformer=transformer,
+ scheduler=scheduler,
+ )
+ self.vae_scale_factor = (
+ 2 ** (len(self.vae.config.block_out_channels)) if hasattr(self, "vae") and self.vae is not None else 16
+ )
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
+ self.tokenizer_max_length = (
+ self.tokenizer.model_max_length if hasattr(self, "tokenizer") and self.tokenizer is not None else 77
+ )
+ self.default_sample_size = 64
+
+ def _get_t5_prompt_embeds(
+ self,
+ prompt: Union[str, List[str]] = None,
+ num_images_per_prompt: int = 1,
+ max_sequence_length: int = 512,
+ device: Optional[torch.device] = None,
+ dtype: Optional[torch.dtype] = None,
+ ):
+ device = device or self._execution_device
+ dtype = dtype or self.text_encoder.dtype
+
+ prompt = [prompt] if isinstance(prompt, str) else prompt
+ batch_size = len(prompt)
+
+ text_inputs = self.tokenizer_2(
+ prompt,
+ padding="max_length",
+ max_length=max_sequence_length,
+ truncation=True,
+ return_length=False,
+ return_overflowing_tokens=False,
+ return_tensors="pt",
+ )
+ text_input_ids = text_inputs.input_ids
+ untruncated_ids = self.tokenizer_2(prompt, padding="longest", return_tensors="pt").input_ids
+
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
+ removed_text = self.tokenizer_2.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1: -1])
+ logger.warning(
+ "The following part of your input was truncated because `max_sequence_length` is set to "
+ f" {max_sequence_length} tokens: {removed_text}"
+ )
+
+ prompt_embeds = self.text_encoder_2(text_input_ids.to(device), output_hidden_states=False)[0]
+
+ dtype = self.text_encoder_2.dtype
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
+
+ _, seq_len, _ = prompt_embeds.shape
+
+ # duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method
+ prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
+ prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
+
+ return prompt_embeds
+
+ def _get_clip_prompt_embeds(
+ self,
+ prompt: Union[str, List[str]],
+ num_images_per_prompt: int = 1,
+ device: Optional[torch.device] = None,
+ ):
+ device = device or self._execution_device
+
+ prompt = [prompt] if isinstance(prompt, str) else prompt
+ batch_size = len(prompt)
+
+ text_inputs = self.tokenizer(
+ prompt,
+ padding="max_length",
+ max_length=self.tokenizer_max_length,
+ truncation=True,
+ return_overflowing_tokens=False,
+ return_length=False,
+ return_tensors="pt",
+ )
+
+ text_input_ids = text_inputs.input_ids
+ untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
+ removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1: -1])
+ logger.warning(
+ "The following part of your input was truncated because CLIP can only handle sequences up to"
+ f" {self.tokenizer_max_length} tokens: {removed_text}"
+ )
+ prompt_embeds = self.text_encoder(text_input_ids.to(device), output_hidden_states=False)
+
+ # Use pooled output of CLIPTextModel
+ prompt_embeds = prompt_embeds.pooler_output
+ prompt_embeds = prompt_embeds.to(dtype=self.text_encoder.dtype, device=device)
+
+ # duplicate text embeddings for each generation per prompt, using mps friendly method
+ prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt)
+ prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, -1)
+
+ return prompt_embeds
+
+ def encode_prompt(
+ self,
+ prompt: Union[str, List[str]],
+ prompt_2: Union[str, List[str]],
+ device: Optional[torch.device] = None,
+ num_images_per_prompt: int = 1,
+ prompt_embeds: Optional[torch.FloatTensor] = None,
+ pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
+ max_sequence_length: int = 512,
+ lora_scale: Optional[float] = None,
+ ):
+ device = device or self._execution_device
+
+ # set lora scale so that monkey patched LoRA
+ # function of text encoder can correctly access it
+ if lora_scale is not None and isinstance(self, FluxLoraLoaderMixin):
+ self._lora_scale = lora_scale
+
+ # dynamically adjust the LoRA scale
+ if self.text_encoder is not None and USE_PEFT_BACKEND:
+ scale_lora_layers(self.text_encoder, lora_scale)
+ if self.text_encoder_2 is not None and USE_PEFT_BACKEND:
+ scale_lora_layers(self.text_encoder_2, lora_scale)
+
+ prompt = [prompt] if isinstance(prompt, str) else prompt
+
+ if prompt_embeds is None:
+ prompt_2 = prompt_2 or prompt
+ prompt_2 = [prompt_2] if isinstance(prompt_2, str) else prompt_2
+
+ # We only use the pooled prompt output from the CLIPTextModel
+ pooled_prompt_embeds = self._get_clip_prompt_embeds(
+ prompt=prompt,
+ device=device,
+ num_images_per_prompt=num_images_per_prompt,
+ )
+ prompt_embeds = self._get_t5_prompt_embeds(
+ prompt=prompt_2,
+ num_images_per_prompt=num_images_per_prompt,
+ max_sequence_length=max_sequence_length,
+ device=device,
+ )
+
+ if self.text_encoder is not None:
+ if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND:
+ # Retrieve the original scale by scaling back the LoRA layers
+ unscale_lora_layers(self.text_encoder, lora_scale)
+
+ if self.text_encoder_2 is not None:
+ if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND:
+ # Retrieve the original scale by scaling back the LoRA layers
+ unscale_lora_layers(self.text_encoder_2, lora_scale)
+
+ dtype = self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype
+ text_ids = torch.zeros(prompt_embeds.shape[1], 3).to(device=device, dtype=dtype)
+
+ return prompt_embeds, pooled_prompt_embeds, text_ids
+
+ # Copied from diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3_inpaint.StableDiffusion3InpaintPipeline._encode_vae_image
+ def _encode_vae_image(self, image: torch.Tensor, generator: torch.Generator):
+ if isinstance(generator, list):
+ image_latents = [
+ retrieve_latents(self.vae.encode(image[i: i + 1]), generator=generator[i])
+ for i in range(image.shape[0])
+ ]
+ image_latents = torch.cat(image_latents, dim=0)
+ else:
+ image_latents = retrieve_latents(self.vae.encode(image), generator=generator)
+
+ image_latents = (image_latents - self.vae.config.shift_factor) * self.vae.config.scaling_factor
+
+ return image_latents
+
+ def check_inputs(
+ self,
+ prompt,
+ prompt_2,
+ height,
+ width,
+ prompt_embeds=None,
+ pooled_prompt_embeds=None,
+ callback_on_step_end_tensor_inputs=None,
+ max_sequence_length=None,
+ ):
+ if height % 8 != 0 or width % 8 != 0:
+ raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
+
+ if prompt is not None and prompt_embeds is not None:
+ raise ValueError(
+ f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
+ " only forward one of the two."
+ )
+ elif prompt_2 is not None and prompt_embeds is not None:
+ raise ValueError(
+ f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
+ " only forward one of the two."
+ )
+ elif prompt is None and prompt_embeds is None:
+ raise ValueError(
+ "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
+ )
+ elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
+ elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)):
+ raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}")
+
+ if prompt_embeds is not None and pooled_prompt_embeds is None:
+ raise ValueError(
+ "If `prompt_embeds` are provided, `pooled_prompt_embeds` also have to be passed. Make sure to generate `pooled_prompt_embeds` from the same text encoder that was used to generate `prompt_embeds`."
+ )
+
+ if max_sequence_length is not None and max_sequence_length > 512:
+ raise ValueError(f"`max_sequence_length` cannot be greater than 512 but is {max_sequence_length}")
+
+ @staticmethod
+ def _prepare_latent_image_ids(batch_size, height, width, device, dtype):
+ latent_image_ids = torch.zeros(height // 2, width // 2, 3)
+ latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height // 2)[:, None]
+ latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width // 2)[None, :]
+ latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
+ latent_image_ids = latent_image_ids.reshape(
+ latent_image_id_height * latent_image_id_width, latent_image_id_channels
+ )
+ return latent_image_ids.to(device=device, dtype=dtype)
+
+ @staticmethod
+ def _pack_latents(latents, batch_size, num_channels_latents, height, width):
+ latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
+ latents = latents.permute(0, 2, 4, 1, 3, 5)
+ latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4)
+ return latents
+
+ @staticmethod
+ def _unpack_latents(latents, height, width, vae_scale_factor):
+ batch_size, num_patches, channels = latents.shape
+
+ height = height // vae_scale_factor
+ width = width // vae_scale_factor
+
+ latents = latents.view(batch_size, height, width, channels // 4, 2, 2)
+ latents = latents.permute(0, 3, 1, 4, 2, 5)
+
+ latents = latents.reshape(batch_size, channels // (2 * 2), height * 2, width * 2)
+
+ return latents
+
+ def enable_vae_slicing(self):
+ r"""
+ Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
+ compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
+ """
+ self.vae.enable_slicing()
+
+ def disable_vae_slicing(self):
+ r"""
+ Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to
+ computing decoding in one step.
+ """
+ self.vae.disable_slicing()
+
+ def enable_vae_tiling(self):
+ r"""
+ Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
+ compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
+ processing larger images.
+ """
+ self.vae.enable_tiling()
+
+ def disable_vae_tiling(self):
+ r"""
+ Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to
+ computing decoding in one step.
+ """
+ self.vae.disable_tiling()
+
+ def prepare_latents(
+ self,
+ batch_size,
+ num_channels_latents,
+ height,
+ width,
+ dtype,
+ device,
+ generator,
+ subject_image,
+ condition_image,
+ latents=None,
+ cond_number=1,
+ sub_number=1
+ ):
+ height_cond = 2 * (self.cond_size // self.vae_scale_factor)
+ width_cond = 2 * (self.cond_size // self.vae_scale_factor)
+ height = 2 * (int(height) // self.vae_scale_factor)
+ width = 2 * (int(width) // self.vae_scale_factor)
+
+ shape = (batch_size, num_channels_latents, height, width) # 1 16 106 80
+ noise_latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
+ noise_latents = self._pack_latents(noise_latents, batch_size, num_channels_latents, height, width)
+ noise_latent_image_ids, cond_latent_image_ids = resize_position_encoding(
+ batch_size,
+ height,
+ width,
+ height_cond,
+ width_cond,
+ device,
+ dtype,
+ )
+
+ latents_to_concat = []
+ latents_ids_to_concat = [noise_latent_image_ids]
+
+ # subject
+ if subject_image is not None:
+ shape_subject = (batch_size, num_channels_latents, height_cond*sub_number, width_cond)
+ subject_image = subject_image.to(device=device, dtype=dtype)
+ subject_image_latents = self._encode_vae_image(image=subject_image, generator=generator)
+ subject_latents = self._pack_latents(subject_image_latents, batch_size, num_channels_latents, height_cond*sub_number, width_cond)
+ mask2 = torch.zeros(shape_subject, device=device, dtype=dtype)
+ mask2 = self._pack_latents(mask2, batch_size, num_channels_latents, height_cond*sub_number, width_cond)
+ latent_subject_ids = prepare_latent_subject_ids(height_cond, width_cond, device, dtype)
+ latent_subject_ids[:, 1] += 64 # fixed offset
+ subject_latent_image_ids = torch.concat([latent_subject_ids for _ in range(sub_number)], dim=-2)
+ latents_to_concat.append(subject_latents)
+ latents_ids_to_concat.append(subject_latent_image_ids)
+
+ # spatial
+ if condition_image is not None:
+ shape_cond = (batch_size, num_channels_latents, height_cond*cond_number, width_cond)
+ condition_image = condition_image.to(device=device, dtype=dtype)
+ image_latents = self._encode_vae_image(image=condition_image, generator=generator)
+ cond_latents = self._pack_latents(image_latents, batch_size, num_channels_latents, height_cond*cond_number, width_cond)
+ mask3 = torch.zeros(shape_cond, device=device, dtype=dtype)
+ mask3 = self._pack_latents(mask3, batch_size, num_channels_latents, height_cond*cond_number, width_cond)
+ cond_latent_image_ids = cond_latent_image_ids
+ cond_latent_image_ids = torch.concat([cond_latent_image_ids for _ in range(cond_number)], dim=-2)
+ latents_ids_to_concat.append(cond_latent_image_ids)
+ latents_to_concat.append(cond_latents)
+
+ cond_latents = torch.concat(latents_to_concat, dim=-2)
+ latent_image_ids = torch.concat(latents_ids_to_concat, dim=-2)
+ return cond_latents, latent_image_ids, noise_latents
+
+ @property
+ def guidance_scale(self):
+ return self._guidance_scale
+
+ @property
+ def joint_attention_kwargs(self):
+ return self._joint_attention_kwargs
+
+ @property
+ def num_timesteps(self):
+ return self._num_timesteps
+
+ @property
+ def interrupt(self):
+ return self._interrupt
+
+ @torch.no_grad()
+ def __call__(
+ self,
+ prompt: Union[str, List[str]] = None,
+ prompt_2: Optional[Union[str, List[str]]] = None,
+ height: Optional[int] = None,
+ width: Optional[int] = None,
+ num_inference_steps: int = 28,
+ timesteps: List[int] = None,
+ guidance_scale: float = 3.5,
+ num_images_per_prompt: Optional[int] = 1,
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
+ latents: Optional[torch.FloatTensor] = None,
+ prompt_embeds: Optional[torch.FloatTensor] = None,
+ pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
+ output_type: Optional[str] = "latent",
+ return_dict: bool = True,
+ joint_attention_kwargs: Optional[Dict[str, Any]] = None,
+ callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
+ max_sequence_length: int = 512,
+ spatial_images=[],
+ subject_images=[],
+ cond_size=512,
+ ):
+
+ height = height or self.default_sample_size * self.vae_scale_factor
+ width = width or self.default_sample_size * self.vae_scale_factor
+ self.cond_size = cond_size
+
+ # 1. Check inputs. Raise error if not correct
+ self.check_inputs(
+ prompt,
+ prompt_2,
+ height,
+ width,
+ prompt_embeds=prompt_embeds,
+ pooled_prompt_embeds=pooled_prompt_embeds,
+ callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
+ max_sequence_length=max_sequence_length,
+ )
+
+ self._guidance_scale = guidance_scale
+ self._joint_attention_kwargs = joint_attention_kwargs
+ self._interrupt = False
+
+ cond_number = len(spatial_images)
+ sub_number = len(subject_images)
+
+ if sub_number > 0:
+ subject_image_ls = []
+ for subject_image in subject_images:
+ w, h = subject_image.size[:2]
+ scale = self.cond_size / max(h, w)
+ new_h, new_w = int(h * scale), int(w * scale)
+ subject_image = self.image_processor.preprocess(subject_image, height=new_h, width=new_w)
+ subject_image = subject_image.to(dtype=torch.float32)
+ pad_h = cond_size - subject_image.shape[-2]
+ pad_w = cond_size - subject_image.shape[-1]
+ subject_image = pad(
+ subject_image,
+ padding=(int(pad_w / 2), int(pad_h / 2), int(pad_w / 2), int(pad_h / 2)),
+ fill=0
+ )
+ subject_image_ls.append(subject_image)
+ subject_image = torch.concat(subject_image_ls, dim=-2)
+ else:
+ subject_image = None
+
+ if cond_number > 0:
+ condition_image_ls = []
+ for img in spatial_images:
+ print(img)
+ condition_image = self.image_processor.preprocess(img, height=self.cond_size, width=self.cond_size)
+ condition_image = condition_image.to(dtype=torch.float32)
+ condition_image_ls.append(condition_image)
+ condition_image = torch.concat(condition_image_ls, dim=-2)
+ else:
+ condition_image = None
+
+ # 2. Define call parameters
+ if prompt is not None and isinstance(prompt, str):
+ batch_size = 1
+ elif prompt is not None and isinstance(prompt, list):
+ batch_size = len(prompt)
+ else:
+ batch_size = prompt_embeds.shape[0]
+
+ device = self._execution_device
+
+ lora_scale = (
+ self.joint_attention_kwargs.get("scale", None) if self.joint_attention_kwargs is not None else None
+ )
+ (
+ prompt_embeds,
+ pooled_prompt_embeds,
+ text_ids,
+ ) = self.encode_prompt(
+ prompt=prompt,
+ prompt_2=prompt_2,
+ prompt_embeds=prompt_embeds,
+ pooled_prompt_embeds=pooled_prompt_embeds,
+ device=device,
+ num_images_per_prompt=num_images_per_prompt,
+ max_sequence_length=max_sequence_length,
+ lora_scale=lora_scale,
+ )
+
+ # 4. Prepare latent variables
+ num_channels_latents = self.transformer.config.in_channels // 4 # 16
+ cond_latents, latent_image_ids, noise_latents = self.prepare_latents(
+ batch_size * num_images_per_prompt,
+ num_channels_latents,
+ height,
+ width,
+ prompt_embeds.dtype,
+ device,
+ generator,
+ subject_image,
+ condition_image,
+ latents,
+ cond_number,
+ sub_number
+ )
+ latents = noise_latents
+ # 5. Prepare timesteps
+ sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps)
+ image_seq_len = latents.shape[1]
+ mu = calculate_shift(
+ image_seq_len,
+ self.scheduler.config.base_image_seq_len,
+ self.scheduler.config.max_image_seq_len,
+ self.scheduler.config.base_shift,
+ self.scheduler.config.max_shift,
+ )
+ timesteps, num_inference_steps = retrieve_timesteps(
+ self.scheduler,
+ num_inference_steps,
+ device,
+ timesteps,
+ sigmas,
+ mu=mu,
+ )
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
+ self._num_timesteps = len(timesteps)
+
+ # handle guidance
+ if self.transformer.config.guidance_embeds:
+ guidance = torch.full([1], guidance_scale, device=device, dtype=torch.float32)
+ guidance = guidance.expand(latents.shape[0])
+ else:
+ guidance = None
+
+ ## Caching conditions
+ # clean the cache
+ try:
+ for name, attn_processor in self.transformer.attn_processors.items():
+ attn_processor.bank_kv.clear()
+ except:
+ pass
+ # cache with warmup latents
+ t = torch.tensor([timesteps[0]], device=device)
+ timestep = t.expand(cond_latents.shape[0]).to(latents.dtype)
+ warmup_image_ids = latent_image_ids[latents.shape[1]:, :]
+ l_ = self.transformer(
+ hidden_states=cond_latents,
+ timestep=torch.ones_like(timestep) * 0,
+ guidance=guidance,
+ pooled_projections=pooled_prompt_embeds,
+ encoder_hidden_states=prompt_embeds,
+ txt_ids=text_ids,
+ img_ids=warmup_image_ids,
+ joint_attention_kwargs=self.joint_attention_kwargs,
+ return_dict=False,
+ )[0]
+
+ del cond_latents, spatial_images, condition_image, l_
+
+ torch.cuda.empty_cache()
+
+ # 6. Denoising loop
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
+ for i, t in enumerate(timesteps):
+ if self.interrupt:
+ continue
+
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
+ timestep = t.expand(latents.shape[0]).to(latents.dtype)
+ noise_pred = self.transformer(
+ hidden_states=latents,
+ timestep=timestep / 1000,
+ guidance=guidance,
+ pooled_projections=pooled_prompt_embeds,
+ encoder_hidden_states=prompt_embeds,
+ txt_ids=text_ids,
+ img_ids=latent_image_ids,
+ joint_attention_kwargs=self.joint_attention_kwargs,
+ return_dict=False,
+ )[0]
+
+ # compute the previous noisy sample x_t -> x_t-1
+ latents_dtype = latents.dtype
+ latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
+
+ if latents.dtype != latents_dtype:
+ if torch.backends.mps.is_available():
+ # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
+ latents = latents.to(latents_dtype)
+
+ if callback_on_step_end is not None:
+ callback_kwargs = {}
+ for k in callback_on_step_end_tensor_inputs:
+ callback_kwargs[k] = locals()[k]
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
+
+ latents = callback_outputs.pop("latents", latents)
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
+
+ # call the callback, if provided
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
+ progress_bar.update()
+
+ if XLA_AVAILABLE:
+ xm.mark_step()
+
+ if output_type == "latent":
+ latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
+ image = (latents / self.vae.config.scaling_factor) + self.vae.config.shift_factor
+ else:
+ latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
+ latents = (latents / self.vae.config.scaling_factor) + self.vae.config.shift_factor
+ image = self.vae.decode(latents.to(dtype=self.vae.dtype), return_dict=False)[0]
+ image = self.image_processor.postprocess(image, output_type=output_type)
+
+ # Offload all models
+ self.maybe_free_model_hooks()
+
+ if not return_dict:
+ return (image,)
+
+ return FluxPipelineOutput(images=image)
diff --git a/OmniConsistency/src_inference/pipeline_.py b/OmniConsistency/src_inference/pipeline_.py
new file mode 100644
index 0000000..2be482d
--- /dev/null
+++ b/OmniConsistency/src_inference/pipeline_.py
@@ -0,0 +1,746 @@
+import inspect
+from typing import Any, Callable, Dict, List, Optional, Union
+
+import numpy as np
+import torch
+from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
+
+from diffusers.image_processor import (VaeImageProcessor)
+from diffusers.loaders import FluxLoraLoaderMixin, FromSingleFileMixin
+from diffusers.models.autoencoders import AutoencoderKL
+from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
+from diffusers.utils import (
+ USE_PEFT_BACKEND,
+ is_torch_xla_available,
+ logging,
+ scale_lora_layers,
+ unscale_lora_layers,
+)
+from diffusers.utils.torch_utils import randn_tensor
+from diffusers.pipelines.pipeline_utils import DiffusionPipeline
+from diffusers.pipelines.flux.pipeline_output import FluxPipelineOutput
+from torchvision.transforms.functional import pad
+from diffusers import FluxTransformer2DModel
+
+if is_torch_xla_available():
+ import torch_xla.core.xla_model as xm
+
+ XLA_AVAILABLE = True
+else:
+ XLA_AVAILABLE = False
+
+logger = logging.get_logger(__name__) # pylint: disable=invalid-name
+
+def calculate_shift(
+ image_seq_len,
+ base_seq_len: int = 256,
+ max_seq_len: int = 4096,
+ base_shift: float = 0.5,
+ max_shift: float = 1.16,
+):
+ m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
+ b = base_shift - m * base_seq_len
+ mu = image_seq_len * m + b
+ return mu
+
+def prepare_latent_image_ids_(height, width, device, dtype):
+ latent_image_ids = torch.zeros(height//2, width//2, 3, device=device, dtype=dtype)
+ latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height//2, device=device)[:, None] # y
+ latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width//2, device=device)[None, :] # x
+ return latent_image_ids
+
+def prepare_latent_subject_ids(height, width, device, dtype):
+ latent_image_ids = torch.zeros(height // 2, width // 2, 3, device=device, dtype=dtype)
+ latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height // 2, device=device)[:, None]
+ latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width // 2, device=device)[None, :]
+ latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
+ latent_image_ids = latent_image_ids.reshape(
+ latent_image_id_height * latent_image_id_width, latent_image_id_channels
+ )
+ return latent_image_ids.to(device=device, dtype=dtype)
+
+def resize_position_encoding(batch_size, original_height, original_width, target_height, target_width, device, dtype):
+ latent_image_ids = prepare_latent_image_ids_(original_height, original_width, device, dtype)
+ latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
+ latent_image_ids = latent_image_ids.reshape(
+ latent_image_id_height * latent_image_id_width, latent_image_id_channels
+ )
+
+ scale_h = original_height / target_height
+ scale_w = original_width / target_width
+ latent_image_ids_resized = torch.zeros(target_height//2, target_width//2, 3, device=device, dtype=dtype)
+ latent_image_ids_resized[..., 1] = latent_image_ids_resized[..., 1] + torch.arange(target_height//2, device=device)[:, None] * scale_h
+ latent_image_ids_resized[..., 2] = latent_image_ids_resized[..., 2] + torch.arange(target_width//2, device=device)[None, :] * scale_w
+
+ cond_latent_image_id_height, cond_latent_image_id_width, cond_latent_image_id_channels = latent_image_ids_resized.shape
+ cond_latent_image_ids = latent_image_ids_resized.reshape(
+ cond_latent_image_id_height * cond_latent_image_id_width, cond_latent_image_id_channels
+ )
+ return latent_image_ids, cond_latent_image_ids
+
+# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
+def retrieve_latents(
+ encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"
+):
+ if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
+ return encoder_output.latent_dist.sample(generator)
+ elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
+ return encoder_output.latent_dist.mode()
+ elif hasattr(encoder_output, "latents"):
+ return encoder_output.latents
+ else:
+ raise AttributeError("Could not access latents of provided encoder_output")
+
+
+# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
+def retrieve_timesteps(
+ scheduler,
+ num_inference_steps: Optional[int] = None,
+ device: Optional[Union[str, torch.device]] = None,
+ timesteps: Optional[List[int]] = None,
+ sigmas: Optional[List[float]] = None,
+ **kwargs,
+):
+ if timesteps is not None and sigmas is not None:
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
+ if timesteps is not None:
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
+ if not accepts_timesteps:
+ raise ValueError(
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
+ f" timestep schedules. Please check whether you are using the correct scheduler."
+ )
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
+ timesteps = scheduler.timesteps
+ num_inference_steps = len(timesteps)
+ elif sigmas is not None:
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
+ if not accept_sigmas:
+ raise ValueError(
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
+ )
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
+ timesteps = scheduler.timesteps
+ num_inference_steps = len(timesteps)
+ else:
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
+ timesteps = scheduler.timesteps
+ return timesteps, num_inference_steps
+
+
+class FluxPipeline(DiffusionPipeline, FluxLoraLoaderMixin, FromSingleFileMixin):
+ def __init__(
+ self,
+ scheduler: FlowMatchEulerDiscreteScheduler,
+ vae: AutoencoderKL,
+ text_encoder: CLIPTextModel,
+ tokenizer: CLIPTokenizer,
+ text_encoder_2: T5EncoderModel,
+ tokenizer_2: T5TokenizerFast,
+ transformer: FluxTransformer2DModel,
+ ):
+ super().__init__()
+
+ self.register_modules(
+ vae=vae,
+ text_encoder=text_encoder,
+ text_encoder_2=text_encoder_2,
+ tokenizer=tokenizer,
+ tokenizer_2=tokenizer_2,
+ transformer=transformer,
+ scheduler=scheduler,
+ )
+ self.vae_scale_factor = (
+ 2 ** (len(self.vae.config.block_out_channels)) if hasattr(self, "vae") and self.vae is not None else 16
+ )
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
+ self.tokenizer_max_length = (
+ self.tokenizer.model_max_length if hasattr(self, "tokenizer") and self.tokenizer is not None else 77
+ )
+ self.default_sample_size = 64
+
+ def _get_t5_prompt_embeds(
+ self,
+ prompt: Union[str, List[str]] = None,
+ num_images_per_prompt: int = 1,
+ max_sequence_length: int = 512,
+ device: Optional[torch.device] = None,
+ dtype: Optional[torch.dtype] = None,
+ ):
+ device = device or self._execution_device
+ dtype = dtype or self.text_encoder.dtype
+
+ prompt = [prompt] if isinstance(prompt, str) else prompt
+ batch_size = len(prompt)
+
+ text_inputs = self.tokenizer_2(
+ prompt,
+ padding="max_length",
+ max_length=max_sequence_length,
+ truncation=True,
+ return_length=False,
+ return_overflowing_tokens=False,
+ return_tensors="pt",
+ )
+ text_input_ids = text_inputs.input_ids
+ untruncated_ids = self.tokenizer_2(prompt, padding="longest", return_tensors="pt").input_ids
+
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
+ removed_text = self.tokenizer_2.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1: -1])
+ logger.warning(
+ "The following part of your input was truncated because `max_sequence_length` is set to "
+ f" {max_sequence_length} tokens: {removed_text}"
+ )
+
+ prompt_embeds = self.text_encoder_2(text_input_ids.to(device), output_hidden_states=False)[0]
+
+ dtype = self.text_encoder_2.dtype
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
+
+ _, seq_len, _ = prompt_embeds.shape
+
+ # duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method
+ prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
+ prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
+
+ return prompt_embeds
+
+ def _get_clip_prompt_embeds(
+ self,
+ prompt: Union[str, List[str]],
+ num_images_per_prompt: int = 1,
+ device: Optional[torch.device] = None,
+ ):
+ device = device or self._execution_device
+
+ prompt = [prompt] if isinstance(prompt, str) else prompt
+ batch_size = len(prompt)
+
+ text_inputs = self.tokenizer(
+ prompt,
+ padding="max_length",
+ max_length=self.tokenizer_max_length,
+ truncation=True,
+ return_overflowing_tokens=False,
+ return_length=False,
+ return_tensors="pt",
+ )
+
+ text_input_ids = text_inputs.input_ids
+ untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
+ removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1: -1])
+ logger.warning(
+ "The following part of your input was truncated because CLIP can only handle sequences up to"
+ f" {self.tokenizer_max_length} tokens: {removed_text}"
+ )
+ prompt_embeds = self.text_encoder(text_input_ids.to(device), output_hidden_states=False)
+
+ # Use pooled output of CLIPTextModel
+ prompt_embeds = prompt_embeds.pooler_output
+ prompt_embeds = prompt_embeds.to(dtype=self.text_encoder.dtype, device=device)
+
+ # duplicate text embeddings for each generation per prompt, using mps friendly method
+ prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt)
+ prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, -1)
+
+ return prompt_embeds
+
+ def encode_prompt(
+ self,
+ prompt: Union[str, List[str]],
+ prompt_2: Union[str, List[str]],
+ device: Optional[torch.device] = None,
+ num_images_per_prompt: int = 1,
+ prompt_embeds: Optional[torch.FloatTensor] = None,
+ pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
+ max_sequence_length: int = 512,
+ lora_scale: Optional[float] = None,
+ ):
+ device = device or self._execution_device
+
+ # set lora scale so that monkey patched LoRA
+ # function of text encoder can correctly access it
+ if lora_scale is not None and isinstance(self, FluxLoraLoaderMixin):
+ self._lora_scale = lora_scale
+
+ # dynamically adjust the LoRA scale
+ if self.text_encoder is not None and USE_PEFT_BACKEND:
+ scale_lora_layers(self.text_encoder, lora_scale)
+ if self.text_encoder_2 is not None and USE_PEFT_BACKEND:
+ scale_lora_layers(self.text_encoder_2, lora_scale)
+
+ prompt = [prompt] if isinstance(prompt, str) else prompt
+
+ if prompt_embeds is None:
+ prompt_2 = prompt_2 or prompt
+ prompt_2 = [prompt_2] if isinstance(prompt_2, str) else prompt_2
+
+ # We only use the pooled prompt output from the CLIPTextModel
+ pooled_prompt_embeds = self._get_clip_prompt_embeds(
+ prompt=prompt,
+ device=device,
+ num_images_per_prompt=num_images_per_prompt,
+ )
+ prompt_embeds = self._get_t5_prompt_embeds(
+ prompt=prompt_2,
+ num_images_per_prompt=num_images_per_prompt,
+ max_sequence_length=max_sequence_length,
+ device=device,
+ )
+
+ if self.text_encoder is not None:
+ if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND:
+ # Retrieve the original scale by scaling back the LoRA layers
+ unscale_lora_layers(self.text_encoder, lora_scale)
+
+ if self.text_encoder_2 is not None:
+ if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND:
+ # Retrieve the original scale by scaling back the LoRA layers
+ unscale_lora_layers(self.text_encoder_2, lora_scale)
+
+ dtype = self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype
+ text_ids = torch.zeros(prompt_embeds.shape[1], 3).to(device=device, dtype=dtype)
+
+ return prompt_embeds, pooled_prompt_embeds, text_ids
+
+ # Copied from diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3_inpaint.StableDiffusion3InpaintPipeline._encode_vae_image
+ def _encode_vae_image(self, image: torch.Tensor, generator: torch.Generator):
+ if isinstance(generator, list):
+ image_latents = [
+ retrieve_latents(self.vae.encode(image[i: i + 1]), generator=generator[i])
+ for i in range(image.shape[0])
+ ]
+ image_latents = torch.cat(image_latents, dim=0)
+ else:
+ image_latents = retrieve_latents(self.vae.encode(image), generator=generator)
+
+ image_latents = (image_latents - self.vae.config.shift_factor) * self.vae.config.scaling_factor
+
+ return image_latents
+
+ def check_inputs(
+ self,
+ prompt,
+ prompt_2,
+ height,
+ width,
+ prompt_embeds=None,
+ pooled_prompt_embeds=None,
+ callback_on_step_end_tensor_inputs=None,
+ max_sequence_length=None,
+ ):
+ if height % 8 != 0 or width % 8 != 0:
+ raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
+
+ if prompt is not None and prompt_embeds is not None:
+ raise ValueError(
+ f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
+ " only forward one of the two."
+ )
+ elif prompt_2 is not None and prompt_embeds is not None:
+ raise ValueError(
+ f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
+ " only forward one of the two."
+ )
+ elif prompt is None and prompt_embeds is None:
+ raise ValueError(
+ "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
+ )
+ elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
+ elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)):
+ raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}")
+
+ if prompt_embeds is not None and pooled_prompt_embeds is None:
+ raise ValueError(
+ "If `prompt_embeds` are provided, `pooled_prompt_embeds` also have to be passed. Make sure to generate `pooled_prompt_embeds` from the same text encoder that was used to generate `prompt_embeds`."
+ )
+
+ if max_sequence_length is not None and max_sequence_length > 512:
+ raise ValueError(f"`max_sequence_length` cannot be greater than 512 but is {max_sequence_length}")
+
+ @staticmethod
+ def _prepare_latent_image_ids(batch_size, height, width, device, dtype):
+ latent_image_ids = torch.zeros(height // 2, width // 2, 3)
+ latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height // 2)[:, None]
+ latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width // 2)[None, :]
+ latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
+ latent_image_ids = latent_image_ids.reshape(
+ latent_image_id_height * latent_image_id_width, latent_image_id_channels
+ )
+ return latent_image_ids.to(device=device, dtype=dtype)
+
+ @staticmethod
+ def _pack_latents(latents, batch_size, num_channels_latents, height, width):
+ latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
+ latents = latents.permute(0, 2, 4, 1, 3, 5)
+ latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4)
+ return latents
+
+ @staticmethod
+ def _unpack_latents(latents, height, width, vae_scale_factor):
+ batch_size, num_patches, channels = latents.shape
+
+ height = height // vae_scale_factor
+ width = width // vae_scale_factor
+
+ latents = latents.view(batch_size, height, width, channels // 4, 2, 2)
+ latents = latents.permute(0, 3, 1, 4, 2, 5)
+
+ latents = latents.reshape(batch_size, channels // (2 * 2), height * 2, width * 2)
+
+ return latents
+
+ def enable_vae_slicing(self):
+ r"""
+ Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
+ compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
+ """
+ self.vae.enable_slicing()
+
+ def disable_vae_slicing(self):
+ r"""
+ Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to
+ computing decoding in one step.
+ """
+ self.vae.disable_slicing()
+
+ def enable_vae_tiling(self):
+ r"""
+ Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
+ compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
+ processing larger images.
+ """
+ self.vae.enable_tiling()
+
+ def disable_vae_tiling(self):
+ r"""
+ Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to
+ computing decoding in one step.
+ """
+ self.vae.disable_tiling()
+
+ def prepare_latents(
+ self,
+ batch_size,
+ num_channels_latents,
+ height,
+ width,
+ dtype,
+ device,
+ generator,
+ subject_image,
+ condition_image,
+ latents=None,
+ cond_number=1,
+ sub_number=1
+ ):
+ height_cond = 2 * (self.cond_size // self.vae_scale_factor)
+ width_cond = 2 * (self.cond_size // self.vae_scale_factor)
+ height = 2 * (int(height) // self.vae_scale_factor)
+ width = 2 * (int(width) // self.vae_scale_factor)
+
+ shape = (batch_size, num_channels_latents, height, width) # 1 16 106 80
+ noise_latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
+ noise_latents = self._pack_latents(noise_latents, batch_size, num_channels_latents, height, width)
+ noise_latent_image_ids, cond_latent_image_ids = resize_position_encoding(
+ batch_size,
+ height,
+ width,
+ height_cond,
+ width_cond,
+ device,
+ dtype,
+ )
+
+ latents_to_concat = []
+ latents_ids_to_concat = [noise_latent_image_ids]
+
+ # subject
+ if subject_image is not None:
+ shape_subject = (batch_size, num_channels_latents, height_cond*sub_number, width_cond)
+ subject_image = subject_image.to(device=device, dtype=dtype)
+ subject_image_latents = self._encode_vae_image(image=subject_image, generator=generator)
+ subject_latents = self._pack_latents(subject_image_latents, batch_size, num_channels_latents, height_cond*sub_number, width_cond)
+ mask2 = torch.zeros(shape_subject, device=device, dtype=dtype)
+ mask2 = self._pack_latents(mask2, batch_size, num_channels_latents, height_cond*sub_number, width_cond)
+ latent_subject_ids = prepare_latent_subject_ids(height_cond, width_cond, device, dtype)
+ latent_subject_ids[:, 1] += 64 # fixed offset
+ subject_latent_image_ids = torch.concat([latent_subject_ids for _ in range(sub_number)], dim=-2)
+ latents_to_concat.append(subject_latents)
+ latents_ids_to_concat.append(subject_latent_image_ids)
+
+ # spatial
+ if condition_image is not None:
+ shape_cond = (batch_size, num_channels_latents, height_cond*cond_number, width_cond)
+ condition_image = condition_image.to(device=device, dtype=dtype)
+ image_latents = self._encode_vae_image(image=condition_image, generator=generator)
+ cond_latents = self._pack_latents(image_latents, batch_size, num_channels_latents, height_cond*cond_number, width_cond)
+ mask3 = torch.zeros(shape_cond, device=device, dtype=dtype)
+ mask3 = self._pack_latents(mask3, batch_size, num_channels_latents, height_cond*cond_number, width_cond)
+ cond_latent_image_ids = cond_latent_image_ids
+ cond_latent_image_ids = torch.concat([cond_latent_image_ids for _ in range(cond_number)], dim=-2)
+ latents_ids_to_concat.append(cond_latent_image_ids)
+ latents_to_concat.append(cond_latents)
+
+ cond_latents = torch.concat(latents_to_concat, dim=-2)
+ latent_image_ids = torch.concat(latents_ids_to_concat, dim=-2)
+ return cond_latents, latent_image_ids, noise_latents
+
+ @property
+ def guidance_scale(self):
+ return self._guidance_scale
+
+ @property
+ def joint_attention_kwargs(self):
+ return self._joint_attention_kwargs
+
+ @property
+ def num_timesteps(self):
+ return self._num_timesteps
+
+ @property
+ def interrupt(self):
+ return self._interrupt
+
+ @torch.no_grad()
+ def __call__(
+ self,
+ prompt: Union[str, List[str]] = None,
+ prompt_2: Optional[Union[str, List[str]]] = None,
+ height: Optional[int] = None,
+ width: Optional[int] = None,
+ num_inference_steps: int = 28,
+ timesteps: List[int] = None,
+ guidance_scale: float = 3.5,
+ num_images_per_prompt: Optional[int] = 1,
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
+ latents: Optional[torch.FloatTensor] = None,
+ prompt_embeds: Optional[torch.FloatTensor] = None,
+ pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
+ output_type: Optional[str] = "pil",
+ return_dict: bool = True,
+ joint_attention_kwargs: Optional[Dict[str, Any]] = None,
+ callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
+ max_sequence_length: int = 512,
+ spatial_images=[],
+ subject_images=[],
+ cond_size=512,
+ ):
+
+ height = height or self.default_sample_size * self.vae_scale_factor
+ width = width or self.default_sample_size * self.vae_scale_factor
+ self.cond_size = cond_size
+
+ # 1. Check inputs. Raise error if not correct
+ self.check_inputs(
+ prompt,
+ prompt_2,
+ height,
+ width,
+ prompt_embeds=prompt_embeds,
+ pooled_prompt_embeds=pooled_prompt_embeds,
+ callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
+ max_sequence_length=max_sequence_length,
+ )
+
+ self._guidance_scale = guidance_scale
+ self._joint_attention_kwargs = joint_attention_kwargs
+ self._interrupt = False
+
+ cond_number = len(spatial_images)
+ sub_number = len(subject_images)
+
+ if sub_number > 0:
+ subject_image_ls = []
+ for subject_image in subject_images:
+ w, h = subject_image.size[:2]
+ scale = self.cond_size / max(h, w)
+ new_h, new_w = int(h * scale), int(w * scale)
+ subject_image = self.image_processor.preprocess(subject_image, height=new_h, width=new_w)
+ subject_image = subject_image.to(dtype=torch.float32)
+ pad_h = cond_size - subject_image.shape[-2]
+ pad_w = cond_size - subject_image.shape[-1]
+ subject_image = pad(
+ subject_image,
+ padding=(int(pad_w / 2), int(pad_h / 2), int(pad_w / 2), int(pad_h / 2)),
+ fill=0
+ )
+ subject_image_ls.append(subject_image)
+ subject_image = torch.concat(subject_image_ls, dim=-2)
+ else:
+ subject_image = None
+
+ if cond_number > 0:
+ condition_image_ls = []
+ for img in spatial_images:
+ print(img)
+ condition_image = self.image_processor.preprocess(img, height=self.cond_size, width=self.cond_size)
+ condition_image = condition_image.to(dtype=torch.float32)
+ condition_image_ls.append(condition_image)
+ condition_image = torch.concat(condition_image_ls, dim=-2)
+ else:
+ condition_image = None
+
+ # 2. Define call parameters
+ if prompt is not None and isinstance(prompt, str):
+ batch_size = 1
+ elif prompt is not None and isinstance(prompt, list):
+ batch_size = len(prompt)
+ else:
+ batch_size = prompt_embeds.shape[0]
+
+ device = self._execution_device
+
+ lora_scale = (
+ self.joint_attention_kwargs.get("scale", None) if self.joint_attention_kwargs is not None else None
+ )
+ (
+ prompt_embeds,
+ pooled_prompt_embeds,
+ text_ids,
+ ) = self.encode_prompt(
+ prompt=prompt,
+ prompt_2=prompt_2,
+ prompt_embeds=prompt_embeds,
+ pooled_prompt_embeds=pooled_prompt_embeds,
+ device=device,
+ num_images_per_prompt=num_images_per_prompt,
+ max_sequence_length=max_sequence_length,
+ lora_scale=lora_scale,
+ )
+
+ # 4. Prepare latent variables
+ num_channels_latents = self.transformer.config.in_channels // 4 # 16
+ cond_latents, latent_image_ids, noise_latents = self.prepare_latents(
+ batch_size * num_images_per_prompt,
+ num_channels_latents,
+ height,
+ width,
+ prompt_embeds.dtype,
+ device,
+ generator,
+ subject_image,
+ condition_image,
+ latents,
+ cond_number,
+ sub_number
+ )
+ latents = noise_latents
+ # 5. Prepare timesteps
+ sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps)
+ image_seq_len = latents.shape[1]
+ mu = calculate_shift(
+ image_seq_len,
+ self.scheduler.config.base_image_seq_len,
+ self.scheduler.config.max_image_seq_len,
+ self.scheduler.config.base_shift,
+ self.scheduler.config.max_shift,
+ )
+ timesteps, num_inference_steps = retrieve_timesteps(
+ self.scheduler,
+ num_inference_steps,
+ device,
+ timesteps,
+ sigmas,
+ mu=mu,
+ )
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
+ self._num_timesteps = len(timesteps)
+
+ # handle guidance
+ if self.transformer.config.guidance_embeds:
+ guidance = torch.full([1], guidance_scale, device=device, dtype=torch.float32)
+ guidance = guidance.expand(latents.shape[0])
+ else:
+ guidance = None
+
+ ## Caching conditions
+ # clean the cache
+ try:
+ for name, attn_processor in self.transformer.attn_processors.items():
+ attn_processor.bank_kv.clear()
+ except:
+ pass
+ # cache with warmup latents
+ t = torch.tensor([timesteps[0]], device=device)
+ timestep = t.expand(cond_latents.shape[0]).to(latents.dtype)
+ warmup_image_ids = latent_image_ids[latents.shape[1]:, :]
+ _ = self.transformer(
+ hidden_states=cond_latents,
+ timestep=torch.ones_like(timestep) * 0,
+ guidance=guidance,
+ pooled_projections=pooled_prompt_embeds,
+ encoder_hidden_states=prompt_embeds,
+ txt_ids=text_ids,
+ img_ids=warmup_image_ids,
+ joint_attention_kwargs=self.joint_attention_kwargs,
+ return_dict=False,
+ )[0]
+
+ del cond_latents, spatial_images, condition_image, condition_image_ls, img, _
+ torch.cuda.empty_cache()
+
+ # 6. Denoising loop
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
+ for i, t in enumerate(timesteps):
+ if self.interrupt:
+ continue
+
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
+ timestep = t.expand(latents.shape[0]).to(latents.dtype)
+ noise_pred = self.transformer(
+ hidden_states=latents,
+ timestep=timestep / 1000,
+ guidance=guidance,
+ pooled_projections=pooled_prompt_embeds,
+ encoder_hidden_states=prompt_embeds,
+ txt_ids=text_ids,
+ img_ids=latent_image_ids,
+ joint_attention_kwargs=self.joint_attention_kwargs,
+ return_dict=False,
+ )[0]
+
+ # compute the previous noisy sample x_t -> x_t-1
+ latents_dtype = latents.dtype
+ latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
+
+ if latents.dtype != latents_dtype:
+ if torch.backends.mps.is_available():
+ # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
+ latents = latents.to(latents_dtype)
+
+ if callback_on_step_end is not None:
+ callback_kwargs = {}
+ for k in callback_on_step_end_tensor_inputs:
+ callback_kwargs[k] = locals()[k]
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
+
+ latents = callback_outputs.pop("latents", latents)
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
+
+ # call the callback, if provided
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
+ progress_bar.update()
+
+ if XLA_AVAILABLE:
+ xm.mark_step()
+
+ if output_type == "latent":
+ image = latents
+ else:
+ latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
+ latents = (latents / self.vae.config.scaling_factor) + self.vae.config.shift_factor
+ image = self.vae.decode(latents.to(dtype=self.vae.dtype), return_dict=False)[0]
+ image = self.image_processor.postprocess(image, output_type=output_type)
+
+ # Offload all models
+ self.maybe_free_model_hooks()
+
+ if not return_dict:
+ return (image,)
+
+ return FluxPipelineOutput(images=image)
diff --git a/OmniConsistency/test_imgs/00.png b/OmniConsistency/test_imgs/00.png
new file mode 100644
index 0000000..bb2d85f
Binary files /dev/null and b/OmniConsistency/test_imgs/00.png differ
diff --git a/OmniConsistency/test_imgs/01.png b/OmniConsistency/test_imgs/01.png
new file mode 100644
index 0000000..46faae3
Binary files /dev/null and b/OmniConsistency/test_imgs/01.png differ
diff --git a/OmniConsistency/test_imgs/02.png b/OmniConsistency/test_imgs/02.png
new file mode 100644
index 0000000..c37e7f4
Binary files /dev/null and b/OmniConsistency/test_imgs/02.png differ
diff --git a/OmniConsistency/test_imgs/03.png b/OmniConsistency/test_imgs/03.png
new file mode 100644
index 0000000..38e7dcc
Binary files /dev/null and b/OmniConsistency/test_imgs/03.png differ
diff --git a/OmniConsistency/test_imgs/04.png b/OmniConsistency/test_imgs/04.png
new file mode 100644
index 0000000..6eeff50
Binary files /dev/null and b/OmniConsistency/test_imgs/04.png differ
diff --git a/Storydiffusion_node.py b/Storydiffusion_node.py
index ece584f..4bc8755 100644
--- a/Storydiffusion_node.py
+++ b/Storydiffusion_node.py
@@ -137,7 +137,7 @@ class StoryDiffusion_Apply:
{
"model": ("MODEL",),
"vae": ("VAE",),
- "infer_mode": (["story", "classic","flux_pulid","infiniteyou","uno","realcustom","instant_character","dreamo","bagel_edit","story_maker","story_and_maker","consistory","kolor_face","msdiffusion" ],),
+ "infer_mode": (["story", "classic","flux_pulid","infiniteyou","uno","realcustom","instant_character","dreamo","flux_omi","bagel_edit","story_maker","story_and_maker","consistory","kolor_face","msdiffusion" ],),
"photomake_ckpt": (["none"] + [i for i in folder_paths.get_filename_list("photomaker") if "v1" in i or "v2" in i],),
"ipadapter_ckpt": (["none"] + folder_paths.get_filename_list("photomaker"),),
"quantize_mode": ([ "fp8", "nf4","fp16", ],),
@@ -276,6 +276,12 @@ class StoryDiffusion_Apply:
raise "EasyFunction_Lite node extra_repo must fill bagel repo"
max_mem_per_gpu=str(int(total_vram/1000))+"GIB"
model = load_bagel_model(repo,quantize_mode,max_mem_per_gpu)
+ elif infer_mode == "flux_omi":
+ from .model_loader_utils import Loader_Flux_Diffuser
+ if not isinstance(model,dict) :
+ raise " must link EasyFunction_Lite node "
+ model = Loader_Flux_Diffuser(model,ipadapter_ckpt_path,vae,quantize_mode)
+
else: # can not choice a mode
print("infer use comfyui classic mode")
@@ -491,6 +497,8 @@ class StoryDiffusion_CLIPTextEncode:
pass
elif infer_mode == "bagel_edit":
image_emb=input_id_images_dict
+ elif infer_mode == "flux_omi":
+ image_emb=input_id_images_dict
elif infer_mode == "dreamo":
from .model_loader_utils import Dreamo_image_encoder
from huggingface_hub import hf_hub_download
@@ -738,7 +746,7 @@ class StoryDiffusion_CLIPTextEncode:
else:
if infer_mode=="classic": #TODO 逆序的角色会出现iD不匹配,受影响的有story文生图
only_role_emb= cf_clip(only_role_list, clip, infer_mode,role_list) #story模式需要拆分prompt,所以这里需要传入role_list
- elif infer_mode=="dreamo" or infer_mode=="bagel_edit":
+ elif infer_mode=="dreamo" or infer_mode=="bagel_edit" or infer_mode=="flux_omi":
pass # TODO 暂时不支持dreamo
else:
only_role_emb= cf_clip(inf_list_split, clip, infer_mode,role_list) #story,story_maker,story_and_maker,msdiffusion,infinite
@@ -757,7 +765,7 @@ class StoryDiffusion_CLIPTextEncode:
else:
if infer_mode!="kolor_face":
nc_emb=cf_clip(nc_txt_list, clip, infer_mode,role_list,input_split=False)
- elif infer_mode=="dreamo" or infer_mode=="bagel_edit":
+ elif infer_mode=="dreamo" or infer_mode=="bagel_edit" or infer_mode=="flux_omi":
pass # TODO 暂时不支持dreamo
else:
nc_emb,_= glm_single_encode(chatglm3_model, nc_txt_list,role_list, neg_text, 1,nc_mode=True)
@@ -866,6 +874,12 @@ class StoryDiffusion_CLIPTextEncode:
only_role_emb[key]=prompts
postive_dict= {"role": only_role_emb, "nc": None, "daul": None}
negative = neg_text # TODO
+ elif infer_mode=="flux_omi":
+ only_role_emb={}
+ for key,prompts in zip(role_list,inf_list_split):
+ only_role_emb[key]=prompts
+ postive_dict= {"role": only_role_emb, "nc": None, "daul": None}
+ negative = neg_text # TODO
elif infer_mode=="realcustom":
postive_dict= {"role": only_role_emb, "nc": None, "daul": daul_emb}
negative=[letent_real]
@@ -1733,6 +1747,30 @@ class StoryDiffusion_KSampler:
samples_list=[VAE.encode(phi2narry(pixels)[:,:,:,:3]) for pixels in samples_list] # TODO pil to tensor
out["samples"] = torch.cat(samples_list, dim=0)
return (out,)
+ elif infer_mode =="flux_omi":
+ samples_list = []
+ for key in role_list:
+ for prompt in only_role_emb[key]:
+ samples=model(
+ prompt=prompt,
+ height=height,
+ width=width,
+ guidance_scale=cfg if cfg==3.5 else 3.5,
+ num_inference_steps=steps,
+ max_sequence_length=512,
+ generator=torch.Generator("cpu").manual_seed(seed),
+ spatial_images=[image_embeds[key]],
+ subject_images=[], #TODO TRY ON
+ cond_size=512,
+ ).images
+ #print(samples.shape)
+ samples_list.append(samples)
+ out = {}
+ out["samples"] = torch.cat(samples_list, dim=0)
+ # Clear cache after generation
+ from .OmniConsistency.infer import clear_cache
+ clear_cache(model.transformer)
+ return (out,)
else: #none:
return
@@ -1792,6 +1830,24 @@ class StoryDiffusion_Lora_Control:
if "blur" in lora_path:
loras.append([lora_path, 'anti_blur', 1.0])
model.load_loras(loras)
+ elif infer_mode=="flux_omi":
+ if loras != "none":
+ lora_path = folder_paths.get_full_path("loras", loras)
+ else:
+ raise Exception("InfiniteYou need to select a lora")
+
+ multi_lora=[lora_path]
+ if switch.get("lora_ckpt_path") is not None:
+ multi_lora.append(switch.get("lora_ckpt_path"))
+ if switch.get("lora_dual_path") is not None:
+ multi_lora.append(switch.get("lora_dual_path"))
+ if switch.get("lora_ckpt_path") is not None and switch.get("lora_dual_path") is not None:
+ from .OmniConsistency.src_inference.lora_helper import set_multi_lora
+ lora_weights = [1]*len(multi_lora)
+ set_multi_lora(model.transformer, multi_lora, lora_weights=[lora_weights], cond_size=512)
+ else:
+ model.load_lora_weights(lora_path)
+
switch["controlnet_scale"]=controlnet_scale
switch["controlnet"]=controlnet
switch["use_lora"]=use_lora
diff --git a/model_loader_utils.py b/model_loader_utils.py
index eb9d0a2..a92e8a2 100644
--- a/model_loader_utils.py
+++ b/model_loader_utils.py
@@ -2934,3 +2934,96 @@ def Dreamo_image_encoder(BEN2_path,ref_image1,ref_image2,ref_task1,ref_task2,ref
}
)
return ref_conds
+
+def Loader_Flux_Diffuser(cf_model,omi_lora_path,VAE,quantize_mode):
+ from .OmniConsistency.src_inference.pipeline import FluxPipeline as FluxPipeline_dif
+ from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig,FluxTransformer2DModel
+ from .OmniConsistency.src_inference.lora_helper import set_single_lora
+ flux_dif_repo=os.path.join(cur_path,"config/FLUX.1-dev")
+ if cf_model.get("use_gguf"):
+ vae=convert_cfvae2diffuser(VAE,use_flux=True)
+ print("use gguf quantization")
+ if cf_model.get("model_path") is None:
+ raise "need chocie a gguf model in EasyFunction_Lite!"
+ from diffusers import GGUFQuantizationConfig
+ transformer = FluxPipeline_dif.from_single_file(
+ cf_model.get("model_path"),
+ config=os.path.join(cur_path, "config/FLUX.1-dev/transformer"),
+ quantization_config=GGUFQuantizationConfig(compute_dtype=torch.bfloat16),
+ torch_dtype=torch.bfloat16,
+ )
+ pipeline = FluxPipeline_dif.from_pretrained(flux_dif_repo, vae=vae,transformer=transformer,text_encoder=None,text_encoder_2=None,torch_dtype=torch.bfloat16)
+
+ elif cf_model.get("use_svdq") :
+ print("use svdq")
+ vae=convert_cfvae2diffuser(VAE,use_flux=True)
+ from nunchaku import NunchakuFluxTransformer2dModel
+ transformer = NunchakuFluxTransformer2dModel.from_pretrained(cf_model.get("select_method"),offload=True)
+ try:
+ transformer.set_attention_impl("nunchaku-fp16")
+ except:
+ pass
+ pipeline = FluxPipeline_dif.from_pretrained(flux_dif_repo, vae=vae,transformer=transformer,text_encoder=None,text_encoder_2=None,torch_dtype=torch.bfloat16)
+
+ elif cf_model.get("use_unet"):
+ print("use single unet")
+ vae=convert_cfvae2diffuser(VAE,use_flux=True)
+ if quantize_mode=="fp8":
+ transformer = FluxTransformer2DModel.from_single_file(cf_model.get("model_path"), config=os.path.join(cur_path,"config/FLUX.1-dev/transformer/config.json"),
+ torch_dtype=torch.bfloat16)
+ else:
+ from safetensors.torch import load_file
+ t_state_dict=load_file(cf_model.get("model_path"))
+ unet_config = FluxTransformer2DModel.load_config(os.path.join(cur_path,"config/FLUX.1-dev/transformer/config.json"))
+ transformer = FluxTransformer2DModel.from_config(unet_config).to(torch.bfloat16)
+ transformer.load_state_dict(t_state_dict, strict=False)
+ del t_state_dict
+ gc_cleanup()
+ pipeline = FluxPipeline_dif.from_pretrained(flux_dif_repo, vae=vae,transformer=transformer,text_encoder=None,text_encoder_2=None,torch_dtype=torch.bfloat16)
+
+ else:
+ print(f"use {quantize_mode} quantization")
+
+ if quantize_mode=="nf4": #nf4
+ transformer = FluxTransformer2DModel.from_pretrained(
+ cf_model.get("extra_repo"),
+ subfolder="transformer",
+ quantization_config=DiffusersBitsAndBytesConfig(
+ load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16
+ ),
+ torch_dtype=torch.bfloat16,)
+ elif quantize_mode=="fp8":
+ transformer = FluxTransformer2DModel.from_pretrained(
+ cf_model.get("extra_repo"),
+ subfolder="transformer",
+ quantization_config=DiffusersBitsAndBytesConfig(load_in_8bit=True,),
+ torch_dtype=torch.bfloat16,
+ )
+ else:
+ transformer = FluxTransformer2DModel.from_pretrained(
+ cf_model.get("extra_repo"),
+ subfolder="transformer",
+ torch_dtype=torch.bfloat16,
+ )
+ pipeline = FluxPipeline_dif.from_pretrained(cf_model.get("extra_repo"),transformer=transformer,torch_dtype=torch.bfloat16)
+
+ multi_lora=[]
+ if cf_model.get("lora_ckpt_path") is not None:
+ multi_lora.append(cf_model.get("lora_ckpt_path"))
+ if cf_model.get("lora_dual_path") is not None:
+ multi_lora.append(cf_model.get("lora_dual_path"))
+ if multi_lora:
+ if len(multi_lora)==1:
+ set_single_lora(pipeline.transformer, omi_lora_path, lora_weights=[1], cond_size=512)
+ pipeline.unload_lora_weights()
+ pipeline.load_lora_weights(os.path.dirname(multi_lora[0]),weight_name=os.path.basename(multi_lora[0]))
+
+ else:
+ from .OmniConsistency.src_inference.lora_helper import set_multi_lora
+ lora_weights = [1]*len(multi_lora)
+ set_multi_lora(pipeline.transformer, multi_lora, lora_weights=[[lora_weights]], cond_size=512)
+ pipeline.unload_lora_weights()
+ else:
+ raise "need chocie a lora model in EasyFunction_Lite!"
+ pipeline.enable_model_cpu_offload()
+ return pipeline
\ No newline at end of file