comdoleger commited on
Commit
470d67e
·
verified ·
1 Parent(s): 99ef8e0

Upload jobs/process/TrainSliderProcess.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. jobs/process/TrainSliderProcess.py +691 -0
jobs/process/TrainSliderProcess.py ADDED
@@ -0,0 +1,691 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import os
3
+ import random
4
+ from collections import OrderedDict
5
+ from typing import Union
6
+
7
+ from PIL import Image
8
+ from diffusers import T2IAdapter
9
+ from torchvision.transforms import transforms
10
+ from tqdm import tqdm
11
+
12
+ from toolkit.basic import value_map
13
+ from toolkit.config_modules import SliderConfig
14
+ from toolkit.data_transfer_object.data_loader import DataLoaderBatchDTO
15
+ from toolkit.sd_device_states_presets import get_train_sd_device_state_preset
16
+ from toolkit.train_tools import get_torch_dtype, apply_snr_weight, apply_learnable_snr_gos
17
+ import gc
18
+ from toolkit import train_tools
19
+ from toolkit.prompt_utils import \
20
+ EncodedPromptPair, ACTION_TYPES_SLIDER, \
21
+ EncodedAnchor, concat_prompt_pairs, \
22
+ concat_anchors, PromptEmbedsCache, encode_prompts_to_cache, build_prompt_pair_batch_from_cache, split_anchors, \
23
+ split_prompt_pairs
24
+
25
+ import torch
26
+ from .BaseSDTrainProcess import BaseSDTrainProcess
27
+
28
+
29
+ def flush():
30
+ torch.cuda.empty_cache()
31
+ gc.collect()
32
+
33
+
34
+ adapter_transforms = transforms.Compose([
35
+ transforms.ToTensor(),
36
+ ])
37
+
38
+
39
+ class TrainSliderProcess(BaseSDTrainProcess):
40
+ def __init__(self, process_id: int, job, config: OrderedDict):
41
+ super().__init__(process_id, job, config)
42
+ self.prompt_txt_list = None
43
+ self.step_num = 0
44
+ self.start_step = 0
45
+ self.device = self.get_conf('device', self.job.device)
46
+ self.device_torch = torch.device(self.device)
47
+ self.slider_config = SliderConfig(**self.get_conf('slider', {}))
48
+ self.prompt_cache = PromptEmbedsCache()
49
+ self.prompt_pairs: list[EncodedPromptPair] = []
50
+ self.anchor_pairs: list[EncodedAnchor] = []
51
+ # keep track of prompt chunk size
52
+ self.prompt_chunk_size = 1
53
+
54
+ # check if we have more targets than steps
55
+ # this can happen because of permutation son shuffling
56
+ if len(self.slider_config.targets) > self.train_config.steps:
57
+ # trim targets
58
+ self.slider_config.targets = self.slider_config.targets[:self.train_config.steps]
59
+
60
+ # get presets
61
+ self.eval_slider_device_state = get_train_sd_device_state_preset(
62
+ self.device_torch,
63
+ train_unet=False,
64
+ train_text_encoder=False,
65
+ cached_latents=self.is_latents_cached,
66
+ train_lora=False,
67
+ train_adapter=False,
68
+ train_embedding=False,
69
+ )
70
+
71
+ self.train_slider_device_state = get_train_sd_device_state_preset(
72
+ self.device_torch,
73
+ train_unet=self.train_config.train_unet,
74
+ train_text_encoder=False,
75
+ cached_latents=self.is_latents_cached,
76
+ train_lora=True,
77
+ train_adapter=False,
78
+ train_embedding=False,
79
+ )
80
+
81
+ def before_model_load(self):
82
+ pass
83
+
84
+ def hook_before_train_loop(self):
85
+
86
+ # read line by line from file
87
+ if self.slider_config.prompt_file:
88
+ self.print(f"Loading prompt file from {self.slider_config.prompt_file}")
89
+ with open(self.slider_config.prompt_file, 'r', encoding='utf-8') as f:
90
+ self.prompt_txt_list = f.readlines()
91
+ # clean empty lines
92
+ self.prompt_txt_list = [line.strip() for line in self.prompt_txt_list if len(line.strip()) > 0]
93
+
94
+ self.print(f"Found {len(self.prompt_txt_list)} prompts.")
95
+
96
+ if not self.slider_config.prompt_tensors:
97
+ print(f"Prompt tensors not found. Building prompt tensors for {self.train_config.steps} steps.")
98
+ # shuffle
99
+ random.shuffle(self.prompt_txt_list)
100
+ # trim to max steps
101
+ self.prompt_txt_list = self.prompt_txt_list[:self.train_config.steps]
102
+ # trim list to our max steps
103
+
104
+ cache = PromptEmbedsCache()
105
+ print(f"Building prompt cache")
106
+
107
+ # get encoded latents for our prompts
108
+ with torch.no_grad():
109
+ # list of neutrals. Can come from file or be empty
110
+ neutral_list = self.prompt_txt_list if self.prompt_txt_list is not None else [""]
111
+
112
+ # build the prompts to cache
113
+ prompts_to_cache = []
114
+ for neutral in neutral_list:
115
+ for target in self.slider_config.targets:
116
+ prompt_list = [
117
+ f"{target.target_class}", # target_class
118
+ f"{target.target_class} {neutral}", # target_class with neutral
119
+ f"{target.positive}", # positive_target
120
+ f"{target.positive} {neutral}", # positive_target with neutral
121
+ f"{target.negative}", # negative_target
122
+ f"{target.negative} {neutral}", # negative_target with neutral
123
+ f"{neutral}", # neutral
124
+ f"{target.positive} {target.negative}", # both targets
125
+ f"{target.negative} {target.positive}", # both targets reverse
126
+ ]
127
+ prompts_to_cache += prompt_list
128
+
129
+ # remove duplicates
130
+ prompts_to_cache = list(dict.fromkeys(prompts_to_cache))
131
+
132
+ # trim to max steps if max steps is lower than prompt count
133
+ # todo, this can break if we have more targets than steps, should be fixed, by reducing permuations, but could stil happen with low steps
134
+ # prompts_to_cache = prompts_to_cache[:self.train_config.steps]
135
+
136
+ # encode them
137
+ cache = encode_prompts_to_cache(
138
+ prompt_list=prompts_to_cache,
139
+ sd=self.sd,
140
+ cache=cache,
141
+ prompt_tensor_file=self.slider_config.prompt_tensors
142
+ )
143
+
144
+ prompt_pairs = []
145
+ prompt_batches = []
146
+ for neutral in tqdm(neutral_list, desc="Building Prompt Pairs", leave=False):
147
+ for target in self.slider_config.targets:
148
+ prompt_pair_batch = build_prompt_pair_batch_from_cache(
149
+ cache=cache,
150
+ target=target,
151
+ neutral=neutral,
152
+
153
+ )
154
+ if self.slider_config.batch_full_slide:
155
+ # concat the prompt pairs
156
+ # this allows us to run the entire 4 part process in one shot (for slider)
157
+ self.prompt_chunk_size = 4
158
+ concat_prompt_pair_batch = concat_prompt_pairs(prompt_pair_batch).to('cpu')
159
+ prompt_pairs += [concat_prompt_pair_batch]
160
+ else:
161
+ self.prompt_chunk_size = 1
162
+ # do them one at a time (probably not necessary after new optimizations)
163
+ prompt_pairs += [x.to('cpu') for x in prompt_pair_batch]
164
+
165
+ # setup anchors
166
+ anchor_pairs = []
167
+ for anchor in self.slider_config.anchors:
168
+ # build the cache
169
+ for prompt in [
170
+ anchor.prompt,
171
+ anchor.neg_prompt # empty neutral
172
+ ]:
173
+ if cache[prompt] == None:
174
+ cache[prompt] = self.sd.encode_prompt(prompt)
175
+
176
+ anchor_batch = []
177
+ # we get the prompt pair multiplier from first prompt pair
178
+ # since they are all the same. We need to match their network polarity
179
+ prompt_pair_multipliers = prompt_pairs[0].multiplier_list
180
+ for prompt_multiplier in prompt_pair_multipliers:
181
+ # match the network multiplier polarity
182
+ anchor_scalar = 1.0 if prompt_multiplier > 0 else -1.0
183
+ anchor_batch += [
184
+ EncodedAnchor(
185
+ prompt=cache[anchor.prompt],
186
+ neg_prompt=cache[anchor.neg_prompt],
187
+ multiplier=anchor.multiplier * anchor_scalar
188
+ )
189
+ ]
190
+
191
+ anchor_pairs += [
192
+ concat_anchors(anchor_batch).to('cpu')
193
+ ]
194
+ if len(anchor_pairs) > 0:
195
+ self.anchor_pairs = anchor_pairs
196
+
197
+ # move to cpu to save vram
198
+ # We don't need text encoder anymore, but keep it on cpu for sampling
199
+ # if text encoder is list
200
+ if isinstance(self.sd.text_encoder, list):
201
+ for encoder in self.sd.text_encoder:
202
+ encoder.to("cpu")
203
+ else:
204
+ self.sd.text_encoder.to("cpu")
205
+ self.prompt_cache = cache
206
+ self.prompt_pairs = prompt_pairs
207
+ # self.anchor_pairs = anchor_pairs
208
+ flush()
209
+ if self.data_loader is not None:
210
+ # we will have images, prep the vae
211
+ self.sd.vae.eval()
212
+ self.sd.vae.to(self.device_torch)
213
+ # end hook_before_train_loop
214
+
215
+ def before_dataset_load(self):
216
+ if self.slider_config.use_adapter == 'depth':
217
+ print(f"Loading T2I Adapter for depth")
218
+ # called before LoRA network is loaded but after model is loaded
219
+ # attach the adapter here so it is there before we load the network
220
+ adapter_path = 'TencentARC/t2iadapter_depth_sd15v2'
221
+ if self.model_config.is_xl:
222
+ adapter_path = 'TencentARC/t2i-adapter-depth-midas-sdxl-1.0'
223
+
224
+ print(f"Loading T2I Adapter from {adapter_path}")
225
+
226
+ # dont name this adapter since we are not training it
227
+ self.t2i_adapter = T2IAdapter.from_pretrained(
228
+ adapter_path, torch_dtype=get_torch_dtype(self.train_config.dtype), varient="fp16"
229
+ ).to(self.device_torch)
230
+ self.t2i_adapter.eval()
231
+ self.t2i_adapter.requires_grad_(False)
232
+ flush()
233
+
234
+ @torch.no_grad()
235
+ def get_adapter_images(self, batch: Union[None, 'DataLoaderBatchDTO']):
236
+
237
+ img_ext_list = ['.jpg', '.jpeg', '.png', '.webp']
238
+ adapter_folder_path = self.slider_config.adapter_img_dir
239
+ adapter_images = []
240
+ # loop through images
241
+ for file_item in batch.file_items:
242
+ img_path = file_item.path
243
+ file_name_no_ext = os.path.basename(img_path).split('.')[0]
244
+ # find the image
245
+ for ext in img_ext_list:
246
+ if os.path.exists(os.path.join(adapter_folder_path, file_name_no_ext + ext)):
247
+ adapter_images.append(os.path.join(adapter_folder_path, file_name_no_ext + ext))
248
+ break
249
+ width, height = batch.file_items[0].crop_width, batch.file_items[0].crop_height
250
+ adapter_tensors = []
251
+ # load images with torch transforms
252
+ for idx, adapter_image in enumerate(adapter_images):
253
+ # we need to centrally crop the largest dimension of the image to match the batch shape after scaling
254
+ # to the smallest dimension
255
+ img: Image.Image = Image.open(adapter_image)
256
+ if img.width > img.height:
257
+ # scale down so height is the same as batch
258
+ new_height = height
259
+ new_width = int(img.width * (height / img.height))
260
+ else:
261
+ new_width = width
262
+ new_height = int(img.height * (width / img.width))
263
+
264
+ img = img.resize((new_width, new_height))
265
+ crop_fn = transforms.CenterCrop((height, width))
266
+ # crop the center to match batch
267
+ img = crop_fn(img)
268
+ img = adapter_transforms(img)
269
+ adapter_tensors.append(img)
270
+
271
+ # stack them
272
+ adapter_tensors = torch.stack(adapter_tensors).to(
273
+ self.device_torch, dtype=get_torch_dtype(self.train_config.dtype)
274
+ )
275
+ return adapter_tensors
276
+
277
+ def hook_train_loop(self, batch: Union['DataLoaderBatchDTO', None]):
278
+ if isinstance(batch, list):
279
+ batch = batch[0]
280
+ # set to eval mode
281
+ self.sd.set_device_state(self.eval_slider_device_state)
282
+ with torch.no_grad():
283
+ dtype = get_torch_dtype(self.train_config.dtype)
284
+
285
+ # get a random pair
286
+ prompt_pair: EncodedPromptPair = self.prompt_pairs[
287
+ torch.randint(0, len(self.prompt_pairs), (1,)).item()
288
+ ]
289
+ # move to device and dtype
290
+ prompt_pair.to(self.device_torch, dtype=dtype)
291
+
292
+ # get a random resolution
293
+ height, width = self.slider_config.resolutions[
294
+ torch.randint(0, len(self.slider_config.resolutions), (1,)).item()
295
+ ]
296
+ if self.train_config.gradient_checkpointing:
297
+ # may get disabled elsewhere
298
+ self.sd.unet.enable_gradient_checkpointing()
299
+
300
+ noise_scheduler = self.sd.noise_scheduler
301
+ optimizer = self.optimizer
302
+ lr_scheduler = self.lr_scheduler
303
+
304
+ loss_function = torch.nn.MSELoss()
305
+
306
+ pred_kwargs = {}
307
+
308
+ def get_noise_pred(neg, pos, gs, cts, dn):
309
+ down_kwargs = copy.deepcopy(pred_kwargs)
310
+ if 'down_block_additional_residuals' in down_kwargs:
311
+ dbr_batch_size = down_kwargs['down_block_additional_residuals'][0].shape[0]
312
+ if dbr_batch_size != dn.shape[0]:
313
+ amount_to_add = int(dn.shape[0] * 2 / dbr_batch_size)
314
+ down_kwargs['down_block_additional_residuals'] = [
315
+ torch.cat([sample.clone()] * amount_to_add) for sample in
316
+ down_kwargs['down_block_additional_residuals']
317
+ ]
318
+ return self.sd.predict_noise(
319
+ latents=dn,
320
+ text_embeddings=train_tools.concat_prompt_embeddings(
321
+ neg, # negative prompt
322
+ pos, # positive prompt
323
+ self.train_config.batch_size,
324
+ ),
325
+ timestep=cts,
326
+ guidance_scale=gs,
327
+ **down_kwargs
328
+ )
329
+
330
+ with torch.no_grad():
331
+ adapter_images = None
332
+ self.sd.unet.eval()
333
+
334
+ # for a complete slider, the batch size is 4 to begin with now
335
+ true_batch_size = prompt_pair.target_class.text_embeds.shape[0] * self.train_config.batch_size
336
+ from_batch = False
337
+ if batch is not None:
338
+ # traing from a batch of images, not generating ourselves
339
+ from_batch = True
340
+ noisy_latents, noise, timesteps, conditioned_prompts, imgs = self.process_general_training_batch(batch)
341
+ if self.slider_config.adapter_img_dir is not None:
342
+ adapter_images = self.get_adapter_images(batch)
343
+ adapter_strength_min = 0.9
344
+ adapter_strength_max = 1.0
345
+
346
+ def rand_strength(sample):
347
+ adapter_conditioning_scale = torch.rand(
348
+ (1,), device=self.device_torch, dtype=dtype
349
+ )
350
+
351
+ adapter_conditioning_scale = value_map(
352
+ adapter_conditioning_scale,
353
+ 0.0,
354
+ 1.0,
355
+ adapter_strength_min,
356
+ adapter_strength_max
357
+ )
358
+ return sample.to(self.device_torch, dtype=dtype).detach() * adapter_conditioning_scale
359
+
360
+ down_block_additional_residuals = self.t2i_adapter(adapter_images)
361
+ down_block_additional_residuals = [
362
+ rand_strength(sample) for sample in down_block_additional_residuals
363
+ ]
364
+ pred_kwargs['down_block_additional_residuals'] = down_block_additional_residuals
365
+
366
+ # denoised_latents = torch.cat([noisy_latents] * self.prompt_chunk_size, dim=0)
367
+ denoised_latents = noisy_latents
368
+ current_timestep = timesteps
369
+ else:
370
+ if self.train_config.noise_scheduler == 'flowmatch':
371
+ linear_timesteps = any([
372
+ self.train_config.linear_timesteps,
373
+ self.train_config.linear_timesteps2,
374
+ self.train_config.timestep_type == 'linear',
375
+ ])
376
+
377
+ timestep_type = 'linear' if linear_timesteps else None
378
+ if timestep_type is None:
379
+ timestep_type = self.train_config.timestep_type
380
+
381
+ # make fake latents
382
+ l = torch.randn(
383
+ true_batch_size, 16, height, width
384
+ ).to(self.device_torch, dtype=dtype)
385
+
386
+ self.sd.noise_scheduler.set_train_timesteps(
387
+ self.train_config.max_denoising_steps,
388
+ device=self.device_torch,
389
+ timestep_type=timestep_type,
390
+ latents=l
391
+ )
392
+ else:
393
+ self.sd.noise_scheduler.set_timesteps(
394
+ self.train_config.max_denoising_steps, device=self.device_torch
395
+ )
396
+
397
+ # ger a random number of steps
398
+ timesteps_to = torch.randint(
399
+ 1, self.train_config.max_denoising_steps - 1, (1,)
400
+ ).item()
401
+
402
+ # get noise
403
+ noise = self.sd.get_latent_noise(
404
+ pixel_height=height,
405
+ pixel_width=width,
406
+ batch_size=true_batch_size,
407
+ noise_offset=self.train_config.noise_offset,
408
+ ).to(self.device_torch, dtype=dtype)
409
+
410
+ # get latents
411
+ latents = noise * self.sd.noise_scheduler.init_noise_sigma
412
+ latents = latents.to(self.device_torch, dtype=dtype)
413
+
414
+ assert not self.network.is_active
415
+ self.sd.unet.eval()
416
+ # pass the multiplier list to the network
417
+ # double up since we are doing cfg
418
+ self.network.multiplier = prompt_pair.multiplier_list + prompt_pair.multiplier_list
419
+ denoised_latents = self.sd.diffuse_some_steps(
420
+ latents, # pass simple noise latents
421
+ prompt_pair.target_class,
422
+ start_timesteps=0,
423
+ total_timesteps=timesteps_to,
424
+ guidance_scale=3,
425
+ bypass_guidance_embedding=False
426
+ )
427
+ if hasattr(self.sd.noise_scheduler, 'set_train_timesteps'):
428
+ noise_scheduler.set_train_timesteps(1000, device=self.device_torch)
429
+ else:
430
+ noise_scheduler.set_timesteps(1000)
431
+
432
+ current_timestep_index = int(timesteps_to * 1000 / self.train_config.max_denoising_steps)
433
+ current_timestep = noise_scheduler.timesteps[current_timestep_index]
434
+
435
+ # split the latents into out prompt pair chunks
436
+ # denoised_latent_chunks = torch.chunk(denoised_latents, self.prompt_chunk_size, dim=0)
437
+ # denoised_latent_chunks = [x.detach() for x in denoised_latent_chunks]
438
+ denoised_latent_chunks = [denoised_latents]
439
+
440
+ # flush() # 4.2GB to 3GB on 512x512
441
+ mask_multiplier = torch.ones((denoised_latents.shape[0], 1, 1, 1), device=self.device_torch, dtype=dtype)
442
+ has_mask = False
443
+ if batch and batch.mask_tensor is not None:
444
+ with self.timer('get_mask_multiplier'):
445
+ # upsampling no supported for bfloat16
446
+ mask_multiplier = batch.mask_tensor.to(self.device_torch, dtype=torch.float16).detach()
447
+ # scale down to the size of the latents, mask multiplier shape(bs, 1, width, height), noisy_latents shape(bs, channels, width, height)
448
+ mask_multiplier = torch.nn.functional.interpolate(
449
+ mask_multiplier, size=(noisy_latents.shape[2], noisy_latents.shape[3])
450
+ )
451
+ # expand to match latents
452
+ mask_multiplier = mask_multiplier.expand(-1, noisy_latents.shape[1], -1, -1)
453
+ mask_multiplier = mask_multiplier.to(self.device_torch, dtype=dtype).detach()
454
+ has_mask = True
455
+
456
+ if has_mask:
457
+ unmasked_target = get_noise_pred(
458
+ prompt_pair.positive_target, # negative prompt
459
+ prompt_pair.target_class, # positive prompt
460
+ 1,
461
+ current_timestep,
462
+ denoised_latents
463
+ )
464
+ unmasked_target = unmasked_target.detach()
465
+ unmasked_target.requires_grad = False
466
+ else:
467
+ unmasked_target = None
468
+
469
+ # 4.20 GB RAM for 512x512
470
+ # positive_latents = get_noise_pred(
471
+ # prompt_pair.positive_target, # negative prompt
472
+ # prompt_pair.negative_target, # positive prompt
473
+ # 1,
474
+ # current_timestep,
475
+ # denoised_latents
476
+ # )
477
+ # positive_latents = positive_latents.detach()
478
+ # positive_latents.requires_grad = False
479
+
480
+ # neutral_latents = get_noise_pred(
481
+ # prompt_pair.positive_target, # negative prompt
482
+ # prompt_pair.empty_prompt, # positive prompt (normally neutral
483
+ # 1,
484
+ # current_timestep,
485
+ # denoised_latents
486
+ # )
487
+ # neutral_latents = neutral_latents.detach()
488
+ # neutral_latents.requires_grad = False
489
+
490
+ # unconditional_latents = get_noise_pred(
491
+ # prompt_pair.positive_target, # negative prompt
492
+ # prompt_pair.positive_target, # positive prompt
493
+ # 1,
494
+ # current_timestep,
495
+ # denoised_latents
496
+ # )
497
+ # unconditional_latents = unconditional_latents.detach()
498
+ # unconditional_latents.requires_grad = False
499
+
500
+ # we just need positive target, negative target, and empty prompt to calculate all
501
+ # since we are in no grad, we can easily do it in a single step
502
+ embeddings = train_tools.concat_prompt_embeddings(
503
+ prompt_pair.positive_target,
504
+ prompt_pair.empty_prompt,
505
+ 1
506
+ )
507
+ embeddings = train_tools.concat_prompt_embeddings(
508
+ embeddings,
509
+ prompt_pair.negative_target,
510
+ 1
511
+ )
512
+ all_pred = self.sd.predict_noise(
513
+ latents=torch.cat([denoised_latents] * 3, dim=0),
514
+ text_embeddings=embeddings,
515
+ timestep=torch.cat([current_timestep] * 3, dim=0),
516
+ )
517
+ all_pred = all_pred.detach()
518
+ all_pred.requires_grad = False
519
+ positive_pred, neutral_pred, unconditional_pred = torch.chunk(all_pred, 3, dim=0)
520
+
521
+ # doing them backward here as it was originally for erasing
522
+ positive_latents = unconditional_pred
523
+ neutral_latents = neutral_pred
524
+ unconditional_latents = positive_pred
525
+
526
+
527
+ denoised_latents = denoised_latents.detach()
528
+
529
+ self.sd.set_device_state(self.train_slider_device_state)
530
+ self.sd.unet.train()
531
+ # start accumulating gradients
532
+ self.optimizer.zero_grad(set_to_none=True)
533
+
534
+ anchor_loss_float = None
535
+
536
+ with torch.no_grad():
537
+ if self.slider_config.low_ram:
538
+ prompt_pair_chunks = split_prompt_pairs(prompt_pair.detach(), self.prompt_chunk_size)
539
+ denoised_latent_chunks = denoised_latent_chunks # just to have it in one place
540
+ positive_latents_chunks = torch.chunk(positive_latents.detach(), self.prompt_chunk_size, dim=0)
541
+ neutral_latents_chunks = torch.chunk(neutral_latents.detach(), self.prompt_chunk_size, dim=0)
542
+ unconditional_latents_chunks = torch.chunk(
543
+ unconditional_latents.detach(),
544
+ self.prompt_chunk_size,
545
+ dim=0
546
+ )
547
+ mask_multiplier_chunks = torch.chunk(mask_multiplier, self.prompt_chunk_size, dim=0)
548
+ if unmasked_target is not None:
549
+ unmasked_target_chunks = torch.chunk(unmasked_target, self.prompt_chunk_size, dim=0)
550
+ else:
551
+ unmasked_target_chunks = [None for _ in range(self.prompt_chunk_size)]
552
+ else:
553
+ # run through in one instance
554
+ prompt_pair_chunks = [prompt_pair.detach()]
555
+ denoised_latent_chunks = [torch.cat(denoised_latent_chunks, dim=0).detach()]
556
+ positive_latents_chunks = [positive_latents.detach()]
557
+ neutral_latents_chunks = [neutral_latents.detach()]
558
+ unconditional_latents_chunks = [unconditional_latents.detach()]
559
+ mask_multiplier_chunks = [mask_multiplier]
560
+ unmasked_target_chunks = [unmasked_target]
561
+
562
+ # flush()
563
+ assert len(prompt_pair_chunks) == len(denoised_latent_chunks)
564
+ # 3.28 GB RAM for 512x512
565
+ with self.network:
566
+ assert self.network.is_active
567
+ loss_list = []
568
+ for prompt_pair_chunk, \
569
+ denoised_latent_chunk, \
570
+ positive_latents_chunk, \
571
+ neutral_latents_chunk, \
572
+ unconditional_latents_chunk, \
573
+ mask_multiplier_chunk, \
574
+ unmasked_target_chunk \
575
+ in zip(
576
+ prompt_pair_chunks,
577
+ denoised_latent_chunks,
578
+ positive_latents_chunks,
579
+ neutral_latents_chunks,
580
+ unconditional_latents_chunks,
581
+ mask_multiplier_chunks,
582
+ unmasked_target_chunks
583
+ ):
584
+ self.network.multiplier = prompt_pair_chunk.multiplier_list
585
+
586
+ target_latents = self.sd.predict_noise(
587
+ latents=denoised_latent_chunk.detach(),
588
+ text_embeddings=prompt_pair_chunk.target_class,
589
+ timestep=current_timestep,
590
+ )
591
+
592
+ guidance_scale = 1.0
593
+
594
+ offset = guidance_scale * (positive_latents_chunk - unconditional_latents_chunk)
595
+
596
+ # make offset multiplier based on actions
597
+ offset_multiplier_list = []
598
+ for action in prompt_pair_chunk.action_list:
599
+ if action == ACTION_TYPES_SLIDER.ERASE_NEGATIVE:
600
+ offset_multiplier_list += [-1.0]
601
+ elif action == ACTION_TYPES_SLIDER.ENHANCE_NEGATIVE:
602
+ offset_multiplier_list += [1.0]
603
+
604
+ offset_multiplier = torch.tensor(offset_multiplier_list).to(offset.device, dtype=offset.dtype)
605
+ # make offset multiplier match rank of offset
606
+ offset_multiplier = offset_multiplier.view(offset.shape[0], 1, 1, 1)
607
+ offset *= offset_multiplier
608
+
609
+ offset_neutral = neutral_latents_chunk
610
+ # offsets are already adjusted on a per-batch basis
611
+ offset_neutral += offset
612
+ offset_neutral = offset_neutral.detach().requires_grad_(False)
613
+
614
+ # 16.15 GB RAM for 512x512 -> 4.20GB RAM for 512x512 with new grad_checkpointing
615
+ loss = torch.nn.functional.mse_loss(target_latents.float(), offset_neutral.float(), reduction="none")
616
+
617
+ # do inverted mask to preserve non masked
618
+ if has_mask and unmasked_target_chunk is not None:
619
+ loss = loss * mask_multiplier_chunk
620
+ # match the mask unmasked_target_chunk
621
+ mask_target_loss = torch.nn.functional.mse_loss(
622
+ target_latents.float(),
623
+ unmasked_target_chunk.float(),
624
+ reduction="none"
625
+ )
626
+ mask_target_loss = mask_target_loss * (1.0 - mask_multiplier_chunk)
627
+ loss += mask_target_loss
628
+
629
+ loss = loss.mean([1, 2, 3])
630
+
631
+ if self.train_config.learnable_snr_gos:
632
+ if from_batch:
633
+ # match batch size
634
+ loss = apply_snr_weight(loss, timesteps, self.sd.noise_scheduler,
635
+ self.train_config.min_snr_gamma)
636
+ else:
637
+ # match batch size
638
+ timesteps_index_list = [current_timestep_index for _ in range(target_latents.shape[0])]
639
+ # add snr_gamma
640
+ loss = apply_learnable_snr_gos(loss, timesteps_index_list, self.snr_gos)
641
+ if self.train_config.min_snr_gamma is not None and self.train_config.min_snr_gamma > 0.000001:
642
+ if from_batch:
643
+ # match batch size
644
+ loss = apply_snr_weight(loss, timesteps, self.sd.noise_scheduler,
645
+ self.train_config.min_snr_gamma)
646
+ else:
647
+ # match batch size
648
+ timesteps_index_list = [current_timestep_index for _ in range(target_latents.shape[0])]
649
+ # add min_snr_gamma
650
+ loss = apply_snr_weight(loss, timesteps_index_list, noise_scheduler,
651
+ self.train_config.min_snr_gamma)
652
+
653
+
654
+ loss = loss.mean() * prompt_pair_chunk.weight
655
+
656
+ loss.backward()
657
+ loss_list.append(loss.item())
658
+ del target_latents
659
+ del offset_neutral
660
+ del loss
661
+ # flush()
662
+
663
+ optimizer.step()
664
+ lr_scheduler.step()
665
+
666
+ loss_float = sum(loss_list) / len(loss_list)
667
+ if anchor_loss_float is not None:
668
+ loss_float += anchor_loss_float
669
+
670
+ del (
671
+ positive_latents,
672
+ neutral_latents,
673
+ unconditional_latents,
674
+ # latents
675
+ )
676
+ # move back to cpu
677
+ prompt_pair.to("cpu")
678
+ # flush()
679
+
680
+ # reset network
681
+ self.network.multiplier = 1.0
682
+
683
+ loss_dict = OrderedDict(
684
+ {'loss': loss_float},
685
+ )
686
+ if anchor_loss_float is not None:
687
+ loss_dict['sl_l'] = loss_float
688
+ loss_dict['an_l'] = anchor_loss_float
689
+
690
+ return loss_dict
691
+ # end hook_train_loop