comdoleger commited on
Commit
7bd3663
·
verified ·
1 Parent(s): 4d9b624

Upload extensions_built_in/diffusion_models/chroma/chroma_model.py with huggingface_hub

Browse files
extensions_built_in/diffusion_models/chroma/chroma_model.py ADDED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ import torch
5
+ from toolkit.config_modules import GenerateImageConfig, ModelConfig
6
+ from PIL import Image
7
+ from toolkit.models.base_model import BaseModel
8
+ from toolkit.basic import flush
9
+ from diffusers import AutoencoderKL
10
+ # from toolkit.pixel_shuffle_encoder import AutoencoderPixelMixer
11
+ from toolkit.prompt_utils import PromptEmbeds
12
+ from toolkit.samplers.custom_flowmatch_sampler import CustomFlowMatchEulerDiscreteScheduler
13
+ from toolkit.dequantize import patch_dequantization_on_save
14
+ from toolkit.accelerator import unwrap_model
15
+ from optimum.quanto import freeze, QTensor
16
+ from toolkit.util.quantize import quantize, get_qtype
17
+ from transformers import T5TokenizerFast, T5EncoderModel, CLIPTextModel, CLIPTokenizer
18
+ from .pipeline import ChromaPipeline, prepare_latent_image_ids
19
+ from einops import rearrange, repeat
20
+ import random
21
+ import torch.nn.functional as F
22
+ from .src.model import Chroma, chroma_params
23
+ from safetensors.torch import load_file, save_file
24
+ from toolkit.metadata import get_meta_for_safetensors
25
+ import huggingface_hub
26
+
27
+ if TYPE_CHECKING:
28
+ from toolkit.data_transfer_object.data_loader import DataLoaderBatchDTO
29
+
30
+ scheduler_config = {
31
+ "base_image_seq_len": 256,
32
+ "base_shift": 0.5,
33
+ "max_image_seq_len": 4096,
34
+ "max_shift": 1.15,
35
+ "num_train_timesteps": 1000,
36
+ "shift": 3.0,
37
+ "use_dynamic_shifting": True
38
+ }
39
+
40
+ class FakeConfig:
41
+ # for diffusers compatability
42
+ def __init__(self):
43
+ self.attention_head_dim = 128
44
+ self.guidance_embeds = True
45
+ self.in_channels = 64
46
+ self.joint_attention_dim = 4096
47
+ self.num_attention_heads = 24
48
+ self.num_layers = 19
49
+ self.num_single_layers = 38
50
+ self.patch_size = 1
51
+
52
+ class FakeCLIP(torch.nn.Module):
53
+ def __init__(self):
54
+ super().__init__()
55
+ self.dtype = torch.bfloat16
56
+ self.device = 'cuda'
57
+ self.text_model = None
58
+ self.tokenizer = None
59
+ self.model_max_length = 77
60
+
61
+ def forward(self, *args, **kwargs):
62
+ return torch.zeros(1, 1, 1).to(self.device)
63
+
64
+
65
+ class ChromaModel(BaseModel):
66
+ arch = "chroma"
67
+
68
+ def __init__(
69
+ self,
70
+ device,
71
+ model_config: ModelConfig,
72
+ dtype='bf16',
73
+ custom_pipeline=None,
74
+ noise_scheduler=None,
75
+ **kwargs
76
+ ):
77
+ super().__init__(
78
+ device,
79
+ model_config,
80
+ dtype,
81
+ custom_pipeline,
82
+ noise_scheduler,
83
+ **kwargs
84
+ )
85
+ self.is_flow_matching = True
86
+ self.is_transformer = True
87
+ self.target_lora_modules = ['Chroma']
88
+
89
+ # static method to get the noise scheduler
90
+ @staticmethod
91
+ def get_train_scheduler():
92
+ return CustomFlowMatchEulerDiscreteScheduler(**scheduler_config)
93
+
94
+ def get_bucket_divisibility(self):
95
+ # return the bucket divisibility for the model
96
+ return 32
97
+
98
+ def load_model(self):
99
+ dtype = self.torch_dtype
100
+
101
+ # will be updated if we detect a existing checkpoint in training folder
102
+ model_path = self.model_config.name_or_path
103
+
104
+ if model_path == "lodestones/Chroma":
105
+ print("Looking for latest Chroma checkpoint")
106
+ # get the latest checkpoint
107
+ files_list = huggingface_hub.list_repo_files(model_path)
108
+ print(files_list)
109
+ latest_version = 28 # current latest version at time of writing
110
+ while True:
111
+ if f"chroma-unlocked-v{latest_version}.safetensors" not in files_list:
112
+ latest_version -= 1
113
+ break
114
+ else:
115
+ latest_version += 1
116
+ print(f"Using latest Chroma version: v{latest_version}")
117
+
118
+ # make sure we have it
119
+ model_path = huggingface_hub.hf_hub_download(
120
+ repo_id=model_path,
121
+ filename=f"chroma-unlocked-v{latest_version}.safetensors",
122
+ )
123
+ elif model_path.startswith("lodestones/Chroma/v"):
124
+ # get the version number
125
+ version = model_path.split("/")[-1].split("v")[-1]
126
+ print(f"Using Chroma version: v{version}")
127
+ # make sure we have it
128
+ model_path = huggingface_hub.hf_hub_download(
129
+ repo_id='lodestones/Chroma',
130
+ filename=f"chroma-unlocked-v{version}.safetensors",
131
+ )
132
+ elif model_path.startswith("lodestones/Chroma1-"):
133
+ # will have a file in the repo that is Chroma1-whatever.safetensors
134
+ model_path = huggingface_hub.hf_hub_download(
135
+ repo_id=model_path,
136
+ filename=f"{model_path.split('/')[-1]}.safetensors",
137
+ )
138
+ else:
139
+ # check if the model path is a local file
140
+ if os.path.exists(model_path):
141
+ print(f"Using local model: {model_path}")
142
+ else:
143
+ raise ValueError(f"Model path {model_path} does not exist")
144
+
145
+ # extras_path = 'black-forest-labs/FLUX.1-schnell'
146
+ # schnell model is gated now, use flex instead
147
+ extras_path = 'ostris/Flex.1-alpha'
148
+
149
+ self.print_and_status_update("Loading transformer")
150
+
151
+ chroma_state_dict = load_file(model_path, 'cpu')
152
+
153
+ # determine number of double and single blocks
154
+ double_blocks = 0
155
+ single_blocks = 0
156
+ for key in chroma_state_dict.keys():
157
+ if "double_blocks" in key:
158
+ block_num = int(key.split(".")[1]) + 1
159
+ if block_num > double_blocks:
160
+ double_blocks = block_num
161
+ elif "single_blocks" in key:
162
+ block_num = int(key.split(".")[1]) + 1
163
+ if block_num > single_blocks:
164
+ single_blocks = block_num
165
+ print(f"Double Blocks: {double_blocks}")
166
+ print(f"Single Blocks: {single_blocks}")
167
+
168
+ chroma_params.depth = double_blocks
169
+ chroma_params.depth_single_blocks = single_blocks
170
+ transformer = Chroma(chroma_params)
171
+
172
+ # add dtype, not sure why it doesnt have it
173
+ transformer.dtype = dtype
174
+ # load the state dict into the model
175
+ transformer.load_state_dict(chroma_state_dict)
176
+
177
+ transformer.to(self.quantize_device, dtype=dtype)
178
+
179
+ transformer.config = FakeConfig()
180
+ transformer.config.num_layers = double_blocks
181
+ transformer.config.num_single_layers = single_blocks
182
+
183
+ if self.model_config.quantize:
184
+ # patch the state dict method
185
+ patch_dequantization_on_save(transformer)
186
+ quantization_type = get_qtype(self.model_config.qtype)
187
+ self.print_and_status_update("Quantizing transformer")
188
+ quantize(transformer, weights=quantization_type,
189
+ **self.model_config.quantize_kwargs)
190
+ freeze(transformer)
191
+ transformer.to(self.device_torch)
192
+ else:
193
+ transformer.to(self.device_torch, dtype=dtype)
194
+
195
+ flush()
196
+
197
+ self.print_and_status_update("Loading T5")
198
+ tokenizer_2 = T5TokenizerFast.from_pretrained(
199
+ extras_path, subfolder="tokenizer_2", torch_dtype=dtype
200
+ )
201
+ text_encoder_2 = T5EncoderModel.from_pretrained(
202
+ extras_path, subfolder="text_encoder_2", torch_dtype=dtype
203
+ )
204
+ text_encoder_2.to(self.device_torch, dtype=dtype)
205
+ flush()
206
+
207
+ if self.model_config.quantize_te:
208
+ self.print_and_status_update("Quantizing T5")
209
+ quantize(text_encoder_2, weights=get_qtype(
210
+ self.model_config.qtype))
211
+ freeze(text_encoder_2)
212
+ flush()
213
+
214
+ # self.print_and_status_update("Loading CLIP")
215
+ text_encoder = FakeCLIP()
216
+ tokenizer = FakeCLIP()
217
+ text_encoder.to(self.device_torch, dtype=dtype)
218
+
219
+ self.noise_scheduler = ChromaModel.get_train_scheduler()
220
+
221
+ self.print_and_status_update("Loading VAE")
222
+ vae = AutoencoderKL.from_pretrained(
223
+ extras_path,
224
+ subfolder="vae",
225
+ torch_dtype=dtype
226
+ )
227
+ vae = vae.to(self.device_torch, dtype=dtype)
228
+
229
+ self.print_and_status_update("Making pipe")
230
+
231
+ pipe: ChromaPipeline = ChromaPipeline(
232
+ scheduler=self.noise_scheduler,
233
+ text_encoder=text_encoder,
234
+ tokenizer=tokenizer,
235
+ text_encoder_2=None,
236
+ tokenizer_2=tokenizer_2,
237
+ vae=vae,
238
+ transformer=None,
239
+ )
240
+ # for quantization, it works best to do these after making the pipe
241
+ pipe.text_encoder_2 = text_encoder_2
242
+ pipe.transformer = transformer
243
+
244
+ self.print_and_status_update("Preparing Model")
245
+
246
+ text_encoder = [pipe.text_encoder, pipe.text_encoder_2]
247
+ tokenizer = [pipe.tokenizer, pipe.tokenizer_2]
248
+
249
+ pipe.transformer = pipe.transformer.to(self.device_torch)
250
+
251
+ flush()
252
+ # just to make sure everything is on the right device and dtype
253
+ text_encoder[0].to(self.device_torch)
254
+ text_encoder[0].requires_grad_(False)
255
+ text_encoder[0].eval()
256
+ text_encoder[1].to(self.device_torch)
257
+ text_encoder[1].requires_grad_(False)
258
+ text_encoder[1].eval()
259
+ pipe.transformer = pipe.transformer.to(self.device_torch)
260
+ flush()
261
+
262
+ # save it to the model class
263
+ self.vae = vae
264
+ self.text_encoder = text_encoder # list of text encoders
265
+ self.tokenizer = tokenizer # list of tokenizers
266
+ self.model = pipe.transformer
267
+ self.pipeline = pipe
268
+ self.print_and_status_update("Model Loaded")
269
+
270
+ def get_generation_pipeline(self):
271
+ scheduler = ChromaModel.get_train_scheduler()
272
+ pipeline = ChromaPipeline(
273
+ scheduler=scheduler,
274
+ text_encoder=unwrap_model(self.text_encoder[0]),
275
+ tokenizer=self.tokenizer[0],
276
+ text_encoder_2=unwrap_model(self.text_encoder[1]),
277
+ tokenizer_2=self.tokenizer[1],
278
+ vae=unwrap_model(self.vae),
279
+ transformer=unwrap_model(self.transformer)
280
+ )
281
+
282
+ # pipeline = pipeline.to(self.device_torch)
283
+
284
+ return pipeline
285
+
286
+ def generate_single_image(
287
+ self,
288
+ pipeline: ChromaPipeline,
289
+ gen_config: GenerateImageConfig,
290
+ conditional_embeds: PromptEmbeds,
291
+ unconditional_embeds: PromptEmbeds,
292
+ generator: torch.Generator,
293
+ extra: dict,
294
+ ):
295
+
296
+ extra['negative_prompt_embeds'] = unconditional_embeds.text_embeds
297
+ extra['negative_prompt_attn_mask'] = unconditional_embeds.attention_mask
298
+
299
+ img = pipeline(
300
+ prompt_embeds=conditional_embeds.text_embeds,
301
+ prompt_attn_mask=conditional_embeds.attention_mask,
302
+ height=gen_config.height,
303
+ width=gen_config.width,
304
+ num_inference_steps=gen_config.num_inference_steps,
305
+ guidance_scale=gen_config.guidance_scale,
306
+ latents=gen_config.latents,
307
+ generator=generator,
308
+ **extra
309
+ ).images[0]
310
+ return img
311
+
312
+ def get_noise_prediction(
313
+ self,
314
+ latent_model_input: torch.Tensor,
315
+ timestep: torch.Tensor, # 0 to 1000 scale
316
+ text_embeddings: PromptEmbeds,
317
+ **kwargs
318
+ ):
319
+ with torch.no_grad():
320
+ bs, c, h, w = latent_model_input.shape
321
+ latent_model_input_packed = rearrange(
322
+ latent_model_input,
323
+ "b c (h ph) (w pw) -> b (h w) (c ph pw)",
324
+ ph=2,
325
+ pw=2
326
+ )
327
+
328
+ img_ids = prepare_latent_image_ids(
329
+ bs,
330
+ h,
331
+ w,
332
+ patch_size=2
333
+ ).to(device=self.device_torch)
334
+
335
+ # img_ids = torch.zeros(h // 2, w // 2, 3)
336
+ # img_ids[..., 1] = img_ids[..., 1] + torch.arange(h // 2)[:, None]
337
+ # img_ids[..., 2] = img_ids[..., 2] + torch.arange(w // 2)[None, :]
338
+ # img_ids = repeat(img_ids, "h w c -> b (h w) c",
339
+ # b=bs).to(self.device_torch)
340
+
341
+ txt_ids = torch.zeros(
342
+ bs, text_embeddings.text_embeds.shape[1], 3).to(self.device_torch)
343
+
344
+ guidance = torch.full([1], 0, device=self.device_torch, dtype=torch.float32)
345
+ guidance = guidance.expand(latent_model_input_packed.shape[0])
346
+
347
+ cast_dtype = self.unet.dtype
348
+
349
+ noise_pred = self.unet(
350
+ img=latent_model_input_packed.to(
351
+ self.device_torch, cast_dtype
352
+ ),
353
+ img_ids=img_ids,
354
+ txt=text_embeddings.text_embeds.to(
355
+ self.device_torch, cast_dtype
356
+ ),
357
+ txt_ids=txt_ids,
358
+ txt_mask=text_embeddings.attention_mask.to(
359
+ self.device_torch, cast_dtype
360
+ ),
361
+ timesteps=timestep / 1000,
362
+ guidance=guidance
363
+ )
364
+
365
+ if isinstance(noise_pred, QTensor):
366
+ noise_pred = noise_pred.dequantize()
367
+
368
+ noise_pred = rearrange(
369
+ noise_pred,
370
+ "b (h w) (c ph pw) -> b c (h ph) (w pw)",
371
+ h=latent_model_input.shape[2] // 2,
372
+ w=latent_model_input.shape[3] // 2,
373
+ ph=2,
374
+ pw=2,
375
+ c=self.vae.config.latent_channels
376
+ )
377
+
378
+ return noise_pred
379
+
380
+ def get_prompt_embeds(self, prompt: str) -> PromptEmbeds:
381
+ if isinstance(prompt, str):
382
+ prompts = [prompt]
383
+ else:
384
+ prompts = prompt
385
+ if self.pipeline.text_encoder.device != self.device_torch:
386
+ self.pipeline.text_encoder.to(self.device_torch)
387
+
388
+ max_length = 512
389
+
390
+ device = self.text_encoder[1].device
391
+ dtype = self.text_encoder[1].dtype
392
+
393
+ # T5
394
+ text_inputs = self.tokenizer[1](
395
+ prompts,
396
+ padding="max_length",
397
+ max_length=max_length,
398
+ truncation=True,
399
+ return_length=False,
400
+ return_overflowing_tokens=False,
401
+ return_tensors="pt",
402
+ )
403
+ text_input_ids = text_inputs.input_ids
404
+
405
+ prompt_embeds = self.text_encoder[1](text_input_ids.to(device), output_hidden_states=False)[0]
406
+
407
+ dtype = self.text_encoder[1].dtype
408
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
409
+
410
+ prompt_attention_mask = text_inputs["attention_mask"]
411
+
412
+ pe = PromptEmbeds(
413
+ prompt_embeds
414
+ )
415
+ pe.attention_mask = prompt_attention_mask
416
+ return pe
417
+
418
+ def get_model_has_grad(self):
419
+ # return from a weight if it has grad
420
+ return self.model.final_layer.linear.weight.requires_grad
421
+
422
+ def get_te_has_grad(self):
423
+ # return from a weight if it has grad
424
+ return self.text_encoder[1].encoder.block[0].layer[0].SelfAttention.q.weight.requires_grad
425
+
426
+ def save_model(self, output_path, meta, save_dtype):
427
+ if not output_path.endswith(".safetensors"):
428
+ output_path = output_path + ".safetensors"
429
+ # only save the unet
430
+ transformer: Chroma = unwrap_model(self.model)
431
+ state_dict = transformer.state_dict()
432
+ save_dict = {}
433
+ for k, v in state_dict.items():
434
+ if isinstance(v, QTensor):
435
+ v = v.dequantize()
436
+ save_dict[k] = v.clone().to('cpu', dtype=save_dtype)
437
+
438
+ meta = get_meta_for_safetensors(meta, name='chroma')
439
+ save_file(save_dict, output_path, metadata=meta)
440
+
441
+ def get_loss_target(self, *args, **kwargs):
442
+ noise = kwargs.get('noise')
443
+ batch = kwargs.get('batch')
444
+ return (noise - batch.latents).detach()
445
+
446
+ def convert_lora_weights_before_save(self, state_dict):
447
+ # currently starte with transformer. but needs to start with diffusion_model. for comfyui
448
+ new_sd = {}
449
+ for key, value in state_dict.items():
450
+ new_key = key.replace("transformer.", "diffusion_model.")
451
+ new_sd[new_key] = value
452
+ return new_sd
453
+
454
+ def convert_lora_weights_before_load(self, state_dict):
455
+ # saved as diffusion_model. but needs to be transformer. for ai-toolkit
456
+ new_sd = {}
457
+ for key, value in state_dict.items():
458
+ new_key = key.replace("diffusion_model.", "transformer.")
459
+ new_sd[new_key] = value
460
+ return new_sd
461
+
462
+ def get_base_model_version(self):
463
+ return "chroma"