multimodalart HF Staff commited on
Commit
5f653cc
·
verified ·
1 Parent(s): 6936157

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ examples/content_0000.jpg filter=lfs diff=lfs merge=lfs -text
37
+ examples/content_0005.jpg filter=lfs diff=lfs merge=lfs -text
38
+ examples/style_1011.jpg filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,13 +1,59 @@
1
  ---
2
- title: Style Controller
3
- emoji: 🐢
4
- colorFrom: indigo
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 6.23.1
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: StyleController
3
+ emoji: 🎨
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 6.15.1
 
8
  app_file: app.py
9
+ short_description: Continuous image stylization with strength control
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
  ---
13
 
14
+ # StyleController
15
+
16
+ **Staying True to the Origin: Continuous Image Stylization with Smooth Transitions**
17
+
18
+ This Space demonstrates [StyleController](https://github.com/ReyChiaro/StyleController), a controllable
19
+ image stylization framework built on Qwen-Image-Edit-2509. Given a content image and a style reference,
20
+ it generates high-fidelity stylized images with explicit control over style strength via a slider.
21
+
22
+ ## How it works
23
+
24
+ 1. Provide a **content image** (the image you want stylized).
25
+ 2. Provide a **style reference image** (the visual style to transfer).
26
+ 3. Adjust the **style strength slider** (0 = no style, 1 = full stylization).
27
+ 4. Click **Stylize** to generate the output.
28
+
29
+ The model combines a full-strength LoRA adapter with lightweight projector anchors learned at discrete
30
+ style strengths. B-spline interpolation between these anchors produces smooth, continuous transitions
31
+ while preserving content structure.
32
+
33
+ ## Technical details
34
+
35
+ - **Base model:** [Qwen/Qwen-Image-Edit-2509](https://huggingface.co/Qwen/Qwen-Image-Edit-2509)
36
+ - **Adapter:** [ReyChiaro/StyleController](https://huggingface.co/ReyChiaro/StyleController)
37
+ - **LoRA rank:** 128, layers 0–59
38
+ - **Projector type:** low-rank linear
39
+ - **Interpolation:** B-spline (order 3, clamped knot vector)
40
+ - **Inference:** 16 steps, bf16, seed 42
41
+
42
+ ## Citation
43
+
44
+ ```bibtex
45
+ @misc{xu2026stayingtrueorigincontinuous,
46
+ title={Staying True to the Origin: Continuous Image Stylization with Smooth Transitions},
47
+ author={Rui Xu and Hanmo Zhang and Songhua Liu},
48
+ year={2026},
49
+ eprint={2608.08125},
50
+ archivePrefix={arXiv},
51
+ primaryClass={cs.CV},
52
+ url={https://arxiv.org/abs/2608.08125},
53
+ }
54
+ ```
55
+
56
+ ## License
57
+
58
+ StyleController source code is MIT-licensed. Adapter weights are Apache-2.0.
59
+ Example images are from the SmoothStyle dataset (CC-BY-4.0).
app.py ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """StyleController: Continuous Image Stylization with Smooth Transitions.
2
+
3
+ A Gradio demo for controllable image stylization using StyleController,
4
+ which combines a LoRA adapter with strength-aware projectors on the
5
+ Qwen-Image-Edit-2509 backbone.
6
+ """
7
+
8
+ import os
9
+
10
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
11
+
12
+ import spaces # MUST come before torch / diffusers / transformers
13
+ import copy
14
+ import bisect
15
+ import logging
16
+ import math
17
+
18
+ import numpy as np
19
+ import torch
20
+ from PIL import Image
21
+ from safetensors.torch import load_file
22
+ from scipy.interpolate import BSpline, make_interp_spline
23
+
24
+ import gradio as gr
25
+ from diffusers.models.autoencoders.autoencoder_kl_qwenimage import AutoencoderKLQwenImage
26
+ from diffusers.image_processor import VaeImageProcessor
27
+ from diffusers.schedulers.scheduling_flow_match_euler_discrete import FlowMatchEulerDiscreteScheduler
28
+ from transformers.models.qwen2_5_vl import Qwen2_5_VLForConditionalGeneration, Qwen2_5_VLProcessor
29
+ from transformers.models.qwen2 import Qwen2Tokenizer
30
+
31
+ from models.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
32
+ from models.styctrl import register_styctrl, add_styctrl, activate
33
+ from models.transformer_qwenimage import QwenImageTransformer2DModel
34
+
35
+ logger = logging.getLogger(__name__)
36
+ logging.basicConfig(level=logging.INFO)
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Configuration
40
+ # ---------------------------------------------------------------------------
41
+
42
+ BASE_MODEL = "Qwen/Qwen-Image-Edit-2509"
43
+ STYLECONTROLLER_REPO = "ReyChiaro/StyleController"
44
+ VARIANT = "qwenimage_edit_2509"
45
+
46
+ LORA_RANK = 128
47
+ LORA_LAYER_START = 0
48
+ LORA_LAYER_END = 59 # inclusive
49
+ TARGET_MODULES = [
50
+ "attn.to_q",
51
+ "attn.to_k",
52
+ "attn.to_v",
53
+ "attn.to_out.0",
54
+ "attn.add_q_proj",
55
+ "attn.add_k_proj",
56
+ "attn.add_v_proj",
57
+ "attn.to_add_out",
58
+ "img_mlp.net.2",
59
+ "txt_mlp.net.2",
60
+ ]
61
+ ANCHOR_STRENGTHS = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
62
+ PROJECTOR_TYPE = "low_rank_linear"
63
+ BSPLINE_ORDER = 3
64
+ BSPLINE_MODE = "control"
65
+ DEFAULT_PROMPT = (
66
+ "Apply the visual style of the second reference image to the first content image "
67
+ "while preserving the content structure and layout."
68
+ )
69
+ DEFAULT_STEPS = 16
70
+ DEFAULT_SEED = 42
71
+ DEFAULT_HEIGHT = 1024
72
+ DEFAULT_WIDTH = 1024
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Download helper
76
+ # ---------------------------------------------------------------------------
77
+
78
+ from huggingface_hub import hf_hub_download
79
+
80
+
81
+ def _download_weights():
82
+ """Download LoRA + projector checkpoints from the HF repo."""
83
+ weights_dir = os.path.join(os.path.dirname(__file__), "weights")
84
+ os.makedirs(weights_dir, exist_ok=True)
85
+
86
+ lora_path = hf_hub_download(
87
+ STYLECONTROLLER_REPO,
88
+ f"{VARIANT}/styctrl_{VARIANT}.safetensors",
89
+ repo_type="model",
90
+ )
91
+ projector_paths = []
92
+ for i in range(1, 10):
93
+ p = hf_hub_download(
94
+ STYLECONTROLLER_REPO,
95
+ f"{VARIANT}/projectors/s{i:02d}.safetensors",
96
+ repo_type="model",
97
+ )
98
+ projector_paths.append(p)
99
+ return lora_path, projector_paths
100
+
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Projector interpolation (ported from infer_styctrl.py)
104
+ # ---------------------------------------------------------------------------
105
+
106
+
107
+ def _projector_only(state, path):
108
+ ps = {k: v for k, v in state.items() if ".projector." in k}
109
+ if not ps:
110
+ raise ValueError(f"No projector parameters found in {path}.")
111
+ return ps
112
+
113
+
114
+ def _load_projector_checkpoints(paths):
115
+ states = []
116
+ expected_keys = None
117
+ expected_shapes = {}
118
+ for raw_path in paths:
119
+ state = _projector_only(load_file(raw_path, device="cpu"), raw_path)
120
+ keys = set(state)
121
+ if expected_keys is None:
122
+ expected_keys = keys
123
+ expected_shapes = {k: v.shape for k, v in state.items()}
124
+ elif keys != expected_keys:
125
+ missing = sorted(expected_keys - keys)
126
+ extra = sorted(keys - expected_keys)
127
+ raise ValueError(
128
+ f"Projector keys do not match for {raw_path}; missing={missing}, extra={extra}"
129
+ )
130
+ else:
131
+ mismatched = [k for k, v in state.items() if v.shape != expected_shapes[k]]
132
+ if mismatched:
133
+ raise ValueError(f"Projector tensor shapes do not match for {raw_path}: {mismatched}")
134
+ states.append(state)
135
+ return states
136
+
137
+
138
+ def _zero_state_like(state):
139
+ return {k: torch.zeros_like(v) for k, v in state.items()}
140
+
141
+
142
+ def _to_device_dtype(state, device, dtype):
143
+ return {k: v.to(device=device, dtype=dtype) for k, v in state.items()}
144
+
145
+
146
+ def _open_clamped_knot_vector(strengths, degree):
147
+ parameters = np.asarray(strengths, dtype=np.float64)
148
+ interior = [
149
+ parameters[i : i + degree].mean()
150
+ for i in range(1, len(parameters) - degree)
151
+ ]
152
+ return np.concatenate(
153
+ (
154
+ np.repeat(parameters[0], degree + 1),
155
+ np.asarray(interior, dtype=np.float64),
156
+ np.repeat(parameters[-1], degree + 1),
157
+ )
158
+ )
159
+
160
+
161
+ def interpolate_projector_states(
162
+ paths,
163
+ anchor_strengths,
164
+ query_strength,
165
+ method="bspline",
166
+ order=3,
167
+ spline_mode="control",
168
+ device="cuda",
169
+ dtype=torch.bfloat16,
170
+ endpoint_state=None,
171
+ ):
172
+ """Interpolate projector tensors at an explicit strength coordinate."""
173
+ states = _load_projector_checkpoints(paths)
174
+ if len(states) == 1:
175
+ if np.isclose(query_strength, 0.0):
176
+ return _to_device_dtype(_zero_state_like(states[0]), device, dtype)
177
+ return _to_device_dtype(states[0], device, dtype)
178
+ if len(anchor_strengths) != len(states):
179
+ raise ValueError("anchor_strengths must contain exactly one value per projector checkpoint.")
180
+
181
+ pairs = sorted(zip(anchor_strengths, states), key=lambda item: item[0])
182
+ strengths = [float(item[0]) for item in pairs]
183
+ states = [item[1] for item in pairs]
184
+ if len(set(strengths)) != len(strengths):
185
+ raise ValueError("Anchor strengths must be unique.")
186
+ if strengths[0] < 0 or strengths[-1] > 1:
187
+ raise ValueError("Anchor strengths must lie within [0, 1].")
188
+
189
+ # A zero projector makes the StyCtrl LoRA contribution zero.
190
+ if strengths[0] > 0:
191
+ strengths.insert(0, 0.0)
192
+ states.insert(0, _zero_state_like(states[0]))
193
+
194
+ if endpoint_state is not None and strengths[-1] < 1.0:
195
+ if endpoint_state.keys() != states[0].keys():
196
+ raise ValueError("Endpoint projector keys do not match anchor checkpoints.")
197
+ mismatched = [
198
+ k for k, v in endpoint_state.items() if v.shape != states[0][k].shape
199
+ ]
200
+ if mismatched:
201
+ raise ValueError(f"Endpoint projector tensor shapes do not match anchors: {mismatched}")
202
+ strengths.append(1.0)
203
+ states.append(endpoint_state)
204
+
205
+ if query_strength < strengths[0] or query_strength > strengths[-1]:
206
+ raise ValueError(
207
+ f"Requested strength {query_strength} is outside the available projector range "
208
+ f"[{strengths[0]}, {strengths[-1]}]."
209
+ )
210
+
211
+ if method != "bspline" or spline_mode == "interpolating":
212
+ for anchor_strength, state in zip(strengths, states):
213
+ if np.isclose(query_strength, anchor_strength):
214
+ return _to_device_dtype(state, device, dtype)
215
+
216
+ if method == "linear":
217
+ upper = bisect.bisect_right(strengths, query_strength)
218
+ lower = upper - 1
219
+ x0, x1 = strengths[lower], strengths[upper]
220
+ alpha = (query_strength - x0) / (x1 - x0)
221
+ result = {
222
+ k: (1.0 - alpha) * states[lower][k].float() + alpha * states[upper][k].float()
223
+ for k in states[0]
224
+ }
225
+ return _to_device_dtype(result, device, dtype)
226
+
227
+ if method != "bspline":
228
+ raise ValueError(f"Unsupported interpolation method: {method}")
229
+ if order < 1 or order >= len(strengths):
230
+ raise ValueError(
231
+ f"B-spline order must satisfy 1 <= order < number of anchors ({len(strengths)})."
232
+ )
233
+
234
+ x = np.asarray(strengths, dtype=np.float64)
235
+ result = {}
236
+ for key in states[0]:
237
+ y = torch.stack([s[key].float() for s in states], dim=0).numpy()
238
+ if spline_mode == "interpolating":
239
+ value = make_interp_spline(x, y, k=order)(np.asarray(query_strength))
240
+ elif spline_mode == "control":
241
+ knots = _open_clamped_knot_vector(x, order)
242
+ value = BSpline(knots, y, k=order, axis=0)(np.asarray(query_strength))
243
+ else:
244
+ raise ValueError(f"Unsupported spline mode: {spline_mode}")
245
+ result[key] = torch.from_numpy(np.asarray(value))
246
+ return _to_device_dtype(result, device, dtype)
247
+
248
+
249
+ # ---------------------------------------------------------------------------
250
+ # Model loading (module scope)
251
+ # ---------------------------------------------------------------------------
252
+
253
+ logger.info("Downloading StyleController weights ...")
254
+ _lora_path, _projector_paths = _download_weights()
255
+ logger.info("Weights downloaded.")
256
+
257
+ logger.info("Loading base pipeline components from %s ...", BASE_MODEL)
258
+ _dtype = torch.bfloat16
259
+
260
+ _scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
261
+ BASE_MODEL, subfolder="scheduler", torch_dtype=_dtype
262
+ )
263
+ _scheduler_val = copy.deepcopy(_scheduler)
264
+
265
+ _vae = AutoencoderKLQwenImage.from_pretrained(
266
+ BASE_MODEL, subfolder="vae", torch_dtype=_dtype
267
+ ).to("cuda").requires_grad_(False)
268
+
269
+ _transformer = QwenImageTransformer2DModel.from_pretrained(
270
+ BASE_MODEL, subfolder="transformer", torch_dtype=_dtype
271
+ ).to("cuda").requires_grad_(False)
272
+
273
+ _tokenizer = Qwen2Tokenizer.from_pretrained(BASE_MODEL, subfolder="tokenizer")
274
+ _text_encoder = Qwen2_5_VLForConditionalGeneration.from_pretrained(
275
+ BASE_MODEL, subfolder="text_encoder", torch_dtype=_dtype
276
+ ).to("cuda").requires_grad_(False)
277
+ _processor = Qwen2_5_VLProcessor.from_pretrained(BASE_MODEL, subfolder="processor")
278
+
279
+ logger.info("Base pipeline components loaded.")
280
+
281
+ # Register StyCtrl LoRA
282
+ register_styctrl(
283
+ model=_transformer,
284
+ target_modules=TARGET_MODULES,
285
+ device=torch.device("cuda"),
286
+ dtype=_dtype,
287
+ )
288
+
289
+ _lora_layer_indices = list(range(LORA_LAYER_START, LORA_LAYER_END + 1))
290
+
291
+ add_styctrl(
292
+ model=_transformer,
293
+ rank=LORA_RANK,
294
+ lora_layer_indices=_lora_layer_indices,
295
+ adapter_name="styctrl",
296
+ proj_type=PROJECTOR_TYPE,
297
+ bias=False,
298
+ checkpoint_path=_lora_path,
299
+ device=torch.device("cuda"),
300
+ dtype=_dtype,
301
+ )
302
+ activate(_transformer, "styctrl")
303
+
304
+ # Pre-load projector states into CPU memory for fast interpolation
305
+ _projector_anchor_states = _load_projector_checkpoints(_projector_paths)
306
+
307
+ # Pre-compute the endpoint projector state (identity = from main LoRA)
308
+ _endpoint_projector_state = {
309
+ k: v.detach().cpu()
310
+ for k, v in _transformer.state_dict().items()
311
+ if ".projector." in k
312
+ }
313
+
314
+ logger.info("StyCtrl LoRA registered and activated.")
315
+
316
+ # Build pipeline
317
+ _pipe = QwenImageEditPlusPipeline(
318
+ transformer=_transformer,
319
+ vae=_vae,
320
+ scheduler=_scheduler_val,
321
+ text_encoder=_text_encoder,
322
+ tokenizer=_tokenizer,
323
+ processor=_processor,
324
+ )
325
+
326
+ logger.info("Pipeline ready.")
327
+
328
+
329
+ # ---------------------------------------------------------------------------
330
+ # Inference
331
+ # ---------------------------------------------------------------------------
332
+
333
+
334
+ @spaces.GPU(duration=120)
335
+ def stylize(
336
+ content_image: Image.Image,
337
+ style_image: Image.Image,
338
+ strength: float = 0.55,
339
+ seed: int = DEFAULT_SEED,
340
+ steps: int = DEFAULT_STEPS,
341
+ progress=gr.Progress(track_tqdm=True),
342
+ ) -> Image.Image:
343
+ """Stylize a content image using a style reference at a given strength.
344
+
345
+ Args:
346
+ content_image: The content image to stylize.
347
+ style_image: The style reference image.
348
+ strength: Style strength in [0, 1]. 0 = no style, 1 = full style.
349
+ seed: Random seed for reproducibility.
350
+ steps: Number of inference steps.
351
+ """
352
+ if content_image is None or style_image is None:
353
+ return None
354
+
355
+ content_image = content_image.convert("RGB").resize((DEFAULT_WIDTH, DEFAULT_HEIGHT))
356
+ style_image = style_image.convert("RGB").resize((DEFAULT_WIDTH, DEFAULT_HEIGHT))
357
+
358
+ # Interpolate projector states at the requested strength
359
+ interp_states = interpolate_projector_states(
360
+ paths=_projector_paths,
361
+ anchor_strengths=ANCHOR_STRENGTHS,
362
+ query_strength=strength,
363
+ method="bspline",
364
+ order=BSPLINE_ORDER,
365
+ spline_mode=BSPLINE_MODE,
366
+ device=torch.device("cuda"),
367
+ dtype=_dtype,
368
+ endpoint_state=_endpoint_projector_state,
369
+ )
370
+ _transformer.load_state_dict(interp_states, strict=False)
371
+ for _, c in _pipe.components.items():
372
+ if hasattr(c, "parameters"):
373
+ for p in c.parameters():
374
+ p.requires_grad_(False)
375
+
376
+ w = torch.tensor([strength], device="cuda", dtype=_dtype)
377
+ attention_kwargs = {
378
+ "lora_layer_indices": _lora_layer_indices,
379
+ "enable_lora": True,
380
+ "w": w,
381
+ }
382
+
383
+ generator = torch.Generator(device="cuda").manual_seed(seed)
384
+
385
+ output = _pipe(
386
+ image=[content_image, style_image],
387
+ prompt=DEFAULT_PROMPT,
388
+ attention_kwargs=attention_kwargs,
389
+ num_inference_steps=steps,
390
+ generator=generator,
391
+ )
392
+ return output.images[0]
393
+
394
+
395
+ # ---------------------------------------------------------------------------
396
+ # Gradio UI
397
+ # ---------------------------------------------------------------------------
398
+
399
+ CSS = """
400
+ #col-container { max-width: 1100px; margin: 0 auto; }
401
+ .dark .gradio-container { color: var(--body-text-color); }
402
+ """
403
+
404
+ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
405
+ gr.Markdown(
406
+ """
407
+ # 🎨 StyleController
408
+ **Continuous Image Stylization with Smooth Transitions**
409
+
410
+ Upload a content image and a style reference, then control the style
411
+ strength with a slider. Built on Qwen-Image-Edit with a LoRA adapter
412
+ and strength-aware projectors.
413
+
414
+ [Paper](https://arxiv.org/abs/2608.08125) ·
415
+ [GitHub](https://github.com/ReyChiaro/StyleController) ·
416
+ [Model](https://huggingface.co/ReyChiaro/StyleController)
417
+ """
418
+ )
419
+
420
+ with gr.Row():
421
+ with gr.Column():
422
+ content_input = gr.Image(
423
+ label="Content Image",
424
+ type="pil",
425
+ height=400,
426
+ )
427
+ style_input = gr.Image(
428
+ label="Style Reference Image",
429
+ type="pil",
430
+ height=400,
431
+ )
432
+ strength_slider = gr.Slider(
433
+ label="Style Strength",
434
+ minimum=0.0,
435
+ maximum=1.0,
436
+ step=0.05,
437
+ value=0.55,
438
+ info="0 = original content, 1 = full stylization",
439
+ )
440
+ run_btn = gr.Button("Stylize", variant="primary", scale=1)
441
+
442
+ with gr.Column():
443
+ output_image = gr.Image(
444
+ label="Stylized Output",
445
+ type="pil",
446
+ height=400,
447
+ )
448
+
449
+ with gr.Accordion("Advanced Settings", open=False):
450
+ seed_input = gr.Number(
451
+ label="Seed",
452
+ value=DEFAULT_SEED,
453
+ precision=0,
454
+ info="Random seed for reproducibility",
455
+ )
456
+ steps_input = gr.Slider(
457
+ label="Inference Steps",
458
+ minimum=4,
459
+ maximum=50,
460
+ step=1,
461
+ value=DEFAULT_STEPS,
462
+ info="More steps = higher quality but slower",
463
+ )
464
+
465
+ run_btn.click(
466
+ fn=stylize,
467
+ inputs=[content_input, style_input, strength_slider, seed_input, steps_input],
468
+ outputs=output_image,
469
+ api_name="stylize",
470
+ )
471
+
472
+ gr.Examples(
473
+ examples=[
474
+ ["examples/content_0000.jpg", "examples/style_1011.jpg", 0.3],
475
+ ["examples/content_0000.jpg", "examples/style_1011.jpg", 0.6],
476
+ ["examples/content_0000.jpg", "examples/style_1011.jpg", 0.9],
477
+ ["examples/content_0005.jpg", "examples/style_0005.jpg", 0.3],
478
+ ["examples/content_0005.jpg", "examples/style_0005.jpg", 0.7],
479
+ ],
480
+ inputs=[content_input, style_input, strength_slider],
481
+ outputs=output_image,
482
+ fn=stylize,
483
+ cache_examples=True,
484
+ cache_mode="lazy",
485
+ )
486
+
487
+ demo.launch(mcp_server=True)
examples/content_0000.jpg ADDED

Git LFS Details

  • SHA256: 7e9bd64dfb87c49c18edc30d890df054bf19a31b564ecbc6ba9ec0aafd719e09
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
examples/content_0005.jpg ADDED

Git LFS Details

  • SHA256: 7b4722690ed6202ca1293933f220580ac654ccf10884c1ab698dfb582b19a108
  • Pointer size: 131 Bytes
  • Size of remote file: 127 kB
examples/style_0005.jpg ADDED
examples/style_1011.jpg ADDED

Git LFS Details

  • SHA256: fe47b11b971e597797344375613d4e9bf9a56f6d7936835d3631e6f65f7e223a
  • Pointer size: 131 Bytes
  • Size of remote file: 357 kB
models/pipeline_qwenimage_edit_plus.py ADDED
@@ -0,0 +1,868 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import inspect
2
+ import math
3
+ from typing import Any, Callable, Dict, List, Optional, Union
4
+
5
+ import numpy as np
6
+ import torch
7
+ from transformers import Qwen2_5_VLForConditionalGeneration, Qwen2Tokenizer, Qwen2VLProcessor
8
+
9
+ from diffusers.image_processor import PipelineImageInput, VaeImageProcessor
10
+ from diffusers.loaders import QwenImageLoraLoaderMixin
11
+ from diffusers.models import AutoencoderKLQwenImage
12
+ from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
13
+ from diffusers.utils import is_torch_xla_available, logging, replace_example_docstring
14
+ from diffusers.utils.torch_utils import randn_tensor
15
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
16
+ from diffusers.pipelines.qwenimage.pipeline_output import QwenImagePipelineOutput
17
+
18
+ from .transformer_qwenimage import QwenImageTransformer2DModel
19
+
20
+
21
+ if is_torch_xla_available():
22
+ import torch_xla.core.xla_model as xm # type: ignore
23
+
24
+ XLA_AVAILABLE = True
25
+ else:
26
+ XLA_AVAILABLE = False
27
+
28
+
29
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
30
+
31
+ EXAMPLE_DOC_STRING = """
32
+ Examples:
33
+ ```py
34
+ >>> import torch
35
+ >>> from PIL import Image
36
+ >>> from diffusers import QwenImageEditPlusPipeline
37
+ >>> from diffusers.utils import load_image
38
+
39
+ >>> pipe = QwenImageEditPlusPipeline.from_pretrained("Qwen/Qwen-Image-Edit-2509", torch_dtype=torch.bfloat16)
40
+ >>> pipe.to("cuda")
41
+ >>> image = load_image(
42
+ ... "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/yarn-art-pikachu.png"
43
+ ... ).convert("RGB")
44
+ >>> prompt = (
45
+ ... "Make Pikachu hold a sign that says 'Qwen Edit is awesome', yarn art style, detailed, vibrant colors"
46
+ ... )
47
+ >>> # Depending on the variant being used, the pipeline call will slightly vary.
48
+ >>> # Refer to the pipeline documentation for more details.
49
+ >>> image = pipe(image, prompt, num_inference_steps=50).images[0]
50
+ >>> image.save("qwenimage_edit_plus.png")
51
+ ```
52
+ """
53
+
54
+ CONDITION_IMAGE_SIZE = 384 * 384
55
+ VAE_IMAGE_SIZE = 1024 * 1024
56
+
57
+
58
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.calculate_shift
59
+ def calculate_shift(
60
+ image_seq_len,
61
+ base_seq_len: int = 256,
62
+ max_seq_len: int = 4096,
63
+ base_shift: float = 0.5,
64
+ max_shift: float = 1.15,
65
+ ):
66
+ m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
67
+ b = base_shift - m * base_seq_len
68
+ mu = image_seq_len * m + b
69
+ return mu
70
+
71
+
72
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
73
+ def retrieve_timesteps(
74
+ scheduler,
75
+ num_inference_steps: Optional[int] = None,
76
+ device: Optional[Union[str, torch.device]] = None,
77
+ timesteps: Optional[List[int]] = None,
78
+ sigmas: Optional[List[float]] = None,
79
+ **kwargs,
80
+ ):
81
+ r"""
82
+ Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
83
+ custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
84
+
85
+ Args:
86
+ scheduler (`SchedulerMixin`):
87
+ The scheduler to get timesteps from.
88
+ num_inference_steps (`int`):
89
+ The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
90
+ must be `None`.
91
+ device (`str` or `torch.device`, *optional*):
92
+ The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
93
+ timesteps (`List[int]`, *optional*):
94
+ Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
95
+ `num_inference_steps` and `sigmas` must be `None`.
96
+ sigmas (`List[float]`, *optional*):
97
+ Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
98
+ `num_inference_steps` and `timesteps` must be `None`.
99
+
100
+ Returns:
101
+ `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
102
+ second element is the number of inference steps.
103
+ """
104
+ if timesteps is not None and sigmas is not None:
105
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
106
+ if timesteps is not None:
107
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
108
+ if not accepts_timesteps:
109
+ raise ValueError(
110
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
111
+ f" timestep schedules. Please check whether you are using the correct scheduler."
112
+ )
113
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
114
+ timesteps = scheduler.timesteps
115
+ num_inference_steps = len(timesteps)
116
+ elif sigmas is not None:
117
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
118
+ if not accept_sigmas:
119
+ raise ValueError(
120
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
121
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
122
+ )
123
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
124
+ timesteps = scheduler.timesteps
125
+ num_inference_steps = len(timesteps)
126
+ else:
127
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
128
+ timesteps = scheduler.timesteps
129
+ return timesteps, num_inference_steps
130
+
131
+
132
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
133
+ def retrieve_latents(encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"):
134
+ if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
135
+ return encoder_output.latent_dist.sample(generator)
136
+ elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
137
+ return encoder_output.latent_dist.mode()
138
+ elif hasattr(encoder_output, "latents"):
139
+ return encoder_output.latents
140
+ else:
141
+ raise AttributeError("Could not access latents of provided encoder_output")
142
+
143
+
144
+ def calculate_dimensions(target_area, ratio):
145
+ width = math.sqrt(target_area * ratio)
146
+ height = width / ratio
147
+
148
+ width = round(width / 32) * 32
149
+ height = round(height / 32) * 32
150
+
151
+ return width, height
152
+
153
+
154
+ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
155
+ r"""
156
+ The Qwen-Image-Edit pipeline for image editing.
157
+
158
+ Args:
159
+ transformer ([`QwenImageTransformer2DModel`]):
160
+ Conditional Transformer (MMDiT) architecture to denoise the encoded image latents.
161
+ scheduler ([`FlowMatchEulerDiscreteScheduler`]):
162
+ A scheduler to be used in combination with `transformer` to denoise the encoded image latents.
163
+ vae ([`AutoencoderKL`]):
164
+ Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
165
+ text_encoder ([`Qwen2.5-VL-7B-Instruct`]):
166
+ [Qwen2.5-VL-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct), specifically the
167
+ [Qwen2.5-VL-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct) variant.
168
+ tokenizer (`QwenTokenizer`):
169
+ Tokenizer of class
170
+ [CLIPTokenizer](https://huggingface.co/docs/transformers/en/model_doc/clip#transformers.CLIPTokenizer).
171
+ """
172
+
173
+ model_cpu_offload_seq = "text_encoder->transformer->vae"
174
+ _callback_tensor_inputs = ["latents", "prompt_embeds"]
175
+
176
+ def __init__(
177
+ self,
178
+ scheduler: FlowMatchEulerDiscreteScheduler,
179
+ vae: AutoencoderKLQwenImage,
180
+ text_encoder: Qwen2_5_VLForConditionalGeneration,
181
+ tokenizer: Qwen2Tokenizer,
182
+ processor: Qwen2VLProcessor,
183
+ transformer: QwenImageTransformer2DModel,
184
+ ):
185
+ super().__init__()
186
+
187
+ self.register_modules(
188
+ vae=vae,
189
+ text_encoder=text_encoder,
190
+ tokenizer=tokenizer,
191
+ processor=processor,
192
+ transformer=transformer,
193
+ scheduler=scheduler,
194
+ )
195
+ self.vae_scale_factor = 2 ** len(self.vae.temperal_downsample) if getattr(self, "vae", None) else 8
196
+ self.latent_channels = self.vae.config.z_dim if getattr(self, "vae", None) else 16
197
+ # QwenImage latents are turned into 2x2 patches and packed. This means the latent width and height has to be divisible
198
+ # by the patch size. So the vae scale factor is multiplied by the patch size to account for this
199
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor * 2)
200
+ self.tokenizer_max_length = 1024
201
+
202
+ self.prompt_template_encode = "<|im_start|>system\nDescribe the key features of the input image (color, shape, size, texture, objects, background), then explain how the user's text instruction should alter or modify the image. Generate a new image that meets the user's requirements while maintaining consistency with the original input where appropriate.<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n"
203
+ self.prompt_template_encode_start_idx = 64
204
+ self.default_sample_size = 128
205
+
206
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.QwenImagePipeline._extract_masked_hidden
207
+ def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
208
+ bool_mask = mask.bool()
209
+ valid_lengths = bool_mask.sum(dim=1)
210
+ selected = hidden_states[bool_mask]
211
+ split_result = torch.split(selected, valid_lengths.tolist(), dim=0)
212
+
213
+ return split_result
214
+
215
+ def _get_qwen_prompt_embeds(
216
+ self,
217
+ prompt: Union[str, List[str]] = None,
218
+ image: Optional[torch.Tensor] = None,
219
+ device: Optional[torch.device] = None,
220
+ dtype: Optional[torch.dtype] = None,
221
+ ):
222
+ device = device or self._execution_device
223
+ dtype = dtype or self.text_encoder.dtype
224
+
225
+ prompt = [prompt] if isinstance(prompt, str) else prompt
226
+ img_prompt_template = "Picture {}: <|vision_start|><|image_pad|><|vision_end|>"
227
+ if isinstance(image, list):
228
+ base_img_prompt = ""
229
+ for i, img in enumerate(image):
230
+ base_img_prompt += img_prompt_template.format(i + 1)
231
+ elif image is not None:
232
+ base_img_prompt = img_prompt_template.format(1)
233
+ else:
234
+ base_img_prompt = ""
235
+
236
+ template = self.prompt_template_encode
237
+
238
+ drop_idx = self.prompt_template_encode_start_idx
239
+ txt = [template.format(base_img_prompt + e) for e in prompt]
240
+
241
+ model_inputs = self.processor(
242
+ text=txt,
243
+ images=image,
244
+ padding=True,
245
+ return_tensors="pt",
246
+ ).to(device)
247
+
248
+ outputs = self.text_encoder(
249
+ input_ids=model_inputs.input_ids,
250
+ attention_mask=model_inputs.attention_mask,
251
+ pixel_values=model_inputs.pixel_values,
252
+ image_grid_thw=model_inputs.image_grid_thw,
253
+ output_hidden_states=True,
254
+ )
255
+
256
+ hidden_states = outputs.hidden_states[-1]
257
+ split_hidden_states = self._extract_masked_hidden(hidden_states, model_inputs.attention_mask)
258
+ split_hidden_states = [e[drop_idx:] for e in split_hidden_states]
259
+ attn_mask_list = [torch.ones(e.size(0), dtype=torch.long, device=e.device) for e in split_hidden_states]
260
+ max_seq_len = max([e.size(0) for e in split_hidden_states])
261
+ prompt_embeds = torch.stack(
262
+ [torch.cat([u, u.new_zeros(max_seq_len - u.size(0), u.size(1))]) for u in split_hidden_states]
263
+ )
264
+ encoder_attention_mask = torch.stack([torch.cat([u, u.new_zeros(max_seq_len - u.size(0))]) for u in attn_mask_list])
265
+
266
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
267
+
268
+ return prompt_embeds, encoder_attention_mask
269
+
270
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline.encode_prompt
271
+ def encode_prompt(
272
+ self,
273
+ prompt: Union[str, List[str]],
274
+ image: Optional[torch.Tensor] = None,
275
+ device: Optional[torch.device] = None,
276
+ num_images_per_prompt: int = 1,
277
+ prompt_embeds: Optional[torch.Tensor] = None,
278
+ prompt_embeds_mask: Optional[torch.Tensor] = None,
279
+ max_sequence_length: int = 1024,
280
+ ):
281
+ r"""
282
+
283
+ Args:
284
+ prompt (`str` or `List[str]`, *optional*):
285
+ prompt to be encoded
286
+ image (`torch.Tensor`, *optional*):
287
+ image to be encoded
288
+ device: (`torch.device`):
289
+ torch device
290
+ num_images_per_prompt (`int`):
291
+ number of images that should be generated per prompt
292
+ prompt_embeds (`torch.Tensor`, *optional*):
293
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
294
+ provided, text embeddings will be generated from `prompt` input argument.
295
+ """
296
+ device = device or self._execution_device
297
+
298
+ prompt = [prompt] if isinstance(prompt, str) else prompt
299
+ batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
300
+
301
+ if prompt_embeds is None:
302
+ prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt, image, device)
303
+
304
+ _, seq_len, _ = prompt_embeds.shape
305
+ prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
306
+ prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
307
+ prompt_embeds_mask = prompt_embeds_mask.repeat(1, num_images_per_prompt, 1)
308
+ prompt_embeds_mask = prompt_embeds_mask.view(batch_size * num_images_per_prompt, seq_len)
309
+
310
+ return prompt_embeds, prompt_embeds_mask
311
+
312
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline.check_inputs
313
+ def check_inputs(
314
+ self,
315
+ prompt,
316
+ height,
317
+ width,
318
+ negative_prompt=None,
319
+ prompt_embeds=None,
320
+ negative_prompt_embeds=None,
321
+ prompt_embeds_mask=None,
322
+ negative_prompt_embeds_mask=None,
323
+ callback_on_step_end_tensor_inputs=None,
324
+ max_sequence_length=None,
325
+ ):
326
+ if height % (self.vae_scale_factor * 2) != 0 or width % (self.vae_scale_factor * 2) != 0:
327
+ logger.warning(
328
+ f"`height` and `width` have to be divisible by {self.vae_scale_factor * 2} but are {height} and {width}. Dimensions will be resized accordingly"
329
+ )
330
+
331
+ if callback_on_step_end_tensor_inputs is not None and not all(
332
+ k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
333
+ ):
334
+ raise ValueError(
335
+ f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
336
+ )
337
+
338
+ if prompt is not None and prompt_embeds is not None:
339
+ raise ValueError(
340
+ f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
341
+ " only forward one of the two."
342
+ )
343
+ elif prompt is None and prompt_embeds is None:
344
+ raise ValueError(
345
+ "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
346
+ )
347
+ elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
348
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
349
+
350
+ if negative_prompt is not None and negative_prompt_embeds is not None:
351
+ raise ValueError(
352
+ f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
353
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
354
+ )
355
+
356
+ if prompt_embeds is not None and prompt_embeds_mask is None:
357
+ raise ValueError(
358
+ "If `prompt_embeds` are provided, `prompt_embeds_mask` also have to be passed. Make sure to generate `prompt_embeds_mask` from the same text encoder that was used to generate `prompt_embeds`."
359
+ )
360
+ if negative_prompt_embeds is not None and negative_prompt_embeds_mask is None:
361
+ raise ValueError(
362
+ "If `negative_prompt_embeds` are provided, `negative_prompt_embeds_mask` also have to be passed. Make sure to generate `negative_prompt_embeds_mask` from the same text encoder that was used to generate `negative_prompt_embeds`."
363
+ )
364
+
365
+ if max_sequence_length is not None and max_sequence_length > 1024:
366
+ raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
367
+
368
+ @staticmethod
369
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.QwenImagePipeline._pack_latents
370
+ def _pack_latents(latents, batch_size, num_channels_latents, height, width):
371
+ latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
372
+ latents = latents.permute(0, 2, 4, 1, 3, 5)
373
+ latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4)
374
+
375
+ return latents
376
+
377
+ @staticmethod
378
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.QwenImagePipeline._unpack_latents
379
+ def _unpack_latents(latents, height, width, vae_scale_factor):
380
+ batch_size, num_patches, channels = latents.shape
381
+
382
+ # VAE applies 8x compression on images but we must also account for packing which requires
383
+ # latent height and width to be divisible by 2.
384
+ height = 2 * (int(height) // (vae_scale_factor * 2))
385
+ width = 2 * (int(width) // (vae_scale_factor * 2))
386
+
387
+ latents = latents.view(batch_size, height // 2, width // 2, channels // 4, 2, 2)
388
+ latents = latents.permute(0, 3, 1, 4, 2, 5)
389
+
390
+ latents = latents.reshape(batch_size, channels // (2 * 2), 1, height, width)
391
+
392
+ return latents
393
+
394
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline._encode_vae_image
395
+ def _encode_vae_image(self, image: torch.Tensor, generator: torch.Generator):
396
+ if isinstance(generator, list):
397
+ image_latents = [
398
+ retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i], sample_mode="argmax")
399
+ for i in range(image.shape[0])
400
+ ]
401
+ image_latents = torch.cat(image_latents, dim=0)
402
+ else:
403
+ image_latents = retrieve_latents(self.vae.encode(image), generator=generator, sample_mode="argmax")
404
+ latents_mean = (
405
+ torch.tensor(self.vae.config.latents_mean)
406
+ .view(1, self.latent_channels, 1, 1, 1)
407
+ .to(image_latents.device, image_latents.dtype)
408
+ )
409
+ latents_std = (
410
+ torch.tensor(self.vae.config.latents_std)
411
+ .view(1, self.latent_channels, 1, 1, 1)
412
+ .to(image_latents.device, image_latents.dtype)
413
+ )
414
+ image_latents = (image_latents - latents_mean) / latents_std
415
+
416
+ return image_latents
417
+
418
+ def prepare_latents(
419
+ self,
420
+ images,
421
+ batch_size,
422
+ num_channels_latents,
423
+ height,
424
+ width,
425
+ dtype,
426
+ device,
427
+ generator,
428
+ latents=None,
429
+ ):
430
+ # VAE applies 8x compression on images but we must also account for packing which requires
431
+ # latent height and width to be divisible by 2.
432
+ height = 2 * (int(height) // (self.vae_scale_factor * 2))
433
+ width = 2 * (int(width) // (self.vae_scale_factor * 2))
434
+
435
+ shape = (batch_size, 1, num_channels_latents, height, width)
436
+
437
+ image_latents = None
438
+ if images is not None:
439
+ if not isinstance(images, list):
440
+ images = [images]
441
+ all_image_latents = []
442
+ for image in images: # [C,_,H,W]
443
+ image = image.to(device=device, dtype=dtype)
444
+ if image.shape[1] != self.latent_channels:
445
+ image_latents = self._encode_vae_image(image=image, generator=generator) # [C,_,H,W]
446
+ else:
447
+ image_latents = image
448
+ if batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] == 0:
449
+ # expand init_latents for batch_size
450
+ additional_image_per_prompt = batch_size // image_latents.shape[0]
451
+ image_latents = torch.cat([image_latents] * additional_image_per_prompt, dim=0)
452
+ elif batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] != 0:
453
+ raise ValueError(
454
+ f"Cannot duplicate `image` of batch size {image_latents.shape[0]} to {batch_size} text prompts."
455
+ )
456
+ else:
457
+ image_latents = torch.cat([image_latents], dim=0)
458
+
459
+ image_latent_height, image_latent_width = image_latents.shape[3:]
460
+ image_latents = self._pack_latents(
461
+ image_latents, batch_size, num_channels_latents, image_latent_height, image_latent_width
462
+ )
463
+ all_image_latents.append(image_latents)
464
+ image_latents = torch.cat(all_image_latents, dim=1)
465
+
466
+ if isinstance(generator, list) and len(generator) != batch_size:
467
+ raise ValueError(
468
+ f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
469
+ f" size of {batch_size}. Make sure the batch size matches the length of the generators."
470
+ )
471
+ if latents is None:
472
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
473
+ latents = self._pack_latents(latents, batch_size, num_channels_latents, height, width)
474
+ else:
475
+ latents = latents.to(device=device, dtype=dtype)
476
+
477
+ return latents, image_latents
478
+
479
+ @property
480
+ def guidance_scale(self):
481
+ return self._guidance_scale
482
+
483
+ @property
484
+ def attention_kwargs(self):
485
+ return self._attention_kwargs
486
+
487
+ @property
488
+ def num_timesteps(self):
489
+ return self._num_timesteps
490
+
491
+ @property
492
+ def current_timestep(self):
493
+ return self._current_timestep
494
+
495
+ @property
496
+ def interrupt(self):
497
+ return self._interrupt
498
+
499
+ @torch.no_grad()
500
+ @replace_example_docstring(EXAMPLE_DOC_STRING)
501
+ def __call__(
502
+ self,
503
+ image: Optional[PipelineImageInput] = None,
504
+ prompt: Union[str, List[str]] = None,
505
+ negative_prompt: Union[str, List[str]] = None,
506
+ true_cfg_scale: float = 4.0,
507
+ height: Optional[int] = None,
508
+ width: Optional[int] = None,
509
+ num_inference_steps: int = 50,
510
+ sigmas: Optional[List[float]] = None,
511
+ guidance_scale: Optional[float] = None,
512
+ num_images_per_prompt: int = 1,
513
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
514
+ latents: Optional[torch.Tensor] = None,
515
+ prompt_embeds: Optional[torch.Tensor] = None,
516
+ prompt_embeds_mask: Optional[torch.Tensor] = None,
517
+ negative_prompt_embeds: Optional[torch.Tensor] = None,
518
+ negative_prompt_embeds_mask: Optional[torch.Tensor] = None,
519
+ output_type: Optional[str] = "pil",
520
+ return_dict: bool = True,
521
+ attention_kwargs: Optional[Dict[str, Any]] = None,
522
+ callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
523
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
524
+ max_sequence_length: int = 512,
525
+ ):
526
+ r"""
527
+ Function invoked when calling the pipeline for generation.
528
+
529
+ Args:
530
+ image (`torch.Tensor`, `PIL.Image.Image`, `np.ndarray`, `List[torch.Tensor]`, `List[PIL.Image.Image]`, or `List[np.ndarray]`):
531
+ `Image`, numpy array or tensor representing an image batch to be used as the starting point. For both
532
+ numpy array and pytorch tensor, the expected value range is between `[0, 1]` If it's a tensor or a list
533
+ or tensors, the expected shape should be `(B, C, H, W)` or `(C, H, W)`. If it is a numpy array or a
534
+ list of arrays, the expected shape should be `(B, H, W, C)` or `(H, W, C)` It can also accept image
535
+ latents as `image`, but if passing latents directly it is not encoded again.
536
+ prompt (`str` or `List[str]`, *optional*):
537
+ The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`.
538
+ instead.
539
+ negative_prompt (`str` or `List[str]`, *optional*):
540
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
541
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `true_cfg_scale` is
542
+ not greater than `1`).
543
+ true_cfg_scale (`float`, *optional*, defaults to 1.0):
544
+ true_cfg_scale (`float`, *optional*, defaults to 1.0): Guidance scale as defined in [Classifier-Free
545
+ Diffusion Guidance](https://huggingface.co/papers/2207.12598). `true_cfg_scale` is defined as `w` of
546
+ equation 2. of [Imagen Paper](https://huggingface.co/papers/2205.11487). Classifier-free guidance is
547
+ enabled by setting `true_cfg_scale > 1` and a provided `negative_prompt`. Higher guidance scale
548
+ encourages to generate images that are closely linked to the text `prompt`, usually at the expense of
549
+ lower image quality.
550
+ height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
551
+ The height in pixels of the generated image. This is set to 1024 by default for the best results.
552
+ width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
553
+ The width in pixels of the generated image. This is set to 1024 by default for the best results.
554
+ num_inference_steps (`int`, *optional*, defaults to 50):
555
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
556
+ expense of slower inference.
557
+ sigmas (`List[float]`, *optional*):
558
+ Custom sigmas to use for the denoising process with schedulers which support a `sigmas` argument in
559
+ their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is passed
560
+ will be used.
561
+ guidance_scale (`float`, *optional*, defaults to None):
562
+ A guidance scale value for guidance distilled models. Unlike the traditional classifier-free guidance
563
+ where the guidance scale is applied during inference through noise prediction rescaling, guidance
564
+ distilled models take the guidance scale directly as an input parameter during forward pass. Guidance
565
+ scale is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages to generate images
566
+ that are closely linked to the text `prompt`, usually at the expense of lower image quality. This
567
+ parameter in the pipeline is there to support future guidance-distilled models when they come up. It is
568
+ ignored when not using guidance distilled models. To enable traditional classifier-free guidance,
569
+ please pass `true_cfg_scale > 1.0` and `negative_prompt` (even an empty negative prompt like " " should
570
+ enable classifier-free guidance computations).
571
+ num_images_per_prompt (`int`, *optional*, defaults to 1):
572
+ The number of images to generate per prompt.
573
+ generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
574
+ One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
575
+ to make generation deterministic.
576
+ latents (`torch.Tensor`, *optional*):
577
+ Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image
578
+ generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
579
+ tensor will be generated by sampling using the supplied random `generator`.
580
+ prompt_embeds (`torch.Tensor`, *optional*):
581
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
582
+ provided, text embeddings will be generated from `prompt` input argument.
583
+ negative_prompt_embeds (`torch.Tensor`, *optional*):
584
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
585
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
586
+ argument.
587
+ output_type (`str`, *optional*, defaults to `"pil"`):
588
+ The output format of the generate image. Choose between
589
+ [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
590
+ return_dict (`bool`, *optional*, defaults to `True`):
591
+ Whether or not to return a [`~pipelines.qwenimage.QwenImagePipelineOutput`] instead of a plain tuple.
592
+ attention_kwargs (`dict`, *optional*):
593
+ A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
594
+ `self.processor` in
595
+ [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
596
+ callback_on_step_end (`Callable`, *optional*):
597
+ A function that calls at the end of each denoising steps during the inference. The function is called
598
+ with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int,
599
+ callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by
600
+ `callback_on_step_end_tensor_inputs`.
601
+ callback_on_step_end_tensor_inputs (`List`, *optional*):
602
+ The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
603
+ will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
604
+ `._callback_tensor_inputs` attribute of your pipeline class.
605
+ max_sequence_length (`int` defaults to 512): Maximum sequence length to use with the `prompt`.
606
+
607
+ Examples:
608
+
609
+ Returns:
610
+ [`~pipelines.qwenimage.QwenImagePipelineOutput`] or `tuple`:
611
+ [`~pipelines.qwenimage.QwenImagePipelineOutput`] if `return_dict` is True, otherwise a `tuple`. When
612
+ returning a tuple, the first element is a list with the generated images.
613
+ """
614
+ image_size = image[-1].size if isinstance(image, list) else image.size
615
+ calculated_width, calculated_height = calculate_dimensions(1024 * 1024, image_size[0] / image_size[1])
616
+ height = height or calculated_height
617
+ width = width or calculated_width
618
+
619
+ multiple_of = self.vae_scale_factor * 2
620
+ width = width // multiple_of * multiple_of
621
+ height = height // multiple_of * multiple_of
622
+
623
+ # 1. Check inputs. Raise error if not correct
624
+ self.check_inputs(
625
+ prompt,
626
+ height,
627
+ width,
628
+ negative_prompt=negative_prompt,
629
+ prompt_embeds=prompt_embeds,
630
+ negative_prompt_embeds=negative_prompt_embeds,
631
+ prompt_embeds_mask=prompt_embeds_mask,
632
+ negative_prompt_embeds_mask=negative_prompt_embeds_mask,
633
+ callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
634
+ max_sequence_length=max_sequence_length,
635
+ )
636
+
637
+ self._guidance_scale = guidance_scale
638
+ self._attention_kwargs = attention_kwargs
639
+ self._current_timestep = None
640
+ self._interrupt = False
641
+
642
+ # 2. Define call parameters
643
+ if prompt is not None and isinstance(prompt, str):
644
+ batch_size = 1
645
+ elif prompt is not None and isinstance(prompt, list):
646
+ batch_size = len(prompt)
647
+ else:
648
+ batch_size = prompt_embeds.shape[0]
649
+
650
+ device = self._execution_device
651
+ # 3. Preprocess image
652
+ if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == self.latent_channels):
653
+ if not isinstance(image, list):
654
+ image = [image]
655
+ condition_image_sizes = []
656
+ condition_images = []
657
+ vae_image_sizes = []
658
+ vae_images = []
659
+ for img in image:
660
+ # Resolution adaptive resize for textual and visual branch
661
+ image_width, image_height = img.size
662
+ condition_width, condition_height = calculate_dimensions(CONDITION_IMAGE_SIZE, image_width / image_height)
663
+ vae_width, vae_height = calculate_dimensions(VAE_IMAGE_SIZE, image_width / image_height)
664
+ condition_image_sizes.append((condition_width, condition_height))
665
+ vae_image_sizes.append((vae_width, vae_height))
666
+ condition_images.append(self.image_processor.resize(img, condition_height, condition_width))
667
+ vae_images.append(
668
+ self.image_processor.preprocess(img, vae_height, vae_width).unsqueeze(2)
669
+ ) # [C,H,W] or [_,C,H,W] -> [_,C,_,H,W]
670
+
671
+ has_neg_prompt = negative_prompt is not None or (
672
+ negative_prompt_embeds is not None and negative_prompt_embeds_mask is not None
673
+ )
674
+
675
+ if true_cfg_scale > 1 and not has_neg_prompt:
676
+ logger.warning(
677
+ f"true_cfg_scale is passed as {true_cfg_scale}, but classifier-free guidance is not enabled since no negative_prompt is provided."
678
+ )
679
+ elif true_cfg_scale <= 1 and has_neg_prompt:
680
+ logger.warning(
681
+ " negative_prompt is passed but classifier-free guidance is not enabled since true_cfg_scale <= 1"
682
+ )
683
+
684
+ do_true_cfg = true_cfg_scale > 1 and has_neg_prompt
685
+ prompt_embeds, prompt_embeds_mask = self.encode_prompt(
686
+ image=condition_images,
687
+ prompt=prompt,
688
+ prompt_embeds=prompt_embeds,
689
+ prompt_embeds_mask=prompt_embeds_mask,
690
+ device=device,
691
+ num_images_per_prompt=num_images_per_prompt,
692
+ max_sequence_length=max_sequence_length,
693
+ )
694
+ if do_true_cfg:
695
+ negative_prompt_embeds, negative_prompt_embeds_mask = self.encode_prompt(
696
+ image=condition_images,
697
+ prompt=negative_prompt,
698
+ prompt_embeds=negative_prompt_embeds,
699
+ prompt_embeds_mask=negative_prompt_embeds_mask,
700
+ device=device,
701
+ num_images_per_prompt=num_images_per_prompt,
702
+ max_sequence_length=max_sequence_length,
703
+ )
704
+
705
+ # 4. Prepare latent variables
706
+ num_channels_latents = self.transformer.config.in_channels // 4 # 16
707
+ latents, image_latents = self.prepare_latents(
708
+ vae_images,
709
+ batch_size * num_images_per_prompt,
710
+ num_channels_latents,
711
+ height,
712
+ width,
713
+ prompt_embeds.dtype,
714
+ device,
715
+ generator,
716
+ latents,
717
+ )
718
+ img_shapes = [
719
+ [
720
+ (1, height // self.vae_scale_factor // 2, width // self.vae_scale_factor // 2),
721
+ *[
722
+ (1, vae_height // self.vae_scale_factor // 2, vae_width // self.vae_scale_factor // 2)
723
+ for vae_width, vae_height in vae_image_sizes
724
+ ],
725
+ ]
726
+ ] * batch_size
727
+
728
+ # 5. Prepare timesteps
729
+ sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) if sigmas is None else sigmas
730
+ image_seq_len = latents.shape[1]
731
+ mu = calculate_shift(
732
+ image_seq_len,
733
+ self.scheduler.config.get("base_image_seq_len", 256),
734
+ self.scheduler.config.get("max_image_seq_len", 4096),
735
+ self.scheduler.config.get("base_shift", 0.5),
736
+ self.scheduler.config.get("max_shift", 1.15),
737
+ )
738
+ timesteps, num_inference_steps = retrieve_timesteps(
739
+ self.scheduler,
740
+ num_inference_steps,
741
+ device,
742
+ sigmas=sigmas,
743
+ mu=mu,
744
+ )
745
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
746
+ self._num_timesteps = len(timesteps)
747
+
748
+ # handle guidance
749
+ if self.transformer.config.guidance_embeds and guidance_scale is None:
750
+ raise ValueError("guidance_scale is required for guidance-distilled model.")
751
+ elif self.transformer.config.guidance_embeds:
752
+ guidance = torch.full([1], guidance_scale, device=device, dtype=torch.float32)
753
+ guidance = guidance.expand(latents.shape[0])
754
+ elif not self.transformer.config.guidance_embeds and guidance_scale is not None:
755
+ logger.warning(
756
+ f"guidance_scale is passed as {guidance_scale}, but ignored since the model is not guidance-distilled."
757
+ )
758
+ guidance = None
759
+ elif not self.transformer.config.guidance_embeds and guidance_scale is None:
760
+ guidance = None
761
+
762
+ if self.attention_kwargs is None:
763
+ self._attention_kwargs = {}
764
+
765
+ txt_seq_lens = prompt_embeds_mask.sum(dim=1).tolist() if prompt_embeds_mask is not None else None
766
+ negative_txt_seq_lens = (
767
+ negative_prompt_embeds_mask.sum(dim=1).tolist() if negative_prompt_embeds_mask is not None else None
768
+ )
769
+
770
+ # 6. Denoising loop
771
+ self.scheduler.set_begin_index(0)
772
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
773
+ for i, t in enumerate(timesteps):
774
+ if self.interrupt:
775
+ continue
776
+
777
+ self._current_timestep = t
778
+
779
+ latent_model_input = latents
780
+ if image_latents is not None:
781
+ latent_model_input = torch.cat([latents, image_latents], dim=1)
782
+
783
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
784
+ timestep = t.expand(latents.shape[0]).to(latents.dtype)
785
+ with self.transformer.cache_context("cond"):
786
+ noise_pred = self.transformer(
787
+ hidden_states=latent_model_input,
788
+ timestep=timestep / 1000,
789
+ guidance=guidance,
790
+ encoder_hidden_states_mask=prompt_embeds_mask,
791
+ encoder_hidden_states=prompt_embeds,
792
+ img_shapes=img_shapes,
793
+ txt_seq_lens=txt_seq_lens,
794
+ attention_kwargs=self.attention_kwargs,
795
+ return_dict=False,
796
+ )[0]
797
+ noise_pred = noise_pred[:, : latents.size(1)]
798
+
799
+ if do_true_cfg:
800
+ with self.transformer.cache_context("uncond"):
801
+ neg_noise_pred = self.transformer(
802
+ hidden_states=latent_model_input,
803
+ timestep=timestep / 1000,
804
+ guidance=guidance,
805
+ encoder_hidden_states_mask=negative_prompt_embeds_mask,
806
+ encoder_hidden_states=negative_prompt_embeds,
807
+ img_shapes=img_shapes,
808
+ txt_seq_lens=negative_txt_seq_lens,
809
+ attention_kwargs=self.attention_kwargs,
810
+ return_dict=False,
811
+ )[0]
812
+ neg_noise_pred = neg_noise_pred[:, : latents.size(1)]
813
+ comb_pred = neg_noise_pred + true_cfg_scale * (noise_pred - neg_noise_pred)
814
+
815
+ cond_norm = torch.norm(noise_pred, dim=-1, keepdim=True)
816
+ noise_norm = torch.norm(comb_pred, dim=-1, keepdim=True)
817
+ noise_pred = comb_pred * (cond_norm / noise_norm)
818
+
819
+ # compute the previous noisy sample x_t -> x_t-1
820
+ latents_dtype = latents.dtype
821
+ latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
822
+
823
+ if latents.dtype != latents_dtype:
824
+ if torch.backends.mps.is_available():
825
+ # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
826
+ latents = latents.to(latents_dtype)
827
+
828
+ if callback_on_step_end is not None:
829
+ callback_kwargs = {}
830
+ for k in callback_on_step_end_tensor_inputs:
831
+ callback_kwargs[k] = locals()[k]
832
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
833
+
834
+ latents = callback_outputs.pop("latents", latents)
835
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
836
+
837
+ # call the callback, if provided
838
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
839
+ progress_bar.update()
840
+
841
+ if XLA_AVAILABLE:
842
+ xm.mark_step()
843
+
844
+ self._current_timestep = None
845
+ if output_type == "latent":
846
+ image = latents
847
+ else:
848
+ latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
849
+ latents = latents.to(self.vae.dtype)
850
+ latents_mean = (
851
+ torch.tensor(self.vae.config.latents_mean)
852
+ .view(1, self.vae.config.z_dim, 1, 1, 1)
853
+ .to(latents.device, latents.dtype)
854
+ )
855
+ latents_std = 1.0 / torch.tensor(self.vae.config.latents_std).view(1, self.vae.config.z_dim, 1, 1, 1).to(
856
+ latents.device, latents.dtype
857
+ )
858
+ latents = latents / latents_std + latents_mean
859
+ image = self.vae.decode(latents, return_dict=False)[0][:, :, 0]
860
+ image = self.image_processor.postprocess(image, output_type=output_type)
861
+
862
+ # Offload all models
863
+ self.maybe_free_model_hooks()
864
+
865
+ if not return_dict:
866
+ return (image,)
867
+
868
+ return QwenImagePipelineOutput(images=image)
models/styctrl.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import logging
4
+ import torch.nn as nn
5
+
6
+ from typing import Literal, Optional
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class HadamardLayer(nn.Module):
13
+
14
+ def __init__(
15
+ self,
16
+ features: int,
17
+ device: Optional[torch.device] = None,
18
+ dtype: Optional[torch.dtype] = None,
19
+ ):
20
+ super().__init__()
21
+
22
+ self.dtype = dtype
23
+ self.weight = nn.Parameter(torch.ones(features, device=device, dtype=dtype))
24
+ self.bias = nn.Parameter(torch.zeros(features, device=device, dtype=dtype))
25
+
26
+ nn.init.normal_(self.weight, mean=1.0, std=0.1)
27
+
28
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
29
+ x_dtype = x.dtype
30
+ x = x.to(dtype=self.dtype)
31
+ x = x * self.weight + self.bias
32
+ return x.to(x_dtype)
33
+
34
+
35
+ class StyCtrlLoRA(nn.Module):
36
+
37
+ def __init__(self, base_model: nn.Linear):
38
+ super().__init__()
39
+
40
+ self.base_model = base_model
41
+
42
+ self.dtype = self.base_model.weight.dtype
43
+ self.in_features = self.base_model.in_features
44
+ self.out_features = self.base_model.out_features
45
+
46
+ self.projector = nn.ModuleDict()
47
+ self.lora_A = nn.ParameterDict()
48
+ self.lora_B = nn.ParameterDict()
49
+ self.bias = nn.ParameterDict()
50
+
51
+ self.active_adapter = "styctrl"
52
+ self.proj_types = {}
53
+
54
+ def get_current_adapters(self) -> list[str]:
55
+ return [k for k in self.lora_A.keys()]
56
+
57
+ def add_adapter(
58
+ self,
59
+ rank: int,
60
+ adapter_name: str = "default",
61
+ proj_type: Literal["none", "low_rank_linear", "low_rank_scale", "in_scale"] = "none",
62
+ bias: bool = False,
63
+ device: torch.device = None,
64
+ dtype: torch.dtype = None,
65
+ ) -> None:
66
+ if adapter_name in self.get_current_adapters():
67
+ logger.warning(
68
+ f"{__class__.__name__}: {adapter_name=} already exists in {__class__}, existing adapters are: {self.get_current_adapters()}."
69
+ )
70
+ return
71
+
72
+ device = device or self.base_model.weight.device
73
+ dtype = dtype or self.base_model.weight.dtype
74
+
75
+ # Add strength projector
76
+ if proj_type == "low_rank_linear":
77
+ # Add linear projector in the low rank space
78
+ self.projector[adapter_name] = nn.Linear(rank, rank, bias=True, device=device, dtype=dtype)
79
+ nn.init.eye_(self.projector[adapter_name].weight)
80
+ nn.init.zeros_(self.projector[adapter_name].bias)
81
+ elif proj_type == "low_rank_scale":
82
+ self.projector[adapter_name] = HadamardLayer(rank, device=device, dtype=dtype)
83
+ elif proj_type == "in_scale":
84
+ self.projector[adapter_name] = HadamardLayer(self.in_features, device=device, dtype=dtype)
85
+ elif proj_type in ["none", "lora"]:
86
+ self.projector[adapter_name] = nn.Identity()
87
+ else:
88
+ raise KeyError(f"{proj_type=} has not been implemented.")
89
+ self.proj_types[adapter_name] = proj_type
90
+
91
+ # Add main LoRA linear projectors
92
+ self.lora_A[adapter_name] = nn.Parameter(torch.empty(rank, self.in_features)).to(device=device, dtype=dtype)
93
+ self.lora_B[adapter_name] = nn.Parameter(torch.empty(self.out_features, rank)).to(device=device, dtype=dtype)
94
+
95
+ nn.init.normal_(self.lora_A[adapter_name].data, mean=0.0, std=0.02)
96
+ nn.init.normal_(self.lora_B[adapter_name].data, mean=0.0, std=0.02)
97
+
98
+ # Add bias
99
+ if bias:
100
+ init_bias = 1 / math.sqrt(rank)
101
+ self.bias[adapter_name] = nn.Parameter(torch.zeros((self.out_features,), dtype=self.dtype, device=device))
102
+ nn.init.uniform_(self.bias[adapter_name], -init_bias, init_bias)
103
+
104
+ def activate(self, adapter_name: str):
105
+ if adapter_name not in self.get_current_adapters():
106
+ logger.warning(
107
+ f"{__class__.__name__}: {adapter_name=} not in current adapter list: {self.get_current_adapters()}"
108
+ )
109
+ return
110
+ self.active_adapter = adapter_name
111
+
112
+ def deactivate(self):
113
+ self.active_adapter = None
114
+
115
+ def forward(
116
+ self,
117
+ x: torch.Tensor,
118
+ enable_lora: bool = False,
119
+ w: Optional[torch.Tensor] = None,
120
+ ) -> torch.Tensor:
121
+ x_dtype = x.dtype
122
+ x = x.to(self.dtype)
123
+ out = self.base_model(x)
124
+
125
+ if enable_lora:
126
+ adapter_name = self.active_adapter
127
+ proj_type = self.proj_types[adapter_name]
128
+
129
+ if w is not None and proj_type == "in_scale":
130
+ w = w.to(self.dtype)
131
+ w = w.unsqueeze(-1).repeat(1, self.in_features)
132
+ w = self.projector[adapter_name](w)
133
+ x = w * x
134
+
135
+ lora_out = x @ self.lora_A[adapter_name].T
136
+
137
+ if w is not None and proj_type == "low_rank_linear":
138
+ w = w.to(self.dtype)
139
+ while w.ndim < lora_out.ndim:
140
+ w = w.unsqueeze(-1)
141
+ lora_out = self.projector[adapter_name](w * lora_out)
142
+
143
+ if w is not None and proj_type == "low_rank_scale":
144
+ w = w.to(self.dtype)
145
+ w = w.unsqueeze(-1).repeat(1, lora_out.shape[-1])
146
+ w = self.projector[adapter_name](w)
147
+ lora_out = w * lora_out
148
+
149
+ lora_out = lora_out @ self.lora_B[adapter_name].T
150
+
151
+ if w is not None and proj_type == "lora":
152
+ w = w.to(self.dtype)
153
+ lora_out = w * lora_out
154
+
155
+ out = out + lora_out
156
+
157
+ out = out.to(x_dtype)
158
+ return out
159
+
160
+
161
+ def register_styctrl(
162
+ model: nn.Module,
163
+ target_modules: list[str],
164
+ device: torch.device = "cpu",
165
+ dtype: torch.dtype = torch.bfloat16,
166
+ ):
167
+ r"""
168
+ This method only insert LoRA adapter into model, not add or activate any adapter.
169
+ If there are already lora_adapter in model, this method do nothing.
170
+ """
171
+ for name, module in model.named_modules():
172
+ if not any(target in name for target in target_modules):
173
+ continue
174
+ if not isinstance(module, nn.Linear):
175
+ continue
176
+
177
+ splited_name = name.split(".")
178
+ parent_node = model.get_submodule(".".join(splited_name[:-1]))
179
+ target_name = splited_name[-1]
180
+
181
+ lora_layer = StyCtrlLoRA(base_model=module).to(device=device, dtype=dtype)
182
+
183
+ module.requires_grad_(False)
184
+ setattr(parent_node, target_name, lora_layer)
185
+ logger.debug(f"Inject LoRA into {name}.")
186
+
187
+
188
+ def add_styctrl(
189
+ model: nn.Module,
190
+ rank: int,
191
+ lora_layer_indices: list[int],
192
+ adapter_name: str = "default",
193
+ proj_type: Literal["none", "linear", "scale"] = "none",
194
+ bias: bool = False,
195
+ device: Optional[torch.device] = None,
196
+ dtype: Optional[torch.dtype] = None,
197
+ ) -> None:
198
+ for name, module in model.named_modules():
199
+ if not any(
200
+ f"transformer_blocks.{i}." in name or f"single_transformer_blocks.{i}." in name
201
+ for i in lora_layer_indices
202
+ ):
203
+ continue
204
+ if isinstance(module, StyCtrlLoRA):
205
+ module.add_adapter(
206
+ rank=rank,
207
+ adapter_name=adapter_name,
208
+ proj_type=proj_type,
209
+ bias=bias,
210
+ device=device,
211
+ dtype=dtype,
212
+ )
213
+
214
+
215
+ def activate(model: nn.Module, adapter_name: str) -> None:
216
+ for n, module in model.named_modules():
217
+ if isinstance(module, StyCtrlLoRA):
218
+ logger.debug(f"Activate LoRA in {n}")
219
+ module.activate(adapter_name)
220
+
221
+
222
+ def deactivate(model: nn.Module) -> None:
223
+ for n, module in model.named_modules():
224
+ if isinstance(module, StyCtrlLoRA):
225
+ logger.debug(f"Deactivate LoRA in {n}")
226
+ module.deactivate()
models/transformer_qwenimage.py ADDED
@@ -0,0 +1,731 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import math
3
+ from typing import Any, Dict, List, Optional, Tuple, Union
4
+
5
+ import numpy as np
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+
10
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
11
+ from diffusers.loaders import FromOriginalModelMixin, PeftAdapterMixin
12
+ from diffusers.utils import USE_PEFT_BACKEND, logging, scale_lora_layers, unscale_lora_layers
13
+ from diffusers.utils.torch_utils import maybe_allow_in_graph
14
+ from diffusers.models._modeling_parallel import ContextParallelInput, ContextParallelOutput
15
+ from diffusers.models.attention import AttentionMixin, FeedForward
16
+ from diffusers.models.attention_dispatch import dispatch_attention_fn
17
+ from diffusers.models.attention_processor import Attention
18
+ from diffusers.models.cache_utils import CacheMixin
19
+ from diffusers.models.embeddings import TimestepEmbedding, Timesteps
20
+ from diffusers.models.modeling_outputs import Transformer2DModelOutput
21
+ from diffusers.models.modeling_utils import ModelMixin
22
+ from diffusers.models.normalization import AdaLayerNormContinuous, RMSNorm
23
+
24
+ from .styctrl import StyCtrlLoRA
25
+
26
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
27
+
28
+
29
+ def get_timestep_embedding(
30
+ timesteps: torch.Tensor,
31
+ embedding_dim: int,
32
+ flip_sin_to_cos: bool = False,
33
+ downscale_freq_shift: float = 1,
34
+ scale: float = 1,
35
+ max_period: int = 10000,
36
+ ) -> torch.Tensor:
37
+ """
38
+ This matches the implementation in Denoising Diffusion Probabilistic Models: Create sinusoidal timestep embeddings.
39
+
40
+ Args
41
+ timesteps (torch.Tensor):
42
+ a 1-D Tensor of N indices, one per batch element. These may be fractional.
43
+ embedding_dim (int):
44
+ the dimension of the output.
45
+ flip_sin_to_cos (bool):
46
+ Whether the embedding order should be `cos, sin` (if True) or `sin, cos` (if False)
47
+ downscale_freq_shift (float):
48
+ Controls the delta between frequencies between dimensions
49
+ scale (float):
50
+ Scaling factor applied to the embeddings.
51
+ max_period (int):
52
+ Controls the maximum frequency of the embeddings
53
+ Returns
54
+ torch.Tensor: an [N x dim] Tensor of positional embeddings.
55
+ """
56
+ assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array"
57
+
58
+ half_dim = embedding_dim // 2
59
+ exponent = -math.log(max_period) * torch.arange(start=0, end=half_dim, dtype=torch.float32, device=timesteps.device)
60
+ exponent = exponent / (half_dim - downscale_freq_shift)
61
+
62
+ emb = torch.exp(exponent).to(timesteps.dtype)
63
+ emb = timesteps[:, None].float() * emb[None, :]
64
+
65
+ # scale embeddings
66
+ emb = scale * emb
67
+
68
+ # concat sine and cosine embeddings
69
+ emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
70
+
71
+ # flip sine and cosine embeddings
72
+ if flip_sin_to_cos:
73
+ emb = torch.cat([emb[:, half_dim:], emb[:, :half_dim]], dim=-1)
74
+
75
+ # zero pad
76
+ if embedding_dim % 2 == 1:
77
+ emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))
78
+ return emb
79
+
80
+
81
+ def apply_rotary_emb_qwen(
82
+ x: torch.Tensor,
83
+ freqs_cis: Union[torch.Tensor, Tuple[torch.Tensor]],
84
+ use_real: bool = True,
85
+ use_real_unbind_dim: int = -1,
86
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
87
+ """
88
+ Apply rotary embeddings to input tensors using the given frequency tensor. This function applies rotary embeddings
89
+ to the given query or key 'x' tensors using the provided frequency tensor 'freqs_cis'. The input tensors are
90
+ reshaped as complex numbers, and the frequency tensor is reshaped for broadcasting compatibility. The resulting
91
+ tensors contain rotary embeddings and are returned as real tensors.
92
+
93
+ Args:
94
+ x (`torch.Tensor`):
95
+ Query or key tensor to apply rotary embeddings. [B, S, H, D] xk (torch.Tensor): Key tensor to apply
96
+ freqs_cis (`Tuple[torch.Tensor]`): Precomputed frequency tensor for complex exponentials. ([S, D], [S, D],)
97
+
98
+ Returns:
99
+ Tuple[torch.Tensor, torch.Tensor]: Tuple of modified query tensor and key tensor with rotary embeddings.
100
+ """
101
+ if use_real:
102
+ cos, sin = freqs_cis # [S, D]
103
+ cos = cos[None, None]
104
+ sin = sin[None, None]
105
+ cos, sin = cos.to(x.device), sin.to(x.device)
106
+
107
+ if use_real_unbind_dim == -1:
108
+ # Used for flux, cogvideox, hunyuan-dit
109
+ x_real, x_imag = x.reshape(*x.shape[:-1], -1, 2).unbind(-1) # [B, S, H, D//2]
110
+ x_rotated = torch.stack([-x_imag, x_real], dim=-1).flatten(3)
111
+ elif use_real_unbind_dim == -2:
112
+ # Used for Stable Audio, OmniGen, CogView4 and Cosmos
113
+ x_real, x_imag = x.reshape(*x.shape[:-1], 2, -1).unbind(-2) # [B, S, H, D//2]
114
+ x_rotated = torch.cat([-x_imag, x_real], dim=-1)
115
+ else:
116
+ raise ValueError(f"`use_real_unbind_dim={use_real_unbind_dim}` but should be -1 or -2.")
117
+
118
+ out = (x.float() * cos + x_rotated.float() * sin).to(x.dtype)
119
+
120
+ return out
121
+ else:
122
+ x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
123
+ freqs_cis = freqs_cis.unsqueeze(1)
124
+ x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(3)
125
+
126
+ return x_out.type_as(x)
127
+
128
+
129
+ class QwenTimestepProjEmbeddings(nn.Module):
130
+ def __init__(self, embedding_dim):
131
+ super().__init__()
132
+
133
+ self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=0, scale=1000)
134
+ self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)
135
+
136
+ def forward(self, timestep, hidden_states):
137
+ timesteps_proj = self.time_proj(timestep)
138
+ timesteps_emb = self.timestep_embedder(timesteps_proj.to(dtype=hidden_states.dtype)) # (N, D)
139
+
140
+ conditioning = timesteps_emb
141
+
142
+ return conditioning
143
+
144
+
145
+ class QwenEmbedRope(nn.Module):
146
+ def __init__(self, theta: int, axes_dim: List[int], scale_rope=False):
147
+ super().__init__()
148
+ self.theta = theta
149
+ self.axes_dim = axes_dim
150
+ pos_index = torch.arange(4096)
151
+ neg_index = torch.arange(4096).flip(0) * -1 - 1
152
+ self.pos_freqs = torch.cat(
153
+ [
154
+ self.rope_params(pos_index, self.axes_dim[0], self.theta),
155
+ self.rope_params(pos_index, self.axes_dim[1], self.theta),
156
+ self.rope_params(pos_index, self.axes_dim[2], self.theta),
157
+ ],
158
+ dim=1,
159
+ )
160
+ self.neg_freqs = torch.cat(
161
+ [
162
+ self.rope_params(neg_index, self.axes_dim[0], self.theta),
163
+ self.rope_params(neg_index, self.axes_dim[1], self.theta),
164
+ self.rope_params(neg_index, self.axes_dim[2], self.theta),
165
+ ],
166
+ dim=1,
167
+ )
168
+
169
+ # DO NOT USING REGISTER BUFFER HERE, IT WILL CAUSE COMPLEX NUMBERS LOSE ITS IMAGINARY PART
170
+ self.scale_rope = scale_rope
171
+
172
+ def rope_params(self, index, dim, theta=10000):
173
+ """
174
+ Args:
175
+ index: [0, 1, 2, 3] 1D Tensor representing the position index of the token
176
+ """
177
+ assert dim % 2 == 0
178
+ freqs = torch.outer(index, 1.0 / torch.pow(theta, torch.arange(0, dim, 2).to(torch.float32).div(dim)))
179
+ freqs = torch.polar(torch.ones_like(freqs), freqs)
180
+ return freqs
181
+
182
+ def forward(
183
+ self,
184
+ video_fhw: Union[Tuple[int, int, int], List[Tuple[int, int, int]]],
185
+ txt_seq_lens: List[int],
186
+ device: torch.device,
187
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
188
+ """
189
+ Args:
190
+ video_fhw (`Tuple[int, int, int]` or `List[Tuple[int, int, int]]`):
191
+ A list of 3 integers [frame, height, width] representing the shape of the video.
192
+ txt_seq_lens (`List[int]`):
193
+ A list of integers of length batch_size representing the length of each text prompt.
194
+ device: (`torch.device`):
195
+ The device on which to perform the RoPE computation.
196
+ """
197
+ if self.pos_freqs.device != device:
198
+ self.pos_freqs = self.pos_freqs.to(device)
199
+ self.neg_freqs = self.neg_freqs.to(device)
200
+
201
+ if isinstance(video_fhw, list):
202
+ video_fhw = video_fhw[0]
203
+ if not isinstance(video_fhw, list):
204
+ video_fhw = [video_fhw]
205
+
206
+ vid_freqs = []
207
+ max_vid_index = 0
208
+ for idx, fhw in enumerate(video_fhw):
209
+ frame, height, width = fhw
210
+ # RoPE frequencies are cached via a lru_cache decorator on _compute_video_freqs
211
+ video_freq = self._compute_video_freqs(frame, height, width, idx)
212
+ video_freq = video_freq.to(device)
213
+ vid_freqs.append(video_freq)
214
+
215
+ if self.scale_rope:
216
+ max_vid_index = max(height // 2, width // 2, max_vid_index)
217
+ else:
218
+ max_vid_index = max(height, width, max_vid_index)
219
+
220
+ max_len = max(txt_seq_lens)
221
+ txt_freqs = self.pos_freqs[max_vid_index : max_vid_index + max_len, ...]
222
+ vid_freqs = torch.cat(vid_freqs, dim=0)
223
+
224
+ return vid_freqs, txt_freqs
225
+
226
+ @functools.lru_cache(maxsize=128)
227
+ def _compute_video_freqs(self, frame: int, height: int, width: int, idx: int = 0) -> torch.Tensor:
228
+ seq_lens = frame * height * width
229
+ freqs_pos = self.pos_freqs.split([x // 2 for x in self.axes_dim], dim=1)
230
+ freqs_neg = self.neg_freqs.split([x // 2 for x in self.axes_dim], dim=1)
231
+
232
+ freqs_frame = freqs_pos[0][idx : idx + frame].view(frame, 1, 1, -1).expand(frame, height, width, -1)
233
+ if self.scale_rope:
234
+ freqs_height = torch.cat([freqs_neg[1][-(height - height // 2) :], freqs_pos[1][: height // 2]], dim=0)
235
+ freqs_height = freqs_height.view(1, height, 1, -1).expand(frame, height, width, -1)
236
+ freqs_width = torch.cat([freqs_neg[2][-(width - width // 2) :], freqs_pos[2][: width // 2]], dim=0)
237
+ freqs_width = freqs_width.view(1, 1, width, -1).expand(frame, height, width, -1)
238
+ else:
239
+ freqs_height = freqs_pos[1][:height].view(1, height, 1, -1).expand(frame, height, width, -1)
240
+ freqs_width = freqs_pos[2][:width].view(1, 1, width, -1).expand(frame, height, width, -1)
241
+
242
+ freqs = torch.cat([freqs_frame, freqs_height, freqs_width], dim=-1).reshape(seq_lens, -1)
243
+ return freqs.clone().contiguous()
244
+
245
+
246
+ class QwenDoubleStreamAttnProcessor2_0:
247
+ """
248
+ Attention processor for Qwen double-stream architecture, matching DoubleStreamLayerMegatron logic. This processor
249
+ implements joint attention computation where text and image streams are processed together.
250
+ """
251
+
252
+ _attention_backend = None
253
+ _parallel_config = None
254
+
255
+ def __init__(self):
256
+ if not hasattr(F, "scaled_dot_product_attention"):
257
+ raise ImportError(
258
+ "QwenDoubleStreamAttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0."
259
+ )
260
+
261
+ def __call__(
262
+ self,
263
+ attn: Attention,
264
+ hidden_states: torch.FloatTensor, # Image stream
265
+ encoder_hidden_states: torch.FloatTensor = None, # Text stream
266
+ encoder_hidden_states_mask: torch.FloatTensor = None,
267
+ attention_mask: Optional[torch.FloatTensor] = None,
268
+ image_rotary_emb: Optional[torch.Tensor] = None,
269
+ enable_lora: bool = False,
270
+ w: Optional[torch.Tensor] = None,
271
+ # in_weight: Optional[torch.Tensor] = None,
272
+ # mid_weight: Optional[torch.Tensor] = None,
273
+ # out_weight: Optional[torch.Tensor] = None,
274
+ ) -> torch.FloatTensor:
275
+ if encoder_hidden_states is None:
276
+ raise ValueError("QwenDoubleStreamAttnProcessor2_0 requires encoder_hidden_states (text stream)")
277
+
278
+ seq_txt = encoder_hidden_states.shape[1]
279
+
280
+ # add LoRA modules and their weights
281
+ if enable_lora and isinstance(attn.to_q, StyCtrlLoRA):
282
+ img_query = attn.to_q(hidden_states, enable_lora=enable_lora, w=w)
283
+ else:
284
+ img_query = attn.to_q(hidden_states)
285
+
286
+ if enable_lora and isinstance(attn.to_k, StyCtrlLoRA):
287
+ img_key = attn.to_k(hidden_states, enable_lora=enable_lora, w=w)
288
+ else:
289
+ img_key = attn.to_k(hidden_states)
290
+
291
+ if enable_lora and isinstance(attn.to_v, StyCtrlLoRA):
292
+ img_value = attn.to_v(hidden_states, enable_lora=enable_lora, w=w)
293
+ else:
294
+ img_value = attn.to_v(hidden_states)
295
+
296
+ if enable_lora and isinstance(attn.add_q_proj, StyCtrlLoRA):
297
+ txt_query = attn.add_q_proj(encoder_hidden_states, enable_lora=enable_lora, w=w)
298
+ else:
299
+ txt_query = attn.add_q_proj(encoder_hidden_states)
300
+
301
+ if enable_lora and isinstance(attn.add_k_proj, StyCtrlLoRA):
302
+ txt_key = attn.add_k_proj(encoder_hidden_states, enable_lora=enable_lora, w=w)
303
+ else:
304
+ txt_key = attn.add_k_proj(encoder_hidden_states)
305
+
306
+ if enable_lora and isinstance(attn.to_q, StyCtrlLoRA):
307
+ txt_value = attn.add_v_proj(encoder_hidden_states, enable_lora=enable_lora, w=w)
308
+ else:
309
+ txt_value = attn.add_v_proj(encoder_hidden_states)
310
+
311
+ # Reshape for multi-head attention
312
+ img_query = img_query.unflatten(-1, (attn.heads, -1))
313
+ img_key = img_key.unflatten(-1, (attn.heads, -1))
314
+ img_value = img_value.unflatten(-1, (attn.heads, -1))
315
+
316
+ txt_query = txt_query.unflatten(-1, (attn.heads, -1))
317
+ txt_key = txt_key.unflatten(-1, (attn.heads, -1))
318
+ txt_value = txt_value.unflatten(-1, (attn.heads, -1))
319
+
320
+ # Apply QK normalization
321
+ if attn.norm_q is not None:
322
+ img_query = attn.norm_q(img_query)
323
+ if attn.norm_k is not None:
324
+ img_key = attn.norm_k(img_key)
325
+ if attn.norm_added_q is not None:
326
+ txt_query = attn.norm_added_q(txt_query)
327
+ if attn.norm_added_k is not None:
328
+ txt_key = attn.norm_added_k(txt_key)
329
+
330
+ # Apply RoPE
331
+ if image_rotary_emb is not None:
332
+ img_freqs, txt_freqs = image_rotary_emb
333
+ img_query = apply_rotary_emb_qwen(img_query, img_freqs, use_real=False)
334
+ img_key = apply_rotary_emb_qwen(img_key, img_freqs, use_real=False)
335
+ txt_query = apply_rotary_emb_qwen(txt_query, txt_freqs, use_real=False)
336
+ txt_key = apply_rotary_emb_qwen(txt_key, txt_freqs, use_real=False)
337
+
338
+ # Concatenate for joint attention
339
+ # Order: [text, image]
340
+ joint_query = torch.cat([txt_query, img_query], dim=1)
341
+ joint_key = torch.cat([txt_key, img_key], dim=1)
342
+ joint_value = torch.cat([txt_value, img_value], dim=1)
343
+
344
+ # Compute joint attention
345
+ joint_hidden_states = dispatch_attention_fn(
346
+ joint_query,
347
+ joint_key,
348
+ joint_value,
349
+ attn_mask=attention_mask,
350
+ dropout_p=0.0,
351
+ is_causal=False,
352
+ backend=self._attention_backend,
353
+ parallel_config=self._parallel_config,
354
+ )
355
+
356
+ # Reshape back
357
+ joint_hidden_states = joint_hidden_states.flatten(2, 3)
358
+ joint_hidden_states = joint_hidden_states.to(joint_query.dtype)
359
+
360
+ # Split attention outputs back
361
+ txt_attn_output = joint_hidden_states[:, :seq_txt, :] # Text part
362
+ img_attn_output = joint_hidden_states[:, seq_txt:, :] # Image part
363
+
364
+ # Apply output projections
365
+ if enable_lora and isinstance(attn.to_out[0], StyCtrlLoRA):
366
+ img_attn_output = attn.to_out[0](img_attn_output, enable_lora=enable_lora, w=w)
367
+ else:
368
+ img_attn_output = attn.to_out[0](img_attn_output)
369
+
370
+ if len(attn.to_out) > 1:
371
+ img_attn_output = attn.to_out[1](img_attn_output) # dropout
372
+
373
+ if enable_lora and isinstance(attn.to_add_out, StyCtrlLoRA):
374
+ txt_attn_output = attn.to_add_out(txt_attn_output, enable_lora=enable_lora, w=w)
375
+ else:
376
+ txt_attn_output = attn.to_add_out(txt_attn_output)
377
+
378
+ return img_attn_output, txt_attn_output
379
+
380
+
381
+ @maybe_allow_in_graph
382
+ class QwenImageTransformerBlock(nn.Module):
383
+ def __init__(
384
+ self,
385
+ dim: int,
386
+ num_attention_heads: int,
387
+ attention_head_dim: int,
388
+ qk_norm: str = "rms_norm",
389
+ eps: float = 1e-6,
390
+ ):
391
+ super().__init__()
392
+
393
+ self.dim = dim
394
+ self.num_attention_heads = num_attention_heads
395
+ self.attention_head_dim = attention_head_dim
396
+
397
+ # Image processing modules
398
+ self.img_mod = nn.Sequential(
399
+ nn.SiLU(),
400
+ nn.Linear(dim, 6 * dim, bias=True), # For scale, shift, gate for norm1 and norm2
401
+ )
402
+ self.img_norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
403
+ self.attn = Attention(
404
+ query_dim=dim,
405
+ cross_attention_dim=None, # Enable cross attention for joint computation
406
+ added_kv_proj_dim=dim, # Enable added KV projections for text stream
407
+ dim_head=attention_head_dim,
408
+ heads=num_attention_heads,
409
+ out_dim=dim,
410
+ context_pre_only=False,
411
+ bias=True,
412
+ processor=QwenDoubleStreamAttnProcessor2_0(),
413
+ qk_norm=qk_norm,
414
+ eps=eps,
415
+ )
416
+ self.img_norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
417
+ self.img_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
418
+
419
+ # Text processing modules
420
+ self.txt_mod = nn.Sequential(
421
+ nn.SiLU(),
422
+ nn.Linear(dim, 6 * dim, bias=True), # For scale, shift, gate for norm1 and norm2
423
+ )
424
+ self.txt_norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
425
+ # Text doesn't need separate attention - it's handled by img_attn joint computation
426
+ self.txt_norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
427
+ self.txt_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
428
+
429
+ def _modulate(self, x, mod_params):
430
+ """Apply modulation to input tensor"""
431
+ shift, scale, gate = mod_params.chunk(3, dim=-1)
432
+ return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1), gate.unsqueeze(1)
433
+
434
+ def forward(
435
+ self,
436
+ hidden_states: torch.Tensor,
437
+ encoder_hidden_states: torch.Tensor,
438
+ encoder_hidden_states_mask: torch.Tensor,
439
+ temb: torch.Tensor,
440
+ image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
441
+ joint_attention_kwargs: Optional[Dict[str, Any]] = None,
442
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
443
+ # Get modulation parameters for both streams
444
+ img_mod_params = self.img_mod(temb) # [B, 6*dim]
445
+ txt_mod_params = self.txt_mod(temb) # [B, 6*dim]
446
+
447
+ # Split modulation parameters for norm1 and norm2
448
+ img_mod1, img_mod2 = img_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
449
+ txt_mod1, txt_mod2 = txt_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
450
+
451
+ # Process image stream - norm1 + modulation
452
+ img_normed = self.img_norm1(hidden_states)
453
+ img_modulated, img_gate1 = self._modulate(img_normed, img_mod1)
454
+
455
+ # Process text stream - norm1 + modulation
456
+ txt_normed = self.txt_norm1(encoder_hidden_states)
457
+ txt_modulated, txt_gate1 = self._modulate(txt_normed, txt_mod1)
458
+
459
+ # Use QwenAttnProcessor2_0 for joint attention computation
460
+ # This directly implements the DoubleStreamLayerMegatron logic:
461
+ # 1. Computes QKV for both streams
462
+ # 2. Applies QK normalization and RoPE
463
+ # 3. Concatenates and runs joint attention
464
+ # 4. Splits results back to separate streams
465
+ joint_attention_kwargs = joint_attention_kwargs or {}
466
+ attn_output = self.attn(
467
+ hidden_states=img_modulated, # Image stream (will be processed as "sample")
468
+ encoder_hidden_states=txt_modulated, # Text stream (will be processed as "context")
469
+ encoder_hidden_states_mask=encoder_hidden_states_mask,
470
+ image_rotary_emb=image_rotary_emb,
471
+ **joint_attention_kwargs,
472
+ )
473
+
474
+ # QwenAttnProcessor2_0 returns (img_output, txt_output) when encoder_hidden_states is provided
475
+ img_attn_output, txt_attn_output = attn_output
476
+
477
+ # Apply attention gates and add residual (like in Megatron)
478
+ hidden_states = hidden_states + img_gate1 * img_attn_output
479
+ encoder_hidden_states = encoder_hidden_states + txt_gate1 * txt_attn_output
480
+
481
+ # Process image stream - norm2 + MLP
482
+ img_normed2 = self.img_norm2(hidden_states)
483
+ img_modulated2, img_gate2 = self._modulate(img_normed2, img_mod2)
484
+
485
+ if joint_attention_kwargs.get("enable_lora", False) and isinstance(self.img_mlp.net[2], StyCtrlLoRA):
486
+ img_mlp_output = self.img_mlp(
487
+ img_modulated2,
488
+ enable_lora=True,
489
+ w=joint_attention_kwargs.get("w", None),
490
+ )
491
+ else:
492
+ img_mlp_output = self.img_mlp(img_modulated2)
493
+ hidden_states = hidden_states + img_gate2 * img_mlp_output
494
+
495
+ # Process text stream - norm2 + MLP
496
+ txt_normed2 = self.txt_norm2(encoder_hidden_states)
497
+ txt_modulated2, txt_gate2 = self._modulate(txt_normed2, txt_mod2)
498
+
499
+ if joint_attention_kwargs.get("enable_lora", False) and isinstance(self.txt_mlp.net[2], StyCtrlLoRA):
500
+ txt_mlp_output = self.txt_mlp(
501
+ txt_modulated2,
502
+ enable_lora=True,
503
+ w=joint_attention_kwargs.get("w", None),
504
+ )
505
+ else:
506
+ txt_mlp_output = self.txt_mlp(txt_modulated2)
507
+ encoder_hidden_states = encoder_hidden_states + txt_gate2 * txt_mlp_output
508
+
509
+ # Clip to prevent overflow for fp16
510
+ if encoder_hidden_states.dtype == torch.float16:
511
+ encoder_hidden_states = encoder_hidden_states.clip(-65504, 65504)
512
+ if hidden_states.dtype == torch.float16:
513
+ hidden_states = hidden_states.clip(-65504, 65504)
514
+
515
+ return encoder_hidden_states, hidden_states
516
+
517
+
518
+ class QwenImageTransformer2DModel(
519
+ ModelMixin, ConfigMixin, PeftAdapterMixin, FromOriginalModelMixin, CacheMixin, AttentionMixin
520
+ ):
521
+ """
522
+ The Transformer model introduced in Qwen.
523
+
524
+ Args:
525
+ patch_size (`int`, defaults to `2`):
526
+ Patch size to turn the input data into small patches.
527
+ in_channels (`int`, defaults to `64`):
528
+ The number of channels in the input.
529
+ out_channels (`int`, *optional*, defaults to `None`):
530
+ The number of channels in the output. If not specified, it defaults to `in_channels`.
531
+ num_layers (`int`, defaults to `60`):
532
+ The number of layers of dual stream DiT blocks to use.
533
+ attention_head_dim (`int`, defaults to `128`):
534
+ The number of dimensions to use for each attention head.
535
+ num_attention_heads (`int`, defaults to `24`):
536
+ The number of attention heads to use.
537
+ joint_attention_dim (`int`, defaults to `3584`):
538
+ The number of dimensions to use for the joint attention (embedding/channel dimension of
539
+ `encoder_hidden_states`).
540
+ guidance_embeds (`bool`, defaults to `False`):
541
+ Whether to use guidance embeddings for guidance-distilled variant of the model.
542
+ axes_dims_rope (`Tuple[int]`, defaults to `(16, 56, 56)`):
543
+ The dimensions to use for the rotary positional embeddings.
544
+ """
545
+
546
+ _supports_gradient_checkpointing = True
547
+ _no_split_modules = ["QwenImageTransformerBlock"]
548
+ _skip_layerwise_casting_patterns = ["pos_embed", "norm"]
549
+ _repeated_blocks = ["QwenImageTransformerBlock"]
550
+ _cp_plan = {
551
+ "": {
552
+ "hidden_states": ContextParallelInput(split_dim=1, expected_dims=3, split_output=False),
553
+ "encoder_hidden_states": ContextParallelInput(split_dim=1, expected_dims=3, split_output=False),
554
+ "encoder_hidden_states_mask": ContextParallelInput(split_dim=1, expected_dims=2, split_output=False),
555
+ },
556
+ "pos_embed": {
557
+ 0: ContextParallelInput(split_dim=0, expected_dims=2, split_output=True),
558
+ 1: ContextParallelInput(split_dim=0, expected_dims=2, split_output=True),
559
+ },
560
+ "proj_out": ContextParallelOutput(gather_dim=1, expected_dims=3),
561
+ }
562
+
563
+ @register_to_config
564
+ def __init__(
565
+ self,
566
+ patch_size: int = 2,
567
+ in_channels: int = 64,
568
+ out_channels: Optional[int] = 16,
569
+ num_layers: int = 60,
570
+ attention_head_dim: int = 128,
571
+ num_attention_heads: int = 24,
572
+ joint_attention_dim: int = 3584,
573
+ guidance_embeds: bool = False, # TODO: this should probably be removed
574
+ axes_dims_rope: Tuple[int, int, int] = (16, 56, 56),
575
+ ):
576
+ super().__init__()
577
+ self.out_channels = out_channels or in_channels
578
+ self.inner_dim = num_attention_heads * attention_head_dim
579
+
580
+ self.pos_embed = QwenEmbedRope(theta=10000, axes_dim=list(axes_dims_rope), scale_rope=True)
581
+
582
+ self.time_text_embed = QwenTimestepProjEmbeddings(embedding_dim=self.inner_dim)
583
+
584
+ self.txt_norm = RMSNorm(joint_attention_dim, eps=1e-6)
585
+
586
+ self.img_in = nn.Linear(in_channels, self.inner_dim)
587
+ self.txt_in = nn.Linear(joint_attention_dim, self.inner_dim)
588
+
589
+ self.transformer_blocks = nn.ModuleList(
590
+ [
591
+ QwenImageTransformerBlock(
592
+ dim=self.inner_dim,
593
+ num_attention_heads=num_attention_heads,
594
+ attention_head_dim=attention_head_dim,
595
+ )
596
+ for _ in range(num_layers)
597
+ ]
598
+ )
599
+
600
+ self.norm_out = AdaLayerNormContinuous(self.inner_dim, self.inner_dim, elementwise_affine=False, eps=1e-6)
601
+ self.proj_out = nn.Linear(self.inner_dim, patch_size * patch_size * self.out_channels, bias=True)
602
+
603
+ self.gradient_checkpointing = False
604
+
605
+ def forward(
606
+ self,
607
+ hidden_states: torch.Tensor,
608
+ encoder_hidden_states: torch.Tensor = None,
609
+ encoder_hidden_states_mask: torch.Tensor = None,
610
+ timestep: torch.LongTensor = None,
611
+ img_shapes: Optional[List[Tuple[int, int, int]]] = None,
612
+ txt_seq_lens: Optional[List[int]] = None,
613
+ guidance: torch.Tensor = None, # TODO: this should probably be removed
614
+ attention_kwargs: Optional[Dict[str, Any]] = None,
615
+ controlnet_block_samples=None,
616
+ return_dict: bool = True,
617
+ ) -> Union[torch.Tensor, Transformer2DModelOutput]:
618
+ """
619
+ The [`QwenTransformer2DModel`] forward method.
620
+
621
+ Args:
622
+ hidden_states (`torch.Tensor` of shape `(batch_size, image_sequence_length, in_channels)`):
623
+ Input `hidden_states`.
624
+ encoder_hidden_states (`torch.Tensor` of shape `(batch_size, text_sequence_length, joint_attention_dim)`):
625
+ Conditional embeddings (embeddings computed from the input conditions such as prompts) to use.
626
+ encoder_hidden_states_mask (`torch.Tensor` of shape `(batch_size, text_sequence_length)`):
627
+ Mask of the input conditions.
628
+ timestep ( `torch.LongTensor`):
629
+ Used to indicate denoising step.
630
+ attention_kwargs (`dict`, *optional*):
631
+ A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
632
+ `self.processor` in
633
+ [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
634
+ return_dict (`bool`, *optional*, defaults to `True`):
635
+ Whether or not to return a [`~models.transformer_2d.Transformer2DModelOutput`] instead of a plain
636
+ tuple.
637
+
638
+ Returns:
639
+ If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a
640
+ `tuple` where the first element is the sample tensor.
641
+ """
642
+ if attention_kwargs is not None:
643
+ attention_kwargs = attention_kwargs.copy()
644
+ lora_scale = attention_kwargs.pop("scale", 1.0)
645
+ else:
646
+ lora_scale = 1.0
647
+
648
+ if USE_PEFT_BACKEND:
649
+ # weight the lora layers by setting `lora_scale` for each PEFT layer
650
+ scale_lora_layers(self, lora_scale)
651
+ else:
652
+ if attention_kwargs is not None and attention_kwargs.get("scale", None) is not None:
653
+ logger.warning(
654
+ "Passing `scale` via `joint_attention_kwargs` when not using the PEFT backend is ineffective."
655
+ )
656
+
657
+ # Specify which layers need LoRA forward
658
+ attention_kwargs_list = [{}] * len(self.transformer_blocks)
659
+ lora_layer_indices = attention_kwargs.pop("lora_layer_indices", list(range(len(self.transformer_blocks))))
660
+ for i in lora_layer_indices:
661
+ attention_kwargs_list[i] = attention_kwargs
662
+
663
+ if isinstance(self.img_in, nn.Linear):
664
+ hidden_states = self.img_in(hidden_states)
665
+ else:
666
+ hidden_states = self.img_in(hidden_states, **attention_kwargs)
667
+
668
+ timestep = timestep.to(hidden_states.dtype)
669
+ encoder_hidden_states = self.txt_norm(encoder_hidden_states)
670
+ if isinstance(self.txt_in, nn.Linear):
671
+ encoder_hidden_states = self.txt_in(encoder_hidden_states)
672
+ else:
673
+ encoder_hidden_states = self.txt_in(encoder_hidden_states, **attention_kwargs)
674
+
675
+ if guidance is not None:
676
+ guidance = guidance.to(hidden_states.dtype) * 1000
677
+
678
+ temb = (
679
+ self.time_text_embed(timestep, hidden_states)
680
+ if guidance is None
681
+ else self.time_text_embed(timestep, guidance, hidden_states)
682
+ )
683
+
684
+ image_rotary_emb = self.pos_embed(img_shapes, txt_seq_lens, device=hidden_states.device)
685
+
686
+ for index_block, block in enumerate(self.transformer_blocks):
687
+ # Extract corresponding attention kwargs
688
+ attn_kwargs = attention_kwargs_list[index_block]
689
+ # print(f"{index_block=}, {attn_kwargs=}")
690
+ if torch.is_grad_enabled() and self.gradient_checkpointing:
691
+ encoder_hidden_states, hidden_states = self._gradient_checkpointing_func(
692
+ block,
693
+ hidden_states,
694
+ encoder_hidden_states,
695
+ encoder_hidden_states_mask,
696
+ temb,
697
+ image_rotary_emb,
698
+ )
699
+
700
+ else:
701
+ encoder_hidden_states, hidden_states = block(
702
+ hidden_states=hidden_states,
703
+ encoder_hidden_states=encoder_hidden_states,
704
+ encoder_hidden_states_mask=encoder_hidden_states_mask,
705
+ temb=temb,
706
+ image_rotary_emb=image_rotary_emb,
707
+ joint_attention_kwargs=attn_kwargs,
708
+ )
709
+
710
+ # controlnet residual
711
+ if controlnet_block_samples is not None:
712
+ interval_control = len(self.transformer_blocks) / len(controlnet_block_samples)
713
+ interval_control = int(np.ceil(interval_control))
714
+ hidden_states = hidden_states + controlnet_block_samples[index_block // interval_control]
715
+
716
+ # Use only the image part (hidden_states) from the dual-stream blocks
717
+ hidden_states = self.norm_out(hidden_states, temb)
718
+
719
+ if isinstance(self.proj_out, nn.Linear):
720
+ output = self.proj_out(hidden_states)
721
+ else:
722
+ output = self.proj_out(hidden_states, **attention_kwargs)
723
+
724
+ if USE_PEFT_BACKEND:
725
+ # remove `lora_scale` from each PEFT layer
726
+ unscale_lora_layers(self, lora_scale)
727
+
728
+ if not return_dict:
729
+ return (output,)
730
+
731
+ return Transformer2DModelOutput(sample=output)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ diffusers>=0.36.0
2
+ transformers>=4.0.0
3
+ accelerate>=1.12.0
4
+ safetensors>=0.7.0
5
+ scipy>=1.10.0
6
+ sentencepiece
7
+ einops
8
+ torchvision
utils.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import numpy as np
4
+
5
+ from scipy.interpolate import make_interp_spline
6
+ from safetensors.torch import load_file
7
+
8
+
9
+ def freeze_parameters(module: torch.nn.Module):
10
+ for p in module.parameters():
11
+ p.requires_grad_(False)
12
+ module.eval()
13
+
14
+
15
+ def summarize_model(model):
16
+ # Calculate parameters
17
+ trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
18
+ total_params = sum(p.numel() for p in model.parameters())
19
+
20
+ # Calculate size on disk/memory (assuming float32 = 4 bytes)
21
+ param_size = 0
22
+ for param in model.parameters():
23
+ param_size += param.nelement() * param.element_size()
24
+
25
+ buffer_size = 0
26
+ for buffer in model.buffers():
27
+ buffer_size += buffer.nelement() * buffer.element_size()
28
+
29
+ size_all_mb = (param_size + buffer_size) / 1024**2
30
+ return {
31
+ "total_params": total_params,
32
+ "trainable_params": trainable_params,
33
+ "trainable_ratio(%)": trainable_params / total_params,
34
+ "model_size_mb(MB)": size_all_mb,
35
+ }
36
+
37
+
38
+ def lora_scale_states(
39
+ anchor_states: list[str],
40
+ query_point: float,
41
+ device: torch.device,
42
+ dtype: torch.dtype,
43
+ ):
44
+ state = load_file(anchor_states[0])
45
+ new_state = {}
46
+ for k, p in state.items():
47
+ if "projector.weight" in k:
48
+ new_state[k] = torch.full_like(p, fill_value=2 * query_point, device=device, dtype=dtype)
49
+ elif "projector.bias" in k:
50
+ new_state[k] = torch.zeros_like(p, device=device, dtype=dtype)
51
+ return new_state
52
+
53
+
54
+ def neighbor_interpolate_states(
55
+ anchor_states: list[str],
56
+ query_point: float,
57
+ device: torch.device,
58
+ dtype: torch.dtype,
59
+ ):
60
+ num_levels = len(anchor_states)
61
+ level = math.floor(query_point * num_levels)
62
+
63
+ interp_state = {}
64
+ if level == 0:
65
+ state = load_file(anchor_states[0])
66
+ w = query_point * num_levels - level
67
+ for k, p in state.items():
68
+ interp_state[k] = (w * p).to(device=device, dtype=dtype)
69
+ elif level == num_levels:
70
+ interp_state = load_file(anchor_states[-1])
71
+ for k, p in interp_state.items():
72
+ interp_state[k] = p.to(device=device, dtype=dtype)
73
+ else:
74
+ state1 = load_file(anchor_states[level - 1])
75
+ state2 = load_file(anchor_states[level])
76
+ w = query_point * num_levels - level
77
+ for k, p1 in state1.items():
78
+ p2 = state2[k]
79
+ interp_state[k] = ((1 - w) * p1 + w * p2).to(device=device, dtype=dtype)
80
+ return interp_state
81
+
82
+
83
+ def bspline_interpolate_states(
84
+ anchor_states: list[str],
85
+ query_point: float,
86
+ X,
87
+ device: torch.device,
88
+ dtype: torch.dtype,
89
+ order=3,
90
+ ):
91
+ states = load_file(anchor_states[0])
92
+ states = {k: [p] for k, p in states.items()}
93
+ for sp in anchor_states[1:]:
94
+ s = load_file(sp)
95
+ for k, p in s.items():
96
+ states[k].append(p)
97
+
98
+ interp_state = {}
99
+ # X = [0.2, 0.4, 0.6, 0.8, 1.0]
100
+ for k, pl in states.items():
101
+ Y = torch.stack(pl, dim=0).to(torch.float32).numpy()
102
+ bsp = make_interp_spline(X, Y, k=order)
103
+ y = torch.from_numpy(bsp(np.array(query_point))).to(device=device, dtype=dtype)
104
+ interp_state[k] = y
105
+ return interp_state