comdoleger commited on
Commit
c851170
·
verified ·
1 Parent(s): aae3e0f

Upload extensions_built_in/concept_slider/ConceptSliderTrainer.py with huggingface_hub

Browse files
extensions_built_in/concept_slider/ConceptSliderTrainer.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import OrderedDict
2
+ from typing import Optional
3
+
4
+ import torch
5
+
6
+ from extensions_built_in.sd_trainer.DiffusionTrainer import DiffusionTrainer
7
+ from toolkit.data_transfer_object.data_loader import DataLoaderBatchDTO
8
+ from toolkit.prompt_utils import PromptEmbeds, concat_prompt_embeds
9
+ from toolkit.train_tools import get_torch_dtype
10
+
11
+
12
+ class ConceptSliderTrainerConfig:
13
+ def __init__(self, **kwargs):
14
+ self.guidance_strength: float = kwargs.get("guidance_strength", 3.0)
15
+ self.anchor_strength: float = kwargs.get("anchor_strength", 1.0)
16
+ self.positive_prompt: str = kwargs.get("positive_prompt", "")
17
+ self.negative_prompt: str = kwargs.get("negative_prompt", "")
18
+ self.target_class: str = kwargs.get("target_class", "")
19
+ self.anchor_class: Optional[str] = kwargs.get("anchor_class", None)
20
+
21
+
22
+ def norm_like_tensor(tensor: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
23
+ """Normalize the tensor to have the same mean and std as the target tensor."""
24
+ tensor_mean = tensor.mean()
25
+ tensor_std = tensor.std()
26
+ target_mean = target.mean()
27
+ target_std = target.std()
28
+ normalized_tensor = (tensor - tensor_mean) / (
29
+ tensor_std + 1e-8
30
+ ) * target_std + target_mean
31
+ return normalized_tensor
32
+
33
+
34
+ class ConceptSliderTrainer(DiffusionTrainer):
35
+ def __init__(self, process_id: int, job, config: OrderedDict, **kwargs):
36
+ super().__init__(process_id, job, config, **kwargs)
37
+ self.do_guided_loss = True
38
+
39
+ self.slider: ConceptSliderTrainerConfig = ConceptSliderTrainerConfig(
40
+ **self.config.get("slider", {})
41
+ )
42
+
43
+ self.positive_prompt = self.slider.positive_prompt
44
+ self.positive_prompt_embeds: Optional[PromptEmbeds] = None
45
+ self.negative_prompt = self.slider.negative_prompt
46
+ self.negative_prompt_embeds: Optional[PromptEmbeds] = None
47
+ self.target_class = self.slider.target_class
48
+ self.target_class_embeds: Optional[PromptEmbeds] = None
49
+ self.anchor_class = self.slider.anchor_class
50
+ self.anchor_class_embeds: Optional[PromptEmbeds] = None
51
+
52
+ def hook_before_train_loop(self):
53
+ # do this before calling parent as it unloads the text encoder if requested
54
+ if self.is_caching_text_embeddings:
55
+ # make sure model is on cpu for this part so we don't oom.
56
+ self.sd.unet.to("cpu")
57
+
58
+ # cache unconditional embeds (blank prompt)
59
+ with torch.no_grad():
60
+ self.positive_prompt_embeds = (
61
+ self.sd.encode_prompt(
62
+ [self.positive_prompt],
63
+ )
64
+ .to(self.device_torch, dtype=self.sd.torch_dtype)
65
+ .detach()
66
+ )
67
+
68
+ self.target_class_embeds = (
69
+ self.sd.encode_prompt(
70
+ [self.target_class],
71
+ )
72
+ .to(self.device_torch, dtype=self.sd.torch_dtype)
73
+ .detach()
74
+ )
75
+
76
+ self.negative_prompt_embeds = (
77
+ self.sd.encode_prompt(
78
+ [self.negative_prompt],
79
+ )
80
+ .to(self.device_torch, dtype=self.sd.torch_dtype)
81
+ .detach()
82
+ )
83
+
84
+ if self.anchor_class is not None:
85
+ self.anchor_class_embeds = (
86
+ self.sd.encode_prompt(
87
+ [self.anchor_class],
88
+ )
89
+ .to(self.device_torch, dtype=self.sd.torch_dtype)
90
+ .detach()
91
+ )
92
+
93
+ # call parent
94
+ super().hook_before_train_loop()
95
+
96
+ def get_guided_loss(
97
+ self,
98
+ noisy_latents: torch.Tensor,
99
+ conditional_embeds: PromptEmbeds,
100
+ match_adapter_assist: bool,
101
+ network_weight_list: list,
102
+ timesteps: torch.Tensor,
103
+ pred_kwargs: dict,
104
+ batch: "DataLoaderBatchDTO",
105
+ noise: torch.Tensor,
106
+ unconditional_embeds: Optional[PromptEmbeds] = None,
107
+ **kwargs,
108
+ ):
109
+ # todo for embeddings, we need to run without trigger words
110
+ was_unet_training = self.sd.unet.training
111
+ was_network_active = False
112
+ if self.network is not None:
113
+ was_network_active = self.network.is_active
114
+ self.network.is_active = False
115
+
116
+ # do out prior preds first
117
+ with torch.no_grad():
118
+ dtype = get_torch_dtype(self.train_config.dtype)
119
+ self.sd.unet.eval()
120
+ noisy_latents = noisy_latents.to(self.device_torch, dtype=dtype).detach()
121
+
122
+ batch_size = noisy_latents.shape[0]
123
+
124
+ positive_embeds = concat_prompt_embeds(
125
+ [self.positive_prompt_embeds] * batch_size
126
+ ).to(self.device_torch, dtype=dtype)
127
+ target_class_embeds = concat_prompt_embeds(
128
+ [self.target_class_embeds] * batch_size
129
+ ).to(self.device_torch, dtype=dtype)
130
+ negative_embeds = concat_prompt_embeds(
131
+ [self.negative_prompt_embeds] * batch_size
132
+ ).to(self.device_torch, dtype=dtype)
133
+
134
+ if self.anchor_class_embeds is not None:
135
+ anchor_embeds = concat_prompt_embeds(
136
+ [self.anchor_class_embeds] * batch_size
137
+ ).to(self.device_torch, dtype=dtype)
138
+
139
+ if self.anchor_class_embeds is not None:
140
+ # if we have an anchor, do it
141
+ combo_embeds = concat_prompt_embeds(
142
+ [
143
+ positive_embeds,
144
+ target_class_embeds,
145
+ negative_embeds,
146
+ anchor_embeds,
147
+ ]
148
+ )
149
+ num_embeds = 4
150
+ else:
151
+ combo_embeds = concat_prompt_embeds(
152
+ [positive_embeds, target_class_embeds, negative_embeds]
153
+ )
154
+ num_embeds = 3
155
+
156
+ # do them in one batch, VRAM should handle it since we are no grad
157
+ combo_pred = self.sd.predict_noise(
158
+ latents=torch.cat([noisy_latents] * num_embeds, dim=0),
159
+ conditional_embeddings=combo_embeds,
160
+ timestep=torch.cat([timesteps] * num_embeds, dim=0),
161
+ guidance_scale=1.0,
162
+ guidance_embedding_scale=1.0,
163
+ batch=batch,
164
+ )
165
+
166
+ if self.anchor_class_embeds is not None:
167
+ positive_pred, neutral_pred, negative_pred, anchor_target = (
168
+ combo_pred.chunk(4, dim=0)
169
+ )
170
+ else:
171
+ anchor_target = None
172
+ positive_pred, neutral_pred, negative_pred = combo_pred.chunk(3, dim=0)
173
+
174
+ # calculate the targets
175
+ guidance_scale = self.slider.guidance_strength
176
+
177
+ # enhance_positive_target = neutral_pred + guidance_scale * (
178
+ # positive_pred - negative_pred
179
+ # )
180
+ # enhance_negative_target = neutral_pred + guidance_scale * (
181
+ # negative_pred - positive_pred
182
+ # )
183
+ # erase_negative_target = neutral_pred - guidance_scale * (
184
+ # negative_pred - positive_pred
185
+ # )
186
+ # erase_positive_target = neutral_pred - guidance_scale * (
187
+ # positive_pred - negative_pred
188
+ # )
189
+
190
+ positive = (positive_pred - neutral_pred) - (negative_pred - neutral_pred)
191
+ negative = (negative_pred - neutral_pred) - (positive_pred - neutral_pred)
192
+
193
+ enhance_positive_target = neutral_pred + guidance_scale * positive
194
+ enhance_negative_target = neutral_pred + guidance_scale * negative
195
+ erase_negative_target = neutral_pred - guidance_scale * negative
196
+ erase_positive_target = neutral_pred - guidance_scale * positive
197
+
198
+ # normalize to neutral std/mean
199
+ enhance_positive_target = norm_like_tensor(
200
+ enhance_positive_target, neutral_pred
201
+ )
202
+ enhance_negative_target = norm_like_tensor(
203
+ enhance_negative_target, neutral_pred
204
+ )
205
+ erase_negative_target = norm_like_tensor(
206
+ erase_negative_target, neutral_pred
207
+ )
208
+ erase_positive_target = norm_like_tensor(
209
+ erase_positive_target, neutral_pred
210
+ )
211
+
212
+ if was_unet_training:
213
+ self.sd.unet.train()
214
+
215
+ # restore network
216
+ if self.network is not None:
217
+ self.network.is_active = was_network_active
218
+
219
+ if self.anchor_class_embeds is not None:
220
+ # do a grad inference with our target prompt
221
+ embeds = concat_prompt_embeds([target_class_embeds, anchor_embeds]).to(
222
+ self.device_torch, dtype=dtype
223
+ )
224
+
225
+ noisy_latents = torch.cat([noisy_latents, noisy_latents], dim=0).to(
226
+ self.device_torch, dtype=dtype
227
+ )
228
+ timesteps = torch.cat([timesteps, timesteps], dim=0)
229
+ else:
230
+ embeds = target_class_embeds.to(self.device_torch, dtype=dtype)
231
+
232
+ # do positive first
233
+ self.network.set_multiplier(1.0)
234
+ pred = self.sd.predict_noise(
235
+ latents=noisy_latents,
236
+ conditional_embeddings=embeds,
237
+ timestep=timesteps,
238
+ guidance_scale=1.0,
239
+ guidance_embedding_scale=1.0,
240
+ batch=batch,
241
+ )
242
+
243
+ if self.anchor_class_embeds is not None:
244
+ class_pred, anchor_pred = pred.chunk(2, dim=0)
245
+ else:
246
+ class_pred = pred
247
+ anchor_pred = None
248
+
249
+ # enhance positive loss
250
+ enhance_loss = torch.nn.functional.mse_loss(class_pred, enhance_positive_target)
251
+
252
+ erase_loss = torch.nn.functional.mse_loss(class_pred, erase_negative_target)
253
+
254
+ if anchor_target is None:
255
+ anchor_loss = torch.zeros_like(erase_loss)
256
+ else:
257
+ anchor_loss = torch.nn.functional.mse_loss(anchor_pred, anchor_target)
258
+
259
+ anchor_loss = anchor_loss * self.slider.anchor_strength
260
+
261
+ # send backward now because gradient checkpointing needs network polarity intact
262
+ total_pos_loss = (enhance_loss + erase_loss + anchor_loss) / 3.0
263
+ total_pos_loss.backward()
264
+ total_pos_loss = total_pos_loss.detach()
265
+
266
+ # now do negative
267
+ self.network.set_multiplier(-1.0)
268
+ pred = self.sd.predict_noise(
269
+ latents=noisy_latents,
270
+ conditional_embeddings=embeds,
271
+ timestep=timesteps,
272
+ guidance_scale=1.0,
273
+ guidance_embedding_scale=1.0,
274
+ batch=batch,
275
+ )
276
+
277
+ if self.anchor_class_embeds is not None:
278
+ class_pred, anchor_pred = pred.chunk(2, dim=0)
279
+ else:
280
+ class_pred = pred
281
+ anchor_pred = None
282
+
283
+ # enhance negative loss
284
+ enhance_loss = torch.nn.functional.mse_loss(class_pred, enhance_negative_target)
285
+ erase_loss = torch.nn.functional.mse_loss(class_pred, erase_positive_target)
286
+
287
+ if anchor_target is None:
288
+ anchor_loss = torch.zeros_like(erase_loss)
289
+ else:
290
+ anchor_loss = torch.nn.functional.mse_loss(anchor_pred, anchor_target)
291
+ anchor_loss = anchor_loss * self.slider.anchor_strength
292
+ total_neg_loss = (enhance_loss + erase_loss + anchor_loss) / 3.0
293
+ total_neg_loss.backward()
294
+ total_neg_loss = total_neg_loss.detach()
295
+
296
+ self.network.set_multiplier(1.0)
297
+
298
+ total_loss = (total_pos_loss + total_neg_loss) / 2.0
299
+
300
+ # add a grad so backward works right
301
+ total_loss.requires_grad_(True)
302
+ return total_loss