comdoleger commited on
Commit
1ef8369
·
verified ·
1 Parent(s): 8dee2d0

Upload extensions_built_in/diffusion_models/qwen_image/qwen_image.py with huggingface_hub

Browse files
extensions_built_in/diffusion_models/qwen_image/qwen_image.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TYPE_CHECKING, List, Optional
3
+
4
+ import torch
5
+ import yaml
6
+ from toolkit import train_tools
7
+ from toolkit.config_modules import GenerateImageConfig, ModelConfig
8
+ from PIL import Image
9
+ from toolkit.models.base_model import BaseModel
10
+ from toolkit.basic import flush
11
+ from toolkit.prompt_utils import PromptEmbeds
12
+ from toolkit.samplers.custom_flowmatch_sampler import (
13
+ CustomFlowMatchEulerDiscreteScheduler,
14
+ )
15
+ from toolkit.accelerator import get_accelerator, unwrap_model
16
+ from optimum.quanto import freeze, QTensor
17
+ from toolkit.util.quantize import quantize, get_qtype, quantize_model
18
+ import torch.nn.functional as F
19
+ from toolkit.memory_management import MemoryManager
20
+ from safetensors.torch import load_file
21
+
22
+ from diffusers import (
23
+ QwenImagePipeline,
24
+ QwenImageTransformer2DModel,
25
+ AutoencoderKLQwenImage,
26
+ )
27
+ from transformers import (
28
+ Qwen2_5_VLForConditionalGeneration,
29
+ Qwen2Tokenizer,
30
+ Qwen2VLProcessor,
31
+ )
32
+ from tqdm import tqdm
33
+
34
+ if TYPE_CHECKING:
35
+ from toolkit.data_transfer_object.data_loader import DataLoaderBatchDTO
36
+
37
+ scheduler_config = {
38
+ "base_image_seq_len": 256,
39
+ "base_shift": 0.5,
40
+ "invert_sigmas": False,
41
+ "max_image_seq_len": 8192,
42
+ "max_shift": 0.9,
43
+ "num_train_timesteps": 1000,
44
+ "shift": 1.0,
45
+ "shift_terminal": 0.02,
46
+ "stochastic_sampling": False,
47
+ "time_shift_type": "exponential",
48
+ "use_beta_sigmas": False,
49
+ "use_dynamic_shifting": True,
50
+ "use_exponential_sigmas": False,
51
+ "use_karras_sigmas": False,
52
+ }
53
+
54
+
55
+ class QwenImageModel(BaseModel):
56
+ arch = "qwen_image"
57
+ _qwen_image_keep_visual = False
58
+ _qwen_pipeline = QwenImagePipeline
59
+
60
+ def __init__(
61
+ self,
62
+ device,
63
+ model_config: ModelConfig,
64
+ dtype="bf16",
65
+ custom_pipeline=None,
66
+ noise_scheduler=None,
67
+ **kwargs,
68
+ ):
69
+ super().__init__(
70
+ device, model_config, dtype, custom_pipeline, noise_scheduler, **kwargs
71
+ )
72
+ self.is_flow_matching = True
73
+ self.is_transformer = True
74
+ self.target_lora_modules = ["QwenImageTransformer2DModel"]
75
+
76
+ # static method to get the noise scheduler
77
+ @staticmethod
78
+ def get_train_scheduler():
79
+ return CustomFlowMatchEulerDiscreteScheduler(**scheduler_config)
80
+
81
+ def get_bucket_divisibility(self):
82
+ return 16 * 2 # 16 for the VAE, 2 for patch size
83
+
84
+ def load_model(self):
85
+ dtype = self.torch_dtype
86
+ self.print_and_status_update("Loading Qwen Image model")
87
+ model_path = self.model_config.name_or_path
88
+ base_model_path = self.model_config.extras_name_or_path
89
+ model_dtype = dtype
90
+
91
+ if base_model_path.endswith(".safetensors"):
92
+ # use the repo for extras
93
+ base_model_path = "Qwen/Qwen-Image"
94
+
95
+ self.print_and_status_update("Loading transformer")
96
+
97
+ if model_path.endswith(".safetensors"):
98
+ # load the safetensors file
99
+ transformer = QwenImageTransformer2DModel.from_single_file(
100
+ model_path,
101
+ config="Qwen/Qwen-Image",
102
+ subfolder="transformer",
103
+ torch_dtype=model_dtype,
104
+ )
105
+ transformer.to(model_dtype)
106
+
107
+ else:
108
+ transformer_path = model_path
109
+ transformer_subfolder = "transformer"
110
+ if os.path.exists(transformer_path):
111
+ transformer_subfolder = None
112
+ transformer_path = os.path.join(transformer_path, "transformer")
113
+ # check if the path is a full checkpoint.
114
+ te_folder_path = os.path.join(model_path, "text_encoder")
115
+ # if we have the te, this folder is a full checkpoint, use it as the base
116
+ if os.path.exists(te_folder_path):
117
+ base_model_path = model_path
118
+
119
+ transformer = QwenImageTransformer2DModel.from_pretrained(
120
+ transformer_path, subfolder=transformer_subfolder, torch_dtype=dtype
121
+ )
122
+
123
+ if self.model_config.quantize:
124
+ self.print_and_status_update("Quantizing Transformer")
125
+ quantize_model(self, transformer)
126
+ flush()
127
+
128
+ if self.model_config.auto_memory:
129
+ MemoryManager.attach(transformer, self.device_torch)
130
+
131
+ if self.model_config.low_vram:
132
+ self.print_and_status_update("Moving transformer to CPU")
133
+ transformer.to("cpu")
134
+
135
+ flush()
136
+
137
+ self.print_and_status_update("Text Encoder")
138
+ tokenizer = Qwen2Tokenizer.from_pretrained(
139
+ base_model_path, subfolder="tokenizer", torch_dtype=dtype
140
+ )
141
+ text_encoder = Qwen2_5_VLForConditionalGeneration.from_pretrained(
142
+ base_model_path, subfolder="text_encoder", torch_dtype=dtype
143
+ )
144
+
145
+ # remove the visual model as it is not needed for image generation
146
+ self.processor = None
147
+ if not self._qwen_image_keep_visual:
148
+ text_encoder.model.visual = None
149
+
150
+ if self.model_config.auto_memory:
151
+ MemoryManager.attach(text_encoder, self.device_torch)
152
+
153
+ text_encoder.to(self.device_torch, dtype=dtype)
154
+ flush()
155
+
156
+ if self.model_config.quantize_te:
157
+ self.print_and_status_update("Quantizing Text Encoder")
158
+ quantize(text_encoder, weights=get_qtype(self.model_config.qtype_te))
159
+ freeze(text_encoder)
160
+ flush()
161
+
162
+ self.print_and_status_update("Loading VAE")
163
+ vae = AutoencoderKLQwenImage.from_pretrained(
164
+ base_model_path, subfolder="vae", torch_dtype=dtype
165
+ )
166
+
167
+ self.noise_scheduler = QwenImageModel.get_train_scheduler()
168
+
169
+ self.print_and_status_update("Making pipe")
170
+
171
+ kwargs = {}
172
+
173
+ if self._qwen_image_keep_visual:
174
+ try:
175
+ self.processor = Qwen2VLProcessor.from_pretrained(
176
+ model_path, subfolder="processor"
177
+ )
178
+ except OSError:
179
+ self.processor = Qwen2VLProcessor.from_pretrained(
180
+ base_model_path, subfolder="processor"
181
+ )
182
+ kwargs["processor"] = self.processor
183
+
184
+ pipe: QwenImagePipeline = self._qwen_pipeline(
185
+ scheduler=self.noise_scheduler,
186
+ text_encoder=None,
187
+ tokenizer=tokenizer,
188
+ vae=vae,
189
+ transformer=None,
190
+ **kwargs,
191
+ )
192
+ # for quantization, it works best to do these after making the pipe
193
+ pipe.text_encoder = text_encoder
194
+ pipe.transformer = transformer
195
+
196
+ self.print_and_status_update("Preparing Model")
197
+
198
+ text_encoder = [pipe.text_encoder]
199
+ tokenizer = [pipe.tokenizer]
200
+
201
+ # leave it on cpu for now
202
+ if not self.low_vram:
203
+ pipe.transformer = pipe.transformer.to(self.device_torch)
204
+
205
+ flush()
206
+ # just to make sure everything is on the right device and dtype
207
+ text_encoder[0].to(self.device_torch)
208
+ text_encoder[0].requires_grad_(False)
209
+ text_encoder[0].eval()
210
+ flush()
211
+
212
+ # save it to the model class
213
+ self.vae = vae
214
+ self.text_encoder = text_encoder # list of text encoders
215
+ self.tokenizer = tokenizer # list of tokenizers
216
+ self.model = pipe.transformer
217
+ self.pipeline = pipe
218
+ self.print_and_status_update("Model Loaded")
219
+
220
+ def get_generation_pipeline(self):
221
+ scheduler = QwenImageModel.get_train_scheduler()
222
+
223
+ pipeline: QwenImagePipeline = QwenImagePipeline(
224
+ scheduler=scheduler,
225
+ text_encoder=unwrap_model(self.text_encoder[0]),
226
+ tokenizer=self.tokenizer[0],
227
+ vae=unwrap_model(self.vae),
228
+ transformer=unwrap_model(self.transformer),
229
+ )
230
+
231
+ pipeline = pipeline.to(self.device_torch)
232
+
233
+ return pipeline
234
+
235
+ def generate_single_image(
236
+ self,
237
+ pipeline: QwenImagePipeline,
238
+ gen_config: GenerateImageConfig,
239
+ conditional_embeds: PromptEmbeds,
240
+ unconditional_embeds: PromptEmbeds,
241
+ generator: torch.Generator,
242
+ extra: dict,
243
+ ):
244
+ self.model.to(self.device_torch, dtype=self.torch_dtype)
245
+ control_img = None
246
+ if gen_config.ctrl_img is not None:
247
+ raise NotImplementedError(
248
+ "Control image generation is not supported in Qwen Image model... yet"
249
+ )
250
+ control_img = Image.open(gen_config.ctrl_img)
251
+ control_img = control_img.convert("RGB")
252
+ # resize to width and height
253
+ if control_img.size != (gen_config.width, gen_config.height):
254
+ control_img = control_img.resize(
255
+ (gen_config.width, gen_config.height), Image.BILINEAR
256
+ )
257
+ self.model.to(self.device_torch)
258
+
259
+ # flush for low vram if we are doing that
260
+ flush_between_steps = self.model_config.low_vram
261
+
262
+ # Fix a bug in diffusers/torch
263
+ def callback_on_step_end(pipe, i, t, callback_kwargs):
264
+ if flush_between_steps:
265
+ flush()
266
+ latents = callback_kwargs["latents"]
267
+
268
+ return {"latents": latents}
269
+
270
+ sc = self.get_bucket_divisibility()
271
+ gen_config.width = int(gen_config.width // sc * sc)
272
+ gen_config.height = int(gen_config.height // sc * sc)
273
+ img = pipeline(
274
+ prompt_embeds=conditional_embeds.text_embeds,
275
+ prompt_embeds_mask=conditional_embeds.attention_mask.to(
276
+ self.device_torch, dtype=torch.int64
277
+ ),
278
+ negative_prompt_embeds=unconditional_embeds.text_embeds,
279
+ negative_prompt_embeds_mask=unconditional_embeds.attention_mask.to(
280
+ self.device_torch, dtype=torch.int64
281
+ ),
282
+ height=gen_config.height,
283
+ width=gen_config.width,
284
+ num_inference_steps=gen_config.num_inference_steps,
285
+ true_cfg_scale=gen_config.guidance_scale,
286
+ latents=gen_config.latents,
287
+ generator=generator,
288
+ callback_on_step_end=callback_on_step_end,
289
+ **extra,
290
+ ).images[0]
291
+ return img
292
+
293
+ def get_noise_prediction(
294
+ self,
295
+ latent_model_input: torch.Tensor,
296
+ timestep: torch.Tensor, # 0 to 1000 scale
297
+ text_embeddings: PromptEmbeds,
298
+ **kwargs,
299
+ ):
300
+ self.model.to(self.device_torch)
301
+ batch_size, num_channels_latents, height, width = latent_model_input.shape
302
+
303
+ ps = self.transformer.config.patch_size
304
+
305
+ # pack image tokens
306
+ latent_model_input = latent_model_input.view(
307
+ batch_size, num_channels_latents, height // ps, ps, width // ps, ps
308
+ )
309
+ latent_model_input = latent_model_input.permute(0, 2, 4, 1, 3, 5)
310
+ latent_model_input = latent_model_input.reshape(
311
+ batch_size, (height // ps) * (width // ps), num_channels_latents * (ps * ps)
312
+ )
313
+
314
+ # img_shapes passed to the model
315
+ img_h2, img_w2 = height // ps, width // ps
316
+ img_shapes = [[(1, img_h2, img_w2)]] * batch_size
317
+
318
+ enc_hs = text_embeddings.text_embeds.to(self.device_torch, self.torch_dtype)
319
+ prompt_embeds_mask = text_embeddings.attention_mask.to(
320
+ self.device_torch, dtype=torch.int64
321
+ )
322
+ txt_seq_lens = prompt_embeds_mask.sum(dim=1).tolist()
323
+
324
+ noise_pred = self.transformer(
325
+ hidden_states=latent_model_input.to(
326
+ self.device_torch, self.torch_dtype
327
+ ).detach(),
328
+ timestep=(timestep / 1000).detach(),
329
+ guidance=None,
330
+ encoder_hidden_states=enc_hs.detach(),
331
+ encoder_hidden_states_mask=prompt_embeds_mask.detach(),
332
+ img_shapes=img_shapes,
333
+ txt_seq_lens=txt_seq_lens,
334
+ return_dict=False,
335
+ **kwargs,
336
+ )[0]
337
+
338
+ # unpack
339
+ noise_pred = noise_pred.view(
340
+ batch_size, height // ps, width // ps, num_channels_latents, ps, ps
341
+ )
342
+ noise_pred = noise_pred.permute(0, 3, 1, 4, 2, 5)
343
+ noise_pred = noise_pred.reshape(batch_size, num_channels_latents, height, width)
344
+ return noise_pred
345
+
346
+ def get_prompt_embeds(self, prompt: str) -> PromptEmbeds:
347
+ if self.pipeline.text_encoder.device != self.device_torch:
348
+ self.pipeline.text_encoder.to(self.device_torch)
349
+
350
+ prompt_embeds, prompt_embeds_mask = self.pipeline.encode_prompt(
351
+ prompt,
352
+ device=self.device_torch,
353
+ num_images_per_prompt=1,
354
+ )
355
+ pe = PromptEmbeds(prompt_embeds)
356
+ pe.attention_mask = prompt_embeds_mask
357
+ return pe
358
+
359
+ def get_model_has_grad(self):
360
+ return False
361
+
362
+ def get_te_has_grad(self):
363
+ return False
364
+
365
+ def save_model(self, output_path, meta, save_dtype):
366
+ # only save the unet
367
+ transformer: QwenImageTransformer2DModel = unwrap_model(self.model)
368
+ transformer.save_pretrained(
369
+ save_directory=os.path.join(output_path, "transformer"),
370
+ safe_serialization=True,
371
+ )
372
+
373
+ meta_path = os.path.join(output_path, "aitk_meta.yaml")
374
+ with open(meta_path, "w") as f:
375
+ yaml.dump(meta, f)
376
+
377
+ def get_loss_target(self, *args, **kwargs):
378
+ noise = kwargs.get("noise")
379
+ batch = kwargs.get("batch")
380
+ return (noise - batch.latents).detach()
381
+
382
+ def get_base_model_version(self):
383
+ return "qwen_image"
384
+
385
+ def get_transformer_block_names(self) -> Optional[List[str]]:
386
+ return ["transformer_blocks"]
387
+
388
+ def convert_lora_weights_before_save(self, state_dict):
389
+ new_sd = {}
390
+ for key, value in state_dict.items():
391
+ new_key = key.replace("transformer.", "diffusion_model.")
392
+ new_sd[new_key] = value
393
+ return new_sd
394
+
395
+ def convert_lora_weights_before_load(self, state_dict):
396
+ new_sd = {}
397
+ for key, value in state_dict.items():
398
+ new_key = key.replace("diffusion_model.", "transformer.")
399
+ new_sd[new_key] = value
400
+ return new_sd
401
+
402
+ def encode_images(self, image_list: List[torch.Tensor], device=None, dtype=None):
403
+ if device is None:
404
+ device = self.vae_device_torch
405
+ if dtype is None:
406
+ dtype = self.vae_torch_dtype
407
+
408
+ # Move to vae to device if on cpu
409
+ if self.vae.device == "cpu":
410
+ self.vae.to(device)
411
+ self.vae.eval()
412
+ self.vae.requires_grad_(False)
413
+ # move to device and dtype
414
+ image_list = [image.to(device, dtype=dtype) for image in image_list]
415
+ images = torch.stack(image_list).to(device, dtype=dtype)
416
+ # it uses wan vae, so add dim for frame count
417
+
418
+ images = images.unsqueeze(2)
419
+ latents = self.vae.encode(images).latent_dist.sample()
420
+
421
+ latents_mean = (
422
+ torch.tensor(self.vae.config.latents_mean)
423
+ .view(1, self.vae.config.z_dim, 1, 1, 1)
424
+ .to(latents.device, latents.dtype)
425
+ )
426
+ latents_std = 1.0 / torch.tensor(self.vae.config.latents_std).view(
427
+ 1, self.vae.config.z_dim, 1, 1, 1
428
+ ).to(latents.device, latents.dtype)
429
+
430
+ latents = (latents - latents_mean) * latents_std
431
+ latents = latents.to(device, dtype=dtype)
432
+
433
+ latents = latents.squeeze(2) # remove the frame count dimension
434
+
435
+ return latents