Spaces:
Running on Zero
Running on Zero
Add AsymFLUX.2-klein Space demo
Browse files- .gitignore +25 -0
- LICENSE.md +11 -0
- README.md +20 -8
- app.py +139 -0
- lakonlab/__init__.py +1 -0
- lakonlab/models/__init__.py +1 -0
- lakonlab/models/architectures/__init__.py +3 -0
- lakonlab/models/architectures/asymflow/__init__.py +3 -0
- lakonlab/models/architectures/asymflow/asymflux2.py +309 -0
- lakonlab/models/architectures/asymflow/common.py +70 -0
- lakonlab/models/architectures/autoencoders/__init__.py +3 -0
- lakonlab/models/architectures/autoencoders/color_encoders.py +126 -0
- lakonlab/models/diffusions/__init__.py +1 -0
- lakonlab/models/diffusions/gaussian_flow.py +20 -0
- lakonlab/models/diffusions/schedulers/__init__.py +3 -0
- lakonlab/models/diffusions/schedulers/flow_adapter.py +252 -0
- lakonlab/pipelines/__init__.py +1 -0
- lakonlab/pipelines/pipeline_pixelflux2_klein.py +310 -0
- lakonlab/pipelines/prompt_rewriters/__init__.py +0 -0
- lakonlab/pipelines/prompt_rewriters/qwen3_vl.py +172 -0
- lakonlab/pipelines/prompt_rewriters/system_prompts/default_text_only.txt +12 -0
- lakonlab/pipelines/prompt_rewriters/system_prompts/default_with_images.txt +10 -0
- lakonlab/pipelines/utils.py +209 -0
- lakonlab/ui/__init__.py +0 -0
- lakonlab/ui/gradio/__init__.py +0 -0
- lakonlab/ui/gradio/create_text_to_img.py +41 -0
- lakonlab/ui/gradio/shared_opts.py +87 -0
- lakonlab/ui/gradio/style.css +73 -0
- requirements.txt +10 -0
.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/.idea/
|
| 2 |
+
/work_dirs*
|
| 3 |
+
.vscode/
|
| 4 |
+
/tmp
|
| 5 |
+
/data
|
| 6 |
+
/checkpoints
|
| 7 |
+
*.so
|
| 8 |
+
*.patch
|
| 9 |
+
__pycache__/
|
| 10 |
+
*.egg-info/
|
| 11 |
+
/viz*
|
| 12 |
+
/submit*
|
| 13 |
+
build/
|
| 14 |
+
*.pyd
|
| 15 |
+
/cache*
|
| 16 |
+
*.stl
|
| 17 |
+
*.pth
|
| 18 |
+
/venv/
|
| 19 |
+
.nk8s
|
| 20 |
+
*.mp4
|
| 21 |
+
.vs
|
| 22 |
+
/exp/
|
| 23 |
+
/dev/
|
| 24 |
+
*.pyi
|
| 25 |
+
!/data/imagenet/imagenet1000_clsidx_to_labels.txt
|
LICENSE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# License for AsymFLUX.2-klein
|
| 2 |
+
|
| 3 |
+
This repository distributes an **AsymFLUX.2-klein app** that is a **Derivative** of
|
| 4 |
+
**FLUX.2 klein Base 9B** by **Black Forest Labs Inc.**
|
| 5 |
+
|
| 6 |
+
Use and distribution of this app are governed by the **FLUX Non-Commercial License**.
|
| 7 |
+
No commercial use of this app or its derivatives is permitted without a separate
|
| 8 |
+
commercial license from Black Forest Labs.
|
| 9 |
+
|
| 10 |
+
- Full license: https://huggingface.co/black-forest-labs/FLUX.2-klein-base-9B/blob/main/LICENSE.md
|
| 11 |
+
- This repository does not grant any rights beyond the license above.
|
README.md
CHANGED
|
@@ -1,14 +1,26 @@
|
|
| 1 |
---
|
| 2 |
-
title: AsymFLUX.2
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AsymFLUX.2-klein Demo
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.49.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: other
|
| 11 |
+
license_name: flux-non-commercial-license
|
| 12 |
+
license_link: LICENSE.md
|
| 13 |
---
|
| 14 |
|
| 15 |
+
Official demo of the paper:
|
| 16 |
+
|
| 17 |
+
**Asymmetric Flow Models**
|
| 18 |
+
<br>
|
| 19 |
+
[Hansheng Chen](https://lakonik.github.io/),
|
| 20 |
+
Jan Ackermann,
|
| 21 |
+
Minseo Kim,
|
| 22 |
+
[Gordon Wetzstein](http://web.stanford.edu/~gordonwz/),
|
| 23 |
+
[Leonidas Guibas](https://geometry.stanford.edu/?member=guibas)<br>
|
| 24 |
+
Stanford University
|
| 25 |
+
<br>
|
| 26 |
+
[[Paper](https://hanshengchen.com/asymflow/static/paper.pdf)] [[Project](https://hanshengchen.com/asymflow)] [[Code](https://github.com/Lakonik/piFlow)]
|
app.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import numpy as np
|
| 7 |
+
import spaces
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from lakonlab.models.architectures import OklabColorEncoder
|
| 11 |
+
from lakonlab.models.diffusions.schedulers import FlowAdapterScheduler
|
| 12 |
+
from lakonlab.pipelines.pipeline_pixelflux2_klein import PixelFlux2KleinPipeline
|
| 13 |
+
from lakonlab.pipelines.prompt_rewriters.qwen3_vl import Qwen3VLPromptRewriter
|
| 14 |
+
from lakonlab.ui.gradio.create_text_to_img import create_interface_text_to_img
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 18 |
+
torch.backends.cudnn.allow_tf32 = True
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
DEFAULT_PROMPT = (
|
| 22 |
+
'Restored color photo from the 1900s. A middle-aged man with cybernetic metal hands is sitting on an old wooden '
|
| 23 |
+
'chair and reading the newspaper. The newspaper has the prominent headline "AsymFLOW RELEASED" in large bold font. '
|
| 24 |
+
'Close-up shot focusing on the newspaper.'
|
| 25 |
+
)
|
| 26 |
+
DEFAULT_NEG_PROMPT = 'Low quality, worst quality, blurry, deformed, bad anatomy, unclear text'
|
| 27 |
+
|
| 28 |
+
SYSTEM_PROMPT_TEXT_ONLY_PATH = 'lakonlab/pipelines/prompt_rewriters/system_prompts/default_text_only.txt'
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def set_random_seed(seed: int, deterministic: bool = True) -> None:
|
| 32 |
+
random.seed(seed)
|
| 33 |
+
np.random.seed(seed)
|
| 34 |
+
torch.manual_seed(seed)
|
| 35 |
+
torch.cuda.manual_seed(seed)
|
| 36 |
+
torch.cuda.manual_seed_all(seed)
|
| 37 |
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
| 38 |
+
if deterministic:
|
| 39 |
+
torch.backends.cudnn.deterministic = True
|
| 40 |
+
torch.backends.cudnn.benchmark = False
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
pipe = PixelFlux2KleinPipeline.from_pretrained(
|
| 44 |
+
'black-forest-labs/FLUX.2-klein-base-9B',
|
| 45 |
+
vae=OklabColorEncoder(
|
| 46 |
+
use_affine_norm=True,
|
| 47 |
+
mean=(0.56, 0.0, 0.01),
|
| 48 |
+
std=0.16),
|
| 49 |
+
scheduler=FlowAdapterScheduler(
|
| 50 |
+
shift=17.0,
|
| 51 |
+
use_dynamic_shifting=True,
|
| 52 |
+
base_seq_len=1024 ** 2,
|
| 53 |
+
max_seq_len=2048 ** 2,
|
| 54 |
+
base_logshift=math.log(17.0),
|
| 55 |
+
max_logshift=math.log(34.0),
|
| 56 |
+
dynamic_shifting_type='sqrt',
|
| 57 |
+
base_scheduler='UniPCMultistep'),
|
| 58 |
+
torch_dtype=torch.bfloat16)
|
| 59 |
+
pipe.load_lakonlab_adapter(
|
| 60 |
+
'Lakonik/AsymFLUX.2-klein-9B',
|
| 61 |
+
target_module_name='transformer')
|
| 62 |
+
pipe = pipe.to('cuda')
|
| 63 |
+
|
| 64 |
+
prompt_rewriter = Qwen3VLPromptRewriter(
|
| 65 |
+
device_map='cuda',
|
| 66 |
+
system_prompt_text_only=open(SYSTEM_PROMPT_TEXT_ONLY_PATH, 'r').read(),
|
| 67 |
+
max_new_tokens_default=512,
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@spaces.GPU
|
| 72 |
+
def run_rewrite_prompt_gpu(seed, prompt, progress):
|
| 73 |
+
set_random_seed(seed)
|
| 74 |
+
progress(0.05, desc='Rewriting prompt...')
|
| 75 |
+
return prompt_rewriter.rewrite_text_batch([prompt])[0]
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def run_rewrite_prompt(seed, prompt, rewrite_prompt, progress=gr.Progress(track_tqdm=True)):
|
| 79 |
+
if rewrite_prompt:
|
| 80 |
+
return run_rewrite_prompt_gpu(seed, prompt, progress), None
|
| 81 |
+
return '', None
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@spaces.GPU
|
| 85 |
+
def generate(
|
| 86 |
+
seed, prompt, negative_prompt, rewrite_prompt, rewritten_prompt, width, height, steps, guidance_scale,
|
| 87 |
+
progress=gr.Progress(track_tqdm=True)):
|
| 88 |
+
return pipe(
|
| 89 |
+
prompt=rewritten_prompt if rewrite_prompt else prompt,
|
| 90 |
+
negative_prompt=negative_prompt,
|
| 91 |
+
width=width,
|
| 92 |
+
height=height,
|
| 93 |
+
num_inference_steps=steps,
|
| 94 |
+
guidance_scale=guidance_scale,
|
| 95 |
+
generator=torch.Generator().manual_seed(seed),
|
| 96 |
+
).images[0]
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
with gr.Blocks(
|
| 100 |
+
analytics_enabled=False,
|
| 101 |
+
title='AsymFLUX.2-klein Demo',
|
| 102 |
+
css_paths='lakonlab/ui/gradio/style.css') as demo:
|
| 103 |
+
gr.Markdown(
|
| 104 |
+
'# AsymFLUX.2-klein Demo\n\n'
|
| 105 |
+
'Pixel-space text-to-image generation demo of the paper '
|
| 106 |
+
'[Asymmetric Flow Models](https://hanshengchen.com/asymflow/static/paper.pdf). '
|
| 107 |
+
'**Base model:** [FLUX.2 klein Base 9B](https://huggingface.co/black-forest-labs/FLUX.2-klein-base-9B). '
|
| 108 |
+
'**Code:** [https://github.com/Lakonik/piFlow](https://github.com/Lakonik/piFlow).\n'
|
| 109 |
+
'<br> Use and distribution of this app are governed by the '
|
| 110 |
+
'[FLUX Non-Commercial License](https://huggingface.co/black-forest-labs/FLUX.2-klein-base-9B/blob/main/LICENSE.md).'
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
create_interface_text_to_img(
|
| 114 |
+
generate,
|
| 115 |
+
prompt=DEFAULT_PROMPT,
|
| 116 |
+
negative_prompt=DEFAULT_NEG_PROMPT,
|
| 117 |
+
steps=38,
|
| 118 |
+
min_steps=4,
|
| 119 |
+
max_steps=50,
|
| 120 |
+
guidance_scale=4.0,
|
| 121 |
+
height=1280,
|
| 122 |
+
width=960,
|
| 123 |
+
create_negative_prompt=True,
|
| 124 |
+
create_prompt_rewrite=True,
|
| 125 |
+
args=[
|
| 126 |
+
'last_seed',
|
| 127 |
+
'prompt',
|
| 128 |
+
'negative_prompt',
|
| 129 |
+
'rewrite_prompt',
|
| 130 |
+
'rewritten_prompt',
|
| 131 |
+
'width',
|
| 132 |
+
'height',
|
| 133 |
+
'steps',
|
| 134 |
+
'guidance_scale',
|
| 135 |
+
],
|
| 136 |
+
rewrite_prompt_api=run_rewrite_prompt,
|
| 137 |
+
rewrite_prompt_args=['last_seed', 'prompt', 'rewrite_prompt'])
|
| 138 |
+
|
| 139 |
+
demo.queue().launch()
|
lakonlab/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__version__ = '0.2.0'
|
lakonlab/models/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
lakonlab/models/architectures/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .autoencoders import OklabColorEncoder, RGBColorEncoder
|
| 2 |
+
|
| 3 |
+
__all__ = ['OklabColorEncoder', 'RGBColorEncoder']
|
lakonlab/models/architectures/asymflow/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .asymflux2 import _AsymFlux2Transformer2DModel
|
| 2 |
+
|
| 3 |
+
__all__ = ['_AsymFlux2Transformer2DModel']
|
lakonlab/models/architectures/asymflow/asymflux2.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Optional, List
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
|
| 6 |
+
from diffusers.models.transformers.transformer_flux2 import (
|
| 7 |
+
Flux2Transformer2DModel, Flux2PosEmbed, Flux2TransformerBlock, Flux2SingleTransformerBlock,
|
| 8 |
+
Flux2TimestepGuidanceEmbeddings, Flux2Modulation)
|
| 9 |
+
from diffusers.models.normalization import AdaLayerNormContinuous
|
| 10 |
+
from diffusers.configuration_utils import register_to_config
|
| 11 |
+
from diffusers.utils import apply_lora_scale
|
| 12 |
+
|
| 13 |
+
from .common import AsymFlowMixin
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class _AsymFlux2Transformer2DModel(AsymFlowMixin, Flux2Transformer2DModel):
|
| 17 |
+
|
| 18 |
+
@register_to_config
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
patch_size=16,
|
| 22 |
+
in_channels: int = 3,
|
| 23 |
+
base_rank: int = 128,
|
| 24 |
+
num_layers: int = 8,
|
| 25 |
+
num_single_layers: int = 48,
|
| 26 |
+
attention_head_dim: int = 128,
|
| 27 |
+
num_attention_heads: int = 48,
|
| 28 |
+
joint_attention_dim: int = 15360,
|
| 29 |
+
timestep_guidance_channels: int = 256,
|
| 30 |
+
mlp_ratio: float = 3.0,
|
| 31 |
+
axes_dims_rope: tuple[int, ...] = (32, 32, 32, 32),
|
| 32 |
+
rope_theta: int = 2000,
|
| 33 |
+
eps: float = 1e-6,
|
| 34 |
+
sigma_min: float = 1e-4,
|
| 35 |
+
num_timesteps=1,
|
| 36 |
+
guidance_embeds: bool = True):
|
| 37 |
+
super(Flux2Transformer2DModel, self).__init__()
|
| 38 |
+
|
| 39 |
+
self.patch_size = patch_size
|
| 40 |
+
|
| 41 |
+
self.in_channels = in_channels
|
| 42 |
+
self.out_channels = in_channels
|
| 43 |
+
|
| 44 |
+
self.inner_dim = num_attention_heads * attention_head_dim
|
| 45 |
+
|
| 46 |
+
# 1. Sinusoidal positional embedding for RoPE on image and text tokens
|
| 47 |
+
self.pos_embed = Flux2PosEmbed(theta=rope_theta, axes_dim=axes_dims_rope)
|
| 48 |
+
|
| 49 |
+
# 2. Combined timestep + guidance embedding
|
| 50 |
+
self.time_guidance_embed = Flux2TimestepGuidanceEmbeddings(
|
| 51 |
+
in_channels=timestep_guidance_channels,
|
| 52 |
+
embedding_dim=self.inner_dim,
|
| 53 |
+
bias=False,
|
| 54 |
+
guidance_embeds=guidance_embeds,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# 3. Modulation (double stream and single stream blocks share modulation parameters, resp.)
|
| 58 |
+
# Two sets of shift/scale/gate modulation parameters for the double stream attn and FF sub-blocks
|
| 59 |
+
self.double_stream_modulation_img = Flux2Modulation(self.inner_dim, mod_param_sets=2, bias=False)
|
| 60 |
+
self.double_stream_modulation_txt = Flux2Modulation(self.inner_dim, mod_param_sets=2, bias=False)
|
| 61 |
+
# Only one set of modulation parameters as the attn and FF sub-blocks are run in parallel for single stream
|
| 62 |
+
self.single_stream_modulation = Flux2Modulation(self.inner_dim, mod_param_sets=1, bias=False)
|
| 63 |
+
|
| 64 |
+
# 4. Input projections
|
| 65 |
+
self.x_embedder = nn.Linear(in_channels * (patch_size ** 2), self.inner_dim, bias=False)
|
| 66 |
+
self.context_embedder = nn.Linear(joint_attention_dim, self.inner_dim, bias=False)
|
| 67 |
+
|
| 68 |
+
# 5. Double Stream Transformer Blocks
|
| 69 |
+
self.transformer_blocks = nn.ModuleList(
|
| 70 |
+
[
|
| 71 |
+
Flux2TransformerBlock(
|
| 72 |
+
dim=self.inner_dim,
|
| 73 |
+
num_attention_heads=num_attention_heads,
|
| 74 |
+
attention_head_dim=attention_head_dim,
|
| 75 |
+
mlp_ratio=mlp_ratio,
|
| 76 |
+
eps=eps,
|
| 77 |
+
bias=False,
|
| 78 |
+
)
|
| 79 |
+
for _ in range(num_layers)
|
| 80 |
+
]
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
# 6. Single Stream Transformer Blocks
|
| 84 |
+
self.single_transformer_blocks = nn.ModuleList(
|
| 85 |
+
[
|
| 86 |
+
Flux2SingleTransformerBlock(
|
| 87 |
+
dim=self.inner_dim,
|
| 88 |
+
num_attention_heads=num_attention_heads,
|
| 89 |
+
attention_head_dim=attention_head_dim,
|
| 90 |
+
mlp_ratio=mlp_ratio,
|
| 91 |
+
eps=eps,
|
| 92 |
+
bias=False,
|
| 93 |
+
)
|
| 94 |
+
for _ in range(num_single_layers)
|
| 95 |
+
]
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
# 7. Output layers
|
| 99 |
+
self.norm_out = AdaLayerNormContinuous(
|
| 100 |
+
self.inner_dim, self.inner_dim, elementwise_affine=False, eps=eps, bias=False
|
| 101 |
+
)
|
| 102 |
+
self.proj_out = nn.Linear(
|
| 103 |
+
self.inner_dim, self.out_channels * (patch_size ** 2), bias=False
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# 8. AsymFlow attributes and buffers
|
| 107 |
+
self.base_rank = base_rank
|
| 108 |
+
self.sigma_min = sigma_min
|
| 109 |
+
self.num_timesteps = num_timesteps
|
| 110 |
+
self.init_asymflow_buffers(self.in_channels * (patch_size ** 2), self.base_rank)
|
| 111 |
+
|
| 112 |
+
self.gradient_checkpointing = False
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def patchify(latents, patch_size, pack_channels=True):
|
| 116 |
+
bs, c, h, w = latents.size()
|
| 117 |
+
latents = latents.reshape(
|
| 118 |
+
bs, c, h // patch_size, patch_size, w // patch_size, patch_size
|
| 119 |
+
).permute(
|
| 120 |
+
0, 1, 3, 5, 2, 4
|
| 121 |
+
)
|
| 122 |
+
if pack_channels:
|
| 123 |
+
latents = latents.reshape(
|
| 124 |
+
bs, c * patch_size * patch_size, h // patch_size, w // patch_size)
|
| 125 |
+
else:
|
| 126 |
+
latents = latents.reshape(
|
| 127 |
+
bs, c, patch_size * patch_size, h // patch_size, w // patch_size)
|
| 128 |
+
return latents
|
| 129 |
+
|
| 130 |
+
@staticmethod
|
| 131 |
+
def unpatchify(latents, patch_size, packed_channels=True):
|
| 132 |
+
if packed_channels:
|
| 133 |
+
bs, c, h, w = latents.size()
|
| 134 |
+
latents = latents.reshape(
|
| 135 |
+
bs, c // (patch_size * patch_size), patch_size, patch_size, h, w
|
| 136 |
+
).permute(
|
| 137 |
+
0, 1, 4, 2, 5, 3
|
| 138 |
+
).reshape(
|
| 139 |
+
bs, c // (patch_size * patch_size), h * patch_size, w * patch_size)
|
| 140 |
+
else:
|
| 141 |
+
bs, c, _, h, w = latents.size()
|
| 142 |
+
latents = latents.reshape(
|
| 143 |
+
bs, c, patch_size, patch_size, h, w
|
| 144 |
+
).permute(
|
| 145 |
+
0, 1, 4, 2, 5, 3
|
| 146 |
+
).reshape(
|
| 147 |
+
bs, c, h * patch_size, w * patch_size)
|
| 148 |
+
return latents
|
| 149 |
+
|
| 150 |
+
@staticmethod
|
| 151 |
+
def pack(latents):
|
| 152 |
+
bs, c, h, w = latents.shape
|
| 153 |
+
latents = latents.reshape(bs, c, h * w).permute(0, 2, 1)
|
| 154 |
+
return latents
|
| 155 |
+
|
| 156 |
+
@staticmethod
|
| 157 |
+
def unpack(latents, h, w):
|
| 158 |
+
bs, _, c = latents.shape
|
| 159 |
+
latents = latents.permute(0, 2, 1).reshape(bs, c, h, w)
|
| 160 |
+
return latents
|
| 161 |
+
|
| 162 |
+
@staticmethod
|
| 163 |
+
def _prepare_latent_ids(latents):
|
| 164 |
+
batch_size, _, height, width = latents.shape
|
| 165 |
+
|
| 166 |
+
t = torch.arange(1)
|
| 167 |
+
h = torch.arange(height)
|
| 168 |
+
w = torch.arange(width)
|
| 169 |
+
l = torch.arange(1)
|
| 170 |
+
|
| 171 |
+
latent_ids = torch.cartesian_prod(t, h, w, l)
|
| 172 |
+
latent_ids = latent_ids.unsqueeze(0).expand(batch_size, -1, -1)
|
| 173 |
+
|
| 174 |
+
return latent_ids.to(device=latents.device)
|
| 175 |
+
|
| 176 |
+
@staticmethod
|
| 177 |
+
def _prepare_condition_latent_ids(
|
| 178 |
+
image_latents: List[torch.Tensor],
|
| 179 |
+
scale: int = 10):
|
| 180 |
+
if not isinstance(image_latents, list):
|
| 181 |
+
raise ValueError(f"Expected `image_latents` to be a list, got {type(image_latents)}.")
|
| 182 |
+
|
| 183 |
+
t_coords = [scale + scale * t for t in torch.arange(0, len(image_latents))]
|
| 184 |
+
t_coords = [t.view(-1) for t in t_coords]
|
| 185 |
+
|
| 186 |
+
image_latent_ids = []
|
| 187 |
+
for x, t in zip(image_latents, t_coords):
|
| 188 |
+
_, _, h, w = x.shape
|
| 189 |
+
x_ids = torch.cartesian_prod(t, torch.arange(h), torch.arange(w), torch.arange(1))
|
| 190 |
+
image_latent_ids.append(x_ids)
|
| 191 |
+
|
| 192 |
+
image_latent_ids = torch.cat(image_latent_ids, dim=0)
|
| 193 |
+
image_latent_ids = image_latent_ids.unsqueeze(0).expand(image_latents[0].size(0), -1, -1)
|
| 194 |
+
|
| 195 |
+
return image_latent_ids.to(device=image_latents[0].device)
|
| 196 |
+
|
| 197 |
+
def _get_rotary_emb(self, img_ids, txt_ids):
|
| 198 |
+
if img_ids.ndim == 3:
|
| 199 |
+
img_ids = img_ids[0]
|
| 200 |
+
if txt_ids.ndim == 3:
|
| 201 |
+
txt_ids = txt_ids[0]
|
| 202 |
+
|
| 203 |
+
with torch.autocast(device_type='cuda', dtype=torch.float32, enabled=False):
|
| 204 |
+
img_ids = img_ids.float()
|
| 205 |
+
image_rotary_emb = self.pos_embed(img_ids)
|
| 206 |
+
text_rotary_emb = self.pos_embed(txt_ids)
|
| 207 |
+
return (
|
| 208 |
+
torch.cat([text_rotary_emb[0], image_rotary_emb[0]], dim=0),
|
| 209 |
+
torch.cat([text_rotary_emb[1], image_rotary_emb[1]], dim=0),
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
@apply_lora_scale("joint_attention_kwargs")
|
| 213 |
+
def forward(
|
| 214 |
+
self,
|
| 215 |
+
x_t: torch.Tensor,
|
| 216 |
+
timestep: torch.Tensor,
|
| 217 |
+
encoder_hidden_states: torch.Tensor = None,
|
| 218 |
+
condition_latents: List[torch.Tensor] | None = None,
|
| 219 |
+
txt_ids: torch.Tensor = None,
|
| 220 |
+
guidance: torch.Tensor = None,
|
| 221 |
+
joint_attention_kwargs: dict[str, Any] | None = None):
|
| 222 |
+
x_t = self.patchify(x_t, self.patch_size)
|
| 223 |
+
img_ids = self._prepare_latent_ids(x_t)
|
| 224 |
+
|
| 225 |
+
bs, _, h, w = x_t.size()
|
| 226 |
+
x_t_packed = self.pack(x_t)
|
| 227 |
+
num_x_tokens = x_t_packed.size(1)
|
| 228 |
+
packed_ndim = x_t_packed.dim()
|
| 229 |
+
|
| 230 |
+
# scale and timestep calibration
|
| 231 |
+
calibration = self.asymflow_calibration(timestep, bs, packed_ndim)
|
| 232 |
+
hidden_states = x_t_packed * calibration.k.to(x_t_packed.dtype)
|
| 233 |
+
|
| 234 |
+
input_img_ids = img_ids
|
| 235 |
+
if condition_latents is not None:
|
| 236 |
+
condition_hidden_states = [self.patchify(z, self.patch_size) for z in condition_latents]
|
| 237 |
+
condition_latent_ids = self._prepare_condition_latent_ids(condition_hidden_states)
|
| 238 |
+
condition_hidden_states = [self.pack(z) / calibration.s for z in condition_hidden_states]
|
| 239 |
+
hidden_states = torch.cat([hidden_states] + condition_hidden_states, dim=1)
|
| 240 |
+
input_img_ids = torch.cat([img_ids, condition_latent_ids], dim=1)
|
| 241 |
+
|
| 242 |
+
hidden_states = self.x_embedder(hidden_states)
|
| 243 |
+
|
| 244 |
+
num_txt_tokens = encoder_hidden_states.shape[1]
|
| 245 |
+
|
| 246 |
+
if guidance is not None:
|
| 247 |
+
guidance = guidance.to(hidden_states.dtype) * 1000
|
| 248 |
+
temb = self.time_guidance_embed(calibration.timestep.to(hidden_states.dtype) * 1000, guidance)
|
| 249 |
+
|
| 250 |
+
double_stream_mod_img = self.double_stream_modulation_img(temb)
|
| 251 |
+
double_stream_mod_txt = self.double_stream_modulation_txt(temb)
|
| 252 |
+
single_stream_mod = self.single_stream_modulation(temb)
|
| 253 |
+
|
| 254 |
+
encoder_hidden_states = self.context_embedder(encoder_hidden_states)
|
| 255 |
+
concat_rotary_emb = self._get_rotary_emb(input_img_ids, txt_ids)
|
| 256 |
+
|
| 257 |
+
for block in self.transformer_blocks:
|
| 258 |
+
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 259 |
+
encoder_hidden_states, hidden_states = self._gradient_checkpointing_func(
|
| 260 |
+
block,
|
| 261 |
+
hidden_states,
|
| 262 |
+
encoder_hidden_states,
|
| 263 |
+
double_stream_mod_img,
|
| 264 |
+
double_stream_mod_txt,
|
| 265 |
+
concat_rotary_emb,
|
| 266 |
+
joint_attention_kwargs,
|
| 267 |
+
)
|
| 268 |
+
else:
|
| 269 |
+
encoder_hidden_states, hidden_states = block(
|
| 270 |
+
hidden_states=hidden_states,
|
| 271 |
+
encoder_hidden_states=encoder_hidden_states,
|
| 272 |
+
temb_mod_img=double_stream_mod_img,
|
| 273 |
+
temb_mod_txt=double_stream_mod_txt,
|
| 274 |
+
image_rotary_emb=concat_rotary_emb,
|
| 275 |
+
joint_attention_kwargs=joint_attention_kwargs,
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1)
|
| 279 |
+
|
| 280 |
+
for block in self.single_transformer_blocks:
|
| 281 |
+
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 282 |
+
hidden_states = self._gradient_checkpointing_func(
|
| 283 |
+
block,
|
| 284 |
+
hidden_states,
|
| 285 |
+
None,
|
| 286 |
+
single_stream_mod,
|
| 287 |
+
concat_rotary_emb,
|
| 288 |
+
joint_attention_kwargs,
|
| 289 |
+
)
|
| 290 |
+
else:
|
| 291 |
+
hidden_states = block(
|
| 292 |
+
hidden_states=hidden_states,
|
| 293 |
+
encoder_hidden_states=None,
|
| 294 |
+
temb_mod=single_stream_mod,
|
| 295 |
+
image_rotary_emb=concat_rotary_emb,
|
| 296 |
+
joint_attention_kwargs=joint_attention_kwargs,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
hidden_states = hidden_states[:, num_txt_tokens:num_txt_tokens + num_x_tokens]
|
| 300 |
+
hidden_states = self.norm_out(hidden_states, temb)
|
| 301 |
+
|
| 302 |
+
u_a_packed = self.proj_out(hidden_states)
|
| 303 |
+
|
| 304 |
+
output_packed = self.asymflow_velocity(u_a_packed, x_t_packed, calibration)
|
| 305 |
+
|
| 306 |
+
output = self.unpack(output_packed.to(hidden_states.dtype), h, w)
|
| 307 |
+
output = self.unpatchify(output, self.patch_size)
|
| 308 |
+
|
| 309 |
+
return output
|
lakonlab/models/architectures/asymflow/common.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import NamedTuple
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class AsymFlowCalibration(NamedTuple):
|
| 8 |
+
s: torch.Tensor
|
| 9 |
+
k: torch.Tensor
|
| 10 |
+
timestep: torch.Tensor
|
| 11 |
+
sigma: torch.Tensor
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class AsymFlowMixin:
|
| 15 |
+
|
| 16 |
+
train_sigma_min = 1e-6
|
| 17 |
+
|
| 18 |
+
def init_asymflow_buffers(self, patch_dim: int, base_rank: int):
|
| 19 |
+
assert patch_dim >= base_rank
|
| 20 |
+
eye = torch.eye(base_rank)
|
| 21 |
+
self.register_buffer('proj_buffer', F.pad(eye, (0, 0, 0, patch_dim - base_rank))) # (patch_dim, base_rank)
|
| 22 |
+
self.register_buffer('scale_buffer', torch.tensor(1.0))
|
| 23 |
+
|
| 24 |
+
def asymflow_calibration(self, timestep, batch_size: int, ndim: int):
|
| 25 |
+
with torch.autocast(device_type='cuda', dtype=torch.float32, enabled=False):
|
| 26 |
+
timestep = timestep.float()
|
| 27 |
+
s = self.scale_buffer.float()
|
| 28 |
+
sigma = timestep / self.num_timesteps
|
| 29 |
+
k = 1 / (s + (1 - s) * sigma)
|
| 30 |
+
cal_timestep = timestep * k
|
| 31 |
+
sigma = sigma.expand(batch_size).reshape(batch_size, *((ndim - 1) * [1])).float()
|
| 32 |
+
k = k.reshape(batch_size, *((ndim - 1) * [1]))
|
| 33 |
+
return AsymFlowCalibration(
|
| 34 |
+
s=s,
|
| 35 |
+
k=k,
|
| 36 |
+
timestep=cal_timestep,
|
| 37 |
+
sigma=sigma,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
@staticmethod
|
| 41 |
+
def orthogonal_decomposition(full_rank_state, proj_buffer):
|
| 42 |
+
subspace = full_rank_state @ proj_buffer @ proj_buffer.T
|
| 43 |
+
complement = full_rank_state - subspace
|
| 44 |
+
return subspace, complement
|
| 45 |
+
|
| 46 |
+
def asymflow_velocity(
|
| 47 |
+
self,
|
| 48 |
+
u_a_packed,
|
| 49 |
+
x_t_packed,
|
| 50 |
+
calibration: AsymFlowCalibration):
|
| 51 |
+
with torch.autocast(device_type='cuda', dtype=torch.float32, enabled=False):
|
| 52 |
+
sigma_min = self.train_sigma_min if self.training else self.sigma_min
|
| 53 |
+
u_a_packed = u_a_packed.float()
|
| 54 |
+
x_t_packed = x_t_packed.float()
|
| 55 |
+
proj_buffer = self.proj_buffer.float()
|
| 56 |
+
# orthogonal decomposition
|
| 57 |
+
u_a_subspace, u_a_complement = self.orthogonal_decomposition(u_a_packed, proj_buffer)
|
| 58 |
+
x_t_subspace, x_t_complement = self.orthogonal_decomposition(x_t_packed, proj_buffer)
|
| 59 |
+
# read calibration output
|
| 60 |
+
sk = calibration.s * calibration.k
|
| 61 |
+
sigma_clamped = calibration.sigma.clamp(min=sigma_min)
|
| 62 |
+
# low-rank subspace
|
| 63 |
+
u_subspace = (
|
| 64 |
+
sk * u_a_subspace
|
| 65 |
+
+ (1 - sk) / sigma_clamped * x_t_subspace
|
| 66 |
+
)
|
| 67 |
+
# orthogonal complement
|
| 68 |
+
u_complement = (x_t_complement + calibration.s * u_a_complement) / sigma_clamped
|
| 69 |
+
# full velocity
|
| 70 |
+
return u_subspace + u_complement
|
lakonlab/models/architectures/autoencoders/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .color_encoders import OklabColorEncoder, RGBColorEncoder
|
| 2 |
+
|
| 3 |
+
__all__ = ['OklabColorEncoder', 'RGBColorEncoder']
|
lakonlab/models/architectures/autoencoders/color_encoders.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 4 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class OklabColorEncoder(ModelMixin, ConfigMixin):
|
| 8 |
+
|
| 9 |
+
@register_to_config
|
| 10 |
+
def __init__(
|
| 11 |
+
self,
|
| 12 |
+
use_affine_norm=True,
|
| 13 |
+
mean=(0.5, 0.0, 0.0),
|
| 14 |
+
std=0.21,
|
| 15 |
+
):
|
| 16 |
+
super().__init__()
|
| 17 |
+
self.use_affine_norm = use_affine_norm
|
| 18 |
+
self.register_buffer('lrgb_to_lms', torch.tensor([
|
| 19 |
+
[0.4122214708, 0.5363325363, 0.0514459929],
|
| 20 |
+
[0.2119034982, 0.6806995451, 0.1073969566],
|
| 21 |
+
[0.0883024619, 0.2817188376, 0.6299787005]
|
| 22 |
+
], dtype=torch.float32))
|
| 23 |
+
self.register_buffer('lms_to_oklab', torch.tensor([
|
| 24 |
+
[0.2104542553, 0.7936177850, -0.0040720468],
|
| 25 |
+
[1.9779984951, -2.4285922050, 0.4505937099],
|
| 26 |
+
[0.0259040371, 0.7827717662, -0.8086757660]
|
| 27 |
+
], dtype=torch.float32))
|
| 28 |
+
self.register_buffer('oklab_to_lms', torch.linalg.inv(self.lms_to_oklab))
|
| 29 |
+
self.register_buffer('lms_to_lrgb', torch.linalg.inv(self.lrgb_to_lms))
|
| 30 |
+
if self.use_affine_norm:
|
| 31 |
+
self.register_buffer('affine_mean', torch.tensor(mean, dtype=torch.float32))
|
| 32 |
+
self.register_buffer('affine_std', torch.tensor(std, dtype=torch.float32))
|
| 33 |
+
|
| 34 |
+
@property
|
| 35 |
+
def dtype(self):
|
| 36 |
+
return self.lrgb_to_lms.dtype
|
| 37 |
+
|
| 38 |
+
@staticmethod
|
| 39 |
+
def srgb_to_lrgb(srgb):
|
| 40 |
+
a = 0.055
|
| 41 |
+
return torch.where(srgb <= 0.04045, srgb / 12.92, ((srgb + a) / (1 + a)) ** 2.4)
|
| 42 |
+
|
| 43 |
+
@staticmethod
|
| 44 |
+
def lrgb_to_srgb(lrgb):
|
| 45 |
+
lrgb = lrgb.clamp(min=0)
|
| 46 |
+
a = 0.055
|
| 47 |
+
return torch.where(lrgb <= 0.0031308, lrgb * 12.92, (1 + a) * (lrgb ** (1 / 2.4)) - a)
|
| 48 |
+
|
| 49 |
+
def lrgb_to_oklab(self, lrgb):
|
| 50 |
+
"""
|
| 51 |
+
Args:
|
| 52 |
+
lrgb (torch.Tensor): Linear RGB, shape (N, 3, *)
|
| 53 |
+
"""
|
| 54 |
+
lms = torch.einsum('ij,bj...->bi...', self.lrgb_to_lms, lrgb).clamp(min=0)
|
| 55 |
+
oklab = torch.einsum('ij,bj...->bi...', self.lms_to_oklab, lms.pow(1/3))
|
| 56 |
+
return oklab
|
| 57 |
+
|
| 58 |
+
def oklab_to_lrgb(self, oklab):
|
| 59 |
+
"""
|
| 60 |
+
Args:
|
| 61 |
+
oklab (torch.Tensor): Oklab, shape (N, 3, *)
|
| 62 |
+
"""
|
| 63 |
+
lms = torch.einsum('ij,bj...->bi...', self.oklab_to_lms, oklab).pow(3)
|
| 64 |
+
lrgb = torch.einsum('ij,bj...->bi...', self.lms_to_lrgb, lms)
|
| 65 |
+
return lrgb.clamp(0, 1)
|
| 66 |
+
|
| 67 |
+
def encode(self, img):
|
| 68 |
+
rgb = img / 2 + 0.5
|
| 69 |
+
lrgb = self.srgb_to_lrgb(rgb)
|
| 70 |
+
oklab = self.lrgb_to_oklab(lrgb)
|
| 71 |
+
if self.use_affine_norm:
|
| 72 |
+
n_dim = img.dim() - 2
|
| 73 |
+
mean = self.affine_mean.reshape(-1, *([1] * n_dim))
|
| 74 |
+
std = self.affine_std.reshape(-1, *([1] * n_dim))
|
| 75 |
+
oklab = (oklab - mean) / std
|
| 76 |
+
return oklab
|
| 77 |
+
|
| 78 |
+
def decode(self, oklab):
|
| 79 |
+
if self.use_affine_norm:
|
| 80 |
+
n_dim = oklab.dim() - 2
|
| 81 |
+
mean = self.affine_mean.reshape(-1, *([1] * n_dim))
|
| 82 |
+
std = self.affine_std.reshape(-1, *([1] * n_dim))
|
| 83 |
+
oklab = oklab * std + mean
|
| 84 |
+
lrgb = self.oklab_to_lrgb(oklab)
|
| 85 |
+
rgb = self.lrgb_to_srgb(lrgb)
|
| 86 |
+
img = rgb * 2 - 1
|
| 87 |
+
return img
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class RGBColorEncoder(ModelMixin, ConfigMixin):
|
| 91 |
+
|
| 92 |
+
@register_to_config
|
| 93 |
+
def __init__(
|
| 94 |
+
self,
|
| 95 |
+
use_affine_norm=False,
|
| 96 |
+
mean=(0.0, 0.0, 0.0),
|
| 97 |
+
std=1.0,
|
| 98 |
+
):
|
| 99 |
+
super().__init__()
|
| 100 |
+
self.use_affine_norm = use_affine_norm
|
| 101 |
+
if self.use_affine_norm:
|
| 102 |
+
self.register_buffer('affine_mean', torch.tensor(mean, dtype=torch.float32))
|
| 103 |
+
self.register_buffer('affine_std', torch.tensor(std, dtype=torch.float32))
|
| 104 |
+
|
| 105 |
+
@property
|
| 106 |
+
def dtype(self):
|
| 107 |
+
if self.use_affine_norm:
|
| 108 |
+
return self.affine_mean.dtype
|
| 109 |
+
else:
|
| 110 |
+
return torch.float32
|
| 111 |
+
|
| 112 |
+
def encode(self, img):
|
| 113 |
+
if self.use_affine_norm:
|
| 114 |
+
n_dim = img.dim() - 2
|
| 115 |
+
mean = self.affine_mean.reshape(-1, *([1] * n_dim))
|
| 116 |
+
std = self.affine_std.reshape(-1, *([1] * n_dim))
|
| 117 |
+
img = (img - mean) / std
|
| 118 |
+
return img
|
| 119 |
+
|
| 120 |
+
def decode(self, img):
|
| 121 |
+
if self.use_affine_norm:
|
| 122 |
+
n_dim = img.dim() - 2
|
| 123 |
+
mean = self.affine_mean.reshape(-1, *([1] * n_dim))
|
| 124 |
+
std = self.affine_std.reshape(-1, *([1] * n_dim))
|
| 125 |
+
img = img * std + mean
|
| 126 |
+
return img.clamp(min=-1, max=1)
|
lakonlab/models/diffusions/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
lakonlab/models/diffusions/gaussian_flow.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@torch.jit.script
|
| 7 |
+
def guidance_jit(
|
| 8 |
+
pos_mean, neg_mean, guidance_scale,
|
| 9 |
+
orthogonal: float = 1.0, parallel_dir: Optional[torch.Tensor] = None):
|
| 10 |
+
bias = (pos_mean - neg_mean) * (guidance_scale - 1)
|
| 11 |
+
if orthogonal:
|
| 12 |
+
dim = list(range(1, pos_mean.dim()))
|
| 13 |
+
if parallel_dir is None:
|
| 14 |
+
parallel_dir = pos_mean
|
| 15 |
+
bias = bias - ((bias * parallel_dir).mean(
|
| 16 |
+
dim=dim, keepdim=True
|
| 17 |
+
) / (parallel_dir * parallel_dir).mean(
|
| 18 |
+
dim=dim, keepdim=True
|
| 19 |
+
).clamp(min=1e-6) * parallel_dir).mul(orthogonal)
|
| 20 |
+
return bias
|
lakonlab/models/diffusions/schedulers/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .flow_adapter import FlowAdapterScheduler
|
| 2 |
+
|
| 3 |
+
__all__ = ['FlowAdapterScheduler']
|
lakonlab/models/diffusions/schedulers/flow_adapter.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import inspect
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import Optional, Tuple, Union, List
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import diffusers
|
| 10 |
+
|
| 11 |
+
from diffusers.configuration_utils import register_to_config
|
| 12 |
+
from diffusers.utils import BaseOutput
|
| 13 |
+
from diffusers.schedulers import SchedulerMixin
|
| 14 |
+
from diffusers.configuration_utils import ConfigMixin
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class FlowWrapperSchedulerOutput(BaseOutput):
|
| 19 |
+
prev_sample: torch.FloatTensor
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class FlowAdapterScheduler(SchedulerMixin, ConfigMixin):
|
| 23 |
+
|
| 24 |
+
order = 1
|
| 25 |
+
|
| 26 |
+
@register_to_config
|
| 27 |
+
def __init__(
|
| 28 |
+
self,
|
| 29 |
+
num_train_timesteps: int = 1000,
|
| 30 |
+
shift: float = 1.0,
|
| 31 |
+
use_dynamic_shifting=False,
|
| 32 |
+
dynamic_shifting_type='exp',
|
| 33 |
+
base_seq_len=256,
|
| 34 |
+
max_seq_len=4096,
|
| 35 |
+
base_logshift=0.5,
|
| 36 |
+
max_logshift=1.15,
|
| 37 |
+
terminal_sigma=None,
|
| 38 |
+
base_scheduler='UniPCMultistep',
|
| 39 |
+
eps=1e-4,
|
| 40 |
+
**kwargs):
|
| 41 |
+
|
| 42 |
+
sigmas = torch.from_numpy(1 - np.linspace(
|
| 43 |
+
0, 1, num_train_timesteps, dtype=np.float32, endpoint=False))
|
| 44 |
+
self.sigmas = shift * sigmas / (1 + (shift - 1) * sigmas)
|
| 45 |
+
self.timesteps = self.sigmas * num_train_timesteps
|
| 46 |
+
alphas = 1 - self.sigmas
|
| 47 |
+
|
| 48 |
+
base_scheduler_class = getattr(diffusers.schedulers, base_scheduler + 'Scheduler', None)
|
| 49 |
+
|
| 50 |
+
if base_scheduler_class is None:
|
| 51 |
+
raise AttributeError(f'Cannot find base_scheduler [{base_scheduler}].')
|
| 52 |
+
if base_scheduler in ['EulerDiscrete', 'EulerAncestralDiscrete']:
|
| 53 |
+
assert kwargs.get('prediction_type', 'epsilon') == 'epsilon'
|
| 54 |
+
kwargs['prediction_type'] = 'epsilon'
|
| 55 |
+
self.scales = ((alphas ** 2 + self.sigmas ** 2) / (
|
| 56 |
+
1 + (self.sigmas / alphas.clamp(min=self.config.eps)) ** 2)).sqrt()
|
| 57 |
+
elif base_scheduler in [
|
| 58 |
+
'UniPCMultistep', 'DPMSolverSinglestep', 'DPMSolverMultistep', 'DEISMultistep', 'SASolver']:
|
| 59 |
+
self.scales = torch.ones_like(alphas)
|
| 60 |
+
assert kwargs.get('prediction_type', 'flow_prediction') == 'flow_prediction'
|
| 61 |
+
kwargs['prediction_type'] = 'flow_prediction'
|
| 62 |
+
kwargs['use_flow_sigmas'] = True
|
| 63 |
+
else:
|
| 64 |
+
raise AttributeError(f'Unsupported base_scheduler [{base_scheduler}].')
|
| 65 |
+
|
| 66 |
+
signatures = inspect.signature(base_scheduler_class).parameters.keys()
|
| 67 |
+
if 'final_sigmas_type' in signatures:
|
| 68 |
+
kwargs['final_sigmas_type'] = 'zero'
|
| 69 |
+
if 'lower_order_final' in signatures:
|
| 70 |
+
kwargs['lower_order_final'] = True
|
| 71 |
+
|
| 72 |
+
self.base_scheduler = base_scheduler_class(
|
| 73 |
+
num_train_timesteps=num_train_timesteps,
|
| 74 |
+
**kwargs)
|
| 75 |
+
self.base_scheduler.timesteps = self.timesteps
|
| 76 |
+
if self.config.base_scheduler in ['EulerDiscrete', 'EulerAncestralDiscrete']:
|
| 77 |
+
self.base_scheduler.sigmas = self.sigmas / alphas.clamp(min=self.config.eps)
|
| 78 |
+
elif self.config.base_scheduler in [
|
| 79 |
+
'UniPCMultistep', 'DPMSolverSinglestep', 'DPMSolverMultistep', 'DEISMultistep', 'SASolver']:
|
| 80 |
+
self.base_scheduler.sigmas = self.sigmas
|
| 81 |
+
else:
|
| 82 |
+
raise AttributeError(f'Unsupported base_scheduler [{self.config.base_scheduler}].')
|
| 83 |
+
|
| 84 |
+
self._step_index = None
|
| 85 |
+
self._begin_index = None
|
| 86 |
+
|
| 87 |
+
@property
|
| 88 |
+
def step_index(self):
|
| 89 |
+
return self._step_index
|
| 90 |
+
|
| 91 |
+
@property
|
| 92 |
+
def begin_index(self):
|
| 93 |
+
return self._begin_index
|
| 94 |
+
|
| 95 |
+
def set_begin_index(self, begin_index: int = 0):
|
| 96 |
+
self._begin_index = begin_index
|
| 97 |
+
|
| 98 |
+
def get_shift(self, seq_len=None):
|
| 99 |
+
if self.config.use_dynamic_shifting and seq_len is not None:
|
| 100 |
+
if self.config.dynamic_shifting_type == 'exp':
|
| 101 |
+
m = (self.config.max_logshift - self.config.base_logshift
|
| 102 |
+
) / (self.config.max_seq_len - self.config.base_seq_len)
|
| 103 |
+
logshift = (seq_len - self.config.base_seq_len) * m + self.config.base_logshift
|
| 104 |
+
if isinstance(logshift, torch.Tensor):
|
| 105 |
+
shift = torch.exp(logshift)
|
| 106 |
+
else:
|
| 107 |
+
shift = np.exp(logshift)
|
| 108 |
+
elif self.config.dynamic_shifting_type == 'sqrt':
|
| 109 |
+
max_shift = np.exp(self.config.max_logshift)
|
| 110 |
+
base_shift = np.exp(self.config.base_logshift)
|
| 111 |
+
sqrt_max_seq_len = np.sqrt(self.config.max_seq_len)
|
| 112 |
+
sqrt_base_seq_len = np.sqrt(self.config.base_seq_len)
|
| 113 |
+
m = (max_shift - base_shift) / (sqrt_max_seq_len - sqrt_base_seq_len)
|
| 114 |
+
shift = (np.sqrt(seq_len) - sqrt_base_seq_len) * m + base_shift
|
| 115 |
+
else:
|
| 116 |
+
raise ValueError(f'Unsupported dynamic_shifting_type [{self.config.dynamic_shifting_type}].')
|
| 117 |
+
else:
|
| 118 |
+
shift = self.config.shift
|
| 119 |
+
return shift
|
| 120 |
+
|
| 121 |
+
def stretch_to_terminal(self, sigma):
|
| 122 |
+
one_minus_sigma = 1 - sigma
|
| 123 |
+
stretched_sigma = 1 - (one_minus_sigma * (1 - self.config.terminal_sigma) / one_minus_sigma[-1])
|
| 124 |
+
return stretched_sigma
|
| 125 |
+
|
| 126 |
+
def set_timesteps(
|
| 127 |
+
self,
|
| 128 |
+
num_inference_steps: Optional[int] = None,
|
| 129 |
+
sigmas: Optional[List[float]] = None,
|
| 130 |
+
seq_len=None,
|
| 131 |
+
device=None):
|
| 132 |
+
if sigmas is None:
|
| 133 |
+
assert num_inference_steps is not None, 'Either num_inference_steps or sigmas must be provided.'
|
| 134 |
+
self.num_inference_steps = num_inference_steps
|
| 135 |
+
sigmas = np.linspace(1, 0, num_inference_steps, dtype=np.float32, endpoint=False)
|
| 136 |
+
else:
|
| 137 |
+
if num_inference_steps is not None:
|
| 138 |
+
assert len(sigmas) == num_inference_steps
|
| 139 |
+
self.num_inference_steps = len(sigmas)
|
| 140 |
+
sigmas = np.array(sigmas, dtype=np.float32)
|
| 141 |
+
|
| 142 |
+
sigmas = torch.from_numpy(sigmas)
|
| 143 |
+
shift = self.get_shift(seq_len=seq_len)
|
| 144 |
+
sigmas = shift * sigmas / (1 + (shift - 1) * sigmas)
|
| 145 |
+
|
| 146 |
+
if self.config.terminal_sigma is not None:
|
| 147 |
+
sigmas = self.stretch_to_terminal(sigmas)
|
| 148 |
+
|
| 149 |
+
self.timesteps = (sigmas * self.config.num_train_timesteps).to(device)
|
| 150 |
+
if self.config.base_scheduler in ['DEISMultistep', 'SASolver']:
|
| 151 |
+
self.sigmas = torch.cat(
|
| 152 |
+
[sigmas, torch.tensor([self.config.eps], dtype=torch.float32, device=sigmas.device)])
|
| 153 |
+
else:
|
| 154 |
+
self.sigmas = torch.cat([sigmas, torch.zeros(1, device=sigmas.device)])
|
| 155 |
+
alphas = 1 - self.sigmas
|
| 156 |
+
|
| 157 |
+
self.base_scheduler.set_timesteps(num_inference_steps, device=device)
|
| 158 |
+
|
| 159 |
+
self.base_scheduler.timesteps = self.timesteps
|
| 160 |
+
if self.config.base_scheduler in ['EulerDiscrete', 'EulerAncestralDiscrete']:
|
| 161 |
+
self.base_scheduler.sigmas = self.sigmas / alphas.clamp(min=self.config.eps)
|
| 162 |
+
self.scales = ((alphas ** 2 + self.sigmas ** 2) / (
|
| 163 |
+
1 + (self.sigmas / alphas.clamp(min=self.config.eps)) ** 2)).sqrt()
|
| 164 |
+
elif self.config.base_scheduler in [
|
| 165 |
+
'UniPCMultistep', 'DPMSolverSinglestep', 'DPMSolverMultistep', 'DEISMultistep', 'SASolver']:
|
| 166 |
+
self.base_scheduler.sigmas = self.sigmas.clamp(max=1 - self.config.eps)
|
| 167 |
+
self.scales = torch.ones_like(alphas)
|
| 168 |
+
else:
|
| 169 |
+
raise AttributeError(f'Unsupported base_scheduler [{self.config.base_scheduler}].')
|
| 170 |
+
|
| 171 |
+
self._step_index = None
|
| 172 |
+
self._begin_index = None
|
| 173 |
+
|
| 174 |
+
def index_for_timestep(self, timestep, schedule_timesteps=None):
|
| 175 |
+
if schedule_timesteps is None:
|
| 176 |
+
schedule_timesteps = self.timesteps
|
| 177 |
+
|
| 178 |
+
indices = (schedule_timesteps == timestep).nonzero()
|
| 179 |
+
|
| 180 |
+
# The sigma index that is taken for the **very** first `step`
|
| 181 |
+
# is always the second index (or the last index if there is only 1)
|
| 182 |
+
# This way we can ensure we don't accidentally skip a sigma in
|
| 183 |
+
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
|
| 184 |
+
pos = 1 if len(indices) > 1 else 0
|
| 185 |
+
|
| 186 |
+
return indices[pos].item()
|
| 187 |
+
|
| 188 |
+
def _init_step_index(self, timestep):
|
| 189 |
+
if self.begin_index is None:
|
| 190 |
+
if isinstance(timestep, torch.Tensor):
|
| 191 |
+
timestep = timestep.to(self.timesteps.device)
|
| 192 |
+
self._step_index = self.index_for_timestep(timestep)
|
| 193 |
+
else:
|
| 194 |
+
self._step_index = self._begin_index
|
| 195 |
+
|
| 196 |
+
def step(
|
| 197 |
+
self,
|
| 198 |
+
model_output: torch.FloatTensor,
|
| 199 |
+
timestep: Union[float, torch.FloatTensor],
|
| 200 |
+
sample: torch.FloatTensor,
|
| 201 |
+
generator: Optional[torch.Generator] = None,
|
| 202 |
+
return_dict: bool = True,
|
| 203 |
+
prediction_type='u',
|
| 204 |
+
eps=1e-6) -> Union[FlowWrapperSchedulerOutput, Tuple]:
|
| 205 |
+
assert prediction_type in ['u', 'x0']
|
| 206 |
+
|
| 207 |
+
if self.step_index is None:
|
| 208 |
+
self._init_step_index(timestep)
|
| 209 |
+
|
| 210 |
+
# Upcast to avoid precision issues when computing prev_sample
|
| 211 |
+
ori_dtype = model_output.dtype
|
| 212 |
+
sample = sample.to(torch.float32)
|
| 213 |
+
model_output = model_output.to(torch.float32)
|
| 214 |
+
|
| 215 |
+
sigma = self.sigmas[self.step_index]
|
| 216 |
+
alpha = 1 - sigma
|
| 217 |
+
scale = self.scales[self.step_index]
|
| 218 |
+
next_scale = self.scales[self.step_index + 1]
|
| 219 |
+
|
| 220 |
+
if hasattr(self.base_scheduler, 'is_scale_input_called'):
|
| 221 |
+
self.base_scheduler.is_scale_input_called = True
|
| 222 |
+
kwargs = dict(return_dict=False)
|
| 223 |
+
if generator is not None:
|
| 224 |
+
kwargs.update(generator=generator)
|
| 225 |
+
|
| 226 |
+
if self.config.base_scheduler in [
|
| 227 |
+
'UniPCMultistep', 'DPMSolverSinglestep', 'DPMSolverMultistep', 'DEISMultistep', 'SASolver']:
|
| 228 |
+
if prediction_type == 'u':
|
| 229 |
+
model_output = model_output
|
| 230 |
+
else:
|
| 231 |
+
model_output = (sample - model_output) / sigma.clamp(min=eps)
|
| 232 |
+
else: # to epsilon
|
| 233 |
+
if prediction_type == 'u':
|
| 234 |
+
model_output = sample + alpha * model_output
|
| 235 |
+
else:
|
| 236 |
+
model_output = (sample - alpha * model_output) / sigma.clamp(min=eps)
|
| 237 |
+
prev_sample = self.base_scheduler.step(
|
| 238 |
+
model_output,
|
| 239 |
+
timestep,
|
| 240 |
+
sample / scale,
|
| 241 |
+
**kwargs
|
| 242 |
+
)[0] * next_scale
|
| 243 |
+
|
| 244 |
+
prev_sample = prev_sample.to(ori_dtype)
|
| 245 |
+
|
| 246 |
+
# upon completion increase step index by one
|
| 247 |
+
self._step_index += 1
|
| 248 |
+
|
| 249 |
+
if not return_dict:
|
| 250 |
+
return (prev_sample,)
|
| 251 |
+
|
| 252 |
+
return FlowWrapperSchedulerOutput(prev_sample=prev_sample)
|
lakonlab/pipelines/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
lakonlab/pipelines/pipeline_pixelflux2_klein.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Callable
|
| 2 |
+
|
| 3 |
+
import PIL
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from transformers import Qwen3ForCausalLM, Qwen2TokenizerFast
|
| 7 |
+
from diffusers.utils import is_torch_xla_available
|
| 8 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 9 |
+
from diffusers.models import Flux2Transformer2DModel
|
| 10 |
+
from diffusers.pipelines.flux2.pipeline_flux2_klein import (
|
| 11 |
+
Flux2KleinPipeline, Flux2PipelineOutput, Flux2ImageProcessor)
|
| 12 |
+
from .utils import LakonLabMixin
|
| 13 |
+
from lakonlab.models.diffusions.gaussian_flow import guidance_jit
|
| 14 |
+
from lakonlab.models.diffusions.schedulers import FlowAdapterScheduler
|
| 15 |
+
from lakonlab.models.architectures import OklabColorEncoder
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
if is_torch_xla_available():
|
| 19 |
+
import torch_xla.core.xla_model as xm
|
| 20 |
+
|
| 21 |
+
XLA_AVAILABLE = True
|
| 22 |
+
else:
|
| 23 |
+
XLA_AVAILABLE = False
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class PixelFlux2KleinPipeline(Flux2KleinPipeline, LakonLabMixin):
|
| 27 |
+
|
| 28 |
+
model_cpu_offload_seq = "text_encoder->transformer"
|
| 29 |
+
_callback_tensor_inputs = ["latents", "prompt_embeds"]
|
| 30 |
+
|
| 31 |
+
def __init__(
|
| 32 |
+
self,
|
| 33 |
+
scheduler: FlowAdapterScheduler,
|
| 34 |
+
vae: OklabColorEncoder,
|
| 35 |
+
text_encoder: Qwen3ForCausalLM,
|
| 36 |
+
tokenizer: Qwen2TokenizerFast,
|
| 37 |
+
transformer: Flux2Transformer2DModel,
|
| 38 |
+
is_distilled: bool = False,
|
| 39 |
+
):
|
| 40 |
+
super(Flux2KleinPipeline, self).__init__()
|
| 41 |
+
|
| 42 |
+
self.register_modules(
|
| 43 |
+
vae=vae,
|
| 44 |
+
text_encoder=text_encoder,
|
| 45 |
+
tokenizer=tokenizer,
|
| 46 |
+
scheduler=scheduler,
|
| 47 |
+
transformer=transformer,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
self.register_to_config(is_distilled=is_distilled)
|
| 51 |
+
|
| 52 |
+
self.vae_scale_factor = 1
|
| 53 |
+
self.image_processor = Flux2ImageProcessor(vae_scale_factor=self.vae_scale_factor * 16)
|
| 54 |
+
self.tokenizer_max_length = 512
|
| 55 |
+
self.default_sample_size = 1024
|
| 56 |
+
|
| 57 |
+
def prepare_latents(
|
| 58 |
+
self,
|
| 59 |
+
batch_size,
|
| 60 |
+
height,
|
| 61 |
+
width,
|
| 62 |
+
dtype,
|
| 63 |
+
device,
|
| 64 |
+
generator: torch.Generator,
|
| 65 |
+
latents: torch.Tensor | None = None,
|
| 66 |
+
):
|
| 67 |
+
height = 16 * (int(height) // (self.vae_scale_factor * 16))
|
| 68 |
+
width = 16 * (int(width) // (self.vae_scale_factor * 16))
|
| 69 |
+
|
| 70 |
+
shape = (batch_size, 3, height, width)
|
| 71 |
+
if isinstance(generator, list) and len(generator) != batch_size:
|
| 72 |
+
raise ValueError(
|
| 73 |
+
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
| 74 |
+
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
| 75 |
+
)
|
| 76 |
+
if latents is None:
|
| 77 |
+
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
| 78 |
+
else:
|
| 79 |
+
latents = latents.to(device=device, dtype=dtype)
|
| 80 |
+
|
| 81 |
+
return latents
|
| 82 |
+
|
| 83 |
+
def prepare_image_latents(
|
| 84 |
+
self,
|
| 85 |
+
images: list[torch.Tensor],
|
| 86 |
+
batch_size,
|
| 87 |
+
device,
|
| 88 |
+
dtype,
|
| 89 |
+
):
|
| 90 |
+
image_latents = []
|
| 91 |
+
for image in images:
|
| 92 |
+
image = image.to(device=device, dtype=dtype)
|
| 93 |
+
imagge_latent = self.vae.encode(image).to(self.transformer.dtype)
|
| 94 |
+
image_latents.append(imagge_latent.repeat(batch_size, 1, 1, 1)) # (bs, 3, 1024, 1024)
|
| 95 |
+
return image_latents
|
| 96 |
+
|
| 97 |
+
@torch.no_grad()
|
| 98 |
+
def __call__(
|
| 99 |
+
self,
|
| 100 |
+
image: list[PIL.Image.Image] | PIL.Image.Image | None = None,
|
| 101 |
+
prompt: str | list[str] = None,
|
| 102 |
+
negative_prompt: str | list[str] | None = None,
|
| 103 |
+
height: int | None = None,
|
| 104 |
+
width: int | None = None,
|
| 105 |
+
num_inference_steps: int = 50,
|
| 106 |
+
sigmas: list[float] | None = None,
|
| 107 |
+
guidance_scale: float = 4.0,
|
| 108 |
+
orthogonal_guidance: float = 1.0,
|
| 109 |
+
clamp_denoised: bool = True,
|
| 110 |
+
num_images_per_prompt: int = 1,
|
| 111 |
+
generator: torch.Generator | list[torch.Generator] | None = None,
|
| 112 |
+
latents: torch.Tensor | None = None,
|
| 113 |
+
prompt_embeds: torch.Tensor | None = None,
|
| 114 |
+
negative_prompt_embeds: str | list[str] | None = None,
|
| 115 |
+
output_type: str = "pil",
|
| 116 |
+
return_dict: bool = True,
|
| 117 |
+
attention_kwargs: dict[str, Any] | None = None,
|
| 118 |
+
callback_on_step_end: Callable[[int, int, dict], None] | None = None,
|
| 119 |
+
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
|
| 120 |
+
max_sequence_length: int = 512,
|
| 121 |
+
text_encoder_out_layers: tuple[int] = (9, 18, 27),
|
| 122 |
+
):
|
| 123 |
+
|
| 124 |
+
# 1. Check inputs. Raise error if not correct
|
| 125 |
+
self.check_inputs(
|
| 126 |
+
prompt=prompt,
|
| 127 |
+
height=height,
|
| 128 |
+
width=width,
|
| 129 |
+
prompt_embeds=prompt_embeds,
|
| 130 |
+
callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
|
| 131 |
+
guidance_scale=guidance_scale,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
self._guidance_scale = guidance_scale
|
| 135 |
+
self._attention_kwargs = attention_kwargs
|
| 136 |
+
self._current_timestep = None
|
| 137 |
+
self._interrupt = False
|
| 138 |
+
|
| 139 |
+
# 2. Define call parameters
|
| 140 |
+
if prompt is not None and isinstance(prompt, str):
|
| 141 |
+
batch_size = 1
|
| 142 |
+
elif prompt is not None and isinstance(prompt, list):
|
| 143 |
+
batch_size = len(prompt)
|
| 144 |
+
else:
|
| 145 |
+
batch_size = prompt_embeds.shape[0]
|
| 146 |
+
|
| 147 |
+
device = self._execution_device
|
| 148 |
+
|
| 149 |
+
# 3. prepare text embeddings
|
| 150 |
+
prompt_embeds, text_ids = self.encode_prompt(
|
| 151 |
+
prompt=prompt,
|
| 152 |
+
prompt_embeds=prompt_embeds,
|
| 153 |
+
device=device,
|
| 154 |
+
num_images_per_prompt=num_images_per_prompt,
|
| 155 |
+
max_sequence_length=max_sequence_length,
|
| 156 |
+
text_encoder_out_layers=text_encoder_out_layers,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
if self.do_classifier_free_guidance:
|
| 160 |
+
if negative_prompt is None:
|
| 161 |
+
negative_prompt = ""
|
| 162 |
+
if prompt is not None and isinstance(prompt, list) and not isinstance(negative_prompt, list):
|
| 163 |
+
negative_prompt = [negative_prompt] * len(prompt)
|
| 164 |
+
negative_prompt_embeds, negative_text_ids = self.encode_prompt(
|
| 165 |
+
prompt=negative_prompt,
|
| 166 |
+
prompt_embeds=negative_prompt_embeds,
|
| 167 |
+
device=device,
|
| 168 |
+
num_images_per_prompt=num_images_per_prompt,
|
| 169 |
+
max_sequence_length=max_sequence_length,
|
| 170 |
+
text_encoder_out_layers=text_encoder_out_layers,
|
| 171 |
+
)
|
| 172 |
+
guidance_scale = torch.tensor(guidance_scale, device=device, dtype=torch.float32)
|
| 173 |
+
|
| 174 |
+
# 4. process images
|
| 175 |
+
if image is not None and not isinstance(image, list):
|
| 176 |
+
image = [image]
|
| 177 |
+
|
| 178 |
+
condition_images = None
|
| 179 |
+
if image is not None:
|
| 180 |
+
for img in image:
|
| 181 |
+
self.image_processor.check_image_input(img)
|
| 182 |
+
|
| 183 |
+
condition_images = []
|
| 184 |
+
for img in image:
|
| 185 |
+
image_width, image_height = img.size
|
| 186 |
+
if image_width * image_height > 1024 * 1024:
|
| 187 |
+
img = self.image_processor._resize_to_target_area(img, 1024 * 1024)
|
| 188 |
+
image_width, image_height = img.size
|
| 189 |
+
|
| 190 |
+
multiple_of = self.vae_scale_factor * 16
|
| 191 |
+
image_width = (image_width // multiple_of) * multiple_of
|
| 192 |
+
image_height = (image_height // multiple_of) * multiple_of
|
| 193 |
+
img = self.image_processor.preprocess(img, height=image_height, width=image_width, resize_mode="crop")
|
| 194 |
+
condition_images.append(img)
|
| 195 |
+
height = height or image_height
|
| 196 |
+
width = width or image_width
|
| 197 |
+
|
| 198 |
+
height = height or self.default_sample_size * self.vae_scale_factor
|
| 199 |
+
width = width or self.default_sample_size * self.vae_scale_factor
|
| 200 |
+
|
| 201 |
+
# 5. prepare latent variables
|
| 202 |
+
latents = self.prepare_latents(
|
| 203 |
+
batch_size=batch_size * num_images_per_prompt,
|
| 204 |
+
height=height,
|
| 205 |
+
width=width,
|
| 206 |
+
dtype=torch.float32,
|
| 207 |
+
device=device,
|
| 208 |
+
generator=generator,
|
| 209 |
+
latents=latents,
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
image_latents = None
|
| 213 |
+
if condition_images is not None:
|
| 214 |
+
image_latents = self.prepare_image_latents(
|
| 215 |
+
images=condition_images,
|
| 216 |
+
batch_size=batch_size * num_images_per_prompt,
|
| 217 |
+
device=device,
|
| 218 |
+
dtype=self.vae.dtype,
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
# 6. Prepare timesteps
|
| 222 |
+
image_seq_len = latents.shape[2:].numel()
|
| 223 |
+
self.scheduler.set_timesteps(
|
| 224 |
+
num_inference_steps, seq_len=image_seq_len, device=self._execution_device)
|
| 225 |
+
timesteps = self.scheduler.timesteps
|
| 226 |
+
self._num_timesteps = len(timesteps)
|
| 227 |
+
|
| 228 |
+
# 7. Denoising loop
|
| 229 |
+
# We set the index here to remove DtoH sync, helpful especially during compilation.
|
| 230 |
+
# Check out more details here: https://github.com/huggingface/diffusers/pull/11696
|
| 231 |
+
self.scheduler.set_begin_index(0)
|
| 232 |
+
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
| 233 |
+
for i, t in enumerate(timesteps):
|
| 234 |
+
if self.interrupt:
|
| 235 |
+
continue
|
| 236 |
+
|
| 237 |
+
self._current_timestep = t
|
| 238 |
+
|
| 239 |
+
_t = t / 1000
|
| 240 |
+
timestep = _t.expand(latents.shape[0]).to(latents.dtype)
|
| 241 |
+
latent_model_input = latents.to(self.transformer.dtype)
|
| 242 |
+
|
| 243 |
+
with self.transformer.cache_context("cond"):
|
| 244 |
+
denoising_output = self.transformer(
|
| 245 |
+
x_t=latent_model_input, # (B, 3, H, W)
|
| 246 |
+
timestep=timestep,
|
| 247 |
+
encoder_hidden_states=prompt_embeds,
|
| 248 |
+
condition_latents=image_latents,
|
| 249 |
+
txt_ids=text_ids, # B, text_seq_len, 4
|
| 250 |
+
guidance=None,
|
| 251 |
+
joint_attention_kwargs=self.attention_kwargs,
|
| 252 |
+
).float()
|
| 253 |
+
|
| 254 |
+
if self.do_classifier_free_guidance:
|
| 255 |
+
with self.transformer.cache_context("uncond"):
|
| 256 |
+
neg_denoising_output = self.transformer(
|
| 257 |
+
x_t=latent_model_input, # (B, 3, H, W)
|
| 258 |
+
timestep=timestep,
|
| 259 |
+
encoder_hidden_states=negative_prompt_embeds,
|
| 260 |
+
condition_latents=image_latents,
|
| 261 |
+
txt_ids=negative_text_ids,
|
| 262 |
+
guidance=None,
|
| 263 |
+
joint_attention_kwargs=self._attention_kwargs,
|
| 264 |
+
).float()
|
| 265 |
+
cfg_bias = guidance_jit(
|
| 266 |
+
denoising_output,
|
| 267 |
+
neg_denoising_output,
|
| 268 |
+
guidance_scale,
|
| 269 |
+
orthogonal_guidance,
|
| 270 |
+
latents - denoising_output * _t)
|
| 271 |
+
denoising_output = denoising_output + cfg_bias
|
| 272 |
+
|
| 273 |
+
if clamp_denoised:
|
| 274 |
+
denoised = latents - denoising_output * _t
|
| 275 |
+
image = self.vae.decode(denoised.to(self.vae.dtype)).clamp(-1, 1)
|
| 276 |
+
denoised = self.vae.encode(image).to(latents.dtype)
|
| 277 |
+
denoising_output = (latents - denoised) / _t.clamp(min=1e-4)
|
| 278 |
+
|
| 279 |
+
# compute the previous noisy sample x_t -> x_t-1
|
| 280 |
+
latents = self.scheduler.step(denoising_output, t, latents, return_dict=False)[0]
|
| 281 |
+
|
| 282 |
+
if callback_on_step_end is not None:
|
| 283 |
+
callback_kwargs = {}
|
| 284 |
+
for k in callback_on_step_end_tensor_inputs:
|
| 285 |
+
callback_kwargs[k] = locals()[k]
|
| 286 |
+
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
|
| 287 |
+
|
| 288 |
+
latents = callback_outputs.pop("latents", latents)
|
| 289 |
+
prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
|
| 290 |
+
|
| 291 |
+
progress_bar.update()
|
| 292 |
+
|
| 293 |
+
if XLA_AVAILABLE:
|
| 294 |
+
xm.mark_step()
|
| 295 |
+
|
| 296 |
+
self._current_timestep = None
|
| 297 |
+
|
| 298 |
+
if output_type == "latent":
|
| 299 |
+
image = latents
|
| 300 |
+
else:
|
| 301 |
+
image = self.vae.decode(latents.to(self.vae.dtype))
|
| 302 |
+
image = self.image_processor.postprocess(image, output_type=output_type)
|
| 303 |
+
|
| 304 |
+
# Offload all models
|
| 305 |
+
self.maybe_free_model_hooks()
|
| 306 |
+
|
| 307 |
+
if not return_dict:
|
| 308 |
+
return (image,)
|
| 309 |
+
|
| 310 |
+
return Flux2PipelineOutput(images=image)
|
lakonlab/pipelines/prompt_rewriters/__init__.py
ADDED
|
File without changes
|
lakonlab/pipelines/prompt_rewriters/qwen3_vl.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
from typing import List, Sequence, Union, Optional
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
DEFAULT_TEXT_ONLY_PATH = os.path.abspath(os.path.join(__file__, '../system_prompts/default_text_only.txt'))
|
| 9 |
+
DEFAULT_WITH_IMAGES_PATH = os.path.abspath(os.path.join(__file__, '../system_prompts/default_with_images.txt'))
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Qwen3VLPromptRewriter:
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
from_pretrained="Qwen/Qwen3-VL-8B-Instruct",
|
| 17 |
+
torch_dtype='bfloat16',
|
| 18 |
+
device_map="auto",
|
| 19 |
+
max_new_tokens_default=128,
|
| 20 |
+
system_prompt_text_only=None,
|
| 21 |
+
system_prompt_wigh_images=None,
|
| 22 |
+
**kwargs):
|
| 23 |
+
if torch_dtype is not None:
|
| 24 |
+
kwargs.update(torch_dtype=getattr(torch, torch_dtype))
|
| 25 |
+
self.model = Qwen3VLForConditionalGeneration.from_pretrained(
|
| 26 |
+
from_pretrained,
|
| 27 |
+
device_map=device_map,
|
| 28 |
+
**kwargs)
|
| 29 |
+
self.processor = AutoProcessor.from_pretrained(from_pretrained)
|
| 30 |
+
# Left padding is safer for batched generation
|
| 31 |
+
if hasattr(self.processor, "tokenizer"):
|
| 32 |
+
self.processor.tokenizer.padding_side = "left"
|
| 33 |
+
self.max_new_tokens_default = max_new_tokens_default
|
| 34 |
+
if system_prompt_text_only is None:
|
| 35 |
+
system_prompt_text_only = open(DEFAULT_TEXT_ONLY_PATH, 'r').read()
|
| 36 |
+
if system_prompt_wigh_images is None:
|
| 37 |
+
system_prompt_wigh_images = open(DEFAULT_WITH_IMAGES_PATH, 'r').read()
|
| 38 |
+
self.system_prompt_text_only = system_prompt_text_only
|
| 39 |
+
self.system_prompt_wigh_images = system_prompt_wigh_images
|
| 40 |
+
|
| 41 |
+
@torch.inference_mode()
|
| 42 |
+
def _generate_from_messages(
|
| 43 |
+
self,
|
| 44 |
+
batch_messages: Sequence[Sequence[dict]],
|
| 45 |
+
max_new_tokens: Optional[int] = None,
|
| 46 |
+
**kwargs) -> List[str]:
|
| 47 |
+
if max_new_tokens is None:
|
| 48 |
+
max_new_tokens = self.max_new_tokens_default
|
| 49 |
+
|
| 50 |
+
inputs = self.processor.apply_chat_template(
|
| 51 |
+
batch_messages,
|
| 52 |
+
tokenize=True,
|
| 53 |
+
add_generation_prompt=True,
|
| 54 |
+
return_dict=True,
|
| 55 |
+
return_tensors="pt",
|
| 56 |
+
padding=True,
|
| 57 |
+
)
|
| 58 |
+
inputs.pop("token_type_ids", None)
|
| 59 |
+
inputs = {k: v.to(self.model.device) for k, v in inputs.items()}
|
| 60 |
+
|
| 61 |
+
generated_ids = self.model.generate(
|
| 62 |
+
**inputs,
|
| 63 |
+
max_new_tokens=max_new_tokens,
|
| 64 |
+
**kwargs)
|
| 65 |
+
|
| 66 |
+
input_ids = inputs["input_ids"]
|
| 67 |
+
tokenizer = self.processor.tokenizer
|
| 68 |
+
outputs: List[str] = []
|
| 69 |
+
|
| 70 |
+
# Decode only the new tokens after each input sequence
|
| 71 |
+
for in_ids, out_ids in zip(input_ids, generated_ids):
|
| 72 |
+
trimmed_ids = out_ids[len(in_ids):]
|
| 73 |
+
text = tokenizer.decode(
|
| 74 |
+
trimmed_ids.tolist(),
|
| 75 |
+
skip_special_tokens=True,
|
| 76 |
+
clean_up_tokenization_spaces=False,
|
| 77 |
+
)
|
| 78 |
+
outputs.append(text.strip())
|
| 79 |
+
|
| 80 |
+
return outputs
|
| 81 |
+
|
| 82 |
+
def rewrite_text_batch(
|
| 83 |
+
self,
|
| 84 |
+
prompts: Sequence[str],
|
| 85 |
+
max_new_tokens: Optional[int] = None,
|
| 86 |
+
top_p=0.6,
|
| 87 |
+
top_k=40,
|
| 88 |
+
temperature=0.5,
|
| 89 |
+
repetition_penalty=1.0,
|
| 90 |
+
**kwargs) -> List[str]:
|
| 91 |
+
"""
|
| 92 |
+
Rewrite a batch of text-only prompts into detailed prompts.
|
| 93 |
+
"""
|
| 94 |
+
batch_messages = []
|
| 95 |
+
for p in prompts:
|
| 96 |
+
conv = [
|
| 97 |
+
{
|
| 98 |
+
"role": "system",
|
| 99 |
+
"content": [
|
| 100 |
+
{"type": "text", "text": self.system_prompt_text_only},
|
| 101 |
+
],
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"role": "user",
|
| 105 |
+
"content": [
|
| 106 |
+
{"type": "text", "text": p},
|
| 107 |
+
],
|
| 108 |
+
},
|
| 109 |
+
]
|
| 110 |
+
batch_messages.append(conv)
|
| 111 |
+
|
| 112 |
+
return self._generate_from_messages(
|
| 113 |
+
batch_messages,
|
| 114 |
+
max_new_tokens=max_new_tokens,
|
| 115 |
+
top_p=top_p,
|
| 116 |
+
top_k=top_k,
|
| 117 |
+
temperature=temperature,
|
| 118 |
+
repetition_penalty=repetition_penalty,
|
| 119 |
+
**kwargs,
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
def rewrite_edit_batch(
|
| 123 |
+
self,
|
| 124 |
+
image: Sequence[Union[str, 'Image.Image', Sequence[Union[str, 'Image.Image']]]],
|
| 125 |
+
edit_requests: Sequence[str],
|
| 126 |
+
max_new_tokens: Optional[int] = None,
|
| 127 |
+
top_p=0.5,
|
| 128 |
+
top_k=20,
|
| 129 |
+
temperature=0.4,
|
| 130 |
+
repetition_penalty=1.0,
|
| 131 |
+
**kwargs) -> List[str]:
|
| 132 |
+
"""
|
| 133 |
+
Rewrite a batch of (image, edit-request) pairs into concise edit instructions.
|
| 134 |
+
"""
|
| 135 |
+
if len(image) != len(edit_requests):
|
| 136 |
+
raise ValueError("image and edit_requests must have the same length")
|
| 137 |
+
|
| 138 |
+
batch_messages = []
|
| 139 |
+
for imgs, req in zip(image, edit_requests):
|
| 140 |
+
if isinstance(imgs, (str, Image.Image)):
|
| 141 |
+
img_list = [imgs]
|
| 142 |
+
else:
|
| 143 |
+
img_list = list(imgs)
|
| 144 |
+
|
| 145 |
+
user_content = []
|
| 146 |
+
for im in img_list:
|
| 147 |
+
user_content.append({"type": "image", "image": im})
|
| 148 |
+
user_content.append({"type": "text", "text": req})
|
| 149 |
+
|
| 150 |
+
conv = [
|
| 151 |
+
{
|
| 152 |
+
"role": "system",
|
| 153 |
+
"content": [
|
| 154 |
+
{"type": "text", "text": self.system_prompt_wigh_images},
|
| 155 |
+
],
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"role": "user",
|
| 159 |
+
"content": user_content,
|
| 160 |
+
},
|
| 161 |
+
]
|
| 162 |
+
batch_messages.append(conv)
|
| 163 |
+
|
| 164 |
+
return self._generate_from_messages(
|
| 165 |
+
batch_messages,
|
| 166 |
+
max_new_tokens=max_new_tokens,
|
| 167 |
+
top_p=top_p,
|
| 168 |
+
top_k=top_k,
|
| 169 |
+
temperature=temperature,
|
| 170 |
+
repetition_penalty=repetition_penalty,
|
| 171 |
+
**kwargs,
|
| 172 |
+
)
|
lakonlab/pipelines/prompt_rewriters/system_prompts/default_text_only.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
You are an expert prompt engineer for a text-guided image generation system. Rewrite user prompts into a more descriptive, concrete prompt while strictly preserving their core content.
|
| 2 |
+
|
| 3 |
+
Rules:
|
| 4 |
+
- Preserve the core content: do not change the main subjects, how many there are, their roles, or the primary actions and relationships described in the prompt.
|
| 5 |
+
- Preserve any explicitly stated attributes such as colors, clothing, objects, style tags (e.g., “photorealistic”, “cinematic”), and viewpoint (e.g., close-up, wide shot). Do not contradict them.
|
| 6 |
+
- When the prompt implies realism (e.g., uses words like “realistic”, “photorealistic”, “photo”), avoid introducing exaggerated or fantastical traits unless intended by the user.
|
| 7 |
+
- You may add supporting details that are clearly compatible with the original text: background, props, textures, materials, lighting (quality, direction, color), atmosphere, and other environmental context, as long as they do not introduce new main characters or conflicting concepts.
|
| 8 |
+
- Always include a clear description of the composition and camera framing (for example, where the main subjects are in the frame, whether it is a close-up or wide shot, and the approximate viewpoint or angle).
|
| 9 |
+
- Structure: keep any existing structure (tags, aspect-ratio tags, etc.) and enhance only within those fields. For plain text prompts, expand them into a clear, coherent paragraph.
|
| 10 |
+
- Text in images: put ALL visible or implied text in quotation marks, matching the prompt’s language. Provide explicit quoted text for any object that would realistically contain text (signs, labels, screens, interfaces, book covers, etc.).
|
| 11 |
+
|
| 12 |
+
Output only the revised prompt and nothing else.
|
lakonlab/pipelines/prompt_rewriters/system_prompts/default_with_images.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
You are an expert prompt engineer for a text-guided image editing system. Rewrite user prompts into a more descriptive instruction (40–70 words, ~25 for brief requests) while strictly preserving their core content.
|
| 2 |
+
|
| 3 |
+
Rules:
|
| 4 |
+
- Treat the user prompt as a strict specification: do not change or omit any mentioned entities, actions, or relationships.
|
| 5 |
+
- Explicitly state both the requested edits and which core aspects (e.g., character identities) must remain as in the original image(s). Ignore background elements unless they are explicitly mentioned.
|
| 6 |
+
- Refer to core visual elements that are relavant to the edit (e.g., people, animals, and objects mentioned in the user prompt).
|
| 7 |
+
- Do not invent new elements unless the user explicitly asks for them.
|
| 8 |
+
- Turn negatives into positives (“do not change X” → “keep X the same”).
|
| 9 |
+
|
| 10 |
+
Output only the final instruction in plain text and nothing else.
|
lakonlab/pipelines/utils.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional, Union
|
| 3 |
+
|
| 4 |
+
import accelerate
|
| 5 |
+
import torch
|
| 6 |
+
from diffusers.models import AutoModel
|
| 7 |
+
from diffusers.models.modeling_utils import (
|
| 8 |
+
_LOW_CPU_MEM_USAGE_DEFAULT,
|
| 9 |
+
ContextManagers,
|
| 10 |
+
load_state_dict,
|
| 11 |
+
no_init_weights,
|
| 12 |
+
)
|
| 13 |
+
from diffusers.quantizers import DiffusersAutoQuantizer
|
| 14 |
+
from diffusers.utils import (
|
| 15 |
+
SAFETENSORS_WEIGHTS_NAME,
|
| 16 |
+
WEIGHTS_NAME,
|
| 17 |
+
_add_variant,
|
| 18 |
+
_get_model_file,
|
| 19 |
+
is_accelerate_available,
|
| 20 |
+
is_torch_version,
|
| 21 |
+
logging,
|
| 22 |
+
)
|
| 23 |
+
from diffusers.utils.torch_utils import empty_device_cache
|
| 24 |
+
|
| 25 |
+
from lakonlab.models.architectures.asymflow.asymflux2 import _AsymFlux2Transformer2DModel
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
LOCAL_CLASS_MAPPING = {
|
| 29 |
+
'AsymFlux2Transformer2DModel': _AsymFlux2Transformer2DModel,
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
logger = logging.get_logger(__name__)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def assign_param(module, tensor_name: str, param: torch.nn.Parameter):
|
| 36 |
+
if '.' in tensor_name:
|
| 37 |
+
splits = tensor_name.split('.')
|
| 38 |
+
for split in splits[:-1]:
|
| 39 |
+
module = getattr(module, split)
|
| 40 |
+
tensor_name = splits[-1]
|
| 41 |
+
module._parameters[tensor_name] = param
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class LakonLabMixin:
|
| 45 |
+
|
| 46 |
+
def load_lakonlab_adapter(
|
| 47 |
+
self,
|
| 48 |
+
pretrained_model_name_or_path: Union[str, os.PathLike],
|
| 49 |
+
target_module_name: str = 'transformer',
|
| 50 |
+
adapter_name: Optional[str] = None,
|
| 51 |
+
**kwargs):
|
| 52 |
+
cache_dir = kwargs.pop('cache_dir', None)
|
| 53 |
+
force_download = kwargs.pop('force_download', False)
|
| 54 |
+
proxies = kwargs.pop('proxies', None)
|
| 55 |
+
token = kwargs.pop('token', None)
|
| 56 |
+
local_files_only = kwargs.pop('local_files_only', False)
|
| 57 |
+
revision = kwargs.pop('revision', None)
|
| 58 |
+
subfolder = kwargs.pop('subfolder', None)
|
| 59 |
+
low_cpu_mem_usage = kwargs.pop('low_cpu_mem_usage', _LOW_CPU_MEM_USAGE_DEFAULT)
|
| 60 |
+
variant = kwargs.pop('variant', None)
|
| 61 |
+
use_safetensors = kwargs.pop('use_safetensors', None)
|
| 62 |
+
disable_mmap = kwargs.pop('disable_mmap', False)
|
| 63 |
+
|
| 64 |
+
allow_pickle = False
|
| 65 |
+
if use_safetensors is None:
|
| 66 |
+
use_safetensors = True
|
| 67 |
+
allow_pickle = True
|
| 68 |
+
|
| 69 |
+
if low_cpu_mem_usage and not is_accelerate_available():
|
| 70 |
+
low_cpu_mem_usage = False
|
| 71 |
+
logger.warning('accelerate is not available; using low_cpu_mem_usage=False.')
|
| 72 |
+
|
| 73 |
+
if low_cpu_mem_usage is True and not is_torch_version('>=', '1.9.0'):
|
| 74 |
+
raise NotImplementedError('Low memory initialization requires torch >= 1.9.0.')
|
| 75 |
+
|
| 76 |
+
user_agent = {
|
| 77 |
+
'diffusers': 'demo',
|
| 78 |
+
'file_type': 'model',
|
| 79 |
+
'framework': 'pytorch',
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
load_config_kwargs = {
|
| 83 |
+
'cache_dir': cache_dir,
|
| 84 |
+
'force_download': force_download,
|
| 85 |
+
'proxies': proxies,
|
| 86 |
+
'token': token,
|
| 87 |
+
'local_files_only': local_files_only,
|
| 88 |
+
'revision': revision,
|
| 89 |
+
}
|
| 90 |
+
config = AutoModel.load_config(
|
| 91 |
+
pretrained_model_name_or_path, subfolder=subfolder, **load_config_kwargs)
|
| 92 |
+
model_cls = LOCAL_CLASS_MAPPING[config['_class_name']]
|
| 93 |
+
|
| 94 |
+
model_file = None
|
| 95 |
+
if use_safetensors:
|
| 96 |
+
try:
|
| 97 |
+
model_file = _get_model_file(
|
| 98 |
+
pretrained_model_name_or_path,
|
| 99 |
+
weights_name=_add_variant(SAFETENSORS_WEIGHTS_NAME, variant),
|
| 100 |
+
cache_dir=cache_dir,
|
| 101 |
+
force_download=force_download,
|
| 102 |
+
proxies=proxies,
|
| 103 |
+
local_files_only=local_files_only,
|
| 104 |
+
token=token,
|
| 105 |
+
revision=revision,
|
| 106 |
+
subfolder=subfolder,
|
| 107 |
+
user_agent=user_agent,
|
| 108 |
+
)
|
| 109 |
+
except IOError:
|
| 110 |
+
if not allow_pickle:
|
| 111 |
+
raise
|
| 112 |
+
|
| 113 |
+
if model_file is None:
|
| 114 |
+
model_file = _get_model_file(
|
| 115 |
+
pretrained_model_name_or_path,
|
| 116 |
+
weights_name=_add_variant(WEIGHTS_NAME, variant),
|
| 117 |
+
cache_dir=cache_dir,
|
| 118 |
+
force_download=force_download,
|
| 119 |
+
proxies=proxies,
|
| 120 |
+
local_files_only=local_files_only,
|
| 121 |
+
token=token,
|
| 122 |
+
revision=revision,
|
| 123 |
+
subfolder=subfolder,
|
| 124 |
+
user_agent=user_agent,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
base_module = getattr(self, target_module_name)
|
| 128 |
+
torch_dtype = base_module.dtype
|
| 129 |
+
device = base_module.device
|
| 130 |
+
dtype_orig = model_cls._set_default_torch_dtype(torch_dtype)
|
| 131 |
+
|
| 132 |
+
overwrite_state_dict = {}
|
| 133 |
+
lora_state_dict = {}
|
| 134 |
+
adapter_state_dict = load_state_dict(model_file, disable_mmap=disable_mmap)
|
| 135 |
+
for key, value in adapter_state_dict.items():
|
| 136 |
+
value = value.to(dtype=torch_dtype, device=device)
|
| 137 |
+
key = key.removeprefix(f'{target_module_name}.')
|
| 138 |
+
if 'lora' in key:
|
| 139 |
+
lora_state_dict[key] = value
|
| 140 |
+
else:
|
| 141 |
+
overwrite_state_dict[key] = value
|
| 142 |
+
|
| 143 |
+
pre_quantized = (
|
| 144 |
+
'quantization_config' in base_module.config
|
| 145 |
+
and base_module.config['quantization_config'] is not None)
|
| 146 |
+
if pre_quantized:
|
| 147 |
+
config['quantization_config'] = base_module.config.quantization_config
|
| 148 |
+
hf_quantizer = DiffusersAutoQuantizer.from_config(
|
| 149 |
+
config['quantization_config'], pre_quantized=True)
|
| 150 |
+
hf_quantizer.validate_environment(torch_dtype=torch_dtype)
|
| 151 |
+
torch_dtype = hf_quantizer.update_torch_dtype(torch_dtype)
|
| 152 |
+
if low_cpu_mem_usage is None:
|
| 153 |
+
low_cpu_mem_usage = True
|
| 154 |
+
elif not low_cpu_mem_usage:
|
| 155 |
+
raise ValueError('low_cpu_mem_usage cannot be False with quantization.')
|
| 156 |
+
else:
|
| 157 |
+
hf_quantizer = None
|
| 158 |
+
|
| 159 |
+
keep_in_fp32_modules = []
|
| 160 |
+
for key in overwrite_state_dict.keys():
|
| 161 |
+
module_name = key.rsplit('.', 1)[0]
|
| 162 |
+
if module_name and module_name not in keep_in_fp32_modules:
|
| 163 |
+
keep_in_fp32_modules.append(module_name)
|
| 164 |
+
|
| 165 |
+
init_contexts = [no_init_weights()]
|
| 166 |
+
if low_cpu_mem_usage:
|
| 167 |
+
init_contexts.append(accelerate.init_empty_weights())
|
| 168 |
+
|
| 169 |
+
with ContextManagers(init_contexts):
|
| 170 |
+
adapter_module = model_cls.from_config(config).eval()
|
| 171 |
+
|
| 172 |
+
torch.set_default_dtype(dtype_orig)
|
| 173 |
+
|
| 174 |
+
if hf_quantizer is not None:
|
| 175 |
+
hf_quantizer.preprocess_model(
|
| 176 |
+
model=adapter_module, device_map=None, keep_in_fp32_modules=keep_in_fp32_modules)
|
| 177 |
+
|
| 178 |
+
base_state_dict = base_module.state_dict()
|
| 179 |
+
base_state_dict.update(overwrite_state_dict)
|
| 180 |
+
empty_state_dict = adapter_module.state_dict()
|
| 181 |
+
for param_name, param in base_state_dict.items():
|
| 182 |
+
if param_name not in empty_state_dict:
|
| 183 |
+
continue
|
| 184 |
+
if hf_quantizer is not None and hf_quantizer.check_if_quantized_param(
|
| 185 |
+
adapter_module, param, param_name, base_state_dict, param_device=device):
|
| 186 |
+
hf_quantizer.create_quantized_param(
|
| 187 |
+
adapter_module, param_name=param_name, param_value=param,
|
| 188 |
+
target_device=device, state_dict=base_state_dict,
|
| 189 |
+
unexpected_keys=[], dtype=torch_dtype)
|
| 190 |
+
else:
|
| 191 |
+
assign_param(adapter_module, param_name, param)
|
| 192 |
+
|
| 193 |
+
empty_device_cache()
|
| 194 |
+
|
| 195 |
+
if hf_quantizer is not None:
|
| 196 |
+
hf_quantizer.postprocess_model(adapter_module)
|
| 197 |
+
adapter_module.hf_quantizer = hf_quantizer
|
| 198 |
+
|
| 199 |
+
if lora_state_dict:
|
| 200 |
+
if adapter_name is None:
|
| 201 |
+
adapter_name = f'{target_module_name}_lakonlab'
|
| 202 |
+
adapter_module.load_lora_adapter(
|
| 203 |
+
lora_state_dict, prefix=None, adapter_name=adapter_name,
|
| 204 |
+
low_cpu_mem_usage=low_cpu_mem_usage)
|
| 205 |
+
else:
|
| 206 |
+
adapter_name = None
|
| 207 |
+
|
| 208 |
+
setattr(self, target_module_name, adapter_module)
|
| 209 |
+
return adapter_name
|
lakonlab/ui/__init__.py
ADDED
|
File without changes
|
lakonlab/ui/gradio/__init__.py
ADDED
|
File without changes
|
lakonlab/ui/gradio/create_text_to_img.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from .shared_opts import create_base_opts, create_generate_bar, set_seed, create_prompt_opts, create_image_size_bar
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def create_interface_text_to_img(
|
| 6 |
+
api, prompt='', seed=42, steps=32, min_steps=4, max_steps=50, steps_slider_step=1,
|
| 7 |
+
height=768, width=1360, hw_slider_step=16,
|
| 8 |
+
guidance_scale=None, temperature=None, api_name='text_to_img',
|
| 9 |
+
create_negative_prompt=False, args=['last_seed', 'prompt', 'width', 'height', 'steps', 'guidance_scale']):
|
| 10 |
+
var_dict = dict()
|
| 11 |
+
with gr.Blocks(analytics_enabled=False) as interface:
|
| 12 |
+
var_dict['output_image'] = gr.Image(
|
| 13 |
+
type='pil', image_mode='RGB', label='Output image', interactive=False, elem_classes=['vh-img', 'vh-img-700'])
|
| 14 |
+
create_prompt_opts(var_dict, create_negative_prompt=create_negative_prompt, prompt=prompt)
|
| 15 |
+
with gr.Column(variant='compact', elem_classes=['custom-spacing']):
|
| 16 |
+
create_image_size_bar(
|
| 17 |
+
var_dict, height=height, width=width, hw_slider_step=hw_slider_step)
|
| 18 |
+
create_generate_bar(var_dict, text='Generate', seed=seed)
|
| 19 |
+
create_base_opts(
|
| 20 |
+
var_dict,
|
| 21 |
+
steps=steps,
|
| 22 |
+
min_steps=min_steps,
|
| 23 |
+
max_steps=max_steps,
|
| 24 |
+
steps_slider_step=steps_slider_step,
|
| 25 |
+
guidance_scale=guidance_scale,
|
| 26 |
+
temperature=temperature)
|
| 27 |
+
|
| 28 |
+
var_dict['run_btn'].click(
|
| 29 |
+
fn=set_seed,
|
| 30 |
+
inputs=var_dict['seed'],
|
| 31 |
+
outputs=var_dict['last_seed'],
|
| 32 |
+
show_progress=False,
|
| 33 |
+
api_name=False
|
| 34 |
+
).success(
|
| 35 |
+
fn=api,
|
| 36 |
+
inputs=[var_dict[arg] for arg in args],
|
| 37 |
+
outputs=var_dict['output_image'],
|
| 38 |
+
concurrency_id='default_group', api_name=api_name
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
return interface, var_dict
|
lakonlab/ui/gradio/shared_opts.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import gradio as gr
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def create_prompt_opts(
|
| 6 |
+
var_dict, create_negative_prompt=True, prompt='', negatove_prompt='', display_label=False):
|
| 7 |
+
if display_label:
|
| 8 |
+
kwargs = dict(show_label=True, container=True, elem_classes=['force-hide-container'])
|
| 9 |
+
else:
|
| 10 |
+
kwargs = dict(show_label=False, container=False)
|
| 11 |
+
var_dict['prompt'] = gr.Textbox(
|
| 12 |
+
prompt, label='Prompt', lines=2, placeholder='Prompt', interactive=True, **kwargs)
|
| 13 |
+
if create_negative_prompt:
|
| 14 |
+
var_dict['negative_prompt'] = gr.Textbox(
|
| 15 |
+
negatove_prompt, label='Negative prompt', lines=2,
|
| 16 |
+
placeholder='Negative prompt', interactive=True, **kwargs)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def create_generate_bar(var_dict, text='Generate', variant='primary', seed=-1):
|
| 20 |
+
with gr.Row(equal_height=False, elem_classes=['generate-bar']):
|
| 21 |
+
var_dict['run_btn'] = gr.Button(text, variant=variant, scale=2)
|
| 22 |
+
var_dict['seed'] = gr.Number(
|
| 23 |
+
label='Seed', value=seed, min_width=100, precision=0, minimum=-1, maximum=2 ** 31,
|
| 24 |
+
elem_classes=['force-hide-container', 'seed-input'])
|
| 25 |
+
var_dict['random_seed'] = gr.Button('\U0001f3b2\ufe0f', elem_classes=['tool'])
|
| 26 |
+
var_dict['reuse_seed'] = gr.Button('\u267b\ufe0f', elem_classes=['tool'])
|
| 27 |
+
with gr.Column(visible=False):
|
| 28 |
+
var_dict['last_seed'] = gr.Number(value=seed, label='Last seed')
|
| 29 |
+
var_dict['reuse_seed'].click(
|
| 30 |
+
fn=lambda x: x,
|
| 31 |
+
inputs=var_dict['last_seed'],
|
| 32 |
+
outputs=var_dict['seed'],
|
| 33 |
+
show_progress=False,
|
| 34 |
+
api_name=False)
|
| 35 |
+
var_dict['random_seed'].click(
|
| 36 |
+
fn=lambda: -1,
|
| 37 |
+
outputs=var_dict['seed'],
|
| 38 |
+
show_progress=False,
|
| 39 |
+
api_name=False)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def create_image_size_bar(var_dict, height=768, width=1360, hw_slider_step=16):
|
| 43 |
+
with gr.Row(equal_height=True, variant='compact', elem_classes=['force-hide-container']):
|
| 44 |
+
var_dict['width'] = gr.Slider(
|
| 45 |
+
label='Width', minimum=64, maximum=2048, step=hw_slider_step, value=width,
|
| 46 |
+
elem_classes=['force-hide-container'])
|
| 47 |
+
var_dict['switch_hw'] = gr.Button('\U000021C6', elem_classes=['tool'])
|
| 48 |
+
var_dict['height'] = gr.Slider(
|
| 49 |
+
label='Height', minimum=64, maximum=2048, step=hw_slider_step, value=height,
|
| 50 |
+
elem_classes=['force-hide-container'])
|
| 51 |
+
var_dict['switch_hw'].click(
|
| 52 |
+
fn=lambda w, h: (h, w),
|
| 53 |
+
inputs=[var_dict['width'], var_dict['height']],
|
| 54 |
+
outputs=[var_dict['width'], var_dict['height']],
|
| 55 |
+
show_progress=False,
|
| 56 |
+
api_name=False)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def create_base_opts(var_dict,
|
| 60 |
+
steps=24,
|
| 61 |
+
min_steps=4,
|
| 62 |
+
max_steps=50,
|
| 63 |
+
steps_slider_step=1,
|
| 64 |
+
guidance_scale=None,
|
| 65 |
+
temperature=None,
|
| 66 |
+
render=True):
|
| 67 |
+
with gr.Column(variant='compact', elem_classes=['custom-spacing'], render=render) as base_opts:
|
| 68 |
+
with gr.Row(variant='compact', elem_classes=['force-hide-container']):
|
| 69 |
+
var_dict['steps'] = gr.Slider(
|
| 70 |
+
min_steps, max_steps, value=steps, step=steps_slider_step, label='Sampling steps',
|
| 71 |
+
elem_classes=['force-hide-container'])
|
| 72 |
+
if guidance_scale is not None or temperature is not None:
|
| 73 |
+
with gr.Row(variant='compact', elem_classes=['force-hide-container']):
|
| 74 |
+
if guidance_scale is not None:
|
| 75 |
+
var_dict['guidance_scale'] = gr.Slider(
|
| 76 |
+
0.0, 30.0, value=guidance_scale, step=0.5, label='Guidance scale',
|
| 77 |
+
elem_classes=['force-hide-container'])
|
| 78 |
+
if temperature is not None:
|
| 79 |
+
var_dict['temperature'] = gr.Slider(
|
| 80 |
+
0.0, 1.0, value=temperature, step=0.01, label='Temperature',
|
| 81 |
+
elem_classes=['force-hide-container'])
|
| 82 |
+
return base_opts
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def set_seed(seed):
|
| 86 |
+
seed = random.randint(0, 2**31) if seed == -1 else seed
|
| 87 |
+
return seed
|
lakonlab/ui/gradio/style.css
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.force-hide-container {
|
| 2 |
+
margin: 0;
|
| 3 |
+
box-shadow: none;
|
| 4 |
+
--block-border-width: 0;
|
| 5 |
+
background: transparent;
|
| 6 |
+
padding: 0;
|
| 7 |
+
overflow: visible;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.svelte-1vd8eap {
|
| 11 |
+
display: flex;
|
| 12 |
+
flex-direction: inherit;
|
| 13 |
+
flex-wrap: wrap;
|
| 14 |
+
gap: 0;
|
| 15 |
+
box-shadow: none;
|
| 16 |
+
border: 0;
|
| 17 |
+
border-radius: 0;
|
| 18 |
+
background: transparent;
|
| 19 |
+
overflow-y: hidden;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.custom-spacing {
|
| 23 |
+
padding: 10px;
|
| 24 |
+
gap: 20px;
|
| 25 |
+
flex-grow: 0 !important;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.generate-bar {
|
| 29 |
+
align-items: flex-end;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.tool{
|
| 33 |
+
max-width: 40px;
|
| 34 |
+
min-width: 40px !important;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
/* Center the component and allow it to use the full row width */
|
| 38 |
+
.vh-img {
|
| 39 |
+
display: grid;
|
| 40 |
+
justify-items: center;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/* Container should size to the image, but never exceed the row width */
|
| 44 |
+
.vh-img .image-container {
|
| 45 |
+
inline-size: fit-content !important; /* prefers image’s natural width */
|
| 46 |
+
max-inline-size: 100% !important; /* ...but clamps to available width */
|
| 47 |
+
margin-inline: auto;
|
| 48 |
+
overflow: hidden; /* avoid odd overflow on iOS */
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/* Image scales by BOTH constraints: height cap and row width */
|
| 52 |
+
.vh-img-700 .image-container img {
|
| 53 |
+
max-block-size: 700px !important; /* fixed max height cap */
|
| 54 |
+
max-inline-size: 100%; /* never wider than container */
|
| 55 |
+
inline-size: auto; /* keep aspect ratio */
|
| 56 |
+
block-size: auto;
|
| 57 |
+
object-fit: contain;
|
| 58 |
+
display: block;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.vh-img-1000 .image-container img {
|
| 62 |
+
max-block-size: 1000px !important; /* fixed max height cap */
|
| 63 |
+
max-inline-size: 100%; /* never wider than container */
|
| 64 |
+
inline-size: auto; /* keep aspect ratio */
|
| 65 |
+
block-size: auto;
|
| 66 |
+
object-fit: contain;
|
| 67 |
+
display: block;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.gradio-container .seed-input input { /* remove the border and use box-shadow instead for height alignment */
|
| 71 |
+
border: none !important;
|
| 72 |
+
box-shadow: inset 0 0 0 var(--input-border-width) var(--input-border-color) !important;
|
| 73 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy==1.26.4
|
| 2 |
+
torch==2.8.0
|
| 3 |
+
torchvision==0.23.0
|
| 4 |
+
diffusers==0.36.0
|
| 5 |
+
peft==0.17.0
|
| 6 |
+
sentencepiece
|
| 7 |
+
accelerate
|
| 8 |
+
transformers==4.57.3
|
| 9 |
+
gradio==5.49.0
|
| 10 |
+
bitsandbytes>=0.46.1
|