comdoleger commited on
Commit
0ec6ccc
·
verified ·
1 Parent(s): 470d67e

Upload jobs/process/TrainSliderProcessOld.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. jobs/process/TrainSliderProcessOld.py +404 -0
jobs/process/TrainSliderProcessOld.py ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ref:
2
+ # - https://github.com/p1atdev/LECO/blob/main/train_lora.py
3
+ import time
4
+ from collections import OrderedDict
5
+ import os
6
+ from typing import Optional
7
+
8
+ from toolkit.config_modules import SliderConfig
9
+ import sys
10
+
11
+ from toolkit.stable_diffusion_model import PromptEmbeds
12
+
13
+ from toolkit.train_tools import get_torch_dtype, apply_noise_offset
14
+ import gc
15
+ from toolkit import train_tools
16
+
17
+ import torch
18
+ from .BaseSDTrainProcess import BaseSDTrainProcess, StableDiffusion
19
+
20
+
21
+ class ACTION_TYPES_SLIDER:
22
+ ERASE_NEGATIVE = 0
23
+ ENHANCE_NEGATIVE = 1
24
+
25
+
26
+ def flush():
27
+ torch.cuda.empty_cache()
28
+ gc.collect()
29
+
30
+
31
+ class EncodedPromptPair:
32
+ def __init__(
33
+ self,
34
+ target_class,
35
+ positive,
36
+ negative,
37
+ neutral,
38
+ width=512,
39
+ height=512,
40
+ action=ACTION_TYPES_SLIDER.ERASE_NEGATIVE,
41
+ multiplier=1.0,
42
+ weight=1.0
43
+ ):
44
+ self.target_class = target_class
45
+ self.positive = positive
46
+ self.negative = negative
47
+ self.neutral = neutral
48
+ self.width = width
49
+ self.height = height
50
+ self.action: int = action
51
+ self.multiplier = multiplier
52
+ self.weight = weight
53
+
54
+
55
+ class PromptEmbedsCache: # 使いまわしたいので
56
+ prompts: dict[str, PromptEmbeds] = {}
57
+
58
+ def __setitem__(self, __name: str, __value: PromptEmbeds) -> None:
59
+ self.prompts[__name] = __value
60
+
61
+ def __getitem__(self, __name: str) -> Optional[PromptEmbeds]:
62
+ if __name in self.prompts:
63
+ return self.prompts[__name]
64
+ else:
65
+ return None
66
+
67
+
68
+ class EncodedAnchor:
69
+ def __init__(
70
+ self,
71
+ prompt,
72
+ neg_prompt,
73
+ multiplier=1.0
74
+ ):
75
+ self.prompt = prompt
76
+ self.neg_prompt = neg_prompt
77
+ self.multiplier = multiplier
78
+
79
+
80
+ class TrainSliderProcessOld(BaseSDTrainProcess):
81
+ def __init__(self, process_id: int, job, config: OrderedDict):
82
+ super().__init__(process_id, job, config)
83
+ self.step_num = 0
84
+ self.start_step = 0
85
+ self.device = self.get_conf('device', self.job.device)
86
+ self.device_torch = torch.device(self.device)
87
+ self.slider_config = SliderConfig(**self.get_conf('slider', {}))
88
+ self.prompt_cache = PromptEmbedsCache()
89
+ self.prompt_pairs: list[EncodedPromptPair] = []
90
+ self.anchor_pairs: list[EncodedAnchor] = []
91
+
92
+ def before_model_load(self):
93
+ pass
94
+
95
+ def hook_before_train_loop(self):
96
+ cache = PromptEmbedsCache()
97
+ prompt_pairs: list[EncodedPromptPair] = []
98
+
99
+ # get encoded latents for our prompts
100
+ with torch.no_grad():
101
+ neutral = ""
102
+ for target in self.slider_config.targets:
103
+ # build the cache
104
+ for prompt in [
105
+ target.target_class,
106
+ target.positive,
107
+ target.negative,
108
+ neutral # empty neutral
109
+ ]:
110
+ if cache[prompt] is None:
111
+ cache[prompt] = self.sd.encode_prompt(prompt)
112
+ for resolution in self.slider_config.resolutions:
113
+ width, height = resolution
114
+ only_erase = len(target.positive.strip()) == 0
115
+ only_enhance = len(target.negative.strip()) == 0
116
+
117
+ both = not only_erase and not only_enhance
118
+
119
+ if only_erase and only_enhance:
120
+ raise ValueError("target must have at least one of positive or negative or both")
121
+ # for slider we need to have an enhancer, an eraser, and then
122
+ # an inverse with negative weights to balance the network
123
+ # if we don't do this, we will get different contrast and focus.
124
+ # we only perform actions of enhancing and erasing on the negative
125
+ # todo work on way to do all of this in one shot
126
+
127
+ if both or only_erase:
128
+ prompt_pairs += [
129
+ # erase standard
130
+ EncodedPromptPair(
131
+ target_class=cache[target.target_class],
132
+ positive=cache[target.positive],
133
+ negative=cache[target.negative],
134
+ neutral=cache[neutral],
135
+ width=width,
136
+ height=height,
137
+ action=ACTION_TYPES_SLIDER.ERASE_NEGATIVE,
138
+ multiplier=target.multiplier,
139
+ weight=target.weight
140
+ ),
141
+ ]
142
+ if both or only_enhance:
143
+ prompt_pairs += [
144
+ # enhance standard, swap pos neg
145
+ EncodedPromptPair(
146
+ target_class=cache[target.target_class],
147
+ positive=cache[target.negative],
148
+ negative=cache[target.positive],
149
+ neutral=cache[neutral],
150
+ width=width,
151
+ height=height,
152
+ action=ACTION_TYPES_SLIDER.ENHANCE_NEGATIVE,
153
+ multiplier=target.multiplier,
154
+ weight=target.weight
155
+ ),
156
+ ]
157
+ if both:
158
+ prompt_pairs += [
159
+ # erase inverted
160
+ EncodedPromptPair(
161
+ target_class=cache[target.target_class],
162
+ positive=cache[target.negative],
163
+ negative=cache[target.positive],
164
+ neutral=cache[neutral],
165
+ width=width,
166
+ height=height,
167
+ action=ACTION_TYPES_SLIDER.ERASE_NEGATIVE,
168
+ multiplier=target.multiplier * -1.0,
169
+ weight=target.weight
170
+ ),
171
+ ]
172
+ prompt_pairs += [
173
+ # enhance inverted
174
+ EncodedPromptPair(
175
+ target_class=cache[target.target_class],
176
+ positive=cache[target.positive],
177
+ negative=cache[target.negative],
178
+ neutral=cache[neutral],
179
+ width=width,
180
+ height=height,
181
+ action=ACTION_TYPES_SLIDER.ENHANCE_NEGATIVE,
182
+ multiplier=target.multiplier * -1.0,
183
+ weight=target.weight
184
+ ),
185
+ ]
186
+
187
+ # setup anchors
188
+ anchor_pairs = []
189
+ for anchor in self.slider_config.anchors:
190
+ # build the cache
191
+ for prompt in [
192
+ anchor.prompt,
193
+ anchor.neg_prompt # empty neutral
194
+ ]:
195
+ if cache[prompt] == None:
196
+ cache[prompt] = self.sd.encode_prompt(prompt)
197
+
198
+ anchor_pairs += [
199
+ EncodedAnchor(
200
+ prompt=cache[anchor.prompt],
201
+ neg_prompt=cache[anchor.neg_prompt],
202
+ multiplier=anchor.multiplier
203
+ )
204
+ ]
205
+
206
+ # move to cpu to save vram
207
+ # We don't need text encoder anymore, but keep it on cpu for sampling
208
+ # if text encoder is list
209
+ if isinstance(self.sd.text_encoder, list):
210
+ for encoder in self.sd.text_encoder:
211
+ encoder.to("cpu")
212
+ else:
213
+ self.sd.text_encoder.to("cpu")
214
+ self.prompt_cache = cache
215
+ self.prompt_pairs = prompt_pairs
216
+ self.anchor_pairs = anchor_pairs
217
+ flush()
218
+ # end hook_before_train_loop
219
+
220
+ def hook_train_loop(self, batch):
221
+ dtype = get_torch_dtype(self.train_config.dtype)
222
+
223
+ # get a random pair
224
+ prompt_pair: EncodedPromptPair = self.prompt_pairs[
225
+ torch.randint(0, len(self.prompt_pairs), (1,)).item()
226
+ ]
227
+
228
+ height = prompt_pair.height
229
+ width = prompt_pair.width
230
+ target_class = prompt_pair.target_class
231
+ neutral = prompt_pair.neutral
232
+ negative = prompt_pair.negative
233
+ positive = prompt_pair.positive
234
+ weight = prompt_pair.weight
235
+ multiplier = prompt_pair.multiplier
236
+
237
+ unet = self.sd.unet
238
+ noise_scheduler = self.sd.noise_scheduler
239
+ optimizer = self.optimizer
240
+ lr_scheduler = self.lr_scheduler
241
+ loss_function = torch.nn.MSELoss()
242
+
243
+ def get_noise_pred(p, n, gs, cts, dn):
244
+ return self.sd.predict_noise(
245
+ latents=dn,
246
+ text_embeddings=train_tools.concat_prompt_embeddings(
247
+ p, # unconditional
248
+ n, # positive
249
+ self.train_config.batch_size,
250
+ ),
251
+ timestep=cts,
252
+ guidance_scale=gs,
253
+ )
254
+
255
+ # set network multiplier
256
+ self.network.multiplier = multiplier
257
+
258
+ with torch.no_grad():
259
+ self.sd.noise_scheduler.set_timesteps(
260
+ self.train_config.max_denoising_steps, device=self.device_torch
261
+ )
262
+
263
+ self.optimizer.zero_grad()
264
+
265
+ # ger a random number of steps
266
+ timesteps_to = torch.randint(
267
+ 1, self.train_config.max_denoising_steps, (1,)
268
+ ).item()
269
+
270
+ # get noise
271
+ noise = self.sd.get_latent_noise(
272
+ pixel_height=height,
273
+ pixel_width=width,
274
+ batch_size=self.train_config.batch_size,
275
+ noise_offset=self.train_config.noise_offset,
276
+ ).to(self.device_torch, dtype=dtype)
277
+
278
+ # get latents
279
+ latents = noise * self.sd.noise_scheduler.init_noise_sigma
280
+ latents = latents.to(self.device_torch, dtype=dtype)
281
+
282
+ with self.network:
283
+ assert self.network.is_active
284
+ self.network.multiplier = multiplier
285
+ denoised_latents = self.sd.diffuse_some_steps(
286
+ latents, # pass simple noise latents
287
+ train_tools.concat_prompt_embeddings(
288
+ positive, # unconditional
289
+ target_class, # target
290
+ self.train_config.batch_size,
291
+ ),
292
+ start_timesteps=0,
293
+ total_timesteps=timesteps_to,
294
+ guidance_scale=3,
295
+ )
296
+
297
+ noise_scheduler.set_timesteps(1000)
298
+
299
+ current_timestep = noise_scheduler.timesteps[
300
+ int(timesteps_to * 1000 / self.train_config.max_denoising_steps)
301
+ ]
302
+
303
+ positive_latents = get_noise_pred(
304
+ positive, negative, 1, current_timestep, denoised_latents
305
+ ).to("cpu", dtype=torch.float32)
306
+
307
+ neutral_latents = get_noise_pred(
308
+ positive, neutral, 1, current_timestep, denoised_latents
309
+ ).to("cpu", dtype=torch.float32)
310
+
311
+ unconditional_latents = get_noise_pred(
312
+ positive, positive, 1, current_timestep, denoised_latents
313
+ ).to("cpu", dtype=torch.float32)
314
+
315
+ anchor_loss = None
316
+ if len(self.anchor_pairs) > 0:
317
+ # get a random anchor pair
318
+ anchor: EncodedAnchor = self.anchor_pairs[
319
+ torch.randint(0, len(self.anchor_pairs), (1,)).item()
320
+ ]
321
+ with torch.no_grad():
322
+ anchor_target_noise = get_noise_pred(
323
+ anchor.prompt, anchor.neg_prompt, 1, current_timestep, denoised_latents
324
+ ).to("cpu", dtype=torch.float32)
325
+ with self.network:
326
+ # anchor whatever weight prompt pair is using
327
+ pos_nem_mult = 1.0 if prompt_pair.multiplier > 0 else -1.0
328
+ self.network.multiplier = anchor.multiplier * pos_nem_mult
329
+
330
+ anchor_pred_noise = get_noise_pred(
331
+ anchor.prompt, anchor.neg_prompt, 1, current_timestep, denoised_latents
332
+ ).to("cpu", dtype=torch.float32)
333
+
334
+ self.network.multiplier = prompt_pair.multiplier
335
+
336
+ with self.network:
337
+ self.network.multiplier = prompt_pair.multiplier
338
+ target_latents = get_noise_pred(
339
+ positive, target_class, 1, current_timestep, denoised_latents
340
+ ).to("cpu", dtype=torch.float32)
341
+
342
+ # if self.logging_config.verbose:
343
+ # self.print("target_latents:", target_latents[0, 0, :5, :5])
344
+
345
+ positive_latents.requires_grad = False
346
+ neutral_latents.requires_grad = False
347
+ unconditional_latents.requires_grad = False
348
+ if len(self.anchor_pairs) > 0:
349
+ anchor_target_noise.requires_grad = False
350
+ anchor_loss = loss_function(
351
+ anchor_target_noise,
352
+ anchor_pred_noise,
353
+ )
354
+ erase = prompt_pair.action == ACTION_TYPES_SLIDER.ERASE_NEGATIVE
355
+ guidance_scale = 1.0
356
+
357
+ offset = guidance_scale * (positive_latents - unconditional_latents)
358
+
359
+ offset_neutral = neutral_latents
360
+ if erase:
361
+ offset_neutral -= offset
362
+ else:
363
+ # enhance
364
+ offset_neutral += offset
365
+
366
+ loss = loss_function(
367
+ target_latents,
368
+ offset_neutral,
369
+ ) * weight
370
+
371
+ loss_slide = loss.item()
372
+
373
+ if anchor_loss is not None:
374
+ loss += anchor_loss
375
+
376
+ loss_float = loss.item()
377
+
378
+ loss = loss.to(self.device_torch)
379
+
380
+ loss.backward()
381
+ optimizer.step()
382
+ lr_scheduler.step()
383
+
384
+ del (
385
+ positive_latents,
386
+ neutral_latents,
387
+ unconditional_latents,
388
+ target_latents,
389
+ latents,
390
+ )
391
+ flush()
392
+
393
+ # reset network
394
+ self.network.multiplier = 1.0
395
+
396
+ loss_dict = OrderedDict(
397
+ {'loss': loss_float},
398
+ )
399
+ if anchor_loss is not None:
400
+ loss_dict['sl_l'] = loss_slide
401
+ loss_dict['an_l'] = anchor_loss.item()
402
+
403
+ return loss_dict
404
+ # end hook_train_loop