Professional Noob commited on
Commit
c16eecf
·
verified ·
1 Parent(s): 4d7813b

Update qwenimage/pipeline_qwenimage_edit_plus.py

Browse files
qwenimage/pipeline_qwenimage_edit_plus.py CHANGED
@@ -1,607 +1,828 @@
1
- model_cpu_offload_seq = "text_encoder->transformer->vae"
2
- _callback_tensor_inputs = ["latents", "prompt_embeds"]
3
-
4
- def __init__(
5
- self,
6
- scheduler: FlowMatchEulerDiscreteScheduler,
7
- vae: AutoencoderKLQwenImage,
8
- text_encoder: Qwen2_5_VLForConditionalGeneration,
9
- tokenizer: Qwen2Tokenizer,
10
- processor: Qwen2VLProcessor,
11
- transformer: QwenImageTransformer2DModel,
12
- ):
13
- super().__init__()
14
- self.register_modules(
15
- vae=vae,
16
- text_encoder=text_encoder,
17
- tokenizer=tokenizer,
18
- processor=processor,
19
- transformer=transformer,
20
- scheduler=scheduler,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  )
22
 
23
- self.vae_scale_factor = 2 ** len(self.vae.temperal_downsample) if getattr(self, "vae", None) else 8
24
- self.latent_channels = self.vae.config.z_dim if getattr(self, "vae", None) else 16
25
 
26
- # QwenImage latents are packed as 2x2 patches => multiply by patch size
27
- self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor * 2)
28
- self.tokenizer_max_length = 1024
 
29
 
30
- self.prompt_template_encode = (
31
- "<|im_start|>system\n"
32
- "Describe the key features of the input image (color, shape, size, texture, objects, background), "
33
- "then explain how the user's text instruction should alter or modify the image.\n"
34
- "Generate a new image that meets the user's requirements while maintaining consistency with the original input where appropriate."
35
- "<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n"
36
- )
37
- self.prompt_template_encode_start_idx = 64
38
- self.default_sample_size = 128
39
-
40
- # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.QwenImagePipeline._extract_masked_hidden
41
- def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
42
- bool_mask = mask.bool()
43
- valid_lengths = bool_mask.sum(dim=1)
44
- selected = hidden_states[bool_mask]
45
- split_result = torch.split(selected, valid_lengths.tolist(), dim=0)
46
- return split_result
47
-
48
- def _get_qwen_prompt_embeds(
49
- self,
50
- prompt: Union[str, List[str]] = None,
51
- image: Optional[torch.Tensor] = None,
52
- device: Optional[torch.device] = None,
53
- dtype: Optional[torch.dtype] = None,
54
  ):
55
- device = device or self._execution_device
56
- dtype = dtype or self.text_encoder.dtype
57
-
58
- prompt = [prompt] if isinstance(prompt, str) else prompt
59
- img_prompt_template = "Picture {}: <|vision_start|><|image_pad|><|vision_end|>"
60
-
61
- if isinstance(image, list):
62
- base_img_prompt = ""
63
- for i, _ in enumerate(image):
64
- base_img_prompt += img_prompt_template.format(i + 1)
65
- elif image is not None:
66
- base_img_prompt = img_prompt_template.format(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  else:
68
- base_img_prompt = ""
69
-
70
- template = self.prompt_template_encode
71
- drop_idx = self.prompt_template_encode_start_idx
72
- txt = [template.format(base_img_prompt + e) for e in prompt]
73
-
74
- model_inputs = self.processor(
75
- text=txt,
76
- images=image,
77
- padding=True,
78
- return_tensors="pt",
79
- ).to(device)
80
-
81
- outputs = self.text_encoder(
82
- input_ids=model_inputs.input_ids,
83
- attention_mask=model_inputs.attention_mask,
84
- pixel_values=model_inputs.pixel_values,
85
- image_grid_thw=model_inputs.image_grid_thw,
86
- output_hidden_states=True,
87
- )
88
 
89
- hidden_states = outputs.hidden_states[-1]
90
- split_hidden_states = self._extract_masked_hidden(hidden_states, model_inputs.attention_mask)
91
- split_hidden_states = [e[drop_idx:] for e in split_hidden_states]
92
 
93
- attn_mask_list = [torch.ones(e.size(0), dtype=torch.long, device=e.device) for e in split_hidden_states]
94
- max_seq_len = max([e.size(0) for e in split_hidden_states])
95
 
96
- prompt_embeds = torch.stack(
97
- [torch.cat([u, u.new_zeros(max_seq_len - u.size(0), u.size(1))]) for u in split_hidden_states]
98
- )
99
- encoder_attention_mask = torch.stack([torch.cat([u, u.new_zeros(max_seq_len - u.size(0))]) for u in attn_mask_list])
100
- prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
101
- return prompt_embeds, encoder_attention_mask
102
-
103
- # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline.encode_prompt
104
- def encode_prompt(
105
- self,
106
- prompt: Union[str, List[str]],
107
- image: Optional[torch.Tensor] = None,
108
- device: Optional[torch.device] = None,
109
- num_images_per_prompt: int = 1,
110
- prompt_embeds: Optional[torch.Tensor] = None,
111
- prompt_embeds_mask: Optional[torch.Tensor] = None,
112
- max_sequence_length: int = 1024,
113
- ):
114
- device = device or self._execution_device
115
- prompt = [prompt] if isinstance(prompt, str) else prompt
116
- batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
117
-
118
- if prompt_embeds is None:
119
- prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt, image, device)
120
-
121
- _, seq_len, _ = prompt_embeds.shape
122
-
123
- prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
124
- prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
125
- prompt_embeds_mask = prompt_embeds_mask.repeat(1, num_images_per_prompt, 1)
126
- prompt_embeds_mask = prompt_embeds_mask.view(batch_size * num_images_per_prompt, seq_len)
127
-
128
- return prompt_embeds, prompt_embeds_mask
129
-
130
- # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline.check_inputs
131
- def check_inputs(
132
- self,
133
- prompt,
134
- height,
135
- width,
136
- negative_prompt=None,
137
- prompt_embeds=None,
138
- negative_prompt_embeds=None,
139
- prompt_embeds_mask=None,
140
- negative_prompt_embeds_mask=None,
141
- callback_on_step_end_tensor_inputs=None,
142
- max_sequence_length=None,
143
- ):
144
- if height % (self.vae_scale_factor * 2) != 0 or width % (self.vae_scale_factor * 2) != 0:
145
- logger.warning(
146
- f"`height` and `width` have to be divisible by {self.vae_scale_factor * 2} but are {height} and {width}. "
147
- "Dimensions will be resized accordingly."
148
- )
149
 
150
- if callback_on_step_end_tensor_inputs is not None and not all(
151
- k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  ):
153
- raise ValueError(
154
- f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found "
155
- f"{[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
 
 
 
 
 
156
  )
157
 
158
- if prompt is not None and prompt_embeds is not None:
159
- raise ValueError("Cannot forward both `prompt` and `prompt_embeds`.")
160
- elif prompt is None and prompt_embeds is None:
161
- raise ValueError("Provide either `prompt` or `prompt_embeds`.")
162
- elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
163
- raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
164
-
165
- if negative_prompt is not None and negative_prompt_embeds is not None:
166
- raise ValueError("Cannot forward both `negative_prompt` and `negative_prompt_embeds`.")
167
-
168
- if prompt_embeds is not None and prompt_embeds_mask is None:
169
- raise ValueError("If `prompt_embeds` are provided, `prompt_embeds_mask` also have to be passed.")
170
-
171
- if negative_prompt_embeds is not None and negative_prompt_embeds_mask is None:
172
- raise ValueError("If `negative_prompt_embeds` are provided, `negative_prompt_embeds_mask` also have to be passed.")
173
-
174
- if max_sequence_length is not None and max_sequence_length > 1024:
175
- raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
176
-
177
- @staticmethod
178
- def _pack_latents(latents, batch_size, num_channels_latents, height, width):
179
- latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
180
- latents = latents.permute(0, 2, 4, 1, 3, 5)
181
- latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4)
182
- return latents
183
-
184
- @staticmethod
185
- def _unpack_latents(latents, height, width, vae_scale_factor):
186
- batch_size, _, channels = latents.shape
187
- height = 2 * (int(height) // (vae_scale_factor * 2))
188
- width = 2 * (int(width) // (vae_scale_factor * 2))
189
- latents = latents.view(batch_size, height // 2, width // 2, channels // 4, 2, 2)
190
- latents = latents.permute(0, 3, 1, 4, 2, 5)
191
- latents = latents.reshape(batch_size, channels // 4, 1, height, width)
192
- return latents
193
-
194
- def _encode_vae_image(self, image: torch.Tensor, generator: torch.Generator):
195
- if isinstance(generator, list):
196
- image_latents = [
197
- retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i], sample_mode="argmax")
198
- for i in range(image.shape[0])
199
- ]
200
- image_latents = torch.cat(image_latents, dim=0)
201
- else:
202
- image_latents = retrieve_latents(self.vae.encode(image), generator=generator, sample_mode="argmax")
203
 
204
- latents_mean = torch.tensor(self.vae.config.latents_mean).view(1, self.latent_channels, 1, 1, 1).to(
205
- image_latents.device, image_latents.dtype
206
- )
207
- latents_std = torch.tensor(self.vae.config.latents_std).view(1, self.latent_channels, 1, 1, 1).to(
208
- image_latents.device, image_latents.dtype
209
- )
210
- image_latents = (image_latents - latents_mean) / latents_std
211
- return image_latents
212
-
213
- def prepare_latents(
214
- self,
215
- images,
216
- batch_size,
217
- num_channels_latents,
218
- height,
219
- width,
220
- dtype,
221
- device,
222
- generator,
223
- latents=None,
224
- ):
225
- height = 2 * (int(height) // (self.vae_scale_factor * 2))
226
- width = 2 * (int(width) // (self.vae_scale_factor * 2))
227
- shape = (batch_size, 1, num_channels_latents, height, width)
228
-
229
- image_latents = None
230
- if images is not None:
231
- if not isinstance(images, list):
232
- images = [images]
233
- all_image_latents = []
234
- for image in images:
235
- image = image.to(device=device, dtype=dtype)
236
- if image.shape[1] != self.latent_channels:
237
- image_latents = self._encode_vae_image(image=image, generator=generator)
238
- else:
239
- image_latents = image
240
-
241
- if batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] == 0:
242
- additional_image_per_prompt = batch_size // image_latents.shape[0]
243
- image_latents = torch.cat([image_latents] * additional_image_per_prompt, dim=0)
244
- elif batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] != 0:
245
- raise ValueError(
246
- f"Cannot duplicate `image` of batch size {image_latents.shape[0]} to {batch_size} text prompts."
247
- )
248
 
249
- image_latent_height, image_latent_width = image_latents.shape[3:]
250
- image_latents = self._pack_latents(
251
- image_latents, batch_size, num_channels_latents, image_latent_height, image_latent_width
252
- )
253
- all_image_latents.append(image_latents)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- image_latents = torch.cat(all_image_latents, dim=1)
 
256
 
257
- if isinstance(generator, list) and len(generator) != batch_size:
258
- raise ValueError(
259
- f"You passed a list of generators of length {len(generator)}, but requested an effective batch size of {batch_size}."
 
 
260
  )
261
 
262
- if latents is None:
263
- latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
264
- latents = self._pack_latents(latents, batch_size, num_channels_latents, height, width)
265
- else:
266
- latents = latents.to(device=device, dtype=dtype)
267
-
268
- return latents, image_latents
269
-
270
- @property
271
- def guidance_scale(self):
272
- return self._guidance_scale
273
-
274
- @property
275
- def attention_kwargs(self):
276
- return self._attention_kwargs
277
-
278
- @property
279
- def num_timesteps(self):
280
- return self._num_timesteps
281
-
282
- @property
283
- def current_timestep(self):
284
- return self._current_timestep
285
-
286
- @property
287
- def interrupt(self):
288
- return self._interrupt
289
-
290
- @torch.no_grad()
291
- @replace_example_docstring(EXAMPLE_DOC_STRING)
292
- def __call__(
293
- self,
294
- image: Optional[PipelineImageInput] = None,
295
- prompt: Union[str, List[str]] = None,
296
- negative_prompt: Union[str, List[str]] = None,
297
- true_cfg_scale: float = 4.0,
298
- height: Optional[int] = None,
299
- width: Optional[int] = None,
300
- condition_area: Optional[int] = None,
301
- vae_image_indices: Optional[List[int]] = None,
302
- pad_to_canvas: bool = True,
303
- # NEW:
304
- resolution_multiple: Optional[int] = None,
305
- vae_ref_area: Optional[int] = None,
306
- vae_ref_start_index: int = 2,
307
- num_inference_steps: int = 50,
308
- sigmas: Optional[List[float]] = None,
309
- guidance_scale: Optional[float] = None,
310
- num_images_per_prompt: int = 1,
311
- generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
312
- latents: Optional[torch.Tensor] = None,
313
- prompt_embeds: Optional[torch.Tensor] = None,
314
- prompt_embeds_mask: Optional[torch.Tensor] = None,
315
- negative_prompt_embeds: Optional[torch.Tensor] = None,
316
- negative_prompt_embeds_mask: Optional[torch.Tensor] = None,
317
- output_type: Optional[str] = "pil",
318
- return_dict: bool = True,
319
- attention_kwargs: Optional[Dict[str, Any]] = None,
320
- callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
321
- callback_on_step_end_tensor_inputs: List[str] = ["latents"],
322
- max_sequence_length: int = 512,
323
- ):
324
- image_size = image[0].size if isinstance(image, list) else image.size
325
 
326
- multiple_of = int(resolution_multiple) if resolution_multiple is not None else int(self.vae_scale_factor * 2)
327
- multiple_of = max(1, multiple_of)
328
 
329
- calculated_width, calculated_height = calculate_dimensions(
330
- 1024 * 1024, image_size[0] / image_size[1], multiple=multiple_of
331
- )
332
- height = height or calculated_height
333
- width = width or calculated_width
334
 
335
- width = (int(width) // multiple_of) * multiple_of
336
- height = (int(height) // multiple_of) * multiple_of
337
 
338
- # 1. Check inputs
339
- self.check_inputs(
 
 
 
 
 
 
340
  prompt,
341
  height,
342
  width,
343
- negative_prompt=negative_prompt,
344
- prompt_embeds=prompt_embeds,
345
- negative_prompt_embeds=negative_prompt_embeds,
346
- prompt_embeds_mask=prompt_embeds_mask,
347
- negative_prompt_embeds_mask=negative_prompt_embeds_mask,
348
- callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
349
- max_sequence_length=max_sequence_length,
350
- )
351
-
352
- self._guidance_scale = guidance_scale
353
- self._attention_kwargs = attention_kwargs
354
- self._current_timestep = None
355
- self._interrupt = False
356
 
357
- # 2. Define call parameters
358
- if prompt is not None and isinstance(prompt, str):
359
- batch_size = 1
360
- elif prompt is not None and isinstance(prompt, list):
361
- batch_size = len(prompt)
362
- else:
363
- batch_size = prompt_embeds.shape[0]
364
-
365
- device = self._execution_device
366
-
367
- # 3. Preprocess image
368
- if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == self.latent_channels):
369
- if not isinstance(image, list):
370
- image = [image]
371
-
372
- canvas_area = int(width) * int(height)
373
- cond_area = int(condition_area) if condition_area is not None else choose_condition_area(canvas_area)
374
-
375
- cond_w, cond_h = calculate_dimensions(cond_area, float(width) / float(height), multiple=multiple_of)
376
-
377
- # Optional VAE ref override (for extra refs)
378
- ref_w = ref_h = None
379
- if vae_ref_area is not None:
380
- try:
381
- ref_w, ref_h = calculate_dimensions(int(vae_ref_area), float(width) / float(height), multiple=multiple_of)
382
- except Exception:
383
- ref_w = ref_h = None
384
-
385
- condition_images = []
386
- vae_images = []
387
- vae_image_sizes = []
388
-
389
- if vae_image_indices is None:
390
- vae_image_indices = list(range(len(image)))
391
- vae_set = set(int(i) for i in vae_image_indices)
392
-
393
- for idx, img in enumerate(image):
394
- pil = img.convert("RGB") if isinstance(img, Image.Image) else img
395
-
396
- if pad_to_canvas and isinstance(pil, Image.Image):
397
- pil = pad_to_aspect(pil, int(width), int(height))
398
-
399
- # Conditioning path: always
400
- condition_images.append(self.image_processor.resize(pil, cond_h, cond_w))
401
-
402
- # VAE path: selected indices only
403
- if idx in vae_set:
404
- if (
405
- ref_w is not None
406
- and ref_h is not None
407
- and vae_ref_area is not None
408
- and int(idx) >= int(vae_ref_start_index)
409
- ):
410
- vw, vh = int(ref_w), int(ref_h)
411
- else:
412
- vw, vh = int(width), int(height)
413
 
414
- vae_image_sizes.append((vw, vh))
415
- vae_images.append(self.image_processor.preprocess(pil, int(vh), int(vw)).unsqueeze(2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
- has_neg_prompt = negative_prompt is not None or (
418
- negative_prompt_embeds is not None and negative_prompt_embeds_mask is not None
 
 
 
419
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
- if true_cfg_scale > 1 and not has_neg_prompt:
422
- logger.warning(
423
- f"true_cfg_scale is passed as {true_cfg_scale}, but classifier-free guidance is not enabled since no negative_prompt is provided."
 
 
424
  )
425
- elif true_cfg_scale <= 1 and has_neg_prompt:
426
- logger.warning("negative_prompt is passed but classifier-free guidance is not enabled since true_cfg_scale <= 1")
427
 
428
- do_true_cfg = true_cfg_scale > 1 and has_neg_prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
 
430
- prompt_embeds, prompt_embeds_mask = self.encode_prompt(
431
- image=condition_images,
432
- prompt=prompt,
 
 
 
433
  prompt_embeds=prompt_embeds,
 
434
  prompt_embeds_mask=prompt_embeds_mask,
435
- device=device,
436
- num_images_per_prompt=num_images_per_prompt,
437
  max_sequence_length=max_sequence_length,
438
  )
439
 
440
- if do_true_cfg:
441
- negative_prompt_embeds, negative_prompt_embeds_mask = self.encode_prompt(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  image=condition_images,
443
- prompt=negative_prompt,
444
- prompt_embeds=negative_prompt_embeds,
445
- prompt_embeds_mask=negative_prompt_embeds_mask,
446
  device=device,
447
  num_images_per_prompt=num_images_per_prompt,
448
  max_sequence_length=max_sequence_length,
449
  )
450
 
451
- # 4. Prepare latent variables
452
- num_channels_latents = self.transformer.config.in_channels // 4
453
- latents, image_latents = self.prepare_latents(
454
- vae_images,
455
- batch_size * num_images_per_prompt,
456
- num_channels_latents,
457
- height,
458
- width,
459
- prompt_embeds.dtype,
460
- device,
461
- generator,
462
- latents,
463
- )
464
-
465
- img_shapes = [
466
- [
467
- (1, height // self.vae_scale_factor // 2, width // self.vae_scale_factor // 2),
468
- *[
469
- (1, vae_h // self.vae_scale_factor // 2, vae_w // self.vae_scale_factor // 2)
470
- for (vae_w, vae_h) in vae_image_sizes
471
- ],
472
- ]
473
- ] * batch_size
474
-
475
- # 5. Prepare timesteps
476
- sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) if sigmas is None else sigmas
477
-
478
- image_seq_len = latents.shape[1]
479
- mu = calculate_shift(
480
- image_seq_len,
481
- self.scheduler.config.get("base_image_seq_len", 256),
482
- self.scheduler.config.get("max_image_seq_len", 4096),
483
- self.scheduler.config.get("base_shift", 0.5),
484
- self.scheduler.config.get("max_shift", 1.15),
485
- )
486
- timesteps, num_inference_steps = retrieve_timesteps(
487
- self.scheduler, num_inference_steps, device, sigmas=sigmas, mu=mu
488
- )
489
 
490
- num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
491
- self._num_timesteps = len(timesteps)
492
-
493
- # handle guidance
494
- if self.transformer.config.guidance_embeds and guidance_scale is None:
495
- raise ValueError("guidance_scale is required for guidance-distilled model.")
496
- elif self.transformer.config.guidance_embeds:
497
- guidance = torch.full([1], guidance_scale, device=device, dtype=torch.float32).expand(latents.shape[0])
498
- elif not self.transformer.config.guidance_embeds and guidance_scale is not None:
499
- logger.warning(
500
- f"guidance_scale is passed as {guidance_scale}, but ignored since the model is not guidance-distilled."
501
- )
502
- guidance = None
503
- else:
504
- guidance = None
505
 
506
- if self.attention_kwargs is None:
507
- self._attention_kwargs = {}
 
 
 
 
 
 
 
 
 
 
 
 
508
 
509
- txt_seq_lens = prompt_embeds_mask.sum(dim=1).tolist() if prompt_embeds_mask is not None else None
510
- image_rotary_emb = self.transformer.pos_embed(img_shapes, txt_seq_lens, device=latents.device)
511
 
512
- if do_true_cfg:
513
- negative_txt_seq_lens = (
514
- negative_prompt_embeds_mask.sum(dim=1).tolist() if negative_prompt_embeds_mask is not None else None
 
 
 
 
 
 
 
515
  )
516
- uncond_image_rotary_emb = self.transformer.pos_embed(img_shapes, negative_txt_seq_lens, device=latents.device)
517
- else:
518
- uncond_image_rotary_emb = None
519
-
520
- # 6. Denoising loop
521
- self.scheduler.set_begin_index(0)
522
- with self.progress_bar(total=num_inference_steps) as progress_bar:
523
- for i, t in enumerate(timesteps):
524
- if self.interrupt:
525
- continue
526
- self._current_timestep = t
527
-
528
- latent_model_input = latents
529
- if image_latents is not None:
530
- latent_model_input = torch.cat([latents, image_latents], dim=1)
531
-
532
- timestep = t.expand(latents.shape[0]).to(latents.dtype)
533
-
534
- with self.transformer.cache_context("cond"):
535
- noise_pred = self.transformer(
536
- hidden_states=latent_model_input,
537
- timestep=timestep / 1000,
538
- guidance=guidance,
539
- encoder_hidden_states_mask=prompt_embeds_mask,
540
- encoder_hidden_states=prompt_embeds,
541
- image_rotary_emb=image_rotary_emb,
542
- attention_kwargs=self.attention_kwargs,
543
- return_dict=False,
544
- )[0]
545
- noise_pred = noise_pred[:, : latents.size(1)]
546
 
547
- if do_true_cfg:
548
- with self.transformer.cache_context("uncond"):
549
- neg_noise_pred = self.transformer(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  hidden_states=latent_model_input,
551
  timestep=timestep / 1000,
552
  guidance=guidance,
553
- encoder_hidden_states_mask=negative_prompt_embeds_mask,
554
- encoder_hidden_states=negative_prompt_embeds,
555
- image_rotary_emb=uncond_image_rotary_emb,
556
  attention_kwargs=self.attention_kwargs,
557
  return_dict=False,
558
  )[0]
559
- neg_noise_pred = neg_noise_pred[:, : latents.size(1)]
560
-
561
- comb_pred = neg_noise_pred + true_cfg_scale * (noise_pred - neg_noise_pred)
562
- cond_norm = torch.norm(noise_pred, dim=-1, keepdim=True)
563
- noise_norm = torch.norm(comb_pred, dim=-1, keepdim=True)
564
- noise_pred = comb_pred * (cond_norm / noise_norm)
565
-
566
- latents_dtype = latents.dtype
567
- latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
568
- if latents.dtype != latents_dtype:
569
- if torch.backends.mps.is_available():
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  latents = latents.to(latents_dtype)
571
 
572
- if callback_on_step_end is not None:
573
- callback_kwargs = {k: locals()[k] for k in callback_on_step_end_tensor_inputs}
574
- callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
575
- latents = callback_outputs.pop("latents", latents)
576
- prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
577
 
578
- if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
579
- progress_bar.update()
580
 
581
- if XLA_AVAILABLE:
582
- xm.mark_step()
583
 
584
- self._current_timestep = None
585
 
586
- if output_type == "latent":
587
- image = latents
588
- else:
589
- latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
590
- latents = latents.to(self.vae.dtype)
 
591
 
592
- latents_mean = torch.tensor(self.vae.config.latents_mean).view(1, self.vae.config.z_dim, 1, 1, 1).to(
593
- latents.device, latents.dtype
594
- )
595
- latents_std = 1.0 / torch.tensor(self.vae.config.latents_std).view(1, self.vae.config.z_dim, 1, 1, 1).to(
596
- latents.device, latents.dtype
597
- )
598
- latents = latents / latents_std + latents_mean
 
 
 
 
 
 
 
 
 
 
 
599
 
600
- image = self.vae.decode(latents, return_dict=False)[0][:, :, 0]
601
- image = self.image_processor.postprocess(image, output_type=output_type)
602
 
603
- self.maybe_free_model_hooks()
604
 
605
- if not return_dict:
606
- return (image,)
607
- return QwenImagePipelineOutput(images=image)
 
1
+ from __future__ import annotations
2
+
3
+ # Copyright 2025 Qwen-Image Team and The HuggingFace Team. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import inspect
18
+ import math
19
+ from typing import Any, Callable, Dict, List, Optional, Union
20
+
21
+ import numpy as np
22
+ import torch
23
+ import torch.nn.functional as F
24
+ from PIL import Image, ImageOps
25
+ from transformers import Qwen2_5_VLForConditionalGeneration, Qwen2Tokenizer, Qwen2VLProcessor
26
+
27
+ from diffusers.image_processor import PipelineImageInput, VaeImageProcessor
28
+ from diffusers.loaders import QwenImageLoraLoaderMixin
29
+ from diffusers.models import AutoencoderKLQwenImage, QwenImageTransformer2DModel
30
+ from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
31
+ from diffusers.utils import is_torch_xla_available, logging, replace_example_docstring
32
+ from diffusers.utils.torch_utils import randn_tensor
33
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
34
+ from diffusers.pipelines.qwenimage.pipeline_output import QwenImagePipelineOutput
35
+
36
+ if is_torch_xla_available():
37
+ import torch_xla.core.xla_model as xm
38
+
39
+ XLA_AVAILABLE = True
40
+ else:
41
+ XLA_AVAILABLE = False
42
+
43
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
44
+
45
+ EXAMPLE_DOC_STRING = """
46
+ Examples:
47
+ ```py
48
+ >>> import torch
49
+ >>> from diffusers import QwenImageEditPlusPipeline
50
+ >>> from diffusers.utils import load_image
51
+
52
+ >>> pipe = QwenImageEditPlusPipeline.from_pretrained(
53
+ ... "Qwen/Qwen-Image-Edit-2509", torch_dtype=torch.bfloat16
54
+ ... ).to("cuda")
55
+
56
+ >>> image = load_image(
57
+ ... "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/yarn-art-pikachu.png"
58
+ ... ).convert("RGB")
59
+
60
+ >>> prompt = "Make Pikachu hold a sign that says 'Qwen Edit is awesome', yarn art style, detailed, vibrant colors"
61
+
62
+ >>> out = pipe(image=image, prompt=prompt, num_inference_steps=50).images[0]
63
+ >>> out.save("qwenimage_edit_plus.png")
64
+ ```
65
+ """
66
+
67
+ CONDITION_IMAGE_SIZE = 384 * 384
68
+ VAE_IMAGE_SIZE = 1024 * 1024
69
+
70
+
71
+ def pad_to_aspect(img: Image.Image, target_w: int, target_h: int) -> Image.Image:
72
+ """Pad (letterbox) to target aspect ratio without warping."""
73
+ return ImageOps.pad(
74
+ img.convert("RGB"),
75
+ (int(target_w), int(target_h)),
76
+ method=Image.Resampling.LANCZOS,
77
+ color=(0, 0, 0),
78
+ centering=(0.5, 0.5),
79
  )
80
 
 
 
81
 
82
+ def choose_condition_area(canvas_area: int, base_area: int = CONDITION_IMAGE_SIZE) -> int:
83
+ """Choose a conditioning target area derived from canvas area with sensible bounds."""
84
+ scaled = int(canvas_area * (base_area / (1024 * 1024)))
85
+ return int(min(base_area, max(256 * 256, scaled)))
86
 
87
+
88
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.calculate_shift
89
+ def calculate_shift(
90
+ image_seq_len,
91
+ base_seq_len: int = 256,
92
+ max_seq_len: int = 4096,
93
+ base_shift: float = 0.5,
94
+ max_shift: float = 1.15,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  ):
96
+ m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
97
+ b = base_shift - m * base_seq_len
98
+ mu = image_seq_len * m + b
99
+ return mu
100
+
101
+
102
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
103
+ def retrieve_timesteps(
104
+ scheduler,
105
+ num_inference_steps: Optional[int] = None,
106
+ device: Optional[Union[str, torch.device]] = None,
107
+ timesteps: Optional[List[int]] = None,
108
+ sigmas: Optional[List[float]] = None,
109
+ **kwargs,
110
+ ):
111
+ if timesteps is not None and sigmas is not None:
112
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one.")
113
+
114
+ if timesteps is not None:
115
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
116
+ if not accepts_timesteps:
117
+ raise ValueError(
118
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom timesteps."
119
+ )
120
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
121
+ timesteps = scheduler.timesteps
122
+ num_inference_steps = len(timesteps)
123
+
124
+ elif sigmas is not None:
125
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
126
+ if not accept_sigmas:
127
+ raise ValueError(
128
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom sigmas."
129
+ )
130
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
131
+ timesteps = scheduler.timesteps
132
+ num_inference_steps = len(timesteps)
133
+
134
  else:
135
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
136
+ timesteps = scheduler.timesteps
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ return timesteps, num_inference_steps
 
 
139
 
 
 
140
 
141
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
142
+ def retrieve_latents(encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"):
143
+ if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
144
+ return encoder_output.latent_dist.sample(generator)
145
+ if hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
146
+ return encoder_output.latent_dist.mode()
147
+ if hasattr(encoder_output, "latents"):
148
+ return encoder_output.latents
149
+ raise AttributeError("Could not access latents of provided encoder_output")
150
+
151
+
152
+ def calculate_dimensions(target_area: int, ratio: float, multiple: int = 32):
153
+ """
154
+ Area-based sizing while snapping to a chosen lattice multiple.
155
+ Used for canvas sizing AND conditioning sizing (anti-drift).
156
+ """
157
+ m = int(multiple) if multiple else 32
158
+ m = max(1, m)
159
+
160
+ width = math.sqrt(float(target_area) * float(ratio))
161
+ height = width / float(ratio)
162
+
163
+ width = round(width / m) * m
164
+ height = round(height / m) * m
165
+ return int(width), int(height)
166
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
+ # Optional: decoder VAE (Wan2x)
169
+ _ALT_VAE_WAN2X = None
170
+
171
+
172
+ def _get_wan2x_vae(device: torch.device, dtype: torch.dtype):
173
+ """
174
+ Decoder-only finetune that outputs 2x resolution via pixel-shuffle.
175
+ Lazy-loaded so it doesn't impact startup unless used.
176
+ """
177
+ global _ALT_VAE_WAN2X
178
+ if _ALT_VAE_WAN2X is None:
179
+ from diffusers import AutoencoderKLWan
180
+
181
+ _ALT_VAE_WAN2X = AutoencoderKLWan.from_pretrained(
182
+ "spacepxl/Wan2.1-VAE-upscale2x",
183
+ subfolder="diffusers/Wan2.1_VAE_upscale2x_imageonly_real_v1",
184
+ torch_dtype=dtype,
185
+ )
186
+ _ALT_VAE_WAN2X.eval()
187
+ _ALT_VAE_WAN2X = _ALT_VAE_WAN2X.to(device=device, dtype=dtype)
188
+ return _ALT_VAE_WAN2X
189
+
190
+
191
+ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
192
+ r"""
193
+ The Qwen-Image-Edit pipeline for image editing.
194
+ """
195
+
196
+ model_cpu_offload_seq = "text_encoder->transformer->vae"
197
+ _callback_tensor_inputs = ["latents", "prompt_embeds"]
198
+
199
+ def __init__(
200
+ self,
201
+ scheduler: FlowMatchEulerDiscreteScheduler,
202
+ vae: AutoencoderKLQwenImage,
203
+ text_encoder: Qwen2_5_VLForConditionalGeneration,
204
+ tokenizer: Qwen2Tokenizer,
205
+ processor: Qwen2VLProcessor,
206
+ transformer: QwenImageTransformer2DModel,
207
  ):
208
+ super().__init__()
209
+ self.register_modules(
210
+ vae=vae,
211
+ text_encoder=text_encoder,
212
+ tokenizer=tokenizer,
213
+ processor=processor,
214
+ transformer=transformer,
215
+ scheduler=scheduler,
216
  )
217
 
218
+ self.vae_scale_factor = 2 ** len(self.vae.temperal_downsample) if getattr(self, "vae", None) else 8
219
+ self.latent_channels = self.vae.config.z_dim if getattr(self, "vae", None) else 16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
+ # QwenImage latents are turned into 2x2 patches and packed; multiply scale-factor by patch size
222
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor * 2)
223
+ self.tokenizer_max_length = 1024
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
+ self.prompt_template_encode = (
226
+ "<|im_start|>system\n"
227
+ "Describe the key features of the input image (color, shape, size, texture, objects, background), "
228
+ "then explain how the user's text instruction should alter or modify the image.\n"
229
+ "Generate a new image that meets the user's requirements while maintaining consistency with the original input where appropriate."
230
+ "<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n"
231
+ )
232
+ self.prompt_template_encode_start_idx = 64
233
+ self.default_sample_size = 128
234
+
235
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage.QwenImagePipeline._extract_masked_hidden
236
+ def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
237
+ bool_mask = mask.bool()
238
+ valid_lengths = bool_mask.sum(dim=1)
239
+ selected = hidden_states[bool_mask]
240
+ split_result = torch.split(selected, valid_lengths.tolist(), dim=0)
241
+ return split_result
242
+
243
+ def _get_qwen_prompt_embeds(
244
+ self,
245
+ prompt: Union[str, List[str]] = None,
246
+ image: Optional[torch.Tensor] = None,
247
+ device: Optional[torch.device] = None,
248
+ dtype: Optional[torch.dtype] = None,
249
+ ):
250
+ device = device or self._execution_device
251
+ dtype = dtype or self.text_encoder.dtype
252
+
253
+ prompt = [prompt] if isinstance(prompt, str) else prompt
254
+ img_prompt_template = "Picture {}: <|vision_start|><|image_pad|><|vision_end|>"
255
+
256
+ if isinstance(image, list):
257
+ base_img_prompt = ""
258
+ for i, _ in enumerate(image):
259
+ base_img_prompt += img_prompt_template.format(i + 1)
260
+ elif image is not None:
261
+ base_img_prompt = img_prompt_template.format(1)
262
+ else:
263
+ base_img_prompt = ""
264
+
265
+ template = self.prompt_template_encode
266
+ drop_idx = self.prompt_template_encode_start_idx
267
+ txt = [template.format(base_img_prompt + e) for e in prompt]
268
+
269
+ model_inputs = self.processor(text=txt, images=image, padding=True, return_tensors="pt").to(device)
270
+
271
+ outputs = self.text_encoder(
272
+ input_ids=model_inputs.input_ids,
273
+ attention_mask=model_inputs.attention_mask,
274
+ pixel_values=model_inputs.pixel_values,
275
+ image_grid_thw=model_inputs.image_grid_thw,
276
+ output_hidden_states=True,
277
+ )
278
+
279
+ hidden_states = outputs.hidden_states[-1]
280
+ split_hidden_states = self._extract_masked_hidden(hidden_states, model_inputs.attention_mask)
281
+ split_hidden_states = [e[drop_idx:] for e in split_hidden_states]
282
 
283
+ attn_mask_list = [torch.ones(e.size(0), dtype=torch.long, device=e.device) for e in split_hidden_states]
284
+ max_seq_len = max([e.size(0) for e in split_hidden_states])
285
 
286
+ prompt_embeds = torch.stack(
287
+ [torch.cat([u, u.new_zeros(max_seq_len - u.size(0), u.size(1))]) for u in split_hidden_states]
288
+ )
289
+ encoder_attention_mask = torch.stack(
290
+ [torch.cat([u, u.new_zeros(max_seq_len - u.size(0))]) for u in attn_mask_list]
291
  )
292
 
293
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
294
+ return prompt_embeds, encoder_attention_mask
295
+
296
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline.encode_prompt
297
+ def encode_prompt(
298
+ self,
299
+ prompt: Union[str, List[str]],
300
+ image: Optional[torch.Tensor] = None,
301
+ device: Optional[torch.device] = None,
302
+ num_images_per_prompt: int = 1,
303
+ prompt_embeds: Optional[torch.Tensor] = None,
304
+ prompt_embeds_mask: Optional[torch.Tensor] = None,
305
+ max_sequence_length: int = 1024,
306
+ ):
307
+ device = device or self._execution_device
308
+ prompt = [prompt] if isinstance(prompt, str) else prompt
309
+ batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
+ if prompt_embeds is None:
312
+ prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt, image, device)
313
 
314
+ _, seq_len, _ = prompt_embeds.shape
 
 
 
 
315
 
316
+ prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
317
+ prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
318
 
319
+ prompt_embeds_mask = prompt_embeds_mask.repeat(1, num_images_per_prompt, 1)
320
+ prompt_embeds_mask = prompt_embeds_mask.view(batch_size * num_images_per_prompt, seq_len)
321
+
322
+ return prompt_embeds, prompt_embeds_mask
323
+
324
+ # Copied from diffusers.pipelines.qwenimage.pipeline_qwenimage_edit.QwenImageEditPipeline.check_inputs
325
+ def check_inputs(
326
+ self,
327
  prompt,
328
  height,
329
  width,
330
+ negative_prompt=None,
331
+ prompt_embeds=None,
332
+ negative_prompt_embeds=None,
333
+ prompt_embeds_mask=None,
334
+ negative_prompt_embeds_mask=None,
335
+ callback_on_step_end_tensor_inputs=None,
336
+ max_sequence_length=None,
337
+ ):
338
+ if height % (self.vae_scale_factor * 2) != 0 or width % (self.vae_scale_factor * 2) != 0:
339
+ logger.warning(
340
+ f"`height` and `width` have to be divisible by {self.vae_scale_factor * 2} but are {height} and {width}. "
341
+ "Dimensions will be resized accordingly."
342
+ )
343
 
344
+ if callback_on_step_end_tensor_inputs is not None and not all(
345
+ k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
346
+ ):
347
+ raise ValueError(
348
+ f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found "
349
+ f"{[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
350
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
+ if prompt is not None and prompt_embeds is not None:
353
+ raise ValueError("Cannot forward both `prompt` and `prompt_embeds`.")
354
+ if prompt is None and prompt_embeds is None:
355
+ raise ValueError("Provide either `prompt` or `prompt_embeds`.")
356
+ if prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
357
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
358
+
359
+ if negative_prompt is not None and negative_prompt_embeds is not None:
360
+ raise ValueError("Cannot forward both `negative_prompt` and `negative_prompt_embeds`.")
361
+
362
+ if prompt_embeds is not None and prompt_embeds_mask is None:
363
+ raise ValueError("If `prompt_embeds` are provided, `prompt_embeds_mask` must also be passed.")
364
+
365
+ if negative_prompt_embeds is not None and negative_prompt_embeds_mask is None:
366
+ raise ValueError("If `negative_prompt_embeds` are provided, `negative_prompt_embeds_mask` must also be passed.")
367
+
368
+ if max_sequence_length is not None and max_sequence_length > 1024:
369
+ raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
370
+
371
+ @staticmethod
372
+ def _pack_latents(latents, batch_size, num_channels_latents, height, width):
373
+ latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
374
+ latents = latents.permute(0, 2, 4, 1, 3, 5)
375
+ latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4)
376
+ return latents
377
+
378
+ @staticmethod
379
+ def _unpack_latents(latents, height, width, vae_scale_factor):
380
+ batch_size, _, channels = latents.shape
381
+ height = 2 * (int(height) // (vae_scale_factor * 2))
382
+ width = 2 * (int(width) // (vae_scale_factor * 2))
383
+ latents = latents.view(batch_size, height // 2, width // 2, channels // 4, 2, 2)
384
+ latents = latents.permute(0, 3, 1, 4, 2, 5)
385
+ latents = latents.reshape(batch_size, channels // 4, 1, height, width)
386
+ return latents
387
+
388
+ def _encode_vae_image(self, image: torch.Tensor, generator: torch.Generator):
389
+ if isinstance(generator, list):
390
+ image_latents = [
391
+ retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i], sample_mode="argmax")
392
+ for i in range(image.shape[0])
393
+ ]
394
+ image_latents = torch.cat(image_latents, dim=0)
395
+ else:
396
+ image_latents = retrieve_latents(self.vae.encode(image), generator=generator, sample_mode="argmax")
397
 
398
+ latents_mean = torch.tensor(self.vae.config.latents_mean).view(1, self.latent_channels, 1, 1, 1).to(
399
+ image_latents.device, image_latents.dtype
400
+ )
401
+ latents_std = torch.tensor(self.vae.config.latents_std).view(1, self.latent_channels, 1, 1, 1).to(
402
+ image_latents.device, image_latents.dtype
403
  )
404
+ image_latents = (image_latents - latents_mean) / latents_std
405
+ return image_latents
406
+
407
+ def prepare_latents(
408
+ self,
409
+ images,
410
+ batch_size,
411
+ num_channels_latents,
412
+ height,
413
+ width,
414
+ dtype,
415
+ device,
416
+ generator,
417
+ latents=None,
418
+ ):
419
+ height = 2 * (int(height) // (self.vae_scale_factor * 2))
420
+ width = 2 * (int(width) // (self.vae_scale_factor * 2))
421
+ shape = (batch_size, 1, num_channels_latents, height, width)
422
+
423
+ image_latents = None
424
+ if images is not None:
425
+ if not isinstance(images, list):
426
+ images = [images]
427
+ all_image_latents = []
428
+
429
+ for image in images:
430
+ image = image.to(device=device, dtype=dtype)
431
+ if image.shape[1] != self.latent_channels:
432
+ image_latents = self._encode_vae_image(image=image, generator=generator)
433
+ else:
434
+ image_latents = image
435
+
436
+ if batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] == 0:
437
+ additional_image_per_prompt = batch_size // image_latents.shape[0]
438
+ image_latents = torch.cat([image_latents] * additional_image_per_prompt, dim=0)
439
+ elif batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] != 0:
440
+ raise ValueError(
441
+ f"Cannot duplicate `image` of batch size {image_latents.shape[0]} to {batch_size} text prompts."
442
+ )
443
+
444
+ image_latent_height, image_latent_width = image_latents.shape[3:]
445
+ image_latents = self._pack_latents(
446
+ image_latents, batch_size, num_channels_latents, image_latent_height, image_latent_width
447
+ )
448
+ all_image_latents.append(image_latents)
449
 
450
+ image_latents = torch.cat(all_image_latents, dim=1)
451
+
452
+ if isinstance(generator, list) and len(generator) != batch_size:
453
+ raise ValueError(
454
+ f"You passed a list of generators of length {len(generator)}, but requested an effective batch size of {batch_size}."
455
  )
 
 
456
 
457
+ if latents is None:
458
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
459
+ latents = self._pack_latents(latents, batch_size, num_channels_latents, height, width)
460
+ else:
461
+ latents = latents.to(device=device, dtype=dtype)
462
+
463
+ return latents, image_latents
464
+
465
+ @property
466
+ def guidance_scale(self):
467
+ return self._guidance_scale
468
+
469
+ @property
470
+ def attention_kwargs(self):
471
+ return self._attention_kwargs
472
+
473
+ @property
474
+ def num_timesteps(self):
475
+ return self._num_timesteps
476
+
477
+ @property
478
+ def current_timestep(self):
479
+ return self._current_timestep
480
+
481
+ @property
482
+ def interrupt(self):
483
+ return self._interrupt
484
+
485
+ @torch.no_grad()
486
+ @replace_example_docstring(EXAMPLE_DOC_STRING)
487
+ def __call__(
488
+ self,
489
+ image: Optional[PipelineImageInput] = None,
490
+ prompt: Union[str, List[str]] = None,
491
+ negative_prompt: Union[str, List[str]] = None,
492
+ true_cfg_scale: float = 4.0,
493
+ height: Optional[int] = None,
494
+ width: Optional[int] = None,
495
+ condition_area: Optional[int] = None,
496
+ vae_image_indices: Optional[List[int]] = None,
497
+ pad_to_canvas: bool = True,
498
+ # NEW: lattice + VAE ref override
499
+ resolution_multiple: Optional[int] = None,
500
+ vae_ref_area: Optional[int] = None,
501
+ vae_ref_start_index: int = 2,
502
+ # Optional: decoder swap
503
+ decoder_vae: str = "qwen", # "qwen" | "wan2x"
504
+ keep_decoder_2x: bool = False,
505
+ # standard args
506
+ num_inference_steps: int = 50,
507
+ sigmas: Optional[List[float]] = None,
508
+ guidance_scale: Optional[float] = None,
509
+ num_images_per_prompt: int = 1,
510
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
511
+ latents: Optional[torch.Tensor] = None,
512
+ prompt_embeds: Optional[torch.Tensor] = None,
513
+ prompt_embeds_mask: Optional[torch.Tensor] = None,
514
+ negative_prompt_embeds: Optional[torch.Tensor] = None,
515
+ negative_prompt_embeds_mask: Optional[torch.Tensor] = None,
516
+ output_type: Optional[str] = "pil",
517
+ return_dict: bool = True,
518
+ attention_kwargs: Optional[Dict[str, Any]] = None,
519
+ callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
520
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
521
+ max_sequence_length: int = 512,
522
+ ):
523
+ # ---- determine input size ----
524
+ if isinstance(image, list):
525
+ image_size = image[0].size
526
+ else:
527
+ image_size = image.size
528
+
529
+ # Lattice multiple used throughout (canvas sizing + condition sizing)
530
+ multiple_of = int(resolution_multiple) if resolution_multiple is not None else (self.vae_scale_factor * 2)
531
+ multiple_of = max(1, multiple_of)
532
+
533
+ calculated_width, calculated_height = calculate_dimensions(
534
+ 1024 * 1024, float(image_size[0]) / float(image_size[1]), multiple=multiple_of
535
+ )
536
+ height = height or calculated_height
537
+ width = width or calculated_width
538
+
539
+ width = (int(width) // multiple_of) * multiple_of
540
+ height = (int(height) // multiple_of) * multiple_of
541
 
542
+ # ---- validate ----
543
+ self.check_inputs(
544
+ prompt,
545
+ height,
546
+ width,
547
+ negative_prompt=negative_prompt,
548
  prompt_embeds=prompt_embeds,
549
+ negative_prompt_embeds=negative_prompt_embeds,
550
  prompt_embeds_mask=prompt_embeds_mask,
551
+ negative_prompt_embeds_mask=negative_prompt_embeds_mask,
552
+ callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
553
  max_sequence_length=max_sequence_length,
554
  )
555
 
556
+ self._guidance_scale = guidance_scale
557
+ self._attention_kwargs = attention_kwargs
558
+ self._current_timestep = None
559
+ self._interrupt = False
560
+
561
+ # ---- call params ----
562
+ if prompt is not None and isinstance(prompt, str):
563
+ batch_size = 1
564
+ elif prompt is not None and isinstance(prompt, list):
565
+ batch_size = len(prompt)
566
+ else:
567
+ batch_size = prompt_embeds.shape[0]
568
+
569
+ device = self._execution_device
570
+
571
+ # ---- preprocess ----
572
+ condition_images = None
573
+ vae_images = None
574
+ vae_image_sizes: List[tuple[int, int]] = []
575
+
576
+ # support pre-latent tensors (rare, but keep compatibility)
577
+ if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == self.latent_channels):
578
+ if not isinstance(image, list):
579
+ image = [image]
580
+
581
+ canvas_area = int(width) * int(height)
582
+ cond_area = int(condition_area) if condition_area is not None else choose_condition_area(canvas_area)
583
+
584
+ cond_w, cond_h = calculate_dimensions(cond_area, float(width) / float(height), multiple=multiple_of)
585
+
586
+ # Optional VAE ref override sizing (applied only to indices >= vae_ref_start_index)
587
+ ref_w = ref_h = None
588
+ if vae_ref_area is not None:
589
+ try:
590
+ ref_w, ref_h = calculate_dimensions(
591
+ int(vae_ref_area),
592
+ float(width) / float(height),
593
+ multiple=multiple_of,
594
+ )
595
+ except Exception:
596
+ ref_w = ref_h = None
597
+
598
+ condition_images = []
599
+ vae_images = []
600
+
601
+ if vae_image_indices is None:
602
+ vae_image_indices = list(range(len(image)))
603
+ vae_set = set(int(i) for i in vae_image_indices)
604
+
605
+ for idx, img in enumerate(image):
606
+ pil = img.convert("RGB") if isinstance(img, Image.Image) else img
607
+
608
+ if pad_to_canvas and isinstance(pil, Image.Image):
609
+ pil = pad_to_aspect(pil, int(width), int(height))
610
+
611
+ # conditioning stream (always)
612
+ condition_images.append(self.image_processor.resize(pil, cond_h, cond_w))
613
+
614
+ # VAE stream (selective)
615
+ if idx in vae_set:
616
+ if (ref_w is not None) and (ref_h is not None) and (int(idx) >= int(vae_ref_start_index)):
617
+ vw, vh = int(ref_w), int(ref_h)
618
+ else:
619
+ vw, vh = int(width), int(height)
620
+
621
+ vae_image_sizes.append((vw, vh))
622
+ vae_images.append(self.image_processor.preprocess(pil, int(vh), int(vw)).unsqueeze(2))
623
+
624
+ has_neg_prompt = negative_prompt is not None or (
625
+ negative_prompt_embeds is not None and negative_prompt_embeds_mask is not None
626
+ )
627
+ if true_cfg_scale > 1 and not has_neg_prompt:
628
+ logger.warning(
629
+ f"true_cfg_scale={true_cfg_scale} but CFG disabled because no negative prompt was provided."
630
+ )
631
+ if true_cfg_scale <= 1 and has_neg_prompt:
632
+ logger.warning("negative_prompt provided but CFG disabled because true_cfg_scale <= 1")
633
+
634
+ do_true_cfg = (true_cfg_scale > 1) and has_neg_prompt
635
+
636
+ prompt_embeds, prompt_embeds_mask = self.encode_prompt(
637
  image=condition_images,
638
+ prompt=prompt,
639
+ prompt_embeds=prompt_embeds,
640
+ prompt_embeds_mask=prompt_embeds_mask,
641
  device=device,
642
  num_images_per_prompt=num_images_per_prompt,
643
  max_sequence_length=max_sequence_length,
644
  )
645
 
646
+ if do_true_cfg:
647
+ negative_prompt_embeds, negative_prompt_embeds_mask = self.encode_prompt(
648
+ image=condition_images,
649
+ prompt=negative_prompt,
650
+ prompt_embeds=negative_prompt_embeds,
651
+ prompt_embeds_mask=negative_prompt_embeds_mask,
652
+ device=device,
653
+ num_images_per_prompt=num_images_per_prompt,
654
+ max_sequence_length=max_sequence_length,
655
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
 
657
+ # ---- prepare latents ----
658
+ num_channels_latents = self.transformer.config.in_channels // 4
659
+ latents, image_latents = self.prepare_latents(
660
+ vae_images,
661
+ batch_size * num_images_per_prompt,
662
+ num_channels_latents,
663
+ height,
664
+ width,
665
+ prompt_embeds.dtype,
666
+ device,
667
+ generator,
668
+ latents,
669
+ )
 
 
670
 
671
+ img_shapes = [
672
+ [
673
+ (1, height // self.vae_scale_factor // 2, width // self.vae_scale_factor // 2),
674
+ *[
675
+ (1, vae_h // self.vae_scale_factor // 2, vae_w // self.vae_scale_factor // 2)
676
+ for (vae_w, vae_h) in vae_image_sizes
677
+ ],
678
+ ]
679
+ ] * batch_size
680
+
681
+ else:
682
+ raise ValueError(
683
+ "This Space pipeline expects `image` as PIL/np inputs (not pre-latents) in this setup."
684
+ )
685
 
686
+ # ---- timesteps ----
687
+ sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) if sigmas is None else sigmas
688
 
689
+ image_seq_len = latents.shape[1]
690
+ mu = calculate_shift(
691
+ image_seq_len,
692
+ self.scheduler.config.get("base_image_seq_len", 256),
693
+ self.scheduler.config.get("max_image_seq_len", 4096),
694
+ self.scheduler.config.get("base_shift", 0.5),
695
+ self.scheduler.config.get("max_shift", 1.15),
696
+ )
697
+ timesteps, num_inference_steps = retrieve_timesteps(
698
+ self.scheduler, num_inference_steps, device, sigmas=sigmas, mu=mu
699
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
 
701
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
702
+ self._num_timesteps = len(timesteps)
703
+
704
+ # guidance-distilled models need explicit guidance input
705
+ if self.transformer.config.guidance_embeds and guidance_scale is None:
706
+ raise ValueError("guidance_scale is required for guidance-distilled model.")
707
+ if self.transformer.config.guidance_embeds:
708
+ guidance = torch.full([1], guidance_scale, device=device, dtype=torch.float32).expand(latents.shape[0])
709
+ else:
710
+ if guidance_scale is not None:
711
+ logger.warning("guidance_scale passed but ignored since model is not guidance-distilled.")
712
+ guidance = None
713
+
714
+ if self.attention_kwargs is None:
715
+ self._attention_kwargs = {}
716
+
717
+ txt_seq_lens = prompt_embeds_mask.sum(dim=1).tolist() if prompt_embeds_mask is not None else None
718
+ image_rotary_emb = self.transformer.pos_embed(img_shapes, txt_seq_lens, device=latents.device)
719
+
720
+ do_true_cfg = (
721
+ (true_cfg_scale > 1)
722
+ and (negative_prompt_embeds is not None)
723
+ and (negative_prompt_embeds_mask is not None)
724
+ )
725
+ if do_true_cfg:
726
+ negative_txt_seq_lens = negative_prompt_embeds_mask.sum(dim=1).tolist()
727
+ uncond_image_rotary_emb = self.transformer.pos_embed(img_shapes, negative_txt_seq_lens, device=latents.device)
728
+ else:
729
+ uncond_image_rotary_emb = None
730
+
731
+ # ---- denoise ----
732
+ self.scheduler.set_begin_index(0)
733
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
734
+ for i, t in enumerate(timesteps):
735
+ if self.interrupt:
736
+ continue
737
+ self._current_timestep = t
738
+
739
+ latent_model_input = latents
740
+ if image_latents is not None:
741
+ latent_model_input = torch.cat([latents, image_latents], dim=1)
742
+
743
+ timestep = t.expand(latents.shape[0]).to(latents.dtype)
744
+
745
+ with self.transformer.cache_context("cond"):
746
+ noise_pred = self.transformer(
747
  hidden_states=latent_model_input,
748
  timestep=timestep / 1000,
749
  guidance=guidance,
750
+ encoder_hidden_states_mask=prompt_embeds_mask,
751
+ encoder_hidden_states=prompt_embeds,
752
+ image_rotary_emb=image_rotary_emb,
753
  attention_kwargs=self.attention_kwargs,
754
  return_dict=False,
755
  )[0]
756
+ noise_pred = noise_pred[:, : latents.size(1)]
757
+
758
+ if do_true_cfg:
759
+ with self.transformer.cache_context("uncond"):
760
+ neg_noise_pred = self.transformer(
761
+ hidden_states=latent_model_input,
762
+ timestep=timestep / 1000,
763
+ guidance=guidance,
764
+ encoder_hidden_states_mask=negative_prompt_embeds_mask,
765
+ encoder_hidden_states=negative_prompt_embeds,
766
+ image_rotary_emb=uncond_image_rotary_emb,
767
+ attention_kwargs=self.attention_kwargs,
768
+ return_dict=False,
769
+ )[0]
770
+ neg_noise_pred = neg_noise_pred[:, : latents.size(1)]
771
+
772
+ comb_pred = neg_noise_pred + true_cfg_scale * (noise_pred - neg_noise_pred)
773
+ cond_norm = torch.norm(noise_pred, dim=-1, keepdim=True)
774
+ noise_norm = torch.norm(comb_pred, dim=-1, keepdim=True)
775
+ noise_pred = comb_pred * (cond_norm / (noise_norm + 1e-8))
776
+
777
+ latents_dtype = latents.dtype
778
+ latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
779
+ if latents.dtype != latents_dtype and torch.backends.mps.is_available():
780
  latents = latents.to(latents_dtype)
781
 
782
+ if callback_on_step_end is not None:
783
+ callback_kwargs = {k: locals()[k] for k in callback_on_step_end_tensor_inputs}
784
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
785
+ latents = callback_outputs.pop("latents", latents)
786
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
787
 
788
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
789
+ progress_bar.update()
790
 
791
+ if XLA_AVAILABLE:
792
+ xm.mark_step()
793
 
794
+ self._current_timestep = None
795
 
796
+ # ---- decode ----
797
+ if output_type == "latent":
798
+ image_out = latents
799
+ else:
800
+ latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
801
+ latents = latents.to(self.vae.dtype)
802
 
803
+ latents_mean = torch.tensor(self.vae.config.latents_mean).view(1, self.vae.config.z_dim, 1, 1, 1).to(
804
+ latents.device, latents.dtype
805
+ )
806
+ latents_std = 1.0 / torch.tensor(self.vae.config.latents_std).view(1, self.vae.config.z_dim, 1, 1, 1).to(
807
+ latents.device, latents.dtype
808
+ )
809
+ latents = latents / latents_std + latents_mean
810
+
811
+ if decoder_vae == "wan2x":
812
+ alt_vae = _get_wan2x_vae(latents.device, self.vae.dtype)
813
+ decoder_out = alt_vae.decode(latents, return_dict=False)[0] # [B, 12, F, H, W]
814
+ img_2x = F.pixel_shuffle(decoder_out[:, :, 0], upscale_factor=2) # [B, 3, 2H, 2W]
815
+ if keep_decoder_2x:
816
+ decoded = img_2x
817
+ else:
818
+ decoded = F.interpolate(img_2x, size=(int(height), int(width)), mode="area")
819
+ else:
820
+ decoded = self.vae.decode(latents, return_dict=False)[0][:, :, 0]
821
 
822
+ image_out = self.image_processor.postprocess(decoded, output_type=output_type)
 
823
 
824
+ self.maybe_free_model_hooks()
825
 
826
+ if not return_dict:
827
+ return (image_out,)
828
+ return QwenImagePipelineOutput(images=image_out)